niwaki.exceptions¶
Niwaki SDK — public exception hierarchy.
All errors raised by the SDK are subclasses of NiwakiError so callers can
write a single broad except NiwakiError or target a specific branch:
from niwaki.exceptions import (
NiwakiError, # catch-all
AuthError, # any authentication failure
LoginError, # wrong credentials
TokenRefreshError, # /aaaRefresh.json failed
SessionExpiredError, # token dead, re-login also failed
TransportError, # any network-level error
ConnectionError, # host unreachable
TimeoutError, # request too slow
TLSError, # SSL/TLS certificate issue
APIError, # APIC returned 4xx / 5xx
UnauthorizedError, # 401 — token rejected by APIC
ForbiddenError, # 403 — insufficient privileges
NotFoundError, # 404 — MO does not exist
ServerError, # 5xx — APIC internal error
DeserializationError, # response cannot be parsed into a typed model
StagedPushError, # staged design push partially succeeded
DanglingReferenceError, # verify_refs: external DN targets the APIC cannot honor
SubscriptionError, # any object-subscription (WebSocket push) failure
StatsClassNotSubscribableError, # subscribed to a stats class — never pushes
SubscribeRejectedError, # the APIC rejected a subscription=yes request
SubscriptionLostError, # a subscription could not be recovered
SubscriptionLostReason, # which recovery path was exhausted (see .reason)
)
Hierarchy:
NiwakiError
├── AuthError
│ ├── LoginError
│ ├── TokenRefreshError
│ └── SessionExpiredError
├── TransportError
│ ├── ConnectionError
│ ├── TimeoutError
│ └── TLSError
├── APIError
│ ├── UnauthorizedError
│ ├── ForbiddenError
│ ├── NotFoundError
│ └── ServerError
├── DeserializationError
├── NoResultError (query .one() matched nothing)
├── UnknownClassError (catalogue lookup on an unknown class; also KeyError)
├── MultipleResultsError (query .one() matched several)
├── MissingDependencyError (an optional extra is not installed)
├── DesignError
│ ├── UnknownMakerError (also an AttributeError)
│ ├── DuplicateDeclarationError
│ ├── UnresolvedReferenceError
│ ├── AmbiguousBindError
│ ├── StagedPushError
│ ├── DanglingReferenceError
│ ├── SnapshotImportError (to_design: items a snapshot holds that cannot import)
│ └── MergeConflictError (merge: two designs disagree on one DN)
└── SubscriptionError
├── StatsClassNotSubscribableError
├── SubscribeRejectedError (also an APIError)
└── SubscriptionLostError (.reason: SubscriptionLostReason)
Warnings (not exceptions — a design the fabric accepts but will fault on):
UserWarning
└── DesignHintWarning (design/: a declaration that guarantees a fault)
- exception niwaki.exceptions.APIError(status_code, apic_message='', *, apic_code=None)[source]¶
Bases:
NiwakiErrorThe APIC responded with an HTTP error status (4xx or 5xx).
- status_code¶
HTTP status code returned by the APIC — or, when the SDK raises without one, the status that best describes the failure:
404for a DN whose read came back empty,0when no request was made at all. A non-zero status is therefore not proof that the controller answered;apic_code is Noneis the reliable way to tell an SDK-synthesised error from a controller’s own.
- apic_message¶
Error text extracted from the APIC payload, if available; otherwise the first 200 characters of the raw body.
- apic_code¶
The APIC’s own error code —
error.attributes.codeon the wire — as the verbatim string the controller sent ("103","801"), orNonewhen the response carried no APIC error envelope, or the SDK raised without one.This is a cause discriminator, never a transience one: on APIC 6.0(9c) many distinct codes all arrive under HTTP 400, and none of the measured ones is retryable. Decide whether to retry from the exception type; decide what went wrong from
apic_code.The value stays a string rather than being parsed to
int, so the SDK never fails to report an error because a controller sent a code it did not expect.
Example:
try: aci.node(dn).delete() except APIError as exc: if exc.apic_code == "107": # the controller refuses to delete it ... raise
- exception niwaki.exceptions.AmbiguousBindError[source]¶
Bases:
DesignErrorNo unambiguous Rs class exists for a
bindedge.Neither
REFERENCE_MAP[owner][target]nor the inverseREFERENCE_MAP[target][owner]resolves to a relationship class. Use.mo(RsClass, ...)to create the relationship explicitly.
- exception niwaki.exceptions.AuthError[source]¶
Bases:
NiwakiErrorBase class for APIC authentication errors.
Subclasses cover login failure, token refresh failure, and full session expiry.
- exception niwaki.exceptions.ConnectionError[source]¶
Bases:
TransportErrorThe APIC host is unreachable (DNS failure, TCP refused, interface down, etc.).
Wraps
httpx.ConnectErrorin non-TLS cases.Note
This name intentionally shadows the Python builtin
ConnectionError. Inside the SDK, useniwaki.exceptions.ConnectionError.
- exception niwaki.exceptions.DanglingReferenceError(failures)[source]¶
Bases:
DesignErrorExternal references the APIC cannot honor, caught before the push.
Raised by
push(verify_refs=True)instrict/stagedmode when at least onebind_dn/literal-DN reference points at a DN the APIC does not serve (or serves with a class outside the referencing class’s accept-set). Nothing has been written when this raises — verification is a read-only pass that runs before the first POST.Every failure is collected before raising (never first-fail); the message carries the full list with DNs in clear, and
failuresexposes the structured checks.- failures¶
The failing checks, as passed.
- exception niwaki.exceptions.DeserializationError[source]¶
Bases:
NiwakiErrorThe APIC payload cannot be parsed into a typed niwaki model.
Raised by the model layer when the response structure returned by the APIC does not match the expected Pydantic schema. This can happen if:
The APIC firmware version returned a field that the SDK schema does not recognise (forwards-compatibility issue).
A required field is missing from the APIC response (schema drift).
A field value has an unexpected type.
- args[]¶
Human-readable description including the class name and the Pydantic validation error.
Example:
try: tenant = session.get_mo("uni/tn-Prod", fvTenant) except DeserializationError as exc: logger.error("Schema mismatch for fvTenant: %s", exc)
- exception niwaki.exceptions.DesignError[source]¶
Bases:
NiwakiErrorBase class for all design-DSL errors.
- exception niwaki.exceptions.DesignHintWarning[source]¶
Bases:
UserWarningA design the SDK can express but the fabric will not be happy with.
Not an error: the push is legal, the APIC accepts it, and forbidding it would take away a shape somebody may genuinely want. It is the case where the declaration is provably going to raise a fault — a floating SVI whose address is left at
0.0.0.0lands outside its own subnet and the controller answers with a major fault every time.Its own category, so that
warnings.simplefilter("error", DesignHintWarning)turns these into failures in a CI pipeline without touching every otherUserWarningin the process, andsimplefilter("ignore", DesignHintWarning)silences them without hiding anything else.
- exception niwaki.exceptions.DuplicateDeclarationError[source]¶
Bases:
DesignErrorThe same object (class + naming) was declared twice in a design.
- exception niwaki.exceptions.ForbiddenError(status_code, apic_message='', *, apic_code=None)[source]¶
Bases:
APIErrorThe APIC returned HTTP 403 — the authenticated user lacks sufficient privileges.
Difference from
UnauthorizedError: - 401 = not authenticated (invalid token). - 403 = authenticated but not authorised on this resource.
- exception niwaki.exceptions.LoginError[source]¶
Bases:
AuthErrorThe APIC rejected the credentials during login.
Raised when POST
/api/aaaLogin.jsonreturns a non-200 status or the response contains an APIC error message (wrong password, locked account, etc.).- args[]¶
Error message including the HTTP status and APIC text.
- exception niwaki.exceptions.MergeConflictError(conflicts)[source]¶
Bases:
DesignErrorTwo designs disagree —
merge()refuses to guess.Raised after the whole merge has been walked (never first-fail): every contradiction is collected, in the style of
SnapshotImportError. A contradiction is one DN carrying the same field, wire property, or class with two different values across the sources — agreement and one-sided declarations merge silently.- Parameters:
conflicts (list[tuple[str, str, tuple[object, object]]]) – One
(dn, what, (value_a, value_b))triple per contradiction, sorted by DN — what is a field name, a wire property name, or"class".
- conflicts¶
The collected contradictions, as passed.
- exception niwaki.exceptions.MissingDependencyError[source]¶
Bases:
NiwakiErrorAn optional feature was used without the extra that provides it.
Raised at import or first use rather than at the first request, so the message names the extra to install instead of surfacing as a confusing failure deep in the transport.
Example:
pip install niwaki[x509]
- exception niwaki.exceptions.MultipleResultsError[source]¶
Bases:
NiwakiErrorA query that required exactly one object matched more than one.
Raised by
one()/one()when the result set holds two or more objects. Narrow the query, or usefirst()/fetch()when several matches are expected.
- exception niwaki.exceptions.NiwakiError[source]¶
Bases:
ExceptionBase class for all niwaki SDK errors.
- exception niwaki.exceptions.NoResultError[source]¶
Bases:
NiwakiErrorA query that required exactly one object matched none.
Raised by
one()/one()when the result set is empty. Usefirst()when no match is an acceptable outcome.
- exception niwaki.exceptions.NotFoundError(status_code, apic_message='', *, apic_code=None)[source]¶
Bases:
APIErrorThe APIC returned HTTP 404 — the requested MO does not exist.
The DN or API path is invalid, or the object has been deleted.
- exception niwaki.exceptions.ServerError(status_code, apic_message='', *, apic_code=None)[source]¶
Bases:
APIErrorThe APIC returned a 5xx error — server-side APIC error.
These errors are considered transient and may be retried. If they persist after all retry attempts, this exception is raised.
- exception niwaki.exceptions.SessionExpiredError[source]¶
Bases:
AuthErrorThe session is fully expired: both refresh and re-login failed.
Raised when the token has expired and no renewal attempt succeeded. The caller must create a new
ApicSession.- args[]¶
Message describing the reason for expiry.
- exception niwaki.exceptions.SnapshotImportError(problems)[source]¶
Bases:
DesignErrorA snapshot holds items
to_design()cannot import.Raised after the whole snapshot tree has been walked (never first-fail): every offending item is collected so one run reports the complete list, in the style of
DanglingReferenceError. Nothing about the failed import leaks out — the partially-built design is discarded.The collected problems cover, by
kind:"unknown-class"/"unknown-property"— the shipped catalogue does not know the item (a snapshot from a newer firmware than this SDK’s schema baseline). Opt into a best-effort import withto_design(snap, on_unknown="raw"): the items are carried verbatim on the wire-attribute channel instead of raising."redacted-value"— the snapshot holds theREDACTEDsentinel where a curated secret was elided at capture time; a design pushing the sentinel literally would be wrong. Opt into dropping those values withto_design(snap, redacted="skip")."invalid-value"— a naming value the typed model refuses: the object’s identity cannot be built, and identity has no wire-channel escape. Non-naming values the model refuses never raise — they drop when they are the property’s schema default (an unset marker) and ride the wire channel verbatim otherwise."structure"— an RN that does not match its class’s RN format, or a repeated DN. A containment the SDK’s tables lack is not a problem: the fabric is the authority on its own edges, so the snapshot’s parent/child placement is trusted as-is.
- Parameters:
problems (list[ImportProblem]) – One
ImportProblemper offending item, sorted by DN.
- problems¶
The collected problems, as passed.
- exception niwaki.exceptions.StagedPushError(report, failures, not_run)[source]¶
Bases:
DesignErrorA
push(mode="staged")partially succeeded.Carries the partial
PushReport(the DNs actually written, in the design’s deterministic order — not the order the controller answered in) and the failures as plain(dn, exception)pairs — no engine internals leak into the public surface.- Parameters:
report (PushReport) – Partial push report —
report.dnsare the DNs written, including every independent branch that succeeded around a failure.failures (list[tuple[str, Exception]]) –
(dn, exception)for every operation that failed.not_run (list[str]) – DNs never attempted because an ancestor object failed — pushing them without their parent would only 404. A failure isolates its own subtree; sibling branches are still written.
Example:
from niwaki.exceptions import StagedPushError try: config.push(aci, mode="staged") except StagedPushError as exc: print(f"written : {exc.report.dns}") print(f"failed : {[dn for dn, _ in exc.failures]}") print(f"skipped : {exc.not_run}")
- exception niwaki.exceptions.StatsClassNotSubscribableError[source]¶
Bases:
SubscriptionErrorA subscription targeted a class the APIC can never push for.
Raised before any network I/O. Stats classes (
isStatin the read catalogue) bypass the APIC’s internal event manager entirely — Cisco’s own documentation states updates are “too frequent and not scalable” to route through it — so a subscription would be silently accepted and never push anything. This is an architectural fact, unlikeisObservable(seeis_observable), which was empirically found not to gate subscribability and is therefore never enforced here.
- exception niwaki.exceptions.SubscribeRejectedError(status_code, apic_message='', *, apic_code=None)[source]¶
Bases:
SubscriptionError,APIErrorThe APIC rejected a
subscription=yesrequest.Multiply inherits
APIErrorfor the familiarstatus_code/apic_messageattributes (precedent:UnknownMakerError(DesignError, AttributeError)), so a caller can inspect the HTTP status while still catching every subscription failure with a singleexcept SubscriptionError.
- exception niwaki.exceptions.SubscriptionError[source]¶
Bases:
NiwakiErrorBase class for every object-subscription failure.
- exception niwaki.exceptions.SubscriptionLostError(message, *, reason=SubscriptionLostReason.RECONNECT_EXHAUSTED)[source]¶
Bases:
SubscriptionErrorA subscription could not be recovered.
Raised out of the subscription’s iterator (
__next__/__anext__), terminating it. This is the one truly fatal outcome — everything short of it (a missed refresh, a reconnect that did succeed) is represented as data in the event stream instead, because there is something left to reconcile toward. Seereasonfor which recovery path was exhausted.- reason¶
Which recovery path failed — see
SubscriptionLostReason.
- class niwaki.exceptions.SubscriptionLostReason(*values)[source]¶
Bases:
StrEnumWhich recovery path was exhausted before
SubscriptionLostErrorwas raised.Distinguishes the distinct fatal paths that would otherwise be indistinguishable from the error message alone:
RECONNECT_EXHAUSTED: the shared WebSocket itself could not be reconnected — every tracked subscription on the socket receives this.RESUBSCRIBE_FAILED: the socket reconnected, but the APIC rejected this subscription’s resubscribe — sibling subscriptions on the same socket may still be fine.REFRESH_ESCALATION: two consecutive scheduled refreshes failed, and the recovery resubscribe attempted for this subscription alone also failed — the socket connection itself was never affected.INTERNAL_ERROR: an unexpected error escaped a background reader/refresh loop. The last-line-of-defense guard fails every tracked subscription rather than let consumers block forever on a queue nothing feeds; the socket is left reopenable by a later subscribe.
- exception niwaki.exceptions.TLSError[source]¶
Bases:
TransportErrorTLS/SSL error when connecting to the APIC.
Common causes: - Self-signed certificate with
verify_ssl=True(default). - Expired certificate or incorrect domain name. - Incomplete CA chain.Quick fix (not recommended in production):
verify_ssl=False.
- exception niwaki.exceptions.TimeoutError[source]¶
Bases:
TransportErrorA request to the APIC exceeded the configured timeout.
Wraps
httpx.TimeoutException(covers connect timeout, read timeout, write timeout, and pool timeout).Note
This name intentionally shadows the Python builtin
TimeoutError.
- exception niwaki.exceptions.TokenRefreshError[source]¶
Bases:
AuthErrorToken refresh via
/api/aaaRefresh.jsonfailed.The session will automatically attempt a full re-login as fallback. This exception should not reach the caller in the normal case; it surfaces only if the fallback itself is disabled or fails.
- exception niwaki.exceptions.TransportError[source]¶
Bases:
NiwakiErrorBase class for network/transport layer errors.
All subclasses wrap corresponding
httpxexceptions.
- exception niwaki.exceptions.UnauthorizedError(status_code, apic_message='', *, apic_code=None)[source]¶
Bases:
APIErrorThe APIC returned HTTP 401 — the session token is invalid or expired server-side.
Raised only if the 401 persists after automatic re-authentication, indicating that the credentials themselves were revoked or that the resource is not accessible to this user.
- exception niwaki.exceptions.UnknownClassError[source]¶
Bases:
NiwakiError,KeyErrorA class name the read catalogue does not know.
Raised by
niwaki.catalog.describe(),niwaki.catalog.class_meta(),niwaki.catalog.prop_meta()andniwaki.catalog.dn_formats()when the wire class name (or property) does not exist in the shipped catalogue — usually a typo, or a class minted by a newer APIC firmware than the one this build tracks.Also a
KeyError: callers that guarded these lookups withexcept KeyErrorbefore this class existed keep working unchanged — the same dual-inheritance precedent asUnknownMakerError.
- exception niwaki.exceptions.UnknownMakerError[source]¶
Bases:
DesignError,AttributeErrorA maker name resolved at no level of the cursor’s ancestor path.
Also an
AttributeErrorso thathasattrand attribute protocols keep working on cursors.
- exception niwaki.exceptions.UnresolvedReferenceError[source]¶
Bases:
DesignErrorA
bind/provide/consumetarget is not declared in the design.Raised during closed-world validation at push time. The message includes the declared instances of the target class and a did-you-mean suggestion.