Many-to-Many (ORM)
The ORM module provides a ManyToMany descriptor and manager API for many-to-many relationships. Junction tables are created automatically when you call SqliterDB.create_table() on a model that defines an M2M field.
from sqliter.orm import BaseDBModel, ManyToMany
Sources: sqliter/orm/m2m.py, sqliter/orm/registry.py, sqliter/sqliter.py
ManyToMany[T]
Descriptor used on ORM models.
class ManyToMany(Generic[T]):
def __init__(
self,
to_model: type[T] | str,
*,
through: str | None = None,
related_name: str | None = None,
symmetrical: bool = False,
) -> None:
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
to_model | type[T] | str | required | Related model or forward ref |
through | str | None | None | Custom junction table name |
related_name | str | None | None | Reverse accessor name |
symmetrical | bool | False | Self-referential symmetry |
Notes:
- When
symmetrical=Trueandto_modelis the same class, SQLiter stores a single row per pair and returns the relationship from either side. No reverse accessor is created for symmetrical self-relations. to_modelcan be a string forward ref. The relationship resolves when the target model class is registered.- Reverse accessors are created automatically when
related_nameis set or auto-generated.
ManyToManyManager
Returned when accessing the descriptor from an instance.
tags = article.tags
Methods:
add(*instances) -> Noneremove(*instances) -> Noneclear() -> Noneset(*instances) -> Nonefetch_all() -> list[T]fetch_one() -> T | Nonecount() -> intexists() -> boolfilter(**kwargs) -> QueryBuilder[Any]
All methods require a valid db_context, which is set on instances returned from SqliterDB operations.
ReverseManyToMany
Reverse accessor descriptor automatically installed on the target model unless suppressed (symmetrical self-ref).
articles = tag.articles.fetch_all()
PrefetchedM2MResult
Returned when accessing an M2M relationship that was loaded via prefetch_related(). Wraps a cached list of related instances and provides the same interface as ManyToManyManager.
from sqliter.orm.m2m import PrefetchedM2MResult
Read methods (served from cache, no DB query):
fetch_all() -> list[T]fetch_one() -> T | Nonecount() -> intexists() -> bool
Write methods (delegated to the real ManyToManyManager):
add(*instances) -> Noneremove(*instances) -> Noneclear() -> Noneset(*instances) -> None
Filter (falls back to a real DB query via the manager):
filter(**kwargs) -> QueryBuilder[Any]
Example:
articles = db.select(Article).prefetch_related("tags").fetch_all()
guide = articles[0]
isinstance(guide.tags, PrefetchedM2MResult) # True
guide.tags.count() # served from cache
guide.tags.add(new_tag) # delegates to ManyToManyManager
PrefetchedResult (Reverse FK)
Returned when accessing a reverse FK relationship that was loaded via prefetch_related(). Wraps a cached list of related instances.
from sqliter.orm.query import PrefetchedResult
Read methods (served from cache):
fetch_all() -> list[BaseDBModel]fetch_one() -> BaseDBModel | Nonecount() -> intexists() -> bool
Filter (falls back to a real DB query via ReverseQuery):
filter(**kwargs) -> ReverseQuery
Junction Tables
By default, the junction table name is generated from the two table names in alphabetical order (e.g., articles_tags). Use through to override it.
Junction tables include:
CASCADEFK constraints- A unique constraint on the pair
- Indexes on both FK columns