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
How Can You Keep Your API Fresh Without Breaking It?

Master API Versioning with FastAPI for Seamless Software Communication

Blog Image
Mastering Python Data Compression: A Comprehensive Guide to Libraries and Best Practices

Discover Python's data compression libraries: zlib, gzip, bz2, lzma, and zipfile. Learn their strengths, use cases, and code examples for efficient data storage and transmission. Optimize your projects now!

Blog Image
6 Essential Python Web Scraping Libraries with Real-World Code Examples

Master 6 essential Python web scraping libraries with practical code examples. Learn Beautiful Soup, Scrapy, Selenium & more for efficient data extraction.

Blog Image
Can Setting Up a CI/CD Pipeline for FastAPI Really Enhance Your Workflow?

FastAPI and CI/CD: The Coolest Duo in Development

Blog Image
What Can FastAPI Teach You About Perfecting API Versioning?

The Art of Seamless Upgrades: Mastering API Versioning with FastAPI

Blog Image
Building Multi-Tenant Applications with NestJS: One Codebase, Multiple Customers

NestJS enables efficient multi-tenant apps, serving multiple clients with one codebase. It offers flexibility in tenant identification, database strategies, and configuration management, while ensuring security and scalability for SaaS platforms.