API reference¶
The public constants and classes are importable from the package root:
from action0.req import Header, Headers, Method, Request, Response, Status
from action0.req import BodyProducer, BytesBody
Headers and Header¶
The Headers mapping and the known header field names (Header).
- action0.req.headers.HeaderValue = str | int | float | bool¶
A single header value; non-strings are coerced to strings on the way in (bools become the web-style
"true"/"false").
- action0.req.headers.HeaderTypes = typing.Iterable[tuple[str, str | int | float | bool | typing.Iterable[str | int | float | bool]]] | typing.Mapping[str, str | int | float | bool | typing.Iterable[str | int | float | bool]] | str¶
Everything that can initialize a
Headersinstance: a raw header block, a mapping, or an iterable of name/value(s) tuples.
- class action0.req.headers.Headers(headers=None)[source]¶
An ordered, case-insensitive, multi-value aware mapping of HTTP header fields. Internally it is a list of
(name, value)lines: the order and casing of the representation are preserved exactly, while every lookup matches field names case-insensitively (per RFC 9110 —headers["content-type"]andheaders["Content-Type"]hit the same field). Names are never normalized beyond that; in particular an underscore is not treated as a dash.Headers implements
typing.MutableMapping: the mapping view (headers[name],get(),items(), …) works with a single value per name — the last one, likesingles(). Multi-value access is available throughget_all(),get_values(),add(),as_dict()andas_lines().Example:
>>> headers = Headers({"Content-Type": "text/html"}) >>> headers.add("Set-Cookie", "a=1") >>> headers.add("set-cookie", "b=2") >>> headers["content-type"] 'text/html' >>> headers.get_all("Set-Cookie") ['a=1', 'b=2'] >>> len(headers), "SET-COOKIE" in headers (2, True) >>> headers.as_str() 'Content-Type: text/html\r\nSet-Cookie: a=1\r\nset-cookie: b=2'
repr()(and thusstr()) redacts the values of thesecret_namesfields; onlyas_str()renders them:>>> headers["Authorization"] = "Bearer secret-token" >>> headers Headers(Content-Type: text/html, Set-Cookie: ***, set-cookie: ***, Authorization: ***)
- Parameters:
headers (
Iterable[tuple[str,str|int|float|bool|Iterable[str|int|float|bool]]] |Mapping[str,str|int|float|bool|Iterable[str|int|float|bool]] |str|None, default:None)headers – the initial header lines, either as a raw header block (lines of
"Name: value"separated by"\r\n"or"\n"; blank lines are skipped, obsolete line folding is not supported), another Headers instance whose lines are copied, or as a list of tuples or a dictionary. The values can be single values or lists of values (one line each); non-string values are coerced to strings (bools become “true” / “false”).
- Raises:
ValueError – if a line of a raw header block has no
":"
- secret_names: frozenset[str] = frozenset({'authorization', 'cookie', 'proxy-authorization', 'set-cookie', 'x-api-key', 'x-csrf-token'})¶
The (casefolded) names of the fields whose values carry secrets and are shown as
***byrepr(). Override on a subclass or instance to redact more (or fewer) fields.
- __getitem__(name)[source]¶
The single value of the field; if the field has multiple lines, the value of the last one — like
singles(). Useget_all()for all values.
- get_all(name)[source]¶
The values of all lines of the field, in representation order; use
headers[name]orget()for the single-value view andget_values()for the comma-split element view.
- get_values(name)[source]¶
The elements of all lines of the field: like
get_all(), but each line is additionally split on","(RFC 9110 list syntax) with whitespace stripped and empty elements dropped.WARNING:
Set-Cookiedoes not use the list syntax — its values may contain literal commas (e.g. in anExpiresattribute), so useget_all()for it.
- add(name, value)[source]¶
Append a line for each given value at the end, keeping existing lines of the field.
- remove(name, value=None)[source]¶
If only a name is given all lines of this field are removed. If a value or a list of values is given only the matching lines are removed.
- Parameters:
- Return type:
- Returns:
a list of removed values
- set(name, value)[source]¶
Replace all lines of the field with the value(s) given, at the position of the field’s first line (new fields are appended at the end). Setting an empty list of values removes the field.
- update(headers=None)[source]¶
Merge the given headers into this instance: lines of fields that already exist are replaced (like
dict.update, at the position of the field’s first line), other fields are appended. Accepts the same forms as the constructor (raw header block, mapping, iterable of tuples, Headers instance).
- clear()[source]¶
Remove all fields, returns the removed lines (unlike
MutableMapping.clearwhich returnsNone).
- sort()[source]¶
Sort the lines in place by their (casefolded) field name. The sort is stable: lines of the same field keep their relative order — unlike
sort(), values are never reordered because their order can be significant (e.g.Set-Cookie).- Return type:
- as_str(separator='\\r\\n')[source]¶
The wire representation of the header lines, in order, with the original casing — secret values are NOT redacted here, unlike in
repr().
- singles()[source]¶
For those who are really sure that each field has only one line and do not want to bother with the lists for the values, this method will return only the last value for each field.
WARNING: be aware, if the field has multiple lines, only one of those will be returned for the name!
- __eq__(other)[source]¶
Headers are equal when they hold the same fields with the same values in the same per-field order; the order of different fields and the casing of the names don’t matter. Plain mappings are converted to Headers before comparing.
- __repr__()[source]¶
- Return type:
- Returns:
the header lines in order, with the values of the
secret_namesfields redacted as***.str()falls back to this representation, so onlyas_str()ever renders the secret values.
- class action0.req.headers.Header(*values)[source]¶
The known HTTP header field names as string constants.
The members are the field names from the IANA HTTP field name registry — all
permanent,deprecatedandprovisionalregistrations as of 2026-08-07 (obsoletedones are left out) — plus a handful of widely used de-facto standardX-…names that were never registered.Being a
enum.StrEnum, every member is the header name string, so it can be used wherever a plainstris expected:>>> from action0.req import Header >>> print(Header.CONTENT_TYPE) Content-Type >>> Header.CONTENT_TYPE == "Content-Type" True >>> Header("ETag") is Header.ETAG True
==on the enum is exact, case-sensitive string equality — case-insensitive header handling is the job of the header mapping, not of these constants.- ACCEPT = 'Accept'¶
- ACCEPT_ADDITIONS = 'Accept-Additions'¶
- ACCEPT_CH = 'Accept-CH'¶
- ACCEPT_DATETIME = 'Accept-Datetime'¶
- ACCEPT_ENCODING = 'Accept-Encoding'¶
- ACCEPT_FEATURES = 'Accept-Features'¶
- ACCEPT_LANGUAGE = 'Accept-Language'¶
- ACCEPT_PATCH = 'Accept-Patch'¶
- ACCEPT_POST = 'Accept-Post'¶
- ACCEPT_QUERY = 'Accept-Query'¶
- ACCEPT_RANGES = 'Accept-Ranges'¶
- ACCEPT_SIGNATURE = 'Accept-Signature'¶
- ACCESS_CONTROL_ALLOW_CREDENTIALS = 'Access-Control-Allow-Credentials'¶
- ACCESS_CONTROL_ALLOW_HEADERS = 'Access-Control-Allow-Headers'¶
- ACCESS_CONTROL_ALLOW_METHODS = 'Access-Control-Allow-Methods'¶
- ACCESS_CONTROL_ALLOW_ORIGIN = 'Access-Control-Allow-Origin'¶
- ACCESS_CONTROL_EXPOSE_HEADERS = 'Access-Control-Expose-Headers'¶
- ACCESS_CONTROL_MAX_AGE = 'Access-Control-Max-Age'¶
- ACCESS_CONTROL_REQUEST_HEADERS = 'Access-Control-Request-Headers'¶
- ACCESS_CONTROL_REQUEST_METHOD = 'Access-Control-Request-Method'¶
- AGE = 'Age'¶
- ALLOW = 'Allow'¶
- ALPN = 'ALPN'¶
- ALTERNATES = 'Alternates'¶
- ALT_SVC = 'Alt-Svc'¶
- ALT_USED = 'Alt-Used'¶
- APPLY_TO_REDIRECT_REF = 'Apply-To-Redirect-Ref'¶
- AUTHENTICATION_CONTROL = 'Authentication-Control'¶
- AUTHENTICATION_INFO = 'Authentication-Info'¶
- AUTHORIZATION = 'Authorization'¶
- AVAILABLE_DICTIONARY = 'Available-Dictionary'¶
- A_IM = 'A-IM'¶
- CACHE_CONTROL = 'Cache-Control'¶
- CACHE_GROUPS = 'Cache-Groups'¶
- CACHE_GROUP_INVALIDATION = 'Cache-Group-Invalidation'¶
- CACHE_STATUS = 'Cache-Status'¶
- CALDAV_TIMEZONES = 'CalDAV-Timezones'¶
- CAL_MANAGED_ID = 'Cal-Managed-ID'¶
- CAPSULE_PROTOCOL = 'Capsule-Protocol'¶
- CDN_CACHE_CONTROL = 'CDN-Cache-Control'¶
- CDN_LOOP = 'CDN-Loop'¶
- CERT_NOT_AFTER = 'Cert-Not-After'¶
- CERT_NOT_BEFORE = 'Cert-Not-Before'¶
- CLEAR_SITE_DATA = 'Clear-Site-Data'¶
- CLIENT_CERT = 'Client-Cert'¶
- CLIENT_CERT_CHAIN = 'Client-Cert-Chain'¶
- CLOSE = 'Close'¶
- CONCEALED_AUTH_EXPORT = 'Concealed-Auth-Export'¶
- CONNECTION = 'Connection'¶
- CONTENT_DIGEST = 'Content-Digest'¶
- CONTENT_DISPOSITION = 'Content-Disposition'¶
- CONTENT_ENCODING = 'Content-Encoding'¶
- CONTENT_LANGUAGE = 'Content-Language'¶
- CONTENT_LENGTH = 'Content-Length'¶
- CONTENT_LOCATION = 'Content-Location'¶
- CONTENT_RANGE = 'Content-Range'¶
- CONTENT_SECURITY_POLICY = 'Content-Security-Policy'¶
- CONTENT_SECURITY_POLICY_REPORT_ONLY = 'Content-Security-Policy-Report-Only'¶
- CONTENT_TYPE = 'Content-Type'¶
- COOKIE = 'Cookie'¶
- CROSS_ORIGIN_EMBEDDER_POLICY = 'Cross-Origin-Embedder-Policy'¶
- CROSS_ORIGIN_EMBEDDER_POLICY_REPORT_ONLY = 'Cross-Origin-Embedder-Policy-Report-Only'¶
- CROSS_ORIGIN_OPENER_POLICY = 'Cross-Origin-Opener-Policy'¶
- CROSS_ORIGIN_OPENER_POLICY_REPORT_ONLY = 'Cross-Origin-Opener-Policy-Report-Only'¶
- CROSS_ORIGIN_RESOURCE_POLICY = 'Cross-Origin-Resource-Policy'¶
- DASL = 'DASL'¶
- DATE = 'Date'¶
- DAV = 'DAV'¶
- DELTA_BASE = 'Delta-Base'¶
- DEPRECATION = 'Deprecation'¶
- DEPTH = 'Depth'¶
- DESTINATION = 'Destination'¶
- DETACHED_JWS = 'Detached-JWS'¶
- DICTIONARY_ID = 'Dictionary-ID'¶
- DPOP = 'DPoP'¶
- DPOP_NONCE = 'DPoP-Nonce'¶
- EARLY_DATA = 'Early-Data'¶
- ETAG = 'ETag'¶
- EXPECT = 'Expect'¶
- EXPIRES = 'Expires'¶
- FORWARDED = 'Forwarded'¶
- FROM = 'From'¶
- HOBAREG = 'Hobareg'¶
- HOST = 'Host'¶
- IF = 'If'¶
- IF_MATCH = 'If-Match'¶
- IF_MODIFIED_SINCE = 'If-Modified-Since'¶
- IF_NONE_MATCH = 'If-None-Match'¶
- IF_RANGE = 'If-Range'¶
- IF_SCHEDULE_TAG_MATCH = 'If-Schedule-Tag-Match'¶
- IF_UNMODIFIED_SINCE = 'If-Unmodified-Since'¶
- IM = 'IM'¶
- INCLUDE_REFERRED_TOKEN_BINDING_ID = 'Include-Referred-Token-Binding-ID'¶
- INCREMENTAL = 'Incremental'¶
- KEEP_ALIVE = 'Keep-Alive'¶
- LABEL = 'Label'¶
- LAST_EVENT_ID = 'Last-Event-ID'¶
- LAST_MODIFIED = 'Last-Modified'¶
- LINK = 'Link'¶
- LINK_TEMPLATE = 'Link-Template'¶
- LOCATION = 'Location'¶
- LOCK_TOKEN = 'Lock-Token'¶
- MAX_FORWARDS = 'Max-Forwards'¶
- MEMENTO_DATETIME = 'Memento-Datetime'¶
- METER = 'Meter'¶
- MIME_VERSION = 'MIME-Version'¶
- NEGOTIATE = 'Negotiate'¶
- NEL = 'NEL'¶
- ODATA_ENTITYID = 'OData-EntityId'¶
- ODATA_ISOLATION = 'OData-Isolation'¶
- ODATA_MAXVERSION = 'OData-MaxVersion'¶
- ODATA_VERSION = 'OData-Version'¶
- OPTIONAL_WWW_AUTHENTICATE = 'Optional-WWW-Authenticate'¶
- ORDERING_TYPE = 'Ordering-Type'¶
- ORIGIN = 'Origin'¶
- ORIGIN_AGENT_CLUSTER = 'Origin-Agent-Cluster'¶
- OSCORE = 'OSCORE'¶
- OSLC_CORE_VERSION = 'OSLC-Core-Version'¶
- OVERWRITE = 'Overwrite'¶
- PING_FROM = 'Ping-From'¶
- PING_TO = 'Ping-To'¶
- POSITION = 'Position'¶
- PREFER = 'Prefer'¶
- PREFERENCE_APPLIED = 'Preference-Applied'¶
- PRIORITY = 'Priority'¶
- PROXY_AUTHENTICATE = 'Proxy-Authenticate'¶
- PROXY_AUTHENTICATION_INFO = 'Proxy-Authentication-Info'¶
- PROXY_AUTHORIZATION = 'Proxy-Authorization'¶
- PROXY_STATUS = 'Proxy-Status'¶
- PUBLIC_KEY_PINS = 'Public-Key-Pins'¶
- PUBLIC_KEY_PINS_REPORT_ONLY = 'Public-Key-Pins-Report-Only'¶
- RANGE = 'Range'¶
- REDIRECT_REF = 'Redirect-Ref'¶
- REFERER = 'Referer'¶
- REFERRER_POLICY = 'Referrer-Policy'¶
- REFRESH = 'Refresh'¶
- REPLAY_NONCE = 'Replay-Nonce'¶
- REPR_DIGEST = 'Repr-Digest'¶
- RETRY_AFTER = 'Retry-After'¶
- SCHEDULE_REPLY = 'Schedule-Reply'¶
- SCHEDULE_TAG = 'Schedule-Tag'¶
- SEC_FETCH_DEST = 'Sec-Fetch-Dest'¶
- SEC_FETCH_MODE = 'Sec-Fetch-Mode'¶
- SEC_FETCH_SITE = 'Sec-Fetch-Site'¶
- SEC_FETCH_USER = 'Sec-Fetch-User'¶
- SEC_PURPOSE = 'Sec-Purpose'¶
- SEC_TOKEN_BINDING = 'Sec-Token-Binding'¶
- SEC_WEBSOCKET_ACCEPT = 'Sec-WebSocket-Accept'¶
- SEC_WEBSOCKET_EXTENSIONS = 'Sec-WebSocket-Extensions'¶
- SEC_WEBSOCKET_KEY = 'Sec-WebSocket-Key'¶
- SEC_WEBSOCKET_PROTOCOL = 'Sec-WebSocket-Protocol'¶
- SEC_WEBSOCKET_VERSION = 'Sec-WebSocket-Version'¶
- SERVER = 'Server'¶
- SERVER_TIMING = 'Server-Timing'¶
- SET_COOKIE = 'Set-Cookie'¶
- SET_TXN = 'Set-Txn'¶
- SIGNATURE = 'Signature'¶
- SIGNATURE_INPUT = 'Signature-Input'¶
- SLUG = 'SLUG'¶
- SOAPACTION = 'SoapAction'¶
- STATUS_URI = 'Status-URI'¶
- STRICT_TRANSPORT_SECURITY = 'Strict-Transport-Security'¶
- SUNSET = 'Sunset'¶
- TCN = 'TCN'¶
- TE = 'TE'¶
- TIMEOUT = 'Timeout'¶
- TOPIC = 'Topic'¶
- TRACEPARENT = 'Traceparent'¶
- TRACESTATE = 'Tracestate'¶
- TRAILER = 'Trailer'¶
- TRANSFER_ENCODING = 'Transfer-Encoding'¶
- TTL = 'TTL'¶
- UNENCODED_DIGEST = 'Unencoded-Digest'¶
- UPGRADE = 'Upgrade'¶
- URGENCY = 'Urgency'¶
- USER_AGENT = 'User-Agent'¶
- USE_AS_DICTIONARY = 'Use-As-Dictionary'¶
- VARIANT_VARY = 'Variant-Vary'¶
- VARY = 'Vary'¶
- VIA = 'Via'¶
- WANT_CONTENT_DIGEST = 'Want-Content-Digest'¶
- WANT_REPR_DIGEST = 'Want-Repr-Digest'¶
- WANT_UNENCODED_DIGEST = 'Want-Unencoded-Digest'¶
- WWW_AUTHENTICATE = 'WWW-Authenticate'¶
- X_CONTENT_TYPE_OPTIONS = 'X-Content-Type-Options'¶
- X_FRAME_OPTIONS = 'X-Frame-Options'¶
- ACCEPT_CHARSET = 'Accept-Charset'¶
- CONTENT_ID = 'Content-ID'¶
- C_PEP_INFO = 'C-PEP-Info'¶
- DIFFERENTIAL_ID = 'Differential-ID'¶
- EXPECT_CT = 'Expect-CT'¶
- PRAGMA = 'Pragma'¶
- PROTOCOL_INFO = 'Protocol-Info'¶
- PROTOCOL_QUERY = 'Protocol-Query'¶
- ACTIVATE_STORAGE_ACCESS = 'Activate-Storage-Access'¶
- AMP_CACHE_TRANSFORM = 'AMP-Cache-Transform'¶
- CMCD_OBJECT = 'CMCD-Object'¶
- CMCD_REQUEST = 'CMCD-Request'¶
- CMCD_SESSION = 'CMCD-Session'¶
- CMCD_STATUS = 'CMCD-Status'¶
- CMSD_DYNAMIC = 'CMSD-Dynamic'¶
- CMSD_STATIC = 'CMSD-Static'¶
- CONFIGURATION_CONTEXT = 'Configuration-Context'¶
- CTA_COMMON_ACCESS_TOKEN = 'CTA-Common-Access-Token'¶
- EDIINT_FEATURES = 'EDIINT-Features'¶
- ISOLATION = 'Isolation'¶
- PERMISSIONS_POLICY = 'Permissions-Policy'¶
- REPEATABILITY_CLIENT_ID = 'Repeatability-Client-ID'¶
- REPEATABILITY_FIRST_SENT = 'Repeatability-First-Sent'¶
- REPEATABILITY_REQUEST_ID = 'Repeatability-Request-ID'¶
- REPEATABILITY_RESULT = 'Repeatability-Result'¶
- REPORTING_ENDPOINTS = 'Reporting-Endpoints'¶
- SEC_FETCH_STORAGE_ACCESS = 'Sec-Fetch-Storage-Access'¶
- SEC_GPC = 'Sec-GPC'¶
- SURROGATE_CAPABILITY = 'Surrogate-Capability'¶
- SURROGATE_CONTROL = 'Surrogate-Control'¶
- TIMING_ALLOW_ORIGIN = 'Timing-Allow-Origin'¶
- X_API_KEY = 'X-API-Key'¶
- X_CORRELATION_ID = 'X-Correlation-Id'¶
- X_CSRF_TOKEN = 'X-CSRF-Token'¶
- X_FORWARDED_FOR = 'X-Forwarded-For'¶
- X_FORWARDED_HOST = 'X-Forwarded-Host'¶
- X_FORWARDED_PROTO = 'X-Forwarded-Proto'¶
- X_POWERED_BY = 'X-Powered-By'¶
- X_REAL_IP = 'X-Real-IP'¶
- X_REQUEST_ID = 'X-Request-Id'¶
- X_ROBOTS_TAG = 'X-Robots-Tag'¶
Request and Method¶
The HTTP request representation (Request) and the method constants.
- class action0.req.request.Method(*values)[source]¶
The HTTP request methods (“verbs”) as string constants.
The members are the methods defined by RFC 9110, plus
PATCH(RFC 5789) andQUERY(RFC 10008). Being aenum.StrEnum, every member is the method string:>>> from action0.req import Method >>> print(Method.GET) GET >>> Method.GET == "GET" True >>> Method("POST") is Method.POST True
- CONNECT = 'CONNECT'¶
- DELETE = 'DELETE'¶
- GET = 'GET'¶
- HEAD = 'HEAD'¶
- OPTIONS = 'OPTIONS'¶
- PATCH = 'PATCH'¶
- POST = 'POST'¶
- PUT = 'PUT'¶
- QUERY = 'QUERY'¶
- TRACE = 'TRACE'¶
- class action0.req.request.Request(url=None, method=Method.GET, *, query=None, headers=None, body=None, http_version='HTTP/1.1', meta=None)[source]¶
Python representation of an HTTP request: method, URL, headers, body and HTTP version — every part a plain mutable attribute (
Urlfor the URL,Headersfor the headers).Example:
>>> req = Request("https://api.example.com/items", query={"page": 2}) >>> req.method 'GET' >>> req.url.as_str() 'https://api.example.com/items?page=2' >>> req.headers["Accept"] = "application/json" >>> print(req.as_str(separator="\n")) GET /items?page=2 HTTP/1.1 Host: api.example.com Accept: application/json >>> req Request(GET https://api.example.com/items?page=2)
The body can be set as
bytes,stror a streamingBodyProducerand retrieved in any of the three forms viabody_bytes(),body_str()andbody_producer(), regardless of how it was set.- Parameters:
method (
str, default:<Method.GET: 'GET'>)query (
Iterable[tuple[str,str|int|float|bool|Iterable[str|int|float|bool]]] |Mapping[str,str|int|float|bool|Iterable[str|int|float|bool]] |str|None, default:None)headers (
Iterable[tuple[str,str|int|float|bool|Iterable[str|int|float|bool]]] |Mapping[str,str|int|float|bool|Iterable[str|int|float|bool]] |str|None, default:None)body (
bytes|str|BodyProducer|None, default:None)http_version (
str, default:'HTTP/1.1')url – the URL to request, as a string or an existing
Url(which is copied, so the request owns its URL);Nonestarts from an empty URL to be filled in viarequest.urlmethod – the HTTP method, e.g. a
Methodmember or any string (uppercased on the way in)query – if given, replaces the query of the given URL — like the
queryargument of theUrlconstructorheaders – the initial header lines, in any form accepted by
Headersbody – the request body as
bytes,stror a streamingBodyProducerhttp_version – the protocol version rendered in the request line
meta – initial application metadata riding along with the request (see
meta)
- body: bytes | str | BodyProducer | None¶
- meta: dict[str, Any]¶
Application metadata riding along with the request — correlation ids, tracing context, per-request knobs for custom backends, … — never sent on the wire and not part of
__eq__(). The dict is the request’s own (the constructor copies the given mapping); libraries should namespace their keys (e.g."my-lib.correlation-id").
- body_bytes()[source]¶
The body as bytes, regardless of how it was set: bytes are returned as-is, a str is encoded with the Content-Type charset (default utf-8), a
BodyProduceris read in full.
- body_str()[source]¶
The body as text, regardless of how it was set: a str is returned as-is, bytes are decoded with the Content-Type charset (default utf-8), a
BodyProduceris read in full and decoded.
- body_producer()[source]¶
The body as a streaming producer, regardless of how it was set: a
BodyProduceris returned as-is, bytes and str are wrapped in an in-memoryBytesBody(str encoded with the Content-Type charset first).- Return type:
- Returns:
the body producer,
Noneif there is no body
- copy(**overrides)[source]¶
An independent copy of this request (with its own
Url,Headersandmetadict), optionally with attributes replaced. The body is carried over as-is — in particular aBodyProduceris shared, not copied; themetavalues are shared too (shallow copy).Example:
>>> req = Request("https://api.example.com/items") >>> req.copy(method="POST", body="{}") Request(POST https://api.example.com/items)
- as_str(include_body=False, separator='\\r\\n')[source]¶
The wire representation: the request line with the origin-form target (path and query), the header lines — with a
Hostheader derived from the URL if none is set — and optionally the body. Secret header values are NOT redacted here, unlike inrepr().- Parameters:
include_body (
bool, default:False) – append a blank line and the body (as text, viabody_str()); aBodyProduceris not consumed — a placeholder is shown insteadseparator (
str, default:'\\r\\n') – the string joining the lines (no trailing one)
- Return type:
- Returns:
e.g.
"GET /items?page=2 HTTP/1.1\r\nHost: api.example.com"
- __eq__(other)[source]¶
Requests are equal when their method, URL, headers, body and HTTP version are equal, each with the part’s own equality semantics (e.g. header name casing doesn’t matter). The body is compared as set:
b"x"and"x"are different bodies. Themetadict is metadata and not compared.
Response¶
The HTTP response representation (Response).
- class action0.req.response.Response(status=Status.OK, *, headers=None, body=None, reason=None, http_version='HTTP/1.1', request=None, meta=None)[source]¶
Python representation of an HTTP response: status, headers, body and HTTP version — every part a plain mutable attribute (the headers a
Headers).Example:
>>> resp = Response(404, headers={"Content-Type": "text/plain"}, body="not here") >>> resp.status 404 >>> resp.phrase 'Not Found' >>> resp.is_client_error True >>> print(resp.as_str(include_body=True, separator="\n")) HTTP/1.1 404 Not Found Content-Type: text/plain not here >>> resp Response(404 Not Found)
The body works exactly like the request body: set as
bytes,stror a streamingBodyProducer, retrieved in any of the three forms viabody_bytes(),body_str()andbody_producer().- Parameters:
status (
int, default:<Status.OK: 200>)headers (
Iterable[tuple[str,str|int|float|bool|Iterable[str|int|float|bool]]] |Mapping[str,str|int|float|bool|Iterable[str|int|float|bool]] |str|None, default:None)body (
bytes|str|BodyProducer|None, default:None)http_version (
str, default:'HTTP/1.1')status – the status code, a
Statusmember or any int — unregistered codes are fineheaders – the initial header lines, in any form accepted by
Headersbody – the response body as
bytes,stror a streamingBodyProducerreason – the reason phrase as sent by the server;
Nonefalls back to the registry phrase of the status code (seephrase)http_version – the protocol version rendered in the status line
request – the request that produced this response — metadata: shared, not copied, and not part of equality
meta – initial application metadata riding along with the response (see
meta)
- meta: dict[str, Any]¶
Application metadata riding along with the response — e.g. a backend’s native response object, cache markers, timings — never sent on the wire and not part of
__eq__(). The dict is the response’s own (the constructor copies the given mapping); libraries should namespace their keys.
- property phrase: str¶
The reason phrase: the explicitly set
reasonif there is one, otherwise the registry phrase of the status code, otherwise (for unregistered codes) an empty string.
- body_bytes()[source]¶
The body as bytes, regardless of how it was set: bytes are returned as-is, a str is encoded with the Content-Type charset (default utf-8), a
BodyProduceris read in full.
- body_str()[source]¶
The body as text, regardless of how it was set: a str is returned as-is, bytes are decoded with the Content-Type charset (default utf-8), a
BodyProduceris read in full and decoded.
- body_producer()[source]¶
The body as a streaming producer, regardless of how it was set: a
BodyProduceris returned as-is, bytes and str are wrapped in an in-memoryBytesBody(str encoded with the Content-Type charset first).- Return type:
- Returns:
the body producer,
Noneif there is no body
- copy(**overrides)[source]¶
An independent copy of this response (with its own
Headersinstance andmetadict), optionally with attributes replaced. The body and therequestreference are carried over as-is — in particular aBodyProduceris shared, not copied; themetavalues are shared too (shallow copy).Example:
>>> resp = Response(200, body="ok") >>> resp.copy(status=404, reason="Nope") Response(404 Nope)
- as_str(include_body=False, separator='\\r\\n')[source]¶
The wire representation: the status line (with the
phrase), the header lines and optionally the body. Secret header values are NOT redacted here, unlike inrepr().- Parameters:
include_body (
bool, default:False) – append a blank line and the body (as text, viabody_str()); aBodyProduceris not consumed — a placeholder is shown insteadseparator (
str, default:'\\r\\n') – the string joining the lines (no trailing one)
- Return type:
- Returns:
e.g.
"HTTP/1.1 404 Not Found\r\nContent-Length: 0"
- __eq__(other)[source]¶
Responses are equal when their status, reason, headers, body and HTTP version are equal, each with the part’s own equality semantics (e.g. header name casing doesn’t matter). The body is compared as set:
b"x"and"x"are different bodies. Therequestreference and themetadict are metadata and not compared.
Body producers¶
Streaming abstractions for request and response bodies.
- class action0.req.body.BodyProducer(*args, **kwargs)[source]¶
The interface for streaming the bytes of a request or response body.
Implementations produce the body as a sequence of chunks — synchronously via
chunks()or asynchronously viaachunks()— and as a whole viaas_bytes().BytesBodyis the simplest implementation; file- and iterable-backed producers are planned.The protocol is
runtime_checkable, soisinstance(obj, BodyProducer)checks that the four methods exist.
- class action0.req.body.BytesBody(data)[source]¶
The simplest
BodyProducer: an in-memory bytes value, produced as a single chunk.Example:
>>> body = BytesBody(b"hello") >>> body.content_length() 5 >>> list(body.chunks()) [b'hello'] >>> body.as_bytes() b'hello'
- Parameters:
data (
bytes)data – the body bytes
- class action0.req.body.FileBody(source, chunk_size=65536)[source]¶
A
BodyProducerstreaming a file in chunks.The source can be a path or an already-open binary file object:
A path is opened freshly for every iteration, so the body is re-iterable (e.g. for retries) and no file descriptor is held between uses.
A file object is used as-is and never closed by this class. If it is seekable it is rewound to the start for every iteration (making it re-iterable too); if not, reading starts at the current position and the body is consumable only once.
achunks()performs every blocking file operation in the default thread pool viaasyncio.to_thread(), so the event loop is never stalled — without any extra dependency.- Parameters:
- Raises:
ValueError – if the chunk size is not positive
- class action0.req.body.IterableBody(iterable)[source]¶
A
BodyProducerwrapping an iterable of byte chunks, e.g. a generator. The total length is unknown (content_length()isNone— such a body would be sent chunked).WARNING: if the iterable is a generator (or any other single-use iterable), the body can be consumed only once — also by
as_bytes(). Pass a list of chunks for a re-iterable body.
- class action0.req.body.AsyncIterableBody(aiterable)[source]¶
A
BodyProducerwrapping an asynchronous iterable of byte chunks, e.g. an async generator proxying another stream. The total length is unknown (content_length()isNone).An asynchronous source is async-only: the synchronous accessors
chunks()andas_bytes()raise aRuntimeError— consume the body withachunks(). Like a generator, an async generator source is consumable only once.- Parameters:
aiterable (
AsyncIterable[bytes])aiterable – the byte chunks of the body
- chunks()[source]¶
- Raises:
RuntimeError – always — an async body has no synchronous chunks; use
achunks()- Return type:
- async achunks()[source]¶
- Return type:
- Returns:
the chunks as given by the wrapped asynchronous iterable
- as_bytes()[source]¶
- Raises:
RuntimeError – always — an async body has no synchronous bytes view; use
achunks()- Return type:
- action0.req.body.BodyTypes = bytes | str | action0.req.body.BodyProducer¶
Everything a request or response accepts as body: raw bytes, text (encoded with the Content-Type charset when accessed as bytes), or a streaming
BodyProducer.
Status¶
Constants for the known HTTP status codes.
- class action0.req.status.Status(*values)[source]¶
The known HTTP status codes as integer constants.
The members are the codes from the IANA HTTP status code registry as of 2026-08-07 with their registered reason phrases, plus the well-known
418 I'm a Teapotfrom RFC 2324. Being anenum.IntEnum, every member is its numeric code:>>> from action0.req import Status >>> print(Status.NOT_FOUND) 404 >>> Status.NOT_FOUND == 404 True >>> Status.NOT_FOUND.phrase 'Not Found' >>> Status(503).phrase 'Service Unavailable' >>> Status.NOT_FOUND.is_client_error True
RFC 9110 renamed two reason phrases; the pre-9110 names remain available as aliases:
>>> Status.PAYLOAD_TOO_LARGE is Status.CONTENT_TOO_LARGE True >>> Status.UNPROCESSABLE_ENTITY is Status.UNPROCESSABLE_CONTENT True
- CONTINUE = 100¶
- SWITCHING_PROTOCOLS = 101¶
- PROCESSING = 102¶
- EARLY_HINTS = 103¶
- OK = 200¶
- CREATED = 201¶
- ACCEPTED = 202¶
- NON_AUTHORITATIVE_INFORMATION = 203¶
- NO_CONTENT = 204¶
- RESET_CONTENT = 205¶
- PARTIAL_CONTENT = 206¶
- MULTI_STATUS = 207¶
- ALREADY_REPORTED = 208¶
- IM_USED = 226¶
- MULTIPLE_CHOICES = 300¶
- MOVED_PERMANENTLY = 301¶
- FOUND = 302¶
- SEE_OTHER = 303¶
- NOT_MODIFIED = 304¶
- USE_PROXY = 305¶
- TEMPORARY_REDIRECT = 307¶
- PERMANENT_REDIRECT = 308¶
- BAD_REQUEST = 400¶
- UNAUTHORIZED = 401¶
- PAYMENT_REQUIRED = 402¶
- FORBIDDEN = 403¶
- NOT_FOUND = 404¶
- METHOD_NOT_ALLOWED = 405¶
- NOT_ACCEPTABLE = 406¶
- PROXY_AUTHENTICATION_REQUIRED = 407¶
- REQUEST_TIMEOUT = 408¶
- CONFLICT = 409¶
- GONE = 410¶
- LENGTH_REQUIRED = 411¶
- PRECONDITION_FAILED = 412¶
- CONTENT_TOO_LARGE = 413¶
- URI_TOO_LONG = 414¶
- UNSUPPORTED_MEDIA_TYPE = 415¶
- RANGE_NOT_SATISFIABLE = 416¶
- EXPECTATION_FAILED = 417¶
- IM_A_TEAPOT = 418¶
- MISDIRECTED_REQUEST = 421¶
- UNPROCESSABLE_CONTENT = 422¶
- LOCKED = 423¶
- FAILED_DEPENDENCY = 424¶
- TOO_EARLY = 425¶
- UPGRADE_REQUIRED = 426¶
- PRECONDITION_REQUIRED = 428¶
- TOO_MANY_REQUESTS = 429¶
- REQUEST_HEADER_FIELDS_TOO_LARGE = 431¶
- UNAVAILABLE_FOR_LEGAL_REASONS = 451¶
- INTERNAL_SERVER_ERROR = 500¶
- NOT_IMPLEMENTED = 501¶
- BAD_GATEWAY = 502¶
- SERVICE_UNAVAILABLE = 503¶
- GATEWAY_TIMEOUT = 504¶
- HTTP_VERSION_NOT_SUPPORTED = 505¶
- VARIANT_ALSO_NEGOTIATES = 506¶
- INSUFFICIENT_STORAGE = 507¶
- LOOP_DETECTED = 508¶
- NOT_EXTENDED = 510¶
- NETWORK_AUTHENTICATION_REQUIRED = 511¶
- PAYLOAD_TOO_LARGE = 413¶
- UNPROCESSABLE_ENTITY = 422¶