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:
+50
-28
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user