diff --git a/fastapi_cache/decorator.py b/fastapi_cache/decorator.py index 7df09e8..510ee0e 100644 --- a/fastapi_cache/decorator.py +++ b/fastapi_cache/decorator.py @@ -1,3 +1,4 @@ +import hashlib import logging import sys from functools import wraps @@ -66,6 +67,16 @@ def _locate_param( return param +def _compute_etag(data: bytes) -> str: + """Compute a deterministic ETag hash from cache data. + + Uses MD5 for speed since this is for caching, not security. + Unlike Python's built-in hash(), this produces consistent values + across different processes and service restarts. + """ + return hashlib.md5(data).hexdigest() + + def _uncacheable(request: Optional[Request]) -> bool: """Determine if this request should not be cached @@ -199,14 +210,14 @@ async def ensure_async_func(*args: P.args, **kwargs: P.kwargs) -> R: response.headers.update( { "Cache-Control": f"max-age={expire}", - "ETag": f"W/{hash(to_cache)}", + "ETag": f"W/{_compute_etag(to_cache)}", cache_status_header: "MISS", } ) else: # cache hit if response: - etag = f"W/{hash(cached)}" + etag = f"W/{_compute_etag(cached)}" response.headers.update( { "Cache-Control": f"max-age={ttl}",