feat: schéma résilient URL→cache + suppression DATA_SCHEMA_LOCAL (#78)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Colin Maudry
2026-06-12 13:26:03 +02:00
parent 21c65c34fb
commit 42ba73cd36
6 changed files with 603 additions and 29 deletions
+50 -28
View File
@@ -2,7 +2,6 @@ import json
import logging
import os
from collections import OrderedDict
from pathlib import Path
import httpx
import polars as pl
@@ -65,34 +64,57 @@ def get_departement_region(code_postal: str | None):
return "", "", ""
def _validate_schema(raw) -> dict | None:
if isinstance(raw, dict) and isinstance(raw.get("fields"), list) and raw["fields"]:
return raw
return None
def _fetch_remote_schema(url: str | None) -> dict | None:
if not url:
return None
try:
raw = get(url, follow_redirects=True).raise_for_status().json()
except (httpx.HTTPError, json.JSONDecodeError) as e:
logger.error(f"Schéma distant indisponible ({url}) : {e}")
return None
return _validate_schema(raw)
def _load_schema_file(path: str) -> dict | None:
if not path or not os.path.exists(path):
return None
try:
with open(path) as f:
raw = json.load(f)
except (OSError, json.JSONDecodeError) as e:
logger.error(f"Schéma local illisible ({path}) : {e}")
return None
return _validate_schema(raw)
def _persist_schema_cache(raw: dict, path: str) -> None:
if not path:
return
try:
tmp = f"{path}.tmp"
with open(tmp, "w") as f:
json.dump(raw, f)
os.replace(tmp, path)
except OSError as e:
logger.warning(f"Écriture du cache schéma échouée ({path}) : {e}")
def get_data_schema() -> dict:
# Récupération du schéma des données tabulaires
url = os.getenv("DATA_SCHEMA_PATH")
local_path = Path(os.getenv("DATA_SCHEMA_LOCAL", ""))
original_schema = {}
if url:
try:
original_schema: dict = get(url, follow_redirects=True).json()
except (
httpx.ReadTimeout,
httpx.ReadError,
httpx.ConnectError,
httpx.ConnectTimeout,
):
logger.error(f"Erreur HTTP lors de la récupération du schéma ({url})")
if os.path.exists(local_path) and original_schema == {}:
with open(local_path) as f:
original_schema: dict = json.load(f)
logger.info(f"Utilisation du schéma local ({local_path})")
new_schema = OrderedDict()
for col in original_schema["fields"]:
new_schema[col["name"]] = col
return new_schema
cache_path = os.getenv("DATA_SCHEMA_CACHE", "./schema.cache.json")
raw = _fetch_remote_schema(os.getenv("DATA_SCHEMA_PATH"))
if raw is not None:
_persist_schema_cache(raw, cache_path)
else:
raw = _load_schema_file(cache_path)
if raw is None:
raise RuntimeError("Aucun schéma disponible (ni distant ni cache).")
return OrderedDict((c["name"], c) for c in raw["fields"])
def prepare_dashboard_data(**filter_params) -> pl.DataFrame: