Termination sub-attribute not found
I found a bug by trying to get the name of a device at the end of a cable. Here is the cable trace from the Nautobot demo.
I wanted to get the device name lhr01-edge-01 from the cable 73a65bf1-333e-4a09-9023-e77e05487151 using the termination_a attribute. Here is the code I wrote to achieve this.
import pynautobot
nautobot = pynautobot.api(
url="https://demo.nautobot.com",
token="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
)
cable = nautobot.dcim.cables.get("73a65bf1-333e-4a09-9023-e77e05487151")
# print(cable.termination_a)
# cable.termination_a.full_details()
print(cable.termination_a.device.name)
I expected to get lhr01-edge-01 as output. I instead got the following exception.
Traceback (most recent call last):
File "c:\Users\foo\Documents\misc\termination_bug.py", line 16, in <module>
print(cable.termination_a.device.name)
AttributeError: type object 'Devices' has no attribute 'name'
The most surprising thing is that if I print the termination first, it works. I guess there is a full_details call missing since calling this method seems to be a workaround ; but I don't think that behaviour is intended.
Nautobot version: 2.2.8 Pynautobot version: 2.2.0
This and #222 will work if I remove lines 221 & 222 from the below code. I just need to do some further testing on why it's not doing the full_details call otherwise.
https://github.com/nautobot/pynautobot/blob/7e3d55dd1486d8b82fee243a58952660fcf76254/pynautobot/models/dcim.py#L211-L223
Can you try the following to see if it solves your issue?
import pynautobot
nautobot = pynautobot.api(
url="https://demo.nautobot.com",
token="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
)
cable = nautobot.dcim.cables.get("73a65bf1-333e-4a09-9023-e77e05487151")
cable.termination_a #Incrementally accessing the endpoints (causes pynautobot to fetch the attributes)
print(cable.termination_a.device.name)
> 'lhr01-edge-01'
I have tested the 3 following options and None of them worked. It always raises the same error.
cable = nautobot.dcim.cables.get("73a65bf1-333e-4a09-9023-e77e05487151")
cable.termination_a
print(cable.termination_a.device.name)
cable = nautobot.dcim.cables.get("73a65bf1-333e-4a09-9023-e77e05487151")
termination_a = cable.termination_a
device = cable.termination_a.device
print(device.name)
cable = nautobot.dcim.cables.get("73a65bf1-333e-4a09-9023-e77e05487151")
termination_a = cable.termination_a
device = termination_a.device
print(device.name)
I am not able to recreate the same behavior on my side. Incrementally accessing the endpoint works on pynautobot==2.2.0 using the demo site. I only get the attribute error when attempting to access the full endpoint like shown in your original example.
I confirm it does not work on my side. I am not using the same Python version, mine is 3.10.11. Maybe that is the point.
Had a python 3.10.12 environment too, confirmed it worked there as well.
I have tried again to execute the following code and I found something interseting.
import pynautobot
nautobot = pynautobot.api(
url="https://demo.nautobot.com",
token="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
)
cable = nautobot.dcim.cables.get("73a65bf1-333e-4a09-9023-e77e05487151")
cable.termination_a
print(cable.termination_a.device.name)
The behaviour is not the same if I execute it as a script or if I use the interactive shell. Using a script the workaround does not work while it does in interactive mode. In the latter case, the termination is printed so I think the line cable.termination_a does the same as print(cable.termination_a), which works.
If I understand correctly, I think I was able to reproduce what you are seeing. It appears something needs to invoke the __str__ method of the cable_termination before pynautobot will fetch it's related objects.
The behavior I observed while running python in script mode instead of interactive:
# Behavior in script mode #
# Example1
print(cable.termination_a)
print(cable.termination_a.device.name) # This works because I printed cable.termination_a first
# Example2
cable.termination_a
print(cable.termination_a.device.name) # This does not not work
Thoughts here @tsm1th?
I never discovered the root cause of this. I came up with several workarounds to the immediate problem, but no permanent fix for the deeper issue. That last comment was about the last time I looked at it.