Skip to content

02 — Search & Structured Query (02_search_and_query.py)

Demonstrates multi-term object search with recursive parent group memberships (client.search_objects(), including exact_match=True), progressive-disclosure schema discovery (client.describe_objects()), SQL-free cross-domain aggregation (client.query_objects()), and cache freshness inspection (client.get_cache_status()).

Source Code (examples/02_search_and_query.py)

examples/02_search_and_query.py
"""Example 02 — Object search, group membership resolution, and structured `query_objects`.

Run:
    uv run examples/02_search_and_query.py

Demonstrates:
1. `client.search_objects(query, max_depth=2)` — classifies comma-separated search terms
   (IPv4, CIDR, range, wildcard, UID, name) and resolves parent group memberships recursively.
2. `client.describe_objects()` — progressive-disclosure schema and type counts across domains.
3. `client.query_objects()` — cross-domain filtering, grouping (`group_by`), and aggregation
   (`aggregate="count" | "list"`), including `scope_origin="local" | "global"`.
4. `client.get_cache_status()` — per-scope cache freshness and `cache_age_seconds`.
"""

from __future__ import annotations

import asyncio
import os
import time

from dotenv import load_dotenv

load_dotenv(".env.lib", override=True)
load_dotenv(".env.secrets", override=True)

from arodonata import ArodonataClient, ArodonataSettings  # noqa: E402


async def main() -> None:
    """Run search_objects, describe_objects, query_objects, and get_cache_status."""
    settings = ArodonataSettings()
    t0 = time.perf_counter()

    async with ArodonataClient(settings=settings) as client:
        print("[Step 1/4] Resolving target domain (get_domains)...", flush=True)
        domains = await client.get_domains()
        target_domain = os.getenv("EXAMPLE_DOMAIN") or (domains[0].name if domains else None)

        # 1. Search objects by wildcard and IPv4/CIDR with recursive parent group resolution
        query_str = os.getenv("EXAMPLE_SEARCH_QUERY", "*host*, 10.0.0.0/8")
        print(
            f"\n[Step 2/4] Searching objects across all 5 types in {target_domain!r} "
            f"(search_objects={query_str!r}, max_depth=2, cache_mode='smart')...",
            flush=True,
        )
        s0 = time.perf_counter()
        search_res = await client.search_objects(
            query_str,
            domains=[target_domain] if target_domain else None,
            max_depth=2,
            cache_mode="smart",
        )
        print(
            f"Search query {query_str!r} matched {len(search_res.matches)} object(s) in {time.perf_counter() - s0:.2f}s:"
        )
        for match in search_res.matches[:5]:
            groups = search_res.memberships.get(match.uid, [])
            group_names = [f"{g.name} (depth={g.depth})" for g in groups]
            print(f"  - {match.name} ({match.type}, domain={match.domain_name}) -> groups={group_names}")

        # 2. Progressive-disclosure metadata via describe_objects
        print(
            "\n[Step 3/4] Inspecting cached schema & aggregating objects (describe_objects / query_objects)...",
            flush=True,
        )
        desc = await client.describe_objects(domain_name=target_domain)
        print(f"Cached summary: {desc['total_objects']} objects, {desc['total_services']} services")
        print(f"  Object types: {desc['object_types']}")
        print(f"  Groupable fields: {desc['groupable_fields']}")

        # 3. Structured cross-domain aggregation via query_objects
        grouped = await client.query_objects(
            domain_name=target_domain,
            group_by=["domain_name", "type"],
            aggregate="count",
        )
        print(f"\nGrouped counts (total={grouped['total']}):")
        for row in grouped.get("groups", [])[:10]:
            print(f"  - {row['domain_name']} / {row['type']}: {row['count']}")

        # 4. Inspect domain_sync_state freshness via get_cache_status
        print("\n[Step 4/4] Checking cache freshness (get_cache_status)...", flush=True)
        status = await client.get_cache_status(domain_name=target_domain)
        print(f"Cache status entries ({status['total_entries']}):")
        for entry in status["entries"][:5]:
            print(
                f"  - [{entry['scope']}:{entry['item_type']}] {entry['mgmt_name']}:{entry['domain_name']} "
                f"status={entry['status']} count={entry['object_count']} age={entry['cache_age_seconds']}s"
            )

    print(f"\nCompleted in {time.perf_counter() - t0:.2f}s")


if __name__ == "__main__":
    asyncio.run(main())