Connection & transport¶
Niwaki (sync) and AsyncNiwaki
(async) own the whole session lifecycle: authentication, token refresh,
retries, pagination. This page covers everything between your code and the
APIC socket.
Constructing a client¶
The context-manager form authenticates on entry and closes the session on exit — it is the right default:
from niwaki import Niwaki
with Niwaki("https://apic.example.com", "admin", "secret") as aci:
tenants = aci.query("fvTenant").fetch()
When a with block does not fit the shape of your program (long-lived
services, interactive sessions), connect() returns an authenticated client
and close() ends it:
aci = Niwaki.connect("https://apic.example.com", "admin", "secret")
tenants = aci.query("fvTenant").fetch()
aci.close()
Credentials from the environment¶
Every constructor argument you omit falls back to an environment variable — the natural fit for CI jobs and containers, where secrets arrive from the runner and never live in code:
import os
os.environ["APIC_HOST"] = "https://apic.example.com"
os.environ["APIC_USERNAME"] = "admin"
os.environ["APIC_PASSWORD"] = "secret"
with Niwaki() as aci: # everything comes from the environment
...
Argument |
Environment variable |
|---|---|
|
|
|
|
|
|
TLS and timeouts¶
Certificate verification is on by default and should stay on for anything that is not a lab. Three situations, three settings:
Publicly trusted CA — the default (verify_ssl=True) verifies against
the system CA store. Nothing to do.
Private or enterprise CA — most production APICs. Point verify_ssl
at your CA bundle; never disable verification to work around a private CA:
with Niwaki(
"https://apic.example.com",
"admin",
"secret",
verify_ssl="/etc/ssl/certs/corp-ca.pem", # PEM bundle, loaded eagerly
) as aci:
...
The bundle is loaded when the session is constructed, so a wrong path fails
immediately. No-code alternative: set
SSL_CERT_FILE=/etc/ssl/certs/corp-ca.pem in the environment — the default
SSL context honours it.
Lab APIC with a self-signed certificate — opt out explicitly; the flag is loud on purpose:
with Niwaki("https://apic.example.com", "admin", "secret", verify_ssl=False) as aci:
... # lab only — never disable verification against production
A failed verification surfaces as TLSError —
the diagnostic ladder is in When you cannot connect.
timeout (seconds, default 30.0) applies per request. Raise it for slow
answers on very large reads; prefer scoping the query first (see
Observing the fabric).
slow_fabric = Niwaki("https://apic.example.com", "admin", "secret", timeout=120.0)
Retries¶
Transient transport failures are retried with exponential backoff and
jitter. The policy is a frozen value object,
RetryConfig, passed at construction:
from niwaki import RetryConfig
fail_fast = RetryConfig(attempts=1) # no retries at all
patient = RetryConfig(attempts=5, wait_max=30.0) # unreliable WAN link
with Niwaki("https://apic.example.com", "admin", "secret", retry=patient) as aci:
...
Field |
Default |
Meaning |
|---|---|---|
|
|
total tries (first call + retries); |
|
|
backoff before the first retry, seconds |
|
|
cap on the exponential backoff, seconds |
|
|
random jitter added to each wait, seconds |
Only transport-level failures are retried. An APIC 4xx answer is not a transient condition — it raises immediately as a typed exception (see Errors & recovery).
Token lifecycle¶
APIC sessions authenticate once (aaaLogin) and then live on a refreshable
token. The client refreshes proactively: when a request finds the token
within refresh_threshold seconds of expiry (default 60), it refreshes
before sending, so a long-running loop never trips over a dead session. A
mid-session 401 triggers one transparent re-login before surfacing an
error.
There is nothing to manage — but two knobs exist:
refresh_threshold— widen it if single requests in your workload can outlive the margin (very large exports over slow links);for the async client, refreshes are serialised internally so concurrent requests never race a re-login.
Pagination¶
Reads are paginated transparently: fetch() returns the complete result
whatever its size, and stream() yields objects as pages arrive — same
query, different executor. Nothing to configure; see Observing the fabric for
when to prefer streaming.
Next steps¶
Errors & recovery — what each transport failure raises
When you cannot connect — the diagnostic ladder