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: BaseModel

Base 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 under naming_only() and config_only() — and it lands in model_extra because it is not a configurable field. A locally-constructed object has no DN yet, so accessing .dn on one raises AttributeError.

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 dn is 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); children is 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), so attrs includes 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:

dict[str, Any]

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_construct with model_fields_set limited to the naming props plus the explicitly provided changes. Calling to_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_construct with _fields_set directly: it centralises the logic and prevents the common mistake of forgetting to include naming props in _fields_set.

Parameters:
  • naming (dict[str, Any]) – Dict of naming prop values (e.g. {"name": "web"}).

  • **changes (Any) – Attribute values to include in the payload.

Returns:

A partially-populated instance ready for to_apic().

Return type:

Self

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:

data (dict[str, Any]) –

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:

ManagedObject

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 in model_extra (absorbed via extra="allow"). Pydantic v2 automatically adds any field you assign directly to model_fields_set, so a plain assignment is enough to include that field in the next to_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) (see niwaki.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_apic narrows it to the naming props, because a full read must stay surgical on round-trip through to_apic(). A subscription push is a different shape entirely — confirmed live, a created event carries the full object, a modified event carries only the changed properties plus dn, and a deleted event carries only dn — so reusing from_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_event sets model_fields_set to exactly the config fields this event’s payload reported: set(coerced) & set(target_cls.model_fields) — mirroring the same “an instance whose model_fields_set faithfully describes what is populated” idiom surgical() already uses for locally-built payloads, applied here to a payload read off the wire instead. event.mo.model_fields_set is therefore the caller’s authoritative answer to “what did this event actually tell me?” — empty for a deleted event (identity only: dn lands in model_extra, which is unaffected by model_fields_set), the complete reported set for a created event.

Parameters:

data (dict[str, Any]) –

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_set matching exactly what the payload reported.

Return type:

ManagedObject

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 by from_apic to deserialise an APIC answer into the right typed class.