Reading — queries and filters

Niwaki query builder — full APIC REST query model as a fluent API.

This module provides two things:

  1. Filter expression DSL — operator functions that produce typed FilterExpr objects serialisable to APIC filter strings.

  2. Query buildersQuery (sync) and AsyncQuery (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(), or stream().

Every accumulator method returns a new Query instance so partial queries can be safely stored and reused.

Created by query() and query(), 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 ManagedObject instances. Empty list when no objects match.

Raises:
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 None when the result set is empty.

Raises:
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:
Return type:

_T

Example:

bd = aci.query(fvBD).where(name="web").one()
exists()[source]

Return whether any object matches — a single lightweight request.

Returns:

True when at least one object matches, False otherwise.

Return type:

bool

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 the count-only argument).

Returns:

Integer count of objects matching the current query.

Raises:
Return type:

int

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 ManagedObject instances in APIC-returned order.

Raises:

Example:

for bd in aci.query(fvBD).stream():
    process(bd)
subscribe(*, refresh_timeout=None)[source]

Subscribe to push notifications for this query.

A subscription is a query plus subscription=yes at 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.

Returns:

A Subscription.initial for the synchronous snapshot, then iterate for live push events.

Raises:
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 from build() and then tweaked) and get back typed, fully-paginated objects — unlike the transport’s raw get helper, which returns a single unparsed page.

Parameters:
  • path (str) – APIC-relative path (e.g. "/api/class/fvBD.json").

  • params (dict[str, str]) – APIC query-string parameters.

Returns:

All matching objects across every page, typed via REGISTRY.

Return type:

list[ManagedObject]

Example:

path, params = aci.query(fvBD).build()
params["rsp-subtree-include"] = "count"
objs = aci.query(fvBD).execute_raw(path, params)
also(*classes)

Return additional ACI classes alongside the queried one (scoped only).

Adds to target-subtree-class so 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:

Self

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:

tuple[str, dict[str, str]]

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 via under() or jargon navigation.

Returns:

New query with query-target=children.

Return type:

Self

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:

Self

include(*classes)

Include children of the given ACI class(es) in each response object.

Sets rsp-subtree=children and rsp-subtree-class on the APIC request. The matching child objects are accessible via the .children attribute 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:

Self

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, …). The with_* methods are ergonomic shortcuts for the common ones.

Parameters:

*kinds (SubtreeInclude) – One or more SubtreeInclude facets.

Returns:

New query with the facets added.

Return type:

Self

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:

Self

only_faulted()

Restrict results to objects that carry the embedded subtree items.

Adds the required modifier to rsp-subtree-include: only top-level objects that actually have the requested facet — typically the faults embedded by with_faults() — are returned.

Returns:

New query with required set.

Return type:

Self

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:
  • prop (str) – Property name (e.g. "name" → qualified as "fvBD.name|asc").

  • desc (bool) – Sort descending when True. Default: ascending.

Returns:

New query with the ordering key appended.

Return type:

Self

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:

ValueErrorn is zero or negative.

Return type:

Self

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:

Self

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 preceding children() call.

Returns:

New query with query-target=subtree.

Return type:

Self

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 .children tree of each result.

Returns:

New query with rsp-subtree=full.

Return type:

Self

subtree_where(*exprs, **kwargs)

Filter the included subtree children by an attribute expression.

Sets rsp-subtree-filter on the APIC request. This is a response-level filter that restricts which child objects are embedded in the response — unlike where() which filters the top-level objects. Requires include() to be called first (or with_faults() / with_health()) so that rsp-subtree is not "no".

Accepts the same expression DSL as where(): explicit FilterExpr objects and keyword shortcuts prop=value auto-prefixed with the queried class name.

Parameters:
  • *exprs (FilterExpr) – One or more FilterExpr objects.

  • **kwargs (FilterValue) – Equality shortcuts applied to the query’s class, e.g. ip="10.0.0.1/24" becomes eq(fvSubnet.ip,"10.0.0.1/24").

Returns:

New query with rsp-subtree-filter set.

Raises:

ValueError – Called with no arguments.

Return type:

Self

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_dn set.

Return type:

Self

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 FilterExpr objects (built with eq(), wcard(), …) and a keyword-argument shorthand where each prop=value pair 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 FilterExpr objects are passed through verbatim: qualify their properties ("fvBD.name") or build them with cls_name=.

Parameters:
  • *exprs (FilterExpr) – One or more FilterExpr objects, with class-qualified property names.

  • **kwargs (FilterValue) – Equality shortcuts — name="web" becomes eq(ClassName.name,"web") automatically.

Returns:

