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.

Deserializing a Response into a Pydantic Model
 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.

property is_late_bind: bool

Return whether this codec is waiting for a model type.

classmethod is_model_type(model_type: type[Any]) → bool

Return whether an annotation contains the configured model type.

static is_sequence(value: Any) → bool

Return whether value is a collection of values to convert.

classmethod late_bind(**kwargs: Any) → BaseCodec

Return a placeholder that stores options until a model type is known.

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.

multiple_serialize(model: Sequence[ModelT]) → list[Any]

Serialize a sequence of models.

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_cls is not late-bound.

abstractmethod single_serialize(model: ModelT) → Any

Serialize one model.

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_cls is not late-bound.

abstractmethod single_deserialize(data: Any) → ModelT

Deserialize one value into a model.

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 model is empty, the serializer is selected from the complete body parameter annotation. Serializer keyword arguments are applied after the model type is resolved.

Parameters:
  • model (Optional[type[ModelT]]) – Model type used to select a registered serializer.

  • **serializer_kwargs – Keyword arguments forwarded to the serializer constructor.

Raises:

TypeError – If no serializer is registered for the model type.

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 model is empty, the deserializer is selected from the return annotation. Deserializer keyword arguments are applied after the model type is resolved.

Parameters:
  • model (Optional[type[ModelT]]) – Model type used to select a registered deserializer.

  • **deserializer_kwargs – Keyword arguments forwarded to the deserializer constructor.

Raises:

TypeError – If no deserializer is registered for the model type.