Models¶
The 2,222 generated classes share one contract — data and validation, never write logic. Import them by package alias:
from niwaki.models.fv.fvBD import fvBD
bd = fvBD(name="web", unicast_routing=True) # validated at construction
bd.rn # "BD-web"
bd.to_apic() # wire payload, ACI attribute names
Where the fields are documented. Every class reachable through the
design DSL has its full attribute table — name, wire alias, type, allowed
values, default and Cisco’s own definition — in the generated
DSL reference, one page per position. For the
other generated classes, the same descriptions live in the code and in your
IDE (fvBD.model_fields["arp_flooding"].description); this site does not
paginate all 2,222 of them.
Conventions and day-to-day usage — readable names vs wire aliases, enums,
validation, the .mo() escape hatch: Working with the models.
The base class¶
- class niwaki.models.ManagedObject(*, children=[], **extra_data)[source]¶
Bases:
BaseModelBase class for every ACI Managed Object.
The 2,222 ACI classes are generated subclasses; this base provides the runtime behaviour they all share — RN computation, child containment validation, APIC serialisation (surgical: only the fields you set), and deserialisation back into the right typed class.
Instances validate at construction: a wrong enum value, an out-of-range integer or a malformed IP never reaches the wire.
Example:
from niwaki.models.fv.fvBD import fvBD bd = fvBD(name="prod-bd", unicast_routing=True) bd.rn # → "BD-prod-bd" bd.to_apic() # → {"fvBD": {"attributes": {"name": "prod-bd", "unicastRoute": "true"}}}
Every generated subclass also carries private ClassVars holding the APIC schema metadata (naming props, containment, fault and config-issue catalogs…) — they are the codegen contract, not a public API; see the module docstring for the full list.
- property rn: str¶
Compute the Relative Name from _rn_format and current naming prop values.
- Returns:
RN string with all
{prop}placeholders replaced.
Example:
bd = fvBD(name="web") bd.rn # → "BD-web"
- property dn: str¶
The APIC Distinguished Name, as returned on a read.
A read result always carries
dn— it is structural, present even undernaming_only()andconfig_only()— and it lands inmodel_extrabecause it is not a configurable field. A locally-constructed object has no DN yet, so accessing.dnon one raisesAttributeError.This reads the same whether the class is generated or not — one uniform accessor over the whole readable object space.
- Returns:
The Distinguished Name string.
- Raises:
AttributeError – The object was built locally, not read from the APIC, so no
dnis present.
- property attrs: dict[str, str]¶
The object’s attributes as APIC wire strings — one flat view for any class.
Every config field, at its current value, is rendered back to wire form (
True→"true", a flags set →"a,b") under its wire name, then merged with the read-only attributes the APIC returned (model_extra, already wire strings);childrenis excluded. A generated object and an operational one read identically through this one call — it never exposes the typed/dynamic split.This is the object’s current state re-rendered to wire form, not a verbatim copy of the APIC response: a generated object read from the APIC carries every field (
from_apic()builds a complete instance), soattrsincludes defaults the APIC may not have sent, and a value is re-rendered (a bool becomes"true"/"false") and so can differ from the exact string the APIC returned.- Returns:
{wire_attribute_name: wire_string_value}.
- to_apic()[source]¶
Serialise this object to the APIC JSON envelope format.
Only fields that were explicitly set at construction time (tracked via
model_fields_set) are included in attributes, plus any naming props. This produces surgical POSTs — the APIC applies its own defaults for unset fields.Extra fields absorbed from APIC GET responses (
model_extra) are never included in the serialised output.- Returns:
Dict in APIC envelope format:
{ "fvBD": { "attributes": {"name": "web", "unicastRoute": "true"}, "children": [...] # only when children exist } }
- Return type:
Example:
bd = fvBD(name="web", unicast_routing=True) bd.to_apic() # → {"fvBD": {"attributes": {"name": "web", "unicastRoute": "true"}}}
- classmethod surgical(naming, **changes)[source]¶
Construct a payload-only instance for surgical APIC writes.
Builds an instance via
model_constructwithmodel_fields_setlimited to the naming props plus the explicitly provided changes. Callingto_apic()on the result sends only those fields — the APIC leaves all other attributes untouched (APIC upsert semantics).This is the canonical alternative to calling
model_constructwith_fields_setdirectly: it centralises the logic and prevents the common mistake of forgetting to include naming props in_fields_set.- Parameters:
- Returns:
A partially-populated instance ready for
to_apic().- Return type:
Example:
# Surgical update: only name + arp_flooding in the POST payload delta = fvBD.surgical({"name": "web"}, arp_flooding=True) delta.to_apic() # → {"fvBD": {"attributes": {"name": "web", "arpFlood": "true"}}}
- classmethod from_apic(data)[source]¶
Deserialise an APIC JSON response envelope into a typed ManagedObject.
Uses REGISTRY to dispatch to the correct subclass. If the ACI class is not registered (not yet generated), falls back to the base ManagedObject. Children are recursively deserialised.
- Parameters:
Dict in APIC envelope format, e.g.:
{ "fvBD": { "attributes": {"name": "web", "unicastRoute": "true"}, "children": [...] } }
- Returns:
Typed ManagedObject instance (or ManagedObject if class unknown).
- Return type:
Note
Deserialized objects start with a minimal ``model_fields_set``. Only the naming props (e.g.
name) are pre-included; all other APIC attributes land inmodel_extra(absorbed viaextra="allow"). Pydantic v2 automatically adds any field you assign directly tomodel_fields_set, so a plain assignment is enough to include that field in the nextto_apic()call:bd = session.get_mo("uni/tn-prod/BD-web", cls=fvBD) bd.unicast_routing = False # ← Pydantic adds to model_fields_set bd.to_apic() # ← unicastRoute: "false" sent ✓
For patch-style configuration, prefer a small design:
tenant("prod").bd("web").set(unicast_routing=False).push(aci)(seeniwaki.design).Example:
raw = {"fvBD": {"attributes": {"name": "web"}, "children": []}} bd = ManagedObject.from_apic(raw) isinstance(bd, fvBD) # → True (if fvBD is registered)
- model_post_init(context, /)¶
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self (BaseModel) – The BaseModel instance.
context (Any) – The context.
- classmethod from_event(data)[source]¶
Deserialise one object-subscription push item into a typed ManagedObject.
A sibling of
from_apic(), differing in exactly one respect:model_fields_set.from_apicnarrows it to the naming props, because a full read must stay surgical on round-trip throughto_apic(). A subscription push is a different shape entirely — confirmed live, acreatedevent carries the full object, amodifiedevent carries only the changed properties plusdn, and adeletedevent carries onlydn— so reusingfrom_apic’s naming-only narrowing here would be actively wrong: every field a modify delta did not mention would read as though it were a reported value instead of “this event said nothing about it,” and the naming props themselves would be marked reported even when a modify/delete payload typically omits them.Instead,
from_eventsetsmodel_fields_setto exactly the config fields this event’s payload reported:set(coerced) & set(target_cls.model_fields)— mirroring the same “an instance whosemodel_fields_setfaithfully describes what is populated” idiomsurgical()already uses for locally-built payloads, applied here to a payload read off the wire instead.event.mo.model_fields_setis therefore the caller’s authoritative answer to “what did this event actually tell me?” — empty for adeletedevent (identity only:dnlands inmodel_extra, which is unaffected bymodel_fields_set), the complete reported set for acreatedevent.- Parameters:
One push item’s envelope, matching
from_apic()’s shape but typically sparse, e.g.:{"fvBD": {"attributes": {"dn": "uni/tn-prod/BD-web", "arpFlood": "yes", "status": "modified"}}}
- Returns:
Typed ManagedObject instance (or the base ManagedObject for a class with no generated model), with
model_fields_setmatching exactly what the payload reported.- Return type:
Example:
mo = ManagedObject.from_event( {"fvBD": {"attributes": {"dn": "uni/tn-prod/BD-web", "arpFlood": "yes"}}} ) mo.model_fields_set # → {"arp_flooding"} — "dn" is not a config field mo.arp_flooding # → True
The registry¶
- niwaki.models.base.REGISTRY: dict[str, type[ManagedObject]]¶
ACI class name → generated Python class, for every imported model.
Populated by
__init_subclass__as model modules are imported (lazily, on first use), and used byfrom_apicto deserialise an APIC answer into the right typed class.