New query with the filter applied.

Return type:

Self

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:

Self

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:

Self

with_relations()

Embed relation objects (Rs/Rt) — rsp-subtree-include=relations.

Returns:

New query with relations embedded.

Return type:

Self

with_stats()

Embed statistics counters in the subtree response (rsp-subtree-include=stats).

Returns:

New query with stats data embedded.

Return type:

Self

class niwaki.query.AsyncQuery(cls, session, *, scope_dn=None)[source]

Bases: _QueryBase[_T]

Fluent ACI query builder — asynchronous variant.

Mirrors Query with async def execution methods. Every accumulator method is synchronous and returns a new AsyncQuery (same immutable-builder pattern as the sync variant).

Created by query() and query(), or by jargon navigation without a name argument on an AsyncNiwakiNode.

Parameters:

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 ManagedObject instances. Empty list when no objects match.

Raises:
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 None when the result set is empty.

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

True when at least one object matches, False otherwise.

Return type:

bool

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:
Return type:

int

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. Each yield returns one page of parsed objects.

Yields:

Typed ManagedObject instances in APIC-returned order.

Raises:

Example:

async for bd in aci.query(fvBD).stream():
    await process(bd)
async subscribe(*, refresh_timeout=None)[source]

Subscribe to push notifications for this query.

Mirrors subscribe() — a subscription is a query plus subscription=yes at 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.

Returns:

An AsyncSubscription.initial for the synchronous snapshot, then async-iterate for live push events.

Raises:
Return type:

AsyncSubscription[_T]

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 from build() and then tweaked) and get back typed, fully-paginated objects — unlike the transport’s raw get helper, which returns a single unparsed page.

Parameters:
  • path (str) – APIC-relative path (e.g. "/api/class/fvBD.json").

  • params (dict[str, str]) – APIC query-string parameters.

Returns:

All matching objects across every page, typed via REGISTRY.

Return type:

list[ManagedObject]

also(*classes)

Return additional ACI classes alongside the queried one (scoped only).

Adds to target-subtree-class so 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:

Self

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:

tuple[str, dict[str, str]]

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 via under() or jargon navigation.

Returns:

New query with query-target=children.

Return type:

Self

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:

Self

include(*classes)

Include children of the given ACI class(es) in each response object.

Sets rsp-subtree=children and rsp-subtree-class on the APIC request. The matching child objects are accessible via the .children attribute 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:

Self

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, …). The with_* methods are ergonomic shortcuts for the common ones.

Parameters:

*kinds (SubtreeInclude) – One or more SubtreeInclude facets.

Returns:

New query with the facets added.

Return type:

Self

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:

Self

only_faulted()

Restrict results to objects that carry the embedded subtree items.

Adds the required modifier to rsp-subtree-include: only top-level objects that actually have the requested facet — typically the faults embedded by with_faults() — are returned.

Returns:

New query with required set.

Return type:

Self

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:
  • prop (str) – Property name (e.g. "name" → qualified as "fvBD.name|asc").

  • desc (bool) – Sort descending when True. Default: ascending.

Returns:

New query with the ordering key appended.

Return type:

Self

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:

ValueErrorn is zero or negative.

Return type:

Self

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:

Self

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 preceding children() call.

Returns:

New query with query-target=subtree.

Return type:

Self

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 .children tree of each result.

Returns:

New query with rsp-subtree=full.

Return type:

Self

subtree_where(*exprs, **kwargs)

Filter the included subtree children by an attribute expression.

Sets rsp-subtree-filter on the APIC request. This is a response-level filter that restricts which child objects are embedded in the response — unlike where() which filters the top-level objects. Requires include() to be called first (or with_faults() / with_health()) so that rsp-subtree is not "no".

Accepts the same expression DSL as where(): explicit FilterExpr objects and keyword shortcuts prop=value auto-prefixed with the queried class name.

Parameters:
  • *exprs (FilterExpr) – One or more FilterExpr objects.

  • **kwargs (FilterValue) – Equality shortcuts applied to the query’s class, e.g. ip="10.0.0.1/24" becomes eq(fvSubnet.ip,"10.0.0.1/24").

Returns:

New query with rsp-subtree-filter set.

Raises:

ValueError – Called with no arguments.

Return type:

Self

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_dn set.

Return type:

Self

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 FilterExpr objects (built with eq(), wcard(), …) and a keyword-argument shorthand where each prop=value pair 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 FilterExpr objects are passed through verbatim: qualify their properties ("fvBD.name") or build them with cls_name=.

