Начальная конфигурация web-приложения на Python и Starlette
programming
На начальном этапе разработки web-приложения на Python и Starlette, о чём и пойдёт речь далее в этом выпуске нашего уютного блога, мне предстоит выполнить конфигурацию ASGI приложения, определить в нём хотя бы один url-адрес с обработчиком запросов на нём, запустить это приложение и получить доступ к этому url в самом обычном web-браузере. Выполнять все необходимые манипуляции я буду на заранее подготовленном рабочем месте с установленным и настроенным соответствующим образом Debian sid. Пожалуй приступим...
Базовые элементы конфигурации сети
Как известно, современный web описывает взаимодействие клиента и сервера в сети по протоколу HTTP, не устану всякий раз повторять этот тезис, ибо в нём содержатся ответы на многие вопросы. И поскольку мне нужно реализовать такое взаимодействие — описать его кодом, мне потребуется сеть. Сеть на домашнем десктопе... Где будем брать?
В общем-то сформулированная в предыдущем абзаце дилемма не особо сложная, достаточно посмотреть на базовые настройки сети рабочего десктопа с помощью нескольких весьма несложных команд. Как настроена и какие интерфейсы включает сеть, мне покажет команда ip.
$ ip a
Выхлоп этой команды содержит список сетевых интерфейсов, известных операционной системе. Вот как этот выхлоп выглядит на моём рабочем десктопе — домашнем компьютере.
Как видно на снимке экрана выше, первым в этом списке следует интерфейс с именем lo
, и для него уже определён IP-адрес подключения, 127.0.0.1
. Получить от этого интерфейса отклик мне поможет следующая команда, ping.
$ ping -c 5 127.0.0.1
Выхлоп этой команды покажет, что сетевой интерфейс великолепно откликается по своему IP, а если ещё и заглянуть в системный файл /etc/hosts
, то мы увидим, что к этому IP-адресу привязано имя хоста — localhost.
$ grep '127.0.0.1' /etc/hosts
Вот этот сетевой интерфейс я и предполагаю использовать для запуска отладочного web-сервера, а доступ к ключевым адресам web-приложения в процессе тестирования и отладки мне поможет получить самый обычный, всем знакомый и горячо любимый браузер. Таков план...
Структура каталогов web-приложения
Web-приложение состоит из файлов. Файлов будет много, файлы будут разного формата, в них будет содержаться код приложения, написанный на разных диалектах. Всё это великолепие впоследствии необходимо поддерживать, дорабатывать и развивать. А чтобы у выполняющих эти операции людей не получилось каши в голове, на начальном этапе разработки следует продумать структуру каталогов, в которых можно упорядочить хранение файлов по используемым форматам, диалектам и назначению. Фронтенду фронтендово, бэкенду бэкендово, и никак иначе. С чего начнём..?
На домашнем десктопе обычно хранится достаточно много всякого разного. Хорошо бы это всё хранимое упорядочить по тематике. И поскольку любое web-приложение в сущности своей является основой для web-сайтов, на своём десктопе я все свои web-разработки храню в одном каталоге с именем sites
. В этом каталоге каждое web-приложение хранится в отдельном вложенном каталоге, имя которого совпадает с именем этого web-приложения. Это может быть любое произвольное имя, для этой демонстрации я выберу имя website
, и каталог с этим именем буду считать корневым. Внутри корневого каталога будет храниться базовый каталог текущего web-приложения и различные вспомогательные файлы и каталоги. Базовый каталог я назову webapp
. Создать корневой и базовый каталоги можно с помощью команды mkdir прямо в терминале.
$ mkdir -p sites/website/webapp
В итоге я имею корневой каталог с именем website
и вложенный в него базовый каталог с именем webapp
. Вхожу в корневой каталог.
$ cd sites/website
В базовом каталоге (webapp
) будет храниться код web-приложения упорядоченный по диалектам и назначению. Поскольку я пишу приложение на языке программирования Python, создаю в этом каталоге файл с именем __init__.py
.
$ touch webapp/__init__.py
Рядом с этим файлом создаю каталог с именем main
, в нём будет храниться код главной подпрограммы web-приложения, и это будут файлы с расширением .py
, написанные на языке программирования Python.
$ mkdir webapp/main
Разрабатываемое приложение будет расширяемым, и чуть позже в нём появятся новые подпрограммы, соответственно для каждой подпрограммы свой каталог в базовом каталоге приложения.
Внутри каталога с именем main
создаю файл с именем __init__.py
, так в Python обычно определяются пакеты.
$ touch webapp/main/__init__.py
С помощью команды tree можно увидеть, как на текущий момент выглядит дерево корневого каталога.
На текущий момент разработки мы имеем следующее:
-
website
— корневой каталог приложения; -
webapp
— базовый каталог приложения; -
main
— каталог главной подпрограммы приложения.
Корневой и базовый каталоги следует научиться различать, эти термины я буду употреблять в этом цикле статей часто. На следующих этапах разработки я создам в этой структуре другие ключевые каталоги для хранения файлов других форматов, всё это по мере необходимости.
Виртуальное окружение
В процессе разработки приложения website я буду использовать сторонний код на Python, достаточно много разнообразных тематических библиотек, разработанных другими авторами. Все эти библиотеки будет необходимо правильно установить. Работая в операционной системе Debian, можно использовать два различающихся способа установки сторонних библиотек. Сторонние библиотеки можно установить непосредственно в систему из официального хранилища пакетов Debian с помощью пакетного менеджера apt. Но в этом случае мы привязываем приложение к официальному хранилищу конкретного вендора, и что будем делать, если в нём не окажется необходимой библиотеки, не совсем ясно. Второй путь предполагает установку сторонних библиотек из индекса PyPI, и мне он представляется более гибким решением, позволяющим в случае необходимости перенести приложение с Debian на любую другую операционную систему.
В рамках этого цикла статей я пойду по второму пути. Это потребует создать так называемое виртуальное окружение. Обращаю ваше внимание, что виртуальное окружение я буду создавать находясь в корневом каталоге web-приложения вот такой командой.
$ python3 -m venv venv
Здесь ключ -m
определяет имя исполняемого модуля, в данном случае это имя venv
. Последний аргумент команды — venv
в конце команды, определяет имя каталога, в котором будет храниться созданное в результате исполнения этой команды виртуальное окружение.
Важное замечание: чтобы предложенная команда сработала, в операционной системе должен быть установлен пакет python3-venv.
В итоге, в корневом каталоге появится ещё один вложенный каталог — каталог с только что созданным виртуальным окружением. И я получаю возможность его активировать.
$ source venv/bin/activate
Как видно на снимке экрана выше, в итоге активации только что созданного виртуального окружения у приглашения командной строки появился префикс (venv)
— это имя активного в этом терминале виртуального окружения. А я получаю возможность установить в это виртуальное окружение любой доступный в индексе PyPI пакет и использовать его в своём web-приложении.
Установка сторонних библиотек Python
В рамках этого цикла статей я собрался писать web-приложение с помощью и на основе web-фреймворка общего назначения, который называется Starlette. Эта библиотека со свободной лицензией опубликована в индексе PyPI, и я могу установить её в своё текущее виртуальное окружение вот такой простой командой.
$ pip install starlette
Выхлоп этой команды достаточно многословен, и я не буду его приводить здесь полностью, менеджер пакетов Pip загрузит из сети Интернет все необходимые пакеты, включая зависимости, и распакует их в заданные сценарием установки каталоги, а выполнение каждого шага сопроводит отчётом в терминал. В самом конце выхлопа должна содержаться строка Successfully installed и перечень всех установленных этой командой пакетов.
С помощью инструментов Starlette уже в ближайшей перспективе я создам экземпляр ASGI приложения, которое запустить можно с помощью соответствующего ASGI сервера. Возможных вариантов таких серверов известно несколько. В процессе разработки мне понадобится отладочный сервер, и я буду использовать Uvicorn, он вполне успешно справится с моими текущими задачами. Эта библиотека тоже содержится в индексе PyPI, и установить её также просто.
$ pip install 'uvicorn[standard]'
Выхлоп этой команды тоже должен содержать строку Successfully installed с перечнем всех установленных по зависимостям пакетов. Если так, то можно двигаться дальше...
Конфигурация ASGI-приложения
Настало время обзавестись собственным экземпляром Starlette, для этого открываю файл __init__.py
в текстовом редакторе. Поскольку я работаю в консоли и использую консольный редактор, снимать пальцы с основной позиции на клавиатуре нет необходимости. Вбиваю в приглашение командной строки следующую команду.
$ vim webapp/__init__.py
Этот "инит" будет хранить все настройки web-приложения, привязки ключевых каталогов, карту url-адресов и прочий полезный инструментарий.
Первым делом мне необходимо создать экземпляр ASGI приложения. Подключаю в этот модуль необходимые мне объекты из только что установленной библиотеки starlette с помощью соответствующих процедур импорта.
from starlette.applications import Starlette
from starlette.routing import Route
from starlette.types import Receive, Scope, Send
Класс Starlette определяет ASGI приложение, с его помощью я могу создать и некоторым образом дополнить собственный экземпляр. Класс Route определяет url-адрес ASGI приложения, с его помощью я буду создавать в приложении точки доступа и привязывать к ним соответствующие обработчики. Объекты Receive, Scope и Send являются вспомогательными, на них будут ссылки в аннотациях.
Разрабатываемое приложение должно быть гибким, для этого ему потребуется файл настроек, который я создам чуть позже. На текущем этапе разработки я определю в карте url-адресов этого приложения всего одну точку доступа — стартовую страницу сайта. Для этой точки доступа мне понадобится обработчик, и он будет храниться в другом модуле, и даже в другом пакете. Дописываю в текущий "инит" ещё пару процедур.
from .dirs import settings
from .main.views import show_index
На данный момент модуль dirs
не существует, как не существует и объект settings
. То же самое можно сказать и о модуле views
из пакета main
. Эти файлы и объекты в них я создам чуть позже.
Базовый библиотечный класс Starlette в процессе разработки этого web-приложения мне будет необходимо некоторым образом модифицировать, но редактировать библиотечные файлы не принято, поэтому я создам собственный класс, который будет наследовать от библиотечного класса Starlette. Этому классу даю имя StApp
, и переопределяю его метод __call__
. Вписываю код этого класса в открытый в текстовом редакторе файл.
class StApp(Starlette):
async def __call__(
self, scope: Scope, receive: Receive, send: Send) -> None:
scope["app"] = self
self.config = settings
if self.middleware_stack is None:
self.middleware_stack = self.build_middleware_stack()
await self.middleware_stack(scope, receive, send)
На данный момент модификация затронула только свойство config
, к нему я привязал ранее подключенный объект settings
, которого на данный момент не существует, чуть позже я его создам.
Имея модифицированный класс StApp
, уже прямо сейчас я могу создать собственный экземпляр этого класса — app
.
app = StApp(
debug=settings.get('DEBUG', cast=bool),
routes=[
Route('/', show_index, name='index')])
Здесь следует обратить внимание на две мелкие детали...
-
Параметр
debug
и присвоенное ему значение, оно будет определяться содержанием файла настроек. -
Параметр
routes
и первый элемент в этом списке, который является экземпляром библиотечного классаRoute
. В нём первый параметр определяет url-адрес стартовой страницы, к которому привязан указанный вторым параметром обработчик — функция представленияshow_index
, она импортирована из модуляviews
пакетаmain
и на данный момент не существует.
Сохраняю изменения в файл. И вот как код только что отредактированного "инита" выглядит в моём текстовом редакторе.
Чтобы этот код заработал без ошибок и исключений, необходимо создать уже подключенные в "инит" объекты settings
и show_index
. Начнём с первого.
У текстового редактора Vim, как известно, есть несколько режимов работы, в том числе командный режим. Чтобы создать и открыть в нём новый файл, достаточно перейти в командный режим и ввести следующую команду.
:tabnew webapp/dirs.py
Несложно догадаться, что сейчас я буду конструировать и править файл dirs.py
из базового каталога web-приложения.
В этом файле я создам привязку базового каталога и выражу его адрес в файловой системе терминами Питона, в этом мне поможет модуль стандартной библиотеки os
. Кроме этого, мне необходим файл настроек приложения, его имя и адрес в файловой системе я задам параметрами экземпляра библиотечного класса Config
. Вот как будет выглядеть код.
import os
from starlette.config import Config
base = os.path.dirname(__file__)
settings = Config(os.path.join(os.path.dirname(base), '.env'))
Здесь следует обратить внимание на второй параметр объекта settings
, строку .env
, она будет определять имя файла настроек, и храниться файл с этим именем будет в корневом каталоге приложения, рядом с базовым каталогом.
Сохраняю изменения в файл. Создаю новый файл с именем .env
, ввожу в командном режиме Vim следующую команду.
:tabnew .env
Редактирую этот файл. На текущий момент файл настроек будет содержать одну единственную строчку.
DEBUG=True
Параметр DEBUG
содержит булево значение, как определено в "ините".
Функцию представления с именем show_index
я создам в новом файле, в полном соответствии с ранее вписанной в "инит" процедурой импорта. Ввожу в командном режиме Vim следующую команду.
:tabnew webapp/main/views.py
Прямо сейчас в приложении пока нет ни инструмента для программирования шаблонов, ни статических файлов, ни хранилища данных. По этой причине в своей первой реализации функция представления show_index
не будет содержать какой-то оригинальной логики. Я просто задам шаблон с элементарным HTML в виде обычной строки, и верну экземпляр библиотечного класса HTMLResponse
с этим шаблоном в качестве параметра.
from starlette.responses import HTMLResponse
async def show_index(request):
template = '<div>Сайт в стадии разработки.</div>'
return HTMLResponse(template)
Сохраняю изменения в файл. Вот как это выглядит в тестовом редакторе на моём рабочем десктопе.
В моих ожиданиях, после запуска приложения и отправки GET запроса на стартовую страницу приложения, сервер должен ответить страницей с HTML кодом, который хранится в объекте template
функции представления show_index
. На повестке остаётся один единственный вопрос... Как только что спроектированный код запустить и протестировать?
Конфигурация отладочного сервера
В виртуальное окружение я установил библиотеку Uvicorn, и теперь, когда у меня есть собственный экземпляр ASGI приложения, я могу с помощью инструментов этой библиотеки реализовать самый обычный отладочный сервер.
В окне текстового редактора, в его командном режиме ввожу следующую команду.
:tabnew runserver.py
Здесь следует обратить внимание, что файл с именем runserver.py
будет создан в корневом каталоге приложения, рядом с базовым каталогом и файлом настроек. Пишу в этот файл следующий код.
import uvicorn
if __name__ == '__main__':
uvicorn.run(
'webapp:app', host='127.0.0.1',
reload=True, port=5000, log_level='info')
Здесь всё просто... В стандартной идиоме Питона содержится одна единственная процедура вызова библиотечной функции run
с заданными параметрами. Здесь следует обратить внимание на параметр host
— об этом IP адресе мы говорили в самом начале этой демонстрации, на этом адресе отладочный сервер будет слушать сеть, и к нему можно будет подключиться из любого клиента, например, браузера. Второй заслуживающий внимания параметр — port
, для этого конкретного приложения я буду использовать порт 5000
. ASGI серверу конечно же нужен экземпляр соответствующего приложения, его я задал первым параметром, он имеет вид webapp:app
. Здесь webapp
— имя базового каталога, а app
— имя экземпляра приложения, который определён в "ините" базового каталога.
Соответственно, я ожидаю, что по адресу localhost:5000
только что созданный сервер откликнется и вернет HTML-страницу с определённым в соответствующей функции представления шаблоном.
Давайте посмотрим на дерево корневого каталога приложения в конечном для данной демонстрации итоге.
Итого, я создал виртуальное окружение и отредактировал пять текстовых файлов в соответствующих каталогах размещения. Далее в процессе разработки в этом дереве будут добавляться новые каталоги и файлы, и таким образом я буду расширять возможности разрабатываемого web-приложения. А пока мне нужно протестировать полученный код.
Тестирование в браузере
Итак, у меня есть приложение и отладочный сервер, в котором я могу это приложение запустить и протестировать предложенные решения. Покидаю текстовый редактор Vim, и в окне этого же терминала, а в нём активно виртуальное окружение, пробую исполнить файл runserver.py
интерпретатором Python активного виртуального окружения.
$ python runserver.py
После запуска этой команды сервер выдаст в своём выхлопе краткий отчёт о выполненных действиях, в том числе укажет адрес, по которому к этому отладочному серверу можно получить доступ. В этом отчёте не должно быть ошибок. Если так, а в моём случае это так, ошибок нет, запускаю самый обычный Интернет-браузер и стучусь в нём по указанному выше адресу, подойдёт как IP-адрес, так и доменное имя.
На снимке экрана видно, что мои запланированные ожидания исполнились, и сервер на GET запрос клиента (браузера) вернул HTML-страницу заданного содержания. Давайте бросим взгляд в окно терминала и посмотрим на отчёт отладочного сервера.
Как видно на снимке экрана выше, в выхлопе сервера после запроса в браузере стартовой страницы web-приложения появились две строчки, описывающие полученные сервером запросы. Оба запроса с методом GET, но у первого статус код ответа 200
, а у второго статус код ответа 404
, что неудивительно, ибо в карте url-адресов приложения пока содержится только один адрес — адрес стартовой страницы. Браузер же на автомате запрашивает у сервера иконку для избранного, которой в web-приложении пока не определено, в результате чего сервер отвечает соответствующим статус кодом, иконка не найдена. Эту досадную неприятность я обязательно устраню на предстоящих этапах проектирования, которые планирую описать в следующих выпусках этого блога. А пока отладочный сервер можно остановить сочетанием ctrl+c
, так как все цели этой демонстрации полностью достигнуты.
Подводим промежуточный итог
Web-приложение в самом общем случае довольно сложный продукт с весьма трудоёмким, длительным и запутанным процессом разработки, оно включает в себя серверный код, клиентский код, дизайн, вёрстку и прочие непонятные неосведомлённому пользователю составляющие. В этой статье я описал только самый начальный этап проектирования web-приложения на языке программирования Python с помощью инструментов Starlette. Имея собственный экземпляр ASGI приложения и отладочный сервер, я могу без труда продолжить разработку, и далее в этом цикле статей я планирую описать многие аспекты этой работы более скрупулёзно и детально. Насколько далеко я зайду в реализации этих планов, зависит от активности в блоге — ваши посещения, подписки, лайки, комментарии, донаты служат главным мотивирующим меня фактором. Все статьи цикла доступны в хронологическом порядке по метке webapp. Не оставайтесь в стороне, продолжение следует, будет интересно...