python

Ready to Supercharge Your FastAPI App with an Async ORM?

Tortoise ORM: A Robust Sidekick for Async Database Management in FastAPI

Ready to Supercharge Your FastAPI App with an Async ORM?

Building a FastAPI application? Well, you’re going to need to manage databases efficiently if you want to keep things fast and scalable. Let me tell you about a nifty tool you might want to use—Tortoise ORM. This ORM is designed to make your life easier, especially if you’re working with SQLite or PostgreSQL databases.

First off, why even bother with Tortoise ORM? Unlike traditional ORMs like SQLAlchemy, which are synchronous, Tortoise ORM is asynchronous. What does that mean for you? Basically, it leverages async/await syntax, making it a perfect companion to FastAPI’s async programming style. Faster performance and smooth operation—sounds like a win-win to me.

Now, want to integrate Tortoise ORM into your FastAPI application? It’s easier than you might think. Let’s break it down step-by-step.

First things first, you’ll need to install Tortoise ORM. On top of that, you’ll need an async driver for your chosen database. For SQLite, go with aiosqlite. For PostgreSQL, asyncpg is your bud.

pip install tortoise-orm aiosqlite

Now, let’s talk about configuration. Hooking up Tortoise ORM to your database involves using the register_tortoise function. For SQLite, it might look something like this:

from fastapi import FastAPI
from tortoise.contrib.fastapi import register_tortoise

app = FastAPI()

register_tortoise(
    app,
    db_url="sqlite://db.sqlite3",
    modules={"models": ["__main__"]},
    generate_schemas=True,
    add_exception_handlers=True
)

Cool, right? After that, it’s all about defining your models. These models are essentially blueprints for your database tables. They tell Tortoise ORM what data to store and how to structure it.

Here’s a simple model for a City:

from tortoise import fields
from tortoise.models import Model

class City(Model):
    id = fields.IntField(pk=True)
    name = fields.CharField(50)
    timezone = fields.CharField(50)

    class Meta:
        table_description = "Cities and their time zones"
        table = "cities"

Run your application, and Tortoise ORM will automatically generate the database schema. The database will align itself based on these models, thanks to generate_schemas=True.

Switching over to PostgreSQL? No problem. It’s a similar setup, but you need to install asyncpg:

pip install asyncpg

Then adjust your config like so:

from fastapi import FastAPI
from tortoise.contrib.fastapi import register_tortoise

app = FastAPI()

register_tortoise(
    app,
    db_url="postgres://user:password@localhost:5432/database",
    modules={"models": ["__main__"]},
    generate_schemas=True,
    add_exception_handlers=True
)

Alright, enough setup. Let’s dive into CRUD operations—Create, Read, Update, Delete. These are your basic building blocks for interacting with the database.

Want to create a new city?

from fastapi import FastAPI, HTTPException
from tortoise.queryset import QuerySet

app = FastAPI()

@app.post("/cities/")
async def create_city(city: City):
    await City.create(**city.dict())
    return {"message": "City created successfully"}

Need to read the list of cities?

@app.get("/cities/")
async def read_cities():
    cities = await City.all()
    return [{"id": city.id, "name": city.name, "timezone": city.timezone} for city in cities]

How about updating an existing city?

@app.put("/cities/{city_id}")
async def update_city(city_id: int, city: City):
    city_obj = await City.get(id=city_id)
    if not city_obj:
        raise HTTPException(status_code=404, detail="City not found")
    await city_obj.update_from_dict(city.dict())
    await city_obj.save()
    return {"message": "City updated successfully"}

And of course, deleting a city:

@app.delete("/cities/{city_id}")
async def delete_city(city_id: int):
    city_obj = await City.get(id=city_id)
    if not city_obj:
        raise HTTPException(status_code=404, detail="City not found")
    await city_obj.delete()
    return {"message": "City deleted successfully"}

Testing is crucial too. You don’t want your tests to mess with your production database, right? Use an in-memory database when running tests. This ensures everything is isolated.

Here’s a setup using pytest with an in-memory SQLite database:

import os
from typing import Generator
import pytest
from fastapi.testclient import TestClient
from tortoise.contrib.test import finalizer, initializer
from ..main import app

DB_URL = "sqlite://:memory:"

@pytest.fixture(scope="session")
def event_loop():
    return asyncio.get_event_loop()

@pytest.fixture(scope="session")
def client() -> Generator:
    initializer(db_url=DB_URL, modules=["users.models"])
    with TestClient(app) as c:
        yield c
    finalizer()

When your app needs to talk to more than one database, Tortoise ORM can handle that too. Just specify multiple connections in your configuration.

Here’s what it might look like:

register_tortoise(
    app,
    config={
        'connections': {
            'default': {
                'engine': 'tortoise.backends.asyncpg',
                'credentials': {
                    'host': 'localhost',
                    'port': '5432',
                    'user': 'tortoise',
                    'password': 'qwerty123',
                    'database': 'test',
                }
            },
            'another_db': {
                'engine': 'tortoise.backends.asyncpg',
                'credentials': {
                    'host': 'localhost',
                    'port': '5432',
                    'user': 'tortoise',
                    'password': 'qwerty123',
                    'database': 'another_test',
                }
            }
        },
        'apps': {
            'models': {
                'models': ['__main__'],
                'default_connection': 'default',
            }
        }
    },
    generate_schemas=True,
    add_exception_handlers=True
)

You can control which database to use for queries by leveraging the using method:

await City.all().using('another_db')

Alright, wrapping things up. Tortoise ORM proves to be a powerful and efficient tool for managing databases in FastAPI applications. Its async nature aligns perfectly with FastAPI, offering smooth and high-performance web development. SQLite for development? No problem. PostgreSQL for production? It’s got you covered. Tortoise ORM brings the flexibility and scalability you need to build robust applications. Dive in, try it out, and watch your FastAPI project take flight!

Keywords: FastAPI applications, manage databases efficiently, Tortoise ORM, SQLite, PostgreSQL, async/await syntax, FastAPI async programming, install Tortoise ORM, CRUD operations, high-performance web development



Similar Posts
Blog Image
Wondering How to Armor Your FastAPI with Modern Security Headers?

Armor Up Your FastAPI App with Essential Security Headers and Practices

Blog Image
Marshmallow Fields vs. Methods: When and How to Use Each for Maximum Flexibility

Marshmallow Fields define data structure, while Methods customize processing. Fields handle simple types and nested structures. Methods offer flexibility for complex scenarios. Use both for powerful, clean schemas in Python data serialization.

Blog Image
Unlock Python's Hidden Power: Mastering Metaclasses for Next-Level Programming

Python metaclasses control class creation and behavior. They customize class attributes, enforce coding standards, implement design patterns, and add functionality across class hierarchies. Powerful but complex, metaclasses should be used judiciously to enhance code without sacrificing clarity.

Blog Image
Is Flask or FastAPI the Perfect Sidekick for Your Next Python API Adventure?

Two Python Frameworks: Flask and FastAPI Duel for Web Development Supremacy

Blog Image
Is FastAPI Dependency Injection the Secret Sauce for API Testing?

Mock Your Way to Dependable FastAPI APIs

Blog Image
Under the Hood: Implementing a Custom Garbage Collector in Python

Python's garbage collection automates memory management. Custom implementations like reference counting, mark-and-sweep, and generational GC offer insights into memory optimization and efficient coding practices.