Skip to content

Tortoise-ORM

Data & AnalyticsDataPython

What it is

Tortoise-ORM is an easy-to-use, asyncio-supporting Object-Relational Mapper (ORM) for Python, designed to provide a simple, familiar interface similar to Django ORM but fully asynchronous.

Tortoise-ORM allows you to define models as Python classes, perform asynchronous CRUD operations, manage relationships, and use querysets similar to Django ORM. It supports migrations via Aerich and integrates seamlessly with async web frameworks.

Installation

pip install tortoise-orm[aiohttp,asyncpg]

Getting started

The smallest useful thing you can do with it, and what each part means.

Defining a simple model
from tortoise import Tortoise, fields, models

class User(models.Model):
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=50)
    email = fields.CharField(max_length=100, unique=True)
Defines a `User` model with id, name, and unique email fields using Tortoise ORM.
Initializing the ORM and connecting to the database
import asyncio

async def init():
    await Tortoise.init(db_url='sqlite://db.sqlite3', modules={'models': ['__main__']})
    await Tortoise.generate_schemas()

asyncio.run(init())
Initializes Tortoise ORM with a SQLite database and generates database tables based on the models.

Advanced usage

Where the library earns its place over a simpler alternative.

Creating a record asynchronously
async def create_user():
    user = await User.create(name='Alice', email='alice@example.com')
    print(user.id)

asyncio.run(create_user())
Creates a new user record asynchronously and prints its auto-generated ID.
Querying data
async def get_users():
    users = await User.filter(name='Alice')
    for user in users:
        print(user.name, user.email)

asyncio.run(get_users())
Fetches all users with the name 'Alice' and prints their details asynchronously.
Updating a record
async def update_user():
    user = await User.get(id=1)
    user.name = 'Bob'
    await user.save()

asyncio.run(update_user())
Fetches a user by ID, updates the name, and saves the changes asynchronously.
Deleting a record
async def delete_user():
    user = await User.get(id=1)
    await user.delete()

asyncio.run(delete_user())
Fetches a user by ID and deletes it from the database asynchronously.
Defining relationships
class Post(models.Model):
    id = fields.IntField(pk=True)
    title = fields.CharField(max_length=100)
    author = fields.ForeignKeyField('models.User', related_name='posts')
Defines a `Post` model with a foreign key relationship to the `User` model.
Querying related objects
async def get_user_posts():
    user = await User.get(id=1)
    posts = await user.posts.all()
    for post in posts:
        print(post.title)

asyncio.run(get_user_posts())
Fetches all posts authored by a specific user using the related name.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

tortoise.exceptions.DoesNotExist
Occurs when querying a model instance that does not exist. Use try/except or `.first()` to avoid exceptions.
tortoise.exceptions.IntegrityError
Raised when unique constraints or foreign key constraints are violated. Ensure input data respects constraints.
OperationalError
Occurs when the database connection fails. Check DB URL, network, or migrations.

Best practices

  • Use async/await syntax consistently when interacting with the database.
  • Define related_name for relationships for cleaner reverse lookups.
  • Use Aerich for database migrations to track schema changes.
  • Keep models modular and organized per app/module.
  • Handle exceptions such as DoesNotExist and IntegrityError when performing CRUD operations.

Background

Why it exists, and what it was reacting to.

Tortoise-ORM was created to offer a lightweight, asynchronous ORM for Python developers using async frameworks like FastAPI and Starlette. It emphasizes simplicity, performance, and developer-friendly APIs, supporting multiple database backends including SQLite, PostgreSQL, and MySQL.