Узнайте, как самостоятельно воссоздать это популярное расширение Flask с помощью всего нескольких строк кода.

Вы когда-нибудь задумывались, как работает расширение Flask-SQLAlchemy? Насколько сложно было бы самостоятельно настроить SQLAlchemy? Сегодня мы собираемся углубиться в то, как настроить SQLAlchemy ORM для использования с приложением Flask. В конце концов, у вас будет собственная версия Flask-SQLAlchemy для использования!

Цель SQLAlchemy - предоставить вашему приложению Flask средство для взаимодействия с базой данных. Хотя документация по Flask и SQLAlchemy определенно заставила меня кружиться в течение нескольких дней, надеюсь, я смогу разбить ее на кусочки побитового размера, которые вы легко проглотите. Чтобы объяснить, как работают различные части SQLAlchemy, я буду использовать аналогию с приложением Flask, которое обращается к своему хорошему другу с базой данных по телефону ...

Объект создания сеанса SQLAlchemy - это телефон, который Flask использует для вызовов базы данных - если хотите, это «фабрика вызовов». Когда вы инициализируете создатель сеанса, вы передаете ему конфигурацию для вызовов, которые вы хотите, чтобы он выполнял, поэтому каждый раз, когда создатель сеанса делает вызов, он будет выглядеть и действовать одинаково. Например, вы можете установить «привязку» для использования при создании сеансов. Привязка, по сути, сообщает создателю сеанса, какой телефонный контакт вызывать, когда Flask сообщает ему «начать вызов», и он всегда будет звонить только этому контакту.

  • Двигатель"

Прежде чем вы сможете сообщить своему создателю сеанса «телефон», по которому следует позвонить, вы должны создать контакт на указанном телефоне. Настройка механизма SQLAlchemy похожа на создание этого нового контакта. Когда вы запускаете sqlalchemy.create_engine(‘database_location’), вы передаете строку местоположения базы данных, с которой вы хотите, чтобы ваше приложение взаимодействовало.

После того, как вы создали движок, вы можете установить привязку sessionmaker, равную вашему движку, и он всегда будет обращаться к этому движку.

  • Сессия"

В нашем примере телефонного звонка объект сеанса SQLAlchemy - это сам звонок. Flask использует объект sessionmaker для выполнения вызова / сеанса, а затем использует его для запроса базы данных, добавления новых строк, обновления строк, удаления строк и т. Д.

Представьте, что вы живете в доме с несколькими стационарными телефонами. Вы разговариваете по телефону на кухне, и вдруг ваша мама решает взять трубку в своей спальне. Ее телефон подключен к тому же телефону, что и ваш. Когда все повесили трубку, звонок завершается, и кто-то должен будет начать новый звонок. Стационарный телефон - это объект SQLAlchemy scoped_session. Объект scoped_session гарантирует, что каждый телефон в доме имеет доступ к тому же вызову и что, когда все нажмут «завершить вызов», вызов будет отключен. Когда телефон снова пытается позвонить, он создает новый вызов, а не продолжает предыдущий.

В примере со стационарным телефоном продолжительность звонка зависела от того, когда люди в доме нажимали «начать звонок» и «завершить звонок». В мире веб-приложений это похоже на то, когда веб-сервер делает запрос к вашей сети. приложение. Когда начинается запрос, объект scoped_session должен использовать объект sessionmaker для создания сеанса, доступного для всех модулей в вашем веб-приложении Flask. Приложение Flask использует сеанс для выполнения любых необходимых запросов или обновлений в базе данных. Затем запрос завершается, и объект scoped_session удаляет текущий сеанс. Когда приходит новый запрос, он создает новый сеанс для его обработки.

Надеюсь, эта аналогия дала вам хорошее представление о том, как все работает, когда вы используете SQLAlchemy для создания подключения к своей базе данных. Теперь давайте запишем это в код.

1. Для начала создадим пустой класс под названием «SQLAlchemy». Настроив соединение SQLAlchemy как класс, будет проще импортировать различные части в разные части нашего приложения Flask.

