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

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

    @classmethod
    async def find_all(cls):
        async with async_session_maker() as session:
            query = select(cls.model)
            result = await session.execute(query)
            return result.scalars().all()

Автору прежде чем писать гайды, надо почитать документацию, почему не надо создавать сессию в самом DAO
https://docs.sqlalchemy.org/en/20/orm/session_basics.html

Да и я тут её и не создавал как-бы. Вы же знаете что такое async_session_maker() и откуда он тянется? Наверняка читали прошлые статьи?

async with async_session_maker() не должно быть в DAO, он должен передаваться как аргумент в функции или же в конструкторе

    def __str__(self):
        return f"{self.__class__.__name__}(id={self.id}, major_name={self.major_name!r})"

    def __repr__(self):
        return str(self)

Ужас... Можно же просто переопределить метод repr

Пожалуйста, указывайте лучший код с комментариями. Чтоб было всем понятно о чем речь. Благодарю

def __repr__(self):
  return f"{self.__class__.__name__}(id={self.id}, major_name={self.major_name!r})"

Все, больше ничего не надо делать. Если метод dunder str не определен, то питон автоматом вызывает dunder repr метод.
А прежде чем писать такие статьи, стоит почитать документацию питона. Вы же понимаете что многие люди будут ваши статьи, особенно новички, потом нахватаются от вас плохого кода. Благодарю

Я посчитал, что такая запись сложная для новичов, но за код благодарю. Уверен, что он кому-то пригодится. Прошу смотреть на серию данных публикаций, как обучение для новичков. Об этом, кстати, я неоднократно указывал в самом тексте.

  @classmethod
    async def find_all(cls):
        async with async_session_maker() as session:
            query = select(cls.model)
            result = await session.execute(query)
            return result.scalars().all()

Хорошо, что вы попробовали и не побоялись выложить, учитывая какие софт-скиллы у многих комментаторов на Хабре. Можно сделать лучше. Приведу примеры для синхронной и асинхронной алхимии, как сделать лучше

ПОРАБОТАЕМ В СИНХРОННОЙ АЛХИМИИ

В обычной синхронной алхимии используется, как правило, либо execute для выполнения произвольных SQL-запросов и возвращает объект ResultProxy, либо scalars для выполнения запросов, которые возвращают ровно один столбец и одну строку, и возвращает значение этой единственной ячейки напрямую. То есть если бы это была обычная синхронная сессия, такой синтаксис был бы далеко не самым лучшим решиением.

Код для синхронной алхимии:

# импортируем то, что нужно
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# где-то ранее создаем движок и сессию
engine = create_engine(os.environ.get('DATABASE_URL'))
Session = sessionmaker(engine)

# теперь работаем в классе
@classmethod
def find_all(cls):
    with Session() as session:
        query = select(cls.model)
        return session.scalars(query)

У scalars не нужно писать all() - оно по умолчанию установлено

ТЕПЕРЬ ПОГОВОРИМ ОБ АСИНХРОННОЙ АЛХИМИИ

Как верно было замечено выше надо создать отдельную асинхронную сессию. У движка дожен быть асинхронный драйвер, например, asyncpg для PostgreSQL

# импортируем все необходимое для работы
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker

# создаем движок и сессию
engine = create_async_engine(os.environ.get('DATABASE_URL'))
Session = async_sessionmaker(engine, expire_on_commit=False)

# теперь работаем в классе
@classmethod
async def find_all(cls):
    async with Session() as session:
        query = select(cls.model)
        return await session.stream_scalars(query)

Метод session.stream_scalars(query) обеспечивают возврат асинхронной версии объекта, который поддерживает протокол асинхронной итерации Python.То есть для его итерации вы будете использовать не простой цикл for, а асинхронный async for . Он позволяет выполнять асинхронные операции в процессе итерации. Итерации выполняются в асинхронном контексте, что позволяет эффективно использовать время CPU при ожидании завершения асинхронных операций вместо блокировки процесса.

И на последок небольшую психологическую поддержку Вам хочу оказать: продолжайте дальше писать статьи, вы работаете с довольно сложными вещами. И официальный туториал по SQLAlchemy написан, слабо говоря, не лучшим образом. Книга только одна вменяемая по этой теме - вот эта. Ну а к хамству и грубости надо просто привыкнуть - как сказал не последний человек в мире Python Никита Соболев, что в России все отлично с хардами, но все ужасно с софтами. Грубость в комментариях к вам никакого отношения не имеет, этих людей так с детсва воспитало наше общество

Благодарю за обратную связь. Правда. Очень радует, что есть такие комментаторы как вы. Благодарю и за примеры качественного кода. Обязательно внедрю его в будущие статьи.

У scalars не нужно писать all() - оно по умолчанию установлено

Это не так. result.scalars() привязан к соединению из пула и к сессии, result.scalars().all() нет. В теории, это может вызвать проблемы при формировании ответа на запрос, ведь сессия в данный момент уже будет закрыта.

Метод session.stream_scalars(query) обеспечивают возврат асинхронной версии объекта, который поддерживает протокол асинхронной итерации Python.

stream_scalars по большей части нужен для работы с большим объёмом данных. Если данных немного, то лучше сразу забрать их все через scalars().all().

То есть для его итерации вы будете использовать не простой цикл for, а асинхронный async for . Он позволяет выполнять асинхронные операции в процессе итерации.

async for служит для других целей. С синхронным итератором вы тоже можете выполнять асинхронные операции в процессе итерации.

У вас в разных pydantic классах используется синтаксис версий 1 и 2 (декораторы validator и field_validator). Тут либо одно, либо другое.

https://docs.pydantic.dev/latest/migration/

Проблема с набитым опытом работы с первой версей. Замечание принято и в будующих статьях переберусь на чистый field_validator. Спасибо за обратную связь)

Спасибо! пошёл читать 5 часть +)

Уточню, при создании новой записи в БД обычно принято возвращать созданный объект?

То есть создаю нового студента и в случае успеха получаю объект студент из БД с присвоенным PK.

Здесь у вас возвращаем сообщение с обектом, который подавали на вход. Это для упрощения?

Интересно было бы увидеть подключение tokena в swagger

http://127.0.0.1:8000/students/by_filter?enrollment_year=2008

и вот такая фигня

Error: Unprocessable Entity
Response body
{
  "detail": [
    {
      "type": "missing",
      "loc": [
        "query",
        "student_id"
      ],
      "msg": "Field required",
      "input": null
    }
  ]
}

RBStudent из примера.

def __init__(self, student_id: int | None = None,

почему оно хочет обязательного указания student_id?

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

Публикации