Skip to main content

Modern, typed, design-first Python SDK for Cisco ACI (APIC)

Project description

niwaki — a cloud-pruned garden tree

NIWAKI 庭木

Cisco ACI for humans — describe, push, observe.

The modern, typed, design-first Python SDK for Cisco ACI.

cisco aci network automation pydantic python license


Niwaki (庭木) is the Japanese art of sculpting full-size, living garden trees — exactly what this SDK does to the APIC Management Information Tree: not a miniature in a pot, a production tree, pruned with intent.

The promise: you should not have to memorise the APIC object model. Navigation, object names, and attributes use operator vocabulary with full IDE autocompletion; the SDK translates to ACI classes, tn*Name relation props, and wire attribute names for you.

Why another SDK for Cisco ACI?

Cisco ships an official Python SDK — cobra. It is authoritative and complete, but it was designed in the Python 2 era and it shows: you install it as two wheels downloaded from your own APIC, version-matched to the firmware, targeting "Python 2.7 or 3.6"; you write wire names and relation classes by hand (RsCtx(bd, tnFvCtxName='prod')); and every mistake is discovered by the APIC, after the POST.

cobra (official) niwaki
Distribution two .whl downloaded from a running APIC, firmware-matched one wheel, standard packaging, installable from an index
Python "2.7 or 3.6", untyped 3.12+, Typing :: Typed, full IDE autocompletion
Writing model imperative: build MOs, ConfigRequest, commit() design-first: describe → plan → push (atomic or staged waves)
Vocabulary ACI classes and wire names (fv.BD, arpFlood, tnFvCtxName) operator vocabulary (.bd("web").set(arp_flooding=True).bind(vrf="prod"))
References relation classes + target-name strings, unchecked bind() resolved closed-world at push time — a typo fails before any request, with a did-you-mean
Validation server-side, after the POST at the call site (Pydantic), plus a plan dry-run diff
Async / retry / pagination first-class async mirror, proactive token refresh, retries, transparent pagination

cobra remains the reference when you need guaranteed write parity with your exact firmware and Cisco support behind it. For everything else — reading fabrics, building and converging configuration as code — niwaki is built to be the SDK you want to write. The deep comparison lives in the documentation.

Installation

uv add niwaki          # or: pip install niwaki

Requires Python 3.12+. To work on the SDK itself, clone and uv sync --extra dev.

Restricted network? Every GitHub Release ships an offline wheelhouse (niwaki + all dependencies as wheels, with checksums and provenance attestations) for environments that cannot reach PyPI — the step-by-step is in the installation guide.

Full documentation — guides, cookbook, vocabulary book, API reference — lives at https://k3l0-dev.github.io/niwaki/.

Quickstart — declarative provisioning (design DSL)

One mental model: describe the desired configuration with the design DSL, apply it with push(), observe with the facade. The DSL covers the whole uni subtree — tenants, access policies (infra), fabric policies (fabric), controller policies — with the same vocabulary everywhere.

Build a detached design tree (no session, no I/O), then validate and push it in one call:

from niwaki import Niwaki
from niwaki.design import tenant

config = (
    tenant("prod")
    .app("shop")
        .epg("frontend").bind(bd="frontend").consume("fe-to-be")
        .epg("backend").bind(bd="backend").provide("fe-to-be")
    .bd("frontend")
        .set(unicast_routing=True)
        .subnet("10.0.1.1/24")
        .bind(vrf="prod")
    .bd("backend")
        .set(unicast_routing=True)
        .subnet("10.0.2.1/24")
        .bind(vrf="prod")
    .vrf("prod")
    .filter("api")
        .entry("rest", tcp=8080)
    .contract("fe-to-be")
        .set(scope="vrf")
        .subject("api").bind(filter="api")
)

with Niwaki("https://apic.example.com", "admin", "secret") as aci:
    config.push(aci, mode="strict")

Fabric and access policies use the same verbs — and multi-domain designs are one design() away:

from niwaki.design import design

config = design()
config.fabric().datetime_policy("prod-ntp").ntp_provider("10.0.0.1")
inf = config.infra()
inf.vlan_pool("prod", "static").range("vlan-100", "vlan-199")
config.phys_dom("prod-phys").bind(vlan_pool="prod")
inf.aaep("prod-aaep").bind(domain="prod-phys")
config.tenant("prod").app("shop").epg("web").bind_dn(domain="uni/phys-prod-phys")

config.push(aci)          # everything above in ONE atomic POST

Day-2 changes are just smaller designs — declare the field you want, the parent chain rides along as attribute-less upserts:

from niwaki.design import infra

flip = infra().cdp_policy("cdp-on", admin_state="disabled")
flip.push(aci, mode="plan")   # shows exactly one field change
flip.push(aci)

What the DSL gives you:

  • Structure is literal, vocabulary is translated — every maker maps 1:1 to a real APIC child class (.subject() is a vzSubj, .pim() is a pimCtxP), but names and parameters are the ones operators actually use (entry("rest", tcp=8080) compiles to etherT/prot/dFromPort/dToPort).
  • Lazy, closed-world referencesbind(), provide(), consume() resolve at push time; forward references are fine; a typo fails before any request, with a did-you-mean. Direction is handled for you: .vrf("prod").bind(l3out="ext") creates the l3extRsEctx on the L3Out side, where ACI expects it.
  • References that configure the relationship — some ACI relations carry fields of their own (the immediacy of a domain attachment, the directives of a filter under a subject — contract logging lives there). ref() sets them without leaving the closed world: subject.bind(filter=ref("web", directives="log")).
  • Typed cursors per position — makers, set() fields, and bind() aliases are generated with full signatures: autocompletion and mypy cover the entire curated vocabulary. .mo(AnyClass, ...) remains as the escape hatch, and bind_dn(alias=dn) references objects outside the design by raw DN.
  • Cisco's definitions in every hover — the APIC schema comments flow into field descriptions, enum values, and maker signatures: your IDE documents ACI while you type.
  • Eager validation — every name and attribute is checked by the Pydantic models at the call site, not on the wire.

