Metadata-Version: 2.4
Name: niwaki
Version: 1.3.1
Summary: Modern, typed, design-first Python SDK for Cisco ACI (APIC)
Project-URL: Homepage, https://github.com/k3l0-dev/niwaki
Project-URL: Repository, https://github.com/k3l0-dev/niwaki
Project-URL: Documentation, https://k3l0-dev.github.io/niwaki/
Project-URL: Changelog, https://github.com/k3l0-dev/niwaki/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/k3l0-dev/niwaki/issues
Author-email: Khalid El-Ouiali <monark.aiops@pm.me>
Maintainer-email: Khalid El-Ouiali <monark.aiops@pm.me>
License-Expression: Apache-2.0
License-File: LICENSE
License-File: NOTICE
Keywords: aci,apic,cisco,datacenter,declarative,infrastructure-as-code,network-automation,pydantic,sdk
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Intended Audience :: Telecommunications Industry
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Networking
Classifier: Topic :: System :: Systems Administration
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.7
Requires-Dist: pyyaml>=6.0.3
Requires-Dist: stamina>=24.2
Requires-Dist: websockets>=13.0
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pyright>=1.1.390; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
Requires-Dist: pytest>=8.2; extra == 'dev'
Requires-Dist: python-dotenv>=1.0; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Requires-Dist: sybil>=9; extra == 'dev'
Provides-Extra: docs
Requires-Dist: furo>=2024.8; extra == 'docs'
Requires-Dist: myst-parser>=4.0; extra == 'docs'
Requires-Dist: sphinx-copybutton>=0.5; extra == 'docs'
Requires-Dist: sphinx>=8.2; extra == 'docs'
Description-Content-Type: text/markdown

<div align="center">

