Parsed body middleware for chocs package.
Parsed body middleware helps to convert json/yaml request payloads into dataclass. Parsed body middleware is build on
the top of chili
package. It supports complex dataclass initialisation and extraction
and does not pollute your codebase as it is solely depends on built-in python dataclasses package.
With pip,
pip install chocs-middleware.parsed_body
or through poetry
poetry add chocs-middleware.parsed_body
Middleware can work in two ways:
- strict mode
- auto hydration
In strict
mode middleware will only rely on defined initializer defined in your dataclasses. If arguments in request
payload are not exactly matching singnature of your initializer method it will fail.
In auto hydration
mode middleware will ignore initializers defined in your dataclasses, but __post_init__
is still
called after data is hydrated into class. Auto hydration may fail in scenarios where property is defined as non-optional
and is missing in the request payload.
from chocs_middleware.parsed_body import ParsedBodyMiddleware
from chocs import Application, HttpRequest, HttpResponse
from chili import asdict
from dataclasses import dataclass
import json
# You can define whether to use strict mode or not for all defined routes.
app = Application(ParsedBodyMiddleware(strict=False))
@dataclass
class Pet:
id: str
name: str
@app.post("/pets", parsed_body=Pet, strict=True) # you can also override default strict mode inside the route
def create_pet(request: HttpRequest) -> HttpResponse:
# You can try to catch exceptions while hydration happens
try:
pet: Pet = request.parsed_body
except Exception:
pet = None
assert isinstance(pet, Pet)
return HttpResponse(json.dumps(asdict(pet)))
In the above example we can see that request.parsed_body
is no longer carrying chocs.JsonHttpMessage
instead it was transformed into dataclass hinted inside the route definition (Pet
).
Strict mode is using initializer defined in a dataclass. Which means the request data is simply unpacked and passed to your dataclass, so you have to manually transform nested data to dataclasses in order to conform your dataclass interface, for example:
from chocs_middleware.parsed_body import ParsedBodyMiddleware
from chocs import Application, HttpRequest, HttpResponse
from dataclasses import dataclass
from typing import List
app = Application(ParsedBodyMiddleware())
@dataclass
class Tag:
name: str
id: str
@dataclass
class Pet:
id: str
name: str
age: int
tags: List[Tag]
def __post_init__(self): # post init might be used to reformat your data
self.age = int(self.age)
tmp_tags = self.tags
self.tags = []
for tag in tmp_tags:
self.tags.append(Tag(**tag))
@app.post("/pets", parsed_body=Pet)
def create_pet(request: HttpRequest) -> HttpResponse:
pet: Pet = request.parsed_body
assert isinstance(pet.tags[0], Tag)
assert isinstance(pet, Pet)
return HttpResponse(pet.name)
In non-strict mode instantiating and hydrating your dataclasses happens automatically. Complex and deeply nested
structures are supported as long as used types are supported by chili
hydration mechanism.
List of supported types can be found in chili's supported data types
Note: post_init method is also called as a part of hydration process.