Fastapi docs error

Есть много ситуаций, когда вам нужно уведомить об ошибке клиента, использующего ваш API.

Есть много ситуаций, когда вам нужно уведомить об ошибке клиента, использующего ваш API.

Этот клиент может быть браузером с внешним интерфейсом, чужим кодом, IoT-устройством и т. д.

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

  • У клиента недостаточно прав для этой операции.
  • У клиента нет доступа к этому ресурсу.
  • Элемент, к которому пытался получить доступ клиент, не существует.
  • etc.

В этих случаях вы обычно возвращаете код состояния HTTP в диапазоне 400 (от 400 до 499).

Это похоже на 200 кодов состояния HTTP (от 200 до 299). Эти коды состояния «200» означают, что запрос каким-то образом был «успешен».

Коды состояния в диапазоне 400 означают, что произошла ошибка клиента.

Помните все эти ошибки «404 Not Found» (и шутки)?

Use HTTPException

Чтобы вернуть клиенту HTTP-ответы с ошибками, вы используете HTTPException .

Import HTTPException

from fastapi import FastAPI, HTTPException

app = FastAPI()

items = {"foo": "The Foo Wrestlers"}


@app.get("/items/{item_id}")
async def read_item(item_id: str):
    if item_id not in items:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"item": items[item_id]}

Поднимите HTTPException в своем коде

HTTPException — это обычное исключение Python с дополнительными данными, относящимися к API.

Поскольку это исключение Python, вы его не return raise .

Это также означает, что если вы находитесь внутри служебной функции, которую вы вызываете внутри своей функции операции пути , и вы вызываете HTTPException изнутри этой служебной функции, она не будет запускать остальную часть кода в функции операции пути , он немедленно завершит этот запрос и отправит клиенту ошибку HTTP из исключения HTTPException .

Преимущество возбуждения исключения по сравнению с return значения будет более очевидным в разделе, посвященном зависимостям и безопасности.

В этом примере, когда клиент запрашивает элемент по несуществующему идентификатору, создайте исключение с кодом состояния 404 :

from fastapi import FastAPI, HTTPException

app = FastAPI()

items = {"foo": "The Foo Wrestlers"}


@app.get("/items/{item_id}")
async def read_item(item_id: str):
    if item_id not in items:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"item": items[item_id]}

Полученная реакция

Если клиент запрашивает http://example.com/items/foo ( item_id "foo" ), этот клиент получит код состояния HTTP 200 и ответ JSON:

{
  "item": "The Foo Wrestlers"
}

Но если клиент запрашивает http://example.com/items/bar (несуществующий item_id "bar" ), этот клиент получит код состояния HTTP 404 (ошибка «не найден») и ответ JSON. из:

{
  "detail": "Item not found"
}

Tip

При возникновении HTTPException вы можете передать любое значение, которое может быть преобразовано в JSON, в качестве detail параметра , а не только str .

Вы можете передать dict , list и т.д.

Они автоматически обрабатываются FastAPI и конвертируются в JSON.

В некоторых ситуациях полезно иметь возможность добавлять собственные заголовки к ошибке HTTP. Например, для некоторых видов безопасности.

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

Но если вам это нужно для расширенного сценария, вы можете добавить собственные заголовки:

from fastapi import FastAPI, HTTPException

app = FastAPI()

items = {"foo": "The Foo Wrestlers"}


@app.get("/items-header/{item_id}")
async def read_item_header(item_id: str):
    if item_id not in items:
        raise HTTPException(
            status_code=404,
            detail="Item not found",
            headers={"X-Error": "There goes my error"},
        )
    return {"item": items[item_id]}

Установите пользовательские обработчики исключений

Вы можете добавить собственные обработчики исключений теми же утилитами исключений от Starlette .

Допустим, у вас есть пользовательское исключение UnicornException , которое вы (или используемая вами библиотека) можете raise .

И вы хотите обрабатывать это исключение глобально с помощью FastAPI.

Вы можете добавить собственный обработчик исключений с помощью @app.exception_handler() :

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse


class UnicornException(Exception):
    def __init__(self, name: str):
        self.name = name


app = FastAPI()


@app.exception_handler(UnicornException)
async def unicorn_exception_handler(request: Request, exc: UnicornException):
    return JSONResponse(
        status_code=418,
        content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."},
    )


