Skip to main content

is a robust, strictly-typed Python library designed to simplify and standardize the manipulation of HTTP headers, authentication, and timeout configurations. It ensures compliance with RFC 7230 and provides a developer-friendly API for building and validating HTTP-related data structures.

Project description

pyhttp-util

Python 3.10+ License: MIT Typed Code style: ruff

pyhttp-util is a robust, strictly-typed Python library designed to simplify and standardize the manipulation of HTTP headers, authentication, cookies, and timeout configurations. It ensures compliance with RFC 7230 and RFC 6265, and provides a developer-friendly API for building and validating HTTP-related data structures.

Table of Contents

Features

  • Strict Validation: Enforces RFC 7230 rules for header names and values, preventing common HTTP vulnerabilities (like CRLF injection)
  • Cookie Support: Full RFC 6265 compliant cookie handling with SameSite, Secure, HttpOnly, and Partitioned attributes
  • Typed Data Structures: Uses dataclasses and enums for predictable, type-safe code with full mypy strict mode support
  • Case-Insensitive Headers: Built on top of multidict, allowing case-insensitive header lookups while preserving insertion order
  • Builders & Enums: Includes a comprehensive HTTPHeader enum (100+ standard headers) and a HeaderBuilder factory to avoid magic strings and typos
  • Authentication: Utilities for encoding and decoding HTTP Basic Auth and Bearer token credentials
  • Timeout Configuration: A unified, immutable structure for managing connection, socket, and total timeouts
  • Zero Runtime Dependencies: Only depends on multidict for case-insensitive dictionary support

Installation

pip install pyhttp-util

Or with uv:

uv add pyhttp-util

Quick Start

from pyhttp_util import (
    Headers,
    Header,
    HeaderBuilder,
    HTTPHeader,
    BasicAuth,
    BearerAuth,
    Cookie,
    CookieJar,
    SameSite,
    Timeout,
    HTTPStatus,
    StatusCodeValidator,
)

# Create headers
headers = Headers()
headers.add(HeaderBuilder.content_type("application/json"))
headers.add(HeaderBuilder.authorization(BasicAuth("user", "pass").encode()))
headers.add_raw("X-Custom-Header", "custom-value")

# Case-insensitive access
print(headers["content-type"])  # application/json

# Create cookies
cookie = Cookie(
    name="session",
    value="abc123",
    secure=True,
    httponly=True,
    samesite=SameSite.STRICT,
)

# Configure timeouts
timeout = Timeout(total=30.0, connect=5.0, sock_read=10.0)

# Check status codes
if StatusCodeValidator.is_success(200):
    print("Request was successful")

status = HTTPStatus.NOT_FOUND
print(f"{status.value} {status.phrase}")  # 404 Not Found

Usage

1. Working with Headers

The core of the library is the Headers collection and the Header dataclass.

from pyhttp_util.headers import Headers, Header, HeaderBuilder, HTTPHeader

# --- Creating Headers ---

# Using the Builder (Recommended for standard headers)
h1 = HeaderBuilder.content_type("application/json")
h2 = HeaderBuilder.user_agent("MyClient/1.0")
h3 = HeaderBuilder.accept("text/html, application/json")

# Manual creation with string name
h4 = Header("X-Custom-Token", "123456")

# Using the HTTPHeader enum directly
h5 = Header(HTTPHeader.CACHE_CONTROL, "no-cache, no-store")

# --- Using the Headers Collection ---

headers = Headers()
headers.add(h1)
headers.add(h2)
headers.add_raw("X-Request-ID", "req-12345")

# Case-insensitive access
print(headers["content-type"])      # application/json
print(headers.get("USER-AGENT"))    # MyClient/1.0
print(headers.get("missing"))       # None

# Check existence
if "content-type" in headers:
    print("Content-Type is set")

# Get all values for a header (useful for Set-Cookie, etc.)
all_values = headers.get_all("Set-Cookie")

# Iterate over headers
for header in headers:
    print(f"{header.name}: {header.value}")

# Dictionary-like operations
headers["X-New-Header"] = "new-value"  # Add or replace
del headers["X-Request-ID"]            # Remove

# Convert to different formats
as_dict = headers.to_dict()      # CIMultiDict
as_list = headers.to_list()      # list[Header]
as_tuples = headers.to_tuples()  # list[tuple[name, value]]

# Copy headers
headers_copy = headers.copy()

# Merge headers (keeps existing, adds missing)
other_headers = Headers()
other_headers.add_raw("Accept-Language", "en-US")
headers.merge(other_headers)

Building Headers from Different Sources

# From a dictionary
headers = Headers.build_from_dict({
    "Content-Type": "application/json",
    "Accept": "application/json",
})

