Skip to content

FastAPI - Аннотации типов Path и Query.

Эта статья посвящена использованию аннотаций типов Path и Query в FastAPI для определения параметров API, улучшения валидации и автоматической генерации интерактивной документации Swagger UI.

python
from fastapi import FastAPI, Path, Query
from typing import Optional, List, Dict, Union

Зачем нужна аннотация типов?

Аннотации типов (type hints) были введены в Python 3.5 (PEP 484). Они служат нескольким важным целям:

  • Улучшение читаемости кода: Аннотации типов сразу показывают, какой тип данных ожидает функция в качестве входных данных и какой тип она вернет. Это упрощает понимание и поддержку кода.
  • Раннее обнаружение ошибок: Инструменты статического анализа (например, MyPy) и IDE (например, VS Code, PyCharm) могут использовать аннотации типов для обнаружения ошибок типов до запуска кода. Это помогает предотвратить ошибки.
  • Улучшенная поддержка IDE: IDE используют аннотации типов для обеспечения лучшего автодополнения, подсказок по коду и возможностей рефакторинга.
  • Основа для FastAPI: FastAPI требует аннотации типов для определения структуры вашего API. Они являются основным механизмом для определения параметров, валидации данных и генерации документации.

2. Тип аннотации Path

Path - это класс, предоставляемый FastAPI специально для определения параметров пути. Параметры пути являются частью самого URL-адреса и обычно используются для идентификации конкретного ресурса.

Часто используемые параметры Path:

  • ... (Многоточие): Это специальное значение, которое указывает, что параметр пути является обязательным. Если клиент не предоставит значение для обязательного параметра пути, FastAPI вернет ошибку 422 Unprocessable Entity.
  • title: str: Добавляет краткий заголовок/описание к параметру, который будет отображаться в документации Swagger UI.
  • description: str: Добавляет более длинное описание к параметру, также отображаемое в Swagger UI.
  • ge: int | float: "Больше или равно" (Greater than or equal to). Указывает минимальное значение для числового параметра пути.
  • gt: int | float: "Больше чем" (Greater than). Указывает значение, которое параметр должен строго превышать.
  • le: int | float: "Меньше или равно" (Less than or equal to). Указывает максимальное значение.
  • lt: int | float: "Меньше чем" (Less than). Указывает значение, которое параметр должен быть строго меньше.
  • min_length: int: Для строковых параметров пути указывает минимальную длину.
  • max_length: int: Для строковых параметров пути указывает максимальную длину.
  • regex: str: Для строковых параметров пути указывает регулярное выражение, которому должен соответствовать параметр.

Пример с Path:

python
from fastapi import FastAPI, Path

app = FastAPI()

@app.get("/items/{item_id}")
async def read_item(
    item_id: int = Path(..., title="The ID of the item", ge=1, description="Must be greater or equal than 1")
):
    """
    Retrieves an item by its ID.

    Args:
        item_id: The ID of the item (must be an integer greater than or equal to 1).
    """
    return {"item_id": item_id}

@app.get("/users/{username}")
async def read_user(
    username: str = Path(..., min_length=3, max_length=20, title="User name")
):
    """
     Get user by username
    Args:
        username: user name, must be string with length between 3 and 20
    """
    return {"username": username}

@app.get("/license-plates/{license_plate}")
async def read_license_plate(
    license_plate: str = Path(..., regex=r"^[A-Z]{2}-\d{3}$", title="License plate UK format")
):
    """
    Get license plate by UK format.
    Args:
        license_plate: License plate number. Format XX-999
    """
    return {"license_plate": license_plate}

Пояснение:

  1. item_id: int = Path(...):
    • item_id: int объявляет, что item_id является параметром пути и должен быть целым числом.
    • = Path(...) делает его обязательным параметром пути.
    • title="The ID of the item" предоставляет краткое описание для Swagger UI.
    • ge=1 гарантирует, что item_id больше или равен 1. FastAPI автоматически проверит это.
    • description добавляет описание для Swagger UI.
  2. username: str = Path(..., min_length=3, max_length=20):
    • username: str объявляет, что username является строковым параметром пути.
    • min_length=3 и max_length=20 обеспечивают ограничения по длине.
  3. license_plate: str = Path(..., regex=r"^[A-Z]{2}-\d{3}$"):
    • regex проверяет формат.

