Skip to main content

aiopynetbox

CI PyPI Python versions License

Fully async NetBox API client for Python, built on httpx2.

Inspired by pynetbox, redesigned for asyncio. This is not a port: pynetbox's core ergonomics (lazy attribute fetches, len() on result sets, sync generators) 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+
  • NetBox 3.x / 4.x (v1 and v2 nbt_ API tokens both supported)

Installation

uv add aiopynetbox   # or: pip install aiopynetbox

Quick start

import asyncio
import aiopynetbox


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

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

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


asyncio.run(main())

Coming from pynetbox

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

pynetbox (sync) aiopynetbox (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.site.region (lazy fetch) await device.site.full_details() then device.site.region
nb.version (property does I/O) await nb.version()
threading=True built in: concurrent page fan-out, bounded by max_concurrency

Nested records come back brief (as NetBox 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.
  • Cursor pagination (NetBox 4.6+): pass pagination="cursor" to the client to page with the start cursor instead, constant-time per page on very large tables (pages are sequential in this mode, since each cursor comes from the previous response).
  • Diff-based writes: save() PATCHes only what you changed, with NetBox's custom_fields merge semantics handled correctly.
  • 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() / .list(), plus available_prefixes and available_vlans.
  • Plugins: nb.plugins.<plugin>.<endpoint> and await nb.plugins.installed_plugins().
  • Choices: await nb.dcim.devices.choices() from OPTIONS metadata.
  • 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.
  • Optimistic locking (NetBox 4.6+): records fetched from a detail endpoint remember their ETag; save() sends If-Match, so a concurrent modification fails with a 412 error instead of being silently overwritten. Repeat full_details() calls revalidate with If-None-Match, so unchanged objects aren't re-downloaded or re-parsed.
  • Data source sync: await data_source.sync.create().
  • Custom models: aiopynetbox.register_model("plugins/bgp", "sessions", BgpSession) maps plugin 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 aiopynetbox.api(url, token=token) as nb:
    # read
    device = await nb.dcim.devices.get(123)  # by id (None if missing)
    device = await nb.dcim.devices.get(name="sw-1")  # by filter (ValueError if >1)
    total = await nb.dcim.devices.count(site="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=12, site=1, role=3)
    device.serial = "XYZ"
    await device.save()  # PATCH {"serial": "XYZ"}
    await device.update({"serial": "XYZ", "comments": "..."})
    await device.delete()

    # bulk
    await nb.dcim.devices.filter(site="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()  # next free IP
    ips = await prefix.available_ips.create([{}, {}])  # next two

    # instance info
    print(await nb.version())  # "4.5"
    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 aiopynetbox.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 aiopynetbox.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: NetBox is a source of truth, and the library can't know your staleness tolerance. If you want HTTP caching, pass a client using hishel's AsyncCacheTransport and set the policy yourself.

Development

Managed with uv:

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

License

Apache 2.0, see LICENSE.

Download files

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

Source Distribution

aiopynetbox-0.2.0.tar.gz (30.3 kB view details)

Uploaded Source

Built Distribution

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

aiopynetbox-0.2.0-py3-none-any.whl (33.5 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for aiopynetbox-0.2.0.tar.gz
Algorithm Hash digest
SHA256 82f3e5f5505d8334c8a542aa64cf8f3fc9c79c82ddfc5d81ec96451d4d6b4a09
MD5 3cecf788031ccbbcc89ab65d59b4399b
BLAKE2b-256 50e17c99d29cf23d98dc0c892ef26e970a71d24cb883fcc376f5e4768ae2ad21

See more details on using hashes here.

Provenance

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

Publisher: release.yml on challey74/aiopynetbox

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

File details

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

File metadata

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

File hashes

Hashes for aiopynetbox-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ee497bea67026eedd37995b435d9a5635f598d7547e40acfa07e6405eb66b7e1
MD5 20ecd941dd875fb71399202a3429a6fe
BLAKE2b-256 c7e296b8fc90c1ea299f2b82417359d38e2f8a7e8e1169f468ab14c77f641273

See more details on using hashes here.

Provenance

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

Publisher: release.yml on challey74/aiopynetbox

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