graflo.architecture.query¶
DB-agnostic read contract: what an agent may ask of a live graph.
Layer 2. Imports filter/ and graph_types/ (layer 1) and nothing from db/ —
enforcement has to be expressible without a driver, or "enforced in core" would
mean "enforced wherever a driver happens to be imported".
Not in contract/, which is the write-side manifest at layer 3: mixing a read
contract into it would muddy a boundary the layering test already guards.
Modules:
| Name | Description |
|---|---|
caps |
Hard limits on what a read request may ask for. |
models |
Read-request models for the DB-agnostic graph query surface. |
result |
What a read returned, and what it left out. |
Attributes¶
HARD_CAPS = QueryCaps()
module-attribute
¶
__all__ = ['HARD_CAPS', 'AggregateQuery', 'CapExceededError', 'GraphQuery', 'NeighborQuery', 'NodeQuery', 'QueryCaps', 'QueryResult', 'TraverseQuery']
module-attribute
¶
Classes¶
AggregateQuery
¶
Bases: GraphQuery
Count or summarise one vertex type.
Source code in graflo/architecture/query/models.py
Attributes¶
aggregated_field = PydanticField(default=None, description='Property to aggregate. Required for everything but COUNT.')
class-attribute
instance-attribute
¶
aggregation
property
¶
function as an enum. See NeighborQuery.edge_direction.
filters = PydanticField(default=None)
class-attribute
instance-attribute
¶
function = PydanticField(default=AggregationType.COUNT, description='Aggregation to apply.')
class-attribute
instance-attribute
¶
group_by = PydanticField(default=None, description='Property to group by. COUNT only.')
class-attribute
instance-attribute
¶
vertex_type = PydanticField(...)
class-attribute
instance-attribute
¶
CapExceededError
¶
Bases: ValueError
A query asked for more than a cap allows.
Carries the cap's name so the surface can say which limit was hit rather than returning a generic validation failure — an agent that is told "too many hops, max is 3" can retry; one told "invalid request" cannot.
Source code in graflo/architecture/query/caps.py
GraphQuery
¶
Bases: ConfigBaseModel
Shared envelope for every read request.
Note
There is intentionally no caps field. Caps come from
:data:HARD_CAPS and are lowered only through :meth:narrowed, so a
request body cannot raise its own ceiling.
Source code in graflo/architecture/query/models.py
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | |
Attributes¶
limit = PydanticField(default=100, ge=1, description='Rows to return, within `max_rows`.')
class-attribute
instance-attribute
¶
projection = PydanticField(default=None, description="Property names to return. None returns whatever the backend stores, subject to the cap's allow-list.")
class-attribute
instance-attribute
¶
timeout_s = PydanticField(default=10.0, gt=0, description='Seconds to allow, within `timeout_s`.')
class-attribute
instance-attribute
¶
Methods:¶
finish_init(caps=None)
¶
Validate against caps, defaulting to the core ceiling.
Returns:
| Type | Description |
|---|---|
Self
|
Self, so the call composes: |
Raises:
| Type | Description |
|---|---|
CapExceededError
|
Naming the cap that was exceeded. |
Source code in graflo/architecture/query/models.py
narrowed(caps)
¶
Return a copy fitted to caps, raising on anything explicitly asked for.
The split is what makes caps both enforceable and usable:
- A value the caller explicitly set above a cap raises
:class:
CapExceededError. Silently clamping it would hand back a partial answer the caller believes is complete — the failure mode this codebase already rejected for TigerGraph edge direction. - A value the caller left at its default is clamped. A policy of
max_rows=5must not 422 every request that simply did not mention a limit; that would make strict policies unusable rather than safe.
model_fields_set is what distinguishes the two, which is precisely
why it must be consulted rather than comparing against the default.
Projection is always intersected rather than raising, since an allow-list exists to hide properties: refusing the request would tell the caller which forbidden property they guessed correctly.
Source code in graflo/architecture/query/models.py
NeighborQuery
¶
Bases: GraphQuery
What is adjacent to one anchor vertex.
The instance-plane counterpart of SchemaGraph.schema_neighbors. They are
different questions and must never share a name: this one asks which rows
are adjacent, the other which types can be.
Source code in graflo/architecture/query/models.py
Attributes¶
direction = PydanticField(default=EdgeDirection.OUT, description='Orientation followed from the anchor. Defaults to OUT, matching `Connection.fetch_edges`; an edge declared `directed: false` is followed both ways regardless.')
class-attribute
instance-attribute
¶
edge_direction
property
¶
direction as an enum, for handing to a driver.
ConfigBaseModel sets use_enum_values=True, so the stored value is
the bare string. Backends compare against EdgeDirection members, and a
string silently matches none of them — which each backend then resolves
differently, so the same query returns different neighbourhoods
depending on where it runs. Always cross the boundary through this.
edge_relations = PydanticField(default=None, description='Restrict to these relations. None means all.')
class-attribute
instance-attribute
¶
filters = PydanticField(default=None)
class-attribute
instance-attribute
¶
hops = PydanticField(default=1, ge=1, description='Hop distance, within `max_hops`.')
class-attribute
instance-attribute
¶
key = PydanticField(..., description='Anchor identity value, or a single-field mapping.')
class-attribute
instance-attribute
¶
vertex_type = PydanticField(..., description='Logical type of the anchor.')
class-attribute
instance-attribute
¶
NodeQuery
¶
Bases: GraphQuery
List vertices of one type, optionally filtered.
Source code in graflo/architecture/query/models.py
QueryCaps
¶
Bases: ConfigBaseModel
Ceilings a read request must fit inside.
Defaults are the core ceiling. A deployment narrows them per connection via
:meth:GraphQuery.narrowed; nothing widens them.
Source code in graflo/architecture/query/caps.py
Attributes¶
max_edge_types = PydanticField(default=20, ge=1, description='Most distinct relations one request may name.')
class-attribute
instance-attribute
¶
max_elements = PydanticField(default=5000, ge=1, description='Most vertices plus edges a response may carry.')
class-attribute
instance-attribute
¶
max_hops = PydanticField(default=3, ge=1, description='Deepest traversal permitted.')
class-attribute
instance-attribute
¶
max_rows = PydanticField(default=1000, ge=1, description='Most rows a single query may return.')
class-attribute
instance-attribute
¶
max_seeds = PydanticField(default=10, ge=1, description='Most anchor vertices a traversal may start from.')
class-attribute
instance-attribute
¶
projection_allow_list = PydanticField(default=None, description='Property names a response may include. None means unrestricted; an empty list means nothing may be projected, which is not the same thing.')
class-attribute
instance-attribute
¶
timeout_s = PydanticField(default=30.0, gt=0, description='Longest a query may run.')
class-attribute
instance-attribute
¶
Methods:¶
narrow(other)
¶
Combine two cap sets, taking the stricter of each.
Narrowing is a lattice meet, not an override: a policy that tried to raise a ceiling silently becomes a no-op rather than a privilege escalation.
Source code in graflo/architecture/query/caps.py
QueryResult
¶
Bases: ConfigBaseModel
A query's answer plus the honest caveats.
Source code in graflo/architecture/query/result.py
Attributes¶
caps_hit = PydanticField(default_factory=list, description='Which caps bound this answer, by name. Empty when nothing bound it — the only way a caller can tell a complete answer from one that happens to fit.')
class-attribute
instance-attribute
¶
container = PydanticField(default_factory=GraphContainer, description='DB-agnostic vertices and edges, identical across backends.')
class-attribute
instance-attribute
¶
elapsed_ms = PydanticField(default=0, ge=0)
class-attribute
instance-attribute
¶
element_count = PydanticField(default=0, ge=0, description='Vertices plus edges carried.')
class-attribute
instance-attribute
¶
truncated = PydanticField(default=False, description='Whether a cap cut the answer short.')
class-attribute
instance-attribute
¶
Methods:¶
of(container, *, caps_hit=None, elapsed_ms=0)
classmethod
¶
Build a result, deriving the element count from the container.
Source code in graflo/architecture/query/result.py
TraverseQuery
¶
Bases: GraphQuery
What is reachable from a set of anchors.
Source code in graflo/architecture/query/models.py
Attributes¶
direction = PydanticField(default=EdgeDirection.ANY)
class-attribute
instance-attribute
¶
edge_direction
property
¶
direction as an enum. See NeighborQuery.edge_direction.