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
    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)
├── MultipleResultsError        (query .one() matched several)
├── DesignError
│   ├── UnknownMakerError          (also an AttributeError)
│   ├── DuplicateDeclarationError
│   ├── UnresolvedReferenceError
│   ├── AmbiguousBindError
│   └── StagedPushError
└── SubscriptionError
    ├── StatsClassNotSubscribableError
    ├── SubscribeRejectedError      (also an APIError)
    └── SubscriptionLostError       (.reason: SubscriptionLostReason)
exception niwaki.exceptions.APIError(status_code, apic_message='')[source]

Bases: NiwakiError

The APIC responded with an HTTP error status (4xx or 5xx).

status_code

HTTP status code returned by the APIC.

apic_message

Error text extracted from the APIC payload, if available.

exception niwaki.exceptions.AmbiguousBindError[source]

Bases: DesignError

No unambiguous Rs class exists for a bind edge.

Neither REFERENCE_MAP[owner][target] nor the inverse REFERENCE_MAP[target][owner] resolves to a relationship class. Use .mo(RsClass, ...) to create the relationship explicitly.

exception niwaki.exceptions.AuthError[source]

Bases: NiwakiError

Base class for APIC authentication errors.

Subclasses cover login failure, token refresh failure, and full session expiry.

exception niwaki.exceptions.ConnectionError[source]

Bases: TransportError

The APIC host is unreachable (DNS failure, TCP refused, interface down, etc.).

Wraps httpx.ConnectError in non-TLS cases.

Note

This name intentionally shadows the Python builtin ConnectionError. Inside the SDK, use niwaki.exceptions.ConnectionError.

exception niwaki.exceptions.DeserializationError[source]

Bases: NiwakiError

The 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: NiwakiError

Base class for all design-DSL errors.

exception niwaki.exceptions.DuplicateDeclarationError[source]

Bases: DesignError

The same object (class + naming) was declared twice in a design.

exception niwaki.exceptions.ForbiddenError(status_code, apic_message='')[source]

Bases: APIError

The 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: AuthError

The APIC rejected the credentials during login.

Raised when POST /api/aaaLogin.json returns 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.MultipleResultsError[source]

Bases: NiwakiError

A 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 use first() / fetch() when several matches are expected.

exception niwaki.exceptions.NiwakiError[source]

Bases: Exception

Base class for all niwaki SDK errors.

exception niwaki.exceptions.NoResultError[source]

Bases: NiwakiError

A query that required exactly one object matched none.

Raised by one() / one() when the result set is empty. Use first() when no match is an acceptable outcome.

exception niwaki.exceptions.NotFoundError(status_code, apic_message='')[source]

Bases: APIError

The 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='')[source]

Bases: APIError

The 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: AuthError

The 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.StagedPushError(report, failures, not_run)[source]

Bases: DesignError

A push(mode="staged") partially succeeded.

Carries the partial PushReport (the DNs actually written, in execution order) and the failures as plain (dn, exception) pairs — no engine internals leak into the public surface.

Parameters:
  • report (PushReport) – Partial push report — report.dns are 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: SubscriptionError

A subscription targeted a class the APIC can never push for.

Raised before any network I/O. Stats classes (isStat in 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, unlike isObservable (see is_observable), which was empirically found not to gate subscribability and is therefore never enforced here.

exception niwaki.exceptions.SubscribeRejectedError(status_code, apic_message='')[source]

Bases: SubscriptionError, APIError

The APIC rejected a subscription=yes request.

Multiply inherits APIError for the familiar status_code/apic_message attributes (precedent: UnknownMakerError(DesignError, AttributeError)), so a caller can inspect the HTTP status while still catching every subscription failure with a single except SubscriptionError.

exception niwaki.exceptions.SubscriptionError[source]

Bases: NiwakiError

Base class for every object-subscription failure.

exception niwaki.exceptions.SubscriptionLostError(message, *, reason=SubscriptionLostReason.RECONNECT_EXHAUSTED)[source]

Bases: SubscriptionError

A 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. See reason for which recovery path was exhausted.

reason

Which recovery path failed — see SubscriptionLostReason.

class niwaki.exceptions.SubscriptionLostReason(*values)[source]

Bases: StrEnum

Which recovery path was exhausted before SubscriptionLostError was raised.

Distinguishes three 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.

exception niwaki.exceptions.TLSError[source]

Bases: TransportError

TLS/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: TransportError

A 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: AuthError

Token refresh via /api/aaaRefresh.json failed.

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

Base class for network/transport layer errors.

All subclasses wrap corresponding httpx exceptions.

exception niwaki.exceptions.UnauthorizedError(status_code, apic_message='')[source]

Bases: APIError

The 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.UnknownMakerError[source]

Bases: DesignError, AttributeError

A maker name resolved at no level of the cursor’s ancestor path.

Also an AttributeError so that hasattr and attribute protocols keep working on cursors.

exception niwaki.exceptions.UnresolvedReferenceError[source]

Bases: DesignError

A bind/provide/consume target 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.