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
Related
I'm using BasePermission decorator as specified in documentation.
#strawberry.type
class Query:
#strawberry.field(permission_classes=[IsAuthenticated])
def user(self) -> User:
# get by token OFC
return User(user_id=1, email="vladimir#cw.tech", first_name = "Vladimir", last_name = "Kirilov")
In my impementation I use VerifyToken class as described in FastAPI auth0 documentation.
class IsAuthenticated(BasePermission):
message = "User is not authenticated"
def has_permission(self, source: Any, info: Info, **kwargs) -> bool:
print(source)
print(info)
token: str = Depends(token_auth_scheme)
print(token)
result = VerifyToken(token.credentials).verify()
if result.get("status"):
print(result)
return False
return True
So I'm trying to get and verify the BEARER from the request, but I'm not able to extract it to process it further and get the error, please advise.
{
"data": null,
"errors": [
{
"message": "'Depends' object has no attribute 'credentials'",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"user"
]
}
]
}
Figured it out, not the cleanest way, but here it is
class IsAuthenticated(BasePermission):
message = "User is not authenticated"
async def has_permission(self, source: Any, info: Info, **kwargs) -> bool:
request: Union[Request, WebSocket] = info.context["request"]
print(request.headers)
if "Authorization" in request.headers:
print(request.headers['Authorization'])
result = VerifyToken( request.headers['Authorization'][7:] ).verify()
if result.get("status") == "error":
print(result.get("msg"))
return False
if result.get("sub"):
print(result)
return True
return False
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'
I want to create a generic endpoint definition in Fast API Python that reads URL path parameter and then calls a specific method to do a derealisation.
But I always get
422 Unprocessable Entity
So I expect that it works like so:
/answer/aaa -> handle_generic_answer -> read_item_aaa, type body to ModelAAA
/answer/bbb -> handle_generic_answer -> read_item_bbb, type body to ModelBBB
etc.
Here's the generic endpoint code:
#app.post("/answer/{type}")
def handle_generic_answer(type: str, item):
# I also tried
# def handle_generic_answer(type: str, item: Any):
# or
# def handle_generic_answer(type: str, item: Optional):
switcher = {
'aaaa': read_item_aaa,
'bbb': read_item_bbb,
'nothing': unrecognised_answer
}
func = switcher.get(type, unrecognised_answer)
print('answer >> ' + type)
func(item)
then I have separate methods called based on a type value:
def read_item_aaa(item: ModelAAA):
update_aaa(item)
return {"type": "aaa", "result": "success"}
def read_item_bbb(item: ModelBBB):
update_bbb(item)
return {"type": "bbb", "result": "success"}
and a default -
def unrecognised_answer(type):
print("unrecognised_answer")
raise HTTPException(status_code=400, detail="answer type not found")
return {}
models are defined like this:
from pydantic import BaseModel, Field
class ModelAAA(BaseModel):
field1: str
field2: list = []
But whether I call
http://localhost:8000/answer/aaa
or http://localhost:8000/answer/some-other-url
I always get 422:
{
"detail": [
{
"loc": [
"query",
"item"
],
"msg": "field required",
"type": "value_error.missing"
}
]
}
You forgot to annotate body parameter item.
Without this item is treated as query str parameter. For example:
#app.post("/answer/{type}")
def handle_generic_answer(type: str, item: Union[ModelAAA, ModelBBB]):
I am developing a chatbot using lex.I created one intent called "latestdetails" and one slot "uniquename".I want to validate the slot "uniquename" such that it does not accept alphabets and ask the message "please enter again" when the input is wrong . Below is my code in python.
When testing the lambda function,I get the correct response but when lambda function is connected with lex via initialization and validation code hook,I get the following error at bot :"Amazon lex error-An error has occurred: Invalid Lambda Response: Received error response from Lambda: Unhandled".
I saw related questions in stackoverflow but I am not able to get the required solution.
import os
import time
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
def get_slots(event):
return event['currentIntent']['slots']
def elicit_slot(intent_name, slots, slot_to_elicit ,message):
response = {
"dialogAction":
{
"type":"ElicitSlot",
"intentName": intent_name,
"slots": slots,
"slotToElicit": slot_to_elicit,
"message": {
"content": message,
"contentType": "PlainText"
}
}
}
return response
def delegate(slots):
response = {
"dialogAction":
{
"type":"Delegate",
"slots": slots
}
}
return response
def close():
response = {
"dialogAction":
{
"fulfillmentState":"Fulfilled",
"type":"Close",
"message":
{
"contentType":"PlainText",
"content": "Hey your ticket has been raised"
}
}
}
return response
def dispatch(event):
intent_name=event['currentIntent']['name']
if intent_name == 'latestdetails':
source = event['invocationSource']
if source == 'DialogCodeHook':
slots= get_slots(event)
uniquename = event["currentIntent"]["slots"]["uniquename"]
if uniquename.isalpha():
return elicit_slot(intent_name,'uniquename','uniquename','please enteragain')
return delegate(slots)
return close('Fulfilled')
elif source == 'FulfillmentCodeHook':
return close()
def lambda_handler(event, context):
return dispatch(event)
I want to create an API by using django-rest-framework. So far I've managed to setup one endpoint of API and managed to fetch all items. A basic response (without the BaseResponse class described later) would look like this:
[
{
"uuid": "1db6a08d-ec63-4beb-8b41-9b042c53ab83",
"created_at": "2018-03-12T19:25:07.073620Z",
"updated_at": "2018-03-12T19:25:37.904350Z",
"deleted_at": null,
"random_name": "random name"
}
]
The result I would like to achieve would be something like this:
[
"success": true
"message": "Some exception message",
"data" :{
"uuid": "1db6a08d-ec63-4beb-8b41-9b042c53ab83",
"created_at": "2018-03-12T19:25:07.073620Z",
"updated_at": "2018-03-12T19:25:37.904350Z",
"deleted_at": null,
"random_name": "random name"
}
]
I managed to achieve this by creating a BaseReponse class and in view I simply return BaseResponse.to_dict() (a method that I have created inside of class).
class BaseResponse(object):
data = None
success = False
message = None
def __init__(self, data, exception):
self.data = data
self.message = str(exception) if exception is not None else None
self.success = exception is None
def to_dict(self):
return {
'success': self.success,
'message': self.message,
'data': self.data,
}
View:
class RandomModelList(APIView):
def get(self, request, format=None):
exception = None
models = None
try:
models = RandomModel.objects.all()
except Exception as e:
exception = e
serializer = RandomModelSerializer(models, many=True)
base_response = BaseResponse(data=serializer.data, exception=exception)
return Response(base_response.to_dict())
I want to mention that with the current code everything its working as expected but I have a huge double about the code (I just feel like I reinvented the wheel). Can someone tell me if this is the optimal solution for my problem and if not what should I change/use?
You can create a Custom Renderer instead. Something like
class CustomRenderer(JSONRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
resp = {
'data': data
}
return super(CustomRenderer, self).render(resp, accepted_media_type, renderer_context)
Then create a middleware like
class CustomResponseMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
response.data.update({'success': is_client_error(response.status_code) or is_server_error(response.status_code)})
return response