"""Zwischenspeicher für API-Antworten — mit Redis, wenn es eins gibt. ## Warum das nicht mehr scheitern darf (Befund 28.08.2026) Hier stand ein nacktes `redis_client.get(key)`. Der Vorgabe-Host ist `redis` — der Dienstname aus `docker-compose.yml`. Im Container stimmt das; auf einem Windows-PC gibt es kein Redis, und jeder Aufruf endete mit: redis.exceptions.ConnectionError: Error 11001 connecting to redis:6379 Das riss den Pre-Scan mit. Der Commander sah die Folge, nicht die Ursache: Eine eingelegte Blu-ray blieb namenlos, und die Metadaten-Suche lief nie an. **Ein Zwischenspeicher ist eine Beschleunigung, keine Voraussetzung.** Ist keiner da, wird eben jedes Mal neu gefragt — TMDB und OMDb halten das aus. Deshalb fängt jeder Zugriff hier den Verbindungsfehler ab und tut so, als wäre der Eintrag nicht vorhanden. Das ist genau die Wahrheit: Er ist es nicht. ## Warum trotzdem einmal gemeldet wird Ein Zwischenspeicher, der still nie greift, ist eine unsichtbare Verlangsamung — und auf der VM wäre ein weggefallenes Redis ein echter Befund. Deshalb sagt `zustand()`, woran man ist, und die erste fehlgeschlagene Verbindung schreibt eine Zeile. Danach Ruhe: Eine Meldung je Anfrage wäre Lärm. """ import json import os from typing import Any, Optional from redis import Redis from redis.exceptions import RedisError redis_host = os.getenv("REDIS_HOST", "redis") redis_port = int(os.getenv("REDIS_PORT", "6379")) # Kurze Zeitgrenzen: Ein Zwischenspeicher, auf den man wartet, ist keiner. # Ohne sie hing jeder Aufruf am Standard-Timeout der Bibliothek. redis_client = Redis(host=redis_host, port=redis_port, decode_responses=True, socket_connect_timeout=1.5, socket_timeout=1.5) # Erst beim ersten Zugriff bekannt. None = noch nichts versucht. _erreichbar: Optional[bool] = None def _melde_einmal(fehler: Exception) -> None: """Beim ERSTEN Fehlschlag eine Zeile, danach Ruhe.""" global _erreichbar if _erreichbar is False: return _erreichbar = False print("Zwischenspeicher (%s:%s) nicht erreichbar — es wird ohne " "gearbeitet: %s" % (redis_host, redis_port, fehler)) def zustand() -> dict: """Woran man ist. Für Diagnose und Selbstauskunft.""" return {"host": redis_host, "port": redis_port, "erreichbar": _erreichbar} def init_cache() -> None: """Einmal anklopfen, damit der Zustand bekannt ist. Wirft nie.""" global _erreichbar try: redis_client.ping() _erreichbar = True except (RedisError, OSError) as e: _melde_einmal(e) def get(key: str) -> Optional[Any]: """Wert aus dem Zwischenspeicher. None = nicht da ODER kein Speicher.""" global _erreichbar try: value = redis_client.get(key) except (RedisError, OSError) as e: _melde_einmal(e) return None _erreichbar = True if value: try: return json.loads(value) except (TypeError, ValueError): # Ein kaputter Eintrag ist kein Grund, den Aufrufer scheitern zu # lassen — er holt sich die Antwort dann eben frisch. return None return None def set(key: str, value: Any, expire: Optional[int] = None) -> bool: """Wert ablegen. False heißt: ging nicht — und das ist in Ordnung.""" global _erreichbar try: serialized = json.dumps(value) if expire: return bool(redis_client.setex(key, expire, serialized)) return bool(redis_client.set(key, serialized)) except (RedisError, OSError) as e: _melde_einmal(e) return False except (TypeError, ValueError): # Nicht serialisierbar. Das ist ein Fehler des Aufrufers, aber keiner, # der eine Metadaten-Abfrage umwerfen darf. return False def delete(key: str) -> bool: try: return bool(redis_client.delete(key)) except (RedisError, OSError) as e: _melde_einmal(e) return False def clear() -> bool: try: return bool(redis_client.flushdb()) except (RedisError, OSError) as e: _melde_einmal(e) return False