Перейти к содержанию

Query-параметры и валидация строк

FastAPI позволяет определять дополнительную информацию и валидацию для ваших параметров.

Давайте рассмотрим следующий пример:

from fastapi import FastAPI

app = FastAPI()


@app.get("/items/")
async def read_items(q: str | None = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Union

from fastapi import FastAPI

app = FastAPI()


@app.get("/items/")
async def read_items(q: Union[str, None] = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Query-параметр q имеет тип Union[str, None] (или str | None в Python 3.10). Это означает, что входной параметр будет типа str, но может быть и None. Ещё параметр имеет значение по умолчанию None, из-за чего FastAPI определит параметр как необязательный.

Технические детали

FastAPI определит параметр q как необязательный, потому что его значение по умолчанию = None.

Union в Union[str, None] позволит редактору кода оказать вам лучшую поддержку и найти ошибки.

Расширенная валидация

Добавим дополнительное условие валидации параметра q - длина строки не более 50 символов (условие проверяется всякий раз, когда параметр q не является None).

Импорт Query и Annotated

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

  • Query из пакета fastapi:
  • Annotated из пакета typing (или из typing_extensions для Python ниже 3.9)

В Python 3.9 или выше, Annotated является частью стандартной библиотеки, таким образом вы можете импортировать его из typing.

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(max_length=50)] = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

В версиях Python ниже Python 3.9 Annotation импортируется из typing_extensions.

Эта библиотека будет установлена вместе с FastAPI.

from typing import Union

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[Union[str, None], Query(max_length=50)] = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Annotated как тип для query-параметра q

Помните, как ранее я говорил об Annotated? Он может быть использован для добавления метаданных для ваших параметров в разделе Введение в аннотации типов Python?

Пришло время использовать их в FastAPI. 🚀

У нас была аннотация следующего типа:

q: str | None = None
q: Union[str, None] = None

Вот что мы получим, если обернём это в Annotated:

q: Annotated[str | None] = None
q: Annotated[Union[str, None]] = None

Обе эти версии означают одно и тоже. q - это параметр, который может быть str или None, и по умолчанию он будет принимать None.

Давайте повеселимся. 🎉

Добавим Query в Annotated для query-параметра q

Теперь, когда у нас есть Annotated, где мы можем добавить больше метаданных, добавим Query со значением параметра max_length равным 50:

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(max_length=50)] = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Union

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[Union[str, None], Query(max_length=50)] = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Обратите внимание, что значение по умолчанию всё ещё None, так что параметр остаётся необязательным.

Однако теперь, имея Query(max_length=50) внутри Annotated, мы говорим FastAPI, что мы хотим извлечь это значение из параметров query-запроса (что произойдёт в любом случае 🤷), и что мы хотим иметь дополнительные условия валидации для этого значения (для чего мы и делаем это - чтобы получить дополнительную валидацию). 😎

Теперь FastAPI:

  • Валидирует (проверяет), что полученные данные состоят максимум из 50 символов
  • Показывает исчерпывающую ошибку (будет описание местонахождения ошибки и её причины) для клиента в случаях, когда данные не валидны
  • Задокументирует параметр в схему OpenAPI операции пути (что будет отображено в UI автоматической документации)

Альтернативный (устаревший) способ задать Query как значение по умолчанию

В предыдущих версиях FastAPI (ниже 0.95.0) необходимо было использовать Query как значение по умолчанию для query-параметра. Так было вместо размещения его в Annotated, так что велика вероятность, что вам встретится такой код. Сейчас объясню.

Подсказка

При написании нового кода и везде где это возможно, используйте Annotated, как было описано ранее. У этого способа есть несколько преимуществ (о них дальше) и никаких недостатков. 🍰

Вот как вы могли бы использовать Query() в качестве значения по умолчанию параметра вашей функции, установив для параметра max_length значение 50:

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str | None = Query(default=None, max_length=50)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Union[str, None] = Query(default=None, max_length=50)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

