Tortoise-ORM

Language: Python

Data

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.

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.

Installation

pip: pip install tortoise-orm[aiohttp,asyncpg]
conda: conda install -c conda-forge tortoise-orm

Usage

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.

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.

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.

Error Handling

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.