Skip to content

HTTP

Request markers

CustomHeader dataclass

Mark a parameter as a header value. Argument is passed «as is» during a service call.

Examples:

>>> class Service(Protocol):
>>>     def service(self, accept_language: Annotated[str, CustomHeader("Accept-Language")]):
>>>         ...
Source code in combadge/support/http/markers/request.py
@dataclass(**SLOTS)
class CustomHeader(ParameterMarker[ContainsHeaders]):
    """
    Mark a parameter as a header value. Argument is passed «as is» during a service call.

    Examples:
        >>> class Service(Protocol):
        >>>     def service(self, accept_language: Annotated[str, CustomHeader("Accept-Language")]):
        >>>         ...
    """

    name: str

    @override
    def __call__(self, request: ContainsHeaders, value: Any) -> None:  # noqa: D102
        request.headers.append((self.name, value))

QueryParam dataclass

Mark parameter as a query parameter.

Examples:

>>> def call(query: Annotated[str, QueryParam("query")]) -> ...:
>>>     ...
Source code in combadge/support/http/markers/request.py
@dataclass(**SLOTS)
class QueryParam(ParameterMarker[ContainsQueryParams]):
    """
    Mark parameter as a query parameter.

    Examples:
        >>> def call(query: Annotated[str, QueryParam("query")]) -> ...:
        >>>     ...
    """

    name: str

    @override
    def __call__(self, request: ContainsQueryParams, value: Any) -> None:  # noqa: D102
        request.query_params.append((self.name, value.value if isinstance(value, Enum) else value))

Payload dataclass

Mark parameter as a request payload. An argument gets converted to a dictionary and passed over to a backend.

Examples:

Simple usage:

>>> def call(body: Payload[BodyModel]) -> ...:
>>>     ...

Equivalent expanded usage:

>>> def call(body: Annotated[BodyModel, Payload()]) -> ...:
>>>     ...
Source code in combadge/support/http/markers/request.py
@dataclass(**SLOTS)
class Payload(ParameterMarker[ContainsPayload]):
    """
    Mark parameter as a request payload. An argument gets converted to a dictionary and passed over to a backend.

    Examples:
        Simple usage:

        >>> def call(body: Payload[BodyModel]) -> ...:
        >>>     ...

        Equivalent expanded usage:

        >>> def call(body: Annotated[BodyModel, Payload()]) -> ...:
        >>>     ...
    """

    exclude_unset: bool = False
    by_alias: bool = False

    @override
    def __call__(self, request: ContainsPayload, value: Any) -> None:  # noqa: D102
        value = get_type_adapter(type(value)).dump_python(
            value,
            by_alias=self.by_alias,
            exclude_unset=self.exclude_unset,
        )
        if request.payload is None:
            request.payload = value
        elif isinstance(request.payload, dict):
            request.payload.update(value)  # merge into the existing payload
        else:
            raise ValueError(f"attempting to merge {type(value)} into {type(request.payload)}")

    def __class_getitem__(cls, item: type[Any]) -> Any:
        return Annotated[item, cls()]

Field dataclass

Mark a parameter as a value of a separate payload field.

Examples:

>>> def call(param: Annotated[int, Field("param")]) -> ...:
>>>     ...
Notes
  • Enum values are passed by value
Source code in combadge/support/http/markers/request.py
@dataclass(**SLOTS)
class Field(ParameterMarker[ContainsPayload]):
    """
    Mark a parameter as a value of a separate payload field.

    Examples:
        >>> def call(param: Annotated[int, Field("param")]) -> ...:
        >>>     ...

    Notes:
        - Enum values are passed by value
    """

    name: str

    @override
    def __call__(self, request: ContainsPayload, value: Any) -> None:  # noqa: D102
        if request.payload is None:
            request.payload = {}
        request.payload[self.name] = value.value if isinstance(value, Enum) else value

FormData dataclass

Mark parameter as a request form data.

An argument gets converted to a dictionary and passed over to a backend.

Examples:

