Skip to content

ORM Mode

The ORM module extends the legacy sqliter.model with lazy loading, reverse relationships, and a descriptor-based ForeignKey class.

from sqliter.orm import BaseDBModel, ForeignKey, ManyToMany, ModelRegistry

Sources: sqliter/orm/model.py, sqliter/orm/fields.py, sqliter/orm/m2m.py, sqliter/orm/registry.py, sqliter/orm/query.py

See also: Guide -- ORM Foreign Keys, Guide -- Many-to-Many

Note

The ORM module is an alternative to the legacy sqliter.model import. Both modes use the same SqliterDB class for database operations. The key difference is how foreign key relationships are defined and accessed.


Legacy vs ORM Mode

FeatureLegacy (sqliter.model)ORM (sqliter.orm)
FK definitionForeignKey() factory functionForeignKey descriptor class
M2M definitionNot availableManyToMany descriptor class
FK accessManual ID lookupLazy loading via book.author.name
Reverse queriesNot availableauthor.books.fetch_all()
Eager loadingNot availableselect_related("author")
Importfrom sqliter.model import BaseDBModelfrom sqliter.orm import BaseDBModel

orm.BaseDBModel

Extends the legacy BaseDBModel with ORM features.

from sqliter.orm import BaseDBModel

Additional Class Variables:

AttributeTypeDescription
fk_descriptorsClassVar[dict[str, ForeignKey]]FK descriptors for this class (not inherited)

Additional Instance Fields:

FieldTypeDefaultDescription
db_contextAny | NoneNoneDatabase connection for lazy loading (excluded from serialization)

Overridden Behavior

__init__(**kwargs)

Converts FK field values to _id fields before Pydantic validation. Accepts model instances, integer IDs, or None.

# All equivalent:
book = Book(author=author_instance)  # Model instance
book = Book(author=42)               # Integer ID
book = Book(author_id=42)            # Direct _id field

model_dump(**kwargs)

Excludes FK descriptor fields (like author) from serialization. Only the _id fields (like author_id) are included.

__getattribute__(name)

Intercepts FK field access to provide lazy loading. Returns a LazyLoader that queries the database on first attribute access. Returns None for null FK values.

__setattr__(name, value)

Intercepts FK field assignment. Accepts model instances, integer IDs, or None. Clears the FK cache when an _id field changes.


ForeignKey[T]

Generic descriptor class for FK fields providing lazy loading and type safety.

from sqliter.orm import ForeignKey
class ForeignKey(Generic[T]):
    def __init__(
        self,
        to_model: type[T],
        *,
        on_delete: FKAction = "RESTRICT",
        on_update: FKAction = "RESTRICT",
        null: bool = False,
        unique: bool = False,
        related_name: str | None = None,
        db_column: str | None = None,
    ) -> None:

Parameters:

ParameterTypeDefaultDescription
to_modeltype[T]requiredThe related model class
on_deleteFKAction"RESTRICT"Action when related record is deleted
on_updateFKAction"RESTRICT"Action when related record's PK is updated
nullboolFalseWhether FK can be null
uniqueboolFalseWhether FK must be unique (one-to-one)
related_namestr | NoneNoneName for reverse relationship (auto-generated if None)
db_columnstr | NoneNoneCustom column name for _id field

Example:

from sqliter.orm import BaseDBModel, ForeignKey


class Author(BaseDBModel):
    name: str


class Book(BaseDBModel):
    title: str
    author: ForeignKey[Author] = ForeignKey(
        Author, on_delete="CASCADE"
    )

ForeignKeyDescriptor

Caution

ForeignKeyDescriptor is a backwards-compatibility alias for ForeignKey. Use ForeignKey directly.


ManyToMany[T]

Descriptor class for many-to-many relationships (ORM mode only).

See Many-to-Many for full usage and manager API.

Notes

  • Accessing the descriptor on an instance returns a ManyToManyManager.
  • Reverse accessors are auto-generated when related_name is omitted.
  • Self-referential symmetrical=True relationships store a single row per pair and suppress the reverse accessor.

LazyLoader[T]

Transparent proxy that lazy-loads a related object when its attributes are accessed. Returned by FK field access on ORM model instances.

class LazyLoader(Generic[T]):
    def __init__(
        self,
        instance: object,
        to_model: type[T],
        fk_id: int | None,
        db_context: SqliterDB | None,
    ) -> None:

Properties

PropertyTypeDescription
db_contextobjectThe database context (for validity checking)

Methods

__getattr__(name)

Loads the related object from the database on first access, then delegates attribute access to it. Raises AttributeError if the FK is null or the object is not found.

__eq__(other)

Compares based on the loaded object. Loads the object if not already cached. Returns True if other equals the loaded object, or if both are None.

__repr__()

Returns <LazyLoader unloaded for ModelName id=N> before loading, or <LazyLoader loaded: <repr>> after loading.