В таком случае (без использования Annotated), мы заменили значение по умолчанию с None на Query() в функции. Теперь нам нужно установить значение по умолчанию для query-параметра Query(default=None), что необходимо для тех же целей, как когда ранее просто указывалось значение по умолчанию (по крайней мере, для FastAPI).

Таким образом:

q: Union[str, None] = Query(default=None)

...делает параметр необязательным со значением по умолчанию None, также как это делает:

q: Union[str, None] = None

И для Python 3.10 и выше:

q: str | None = Query(default=None)

...делает параметр необязательным со значением по умолчанию None, также как это делает:

q: str | None = None

Но он явно объявляет его как query-параметр.

Дополнительная информация

Запомните, важной частью объявления параметра как необязательного является:

= None

или:

= Query(default=None)

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

Union[str, None] позволит редактору кода оказать вам лучшую поддержку. Но это не то, на что обращает внимание FastAPI для определения необязательности параметра.

Теперь, мы можем указать больше параметров для Query. В данном случае, параметр max_length применяется к строкам:

q: Union[str, None] = Query(default=None, max_length=50)

Входные данные будут проверены. Если данные недействительны, тогда будет указано на ошибку в запросе (будет описание местонахождения ошибки и её причины). Кроме того, параметр задокументируется в схеме OpenAPI данной операции пути.

Использовать Query как значение по умолчанию или добавить в Annotated

Когда Query используется внутри Annotated, вы не можете использовать параметр default у Query.

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

Следующий пример не рабочий:

q: Annotated[str, Query(default="rick")] = "morty"

...потому что нельзя однозначно определить, что именно должно быть значением по умолчанию: "rick" или "morty".

Вам следует использовать (предпочтительно):

q: Annotated[str, Query()] = "rick"

...или как в старом коде, который вам может попасться:

q: str = Query(default="rick")

Преимущества Annotated

Рекомендуется использовать Annotated вместо значения по умолчанию в параметрах функции, потому что так лучше по нескольким причинам. 🤓

Значение по умолчанию у параметров функции - это действительно значение по умолчанию, что более интуитивно понятно для пользователей Python. 😌

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

Если вы вместо Annotated используете (устаревший) стиль значений по умолчанию, тогда при вызове этой функции без FastAPI в другом месте вам необходимо помнить о передаче аргументов функции, чтобы она работала корректно. В противном случае, значения будут отличаться от тех, что вы ожидаете (например, QueryInfo или что-то подобное вместо str). И ни ваш редактор кода, ни Python не будут жаловаться на работу этой функции, только когда вычисления внутри дадут сбой.

Так как Annotated может принимать более одной аннотации метаданных, то теперь вы можете использовать ту же функцию с другими инструментами, например Typer. 🚀

Больше валидации

Вы также можете добавить параметр min_length:

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[str | None, Query(min_length=3, max_length=50)] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Annotated, Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[Union[str, None], Query(min_length=3, max_length=50)] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Union

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[Union[str, None], Query(min_length=3, max_length=50)] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str | None = Query(default=None, min_length=3, max_length=50)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Union[str, None] = Query(default=None, min_length=3, max_length=50),
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Регулярные выражения

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

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[
        str | None, Query(min_length=3, max_length=50, pattern="^fixedquery$")
    ] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Annotated, Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[
        Union[str, None], Query(min_length=3, max_length=50, pattern="^fixedquery$")
    ] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Union

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[
        Union[str, None], Query(min_length=3, max_length=50, pattern="^fixedquery$")
    ] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: str | None = Query(
        default=None, min_length=3, max_length=50, pattern="^fixedquery$"
    ),
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Union[str, None] = Query(
        default=None, min_length=3, max_length=50, pattern="^fixedquery$"
    ),
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Данное регулярное выражение проверяет, что полученное значение параметра:

  • ^: начало строки.
  • fixedquery: в точности содержит строку fixedquery.
  • $: конец строки, не имеет символов после fixedquery.

