Typed Python representations of HTTP requests and responses with convenient creation, duplication and manipulation. The companion of action0-url, which provides the URL representation.
Requires Python 3.11 or newer.
Full documentation including the API reference: https://laughinjar.github.io/action0-req/
Status: the core API is complete — the header name, status code and
method constants, the Headers mapping, Request and Response, and
sync/async body streaming via BodyProducer (BytesBody, FileBody,
IterableBody, AsyncIterableBody).
pip install action0-req # or uv add action0-reqHeader, Status and Method are StrEnum/IntEnum constants for the
IANA-registered header field names, status codes and request methods —
every member is its string or integer value:
from action0.req import Header, Method, Status
print(Header.CONTENT_TYPE) # Content-Type
print(Method.POST) # POST
print(Status.NOT_FOUND) # 404
print(Status.NOT_FOUND.phrase) # Not Found
print(Status.NOT_FOUND.is_client_error) # TrueHeaders is an ordered, case-insensitive, multi-value aware mapping of
header fields that preserves the representation (order and casing)
exactly:
from action0.req import Headers
headers = Headers({"Content-Type": "text/html"})
headers.add("Set-Cookie", "a=1")
headers.add("set-cookie", "b=2")
print(headers["CONTENT-TYPE"]) # text/html
print(headers.get_all("Set-Cookie")) # ['a=1', 'b=2']
print(headers.as_str(separator="\n")) # "\r\n" by default
# Content-Type: text/html
# Set-Cookie: a=1
# set-cookie: b=2
# repr()/str() redact secrets, only as_str() renders them
print(Headers({"Authorization": "Bearer tok"})) # Headers(Authorization: ***)Request combines a method, a Url (from action0-url), Headers, a
meta dict for application metadata (never on the wire) and a body (bytes, str or a streaming BodyProducer — retrievable in any
of the three forms):
from action0.req import Request
req = Request("https://api.example.com/items", query={"page": 2})
req.headers["Accept"] = "application/json"
print(req.as_str(separator="\n")) # "\r\n" by default
# GET /items?page=2 HTTP/1.1
# Host: api.example.com
# Accept: application/json
post = req.copy(method="POST", body='{"a": 1}')
print(post.body_bytes()) # b'{"a": 1}'
print(post) # Request(POST https://api.example.com/items?page=2)Response mirrors it for the server side — status (with reason phrase
fallback and category properties), headers, body:
from action0.req import Response, Status
resp = Response(Status.NOT_FOUND, headers={"Content-Type": "text/plain"}, body="not here")
print(resp.phrase, resp.is_client_error) # Not Found True
print(resp.as_str(separator="\n")) # "\r\n" by default
# HTTP/1.1 404 Not Found
# Content-Type: text/plainThe project is managed with uv; uv run
creates and syncs the virtual environment automatically:
uv run pytest # run the tests (incl. the docstring examples as doctests)
uv run ruff check # lint
uv run ruff format # format
uv run mypy # type-check (also: uv run pyright, uv run ty check)
# build the docs (Sphinx; deployed to GitHub Pages on push to main)
uv run --group docs sphinx-build -W docs docs/_build/htmlThe version lives only in src/action0/req/__init__.py (__version__).
To release: bump it, merge to main, then tag the release commit and push
the tag — the release workflow re-runs all checks, verifies the tag
matches __version__, builds sdist + wheel and publishes to PyPI via
trusted publishing:
git tag v0.1.0
git push origin v0.1.0In the spirit of transparency: most of this project's code, tests and
documentation are written by Claude Code,
Anthropic's coding agent — under human direction and review. The designs
are specified, discussed and iterated by a human, and every change is
reviewed before it lands in main or in a release. AI-authored commits
carry a Co-Authored-By: Claude ... trailer.
This is just the namespace I like to use for my personal projects. I quite like namespaces.