Это перевод статьи от Philipp Acsany
Большинство современных веб-приложений работают на основе REST API - методологии, позволяющей разработчикам отделить разработку пользовательского интерфейса (FrontEnd) от разработки внутренней серверной логики (BackEnd), а пользователи получают интерфейс с динамически подгружаемыми данными. В этой серии из трех частей вы создадите REST API с помощью веб-фреймворка Flask.
В этой первой части серии вы узнаете, как:
Создать базовый проект REST API на Flask
Обрабатывать HTTP-запросы с помощью Connexion
Определять конечные точки API с помощью спецификации OpenAPI
Взаимодействовать с вашим API для управления данными
Создавать аннотации для API с помощью Swagger UI
После завершения первой части, во второй научитесь использовать базу данных для постоянного хранения данных вместо того, чтобы полагаться на оперативную память.
Техзадание
Создать приложение для управления открытками персонажам, от которых вы можете получить подарки в течение года. Вот эти сказочные лица: Tooth Fairy (Зубная фея), Easter Bunny (Пасхальный кролик) и Knecht Ruprecht (Кнехт Рупрехт).
В идеале вы хотите быть в хороших отношениях со всеми тремя из них, вот почему вы будете отправлять им открытки, чтобы увеличить шансы получить от них ценные подарки.
План части 1
Помимо реализации списка открыток, вы собираетесь создать REST API, который предоставляет доступ к списку персонажей и к отдельным персонажам внутри этого списка. Вот дизайн API для персонажей:
Действие |
HTTP метод |
URL |
Описание |
---|---|---|---|
Read |
|
|
Чтение списка персонажей |
Create |
|
|
Создание нового персонажа |
Read |
|
|
Получение данных персонажа |
Update |
|
|
Обновление существующего персонажа |
Delete |
|
|
Удаление существующего персонажа |
Стуктура данных персонажей, которая будет использоваться в разрабатываемом приложении REST API, выглядит следующим образом (персонаж определяются по фамилии, а любые изменения отмечаются меткой времени):
PEOPLE = {
"Fairy": {
"fname": "Tooth",
"lname": "Fairy",
"timestamp": "2022-10-08 09:15:10",
},
"Ruprecht": {
"fname": "Knecht",
"lname": "Ruprecht",
"timestamp": "2022-10-08 09:15:13",
},
"Bunny": {
"fname": "Easter",
"lname": "Bunny",
"timestamp": "2022-10-08 09:15:27",
}
}
Поехали!
В этом разделе вы подготовите среду разработки для проекта Flask REST API. Сначала вы создадите виртуальное окружение и установиет все зависимости, необходимые для проекта.
Создание виртуального окружения
В этой секции вы создадите структуру своего проекта. Вы можете назвать корневую папку вашего проекта любым удобным для вас способом. Например, вы можете назвать ее rp_flask_api
. Создайте папку и перейдите в нее:
mkdir rp_flask_api
cd rp_flask_api
Файлы и папки, которые вы создаете в ходе этой серии, будут располагаться либо в этой папке, либо в ее подпапках.
После того, как вы перейдете в папку проекта, хорошей идеей будет создать и активировать виртуальную среду. Таким образом, вы устанавливаете все зависимости проекта не на всю систему, а только в виртуальной среде вашего проекта.
для Windows:
python -m venv venv
.\venv\Scripts\activate
для Linux или macOS:
python -m venv venv
source venv/bin/activate
В результате в папке вашего проекта будет создана папка venv
с файлами виртуального окружения, а также в приглашении операционной системы должно появиться значение (venv)
, означающее, что виртуальное окружение из вашей папки проекта активировано и вы работаете именно в нём.
Добавление зависимостей
После установки и активации виртуального окружения вам нужно добавить с помощью pip
библиотеку Flask для разработки:
pip install Flask==2.2.2
Микро веб-фреймворк Flask — это основная зависимость, которая требуется вашему проекту. Поверх Flask установите Connexion для обработки HTTP-запросов:
pip install "connexion[swagger-ui]==2.14.1"
Также чтобы использовать автоматически сгенерированную документацию API, вы устанавливаете Connexion с добавлением поддержки Swagger UI.
Создание начального проекта Flask
Основным файлом вашего проекта Flask будет app.py
. Создайте app.py
в rp_flask_api
и добавьте следующее содержимое:
# app.py
# импорт модуля Flask, который вы ранее установили с помощью
# pip install Flask==2.2.2 "connexion[swagger-ui]==2.14.1"
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/") # декоратор функции для "/" (корневого URL веб-приложения)
def home():
return render_template("home.html") # функция, выводящая home.html в качестве шаблона
if __name__ == "__main__":
# основной вызов приложения с указанием хоста и порта
app.run(host="0.0.0.0", port=8000, debug=True)
Для приложения Flask необходимо создать файл home.html
в каталоге шаблонов с именем templates
. Создайте каталог templates
в корневом каталоге приложения и добавьте туда следующий home.html
:
<!-- templates/home.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>RP Flask REST API</title>
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>
Flask поставляется с Jinja Templating Engine, который позволяет вам улучшать ваши шаблоны. Но ваш шаблон home.html
— это простой HTML-файл без каких-либо функций Jinja. Пока это нормально, потому что цель home.html
— проверить, что ваш проект Flask отдаёт в браузер всё так, как задумано.
При активированной виртуальной среде Python вы можете запустить свое приложение с помощью этой командной строки в каталоге, содержащем файл app.py
:
python app.py
При запуске app.py
веб-сервер запустится на порту 8000. Если вы откроете браузер и перейдете по адресу http://localhost:8000
, вы должны увидеть сообщение Hello, World!:
Прекрасно, ваш веб-сервер запущен! Позже вы расширите файл home.html
для работы с REST API, который вы разрабатываете.
К настоящему моменту структура вашего проекта Flask должна выглядеть так:
rp_flask_api/
├── templates/
│ └── home.html
└── app.py
Это отличная структура для начала любого проекта Flask. Возможно, исходный код пригодится вам при работе над будущими проектами. В следующих разделах вы расширите проект и добавите свои первые конечные точки REST API.
Добавление первой конечной точки REST API
Теперь, когда у вас есть работающий веб-сервер, вы можете добавить свою первую конечную точку REST API. Для этого вы будете использовать Connexion, который вы установили в предыдущем разделе.
Модуль Connexion позволяет программе Python использовать спецификацию OpenAPI с Swagger. Спецификация OpenAPI — это формат описания API для REST API, который предоставляет множество функций, включая:
Проверка входных и выходных данных в ваш API и из него
Конфигурация конечных точек URL API и ожидаемых параметров
При использовании OpenAPI с Swagger вы создаете пользовательский интерфейс (UI) для описания функций API путём создания файла конфигурации, к которому может получить доступ ваше приложение Flask.
Создание конфигурационного файла API
Файл конфигурации Swagger — это файл YAML или JSON, содержащий аннотации OpenAPI. Этот файл содержит всю информацию, необходимую для настройки сервера для обеспечения проверки входных параметров, проверки выходных данных ответа и определения конечной точки URL.
Создайте файл с именем swagger.yml
и начните добавлять в него метаданные:
# swagger.yml
# версия OpenAPI: важно, так как названия параметров могут меняться от версии к версии
openapi: 3.0.0
info: # информационный блок
title: "RP Flask REST API" # заголовок
description: "An API about people and notes" # описание
version: "1.0.0" # версия вашего API: придётся менять при дальнейшем расширении
Далее в блоке servers
добавьте url, которые определяют путь к вашему API:
# swagger.yml
# ...
servers:
- url: "/api"
Указав "/api"
в качестве значения url
, вы сможете получить доступ ко всем путям API по адресу http://localhost:8000/api.
Далее вы определяете конечные точки API в блоке путей path
:
# swagger.yml
# ...
paths:
/people:
get:
operationId: "people.read_all"
tags:
- "People"
summary: "Read the list of people"
responses:
"200":
description: "Successfully read people list"
Блок paths
определяет настройку путей конечной точки URL API:
/people
: Относительный URL конечной точки APIget:
Метод HTTP, на который будет отвечать эта конечная точка с URL
Вместе с определением URL это создает конечную точку с URL GET /api/people
, к которой вы можете получить доступ по адресу http://localhost:8000/api/people.
Блок get
определяет настройку единственной конечной точки URL /api/people
:
operationId:
Функция Python, которая будет отвечать на запросtags:
Теги, назначенные этой конечной точке, которые позволяют группировать операции в пользовательском интерфейсеsummary:
Текст аннотации в пользовательском интерфейсе для этой конечной точкиresponses:
Коды состояния, которыми отвечает конечная точкаoperationId
должен содержать строку. Connexion будет использовать "people.read_all"
для поиска функции Python с именем read_all()
в модуле people
вашего проекта. Вы создадите соответствующий код Python позже в этом руководстве.
Блок ответов определяет конфигурацию возможных кодов статуса. Здесь вы определяете успешный ответ для кода статуса «200»
, содержащий некоторый текст описания.
Вы можете найти полное содержимое файла swagger.yml в сворачиваемом файле ниже:
Полный код файла swagger.yml
# swagger.yml
openapi: 3.0.0
info:
title: "RP Flask REST API"
description: "An API about people and notes"
version: "1.0.0"
servers:
- url: "/api"
paths:
/people:
get:
operationId: "people.read_all"
tags:
- "People"
summary: "Read the list of people"
responses:
"200":
description: "Successfully read people list"
Этот файл организован иерархически, что характерно для всех YAML-файлов: размер отступа слева определяет уровень вложенности: как в языке Python.
Например, paths
отмечает начало, где определяются все конечные точки URL API. Значение /people
с отступом под ним представляет начало, где будут определены все конечные точки URL /api/people
. Область действия get
: с отступом под /people
содержит определения, связанные с запросом HTTP GET к конечной точке URL /api/people
.
Файл swagger.yml
— это, как план вашего API. С помощью аннотаций, которые вы включаете в swagger.yml, вы определяете, какие данные может ожидать ваш веб-сервер и как ваш сервер должен отвечать на запросы. Но пока ваш проект Flask не знает о вашем файле swagger.yml. Читайте дальше, чтобы использовать Connexion для подключения спецификации OpenAPI к вашему приложению Flask.
Добавление Connexion к вашему приложению
Добавление конечной точки URL REST API в приложение Flask с помощью Connexion состоит из двух шагов:
Добавьте файл конфигурации API в свой проект.
Подключите файл конфигурации к своему приложению Flask.
Для подключения файла конфигурации API к своему приложению Flask, необходимо сослаться на swagger.yml
в файле app.py
:
# app.py
from flask import render_template # удаляем: import Flask
import connexion # добавляем: connexion
# создание экземпляра приложения с использованием Connexion, а не Flask
# Внутри приложение Flask все еще создается, но теперь к нему добавлены
# дополнительные функции.
app = connexion.App(__name__, specification_dir="./")
app.add_api("swagger.yml")
@app.route("/")
def home():
return render_template("home.html")
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000, debug=True)
Получение данных Персонажа из конечной точки
В файле swagger.yml
вы настроили Connexion со значением operationId "people.read_all"
. Таким образом, когда API получает HTTP-запрос GET /api/people
, ваше приложение Flask вызывает функцию read_all()
в модуле people
.
Чтобы это заработало, создайте файл people.py
с функцией read_all()
:
# people.py
from datetime import datetime
# функция, возвращающая текущее время в заданном формате
def get_timestamp():
return datetime.now().strftime(("%Y-%m-%d %H:%M:%S"))
# словарь со значениями Персонажей
PEOPLE = {
"Fairy": {
"fname": "Tooth",
"lname": "Fairy",
"timestamp": get_timestamp(),
},
"Ruprecht": {
"fname": "Knecht",
"lname": "Ruprecht",
"timestamp": get_timestamp(),
},
"Bunny": {
"fname": "Easter",
"lname": "Bunny",
"timestamp": get_timestamp(),
}
}
# сервер запускает эту функцию при вызове /api/people
def read_all():
return list(PEOPLE.values())
После запуска приложения, в браузере по адресу http://localhost:8000/api/people вы получите следующий вывод:
Превосходно, вы создали свою первую конечную точку API! Прежде чем продолжить путь к созданию REST API с несколькими конечными точками, уделите немного времени и изучите API немного подробнее в следующем разделе.
Немного о API документации
В настоящее время у вас есть REST API, работающий с одной конечной точкой URL. Ваше приложение Flask знает, что обслуживать, на основе спецификации вашего API в swagger.yml
. Кроме того, Connexion использует swagger.yml
для создания документации API для вас.
Перейдите на localhost:8000/api/ui
, чтобы увидеть документацию API в действии:
Это начальный интерфейс Swagger. Он показывает список конечных точек URL, поддерживаемых в вашей конечной точке http://localhost:8000/api. Connexion создает его автоматически при анализе файла swagger.yml
.
Если вы нажмете на конечную точку /people
в интерфейсе, то интерфейс развернется, чтобы показать больше информации о вашем API: это отобразит структуру ожидаемого ответа, тип содержимого этого ответа и текст описания, который вы ввели для конечной точки в файле swagger.yml
. Каждый раз, когда изменяется файл конфигурации, пользовательский интерфейс Swagger также изменяется.
Вы даже можете попробовать использовать конечную точку, нажав кнопку Try it out
. Эта функция может быть чрезвычайно полезна, когда ваш API растет. Документация API Swagger UI дает вам возможность исследовать и экспериментировать с API без необходимости писать какой-либо код для этого.
Использование OpenAPI с пользовательским интерфейсом Swagger предлагает хороший, понятный способ создания конечных точек URL API. До сих пор вы создали только одну конечную точку для обслуживания всех персонажей. В следующем разделе вы добавите дополнительные конечные точки для создания, обновления и удаления конкретных персонажей в вашем списке.
Создание полноценного API
До сих пор ваш Flask REST API имел одну конечную точку. Теперь пришло время создать API, предоставляющий полный CRUD-доступ к вашей структуре людей. Как вы помните, план вашего API выглядит следующим образом:
Действие |
HTTP метод |
URL |
Описание |
---|---|---|---|
Read |
|
|
Чтение списка персонажей |
Create |
|
|
Создание нового персонажа |
Read |
|
|
Получение данных персонажа |
Update |
|
|
Обновление существующего персонажа |
Delete |
|
|
Удаление существующего персонажа |
Для этого вам нужно будет расширить файлы swagger.yml
и people.py
для полной поддержки API, определенного выше.
Работа с компонентами
Прежде чем определять новые пути API в swagger.yml
, добавьте новый блок components
для компонентов. Компоненты — это строительные блоки в вашей спецификации OpenAPI, на которые вы можете ссылаться из других частей вашей спецификации.
Добавьте блок компонентов со схемами для одного персонажа:
# swagger.yml
openapi: 3.0.0
info:
title: "RP Flask REST API"
description: "An API about people and notes"
version: "1.0.0"
servers:
- url: "/api"
components:
schemas:
Person:
type: "object"
required:
- lname
properties:
fname:
type: "string"
lname:
type: "string"
# ...
Чтобы избежать дублирования кода, вы создаете блок компонентов. На данный момент вы сохраняете только модель данных Person
в блоке схем:
type
: тип данных схемыrequired
: требуемые свойства
Тире (-) перед - lname
указывает, что required
может содержать список свойств. Любое свойство, которое вы определяете как required
, также должно существовать в свойствах, включая следующее:
fname
: имя персонажаlname
: фамилия персонажа
Ключ type
определяет значение, связанное с его родительским ключом. Для Person
все свойства являются строками. Вы представите эту схему в своем коде Python как словарь позже в этом руководстве.
Создание нового персонажа
Расширьте конечные точки API, добавив новый блок для POST-запроса в блок /people
:
# swagger.yml
# ...
paths:
/people:
get:
# ...
post:
operationId: "people.create"
tags:
- People
summary: "Create a person"
requestBody:
description: "Person to create"
required: True
content:
application/json:
schema:
x-body-name: "person"
$ref: "#/components/schemas/Person"
responses:
"201":
description: "Successfully created person"
Структура для post
похожа на существующую схему get
. Одно отличие в том, что вы также отправляете requestBody
на сервер. В конце концов, вам нужно сообщить Flask информацию, которая ему нужна для создания нового персонажа. Другое отличие — operationId
, который вы устанавливаете в people.create
.
Внутри контента вы определяете application/json
как формат обмена данными вашего API.
Вы можете обслуживать различные типы медиа в ваших запросах API и ответах API. В настоящее время API обычно используют JSON в качестве формата обмена данными. Это хорошие новости для вас как разработчика Python, потому что объекты JSON очень похожи на словари Python. Например:
{
"fname": "Tooth",
"lname": "Fairy"
}
Этот объект JSON напоминает компонент Person, который вы определили ранее в swagger.yml
и на который вы ссылаетесь с помощью $ref
в схеме.
Вы также используете код статуса HTTP 201, который является ответом об успешном выполнении, указывающим на создание нового ресурса.
Если вы хотите узнать больше о кодах состояния HTTP, вы можете ознакомиться с документацией Mozilla о кодах ответов HTTP.
С помощью people.create
вы сообщаете серверу, что нужно искать функцию create()
в модуле people
. Откройте файл people.py
и добавьте функцию create()
:
# people.py
from datetime import datetime
from flask import abort # импорт функции abort из Flask
# ...
def create(person):
lname = person.get("lname")
fname = person.get("fname", "")
if lname and lname not in PEOPLE:
PEOPLE[lname] = {
"lname": lname,
"fname": fname,
"timestamp": get_timestamp(),
}
return PEOPLE[lname], 201
else:
# использование abort() помогает отправить сообщение об ошибке
# когда тело запроса не содержит фамилию или когда персонаж с такой
# фамилией уже существует.
abort(
406,
f"Person with last name {lname} already exists",
)
Фамилия персонажа должна быть уникальной, потому что вы используете
lname
как ключ словаря PEOPLE. Это означает, что на данный момент в вашем проекте не может быть двух персонажей с одинаковой фамилией.
Если данные в теле запроса действительны, вы обновляете PEOPLE в строке 13 и отвечаете новым объектом и HTTP-кодом 201 в строке 18.
Обработка Персонажа
Откройте swagger.yml
и добавьте следующий код:
# swagger.yml
# ...
components:
schemas:
# ...
parameters:
lname:
name: "lname"
description: "Last name of the person to get"
in: path
required: True
schema:
type: "string"
paths:
/people:
# ...
/people/{lname}:
get:
operationId: "people.read_one"
tags:
- People
summary: "Read one person"
parameters:
- $ref: "#/components/parameters/lname"
responses:
"200":
description: "Successfully read person"
Подобно пути /people
, вы начинаете с операции get
для пути /people/{lname}
. Подстрока {lname}
является заполнителем для фамилии, которую вы должны передать как параметр URL. Так, например, путь URL api/people/Ruprecht
содержит Ruprecht
как lname
.
Параметры URL чувствительны к регистру. Это значит, что вы должны ввести фамилию, например, Ruprecht с заглавной буквой R.
Параметр lname
вы будете использовать и в других операциях. Поэтому имеет смысл создать для него компонент и ссылаться на него при необходимости.
operationId
указывает на функцию read_one()
в people.py
, поэтому снова перейдите к этому файлу и создайте отсутствующую функцию:
# people.py
# ...
def read_one(lname):
if lname in PEOPLE:
return PEOPLE[lname]
else:
abort(
404, f"Person with last name {lname} not found"
)
Когда ваше приложение Flask находит указанную фамилию в PEOPLE, оно возвращает данные для этого конкретного персонажа. В противном случае сервер вернет ошибку HTTP 404.
Чтобы обновить существующего персонажа, обновите swagger.yml
с помощью этого кода:
# swagger.yml
# ...
paths:
/people:
# ...
/people/{lname}:
get:
# ...
put:
tags:
- People
operationId: "people.update"
summary: "Update a person"
parameters:
- $ref: "#/components/parameters/lname"
responses:
"200":
description: "Successfully updated person"
requestBody:
content:
application/json:
schema:
x-body-name: "person"
$ref: "#/components/schemas/Person"
При таком определении операции put
ваш сервер ожидает update()
в people.py
:
# people.py
# ...
def update(lname, person):
if lname in PEOPLE:
PEOPLE[lname]["fname"] = person.get("fname", PEOPLE[lname]["fname"])
PEOPLE[lname]["timestamp"] = get_timestamp()
return PEOPLE[lname]
else:
abort(
404,
f"Person with last name {lname} not found"
)
Функция update()
ожидает аргументы lname
и person
. Когда персонаж с указанной фамилией существует, вы обновляете соответствующие значения в PEOPLE данными о персонаже.
Чтобы избавиться от персонажа в вашем наборе данных, вам нужно работать с операцией удаления:
# swagger.yml
# ...
paths:
/people:
# ...
/people/{lname}:
get:
# ...
put:
# ...
delete:
tags:
- People
operationId: "people.delete"
summary: "Delete a person"
parameters:
- $ref: "#/components/parameters/lname"
responses:
"204":
description: "Successfully deleted person"
Добавьте соответствующую функцию delete()
в person.py
:
# people.py
from flask import abort, make_response
# ...
def delete(lname):
if lname in PEOPLE:
del PEOPLE[lname]
return make_response(
f"{lname} successfully deleted", 200
)
else:
abort(
404,
f"Person with last name {lname} not found"
)
Если персонаж, которого вы хотите удалить, существует в вашем наборе данных, то вы удаляете элемент из PEOPLE.
Со всеми конечными точками для управления персонажами пришло время протестировать ваш API. Поскольку вы использовали Connexion для подключения вашего проекта Flask к Swagger, ваши аннотации API станут доступны, когда вы перезапустите свой сервер.
Этот пользовательский интерфейс позволяет вам видеть всю документацию, которую вы включили в файл swagger.yml, и взаимодействовать со всеми конечными точками URL, составляющими функциональность CRUD интерфейса people.
На данный момент, любые внесенные вами изменения не сохранятся при перезапуске вашего приложения Flask. Вот почему вы подключите базу данных к своему проекту в следующей части.
Заключение
В этой части вы создали комплексный REST API с помощью веб-фреймворка Python Flask. С помощью модуля Connexion и некоторой дополнительной работы по настройке можно создать полезную интерактивную документацию. Надеемся, что создание REST API веб-приложения оказалось несложным.
В первой части вы узнали, как:
Создать базовый проект Flask с помощью REST API
Обрабатывать HTTP-запросы с помощью Connexion
Определять конечные точки API с помощью спецификации OpenAPI
Взаимодействовать с вашим API для управления данными
Создавать документацию API с помощью Swagger UI
Во второй части этой серии вы узнаете, как использовать базу данных для постоянного хранения ваших данных вместо того, чтобы полагаться на хранилище в памяти, как вы делали здесь.