База данных PostgreSQL в web-приложении на Python и Starlette
programming
Для продолжения проектирования web-приложения на Python и Starlette, процесс разработки которого мы сосредоточенно исследуем в этом цикле статей, мне потребуется инструмент для хранения данных на сервере. В качестве такого инструмента я выбрал базу данных PostgreSQL, установка и настройка которой рассмотрена в статье по короткой ссылке. На текущем этапе разработки мне необходимо подключить эту базу данных в проект. Об этом и пойдёт речь в очередном выпуске цикла.
Методы работы с базой данных в Python
Python — это язык программирования, PostgreSQL — это SQL база данных, чтобы их "поженить", необходима специальная программа, такую обычно называют адаптером. Адаптер служит самым обычным переводчиком, преобразует данные из одной интерпретации в другую интерпретацию. В Python данные интерпретируются посредством встроенных типов, для каждого типа разработан класс в модуле стандартной библиотеки builtins. В PostgreSQL, в свою очередь, данные интерпретируются посредством SQL запросов — текстовых фраз, построенных в строгом соответствии с синтаксисом SQL. И ответ на запрос база данных отдаёт в текстовой форме. Перед тем, как такой ответ использовать в программе на Python, его текст нужно разобрать, программисты говорят, распарсить.
Адаптеров PostgreSQL для языка программирования Python известно несколько. В своей практике я в разное время использовал два: psycopg2 — программа с блокирующим вводом-выводом, asyncpg — программа с асинхронным вводом-выводом. Поскольку web-приложение в этом цикле статей я описываю с помощью инструментов Starlette, а это web-фреймворк общего назначения с асинхронным вводом-выводом, то и выбор мой очевиден. Работать с базой данных я буду с помощью асинхронного адаптера asyncpg.
Все читали великолепный манускрипт Мигеля, все читали совершенно потрясающий манускрипт официального мануала на Django, все думают, что работать с базой данных в программах на Python нужно с помощью так называемой ORM, сокращение от анлийского Object Relational Mapper, — технологии программирования, связывающей базу данных с концепцией объектно ориентированного программирования. Но нет, не нужно... Вернее, не обязательно.
У ORM есть свои плюсы и минусы. Мне, собственно, очень не нравится два минуса ORM:
-
ORM — это программа, разработанная кем-то библиотека, а это значит, что эту библиотеку придётся вдумчиво и досконально изучать во-первых, вешать в свой проект ещё одну дополнительную, довольно объёмную зависимость во-вторых, и бороться потом с некоторыми недостатками этой библиотеки уже в рамках кода собственной программы в-третьих, для меня перспектива не очень заманчивая;
-
второй минус приходится на конкретную ORM, с которой я в своё время имел дело, когда разрабатывал web на Flask, это SQLAlchemy, она использует блокирующий ввод-вывод, и для проекта на Starlette не очень подходит.
Искать ORM с асинхронным вводом-выводом мне не захотелось, её же придётся детально изучать. Я помню долгие вечера, когда я читал документацию SQLAlchemy, хорошее было время. Как люди жили, как тонко чувствовали... В общем, в своей практике я решил отказаться от ORM вообще. В рамках этого цикла статей я покажу работу с базой данных на прямых, текстовых SQL-запросах. Для меня оказалось, что изучать SQL выгоднее, чем изучать SQLAlchemy. В конце концов, в этом блоге мы говорим о web-разработке, как о хобби, странном увлечении, не более.., на вакансию в Яндексе не претендуем, а значит, можем делать всё, что захотим. Поехали...
Подключаем PostgreSQL в проект
Как я уже отметил выше, для работы с PostgreSQL мне понадобится адаптер, мой выбор — asyncpg. Запускаю терминал, вхожу в корневой каталог приложения и активирую его виртуальное окружение. Устанавливаю asyncpg с помощью Pip.
$ pip install asyncpg
На снимке экрана видно, что в результате выполнения этой команды на терминале появилось сообщение Successfully installed ... Адаптер есть. Теперь его нужно интегрировать в проект.
Открываю в текстовом редакторе Vim файл настроек приложения — .env
.
$ vim .env
В одном из предыдущих выпусков этого цикла статей я показал настройку пользователя PostgreSQL, там же создал базу данных с именем websitedev
, которую намерен использовать в процессе разработки приложения. База данных websitedev
принадлежит пользователю jazz
, и это имя совпадает с именем текущего пользователя операционной системы — jazz, it is me. PostgreSQL из коробки настроена так, что к ней можно подключиться на localhost от имени текущего пользователя операционной системы без указания хоста и имени пользователя, и в этом случае процедура авторизации с вводом пароля задействована не будет, а значит подключение к базе данных будет молниеносным. Эту характерную особенность я и буду использовать.
В файл настроек вписываю дополнительно следующие три строчки.
USER=jazz
DB=websitedev
DSN='postgres://jazz:aa@localhost:5432/websitedev'
Здесь я определяю имя пользователя базы данных, имя используемой базы данных и третий параметр — DSN, который сочетает в себе все параметры подключения, включая пароль пользователя. Подключение по DSN слишком затратно по времени, и оправдано только в одном случае, когда база данных расположена на другом сервере сети. Этот проект я предполагаю впоследствии развернуть на VDS, и база данных будет расположена на этом же VDS. Поэтому подключаться к базе данных в рамках web-приложения я буду только по имени пользователя и имени базы данных. Обойдёмся без явок, адресов и паролей...
Подключиться к базе данных в asyncpg можно двумя способами:
-
С использованием индивидуального подключения;
-
С использование пула подключений и получением индивидуального подключения из пула, в этом случае вся затратная часть приходится на создание пула, а индивидуальные подключения отдаются обработчикам из пула быстро.
Первый вариант является очень простым, самым безотказным и самым надёжным. При этом затраты на создание индивидуального подключения невелируются характерной особенностью настройки PostgreSQL, о которой я сказал выше.
Второй вариант, наверное, можно условно считать более правильным, но он менее надёжен, сопровождается вероятностью возникновения непредсказуемых исключений, и поэтому требует более детального и трудоёмкого программирования, с ним придётся писать больше кода.
Поскольку проект в рамках этого цикла статей я разрабатываю учебный, и мне очень хочется избежать пространных листингов кода, которые придётся детально комментировать, в этом проекте я буду использовать первый вариант — индивидуальное подключение к базе данных для каждого обработчика каждого HTTP запроса. Моя практика показала, что этот вариант очень хорошо себя оправдывает на бюджетном VDS за сто рублей и с огромным дефицитом аппаратных ресурсов.
Создаю ещё один файл, в командном режиме Vim вбиваю следующую команду.
:edit webapp/common/pg.py
В этот файл пишу следующий код.
import asyncpg
## функция get_conn создаёт индивидуальное подключение
## к базе данных, используя параметры из файла настроек приложения
async def get_conn(config):
## создаю две переменные и присваиваю им значения
## соответствующих полей из файла настроек
user, db = config.get('USER', default=None), config.get('DB', default=None)
## проверяю условие
if user and db:
## если условие выполняется, создаю подключение
## к базе данных, используя в качестве параметров
## значения переменных user и db
conn = await asyncpg.connect(user=user, database=db)
else:
## если условие не выполняется, создаю подключение
## к базе данных, используя в качестве параметра
## значение поля DSN из файла настроек
conn = await asyncpg.connect(config.get('DSN'))
## возвращаю созданный объект
return conn
Сохраняю изменения в файл.
Инструмент для создания подключения к базе данных PostgreSQL, который я только что реализовал достаточно элементарным кодом, я намерен использовать во всех обработчиках HTTP запросов, которые требуют взаимодействия с базой данных. На текущем этапе разработки в приложении есть один единственный обработчик такого типа — функция представления show_index
из файла views.py
главной подпрограммы приложения. Открываю этот файл в текстовом редакторе Vim.
:edit webapp/main/views.py
Подключаю в этот модуль функцию get_conn
из только что разработанного модуля pg.py
.
from ..common.pg import get_conn
В тело функции show_index
вношу следующие правки.
## Создаю подключение к базе данных
## в соответствии с содержанием файла настроек
conn = await get_conn(request.app.config)
## Инициализирую переменную amount
## отправляю запрос в базу данных и полученное значение
## присваиваю этой переменной
amount = await conn.fetchval('SELECT count(*) FROM users')
## Закрываю соединение с базой данных
await conn.close()
## Редактирую строку в сообщении flashed
await set_flashed(request, f'Известно пользователей: {amount}.')
Здесь следует обратить внимание на текстовый запрос в базу данных SELECT
. В таблице users
на данный момент имеется две строки с пользовательскими данными, я их создал в предыдущем выпуске этого цикла. Продемонстрированный в функции представления запрос SELECT
должен вернуть целое число — количество строк в таблице users
. Вот как модуль views.py
выглядит после правок в окне моего текстового редактора, внимание на следующий снимок экрана.
Всё... Можно приступать к тестированию разработанного кода в браузере.
Тестируем в браузере
Я ожидаю, что на стартовой странице приложения браузер отобразит flashed сообщение, извещающее о количестве зарегистрированных в базе данных приложения пользователей. Меня будет интересовать скорость обработки HTTP запроса на url адресе стартовой страницы, вернее время ответа сервера на полученный от браузера запрос.
В терминале с активным виртуальным окружением запускаю отладочный сервер приложения.
$ python runserver.py
Запускаю браузер. В окне браузера активирую инструменты разработчика (ctrl+shift+i
), и перехожу на вкладку Network
. Только после этого вбиваю в адресную строку браузера адрес стартовой страницы и жму enter
. Внимание на следующий снимок экрана.
Снимок экрана выше полностью подтверждает мои ожидания в части содержания страницы. В базе данных, в её таблице users на текущий момент имеется две строчки, браузер отобразил именно это значение в сообщении flashed. Обращаю внимание на время обработки запроса браузером, я выделил поле красным фломастером на снимке. Учитывая, что я разрабатываю, отлаживаю и тестирую приложение на своём стареньком лэптопе фирмы Acer с очень задумчивым процессором AMD e1, 232 миллисекунды — показатель вполне приемлемый. На современном серверном процессоре этот запрос будет отдан клиенту за вдвое более короткий промежуток времени. Любознательный и дотошный читатель может протестировать этот запрос в варианте подключения к базе данных по DSN
и убедиться, что рассматриваемый параметр (время) при этом увеличится чуть ли не в два раза. Использование для подключения пула в этом случае не намного сильно сократит затраты времени на обработку запроса, но качественно прибавит хлопот web-разработчику.
При тестировании приложения в браузере всегда следует очень настойчиво заглядывать в терминал, в котором запущен отладочный сервер, он даёт очень много информации для отладки. На текущий момент в терминале царит божья благодать, это значит, что разраб не зря кушает свой хлеб.
В Git
Прежде чем фиксировать очередную транзакцию в Git, следует отразить в хранилище изменения, которые я внёс на данном этапе разработки в виртуальное окружение, и сохранить новый состав виртуального окружения в файл requirements.txt
.
$ pip freeze > requirements.txt
Файл настроек приложения тоже получил новые поля в результате внесённых правок кода. Эти изменения тоже нужно сохранить в Git-хранилище проекта.
$ cp .env evn_template
И только после этого можно делать очередной commit. Код приложения в текущей версии можно скрупулёзно рассмотреть в моём профиле на github.com по этой короткой ссылке.
Подводим промежуточный итог
Разрабатываемое приложение в результате описанных в этой демонстрации действий получило инструмент для подключения к базе данных PostgreSQL, а это значит, что я могу приступить к разработке системы аутентификации пользователей будущего сайта, а сам процесс разработки уже со следующего выпуска в этом цикле статей станет более насыщенным, а у приложения начнёт появляться новый интересный функционал. Как растут белые грибы после непродолжительного дождя тёплым летним днём, видели? Продолжение следует...
Напоминаю, что продолжение этого цикла статей возможно, только если в блоге будет активность. Ваши посещения, подписки, лайки, комментарии и донаты имеют большое значение и качественно мотивируют автора, заряжают его соответствующей жизненной силой. Не оставайтесь в стороне, будет интересно...