<img src="https://raw.githubusercontent.com/k3l0-dev/niwaki/main/assets/niwaki-banner.svg" width="360" alt="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](https://img.shields.io/badge/Cisco%20ACI-049fd9)](https://k3l0-dev.github.io/niwaki/)
[![network automation](https://img.shields.io/badge/Network%20Automation-2e6f45)](https://k3l0-dev.github.io/niwaki/cookbook/index.html)
[![pydantic](https://img.shields.io/badge/Pydantic-e92063?logo=pydantic&logoColor=white)](https://docs.pydantic.dev)
[![python](https://img.shields.io/badge/Python-3.12%2B-3776AB?logo=python&logoColor=white)](https://pypi.org/project/niwaki/)
[![license](https://img.shields.io/badge/License-Apache--2.0-blue)](https://github.com/k3l0-dev/niwaki/blob/main/LICENSE)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/k3l0-dev/niwaki)
</div>

---

*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](https://cobra.readthedocs.io/).
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](https://k3l0-dev.github.io/niwaki/why.html).

## Installation

```bash
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](https://github.com/k3l0-dev/niwaki/releases)
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](https://k3l0-dev.github.io/niwaki/guide/installation.html).

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:

```python
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)
        .bind(vrf="prod")
        .subnet("10.0.1.1/24")
    .bd("backend")
        .set(unicast_routing=True)
        .bind(vrf="prod")
        .subnet("10.0.2.1/24")
    .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:

```python
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:

```python
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 references** — `bind()`, `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

```python
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:

```python
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
```

## Features

**Design-first configuration — one way to write, three ways to apply.**

- Describe the **whole `uni` subtree** — tenants, access policies (`infra`),
  fabric policies (`fabric`), controller, AAA — with one operator vocabulary and
  full IDE autocompletion. Structure is literal (every maker maps 1:1 to a real
  APIC child class), vocabulary is translated (`entry("rest", tcp=8080)` compiles
  to `etherT` / `prot` / `dFromPort` / `dToPort`).
- **Three push modes**: `strict` (one atomic POST of the whole design), `staged`
  (dependency-ordered waves, a partial failure raises `StagedPushError` with
  plain DNs), `plan` (dry-run diff — nothing is pushed). `to_payload()` inspects
  the exact payload without executing.
- **Closed-world references** — `bind()`, `provide()`, `consume()` resolve at
  push time; forward references are fine; a typo fails **before any request**,
  with a did-you-mean. Relation direction is handled for you.
- **References that configure the relationship** — `ref()` sets the fields some
  ACI relations carry (domain-attachment immediacy, the `directives` of a filter
  under a subject = contract logging). `bind_dn()` references objects outside the
  design by raw DN; `.mo(AnyClass, ...)` is the escape hatch to any class.
- **Day-2 changes are just smaller designs** — declare the field you want and the
  parent chain rides along as attribute-less upserts.

**Typed models and vocabulary.**

- **2,222 Pydantic models** generated from APIC v6.0 schemas, with human-readable
  field names (`arp_flooding` ↔ wire `arpFlood`), schema constraints and **676
  enums**. Models carry data + validation only, never write logic.
- A **complete type system**: named numbers (port `80` ↔ `"http"`), bitmask
  flags, validated IP/MAC addresses, and wire-name aliases that stay visible to
  the type-checker (PEP 681). Validation is **eager, at the call site** — never
  "discover it on the wire".
- **Typed cursors per position** — makers, `set()` fields and `bind()` aliases are
  generated with full signatures, so autocompletion and mypy cover the entire
  curated vocabulary. Cisco's own schema comments flow into every hover.

**Reading and observing.**

- **Query builder**: filters, scoping (`under`, `children`), enrichment
  (`with_faults` / `health` / `stats`), `naming_only` / `config_only`, sorting,
  pagination; `build()` to inspect. Any of ~15k APIC classes by name.
- **Observation facade**: vocabulary navigation (`aci.tenant("x").bd("y").read()`),
  typed reads, queries, delete.

**Transport and packaging.**

- **Sync + async mirrors**: cookie/token auth with proactive refresh, retries with
  backoff, transparent pagination, a typed exception hierarchy, and `gather()` for
  concurrent reads.
- **One wheel**, standard packaging, installable from any index; `Typing ::
  Typed`. Every [GitHub Release](https://github.com/k3l0-dev/niwaki/releases)
  ships an offline wheelhouse (all dependencies, checksums, provenance
  attestations) for air-gapped networks. Imports stay lightweight — heavy tables
  load lazily on first use.

## Validated on a live fabric

The design-first **configuration surface is proven against a live Cisco APIC**.
An exhaustive integration suite provisions a real controller (or the APIC
simulator) through the SDK — eight domain walkthroughs (fabric/access, fabric,
tenant, contracts, L3Out/L2Out, service graphs, observability, management) that
sweep the configuration surface in depth: every enum value, the combinations of
interacting fields, and **every curated child of every parent**, with
mutually-exclusive settings factored across separate tenants and VRFs so both
sides of each exclusion are covered. The controller **accepts what the SDK
produces**, and each declared object is confirmed live with the attributes and
children its design declared.

**You can reproduce every one of these on your own lab.** Point the suite at your
fabric and run it in a single pass:

```bash
uv run pytest tests/integration -m integration -s
```

The suite reads your APIC from a `.env` file and skips when no fabric is
reachable, so it is always safe to run. The step-by-step method — install, point
at your fabric, run the whole suite or one phase, verify what landed, and clean
up — is in
**[`tests/integration/README.md`](https://github.com/k3l0-dev/niwaki/blob/main/tests/integration/README.md)**.

## Development

```bash
uv sync --extra dev
```

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

```bash
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):

```bash
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](https://k3l0-dev.github.io/niwaki/project/CHANGELOG.html)
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](https://k3l0-dev.github.io/niwaki/reference/vocabulary/coverage.html)
lists every position; everything else stays reachable via `.mo()` and
`bind_dn()`.  Why this SDK exists:
[the comparison with cobra](https://k3l0-dev.github.io/niwaki/why.html).

## License

Apache License 2.0 — see [LICENSE](https://github.com/k3l0-dev/niwaki/blob/main/LICENSE) and [NOTICE](https://github.com/k3l0-dev/niwaki/blob/main/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.