@app.get("/unicorns/{name}")
async def read_unicorn(name: str):
    if name == "yolo":
        raise UnicornException(name=name)
    return {"unicorn_name": name}

UnicornException вы запросите /unicorns/yolo , операция пути вызовет raise UnicornException .

Но это будет обрабатываться unicorn_exception_handler .

Итак, вы получите чистую ошибку с кодом состояния HTTP 418 и содержимым JSON:

{"message": "Oops! yolo did something. There goes a rainbow..."}

Technical Details

Вы также можете использовать from starlette.requests import Request и from starlette.responses import JSONResponse .

FastAPI предоставляет те же starlette.responses , что и fastapi.responses , только для удобства вас, разработчика. Но большинство доступных ответов исходит непосредственно от Старлетт. То же самое с Request .

Переопределить обработчики исключений по умолчанию

FastAPI имеет несколько обработчиков исключений по умолчанию.

Эти обработчики отвечают за возврат ответов JSON по умолчанию, когда вы HTTPException raise когда запрос содержит недопустимые данные.

Вы можете переопределить эти обработчики исключений своими собственными.

Переопределить исключения проверки запроса

Когда запрос содержит недопустимые данные, FastAPI внутренне вызывает RequestValidationError .

И он также включает в себя обработчик исключений по умолчанию.

Чтобы переопределить его, импортируйте RequestValidationError и используйте его с @app.exception_handler(RequestValidationError) для оформления обработчика исключений.

Обработчик исключений получит Request и исключение.

from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
from fastapi.responses import PlainTextResponse
from starlette.exceptions import HTTPException as StarletteHTTPException

app = FastAPI()


@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request, exc):
    return PlainTextResponse(str(exc.detail), status_code=exc.status_code)


@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
    return PlainTextResponse(str(exc), status_code=400)


@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id == 3:
        raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
    return {"item_id": item_id}

Теперь, если вы перейдете к /items/foo , вместо того, чтобы получить ошибку JSON по умолчанию с:

{
    "detail": [
        {
            "loc": [
                "path",
                "item_id"
            ],
            "msg": "value is not a valid integer",
            "type": "type_error.integer"
        }
    ]
}

вы получите текстовую версию с:

1 validation error
path -> item_id
  value is not a valid integer (type=type_error.integer)

RequestValidationError против ValidationError

Warning

Это технические детали, которые вы можете пропустить, если это не важно для вас сейчас.

RequestValidationError — это подкласс ValidationError Pydantic .

FastAPI использует его, поэтому, если вы используете модель Pydantic в response_model и в ваших данных есть ошибка, вы увидите ошибку в своем журнале.

Но клиент/пользователь этого не увидит. Вместо этого клиент получит «Внутреннюю ошибку сервера» с кодом состояния HTTP 500 .

Так и должно быть, потому что если у вас есть Pydantic ValidationError в вашем ответе или где-либо в вашем коде (не в запросе клиента ), это на самом деле ошибка в вашем коде.

И пока вы ее исправляете, ваши клиенты/пользователи не должны иметь доступа к внутренней информации об ошибке, так как это может привести к уязвимости системы безопасности.

Переопределить обработчик ошибок HTTPException

Точно так же вы можете переопределить обработчик HTTPException .

Например, вы можете захотеть вернуть простой текстовый ответ вместо JSON для этих ошибок:

from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
from fastapi.responses import PlainTextResponse
from starlette.exceptions import HTTPException as StarletteHTTPException

app = FastAPI()


@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request, exc):
    return PlainTextResponse(str(exc.detail), status_code=exc.status_code)


@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
    return PlainTextResponse(str(exc), status_code=400)


@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id == 3:
        raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
    return {"item_id": item_id}

Technical Details

Вы также можете использовать from starlette.responses import PlainTextResponse .

FastAPI предоставляет те же starlette.responses , что и fastapi.responses , только для удобства вас, разработчика. Но большинство доступных ответов исходит непосредственно от Старлетт.

Используйте тело RequestValidationError

Ошибка RequestValidationError содержит полученное body

Вы можете использовать его при разработке своего приложения для регистрации тела и его отладки, возврата пользователю и т. д.

from fastapi import FastAPI, Request, status
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel

app = FastAPI()