Не переживайте, если "регулярное выражение" вызывает у вас трудности. Это достаточно сложная тема для многих людей. Вы можете сделать множество вещей без использования регулярных выражений.

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

Значения по умолчанию

Вы точно также можете указать любое значение по умолчанию, как ранее указывали None.

Например, вы хотите для параметра запроса q указать, что он должен состоять минимум из 3 символов (min_length=3) и иметь значение по умолчанию "fixedquery":

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str, Query(min_length=3)] = "fixedquery"):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str, Query(min_length=3)] = "fixedquery"):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str = Query(default="fixedquery", min_length=3)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Технические детали

Наличие значения по умолчанию делает параметр необязательным.

Обязательный параметр

Когда вам не требуется дополнительная валидация или дополнительные метаданные для параметра запроса, вы можете сделать параметр q обязательным просто не указывая значения по умолчанию. Например:

q: str

вместо:

q: Union[str, None] = None

Но у нас query-параметр определён как Query. Например:

q: Annotated[Union[str, None], Query(min_length=3)] = None
q: Union[str, None] = Query(default=None, min_length=3)

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

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str, Query(min_length=3)]):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str, Query(min_length=3)]):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str = Query(min_length=3)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Подсказка

Обратите внимание, что даже когда Query() используется как значение по умолчанию для параметра функции, мы не передаём default=None в Query().

Лучше будет использовать версию с Annotated. 😉

Обязательный параметр с Ellipsis (...)

Альтернативный способ указать обязательность параметра запроса - это указать параметр default через многоточие ...:

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str, Query(min_length=3)] = ...):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str, Query(min_length=3)] = ...):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str = Query(default=..., min_length=3)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Дополнительная информация

Если вы ранее не сталкивались с ...: это специальное значение, часть языка Python и называется "Ellipsis".

Используется в Pydantic и FastAPI для определения, что значение требуется обязательно.

Таким образом, FastAPI определяет, что параметр является обязательным.

Обязательный параметр с None

Вы можете определить, что параметр может принимать None, но всё ещё является обязательным. Это может потребоваться для того, чтобы пользователи явно указали параметр, даже если его значение будет None.

Чтобы этого добиться, вам нужно определить None как валидный тип для параметра запроса, но также указать default=...:

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(min_length=3)] = ...):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Annotated, Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[Union[str, None], Query(min_length=3)] = ...):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Union

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[Union[str, None], Query(min_length=3)] = ...):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str | None = Query(default=..., min_length=3)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Union[str, None] = Query(default=..., min_length=3)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Подсказка

Pydantic, мощь которого используется в FastAPI для валидации и сериализации, имеет специальное поведение для Optional или Union[Something, None] без значения по умолчанию. Вы можете узнать об этом больше в документации Pydantic, раздел Обязательные Опциональные поля.

Использование Pydantic's Required вместо Ellipsis (...)

Если вас смущает ..., вы можете использовать Required из Pydantic:

from typing import Annotated

from fastapi import FastAPI, Query
from pydantic import Required

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str, Query(min_length=3)] = Required):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from fastapi import FastAPI, Query
from pydantic import Required
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str, Query(min_length=3)] = Required):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from fastapi import FastAPI, Query
from pydantic import Required

app = FastAPI()


@app.get("/items/")
async def read_items(q: str = Query(default=Required, min_length=3)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Подсказка

Запомните, когда вам необходимо объявить query-параметр обязательным, вы можете просто не указывать параметр default. Таким образом, вам редко придётся использовать ... или Required.

Множество значений для query-параметра

Для query-параметра Query можно указать, что он принимает список значений (множество значений).

Например, query-параметр q может быть указан в URL несколько раз. И если вы ожидаете такой формат запроса, то можете указать это следующим образом:

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[list[str] | None, Query()] = None):
    query_items = {"q": q}
    return query_items
