Создание REST API приложения со Swagger документацией на Flask Python

Создание REST API приложения со Swagger документацией на Flask

Сейчас, с развитием информационных и иных технологий, способность создаваемых нами программ функционировать в онлайн режиме через сеть Internet, является чуть ли не основополагающим требованием для их потенциальной востребованности и массового использования. Действительно, работа наших программ в онлайн в виде web-приложений дает массу преимуществ, начиная от обеспечения их кроссплатформенности без соответствующих инсталляций и, заканчивая возможностью мгновенного доступа к наиактуальнейшим версиям кода и используемых этими приложениями баз данных.

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

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

Особенно актуальным применение Flask является при создании REST API web-приложений с целью обработки общедоступной информации, например, в виде открытых данных, удаленно хранящихся на серверах порталов открытых данных. В этой статье мы как раз и будем рассматривать пример создания такого чрезвычайно простого REST API web-приложения, которое, благодаря фиктивной реализации таких CRUD действий, как: создание, чтение, модификация и удаление, сможет через API смоделировать работу с виртуальными данными (сущностями).

В частности, в данной статье будет рассмотрено, как просто, гибко, модульно и, во многом, даже увлекательно можно создать вышеназванное web-приложение, имея при этом лишь базовые знания по Python. Это в свою очередь позволит вам, используя полученные таким образом навыки, легко и быстро приступить к разработке собственных реальных приложений на Python с применением Flask.

Что такое Flask Python?

Под термином фреймворк, переводимым с английского, как остов или каркас, обычно подразумевается библиотека готовых функций, классов и иных программных структур, используемых разработчиками для создания и поддержки надежных, масштабируемых web-приложений. В частности, для Python на сегодняшний день, кроме Flask наиболее популярными являются такие фреймворки, как: Tornado, Pyramind и, конечно же, Django (которую часто сравнивают с Flask).

Сам же Flask(🌶) представляет собой минималистичный фреймворк (микрофреймворк), используемый для web-разработки, основанной на WSGI стандарте взаимодействия между Python кодом и web-сервером. Несмотря на то, что Flask весьма невелик по размерам и обладает минимальным интерфейсом WSGI взаимодействия, он в то же время объединяет в себе простоту использования и достаточно обширную функциональность. К тому же, данный фреймворк предусматривает обширные возможности, как для простого и быстрого запуска создаваемых на нем приложений, так и для их масштабирования вплоть до сложных web-сервисов с многотысячными ежедневными посещениями.

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

Что такое REST API?

Под термином REST API, также употребляемым, как RESTful, принято подразумевать способ взаимодействия web-приложений с сервером.

Вышеназванный термин состоит из двух аббревиатур, которые расшифровываются следующим образом. API (интерфейс прикладного программирования) представляет собой набор функций, использующихся для формирования интерфейса взаимодействия (обмена данными) между сервером с клиентскими web-приложениями. REST же (Передача состояния представления) является своеобразным каркасом для непосредственного взаимодействие сервера с клиентскими web-приложениями через API с помощью протокола HTTP. Также REST принято ассоциировать с неким архитектурным стилем (набором правил), обеспечивающим взаимодействие компонентов клиент-серверного приложения в сети без сохранения состояния связи между ними, но при этом четко разделяющим задачи сервера и клиентских приложений.

Как уже было упомянуто выше, ниже в данной статье рассмотрен процесс создания REST API web-приложения (сервиса), моделирующего CRUD действия над виртуальными сущностями посредством использования соответствующего API.

Как создать REST API приложение на Flask?

Итак, благодаря данной статьи мы с вами достаточно подробно рассмотрим все основные шаги, которые должны быть предприняты при создании REST API приложения с помощью Flask(🌶).

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

image

Рис. 1. Скриншот финального сервиса нашего web-приложения, созданного в Flask на основе генерирования Swagger API документации по всем предшествующим сервисам данного приложения

Примечание. В отношении вышеприведенного скриншота безусловно необходимо отметить, что наличие столь изящного вида и достаточно широкой функциональности у отображаемого на нем финального web-сервиса, главным образом, обусловлено генерированием соответствующей Swagger API документации для всех сервисов нашего будущего приложения. Вместе с тем, существенное внимание, отводимое в статье вопросам генерации API документации, несомненно, является гораздо более важной составляющей для создания REST API приложений нежели освещение потенциальных возможностей их дизайна.

Увидеть работу web-приложения очень похожего на создаваемый нами сервис в рамках данной статьи можно перейдя на соответствующий англоязычный сайт с демонстрацией возможностей Swagger API документации. Готовый же Python, HTML и CSS код для создаваемого нами в рамках этой статьи web-приложения можно скачать по следующей ссылке.

Создание виртуальной среды под Flask проект

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

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

mkdir flask_api_demo
cd flask_api_demo

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

# Для Linux подобных операционных систем
$ python3 -m venv venv 

# Для Windows
> python3 -m venv venv

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

# Для Linux подобных операционных систем
$ .venv/bin/activate

# Для Windows
> venv\Scripts\activate

Последняя команда обеспечить нам в папке с нашем проектом обособленную интегрированную среду разработки (IDE), где мы сможем устанавливать любые внешние программные пакеты для Python или даже непосредственно сам Python интерпретатор нужной нам версии, не боясь при этом, как-то навредить другим нашим проектам. Естественно, мы можем при необходимости покидать нашу виртуальную среду и снова в нее заходить, установив каталог нашего проекта в качестве текущего и выполнив ранее приведенную команду activate.

Всегда, прежде чем приступать к работе в той или иной виртуальной среде следует удостовериться, в том, что интересующая нас среда является активной. С этой целью достаточно просто обратить внимание на левую сторону консоли. Если там с самого начала строки будет стоять – (venv), значить все в порядке и, интересующая нас виртуальная среда является активной.

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

deactivate

