When you cannot connect¶
Problem — the client raises before you get to do anything, or a long-running
job dies mid-flight with an auth error. This is the diagnostic ladder, from
“cannot reach the APIC” to “the session expired”, one exception at a time. Every
rung is a subclass of NiwakiError, so one except
catches the lot while you branch on the type (Errors & recovery).
Rung 1 — ConnectionError: nothing answered¶
The host never responded: wrong URL, DNS, a proxy in the way, or the APIC management address unreachable from where the code runs.
Check the scheme and host:
https://apic.example.com, with no trailing API path — the SDK adds it.Can this machine reach the APIC’s OOB/inband address at all? A plain
curl -k https://apic.example.comfrom the same shell settles it.Corporate proxies: httpx honours
HTTPS_PROXY/NO_PROXY; an APIC on a management network usually belongs inNO_PROXY.
Rung 2 — TLSError: answered, but not trusted¶
The socket opened and certificate verification failed. Three cases, three fixes, in this order:
Enterprise CA (most production fabrics): point the client at the CA bundle —
verify_ssl="/etc/ssl/certs/corp-ca.pem"or theSSL_CERT_FILEenvironment variable (Connection & transport).Wrong hostname: the certificate is valid but you connected by IP or a non-SAN alias — connect by the name in the certificate.
Self-signed lab:
verify_ssl=False, loud and lab-only.
Never reach for verify_ssl=False to work around case 1 or 2.
Rung 3 — LoginError: reached, refused¶
The APIC answered and rejected aaaLogin: wrong credentials, a locked account,
or a remote-auth (TACACS/RADIUS) domain that needs the apic:domain\\username
form.
When credentials come from the environment, check the fallback rules first: a
set-but-empty variable is used as-is (empty password → LoginError), while a
missing variable raises KeyError at construction. Print what the process
actually sees:
import os
seen = {k: bool(os.environ.get(k)) for k in ("APIC_HOST", "APIC_USERNAME", "APIC_PASSWORD")}
print(seen)
Rung 5 — TimeoutError: too slow¶
The retries are already spent by the time this reaches you. Scope the request
before reaching for knobs — a filtered, class-scoped query beats a raised timeout
(Observe and verify a fabric). Then: timeout= (per request, default 30 s) and
RetryConfig for flaky links.
The ladder at a glance¶
Exception |
The question to ask |
The knob |
|---|---|---|
|
Can this machine reach that URL at all? |
URL, DNS, |
|
Who signed the APIC’s certificate? |
|
|
What credentials did the process really use? |
env vars, auth-domain prefix |
|
What killed the token mid-run? |
|
|
Is the request scoped as tightly as it could be? |
query scoping, |
One handler, typed branches¶
A connection that works ends up here — everything after this is When a push fails (push-failure) territory:
from niwaki import Niwaki
from niwaki.exceptions import NiwakiError
try:
with Niwaki("https://apic.example.com", "admin", "secret") as aci:
print(len(aci.query("fvTenant").fetch()), "tenants visible")
except NiwakiError as exc:
print(f"{type(exc).__name__}: {exc}")