import hashlib import logging import time from pathlib import Path from flask import Flask, request from app.config import LOG_LEVEL logging.basicConfig( level=getattr(logging, LOG_LEVEL.upper(), logging.INFO), format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) app = Flask(__name__) # ── Static asset versioning ──────────────────────────────────────── # Each /static/ reference gets a ?v= query string so we can serve # it with `Cache-Control: immutable` and still bust the cache when bytes change. _FALLBACK_TOKEN = hashlib.sha1(str(time.time()).encode()).hexdigest()[:8] _static_hashes: dict[str, str] = {} def _hash_file(path: Path) -> str: h = hashlib.sha1() with path.open("rb") as fh: for chunk in iter(lambda: fh.read(65536), b""): h.update(chunk) return h.hexdigest()[:8] def static_hash(filename: str) -> str: if filename in _static_hashes: return _static_hashes[filename] path = Path(app.static_folder) / filename try: token = _hash_file(path) except OSError: logging.getLogger(__name__).warning( "static_hash: cannot hash %s, using fallback token", filename ) return _FALLBACK_TOKEN _static_hashes[filename] = token return token def static_v(filename: str) -> str: return f"/static/{filename}?v={static_hash(filename)}" _static_dir = Path(app.static_folder) if _static_dir.is_dir(): for _path in sorted(_static_dir.iterdir()): if _path.is_file(): try: _static_hashes[_path.name] = _hash_file(_path) except OSError: continue APP_VERSION = ( hashlib.sha1( "|".join(f"{n}:{h}" for n, h in sorted(_static_hashes.items())).encode() ).hexdigest()[:8] if _static_hashes else _FALLBACK_TOKEN ) app.jinja_env.globals["static_v"] = static_v @app.after_request def _add_cache_headers(response): if response.mimetype == "text/html": response.headers["Cache-Control"] = "no-cache, must-revalidate" return response if request.path.startswith("/static/") and request.args.get("v"): response.headers["Cache-Control"] = "public, max-age=31536000, immutable" return response from app import routes # noqa: E402, F401