How to find and match entities#
This guide shows you how to read entities back out of a FactGraph: filter them by exact field values with fg.entities.where, and select them by a rule or rule-expression pattern (with port constraints) using fg.entities.match. Both return a tuple/list of read-only EntitySnapshot objects.
Prerequisites#
- A
FactGraphwith a registered schema and some entities written to it. See How to define a schema and How to write and read facts. - The
Entitysubclasses you query with must be the same classes you registered.
Both verbs require an Entity subclass as the first positional argument. Passing a string, a Field descriptor, or any other type raises SDKStoreError (the Layer 1 navigation key check, ADR-API §4.1.1).
Find entities by exact field values (where)#
fg.entities.where(entity_cls, *, policy=..., limit=None, _meta=None, **field_filters) returns every visible entity of entity_cls whose identity and field values match the keyword filters exactly.
Filter by one or more fields. Each keyword is an identity name or a field name on the entity; the value must match exactly.
active = fg.entities.where(EntitiesNsUser, status="active") for snap in active: print(snap.user_id, snap.name)Pass several filters to require all of them at once, and omit filters to list every visible entity of that type.
alices = fg.entities.where(EntitiesNsUser, name="Alice", status="active") everyone = fg.entities.where(EntitiesNsUser)To cap the number of returned snapshots, pass
limit=as a non-negative integer.limit=0returns an empty result;limit=None(the default) returns all matches.first_two = fg.entities.where(EntitiesNsUser, status="active", limit=2)To verify: each returned object is an
EntitySnapshot; read its identity and field values as attributes (for examplesnap.name). Snapshots are read-only, so attribute assignment raisesFrozenSnapshotError.
Notes on filter keys and metadata:
- An unknown filter key (not an identity or field name on the entity) raises
SDKSchemaError:unknown filter fields for <Entity>: [...]. wherefilters on values only. It rejects the flat metadata kwargssource=,trace_id=,version=, andmeta=with anSDKStoreErrorpointing to_meta=(ADR-API §4.4). A non-empty_meta={...}is also rejected, because entity-query metadata projection is not implemented; the error directs you tofg.assertions.where(_meta=...)orsnapshot.assertions.where(_meta=...)for assertion-level metadata filtering. See the assertion layer in Three-layer API. An empty_meta={}and_meta=Noneare accepted and apply no metadata filter.
Match entities against a rule pattern (match)#
fg.entities.match(entity_cls, template, *, limit=None, **port_constraints) evaluates a rule pattern over the current view and projects out the matching entities of entity_cls. Use this when “exact field equality” is not enough: when the selection depends on a relationship, an aggregate, a comparison, or a join across entities expressed as a rule.
The template is either an application Rule or an AND RuleExpr. Author it with the rule DSL; see Rules reference for the full atom and expression surface.
Author a rule whose body describes the pattern, and declare a port for the entity you want back plus a port for any value you want to constrain. The template must declare exactly one
entity_refport forentity_cls(the projection port).from factgraph.sdk import build_application_rule, vars with vars("user") as (user,): active_user = build_application_rule( id="active_user", when=[User(user).status == "active"], ports={"user": user}, ) results = fg.entities.match(User, active_user)Constrain a declared port by passing it as a keyword. The keyword name must be one of the template’s port names; an unknown name raises
SDKStoreError:unknown match port '<name>'; valid ports: ....To pin a value port to a literal, pass the literal:
with vars("person", "region") as (person, region): person_region = build_application_rule( id="person_region", when=[Person(person).region == region], ports={"person": person, "region": region}, ) in_us = fg.entities.match(Person, person_region, region="us")To pin the projected entity port to a specific entity, pass an
idref_v1:token or anEntitySnapshot. The token’s entity type must agree with the port’s declared type, otherwiseSDKStoreErroris raised.alice = fg.entities.get(Person, name="alice") only_alice = fg.entities.match(Person, person_region, person=alice)To require that a port equals one of the projected entity’s own fields, pass the
Fielddescriptor for that entity:fg.entities.match(Person, person_region, region=Person.region)A
Fielddescriptor from a different entity raisesSDKStoreError:cross-entity Field constraints are not supported by fg.read.match(...); use RuleExpr.join_by_ports(...) to connect entities.
To combine sub-patterns, build a
RuleExpr. Use&for AND (join the shared entity port with.join_by_ports("...")) and|for OR; alias each operand with.as_("..."). Constraints distribute across OR branches, and results are de-duplicated by entity ref.exists_expr = person_exists.as_("exists") region_expr = person_region.as_("region") anded = (exists_expr & region_expr).join_by_ports("person") fg.entities.match(Person, anded, region="us") ored = person_region.as_("region") | person_tag.as_("tag") fg.entities.match(Person, ored, marker="vip")To cap results, pass
limit=as a non-negative integer orNone. The limit is applied after de-duplication.To verify:
matchreturns a tuple of distinctEntitySnapshotobjects, each ofentity_cls. Read identity and field values as attributes, just likewhereresults.
Notes on match templates and connectivity:
- The template must be a
RuleorRuleExpr. Anything else (for example a list,None, or a legacyQuery) raisesSDKStoreError:fg.read.match(...) template must be application Rule or AND RuleExpr; got <type>. - If no
entity_refport matchesentity_cls, you getrequires exactly one entity_ref port for '<Entity>'. If more than one matches, you get anambiguouserror. - Every constrained port must be connected to the projected entity through the rule body. A disconnected constrained port raises
SDKStoreError:disconnected match pattern would create a silent cross product; .... In an OR expression, constraining a port that is declared in only some branches raisesmatch port '<name>' is only declared in some RuleExpr branches. - A method-level
view=keyword is rejected: attach a view at construction time instead (FactGraph.attach(db, view=view)). See Persistence reference.
When to use which#
- Use
wherewhen selection is exact value equality on the entity’s own identity and fields. It is the simplest read. - Use
matchwhen selection needs a rule: comparisons, aggregates, joins across entities, or OR/AND combinations of patterns. The match runtime evaluates the rule body over the view and projects the entity port.
See also: Three-layer API reference, Rules reference, Evaluation and explanation reference, Errors reference.