CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-hishel

Persistent cache implementation for httpx and httpcore following RFC 9111 specification

74

1.48x
Overview
Eval results
Files

cache-controller.mddocs/

Cache Controller

RFC 9111 compliant cache controller that implements HTTP caching logic, determines response cacheability, validates cached responses, and handles cache directives from HTTP headers.

Capabilities

Cache Controller

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")

Cacheability Determination

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")

Cache Response Construction

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.
    """

Validation Response Handling

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_response

Cache Key Generation

The 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)

Caching Constants

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.

Clock Implementations

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)

Cache Behavior Control

The controller provides fine-grained control over caching behavior:

  • Method-based caching: Control which HTTP methods are cached
  • Status code filtering: Specify which response status codes to cache
  • Private response handling: Control caching of responses with Cache-Control: private
  • Heuristic caching: Enable caching based on heuristics when no explicit cache headers
  • Stale response serving: Allow serving stale responses when permitted
  • Forced revalidation: Always revalidate cached responses
  • Forced caching: Cache responses regardless of cache headers
  • Custom key generation: Use custom logic for generating cache keys

Install with Tessl CLI

npx tessl i tessl/pypi-hishel

docs

cache-clients.md

cache-controller.md

cache-transports.md

http-headers.md

index.md

serializers.md

storage-backends.md

testing-utilities.md

tile.json