Swagger UI:

Когда вы запустите это приложение и откроете конечную точку /docs, Swagger UI:

  • Покажет /items/{item_id} и /users/{username} как конечные точки.
  • Четко укажет, что item_id и username являются параметрами пути.
  • Отобразит title и любые другие метаданные, которые вы предоставили.
  • Покажет тип (целое число, строка) и любые ограничения (например, ge=1, min_length=3).
  • Позволит вам "Try it out", введя значение для item_id и username и отправив запрос. FastAPI автоматически проверит ввод на основе вашей конфигурации Path.

Тип аннотации Query

Query похож на Path, но используется для определения параметров запроса. Параметры запроса добавляются к URL-адресу после вопросительного знака (?) и обычно используются для фильтрации, сортировки или пагинации.

Часто используемые параметры Query:

Query поддерживает все те же параметры, что и Path (title, description, ge, gt, le, lt, min_length, max_length, regex), а также:

  • default: Any: Указывает значение по умолчанию для параметра запроса. Если клиент не предоставит значение, будет использовано значение по умолчанию. Если default не указан, а тип не является необязательным, параметр обязателен.
  • alias: str: Позволяет использовать другое имя для параметра запроса в вашем коде Python, чем имя, которое появляется в URL.
  • deprecated: bool: Помечает параметр запроса как устаревший в документации Swagger UI.

Пример с Query:

python
from fastapi import FastAPI, Query
from typing import Optional, List

app = FastAPI()

@app.get("/items/")
async def read_items(
    q: Optional[str] = Query(None, title="Search query", max_length=50),
    skip: int = Query(0, title="Number of items to skip", ge=0),
    limit: int = Query(10, title="Maximum number of items to return", le=100),
    sort_by: Optional[List[str]] = Query(None, title="Fields to sort by")
):
    """
    Retrieves a list of items.

    Args:
        q: Optional search query (string, max length 50).
        skip: Number of items to skip (integer, >= 0).
        limit: Maximum number of items to return (integer, <= 100).
        sort_by: list of fields to sort
    """
    # In a real application, you would use these parameters to query a database.
    results = [
        {"item_id": 1, "name": "Bad"},
        {"item_id": 2, "name": "Bar"},
        {"item_id": 3, "name": "Baz"}
    ]
    if q:
        results = [item for item in results if q.lower() in item["name"].lower()]
    if sort_by:
        # Basic sort implementation (for demonstration purposes)
        for field in reversed(sort_by):  # Apply sorts in reverse order
            results.sort(key=lambda item: item.get(field, ""))

    return results[skip : skip + limit]

@app.get("/cars/")
async def read_cars(
        color: Optional[str] = Query(None, alias="car-color", title="Car's color"),
        year: int = Query(..., gt=1900, title="Manufacture year")
):
    """
    Retrieves cars based on color and year.

    Args:
        color:  The color of the car (optional, uses alias 'car-color' in the URL).
        year: The manufacture year (required, must be greater than 1900).
    """
    return {"color": color, "year": year}

Пояснение:

  1. q: Optional[str] = Query(None, ...):
    • Optional[str] означает, что параметр q является необязательным строковым параметром.
    • = Query(None, ...) устанавливает значение по умолчанию None, если параметр не предоставлен.
    • max_length=50 ограничивает длину строки.
  2. skip: int = Query(0, ...):
    • skip - обязательный целочисленный параметр (потому что указано значение по умолчанию 0).
    • ge=0 гарантирует, что skip неотрицателен.
  3. limit: int = Query(10, ...):
    • limit - обязательный целочисленный параметр (по умолчанию 10).
    • le=100 ограничивает максимальное значение.
  4. sort_by: Optional[List[str]] = Query(None, ...):
    • sort_by - Необязательный параметр, который принимает список строк.
  5. color: Optional[str] = Query(None, alias="car-color", ...):
    • alias="car-color" означает, что в URL-адресе будет использоваться имя car-color, но в коде Python вы будете обращаться к этому параметру как color.
  6. year: int = Query(..., gt=1900, ...):
    • year - обязательный целочисленный параметр (многоточие ...).
    • gt=1900 - год должен быть больше 1900.

