graflo.architecture.query.models¶
Read-request models for the DB-agnostic graph query surface.
Four questions an agent asks of a live graph — which nodes, what is adjacent to this one, what is reachable from these, how many — expressed once, in logical schema names, and answered identically by every backend.
Filters are :class:FilterExpression, which already renders to AQL, Cypher,
nGQL, GSQL, SQL and Python. There is no new query language here, and there is
deliberately no way to pass a backend query through: Connection.execute must
never be reachable from an agent-facing path, so nothing in this module carries
a raw query string.
Validation raises rather than clamps. Silently reducing hops=99 to 3
hands the caller a partial answer it believes is complete — the failure mode
this codebase already rejected for TigerGraph edge direction.
Attributes¶
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
¶
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
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.