Skip to main content

aiopynautobot

CI PyPI Python versions License

Fully async Nautobot API client for Python, built on httpx2 (Pydantic's maintained continuation of httpx).

Inspired by pynautobot, redesigned for asyncio, and a sister project to aiopynetbox. This is not a port: pynautobot's core ergonomics (lazy attribute fetches, eagerly materialized result lists, sync pagination) depend on Python protocols that cannot be awaited, so the API surface here is deliberately different. All I/O is explicit and awaitable, and nothing does network I/O behind your back.

Requirements

  • Python 3.11+
  • Nautobot 2.4+ (including 3.x)

Installation

uv add aiopynautobot   # or: pip install aiopynautobot

Quick start

import asyncio
import aiopynautobot


async def main():
    async with aiopynautobot.api("https://nautobot.example.com", token="...") as nb:
        # single object
        device = await nb.dcim.devices.get(name="sw-1")
        print(device.name, device.status, device.location)

        # filtered query, pages are fetched concurrently
        async for iface in nb.dcim.interfaces.filter(device=device.name):
            print(iface.name)

        # diff-based save: only changed fields are PATCHed
        device.serial = "ABC123"
        await device.save()


asyncio.run(main())

Coming from pynautobot

The traversal (nb.dcim.devices), diff-based save(), and exception taxonomy all carry over. What changes is that implicit I/O becomes explicit:

pynautobot (sync) aiopynautobot (async)
nb.dcim.devices.get(name="x") await nb.dcim.devices.get(name="x")
for d in nb.dcim.devices.all() async for d in nb.dcim.devices.all()
len(nb.dcim.devices.all()) await nb.dcim.devices.count()
device.location.parent (lazy GET) await device.location.full_details() then .parent
nb.version (property does I/O) await nb.version()
threading=True built in: concurrent page fan-out, bounded by max_concurrency
filter("search-term") filter(q="search-term")

Nested records come back brief (as Nautobot sends them). Touching a field that isn't loaded raises AttributeError telling you to await record.full_details(). It never fires a hidden HTTP request.

Features

  • Explicit async everywhere: httpx2.AsyncClient under the hood, used as an async context manager so the connection pool closes deterministically.
  • Concurrent pagination: after the first page reveals the count, the remaining pages are fetched in parallel (bounded by max_concurrency, default 4) and yielded in order.
  • Diff-based writes: save() PATCHes only what you changed, with Nautobot's custom_fields merge semantics handled correctly.
  • GraphQL: await nb.graphql.query(...) plus saved queries via await nb.extras.graphql_queries.run(query_id).
  • Jobs: await nb.extras.jobs.run(job_id=...) and run_and_wait(), which polls with asyncio.sleep and raises JobTimeoutError rather than blocking a thread.
  • API versioning: api_version="2.4" pins Accept: application/json; version=2.4 for every request.
  • Default filters: exclude_m2m=True and include_default="config_context,computed_fields" are merged into every read, including count().
  • Bulk operations: await nb.dcim.devices.filter(status="offline").update(comments="audit"), await recordset.delete(), and list forms on the endpoint (endpoint.update([...]) / endpoint.delete([...])).
  • IPAM allocation: await prefix.available_ips.create() to allocate, async for ip in prefix.available_ips.list() to browse, plus available_prefixes. An exhausted pool raises AllocationError (Nautobot answers 204 No Content, not NetBox's 409).
  • Cable tracing: await interface.trace() returns [termination_a, cable, termination_b] hops, with None where a path is unterminated.
  • Notes: record.notes.list() / .create({"note": "..."}) on any object.
  • Apps: nb.plugins.<app>.<endpoint> and await nb.plugins.installed_plugins().
  • Choices: await nb.dcim.devices.choices() from OPTIONS metadata, handling both plain and list-typed choice fields.
  • Retries with backoff: 429 responses are retried automatically for any method (honoring Retry-After); transient 502/503/504 and connection failures are retried for GETs only, since an ambiguous write may already have been processed. Exponential backoff with jitter; tune with retries=, disable with retries=0.
  • Custom models: aiopynautobot.register_model("plugins/bgp", "sessions", BgpSession) maps app endpoints to your own Record subclasses; app.endpoint("literal_name") reaches endpoint slugs that contain real underscores.
  • Typed: full type hints and a py.typed marker, plus generated hints so IDEs autocomplete endpoint names (nb.dcim.devices) and per-endpoint kwargs (filter(name=...), create(device_type=...)). Hints never restrict anything at runtime: unknown endpoints, lookup expressions, and custom-field filters keep working.

API tour

async with aiopynautobot.api(url, token=token) as nb:
    # read (Nautobot primary keys are UUID strings)
    device = await nb.dcim.devices.get("0238a4e3-66f2-455a-831f-5f177215de0f")
    device = await nb.dcim.devices.get(name="sw-1")  # ValueError if >1
    total = await nb.dcim.devices.count(location="main")
    async for d in nb.dcim.devices.filter(status="active", tag=["prod", "core"]):
        ...
    async for d in nb.dcim.devices.all(limit=100, offset=200):  # single page
        ...

    # write
    new = await nb.dcim.devices.create(
        name="sw-9", device_type=dt_id, location=loc_id, role=role_id, status="Active"
    )
    device.serial = "XYZ"
    await device.save()  # PATCH {"serial": "XYZ"}
    await device.update({"serial": "XYZ", "comments": "..."})
    await device.delete()

    # bulk
    await nb.dcim.devices.filter(location="old").update(status="Decommissioning")
    await nb.dcim.devices.filter(status="Decommissioning").delete()

    # ipam allocation
    prefix = await nb.ipam.prefixes.get(prefix="10.0.0.0/24")
    ip = await prefix.available_ips.create({"status": "Active"})
    ips = await prefix.available_ips.create([{"status": "Active"}] * 2)

    # graphql
    result = await nb.graphql.query("query { devices { name } }")
    print(result.data["devices"])

    # jobs
    job = await nb.extras.jobs.run_and_wait(
        job_name="Verify Hostnames", data={"hostname_regex": ".*"}
    )
    print(job.job_result.status.value)

    # instance info
    print(await nb.version())  # "2.4"
    print(await nb.status())

Long-lived apps (FastAPI, services)

Create the client once and share it; the async context manager is one-shot, so enter it for the app's lifetime, not per request:

@asynccontextmanager
async def lifespan(app: FastAPI):
    async with aiopynautobot.api(url, token=token) as nb:
        app.state.nb = nb
        yield  # handlers use `await app.state.nb...`; pool closes on shutdown

One shared instance is safe under concurrent requests. See examples/fastapi_app.py for a runnable app.

Custom httpx2 client

Pass your own httpx2.AsyncClient for custom SSL, proxies, event hooks, or MockTransport in tests:

client = httpx2.AsyncClient(verify="/path/to/ca.pem", timeout=60)
async with aiopynautobot.api(url, token=token, client=client) as nb:
    ...

Per httpx2 convention, a client you pass in is yours to close: aclose() and the context manager only close clients the Api created itself, so one client can safely back several Api instances.

Response caching is deliberately not built in: Nautobot is a source of truth, and the library can't know your staleness tolerance. If you want HTTP caching, pass a client with a caching transport and set the policy yourself.

Development

Managed with uv:

uv sync              # install environment
uv run pytest        # tests (in-memory fake Nautobot, no network)
uv run ruff check    # lint
uv run ruff format   # format
uv run pyright       # type check

See CONTRIBUTING.md.

License

Apache 2.0, see LICENSE and NOTICE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aiopynautobot-0.2.0.tar.gz (63.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aiopynautobot-0.2.0-py3-none-any.whl (49.1 kB view details)

Uploaded Python 3

File details

Details for the file aiopynautobot-0.2.0.tar.gz.

File metadata

  • Download URL: aiopynautobot-0.2.0.tar.gz
  • Upload date:
  • Size: 63.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for aiopynautobot-0.2.0.tar.gz
Algorithm Hash digest
SHA256 0f36375192a6a4c60abb1bd242c4475b21c8fa1994fb139189fdcef3cf2196c9
MD5 4291d6bf1dd90e7508a189be138fa645
BLAKE2b-256 0c80c1fc10b335f6364a774e4ac9747c1ba09601551121aac54bcf7aac972437

See more details on using hashes here.

Provenance

The following attestation bundles were made for aiopynautobot-0.2.0.tar.gz:

Publisher: release.yml on challey74/aiopynautobot

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aiopynautobot-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: aiopynautobot-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 49.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for aiopynautobot-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 86132779d14a96c7ff4d4584731a5691e0e5fd02f73a0347f29a427964ef93c9
MD5 c5d4e4c1be7482e0bc3bb7e341c3fca8
BLAKE2b-256 e6f03b1f56227f7dd6dc297ab220d162b990ab9e5338f5756a9fc562d101e944

See more details on using hashes here.

Provenance

The following attestation bundles were made for aiopynautobot-0.2.0-py3-none-any.whl:

Publisher: release.yml on challey74/aiopynautobot

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page