DEV Community

Cover image for Why Developers Are Choosing Tortoise ORM as Python’s Modern ORM
Leapcell
Leapcell

Posted on

2 1 1 1 1

Why Developers Are Choosing Tortoise ORM as Python’s Modern ORM

Image description

Leapcell: The Best of Serverless Web Hosting

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) or psycopg (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

  1. Install Tortoise ORM
pip install tortoise-orm
Enter fullscreen mode Exit fullscreen mode
  1. Install the corresponding database driver

    • PostgreSQL (Asynchronous):
    pip install tortoise-orm[asyncpg]
    
- **MySQL/MariaDB**:
Enter fullscreen mode Exit fullscreen mode
```bash
pip install tortoise-orm[asyncmy]
```
Enter fullscreen mode Exit fullscreen mode
- **SQLite**: Supported by default, no additional driver installation is required
Enter fullscreen mode Exit fullscreen mode

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 username
  • password: User password
  • host: Database host address
  • port: 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: Username
  • password: Password
  • host: Host address
  • port: Port (default is 1433)
  • database: Database name
  • driver: ODBC driver name, which needs to be configured in the odbcinst.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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
)
Enter fullscreen mode Exit fullscreen mode

Relationship Fields

  1. 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
)
Enter fullscreen mode Exit fullscreen mode
  1. One-to-One Field
fields.OneToOneField(
    model_name,
    related_name=None,
    on_delete='CASCADE',
    db_constraint=True
)
Enter fullscreen mode Exit fullscreen mode
  1. 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
)
Enter fullscreen mode Exit fullscreen mode

Query Operations

The model provides multiple query methods:

  • filter(*args, **kwargs): Filter data according to conditions
  • exclude(*args, **kwargs): Exclude data that meets the conditions
  • all(): Get all data
  • first(): Get the first piece of data
  • annotate(): 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())
Enter fullscreen mode Exit fullscreen mode

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())
Enter fullscreen mode Exit fullscreen mode

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())
Enter fullscreen mode Exit fullscreen mode

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

Image description

🚀 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.

Image description

📖 Explore Our Documentation

🔹 Follow us on Twitter: @LeapcellHQ

Heroku

Amplify your impact where it matters most — building exceptional apps.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (0)

Postmark Image

"Please fix this..."

Focus on creating stellar experiences without email headaches. Postmark's reliable API and detailed analytics make your transactional emails as polished as your product.

Start free

👋 Kindness is contagious

Explore a trove of insights in this engaging article, celebrated within our welcoming DEV Community. Developers from every background are invited to join and enhance our shared wisdom.

A genuine "thank you" can truly uplift someone’s day. Feel free to express your gratitude in the comments below!

On DEV, our collective exchange of knowledge lightens the road ahead and strengthens our community bonds. Found something valuable here? A small thank you to the author can make a big difference.

Okay