xtr-http-message-contracts
The HTTP message contract, and nothing else — so two libraries can pass a request between them without agreeing on a class.
Why?
An authorization server, a rate limiter and a firewall all read a request and fill in a response. None of them should care whose request object it is — and if each brings its own, the application ends up converting between three shapes of the same thing on every call.
So they need the seam, not an implementation. This package is that seam and nothing else:
thirteen protocols and an enumeration, built from the standard library, with one dependency —
typing-extensions, for @override on 3.11.
- 🧩
ServerRequestInterface— what arrived: method, URI, headers, body, cookies, query, uploaded files, parsed body, plus the attributes a handler annotates it with. - 📤
ResponseInterface— a status code, a reason phrase, headers and a body. - 🌊
StreamInterface— the body itself, as a cursor over bytes. - 🔗
UriInterface— a URI taken apart into components and put back together as text. - 📎
UploadedFileInterface/UploadError— a file that came in, and how it went. - 🏭 Six factories — build any of the above without naming a class.
from xtr_http_message_contracts import ResponseInterface, ServerRequestInterface
async def whoami(
request: ServerRequestInterface,
response: ResponseInterface,
) -> ResponseInterface:
_ = await response.get_body().write(request.get_header_line("user-agent").encode())
return response.with_header("content-type", "text/plain")
Install
uv add xtr-http-message-contracts
Requires Python 3.11+.
Who installs what
| Depends on | |
|---|---|
| A library that handles requests | xtr-http-message-contracts at runtime, xtr-http-message as a dev dependency — its tests build real messages. |
| An application | xtr-http-message, which implements the contract, plus the integration that bridges these types to the web framework it runs on. |
An implementation builds on these types rather than redefining them. Import them from
xtr_http_message_contracts; that one set of types is what lets a container register an
implementation under an interface, and a library that never imported the implementation receive
it.
The interfaces
| Interface | What it is |
|---|---|
MessageInterface |
What a request and a response share: protocol version, headers, body. |
RequestInterface |
A message going out: a method and a URI on top. |
ServerRequestInterface |
A request that arrived, with everything the server learned alongside it. |
ResponseInterface |
A message coming back: a status code and a reason phrase on top. |
StreamInterface |
A message body: read, write, seek, and the whole of it in one awaited call. |
UriInterface |
A URI as a value, every component readable. |
UploadedFileInterface |
One file that came in with a request, movable once. |
Under xtr_http_message_contracts.factory, one factory each for a request, a server request, a
response, a stream, a URI and an uploaded file — RequestFactoryInterface,
ServerRequestFactoryInterface, ResponseFactoryInterface, StreamFactoryInterface,
UriFactoryInterface, UploadedFileFactoryInterface. They are re-exported at the top level too,
so either import works.
The rules that hold across all of them:
- Messages are immutable. Every
with_*method leaves the instance it was called on as it was and returns one carrying the change, so a message handed on cannot be altered behind the caller's back. The return type isSelf, so a subtype keeps its own type through a chain. - Header names are matched without regard to case, and reported in the case they were first
given in. A header the message does not carry reads as an empty sequence from
get_headerand an empty string fromget_header_line— never as an error. - A component the URI has not got reads as an empty string, and
get_portreads asNonewhen there is no port worth reporting. - A stream is the one thing that is not immutable: reading, writing and seeking move its
cursor in place. Its payload is
bytes, since a body may be binary;await stream.get_contents()rewinds and reads the whole of it, and raises when the body cannot be read. - A body is awaited.
read,write,get_contents,seek,rewindandcloseareasync, so a body can come off the network as it is asked for;move_toon an uploaded file is too. The methods that only report what the stream already knows —get_size,tell,eof,is_seekable,is_readable,is_writable,get_metadata,detach— are not. with_uridecides theHostheader. By default it follows the new URI, and aHostheader already there carries over only when the URI has no host;preserve_host=Truekeeps a non-empty one exactly as it is.
Upload errors
get_error() on an uploaded file, and error= when building one, carry one of these:
| Member | Value | Means |
|---|---|---|
OK |
0 | The file arrived whole. |
INI_SIZE |
1 | Larger than the server configuration allows. |
FORM_SIZE |
2 | Larger than the submitted form named. |
PARTIAL |
3 | The upload stopped part way. |
NO_FILE |
4 | The field was submitted with no file in it. |
NO_TMP_DIR |
6 | Nowhere to keep the file while receiving it. |
CANT_WRITE |
7 | It could not be written to where it was being kept. |
EXTENSION |
8 | Something loaded on the server refused it. |
UploadError is an IntEnum, so a member passes anywhere the int the contract calls for is
expected. 5 is absent because that number was never assigned.
What is not here
Anything that is a message rather than describing one: the request, response, stream, URI and
uploaded file classes, the status-code phrase table, URI parsing, and the factory that builds all
six. That is xtr-http-message.
Anything that bridges a message to a web framework — building one from the call in flight, returning one from a route, registering either on a container — is not here either: it belongs with that framework's integration, which for a FastAPI application is xtr-http-kernel.
Errors
This package defines no exception of its own; a contract with no behaviour has nothing to raise. The interfaces name two builtins instead, so a caller catches the same thing whichever implementation is behind them:
| Error | Raised when |
|---|---|
ValueError |
An argument cannot be what it is being used as — a malformed header name, an out-of-range port, a status code that is not one |
RuntimeError |
An operation cannot be carried out now — a stream that will not seek, an uploaded file already moved |
Development
Developed in the python-xtr monorepo, under
packages/xtr-http-message-contracts; run the commands below from there. The
python-xtr-http-message-contracts repository is a read-only copy, so send issues and pull
requests to the monorepo.
uv sync
uv run ruff check . && uv run ruff format --check .
uv run basedpyright
uv run ty check
uv run pytest
License
MIT — see LICENSE.
Metadata
Release files for xtr-http-message-contracts 4.0.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| xtr_http_message_contracts-4.0.4.tar.gz | 19.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| xtr_http_message_contracts-4.0.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 46.5 kB
Release files / xtr_http_message_contracts-4.0.4.tar.gz
| Download URL | xtr_http_message_contracts-4.0.4.tar.gz |
|---|---|
| Size | 19.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7cb7060c8abfe7ca23ba127cd4df12cc7e786b8af6bb6563acf9a30b70a02381
|
|
BLAKE2b-256 checksum How to use checksums |
445eeb751a978dcbbbeece3f03806f0269c40b5b27af44fb629cbe999afada0e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency logRelease files / xtr_http_message_contracts-4.0.4-py3-none-any.whl
| Download URL | xtr_http_message_contracts-4.0.4-py3-none-any.whl |
|---|---|
| Size | 27.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4247f0ef2f9f271fd97e44bfcbc7119d05f0fa0c4b671936c351950ef98382cf
|
|
BLAKE2b-256 checksum How to use checksums |
dec99a54e480e44157e3c003aeea56ca3f72136bb8f29894bc93f1d2772a97c2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency log