Skip to main content

MetaORM

Async repository layer over SQLModel. Define a table, a repository with keyword arguments, and you have a complete async CRUD layer.

📖 Full documentation

  • 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 — RepositoriesContainer lets several repositories share one atomic transaction.
  • Filters, pagination, sorting — powered by pydantic-filters.
  • Eager loading — pass SQLAlchemy joinedload / selectinload via options.

Install

pip install metaorm

Requires Python >=3.12.

Note: metaorm depends on a patched version of pydantic-filters that 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:

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)

Source distribution for metaorm 0.2.1
File Size Uploaded
metaorm-0.2.1.tar.gz 12.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for metaorm 0.2.1
File Interpreter ABI Platform
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}

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page