# From a list of tuples
headers = Headers.build_from_tuples([
    ("Content-Type", "application/json"),
    ("Accept", "application/json"),
])

# From a list of Header objects
headers = Headers.build_from_list([
    Header(HTTPHeader.CONTENT_TYPE, "application/json"),
    Header(HTTPHeader.ACCEPT, "application/json"),
])

# All build methods support allow_duplicates parameter
headers = Headers.build_from_dict(
    {"Content-Type": "application/json"},
    allow_duplicates=True
)

2. Validation

The library validates headers upon creation to ensure RFC 7230 compliance and security.

from pyhttp_util.headers import Header, Headers
from pyhttp_util.headers.validator import (
    RFC7230Validator,
    ValidationError,
    ValidationResult,
)

# --- Automatic Validation on Creation ---

try:
    # CRLF injection protection - raises ValidationError
    invalid = Header("X-Bad", "value\r\nSet-Cookie: evil")
except ValidationError as e:
    print(f"Blocked: {e}")

try:
    # Invalid header name characters - raises ValidationError
    invalid = Header("Invalid Header", "value")  # Space not allowed
except ValidationError as e:
    print(f"Blocked: {e}")

# --- Duplicate Header Validation ---

# Strict mode (default) - no duplicates allowed except Set-Cookie
strict_headers = Headers(allow_duplicates=False)
strict_headers.add_raw("Content-Type", "application/json")

try:
    strict_headers.add_raw("Content-Type", "text/plain")  # Raises!
except ValidationError as e:
    print(f"Duplicate blocked: {e}")

# Lenient mode - duplicates allowed
lenient_headers = Headers(allow_duplicates=True)
lenient_headers.add_raw("X-Custom", "value1")
lenient_headers.add_raw("X-Custom", "value2")  # OK

# --- Manual Validation ---

# Validate header name
result: ValidationResult = RFC7230Validator.validate_field_name("Content-Type")
if result:
    print("Valid header name")
else:
    print(f"Invalid: {result.error}")

# Validate header value
result = RFC7230Validator.validate_field_value("application/json")

# Validate both name and value
result = RFC7230Validator.validate_header_field("Content-Type", "application/json")

# Validate header size (default max: 8192 bytes)
result = RFC7230Validator.validate_header_size(
    "X-Large-Header",
    "x" * 10000,
    max_size=8192
)

# Raise on error instead of returning ValidationResult
RFC7230Validator.validate_field_name("Content-Type", raise_on_error=True)

3. Authentication

Handle HTTP Basic and Bearer authentication safely.

from pyhttp_util.auth import BasicAuth, BearerAuth
from pyhttp_util.headers import Header

# === Basic Authentication ===

# Encoding credentials (client side)
credentials = BasicAuth(login="username", password="secret123")
auth_value = credentials.encode()
print(auth_value)  # Basic dXNlcm5hbWU6c2VjcmV0MTIz

# Use as string directly
print(str(credentials))  # Basic dXNlcm5hbWU6c2VjcmV0MTIz

# Decoding credentials (server side)
received = "Basic dXNlcm5hbWU6c2VjcmV0MTIz"
decoded = BasicAuth.decode(received)
print(f"User: {decoded.login}")      # username
print(f"Password: {decoded.password}")  # secret123

# Decode from Header object
header = Header("Authorization", "Basic dXNlcm5hbWU6c2VjcmV0MTIz")
decoded = BasicAuth.decode(header)

# Custom encoding (default is latin1)
credentials = BasicAuth(login="user", password="пароль", encoding="utf-8")

# Validation: login cannot contain ":"
try:
    invalid = BasicAuth(login="user:name", password="pass")  # Raises!
except ValueError as e:
    print(f"Invalid: {e}")

# === Bearer Authentication ===

