TortoiseORM is a modern, easy-to-use Object-Relational Mapping (ORM) library for Python that leverages the power of asyncio to provide high-performance database operations.
Unlike traditional ORMs that use synchronous operations, TortoiseORM enables you to work with databases asynchronously, making it ideal for modern web applications and APIs where responsiveness is crucial.
This tutorial will guide you through using TortoiseORM with SQLite.
Prerequisites
Before starting this tutorial, make sure you have installed Python 3.13 or a newer version. A basic understanding of Python programming and familiarity with the async/await concepts in Python are essential. While basic SQL knowledge can be helpful, it is not a strict requirement.
Step 1 — Setting up your TortoiseORM project
Before diving into database operations, you must set up your project environment. In this step, you'll organize your project directory, create a virtual environment to manage dependencies, and install TortoiseORM.
To get started, create a directory for your project and move into it:
Next, create a Python virtual environment to keep your project dependencies isolated:
Activate the virtual environment to start using it:
Now that your virtual environment is active, install TortoiseORM and pydantic:
This installs the latest version of TortoiseORM and Pydantic, which are necessary for model validation and serialization.
For this tutorial, you'll use SQLite as the database backend because it doesn't require a separate server installation and stores data in a single file, making it a convenient choice. With TortoiseORM set up, you’re now ready to create your first database connection.
Step 2 — Understanding TortoiseORM components and creating your first model
This section introduces the core components of TortoiseORM and guides you through creating your first database model. Understanding these elements is vital for developing efficient and scalable database-driven applications with TortoiseORM.
Before we dive into code, let's explore the key components of TortoiseORM:
- Tortoise: The main controller that manages connections and model registration
- Model: The base class for defining database models
- Field: Various field types for different data types
- QuerySet: Provides methods for database querying
- Connection: Manages database connection pooling and query execution
Let's create a basic script called database.py that sets up these components:
This script sets up the foundation for your TortoiseORM database.
The DB_URL defines the connection string for SQLite. TortoiseORM supports various databases, but we're using SQLite for simplicity.
The init_db() function initializes Tortoise with the database URL and registers the model modules. The modules parameter specifies where to find your model definitions. In this case, it will look for models in the models.py file.
Finally, the generate_schemas() method automatically creates tables based on your model definitions. The close_db() function properly closes database connections when you're done.
In this tutorial, you'll create a basic task management system. Let's start by defining a model for tasks. \
Create a file called models.py:
This code defines a Task model that maps to a 'tasks' table in the database.
The __tablename__ attribute is defined through the Meta class with the table property, specifying the table name as "tasks".
This model includes multiple fields, each with a specific data type to represent different attributes of a task:
id: A primary key that auto-increments.title: A string field that cannot be null (nullable=False).description: An optional text field for task details.is_completed: A boolean field with a default value ofFalse.due_date: An optional date field.priority: An integer field with a default value of 1.category: A string field with a default value of "General".created_at: A datetime field that automatically records when the task is created.modified_at: A datetime field that automatically updates when the task is modified.
The __str__ method returns a readable string representation of Task objects, making them easier to inspect in the console.
A notable feature of TortoiseORM is the pydantic_model_creator function, which generates Pydantic models for data validation and serialization. This is particularly useful when integrating with APIs like FastAPI. In this example, we create two versions:
Task_Pydantic: Represents the complete task model including read-only fields.TaskIn_Pydantic: Used for input validation when creating or updating tasks, excluding read-only fields likeidand timestamps.
Now, let's create a script to initialize the database. Create a file called init_db.py:
Since TortoiseORM is asynchronous, you must use asyncio to run the functions. The script creates the database and generates tables based on your model definitions.
Run this script to initialize the database:
You should see output similar to:
The db.sqlite3 file is created in your project directory, containing an empty 'tasks' table, ready for storing task data.
Step 3 — Adding data to your database
This section covers inserting records into an SQLite database using TortoiseORM's async API. You'll create task objects, store them in the database, and see how TortoiseORM translates Python objects into database entries.
With the Task model in place, it's time to add some data. Create a new file named add_data.py to get started:
In this code, you first call init_db() to ensure the database is properly initialized. Then, you create task objects using the Task.create() method, which is an asynchronous method that returns the created object after inserting it into the database.
The script uses the date and timedelta classes from the datetime module to set due dates for tasks. Each task is assigned a category as a string, a priority level, and other attributes.
The Task.create() method is an awaitable, so you use the await keyword to wait for the database operation to complete. This is a key aspect of TortoiseORM's asynchronous approach.
Finally, you close the database connections with close_db() to release resources.
Run this script to add the data:
You should see output like:
You can query this data in your database in the next step.
Step 4 — Querying data from your database
In this section, you'll learn how to fetch data from your SQLite database using TortoiseORM's async query API.
With tasks now stored in the database, let's write queries to retrieve them.
Create a new file called query_tasks.py with the following:
In this code, the Task.all() method retrieves all tasks from the database as a list of Task objects. Each object allows access to its attributes, such as title, category, and due_date.
Run this script to see all the tasks in your database:
You should see output like:
The output shows that TortoiseORM retrieves all tasks from the database and presents them in a structured, readable format, displaying key details like title, category, and due date.
In many cases, you may need to retrieve only the records that meet specific criteria. You can achieve this using queries like the following:
The filter() method allows you to define conditions for your query, similar to SQL's WHERE clause.
To control the order of the retrieved records, you can use the order_by() method:
If you need only a single record, use first() instead of all(). This retrieves the first matching result and avoids returning a list:
For more complex queries, TortoiseORM provides the Q object for combining conditions:
These query methods are essential for retrieving data with TortoiseORM.
Now that you can fetch records, the next step is updating existing records in your database.
Step 5 — Updating records in your database
In this section, you'll update existing records in your SQLite database using TortoiseORM. You'll retrieve objects from the database, modify their attributes, and save the changes, allowing you to manage and update your data efficiently.
The most common way to update data with TortoiseORM is to query for a record, modify its attributes, and commit the changes. To get started, create a file called update_tasks.py:
First, you query the database to find the task you want to update using get(). Following that, you modify the task's attributes directly, changing its priority. Finally, you save the changes with task.save(), which sends the UPDATE statement to the database.
Run this script:
You should see output like:
This approach, where you modify objects and let TortoiseORM handle the SQL generation, is both intuitive and powerful. It lets you update multiple attributes simultaneously, while TortoiseORM automatically creates the appropriate UPDATE statement.
You can modify multiple attributes of an object before saving:
TortoiseORM will generate a single UPDATE statement that changes all modified attributes.
If you need to update multiple records that meet specific criteria, use the update() method on the query object:
This method is more efficient for bulk updates since it executes a single SQL UPDATE statement instead of fetching and modifying each record individually.
Now that you can update records in the database, you will delete them next.
Step 6 — Deleting records from your database
In this section, you'll delete records from your SQLite database using TortoiseORM. You'll explore how to remove individual records and perform bulk deletions based on specific conditions.
A typical way to delete records is to retrieve the object and then use the delete() method.
To implement this, create a file called delete_tasks.py:
In this code, you first check how many tasks you have by using Task.all().count(). Then you find the specific task to remove with Task.get().
Once found, you delete it with task_to_delete.delete() and actually perform the deletion by awaiting this coroutine.
To confirm the deletion worked, you count the tasks again and list the remaining ones to see that "Grocery shopping" is no longer in the database.
Run this script:
You should see output like:
You can also delete records by primary key if you know it:
For cases where you need to delete multiple records that match certain criteria without retrieving them first, you can use the query's delete() method:
This method is more efficient than fetching objects and deleting them individually, especially when dealing with large numbers of records, because it executes a single DELETE statement with a WHERE clause instead of multiple statements.
With this, you now have a solid understanding of the basic CRUD operations with TortoiseORM.
Final thoughts
This tutorial has covered the essential features of TortoiseORM, from setting up an asynchronous database connection to defining models and performing CRUD operations with SQLite.
TortoiseORM’s integration with Pydantic and frameworks like FastAPI makes it a strong choice for API development, allowing for efficient data validation and serialization.
Now that you have a solid foundation, you can explore more advanced topics, such as defining relationships between tables, optimizing queries, and using model signals for event-driven actions.
To learn more, visit the official TortoiseORM documentation.