Процесс установки Flask

Поскольку Flask является обычным внешним программным пакетом Python, то его установка также производится с помощью соответствующей стандартной системы управления пакетами pip через следующую команду:

pip install Flask

Примечание. Следует отметить, что на момент написания данной статьи уже существовала версия Flask – 2.2.2, которая, однако, пока еще при работе с расширениями по генерированию Swagger API документации выдавала ошибки. Поэтому в данной статье используется Flask – 2.1.2, являющейся последней стабильной версией рассматриваемого фреймворка, позволяющей корректно работать, как с внешним пакетом flask-restx для генерации вышеназванной API документации, так и с пакетом Werkzeug, расширяющим возможности создаваемых нами web-приложений. Вместе с тем, вполне возможно, что довольно скоро данная стабильная версия также уже устареет. Но, тем не менее, если вы все же не пожелаете «испытывать судьбу» и, все нижеследующие практические примеры захотите выполнять в версии Flask, идентичной той, которая используется в этой статье, то вместо команды pip install вам нужно будет воспользоваться файлом requirements.txt, размещенном в корневом каталоге рассматриваемого проекта:

Flask==2.1.2
flask-restx==0.5.1
Werkzeug==2.1.2
gunicorn==20.1.0

Открыв, приведенное выше содержимое файла requirements.txt, мы увидим, что кроме Flask он дополнительно содержит еще три внешних пакета, задействованные при последующем создании нашего web-приложения. Но подробнее все эти пакеты мы рассмотрим далее в нашей статье. Сейчас же установим перечисленные в файле requirements.txt пакеты запустив команду:

pip install -r requirements.txt

После выполнения данной команды, мы смело можем уже приступать к разработке наших, предусматриваемых этой статьей, REST API web-сервисов. Вообще, следует отметить, что использование файла requirements.txt является довольно популярным средством, чрезвычайно упрощающим распространение Python проектов благодаря обеспечению автоматической установки для них всех необходимых внешних пакетов.

Наша первая страничка в web-приложении

Вот и наступило время попытаться создать нашу первую web-страничку на Flask. Для этого в каталоге с проектом нам нужно будет создать файл page1.py, а затем вставить в него следующий код:

# page1.py
from flask import Flask
app = Flask(__name__)

