"""In-memory cache OAuth Bearer tokenů PPL CPL API. PPL vydá max. 12 tokenů za minutu a token platí 30 minut — generovat token per-request nelze. Cache je klíčovaná SHA-256 hashem přihlašovacích údajů (samotné údaje se neukládají) a token se obnovuje s předstihem před expirací. Per-key asyncio.Lock brání souběžnému vyžádání tokenu pro stejné údaje (thundering herd při paralelních requestech). """ import asyncio import time from .config import TOKEN_REFRESH_MARGIN_SECONDS # cache_key -> (access_token, expires_at_monotonic) _tokens: dict[str, tuple[str, float]] = {} _locks: dict[str, asyncio.Lock] = {} def _lock_for(cache_key: str) -> asyncio.Lock: lock = _locks.get(cache_key) if lock is None: lock = asyncio.Lock() _locks[cache_key] = lock return lock def get_cached(cache_key: str) -> str | None: entry = _tokens.get(cache_key) if entry is None: return None token, expires_at = entry if time.monotonic() >= expires_at: _tokens.pop(cache_key, None) return None return token def store(cache_key: str, token: str, expires_in_seconds: float) -> None: expires_at = time.monotonic() + max( expires_in_seconds - TOKEN_REFRESH_MARGIN_SECONDS, 30.0 ) _tokens[cache_key] = (token, expires_at) def invalidate(cache_key: str) -> None: _tokens.pop(cache_key, None) async def acquire_lock(cache_key: str) -> asyncio.Lock: return _lock_for(cache_key)