Skip to content

blackbull.headers

blackbull.headers

Case-insensitive, ordered, multi-valued HTTP header store.

Provides:

  • Headers: satisfies the ASGI Iterable[tuple[bytes, bytes]] contract while adding get, getlist, case-insensitive lookup, append, and + concatenation.
  • HeaderList: type alias for Iterable[tuple[bytes, bytes]].

Headers

Ordered multi-valued HTTP header store.

Satisfies the ASGI Iterable[[byte string, byte string]] contract while also providing O(1) dict-like lookup.

Invariants:

  • Header names and values are always bytes (per ASGI spec).
  • Lookups are case-insensitive: the internal index is keyed on name.lower() (RFC 7230 §3.2 — header field names are case-insensitive). __contains__, __getitem__, getlist, and get accept any casing; iteration preserves the original casing of the input.
  • Insertion order of duplicate names is preserved (RFC 7230 §3.2.2).

Examples::

headers = Headers([(b'set-cookie', b'a=1'), (b'set-cookie', b'b=2')])

list(headers)
# [(b'set-cookie', b'a=1'), (b'set-cookie', b'b=2')]   # ASGI iteration

headers.getlist(b'set-cookie')
# [(b'set-cookie', b'a=1'), (b'set-cookie', b'b=2')]

headers.getlist(b'missing')
# []

headers.get(b'host')          # first value, or default
# b'localhost:8000'

__add__(other)

Return a new Headers containing all pairs from self then other.

__eq__(other)

Value equality on the ordered (name, value) pair list.

Two Headers are equal when they carry the same fields in the same order (RFC 7230 §3.2.2 — order is significant for repeated fields). Enables Connection round-trip equality.

__getitem__(name)

Return all pairs for name. Raises KeyError if absent.

append(name_or_pairs, value=None)

Append header(s) to the end of the list.

Two-argument form: append(name, value) — adds a single pair. One-argument form: append(pairs) — adds every pair in the iterable.

from_lowered(pairs) classmethod

Build from pairs whose names are already lowercase.

The caller must guarantee that; nothing here checks it, and a name containing uppercase would be indexed unreachably (every accessor lowercases before its fallback probe, so the field would be invisible to lookup while still appearing in iteration).

Two callers can guarantee it. http1_actor._parse lowercases each name while validating it, so re-lowercasing in __init__ recomputes a known answer. HTTP/2 field names are lowercase by protocol — RFC 9113 §8.2.1 makes an uppercase name malformed, and HeadersFrame.parse_payload rejects the frame before any pair reaches the header list.

Takes ownership of pairs rather than copying it; the parser builds a throwaway list per request, and the copy is the point of the shortcut. Do not pass a list you intend to keep mutating.

get(name, default=b'')

Return the first value for name, or default if absent.

Mirrors dict.get(key, default): single value, optional default. For headers that may repeat use getlist(name).

get_sf_dict(name)

Parse name as a Structured Field Dictionary (RFC 9651).

Multiple field lines are combined first. Returns an ordered dict of member name → Item / Inner List, or None if the field is absent or fails strict parsing (per RFC 9651 §4.2 the whole field is then ignored).

Example::

headers.get_sf_dict(b'priority')      # {'u': (2, {}), 'i': (True, {})}

get_sf_item(name)

Parse name as a Structured Field Item (RFC 9651).

Returns (bare_item, parameters), or None if the field is absent or fails strict parsing (per RFC 9651 §4.2 the whole field is then ignored).

Example::

headers.get_sf_item(b'deprecation')   # (Date(1659578233), {})

get_sf_list(name)

Parse name as a Structured Field List (RFC 9651).

Multiple field lines are combined first. Returns a list of Items / Inner Lists, or None if the field is absent or fails strict parsing (per RFC 9651 §4.2 the whole field is then ignored).

Example::

headers.get_sf_list(b'accept-query')  # [('a', {}), ('b', {})]

getlist(name)

Return all pairs for name, or [] if the header is absent.