Главная | Все статьи | Код

Вышел SQLAlchemy 2.0: что нового в культовой ORM для работы с базами данных на Python

Python Время чтения статьи ~8 минут
Вышел SQLAlchemy 2.0: что нового в культовой ORM для работы с базами данных н... главное изображение

Рассказываем про обновление опенсорс-проекта SQLAlchemy, с помощью которого миллионы Python-разработчиков работают с базами данных.

В начале 2023 года вышла SQLAlchemy 2.0 — библиотека на Python для работы с реляционными СУБД, которая работает с Object Relational Mapper (объектно-реляционным отображением). Основная задача SQLAlchemy — синхронизация объектов Python с данными в БД.

То есть с помощью SQLAlchemy можно описывать структуры БД и работать с их данными на объектно-ориентированном коде на Python без использования чистого SQL. Другая важная особенность SQLAlchemy — код для работы с базой данных будет одинаковым вне зависимости от БД, которую использует разработчик. Такой подход позволяет без проблем мигрировать с одной базой данных на другую.

Полный список нововведений в SQLAlchemy 2.0 можно посмотреть в официальной документации сервиса.

Читайте также: Программирование на Python: особенности обучения, перспективы, ситуация на рынке труда

Новый интерфейс запросов

В SQLAlchemy 2.0 появился новый интерфейс запросов. Если быть точным, эта функция была представлена в релизе SQLAlchemy 1.4 как способ помочь разработчикам перейти на версию 2.0.

До этого основной (но теперь уже устаревший) способ выполнения запросов в ORM SQLAlchemy заключался в использовании объекта Query, доступного из метода Session.query(). Либо метода Model.query, если разработчик использовал расширение Flask-SQLAlchemy для микрофреймворка Flask.

# using native SQLAlchemy
user = session.query(User).filter(User.username == 'susan').first()

# using Flask-SQLAlchemy
user = User.query.filter(User.username == 'susan').first()

В релизе SQLAlchemy 2.0 это теперь считается устаревшим способом выполнения запросов. Разработчики по-прежнему могут делать запросы этими методами, но в документации к SQLAlchemy такой подход уже называется «API запросов 1.x» или «устаревший API запросов».

Новый Query API имеет четкое разделение между самими запросами и средой выполнения, в которой они выполняются. Приведенный выше запрос для поиска пользователя по атрибуту username теперь можно записать вот так:

query = select(User).where(User.username == 'susan')

В этом примере запрос сохраняется в переменной query. При этом сейчас запрос еще не выполнен и даже пока не связан с сеансом. Для выполнения этого запроса его нужно передать в метод execute() объекта сеанса:

results = session.execute(query)

Возвращаемое значение из execute() — это объект Result, который функционирует как итерируемый объект, возвращающий объекты Row с интерфейсом как у кортежа. При этом в самом Python нет функции с таким названием, под Row обычно понимают «строку» или «запись» в контексте работы с базами данных. Если же разработчик хочет получить результаты, не дублируя их, есть несколько методов, которые можно вызвать для этого объекта:

  • Метод all() позволяет вернуть list с объектом строки для каждой строки результата
  • Метод first() вернет первую строку результата
  • Метод one() вернет первую строку результата и вызовет исключение, если в ответе нет результата. Либо в объекте есть несколько одинаковых объектов, которые подходят под результат
  • Метод one_or_none() вернет первую строку результата, None — если результатов нет, или вызовет исключение, если есть более чем один подходящий к результату объект.

Работа с результатами в виде кортежей имеет смысл, когда каждая строка результата может содержать несколько подходящих значений. Однако, когда у нас есть только одно значение на строку, то извлекать данные из одноэлементных кортежей может быть достаточно утомительно. Поэтому новый интерфейс имеет два дополнительных метода, которые делают работу со строками с одним значением более удобной:

  • Метод scalars() возвращает ScalarResult объект с первым значением каждой строки результата. И перечисленные выше методы остаются доступны для этого нового объекта результата.
  • Метод scalar() возвращает первое значение первой строки результата.

Поэтому устаревший запрос может быть теперь выполнен вот так:

user = session.scalar(query)

Обновленные менеджеры контекста

Менеджеры контекста сеанса впервые появились, как и интерфейс запросов, еще в SQLAlchemy 1.4. Но теперь этот подход стал стандартом, который используется в SQLAlchemy.

Раньше сеанс с локальной областью видимости был основным шаблоном для работы с сеансами. Расширение Flask-SQLAlchemy, например, включило его в переменную db.session, которая является его сигнатурой. То есть раньше сеанс имел ограниченную зону видимости и привязывался к потоку. Даже несмотря на то, что условная жизнь сеанса может быть намного короче, чем потока — и лечилось это только ручным управлением.

Теперь сеанс можно инициировать с помощью менеджера контекста, что позволяет прозрачно наблюдать за началом сеанса и его концом:

with Session() as session:
    session.add(user)
    session.commit()

Здесь сеанс закрывается, когда заканчивается блок диспетчера контекста. И, если внутри него возникает ошибка, то сессия откатывается.

