NetBox / Source of Truth
pynetbox: Querying NetBox as a Real REST API Client
What you'll learn
- pynetbox's real client object and endpoint-query methods
- How pynetbox differs from the raw requests calls Phase 3.1 already taught
- A real, verified, non-obvious behavior of .get() with ambiguous results
- That verification here runs against a local mock, never a real or hosted NetBox instance
Listen to a guided explanation of this lesson.
Spoken narration isn't supported in this browser.
A real client, not raw requests calls
Built on Phase 3.1, not a repeat of it
APIs and REST for Network Engineers (Phase 3.1) taught raw requests.get() calls, status codes, and JSON parsing by hand. pynetbox is a real client library built on top of exactly that -- it does the HTTP and JSON work for you, returning typed record objects instead of a raw dict. Phase 3.1's mechanics aren't repeated here.
def __init__(self, url, token=None, threading=False, strict_filters=False,
extensions=None, pagination='offset', thread_pool_executor=None,
max_workers=4): ...Verified directly via inspect.signature. url and token are the only two arguments most examples need.
import os
import pynetbox
nb = pynetbox.api(
"http://127.0.0.1:8000",
token=os.environ.get("NETBOX_TOKEN", "test-token"),
)
devices = list(nb.dcim.devices.all())
for d in devices:
print(d.id, d.name, d.site.name, d.platform.name if d.platform else None)Never a real or hosted NetBox instance -- 127.0.0.1:8000 is this module's own local, stdlib-only mock backend (built the same way Phase 3.1/3.2's mock backends were).
1 SW1 HQ Cisco IOS
2 SW2 HQ Arista EOS
3 SW3 Branch1 Cisco IOSReal, verified output. d.site and d.platform are themselves typed record objects -- d.site.name works directly, no manual dict-key lookup needed.
Endpoint methods: real, verified signatures
Endpoint.all(self, limit=0, offset=None)
Endpoint.get(self, *args, **kwargs)
Endpoint.filter(self, *args, **kwargs)Verified via inspect.signature against pynetbox.core.endpoint.Endpoint -- nb.dcim.devices, nb.dcim.sites, etc. are all Endpoint instances sharing this same interface.
hq_devices = list(nb.dcim.devices.filter(site="hq"))
print([d.name for d in hq_devices])
sw1 = nb.dcim.devices.get(name="SW1")
print(sw1.id, sw1.name, sw1.serial)filter() narrows by field; get() expects exactly one match.
['SW1', 'SW2']
1 SW1 SW1-SN-001Real, verified output against the local mock's 3-device fleet (2 at site 'hq', 1 at 'branch1').
A real, non-obvious discovery: .get() on an ambiguous query
Found by actually running it, not assumed
The first attempt at this lesson called nb.dcim.devices.get(name="SW1") without first narrowing the mock backend's own query-matching logic -- and got a real ValueError, not the single record expected.
ValueError: get() returned more than one result. Check that the kwarg(s)
passed are valid for this endpoint or use filter() or all() instead.Real, verified exception. .get() is documented and designed to require exactly one match -- ambiguous queries are a real, deliberate error condition, not a bug.
Key takeaways
- pynetbox.api(url, token) is the real, verified client entry point -- built on requests, not a replacement for Phase 3.1's HTTP fundamentals.
- nb.dcim.devices.all()/.filter()/.get() are real, verified Endpoint methods -- record objects support attribute access (d.site.name), not just dict lookups.
- A real, verified discovery: .get() raises ValueError on an ambiguous (multi-match) query -- a deliberate design choice, not a bug, confirmed by triggering it directly.
- Every example in this lesson ran against a local, stdlib-only mock backend on 127.0.0.1 -- never a real or hosted NetBox instance.