Tortoise ORM: The Next-Gen Python ORM Everyone’s Talking About
Daniel Hayes
Full-Stack Engineer · Leapcell

Tortoise ORM: A Powerful Object Relational Mapper Based on asyncio
Tortoise ORM is an easy-to-use asyncio ORM (Object Relational Mapper) for Python, inspired by Django ORM. It borrows the design concept of Django ORM. It not only supports the processing of traditional tabular data but also can efficiently manage relational data. In terms of performance, it is not inferior to other Python ORMs.
Supported Databases
Tortoise ORM currently supports multiple mainstream databases:
- SQLite: Driven by
aiosqlite
, suitable for lightweight application scenarios. - PostgreSQL: The version should be >= 9.4, supporting
asyncpg
(asynchronous mode) orpsycopg
(synchronous mode) drivers. - MySQL/MariaDB: Achieves efficient connection with the help of the
asyncmy
driver. - Microsoft SQL Server: Completes data interaction through the
asyncodbc
driver.
Environment Configuration and Installation
- Install Tortoise ORM
pip install tortoise-orm
- Install the corresponding database driver
- PostgreSQL (Asynchronous):
pip install tortoise-orm[asyncpg]
- MySQL/MariaDB:
pip install tortoise-orm[asyncmy]
- SQLite: Supported by default, no additional driver installation is required
Database Connection Configuration
SQLite
The connection string format is sqlite://DB_FILE
. For example, if the database file is /data/DB.sqlite3
, the complete connection string is sqlite:///data/db.sqlite3
(note the three slashes).
MySQL
The connection string format: mysql://user:password@host:3306/somedb
, parameter description:
user
: Database usernamepassword
: User passwordhost
: Database host addressport
: Database port (default is 3306)database
: The name of the database to connect to
PostgreSQL
- Asynchronous mode:
asyncpg://postgres:pass@db.host:5432/somedb
- Synchronous mode:
psycopg://postgres:pass@db.host:5432/somedb
Microsoft SQL Server
The connection string format: mssql://user:pass@host:1433/db?driver=theodbcdriver
, parameter description:
user
: Usernamepassword
: Passwordhost
: Host addressport
: Port (default is 1433)database
: Database namedriver
: ODBC driver name, which needs to be configured in theodbcinst.ini
file
Database Creation and Initialization
from tortoise import Tortoise, run_async async def init(): # Use the SQLite database, and the file name is db.sqlite3 # And specify the application name containing the model as "models" await Tortoise.init( db_url='sqlite://db.sqlite3', modules={'models': ['models']} ) # Generate the database table structure await Tortoise.generate_schemas() # safe parameter: When set to True, the table will only be created if it does not exist run_async(init()) # Automatically handle the context and close the database connection after the operation ends
If using the MySQL database, you need to install the tortoise-orm[aiomysql]
dependency first.
Model Definition
from tortoise.models import Model from tortoise import fields from tortoise.manager import Manager class Team(Model): id = fields.IntField(pk=True) name = fields.TextField() class Meta: abstract = False # Whether it is an abstract class, if True, no data table will be generated table = "team" # Table name, if not set, the class name will be used by default table_description = "" # Table comment unique_together = () # Composite unique index indexes = () # Composite non-unique index ordering = [] # Default sorting manager = Manager # Custom manager
Field Types
Data Fields
from tortoise import fields fields.Field( source_field=None, # Custom database column name generated=False, # Whether it is automatically generated by the database pk=False, # Whether it is the primary key null=False, # Whether the field can be empty default=None, # Default value unique=False, # Whether the value is unique index=False, # Whether to create an index description=None, # Field description validators=None # List of validators )
Relationship Fields
- Foreign Key Field
fields.ForeignKeyField( model_name, # Associated model name, in the format of {app}.{models} related_name=None, # Reverse resolution attribute name on_delete='CASCADE', # Deletion strategy, optional values: CASCADE, RESTRICT, SET_NULL, SET_DEFAULT db_constraint=True, # Whether to create a foreign key constraint in the database )
- One-to-One Field
fields.OneToOneField( model_name, related_name=None, on_delete='CASCADE', db_constraint=True )
- Many-to-Many Field
fields.ManyToManyField( model_name, through=None, # Intermediate table forward_key=None, # Forward lookup key backward_key='', # Reverse lookup key related_name='', on_delete='CASCADE', db_constraint=True )
Query Operations
The model provides multiple query methods:
filter(*args, **kwargs)
: Filter data according to conditionsexclude(*args, **kwargs)
: Exclude data that meets the conditionsall()
: Get all datafirst()
: Get the first piece of dataannotate()
: Aggregate query
The query conditions supported by the filter
method:
- Range query:
in
,not_in
,gte
,gt
,lte
,lt
,range
- Null value query:
isnull
,not_isnull
- String query:
contains
,icontains
,startswith
,istartswith
,endswith
,iendswith
,iexact
- Full-text search:
search
Here are some specific query examples:
Simple Query Example
Suppose the Team
model has been defined:
from tortoise import run_async from models import Team # Assume the model is defined in the models.py file async def simple_query(): # Get all Team records all_teams = await Team.all() print("All teams:", all_teams) # Get the first Team record first_team = await Team.first() print("The first team:", first_team) # Filter according to the condition and get the team whose name is "Team A" filtered_teams = await Team.filter(name="Team A") print("Teams named Team A:", filtered_teams) run_async(simple_query())
Range Query Example
from tortoise import run_async from models import Team async def range_query(): # Query teams with id greater than 5 greater_than_5 = await Team.filter(id__gt=5) print("Teams with id greater than 5:", greater_than_5) # Query teams with id between 2 and 8 (including 2 and 8) in_range = await Team.filter(id__range=(2, 8)) print("Teams with id between 2 and 8:", in_range) # Query teams whose id is not in [1, 3, 5] not_in_list = await Team.filter(id__not_in=[1, 3, 5]) print("Teams whose id is not in [1, 3, 5]:", not_in_list) run_async(range_query())
String Query Example
from tortoise import run_async from models import Team async def string_query(): # Query teams whose name contains the string "team" (case-insensitive) contains_team = await Team.filter(name__icontains="team") print("Teams whose name contains team (case-insensitive):", contains_team) # Query teams whose name starts with "A" (case-sensitive) startswith_A = await Team.filter(name__startswith="A") print("Teams whose name starts with A:", startswith_A) # Query teams whose name ends with "B" (case-insensitive) endswith_B = await Team.filter(name__iendswith="B") print("Teams whose name ends with B (case-insensitive):", endswith_B) run_async(string_query())
For more detailed usage, please refer to the Tortoise ORM Official Documentation.
Leapcell: The Best of Serverless Web Hosting
Finally, I would like to recommend a platform that is most suitable for deploying Python services: Leapcell
🚀 Build with Your Favorite Language
Develop effortlessly in JavaScript, Python, Go, or Rust.
🌍 Deploy Unlimited Projects for Free
Only pay for what you use—no requests, no charges.
⚡ Pay-as-You-Go, No Hidden Costs
No idle fees, just seamless scalability.
🔹 Follow us on Twitter: @LeapcellHQ