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
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, andPartitionedattributes - Typed Data Structures: Uses
dataclassesandenumsfor predictable, type-safe code with fullmypystrict mode support - Case-Insensitive Headers: Built on top of
multidict, allowing case-insensitive header lookups while preserving insertion order - Builders & Enums: Includes a comprehensive
HTTPHeaderenum (100+ standard headers) and aHeaderBuilderfactory 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
multidictfor 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
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 |
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyhttp_util-0.2.1.tar.gz.
File metadata
- Download URL: pyhttp_util-0.2.1.tar.gz
- Upload date:
- Size: 31.3 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
191b6bf6217122853d2d8935307f6cf44cda5234d32e07b0d6d1446d68df4e8b
|
|
| MD5 |
3c8daad1fa46b231a1e84c8a99ee3c49
|
|
| BLAKE2b-256 |
2234f6d5a86fd71294189fb6463e8dde2a4ba322b4cdafd64bd6572f4695e568
|
File details
Details for the file pyhttp_util-0.2.1-py3-none-any.whl.
File metadata
- Download URL: pyhttp_util-0.2.1-py3-none-any.whl
- Upload date:
- Size: 36.3 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
288959a0e90b2ed6d4715aa7b5d368e86cb124afc61f3507af10b441e2f5576d
|
|
| MD5 |
c17161ef601012e8ef29efe010299b59
|
|
| BLAKE2b-256 |
81cee5d387f9706dd33a8cdeb3858ce45668eebc5cf806c82586bd20918248b4
|