Cat

Tortoise ORM - Простая асинхронная альтернатива SQLAlchemy

В этом посте, кратко расскажу про библиотеку Tortoise ORM и о том, как она отличается от SQLAlchemy.

Tips & Tricks proDream 03 Сентябрь 2024 Просмотров: 309

Когда речь заходит об использовании ORM в Python, многие сразу вспоминают SQLAlchemy. Это мощный инструмент для работы с базами данных, но для новичков его порог входа может быть довольно высоким. Особенно когда дело доходит до асинхронных приложений или сложных моделей данных. Если вы ищете более простой, интуитивно понятный и лёгкий в освоении инструмент, обратите внимание на Tortoise ORM.

Работа с Tortoise будет особенно простой для тех, кто уже знаком с Django ORM, так как многие концепции и синтаксис этих библиотек очень похожи. При этом Tortoise ORM поддерживает асинхронные запросы к базе данных, что делает её отличным выбором для современных асинхронных приложений.

 

Установка

Tortoise ORM поддерживает различные базы данных, такие как PostgreSQL и MySQL. Для работы с PostgreSQL используйте версию tortoise-orm[asyncpg], а для MySQL — tortoise-orm[asyncmy].

# Для PostgreSQL
pip install tortoise-orm[asyncpg]

# Для MySQL
pip install tortoise-orm[asyncmy]

 

Сравнение Tortoise ORM и SQLAlchemy

Для начала посмотрим, как отличаются подходы к созданию моделей и их использованию в Tortoise ORM и SQLAlchemy.

 

Модель в SQLAlchemy

Пример модели User, написанной с использованием SQLAlchemy:

import datetime
from sqlalchemy import BigInteger, String, Integer, func
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

class Base(DeclarativeBase):
    __abstract__ = True

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)

class User(Base):
    __tablename__ = "user"

    telegram_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
    first_name: Mapped[str] = mapped_column(String, nullable=False)
    last_name: Mapped[str] = mapped_column(String, nullable=True)
    username: Mapped[str] = mapped_column(String, nullable=True)
    language_code: Mapped[str] = mapped_column(String, nullable=False)
    created_at: Mapped[datetime.datetime] = mapped_column(server_default=func.now(), default=datetime.datetime.now)

 

Модель в Tortoise ORM

Теперь посмотрим, как будет выглядеть аналогичная модель в Tortoise ORM:

from tortoise.models import Model
from tortoise import fields
import datetime

class User(Model):
    id = fields.IntField(pk=True)
    telegram_id = fields.BigIntField(null=False)
    first_name = fields.CharField(max_length=255)
    last_name = fields.CharField(max_length=255, null=True)
    username = fields.CharField(max_length=255, null=True)
    language_code = fields.CharField(max_length=10, null=False)
    created_at = fields.DatetimeField(auto_now_add=True)

    class Meta:
        table = "user"

 

Обратите внимание, насколько синтаксис в Tortoise ORM проще. Модель создаётся как класс, наследуемый от Model, а поля данных задаются через типы из tortoise.fields. В "Мета-классе" указываем, как будет называться таблица модели. На этом этапе главное не начать думать, что работаешь в Django 😉.

 

Пример получения или создания записи

Теперь давайте сравним, как в SQLAlchemy и Tortoise ORM можно получить или создать запись пользователя.

 

Пример в SQLAlchemy

SQLAlchemy требует явного использования сессий, запросов и обработчиков. Вот пример функции для получения или создания пользователя:

async def get_or_create_user(user: UserSchemaBaseInput) -> UserSchemaOutput:
    async with session_maker() as session:
        query = select(User).where(user.telegram_id  User.telegram_id)
        result = await session.execute(query)
        exist_user = result.scalar_one_or_none()
        if exist_user:
            return UserSchemaOutput.model_validate(obj=exist_user, from_attributes=True)

        user_query = (
            insert(User)
            .values(**user.dict())
            .returning(
                User.id,
                User.telegram_id,
                User.first_name,
                User.last_name,
                User.username,
                User.created_at,
                User.language_code,
            )
        )
        result = await session.execute(user_query)
        await session.commit()
        new_user = result.mappings().first()
        return UserSchemaOutput.model_validate(obj=new_user, from_attributes=True)

 

Как видим, получается достаточно много действий.

 

Пример в Tortoise ORM

Теперь аналогичный пример в Tortoise ORM:

async def get_or_create_user(telegram_id: int, first_name: str, last_name: str, username: str, language_code: str):
    user, created = await User.get_or_create(
        telegram_id=telegram_id,
        defaults={
            "first_name": first_name,
            "last_name": last_name,
            "username": username,
            "language_code": language_code,
            "created_at": datetime.datetime.now(),
        }
    )
    return user, created

 

В Tortoise ORM этот процесс значительно проще: мы используем метод get_or_create, который выполняет поиск записи по указанным полям и создаёт новую, если такая запись не найдена. Здесь нет необходимости явно работать с сессиями или писать SQL-запросы — всё выполняется через ORM.

 

Использование Pydantic с Tortoise ORM

SQLAlchemy и Pydantic часто используются вместе для валидации и сериализации данных, но в Tortoise ORM нет строгой необходимости использовать Pydantic. Tortoise имеет встроенные методы для сериализации моделей, например, метод .to_dict().

Тем не менее, если вы уже используете Pydantic в вашем проекте, его можно легко интегрировать с Tortoise ORM. Важно отметить, что Tortoise ORM также имеет встроенную поддержку Pydantic через методы .model_validate() для создания и валидации схем данных.

Пример использования Pydantic с Tortoise ORM:

from pydantic import BaseModel

class UserSchema(BaseModel):
    telegram_id: int
    first_name: str
    last_name: str | None
    username: str | None
    language_code: str

async def get_user_schema(user_id: int) -> UserSchema:
    user = await User.get(id=user_id)
    return UserSchema.model_validate(user, from_attributes=True)

 

Таким образом, Tortoise ORM прекрасно сочетается с Pydantic, но также может обходиться и без него.

 

Заключение

Tortoise ORM — это отличная альтернатива для тех, кто ищет простой и понятный способ работы с базами данных в  небольших асинхронных приложениях. Если SQLAlchemy кажется сложным, Tortoise предложит более интуитивный подход, сохраняя при этом поддержку всех современных возможностей ORM, таких как асинхронность и валидация данных.

Напишите в комментариях, стоит ли его применить в одном из будущих гайдов, для более конкретного примера использования?

Автор

    Нет комментариев

    Реклама