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#

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.

  1. 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)
  2. 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)
  3. To cap the number of returned snapshots, pass limit= as a non-negative integer. limit=0 returns 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 example snap.name). Snapshots are read-only, so attribute assignment raises FrozenSnapshotError.

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>: [...].
  • where filters on values only. It rejects the flat metadata kwargs source=, trace_id=, version=, and meta= with an SDKStoreError pointing 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 to fg.assertions.where(_meta=...) or snapshot.assertions.where(_meta=...) for assertion-level metadata filtering. See the assertion layer in Three-layer API. An empty _meta={} and _meta=None are 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.

  1. 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_ref port for entity_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)
  2. 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 an EntitySnapshot. The token’s entity type must agree with the port’s declared type, otherwise SDKStoreError is 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 Field descriptor for that entity:

      fg.entities.match(Person, person_region, region=Person.region)

      A Field descriptor from a different entity raises SDKStoreError: cross-entity Field constraints are not supported by fg.read.match(...); use RuleExpr.join_by_ports(...) to connect entities.

  3. 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")
  4. To cap results, pass limit= as a non-negative integer or None. The limit is applied after de-duplication.

    To verify: match returns a tuple of distinct EntitySnapshot objects, each of entity_cls. Read identity and field values as attributes, just like where results.

Notes on match templates and connectivity:

  • The template must be a Rule or RuleExpr. Anything else (for example a list, None, or a legacy Query) raises SDKStoreError: fg.read.match(...) template must be application Rule or AND RuleExpr; got <type>.
  • If no entity_ref port matches entity_cls, you get requires exactly one entity_ref port for '<Entity>'. If more than one matches, you get an ambiguous error.
  • 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 raises match 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 where when selection is exact value equality on the entity’s own identity and fields. It is the simplest read.
  • Use match when 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.

comments powered by Disqus