Metadata-Version: 2.4
Name: niwaki
Version: 0.12.0
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 :: 4 - Beta
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
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)

</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)
        .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:

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

## 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

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