@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    return JSONResponse(
        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}),
    )


class Item(BaseModel):
    title: str
    size: int


@app.post("/items/")
async def create_item(item: Item):
    return item

Теперь попробуйте отправить недопустимый элемент, например:

{
  "title": "towel",
  "size": "XL"
}

Вы получите ответ о том, что данные, содержащие полученное тело, недействительны:

{
  "detail": [
    {
      "loc": [
        "body",
        "size"
      ],
      "msg": "value is not a valid integer",
      "type": "type_error.integer"
    }
  ],
  "body": {
    "title": "towel",
    "size": "XL"
  }
}

HTTPException FastAPI против HTTPException Starlette

FastAPI имеет собственное HTTPException .

И класс HTTPException FastAPI HTTPException наследуется от класса ошибок HTTPException Starlette .

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

Это необходимо/используется внутри OAuth 2.0 и некоторых утилит безопасности.

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

Но когда вы регистрируете обработчик исключений, вы должны зарегистрировать его для HTTPException Starlette .

Таким образом, если какая-либо часть внутреннего кода Starlette, расширение или подключаемый модуль Starlette вызывает исключение HTTPException Starlette , ваш обработчик сможет его перехватить и обработать.

В этом примере, чтобы иметь возможность иметь оба HTTPException в одном и том же коде, исключения Starlette переименовываются в StarletteHTTPException :

from starlette.exceptions import HTTPException as StarletteHTTPException

Повторно использовать обработчики исключений FastAPI

Если вы хотите использовать исключение вместе с теми же обработчиками исключений по умолчанию из FastAPI , вы можете импортировать и повторно использовать обработчики исключений по умолчанию из fastapi.exception_handlers :

from fastapi import FastAPI, HTTPException
from fastapi.exception_handlers import (
    http_exception_handler,
    request_validation_exception_handler,
)
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException

app = FastAPI()


@app.exception_handler(StarletteHTTPException)
async def custom_http_exception_handler(request, exc):
    print(f"OMG! An HTTP error!: {repr(exc)}")
    return await http_exception_handler(request, exc)


@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
    print(f"OMG! The client sent invalid data!: {exc}")
    return await request_validation_exception_handler(request, exc)


@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id == 3:
        raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
    return {"item_id": item_id}

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


FastAPI

0.86

  • Extra Models

  • First Steps

  • Header Parameters

  • Tutorial — User Guide — Intro

The documentation suggests raising an HTTPException with client errors, which is great.
But how can I show those specific errors in the documentation following HTTPException’s model? Meaning a dict with the «detail» key.

The following does not work because HTTPException is not a Pydantic model.

@app.get(
    '/test', 
    responses={
        409 : {
            'model' : HTTPException, 
            'description': 'This endpoint always raises an error'
        }
    }
)
def raises_error():
    raise HTTPException(409, detail='Error raised')

VisioN's user avatar

VisioN

142k32 gold badges278 silver badges278 bronze badges

asked Oct 23, 2020 at 13:45

Mojimi's user avatar

1

Yes it is not a valid Pydantic type however since you can create your own models, it is easy to create a Model for it.

from fastapi import FastAPI
from fastapi.exceptions import HTTPException
from pydantic import BaseModel


class Dummy(BaseModel):
    name: str


class HTTPError(BaseModel):
    detail: str

    class Config:
        schema_extra = {
            "example": {"detail": "HTTPException raised."},
        }


app = FastAPI()


@app.get(
    "/test",
    responses={
        200: {"model": Dummy},
        409: {
            "model": HTTPError,
            "description": "This endpoint always raises an error",
        },
    },
)
def raises_error():
    raise HTTPException(409, detail="Error raised")

I believe this is what you are expecting

enter image description here

answered Oct 23, 2020 at 19:09

Yagiz Degirmenci's user avatar

Yagiz DegirmenciYagiz Degirmenci

14.2k4 gold badges53 silver badges80 bronze badges

2

There are many situations in where you need to notify an error to a client that is using your API.

This client could be a browser with a frontend, a code from someone else, an IoT device, etc.

You could need to tell the client that:

  • The client doesn’t have enough privileges for that operation.
  • The client doesn’t have access to that resource.
  • The item the client was trying to access doesn’t exist.
  • etc.

In these cases, you would normally return an HTTP status code in the range of 400 (from 400 to 499).

This is similar to the 200 HTTP status codes (from 200 to 299). Those «200» status codes mean that somehow there was a «success» in the request.

The status codes in the 400 range mean that there was an error from the client.

Remember all those «404 Not Found» errors (and jokes)?

Use HTTPException

To return HTTP responses with errors to the client you use HTTPException.

Import HTTPException

Raise an HTTPException in your code

HTTPException is a normal Python exception with additional data relevant for APIs.

Because it’s a Python exception, you don’t return it, you raise it.

This also means that if you are inside a utility function that you are calling inside of your path operation function, and you raise the HTTPException from inside of that utility function, it won’t run the rest of the code in the path operation function, it will terminate that request right away and send the HTTP error from the HTTPException to the client.

The benefit of raising an exception over returning a value will be more evident in the section about Dependencies and Security.

In this example, when the client requests an item by an ID that doesn’t exist, raise an exception with a status code of 404

The resulting response

If the client requests http://example.com/items/foo (an item_id "foo"), that client will receive an HTTP status code of 200, and a JSON response of:

{
  "item": "The Foo Wrestlers"
}

But if the client requests http://example.com/items/bar (a non-existent item_id "bar"), that client will receive an HTTP status code of 404 (the «not found» error), and a JSON response of:

{
  "detail": "Item not found"
}

!!! tip When raising an HTTPException, you can pass any value that can be converted to JSON as the parameter detail, not only str.

You could pass a `dict`, a `list`, etc.

They are handled automatically by **FastAPI** and converted to JSON.

example:

from fastapi import FastAPI, HTTPException

app = FastAPI()

items = {"foo": "The Foo Wrestlers"}


@app.get("/items/{item_id}")
async def read_item(item_id: str):
    if item_id not in items:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"item": items[item_id]}

Add custom headers

There are some situations in where it’s useful to be able to add custom headers to the HTTP error. For example, for some types of security.

You probably won’t need to use it directly in your code.

But in case you needed it for an advanced scenario, you can add custom headers

Install custom exception handlers

You can add custom exception handlers with the same exception utilities from Starlette.

Let’s say you have a custom exception UnicornException that you (or a library you use) might raise.

And you want to handle this exception globally with FastAPI.

You could add a custom exception handler with @app.exception_handler()

Here, if you request /unicorns/yolo, the path operation will raise a UnicornException.

But it will be handled by the unicorn_exception_handler.

So, you will receive a clean error, with an HTTP status code of 418 and a JSON content of:

{"message": "Oops! yolo did something. There goes a rainbow..."}

!!! note «Technical Details» You could also use from starlette.requests import Request and from starlette.responses import JSONResponse.

**FastAPI** provides the same `starlette.responses` as `fastapi.responses` just as a convenience for you, the developer. But most of the available responses come directly from Starlette. The same with `Request`.

example:

from fastapi import FastAPI, HTTPException

app = FastAPI()

items = {"foo": "The Foo Wrestlers"}


@app.get("/items/{item_id}")
async def read_item(item_id: str):
    if item_id not in items:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"item": items[item_id]}

Override the default exception handlers

FastAPI has some default exception handlers.

These handlers are in charge of returning the default JSON responses when you raise an HTTPException and when the request has invalid data.

You can override these exception handlers with your own.

Override request validation exceptions

When a request contains invalid data, FastAPI internally raises a RequestValidationError.

And it also includes a default exception handler for it.

To override it, import the RequestValidationError and use it with @app.exception_handler(RequestValidationError) to decorate the exception handler.

The exception handler will receive a Request and the exception.

Now, if you go to /items/foo, instead of getting the default JSON error with:

{
    "detail": [
        {
            "loc": [
                "path",
                "item_id"
            ],
            "msg": "value is not a valid integer",
            "type": "type_error.integer"
        }
    ]
}

you will get a text version, with:

1 validation error
path -> item_id
  value is not a valid integer (type=type_error.integer)

RequestValidationError vs ValidationError

!!! warning These are technical details that you might skip if it’s not important for you now.

RequestValidationError is a sub-class of Pydantic’s ValidationError.

FastAPI uses it so that, if you use a Pydantic model in response_model, and your data has an error, you will see the error in your log.

But the client/user will not see it. Instead, the client will receive an «Internal Server Error» with a HTTP status code 500.

It should be this way because if you have a Pydantic ValidationError in your response or anywhere in your code (not in the client’s request), it’s actually a bug in your code.

And while you fix it, your clients/users shouldn’t have access to internal information about the error, as that could expose a security vulnerability.

Override the HTTPException error handler

The same way, you can override the HTTPException handler.

For example, you could want to return a plain text response instead of JSON for these errors:

!!! note «Technical Details» You could also use from starlette.responses import PlainTextResponse.

**FastAPI** provides the same `starlette.responses` as `fastapi.responses` just as a convenience for you, the developer. But most of the available responses come directly from Starlette.

example:

from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
from fastapi.responses import PlainTextResponse
from starlette.exceptions import HTTPException as StarletteHTTPException

app = FastAPI()


@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request, exc):
    return PlainTextResponse(str(exc.detail), status_code=exc.status_code)


@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
    return PlainTextResponse(str(exc), status_code=400)


@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id == 3:
        raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
    return {"item_id": item_id}

Use the RequestValidationError body

The RequestValidationError contains the body it received with invalid data.

You could use it while developing your app to log the body and debug it, return it to the user, etc.

Now try sending an invalid item like:

{
  "title": "towel",
  "size": "XL"
}

You will receive a response telling you that the data is invalid containing the received body:

{
  "detail": [
    {
      "loc": [
        "body",
        "size"
      ],
      "msg": "value is not a valid integer",
      "type": "type_error.integer"
    }
  ],
  "body": {
    "title": "towel",
    "size": "XL"
  }
}

FastAPI’s HTTPException vs Starlette’s HTTPException

FastAPI has its own HTTPException.

And FastAPI‘s HTTPException error class inherits from Starlette’s HTTPException error class.

The only difference, is that FastAPI‘s HTTPException allows you to add headers to be included in the response.

This is needed/used internally for OAuth 2.0 and some security utilities.

So, you can keep raising FastAPI‘s HTTPException as normally in your code.

But when you register an exception handler, you should register it for Starlette’s HTTPException.

This way, if any part of Starlette’s internal code, or a Starlette extension or plug-in, raises a Starlette HTTPException, your handler will be able to catch and handle it.

In this example, to be able to have both HTTPExceptions in the same code, Starlette’s exceptions is renamed to StarletteHTTPException:

from starlette.exceptions import HTTPException as StarletteHTTPException

example:

from fastapi import FastAPI, Request, status
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel

app = FastAPI()


@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    return JSONResponse(
        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}),
    )


class Item(BaseModel):
    title: str
    size: int


@app.post("/items/")
async def create_item(item: Item):
    return item

Re-use FastAPI‘s exception handlers

If you want to use the exception along with the same default exception handlers from FastAPI, You can import and re-use the default exception handlers from fastapi.exception_handlers:

from fastapi import FastAPI, HTTPException
from fastapi.exception_handlers import (
    http_exception_handler,
    request_validation_exception_handler,
)
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException

app = FastAPI()


@app.exception_handler(StarletteHTTPException)
async def custom_http_exception_handler(request, exc):
    print(f"OMG! An HTTP error!: {repr(exc)}")
    return await http_exception_handler(request, exc)


@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
    print(f"OMG! The client sent invalid data!: {exc}")
    return await request_validation_exception_handler(request, exc)


@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id == 3:
        raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
    return {"item_id": item_id}

In this example you are just printing the error with a very expressive message, but you get the idea. You can use the exception and then just re-use the default exception handlers.

fastapi extra data types

Up to now, you have been using common data types, like: int float str bool But you can also use more complex data types. And you will still have the same features as seen up to now: Great editor support. Data conversion from incoming requests.

fastapi json encoder

There are some cases where you might need to convert a data type (like a Pydantic model) to something compatible with JSON (like a dict, list, etc). For example, if you need to store it in a database. For that, FastAPI provides a jsonable_encoder() func

fastapi debugging

You can connect the debugger in your editor, for example with Visual Studio Code or PyCharm. Call uvicorn In your FastAPI application, import and run uvicorn directly: import uvicorn from fastapi import FastAPI

fastapi cookie parameters

You can define Cookie parameters the same way you define Query and Path parameters. Import Cookie example: from typing import Union from fastapi import Cookie, FastAPI

fastapi request body

When you need to send data from a client (let’s say, a browser) to your API, you send it as a request body. A request body is data sent by the client to your API. A response body is the data your API sends to the client

fastapi body nested models

With FastAPI, you can define, validate, document, and use arbitrarily deeply nested models (thanks to Pydantic). List fields List fields example from typing import Union from fastapi import FastAPI from pydantic import BaseModel app = FastAPI()

fastapi body multiple parameters

Now that we have seen how to use Path and Query, let’s see more advanced uses of request body declarations. Mix Path, Query and body parameters example1 from typing import Union from fastapi import FastAPI, Path from pydantic import BaseModel app = Fa

fastapi body fields

Body — Fields The same way you can declare additional validation and metadata in path operation function parameters with Query, Path and Body, you can declare validation and metadata inside of Pydantic models using Pydantic’s Field. Import Field First

fastapi background tasks

You can define background tasks to be run after returning a response. This is useful for operations that need to happen after a request, but that the client doesn’t really have to be waiting for the operation to complete before receiving the response

Google adverts for django-cms

install pip install djangocms-ads Provides plugins to add ad slots to the <head> tag and then a plugin to create the advert element in the page content. Plugin templates can be extended using the DJANGOCMS_ADS_TEMPLATES setting

Permalink

Cannot retrieve contributors at this time


This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters

Show hidden characters

from typing import Any, Dict, Optional, Sequence, Type
from pydantic import BaseModel, ValidationError, create_model
from pydantic.error_wrappers import ErrorList
from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.exceptions import WebSocketException as WebSocketException # noqa: F401
class HTTPException(StarletteHTTPException):
def __init__(
self,
status_code: int,
detail: Any = None,
headers: Optional[Dict[str, Any]] = None,
) -> None:
super().__init__(status_code=status_code, detail=detail, headers=headers)
RequestErrorModel: Type[BaseModel] = create_model(«Request»)
WebSocketErrorModel: Type[BaseModel] = create_model(«WebSocket»)
class FastAPIError(RuntimeError):
«»»
A generic, FastAPI-specific error.
«»»
class RequestValidationError(ValidationError):
def __init__(self, errors: Sequence[ErrorList], *, body: Any = None) -> None:
self.body = body
super().__init__(errors, RequestErrorModel)
class WebSocketRequestValidationError(ValidationError):
def __init__(self, errors: Sequence[ErrorList]) -> None:
super().__init__(errors, WebSocketErrorModel)

This post is part of the FastAPI series.

In this post, I am going to introduce FastAPI: A Python-based framework to create Rest APIs. I will briefly introduce you to some basic features of this framework and then we will create a simple set of APIs for a contact management system. Knowledge of Python is very necessary to use this framework.

Before we discuss the FastAPI framework, let’s talk a bit about REST itself.

From Wikipedia:

Representational state transfer (REST) is a software architectural style that defines a set of constraints to be used for creating Web services. Web services that conform to the REST architectural style, called RESTful Web services, provide interoperability between computer systems on the Internet. RESTful Web services allow the requesting systems to access and manipulate textual representations of Web resources by using a uniform and predefined set of stateless operations. Other kinds of Web services, such as SOAP Web services, expose their own arbitrary sets of operations.[1]

What is the FastAPI framework?

From the official website:

FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints.

Yes, it is fast, very fast and it is due to out of the box support of the async feature of Python 3.6+ this is why it is recommended to use the latest versions of Python.

FastAPI was created by Sebastián Ramírez who was not happy with the existing frameworks like Flask and DRF. More you can learn about it here. Some of the key features mentioned on their website are:

  • Fast: Very high performance, on par with NodeJS and Go (thanks to Starlette and Pydantic). One of the fastest Python frameworks available.
  • Fast to code: Increase the speed to develop features by about 200% to 300%. *
  • Fewer bugs: Reduce about 40% of human (developer) induced errors. *
  • Intuitive: Great editor support. Completion everywhere. Less time debugging.
  • Easy: Designed to be easy to use and learn. Less time reading docs.
  • Short: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs.
  • Robust: Get production-ready code. With automatic interactive documentation.
  • Standards-based: Based on (and fully compatible with) the open standards for APIs(Open API)

The creator of the FastAPI believed in standing on the shoulder of giants and used existing tools and frameworks like Starlette and Pydantic

Installation and Setup

I am going to use Pipenv for setting up the development environment for our APIs. Pipenv makes it easier to isolate your development environment irrespective of what things are installed on your machine. It also lets you pick a different Python version than whatever is installed on your machine. It uses Pipfile to manage all your project-related dependencies. I am not gonna cover Pipenv here in detail so will only be using the commands that are necessary for the project.

You can install Pipenv via PyPy by running pip install pipenv

pipenv install --python 3.9

Once installed you can activate the virtual environment by running the command pipenv shell

FastAPI Setup

You may also run pipenv install --three where three means Python 3.x.

Once installed you can activate the virtual environment by running the command pipenv shell

First, we will install FastAPI by running the following command: pipenv install fastapi

Note it’s pipenv, NOT pip. When you are into the shell you will be using pipenv. Underlying it is using pip but all entries are being stored in Pipfile. The Pipfile will be looking like below:

OK, so we have set up our dev environment. It’s time to start writing our first API endpoint. I am going to create a file called main.py. This will be the entry point of our app.

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def home():
    return {"Hello": "FastAPI"}

If you have worked on Flask then you will be finding it pretty much similar. After importing the required library you created an app instance and created your first route with a decorator.

Now you wonder how to run it. Well, FastAPI comes with the uvicorn which is an ASGI server. You will simply be running the command uvicorn main:app --reload

You provide the file name(main.py in this case) and the class object(app in this case) and it will initiate the server. I am using the --reload flag so that it reloads itself after every change.

Visit http://localhost:8000/ and you will see the message in JSON format {"Hello":"FastAPI"}

Cool, No?

FastAPI provides an API document engine too. If you visit http://localhost:8000/docs which is using the Swagger UI interface.

Or if you need something fancy then visit http://localhost:8080/redoc

FastAPI also provides an OpenAPI version of API endpoints, like this http://127.0.0.1:8000/openapi.json

Path and Parameters

Let’s move forward. We add another API endpoint. Say, it’s about fetching contact details by its id.

@app.get("/contact/{contact_id}")
def contact_details(contact_id: int):
    return {'contact_id': contact_id}

So here is a method, contact_details that accepts only an int parameter and just returns it as it in a dict format. Now when I access it via cURL it looks like below:

Now, what if I pass a string instead of an integer? You will see the below

Did you see it? it returned an error message that you sent the wrong data type. You do not need to write a validator for such petty things. That’s the beauty while working in FaastAPI.

Query String

What if you pass extra data in the form of query strings? For instance your API end-point returns loads of records hence you need pagination. Well, no issue, you can fetch that info as well.

First, we will import the Optional type:

from typing import Optional

@app.get("/contact/{contact_id}")
def contact_details(contact_id: int, page: Optional[int] = 1):
    if page:
        return {'contact_id': contact_id, 'page': page}
    return {'contact_id': contact_id}

Here, I passed another parameter, page and set its type Optional[int] here. Optional, well as the name suggests it’s an optional parameter. Setting the type int is making sure that it only accepts the integer value otherwise, it’d be throwing an error like it did above.

Access the URL http://127.0.0.1:8000/contact/1?page=5 and you will see something like below:

Cool, No?

So far we just manually returned the dict. It is not cool. It is quite common that you input a single value and return a YUUGE JSON structure. FastAPI provides an elegant way to deal with it, using Pydantic models.

Pydantic models actually help in data validation, what does it mean? It means it makes sure that the data which is being passed is valid, if not otherwise it returns an error. We are already using Python’s type hinting and these data models make that the sanitized data is being passed thru. Let’s write a bit of code. I am again going to extend the contact API for this purpose.

from typing import Optional

from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()


class Contact(BaseModel):
    contact_id:int
    first_name:str
    last_name:str
    user_name:str
    password:str

@app.post('/contact')
async def create_contact(contact: Contact):
    return contact

I imported the BaseModel class from pydantic. After that, I created a model class that extended the BaseModel class and set 3 fields in it. Do notice I am also setting the type of it. Once it’s done I created a POST API endpoint and passed an Contact parameter to it. I am also using async here which converts the simple python function into a coroutine. FastAPI supports it out of the box.

Go to http://localhost:8080/docs and you will see something like below:

When you run the CURL command you would see something like the below:

As expected it just returned the Contact object in JSON format.

As you notice, it just dumps the entire model in JSON format, including password. It does not make sense even if your password is not in plain-text format. So what to do? Response Model is the answer.

What is Response Model

As the name suggests, a Response Model is a model that is used while sending a response against a request. Basically, when you just used a model it just returns all fields. By using a response model you can control what kind of data should be returned back to the user. Let’s change the code a bit.

class Contact(BaseModel):
    contact_id:int
    first_name:str
    last_name:str
    user_name:str
    password:str


class ContactOut(BaseModel):
    contact_id:int
    first_name:str
    last_name:str
    user_name:str


@app.get("/")
def home():
    return {"Hello": "FastAPI"}


@app.post('/contact', response_model=ContactOut)
async def create_contact(contact: Contact):
    return contact

I have added another class, ContactOut which is almost a copy of the Contact class. The only thing which is different here is the absence of the password field. In order to use it, we are going to assign it in the response_model parameter of the post decorator. That’s it. Now when I run hit the same URL it will not return the password field.

As you can see, no password field is visible here. If you notice the /docs URL you will see it visible over there as well.

Using a different Response Model is feasible if you are willing to use it in multiple methods but if you just want to omit the confidential information from a single method then you can also use response_model_exclude parameter in the decorator.

@app.post('/contact', response_model=Contact, response_model_exclude={"password"})
async def create_contact(contact: Contact):
    return contact

The output will be similar. You are setting the response_model and response_model_exclude here. The result is the same. You can also attach metadata with your API endpoint.

@app.post('/contact', response_model=Contact, response_model_exclude={"password"},description="Create a single contact")
async def create_contact(contact: Contact):
    return contact

We added the description of this endpoint which you can see in the doc.

FastAPI documentation awesomeness does not end here, it also lets you set the example JSON structure of the model.

class Contact(BaseModel):
    contact_id:int
    first_name:str
    last_name:str
    user_name:str
    password:str

    class Config:
        schema_extra = {
            "example": {
                "contact_id": 1,
                "first_name": "Jhon",
                "last_name": "Doe",
                "user_name": "jhon_123",
            }
        }

And when you do that, it is rendered as:

Error handling in FastAPI

It is always possible that you do not get the required info. FastAPI provides HTTPException class to deal with such situations.

@app.get("/contact/{id}", response_model=Contact, response_model_exclude={"password"},description="Fetch a single contact")
async def contact_details(id: int):
    if id < 1:
        raise HTTPException(status_code=404, detail="The required contact details not found")
    contact = Contact(contact_id=id, first_name='Adnan', last_name='Siddiqi', user_name='adnan1', password='adn34')
    return contact

A simple endpoint. It returns contact details based on the id. If the id is less than 1 it returns a 404 error message with details.

Before I leave, let me tell you how you can send custom headers.

from fastapi import FastAPI, HTTPException, Response

@app.get("/contact/{id}", response_model=Contact, response_model_exclude={"password"},
         description="Fetch a single contact")
async def contact_details(id: int, response: Response):
    response.headers["X-LOL"] = "1"
    if id < 1:
        raise HTTPException(status_code=404, detail="The required contact details not found")
    contact = Contact(contact_id=id, first_name='Adnan', last_name='Siddiqi', user_name='adnan1', password='adn34')
    return contact

After importing the Response class I passed request parameter of type Request and set the header X-LOL

After running the curl command you will see something like the below:

You can find x-lol among headers. LOL!

Conclusion

So in this post, you learned how you can start using FastAPI for building high-performance APIs. We already have a minimal framework called Flask but FastAPI’s asynchronous support makes it much attractive for modern production systems especially machine learning models that are accessed via REST APIs. I have only scratched the surface of it. You can learn further about it on the official FastAPI website.

Hopefully, in the next post, I will be discussing some advanced topics like integrating with DB, Authentication, and other things.

Понравилась статья? Поделить с друзьями:

Читайте также:

  • Fast api response error
  • Fasm include win32a inc error file not found
  • Fasm error illegal instruction
  • Farming simulator 22 ошибка драйвера видеокарты
  • Farming simulator 22 ошибка msvcp110

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии