MetaORM
Async repository layer over SQLModel. Define a table, a repository with keyword arguments, and you have a complete async CRUD layer.
- Minimal API —
create_item,get_items,update_items,delete_items. That's it. - Built-in DTO mapping — return table instances directly or map to separate Pydantic models.
- Intuitive transactions — every CRUD call runs in a transaction; explicit
transaction()context manager for custom scopes. - Nested transactions (savepoints) —
nested_transaction()allows partial rollback inside a shared transaction. - Multi-repo atomic transactions —
RepositoriesContainerlets several repositories share one atomic transaction. - Filters, pagination, sorting — powered by
pydantic-filters. - Eager loading — pass SQLAlchemy
joinedload/selectinloadviaoptions.
Install
pip install metaorm
Requires Python >=3.12.
Note:
metaormdepends on a patched version ofpydantic-filtersthat is not yet available on PyPI. The dependency is pulled automatically from GitHub during installation.
Quick start
from metaorm import BaseFilter, BaseRepository, BaseTable, RepositorySettings, Field
class UserTable(BaseTable, table=True):
__tablename__ = "users"
id: int | None = Field(default=None, primary_key=True)
name: str
email: str = Field(unique=True)
class UserFilter(BaseFilter):
name: str | None = None
email: str | None = None
class UserRepository(BaseRepository, table=UserTable, filter_=UserFilter):
pass
async def main():
repo = UserRepository(
settings=RepositorySettings(dsn="sqlite+aiosqlite:///:memory:"),
)
await repo.create_tables()
user = await repo.create_item(UserTable(name="Alice", email="alice@example.com"))
print(user.id, user.name)
all_users = [u async for u in repo.get_items()]
print(len(all_users))
Repository API
Subclass BaseRepository with keyword arguments table, filter_, and optionally dto:
class MyRepository(BaseRepository, table=MyTable, filter_=MyFilter):
pass # returns table instances directly
class MyRepositoryWithDto(BaseRepository, table=MyTable, filter_=MyFilter, dto=MyDto):
pass # maps rows to MyDto
Keyword arguments are checked at class-definition time. If you forget table or filter_, Python raises TypeError immediately. table= must still be provided on the first subclass in the hierarchy.
Constructor
# Simple — container is created internally
repo = MyRepository(settings=RepositorySettings(dsn="..."))
# Advanced — share a container for atomic multi-repo transactions
container = RepositoriesContainer(settings=settings)
repo = MyRepository(container=container)
Methods
| Method | Signature | Description |
|---|---|---|
create_tables |
async () -> None |
Creates the table in the database. |
create_item |
async (item) -> Any |
Inserts one row. Returns the table instance or DTO when dto= is set. |
get_items |
async (filter_=None, pagination=None, sort=None, options=None) -> AsyncGenerator[Any] |
Streams matching rows. options accepts SQLAlchemy eager-loading strategies such as joinedload. |
get_items_count |
async (filter_=None) -> int |
Returns the number of matching rows. |
update_items |
async (filter_=None, options=None, **values) -> AsyncGenerator[Any] |
Updates matching rows and yields the updated instances. |
delete_items |
async (filter_=None) -> None |
Deletes matching rows. |
transaction |
async contextmanager () -> AsyncSession |
Explicit transaction scope. Automatically used by all CRUD methods. Reuses an existing session when nested. |
nested_transaction |
async contextmanager () -> AsyncSession |
Creates a savepoint (nested transaction). Rolls back only the inner scope on error while leaving the outer transaction intact. |
Multi-repository transactions
Use RepositoriesContainer when you need a single atomic transaction spanning multiple repositories:
from metaorm import RepositoriesContainer
container = RepositoriesContainer(settings=settings)
user_repo = container.get_repository(UserRepository)
order_repo = container.get_repository(OrderRepository)
async with container.transaction():
user = await user_repo.create_item(UserTable(name="Alice"))
await order_repo.create_item(OrderTable(user_id=user.id, total=100))
container.transaction() stores the session in a contextvars.ContextVar. All repository operations within the async with block automatically reuse that session. Nested transaction() calls yield the same session.
For partial rollback inside a shared transaction use container.nested_transaction() (or repository.nested_transaction()). It creates a SQLAlchemy savepoint: an error inside the block rolls back only the savepoint, leaving the outer transaction open for further operations or commit.
More examples
See examples/ for detailed usage patterns:
basic_usage.py— CRUD with tables directlydto_usage.py— DTO mapping viadto=keywordtransactions.py— Explicit transaction managementnested_transactions.py— Savepoints and partial rollbackfilter_usage.py— Query filters, pagination and sortingrelationships.py— Eager loading withjoinedloadcontainer_usage.py— Multi-repository atomic transactions
Exceptions
DatabaseException
├── NotFoundError
├── HaveNoSessionError
└── AlreadyExistsError
All repository methods raise DatabaseException subclasses or SQLAlchemy errors.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file metaorm-0.2.0.tar.gz.
File metadata
- Download URL: metaorm-0.2.0.tar.gz
- Upload date:
- Size: 12.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"42","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b76b0db491498c0328cdae1b752f87fdc83db76f1150c4e4f1ccba99005c9862
|
|
| MD5 |
c4ba73aa9bcf99ada2679f7eb9cd8a94
|
|
| BLAKE2b-256 |
91210133a834926acd44218f1c370fb8e45f7be413fc53b0beffcdcb9d179865
|
File details
Details for the file metaorm-0.2.0-py3-none-any.whl.
File metadata
- Download URL: metaorm-0.2.0-py3-none-any.whl
- Upload date:
- Size: 7.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"42","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
09744359f2c6e96db1ce89fea6ba964bf83063f4221afb88cd9f665f645e12d6
|
|
| MD5 |
70dc53b476f321dad7bcaa43c7a1fa8d
|
|
| BLAKE2b-256 |
ff1740743bad5db407e1224cfe23350ab3a67dcc61ef8ec89aff7b35db276c99
|