База данных PostgreSQL в web-приложении на Python и Starlette

programming

Опубликован:
2024-12-25T03:14:39.550881Z
Отредактирован:
2024-12-25T03:14:39.550881Z
Статус:
публичный
24
0
0

Для продолжения проектирования 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

YeLi0qKoPG.png

На снимке экрана видно, что в результате выполнения этой команды на терминале появилось сообщение 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 можно двумя способами:

  1. С использованием индивидуального подключения;

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

Первый вариант является очень простым, самым безотказным и самым надёжным. При этом затраты на создание индивидуального подключения невелируются характерной особенностью настройки 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 выглядит после правок в окне моего текстового редактора, внимание на следующий снимок экрана.

oiaAniALTT.png

Всё... Можно приступать к тестированию разработанного кода в браузере.

Тестируем в браузере

Я ожидаю, что на стартовой странице приложения браузер отобразит flashed сообщение, извещающее о количестве зарегистрированных в базе данных приложения пользователей. Меня будет интересовать скорость обработки HTTP запроса на url адресе стартовой страницы, вернее время ответа сервера на полученный от браузера запрос.

В терминале с активным виртуальным окружением запускаю отладочный сервер приложения.

$ python runserver.py

Запускаю браузер. В окне браузера активирую инструменты разработчика (ctrl+shift+i), и перехожу на вкладку Network. Только после этого вбиваю в адресную строку браузера адрес стартовой страницы и жму enter. Внимание на следующий снимок экрана.

ARLtRjJAE2.png

Снимок экрана выше полностью подтверждает мои ожидания в части содержания страницы. В базе данных, в её таблице users на текущий момент имеется две строчки, браузер отобразил именно это значение в сообщении flashed. Обращаю внимание на время обработки запроса браузером, я выделил поле красным фломастером на снимке. Учитывая, что я разрабатываю, отлаживаю и тестирую приложение на своём стареньком лэптопе фирмы Acer с очень задумчивым процессором AMD e1, 232 миллисекунды — показатель вполне приемлемый. На современном серверном процессоре этот запрос будет отдан клиенту за вдвое более короткий промежуток времени. Любознательный и дотошный читатель может протестировать этот запрос в варианте подключения к базе данных по DSN и убедиться, что рассматриваемый параметр (время) при этом увеличится чуть ли не в два раза. Использование для подключения пула в этом случае не намного сильно сократит затраты времени на обработку запроса, но качественно прибавит хлопот web-разработчику.

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

LNlf4gezS8.png

В Git

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

$ pip freeze > requirements.txt

Файл настроек приложения тоже получил новые поля в результате внесённых правок кода. Эти изменения тоже нужно сохранить в Git-хранилище проекта.

$ cp .env evn_template

И только после этого можно делать очередной commit. Код приложения в текущей версии можно скрупулёзно рассмотреть в моём профиле на github.com по этой короткой ссылке.

Подводим промежуточный итог

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

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