Releases: k3l0-dev/niwaki
Release list
niwaki v1.3.1
Fixed
- Subscription push silently never delivered.
login()/_refresh_token()
(sync and async) explicitly set theAPIC-cookiecookie on the httpx
client, on top of the cookie httpx already stores automatically from the
APIC's ownSet-Cookieresponse header. The explicit call didn't pin a
domain, so it landed as a second, distinct jar entry (same name, empty
domain) rather than overwriting the first. The APIC accepts the resulting
malformed, duplicatedCookie: APIC-cookie=...; APIC-cookie=...header for
ordinary reads/writes and even for the subscribe GET itself (a valid
subscriptionIdstill comes back) — but it silently breaks the APIC's
internal link between that request and the caller's already-open
WebSocket, so no push ever arrives, and the subscribe response's initial
imdatacan come back empty even when matching objects exist. Fixed by
pinning the cookie to the session's own host on everylogin()/
_refresh_token()call, so it overwrites in place instead of duplicating.
Onlysubscribe()was affected — every other read/write path was already
correct, which is why this went unnoticed until a live subscription
investigation traced the exactCookieheader sent on the wire.
Re-validated live end to end on a freshly provisioned fabric: create,
modify, delete, filtered subscriptions, forced reconnect, and refresh
escalation all deliver correctly now.
niwaki v1.3.0
Native APIC object-subscription: a query becomes a live push stream instead
of a one-off read, over the same WebSocket mechanism the APIC GUI itself
uses. Purely additive — the configuration API and the query surface are
unchanged.
Added
-
Query.subscribe()/AsyncQuery.subscribe(). Any single-class query
can be subscribed instead of fetched:.initialgives the synchronous
snapshot, then the returnedSubscription/AsyncSubscriptioniterates
live push events for as long as it stays open. One shared WebSocket per
session multiplexes every subscription; refresh and reconnect run
automatically in the background — nothing here needs a caller-driven loop. -
Typed events. Each item is a
SubscriptionEvent:.kind
(EventKind.CREATED/MODIFIED/DELETED/GAP/REFRESH_FAILED),.mo
deserialised through the same readable field names a normal read uses,
with.mo.model_fields_setreporting exactly what that push carried (the
APIC sends sparse deltas onMODIFIED,dnonly onDELETED). -
Automatic recovery, never silent. The APIC has no replay mechanism at
all, so a reconnect resubscribes everything from scratch and delivers a
GAPevent rather than continuing as if nothing happened. Two consecutive
missed refreshes trigger the same kind of recovery for that one
subscription.SubscriptionLostError— with.reason— is raised only
once recovery itself has been tried and failed. -
Bulk and single-subscription tools.
aci.subscriptions.list()/
.refresh_all()/.close_all()manage every subscription open on a
session at once (close_all()stops them without tearing down the shared
socket);sub.info/sub.refresh_now()do the same for one subscription. -
Validated live against a real fabric: genuine create/modify/delete push
payloads, a realsubscriptionId, and thesubscriptionRefreshendpoint
accepting a real id.
niwaki v1.2.0
Discovery for the ~15,300 ACI classes the SDK does not generate a model for —
learned endpoints, stats, hardware, routing runtime. Purely additive: the
configuration API and the 1.1.0 query surface are unchanged.
Added
-
The read catalogue. A shipped, lazily-opened sqlite store (~31 MB) of
read metadata for every readable ACI class, not just the 2,239 with generated
models — so any class can be searched, described, and read with readable
field names.Readable names are recomputed with the code generator's own naming logic, so
a catalogue-served class reads with the same field names its model would
use — except for ~0.07 % of properties on a handful of generated classes
(11 properties across 7 of 2,211 on APIC 6.0(9c)), where the catalogue
resolves name collisions over a class's whole readable property set while
the model resolves over its configurable subset, so the two can pick
different names (e.g.l3extOut.enforceRtctrl→enforce_route_control
vsenforce_rtctrl). This never affects a result object — a generated
class is served by its typed model, never the catalogue — and is visible
only when introspecting those classes. -
niwaki.catalog— the public door to it:search(term),
describe(class_name),prop_meta(class_name, name),find_prop(term),
concrete_subclasses(class_name),class_meta(class_name), and
fault_name(code)(a fault code to its rule name, independent of which class
raised it). Entirely offline — no APIC connection needed. -
Readable field access on any result object. A
ManagedObjectbuilt
from a class with no generated model now exposes readable attribute names
(ep.infrastructure_ip, not justep["address"]) via the catalogue, with
the same per-property coercion (bool/int/float/flags/…) the wire
boundary uses for generated models — reading is uniform across all ~15,300
classes, generated or not. -
Validated live: real non-generated classes (
topSystem,fabricNode,
lldpAdjEp,eqptSensor,faultInst), the abstract-class query fan-out
(aci.query("fvEPg")resolving server-side to its concrete descendants), and
fault_nameagainst every fault code a live fabric actually raised.
niwaki v1.1.0
The observation / query surface — which 1.0.0 declared still-evolving — gets
its read foundation: the full APIC query grammar, expressed as a fluent, typed
API and validated against a live controller. The configuration API (design
DSL, push modes) is unchanged and remains stable; the breaking changes below are
confined to reading.
Added
- Filter operators.
anybit,allbit(emulated asand_(anybit, …)),
xorandrawjoineq/ne/lt/le/gt/ge/bw/wcardand
and_/or_/not_.anybitcloses the write-but-not-filter gap on bitmask
(Flags) fields — you can now filter "where this flag is set". - Smart values in
where(...). A list or tuple means membership, a*in a
string means wildcard, asetstays bitmask equality; explicit wrappers
any_of(...),like(pattern)andbetween(start, end)remove any ambiguity. - Response shaping — full GET grammar.
self_only(),also(...),
subtree_full(), andinclude_subtree(...)with theSubtreeIncludefacets
(faults, health, stats, audit/event/fault/health records, tasks, count, …);
multi-keyorder_by. - Executors and Python protocols. A query is a lazy iterable
(for mo in q,list(q));q[:n]sets a server-side limit;.one()and
.exists();execute_raw().count()andexists()honor the limit. New
typedNoResultError/MultipleResultsError. - Uniform result access on every object, generated model or operational
class alike:.dn,mo["wireName"], and.attrs(the full wire view).
Changed
with_faults()no longer filters. It embeds faults on each object without
restricting the result; chainonly_faulted()to return only faulted objects.
(Previouslywith_faults()implicitly restricted to faulted objects.)bool(query)/if query:now raisesTypeError. A query is lazy, so
truthiness would hide a network call — use.exists().subtree_where(prop=value)qualifies the property with the included subtree
class.include(fvSubnet).subtree_where(scope=...)filtersfvSubnet.scope,
not the queried class; when several classes are included, pass an explicitly
qualified expression (subtree_where(eq("fvSubnet.scope", …))).
Removed
contains()andisdigit()filter operators — the APIC has no such filter
types (verified on 6.0(9c)). Uselike("*x*")(subject to the property's
format) orraw(...).
niwaki v1.0.0
Niwaki leaves beta. The design-first configuration surface is proven against
a live Cisco APIC: the SDK expresses the ACI configuration model in depth, and a
real controller accepts what it produces.
Out of beta
- Stable configuration API. The design DSL (
design,tenant,infra,
fabric,controller,aaa, the makers,bind,bind_dn,ref, the verbs),
the push modes (strict,staged,plan) and the observation façade are now
stable; breaking changes will land in a new major version with a migration note. - Development-status classifier moved from 4 - Beta to 5 -
Production/Stable.
Changed
bind()no longer climbs to an ancestor. A relation attaches to the
object the cursor is on — declare each alias on the level that owns it
(.bd("web").bind(vrf="prod").subnet(...), not
.bd("web").subnet(...).bind(vrf="prod")). Binding an alias the current level
does not curate now fails loud — at the type level (the typed cursor no longer
exposes the alias) and at runtime (DesignError) — instead of silently placing
the relation on a parent object.- Typed cursor
bind()signatures expose only the aliases curated on that
level, never an ancestor's, so the editor and type-checker reject a bind the
controller would never accept. - Two references that resolve to the same relation now coexist instead of
raising:vrf.bind(l3out="x")andl3out("x").bind(vrf="v")build the same
l3extRsEctxand collapse to one; a same-relation collision with conflicting
attributes still raises.
Proven on a live fabric
An exhaustive integration suite drives the SDK against a live APIC simulator,
organised as eight domain walkthroughs an operator would recognise —
fabric/access, fabric, tenant, contracts, external connectivity (L3Out / L2Out),
service graphs, observability and management. Together they:
- push more than 10,000 configuration objects across 101 walkthrough files
and 225 test functions — every one accepted by the controller, with zero
rejections; - sweep each object's 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, VRFs and
bridge domains so both sides of each exclusion are covered; - encode the controller's real cross-field rules that the schema does not
express (one SPAN destination per session, NetFlow v9-only, OSPF/EIGRP mutual
exclusion on an L3Out, redistribution route-maps permit-only, a backup policy
serving a single redirect, …), so every pushed object is accepted in context,
not merely syntactically valid; - confirm that every declared object is present on the fabric with the
attributes and children its design declared, and that every relation reads
state=formed.
Residual faults a lab run may show are deployment-layer, not configuration
defects — a static path whose VLAN no domain binds yet, a routing peer with no
neighbour on the simulator, a VMM infra port-group without the fabric-wide
infra-VLAN scaffolding — and each is documented in its domain's walkthrough
README. The SDK expresses every knob; the controller accepts every object.
Scope
The walkthroughs run on a simulator, so they prove the configuration surface —
the SDK expresses the ACI model and a real controller accepts it — not hardware
or data-plane behaviour. The configuration side of the SDK is
production-ready; the read / query and observation surfaces continue to grow.
Verifying it yourself
Point the suite at your own lab and watch it configure a fabric end to end
(uv run pytest tests/integration/<phase> -m integration -s), then confront the
result through an independent read path — for example a read-only oracle over
the APIC such as aci-mcp — to confirm
each object landed. See tests/integration/README.md.
niwaki v0.14.16
Added
tests/integration/README.md— explains what the live walkthroughs are
for (evaluating the SDK against a lab APIC / simulator) and states plainly that
they are not production configuration and not best-practice snippets:
their values are illustrative and their job is to confirm the code runs against
a real controller.
Fixed
- The generated coverage-matrix intro no longer hardcodes the number of design
domains — it read "the four design domains" after further roots (aaa,
vmm_provider) were added.
niwaki v0.14.15
Added
- Fabric switch profiles, module cards and vPC protection. The fabric root
gainsleaf_path_selector,spine_path_selector,override_leaf_selectorand
override_spine_selectormakers; leaf and spine switch profiles bind their
module_profile(FEX card); the vPC explicit protection group binds its
vpc_policy; and switch event/fault/health retention policies are declarable. - Management and DHCP node-group associations. Management, DHCP and
deployment-zone pod groups bind their node groups (management_group,
node_group); DHCP node groups bind theirdhcp_relaypolicy; DNS server
groups bind theirepg; and bridge domains gain adhcp_relay_labelmaker. - Observability — monitoring sources, syslog destinations, SPAN and
retention. EPG monitoring policies gain the SNMP/syslog/callhome/TACACS
sources pluslifecycle_policyandstats_limit_pol(the latter two across
every monitoring policy); syslog groups gainconsole,fileand
protocol_profiledestinations; tenants declare VSPAN sessions and destination
groups, whose destinations bind an EPG, path, APIC node or virtual port.
Coverage
Declarable-surface coverage after these fixes — curated positions against the
remaining in-scope backlog, by design domain:
| Domain | Curated | In-scope gaps | Coverage |
|---|---|---|---|
| tenant | 553 | 141 | 80 % |
| access | 212 | 29 | 88 % |
| fabric | 206 | 36 | 85 % |
| controller | 12 | 1 | 92 % |
| aaa | 14 | 1 | 93 % |
| vmm | 33 | 1 | 97 % |
| Total | 1030 | 209 | 83 % |
The 322 detected gaps split into 209 in-scope, 26 deferred (VMM,
Intersight and on-switch third-party integrations — these need a live backend to
verify) and 87 out of scope (imperative actions, Cloud Network Controller,
Nexus Dashboard Orchestrator / multi-site, and SD-WAN). Anything outside the
curated vocabulary stays reachable through .mo(Class, ...) and
bind_dn(alias=dn).
niwaki v0.14.12
Fixed
- A staged push now isolates a failure to its own subtree. When one object
failed,push(mode="staged")marked every deeper object as "not attempted"
— keyed on DN depth alone — so an unrelated sibling branch whose parent had
succeeded was left half-built (e.g. a failure onBD-askippedBD-b's
subnet). The engine now skips only the descendants of a failed object;
independent branches run to completion, andStagedPushError.not_runlists
only objects whose ancestor genuinely failed.
niwaki v0.14.9
Fixed
- Writes are no longer retried after a timeout. A read/write timeout can
mean the APIC already accepted thePOST/DELETE, so retrying risked a
double-apply or a spuriousNotFoundErroron a delete that actually
succeeded. Writes now retry only on pre-send errors (connection/pool); reads
retry on any transport error as before.
niwaki v0.14.0
L4-L7 service graphs join the vocabulary — the last large domain of the ACI
configuration plane.
Added
- Service graph templates.
tenant(name).service_graph(...)with function
nodes (+ function connectors carrying config folders and parameters, copy
connectors), connections, and consumer/provider terminal nodes. - Logical devices.
logical_device(the L4-L7 cluster) with concrete
devices (+ their interfaces and parameters), logical interfaces, credentials
and management interface; the graph's function node binds a logical device. - Device context.
logical_device_context(keyed by contract/graph/node)
selecting a device and router configuration, with per-connector interface
contexts mapped to bridge domains and their virtual IPs. - Function profiles. Profile container → group → profile with function,
device and group shared configs, and the abstract folder/parameter model. - Device manager, chassis and instance config. Device manager and chassis
(with credentials), the deployed L4-L7 policy container with folder/parameter
instances, and normalized firewall parameters.
Service graphs define the ACI-side topology and configuration; rendering a graph
needs a real L4-L7 appliance — see the "Hardware-dependent integrations" note.
The device-package metamodel and normalized LB/NAT requests stay uncurated.
Coverage
The declarable config plane across the five domains (operational, diagnostic and
out-of-scope families — cloud, multi-site/NDO, device-package meta — excluded):
| Domain | Declarable | Curated | % |
|---|---|---|---|
| Tenant | 368 | 318 | 86 % |
Access (infra) |
164 | 141 | 85 % |
| Fabric | 179 | 145 | 81 % |
| Controller | 20 | 9 | 45 % |
| VMM | 22 | 15 | 68 % |
| Global (union) | 753 | 628 | 83 % |
790 curated positions across 652 distinct classes.