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
Metadata
Release files for metaorm 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| metaorm-0.2.1.tar.gz | 12.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| metaorm-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 20.2 kB
Release files / metaorm-0.2.1.tar.gz
| Download URL | metaorm-0.2.1.tar.gz |
|---|---|
| Size | 12.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
23edbc6b6a58a33284ae60234b1bd3c30d84c41db79c40772886508da3ff9893
|
|
BLAKE2b-256 checksum How to use checksums |
8645d02c83470e2534027b25ad14fa2fceaa88ae5b1742175774153d08203aba
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","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}
|
Release files / metaorm-0.2.1-py3-none-any.whl
| Download URL | metaorm-0.2.1-py3-none-any.whl |
|---|---|
| Size | 7.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4a3f7572ab21a58c3f54bbcc1578885ed493a16b13d339d503b2819b85cd6779
|
|
BLAKE2b-256 checksum How to use checksums |
4e8100f3a4462ea80145fc6fa2a77094e2e12c0634e7ea06dd33c4db55aacb8f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","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}
|