Source code for flowno.io.headers

"""
HTTP header handling for Flowno's HTTP client.

This module provides a simple, case-insensitive HTTP header container class
that can be used to store, retrieve, and manipulate HTTP headers. It's used
by the :py:mod:`flowno.io.http_client` module for handling request and response
headers.

Example:
    >>> from flowno.io.headers import Headers
    >>> 
    >>> # Create headers collection
    >>> headers = Headers()
    >>> headers.set("Content-Type", "application/json")
    >>> headers.set("Accept-Encoding", ["gzip", "deflate"])
    >>> 
    >>> # Get a header value
    >>> print(headers.get("content-type"))
    application/json
    >>> 
    >>> # Headers are case-insensitive
    >>> print(headers.get("CONTENT-TYPE"))
    application/json
    >>> 
    >>> # Convert headers to a string for HTTP request
    >>> print(headers.stringify())
    content-type: application/json
    accept-encoding: gzip, deflate
"""

[docs] class Headers: """ Case-insensitive container for HTTP headers. This class provides methods for working with HTTP headers, ensuring that header names are handled case-insensitively as per HTTP specifications. It also handles automatic conversion of list values to comma-separated strings as required by the HTTP protocol. Examples: >>> headers = Headers() >>> headers.set("Content-Type", "application/json") >>> headers.get("content-type") 'application/json' # Using list values for headers that accept multiple values >>> headers.set("Accept", ["text/html", "application/json"]) >>> headers.get("Accept") 'text/html, application/json' """ def __init__(self): """Initialize an empty headers collection.""" self._headers: dict[str, str | list[str]] = {}
[docs] def set(self, name: str, value: str | list[str]) -> None: """ Set a header value. If the value is a list, it's joined with commas to create a single header value, which is the standard way to represent multiple values for a single header in HTTP. Args: name: Header name (case-insensitive) value: Header value or list of values Examples: >>> headers = Headers() >>> headers.set("Content-Type", "application/json") >>> headers.set("Accept-Encoding", ["gzip", "deflate"]) """ if isinstance(value, list): value = ", ".join(value) self._headers[name.lower()] = value
[docs] def get(self, name: str, default: str | list[str] | None = None) -> str | list[str] | None: """ Get a header value. Args: name: Header name (case-insensitive) default: Value to return if the header is not found Returns: The header value, or the default value if not found Examples: >>> headers = Headers() >>> headers.set("Content-Type", "application/json") >>> headers.get("content-type") 'application/json' >>> headers.get("nonexistent-header", "default-value") 'default-value' """ return self._headers.get(name.lower(), default)
[docs] def delete(self, name: str) -> None: """ Remove a header. Args: name: Header name (case-insensitive) Examples: >>> headers = Headers() >>> headers.set("X-Custom-Header", "value") >>> headers.delete("X-Custom-Header") >>> headers.get("X-Custom-Header") None """ if name.lower() in self._headers: del self._headers[name.lower()]
def __iter__(self): """ Return an iterator over header name-value pairs. Returns: Iterator yielding (name, value) tuples Examples: >>> headers = Headers() >>> headers.set("Content-Type", "application/json") >>> headers.set("Accept", "text/html") >>> for name, value in headers: ... print(f"{name}: {value}") content-type: application/json accept: text/html """ return iter(self._headers.items())
[docs] def stringify(self) -> str: """ Convert headers to a string suitable for an HTTP request. Returns: HTTP headers as a string with CRLF line endings Examples: >>> headers = Headers() >>> headers.set("Content-Type", "application/json") >>> headers.set("Accept", "text/html") >>> print(headers.stringify()) content-type: application/json accept: text/html """ return "\r\n".join(f"{name}: {value}" for name, value in self._headers.items())
[docs] def merge(self, headers: "Headers") -> None: """ Merge headers from another Headers instance. This will override any existing headers with the same names. Args: headers: Another Headers instance to merge from Examples: >>> headers1 = Headers() >>> headers1.set("Content-Type", "application/json") >>> >>> headers2 = Headers() >>> headers2.set("Accept", "text/html") >>> headers2.set("Content-Type", "text/plain") # Will override >>> >>> headers1.merge(headers2) >>> headers1.get("Content-Type") 'text/plain' >>> headers1.get("Accept") 'text/html' """ for name, value in headers: self.set(name, value)
__all__ = ["Headers"]