Tortoise ORM migrations with aerich

Ashfak TM
3 min readMay 8, 2021

Tortoise ORM

Tortoise ORM is an easy-to-use asyncio ORM (Object Relational Mapper) inspired by Django. Tortoise ORM was built with relations in mind and admiration for the excellent and popular Django ORM. It’s engraved in its design that you are working not with just tables, you work with relational data.

Tortoise ORM is a young project and breaking changes are to be expected.

https://tortoise-orm.readthedocs.io/en/latest/

Aerich

Aerich is a database migrations tool for Tortoise-ORM, which is like alembic for SQLAlchemy, or like Django ORM with its own migration solution.

https://github.com/tortoise/aerich

Setup

For this example, I am creating a contact book app that can be used to create new contacts and in our database and view them. Firstly we need to install fast API, tortoise ORM, and aerich in our python environment. I am using the PostgreSQL database so I need async implementation of the database interface (Here we are using asyncpg).

pip install fastapi
pip install uvicorn[standard]
pip install tortoise-orm[asyncpg]
pip install aerich

The file structure for this example fast API app will look like this

├── crud.py        # contains coroutines for basic crud operations
├── database.py # db config
├── main.py # fast API endpoints
├── models.py # tortoise models

Now create one database table named contact where we will save all our contacts. The tortoise model will look like this

# models.pyfrom tortoise.models import Model
from tortoise import fields
class Contact(Model):
name = fields.CharField(max_length=255)
phone = fields.CharField(max_length=20)
def __str__(self):
return self.name

Now we need to create some configuration for our database as follows

# database.pyTORTOISE_ORM = {
"connections": {
"default": "postgres://user:pass@localhost:5432/tuts"
},
"apps": {
"contact": {
"models": [
"models", "aerich.models"
],
"default_connection": "default",
},
},
}

For bigger applications with multiple apps, we can specify each app in the configuration as we specified in the contact app above. remember we only need to specify aerich.model only in one of our app.

now we have created our model and added orm configuration lets initialize aerich to create our first migration.

$ aerich init -t database.TORTOISE_ORM
Success create migrate location ./migrations
Success generate config file aerich.ini

Now we have initialized the config file and migrations location. So next step is to make the migration file for our contact app

$ aerich init-db
Success create app migrate location migrations/contact
Success generate schema for app "contact"

At this point, we have created migration for our app and we can see the migration file in the directory.

$ tree migrations/
migrations/
└── contact
└── 0_20210508115443_init.sql
1 directory, 1 file

So everything is working great, now let's update our models and make new migration, for this I have added one additional column for our contact table

email = fields.CharField(max_length=100)

lets migrate

$ aerich migrate --name add_email
Success migrate 1_20210508120617_add_email.sql
$ tree migrations/
migrations/
└── contact
├── 0_20210508115443_init.sql
└── 1_20210508120617_add_email.sql
1 directory, 2 files

for convenience, I have added a name for the migration file, In order to reflect the new changes in our database let's apply these migrations

$ aerich upgrade
Success upgrade 1_20210508120617_add_email.sql

We can also roll back our changes by downgrading to a specific version by running aerich downgrade

You can find the source code for this example here: https://gitlab.com/ashfak123/tortoise-migration-example

--

--