Customize bad request payload handling with dataclasses #339
-
I wish to receive array of objects Payload:
Response:
Payload:
Response:
Payload:
Response:
This one is bothering me as response is quite cryptic I do not want things like I want some friendly messages for front end developer so that front end people will not complain about not understanding the response and blaming backend people for the delay |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments
-
@techntools good question. I just verified the cryptic error is caused by poor handling in a function ( When it comes to validating in general and producing user friendly error messages, I need a bit of time to articulate a good answer. |
Beta Was this translation helpful? Give feedback.
-
@techntools I prepared an example for you, using from typing import Any, TypeVar
from marshmallow import Schema, ValidationError, fields
from blacksheep import Application
from blacksheep.messages import Request
from blacksheep.server.bindings import Binder, BoundValue
from blacksheep.server.responses import pretty_json
SchemaType = TypeVar("SchemaType", bound=Schema)
class InvalidBodyError(Exception):
"""
Kind of BadRequest exception that include error details as complex objects.
"""
def __init__(self, data: Any):
super().__init__("Invalid payload")
self.details = data
# Example Marshmallow schema, from the marshmallow documentation
class BandMemberSchema(Schema):
name = fields.String(required=True)
email = fields.Email()
# Example binding for a Marshmallow schema, to be used to obtain list of objects
class FromMultiSchema(BoundValue[SchemaType]):
"""
Custom bound value that can be used to describe a list of objects validated using a
Marshmallow schema.
"""
class MultiSchemaBinder(Binder):
"""
Binder that handles a FromMultiSchema, returning list of objects from a
Marshmallow schema.
"""
handle = FromMultiSchema
async def get_value(self, request: Request) -> Any:
data = await request.json()
try:
return self.expected_type(many=True).load(data)
except ValidationError as err:
raise InvalidBodyError(err.messages)
app = Application()
@app.exception_handler(InvalidBodyError)
async def invalid_body_handler(app, request, exc: InvalidBodyError):
return pretty_json(exc.details, 400)
@app.router.post("/")
def example(data: FromMultiSchema[BandMemberSchema]):
return pretty_json(data.value)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, port=44555, lifespan="on") In this case you get the same error messages produced by Marshmallow, when validating input values: curl -X POST http://127.0.0.1:44555 -H "Content-Type: application/json" -d '[{"id": 1, "name": "foo", "permissions": []}]'
{
"0": {
"permissions": [
"Unknown field."
],
"id": [
"Unknown field."
]
}
}
curl -X POST http://127.0.0.1:44555 -H "Content-Type: application/json" -d '[{"id": 1, "name": "foo", "email": "wrong-value"}]'
{
"0": {
"email": [
"Not a valid email address."
],
"id": [
"Unknown field."
]
}
} I am considering to add built-in support for Marshmallow in version 2 of the web framework (eventually in a dedicated library), but haven't had the time to do it, yet. |
Beta Was this translation helpful? Give feedback.
-
It great to know that framework already supports raw material for customization. Marshmallow support would be nice. It comes with apispec as well. I would ask to take a look at msgspec as well. It also comes with OpenAPI I think this example should be added in docs. I will add the example with msgspec. |
Beta Was this translation helpful? Give feedback.
@techntools I prepared an example for you, using
Marshmallow
. Implementing a generic solution to validate input and produce user friendly error messages is not in the scope of BlackSheep, I recommend using Marshmallow. This is currently possible defining a couple of custom binders, a custom exception, and a custom exception handler like in the example below.