docs: spec surfaces C/D (chargements pages au boot) (#78)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -177,6 +177,62 @@ Helpers :
|
|||||||
un échec d'écriture est loggé mais **non bloquant** (le schéma en mémoire reste
|
un échec d'écriture est loggé mais **non bloquant** (le schéma en mémoire reste
|
||||||
valide).
|
valide).
|
||||||
|
|
||||||
|
### Invariant 3 — Chargements au niveau module des pages
|
||||||
|
|
||||||
|
> L'import d'une page (exécuté au boot via `use_pages`) ne doit jamais tuer le
|
||||||
|
> démarrage à cause d'une ressource externe KO. Une ressource indisponible
|
||||||
|
> dégrade gracieusement l'affichage.
|
||||||
|
|
||||||
|
Audit des chargements à l'import (tous les `layout` de pages sont au niveau
|
||||||
|
module ⇒ leur contenu s'exécute au boot). Deux points de rupture **externes** :
|
||||||
|
|
||||||
|
**C — `src/pages/tableau.py:36-38`.** `get_last_modified(URL parquet)` fait un
|
||||||
|
HTTP HEAD **sans gestion d'erreur** (URL injoignable, en-tête `last-modified`
|
||||||
|
absent) ⇒ import KO ⇒ boot KO. C'est le même piège que `db.py`, mais dans une page.
|
||||||
|
|
||||||
|
Correctif : un helper best-effort dans `src/utils/__init__.py` qui ne lève jamais
|
||||||
|
et retombe sur le mtime du DuckDB (garanti présent par l'Invariant 1) :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_data_update_timestamp(parquet_path: str, fallback_path: str | None = None) -> 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
|
||||||
|
```
|
||||||
|
|
||||||
|
`tableau.py` l'utilise et gère le cas `None` (affiche « date inconnue »,
|
||||||
|
`update_date_iso = ""`).
|
||||||
|
|
||||||
|
**D — `src/pages/a-propos.py:103`.** `get_sources_tables(SOURCE_STATS_CSV_PATH)`
|
||||||
|
(`src/figures.py:121`) fait `pl.read_csv(source_path)` mais ne rattrape que
|
||||||
|
`URLError, HTTPError` — pas les erreurs Polars, ni `source_path` vide/`None`, ni
|
||||||
|
fichier absent ⇒ import KO ⇒ boot KO.
|
||||||
|
|
||||||
|
Correctif : élargir le `except` et gérer le chemin vide :
|
||||||
|
|
||||||
|
```python
|
||||||
|
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 Exception as e:
|
||||||
|
logger.warning(f"Sources de données indisponibles ({e})")
|
||||||
|
return html.Div("Sources de données momentanément indisponibles.")
|
||||||
|
... # suite inchangée
|
||||||
|
```
|
||||||
|
|
||||||
|
Hors périmètre des pages : `data/departements.json` + `.geojson` (fichiers
|
||||||
|
in-repo apportés par `git pull`, pas pilotés par env/URL — voir Hors périmètre).
|
||||||
|
|
||||||
## Tests (TDD)
|
## Tests (TDD)
|
||||||
|
|
||||||
Couvrir chaque branche de fallback. Sans dépendre du réseau réel.
|
Couvrir chaque branche de fallback. Sans dépendre du réseau réel.
|
||||||
@@ -200,11 +256,24 @@ Couvrir chaque branche de fallback. Sans dépendre du réseau réel.
|
|||||||
Mocker `get_last_modified` / `build_database` / `httpx.get` ; utiliser des fichiers
|
Mocker `get_last_modified` / `build_database` / `httpx.get` ; utiliser des fichiers
|
||||||
DuckDB et schéma temporaires (`tmp_path`).
|
DuckDB et schéma temporaires (`tmp_path`).
|
||||||
|
|
||||||
|
**Chargements de pages (Invariant 3) :**
|
||||||
|
|
||||||
|
11. `get_data_update_timestamp` : `get_last_modified` lève + `fallback_path`
|
||||||
|
existant ⇒ retourne le mtime du fallback (pas d'exception).
|
||||||
|
12. `get_data_update_timestamp` : tout KO (lève + pas de fallback) ⇒ `None`.
|
||||||
|
13. `get_data_update_timestamp` : cas nominal ⇒ retourne la valeur de
|
||||||
|
`get_last_modified` (mocké).
|
||||||
|
14. `get_sources_tables(None)` ⇒ `html.Div` de repli (pas d'exception).
|
||||||
|
15. `get_sources_tables("/inexistant.csv")` ⇒ `html.Div` de repli.
|
||||||
|
16. `get_sources_tables(<csv valide>)` ⇒ `html.Div` contenant la `DataTable`.
|
||||||
|
|
||||||
## Hors périmètre
|
## Hors périmètre
|
||||||
|
|
||||||
- Séparation des process API / Web (reportée — voir plus haut).
|
- Séparation des process API / Web (reportée — voir plus haut).
|
||||||
- Surveillance / alerting externe (les logs `error`/`critical` suffisent pour ce lot).
|
- Surveillance / alerting externe (les logs `error`/`critical` suffisent pour ce lot).
|
||||||
- Validation fine du contenu du parquet au-delà de « lisible par Polars/DuckDB ».
|
- Validation fine du contenu du parquet au-delà de « lisible par Polars/DuckDB ».
|
||||||
|
- Durcissement des `open()` in-repo (`data/departements.json` + `.geojson`) :
|
||||||
|
fichiers versionnés, apportés par `git pull`, jamais pilotés par env/URL (YAGNI).
|
||||||
|
|
||||||
## Variables d'environnement
|
## Variables d'environnement
|
||||||
|
|
||||||
@@ -215,6 +284,7 @@ DuckDB et schéma temporaires (`tmp_path`).
|
|||||||
| `DATA_SCHEMA_LOCAL` | Ancien fichier de secours statique | **supprimé** |
|
| `DATA_SCHEMA_LOCAL` | Ancien fichier de secours statique | **supprimé** |
|
||||||
| `DATA_SCHEMA_CACHE` | Cache last-known-good du schéma distant | **nouveau** |
|
| `DATA_SCHEMA_CACHE` | Cache last-known-good du schéma distant | **nouveau** |
|
||||||
| `DUCKDB_PATH` | Fichier DuckDB | inchangé |
|
| `DUCKDB_PATH` | Fichier DuckDB | inchangé |
|
||||||
|
| `SOURCE_STATS_CSV_PATH` | CSV stats sources (page À propos, D) | inchangé |
|
||||||
|
|
||||||
À faire côté config :
|
À faire côté config :
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user