Parameters:
  • *exprs (FilterExpr) – One or more FilterExpr objects, with class-qualified property names.

  • **kwargs (FilterValue) – Equality shortcuts — name="web" becomes eq(ClassName.name,"web") automatically.

Returns:

New query with the filter applied.

Return type:

Self

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:

Self

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:

Self

with_relations()

Embed relation objects (Rs/Rt) — rsp-subtree-include=relations.

Returns:

New query with relations embedded.

Return type:

Self

with_stats()

Embed statistics counters in the subtree response (rsp-subtree-include=stats).

Returns:

New query with stats data embedded.

Return type:

Self

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

A 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 normal from_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 GAP event); 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:

SubscriptionInfo

close()[source]

Stop this subscription: local bookkeeping only.

No server-side “unsubscribe” endpoint is known to exist — this ends the local iterator and stops routing pushes to it. It does not close the session’s shared WebSocket, which stays open for any other active subscription.

class niwaki.query.AsyncSubscription(raw)[source]

Bases: Generic

A 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 normal from_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 GAP event); 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:

SubscriptionInfo

async close()[source]

Stop this subscription: local bookkeeping only.

No server-side “unsubscribe” endpoint is known to exist — this ends the local iterator and stops routing pushes to it. It does not close the session’s shared WebSocket, which stays open for any other active subscription.

class niwaki.query.SubscriptionEvent(kind, mo, subscription_ids, raw)[source]

Bases: Generic

One item from a Subscription’s live stream.

kind

What happened — see EventKind.

Type:

EventKind

mo

The typed object, deserialised via from_event() — its model_fields_set reports exactly the fields this event’s payload carried (empty for a DELETED event, the full set for a CREATED one). None for GAP/REFRESH_FAILED, which describe the subscription itself, not a watched object.

Type:

T | None

subscription_ids

Every wire subscriptionId this push satisfied. Empty for GAP/REFRESH_FAILED, which are per-subscription already (delivered only into the affected subscription’s stream).

Type:

tuple[str, …]

raw

The transport-layer item this event was built from — a SubscriptionGap or SubscriptionRefreshFailed for those two kinds, holding their own detail (old/new subscription id, timestamps); the originating RawSubscriptionEvent otherwise.

Type:

RawPushItem

property dn: str | None

self.mo.dn, or None when there is no object (gap/refresh-failed).

property class_name: str | None

The ACI wire class name, or None when there is no object.

class niwaki.query.EventKind(*values)[source]

Bases: StrEnum

What a SubscriptionEvent represents.

CREATED/MODIFIED/DELETED values equal the wire status string a push carries, so EventKind(raw_status) maps directly. GAP/REFRESH_FAILED have 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,
)[source]

Bases: object

A point-in-time diagnostic snapshot of one tracked subscription.

Returned by list_subscriptions(), refresh_all_subscriptions(), and info — purely informational, not something to act on directly. Use refresh_all_subscriptions(), close_all_subscriptions(), or refresh_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:

int

subscription_id

The current wire subscriptionId — debug/ observability only, changes across a reconnect or an escalation recovery.

Type:

str

path

API path this subscription was opened against.

Type:

str

params

Query string parameters this subscription was opened with.

Type:

dict[str, str]

refresh_timeout

The caller-chosen refresh timeout override, if any.

Type:

int | None

consecutive_refresh_failures

How many scheduled refreshes have failed in a row, right now. 0 means healthy.

Type:

int

seconds_until_refresh

Time remaining until the next scheduled refresh sweep considers this subscription due.

Type:

float

property is_stale: bool

Whether this subscription has at least one recent refresh failure.

Not based on an overdue refresh schedule — the ~1 s sweep self-heals that within a second, so it carries no diagnostic signal on its own.

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

A response-subtree facet an APIC query can embed (rsp-subtree-include).

The exhaustive set the APIC offers. Pass any of these to include_subtree(); the with_* builder methods are ergonomic shortcuts for the common ones.

Filter expressions

Filter expressions compose with the &, | and ~ operators — the methods below are what those operators call.

class niwaki.query.FilterExpr(node)[source]

Bases: object

An 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(...)')
FilterExpr.__and__(other)[source]

Combine two expressions with logical AND (&).

FilterExpr.__or__(other)[source]

Combine two expressions with logical OR (|).

FilterExpr.__invert__()[source]

Negate this expression (~).

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. bool values 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:

FilterExpr representing the equality check.

Return type:

FilterExpr

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:
  • prop (str) – Property name.

  • value (Any) – Comparison value.

  • cls_name (str) – ACI class name for auto-prefix.

Returns:

FilterExpr.

Return type:

FilterExpr

niwaki.query.gt(prop, value, *, cls_name='')[source]