>>> def call(body: FormData[FormModel]) -> ...:
>>>     ...
>>> def call(body: Annotated[FormModel, FormData()]) -> ...:
>>>     ...
Source code in combadge/support/http/markers/request.py
@dataclass(**SLOTS)
class FormData(ParameterMarker[ContainsFormData]):
    """
    Mark parameter as a request form data.

    An argument gets converted to a dictionary and passed over to a backend.

    Examples:
        >>> def call(body: FormData[FormModel]) -> ...:
        >>>     ...

        >>> def call(body: Annotated[FormModel, FormData()]) -> ...:
        >>>     ...
    """

    @override
    def __call__(self, request: ContainsFormData, value: Any) -> None:  # noqa: D102
        value = get_type_adapter(type(value)).dump_python(value, by_alias=True)
        if not isinstance(value, dict):
            raise TypeError(f"form data requires a dictionary, got {type(value)}")
        for item_name, item_value in value.items():
            request.append_form_field(item_name, item_value)

    def __class_getitem__(cls, item: type[Any]) -> Any:
        return Annotated[item, FormData()]

FormField dataclass

Mark a parameter as a separate form field value.

Examples:

>>> def call(param: Annotated[int, FormField("param")]) -> ...:
>>>     ...
Notes
  • Multiple arguments with the same field name are allowed
  • FormData marker's fields get merged with FormField ones (if present)
  • Enum values are passed by value
Source code in combadge/support/http/markers/request.py
@dataclass(**SLOTS)
class FormField(ParameterMarker[ContainsFormData]):
    """
    Mark a parameter as a separate form field value.

    Examples:
        >>> def call(param: Annotated[int, FormField("param")]) -> ...:
        >>>     ...

    Notes:
        - Multiple arguments with the same field name are allowed
        - [`FormData`][combadge.support.http.markers.FormData] marker's fields get merged with `FormField` ones (if present)
        - Enum values are passed by value
    """  # noqa: E501

    name: str

    @override
    def __call__(self, request: ContainsFormData, value: Any) -> None:  # noqa: D102
        request.append_form_field(self.name, value.value if isinstance(value, Enum) else value)

path

path(
    path_or_factory: str | Callable[..., str]
) -> Callable[[FunctionT], FunctionT]

Specify a URL path.

Examples:

>>> @path("/hello/world")
>>> def call() -> None: ...
>>> @path("/hello/{name}")
>>> def call(name: str) -> None: ...
>>> @path("/hello/{0}")
>>> def call(name: str) -> None: ...
>>> @path(lambda name, **_: f"/hello/{name}")
>>> def call(name: str) -> None: ...
Source code in combadge/support/http/markers/request.py
def path(path_or_factory: str | Callable[..., str]) -> Callable[[FunctionT], FunctionT]:
    """
    Specify a URL path.

    Examples:
        >>> @path("/hello/world")
        >>> def call() -> None: ...

        >>> @path("/hello/{name}")
        >>> def call(name: str) -> None: ...

        >>> @path("/hello/{0}")
        >>> def call(name: str) -> None: ...

        >>> @path(lambda name, **_: f"/hello/{name}")
        >>> def call(name: str) -> None: ...
    """
    return Path[Any](path_or_factory).mark

http_method

http_method(method: str) -> Callable[[FunctionT], FunctionT]

Specify an HTTP method.

Examples:

>>> @http_method("POST")
>>> def call() -> None: ...
Source code in combadge/support/http/markers/request.py
def http_method(method: str) -> Callable[[FunctionT], FunctionT]:
    """
    Specify an HTTP method.

    Examples:
        >>> @http_method("POST")
        >>> def call() -> None: ...
    """
    return HttpMethod[Any](method).mark

Response markers

StatusCode dataclass

Enrich the payload with response status code.

Examples:

