Skip to main content

mxhttp

Declarative HTTP client on top of msgspec and httpx. Write an API as a class of annotated stub methods and mxhttp will handle the rest (request building, sending, and response decoding).

Install

pip install mxhttp

Usage

from typing import Annotated
import msgspec
from mxhttp import Body, Query, SyncConsumer, get, post


class Item(msgspec.Struct):
    id: int
    name: str
    price: float


class NewItem(msgspec.Struct):
    name: str
    price: float


class Shop(SyncConsumer):
    @get("/items/{item_id}")
    def get_item(self, item_id: int) -> Item: ...  # type: ignore[empty-body]

    @get("/search")
    def search(self, q: Annotated[str, Query], limit: Annotated[int, Query] = 20) -> list[Item]: ...  # type: ignore[empty-body]

    @post("/items")
    def create_item(self, item: Annotated[NewItem, Body]) -> Item: ...  # type: ignore[empty-body]


shop = Shop("https://api.example.com")
item = shop.get_item(item_id=7)
new = shop.create_item(item=NewItem(name="Gadget", price=4.5))

The method body is never run as it is replaced by the decorator. Parameters are bound based on their Annotated[...] marker:

Marker class Request Target Info
Path (or implicit) Path Matched by parameter name unless annotated explicitly (Path["name"]). Must be a non-nullable str, int, or float.
Query Query Must be a nullable str, int, float, or bool, or a Sequence of those, sent as key=a&key=b&....
Field Form Field application/x-www-form-urlencoded. Accepts same types as Query.
Part Multipart File Part Forces the whole request to be multipart and any Field params on the same call will become multipart fields as well. Accepts the same types httpx takes for files=.
Header HTTP Header Must be str, int, float, or bool, but no list of those.
Cookie Cookie Is superseded by the cookie jar of the client if it already has a same-named cookie, unless override=True is set. Accepts same types as Header
Body JSON Body Whole object, serialized with msgspec.to_builtins. Can't be a scalar type.
  • Use Path["name"], Query["name"], Field["name"], Header["name"], or Cookie["name"] to bind under a different name than the parameter (e.g. reserved from, or a header like X-Request-Id, unsupported string format arguments like ?).
  • None-valued Query, Field, Header, and Cookie parameters are omitted from the request.
  • Path parameters cannot be optional as a placeholder cannot be ommited from the URL.
  • Mismatched marker/type combinations raise a TypeError as soon as the class body runs, not at call time.

Decoding the response

The return type defines the reponse decoding:

  • httpx.Response for the raw response.
  • str or bytes for the corresponding .text or .content with no JSON round-trip.
  • pydantic.BaseModel subclasses via their own .model_validate_json.
  • Anything else msgspec.json.decode can decode: msgspec.Struct, dataclasses, TypedDict, NamedTuple, and list, dict, or other containers of those.
  • Response[Item] for a small struct with the decoded Item as .data and the raw httpx.Response in .response.
  • Plain attrs classes are decoded by msgspec, for type hinting attrs is needed as dependency.

For an async client, subclass AsyncConsumer and declare the methods async def, everything else stays the same.

Response handling

By default, every response is checked by response.raise_for_status() before decoding, so errors during the request raise httpx.HTTPStatusError automatically. This behavior can be overriden by @response_handler decorator for the class.

import httpx
from mxhttp import response_handler


def ignore_errors(response: httpx.Response) -> httpx.Response:
    return response


@response_handler(ignore_errors)
class Shop(SyncConsumer): ...

The hook runs on every response before decoding.

Streaming responses

Annotate the return type as Iterator[bytes] (sync) or AsyncIterator[bytes] (async) to stream the response body in chunks.

from collections.abc import AsyncIterator, Iterator


class Files(SyncConsumer):
    @get("/files/{file_id}")
    def download(self, file_id: int) -> Iterator[bytes]: ...  # type: ignore[empty-body]


for chunk in shop_files.download(file_id=7):
    ...

class AsyncFiles(AsyncConsumer)
    @get("/files/{file_id}")
    def download(self, file_id: int) -> AsyncIterator[bytes]: ...  # type: ignore[empty-body]

async for chunk in await shop_async_files.download(file_id=7):
    ...

httpx already decompresses chunks before responding according to Content-Encoding (gzip/deflate/br/zstd).

Streaming responses run @streaming_response_handler instead of @response_handler (defaults to raise_for_status as well). The handler can only inspect status line and headers.

from mxhttp import streaming_response_handler


def check_status(response: httpx.Response) -> httpx.Response:
    response.raise_for_status()
    return response


@streaming_response_handler(check_status)
class Files(SyncConsumer): ...

Server-Sent Events

Annotate the return type as Iterator[Event] (sync) or AsyncIterator[Event] (async) to parse the response as a Server-Sent Events stream instead of raw bytes:

from collections.abc import Iterator
from mxhttp import Event


class Chat(SyncConsumer):
    @get("/stream")
    def events(self) -> Iterator[Event]: ...  # type: ignore[empty-body]


for event in chat.events():
    print(event.event, event.data)  # event.event defaults to "message"

Event has four attributes, data, event, id, and retry:

  • data is the raw payload, decode it manually if the server sends JSON.
  • Multi-line data fields are joined with \n.
  • id and retry persist across events once set and reset on reconnect only.
  • An event without a trailing blank line at the end of the stream is discarded.

SSE streams use @streaming_response_handler matching byte streaming above.

Further configuration

The underlying httpx.Client or httpx.AsyncClient is stored at .session to set default headers, auth, or timeouts.

Typing

The package and all its generators are typed.

Tests

pytest

Acknowledgements

mxhttp is inspired by Uplink but combining it with Python typing features.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mxhttp-1.0.0.tar.gz (24.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mxhttp-1.0.0-py3-none-any.whl (16.8 kB view details)

Uploaded Python 3

File details

Details for the file mxhttp-1.0.0.tar.gz.

File metadata

  • Download URL: mxhttp-1.0.0.tar.gz
  • Upload date:
  • Size: 24.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for mxhttp-1.0.0.tar.gz
Algorithm Hash digest
SHA256 68f122303bad2cf3ca9f47cbf14527eb9bf7634ec4e53df6ab2c3a0810ec118c
MD5 5f60010811c7aa702b9abd41c9471e14
BLAKE2b-256 3cb497ed53086d91b2605276e3404a9b496c18fbc92dcff313a68a04b1351ca1

See more details on using hashes here.

Provenance

The following attestation bundles were made for mxhttp-1.0.0.tar.gz:

Publisher: release.yml on audivir/mxhttp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mxhttp-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: mxhttp-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 16.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for mxhttp-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5dc33fc2d016d01625598bc3d4c6348a4445979609ece9df956649703ee0a57f
MD5 3f4edbb8aef45a35a9d8dbcfd5a13317
BLAKE2b-256 3ff2d37309877f74e7a123b01b368ab54a0f2c258cf1edc3d7828823f6d592cb

See more details on using hashes here.

Provenance

The following attestation bundles were made for mxhttp-1.0.0-py3-none-any.whl:

Publisher: release.yml on audivir/mxhttp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

1.7.1

2 files

1.7.0

2 files

1.5.7

2 files

1.3.0

2 files

1.2.0

2 files

1.1.0

2 files

This release

1.0.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page