from typing import Annotated, Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[Union[list[str], None], Query()] = None):
    query_items = {"q": q}
    return query_items
from typing import List, Union

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[Union[List[str], None], Query()] = None):
    query_items = {"q": q}
    return query_items

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: list[str] | None = Query(default=None)):
    query_items = {"q": q}
    return query_items

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Union[list[str], None] = Query(default=None)):
    query_items = {"q": q}
    return query_items

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from typing import List, Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Union[List[str], None] = Query(default=None)):
    query_items = {"q": q}
    return query_items

Затем, получив такой URL:

http://localhost:8000/items/?q=foo&q=bar

вы бы получили несколько значений (foo и bar), которые относятся к параметру q, в виде Python list внутри вашей функции обработки пути, в параметре функции q.

Таким образом, ответ на этот URL будет:

{
  "q": [
    "foo",
    "bar"
  ]
}

Подсказка

Чтобы объявить query-параметр типом list, как в примере выше, вам нужно явно использовать Query, иначе он будет интерпретирован как тело запроса.

Интерактивная документация API будет обновлена соответствующим образом, где будет разрешено множество значений:

Query-параметр со множеством значений по умолчанию

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

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[list[str], Query()] = ["foo", "bar"]):
    query_items = {"q": q}
    return query_items
from typing import List

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[List[str], Query()] = ["foo", "bar"]):
    query_items = {"q": q}
    return query_items

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: list[str] = Query(default=["foo", "bar"])):
    query_items = {"q": q}
    return query_items

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from typing import List

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: List[str] = Query(default=["foo", "bar"])):
    query_items = {"q": q}
    return query_items

Если вы перейдёте по ссылке:

http://localhost:8000/items/

значение по умолчанию для q будет: ["foo", "bar"] и ответом для вас будет:

{
  "q": [
    "foo",
    "bar"
  ]
}

Использование list

Вы также можете использовать list напрямую вместо List[str] (или list[str] в Python 3.9+):

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[list, Query()] = []):
    query_items = {"q": q}
    return query_items
from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[list, Query()] = []):
    query_items = {"q": q}
    return query_items

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: list = Query(default=[])):
    query_items = {"q": q}
    return query_items

Технические детали

Запомните, что в таком случае, FastAPI не будет проверять содержимое списка.

Например, для List[int] список будет провалидирован (и задокументирован) на содержание только целочисленных элементов. Но для простого list такой проверки не будет.

Больше метаданных

Вы можете добавить больше информации об query-параметре.

Указанная информация будет включена в генерируемую OpenAPI документацию и использована в пользовательском интерфейсе и внешних инструментах.

Технические детали

Имейте в виду, что разные инструменты могут иметь разные уровни поддержки OpenAPI.

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

Вы можете указать название query-параметра, используя параметр title:

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[str | None, Query(title="Query string", min_length=3)] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Annotated, Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[Union[str, None], Query(title="Query string", min_length=3)] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Union

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[Union[str, None], Query(title="Query string", min_length=3)] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: str | None = Query(default=None, title="Query string", min_length=3),
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Union[str, None] = Query(default=None, title="Query string", min_length=3),
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Добавить описание, используя параметр description:

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[
        str | None,
        Query(
            title="Query string",
            description="Query string for the items to search in the database that have a good match",
            min_length=3,
        ),
    ] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Annotated, Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[
        Union[str, None],
        Query(
            title="Query string",
            description="Query string for the items to search in the database that have a good match",
            min_length=3,
        ),
    ] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Union

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[
        Union[str, None],
        Query(
            title="Query string",
            description="Query string for the items to search in the database that have a good match",
            min_length=3,
        ),
    ] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: str | None = Query(
        default=None,
        title="Query string",
        description="Query string for the items to search in the database that have a good match",
        min_length=3,
    ),
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Union[str, None] = Query(
        default=None,
        title="Query string",
        description="Query string for the items to search in the database that have a good match",
        min_length=3,
    ),
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Псевдонимы параметров

