Reading — queries and filters¶
Niwaki query builder — full APIC REST query model as a fluent API.
This module provides two things:
Filter expression DSL — operator functions that produce typed
FilterExprobjects serialisable to APIC filter strings.Query builders —
Query(sync) andAsyncQuery(async) that accumulate query parameters lazily and execute on demand.
Quick-start¶
Reading objects without knowing class names (via jargon navigation):
with Niwaki(...) as aci:
# All BDs in tenant "prod" — no class import needed
bds = aci.root.tenant("prod").bd().fetch()
# Filter with keyword shorthand
bds = aci.root.tenant("prod").bd().where(arpFlood=True).fetch()
# Subtree included in response
bds = aci.query(fvBD).include(fvSubnet).fetch()
Filter DSL:
from niwaki.query import eq, ne, wcard, and_, or_, not_
# Keyword shorthand (simplest — no import needed)
query.where(name="web")
# Explicit operators
query.where(wcard("name", "prod-*"))
query.where(and_(wcard("name", "prod-*"), eq("arpFlood", True)))
query.where(~eq("name", "infra")) # NOT
Scoping:
# Global class query (entire fabric)
aci.query(fvBD).fetch()
# Scoped to a DN
aci.query(fvBD).under("uni/tn-prod").fetch()
# Via jargon navigation (DN inferred automatically)
aci.root.tenant("prod").bd().fetch()
Unregistered classes (read-only, operational, stats…):
# 15 000+ APIC classes accessible by string name
nodes = aci.query("topSystem").naming_only().fetch()
# Uniform read access, generated class or not: .dn and obj["wireName"]
for node in nodes:
print(node.dn, node["role"])
Response enrichment:
aci.root.tenant("prod").bd().with_faults().fetch() # faults embedded on each BD
aci.query(fvBD).with_health().fetch() # include health
aci.query(fvBD).include(fvSubnet).fetch() # embed children
Execution methods:
query.fetch() # list[T] — all pages, in memory
query.first() # T | None — page-size=1 optimisation
query.count() # int — one lightweight request, no objects transferred
query.stream() # Iterator[T] / AsyncIterator[T] — page-by-page
Public API¶
Builders¶
Accumulators are synchronous on both builders; only the executors differ
(fetch/first/count/stream — awaitable on AsyncQuery).
- class niwaki.query.Query(cls, session, *, scope_dn=None)[source]¶
Bases:
_QueryBase[_T]Fluent ACI query builder — synchronous variant.
Build queries by chaining scope, filter, and enrichment methods, then execute with
fetch(),first(),count(), orstream().Every accumulator method returns a new
Queryinstance so partial queries can be safely stored and reused.Created by
query()andquery(), or by jargon navigation without a name argument (e.g.aci.root.tenant("prod").bd()).- Parameters:
cls (type[_T] | str) – ACI class type or plain string class name.
session (ApicSession) – Authenticated
ApicSession.scope_dn (str | None) – Optional DN to scope the query.
Example:
with Niwaki("https://apic.example.com", "admin", "secret") as aci: # All BDs in tenant "prod" bds = aci.root.tenant("prod").bd().fetch() # First BD matching a name pattern bd = aci.query(fvBD).where(name="web").first() # Count BDs with flood enabled n = aci.query(fvBD).where(arpFlood=True).count() # Stream a very large result set for bd in aci.query(fvBD).stream(): process(bd)
- fetch()[source]¶
Execute the query and return all matching objects.
Transparently paginates through all APIC pages. For very large result sets (tens of thousands of objects) consider
stream()to process objects page-by-page without holding everything in memory at once. When the query is limited by a[:n]slice, only that many objects are fetched.- Returns:
List of typed
ManagedObjectinstances. Empty list when no objects match.- Raises:
AuthError – Session not authenticated.
ForbiddenError – Insufficient APIC privileges.
ServerError – APIC server-side error.
ConnectionError – Network error after all retry attempts.
- Return type:
list[_T]
Example:
bds = aci.root.tenant("prod").bd().fetch()
- first()[source]¶
Execute the query and return the first matching object, or
None.More efficient than
fetch()[0]— internally requests only a single object (page=0&page-size=1).- Returns:
First matching instance, or
Nonewhen the result set is empty.- Raises:
AuthError – Session not authenticated.
ForbiddenError – Insufficient privileges.
ServerError – APIC server-side error.
- Return type:
_T | None
Example:
bd = aci.root.tenant("prod").bd().where(name="web").first() if bd is None: print("not found")
- one()[source]¶
Execute the query and return the single matching object.
For queries that must resolve to exactly one object. Fetches at most two objects (
page=0&page-size=2) so it can tell “none”, “one” and “more than one” apart in a single request.- Returns:
The one matching instance.
- Raises:
NoResultError – No object matched — use
first()when no match is acceptable.MultipleResultsError – More than one object matched — narrow the query or use
first()/fetch().
- Return type:
_T
Example:
bd = aci.query(fvBD).where(name="web").one()
- exists()[source]¶
Return whether any object matches — a single lightweight request.
- Returns:
Truewhen at least one object matches,Falseotherwise.- Return type:
Example:
if aci.query(fvBD).where(name="web").exists(): ...
- count()[source]¶
Return the count of matching objects without fetching them.
Issues a single one-object page and reads the APIC
totalCount— this composes with any query and works on every APIC version (6.0 rejects thecount-onlyargument).- Returns:
Integer count of objects matching the current query.
- Raises:
AuthError – Session not authenticated.
ForbiddenError – Insufficient privileges.
ServerError – APIC server-side error.
- Return type:
Example:
n = aci.query(fvBD).under("uni/tn-prod").count() print(f"{n} BDs in tenant prod")
- stream()[source]¶
Yield objects one page at a time — O(page_size) memory footprint.
Preferred over
fetch()for large result sets where loading everything into a list would consume excessive memory.- Yields:
Typed
ManagedObjectinstances in APIC-returned order.- Raises:
AuthError – Session not authenticated.
ForbiddenError – Insufficient privileges.
ServerError – APIC server-side error.
Example:
for bd in aci.query(fvBD).stream(): process(bd)
- subscribe(*, refresh_timeout=None, max_pending=10000)[source]¶
Subscribe to push notifications for this query.
A subscription is a query plus
subscription=yesat the wire level, so every accumulator that maps onto the same GET mechanism —where(),under(),self_only()/children(),naming_only()/config_only()— carries over unchanged. State with no meaning on an open-ended push stream (order_by(), a slice limit, subtree enrichment,also()) is rejected up front, before any network I/O — see_reject_unstreamable()for exactly why each one is rejected.page_size()is silently ignored (it never reaches a request parameter here — the subscribe response is a single page).The APIC multiplexes every subscription for a session over one WebSocket, opened lazily on the first call to this method on the session; refresh and reconnect-and-resubscribe run automatically in the background, so nothing here needs a caller-driven loop.
- Parameters:
refresh_timeout (int | None) – Override the APIC’s default 60 s subscription timeout. The subscription refreshes itself automatically on a schedule derived from this value regardless.
max_pending (int) – Bound on buffered, not-yet-consumed events for this subscription. Past it, incoming events are dropped (other subscriptions are never affected) and the stream receives one
SubscriptionOverflowmarker per overload episode — reconcile with a fresh read, exactly like a gap.
- Returns:
A
Subscription—.initialfor the synchronous snapshot, then iterate for live push events.- Raises:
ValueError – The query carries state with no meaning on a live stream (
order_by(), a slice limit, subtree enrichment,also()).StatsClassNotSubscribableError – The queried class is a statistics class — the APIC never pushes for it.
SubscribeRejectedError – The APIC rejected the subscribe request.
- Return type:
Subscription[_T]
Example:
with aci.query(fvBD).under("uni/tn-prod").subscribe() as sub: for event in sub: print(event.kind, event.dn)
- execute_raw(path, params)[source]¶
Run raw APIC query params through the typed, paginated pipeline.
The escape hatch for anything
build()cannot express yet: pass an APIC path and parameter dict (often derived frombuild()and then tweaked) and get back typed, fully-paginated objects — unlike the transport’s rawgethelper, which returns a single unparsed page.- Parameters:
- Returns:
All matching objects across every page, typed via
REGISTRY.- Return type:
To count matching objects, use
count()— it is the only counting idiom this SDK trusts. This method returns objects, so a parameter that makes the APIC answer with a tally instead yields onemoCountenvelope where the caller expected their class.Example:
# Start from build() so scoping and filters are already right, then # set whatever the query grammar does not model yet and run it # through the same typed, paginated path. path, params = aci.query(fvBD).under("uni/tn-prod").build() params["some-apic-parameter"] = "value" objs = aci.query(fvBD).execute_raw(path, params)
- also(*classes)¶
Return additional ACI classes alongside the queried one (scoped only).
Adds to
target-subtree-classso a DN-scoped subtree/children query returns several types at once (e.g. BDs and their subnets) in a single request. Results are polymorphic — each object deserialises to its own type. Only affects a scoped query (under()); it is ignored on a global class query, which addresses a single class by URL.- Parameters:
*classes (type[ManagedObject] | str) – Extra ACI class type(s) or string class name(s) to return.
- Returns:
New query with the extra target classes registered.
- Return type:
Example:
# Every BD and every subnet under the tenant, in one request objs = aci.query(fvBD).under("uni/tn-prod").also(fvSubnet).fetch()
- build()¶
Translate accumulated builder state into an APIC path and param dict.
Returns the APIC-relative path and a flat query-string dict that together represent this query. Useful for inspection, debugging, and testing without executing an HTTP request.
- Returns:
(path, params)tuple where path is relative to the APIC base URL (e.g."/api/class/fvBD.json") and params maps APIC parameter names to string values.- Return type:
Example:
path, params = aci.query(fvBD).where(arpFlood=True).build() # path → "/api/class/fvBD.json" # params → {"query-target-filter": 'eq(fvBD.arpFlood,"yes")'}
- children()¶
Limit to direct children of the scope DN (one level deep).
Sets
query-target=children. Only meaningful when a scope DN is set viaunder()or jargon navigation.- Returns:
New query with
query-target=children.- Return type:
- config_only()¶
Return only configurable properties, omitting read-only APIC metadata.
Sets
rsp-prop-include=config-only. Reduces payload size for write-oriented inventory queries.- Returns:
New query with
rsp-prop-include=config-only.- Return type:
- include(*classes)¶
Include children of the given ACI class(es) in each response object.
Sets
rsp-subtree=childrenandrsp-subtree-classon the APIC request. The matching child objects are accessible via the.childrenattribute of each returned instance.- Parameters:
*classes (type[ManagedObject] | str) – ACI class type(s) or string class name(s) to include.
- Returns:
New query with child inclusion set.
- Return type:
Example:
# Fetch BDs with their subnets embedded bds = aci.query(fvBD).include(fvSubnet).fetch() for bd in bds: for subnet in bd.children: print(subnet.model_extra.get("ip"))
- include_subtree(*kinds)¶
Embed one or more response-subtree facets (
rsp-subtree-include).The typed, exhaustive entry point: every facet the APIC offers is a member of
SubtreeInclude(faults, health, stats, relations, count, the audit/event/fault/health record streams, tasks, …). Thewith_*methods are ergonomic shortcuts for the common ones.- Parameters:
*kinds (SubtreeInclude) – One or more
SubtreeIncludefacets.- Returns:
New query with the facets added.
- Return type:
Example:
from niwaki.query import SubtreeInclude aci.query(fvBD).include_subtree( SubtreeInclude.FAULT_RECORDS, SubtreeInclude.AUDIT_LOGS ).fetch()
- naming_only()¶
Return only naming properties (name/DN).
Sets
rsp-prop-include=naming-only. Ideal for large-scale inventory queries where only identifiers are needed and payload size matters.- Returns:
New query with
rsp-prop-include=naming-only.- Return type:
- only_faulted()¶
Restrict results to objects that carry the embedded subtree items.
Adds the
requiredmodifier torsp-subtree-include: only top-level objects that actually have the requested facet — typically the faults embedded bywith_faults()— are returned.- Returns:
New query with
requiredset.- Return type:
- order_by(prop, *, desc=False)¶
Sort results by a property (chain for multi-key ordering).
The property name is auto-prefixed with the ACI class name when it does not already contain a dot. Calling
order_by()more than once appends additional sort keys, applied left to right.- Parameters:
- Returns:
New query with the ordering key appended.
- Return type:
Example:
aci.query(fvBD).order_by("name").fetch() aci.query(fvBD).order_by("name", desc=True).fetch() # Multi-key: most severe first, then by code aci.query("faultInst").order_by("severity", desc=True).order_by("code").fetch()
- page_size(n)¶
Override the page size for auto-pagination.
The default page size is 500 objects per page. Decrease for slow connections or very large objects; increase if the APIC supports it.
- Parameters:
n (int) – Objects per page. Must be greater than zero.
- Returns:
New query with the page size set.
- Raises:
ValueError – n is zero or negative.
- Return type:
- self_only()¶
Return only the scoped object itself (
query-target=self).Only meaningful with a scope DN (
under()or jargon navigation): the MO at that DN is returned with no descendants. A no-op on a global class query, which already addresses a single class.- Returns:
New query with
query-target=self.- Return type:
- subtree()¶
Include all descendants of the scope DN (unlimited depth).
Sets
query-target=subtree. This is the default when a scope DN is set — call this explicitly only after a precedingchildren()call.- Returns:
New query with
query-target=subtree.- Return type:
- subtree_full()¶
Embed the entire subtree of each object (
rsp-subtree=full).Unlike
include(), which embeds only the named direct children, this returns every descendant at unlimited depth, reachable through the.childrentree of each result.- Returns:
New query with
rsp-subtree=full.- Return type:
- subtree_where(*exprs, **kwargs)¶
Filter the included subtree children by an attribute expression.
Sets
rsp-subtree-filteron the APIC request. This is a response-level filter that restricts which child objects are embedded in the response — unlikewhere()which filters the top-level objects. Requiresinclude()to be called first (orwith_faults()/with_health()) so thatrsp-subtreeis not"no".Accepts the same expression DSL as
where(): explicitFilterExprobjects and keyword shortcutsprop=valueauto-prefixed with the queried class name.- Parameters:
*exprs (FilterExpr) – One or more
FilterExprobjects.**kwargs (FilterValue) – Equality shortcuts applied to the query’s class, e.g.
ip="10.0.0.1/24"becomeseq(fvSubnet.ip,"10.0.0.1/24").
- Returns:
New query with
rsp-subtree-filterset.- Raises:
ValueError – Called with no arguments.
- Return type:
Example:
# Fetch BDs, but embed only subnets in the 10.0.0.0/8 scope from niwaki.query import wcard bds = ( aci.query(fvBD) .include(fvSubnet) .subtree_where(wcard("fvSubnet.ip", "10.*")) .fetch() ) # Keyword form bds = aci.query(fvBD).include(fvSubnet).subtree_where(ip="10.0.0.1/24").fetch()
- under(dn)¶
Scope the query to the subtree rooted at dn.
Converts a global class query into a DN-scoped subtree query. When called on an already-scoped query the scope DN is replaced.
- Parameters:
dn (str) – APIC Distinguished Name (e.g.
"uni/tn-prod").- Returns:
New query with
scope_dnset.- Return type:
Example:
# Global → scoped aci.query(fvBD).under("uni/tn-prod").fetch() # GET /api/mo/uni/tn-prod.json?query-target=subtree&target-subtree-class=fvBD
- where(*exprs, **kwargs)¶
Add a filter to the query.
Accepts explicit
FilterExprobjects (built witheq(),wcard(), …) and a keyword-argument shorthand where eachprop=valuepair becomes an equality check auto-prefixed with the queried class name.Multiple arguments — whether positional or keyword — are combined with a logical AND. Calling
.where()multiple times chains the filters with AND.The keyword form is the quickest path for simple equality checks: no import of operator functions required, and no class name to remember — the property is qualified with the queried class automatically. Explicit
FilterExprobjects are passed through verbatim: qualify their properties ("fvBD.name") or build them withcls_name=.- Parameters:
*exprs (FilterExpr) – One or more
FilterExprobjects, with class-qualified property names.**kwargs (FilterValue) – Equality shortcuts —
name="web"becomeseq(ClassName.name,"web")automatically.
- Returns:
New query with the filter applied.
- Return type:
Example:
from niwaki.query import wcard, and_ # Keyword shorthand — simplest, auto-qualified aci.root.tenant("prod").bd().where(name="web").fetch() # Explicit expression — qualified property aci.query(fvBD).where(wcard("fvBD.name", "prod-*")).fetch() # Chained (implicit AND) aci.query(fvBD).where(wcard("fvBD.name", "prod-*")).where(arpFlood=True).fetch() # Combined in one call aci.query(fvBD).where( wcard("fvBD.name", "prod-*"), arpFlood=True, ).fetch()
- with_faults()¶
Embed fault objects in the subtree response (
rsp-subtree-include=faults).By default this embeds faults on every returned object, faulted or not. Chain
only_faulted()to restrict the result to objects that actually carry a fault.- Returns:
New query with faults embedded.
- Return type:
Example:
# Every BD, faults embedded where present aci.root.tenant("prod").bd().with_faults().fetch() # Only the faulted BDs aci.root.tenant("prod").bd().with_faults().only_faulted().fetch()
- with_health()¶
Embed health score data in the subtree response (
rsp-subtree-include=health).- Returns:
New query with health data embedded.
- Return type:
- with_relations()¶
Embed relation objects (Rs/Rt) —
rsp-subtree-include=relations.- Returns:
New query with relations embedded.
- Return type:
- class niwaki.query.AsyncQuery(cls, session, *, scope_dn=None)[source]¶
Bases:
_QueryBase[_T]Fluent ACI query builder — asynchronous variant.
Mirrors
Querywithasync defexecution methods. Every accumulator method is synchronous and returns a newAsyncQuery(same immutable-builder pattern as the sync variant).Created by
query()andquery(), or by jargon navigation without a name argument on anAsyncNiwakiNode.- Parameters:
cls (type[_T] | str) – ACI class type or plain string class name.
session (AsyncApicSession) – Authenticated
AsyncApicSession.scope_dn (str | None) – Optional DN to scope the query.
Example:
async with AsyncNiwaki("https://apic.example.com", "admin", "secret") as aci: # All BDs in tenant "prod" bds = await aci.root.tenant("prod").bd().fetch() # First matching bd = await aci.query(fvBD).where(name="web").first() # Count n = await aci.query(fvBD).under("uni/tn-prod").count() # Concurrent reads tenants, bds = await aci.gather( aci.query(fvTenant).fetch(), aci.root.tenant("prod").bd().fetch(), )
- async fetch()[source]¶
Execute the query and return all matching objects.
Transparently paginates through all APIC pages. For very large result sets consider
stream()to process objects incrementally.- Returns:
List of typed
ManagedObjectinstances. Empty list when no objects match.- Raises:
AuthError – Session not authenticated.
ForbiddenError – Insufficient APIC privileges.
ServerError – APIC server-side error.
ConnectionError – Network error after all retry attempts.
- Return type:
list[_T]
Example:
bds = await aci.root.tenant("prod").bd().fetch()
- async first()[source]¶
Execute the query and return the first matching object, or
None.Requests only a single object (
page=0&page-size=1) — more efficient than(await fetch())[0]for large result sets.- Returns:
First matching instance, or
Nonewhen the result set is empty.- Raises:
AuthError – Session not authenticated.
ForbiddenError – Insufficient privileges.
ServerError – APIC server-side error.
- Return type:
_T | None
Example:
bd = await aci.root.tenant("prod").bd().where(name="web").first()
- async one()[source]¶
Execute the query and return the single matching object.
For queries that must resolve to exactly one object. Fetches at most two objects (
page=0&page-size=2) so it can tell “none”, “one” and “more than one” apart in a single request.- Returns:
The one matching instance.
- Raises:
NoResultError – No object matched — use
first()when no match is acceptable.MultipleResultsError – More than one object matched — narrow the query or use
first()/fetch().
- Return type:
_T
Example:
bd = await aci.query(fvBD).where(name="web").one()
- async exists()[source]¶
Return whether any object matches — a single lightweight request.
- Returns:
Truewhen at least one object matches,Falseotherwise.- Return type:
Example:
if await aci.query(fvBD).where(name="web").exists(): ...
- async count()[source]¶
Return the count of matching objects without fetching them.
Issues a single one-object page and reads the APIC
totalCount.- Returns:
Integer count of objects matching the current query.
- Raises:
AuthError – Session not authenticated.
ForbiddenError – Insufficient privileges.
ServerError – APIC server-side error.
- Return type:
Example:
n = await aci.query(fvBD).under("uni/tn-prod").count()
- async stream()[source]¶
Yield objects one page at a time — O(page_size) memory footprint.
Preferred over
fetch()for large result sets. Eachyieldreturns one page of parsed objects.- Yields:
Typed
ManagedObjectinstances in APIC-returned order.- Raises:
AuthError – Session not authenticated.
ForbiddenError – Insufficient privileges.
ServerError – APIC server-side error.
Example:
async for bd in aci.query(fvBD).stream(): await process(bd)
- async subscribe(*, refresh_timeout=None, max_pending=10000)[source]¶
Subscribe to push notifications for this query.
Mirrors
subscribe()— a subscription is a query plussubscription=yesat the wire level, so every accumulator that maps onto the same GET mechanism carries over unchanged, and state with no meaning on an open-ended push stream is rejected up front, before any network I/O — see_reject_unstreamable(). Because that rejection is synchronous, it surfaces the moment this coroutine is awaited, still before any network call.The APIC multiplexes every subscription for a session over one WebSocket, opened lazily on the first call to this method on the session; refresh and reconnect-and-resubscribe run automatically in the background, so nothing here needs a caller-driven loop.
- Parameters:
refresh_timeout (int | None) – Override the APIC’s default 60 s subscription timeout. The subscription refreshes itself automatically on a schedule derived from this value regardless.
max_pending (int) – Bound on buffered, not-yet-consumed events for this subscription. Past it, incoming events are dropped (other subscriptions are never affected) and the stream receives one
SubscriptionOverflowmarker per overload episode — reconcile with a fresh read, exactly like a gap.
- Returns:
An
AsyncSubscription—.initialfor the synchronous snapshot, then async-iterate for live push events.- Raises:
ValueError – The query carries state with no meaning on a live stream (
order_by(), a slice limit, subtree enrichment,also()).StatsClassNotSubscribableError – The queried class is a statistics class — the APIC never pushes for it.
SubscribeRejectedError – The APIC rejected the subscribe request.
- Return type:
Example:
async with aci.query(fvBD).under("uni/tn-prod").subscribe() as sub: async for event in sub: print(event.kind, event.dn)
- async execute_raw(path, params)[source]¶
Run raw APIC query params through the typed, paginated pipeline.
The escape hatch for anything
build()cannot express yet: pass an APIC path and parameter dict (often derived frombuild()and then tweaked) and get back typed, fully-paginated objects — unlike the transport’s rawgethelper, which returns a single unparsed page.
- also(*classes)¶
Return additional ACI classes alongside the queried one (scoped only).
Adds to
target-subtree-classso a DN-scoped subtree/children query returns several types at once (e.g. BDs and their subnets) in a single request. Results are polymorphic — each object deserialises to its own type. Only affects a scoped query (under()); it is ignored on a global class query, which addresses a single class by URL.- Parameters:
*classes (type[ManagedObject] | str) – Extra ACI class type(s) or string class name(s) to return.
- Returns:
New query with the extra target classes registered.
- Return type:
Example:
# Every BD and every subnet under the tenant, in one request objs = aci.query(fvBD).under("uni/tn-prod").also(fvSubnet).fetch()
- build()¶
Translate accumulated builder state into an APIC path and param dict.
Returns the APIC-relative path and a flat query-string dict that together represent this query. Useful for inspection, debugging, and testing without executing an HTTP request.
- Returns:
(path, params)tuple where path is relative to the APIC base URL (e.g."/api/class/fvBD.json") and params maps APIC parameter names to string values.- Return type:
Example:
path, params = aci.query(fvBD).where(arpFlood=True).build() # path → "/api/class/fvBD.json" # params → {"query-target-filter": 'eq(fvBD.arpFlood,"yes")'}
- children()¶
Limit to direct children of the scope DN (one level deep).
Sets
query-target=children. Only meaningful when a scope DN is set viaunder()or jargon navigation.- Returns:
New query with
query-target=children.- Return type:
- config_only()¶
Return only configurable properties, omitting read-only APIC metadata.
Sets
rsp-prop-include=config-only. Reduces payload size for write-oriented inventory queries.- Returns:
New query with
rsp-prop-include=config-only.- Return type:
- include(*classes)¶
Include children of the given ACI class(es) in each response object.
Sets
rsp-subtree=childrenandrsp-subtree-classon the APIC request. The matching child objects are accessible via the.childrenattribute of each returned instance.- Parameters:
*classes (type[ManagedObject] | str) – ACI class type(s) or string class name(s) to include.
- Returns:
New query with child inclusion set.
- Return type:
Example:
# Fetch BDs with their subnets embedded bds = aci.query(fvBD).include(fvSubnet).fetch() for bd in bds: for subnet in bd.children: print(subnet.model_extra.get("ip"))
- include_subtree(*kinds)¶
Embed one or more response-subtree facets (
rsp-subtree-include).The typed, exhaustive entry point: every facet the APIC offers is a member of
SubtreeInclude(faults, health, stats, relations, count, the audit/event/fault/health record streams, tasks, …). Thewith_*methods are ergonomic shortcuts for the common ones.- Parameters:
*kinds (SubtreeInclude) – One or more
SubtreeIncludefacets.- Returns:
New query with the facets added.
- Return type:
Example:
from niwaki.query import SubtreeInclude aci.query(fvBD).include_subtree( SubtreeInclude.FAULT_RECORDS, SubtreeInclude.AUDIT_LOGS ).fetch()
- naming_only()¶
Return only naming properties (name/DN).
Sets
rsp-prop-include=naming-only. Ideal for large-scale inventory queries where only identifiers are needed and payload size matters.- Returns:
New query with
rsp-prop-include=naming-only.- Return type:
- only_faulted()¶
Restrict results to objects that carry the embedded subtree items.
Adds the
requiredmodifier torsp-subtree-include: only top-level objects that actually have the requested facet — typically the faults embedded bywith_faults()— are returned.- Returns:
New query with
requiredset.- Return type:
- order_by(prop, *, desc=False)¶
Sort results by a property (chain for multi-key ordering).
The property name is auto-prefixed with the ACI class name when it does not already contain a dot. Calling
order_by()more than once appends additional sort keys, applied left to right.- Parameters:
- Returns:
New query with the ordering key appended.
- Return type:
Example:
aci.query(fvBD).order_by("name").fetch() aci.query(fvBD).order_by("name", desc=True).fetch() # Multi-key: most severe first, then by code aci.query("faultInst").order_by("severity", desc=True).order_by("code").fetch()
- page_size(n)¶
Override the page size for auto-pagination.
The default page size is 500 objects per page. Decrease for slow connections or very large objects; increase if the APIC supports it.
- Parameters:
n (int) – Objects per page. Must be greater than zero.
- Returns:
New query with the page size set.
- Raises:
ValueError – n is zero or negative.
- Return type:
- self_only()¶
Return only the scoped object itself (
query-target=self).Only meaningful with a scope DN (
under()or jargon navigation): the MO at that DN is returned with no descendants. A no-op on a global class query, which already addresses a single class.- Returns:
New query with
query-target=self.- Return type:
- subtree()¶
Include all descendants of the scope DN (unlimited depth).
Sets
query-target=subtree. This is the default when a scope DN is set — call this explicitly only after a precedingchildren()call.- Returns:
New query with
query-target=subtree.- Return type:
- subtree_full()¶
Embed the entire subtree of each object (
rsp-subtree=full).Unlike
include(), which embeds only the named direct children, this returns every descendant at unlimited depth, reachable through the.childrentree of each result.- Returns:
New query with
rsp-subtree=full.- Return type:
- subtree_where(*exprs, **kwargs)¶
Filter the included subtree children by an attribute expression.
Sets
rsp-subtree-filteron the APIC request. This is a response-level filter that restricts which child objects are embedded in the response — unlikewhere()which filters the top-level objects. Requiresinclude()to be called first (orwith_faults()/with_health()) so thatrsp-subtreeis not"no".Accepts the same expression DSL as
where(): explicitFilterExprobjects and keyword shortcutsprop=valueauto-prefixed with the queried class name.- Parameters:
*exprs (FilterExpr) – One or more
FilterExprobjects.**kwargs (FilterValue) – Equality shortcuts applied to the query’s class, e.g.
ip="10.0.0.1/24"becomeseq(fvSubnet.ip,"10.0.0.1/24").
- Returns:
New query with
rsp-subtree-filterset.- Raises:
ValueError – Called with no arguments.
- Return type:
Example:
# Fetch BDs, but embed only subnets in the 10.0.0.0/8 scope from niwaki.query import wcard bds = ( aci.query(fvBD) .include(fvSubnet) .subtree_where(wcard("fvSubnet.ip", "10.*")) .fetch() ) # Keyword form bds = aci.query(fvBD).include(fvSubnet).subtree_where(ip="10.0.0.1/24").fetch()
- under(dn)¶
Scope the query to the subtree rooted at dn.
Converts a global class query into a DN-scoped subtree query. When called on an already-scoped query the scope DN is replaced.
- Parameters:
dn (str) – APIC Distinguished Name (e.g.
"uni/tn-prod").- Returns:
New query with
scope_dnset.- Return type:
Example:
# Global → scoped aci.query(fvBD).under("uni/tn-prod").fetch() # GET /api/mo/uni/tn-prod.json?query-target=subtree&target-subtree-class=fvBD
- where(*exprs, **kwargs)¶
Add a filter to the query.
Accepts explicit
FilterExprobjects (built witheq(),wcard(), …) and a keyword-argument shorthand where eachprop=valuepair becomes an equality check auto-prefixed with the queried class name.Multiple arguments — whether positional or keyword — are combined with a logical AND. Calling
.where()multiple times chains the filters with AND.The keyword form is the quickest path for simple equality checks: no import of operator functions required, and no class name to remember — the property is qualified with the queried class automatically. Explicit
FilterExprobjects are passed through verbatim: qualify their properties ("fvBD.name") or build them withcls_name=.- Parameters:
*exprs (FilterExpr) – One or more
FilterExprobjects, with class-qualified property names.**kwargs (FilterValue) – Equality shortcuts —
name="web"becomeseq(ClassName.name,"web")automatically.
- Returns:
New query with the filter applied.
- Return type:
Example:
from niwaki.query import wcard, and_ # Keyword shorthand — simplest, auto-qualified aci.root.tenant("prod").bd().where(name="web").fetch() # Explicit expression — qualified property aci.query(fvBD).where(wcard("fvBD.name", "prod-*")).fetch() # Chained (implicit AND) aci.query(fvBD).where(wcard("fvBD.name", "prod-*")).where(arpFlood=True).fetch() # Combined in one call aci.query(fvBD).where( wcard("fvBD.name", "prod-*"), arpFlood=True, ).fetch()
- with_faults()¶
Embed fault objects in the subtree response (
rsp-subtree-include=faults).By default this embeds faults on every returned object, faulted or not. Chain
only_faulted()to restrict the result to objects that actually carry a fault.- Returns:
New query with faults embedded.
- Return type:
Example:
# Every BD, faults embedded where present aci.root.tenant("prod").bd().with_faults().fetch() # Only the faulted BDs aci.root.tenant("prod").bd().with_faults().only_faulted().fetch()
- with_health()¶
Embed health score data in the subtree response (
rsp-subtree-include=health).- Returns:
New query with health data embedded.
- Return type:
- with_relations()¶
Embed relation objects (Rs/Rt) —
rsp-subtree-include=relations.- Returns:
New query with relations embedded.
- Return type:
Subscribing (push)¶
Query.subscribe()/AsyncQuery.subscribe() open a live push stream instead
of a one-off read — see Subscribing to live changes.
- class niwaki.query.Subscription(raw)[source]¶
Bases:
GenericA live object-subscription — iterate it for typed push events.
Returned by
subscribe(). Refresh and reconnect-and-resubscribe are handled automatically in the background — nothing here needs a caller-driven loop beyond iterating the stream.A subscription is both an iterator and a context manager:
with aci.query(fvBD).under("uni/tn-prod").subscribe() as sub: for bd in sub.initial: print("already there:", bd.dn) for event in sub: print(event.kind, event.dn, event.mo.model_fields_set if event.mo else None)
- initial¶
The subscribe response’s own synchronous snapshot — a single, un-paginated page (not the exhaustive read
fetch()would give you), typed via the normalfrom_apic()path since it is a real read, not a push event.
- property subscription_id: str¶
The current wire
subscriptionId— for observability/debugging only.This changes across a reconnect (delivered to the stream as a
GAPevent); it is not a stable identity to key application state on.
- property info: SubscriptionInfo¶
Current diagnostic snapshot of this subscription.
See
subscribe()’s session-level counterpart for listing every subscription at once; this is the single-item equivalent.- Raises:
SubscriptionError – This subscription has already been closed.
- refresh_now()[source]¶
Force an immediate refresh of this subscription, outside its schedule.
A manual refresh never triggers the automatic recovery escalation on failure — only the scheduled background refresh does — but a success resets the failure counter exactly like a scheduled refresh succeeding would.
- Returns:
The updated
SubscriptionInfo.- Raises:
SubscriptionError – This subscription has already been closed.
- Return type:
- class niwaki.query.AsyncSubscription(raw)[source]¶
Bases:
GenericA live object-subscription — async-iterate it for typed push events.
Returned by
subscribe(). Refresh and reconnect-and-resubscribe are handled automatically in the background — nothing here needs a caller-driven loop beyond iterating the stream.A subscription is both an async iterator and an async context manager:
async with aci.query(fvBD).under("uni/tn-prod").subscribe() as sub: for bd in sub.initial: print("already there:", bd.dn) async for event in sub: print(event.kind, event.dn, event.mo.model_fields_set if event.mo else None)
- initial¶
The subscribe response’s own synchronous snapshot — a single, un-paginated page (not the exhaustive read
fetch()would give you), typed via the normalfrom_apic()path since it is a real read, not a push event.
- property subscription_id: str¶
The current wire
subscriptionId— for observability/debugging only.This changes across a reconnect (delivered to the stream as a
GAPevent); it is not a stable identity to key application state on.
- property info: SubscriptionInfo¶
Current diagnostic snapshot of this subscription.
See
info()(sync twin).- Raises:
SubscriptionError – This subscription has already been closed.
- async refresh_now()[source]¶
Force an immediate refresh of this subscription, outside its schedule.
See
refresh_now()(sync twin).- Returns:
The updated
SubscriptionInfo.- Raises:
SubscriptionError – This subscription has already been closed.
- Return type:
- class niwaki.query.SubscriptionEvent(kind, mo, subscription_ids, raw)[source]¶
Bases:
GenericOne item from a
Subscription’s live stream.- mo¶
The typed object, deserialised via
from_event()— itsmodel_fields_setreports exactly the fields this event’s payload carried (empty for aDELETEDevent, the full set for aCREATEDone).NoneforGAP/REFRESH_FAILED, which describe the subscription itself, not a watched object.- Type:
T | None
- subscription_ids¶
Every wire
subscriptionIdthis push satisfied. Empty forGAP/REFRESH_FAILED, which are per-subscription already (delivered only into the affected subscription’s stream).
- raw¶
The transport-layer item this event was built from — a
SubscriptionGaporSubscriptionRefreshFailedfor those two kinds, holding their own detail (old/new subscription id, timestamps); the originatingRawSubscriptionEventotherwise.- Type:
RawPushItem
- class niwaki.query.EventKind(*values)[source]¶
Bases:
StrEnumWhat a
SubscriptionEventrepresents.CREATED/MODIFIED/DELETEDvalues equal the wirestatusstring a push carries, soEventKind(raw_status)maps directly.GAP/REFRESH_FAILED/OVERFLOWhave no wire equivalent — they are synthesised by the transport layer for conditions of the subscription itself, not the watched object.
- class niwaki.query.SubscriptionInfo(
- local_id,
- subscription_id,
- path,
- params,
- refresh_timeout,
- consecutive_refresh_failures,
- seconds_until_refresh,
- pending,
- dropped,
Bases:
objectA point-in-time diagnostic snapshot of one tracked subscription.
Returned by
list_subscriptions(),refresh_all_subscriptions(), andinfo— purely informational, not something to act on directly. Userefresh_all_subscriptions(),close_all_subscriptions(), orrefresh_now()for that.- local_id¶
The stable local identity — survives a reconnect, unlike
subscription_id. What lets a caller correlate two snapshots taken at different times.- Type:
- subscription_id¶
The current wire
subscriptionId— debug/ observability only, changes across a reconnect or an escalation recovery.- Type:
- consecutive_refresh_failures¶
How many scheduled refreshes have failed in a row, right now. 0 means healthy.
- Type:
- seconds_until_refresh¶
Time remaining until the next scheduled refresh sweep considers this subscription due.
- Type:
- dropped¶
Cumulative events dropped by the backpressure bound (
max_pending) since the subscription was opened. Non-zero means the consumer fell behind and at least oneSubscriptionOverflowmarker was delivered.- Type:
Response-subtree facets¶
include_subtree embeds one or more of these facets into the response
(rsp-subtree-include); with_faults, with_health and with_stats are
shortcuts for the common ones.
- class niwaki.query.SubtreeInclude(*values)[source]¶
Bases:
StrEnumA response-subtree facet an APIC query can embed (
rsp-subtree-include).The exhaustive set the APIC offers. Pass any of these to
include_subtree(); thewith_*builder methods are ergonomic shortcuts for the common ones.COUNTis the odd one out and is kept only for completeness of the set. Unlike its siblings it does not enrich the answer, it replaces it: the APIC returns a singlemoCountenvelope instead of the objects that were asked for. Its tally is also unreliable on a scoped query — measured against a 6.0(9c) fabric, five tenants out of twenty-eight reported zero bridge domains while holding between one and a hundred and ninety-two, and the request succeeds either way. Usecount(), which reads thetotalCountof a one-object page and composes with any query.
Filter expressions¶
Filter expressions compose with the &, | and ~ operators — the
methods below are what those operators call.
- class niwaki.query.FilterExpr(node)[source]¶
Bases:
objectAn APIC filter expression backed by a small render-once AST.
Created by the operator functions (
eq(),ne(),wcard(), …). Combines with&(AND),|(OR), and~(NOT) for ergonomic composition.str(expr)renders the wire filter string.A raw string may still be passed for backward compatibility and as a verbatim escape hatch (identical to
raw()) — it is rendered exactly as given, with no further escaping.- Parameters:
node (_FilterNode | str) – An internal filter AST node, or a raw APIC filter string (e.g.
'eq(fvBD.name,"web")') taken verbatim.
Example:
from niwaki.query import eq, wcard expr = eq("fvBD.name", "web") ~expr # → FilterExpr('not(eq(fvBD.name,"web"))') expr & wcard("fvBD.name", "prod-*") # → FilterExpr('and(...)')
Filter functions¶
- niwaki.query.eq(prop, value, *, cls_name='')[source]¶
Equal:
eq(cls.prop,"value").- Parameters:
prop (str) – Property name. Qualify it yourself (
"fvBD.name") or provide cls_name — an unqualified property is not a valid APIC filter.value (Any) – Comparison value.
boolvalues are coerced to APIC"yes"/"no"; all others via the wire boundary.cls_name (str) – ACI class name prepended to prop when it is not already qualified.
- Returns:
FilterExprrepresenting the equality check.- Return type:
Example:
eq("name", "web", cls_name="fvBD") # → FilterExpr('eq(fvBD.name,"web")')
- niwaki.query.ne(prop, value, *, cls_name='')[source]¶
Not equal:
ne(cls.prop,"value").- Parameters:
- Returns:
- Return type:
- niwaki.query.gt(prop, value, *, cls_name='')[source]¶
Greater than:
gt(cls.prop,"value").- Parameters:
- Returns:
- Return type:
- niwaki.query.ge(prop, value, *, cls_name='')[source]¶
Greater or equal:
ge(cls.prop,"value").- Parameters:
- Returns:
- Return type:
- niwaki.query.lt(prop, value, *, cls_name='')[source]¶
Less than:
lt(cls.prop,"value").- Parameters:
- Returns:
- Return type:
- niwaki.query.le(prop, value, *, cls_name='')[source]¶
Less or equal:
le(cls.prop,"value").- Parameters:
- Returns:
- Return type:
- niwaki.query.bw(prop, start, end, *, cls_name='')[source]¶
Between (inclusive):
bw(cls.prop,"start","end").- Parameters:
- Returns:
- Return type:
- niwaki.query.wcard(prop, pattern, *, cls_name='')[source]¶
Wildcard match:
wcard(cls.prop,"prod-*").The APIC supports
*as a glob wildcard, but it validates the pattern against the property’s own format, which has two consequences (both verified live on 6.0(9c)): a leading*is rejected on most properties (wcard(name,"prod*")works,wcard(name,"*prod*")→ HTTP 301), so prefer a trailing wildcard; and strictly-formatted properties (ip,dn,descr) reject any wildcard value. The pattern is taken as-is — only a literal"is escaped — so wildcards are preserved.- Parameters:
- Returns:
- Return type:
Example:
wcard("name", "prod-*", cls_name="fvBD") # → FilterExpr('wcard(fvBD.name,"prod-*")')
- niwaki.query.and_(*exprs)[source]¶
Logical AND of two or more expressions:
and(e1,e2,...).- Parameters:
*exprs (FilterExpr) – Two or more
FilterExprobjects to combine.- Returns:
FilterExprrepresenting the conjunction.- Raises:
ValueError – Fewer than two expressions provided.
- Return type:
Example:
and_(eq("name", "web"), eq("arpFlood", True)) # → FilterExpr('and(eq(name,"web"),eq(arpFlood,"yes"))')
- niwaki.query.or_(*exprs)[source]¶
Logical OR of two or more expressions:
or(e1,e2,...).- Parameters:
*exprs (FilterExpr) – Two or more
FilterExprobjects to combine.- Returns:
FilterExprrepresenting the disjunction.- Raises:
ValueError – Fewer than two expressions provided.
- Return type:
Example:
or_(wcard("name", "prod-*"), wcard("name", "dev-*")) # → FilterExpr('or(wcard(name,"prod-*"),wcard(name,"dev-*"))')
- niwaki.query.not_(expr)[source]¶
Logical NOT:
not(expr).- Parameters:
expr (FilterExpr) –
FilterExprto negate.- Returns:
FilterExprrepresenting the negation.- Return type:
Example:
not_(eq("name", "infra")) # → FilterExpr('not(eq(name,"infra"))')
- niwaki.query.anybit(prop, flags, *, cls_name='')[source]¶
Any-bit bitmask test:
anybit(cls.prop,"syn,ack").True when at least one of the given flags is set in the property’s bitmask. This is the read counterpart of a
Flagsfield:eqmatches the whole mask exactly,anybitmatches on a single bit regardless of the others.- Parameters:
- Returns:
- Return type:
Example:
anybit("vzEntry.tcpRules", {"syn", "ack"}) # → FilterExpr('anybit(vzEntry.tcpRules,"ack,syn")')
- niwaki.query.allbit(prop, flags, *, cls_name='')[source]¶
All-bit bitmask test — every given flag must be set.
The APIC 6.0 filter grammar has no
allbitoperator (verified live:allbit(...)→ HTTP 301 “no such filter type”), so this compiles to a conjunction ofanybit()tests —and(anybit(prop,a),anybit(prop,b))— which is exactly “bit a set AND bit b set”.- Parameters:
- Returns:
- Raises:
ValueError – No flags were given.
- Return type:
- niwaki.query.xor(*exprs)[source]¶
Logical XOR of two or more expressions:
xor(e1,e2,...).- Parameters:
*exprs (FilterExpr) – Two or more
FilterExprobjects to combine.- Returns:
FilterExprrepresenting the exclusive-or.- Raises:
ValueError – Fewer than two expressions provided.
- Return type:
- niwaki.query.raw(text)[source]¶
Wrap a verbatim APIC filter string — the universal escape hatch.
Every operator the SDK does not model (
true,false,pholder,passive, or any future addition) is reachable this way: the string is embedded exactly as given, with no coercion or escaping, and composes with&/|/~like any other expression.- Parameters:
text (str) – A raw APIC filter expression (e.g.
'true()').- Returns:
FilterExprwrapping text verbatim.- Return type:
Value wrappers¶
Smart values for where(...) — a list means membership, a * means wildcard, a
set stays bitmask equality; these wrappers make the intent explicit.
- niwaki.query.any_of(*values)[source]¶
Match a property against any of several values — a membership OR.
where(code=any_of("F0467", "F1394"))is the explicit form ofwhere(code=["F0467", "F1394"]); both compile toor(eq(...),eq(...))(the APIC filter grammar has noin).- Parameters:
*values (_FilterScalar) – One or more values to match.
- Returns:
A tag consumed by
where().- Raises:
ValueError – No values were given.
- Return type:
_AnyOf
- niwaki.query.like(pattern)[source]¶
Match a property against a wildcard pattern — the explicit form of a glob.
where(name=like("prod-*"))is the explicit form ofwhere(name="prod-*"); use it to force a wildcard where the value would otherwise read as a literal.
- niwaki.query.between(start, end)[source]¶
Match a property within an inclusive range —
bw(prop,"start","end").where(pri=between(1, 5))compiles tobw(cls.pri,"1","5").- Parameters:
start (_FilterScalar) – Lower bound (inclusive).
end (_FilterScalar) – Upper bound (inclusive).
- Returns:
A tag consumed by
where().- Return type:
_Range