A persistent cache for python requests
76
Response and request models provide cached versions of HTTP requests and responses that maintain compatibility with the standard requests library while adding cache-specific metadata and functionality. These models handle serialization, deserialization, and provide cache status information.
The main response object returned by cached requests, providing full compatibility with requests.Response while adding cache-specific properties and methods.
class CachedResponse:
"""
Cached response object that emulates requests.Response.
Provides all standard Response properties and methods plus
cache-specific functionality and metadata.
"""
def __init__(
self,
url: str = '',
status_code: int = 200,
reason: str = 'OK',
headers: Optional[Mapping[str, str]] = None,
content: bytes = b'',
encoding: Optional[str] = None,
history: Optional[List] = None,
request: Optional[AnyRequest] = None,
cookies: Optional[RequestsCookieJar] = None,
elapsed: Optional[timedelta] = None,
expires: Optional[datetime] = None,
**kwargs
):
"""
Create cached response object.
Parameters mirror requests.Response constructor plus:
- expires: When this cached response expires
"""
# Cache-specific properties
@property
def from_cache(self) -> bool:
"""True if response was served from cache, False if fresh."""
@property
def is_expired(self) -> bool:
"""True if cached response has expired."""
@property
def expires_delta(self) -> Optional[timedelta]:
"""Time until expiration (positive) or since expiration (negative)."""
@property
def expires_unix(self) -> Optional[float]:
"""Expiration time as Unix timestamp."""
@property
def size(self) -> int:
"""Size of cached response in bytes."""
# Cache management methods
def is_older_than(self, time: ExpirationTime) -> bool:
"""
Check if response is older than specified time.
Parameters:
- time: Time threshold in various formats (int, timedelta, datetime, str)
Returns:
True if response age exceeds threshold
"""
def reset_expiration(self, expire_after: ExpirationTime = None) -> None:
"""
Reset expiration time for cached response.
Parameters:
- expire_after: New expiration time (None for no expiration)
"""
# Standard requests.Response interface
@property
def text(self) -> str:
"""Response body as text."""
@property
def content(self) -> bytes:
"""Response body as bytes."""
def json(self, **kwargs) -> Any:
"""Parse response body as JSON."""
def iter_content(self, chunk_size: int = 1) -> Iterator[bytes]:
"""Iterate over response content in chunks."""
def iter_lines(self, **kwargs) -> Iterator[str]:
"""Iterate over response content line by line."""
def raise_for_status(self) -> None:
"""Raise HTTPError for bad status codes."""
# Standard response attributes
status_code: int
reason: str
url: str
headers: CaseInsensitiveDict
cookies: RequestsCookieJar
history: List
request: AnyRequest
elapsed: timedelta
encoding: Optional[str]
apparent_encoding: str
ok: boolBasic cached response usage:
from requests_cache import CachedSession
session = CachedSession('demo_cache', expire_after=3600)
# First request - fetched from server
response = session.get('https://httpbin.org/json')
print(f"From cache: {response.from_cache}") # False
print(f"Status: {response.status_code}") # 200
print(f"Size: {response.size} bytes")
# Second request - served from cache
response = session.get('https://httpbin.org/json')
print(f"From cache: {response.from_cache}") # True
print(f"Expires in: {response.expires_delta}")
# Check expiration status
if not response.is_expired:
data = response.json()
print("Using cached data:", data)
# Reset expiration
response.reset_expiration(expire_after=7200) # 2 hours from nowCache metadata inspection:
import requests_cache
from datetime import datetime, timedelta
session = requests_cache.CachedSession('cache')
response = session.get('https://api.example.com/data')
# Cache status
print(f"From cache: {response.from_cache}")
print(f"Expired: {response.is_expired}")
print(f"Size: {response.size} bytes")
# Expiration info
if response.expires_delta:
if response.expires_delta.total_seconds() > 0:
print(f"Expires in: {response.expires_delta}")
else:
print(f"Expired {abs(response.expires_delta)} ago")
# Age checking
if response.is_older_than('1 hour'):
print("Response is older than 1 hour")
if response.is_older_than(timedelta(minutes=30)):
print("Response is older than 30 minutes")Wrapper for non-cached responses that provides cache-related properties for consistency.
class OriginalResponse:
"""
Wrapper for requests.Response that adds cache-related properties.
Used for responses that bypass caching or are fetched fresh
from the server.
"""
@classmethod
def wrap_response(
cls,
response: Response,
actions: CacheActions
) -> 'OriginalResponse':
"""
Wrap a requests.Response with cache metadata.
Parameters:
- response: Original requests.Response
- actions: Cache actions that determined response handling
Returns:
OriginalResponse with cache properties
"""
@property
def from_cache(self) -> bool:
"""Always False for original responses."""
# Delegates all other attributes to wrapped responseCached version of HTTP requests that can be serialized and stored with responses.
class CachedRequest:
"""
Serializable request object that emulates requests.PreparedRequest.
Stores request data alongside cached responses for cache key
generation and request matching.
"""
def __init__(
self,
method: Optional[str] = None,
url: Optional[str] = None,
headers: Optional[CaseInsensitiveDict] = None,
body: Optional[bytes] = None,
**kwargs
):
"""
Create cached request object.
Parameters mirror requests.PreparedRequest constructor.
"""
def copy(self) -> 'CachedRequest':
"""Create a copy of the cached request."""
# Standard PreparedRequest interface
method: Optional[str]
url: Optional[str]
headers: CaseInsensitiveDict
body: Optional[bytes]
hooks: Dict[str, List]Low-level cached HTTP response for urllib3 compatibility.
class CachedHTTPResponse:
"""
Cached HTTP response that provides urllib3.HTTPResponse compatibility.
Used internally by the caching system to maintain compatibility
with the requests library's response processing pipeline.
"""
def __init__(
self,
body: bytes = b'',
headers: Optional[Mapping[str, str]] = None,
status: int = 200,
reason: Optional[str] = None,
version: int = 11,
**kwargs
):
"""Create cached HTTP response for urllib3 compatibility."""
def read(self, amt: Optional[int] = None) -> bytes:
"""Read response body."""
def stream(self, amt: int = 1024) -> Iterator[bytes]:
"""Stream response body in chunks."""
def close(self) -> None:
"""Close response (no-op for cached responses)."""Foundation classes providing common functionality for all model objects.
class RichMixin:
"""
Mixin providing rich repr formatting for model objects.
Adds pretty-printed string representations with syntax
highlighting when used in rich-enabled environments.
"""
def __rich_repr__(self):
"""Rich representation for pretty printing."""
def __repr__(self) -> str:
"""Standard string representation."""# Response type unions
AnyResponse = Union[OriginalResponse, CachedResponse]
"""Union type representing any response object (cached or original)."""
AnyRequest = Union[Request, PreparedRequest, CachedRequest]
"""Union type representing any request object."""
AnyPreparedRequest = Union[PreparedRequest, CachedRequest]
"""Union type for prepared request objects."""
# Content type for JSON-compatible data
DecodedContent = Union[str, bytes, int, float, bool, None, Dict, List]
"""JSON-compatible content types for serialization."""Understanding how responses flow through the caching system:
from_cache=Truefrom_cache=False# Example showing response lifecycle
from requests_cache import CachedSession
session = CachedSession('demo')
# First request - cache miss
response1 = session.get('https://httpbin.org/json')
print(type(response1)) # <class 'OriginalResponse'>
print(response1.from_cache) # False
# Second request - cache hit
response2 = session.get('https://httpbin.org/json')
print(type(response2)) # <class 'CachedResponse'>
print(response2.from_cache) # True
# Both provide same interface
print(response1.json() == response2.json()) # TrueResponse models integrate seamlessly with the serialization system:
from requests_cache import CachedSession
# Different serializers handle response data differently
json_session = CachedSession('json_cache', serializer='json')
pickle_session = CachedSession('pickle_cache', serializer='pickle')
# All serializers preserve response properties and methods
response = json_session.get('https://httpbin.org/json')
print(f"From cache: {response.from_cache}")
print(f"Data: {response.json()}")Install with Tessl CLI
npx tessl i tessl/pypi-requests-cacheevals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10