Utilities¶
Two building blocks the SDK uses internally and exposes for the same jobs in your own code: computing a surgical delta between two objects, and unwrapping an APIC response envelope.
Diffing¶
ManagedObject diff utility for surgical APIC updates.
mo_diff() compares a desired state object against a current state
object (typically retrieved from the APIC via from_apic())
and returns a new instance carrying only the changed fields.
The returned object’s model_fields_set contains exclusively the naming
props plus the fields that actually differ, so calling to_apic() on it
produces a surgical PATCH payload — the APIC leaves untouched fields at their
current values.
Children are diffed recursively by default (see recurse_children):
matched by (_aci_class, naming prop values) identity. Children present
in desired but absent in current are included as-is (additions). Children
present only in current are ignored — mo_diff does not produce DELETE
ops; handle removals explicitly via the facade.
Example:
from niwaki.models._generated.fv.fvBD import fvBD
from niwaki.utils.diff import mo_diff
desired = fvBD(name="web", unicast_routing=False, arp_flooding=True)
current = fvBD.model_validate({"name": "web", "unicastRoute": "yes", "arpFlood": "no"})
delta = mo_diff(desired, current)
if delta:
delta.to_apic()
# → {"fvBD": {"attributes": {"name": "web",
# "unicastRoute": "false",
# "arpFlood": "true"}}}
- niwaki.utils.diff.mo_diff(desired, current, *, recurse_children=True, respect_fields_set=False)[source]¶
Compute the delta between a desired and current ManagedObject state.
Only declared model fields are compared — APIC-originated read-only extra fields (
modTs,uid, …) stored inmodel_extraare ignored on both sides.- Parameters:
desired (T) – Target state — the object as you want it to be.
current (T) – Current state — typically from
from_apic().recurse_children (bool) – When
True(default), children are diffed recursively and any changed or new children are included in the returned delta. Set toFalseto restrict the diff to scalar fields only (original behaviour).respect_fields_set (bool) – When
True, only fields present indesired.model_fields_setare compared — fields the caller never touched are ignored even if the current state differs from the schema default. This is the mode used by the design DSL’splanpush: a design that does not setunicast_routingmust not report a change just because the APIC value differs from the model default. DefaultFalse(compare all declared fields — original behaviour).
- Returns:
A new instance of the same class with
model_fields_setlimited to naming props + changed fields (+ changed children whenrecurse_children=True). ReturnsNonewhen the objects are identical (no diff to apply).- Raises:
TypeError – When
desiredandcurrentare of different classes.ValueError – When a naming prop differs between the two objects (they represent different objects and cannot be diffed).
- Return type:
T | None
Example:
desired = fvBD(name="web", unicast_routing=False) current = fvBD.model_validate({"name": "web", "unicastRoute": "yes"}) delta = mo_diff(desired, current) # delta.to_apic() → {"fvBD": {"attributes": {"name": "web", "unicastRoute": "false"}}} mo_diff(desired, desired) # → None (no change)
Children example:
from niwaki.models._generated.fv.fvSubnet import fvSubnet desired_bd = fvBD(name="web") desired_bd.children = [fvSubnet(ip="10.1.0.1/24", scope="public")] current_bd = fvBD.model_validate({"name": "web"}) current_bd.children = [fvSubnet(ip="10.1.0.1/24", scope="private")] delta = mo_diff(desired_bd, current_bd) # delta has one child: fvSubnet with scope="public"
Response parsing¶
APIC REST response parsing utilities.
APIC responses always have the envelope:
{
"totalCount": "42",
"imdata": [
{"fvBD": {"attributes": {...}, "children": [...]}},
...
]
}
parse_imdata() consumes that envelope and returns typed ManagedObjects.
HTTP-level error extraction lives in niwaki.transport._errors.
- niwaki.utils.response.parse_imdata(data)[source]¶
Deserialise all objects in the
imdataarray into typed ManagedObjects.Uses
from_apic()for each item, which dispatches to the correct generated subclass viaREGISTRY. Error entries (items keyed"error") are skipped — HTTP status handling raises typed exceptions before this parser runs.- Parameters:
data (dict[str, Any]) – Raw APIC response dict (the top-level JSON object).
- Returns:
List of
ManagedObjectinstances (may be empty).- Raises:
niwaki.exceptions.DeserializationError – When any item in
imdatacannot be deserialised.- Return type:
Example:
raw = {"totalCount": "2", "imdata": [ {"fvBD": {"attributes": {"name": "web"}}}, {"fvBD": {"attributes": {"name": "db"}}}, ]} bds = parse_imdata(raw) # → [fvBD(name="web"), fvBD(name="db")]