>>> def call(...) -> Annotated[Model, Mixin(StatusCode())]:
>>>     ...
Source code in combadge/support/http/markers/response.py
@dataclass(**SLOTS)
class StatusCode(ResponseMarker):
    """
    Enrich the payload with response status code.

    Examples:
        >>> def call(...) -> Annotated[Model, Mixin(StatusCode())]:
        >>>     ...
    """

    key: Any = "status_code"
    """Key under which the status code should mapped in the payload."""

    @override
    def __call__(self, response: SupportsStatusCode, payload: Any) -> Dict[Any, Any]:  # noqa: D102
        return {self.key: HTTPStatus(response.status_code)}

key class-attribute instance-attribute

key: Any = 'status_code'

Key under which the status code should mapped in the payload.

ReasonPhrase dataclass

Enrich the payload with HTTP reason message.

Source code in combadge/support/http/markers/response.py
@dataclass(**SLOTS)
class ReasonPhrase(ResponseMarker):
    """Enrich the payload with HTTP reason message."""

    key: Any = "reason"
    """Key under which the reason message should mapped in the payload."""

    @override
    def __call__(self, response: SupportsReasonPhrase, payload: Any) -> Dict[Any, Any]:  # noqa: D102
        return {self.key: response.reason_phrase}

key class-attribute instance-attribute

key: Any = 'reason'

Key under which the reason message should mapped in the payload.

Text dataclass

Enrich the payload with HTTP response text.

Examples:

>>> class MyResponse(BaseModel):
>>>     my_text: str
>>>
>>> class MyService(Protocol):
>>>     @http_method("GET")
>>>     @path(...)
>>>     def get_text(self) -> Annotated[MyResponse, Text("my_text")]:
>>>         ...
Source code in combadge/support/http/markers/response.py
@dataclass(**SLOTS)
class Text(ResponseMarker):
    """
    Enrich the payload with HTTP response text.

    Examples:
        >>> class MyResponse(BaseModel):
        >>>     my_text: str
        >>>
        >>> class MyService(Protocol):
        >>>     @http_method("GET")
        >>>     @path(...)
        >>>     def get_text(self) -> Annotated[MyResponse, Text("my_text")]:
        >>>         ...
    """

    key: Any = "text"
    """Key under which the text contents should assigned in the payload."""

    @override
    def __call__(self, response: SupportsText, payload: Any) -> Dict[Any, Any]:  # noqa: D102
        return {self.key: response.text}

key class-attribute instance-attribute

key: Any = 'text'

Key under which the text contents should assigned in the payload.

Header dataclass

Enrich the payload with the specified HTTP header's value.

If the header be missing, the payload will not be enriched.

Examples:

>>> class MyResponse(BaseModel):
>>>     content_length: int
>>>     optional: str = "default"
>>>
>>> class MyService(Protocol):
>>>     @http_method("GET")
>>>     @path(...)
>>>     def get_something(self) -> Annotated[
>>>         MyResponse,
>>>         Header(header="content-length", key="content_length"),
>>>         Header(header="x-optional", key="optional"),
>>>     ]:
>>>         ...
Source code in combadge/support/http/markers/response.py
@dataclass(**SLOTS)
class Header(ResponseMarker):
    """
    Enrich the payload with the specified HTTP header's value.

    If the header be missing, the payload will not be enriched.

    Examples:
        >>> class MyResponse(BaseModel):
        >>>     content_length: int
        >>>     optional: str = "default"
        >>>
        >>> class MyService(Protocol):
        >>>     @http_method("GET")
        >>>     @path(...)
        >>>     def get_something(self) -> Annotated[
        >>>         MyResponse,
        >>>         Header(header="content-length", key="content_length"),
        >>>         Header(header="x-optional", key="optional"),
        >>>     ]:
        >>>         ...
    """

    header: str
    """HTTP header name, case-insensitive."""

    key: Any
    """Key under which the header contents should assigned in the payload."""

    @override
    def __call__(self, response: SupportsHeaders, payload: Any) -> Dict[Any, Any]:  # noqa: D102
        try:
            value = response.headers[self.header]
        except KeyError:
            return {}
        else:
            return {self.key: value}

header instance-attribute

header: str

HTTP header name, case-insensitive.

key instance-attribute

key: Any

Key under which the header contents should assigned in the payload.