This question already has answers here:
FastApi 422 Unprocessable Entity, on authentication, how to fix?
(2 answers)
Closed last month.
I'm getting this error while trying to accept a pedantic model. After debugging for quite some time I believe the problem is with accepting CodeCreate
Pydantic model
class BaseCode(BaseModel):
index: Optional[int] = Field(None)
email: EmailStr
gen_time: datetime
expire_time: datetime
class CodeCreate(BaseCode):
code: int
used_time: Optional[datetime] = Field(None)
class Config:
orm_mode = True
class Message(BaseModel):
message: str
Code ORM
class Code(Base):
__tablename__ = 'code'
index = Column(Integer, primary_key=True, autoincrement=True)
code = Column(Integer)
email = Column(String, ForeignKey('user.email'))
gen_time = Column(DateTime)
expire_time = Column(DateTime)
used_time = Column(DateTime, nullable=True)
Handler
#app.post('/verify-code', response_model=schemas.Message, responses={404: {"model": schemas.Message}, 406: {"model": schemas.Message}})
async def verify_code(code: schemas.CodeCreate, response: Response, device_name: str = Body(..., embed=True), db=Depends(get_db)):
result = crud.verify_and_insert_code(db=db, code=code)
if result == 'matched':
response.status_code = status.HTTP_202_ACCEPTED
return crud.start_new_session(db=db, session=schemas.Session(session_id='1234', start_time=datetime.now(), email=code.email, device_name=device_name))
elif result == 'not-matched':
response.status_code = status.HTTP_200_OK
elif result == 'expire':
response.status_code = status.HTTP_406_NOT_ACCEPTABLE
elif result == 'invalid':
response.status_code = status.HTTP_404_NOT_FOUND
return schemas.Message(message="Item not found")
Body of request from the client
{
"code": {
"index": 0,
"email": "user#example.com",
"gen_time": "2022-01-24T16:38:12.612Z",
"expire_time": "2022-01-24T16:38:12.612Z",
"code": 0,
"used_time": "2022-01-24T16:38:12.612Z"
},
"device_name": "string"
}
Response body for 422
{
"detail": [
{
"loc": [
"body",
"code",
"email"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"body",
"code",
"gen_time"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"body",
"code",
"expire_time"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"body",
"code",
"code"
],
"msg": "field required",
"type": "value_error.missing"
}
]
}
Temporary Solution
Removing this ORM confirmation code from Pydantic model solves the issue. I believe there might be a clash between CodeCreate pydantic model and Code ORM model, but I don't know how to resolve it.
class Config:
orm_mode = True
The 422 Unprocessable Entity error because of ContentType is incorrect. The FastAPI/Pydantic need ContentType = application/json to parse request body.
Are you sure your POST request has ContentType header is application/json?
If not add it!
According to MDN
here,
a 422 Unprocessable Entity means that the information of the request could not be processed.
In this case, the most likely problem is that the data of the POST request that is sent does not match with the Pydantic model.
Make sure the data that is sent is in the correct format.
Just as stated above by Brian Law, your request body is in the form of Code, which is not a pydantic model, but a database one.
When you send the POST request, the body should match up with CodeCreate, not Code.
Found the solution after debugging for quite a while.
The ORM configured pedantic model can't be used to receive requests from the client.
In my case, I had to create another class that extends the CodeCreate class add ORM configuration to that class and use CodeCreate for body from the client.
class BaseCode(BaseModel):
index: Optional[int] = Field(None)
email: EmailStr
gen_time: datetime
expire_time: datetime
class CodeCreate(BaseCode):
code: int
used_time: Optional[datetime] = Field(None)
class Code(CodeCreate):
class Config:
orm_mode = True
Post request
#app.post('/verify-code')
async def verify_code(code: schemas.CodeCreate):
return 'success'
Related
I am trying to learn FastAPI and Pydantic to put a list of object into MongoDB, but I got an error saying 422 Unprocessable Entity. I understand the error message indicated the server understands the JSON format, but is unable to handle it. I tried to wrap it with another model, but it looks like it doesn't work.
Let's say I have a list of object as:
[
{
"date": "2022-12-13",
"symbol": "nsht",
"price": "45.12"
},
{
"date": "2022-12-13",
"symbol": "asdf",
"price": "45.14442"
}
]
And I want to add it to the database by following object model as:
class EODCoinModel(BaseModel):
id: PyObjectId = Field(default_factory=PyObjectId, alias="_id")
date: str = Field(...)
symbol: str = Field(...)
price: float = Field(...)
class Config:
allow_population_by_field_name = True
arbitrary_types_allowed = True
json_encoders = {ObjectId: str}
schema_extra = {
"example": {
"date": "2022-12-13",
"symbol": "AAPL",
"price": "45.12"
}
}
class ItemList(BaseModel):
data: List[EODCoinModel]
And PUT method as:
#app.post("/", response_description="Add new coin", response_model=ItemList)
async def create_coin(coin: ItemList = Body(...)):
coin = jsonable_encoder(coin)
print(coin)
new_coin = await db["coin"].insert_one(coin)
created_coin = await db["coin"].find_one({"_id": new_coin.inserted_id})
return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_coin)
the Post request expects a body like this:
{"data" : [
"date": "2022-12-13",
"symbol": "nsht",
"price": "45.12"]
}
So then I ended up with not using Pydantic model validation. Just take the whole list and do inset_many.
#app.post("/", response_description="Add new coin")
async def create_coin(coin: List):
coin = jsonable_encoder(coin)
print(coin)
new_ coin = await db["coin"].insert_many(coin)
return JSONResponse(status_code=status.HTTP_201_CREATED)
In the FastAPI framework, the pydantic error messages are showing like below.
{"detail": [
{
"loc": [
"body",
"location",
"name"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"body",
"location",
"name12"
],
"msg": "extra fields not permitted",
"type": "value_error.extra"
}
]
}
I want to send a simple message: {"field-name":"error message"}.
In Pydantic document they mentioned like, create a model instance in the try: except blocks and construct the error message in the except block. But in fast API, model instance created by fastapi itself, for example, if I write an URL like below
#router.post("/", response_model=DataModelOut)
async def create_location(location: schemas.LocationIn, user: str = Depends(get_current_user) ):
return model.save(location,user)
Here the location instance created by fastapi itself is the problem.
Is there any way to construct the error message?
Actually this error messages coming from fastapi.exceptions, You can achieve that by overriding the custom exceptions,
Imagine i have simple app like this:
from fastapi import Request, status
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel
#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,
"your_additional_errors": {"Will be": "Inside", "This":" Error message"}}),
)
class Item(BaseModel):
title: str
size: int
#app.post("/items/")
async def create_item(item: Item):
return item
If i send values invalid values to my Request body
{
"title": 22,
"size": "hehe"
}
Now the error will be more customized:
{
"detail": [
{
"loc": [
"body",
"size"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
],
"body": {
"title": 22,
"size": "hehe"
},
"your_additional_errors": {
"Will be": "Inside the",
"Error": "Message"
}
}
You can change the content of exception, everything is up to you.
I am writing a middle ware for it.
async def genrange(s):
import json
s = json.loads(s)
yield json.dumps({"message":{k.get("loc")[-1]:k.get("msg") for k in s['detail']},
"id":None})
#app.middleware("http")
async def add_process_time_header(request: Request, call_next):
response = await call_next(request)
status_code = response.status_code
if status_code >=300:
async for i in response.body_iterator:
data = genrange(i)
response.body_iterator = data
return response
I'm not sure you'll like my answer better than the other answers. You can create a custom pydantic, with diffrent error messages. Kinda overkill, but could solve you a specific problem. In this example, I am changing the error message, when I insert an unpermitted HttpUrlSceme.
class UrlSchemePermittedError(errors.UrlError):
code = 'url.scheme'
msg_template = 'URL scheme not cool'
def __init__(self, allowed_schemes: Set[str]):
super().__init__(allowed_schemes=allowed_schemes)
class AnyHttpUrlDirrentMessage(AnyUrl):
allowed_schemes = {'http', 'https'}
#classmethod
def validate_parts(cls, parts: Dict[str, str]) -> Dict[str, str]:
"""
A method used to validate parts of an URL.
Could be overridden to set default values for parts if missing
"""
scheme = parts['scheme']
if scheme is None:
raise errors.UrlSchemeError()
if cls.allowed_schemes and scheme.lower() not in cls.allowed_schemes:
raise UrlSchemePermittedError(cls.allowed_schemes)
port = parts['port']
if port is not None and int(port) > 65_535:
raise errors.UrlPortError()
user = parts['user']
if cls.user_required and user is None:
raise errors.UrlUserInfoError()
return parts
I would like to map object from another API and send in GET response. I'm going to change only id of received object. Let's assume I get data from another API in such format:
{
"id": "31242",
"name": "sth1",
"price": "44",
"data": "2017-06-07",
}
In my database I have table object1 with values:
{
"id": "123",
"name": "sth1",
},
{
"id": "124",
"name": "sth2",
},
{
"id": "125",
"name": "sth3",
}
Field name is unique both in data from API and in data from database. I receive an object named sth1. So now I would like to find it in my database and get his id, replace with id from API and send GET response. In this case my response would look in this way:
{
"id": "123",
"name": "sth1",
"price": "44",
"data": "2017-06-07",
}
At this moment this is my URL - url(r'^data/(?P<name>\w+)$', views.DataList),
but I would like to have such URL - localhost:8000/data?name=sth
Myview.py:
#api_view(['GET'])
def DataList(request, name=None):
if request.method == 'GET':
quote = getDataFromAPI().get(name)
return Response(quote)
serializers.py:
class Object1Serializer(serializers.ModelSerializer):
class Meta:
model = Object1
depth = 1
fields = '__all__'
models.py:
class Object1(models.Model):
name = models.CharField(max_length=200)
I have done it in this way:
#api_view(['GET'])
def DataList(request):
t = request.GET.get("t","")
quote = getDataFromAPI().get(t)
id = Object1.objects.get(t=t)
quote["id"] = id
return Response(quote)
But I get error:
TypeError: Object of type 'Object1' is not JSON serializable
I suppose, your view should look somewhat like this,
#api_view(['GET'])
def DataList(request):
t = request.GET.get("t","")
quote = getDataFromAPI().get(t)
id = Object1.objects.get(t=t).id #put the id of the object in the variable.
#not the object itself.
quote["id"] = id
return Response(quote)
If you want to change the url from
url(r'^data/(?P<name>\w+)$', views.DataList) to localhost:8000/data?name=sth you'd need to change your api endpoint from
#api_view(['GET'])
def DataList(request, name=None):
to
#api_view(['GET'])
def DataList(request):
name = request.GET.get("name","")
and then take the id of object from your database by querying
id = Object1.objects.get(name=name)
and then updating id in response to be sent
quote["id"] = id
I'm newbie in Python and building API in Django using rest framework and using mysql for database.
I'm using filter query to get user info object but its returning array.
In Login API my code is:
is_valid_user = users.objects.filter(email=req_email, password=req_password)
serializer = usersSerializer(is_valid_user,many=True)
if is_valid_user:
response = {
'message': 'success',
'code': 1,
'data':serializer.data
else:
response = {
'message': 'User not found',
'code': 0,
}
return Response(response)
My usersSerializer Class :
class usersSerializer(serializers.ModelSerializer):
class Meta:
model = users
fields = ('uid', 'first_name', 'last_name', 'email', 'gender')
For this code response is :
{
"message": "success",
"code": 1,
"data": [
{
"uid": 6,
"first_name": "anuj",
"last_name": "sharma",
"email": "anujs1991#gmail.com",
"gender": "0"
}
]
}
But for this I don't want array of data .
Expected result should be :
{
"message": "success",
"code": 1,
"data": {
"uid": 6,
"first_name": "anuj",
"last_name": "sharma",
"email": "anujs1991#gmail.com",
"gender": "0"
}
}
Kindly help me for this.
The comments under your questions should point you to another solution. Here I'd like to give an explanation, why you're getting an array, and not an object.
In this line:
isValidUser = users.objects.filter(email=req_email, password=req_password)
you use the filter method, which may return more than 1 result (or none). You'll always get an array (a list in python), regardless of the number of results. The filter() method returns a new QuerySet.
If you want to retrieve a single result explicitly, and you have a unique field in your model class, then you should use the method get(), which doesn't return a QuerySet, but an object.
So if, let's say the field email is set to be unique, you could do this:
isValidUser = users.objects.get(email=req_email)
That will return an object, if there is an entry that can be matched.
Also, it is a good practice to follow the naming conventions for Python and name the variables with snake case:
is_valid_user
instead of
isValidUser
I am trying to connect my Django application to the mailchimps API with help from the Django Rest Framework, if I want to create a batch operation, I need to send the following call:
{
"operations": [
{
"method": "PUT",
"path": "lists/abc123/members/817f1571000f0b843c2b8a6982813db2",
"body": "{\"email_address\":\"hall#hallandoates.com\", \"status_if_new\":\"subscribed\"}"
},...
]
}
As you can see, the body object should be a json string. To create these calls, I created a model operation:
models.py
class Operation(object):
def __init__(self, method, list, contact):
email_clean = contact.email_address.lower().encode('utf-8')
subscriber_hash = hashlib.md5(email_clean).hexdigest()
serializer = ContactSerializer(contact)
body = serializer.data
self.method = method
self.path = "lists/%s/members/%s" % (list, subscriber_hash)
self.body = body
And the following serializer:
serializer.py
class OperationSerializer(serializers.Serializer):
method = serializers.CharField()
path = serializers.CharField()
body = serializers.CharField(allow_blank=True, required=False)
When I use my serializer to generate JSON, and parse the data with JSONRenderer(), the following call is returned:
{
"operations": [
{
"method": "PUT",
"path": "lists/abc123/members/817f1571000f0b843c2b8a6982813db2",
"body": "{\'email_address\':\'hall#hallandoates.com\', \'status_if_new\':\'subscribed\'}"
},...
]
}
This call breaks because of the single quotes, can anyone help me a hand in solving this?