Представьте, что вы хотите использовать query-параметр с названием item-query.

Например:

http://127.0.0.1:8000/items/?item-query=foobaritems

Но item-query является невалидным именем переменной в Python.

Наиболее похожее валидное имя item_query.

Но вам всё равно необходим item-query...

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

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(alias="item-query")] = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Annotated, Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[Union[str, None], Query(alias="item-query")] = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Union

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[Union[str, None], Query(alias="item-query")] = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str | None = Query(default=None, alias="item-query")):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Union[str, None] = Query(default=None, alias="item-query")):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Устаревшие параметры

Предположим, вы больше не хотите использовать какой-либо параметр.

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

Тогда для Query укажите параметр deprecated=True:

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[
        str | None,
        Query(
            alias="item-query",
            title="Query string",
            description="Query string for the items to search in the database that have a good match",
            min_length=3,
            max_length=50,
            pattern="^fixedquery$",
            deprecated=True,
        ),
    ] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Annotated, Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[
        Union[str, None],
        Query(
            alias="item-query",
            title="Query string",
            description="Query string for the items to search in the database that have a good match",
            min_length=3,
            max_length=50,
            pattern="^fixedquery$",
            deprecated=True,
        ),
    ] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Union

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[
        Union[str, None],
        Query(
            alias="item-query",
            title="Query string",
            description="Query string for the items to search in the database that have a good match",
            min_length=3,
            max_length=50,
            pattern="^fixedquery$",
            deprecated=True,
        ),
    ] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: str | None = Query(
        default=None,
        alias="item-query",
        title="Query string",
        description="Query string for the items to search in the database that have a good match",
        min_length=3,
        max_length=50,
        pattern="^fixedquery$",
        deprecated=True,
    ),
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Union[str, None] = Query(
        default=None,
        alias="item-query",
        title="Query string",
        description="Query string for the items to search in the database that have a good match",
        min_length=3,
        max_length=50,
        pattern="^fixedquery$",
        deprecated=True,
    ),
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

В документации это будет отображено следующим образом:

Исключить из OpenAPI

Чтобы исключить query-параметр из генерируемой OpenAPI схемы (а также из системы автоматической генерации документации), укажите в Query параметр include_in_schema=False:

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    hidden_query: Annotated[str | None, Query(include_in_schema=False)] = None,
):
    if hidden_query:
        return {"hidden_query": hidden_query}
    else:
        return {"hidden_query": "Not found"}
from typing import Annotated, Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    hidden_query: Annotated[Union[str, None], Query(include_in_schema=False)] = None,
):
    if hidden_query:
        return {"hidden_query": hidden_query}
    else:
        return {"hidden_query": "Not found"}
from typing import Union

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(
    hidden_query: Annotated[Union[str, None], Query(include_in_schema=False)] = None,
):
    if hidden_query:
        return {"hidden_query": hidden_query}
    else:
        return {"hidden_query": "Not found"}

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    hidden_query: str | None = Query(default=None, include_in_schema=False),
):
    if hidden_query:
        return {"hidden_query": hidden_query}
    else:
        return {"hidden_query": "Not found"}

Подсказка

Рекомендуется использовать версию с Annotated если возможно.

from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    hidden_query: Union[str, None] = Query(default=None, include_in_schema=False),
):
    if hidden_query:
        return {"hidden_query": hidden_query}
    else:
        return {"hidden_query": "Not found"}

Резюме

Вы можете объявлять дополнительные правила валидации и метаданные для ваших параметров запроса.

Общие метаданные:

  • alias
  • title
  • description
  • deprecated
  • include_in_schema

Специфичные правила валидации для строк:

  • min_length
  • max_length
  • regex

В рассмотренных примерах показано объявление правил валидации для строковых значений str.

В следующих главах вы увидете, как объявлять правила валидации для других типов (например, чисел).