2. Давайте создадим нашу функцию __init__. Единственное, что нам здесь нужно сделать, это указать объекту SQLAlchemy, как мы будем определять таблицы в нашей базе данных. Мы будем использовать декларативное расширение, которое позволяет нам определять таблицы и модели за один раз, подобно тому, как вы это делаете при использовании обычного расширения Flask-SQLAlchemy. Мы присвоим ему self.Base, чтобы передать его в наши таблицы при настройке базы данных. Подробнее о декларативном расширении здесь.

3. Теперь давайте создадим функцию, в которой происходит все волшебство - функцию init_app. Это функция, которую мы запустим для подключения флеш-приложения.

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

  • Строка 4: мы инициализируем механизм с помощью функции create_engine SQLAlchemy и передаем расположение нашей базы данных (которое задается в конфигурации приложения в переменной «DATABASE»).
  • Строка 6: Мы создаем создатель сеансов и устанавливаем несколько параметров по умолчанию для использования при создании сеансов (например, установка bind = self.engine в строке 9, чтобы все сеансы обращались к правильной базе данных).
  • Строка 13: мы инициализируем реестр scoped_session, передавая self.sessionmaker, чтобы реестр знал, как создавать сеансы. Первоначально реестр пуст, но когда наше приложение получает запрос и вызывает self.session, реестр scoped_session создаст и сохранит сеанс для связи с запросом.
  • Строка 15: мы устанавливаем scopefunc scoped_session на функцию flask, которая идентифицирует стек вызовов текущего приложения. Приложение настраивает и удаляет стек вызовов с каждым запросом, так что это подходящий объект, с которым можно связать продолжительность жизни сеанса. Узнать больше о функциях прицела.
  • Строка 18: Мы устанавливаем self.Base.query на session.query_property (). Если вы раньше использовали Flask-SQLAlchemy, вы знаете, что можете выполнить запрос непосредственно к таблице, а не к db.session. IE, если у вас есть таблица с именем user, вы можете запустить User.query… вместо db.session.query (User)…
  • Строка 20: Эта строка создает любые определенные модели и создает их в базе данных, если они еще не существуют. Подробнее об этом позже.

4. Убедитесь, что объект scoped_session удаляет текущий сеанс после каждого запроса. Для этого мы напишем функцию remove_session, которая вызывает self.session.remove (), и добавим ее в список функций запроса удаления (функций, которые выполняются в конце каждого запроса) в нашем приложении.

Обратите внимание, что мы добавили app.teardown_request(self.remove_session) в конец нашей init_app() функции.

10. Теперь давайте определим таблицы нашей базы данных с помощью декларативного расширения self.Base ... Вот краткий пример того, как создать таблицу:

Обратите внимание, как мы импортировали db из your_application. Предполагается, что вы установили db = SQLAlchemy () в файле __init__.py вашего приложения. Обязательно передайте db.Base в каждый из классов таблиц, чтобы SQLAlchemy мог их понять. Вам также потребуется импортировать различные структуры таблиц из SQLAlchemy (например, Column, Integer, String и т. Д.).

11. Настройте фабрику фляг-приложений и используйте SQLAlchemy ORM! Вот пример простого файла __init__.py, который я создал с помощью объекта FLask_SQLAlchemy, который мы создали в этом руководстве:

Обратите внимание, что сначала я определил db = SQLAlchemy (), затем я импортировал свои классы таблиц из модуля, в котором я их определил, затем я запустил db.init_app (app). Вы должны делать это в указанном порядке, иначе вы обнаружите, что таблицы базы данных созданы неправильно. db = SQLAlchemy () создает db.Base, который нам нужен для определения наших таблиц. Если вы выполните db.init_app (app) перед импортом классов таблиц, SQLAlchemy не узнает о них и не создаст их в вашей базе данных.

Вот как это сделано! Вот краткий обзор того, как использовать объект сеанса SQLAlchemy для выполнения запросов и добавления вещей в вашу базу данных.