Greater than: gt(cls.prop,"value").

Parameters:
  • prop (str) – Property name.

  • value (Any) – Comparison value.

  • cls_name (str) – ACI class name for auto-prefix.

Returns:

FilterExpr.

Return type:

FilterExpr

niwaki.query.ge(prop, value, *, cls_name='')[source]

Greater or equal: ge(cls.prop,"value").

Parameters:
  • prop (str) – Property name.

  • value (Any) – Comparison value.

  • cls_name (str) – ACI class name for auto-prefix.

Returns:

FilterExpr.

Return type:

FilterExpr

niwaki.query.lt(prop, value, *, cls_name='')[source]

Less than: lt(cls.prop,"value").

Parameters:
  • prop (str) – Property name.

  • value (Any) – Comparison value.

  • cls_name (str) – ACI class name for auto-prefix.

Returns:

FilterExpr.

Return type:

FilterExpr

niwaki.query.le(prop, value, *, cls_name='')[source]

Less or equal: le(cls.prop,"value").

Parameters:
  • prop (str) – Property name.

  • value (Any) – Comparison value.

  • cls_name (str) – ACI class name for auto-prefix.

Returns:

FilterExpr.

Return type:

FilterExpr

niwaki.query.bw(prop, start, end, *, cls_name='')[source]

Between (inclusive): bw(cls.prop,"start","end").

Parameters:
  • prop (str) – Property name.

  • start (Any) – Lower bound (inclusive).

  • end (Any) – Upper bound (inclusive).

  • cls_name (str) – ACI class name for auto-prefix.

Returns:

FilterExpr.

Return type:

FilterExpr

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:
  • prop (str) – Property name.

  • pattern (str) – Glob pattern, preferably prefix + trailing * ("prod-*").

  • cls_name (str) – ACI class name for auto-prefix.

Returns:

FilterExpr.

Return type:

FilterExpr

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 FilterExpr objects to combine.

Returns:

FilterExpr representing the conjunction.

Raises:

ValueError – Fewer than two expressions provided.

Return type:

FilterExpr

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 FilterExpr objects to combine.

Returns:

FilterExpr representing the disjunction.

Raises:

ValueError – Fewer than two expressions provided.

Return type:

FilterExpr

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) – FilterExpr to negate.

Returns:

FilterExpr representing the negation.

Return type:

FilterExpr

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 Flags field: eq matches the whole mask exactly, anybit matches on a single bit regardless of the others.

Parameters:
  • prop (str) – Property name of a bitmask (Flags) attribute.

  • flags (str | Enum | Iterable[str | Enum]) – The flags to test — a comma-joined wire string ("syn,ack"), a single flag, or an iterable of members/names.

  • cls_name (str) – ACI class name for auto-prefix.

Returns:

FilterExpr.

Return type:

FilterExpr

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 allbit operator (verified live: allbit(...) → HTTP 301 “no such filter type”), so this compiles to a conjunction of anybit() tests — and(anybit(prop,a),anybit(prop,b)) — which is exactly “bit a set AND bit b set”.

Parameters:
  • prop (str) – Property name of a bitmask (Flags) attribute.

  • flags (str | Enum | Iterable[str | Enum]) – The flags that must all be set — a comma-joined wire string ("syn,ack"), a single flag, or an iterable of members/names.

  • cls_name (str) – ACI class name for auto-prefix.

Returns:

FilterExpr.

Raises:

ValueError – No flags were given.

Return type:

FilterExpr

niwaki.query.xor(*exprs)[source]

Logical XOR of two or more expressions: xor(e1,e2,...).

Parameters:

*exprs (FilterExpr) – Two or more FilterExpr objects to combine.

Returns:

FilterExpr representing the exclusive-or.

Raises:

ValueError – Fewer than two expressions provided.

Return type:

FilterExpr

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:

FilterExpr wrapping text verbatim.

Return type:

FilterExpr

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 of where(code=["F0467", "F1394"]); both compile to or(eq(...),eq(...)) (the APIC filter grammar has no in).

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 of where(name="prod-*"); use it to force a wildcard where the value would otherwise read as a literal.

Parameters:

pattern (str) – A glob pattern (* wildcard).

Returns:

A tag consumed by where().

Return type:

_Like

niwaki.query.between(start, end)[source]

Match a property within an inclusive range — bw(prop,"start","end").

where(pri=between(1, 5)) compiles to bw(cls.pri,"1","5").

Parameters:
  • start (_FilterScalar) – Lower bound (inclusive).

  • end (_FilterScalar) – Upper bound (inclusive).

Returns:

A tag consumed by where().

Return type:

_Range