Tortoise ORM
Async ORM for Python inspired by Django ORM. Built on asyncio — requires async context. Current version: 1.1.7 (Mar 2026). v1.0 released 2024 — first stable release after years of 0.x. Breaking changes from 0.x: connections.close_all() removed, ConnectionHandler uses per-instance ContextVar storage. FastAPI integration changed from register_tortoise() to RegisterTortoise context manager. Does not work on serverless/Vercel without workarounds.
Warnings
- breaking connections.close_all() removed in v1.0. Raises AttributeError.
- breaking Running multiple FastAPI apps in the same process (e.g., in tests) raises ConfigurationError: 'Global context fallback is already enabled'. Happens with lifespan-based testing.
- gotcha Must use 'modules' dict when calling Tortoise.init() — must list all model module paths explicitly. Missing a module means those models are unknown to Tortoise and relations fail silently.
- gotcha tortoise-orm does not work on serverless/Vercel without workarounds. Connection pooling assumes long-running process — serverless cold starts cause 'connection pool is closed' errors.
- gotcha generate_schemas=True in production silently drops and recreates tables if schema changes. Use Aerich (tortoise's migration tool) for production schema management.
- gotcha pydantic_model_creator() generates Pydantic models from Tortoise models. In v1.0, internal validator logic was cleaned up — custom PydanticMeta overrides may behave differently.
- gotcha DB-specific drivers must be installed separately. 'pip install tortoise-orm' alone raises ImproperlyConfigured when connecting to PostgreSQL or MySQL.
Install
-
pip install tortoise-orm -
pip install 'tortoise-orm[accel]' -
pip install 'tortoise-orm[asyncpg]'
Imports
- RegisterTortoise (FastAPI — v1.0+ recommended)
from contextlib import asynccontextmanager from fastapi import FastAPI from tortoise.contrib.fastapi import RegisterTortoise @asynccontextmanager async def lifespan(app: FastAPI): async with RegisterTortoise( app, db_url='postgres://user:pass@localhost/mydb', modules={'models': ['myapp.models']}, generate_schemas=True, ): yield app = FastAPI(lifespan=lifespan) - Tortoise.init (standalone scripts)
from tortoise import Tortoise async def main(): await Tortoise.init( db_url='sqlite://db.sqlite3', modules={'models': ['myapp.models']} ) await Tortoise.generate_schemas() user = await User.create(name='Alice') users = await User.filter(name='Alice').all() await Tortoise.close_connections() import asyncio asyncio.run(main())
Quickstart
# pip install 'tortoise-orm[asyncpg]'
from tortoise import Tortoise, fields
from tortoise.models import Model
import asyncio
class User(Model):
id = fields.IntField(pk=True)
name = fields.CharField(max_length=50)
email = fields.CharField(max_length=120, unique=True)
class Meta:
table = 'users'
async def main():
await Tortoise.init(
db_url='postgres://user:pass@localhost/mydb',
modules={'models': ['__main__']}
)
await Tortoise.generate_schemas()
# Create
user = await User.create(name='Alice', email='alice@example.com')
# Query
users = await User.filter(name='Alice').all()
user = await User.get(id=1)
user = await User.get_or_none(email='alice@example.com')
await Tortoise.close_connections()
asyncio.run(main())