# Encoding token
token_auth = BearerAuth(token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")
auth_value = token_auth.encode()
print(auth_value)  # Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

# Decoding token
received = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
decoded = BearerAuth.decode(received)
print(f"Token: {decoded.token}")

# Validation: token cannot be empty or contain whitespace
try:
    invalid = BearerAuth(token="")  # Raises!
except ValueError as e:
    print(f"Invalid: {e}")

4. Cookies

Full RFC 6265 compliant cookie handling.

from datetime import datetime, timezone, timedelta
from pyhttp_util.headers import Cookie, CookieJar, SameSite

# === Creating Cookies ===

# Simple session cookie
session = Cookie(name="session_id", value="abc123")

# Secure cookie with all attributes
secure_cookie = Cookie(
    name="auth_token",
    value="xyz789",
    domain=".example.com",
    path="/api",
    expires=datetime.now(timezone.utc) + timedelta(days=7), # or just provide seconds to expiration
    max_age=604800,  # 7 days in seconds
    secure=True,
    httponly=True,
    samesite=SameSite.STRICT,
    partitioned=False,
)

# SameSite options
Cookie(name="lax", value="1", samesite=SameSite.LAX)
Cookie(name="strict", value="1", samesite=SameSite.STRICT)
Cookie(name="none", value="1", samesite=SameSite.NONE, secure=True)  # Secure required!

# Convert to Set-Cookie header value
print(str(secure_cookie))
# auth_token=xyz789; Domain=.example.com; Path=/api; Expires=...; Max-Age=604800; Secure; HttpOnly; SameSite=Strict

# === Cookie Validation ===

try:
    # SameSite=None requires Secure
    invalid = Cookie(name="bad", value="1", samesite=SameSite.NONE, secure=False)
except Exception as e:
    print(f"Validation error: {e}")

# === Using CookieJar ===

jar = CookieJar()

# Add cookies
jar.add(Cookie(name="user_id", value="12345", domain=".example.com"))
jar.add(Cookie(name="prefs", value="dark_mode", path="/settings"))
jar.add(Cookie(
    name="temp",
    value="data",
    max_age=3600,  # expires is auto-calculated
))

# Filter cookies for a request
matching = jar.filter(
    domain="www.example.com",
    path="/settings/profile",
    secure=True,
)

# Get Cookie header value for request
cookie_header = jar.output(
    domain="www.example.com",
    path="/api/users",
    secure=True,
)
print(cookie_header)  # user_id=12345; prefs=dark_mode

# Remove a cookie
jar.discard(name="temp", domain=None, path="/")

# Clear all cookies
jar.clear()

# Iterate over cookies
for cookie in jar:
    print(f"{cookie.name}={cookie.value}")

# Count cookies
print(f"Total cookies: {len(jar)}")

5. Timeouts

Configure timeouts for HTTP clients with an immutable, validated structure.

from pyhttp_util.timeout import Timeout

# === Creating Timeouts ===

# Simple total timeout
timeout = Timeout.create(total=30.0)

# Granular configuration
timeout = Timeout(
    total=60.0,       # Total operation timeout
    connect=5.0,      # Connection establishment timeout
    sock_connect=3.0, # Socket connection phase timeout
    sock_read=10.0,   # Socket read timeout
)

# All values are optional (None = no timeout)
timeout = Timeout(connect=5.0)  # Only connect timeout

# === Validation ===

# Negative values are not allowed
try:
    invalid = Timeout(total=-1.0)  # Raises ValueError!
except ValueError as e:
    print(f"Invalid: {e}")

# === Normalizing Different Timeout Types ===

# From int/float (becomes total timeout)
timeout = Timeout.normalize(30)      # Timeout(total=30)
timeout = Timeout.normalize(30.5)    # Timeout(total=30.5)

# From existing Timeout (returns as-is)
existing = Timeout(total=60.0)
timeout = Timeout.normalize(existing)  # Same object

# From any object with timeout attributes (duck typing)
class CustomTimeout:
    total = 30.0
    connect = 5.0
    sock_connect = None
    sock_read = 10.0

timeout = Timeout.normalize(CustomTimeout())

# === Immutability ===

timeout = Timeout(total=30.0)
# timeout.total = 60.0  # Raises FrozenInstanceError!

# Create a new timeout instead
new_timeout = Timeout(
    total=60.0,
    connect=timeout.connect,
    sock_connect=timeout.sock_connect,
    sock_read=timeout.sock_read,
)

6. Status Codes

Work with HTTP status codes using typed enums and validation.

from pyhttp_util.status import HTTPStatus, StatusCodeValidator, HTTPStatusError

# === Using HTTPStatus Enum ===

# Access standard status codes
status = HTTPStatus.OK
print(f"{status.value} {status.phrase}")  # 200 OK
print(f"Category: {status.category}")     # SUCCESSFUL

# Check status categories
if HTTPStatus.NOT_FOUND.is_client_error():
    print("4xx client error")

if HTTPStatus.INTERNAL_SERVER_ERROR.is_server_error():
    print("5xx server error")

if HTTPStatus.CREATED.is_success():
    print("2xx successful response")

# === Status Code Validation ===

# Validate status codes
print(StatusCodeValidator.is_valid(200))     # True
print(StatusCodeValidator.is_valid(999))     # False

# Check specific categories
print(StatusCodeValidator.is_informational(100))  # True (1xx)
print(StatusCodeValidator.is_success(200))        # True (2xx)
print(StatusCodeValidator.is_redirect(301))       # True (3xx)
print(StatusCodeValidator.is_client_error(404))   # True (4xx)
print(StatusCodeValidator.is_server_error(500))  # True (5xx)

# Raise for status (like requests)
try:
    StatusCodeValidator.raise_for_status(404)
except HTTPStatusError as e:
    print(f"HTTP {e.status.value} {e.status.phrase}: {e}")

# === Working with Exceptions ===

# Create specific exceptions
error = HTTPStatusError(status=HTTPStatus.TOO_MANY_REQUESTS, message="Rate limit exceeded")
print(error)  # HTTP 429 Too Many Requests: Rate limit exceeded

# Use built-in exception classes
from pyhttp_util.status.exceptions import (
    ClientError,
    ServerError,
    NotFoundError,
    UnauthorizedError,
    ForbiddenError,
    TooManyRequestsError,
    InternalServerError,
)

# Raise specific errors
if response.status == 404:
    raise NotFoundError("Resource not found")

if response.status == 401:
    raise UnauthorizedError("Authentication required")

if response.status == 429:
    raise TooManyRequestsError("Rate limit exceeded")

# === Status Code Utilities ===

# Get status by code
status = HTTPStatus.from_code(201)
print(status)  # 201 Created

# Get all codes in a category
success_codes = HTTPStatus.get_all_in_category("SUCCESSFUL")
print([code.value for code in success_codes])  # [200, 201, 202, 203, 204, 205, 206]

# Check if code is standard
print(HTTPStatus.is_standard(200))  # True
print(HTTPStatus.is_standard(1024))  # False

7. Methods

Work with HTTP methods using typed enums and descriptions.

from pyhttp_util.method import HTTPMethod

# === Using HTTPMethod Enum ===

# Access standard methods
method = HTTPMethod.GET
print(method)              # GET
print(method.description)  # Retrieve the target.

# Compare with strings (inherited from str)
if method == "GET":
    print("It's a GET request")

# Use in requests (example)
# request(method=HTTPMethod.POST, url=...)

API Reference

Headers Module

Class Description
Header Immutable dataclass representing a single HTTP header
Headers Mutable collection of headers with case-insensitive lookups
HTTPHeader Enum of 100+ standard HTTP header names
HeaderBuilder Static factory methods for creating standard headers
HeadersDict TypedDict for type-safe header dictionaries

Cookies Module

Class Description
Cookie Immutable dataclass representing an HTTP cookie
CookieJar Container for managing multiple cookies
SameSite Enum for SameSite attribute (LAX, STRICT, NONE)

Auth Module

Class Description
BasicAuth HTTP Basic Authentication encoding/decoding
BearerAuth HTTP Bearer Token encoding/decoding

Timeout Module

Class Description
Timeout Immutable timeout configuration

Status Module

Class Description
HTTPStatus Enum of all standard HTTP status codes
HTTPStatusError Base exception for HTTP status errors
ClientError Base class for 4xx status errors
ServerError Base class for 5xx status errors
StatusCodeValidator Static methods for status code validation
InformationalResponse 1xx informational response

Validator Module

Class Description
RFC7230Validator Static methods for RFC 7230 validation
ValidationError Exception raised on validation failure
ValidationResult Result object with valid and error attributes

Method Module

Class Description
HTTPMethod Enum of standard HTTP methods (GET, POST, etc.)

Development

Prerequisites

  • Python 3.10+
  • uv (recommended) or pip

Setup

# Clone the repository
git clone https://github.com/GrehBan/pyhttp-util.git
cd pyhttp-util

# Install dependencies
uv sync

Running Linters

# Check code style
uv run ruff check .

# Format code
uv run ruff format .

# Type checking
uv run mypy pyhttp_util

License

MIT License - see LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyhttp_util-0.2.2.tar.gz (32.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyhttp_util-0.2.2-py3-none-any.whl (38.6 kB view details)

Uploaded Python 3

File details

Details for the file pyhttp_util-0.2.2.tar.gz.

File metadata

  • Download URL: pyhttp_util-0.2.2.tar.gz
  • Upload date:
  • Size: 32.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.22 {"installer":{"name":"uv","version":"0.9.22","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"43","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for pyhttp_util-0.2.2.tar.gz
Algorithm Hash digest
SHA256 5da3df9a838c367d696a267499c1b5787f5bb5186b096b68a589fb6d65f7f726
MD5 d450ddabfe2d9db61c2cf1fb3269811b
BLAKE2b-256 934f432e262e2a28a8b0c6f4fd402f69a7f063b72e240f97852a60f1290fd6ee

See more details on using hashes here.

File details

Details for the file pyhttp_util-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: pyhttp_util-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 38.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.22 {"installer":{"name":"uv","version":"0.9.22","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"43","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for pyhttp_util-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 6f77ce0a6749817be48a4e06ce0a72e46106c4548b0310c058d7e94d0cf574f0
MD5 aac634b058355f098ff250f66bfc7720
BLAKE2b-256 1599ca575bb9c3a6e302168724fbc83932008010bec5cab9bb5e1d3072a9e664

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page