Note

LazyLoader is unhashable (__hash__ = None) because its equality depends on mutable cached state.


ModelRegistry

Class-level registry for ORM models, FK relationships, and pending reverse relationships. Uses automatic setup via the descriptor __set_name__ hook -- no manual registration required.

from sqliter.orm import ModelRegistry

register_model()

Register a model class in the global registry.

@classmethod
def register_model(
    cls,
    model_class: type[Any],
) -> None:

Parameters:

ParameterTypeDescription
model_classtype[Any]The model class to register

Also processes any pending reverse relationships for this model.

register_foreign_key()

Register a FK relationship.

@classmethod
def register_foreign_key(
    cls,
    from_model: type[Any],
    to_model: type[Any],
    fk_field: str,
    on_delete: str,
    related_name: str | None = None,
) -> None:

Parameters:

ParameterTypeDescription
from_modeltype[Any]The model with the FK field
to_modeltype[Any]The referenced model
fk_fieldstrName of the FK field
on_deletestrDelete action
related_namestr | NoneReverse relationship name

get_model()

Get a model class by table name.

@classmethod
def get_model(
    cls,
    table_name: str,
) -> type[Any] | None:

Parameters:

ParameterTypeDescription
table_namestrTable name to look up

Returns:

type[Any] | None -- The model class, or None if not found.

get_foreign_keys()

Get FK relationships for a model by table name.

@classmethod
def get_foreign_keys(
    cls,
    table_name: str,
) -> list[dict[str, Any]]:

Parameters:

ParameterTypeDescription
table_namestrTable name to look up

Returns:

list[dict[str, Any]] -- List of FK relationship dictionaries with keys: to_model, fk_field, on_delete, related_name.

add_reverse_relationship()

Add a reverse relationship descriptor to the target model. Called automatically by ForeignKey.__set_name__() during class creation. If the target model does not exist yet, stores the relationship as pending.

@classmethod
def add_reverse_relationship(
    cls,
    from_model: type[Any],
    to_model: type[Any],
    fk_field: str,
    related_name: str,
) -> None:

Parameters:

ParameterTypeDescription
from_modeltype[Any]Model with the FK (e.g., Book)
to_modeltype[Any]Referenced model (e.g., Author)
fk_fieldstrFK field name (e.g., "author")
related_namestrReverse relationship name (e.g., "books")

ReverseQuery

Query builder for reverse relationships. Returned when accessing a reverse relationship on a model instance (e.g., author.books). Delegates to QueryBuilder for SQL execution.

class ReverseQuery:
    def __init__(
        self,
        instance: HasPKAndContext,
        to_model: type[BaseDBModel],
        fk_field: str,
        db_context: SqliterDB | None,
    ) -> None:

filter()

Add filter conditions to the reverse query.

def filter(
    self,
    **kwargs: Any,
) -> ReverseQuery:

Returns: ReverseQuery for method chaining.

limit()

Set a limit on query results.

def limit(self, count: int) -> ReverseQuery:

Returns: ReverseQuery for method chaining.

offset()

Set an offset on query results.

def offset(self, count: int) -> ReverseQuery:

Returns: ReverseQuery for method chaining.

fetch_all()

Execute the query and return all matching related objects.

def fetch_all(self) -> list[BaseDBModel]:

Returns: list[BaseDBModel] -- Related model instances.

fetch_one()

Execute the query and return a single result.

def fetch_one(self) -> BaseDBModel | None:

Returns: BaseDBModel | None -- A single related instance or None.

count()

Count the number of related objects.

def count(self) -> int:

Returns: int -- Number of matching related objects.

exists()

Check if any related objects exist.

def exists(self) -> bool:

Returns: bool -- True if at least one related object exists.

Example:

# Fetch all books by an author
books = author.books.fetch_all()

# Filter and count
count = author.books.filter(title__contains="Python").count()

# Check existence
has_books = author.books.exists()

ReverseRelationship

Descriptor that returns a ReverseQuery when accessed on a model instance. Added automatically to models by ForeignKey.__set_name__() during class creation.

class ReverseRelationship:
    def __init__(
        self,
        from_model: type[BaseDBModel],
        fk_field: str,
        related_name: str,
    ) -> None:

Descriptor Protocol:

  • On a class: returns the ReverseRelationship descriptor itself.
  • On an instance: returns a ReverseQuery bound to that instance.
  • __set__: Raises AttributeError. Reverse relationships are read-only.

Protocols

HasPK

Runtime-checkable protocol for objects that have a pk attribute. Used for duck-typed FK assignment.

@runtime_checkable
class HasPK(Protocol):
    pk: int | None

HasPKAndContext

Runtime-checkable protocol for model instances with pk and db_context. Used by ReverseQuery.

@runtime_checkable
class HasPKAndContext(Protocol):
    pk: int | None
    db_context: SqliterDB | None