Вариант этого шаблона можно использовать для сеанса, который автоматически фиксируется в конце, но все еще откатывается при ошибках:

with Session() as session:
    with session.begin():
        session.add(user)

Аннотации типов

Еще одно интересное изменение, представленное в версии SQLAlchemy 2.0, — это возможность использовать подсказки при вводе данных во время объявления столбцов и связей в моделях. По сути, это введение элементов строгой типизации. Рассмотрим это на модели User:

class User(Model):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    username = Column(String(64), index=True, unique=True, nullable=False)
    email = Column(String(120), index=True, unique=True, nullable=False)
    password_hash = Column(String(128))
    about_me = Column(String(140))

В версии 2.0 тип столбца можно определить с помощью Mapped-подсказки. Если есть какие-то дополнительные опции, их можно указать в mapped_column().

import sqlalchemy as sa
import sqlalchemy.orm as so

class User(Model):
    __tablename__ = 'users'

    id: so.Mapped[int] = so.mapped_column(primary_key=True)
    username: so.Mapped[str] = so.mapped_column(String(64), index=True, unique=True)
    email: so.Mapped[str] = so.mapped_column(String(120), index=True, unique=True)
    password_hash: so.Mapped[Optional[str]] = so.mapped_column(String(128))
    about_me: so.Mapped[Optional[str]] = so.mapped_column(String(140))

Применение подсказок для типов данных может дать разработчикам несколько преимуществ:

  • При использовании IDE, которая выполняет статический анализ кода и предлагает изменения по мере ввода, строго типизированная модель поможет вашей IDE лучше понять код.
  • Это позволяет проще работать с dataclasses, которые также опираются на выбранный разработчиком тип данных.
  • Это приводит к тому, что из SQLAlchemy теперь нужно импортировать меньше символов. Потому что теперь для столбцов, которые принимают данные только в числах, датах, времени или даже UUID, можно указывать формат в виде сразу в виде подсказки.

Как и в случае с запросами, SQLAlchemy по-прежнему поддерживает старый способ определения столбцов и связей.

Читайте также: Как создатель Python Гвидо ван Россум устроился в Microsoft и теперь работает над развитием CPython

Отношения Write-Only

Динамические отношения считаются устаревшими в SQLAlchemy 2.0, поскольку они несовместимы с новым интерфейсом запросов. Вместо этого рекомендуемым решением является новый тип отношений под названием Write-Only («Только для записи»). Вот как определить отношения только для записи:

class User(Model):
    # ...
    tokens: WriteOnlyMapped['Token'] = relationship(back_populates='user')

Отличие от старой динамической связи в том, что связь Write-Only не загружает и не читает связанные объекты. Она только предоставляет методы add() и remove() для внесения изменений или для записи в них.

С таким типом отношений можно получать связанные объекты через метод select(), который возвращает запрос, его можно выполнить в сеансе — например, после добавления фильтров, сортировки или разбивки на страницы.

Вот пример того, как получить объекты tokens, связанные с пользователем, и отсортированные по сроку их действия:

tokens = session.scalars(user.tokens.select().order_by(Token.expiration)).all()

Поддержка асинхронности

SQLAlchemy 1.4 представила бета-версию расширения asyncio, которая показала асинхронные версии объектов Engine и Session. В версии 2.0 это расширение больше не считается бета-версией.

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

Этот текст — адаптированный перевод материала What's New in SQLAlchemy 2.0? с сайта blog.miguelgrinberg.com

Изучите Python на Хекслете Пройдите нашу профессию «Python-разработчик», чтобы поменять свою жизнь и стать бэкенд-программистом.

Пройти курс

Аватар пользователя Svet Ivanov
Svet Ivanov 14 марта 2023
6
Рекомендуемые программы
профессия
от 6 300 ₽ в месяц
Разработка фронтенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 25 апреля
профессия
от 6 300 ₽ в месяц
Разработка веб-приложений на Django
10 месяцев
с нуля
Старт 25 апреля
профессия
от 6 183 ₽ в месяц
Ручное тестирование веб-приложений
4 месяца
с нуля
Старт 25 апреля
профессия
от 6 300 ₽ в месяц
Разработка приложений на языке Java
10 месяцев
с нуля
Старт 25 апреля
профессия
от 5 025 ₽ в месяц
новый
Сбор, анализ и интерпретация данных
9 месяцев
с нуля
Старт 25 апреля
профессия
от 6 300 ₽ в месяц
Разработка веб-приложений на Laravel
10 месяцев
с нуля
Старт 25 апреля
профессия
от 5 840 ₽ в месяц
Создание веб-приложений со скоростью света
5 месяцев
c опытом
Старт 25 апреля
профессия
от 9 900 ₽ в месяц
Разработка фронтенд- и бэкенд-компонентов для веб-приложений
16 месяцев
с нуля
Старт 25 апреля
профессия
от 6 300 ₽ в месяц
Разработка бэкенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 25 апреля
профессия
новый
Автоматизированное тестирование веб-приложений на JavaScript
8 месяцев
c опытом
в разработке
Старт 25 апреля
профессия
Верстка с использованием последних стандартов CSS
5 месяцев
с нуля
Старт в любое время