loom.core.model¶
- class loom.core.model.BaseModel[source]¶
Bases:
LoomStructBase for all loom domain models.
Subclasses must declare
__tablename__and provide typed attributes. Column metadata is optional viaColumnField(...).
- class loom.core.model.Privilege(value)[source]¶
Bases:
StrEnumRow operations subject to row-level security.
TRUNCATE, REFERENCES and TRIGGER are deliberately absent: none of them honours row-level security, so none of them can ever be granted to a database user that is not meant to bypass it.
- class loom.core.model.RowScoped[source]¶
Bases:
objectMark a model as row-scoped; adds no column.
The product names the scope on one of the model’s columns with
ColumnField(scope=...). A read-only scoped table narrows__scope_privileges__to{Privilege.SELECT}.
- class loom.core.model.ScopeColumn(scope, column, on, elevable)[source]¶
Bases:
objectOne scoped column: which scope, which column, and how far it reaches.
- class loom.core.model.ScopedTable(schema, name, scopes, privileges, partition_by=None)[source]¶
Bases:
objectA compiled scoped table: its scopes, the privileges it grants and its partition column.
partition_bynames the column a range-partitioned table is partitioned by,Nonefor a plain table.
- class loom.core.model.PartialUnique(rule, columns, where)[source]¶
Bases:
objectA unique index over
columnsrestricted to the rows matchingwhere.
- class loom.core.model.PartitionRange(name, lower, upper)[source]¶
Bases:
objectOne partition: its name and the half-open range
[lower, upper)it holds.
- class loom.core.model.RangePartition(column)[source]¶
Bases:
objectA table partitioned by range over
column.- Parameters:
column (str)
- loom.core.model.declared_checks(cls)[source]¶
Named CHECK constraints declared with
__checks__as{rule: sql_expression}.The rule is the constraint name, or its
%(constraint_name)sunder a naming convention; the expression is SQL that loom passes through verbatim.
- loom.core.model.declared_partial_unique(cls)[source]¶
Partial unique indexes declared with
__partial_unique__.Each entry reads
{rule: (columns, where)}:columnsis a tuple of column names,wherethe SQL predicate passed through verbatim.- Parameters:
cls (type)
- Return type:
tuple[PartialUnique, …]
- loom.core.model.declared_partition(cls)[source]¶
The range partitioning declared with
__partition_by__ = ("RANGE", "<column>").The column must be a
DateTimecolumn of the primary key, and every unique key must contain it, as Postgres requires of a partitioned table. Only row-scoped models may be partitioned, so the guard protects every partition; LIST and HASH partitioning are not supported.- Raises:
ValueError – Naming the model and what is wrong with the declaration.
- Parameters:
cls (type)
- Return type:
RangePartition | None
- loom.core.model.declared_privileges(cls)[source]¶
Group privileges declared with
__privileges__on an unscoped model.
- loom.core.model.declared_unique(cls)[source]¶
Composite UNIQUE constraints declared with
__unique__.
- loom.core.model.range_partitions(table, start, end, interval='month')[source]¶
The partitions of
tablecovering[start, end), from the period holdingstart.A
datetimebound is read as its date.- Raises:
ValueError – When
intervalis unknown or a partition name would be longer than Postgres keeps.- Parameters:
- Return type:
tuple[PartitionRange, …]
- loom.core.model.scope_columns(cls)[source]¶
Resolve the scoped columns of
cls, enforcing rules C1 to C5 and C8.Raises
ValueErrornaming the rule, the model and the column.- Parameters:
cls (type)
- Return type:
tuple[ScopeColumn, …]
- exception loom.core.model.BoundaryValidationError(message, violations=())[source]¶
Bases:
LoomError,ValueErrorA boundary value failed validation.
Raised by every
LoomTypeimplementation with the library error as__cause__. The pydantic message is built field by field and omits the input and any documentation URL; the msgspec message is forwarded verbatim, and is one line and free of the input already. It is also aValueError, so anexcept ValueErrorthat used to catchmsgspec.ValidationErrororpydantic.ValidationErrorkeeps catching it.
- class loom.core.model.LoomFrozenStruct[source]¶
Bases:
StructBase immutable struct for Loom value contracts.
Use this for configuration, DSL declarations, wire envelopes, and other value objects that should not expose mutable assignment after creation.
- class loom.core.model.LoomStruct[source]¶
Bases:
StructBase struct for Loom logical data types.
LoomStructis intentionally neutral. Specialised framework types such as responses or persistence models define their own serialisation behaviour.
- class loom.core.model.TimestampedModel(*, created_at=UNSET, updated_at=UNSET)[source]¶
Bases:
BaseModelOpt-in base for domain models that need audit timestamps.
Adds
created_atandupdated_atas nullable server-default columns. Both fields are omitted from serialisation output whenNone.Design note:
trace_idis intentionally absent. Trace identifiers are an observability concern belonging to the transport layer, not to the domain model.- created_at¶
UTC timestamp set by the database at INSERT time.
Noneuntil the first flush/commit.- Type:
datetime.datetime | None
- updated_at¶
UTC timestamp updated by the database on every UPDATE.
Noneuntil the first flush/commit.- Type:
datetime.datetime | None
- class loom.core.model.ColumnFieldInfo(name, python_type, column_type, field)[source]¶
Bases:
objectResolved metadata for a single column field.
- Parameters:
name (str)
python_type (type)
column_type (ColumnType)
field (Field)
- loom.core.model.ColumnField(column_type=None, *, primary_key=False, unique=False, index=False, nullable=False, autoincrement=False, server_default=None, server_onupdate=None, foreign_key=None, on_delete=None, default=msgspec.UNSET, length=None, db_type=None)[source]¶
Declare column metadata without using explicit
Annotatedsyntax.- Parameters:
column_type (ColumnType | None)
primary_key (bool)
unique (bool)
index (bool)
nullable (bool)
autoincrement (bool)
server_default (ServerDefault | None)
server_onupdate (ServerOnUpdate | str | None)
foreign_key (str | None)
on_delete (OnDelete | None)
default (Any)
length (int | None)
db_type (ColumnType | None)
- Return type:
- loom.core.model.ScopedField(column_type=None, *, scope, on='both', elevable=False, primary_key=False, nullable=False, foreign_key=None, on_delete=None, length=None, db_type=None)[source]¶
Declare the column that scopes the rows of a
RowScopedmodel.scopenames the session value the column is compared with;onlimits the comparison to reads or writes, andelevablelets an authorized execution lift a write-only scope within the table’s boundary.
- class loom.core.model.ColumnType(type_name, args=(), kwargs=<factory>)[source]¶
Bases:
objectBackend column type descriptor stored inside
Annotated[...].
- class loom.core.model.Field(primary_key=False, unique=False, index=False, nullable=False, autoincrement=False, server_default=None, server_onupdate=None, foreign_key=None, on_delete=None, default=UNSET, length=None, scope=None, on='both', elevable=False)[source]¶
Bases:
objectColumn constraint metadata stored inside
Annotated[...].- Parameters:
primary_key (bool)
unique (bool)
index (bool)
nullable (bool)
autoincrement (bool)
server_default (ServerDefault | None)
server_onupdate (ServerOnUpdate | str | None)
foreign_key (str | None)
on_delete (OnDelete | None)
default (Any)
length (int | None)
scope (str | None)
on (Literal['read', 'write', 'both'])
elevable (bool)
- loom.core.model.ProjectionField(*, loader, profiles=('default',), depends_on=(), default=PROJECTION_DEFAULT_MISSING)[source]¶
Declare a projection field without triggering assignment type errors.
- Parameters:
loader (Any) – Loader descriptor or instance responsible for computing the value.
profiles (tuple[str, ...]) – Profile names in which this projection is active.
depends_on (tuple[str, ...]) – Names of other projections or relation events this depends on.
default (Any) – Fallback value when the loader returns no result for an entity.
- Returns:
A
Projectioninstance cast toAnyfor clean class-body assignment.- Return type:
Example:
count_reviews: int = ProjectionField( loader=CountLoader(model=ProductReview), profiles=("with_details",), default=0, )
- class loom.core.model.LoomStructMeta(name, bases, namespace, **kwargs)[source]¶
Bases:
StructMetaMetaclass that intercepts
RelationandProjectionassignments beforeStructMetaprocesses the class body.Each intercepted attribute is replaced with
UNSETas its default, and the type annotation is widened toT | UnsetTypeso thatomit_defaults=Truestrips unloaded fields from serialisation output.
- class loom.core.model.LoomType(*args, **kwargs)[source]¶
Bases:
ProtocolBoundary contract a compiled type exposes to its consumers.
- type¶
The declared type or a compiler-generated annotation, kept for introspection.
- Type:
Any
- library¶
Closed tag naming the implementation behind the value.
- Type:
Literal[‘msgspec’, ‘pydantic’]
- decode_json(data, /)[source]¶
Validate
datastrictly and build the typed value in one pass.- Raises:
BoundaryValidationError –
datais malformed JSON or does not satisfy the type.- Parameters:
- Return type:
- from_builtins(payload, /)[source]¶
Validate builtins already produced by a trusted source and build the typed value.
Unlike
decode_json()this takes Python builtins rather than JSON text, and pydantic validates them in its lax mode: it is the entry point for a value that already round-tripped through a cache backend or an MCP client rather than through raw JSON.- Raises:
BoundaryValidationError –
payloaddoes not satisfy the type.- Parameters:
payload (Any)
- Return type:
- class loom.core.model.Projection(loader, profiles=('default', ), depends_on=(), default=<object object>)[source]¶
Bases:
objectDerived-field metadata assigned as a class attribute on a
BaseModel.
- class loom.core.model.Relation(foreign_key, cardinality, secondary=None, back_populates=None, on_delete=None, on_update=None, profiles=('default',), depends_on=(), default=None)[source]¶
Bases:
objectRelationship metadata assigned as a class attribute on a
BaseModel.
- loom.core.model.RelationField(*, foreign_key, cardinality, secondary=None, back_populates=None, on_delete=None, on_update=None, profiles=('default',), depends_on=(), default=None)[source]¶
Declare a relation field with normal typing (without assignment type errors).
- class loom.core.model.ServerOnUpdate(value)[source]¶
Bases:
StrEnum- classmethod is_now(value)[source]¶
Return whether
valuerequests the server clock, as enum or string.- Parameters:
value (ServerOnUpdate | str | None) – A
server_onupdatedeclaration; the string form is matched case-insensitively.- Returns:
TrueforServerOnUpdate.NOWor"now"in any case.- Return type:
- exception loom.core.model.UnsupportedBoundaryType(reason)[source]¶
Bases:
LoomErrorA symbol cannot be compiled into a
LoomType.- Parameters:
reason (str) – Why the symbol is rejected, shared by every boundary’s issue.
- Return type:
None
- loom.core.model.get_column_fields(cls)[source]¶
Extract column fields from a model class.
- Parameters:
cls (type)
- Return type:
- loom.core.model.get_id_attributes(cls)[source]¶
Return the names of the primary-key fields, in declaration order.
- Raises:
ValueError – If the model declares no primary-key field.
- Parameters:
cls (type)
- Return type:
- loom.core.model.get_projections(cls)[source]¶
Return projections registered by
LoomStructMeta.- Parameters:
cls (type)
- Return type:
- loom.core.model.is_pydantic_model(symbol)[source]¶
Report whether symbol is a
pydantic.BaseModelsubclass.Total over any symbol: a non-class value answers
Falserather than raising, so a caller can probe an annotation argument without first checking it is a class.
- loom.core.model.loom_type(symbol)[source]¶
Compile an authored class into a
LoomType.Strictness is a compile-time property of an authored symbol only: a
msgspec.Structmust declareforbid_unknown_fields=Trueand apydantic.BaseModelmust resolvemodel_config["extra"] == "forbid". The check happens once, here, before the symbol is handed toloom_type_of(); a compiler-generated annotation built from an authored symbol never repeats it.- Parameters:
symbol (type) – A strict
msgspec.Structor strictpydantic.BaseModelsubclass.- Returns:
The boundary type wrapping
symbol.- Raises:
UnsupportedBoundaryType –
symbolis neither library’s model class or does not reject unknown fields.- Return type:
- loom.core.model.loom_type_of(annotation)[source]¶
Compile any annotation, authored or compiler-generated, into a
LoomType.The single dispatcher every boundary uses once
loom_typehas already checked the strictness of an authored root: amsgspec.Structclass resolves tomsgspec_type(), apydantic.BaseModelclass or an annotation that mentions one anywhere inside itstyping.get_argswalk resolves topydantic_type(), an annotation that mentions both libraries is rejected, and anything else resolves tomsgspec_type().The result is memoised by hash/equality of the annotation (typing generics compare structurally); the memo lives for the process, because it is a pure factory: the same annotation always builds the same kind of
LoomType, and building one is not free — it compiles a decoder or aTypeAdapter. An annotation that is not hashable (for exampleAnnotatedmetadata carrying adict) simply skips the cache.- Parameters:
annotation (Any) – Any annotation, authored or compiler-generated.
- Returns:
The boundary type wrapping
annotation.- Raises:
UnsupportedBoundaryType –
annotationmentions both amsgspec.Structand apydantic.BaseModel.- Return type:
- loom.core.model.msgspec_type(annotation)[source]¶
Wrap a compiler-generated annotation in a msgspec-backed
LoomType.Accepts whatever
msgspec.json.Decoderaccepts: adefstructresult,dict[str, Any],list[...]or a scalar. Strictness is not checked because the annotation is not authored by a user.- Parameters:
annotation (Any) – Any annotation
msgspec.json.Decodercan build.- Returns:
The boundary type wrapping
annotation.- Raises:
UnsupportedBoundaryType –
msgspec.json.Decodercannot buildannotation.- Return type:
- loom.core.model.pydantic_type(annotation)[source]¶
Wrap any annotation pydantic can describe in a pydantic-backed
LoomType.Covers a
BaseModel, aRootModel, a parameterised generic model, apydantic.dataclassestype andlist/tuple/union of those — the sameTypeAdapter-backed implementation the authoredBaseModelcase inloom_type()uses. Strictness is not checked because the annotation is not necessarily an authored root symbol.- Parameters:
annotation (Any) – Any annotation
pydantic.TypeAdaptercan build.- Returns:
The boundary type wrapping
annotation.- Raises:
UnsupportedBoundaryType –
pydantic.TypeAdaptercannot buildannotation.- Return type: