I am a newbie to fastAPI. While I tried to write an API to get the uploaded image I got this error:
INFO: 127.0.0.1:50702 — «POST /faces/identify HTTP/1.1» 422 Unprocessable Entity
my codes here. It very thankful if someone can help
router = APIRouter()
# Identify faces on the image
@router.post('/faces/identify')
async def web_recognize(file_upload: UploadFile = File(...)):
face_recognition_service = faceRecognition.FaceRecognition_service(faces_dict)
file = face_recognition_service.extract_image(file_upload)
if file and face_recognition_service.is_picture(file.filename):
return face_recognition_service.detect_faces_in_image(file)
else:
raise ErrorResponse(code=codes.code_files_invalid, message=messages.message_files_invalid, `status=status.HTTP_400_BAD_REQUEST)`
A response having status code 422 will have a response body that specifies the error message, please have a look at that.
Also please mention from where are you making the request.
Below example code should work
import requests
url = "http://localhost/faces/identify"
files = {'file_upload': open('test.jpg', 'rb')}
requests.post(url, files=files)
Basically, you will have to make a POST request having content-type: multipart/form-data
Dear here is my response
{ "detail": [ { "loc": [ "body", "image" ], "msg": "field required", "type": "value_error.missing" } ] }
and here is my curl in postman
curl --location --request POST 'http://localhost:8080/faces/identify' --form 'file=@"1.jpg"'
where the image is in working directory. I just put it there to test my api
Try curl --location --request POST 'http://localhost:8080/faces/identify' --form 'file_upload=@"1.jpg"'
Hello, i’m having the same issue when passing an image with request. Have you been able to fix this?
Mmmm, i need to do it with requests due to how my app works.
let me see the code when you get the image?
I managed to make it work with requests, i followed this issue here:
#579
Had to use starlette’s requests and responses, here is my code if anyone runs into this issue
— API
@router.post('/detect_vehicle_bytes',response_class=Response) async def detect_vehicle(data: Request): data_b = await data.body() result = vehicle_detector.detect(data_b) return JSONResponse(result)`
— Client
img_bytes = cv2.imencode('.jpg',image_input)[1].tobytes() data = BytesIO(img_bytes) response=requests.post(self.endpoint,data=data) result = response.json()
Thanks!
what is data_b, is it image type? or just json and what you do in vehicale_detector is parsing this json?
I have a file called main.py
as follows:
from typing import Optional from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() fake_db = { "Foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"}, "Bar": {"id": "bar", "title": "Bar", "description": "The bartenders"}, } class Item(BaseModel): id: str title: str description: Optional[str] = None @app.post("/items/", response_model=Item) async def create_item(item: Item, key: str): fake_db[key] = item return item
Now, if I run the code for the test, saved in the file test_main.py
from fastapi.testclient import TestClient from main import app client = TestClient(app) def test_create_item(): response = client.post( "/items/", {"id": "baz", "title": "A test title", "description": "A test description"}, "Baz" ) return response.json() print(test_create_item())
I don’t get the desired result, that is
{"id": "baz", "title": "A test title", "description": "A test description"}
What is the mistake?
Advertisement
Answer
Let’s start by explaining what you are doing wrong.
FastAPI’s TestClient
is just a re-export of Starlette’s TestClient
which is a subclass of requests.Session. The requests library’s post
method has the following signature:
def post(self, url, data=None, json=None, **kwargs): r"""Sends a POST request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. :param data: (optional) Dictionary, list of tuples, bytes, or file-like object to send in the body of the :class:`Request`. :param json: (optional) json to send in the body of the :class:`Request`. :param **kwargs: Optional arguments that ``request`` takes. :rtype: requests.Response """
Your code
response = client.post( "/items/", {"id": "baz", "title": "A test title", "description": "A test description"}, "Baz", )
is doing multiple things wrong:
- It is passing in arguments to both the
data
and thejson
parameter, which is wrong because you can’t have 2 different request bodies. You either pass indata
orjson
, but not both. Thedata
is typically used for form-encoded inputs from HTML forms, whilejson
is for raw JSON objects. See the requests docs on “More complicated POST requests”. - The requests library will simply drop the
json
argument because:
Note, the
json
parameter is ignored if eitherdata
orfiles
is passed. - It is passing-in the plain string
"Baz"
to thejson
parameter, which is not a valid JSON object. - The
data
parameter expects form-encoded data.
The full error returned by FastAPI in the response is:
def test_create_item(): response = client.post( "/items/", {"id": "baz", "title": "A test title", "description": "A test description"}, "Baz" ) print(response.status_code) print(response.reason) return response.json() # 422 Unprocessable Entity # {'detail': [{'loc': ['query', 'key'], # 'msg': 'field required', # 'type': 'value_error.missing'}, # {'loc': ['body'], # 'msg': 'value is not a valid dict', # 'type': 'type_error.dict'}]}
The 1st error says key
is missing from the query, meaning the route parameter key
value "Baz"
was not in the request body and FastAPI tried to look for it from the query parameters (see the FastAPI docs on Request body + path + query parameters).
The 2nd error is from point #4 I listed above about data
not being properly form-encoded (that error does go away when you wrap the dict value in json.dumps
, but that’s not important nor is it part of the solution).
You said in a comment that you were trying to do the same thing as in the FastAPI Testing Tutorial. The difference of that tutorial from your code is that was POSTing all the attributes of the Item
object in 1 body, and that it was using the json=
parameter of .post
.
Now on the solutions!
Solution #1: Have a separate class for POSTing the item attributes with a key
Here, you’ll need 2 classes, one with a key
attribute that you use for the POST request body (let’s call it NewItem
), and your current one Item
for the internal DB and for the response model. Your route function will then have just 1 parameter (new_item
) and you can just get the key
from that object.
main.py
class Item(BaseModel): id: str title: str description: Optional[str] = None class NewItem(Item): key: str @app.post("/items/", response_model=Item) async def create_item(new_item: NewItem): # See Pydantic https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeldict # Also, Pydantic by default will ignore the extra attribute `key` when creating `Item` item = Item(**new_item.dict()) print(item) fake_db[new_item.key] = item return item
For the test .post
code, use json=
to pass all the fields in 1 dictionary.
test_main.py
def test_create_item(): response = client.post( "/items/", json={ "key": "Baz", "id": "baz", "title": "A test title", "description": "A test description", }, ) print(response.status_code, response.reason) return response.json()
Output
id='baz' title='A test title' description='A test description' 200 OK {'description': 'A test description', 'id': 'baz', 'title': 'A test title'}
Solution #2: Have 2 body parts, 1 for the item attributes and 1 for the key
You can structure the POSTed body like this instead:
{ "item": { "id": "baz", "title": "A test title", "description": "A test description", }, "key": "Baz", },
where you have the Item
attributes in a nested dict and then have a simple key
-value pair in the same level as item
. FastAPI can handle this, see the docs on Singular values in body, which fits your example quite nicely:
For example, extending the previous model, you could decide that you want to have another key
importance
in the same body, besides theitem
anduser
.If you declare it as is, because it is a singular value, FastAPI will assume that it is a
query
parameter.But you can instruct FastAPI to treat it as another body
key
usingBody
Note the parts I emphasized, about telling FastAPI to look for key
in the same body. It is important here that the parameter names item
and key
match the ones in the request body.
main.py
from fastapi import Body, FastAPI class Item(BaseModel): id: str title: str description: Optional[str] = None @app.post("/items/", response_model=Item) async def create_item(item: Item, key: str = Body(...)): print(item) fake_db[key] = item return item
Again, for making the .post
request, use json=
to pass the entire dictionary.
test_main.py
def test_create_item(): response = client.post( "/items/", json={ "item": { "id": "baz", "title": "A test title", "description": "A test description", }, "key": "Baz", }, ) print(response.status_code, response.reason) return response.json()
Output
id='baz' title='A test title' description='A test description' 200 OK {'description': 'A test description', 'id': 'baz', 'title': 'A test title'}
8 People found this is helpful
Issue
I am trying to upload an image but FastAPI is coming back with an error I can’t figure out.
If I leave out the «file: UploadFile = File(...)
» from the function definition, it works correctly. But when I add the file
to the function definition, then it throws the error.
Here is the complete code.
@router.post('/', response_model=schemas.PostItem, status_code=status.HTTP_201_CREATED)
def create(request: schemas.Item, file: UploadFile = File(...), db: Session = Depends(get_db)):
new_item = models.Item(
name=request.name,
price=request.price,
user_id=1,
)
print(file.filename)
db.add(new_item)
db.commit()
db.refresh(new_item)
return new_item
The Item
Pydantic model is just
class Item(BaseModel):
name: str
price: float
The error is:
Code 422 Error: Unprocessable Entity
{
"detail": [
{
"loc": [
"body",
"request",
"name"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"body",
"request",
"price"
],
"msg": "field required",
"type": "value_error.missing"
}
]
}
Solution
The problem is that your route is expecting 2 types of request body:
-
request: schemas.Item
- This is expecting POSTing an
application/json
body - See the Request Body section of the FastAPI docs: «Read the body of the request as JSON«
- This is expecting POSTing an
-
file: UploadFile = File(...)
- This is expecting POSTing a
multipart/form-data
- See the Request Files section of the FastAPI docs: «FastAPI will make sure to read that data from the right place instead of JSON. …when the form includes files, it is encoded as
multipart/form-data
«
- This is expecting POSTing a
That will not work as that breaks not just FastAPI, but general HTTP protocols. FastAPI mentions this in a warning when using File
:
You can declare multiple
File
andForm
parameters in a path operation, but you can’t also declareBody
fields that you expect to receive as JSON, as the request will have the body encoded usingmultipart/form-data
instead ofapplication/json
.This is not a limitation of FastAPI, it’s part of the HTTP protocol.
The common solutions, as discussed in Posting a File and Associated Data to a RESTful WebService preferably as JSON, is to either:
- Break the API into 2 POST requests: 1 for the file, 1 for the metadata
- Send it all in 1
multipart/form-data
Fortunately, FastAPI supports solution 2, combining both your Item
model and uploading a file into 1 multipart/form-data
. See the section on Request Forms and Files:
Use
File
andForm
together when you need to receive data and files in the same request.
Here’s your modified route (I removed db
as that’s irrelevant to the problem):
class Item(BaseModel):
name: str
price: float
class PostItem(BaseModel):
name: str
@router.post('/', response_model=PostItem, status_code=status.HTTP_201_CREATED)
def create(
# Here we expect parameters for each field of the model
name: str = Form(...),
price: float = Form(...),
# Here we expect an uploaded file
file: UploadFile = File(...),
):
new_item = Item(name=name, price=price)
print(new_item)
print(file.filename)
return new_item
The Swagger docs present it as 1 form
…and you should be able now to send both Item
params and the file in one request.
If you don’t like splitting your Item
model into separate parameters (it would indeed be annoying for models with many fields), see this Q&A on fastapi form data with pydantic model.
Here’s the modified code where Item
is changed to ItemForm
to support accepting its fields as Form
values instead of JSON:
class ItemForm(BaseModel):
name: str
price: float
@classmethod
def as_form(cls, name: str = Form(...), price: float = Form(...)) -> 'ItemForm':
return cls(name=name, price=price)
class PostItem(BaseModel):
name: str
@router.post('/', response_model=PostItem, status_code=status.HTTP_201_CREATED)
def create(
item: ItemForm = Depends(ItemForm.as_form),
file: UploadFile = File(...),
):
new_item = Item(name=item.name, price=item.price)
print(new_item)
print(file.filename)
return new_item
The Swagger UI should still be the same (all the Item
fields and the file upload all in one form).
For this:
If I leave out the «
file: UploadFile = File(...)
» from the function definition, it works correctly
It’s not important to focus on this, but it worked because removing File
turned the expected request body back to an application/json
type, so the JSON body would work.
Finally, as a side note, I strongly suggest NOT using request
as a parameter name for your route. Aside from being vague (everything is a request), it could conflict with FastAPI’s request: Request
parameter when using the Request object directly.
Answered By – Gino Mempin
This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0
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
¶
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]}
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 return
ing 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
:
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]}
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.
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:
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]}
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()
:
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}
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..."}
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
.
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.
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}
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:
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
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.
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.
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
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 HTTPException
s in the same code, Starlette’s exceptions is renamed to StarletteHTTPException
:
from starlette.exceptions import HTTPException as StarletteHTTPException
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 print
ing 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.
Содержание
- ReactJS Form Submission FastAPI 422 Unprocessable Entity #3747
- Comments
- First Check
- Commit to Help
- Example Code
- Backend
- Pydantic Models
- Frontend
- Request
- Response
- Description
- Operating System
- Operating System Details
- FastAPI Version
- Python Version
- Additional Context
- Footer
- “422 Unprocessable Entity” error when making POST request with both attributes and key using FastAPI
- Advertisement
- Answer
- Solution #1: Have a separate class for POSTing the item attributes with a key
- Solution #2: Have 2 body parts, 1 for the item attributes and 1 for the key
- How to log more info on 422 unprocessable entity #3361
- Comments
- Handling Errors¶
- Use HTTPException ¶
- Import HTTPException ¶
- Raise an HTTPException in your code¶
- The resulting response¶
- Add custom headers¶
- Install custom exception handlers¶
- Override the default exception handlers¶
- Override request validation exceptions¶
- RequestValidationError vs ValidationError ¶
- Override the HTTPException error handler¶
- Use the RequestValidationError body¶
- FastAPI’s HTTPException vs Starlette’s HTTPException ¶
- Re-use FastAPI‘s exception handlers¶
ReactJS Form Submission FastAPI 422 Unprocessable Entity #3747
First Check
- I added a very descriptive title to this issue.
- I used the GitHub search to find a similar issue and didn’t find it.
- I searched the FastAPI documentation, with the integrated search.
- I already searched in Google «How to X in FastAPI» and didn’t find any information.
- I already read and followed all the tutorial in the docs and didn’t find an answer.
- I already checked if it is not related to FastAPI but to Pydantic.
- I already checked if it is not related to FastAPI but to Swagger UI.
- I already checked if it is not related to FastAPI but to ReDoc.
Commit to Help
- I commit to help with one of those options 👆
Example Code
Backend
Pydantic Models
Frontend
Request
Response
Description
- I’m trying to create a simple signup form Backend FastAPI and serving Frontend in React. I’ve created the Pydantic Models for the body request but still getting this 422 Unprocessable Entity error. How can I fix this?
- It is working fine in Insomnia client when I hit the POST request it gives me the correct data with 200 OK response.
Operating System
Operating System Details
FastAPI Version
Python Version
Additional Context
The text was updated successfully, but these errors were encountered:
@0xlearner I think that the issue is where you define your form_data parameter. You should remove the = Depends() part. The function declaration should be:
@0xlearner I think that the issue is where you define your form_data parameter. You should remove the = Depends() part. The function declaration should be:
@STeveShary I tried without Depends() but still getting 422 Unprocessable Entity
What error is being returned in the response?
What error is being returned in the response?
@0xlearner looks like the issue is in your frontend then, not with your fastapi backend
@0xlearner The problem is that form_data fields appear as string, if you are setting the content type to form data or x-www-form-urlencoded, whereas the models are expecting this to be part of the body. Either you should set the content type to application/json or follow this. A workaround on this is as follows where you send your form data as is and expect the entire json in one field. Later
© 2023 GitHub, Inc.
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Источник
“422 Unprocessable Entity” error when making POST request with both attributes and key using FastAPI
I have a file called main.py as follows:
Now, if I run the code for the test, saved in the file test_main.py
I don’t get the desired result, that is
What is the mistake?
Advertisement
Answer
Let’s start by explaining what you are doing wrong.
FastAPI’s TestClient is just a re-export of Starlette’s TestClient which is a subclass of requests.Session. The requests library’s post method has the following signature:
is doing multiple things wrong:
- It is passing in arguments to both the data and the json parameter, which is wrong because you can’t have 2 different request bodies. You either pass in data or json , but not both. The data is typically used for form-encoded inputs from HTML forms, while json is for raw JSON objects. See the requests docs on “More complicated POST requests”.
- The requests library will simply drop the json argument because:
Note, the json parameter is ignored if either data or files is passed.
The full error returned by FastAPI in the response is:
The 1st error says key is missing from the query, meaning the route parameter key value «Baz» was not in the request body and FastAPI tried to look for it from the query parameters (see the FastAPI docs on Request body + path + query parameters).
The 2nd error is from point #4 I listed above about data not being properly form-encoded (that error does go away when you wrap the dict value in json.dumps , but that’s not important nor is it part of the solution).
You said in a comment that you were trying to do the same thing as in the FastAPI Testing Tutorial. The difference of that tutorial from your code is that was POSTing all the attributes of the Item object in 1 body, and that it was using the json= parameter of .post .
Now on the solutions!
Solution #1: Have a separate class for POSTing the item attributes with a key
Here, you’ll need 2 classes, one with a key attribute that you use for the POST request body (let’s call it NewItem ), and your current one Item for the internal DB and for the response model. Your route function will then have just 1 parameter ( new_item ) and you can just get the key from that object.
main.py
For the test .post code, use json= to pass all the fields in 1 dictionary.
test_main.py
Output
Solution #2: Have 2 body parts, 1 for the item attributes and 1 for the key
You can structure the POSTed body like this instead:
where you have the Item attributes in a nested dict and then have a simple key -value pair in the same level as item . FastAPI can handle this, see the docs on Singular values in body, which fits your example quite nicely:
For example, extending the previous model, you could decide that you want to have another key importance in the same body, besides the item and user .
If you declare it as is, because it is a singular value, FastAPI will assume that it is a query parameter.
But you can instruct FastAPI to treat it as another body key using Body
Note the parts I emphasized, about telling FastAPI to look for key in the same body. It is important here that the parameter names item and key match the ones in the request body.
main.py
Again, for making the .post request, use json= to pass the entire dictionary.
Источник
How to log more info on 422 unprocessable entity #3361
I get this a lot in development and can usually pretty quickly figure out what is wrong with my json payload. However, I’d love a way to see what exactly the validation error is when I get this error. Is there a way I can log what the specific schema validation error is on the server side?
The text was updated successfully, but these errors were encountered:
Just the other day I had a pretty absurd issue about exactly that
My gui was doing a fetch request and thanks to the apple keyword crap, I set the content-type to applications/json (notice the applications instead of application )
The 422 error indicated that value wasn’t a dict
I spend too much time till I noticed the additional s
This would be avoided with better 422 error or a way to debug it
after you can log error and get response
after you can log error and get response
Thanks. Can you tell me what logger that is that you can pass request as the first argument?
The above examples are excerpts of handling-errors documentation.
For the sake of clarity to anyone ending up here after having the same issues as OP, here’s the full code @panla scissored in:
This will produce something along the lines of:
This error is not only bound to the header, but also if the data submitted does not match the BaseModel supplied to the API endpoint.
presume it should be 422 ?
What is the significance of 10422 ?
The above examples are excerpts of handling-errors documentation. For the sake of clarity to anyone ending up here after having the same issues as OP, here’s the full code @panla scissored in:
This will produce something along the lines of:
This error is not only bound to the header, but also if the data submitted does not match the BaseModel supplied to the API endpoint.
This should be the standard implementation IMHO. a beginner like me had to do quite a lot of digging simply because the error wasn’t expressive enough 😛
Источник
Handling Errors¶
The current page still doesn’t have a translation for this language.
But you can help translating it: Contributing.
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 return ing 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:
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:
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.
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:
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 .
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:
you will get a text version, with:
RequestValidationError vs ValidationError ¶
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:
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.
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:
You will receive a response telling you that the data is invalid containing the received body:
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 HTTPException s in the same code, Starlette’s exceptions is renamed to StarletteHTTPException :
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 :
In this example you are just print ing 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.
Источник
Отправка статусных кодов
Последнее обновление: 06.09.2022
Одной из расспространненых задач в веб-приложении является отправка статусных кодов, которые указывают на статус выполнения операции на сервере.
-
1xx: предназначены для информации. Ответ с таким кодом не может иметь содержимого
-
2xx: указывает на успешноее выполнение операции
-
3xx: предназначены для переадресации
-
4xx: предназначены для отправки информации об ошибок клиента
-
5xx: предназначены для информации об ошибках сервера
По умолчанию функции обработки отправляют статусный код 200, но при необходимости мы можем отправить любой статусный код. Для этого у методов get()
, post()
, put()
, delete()
, options()
, head()
, patch()
,
trace()
в классе FastAPI применяется параметр status_code
,
который принимает числовой код статуса HTTP. Например:
.
from fastapi import FastAPI app = FastAPI() @app.get("/notfound", status_code=404) def notfound(): return {"message": "Resource Not Found"}
В данном случае при обращении по пути «/notfound» клиенту отправляется статусный код ошибки 404, который говорит о том, что ресурс не найден.
Для упрощения в FastAPI есть модуль status, в котором определены константы для представления статусных кодов:
-
HTTP_100_CONTINUE
(код 100) -
HTTP_101_SWITCHING_PROTOCOLS
(код 101) -
HTTP_102_PROCESSING
(код 102) -
HTTP_103_EARLY_HINTS
(код 103) -
HTTP_200_OK
(код 200) -
HTTP_201_CREATED
(код 201) -
HTTP_202_ACCEPTED
(код 202) -
HTTP_203_NON_AUTHORITATIVE_INFORMATION
(код 203) -
HTTP_204_NO_CONTENT
(код 204) -
HTTP_205_RESET_CONTENT
(код 205) -
HTTP_206_PARTIAL_CONTENT
(код 206) -
HTTP_207_MULTI_STATUS
(код 207) -
HTTP_208_ALREADY_REPORTED
(код 208) -
HTTP_226_IM_USED
(код 226) -
HTTP_300_MULTIPLE_CHOICES
(код 300) -
HTTP_301_MOVED_PERMANENTLY
(код 301) -
HTTP_302_FOUND
(код 302) -
HTTP_303_SEE_OTHER
(код 303) -
HTTP_304_NOT_MODIFIED
(код 304) -
HTTP_305_USE_PROXY
(код 305) -
HTTP_306_RESERVED
(код 306) -
HTTP_307_TEMPORARY_REDIRECT
(код 307) -
HTTP_308_PERMANENT_REDIRECT
(код 308) -
HTTP_400_BAD_REQUEST
(код 400) -
HTTP_401_UNAUTHORIZED
(код 401) -
HTTP_402_PAYMENT_REQUIRED
(код 402) -
HTTP_403_FORBIDDEN
(код 403) -
HTTP_404_NOT_FOUND
(код 404) -
HTTP_405_METHOD_NOT_ALLOWED
(код 405) -
HTTP_406_NOT_ACCEPTABLE
(код 406) -
HTTP_407_PROXY_AUTHENTICATION_REQUIRED
(код 407) -
HTTP_408_REQUEST_TIMEOUT
(код 408) -
HTTP_409_CONFLICT
(код 409) -
HTTP_410_GONE
(код 410) -
HTTP_411_LENGTH_REQUIRED
(код 411) -
HTTP_412_PRECONDITION_FAILED
(код 412) -
HTTP_413_REQUEST_ENTITY_TOO_LARGE
(код 413) -
HTTP_414_REQUEST_URI_TOO_LONG
(код 414) -
HTTP_415_UNSUPPORTED_MEDIA_TYPE
(код 415) -
HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE
(код 416) -
HTTP_417_EXPECTATION_FAILED
(код 417) -
HTTP_418_IM_A_TEAPOT
(код 418) -
HTTP_421_MISDIRECTED_REQUEST
(код 421) -
HTTP_422_UNPROCESSABLE_ENTITY
(код 422) -
HTTP_423_LOCKED
(код 423) -
HTTP_424_FAILED_DEPENDENCY
(код 424) -
HTTP_425_TOO_EARLY
(код 425) -
HTTP_426_UPGRADE_REQUIRED
(код 426) -
HTTP_428_PRECONDITION_REQUIRED
(код 428) -
HTTP_429_TOO_MANY_REQUESTS
(код 429) -
HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE
(код 431) -
HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS
(код 451) -
HTTP_500_INTERNAL_SERVER_ERROR
(код 500) -
HTTP_501_NOT_IMPLEMENTED
(код 501) -
HTTP_502_BAD_GATEWAY
(код 502) -
HTTP_503_SERVICE_UNAVAILABLE
(код 503) -
HTTP_504_GATEWAY_TIMEOUT
(код 504) -
HTTP_505_
-
HTTP_VERSION_NOT_SUPPORTED
(код 505) -
HTTP_506_VARIANT_ALSO_NEGOTIATES
(код 506) -
HTTP_507_INSUFFICIENT_STORAGE
(код 507) -
HTTP_508_LOOP_DETECTED
(код 508) -
HTTP_510_NOT_EXTENDED
(код 510) -
HTTP_511_NETWORK_AUTHENTICATION_REQUIRED
(код 511)
Пример использования
from fastapi import FastAPI app = FastAPI() @app.get("/notfound", status_code=status.HTTP_404_NOT_FOUND) def notfound(): return {"message": "Resource Not Found"}
Определение статусного кода в ответе
В примере выше функция вне зависимости от данных запроса или каких-то других условий в любом случае возвращала статусный код 404. Однако чаще бывает необходимо возвращать статусный код в
зависимости от некоторых условий. В этом случае мы можем использовать параметр status_code конструктора класса Response или его наследников:
from fastapi import FastAPI from fastapi.responses import JSONResponse app = FastAPI() @app.get("/notfound") def notfound(): return JSONResponse(content={"message": "Resource Not Found"}, status_code=404)
Изменение статусного кода
Можно комбинировать оба подхода:
from fastapi import FastAPI, Response, Path app = FastAPI() @app.get("/users/{id}", status_code=200) def users(response: Response, id: int = Path()): if id < 1: response.status_code = 400 return {"message": "Incorrect Data"} return {"message": f"Id = {id}"}
В данном случае если параметр пути меньше 1, то условно считаем, что переданные некорректные данные, и отправляем в ответ статусный код 400 (Bad Request)
НазадСодержаниеВперед
Есть много ситуаций, когда вам нужно уведомить об ошибке клиента, использующего ваш 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
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 return
ing 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 HTTPException
s 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 print
ing 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