Serialization¶
@serialize and @deserialize convert a request body or a response to and from a model class. ahttp_client ships codecs for dataclasses (no extra dependency), pydantic, and marshmallow out of the box.
1from typing import Annotated
2
3import aiohttp
4from pydantic import BaseModel, ConfigDict
5from pydantic.alias_generators import to_camel
6
7from ahttp_client import AsyncSession, Query, get
8from ahttp_client.serializer import deserialize
9
10
11class Station(BaseModel):
12 id: str
13 name: str
14 model_config = ConfigDict(alias_generator=to_camel)
15
16
17class MetroAPI(AsyncSession):
18 def __init__(self) -> None:
19 super().__init__("https://api.yhs.kr", aiohttp.ClientSession)
20
21 @get("/metro/station", directly_response=True)
22 @deserialize(by_alias=True)
23 async def search_stations(
24 self, name: Annotated[str, Query]
25 ) -> list[Station]:
26 raise AssertionError("direct deserialization skips the method body")
@deserialize looks at the method’s return annotation (list[Station]), picks the deserializer registered for Station, then reads it off the response through Response.model.
Pair it with directly_response=True and the method body never runs at all — the decorated method just hands back the deserialized model.
@serialize mirrors this in the other direction: instead of the return annotation, it picks its serializer from whichever parameter is annotated as the complete request Body (see HTTP Component).
Selecting a Model Type¶
When the model type cannot be inferred from an annotation, pass it explicitly, such as @serialize(CreateUser) or @deserialize(Station).
Any other keyword arguments get forwarded to the serializer or deserializer once the model type is known — this is the “late-bind” mechanism at work: late_bind() holds onto the options until set_model()/set_model() can resolve them against the actual annotated type.
Built-in Codecs¶
dataclasses¶
No extra dependency is required. Any @dataclasses.dataclass-decorated class is supported automatically, including nested dataclasses, lists, and common field types such as datetime, UUID, Decimal, and Enum.
from dataclasses import dataclass
from typing import Annotated
from ahttp_client import AsyncSession, Body, Response, post
from ahttp_client.serializer import serialize, deserialize
@dataclass
class CreateUser:
name: str
age: int
class UserAPI(AsyncSession):
@post("/users")
@serialize()
@deserialize()
async def create_user(
self, response: Response, user: Annotated[CreateUser, Body]
) -> CreateUser:
return response.model
pydantic¶
Note
pydantic package is required.
pip install pydantic
Any pydantic.BaseModel subclass is supported. The serializer/deserializer keyword arguments (by_alias, exclude_none, strict, …) map directly onto pydantic.BaseModel.model_dump/TypeAdapter.validate_python; see PydanticSerializeOptions and PydanticDeserializeOptions for the full list.
marshmallow¶
Note
marshmallow package is required.
pip install marshmallow
A marshmallow.Schema instance always has to be passed explicitly as the schema keyword argument here — unlike the dataclasses and pydantic codecs, a schema isn’t itself the data’s Python type, so there’s nothing for ahttp_client to infer it from. For the same reason, pass the schema class explicitly as model too, rather than counting on inference from the Body/return annotation: a plain dict doesn’t tell ahttp_client which schema to use, the way a dataclass or pydantic model would.
from typing import Annotated
from marshmallow import Schema, fields
from ahttp_client import AsyncSession, Body, Response, post
from ahttp_client.serializer import serialize, deserialize
class UserSchema(Schema):
name = fields.Str()
age = fields.Int()
class UserAPI(AsyncSession):
@post("/users")
@serialize(model=UserSchema, schema=UserSchema())
@deserialize(model=UserSchema, schema=UserSchema())
async def create_user(
self, response: Response, user: Annotated[dict, Body]
) -> dict:
return response.model
Reference¶
- class BaseCodec¶
Base class for codecs that resolve model-specific implementations.
- class BaseSerializer¶
Bases:
BaseCodec,ABC,Generic[ModelT]Base codec that converts models into transport-safe values.
- classmethod from_model(model: type[ModelT], **kwargs: Any) BaseSerializer[ModelT] | None¶
Return the serializer registered for a model annotation, if any.
- serialize(model: ModelT | Sequence[ModelT]) Any | list[Any]¶
Serialize one model or a sequence of models.
- classmethod set_model(model: type[ModelT], origin_cls: BaseCodec, **kwargs: Any) BaseSerializer[ModelT] | None¶
Resolve a late-bound serializer for a model annotation.
Keyword arguments override options stored by
origin_cls.- Raises:
ValueError – If
origin_clsis not late-bound.
- class BaseDeserializer¶
Bases:
BaseCodec,ABC,Generic[ModelT]Base codec that converts transport-safe values into models.
- deserialize(data: Any | Sequence[Any]) ModelT | list[ModelT]¶
Deserialize one value or a sequence of values.
- classmethod from_model(model: type[ModelT], **kwargs: Any) BaseDeserializer[ModelT] | None¶
Return the deserializer registered for a model annotation, if any.
- get_data(response: Response) Any¶
Extract deserializer input from a response.
Custom deserializers that consume the response object directly can rely on this identity implementation.
- multiple_deserialize(data: Sequence[Any]) list[ModelT]¶
Deserialize a sequence of values into models.
- classmethod set_model(model: type[ModelT], origin_cls: BaseCodec, **kwargs: Any) BaseDeserializer[ModelT] | None¶
Resolve a late-bound deserializer for a model annotation.
Keyword arguments override options stored by
origin_cls.- Raises:
ValueError – If
origin_clsis not late-bound.
- serialize(model: type[ModelT] | None = None, **serializer_kwargs: Unpack[PydanticSerializeOptions]) RequestDecorator[Any, Any]¶
- serialize(model: type[ModelT] | None = None, **serializer_kwargs: Unpack[DataclassSerializeOptions]) RequestDecorator[Any, Any]
- serialize(model: type[ModelT] | None = None, **serializer_kwargs: Unpack[MarshmallowSerializeOptions]) RequestDecorator[Any, Any]
Decorate a request to serialize its complete body.
When
modelis empty, the serializer is selected from the complete body parameter annotation. Serializer keyword arguments are applied after the model type is resolved.
- deserialize(model: type[ModelT] | None = None, **deserializer_kwargs: Unpack[PydanticDeserializeOptions]) RequestDecorator[Any, Any]¶
- deserialize(model: type[ModelT] | None = None, **deserializer_kwargs: Unpack[DataclassDeserializeOptions]) RequestDecorator[Any, Any]
- deserialize(model: type[ModelT] | None = None, **deserializer_kwargs: Unpack[MarshmallowDeserializeOptions]) RequestDecorator[Any, Any]
Decorate a request to deserialize its HTTP response.
When
modelis empty, the deserializer is selected from the return annotation. Deserializer keyword arguments are applied after the model type is resolved.