From cb93dbe05a6bdcc98275da8737307ebd749abc26 Mon Sep 17 00:00:00 2001 From: Colin Maudry Date: Fri, 12 Jun 2026 13:58:50 +0200 Subject: [PATCH] feat: chargements de pages best-effort au boot (tableau, sources) (#78) Co-Authored-By: Claude Sonnet 4.6 --- src/figures.py | 8 ++++-- src/pages/tableau.py | 15 +++++++--- src/utils/__init__.py | 16 +++++++++++ tests/test_page_loads.py | 61 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 tests/test_page_loads.py diff --git a/src/figures.py b/src/figures.py index 4670723..966fd9b 100644 --- a/src/figures.py +++ b/src/figures.py @@ -1,6 +1,5 @@ from datetime import datetime from typing import Literal -from urllib.error import HTTPError, URLError import dash_bootstrap_components as dbc import dash_leaflet as dl @@ -120,9 +119,12 @@ def get_barchart_sources(lff: pl.LazyFrame, type_date: str): def get_sources_tables(source_path) -> html.Div: try: + if not source_path: + raise ValueError("SOURCE_STATS_CSV_PATH non défini") dff = pl.read_csv(source_path) - except (URLError, HTTPError): - return html.Div("Erreur de connexion") + except Exception as e: + logger.warning(f"Sources de données indisponibles ({e})") + return html.Div("Sources de données momentanément indisponibles.") dff = dff.with_columns( ( pl.lit(' float | None: + """Date de MAJ des données, best-effort, sans jamais lever (usage au boot).""" + try: + return get_last_modified(parquet_path) + except Exception as e: + logger.warning(f"Date de mise à jour des données indisponible ({e})") + if fallback_path: + try: + return os.path.getmtime(fallback_path) + except OSError: + pass + return None diff --git a/tests/test_page_loads.py b/tests/test_page_loads.py new file mode 100644 index 0000000..039090a --- /dev/null +++ b/tests/test_page_loads.py @@ -0,0 +1,61 @@ +import os + + +def test_update_timestamp_falls_back_to_db_mtime(tmp_path, monkeypatch): + import src.utils as u + from src.utils import get_data_update_timestamp + + def boom(*a, **k): + raise RuntimeError("net down") + + monkeypatch.setattr(u, "get_last_modified", boom) + fb = tmp_path / "decp.duckdb" + fb.write_bytes(b"x") + assert get_data_update_timestamp("http://x", str(fb)) == os.path.getmtime(str(fb)) + + +def test_update_timestamp_none_when_all_fail(monkeypatch): + import src.utils as u + from src.utils import get_data_update_timestamp + + def boom(*a, **k): + raise RuntimeError("net down") + + monkeypatch.setattr(u, "get_last_modified", boom) + assert get_data_update_timestamp("http://x", None) is None + + +def test_update_timestamp_nominal(monkeypatch): + import src.utils as u + from src.utils import get_data_update_timestamp + + monkeypatch.setattr(u, "get_last_modified", lambda p: 123.0) + assert get_data_update_timestamp("http://x", None) == 123.0 + + +def test_sources_tables_none_path(): + from src.figures import get_sources_tables + + div = get_sources_tables(None) + assert "indisponible" in str(div.children).lower() + + +def test_sources_tables_missing_file(): + from src.figures import get_sources_tables + + div = get_sources_tables("/does/not/exist.csv") + assert "indisponible" in str(div.children).lower() + + +def test_sources_tables_valid_csv(tmp_path): + from dash import dash_table + + from src.figures import get_sources_tables + + csv = tmp_path / "s.csv" + csv.write_text( + "nom,organisation,nb_marchés,nb_acheteurs,code,url,unique\n" + "Source A,Org A,5,2,XA,http://a,1\n" + ) + div = get_sources_tables(str(csv)) + assert isinstance(div.children, dash_table.DataTable)