Persistent cache implementation for httpx and httpcore following RFC 9111 specification
74
RFC 9111 compliant cache controller that implements HTTP caching logic, determines response cacheability, validates cached responses, and handles cache directives from HTTP headers.
The main controller class that implements HTTP caching specification logic for determining what to cache and when to serve cached responses.
class Controller:
def __init__(self, *, cacheable_methods=None, cacheable_status_codes=None,
cache_private=True, allow_heuristics=False, clock=None,
allow_stale=False, always_revalidate=False, force_cache=False,
key_generator=None):
"""
Initialize cache controller with caching policies.
Parameters:
- cacheable_methods: HTTP methods to cache (defaults to ["GET"])
- cacheable_status_codes: Status codes to cache (defaults to [200, 301, 308])
- cache_private: Whether to cache responses with private directives
- allow_heuristics: Enable heuristic caching for responses without explicit cache headers
- clock: Custom clock implementation for time-based operations
- allow_stale: Whether to serve stale responses when allowed
- always_revalidate: Always revalidate cached responses
- force_cache: Force caching regardless of cache headers
- key_generator: Custom function for generating cache keys
"""Usage Examples:
import hishel
# Default controller (GET requests, status codes 200/301/308)
controller = hishel.Controller()
# Conservative caching (only explicitly cacheable responses)
controller = hishel.Controller(
cacheable_methods=["GET", "HEAD"],
cacheable_status_codes=[200],
cache_private=False,
allow_heuristics=False
)
# Aggressive caching with heuristics
controller = hishel.Controller(
cacheable_methods=["GET", "HEAD", "POST"],
cacheable_status_codes=[200, 201, 204, 301, 308, 404],
allow_heuristics=True,
allow_stale=True
)
# Always revalidate for critical applications
controller = hishel.Controller(
always_revalidate=True,
force_cache=False
)
# Use with cache client
with hishel.CacheClient(controller=controller) as client:
response = client.get("https://api.example.com/data")Method to determine if a response can be cached based on request/response headers and controller configuration.
def is_cachable(self, request: Request, response: Response) -> bool:
"""
Determine whether the response may be cached.
Parameters:
- request: HTTP request object
- response: HTTP response object
Returns:
- bool: True if response can be cached, False otherwise
Implements RFC 9111 Section 3 logic for storing responses in caches.
"""Usage Examples:
from httpcore import Request, Response
import hishel
controller = hishel.Controller()
# Check if response is cacheable
request = Request(b"GET", b"https://api.example.com/data")
response = Response(200, [(b"cache-control", b"max-age=3600")])
if controller.is_cachable(request, response):
print("Response can be cached")
else:
print("Response should not be cached")Method to construct responses from cache, handling validation, freshness checks, and conditional requests.
def construct_response_from_cache(self, request: Request, response: Response,
original_request: Request) -> Request | Response | None:
"""
Determine how to use a cached response for the current request.
Parameters:
- request: Current HTTP request
- response: Cached HTTP response
- original_request: Original request that created the cached response
Returns:
- Response: Cached response is valid and can be used
- Request: Cached response needs revalidation (returns conditional request)
- None: Cached response cannot be used
Implements RFC 9111 Section 4 logic for constructing responses from caches.
"""Method to handle validation responses, particularly 304 Not Modified responses.
def handle_validation_response(self, old_response: Response, new_response: Response) -> Response:
"""
Handle incoming validation response from server.
Parameters:
- old_response: Previously cached response
- new_response: Validation response from server
Returns:
- Response: Updated response (old response with new headers for 304,
or new response for other status codes)
Implements RFC 9111 Section 4.3.4 logic for handling validation responses.
"""Usage Examples:
# Handle 304 Not Modified response
cached_response = Response(200, [(b"etag", b'"abc123"')])
validation_response = Response(304, [(b"cache-control", b"max-age=7200")])
updated_response = controller.handle_validation_response(
cached_response,
validation_response
)
# Returns cached_response with updated headers from validation_responseThe controller uses a default key generation strategy but supports custom key generators:
import hashlib
from httpcore import Request
def custom_key_generator(request: Request, body: bytes = b"") -> str:
"""
Generate cache key from request and body.
Parameters:
- request: HTTP request object
- body: Request body bytes
Returns:
- str: Cache key for the request
"""
# Custom logic here
url = request.url
method = request.method
return hashlib.sha256(method + url + body).hexdigest()
controller = hishel.Controller(key_generator=custom_key_generator)HEURISTICALLY_CACHEABLE_STATUS_CODES: tuple[int, ...] = (
200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, 501
)Status codes that are considered cacheable when heuristic caching is enabled and no explicit cache headers are present.
Custom clock implementations for testing and time manipulation:
class BaseClock:
def now(self) -> int:
"""Return current timestamp as integer seconds"""
class Clock(BaseClock):
def now(self) -> int:
"""Return current Unix timestamp"""Usage Examples:
import time
class TestClock(hishel.BaseClock):
def __init__(self, fixed_time=None):
self.fixed_time = fixed_time or time.time()
def now(self):
return int(self.fixed_time)
# Use custom clock for testing
test_clock = TestClock(fixed_time=1609459200) # 2021-01-01
controller = hishel.Controller(clock=test_clock)The controller provides fine-grained control over caching behavior:
Install with Tessl CLI
npx tessl i tessl/pypi-hisheldocs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10