Swagger UI:

  • Покажет /items/ и /cars/ как конечные точки.
  • Для /items/:
    • Отобразит q, skip, limit и sort_by как параметры запроса.
    • Покажет их типы, описания, значения по умолчанию и ограничения.
  • Для /cars/:
    • Отобразит car-color (из-за alias) и year как параметры запроса.
    • Покажет, что car-color необязателен, а year обязателен.
  • "Try it out" позволит вам вводить значения для параметров запроса и отправлять запросы.

Сравнение оформления:

1. Без аннотаций типов:

python
from fastapi import FastAPI

app = FastAPI()

@app.get("/items_no_types")
async def read_items_no_types(item_id, q):  # No type hints!
    return {"item_id": item_id, "q": q}
  • Swagger UI: Покажет item_id и q как параметры, но без указания их типов. Swagger UI определит их как "string", но это не совсем точно. Нет никакой валидации.
  • Проблемы: Нет валидации типов. Любые входные данные будут приняты, что может привести к ошибкам внутри вашей функции. IDE не сможет предоставить полезные подсказки.

2. С аннотациями базовых типов:

python
from fastapi import FastAPI
from typing import Optional

app = FastAPI()

@app.get("/items_basic_types")
async def read_items_basic_types(item_id: int, q: Optional[str]):
    return {"item_id": item_id, "q": q}
  • Swagger UI: Покажет item_id как integer, а q как string (и как необязательный параметр). Это уже намного лучше!
  • Преимущества: FastAPI выполнит базовую проверку типов. Если вы попытаетесь передать строку в item_id, вы получите ошибку. IDE предоставит лучшие подсказки.

3. С аннотациями Path и Query:

python
from fastapi import FastAPI, Path, Query
from typing import Optional

app = FastAPI()

@app.get("/items_with_path_query/{item_id}")
async def read_items_with_path_query(
    item_id: int = Path(..., title="Item ID", ge=1),
    q: Optional[str] = Query(None, title="Query String", max_length=50)
):
    return {"item_id": item_id, "q": q}
  • Swagger UI: Наиболее информативный вариант. Покажет item_id как параметр пути, а q как параметр запроса. Отобразит все метаданные (title, description, ограничения). "Try it out" будет работать максимально корректно.
  • Преимущества: Полная валидация, лучшая документация, лучший опыт разработки.

Упражнения:

  1. "Добавление пользователя" (Add User): Создайте endpoint /users/{user_id} (GET), который принимает user_id как параметр пути (целое число, больше 0). Добавьте параметр запроса is_active (булево значение, по умолчанию True).

  2. "Поиск книг" (Book Search): Создайте endpoint /books/ (GET), который принимает параметры запроса: title (строка, необязательный), author (строка, необязательный), min_pages (целое число, по умолчанию 0), max_pages (целое число, необязательный).

  3. "Валидация ISBN" (ISBN Validation): Создайте endpoint /books/{isbn} (GET), который принимает isbn как параметр пути (строка). Используйте regex в Path, чтобы проверить, что isbn соответствует формату ISBN-10 или ISBN-13 (найдите регулярные выражения для ISBN в Интернете).

  4. "Устаревший параметр" (Deprecated Parameter): Добавьте к endpoint /books/ параметр запроса sort_order (строка, по умолчанию "asc"). Пометьте его как устаревший (deprecated=True). Проверьте, как это отображается в Swagger UI.

  5. "Псевдоним параметра" (Parameter Alias): Добавьте к endpoint /users/{user_id} параметр запроса email_address, но используйте alias так, чтобы в URL-адресе он назывался email.

  6. "Список с ограничениями"(Constrained List): Создайте endpoint /tags/ (GET), который принимает параметр запроса tags (список строк). Ограничьте максимальное количество тегов до 5.

  7. "Сложный фильтр" (Complex Filter): Создайте endpoint /products/ (GET) с параметрами запроса min_price (число с плавающей точкой), max_price (число с плавающей точкой) и category (строка). Все параметры должны быть необязательными.

Contacts: teffal@mail.ru