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 | Provenance | Actions |
|---|---|---|---|---|
| metaorm-0.2.1.tar.gz | 12.4 kB | Without provenance | Details for metaorm-0.2.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| metaorm-0.2.1-py3-none-any.whl | Python 3 | none | any | Details for metaorm-0.2.1-py3-none-any.whl |
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}
|
Security for metaorm 0.2.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.