nautobot / pynautobot

Nautobot Python SDK
https://pynautobot.readthedocs.io/en/latest/index.html
Apache License 2.0
36 stars 32 forks source link

Termination sub-attribute not found #223

Open TitouanS31 opened 1 month ago

TitouanS31 commented 1 month ago

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.

image

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

tsm1th commented 1 month ago

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

tsm1th commented 1 month ago

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'
TitouanS31 commented 1 month ago

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)
tsm1th commented 1 month ago

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.

image

TitouanS31 commented 1 month ago

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.

tsm1th commented 1 month ago

Had a python 3.10.12 environment too, confirmed it worked there as well.

image

TitouanS31 commented 1 month ago

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.

tsm1th commented 1 month ago

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