Python REST API: Flask, Connexion и SQLAlchemy (часть 1)

в 14:16, , рубрики: connexion, flask, python, rest api

Python REST API: Flask, Connexion и SQLAlchemy (часть 1)

Это перевод статьи от 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

GET

/api/people

Чтение списка персонажей

Create

POST

/api/people

Создание нового персонажа

Read

GET

/api/people/<lname>

Получение данных персонажа

Update

PUT

/api/people/<lname>

Обновление существующего персонажа

Delete

DELETE

/api/people/<lname>

Удаление существующего персонажа

Стуктура данных персонажей, которая будет использоваться в разрабатываемом приложении 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
.venvScriptsactivate
  • для 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!:

Python REST API: Flask, Connexion и SQLAlchemy (часть 1) - 1

Прекрасно, ваш веб-сервер запущен! Позже вы расширите файл 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 конечной точки API
get: Метод 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"

Этот файл организован иерархически. Каждый отступ слева оздачает определенный уровень вложенности: как в иерархии Python.

Например, paths отмечает начало, где определяются все конечные точки URL API. Значение /people с отступом под ним представляет начало, где будут определены все конечные точки URL /api/people. Область действия get: с отступом под /people содержит определения, связанные с запросом HTTP GET к конечной точке URL /api/people. Организация иерархия подобным образом характерна для всех файлов YAML.

Файл 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(),
    }
}

def read_all(): # сервер запускает эту функцию при вызове /api/people
    return list(PEOPLE.values())

После запуска приложения, в браузере по адресу http://localhost:8000/api/people вы получите следующий вывод:

Python REST API: Flask, Connexion и SQLAlchemy (часть 1) - 2

Превосходно, вы создали свою первую конечную точку API! Прежде чем продолжить путь к созданию REST API с несколькими конечными точками, уделите немного времени и изучите API немного подробнее в следующем разделе.

Немного о API документации

В настоящее время у вас есть REST API, работающий с одной конечной точкой URL. Ваше приложение Flask знает, что обслуживать, на основе спецификации вашего API в swagger.yml. Кроме того, Connexion использует swagger.yml для создания документации API для вас.

Перейдите на localhost:8000/api/ui, чтобы увидеть документацию API в действии:

Python REST API: Flask, Connexion и SQLAlchemy (часть 1) - 3

Это начальный интерфейс 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

GET

/api/people

Чтение списка персонажей

Create

POST

/api/people

Создание нового персонажа

Read

GET

/api/people/<lname>

Получение данных персонажа

Update

PUT

/api/people/<lname>

Обновление существующего персонажа

Delete

DELETE

/api/people/<lname>

Удаление существующего персонажа

Для этого вам нужно будет расширить файлы 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

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

Автор: tabu1977

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js