Мне никогда не нравится создавать что-то без тестирования, чтобы убедиться, что оно работает ... давайте создадим несколько тестов, чтобы убедиться, что наш объект Flask-SQLAlchemy будет работать правильно. В документации SQLAlchemy говорится, что в контексте веб-приложения необходимо реализовать две вещи:

  1. Создайте единый реестр scoped_session при первом запуске веб-приложения и убедитесь, что этот реестр доступен для всего приложения.
  2. Убедитесь, что scoped_session.remove () вызывается после завершения каждого запроса.

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

Для простоты тестирования я изменил функцию init_app из приведенного выше примера self.session = sqlalchemy.orm.scoped_session(self.sessionmaker, scopefunc=flask._app_ctx_stack.__ident_func__) на self.session = init_scoped_session(), где init_scoped_session - это функция, которая возвращает исходный код (создавая объект scoped_session). Таким образом я могу привязать декоратор к функции, которая будет записывать много раз, когда функция вызывается.

1а. Создайте единый реестр scoped_session при первом запуске веб-приложения

1b. Убедитесь, что этот реестр scoped_session доступен для всего приложения.

2. Убедитесь, что scoped_session.remove () вызывается после завершения каждого запроса.

Оба теста прошли отлично!

Если вы похожи на меня, то, вероятно, все еще немного озадачены тем, как все работает. Здесь я рассмотрю некоторые вопросы, которые возникали у меня в голове, и ответы, которые я нашел.

1. Во-первых, почему мы создаем новую сессию с каждым запросом и удаляем ее в конце запроса? Почему бы нам просто не создать один сеанс на весь срок службы приложения?

Для веб-приложений привязка продолжительности сеанса к продолжительности жизни запроса является нормой для курса. Запрос, означающий, что вызывается один из маршрутов вашего приложения (например, запрос GET или POST к вашему приложению). Эта страница имеет красивую диаграмму того, как поток привязки SQLAlchemy должен работать при следовании этому шаблону.

2. В документации SQLAlchemy говорится, что когда вызывается объект scoped_session, он всегда будет возвращать тот же сеанс, пока область не закончится и не будет вызвана scoped_session.remove. Как это работает?

Позвольте мне показать два теста, которые я провел, чтобы доказать это поведение:

Обратите внимание, что вы должны явно вызвать объект scoped_session (db.session), чтобы это сработало. Вызывая db.session (), вы передаете сам объект сеанса в session1 и session2. Если вы только что запустили session1 = db.session, вы передадите сам объект scoped_session, и второй тест (который утверждает, что session1 и session2 отличаются в разных запросах) завершится неудачно, потому что сам объект scoped_session не изменяется между запросами.

Вот пример:

3. С Session = scoped_session (sessionmaker), вызывает ли Session () и Session одно и то же? Т.е. в вашем примере кода, почему вы можете вызвать db.session вместо того, чтобы вызывать db.session = db.session (), как в этих примерах в документации SQLAlchemy?

Это прекрасная штука, называемая неявным доступом к методу. Рекомендую прочитать об этом здесь. Я также немного объясняю это в вопросе выше.

4. Нужно ли мне хранить что-нибудь в объекте g во фляге?

Нет. Если вы похожи на меня, возможно, вы сначала пытались написать тяжелый код вроде этого: в этом нет необходимости. Вы слишком много думаете. Все это обрабатывает объект scoped_session.

5. Вы публиковали свой проект на PyPi?

Да, действительно. Вы можете установить его с помощью pip, позвонив pip install flask-sqlalchemy-bind. Узнай больше об этом здесь". Я могу опубликовать статью о том, как я загрузил его в PyPi, и о процессе, который я прошел для этого. Но, может быть, не потому, что после этого поста я немного увяз в блоге.

Надеюсь, что вы ответили на все ваши вопросы в полукруглой форме. Если у вас есть что-то еще (или если вы обнаружили какие-то ошибки в моем образе мышления), не стесняйтесь обращаться ко мне по адресу [email protected].