@app.route('/page_primer)
def welcome_text():
    return '<h1>Мне кажется, или я реально уже в браузере?!</h1>'

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

Как мы видим, вышеприведенный код для данной странички весьма прост, но уже подразумевает наличие у нее URL адреса, заканчивающегося на /page_primer, а также вывод текста в виде заголовка, отформатированного с помощью HTML тега h1. Однако, перед тем как открывать данную страничку, нам еще понадобиться определить для Flask переменные среды, а также запустить его в качестве сервера разработки. К сожалению, синтаксис команд, определяющих переменные для Flask различается в зависимости от операционных систем и командных оболочек. Поэтому, ниже приведены варианты использования этих команд для наиболее распространенных ситуаций:

# Определение Flask переменных среды в Linux подобных системах
export FLASK_APP=page1.py
export FLASK_DEBUG=1

# Определение Flask переменных среды в командной строке (CMD) Windows
set FLASK_APP=page1.py
set FLASK_DEBUG=1

# Определение Flask переменных среды в командной оболочке PowerShell под Windows
$env:FLASK_APP = "page1.py"
$env:FLASK_DEBUG = "1"

# Запуск сервера разработки (не зависимо от операционных систем и
# командных оболочек)
flask run

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

После запуска сервера разработки, мы уже смело сможем переходить в любой наш излюбленный браузер, где после вставки в адресную строку URL http://localhost:5000/page_primer откроется наша перва web-страничка, созданная на Flask.

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

if __name__ == "__main__":
    app.run(debug=True)

При обработке этих строк, интерпретатор Python понимает, что именно файл, их содержащий, является web-приложением, для которого нужно запустить сервер разработки в режиме отладки. Давайте добавим данные строки в файл page1.py с кодом нашей первой web-странички:

# page1.py
from flask import Flask
app = Flask(__name__)

@app.route('/page_primer)
def welcome_text():
    return '<h1>Мне кажется, или я реально уже в браузере?!</h1>'

if __name__ == "__main__":
    app.run(debug=True)

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

python page1.py

После этого можно переходить в браузер по уже упоминаемой выше ссылке http://localhost:5000/page_primer и любоваться нашей web-страничкой.

REST API сервис со структурой CRUD

Создав нашу первую web-страничку и получив начальное представление о том, что такое Flask, мы уже смело можем возвращаться к основной цели данной статьи и приступать к созданию нашей первой, но отнюдь не окончательной в этой статье, версии REST API web-сервиса с базовой CRUD структурой. Для этого, с целью создания API запросов, осуществляющих CRUD действия над отдельными сущностями (элементами), давайте сначала создадим (или перепишем из архива в свой проект) файл page2.py, куда вставим следующий код:

# page2.py
from flask import Flask, request
app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False
app.config['JSON_SORT_KEYS'] = False

@app.route('/primery_api', methods=['GET', 'POST'])

def sushchnosti():
    if request.method == "GET":
        return {
            'сообщение': 'Настоящий API запрос должен возвращать'
                         ' список сущностей',
            'метод': request.method
        }
    if request.method == "POST":
        return {
            'сообщение': 'Данный API запрос обеспечивает создание'
                         ' дополнительной сущности',
            'дополнительная_сущность': request.json,
            'метод': request.method
            }

@app.route('/primery_api/<int:element_id>', methods=['GET', 'PUT', 'DELETE'])
def element(element_id):
    if request.method == "GET":
        return {
            'id': element_id,
            'сообщение': 'Настоящий API запрос возвращает информацию об'
                         ' отдельной сущности {}'.format(element_id),
            'метод': request.method
        }
    if request.method == "PUT":
        return {
            'id': element_id,
            'сообщение': 'Данный API запрос обновляет отдельную сущность {}'
            .format(element_id),
            'метод': request.method,
        'body': request.json
        }
    if request.method == "DELETE":
        return {
            'id': element_id,
            'сообщение': 'Настоящий API запрос удаляет отдельную сущность {}'
            .format(element_id),
            'метод': request.method
        }

if __name__ == "__main__":
    app.run(debug=True)

Вышеприведенный код в зависимости от тех или иных URL маршрутов обеспечить нам осуществление следующих API запросов в рамках CRUD действий:

  • GET / primery_api – получение списка сущностей
  • POST/ primery_api – создание отдельной (дополнительной) сущности
  • GET / primery_api/ – получение информации об отдельной сущности
  • PUT / primery_api/ – обновление отдельной сущности
  • DELETE / primery_api/ – удаление отдельной сущности

Примечание. Содержащиеся в начале раннее приведенного кода строчки, задающие соответствующую конфигурацию Flask: app.config['JSON_AS_ASCII'] = False и app.config['JSON_SORT_KEYS'] = False чрезвычайно важны для корректного отображения в REST API приложениях интерфейсного текста, нуждающегося в unicode кодировке. Так, если бы в нашем приложении не было бы задано app.config['JSON_AS_ASCII'] = False, то весь его кириллический интерфейсный текст вместо кодировки UTF-8 был бы закодирован в ASCII и, следовательно, отображался бы в виде непонятных закорючек. Если же мы не задали бы app.config['JSON_SORT_KEYS'] = False, то Flask автоматически сортировал бы все наши интерфейсные надписи и передаваемые JSON параметры в алфавитном порядке, а не так, как мы бы их задавали бы в приложении или же при передаче в JSON формате.

Поскольку наше приложение предусматривает весь спектр CRUD действий, то реализуемое им API практически полностью охватывает всю необходимую нам функциональность. Однако, данное приложение отнюдь еще не является масштабируемым, так как, во-первых, требует ручного ввода каждого, заданного нами в коде, URL маршрута, а во-вторых, имеет недостаточно жесткую структуру. Поэтому, в следующих разделах данной статьи мы с вами рассмотрим более масштабируемое решение, под которое, по мере разработки новых web-страничек и запросов, можно будет легко создавать надлежащую API документацию.

Чтобы воспользоваться теми запросами, которые были предусмотрены в приведенном выше коде, нам с вами в зависимости от используемых операционных систем и оболочек командных строк, к сожалению, придется применять различный синтаксис команд. Так, например, чтобы проверить работу POST запроса для создаваемого нами REST API web-сервиса можно воспользоваться следующими командами:

# POST запрос с использованием программы CURL в Linux подобных системах
curl -X POST -H "Content-Type: application/json" -d
    '{"familiya": "Drozd", "imya": "Elena"}' http://localhost:5000/primery_api

# POST запрос с использованием программы CURL в командной строке (CMD) Windows
curl -X POST -H "Content-Type: application/json" -d
     """{"familiya": "Drozd", "imya": "Elena"}""" http://localhost:5000/primery_api

# POST запрос с использованием командлета Invoke-RestMethod в командной оболочке
# PowerShell под Windows
$param = @{
    Uri         = "http://localhost:5000/primery_api"
    Method      = "Post"
    Body        = '{"familiya": "Drozd", "imya": "Elena"}'
    ContentType = "application/json"
}
Invoke-RestMethod @param

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

# Вывод программы CURL для соответствующего POST запроса в Windows или
# в Linux подобных системах

{
  "сообщение": "Данный API запрос обеспечивает создание дополнительной сущности",
  "дополнительная_сущность": "{familiya: Drozd, imya: Elena}",
  "метод": "POST"
}

# Вывод командлета Invoke-RestMethod для соответствующего POST запроса в оболочке
# PowerShell под Windows

сообщение                                      дополнительная_сущность       метод
---------                                      -----------------------       -----
Этот API запрос создает дополнительную сущность @{familiya=Drozd; imya=Elena} POST

Примечание. Следует отметить, что в случае, если через POST запрос с помощью раннее приведенных команд данные передаются не в ASCII кодировке, то возникает ошибка декодирования. Для того, чтобы избежать этой проблемы все необходимые нам данные для передачи через POST запрос в JSON формате следует записывать в виде отдельного json файла в кодировке UTF-8. Это можно сделать посредством использования, например, редактора Notepad++. Готовый же JSON файл можно «подтянуть» в соответствующий POST запрос, например, при помощи следующей команды:

curl -X POST -H "Content-Type: application/json"
     -d @D:\request_body.json http://localhost:5000/primery_api
где содержимое файла request_body.json, размещенного на диске D передается через POST запрос по URL http://localhost:5000/primery_api.

При этом, отдельно следует отметить, что вышеприведенная CURL команда с POST запросом на основе JSON файла является универсальной как для Windows, так и для Linux подобных систем. Кроме того, данная CURL команда также может применяться и для командной оболочки PowerShell под Windows.

Вывод результата приведенной выше CURL команды может, например, отображаться в следующем виде:

{
  "сообщение": "Данный API запрос обеспечивает создание дополнительной"
               " сущности",
  "дополнительная_сущность": {
    "Фамилия": "Стрелковская",
    "Имя": "Елена",
    "Отчество": "Петровна"
  },
  "метод": "POST"
}

Наилучшем же вариантом для разработки, формирования и отладки API запросов, конечно же, несомненно является Postman. Безусловно, это довольно сложный и многофункциональный программный инструментарий, изучение которого, к сожалению, выходит за рамки нашей статьи. Однако, если вы действительно хотите в совершенстве овладеть навыками создания REST API web-приложений, то вам просто не обойтись без основательного изучения Postman.

Схемы (подпроекты)

Одной из действительно сильных сторон Flask является возможность применения в нем так называемых схем (подпроектов). Схема (на английском – Blueprint) представляет собой класс, экземпляры которого очень схожи с непосредственным Flask приложением. Однако, вместо инициализации нового приложения, вновь создаваемые экземпляры Blueprint инициализируются в виде дополнительных программных блоков (своеобразных подпроектов), призванных в том или ином аспекте расширять текущее приложение. Такая возможность, например, может быть полезной, при необходимости создания нескольких версий API или просто для разделения функций в одном приложении.

Мы с вами будем использовать вышеназванный класс для определения различных сценариев (web-сервисов), входящих в состав создаваемого нами в этой статье web-приложения с элементами REST API функциональности. Для начала же, давайте преобразуем приведенные раннее коды уже созданных нами ресурсов (web-странички и первоначального web-сервиса) в соответствующие схемы (подпроекты) с целью их объединения в единый web-ресурс.

С этой целью в каталоге с проектом нам следует создать (или переписать из архива) новый подкаталог blueprints, где далее мы должны будем размещать соответствующие схемы (подпроекты) по мере добавления функциональности для нашего итогового web-приложения. Кроме того, внутри подкаталога blueprints нам опять же потребуется создать подкаталог azy, в котором под наши первоначальные примеры (своеобразные азы практической работы во Flask), сформировать еще два соответствующие подкаталога page_primer и primery_api. Затем, в каждом из этих двух подкаталогов нам нужно будет также создать отдельные пакетные файлы с одинаковым названием __init__.py, но с разным содержанием. Так, для подкаталога page_primer нужен будет пакетный файл __init__.py следующего содержания:

# blueprints/azy/page_primer/__ini__.py
from flask import Blueprint

blueprint = Blueprint('page', __name__, url_prefix='/azy')

@blueprint.route('/page_primer')
def welcome_text():
    return '<h1>Мне кажется, или я реально уже в браузере?!</h1>'

Для каталога же primery_api потребуется такой же пакетный файл с аналогичным названием, в который нужно будет вставить следующее содержание:

# blueprints/azy/primery_api/__ini__.py
from flask import Blueprint, request

blueprint = Blueprint('api', __name__, url_prefix='/azy')

@blueprint.route('/primery_api', methods=['GET', 'POST'])
def sushchnosti():
    if request.method == "GET":
        return {
            'сообщение': 'Настоящий API запрос должен возвращать'
                         ' список сущностей',
            'метод': request.method
        }
    if request.method == "POST":
        return {
            'сообщение': 'Данный API запрос обеспечивает создание'
                         ' дополнительной сущности',
            'дополнительная_сущность': request.json,
            'метод': request.method
            }

@blueprint.route('/primery_api/<int:element_id>', methods=['GET', 'PUT', 'DELETE'])
def element(element_id):
    if request.method == "GET":
        return {
            'id': element_id,
            'сообщение': 'Настоящий API запрос возвращает информацию об отдельной
                         ' сущности {}'.format(element_id),
            'метод': request.method
        }
    if request.method == "PUT":
        return {
            'id': element_id,
            'сообщение': 'Данный API запрос обновляет отдельную сущность {}'
            .format(element_id),
            'метод': request.method,
        'body': request.json
        }
    if request.method == "DELETE":
        return {
            'id': element_id,
            'сообщение': 'Настоящий API запрос удаляет отдельную сущность {}'
            .format(element_id),
            'метод': request.method
        }

После того, как оба файла __init__.py мы наполним нужным содержанием, нам в корневом каталоге проекта предстоит еще создать файл main.py со следующим содержанием:

# main.py
from flask import Flask
from blueprints.azy.page_primer import blueprint as page_primer
from blueprints.azy.primery_api import blueprint as primery_api

app = Flask(__name__)
app.register_blueprint(page_primer)
app.register_blueprint(primery_api)
app.config['JSON_AS_ASCII'] = False
app.config['JSON_SORT_KEYS'] = False

if __name__ == "__main__":
    app.run(debug=True)

В результате этих действий мы получим объединенный web-ресурс (наш первый сайт), страницы которого можно открывать по следующим ссылкам:

Применение Jinja шаблонов в Flask

Кроме использования схем (Blueprint), еще одним мощным достоинством фреймворка Flask, во многом компенсирующим недостатки его минималистичности, является наличие в нем чрезвычайно удобного механизма шаблонов Jinja, обеспечивающего присоединения к создаваемым во Flask web-приложениям так называемых динамических HTML-шаблонов. Хотя подробное рассмотрение шаблонов Jinja выходит за рамки данной статьи, однако здесь мы все же приведем небольшой пример их использования для того, чтобы просто демонстрировать концепцию применения этого столь мощного механизма.

Для реализации данного примера, нам в корневом каталоге нашего проекта понадобиться создать подкаталог templates и вставить в него файл primer.html со следующим содержанием:

<!-- templates/primer.html -->
<html>
<head>
</head>
<a href="https://flask-russian-docs.readthedocs.io/ru/latest/">
    <div class="meme">
        <p class="top">{{top}}</p>
        <p class="bottom">{{bottom}}</p>
    </div>
</a>
</html>

Вышеприведенный HTML шаблон за счет применения стандартных конструкций {{ }} принимает в себя две соответствующие переменные из внешнего Python файла. Таким образом, благодаря этим самым конструкциям обеспечивается включения Python кода в HTML шаблоны, что позволяет отображать динамическое содержимое соответствующих web -страниц. В нашем случае используются top и bottom переменные, которые при открытии шаблона в web-сервисе, должны будут внутри тега

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

Теперь, когда данный шаблон уже создан, давайте подключим его к нашему единому web-сервису с помощью Blueprint в виде отдельной схемы (подпроекта). Для этого в подкаталоге blueprints нам понадобится создать еще один подкаталог jinja_page, где сформировать пакетный файл __init__.py со следующим содержанием:

# blueprints/jinja_page/__init__.py
from flask import Blueprint, request, render_template

blueprint = Blueprint('jinja_primer', __name__, url_prefix='/jinja_primer')

@blueprint.route('')
def get_template():
    top = request.args.get('top') if 'top' in request.args else ''
    bottom = request.args.get('bottom') if 'bottom' in request.args else ''

    return render_template('primer.html', top=top, bottom=bottom)

После этого, вновь созданный подпроект нам нужно будет также зарегистрировать в уже знакомом нам стартовом (главном) файле main.py, размещенном в корневом каталоге нашего проекта:

# main.py
...
from blueprints.jinja_page import blueprint as jinja_primer_blueprint
...
app.register_blueprint(jinja_primer_blueprint)
...   

Затем, запустив наш главный файл проекта посредством команды python main.py и перейдя в любой из имеющихся у нас браузеров, мы сможем опробовать вновь добавленную функциональность нашего web-сервиса введя в этом браузере соответствующий URL запрос с текстом для верхнего и нижнего колонтитулов. Например, задав в адресной строке нашего браузера URL запрос:

http://localhost:5000/jinja_primer?top=верхний_колонтитул&bottom=нижний_колонтитул 

мы получим web-страничку следующего вида:

image

Рис. 2. Web-страничка, созданная на Flask с использованием шаблона Jinja

Jinja шаблоны с добавлением CSS

Ради расширения нашего кругозора в сфере дизайна, давайте с вами попробуем к соответствующему Jinja шаблону добавить CSS (каскадную таблицу стилей) и, придать тем самым нашей страничке с колонтитулами достаточно неординарный внешний вид. С этой целью нам в корневом каталоге проекта понадобиться создать подкаталог static, где сформировать файл primer.css со следующим содержанием:

/* static/primer.css */
* {
    box-sizing: border-box;
  }

  .meme {
    margin: auto;
    width: 1500px;
    height: 864px;
    background-image:
    url('https://www.dl.dropboxusercontent.com/s/zhkx2mwjt3t4alx/flask.jpg?dl=0');
    background-size: 100%; 
    text-align: center;
    position: relative;
  }

  p {
    position: absolute; 
    left: 0;
    right: 0; 
    margin: 15px 0;
    padding: 0 5px;
    font-family: impact;
    font-size: 2.5em;
    text-transform: uppercase;
    color: white;
    letter-spacing: 1px;
    text-shadow:2px 2px 0 #000,
    -2px -2px 0 #000,
    2px -2px 0 #000,
    -2px 2px 0 #000,
    0px 2px 0 #000,
    2px 0px 0 #000,
    0px -2px 0 #000,
    -2px 0px 0 #000,
    2px 2px 5px #000;
  }

  .bottom {
     bottom: 0;
   }

  .top {
     top: 0;
   } 

Затем, для того чтобы подключить этот CSS файл к нашему Jinja шаблону, нам в HTML файле этого шаблона просто нужно прописать следующую строчку:

<!-- templates/primer.html -->
<html>
<head>
    <link rel="stylesheet" href="{{ url_for('static', filename='primer.css') }}">
</head>
...

Теперь давайте снова попробуем запустить файл main.py и перейдя в наш любимый браузер ввести в его адресной строке следующий URL запрос:

http://localhost:5000/jinja_primer?top=вы%20можете%20освоить%20flask%20самостоятельно&bottom=но%20гарантировать%20100%%20овладение%20им%20-%20могут%20только%20на%20курсах%20pylot

В результате произведенных действий, наш браузер должен отобразить следующую картинку:

image

Рис. 3. Web-страничка, созданная на Flask с использованием шаблона Jinja и каскадных таблиц стилей - CSS

Как сгенерировать Swagger документацию на Flask?

Вот и настало для нас время, когда можно попытаться сгенерировать действительно масштабируемое web-приложение с развитой системой интерфейсных ссылок, реализующих его функциональность. Создавать данное web-приложение мы будем на основе использования так называемого Swagger набора инструментов, позволяющего легко и быстро формировать документацию для REST API приложений без непосредственного доступа к их коду. По сути говоря, сформированная таким образом API документация для уже созданных нами web-сервисов будет в конечном счете тем самым масштабируемым приложением в виде завершающего многофункционального web-сервиса с достаточно мощным интерфейсом, которое нами и было запланировано.

Подключение к Flask вышеназванного набора инструментов Swagger осуществляется с помощью внешнего Python пакета Flask-RESTX, который рассматривается как Flask расширение, содержащее коллекцию взаимосвязанных декораторов и иных инструментариев, упрощающих разработку API с параллельной генерацией по нему Swagger документации. Это, в свою очередь, обеспечивает минимизацию настроек REST API приложений ускоряя их создания.

Вышеназванный пакет Flask-RESTX был установлен нами в рамках запуска файла requirements.txt, который мы с вами рассматривали еще в начале данной статьи. Кроме Flask-RESTX, с помощью requirements.txt, дополнительно к Flask, нами были также установлены такие внешние вспомогательные программные пакеты, как Werkzeug и gunicorn, которые еще с самого начала данной статьи обеспечили нам базовые комфортные условия для запуска и работы с соответствующими практическими примерами. Так, в частности gunicorn, являющейся WSGI-сервером, позволил обеспечить для нашего проекта непосредственный запуск сервера разработки и последующее его взаимодействие с создаваемыми нами web-сервисами. А пакет Werkzeug, обобщающий многочисленные инструментарии по взаимодействию приложений с серверами, вместе с расширением возможностей, создаваемых нами REST API сервисов, в последующем должен будет обеспечивать корректную генерацию API документации для нашего вновь создаваемого сервиса.

Примечание. Как правило, внешние программные пакеты gunicorn и Werkzeug должны быть подключены ко всем нашим Flask проектам с целью обеспечения комфортной работы с ними на этапе создания и отладки соответствующих web-приложений.

Помимо наглядного расширяемого интерфейса, масштабируемое web-приложение должно иметь еще и оптимизированную структуру для формируемого под него проекта. Такая структура проекта чаще всего формируется по аналогии со структурой того меню (интерфейса), которое предусматривается создаваемым нами приложением. Также поступим и мы при усовершенствовании структуры нашего проекта для вновь создаваемого многофункционального сервиса API документации. Но, для наглядности и упрощения дальнейшего изложения, давайте сначала с помощью приведенного ниже рисунка сравним имеющуюся у нас сейчас структуру проекта с той, которую нам еще только предстоит сформировать по итогам создания завершающего web-сервиса для данной статьи:

image

Рис. 4. Структура проекта до и после добавление к нему функциональности по формированию Swagger API документации

По аналогии с приведенной на рисунке выше завершающей структурой проекта, давайте сначала создадим каталог final_servis с тремя вложенными подкаталогами со следующими названиями: page_primer, primery_api и jinja_page. Таким образом, вложенные в каталог final_servis подкаталоги с созданными затем в них пакетными файлами __init__.py, будут восприниматься нашим завершающим web-сервисом, как своеобразные программные модули, выполняющие ту или иную его функцию. Все эти программные модули, в свою очередь, должны будут обобщаться в таком же пакетном файле __init__.py, но уже размещенном непосредственно в каталоге final_servis.

Для начала, давайте попробуем к нашему завершающему web-сервису «прикрутить» функциональность по API документации для уже созданной нами статической web-странички с приветственным текстом. С этой целью нам нужно будет в каталоге final_servis/page_primer создать пакетный файл __init__.py со следующим содержанием:

# blueprints/final_servis/page_primer/__init__.py
from flask import request
from flask_restx import Namespace, Resource, fields

namespace = Namespace('page_primer',
                      'Статическая страничка с приветственным текстом')

welcome_text_model = namespace.model('WelcomeText', {
    'message': fields.String(
        readonly=True,
        description='<h1>Мне кажется, или я реально уже в браузере?!</h1>'
    )
})

welcome_text_primer = {'message': 'Мне кажется, или я реально уже в браузере?!'}

@namespace.route('')
class WelcomeText(Resource):
    @namespace.marshal_list_with(welcome_text_model)
    @namespace.response(500, 'Internal Server error')
    def get(self):
        '''Статическая страничка с приветственным текстом'''
        return welcome_text_primer

Первым, на чем следует сконцентрироваться при более подробном рассмотрении вышеприведенного кода, является использование классов Namespace и Resource. Так, благодаря созданию из класса Namespace его одноименного экземпляра namespace мы в нашем сервисе не только создаем отдельный раздел page_primer с разъясняющим текстом по нему, но и определяем общее пространство имен присущих этому разделу. В частности, благодаря использованию данного экземпляра далее в коде помимо формирования доступа к различным функциям формируемого раздела мы также еще и определяем связанную с этим разделом модель, показывающую результат от непосредственного выполнения задокументированного им сервиса.

С целью же создания маршрутов (функциональных подразделов) в раннее определенном разделе page_primer мы используем класс Resource, из которого в нашем коде создаем класс WelcomeText. Этот класс, в свою очередь, оборачивается декоратором @namespace.route(' '), позволяющим за счет определения специфических свойств и методов подогнать его сугубо под особенности названного выше раздела page_primer.

Из-за того, что вновь создаваемый нами раздел предусматривает лишь вывод статической web-странички с приветствием, его формирование позволяет ограничиться определением всего одного маршрута (функционального подраздела). Следовательно, в нашем классе WelcomeText в соответствии с этим маршрутом мы определяем лишь один метод get(), который и будет реализовать вывод приветственной странички.

При формировании кода для генерации Swagger API документации очень важно также заносить в него четкий разъяснительный (интерфейсный) текст и сообщения о возможных ошибках в создаваемых нами сервисах. Все эти тексты и сообщения, которыми инициализируются те или иные свойства экземпляров используемых нами классов, мы можем увидеть, как в коде файла с непосредственно создаваемым нами разделом page_primer, так и в общем пакетном файле __init__.py, который нам еще предстоит только создать в каталоге blueprints/final_servis.

Примечание. Задаваемые нами в свойствах экземпляров классов русскоязычные разъяснительные тексты и сообщения об ошибках, будут соответствующим образом выведены на наш монитор при запуске сервиса. Однако большая часть интерфейсного текста запущенного сервиса все-таки останется англоязычной. Это объясняется тем, что англоязычный интерфейс базовой Swagger API документации изначально «забит» в самом программном пакете Flask-RESTX. С целью решения выше обозначенной проблемы, как вариант, можно обратиться за информацией к англоязычному Учебному пособию по интернационализации Flask приложений.

Следует таже отметить, что программный пакет Flask-RESTX, кроме рассматриваемой в этой статье базовой функциональности, имеет множество дополнительных возможностей, например, в таких направлениях, как синтаксический анализ или обработка исключений. Для более подробного ознакомления с возможностями данного программного пакета можно обратиться к англоязычной Документации по Flask-RESTX.

Теперь, с целью включения уже созданного раздела page_primer в общий наш финальный сервис со Swagger API документацией, нам понадобится в каталоге blueprints/final_servis создать общий пакетный файл __init__.py со следующим содержанием:

# blueprints/final_servis/__init__.py
from flask import Blueprint
from flask_restx import Api
from blueprints.final_servis.page_primer import namespace as page_primer_ns

blueprint = Blueprint('swagger', __name__, url_prefix='/swagger')

api_extension = Api(
    blueprint,
    title='Демонстрация возможностей Flask-RESTX',
    version='1.0',
    description='Инструкция к приложению для <b>статьи по Flask REST API\
    </b>, демонстрирующему возможности <b>пакета RESTX</b>, позволяющему\
    создавать масштабируемые сервисы и генерировать API документацию по ним',
    doc='/doc'
)

api_extension.add_namespace(page_primer_ns)

Здесь на основе класса Api из программного модуля Flask-RESTX создается его экземпляр api_extension, где, в частности, при помощи экземпляра blueprint инициализируется единый URL доступ /swagger/doc для запуска создаваемого нами финального сервиса. Вмести с этим, за счет применения одноименного экземпляра класса Namespace, импортированного из соответствующего вложенного пакета, на основе использования метода add_namespace() обеспечивается присоединение уже созданного раздела page_primer к нашему финальному сервису.

Таким образом, в вышеприведенном пакетном файле финального сервиса final_servis мы создаем соответствующую схему (подпроект), которая также должна быть зарегистрирована в главном (стартовом) файле нашего общего приложения main.py со следующим результирующим содержанием:

# main.py
from flask import Flask
from blueprints.azy.page_primer import blueprint as page_primer
from blueprints.azy.primery_api import blueprint as primery_api
from blueprints.jinja_page import blueprint as jinja_primer_blueprint
from blueprints.final_servis import blueprint as final_servis

app = Flask(__name__)
app.register_blueprint(page_primer)
app.register_blueprint(primery_api)
app.register_blueprint(jinja_primer_blueprint)
app.register_blueprint(final_servis)
app.config['JSON_AS_ASCII'] = False
app.config['JSON_SORT_KEYS'] = False

if __name__ == "__main__":
    app.run(debug=True)

Примечание. Следует отметить, что вышеприведенное содержание файла main.py не нуждается более в изменениях, поскольку мы уже присоединили к нему подпроект final_servis. Поэтому, теперь нам нужно только создать код для двух оставшихся разделов, а также присоединить эти разделы к ранее упомянутому экземпляру api_extension в пакетном файле для final_servis.

После сохранения кода для трех вышеприведенных файлов и запуска команды python main.py мы можем полюбоваться результатами нашего труда, открыв в браузере ссылку http://localhost:5000/swagger/doc и попав на следующую страницу:

image

Рис. 5. Вид частично завершенного сервиса по API документации с уже готовым 1-м разделом, посвященным статичной страничке с приветственным текстом

Теперь настало время сгенерировать Swagger API документацию для уже созданных нами: сервиса с примерами API запросов в рамках GRUD и сервиса со страничкой, основанной на Jinja шаблоне. Подобно уже созданному разделу для нашей статичной странички с приветствием, API документацию для этих двух web-сервисов мы сгенерируем в виде отдельных разделов, путем создания пакетных файлов __init__.py в соответствующих подкаталогах primery_api и jinja_page. Для каждого из таких пакетных файлов нам следует занести соответственно следующий код:

Для пакетного файла в подкаталоге primery_api:

# blueprints/final_servis/primery_api/__init__.py
from flask import request
from flask_restx import Namespace, Resource, fields
from http import HTTPStatus

namespace = Namespace('sushchnosti',
                      'Примеры API запросов, оперирующих сущностями')

model_sushchnosti = namespace.model('Element', {
    'id': fields.Integer(
        readonly=True,
        description='Идентификатор сущности'
    ),
    'name': fields.String(
        required=True,
        description='Имя сущности'
    )
})

entity_list_model = namespace.model('SpisokElementov', {
    'entities': fields.Nested(
        model_sushchnosti,
        description='Список сущностей',
        as_list=True
    ),
    'total_records': fields.Integer(
        description='Общее количество сущностей',
    ),
})

primer_sushchnosti = {'id': 1, 'name': 'Имя сущности'}

@namespace.route('')
class sushchnosti(Resource):
    '''Получение списка уже имеющихся сущностей, а также создание новой сущности'''
    @namespace.response(500, 'Internal Server error')
    @namespace.marshal_list_with(entity_list_model)
    def get(self):
        '''Получение списка со всеми уже имеющимися сущностями'''
        entity_list = [primer_sushchnosti]
        return {
            'entities': entity_list,
            'total_records': len(entity_list)
        }
    @namespace.response(400, 'Entity with the given name already exists')
    @namespace.response(500, 'Internal Server error')
    @namespace.expect(model_sushchnosti)
    @namespace.marshal_with(model_sushchnosti, code=HTTPStatus.CREATED)
    def post(self):
        '''Создание новой (дополнительной) сущности'''

        if request.json['name'] == 'Имя сущности':
            namespace.abort(400, 'Entity with the given name already exists')
        return primer_sushchnosti, 201

@namespace.route('/<int:entity_id>')
class element(Resource):
    '''Чтение, обновление и удаление отдельно указываемой сущности'''
    @namespace.response(404, 'Entity not found')
    @namespace.response(500, 'Internal Server error')
    @namespace.marshal_with(model_sushchnosti)
    def get(self, entity_id):
        '''Получение информации об отдельно указанной сущности'''
        return primer_sushchnosti

    @namespace.response(400, 'Entity with the given name already exists')
    @namespace.response(404, 'Entity not found')
    @namespace.response(500, 'Internal Server error')
    @namespace.expect(model_sushchnosti, validate=True)
    @namespace.marshal_with(model_sushchnosti)
    def put(self, entity_id):
        '''Обновление (изменение) отдельно указанной сущности'''
        if request.json['name'] == 'Имя сущности':
            namespace.abort(400, 'Entity with the given name already exists')
        return primer_sushchnosti

    @namespace.response(204, 'Request Success (No Content)')
    @namespace.response(404, 'Entity not found')
    @namespace.response(500, 'Internal Server error')
    def delete(self, entity_id):
        '''Удаление отдельно указанной сущности'''
        return '', 204

Для пакетного файла в подкаталоге jinja_page:

# blueprints/final_servis/jinja_page/__init__.py
from flask import request, render_template, make_response
from flask_restx import Namespace, Resource, reqparse

namespace = Namespace(
    'jinja_page',
    'Пример использования Jinja шаблона. ВНИМАНИЕ: Данный\
    раздел не предполагает создание модели и, следовательно,\
    не отображает внешний вид документируемого сервиса')

parser = reqparse.RequestParser()
parser.add_argument('верхний колонтитул', type=str, help='Верхнее значение:')
parser.add_argument('нижний колонтитул', type=str, help='Нижнее значение:')

@namespace.route('')
class JinjaTemplate(Resource):
    @namespace.response(200, 'Render jinja template')
    @namespace.response(500, 'Internal Server error')
    @namespace.expect(parser)
    def get(self):
        '''Визуализация параметров запросов для html-странички на основе
        Jinja шаблона'''
        top = request.args.get('top') if 'top' in request.args else ''
        bottom = request.args.get('bottom') if 'bottom' in request.args else ''
        return make_response(render_template('primer.html', top=top,
                                              bottom=bottom), 200) 

Несмотря на то, что содержание двух представленных выше пакетных файлов выглядит довольно пугающе, логика их кода абсолютно такая же, как и для нашего первого раздела, посвященного статической странички с приветственным текстом. Вместе с тем, между этими кодами есть и некоторые различия. Так в пакетном файле, генерирующем документацию по примерам API запросов, предусмотрена проверка имени добавляемой (обновляемой) сущности при использовании методов POST и PUT. В файле же, создающим документацию для html-странички с примером Jinja шаблона отсутствует определение модели для непосредственного отображения этой странички, но зато используется специальный parser объект, обеспечивающий вывод в документацию необходимых параметров для запроса к сервису. Использование parser объектов зачастую может оказаться весьма полезным для тех приложений, прием данных в которые жестко не фиксируется программой, а осуществляется через URL адрес или же через сторонние web-формы.

В завершении нашей весьма объемной, но отнюдь не бесплодной работы, мы еще должны добавить два вновь созданные раздела к общему пакетному файлу нашего финального сервиса, расположенному в каталоге blueprints/final_servis. Таким образом, завершающий вариант этого файла должен выглядеть следующим образом:

# blueprints/final_servis/__init__.py
from flask import Blueprint
from flask_restx import Api
from blueprints.final_servis.page_primer import namespace as page_primer_ns
from blueprints.final_servis.primery_api import namespace as sushchnosti_ns
from blueprints.final_servis.jinja_page import namespace as jinja_page_ns

blueprint = Blueprint('swagger', __name__, url_prefix='/swagger')

api_extension = Api(
    blueprint,
    title='Демонстрация возможностей Flask-RESTX',
    version='1.0',
    description='Инструкция к приложению для <b>статьи по Flask REST API\
    </b>, демонстрирующему возможности <b>пакета RESTX</b>, позволяющему\
    создавать масштабируемые сервисы и генерировать API документацию по ним',
    doc='/doc'
)

api_extension.add_namespace(page_primer_ns)
api_extension.add_namespace(sushchnosti_ns)
api_extension.add_namespace(jinja_page_ns)

Теперь, давайте запустим наш окончательный вариант финального сервиса и посмотрим, как выглядит раздел jinja_page, имеющий специфический подраздел, созданный при помощи parser объекта с целью предоставления информации о необходимых параметрах запросов к html-страничке, основанной на Jinja шаблоне:

image

Рис. 6. Раздел JINJA_PAGE со специфическим подразделом, созданным при помощи PARSER объекта

Вот и наступило время, когда вся наша работа уже завершена и, мы можем полюбоваться страничками своего web-приложения, например, по следующим ссылкам:

При желании, можно сравнить то, что у нас сейчас получилось с довольно схожим англоязычным web-приложением, посвященном демонстрации возможностей Swagger API документации

Выводы

Несмотря на свой минимализм, Flask обладает чрезвычайно мощными возможностями по относительно быстрому созданию довольно сложных и многофункциональных REST API web-приложений с применением Python. К тому же, Flask присущи гораздо более простые и понятные алгоритмы программных решений, нежели те, которые требуются для других фреймворков при создании полноценных серверных приложений.

К сожалению, об истинном потенциале и возможностях Flask знает очень мало тех разработчиков, которые на данный момент находятся в поисках удобного и эффективного инструментария по web-разработке. Именно поэтому, целью данной статьи являлась не только презентация на практике базовых преимуществ Flask, но и достаточно полное освещение вопросов, связанных с применением данного фреймворка в сфере REST API приложений, расширения проектов с помощью объектов Blueprint, генерации Swagger документации и применения Jinja шаблонов. Очень надеемся, что все это у нас получилось и, вы благодаря этой статье получили тот оптимальный запас знаний, который позволит вам легко и быстро приступить к разработке собственных реальных приложений на Python с применением Flask.

Практический Python для начинающих
Практический Python для начинающих

Станьте junior Python программистом за 7 месяцев

 7 месяцев

Возможно будет интересно

Применение геттер и сеттер методов для закрытых атрибутов классов Python
Продвинутый
Применение геттер и сеттер методов для закрытых атрибутов классов

Что же все-таки обозначает эта загадочная ИНКАПСУЛЯЦИЯ и, как она может быть реализована в классах Python с помощью не менее таинственных ГЕТТЕР и СЕТТЕР методов. Обо всем этом мы с вами узнаем из этой статьи.

2022-10-17
Как практиковаться в Python? Python
Новичок
Как практиковаться в Python?

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

2022-10-19
Операторы в Python Шпаргалки
Новичок
Операторы в Python

Шпаргалка по арифметическим и условным операторам в Python

2022-11-09