Push modes

Mode Behaviour
strict (default) Closed-world validation, then one atomic POST of the whole design to /api/mo/uni.json — all or nothing.
staged One operation per object, executed in DN-depth waves (parents before children); atomic classes (vPC pairs) ship whole; a partial failure raises StagedPushError with plain DNs.
plan Dry run: reads only what the design declares and reports creates/updates — nothing is pushed.

config.to_payload() returns the exact strict-mode payload without executing anything (same philosophy as Query.build()).

Reading — typed queries

from niwaki import Niwaki
from niwaki.models.fv.fvBD import fvBD

with Niwaki("https://apic.example.com", "admin", "secret") as aci:
    # Vocabulary navigation, no class imports needed
    bd = aci.tenant("prod").bd("frontend").read()

    # Query builder: filters, scoping, enrichment, pagination
    # (filters address the APIC attribute names — the wire side)
    bds = aci.query(fvBD).where(arpFlood=True).under("uni/tn-prod").fetch()
    n = aci.tenant("prod").query(fvBD).count()

    # Any of the ~15k APIC classes by name (read-only/operational included)
    nodes = aci.query("topSystem").naming_only().fetch()

Async is a first-class mirror of the sync API:

from niwaki import AsyncNiwaki
from niwaki.models.fv.fvTenant import fvTenant

async with AsyncNiwaki("https://apic.example.com", "admin", "secret") as aci:
    tenants, bd = await aci.gather(
        aci.query(fvTenant).fetch(),
        aci.tenant("prod").bd("frontend").read(),
    )
    await config.push(aci, mode="strict")   # the design DSL is async-ready too

What's inside

  • Design DSL (niwaki.design): THE write path — see above. Curated vocabulary in domain/vocabulary.yaml, typed cursors generated per position, unified reference resolver (REFERENCE_MAP, name + DN flavors, abstract targets).
  • 2,222 generated Pydantic models (APIC v6.0 schemas) with human-readable field names, constraints, and 676 enums — models carry data and validation, never write logic.
  • Facade (observation): vocabulary navigation (aci.tenant("x").bd("y")), typed reads, queries, delete.
  • Sync + async transport: cookie/token auth, proactive refresh, retry with backoff, transparent pagination, typed exception hierarchy.
  • Imports stay lightweight — everything heavy (child maps, vocabulary tables) loads lazily on first use.

Development

uv sync --extra dev

Documentation (the hosted site is https://k3l0-dev.github.io/niwaki/; to build it locally — static HTML, no server needed):

uv sync --extra docs
bash scripts/docs.sh open     # build + open docs/_build/html/index.html

The full test suite ships with the repository — the unit suite plus the executable documentation (every python fence in docs/ runs as a test):

uv run pytest --ignore=tests/integration tests/ docs/

The integration suite (a live walkthrough against a lab APIC) skips itself when no fabric is reachable. Release engineering runs in the maintainers' private infrastructure; this repository is the public home of the SDK: source, tests, documentation, releases and issues.

Status

Actively developed — the changelog says what each release brings. Typed end to end (mypy strict), linted and formatted (ruff). The design DSL covers a curated vocabulary across tenant, access (infra), fabric, and controller policies — the generated coverage matrix lists every position; everything else stays reachable via .mo() and bind_dn(). Why this SDK exists: the comparison with cobra.

License

Apache License 2.0 — see LICENSE and NOTICE. Copyright 2026 Monark AIOPS SRL. Developed by Khalid El-Ouiali.

Cisco, Cisco ACI and APIC are trademarks of Cisco Systems, Inc. niwaki is an independent project, not affiliated with or endorsed by Cisco Systems, Inc.

Project details


Download files

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

Source Distribution

niwaki-0.13.0.tar.gz (4.9 MB view details)

Uploaded Source

Built Distribution

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

niwaki-0.13.0-py3-none-any.whl (7.2 MB view details)

Uploaded Python 3

File details

Details for the file niwaki-0.13.0.tar.gz.

File metadata

  • Download URL: niwaki-0.13.0.tar.gz
  • Upload date:
  • Size: 4.9 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for niwaki-0.13.0.tar.gz
Algorithm Hash digest
SHA256 86e72bbfbcc3e00486074937c42bded5a3b2cb5a13e37deb5409012f52d31222
MD5 9e7a6772636caf348f0441c76349217c
BLAKE2b-256 3912ffcdc9c8846c97570a23f778f8106af61116022211e64cce3b0000edb121

See more details on using hashes here.

Provenance

The following attestation bundles were made for niwaki-0.13.0.tar.gz:

Publisher: release.yml on k3l0-dev/niwaki

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

File details

Details for the file niwaki-0.13.0-py3-none-any.whl.

File metadata

  • Download URL: niwaki-0.13.0-py3-none-any.whl
  • Upload date:
  • Size: 7.2 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for niwaki-0.13.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6595f6d087a11af50bacb464701f5298fc879cdcaa6ca979bdcf92aa201d7de3
MD5 d6f9f9df79e16295fc471e72aa61bb78
BLAKE2b-256 57cc8a8a09ce9b68c29c24e422e9eaab66c5bb80f419fb99c68cc00b030eca8b

See more details on using hashes here.

Provenance

The following attestation bundles were made for niwaki-0.13.0-py3-none-any.whl:

Publisher: release.yml on k3l0-dev/niwaki

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

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page