# Annotated FastAPI router
Этот пакет содержит улучшенный `fastapi.APIRouter`, который содержит шорт-каты для удобной аннотации
эндпоинтов через `typing.Annotated`, что бы задавать значения `status_code` или модели ответов в `responses`.
- [примеры](#примеры)
- [статус-код](#статус-код)
- [модели ошибок](#модель-ответов-для-ошибок)
Warning *the English part of README.md is translated via ChatGPT and may not be accurate*
This package contains an enhanced `fastapi.APIRouter` that includes shortcuts for annotating endpoints conveniently using `typing.Annotated`, allowing you to set values like `status_code` or response models in `responses`.
- [examples](#examples)
- [status code](#status-code)
- [responses](#responses)
## Примеры
### Статус-код
Указывать `status_code` у эндпоинта можно следующим образом:
```python
from http import HTTPStatus
from typing import Annotated
from fastapi import FastAPI
from pydantic import BaseModel
from annotated_fastapi_router import AnnotatedAPIRouter, Status
class ResponseModel(BaseModel):
message: str
router = AnnotatedAPIRouter()
@router.get("/my_endpoint")
async def my_endpoint() -> Annotated[ResponseModel, Status[HTTPStatus.ACCEPTED]]:
return ResponseModel(message="hello")
router.add_api_route("/my_endpoint", my_endpoint, methods=["POST"])
```
В результате swagger будет содержать следующую OpenAPI-схему:
![GET request swagger](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/use_status_get.png)
![POST request swagger](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/use_status_post.png)
При запросе статус код так же будет проставлен в ответ:
![POST response swagger](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/use_status_get_response.png)
Однако если статус код будет указан явно в декораторе или при добавлении в роутер, `Status` в аннотации будет проигнорирован.
```python
@router.get("/my_endpoint")
@router.get("/my_endpoint_too", status_code=HTTPStatus.CREATED)
async def my_endpoint() -> Annotated[ResponseModel, Status[HTTPStatus.ACCEPTED]]:
return ResponseModel(message="hello")
router.add_api_route("/my_endpoint", my_endpoint, methods=["POST"])
router.add_api_route("/my_endpoint_too", my_endpoint, status_code=HTTPStatus.CREATED, methods=["POST"])
```
![GET request swagger keep](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/keep_status_get_swagger.png)
![POST request swagger keep](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/keep_status_post_swagger.png)
**Важно:** `Status` принимает в себя только перечисления из `HTTPStatus`,
на любые другие объекты будет подыматься ошибка `TypeError`.
### Модель ответов для ошибок
Для автоматического построения моделей для `responses` используется тип ошибок `ResponseError`.
В его наследниках обязательно нужно указывать переменную типа `status` и `model`.
`status` указывает для каких кодов ошибок нужно будет построить модель, если будет передано несколько
типов ошибок для одного и того же `HTTPStatus`, то полученная модель будет объединять в себе все модели `model`.
```python
from http import HTTPStatus
from typing import Annotated, Self
from fastapi import FastAPI
from pydantic import BaseModel
from annotated_fastapi_router import AnnotatedAPIRouter, Errors, ResponseError
class ResponseModel(BaseModel):
message: str
class ErrorMessageModel(BaseModel):
message: str
class OtherErrorModel(BaseModel):
code: int
class MyError(ResponseError[ErrorMessageModel]):
status = HTTPStatus.BAD_REQUEST
model = ErrorMessageModel
def __init__(self: "MyError", msg: str) -> None:
self.message = msg
class OtherError(ResponseError[OtherErrorModel]):
status = HTTPStatus.BAD_REQUEST
model = OtherErrorModel
async def entity(self: Self) -> OtherErrorModel:
return self.model(code=self.status)
router = AnnotatedAPIRouter()
@router.get("/my_endpoint")
@router.post("/my_endpoint")
async def endpoint(how_iam: str) -> Annotated[ResponseModel, Errors[MyError, OtherError]]:
if how_iam == "me":
return ResponseModel(message="hello")
if how_iam.isdigit():
raise OtherError
raise MyError("I don't know you")
```
Таким образом будет построены две модели ошибок – `MyEndpointPOSTBadRequest` и `MyEndpointGETBadRequest`.
![Build error model GET](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/build_error_model_get.png)
![Build error model POST](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/build_error_model_post.png)
Имя модели генерируется из пути, методов (если указаны) роута и phrase статус-кода ошибки.
Делается это потому что для `fastapi<=0.95.0` требуется что бы все названия моделей (если они отличаются) имели уникальные имена.
Если модель ошибки состоит только из одной модели – тогда будет использовано имя оригинальной модели (что бы избегать баг в OpenAPI-схеме с некорректным определением компонента).
Логику построения имени модели можно изменить наследованием от `AnnotatedAPIRouter` и переопределением метода `response_model_name`.
Так же для построение экземпляра модели ошибки по умолчанию использует атрибуты экземпляра ошибки (через `__dict__`),
но если это поведение необходимо изменить можно реализовать метод `entity` (как в примере в класса `OtherError`).
Если `responses` уже определяется в декораторе роута или при явном добавлении в него и содержит описание для ошибки,
которая указана в `Annotated`, то она не будет перезаписывать данные переданные в `responses`.
```python
@router.get("/my_endpoint_too", responses={HTTPStatus.BAD_REQUEST: {"model": dict[str, str]}})
async def endpoint(how_iam: str) -> Annotated[ResponseModel, Errors[MyError, OtherError]]:
if how_iam == "me":
return ResponseModel(message="hello")
if how_iam.isdigit():
raise OtherError
raise MyError("I don't know you")
```
![Keep responses data model](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/keep_build_error_model.png)
Для того что бы автоматически отлавливать ошибки из эндпоинтов и указывать в ответе тот статус-код, который в указан в них,
необходимо добавить в `FastAPI` приложение обработчик ошибок `ResponseError.handler`.
```python
app.add_exception_handler(ResponseError, ResponseError.handler)
```
![Handle MyError](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/handle_my_error.png)
![Handle OtherError](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/handle_other_error.png)
## Examples
### Status-code
To specify the status_code of the endpoint, you can do it as follows:
```python
from http import HTTPStatus
from typing import Annotated
from fastapi import FastAPI
from pydantic import BaseModel
from annotated_fastapi_router import AnnotatedAPIRouter, Status
class ResponseModel(BaseModel):
message: str
router = AnnotatedAPIRouter()
@router.get("/my_endpoint")
async def my_endpoint() -> Annotated[ResponseModel, Status[HTTPStatus.ACCEPTED]]:
return ResponseModel(message="hello")
router.add_api_route("/my_endpoint", my_endpoint, methods=["POST"])
```
As a result, the swagger will contain the following OpenAPI schema:
![GET request swagger](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/use_status_get.png)
![POST request swagger](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/use_status_post.png)
When the endpoint is requested, the status code will also be included in the response:
![POST response swagger](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/use_status_get_response.png)
However, if the status code is explicitly provided in the decorator or when adding it to the router, the `Status` in the annotation will be ignored.
```python
@router.get("/my_endpoint")
@router.get("/my_endpoint_too", status_code=HTTPStatus.CREATED)
async def my_endpoint() -> Annotated[ResponseModel, Status[HTTPStatus.ACCEPTED]]:
return ResponseModel(message="hello")
router.add_api_route("/my_endpoint", my_endpoint, methods=["POST"])
router.add_api_route("/my_endpoint_too", my_endpoint, status_code=HTTPStatus.CREATED, methods=["POST"])
```
![GET request swagger keep](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/keep_status_get_swagger.png)
![POST request swagger keep](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/keep_status_post_swagger.png)
**Important**: `Status` only accepts enumerations from `HTTPStatus`; any other objects will raise a `TypeError`
### Responses
To automatically build models for `responses`, the error type `ResponseError` is used.
In its subclasses, it is necessary to specify variables of type `status` and `model`.
`status` indicates for which error codes the model will need to be constructed.
If multiple error types are provided for the same HTTPStatus, the resulting model will combine all model models.
```python
from http import HTTPStatus
from typing import Annotated, Self
from fastapi import FastAPI
from pydantic import BaseModel
from annotated_fastapi_router import AnnotatedAPIRouter, Errors, ResponseError
class ResponseModel(BaseModel):
message: str
class ErrorMessageModel(BaseModel):
message: str
class OtherErrorModel(BaseModel):
code: int
class MyError(ResponseError[ErrorMessageModel]):
status = HTTPStatus.BAD_REQUEST
model = ErrorMessageModel
def __init__(self: "MyError", msg: str) -> None:
self.message = msg
class OtherError(ResponseError[OtherErrorModel]):
status = HTTPStatus.BAD_REQUEST
model = OtherErrorModel
async def entity(self: Self) -> OtherErrorModel:
return self.model(code=self.status)
router = AnnotatedAPIRouter()
@router.get("/my_endpoint")
@router.post("/my_endpoint")
async def endpoint(how_iam: str) -> Annotated[ResponseModel, Errors[MyError, OtherError]]:
if how_iam == "me":
return ResponseModel(message="hello")
if how_iam.isdigit():
raise OtherError
raise MyError("I don't know you")
```
In this way, two error models will be built - `MyEndpointPOSTBadRequest` and `MyEndpointGETBadRequest`.
![Build error model GET](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/build_error_model_get.png)
![Build error model POST](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/build_error_model_post.png)
The model name is generated from the path, methods (if specified) of the route, and the status code error phrase.
This is done because for `fastapi<=0.95.0`, all model names (if they differ) must have unique names.
If the error model consists of only one model, the name of the original model will be used (to avoid a bug in the OpenAPI schema with an incorrect component definition).
The logic for naming the model can be changed by inheriting from `AnnotatedAPIRouter`
and overriding the `response_model_name` method.
Also, to build an instance of the error model by default, it uses the error instance attributes (via `__dict__`),
but if this behavior needs to be changed, you can implement the `entity` method
(as shown in the example in the `OtherError` class).
If `responses` are already defined in the route decorator or explicitly added to it and contain
a description for an error specified in `Annotated`, it will not overwrite the data passed to `responses`.
```python
@router.get("/my_endpoint_too", responses={HTTPStatus.BAD_REQUEST: {"model": dict[str, str]}})
async def endpoint(how_iam: str) -> Annotated[ResponseModel, Errors[MyError, OtherError]]:
if how_iam == "me":
return ResponseModel(message="hello")
if how_iam.isdigit():
raise OtherError
raise MyError("I don't know you")
```
![Keep responses data model](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/keep_build_error_model.png)
To automatically catch errors from endpoints and include the status code specified in them in the response, you need to add the error handler `ResponseError.handler` to the `FastAPI` application.
```python
app.add_exception_handler(ResponseError, ResponseError.handler)
```
![Handle MyError](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/handle_my_error.png)
![Handle OtherError](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/handle_other_error.png)
Raw data
{
"_id": null,
"home_page": "https://github.com/feodor-ra/annotated-fastapi-router",
"name": "annotated_fastapi_router",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.11",
"maintainer_email": null,
"keywords": "annotated, fastapi",
"author": "Fedor Ratschew",
"author_email": "feodor.ra@me.com",
"download_url": "https://files.pythonhosted.org/packages/3d/b1/eb50222fb44cf202dbf67dea2ecf8376ee513c9230b11d505c56153b032a/annotated_fastapi_router-0.1.0.tar.gz",
"platform": null,
"description": "# Annotated FastAPI router\n\n\u042d\u0442\u043e\u0442 \u043f\u0430\u043a\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u043d\u044b\u0439 `fastapi.APIRouter`, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0448\u043e\u0440\u0442-\u043a\u0430\u0442\u044b \u0434\u043b\u044f \u0443\u0434\u043e\u0431\u043d\u043e\u0439 \u0430\u043d\u043d\u043e\u0442\u0430\u0446\u0438\u0438\n\u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u043e\u0432 \u0447\u0435\u0440\u0435\u0437 `typing.Annotated`, \u0447\u0442\u043e \u0431\u044b \u0437\u0430\u0434\u0430\u0432\u0430\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f `status_code` \u0438\u043b\u0438 \u043c\u043e\u0434\u0435\u043b\u0438 \u043e\u0442\u0432\u0435\u0442\u043e\u0432 \u0432 `responses`.\n\n- [\u043f\u0440\u0438\u043c\u0435\u0440\u044b](#\u043f\u0440\u0438\u043c\u0435\u0440\u044b)\n - [\u0441\u0442\u0430\u0442\u0443\u0441-\u043a\u043e\u0434](#\u0441\u0442\u0430\u0442\u0443\u0441-\u043a\u043e\u0434)\n - [\u043c\u043e\u0434\u0435\u043b\u0438 \u043e\u0448\u0438\u0431\u043e\u043a](#\u043c\u043e\u0434\u0435\u043b\u044c-\u043e\u0442\u0432\u0435\u0442\u043e\u0432-\u0434\u043b\u044f-\u043e\u0448\u0438\u0431\u043e\u043a)\n\nWarning *the English part of README.md is translated via ChatGPT and may not be accurate*\n\nThis package contains an enhanced `fastapi.APIRouter` that includes shortcuts for annotating endpoints conveniently using `typing.Annotated`, allowing you to set values like `status_code` or response models in `responses`.\n\n- [examples](#examples)\n - [status code](#status-code)\n - [responses](#responses)\n\n## \u041f\u0440\u0438\u043c\u0435\u0440\u044b\n\n### \u0421\u0442\u0430\u0442\u0443\u0441-\u043a\u043e\u0434\n\n\u0423\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c `status_code` \u0443 \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u0430 \u043c\u043e\u0436\u043d\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:\n\n```python\nfrom http import HTTPStatus\nfrom typing import Annotated\n\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel\n\nfrom annotated_fastapi_router import AnnotatedAPIRouter, Status\n\n\nclass ResponseModel(BaseModel):\n message: str\n\n\nrouter = AnnotatedAPIRouter()\n\n\n@router.get(\"/my_endpoint\")\nasync def my_endpoint() -> Annotated[ResponseModel, Status[HTTPStatus.ACCEPTED]]:\n return ResponseModel(message=\"hello\")\n\n\nrouter.add_api_route(\"/my_endpoint\", my_endpoint, methods=[\"POST\"])\n```\n\n\u0412 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 swagger \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e OpenAPI-\u0441\u0445\u0435\u043c\u0443:\n\n![GET request swagger](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/use_status_get.png)\n![POST request swagger](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/use_status_post.png)\n\n\u041f\u0440\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0435 \u0441\u0442\u0430\u0442\u0443\u0441 \u043a\u043e\u0434 \u0442\u0430\u043a \u0436\u0435 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d \u0432 \u043e\u0442\u0432\u0435\u0442:\n\n![POST response swagger](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/use_status_get_response.png)\n\n\u041e\u0434\u043d\u0430\u043a\u043e \u0435\u0441\u043b\u0438 \u0441\u0442\u0430\u0442\u0443\u0441 \u043a\u043e\u0434 \u0431\u0443\u0434\u0435\u0442 \u0443\u043a\u0430\u0437\u0430\u043d \u044f\u0432\u043d\u043e \u0432 \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440\u0435 \u0438\u043b\u0438 \u043f\u0440\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u0432 \u0440\u043e\u0443\u0442\u0435\u0440, `Status` \u0432 \u0430\u043d\u043d\u043e\u0442\u0430\u0446\u0438\u0438 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u043d.\n\n```python\n@router.get(\"/my_endpoint\")\n@router.get(\"/my_endpoint_too\", status_code=HTTPStatus.CREATED)\nasync def my_endpoint() -> Annotated[ResponseModel, Status[HTTPStatus.ACCEPTED]]:\n return ResponseModel(message=\"hello\")\n\n\nrouter.add_api_route(\"/my_endpoint\", my_endpoint, methods=[\"POST\"])\nrouter.add_api_route(\"/my_endpoint_too\", my_endpoint, status_code=HTTPStatus.CREATED, methods=[\"POST\"])\n```\n\n![GET request swagger keep](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/keep_status_get_swagger.png)\n![POST request swagger keep](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/keep_status_post_swagger.png)\n\n**\u0412\u0430\u0436\u043d\u043e:** `Status` \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0432 \u0441\u0435\u0431\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f \u0438\u0437 `HTTPStatus`,\n\u043d\u0430 \u043b\u044e\u0431\u044b\u0435 \u0434\u0440\u0443\u0433\u0438\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0434\u044b\u043c\u0430\u0442\u044c\u0441\u044f \u043e\u0448\u0438\u0431\u043a\u0430 `TypeError`.\n\n### \u041c\u043e\u0434\u0435\u043b\u044c \u043e\u0442\u0432\u0435\u0442\u043e\u0432 \u0434\u043b\u044f \u043e\u0448\u0438\u0431\u043e\u043a\n\n\u0414\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u044f \u043c\u043e\u0434\u0435\u043b\u0435\u0439 \u0434\u043b\u044f `responses` \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0442\u0438\u043f \u043e\u0448\u0438\u0431\u043e\u043a `ResponseError`.\n\u0412 \u0435\u0433\u043e \u043d\u0430\u0441\u043b\u0435\u0434\u043d\u0438\u043a\u0430\u0445 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u043d\u0443\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u0442\u0438\u043f\u0430 `status` \u0438 `model`.\n\n`status` \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u0434\u043b\u044f \u043a\u0430\u043a\u0438\u0445 \u043a\u043e\u0434\u043e\u0432 \u043e\u0448\u0438\u0431\u043e\u043a \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043c\u043e\u0434\u0435\u043b\u044c, \u0435\u0441\u043b\u0438 \u0431\u0443\u0434\u0435\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e\n\u0442\u0438\u043f\u043e\u0432 \u043e\u0448\u0438\u0431\u043e\u043a \u0434\u043b\u044f \u043e\u0434\u043d\u043e\u0433\u043e \u0438 \u0442\u043e\u0433\u043e \u0436\u0435 `HTTPStatus`, \u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u0431\u0443\u0434\u0435\u0442 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u0442\u044c \u0432 \u0441\u0435\u0431\u0435 \u0432\u0441\u0435 \u043c\u043e\u0434\u0435\u043b\u0438 `model`.\n\n```python\nfrom http import HTTPStatus\nfrom typing import Annotated, Self\n\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel\n\nfrom annotated_fastapi_router import AnnotatedAPIRouter, Errors, ResponseError\n\n\nclass ResponseModel(BaseModel):\n message: str\n\n\nclass ErrorMessageModel(BaseModel):\n message: str\n\n\nclass OtherErrorModel(BaseModel):\n code: int\n\n\nclass MyError(ResponseError[ErrorMessageModel]):\n status = HTTPStatus.BAD_REQUEST\n model = ErrorMessageModel\n\n def __init__(self: \"MyError\", msg: str) -> None:\n self.message = msg\n\n\nclass OtherError(ResponseError[OtherErrorModel]):\n status = HTTPStatus.BAD_REQUEST\n model = OtherErrorModel\n\n async def entity(self: Self) -> OtherErrorModel:\n return self.model(code=self.status)\n\n\nrouter = AnnotatedAPIRouter()\n\n\n@router.get(\"/my_endpoint\")\n@router.post(\"/my_endpoint\")\nasync def endpoint(how_iam: str) -> Annotated[ResponseModel, Errors[MyError, OtherError]]:\n if how_iam == \"me\":\n return ResponseModel(message=\"hello\")\n if how_iam.isdigit():\n raise OtherError\n raise MyError(\"I don't know you\")\n```\n\n\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u044b \u0434\u0432\u0435 \u043c\u043e\u0434\u0435\u043b\u0438 \u043e\u0448\u0438\u0431\u043e\u043a \u2013 `MyEndpointPOSTBadRequest` \u0438 `MyEndpointGETBadRequest`.\n\n![Build error model GET](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/build_error_model_get.png)\n![Build error model POST](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/build_error_model_post.png)\n\n\u0418\u043c\u044f \u043c\u043e\u0434\u0435\u043b\u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0438\u0437 \u043f\u0443\u0442\u0438, \u043c\u0435\u0442\u043e\u0434\u043e\u0432 (\u0435\u0441\u043b\u0438 \u0443\u043a\u0430\u0437\u0430\u043d\u044b) \u0440\u043e\u0443\u0442\u0430 \u0438 phrase \u0441\u0442\u0430\u0442\u0443\u0441-\u043a\u043e\u0434\u0430 \u043e\u0448\u0438\u0431\u043a\u0438.\n\u0414\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u044d\u0442\u043e \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0434\u043b\u044f `fastapi<=0.95.0` \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0447\u0442\u043e \u0431\u044b \u0432\u0441\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u043c\u043e\u0434\u0435\u043b\u0435\u0439 (\u0435\u0441\u043b\u0438 \u043e\u043d\u0438 \u043e\u0442\u043b\u0438\u0447\u0430\u044e\u0442\u0441\u044f) \u0438\u043c\u0435\u043b\u0438 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0435 \u0438\u043c\u0435\u043d\u0430.\n\u0415\u0441\u043b\u0438 \u043c\u043e\u0434\u0435\u043b\u044c \u043e\u0448\u0438\u0431\u043a\u0438 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0438\u0437 \u043e\u0434\u043d\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438 \u2013 \u0442\u043e\u0433\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u043e \u0438\u043c\u044f \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438 (\u0447\u0442\u043e \u0431\u044b \u0438\u0437\u0431\u0435\u0433\u0430\u0442\u044c \u0431\u0430\u0433 \u0432 OpenAPI-\u0441\u0445\u0435\u043c\u0435 \u0441 \u043d\u0435\u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u044b\u043c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430).\n\n\u041b\u043e\u0433\u0438\u043a\u0443 \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u044f \u0438\u043c\u0435\u043d\u0438 \u043c\u043e\u0434\u0435\u043b\u0438 \u043c\u043e\u0436\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043e\u0442 `AnnotatedAPIRouter` \u0438 \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435\u043c \u043c\u0435\u0442\u043e\u0434\u0430 `response_model_name`.\n\n\u0422\u0430\u043a \u0436\u0435 \u0434\u043b\u044f \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u0435 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430 \u043c\u043e\u0434\u0435\u043b\u0438 \u043e\u0448\u0438\u0431\u043a\u0438 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430 \u043e\u0448\u0438\u0431\u043a\u0438 (\u0447\u0435\u0440\u0435\u0437 `__dict__`),\n\u043d\u043e \u0435\u0441\u043b\u0438 \u044d\u0442\u043e \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043c\u043e\u0436\u043d\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434 `entity` (\u043a\u0430\u043a \u0432 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0432 \u043a\u043b\u0430\u0441\u0441\u0430 `OtherError`).\n\n\u0415\u0441\u043b\u0438 `responses` \u0443\u0436\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442\u0441\u044f \u0432 \u0434\u0435\u043a\u043e\u0440\u0430\u0442\u043e\u0440\u0435 \u0440\u043e\u0443\u0442\u0430 \u0438\u043b\u0438 \u043f\u0440\u0438 \u044f\u0432\u043d\u043e\u043c \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u0432 \u043d\u0435\u0433\u043e \u0438 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0434\u043b\u044f \u043e\u0448\u0438\u0431\u043a\u0438,\n\u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0443\u043a\u0430\u0437\u0430\u043d\u0430 \u0432 `Annotated`, \u0442\u043e \u043e\u043d\u0430 \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u044b\u0435 \u0432 `responses`.\n\n```python\n@router.get(\"/my_endpoint_too\", responses={HTTPStatus.BAD_REQUEST: {\"model\": dict[str, str]}})\nasync def endpoint(how_iam: str) -> Annotated[ResponseModel, Errors[MyError, OtherError]]:\n if how_iam == \"me\":\n return ResponseModel(message=\"hello\")\n if how_iam.isdigit():\n raise OtherError\n raise MyError(\"I don't know you\")\n```\n\n![Keep responses data model](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/keep_build_error_model.png)\n\n\u0414\u043b\u044f \u0442\u043e\u0433\u043e \u0447\u0442\u043e \u0431\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043e\u0442\u043b\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0438 \u0438\u0437 \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u043e\u0432 \u0438 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0432 \u043e\u0442\u0432\u0435\u0442\u0435 \u0442\u043e\u0442 \u0441\u0442\u0430\u0442\u0443\u0441-\u043a\u043e\u0434, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432 \u0443\u043a\u0430\u0437\u0430\u043d \u0432 \u043d\u0438\u0445,\n\u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 `FastAPI` \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043e\u0448\u0438\u0431\u043e\u043a `ResponseError.handler`.\n\n```python\napp.add_exception_handler(ResponseError, ResponseError.handler)\n```\n\n![Handle MyError](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/handle_my_error.png)\n![Handle OtherError](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/handle_other_error.png)\n\n## Examples\n\n### Status-code\n\nTo specify the status_code of the endpoint, you can do it as follows:\n\n```python\nfrom http import HTTPStatus\nfrom typing import Annotated\n\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel\n\nfrom annotated_fastapi_router import AnnotatedAPIRouter, Status\n\n\nclass ResponseModel(BaseModel):\n message: str\n\n\nrouter = AnnotatedAPIRouter()\n\n\n@router.get(\"/my_endpoint\")\nasync def my_endpoint() -> Annotated[ResponseModel, Status[HTTPStatus.ACCEPTED]]:\n return ResponseModel(message=\"hello\")\n\n\nrouter.add_api_route(\"/my_endpoint\", my_endpoint, methods=[\"POST\"])\n```\n\nAs a result, the swagger will contain the following OpenAPI schema:\n\n![GET request swagger](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/use_status_get.png)\n![POST request swagger](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/use_status_post.png)\n\nWhen the endpoint is requested, the status code will also be included in the response:\n\n![POST response swagger](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/use_status_get_response.png)\n\nHowever, if the status code is explicitly provided in the decorator or when adding it to the router, the `Status` in the annotation will be ignored.\n\n```python\n@router.get(\"/my_endpoint\")\n@router.get(\"/my_endpoint_too\", status_code=HTTPStatus.CREATED)\nasync def my_endpoint() -> Annotated[ResponseModel, Status[HTTPStatus.ACCEPTED]]:\n return ResponseModel(message=\"hello\")\n\n\nrouter.add_api_route(\"/my_endpoint\", my_endpoint, methods=[\"POST\"])\nrouter.add_api_route(\"/my_endpoint_too\", my_endpoint, status_code=HTTPStatus.CREATED, methods=[\"POST\"])\n```\n\n![GET request swagger keep](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/keep_status_get_swagger.png)\n![POST request swagger keep](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/keep_status_post_swagger.png)\n\n**Important**: `Status` only accepts enumerations from `HTTPStatus`; any other objects will raise a `TypeError`\n\n### Responses\n\nTo automatically build models for `responses`, the error type `ResponseError` is used.\nIn its subclasses, it is necessary to specify variables of type `status` and `model`.\n\n`status` indicates for which error codes the model will need to be constructed.\nIf multiple error types are provided for the same HTTPStatus, the resulting model will combine all model models.\n\n```python\nfrom http import HTTPStatus\nfrom typing import Annotated, Self\n\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel\n\nfrom annotated_fastapi_router import AnnotatedAPIRouter, Errors, ResponseError\n\n\nclass ResponseModel(BaseModel):\n message: str\n\n\nclass ErrorMessageModel(BaseModel):\n message: str\n\n\nclass OtherErrorModel(BaseModel):\n code: int\n\n\nclass MyError(ResponseError[ErrorMessageModel]):\n status = HTTPStatus.BAD_REQUEST\n model = ErrorMessageModel\n\n def __init__(self: \"MyError\", msg: str) -> None:\n self.message = msg\n\n\nclass OtherError(ResponseError[OtherErrorModel]):\n status = HTTPStatus.BAD_REQUEST\n model = OtherErrorModel\n\n async def entity(self: Self) -> OtherErrorModel:\n return self.model(code=self.status)\n\n\nrouter = AnnotatedAPIRouter()\n\n\n@router.get(\"/my_endpoint\")\n@router.post(\"/my_endpoint\")\nasync def endpoint(how_iam: str) -> Annotated[ResponseModel, Errors[MyError, OtherError]]:\n if how_iam == \"me\":\n return ResponseModel(message=\"hello\")\n if how_iam.isdigit():\n raise OtherError\n raise MyError(\"I don't know you\")\n```\n\nIn this way, two error models will be built - `MyEndpointPOSTBadRequest` and `MyEndpointGETBadRequest`.\n\n![Build error model GET](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/build_error_model_get.png)\n![Build error model POST](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/build_error_model_post.png)\n\nThe model name is generated from the path, methods (if specified) of the route, and the status code error phrase.\nThis is done because for `fastapi<=0.95.0`, all model names (if they differ) must have unique names.\nIf the error model consists of only one model, the name of the original model will be used (to avoid a bug in the OpenAPI schema with an incorrect component definition).\n\nThe logic for naming the model can be changed by inheriting from `AnnotatedAPIRouter`\nand overriding the `response_model_name` method.\n\nAlso, to build an instance of the error model by default, it uses the error instance attributes (via `__dict__`),\nbut if this behavior needs to be changed, you can implement the `entity` method\n(as shown in the example in the `OtherError` class).\n\nIf `responses` are already defined in the route decorator or explicitly added to it and contain\na description for an error specified in `Annotated`, it will not overwrite the data passed to `responses`.\n\n```python\n@router.get(\"/my_endpoint_too\", responses={HTTPStatus.BAD_REQUEST: {\"model\": dict[str, str]}})\nasync def endpoint(how_iam: str) -> Annotated[ResponseModel, Errors[MyError, OtherError]]:\n if how_iam == \"me\":\n return ResponseModel(message=\"hello\")\n if how_iam.isdigit():\n raise OtherError\n raise MyError(\"I don't know you\")\n```\n\n![Keep responses data model](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/keep_build_error_model.png)\n\nTo automatically catch errors from endpoints and include the status code specified in them in the response, you need to add the error handler `ResponseError.handler` to the `FastAPI` application.\n\n```python\napp.add_exception_handler(ResponseError, ResponseError.handler)\n```\n\n![Handle MyError](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/handle_my_error.png)\n![Handle OtherError](https://github.com/feodor-ra/annotated-fastapi-router/blob/f4a0b2028ba5ceedca76de6af491f9ef6e312c3a/docs/handle_other_error.png)\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "FastAPI annotated api routed",
"version": "0.1.0",
"project_urls": {
"Homepage": "https://github.com/feodor-ra/annotated-fastapi-router",
"Repository": "https://github.com/feodor-ra/annotated-fastapi-router"
},
"split_keywords": [
"annotated",
" fastapi"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "18fefa366676b1e9b530aeb796797542258ec2579361a32806aaeb35f61dee1e",
"md5": "022b5dd62dd0635bbaa72d86de47d663",
"sha256": "8cca229a4ae1c5a64f3f06c2e1e24618916f74c045d0ab6f7c095be7cff6e2a1"
},
"downloads": -1,
"filename": "annotated_fastapi_router-0.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "022b5dd62dd0635bbaa72d86de47d663",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.11",
"size": 9672,
"upload_time": "2024-07-06T08:00:26",
"upload_time_iso_8601": "2024-07-06T08:00:26.701287Z",
"url": "https://files.pythonhosted.org/packages/18/fe/fa366676b1e9b530aeb796797542258ec2579361a32806aaeb35f61dee1e/annotated_fastapi_router-0.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "3db1eb50222fb44cf202dbf67dea2ecf8376ee513c9230b11d505c56153b032a",
"md5": "4aeff5a360910add66fab75bef2b717f",
"sha256": "cb2c37cbe7e9467ff1c2fb2984f387573e8b5a99efa356584187943db4e2b130"
},
"downloads": -1,
"filename": "annotated_fastapi_router-0.1.0.tar.gz",
"has_sig": false,
"md5_digest": "4aeff5a360910add66fab75bef2b717f",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.11",
"size": 10919,
"upload_time": "2024-07-06T08:00:28",
"upload_time_iso_8601": "2024-07-06T08:00:28.237110Z",
"url": "https://files.pythonhosted.org/packages/3d/b1/eb50222fb44cf202dbf67dea2ecf8376ee513c9230b11d505c56153b032a/annotated_fastapi_router-0.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-07-06 08:00:28",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "feodor-ra",
"github_project": "annotated-fastapi-router",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "annotated_fastapi_router"
}