How to define a schema#

This guide shows you how to declare entity types with identity and fields in Python and build a FactGraph from them, which is the first step before you write or read any facts.

Prerequisites#

  • The factgraph package installed (v0.2.0).
  • The public symbols Entity, Identity, Field, and FactGraph, all importable from factgraph.sdk.

Steps#

  1. Declare an entity type by subclassing Entity. Annotate each member with a Python type and assign either an Identity() or a Field() descriptor. Every entity must declare at least one Identity() member; the metaclass raises SDKSchemaError with the message Entity '<name>' must declare at least one Identity field otherwise.

    from factgraph.sdk import Entity, Identity, Field, FactGraph
    
    class SchemaUser(Entity):
        user_id: str = Identity()
        name: str = Field()
        tags: list[str] = Field()

    Identity members form the entity’s stable, immutable identity bundle. name is a single-value field. tags is a multi-value field because its annotation is list[str].

  2. Choose the cardinality of each Field() through its annotation, not through a keyword argument. A scalar annotation (for example str, int, bool, bytes, float) produces a single-value field. A list[T], set[T], frozenset[T], or variadic tuple[T, ...] annotation produces a multi-value field. Optional, Union, the T | None form, plain dict, and non-variadic tuple annotations are rejected with SDKSchemaError.

    To constrain a string value, pass pattern= (a regular expression). pattern= is only supported for string-typed members; using it on a non-string member raises SDKSchemaError.

    class SchemaTenantUser(Entity):
        user_id: str = Identity()
        tenant_id: str = Identity()
        name: str = Field()

    Identity() and Field() accept only pattern= and repr=. Passing legacy keywords such as primary_key, default, default_factory, or cardinality raises SDKSchemaError. Fields have no defaults: a missing single field reads as None and a missing multi field reads as ().

  3. Declare more than one identity member when an entity’s identity is composite. All identity members together make up the complete bundle, and callers must later provide every one of them when they create the entity.

    See reference/schema.md for the full cardinality rules, the type-domain mapping (including Literal[...] enum fields and entity-reference fields), and the Entity.Meta keys (version, tags, repr).

  4. Optionally add explain-layer representation templates with repr= on members and a nested Meta class on the entity. Member templates use the %CLS, %ENT, and %FLD placeholders; the Meta.repr template uses %CLS plus identity field names (for example %user_id). These templates are validated at class-definition time and compiled into the schema IR without changing the schema digest. Invalid placeholders raise SDKSchemaError.

    class CEUser(Entity):
        class Meta:
            repr = "User %user_id"
    
        user_id: str = Identity()
        country: str = Field(repr="%ENT country %FLD")
  5. Build a FactGraph by passing your entity classes to FactGraph.create(schema_classes=[...]). This compiles the declarations into the graph schema. Pass path= if you want a durable workspace you can later save and reload.

    fg = FactGraph.create(schema_classes=[SchemaUser, SchemaTenantUser])

    To verify: the entity types appear in fg.schema_ir["entities"] and their compiled predicates appear in the separate top-level fg.schema_ir["predicates"] list (entries identified by pred_id), and the graph is ready for entity and field writes.

Variations#

  • To add a brand-new entity type to a live graph, call fg.schema.register(NewEntityClass). Calling register for an entity type that is already registered raises SchemaConflictError. To add a non-identity field to an already-registered entity, call fg.schema.extend(UpdatedEntityClass). Both calls are additive-only: on extend, non-additive changes (removing a field, changing a field’s type or cardinality, or altering the identity bundle) raise SchemaNonAdditiveError. See reference/schema.md.
  • To validate schema classes without building a graph, call schema_preflight_from_classes([...]) from factgraph.sdk.compile, which returns a preflight report dictionary.
  • To build a graph with no durable workspace, use FactGraph.from_schema_classes([...]) instead of FactGraph.create(...).

See also: reference/schema.md, reference/factgraph.md, How to write and read facts, reference/persistence.md.

comments powered by Disqus