Async ORM for Python — fully async from connection to migration
Project description
async-orm
An async fork of Masonite ORM — the same familiar API, fully async from connection to migration.
Fork notice: This project is based on masonite-orm v2.24.0 by Joe Mancuso, licensed under MIT. All connection, query, model, relationship, scope, schema, and migration layers have been converted to use
async/await.
What changed from Masonite ORM
| Layer | Original | This fork |
|---|---|---|
| Connections | Synchronous drivers | aiosqlite, aiomysql, asyncpg, aioodbc |
| QueryBuilder | .get(), .first(), .count() are sync |
Terminal methods return coroutines (await) |
| Models | User.all() returns a collection |
await User.all() returns a collection |
| Relationships | Sync eager loading | await User.with_("posts").find(1) |
| Schema/Blueprint | with schema.create(...) as table: |
async with schema.create(...) as table: |
| Migrations | def up(self): |
async def up(self): |
| CLI commands | Direct calls | asyncio.run() bridge |
Chain methods (where, order_by, select, limit, etc.) remain synchronous — only terminal methods that hit the database are async.
Installation
pip install masonite-orm-async
With optional Pydantic support:
pip install masonite-orm-async[pydantic]
Quick start
Models
from masoniteorm.models import Model
from masoniteorm.relationships import has_many
class User(Model):
__connection__ = "sqlite"
id: int
name: str
email: str
@has_many("id", "user_id")
def posts(self):
return Post
class Post(Model):
__connection__ = "sqlite"
id: int
user_id: int
title: str
body: str
Field annotations are optional but give you IDE autocomplete on user.name, user.email, etc. They are bare annotations with no default values — they don't affect runtime behavior.
Queries
# All terminal operations are awaited
users = await User.all()
user = await User.find(1)
user = await User.where("email", "alice@example.com").first()
# Chain methods are still synchronous
await User.where("active", True).order_by("name").limit(10).get()
# CRUD
user = await User.create({"name": "Alice", "email": "alice@example.com"})
user.name = "Alice Updated"
await user.save()
await user.delete()
# Eager loading
user = await User.with_("posts").find(1)
Migrations
from masoniteorm.migrations import Migration
class CreateUsersTable(Migration):
async def up(self):
async with self.schema.create("users") as table:
table.increments("id")
table.string("name")
table.string("email").unique()
table.timestamps()
async def down(self):
await self.schema.drop("users")
Running migrations programmatically
import asyncio
from masoniteorm.migrations import Migration
async def main():
migration = Migration(
connection="sqlite",
migration_directory="databases/migrations",
config_path="config",
)
await migration.create_table_if_not_exists()
await migration.migrate()
asyncio.run(main())
FastAPI integration
from contextlib import asynccontextmanager
from fastapi import FastAPI
from masoniteorm.migrations import Migration
@asynccontextmanager
async def lifespan(app: FastAPI):
migration = Migration(
connection="sqlite",
migration_directory="databases/migrations",
config_path="config",
)
await migration.create_table_if_not_exists()
await migration.migrate()
yield
from config import DB
await DB.close_all()
app = FastAPI(lifespan=lifespan)
@app.get("/users")
async def list_users():
users = await User.all()
return [u.model_dump() for u in users]
Sanic integration
from sanic import Sanic, json
from masoniteorm.migrations import Migration
app = Sanic("MyApp")
@app.before_server_start
async def setup(app, loop):
migration = Migration(
connection="sqlite",
migration_directory="databases/migrations",
config_path="config",
)
await migration.create_table_if_not_exists()
await migration.migrate()
@app.after_server_stop
async def teardown(app, loop):
from config import DB
await DB.close_all()
@app.get("/users")
async def list_users(request):
users = await User.all()
return json([u.serialize() for u in users])
Type hints
All generic classes are subscriptable at runtime and have .pyi stubs for full IDE/type-checker support:
from masoniteorm.collection import Collection
from masoniteorm.query import QueryBuilder
# IDE knows: users is Collection[User], user is User | None
users = await User.all() # → Collection[User]
user = await User.find(1) # → User | None
# Chain methods preserve the type
query = User.where("active", True).order_by("name").limit(10) # → QueryBuilder[User]
results = await query.get() # → Collection[User]
Pydantic integration (optional)
Auto-generate Pydantic schemas from your models:
# Get the Pydantic model class
UserSchema = User.to_schema() # → UserSchema(id: int | None, name: str | None, email: str | None)
# Serialize a model instance through Pydantic
user = await User.find(1)
data = user.model_dump() # → {"id": 1, "name": "Alice", "email": "alice@example.com"}
# Use the schema directly for validation
validated = UserSchema(name="Bob", email="bob@example.com")
If pydantic is not installed, model_dump() falls back to serialize().
Configuration
Create a config.py (or config/database.py) that exposes a DB object:
from masoniteorm.connections import ConnectionResolver
DATABASES = {
"default": "sqlite",
"sqlite": {
"driver": "sqlite",
"database": "app.db",
},
}
DB = ConnectionResolver().set_connection_details(DATABASES)
Set the config path via environment variable or pass it directly:
import os
os.environ["DB_CONFIG_PATH"] = "config"
Supported databases
| Database | Async driver | Connection key |
|---|---|---|
| SQLite | aiosqlite |
sqlite |
| MySQL | aiomysql |
mysql |
| PostgreSQL | asyncpg |
postgres |
| MSSQL | aioodbc |
mssql |
Transactions
from config import DB
# Context manager — auto-rollback on exception
async with DB.transaction():
user = await User.create({"name": "Alice", "email": "alice@example.com"})
await Post.create({"title": "Hello", "user_id": user.id})
# Manual control
await DB.begin_transaction()
await User.create({"name": "Bob", "email": "bob@example.com"})
await DB.commit() # or DB.rollback()
Connection resilience
Pool creation retries automatically with exponential backoff (configurable):
DATABASES = {
"default": "postgres",
"postgres": {
"driver": "postgres",
"host": "127.0.0.1",
"database": "mydb",
"user": "myuser",
"password": "mypass",
"port": 5432,
"connection_retries": 3, # retry pool creation (default: 3)
"connection_pool_timeout": 30, # seconds to wait for a connection (default: 30)
"connection_pooling_min_size": 1,
"connection_pooling_max_size": 10,
},
}
When the pool is exhausted, a PoolExhaustedError is raised instead of hanging:
from masoniteorm.exceptions import PoolExhaustedError
Testing
# ORM unit tests (SQLite, no dependencies)
python orm/test_async.py
# FastAPI demo tests
cd fastapi_demo && PYTHONPATH="../orm/src:." python -m pytest test_app.py -v
# Integration tests (requires Docker)
docker compose up -d --wait
PYTHONPATH="orm/src:." python -m pytest tests/ -v
# Load test
PYTHONPATH="orm/src:." python tests/load_test.py --db postgres --ops 1000 --concurrency 50
License
MIT — same as the original Masonite ORM. See LICENSE.
Credits
- Masonite ORM by Joe Mancuso and contributors — the original synchronous ORM this project is forked from.
Project details
Release history Release notifications | RSS feed
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 masonite_orm_async-1.2.0.tar.gz.
File metadata
- Download URL: masonite_orm_async-1.2.0.tar.gz
- Upload date:
- Size: 89.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9c33c45c064b09d67b1ae761e6692f89276756c1f4451dd2ba8c5cd32b38303c
|
|
| MD5 |
5c2e8fe3c4fc53864b47b7d87d818839
|
|
| BLAKE2b-256 |
5c546c073bfa107322868ebacda8cd9c105a6ace786f72e439536fcc544811f5
|
File details
Details for the file masonite_orm_async-1.2.0-py3-none-any.whl.
File metadata
- Download URL: masonite_orm_async-1.2.0-py3-none-any.whl
- Upload date:
- Size: 138.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1646a9cfc53720b141b6e457164b80c400374adee0754811cb7b4abe611335ed
|
|
| MD5 |
0ad7d891b82957ca997e96cff9619dd5
|
|
| BLAKE2b-256 |
d8be81224149c5a65c525814c12cc9ac2f858a0ac49449b8d9ecfd58320edce6
|