Changelog¶
All notable changes to this project are documented here. The format follows Keep a Changelog; versions follow semver. From 1.0.0 the configuration API is stable: breaking changes ship in a new major version with a migration note.
[1.3.0] — 2026-07-20¶
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),.modeserialised 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 real
subscriptionId, and thesubscriptionRefreshendpoint accepting a real id.
[1.2.0] — 2026-07-20¶
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_controlvsenforce_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), andfault_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), andfault_nameagainst every fault code a live fabric actually raised.
[1.1.0] — 2026-07-19¶
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/wcardandand_/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 wrappersany_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(...).
[1.0.0] — 2026-07-17¶
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 samel3extRsEctxand 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.
[0.14.16] — 2026-07-17¶
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.
[0.14.15] — 2026-07-17¶
Added¶
Fabric switch profiles, module cards and vPC protection. The fabric root gains
leaf_path_selector,spine_path_selector,override_leaf_selectorandoverride_spine_selectormakers; leaf and spine switch profiles bind theirmodule_profile(FEX card); the vPC explicit protection group binds itsvpc_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 plus
lifecycle_policyandstats_limit_pol(the latter two across every monitoring policy); syslog groups gainconsole,fileandprotocol_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).
[0.14.14] — 2026-07-17¶
Added¶
VRF, bridge-domain and external-EPG protocol relations are now declarable (tenant). Bridge domains bind
fhs,nd_policy,igmp_snoop,mld_snoop,dhcp_relay,endpoint_retention,netflow_monitor,monitoring_policyandflood_filter; VRFs bindbgp_timers,bgp_address_family,eigrp_address_family,endpoint_retention,route_tag,route_control_profile,vrf_validation,monitoring_policyand theospf_timersverb (per-address-family). L3Out external EPGs gainimported_contractand theintra_epgverb; in-band management EPGs gaintaboo_contractandintra_epg.L3Out route-control, QoS and SR-MPLS relations (tenant). The L3Out gains the
dampening,interleakandredistributeverbs (three route-control profiles that automatic resolution cannot tell apart); logical interface profiles gainingress_dpp/egress_dpp(data-plane policing); node attachments gain the SR-MPLSnode_sid_profilemaker; path attachments gainsecondary_ip_addressandmember_node_configurationmakers.Contract service-graph attachment and in-band management contract labels. Contracts and subjects bind
service_graph(vzRsGraphAtt/vzRsSubjGraphAtt); in-band management EPGs gain the six contract and subject labels (consumer_contract_label,provider_label, …).
[0.14.13] — 2026-07-17¶
Changed¶
SNMP trap-forward-server maker renamed to
trap_forward_server. Underfabric().snmp_policy(...), the maker that declares ansnmpTrapFwdServerPwas calledclient_entry— the same word as the SNMP client-group’s client entry, which was misleading. Use.trap_forward_server(<address>).
Added¶
Local AAA & security objects are now declarable under
aaa(). Alongsideradius(), theuni/userextroot makes local users (local_user), remote users (remote_user), roles (aaa_role), security domains (security_domain) and login domains (login_domain), plus the fabric-security singletonspassword_strength_policy,block_user_logins_policy,pre_login_banner,fabric_secandservice_node_cluster_settings.Interface policies attach to PC/vPC policy groups, and switch selectors to their policy groups (access-binds).
infra().func_profile().port_channel(...)now bindsfc_interface,l2_mtu,port_authentication,port_security,link_flap,monitoring,netflow_monitor,optics,slow_drain,synce,span_destination_groupandspan_source_group, plus theingress_dpp/egress_dppverbs for data-plane policing (two relations to oneqosDppPol). Leaf and spine switch selectors bindpolicy_group(infraRsAccNodePGrp/infraRsSpineAccNodePGrp) — the link that attaches a switch profile to its switch policy group.Associated management EPG is now declarable on SNMP client groups, SNMP trap destinations, syslog remote destinations, NTP providers, and DNS profiles.
snmp_client_group_profile(...),snmp_monitoring_destination_group(...).snmp_trap_destination(...),syslog_group(...).remote_destination(...),datetime_policy(...).ntp_provider(...), andfabric().dns_profile(...)acceptbind_dn(management_epg="uni/tn-mgmt/mgmtp-default/oob-default")— thesnmpRsEpg/fileRsARemoteHostToEpg/datetimeRsNtpProvToEpg/dnsRsProfileToEpgrelation to the associated (OOB or in-band) management EPG.AAA / RADIUS is now declarable. A new top-level
aaa()root — a sibling ofcontroller(), mirroring the MIT’suni/userextbranch — withradius(),radius_provider(...),radius_provider_group(...)and its provider members. The 802.1x node-auth policy can point at a group withdot1x_node_authentication(...).bind_dn(radius_provider_group=<dn>).Maintenance groups can reference their maintenance policy —
fabric().maintenance_group(...).bind(maintenance_policy=<name>)creates themaintRsMgrpprelation to amaintMaintP.NTP authentication keys are now declarable —
datetime_policy(...).ntp_auth_key(<id>, key=..., type_of_authentication_key=...)creates adatetimeNtpAuthKey, anddatetime_policy(...).ntp_provider(...).authentication_key(<id>)trusts one on a provider (thedatetimeRsNtpProvToNtpAuthKeyrelation, keyed by the key id).
[0.14.12] — 2026-07-16¶
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.
[0.14.11] — 2026-07-16¶
Fixed¶
Concurrent mid-session 401s no longer stampede re-logins (async). When many coroutines shared an
AsyncApicSessionwhose token was revoked, each received a 401 and calledlogin()directly — outside the token lock and semaphore — so N coroutines raced N concurrent re-logins on the shared token state and cookie jar. Reactive re-login now goes through a lock-guarded path that re-authenticates only once (the first coroutine in; the rest see the fresh token and replay), and the replay runs under the concurrency semaphore.
[0.14.10] — 2026-07-16¶
Fixed¶
References resolve to the nearest scope, not globally. A name reused across tenants (a
bd("web")orvrf("prod")in two tenants) previously made that name unbindable — everybind()targeting it raisedAmbiguousBindError, even though ACI namespaces object names per parent. A reference now resolves to the same-named target sharing the deepest enclosing scope with its owner (a BD in tenant a binds tenant a’s VRF); only two candidates at the same scope remain a genuine ambiguity.
[0.14.9] — 2026-07-16¶
Fixed¶
Writes are no longer retried after a timeout. A read/write timeout can mean the APIC already accepted the
POST/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.
[0.14.8] — 2026-07-16¶
Fixed¶
Query filter values are escaped. A
"in awhere(prop=value)/wcardvalue is now escaped instead of breaking theeq(prop,"...")filter grammar (which could 400 or silently match the wrong set).Pagination no longer stops after page 0 when
totalCountis absent. A missing/zerototalCountis treated as “unknown” and pages continue until an empty page, instead of being read as “no more pages”.PushReport.request_counton a failed staged push now counts the requests actually issued, not the full op list.gather()docstring corrected toTaskGroupsemantics — the first raise cancels in-flight siblings, so it must not be used for concurrent writes.
[0.14.7] — 2026-07-16¶
Fixed¶
planno longer reports a changepushnever makes.to_apic()drops an empty string on a non-naming field (sending""would clobber the APIC value), sopushnever sends it — butmo_diffstill compared it, so a design with a field set to""produced a planupdatethat never applied and never converged.mo_diffnow mirrors theto_apicrule (found in a runtime audit).
[0.14.6] — 2026-07-16¶
Fixed¶
protocol="icmpv6"filter-entry sugar now defaultsethernet_typetoipv6(wasip), since ICMPv6 exists only over IPv6.tcp=/udp=keep the genericipether-type, which already matches both IPv4 and IPv6 — no extraethernet_typeis needed for an IPv6 port filter.
[0.14.5] — 2026-07-16¶
Fixed¶
Readable names on the high-traffic classes (batch 3, from a deep audit):
fvSubnet.virtual/.preferred,vzEntry.apply_to_frag/.match_dscp,l3extSubnet.aggregate/.scope, andfvBD.optimize_wan_bandwidth/.intersite_bum_traffic_allow/.service_bd_routing_disable. Thel3extSubnet.scoperename also makesscopeconsistent across the three subnet-like classes (it wasscope_of_the_external_subnet).
[0.14.4] — 2026-07-16¶
Fixed¶
Readable names for 45 more sentence-labelled fields (batch 2): the telemetry FTE event fields (
telemetryFteEventSetP.drop_flow_count, …),fvBD.limit_ip_learn_to_subnets/.mcast_arp_drop,fvTagSelector.match_key/.match_value,fvVmAttr.value,l3extRogueExceptMacP.enable_all_macs,l3extVrfValidationPol.enable_vrf_validation_*,ptpProfile.node_profile_override/.delay_intvl,qosLlfcIfPol.llfc_rcv_admin_st/.llfc_send_admin_st, andbfdIpv4InstPol/bfdIpv6InstPol.echo_src_addr— the last also sheds a mislabelled “ipv4” that the schema stamped on the ipv6 policy. Cryptic wire names (qiqL2ProtTunMask, the flash counters) are deliberately left as-is.
[0.14.3] — 2026-07-16¶
Fixed¶
Readable names for 20 everyday config knobs. Fields whose ACI schema label is a full sentence (which slipped under the codegen’s length cap) now take their wire prop name instead:
bgpPeerP.weight/.connectivity_type,bgpInfraPeerP.weight,bgpCtxAfPol.max_local_ecmp,l2IfPol.vlan_scope,l2PortSecurityPol.maximum/.timeout,hsrpGroupP.mac,hsrpGroupPol.preempt_delay_reload,l3extDefaultRouteLeakP.scope,infraPortTrackPol.minlinks,l4VxlanInstPol.udp_port,ospfCtxPol.max_lsa_num,isakmpKeyring.address,mplsNodeSidP.loopback_addr,fvCepNetCfgPol.start_ip/.end_ip/.dns_suffix/.dns_search_suffix,infraSetPol.enforce_subnet_check.
[0.14.2] — 2026-07-16¶
Fixed¶
Dropped the
dscp_translation_policytenant maker.qosDscpTransPolis anever-creatable global singleton that exists only atuni/tn-infra/dscptranspol-default, so the per-tenant maker always failed (HTTP 400) under any user tenant. Configure the infra default via.mo(qosDscpTransPol, ...)if needed.
[0.14.1] — 2026-07-16¶
Added¶
VRF route targets.
vrf(name).route_target_profile(af)(per address family,ipv4-ucast/ipv6-ucast) withroute_target(value, "import")/route_target(value, "export")— the BGP route targets that map a VRF into an MPLS-VPN, EVPN or SR-MPLS hand-off. Completes the tenant-side SR-MPLS VRF L3Out: consumer label, route targets, and import/export route maps.
Fixed¶
SR-MPLS VRF L3Out.
l3out(name, mpls_enabled=True)now marks an L3Out as a SR-MPLS VRF L3Out (themplsEnabledflag) rather than a classic L3Out.Field name.
l3extOut’s MPLS flag is nowmpls_enabled(its schema label — a full sentence — had produced an unusable name).
[0.14.0] — 2026-07-15¶
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 ( |
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.
[0.13.0] — 2026-07-15¶
VMM domains join the vocabulary, and the push engine learns to fold the plugin-managed path prefixes they need.
Added¶
VMM domains.
design().vmm_provider(vendor).vmm_dom(name)with its vCenter/SCVMM controller (cluster controller, host-availability with protect-VM group and host-desired-state, EP-validator), credentials, vSwitch policy group (enhanced LACP) and uplink container/policies, plus the domain’s EPG aggregators. The domain and vSwitch container bind their default interface policies and pools; the AAEP’s abstractdomainbind now resolves to a declared VMM domain, closing the access-domain loop.Carrier classes. A curated
carrierset names non-creatable, path-only classes the APIC rejects on a standalone POST orrsp-subtreeread (a VMM provider,uni/vmmp-VMware). The staged push emits no op for them — their declared children post at their full DNs and the APIC materialises the path — and the plan diffs those children instead.
Notes¶
Pushing a VMM domain lands the APIC-side config and re-plans cleanly, but a reachable vCenter / SCVMM controller is required before inventory syncs — see the new “Hardware-dependent integrations” note in the design-first guide. The VMM orchestrator provider (NDO) stays out of scope.
[0.12.0] — 2026-07-15¶
The fabric-policy (fabric) and controller (controller) domains join the
tenant and access-policy planes as first-class, live-verified vocabulary, and
non-creatable default singletons finally read as the singletons they are.
Added¶
Fabric policies (six waves). The fabric-internal ports charpente (leaf/spine switch, interface and module profiles, selectors, policy groups, pod profile); fabric interface and protocol policies (link-level, link-flap, L3, L2 MTU, MACsec fabric, ISIS, COOP, fabric VXLAN, vPC domain, PSU redundancy, WWN, load-balance, ZR/ZRP/DWDM optics, node control); fabric monitoring (callhome/SNMP/TACACS destination groups, SNMP policy, fabric and common monitoring policies with their sources); system and global policies (communication services, geo-location hierarchy, proxy, datetime format, connectivity preference, admin-down, deployment, out-of-service); firmware, maintenance and config management (policies, groups, catalogs, export/import/ snapshot/rollback, scheduler, license); telemetry, analytics, TWAMP, core/ techsupport export, latency modes and fabric VSPAN.
Controller policies. Cluster, audit-log retention, controller firmware and maintenance, DRR, fabric first-time setup (+ per-pod), scheduler, cores and CIMC-node policies, alongside the existing fabric membership.
Singleton-aware makers. APIC creatability is baked into every generated model (
_is_creatable). A maker whose target is a non-creatable, name-only default singleton now defaults its name to"default"—.qos_instance_ policy(),.communication_policy("default"),.coop_group_policy()read as the singletons they are, configuring the existing instance in place. Spanning Tree (MST) rejoins the access vocabulary on this basis.
Notes¶
Deprecated or feature-restricted classes are omitted where the 6.0 APIC rejects them (telnet service, telemetry server groups, SD-WAN SLA). Kafka policy and multi-domain (NDO) stay out of scope.
[0.11.0] — 2026-07-15¶
The access-policy (infra) configuration surface is now substantially complete.
The fabric’s physical side — pools, domains, policy groups, interface and switch
policies, QoS and control-plane protection, fabric-wide system settings, and
observability — is first-class vocabulary, typed and live-verified against a
6.0(9c) fabric.
Added¶
Pools and the Fibre Channel domain. VXLAN, VSAN and multicast-address pools with their ranges; the FC domain binding its VLAN/VSAN/address pools and VSAN attributes.
Policy groups and profiles. Leaf/spine switch groups, the spine access group, PC/vPC override, the FC port/PC/PC-override groups, breakout group and modular-card group; the spine interface profile with its port selector; FEX, pod and access-module profiles with their selectors and blocks.
Interface policies. L2 interface, LACP member, PoE, FC, MACsec (container with parameters/keychain/key policies), SyncE, link-flap and 802.1x port/node authentication; the PoE/FC/SyncE instance and fabric policies.
QoS and control-plane protection. The QoS instance policy and its six classes with per-class buffer, congestion, priority-flow-control, queue, schedule and microburst policies; interface LLFC/PFC/slow-drain; CoPP leaf/spine and per-interface policies; the CoPP prefilter with its ACL entries.
Fabric-wide and system policies. CP/controller MTU, TCP MSS, fabric-wide settings, port tracking and status, forwarding-scale profile, USB configuration, fast link-failover, flash configuration, remote-leaf pod redundancy, system GIPo, infrastructure zoning; DHCP relay node/pod groups, node/pod management addresses and the managed-node connectivity group.
Observability and timing. The monitoring policy with syslog/SNMP/callhome/ smart-callhome/TACACS sources and fault/event severity assignment; PTP node policy, profile, domain and template; the four global BFD policies; the NetFlow node policy; VSPAN sessions and destination groups.
Policy-group wiring. The interface and switch policy groups now bind every relevant policy above (CoPP, QoS, MACsec, BFD, PTP, monitoring, and the rest).
Notes¶
Non-creatable fabric singletons — the QoS instance/classes, CP MTU, TCP MSS, fabric-wide settings, port tracking/status, system GIPo, and the zoning profile — are configured in place through their makers rather than created.
[0.10.0] — 2026-07-15¶
The tenant’s configuration surface is now substantially complete. A large body
of tenant protocol and policy configuration that previously needed the .mo()
escape hatch is first-class vocabulary — typed makers, per-position reference
pages, live-verified against a 6.0(9c) fabric.
Added¶
Multicast. VRF-level PIM (
.pim), IPv6 PIM (.pim6) and IGMP (.igmp) with the full RP, pattern and filter policies; BD-level PIM with its route-map filters; the PIM/IGMP interface-policy filters; IGMP/MLD snooping groups.Route-control and leaking. Route-map
match_*andset_*clauses on the match rule and action profile, inter-VRF route leaking (leak_routes), and static routes with their next hops.L3Out. External connectivity labels, node loopbacks and infra nodes, path-level forwarding and rogue-exception MAC, VRF validation and a global VRF name.
Security and VPN. Host protection (microsegmentation) with its subject → rule tree, and site-to-site IKE/IPsec (
isakmp_*,ipsec_phase1/2); port security on a static path.Protocol policies. DNS server groups, tenant AAA server groups, SNMP contexts and communities, QoS class mappings, Fibre-Channel uplink pinning, SR-MPLS node SIDs and SRGB, ND RA subnets, HSRP secondary VIPs, micro-BFD, PTP, BGP data-plane, DHCP relay gateway IP, and virtual SPAN.
Endpoints and pools. Anycast and NLB endpoints, IP address-management pools, VRF route summarization and deployment, uSeg BD associations, ESG LIfCtx selectors.
Curation coverage of the tenant’s declarable configuration rose from roughly 40% to 85%, and the reference now documents 462 curated positions (up from 293).
Notes¶
Multi-site / intersite objects (managed by Nexus Dashboard Orchestrator), Cloud APIC classes, orchestrator-injected config, learned endpoints and L4-L7 service graphs remain out of the tenant vocabulary by design; the
.mo()andbind_dn()escapes keep the rest of the 2,222 generated classes one call away.A handful of tenant classes the APIC auto-manages and refuses to create (
extdevSDWanPolCont,fvConnInstrPol) are deferred until the push engine can upsert such carriers.
[0.9.0] — 2026-07-15¶
The models now carry the right Python type for every field, and the
type checker sees it. Before this release the code generator knew only a
handful of schema types and quietly rendered everything else as str; that
made numbers into text, made bitmasks unusable, and hid the readable field
names from your IDE.
Changed¶
Numbers are numbers. A field the schema declares numeric is now
intorfloat, with the schema’s own bounds — not a string. A field the APIC stores under a name (a filter port, a BGP stale interval) accepts the number and canonicalises the way the APIC does:vzEntry(destination_from_port=80)round-trips as"http", sopush(mode="plan")finally converges on it.A bitmask is a set. A field that is a subset of a closed set (a subnet
scope,vzEntrytcp_rules, a LACPctrl) is nowfrozenset[SomeEnum]. It accepts everything reasonable — the wire string, a set of names, a set of members, a single flag — and order never matters, so the phantom drift the APIC’s own re-ordering used to cause is gone:fvSubnet(ip="10.1.1.1/24", scope="public,shared") # the wire form fvSubnet(ip="10.1.1.1/24", scope={"public", "shared"}) # a set of names vzEntry(name="ssh", tcp_rules="syn,ack") # was rejected before
Addresses are validated. IPv4 and IPv6 fields carry an address pattern instead of accepting any string.
Readable names reach your IDE. A renamed field (
arp_floodingforarpFlood) is now accepted by that name by type checkers — Pylance and pyright no longer flagfvBD(name="web", arp_flooding=True)while accepting the wire spelling. The wire name still works on reads and in query filters.
Added¶
niwaki.query(cls)is overloaded: a model class in gives typed instances out; a class name string in gives baseManagedObjects (their attributes inmodel_extra).ref()is accepted bybind_dn()and by the contract verbs, not only bybind().The generated reference now documents every field’s real type and, for an enum or a set of flags, its allowed values and default.
Migration¶
A field you read back may now be a number or a
frozensetwhere it used to be a string — compare against80, not"80", and against{"public", "shared"}, not"public,shared". Construction is unchanged: the wire string is still accepted everywhere.A bitmask default is a
frozenset; a numeric field’s default is a number.One long-standing default was corrected:
bgpBestPathCtrlPol.ctrldefaulted toas-path-multipath-relaxenabled and now defaults to no flags, as the schema declares.ospfExtPregained itsarea_ctrlfield, previously dropped by a name collision.
Internal¶
pyright now type-checks the whole repository (the generated tree excepted) in the commit gate and CI, alongside mypy — it reads the constructor signature Pydantic synthesises, which mypy does not. A cold-start budget and a documentation type-column guard were added.
[0.8.0] — 2026-07-14¶
Added¶
The EPG/ESG world enters the vocabulary. An application EPG now reaches everything the APIC hangs under it:
subnet()(with itsl3outandnd_ra_prefix_policybinds),static_endpoint()(plusstatic_ip(), and the path/node it lives on),criterion()— the uSeg selector, withip_attribute(),mac_attribute(),vm_attribute(),dns_attribute()and nestedsub_criterion()—virtual_ip()for L4-L7 VIPs, andfc_path()for Fibre-Channel paths.Endpoint security groups:
app().esg()with its selectors (ep_selector(),epg_selector(),tag_selector()), its mandatoryvrfbind, and the contract verbs.New EPG binds:
contract_master(contract inheritance — one alias, EPG or ESG alike),imported_contract,taboo_contract,custom_qos_policy,dpp_policy,monitoring_policy,trust_control_policy; and the tenant objects they point at:taboo_contract()(with itssubject()),imported_contract()andmonitoring_policy().A third contract verb,
intra_epg()(fvRsIntraEpg), on EPGs and ESGs. Contract verbs are now fully data-driven: curating one in the vocabulary is enough — the runtime no longer hardcodes the list.The contract world completes (229 curated positions).
vrf().vzany()arrives — contracts for a whole VRF, reached through relation classes of its own (vzRsAnyToProv/vzRsAnyToCons/vzRsAnyToConsIf), which the data-driven verbs absorb without a line of engine code. A subject that stops applying both ways gets one filter per direction within_term()andout_term();exception()excludes an EPG from a contract, on the contract or on the subject;oob_contract()covers out-of-band (management) contracts with their own subjects.The six contract labels (
provider_label(),consumer_label(),provider_subject_label(),consumer_subject_label(),provider_contract_label(),consumer_contract_label()) are curated wherever the MIT hangs them — EPG, ESG, vzAny, subject and external EPG — so theprovider_label_match_criteriaattribute finally has labels to compare.ref()— a reference can configure the relationship itself. Most relations are pure edges, but 26 curated binds resolve to a class that carries fields of its own, and they were unreachable: the resolution immediacy of an EPG-to-domain attachment, thedirectivesof a filter under a subject (this is where contract logging lives), thedirectionof a route-control profile, a node’s management address, an ERSPAN collector’s IP. Wrap the target —epg.bind(domain=ref("prod-phys", resolution_immediacy="immediate"))— anywhere a plain name goes, includingbind_dn()and the contract verbs. The fields are validated against the relation class at declaration time.Observability: SPAN (
span_source_group()with its sources, label and filter group;span_destination_group()with its destinations), NetFlow (netflow_monitor(),netflow_exporter(),netflow_record()) and QoS requirements (qos_requirement(), withingress_dpp()/egress_dpp()and the EPG bind that reaches it). SPAN and NetFlow are curated under the tenant, underinfraand underfabricalike.The L2 edge and management:
l2out()complete (node profile, interface profile, static path, external EPG with labels and contract verbs), the in-band and out-of-band management EPGs — which give the out-of-band contract someone to provide and consume it — endpoint tags (what an ESGtag_selectormatches), IP address pools, and fallback route groups.The closed world is closed: every curated
bind()now has a declarable target, except the ones the fabric discovers for you (fabricNode,fabricPathEp), which is whatbind_dn()is for. 293 curated positions, up from 176 at the start of the wave.
Changed¶
Renamed, on an L3Out external EPG: the two subject-label makers were curated before those classes had a name of their own and carried a generated one. They now speak the same word as everywhere else:
.vz_prov_subject_label(...)→.provider_subject_label(...)and.vz_cons_subject_label(...)→.consumer_subject_label(...). Their reference pages moved with them.A verb’s parameter is named after what it points at (
provide(contract),ingress_dpp(dpp_policy)), and its flavor is read off the relation class rather than assumed to be name-flavored. Existing call sites are unaffected:provide(contract)keeps its exact signature.
[0.7.0] — 2026-07-13¶
Added¶
The models now carry the APIC’s own catalog of accepted-but-inconsistent configuration states: 98 classes declare a read-only
configIssuesenum in the schemas (~2,500 codes —fvBDalone listsFHS-enabled-on-l2-only-bd), previously invisible to users. Each such class exposes_config_issues({code: description}) and lists the codes in its docstring — the states your IDE can warn you about before the APIC flags them. Descriptions come from the value’scomment(rich prose) with the schemalabelas fallback — every code is described.Two more declared constraint channels reach the models:
_fault_codes(659 classes, 739 F-codes —fvBDcarriesfltFvBDMulticastEnabledOnL2BD) and_relation_infoon relation classes (cardinality, enforceable, resolvable). Both are guarded by an anti-drift integrity suite that re-derives them from the raw schemas for all 2,222 generated classes.The DSL reference: the generated vocabulary book becomes a full reference — one page per curated position with an attribute table (parameter, wire alias, type, allowed values, default, Cisco’s description), the children/binds/verbs it reaches, and the APIC diagnostics (config issues, fault codes) it can raise. Plus a page of the 106 enums the vocabulary uses (each value with Cisco’s meaning) and the read-side navigation vocabulary. The typed keyword arguments of every maker — the SDK’s core surface — were previously visible only by hovering in an IDE.
Changed¶
The transport boundary is public:
niwaki.transportexports the four structural protocols (MoWriter,MoReader, and their async mirrors) and both sessions (ApicSession,AsyncApicSession) — the extension point the testing guide relies on no longer lives in a private module.The API reference renders objects under their real import path (
niwaki.Niwaki, notniwaki.facade.Niwaki), no longer exposes the models’ private ClassVars as public attributes, and gained the entries it was missing: the root package,mo_diff/parse_imdata,REGISTRY, the filter operators, and vocabulary navigation.Deep anchors of the vocabulary pages moved: each position now has its own page (
reference/vocabulary/tenant/tenant-bd.html) instead of an anchor on the domain page.
[0.6.0] — 2026-07-13¶
A professional documentation overhaul, and the enterprise-CA answer.
Added¶
verify_sslaccepts a path to a CA bundle (PEM) onNiwaki,AsyncNiwakiand both sessions — TLS verification against a private or enterprise CA no longer requires disabling verification (the bundle loads eagerly into anssl.SSLContext; a wrong path fails at construction).Three documentation pages the adopting coder was missing: Testing your automation (payload asserts, plan as a convergence test, a fake APIC at the httpx boundary, transport-protocol stubs — all executable), Compatibility & limits, and Troubleshooting connection & auth (the exception → question → knob ladder).
The documentation home is a real landing page (orientation, a “Start here” path, the Diátaxis compass), every guide page ends with next steps, and deletion semantics have a canonical section.
Fixed¶
The
plandocumentation described pre-0.3.0 behaviour: plan reads are scoped withrsp-subtree-classto the classes the design declares — the stale “avoid planning large domains” advice is gone, and the write-only-secrets caveat now lives whereplanis taught.One maxim (“structure is literal, vocabulary is translated”), one term per concept, position counts generated straight from the vocabulary — the terminology and numbers can no longer drift.
Cisco placeholder comments (the literal text “null”, on 621 schema properties) no longer leak into maker Args sections and field descriptions — those fields simply stay undescribed.
[0.5.0] — 2026-07-13¶
Cisco’s own definitions, everywhere the IDE looks.
Added¶
The APIC schema comments — Cisco’s human-written definitions, covering 79% of configurable properties and 84% of classes — now flow through the entire generated surface:
every described model field carries
Field(description=...): IDE hover, Pydantic error context and Sphinx autodoc all show Cisco’s definition;model class docstrings carry the class definition;
enum members carry per-value docstrings (
OspfNwT.BCAST— “Broadcast interface”);every DSL maker exposes a generated Args section: field definition, allowed enum values and non-empty defaults, straight from the schemas.
Wire behaviour is untouched (golden payloads pass unchanged) and cold-start stays at ~90 ms — models remain lazily loaded.
[0.4.0] — 2026-07-12¶
The vocabulary triples and the whole delivery pipeline matures.
Added¶
L3Out, complete (wave 1): node and interface profiles, node/path attachments as literal-DN makers, BGP peers with ASN and prefix policies, OSPF/EIGRP/HSRP/PIM/IGMP/BFD/MPLS interface profiles, floating SVIs, external EPGs with subnets and contract verbs, route-control profiles and contexts.
Tenant > Policies > Protocol, 28/28 GUI folders (wave 1bis): BGP and EIGRP address-family contexts, OSPF timers, data-plane policing, DHCP relay (provider carries the server address) and options, endpoint retention, external bridge group profiles, First Hop Security with RA guard, IGMP/MLD snooping, IP SLA with ICMP/TCP probes, track lists/members, PIM route maps with entries, route tags, tenant-level route maps, keychains with key tables — plus the standalone L4-L7 policies (PBR with destinations, backup, health groups, service EPG). The vocabulary grows from 57 to 176 curated positions.
propose_vocabularycodegen tool: assisted-curation candidates (makers from the navigation jargon, binds from the reference map, contract-verb detection, review flags) — the vocabulary now grows in reviewed waves, and contributions need no fabric.Write-only schema properties (passwords, pre-shared keys) are tracked as
_secure_propson the models and excluded fromplan/diff comparison — a pushed secret no longer reports phantom drift. Consequence: rotating a secret requires a push;plancannot see it.Documentation: hosted site (GitHub Pages) with an executable-docs suite, a cookbook of operator recipes, the generated coverage matrix, the cobra comparison gallery, and the Inside the DSL page; offline wheelhouse (niwaki + all dependencies) attached to every GitHub Release for restricted networks.
The full unit-test suite (14,200+) ships with the repository and runs in the public CI on Python 3.12 and 3.13.
Changed¶
The generated cursor layout scales: one module per design domain, loaded lazily; ancestor makers are inherited through per-position mixins (nearest level wins, like the runtime) — 25k generated lines became 4.3k at 57 positions, ~75 lines per position since.
Cursor class names disambiguate with as many ancestor labels as needed (
bgpPeerPunder two positions yields distinct cursors).
[0.3.0] — 2026-07-11¶
First PyPI release.
Added¶
Published on PyPI:
pip install niwaki/uv add niwaki(trusted publishing with provenance attestations, from the public repository).Fabric ASN as a curated position (
bgp_instance().autonomous_system()), per-port interface-profile convention support proven in the live walkthrough (one selector per port, reserved ports never profiled).
Fixed¶
planreads are scoped withrsp-subtree-classto the classes the design declares — an unscoped full read ofuni/fabricexceeds the APIC query limit (“result dataset is too big”).Field comparison in
planis numeric-aware: the APIC canonicalises numeric strings (“80.0” reads back as “80.000000”); designs carrying float-like values stay idempotent.fvSubnet.scopecarries its operator name (wasvisibility_of_the_subnet).
[0.2.0] — 2026-07-11¶
Changed¶
The SDK is now named
niwaki(庭木 — the Japanese art of sculpting full-size, living garden trees). The former working name collided with an existing PyPI package;niwakiis free as both a distribution and an import name, and says exactly what the SDK does to the APIC Management Information Tree.Everything follows the new name: the import package (
import niwaki), the clients (Niwaki/AsyncNiwaki), the nodes (NiwakiNode/AsyncNiwakiNode) and the exception root (NiwakiError). No behavioural change.
[0.1.0] — 2026-07-10¶
Initial private milestone, under the project’s former working name.
Design-first architecture: the design DSL describes the whole
unisubtree (tenant, access, fabric, controller),push()applies (strict/staged/plan), the facade observes (navigation, typed reads, queries, delete).2,222 generated Pydantic models (APIC v6.0 schemas), 558 enums, human-readable field names with wire aliases.
Curated vocabulary (
domain/vocabulary.yaml), typed cursors generated per position, unified reference resolver (name + DN flavors, abstract targets),bind_dn()escape, atomic staged classes.Sync + async transport with proactive token refresh, retry, pagination.
Sphinx documentation with a generated vocabulary book; 13,700+ unit tests, mypy strict.