Как стать автором
Обновить

Асинхронный SQLAlchemy 2: простой пошаговый гайд по настройке, моделям, связям и миграциям с использованием Alembic

Время на прочтение30 мин
Количество просмотров12K
Всего голосов 29: ↑28 и ↓1+30
Комментарии27

Комментарии 27

class GenderEnum(str, enum.Enum):

Здесь можно не использовать множественное наследование. В стандартной библиотеке Enum есть класс StrEnum, также есть и IntEnum

будет, но мы же о красоте и современном синтаксисе (до 3.11 нужно как в статье)

Принял)

А если генерировать запросы к postgress, и вызывать эти запросы асинхронно, вместо одного большого запроса, также можно использовать sqlachemy ? Только непонятно, не ту ли ограничения, у postgres на количесто асинхронных вызовов

Эту тему плотно планирую рассмотреть в следующей статье. Что касается ограничений на стороне PostgreSQL - они снимаются достаточно просто. Суть алхимии в том, что при использовании питоновского кода появляется возможность генерировать сложные SQL запросы.

По вызову запосов асинхронно. Тут нужно хорошо разобраться в теме сессий и об этом я подробно расскажу в следующий раз)

Спасибо!!! Достойный материал. Очень доходчево написано.

Спасибо за обратную связь)

Спасибо. Отличная статья. Отдельное спасибо за ENUM миграции. В своё время попал в очень неприятную ситуацию из-за незнания этого нюанса на боевом проекте. В связи с чем в дальнейшем стал избегать этого типа в алхимии. Теперь можно попробовать вернуться. Вопрос: нужно изначально указывать create_type=False? С самой первой миграции?

Спасибо за обратную связь. Вообще можно использовать ENUM из SQLAlchemy и там прописывать, но оно как-то странно работает и на Alembic параметр все равно не попадает. Так что я решил это дело на "ручное" управление вынести)

Тс, asyncpg на какой версии питона тут работает? На 3.13 не хочет подтягиваться.

Спасибо за обратную связь)

Для использования этого параметра с ENUM, нужно передавать значение в виде текстового выражения с помощью метода text, который импортируется из SQLAlchemy. Значение ENUM указывается в кавычках как текст, например: "WRITER", а не само значение, такое как ProfessionEnum.WRITER. Это необходимо для корректного выполнения запроса на стороне базы данных.

Но ведь можно просто указать ProfessionEnum.WRITER.name .

В остальном достаточно неплохая статья для новичков.

Во-первых не name, а value. Во-вторых вы не правы. Такая запись как вы предложили вызовет ошибку. Для получения данных нам ничего не будет мешать использовать конструкцию ProfessionEnum.WRITER.value, но в качестве описания ENUM через Mapped такой подход вызовет ошибку.

Или, чтоб не заморачиваться, можно описать Pydantic модель, которая автоматически вернет значение.

class ProfilePydantic(BaseModel):
    first_name: str
    last_name: str | None
    age: int | None
    gender: GenderEnum
    profession: ProfessionEnum
    interests: List[str] | None
    contacts: dict | None

    class Config:
        from_attributes = True
        use_enum_values = True

Во-первых, name, а не value, потому что name вернёт "WRITER" (именно строку), когда как value вернёт "писатель". Во-вторых, не будет никакой ошибки, так как по сути ничего не меняется — вы просто более явно передаёте параметр. Вы бы хоть попробовали, прежде чем минус мне лепить.

Не понял, какое имеет отношение ваш пример со схемой Pydantic к server_default, но вместо Config сейчас используется параметр model_config с ConfigDict.

Я не понимаю почему мой метод ошибочен? Востановил баланс несправедливой оценки. Неправильно вас понял)

Он не ошибочен, он менее явный. Допустим, вы удалили WRITER из enum класса, при моей реализацией IDE или алембик ругнётся, что в server_defaultпередаётся то, чего нет, в вашем же случае нигде это не подсветится и может быть легко пропущено.

Понял. Учту. Спасибо

Мне тут предстоит на fastapi API к mysql писать (БД для работающего недоCRM, поменять нельзя). Насколько безопасно инфу из статей с asyncmy использовать не подскажите? Или ждет увлекательное хождения по граблям?

Классная статья, также по работе с enum можно было рассказать про параметр native_enum, который при отключении будет преобразовывать объекты enum на стороне алхимии, а в базе просто хранить str значение, тогда при добавлении/удалении нового объекта в enum не придется думать, что нужно в миграции дописать руками обновление enum таблицы

	building_type: Mapped[HouseTypeEnum] = mapped_column(  # noqa: F821
		Enum(HouseTypeEnum, native_enum=False, length=255),
	)


Для новичка отлично, спасибо! Хотелось бы разобрать вопрос с созданием таблицы группы, подгруппы, когда запись в таблице ссылается на запись в этой же таблице. Как ее создавать, как тянуть из нее данные, как писать сериализатор для FastAPI.

А меня одного смущает пропажа полей названия таблицы и id, для кода в конце?
Тут есть

class User(Base):    

__tablename__ = 'users'    

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

А тут уже нету:

class User(Base):
    username: Mapped[uniq_str_an]
    email: Mapped[uniq_str_an]
    password: Mapped[str]
    profile_id: Mapped[int | None] = mapped_column(ForeignKey('profiles.id'))

Это баг или фича?

Я думаю, что если бы вы внимательно читали, то у вас бы не было вопросов) Мы такие поля как id, created_at и updatet_at, как и название таблиц, уже закрыли в базовом классе Base. Для этого мы прописали следующее:

class Base(AsyncAttrs, DeclarativeBase):
    __abstract__ = True  # Класс абстрактный, чтобы не создавать отдельную таблицу для него

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    created_at: Mapped[datetime] = mapped_column(server_default=func.now())
    updated_at: Mapped[datetime] = mapped_column(server_default=func.now(), onupdate=func.now())

    @declared_attr.directive
    def __tablename__(cls) -> str:
        return cls.__name__.lower() + 's'

Спасибо, чётко и по делу

from database import Base, uniq_str_an, array_or_none_an, content_an

Нигде не нашел реализацию алиаса -> content_an.

напишите его пожалуйста, или подскажите где посмотреть.

Файл database.py

from typing import Annotated


uniq_str_an = Annotated[str, mapped_column(unique=True)]
content_an = Annotated[str | None, mapped_column(Text)]
array_or_none_an = Annotated[List[str] | None, mapped_column(ARRAY(String))]

Зарегистрируйтесь на Хабре, чтобы оставить комментарий