From 5beb70500123217b1d07339594936a0b51fa0645 Mon Sep 17 00:00:00 2001 From: miki4iaml Date: Sat, 27 Jun 2026 18:48:57 +0200 Subject: [PATCH 1/2] =?UTF-8?q?fix(test=5Fmodels.py):=20=C3=A9vite=20la=20?= =?UTF-8?q?mutation=20de=20classe=20m.groups?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FIX: l'ancienne version mutait directement les attributs de classe m.groups_ = m.groups_[:2] par affectations au niveau du module. Bonjour, en recherchant l'origine des fails sur pytest, je relève cette mutation qui pourrait avoir 2 effets indésirables : 1. La mutation permanente pour toute la durée du processus pytest, affecte tous les tests suivants et tout code qui importait la même classe. 2. En production, si meteofetch est importé dans un processus long (serveur, notebook), une mutation accidentelle des classes a des effets de bord impossibles à tracer. La correction proposée crée des sous-classes anonymes locales aux tests, ce qui isole complètement la modification sans toucher aux classes originales. --- tests/test_models.py | 51 ++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/tests/test_models.py b/tests/test_models.py index 0a94fca..955aa6a 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,3 +1,7 @@ +""" +Tests fonctionnels pour les modèles meteofetch. +""" + from gc import collect import pytest @@ -22,7 +26,7 @@ set_test_mode() -MODELS = ( +_METEOFRANCE_MODELS = ( Arome001, Arome0025, AromeOutreMerAntilles, @@ -36,28 +40,40 @@ MFWAM01, ) -# Limiter le nombre de groupes pour tous les modèles -for m in MODELS + (Ifs, Aifs): - m.groups_ = m.groups_[:2] +_ECMWF_MODELS = (Ifs, Aifs) +_ALL_MODELS = _METEOFRANCE_MODELS + _ECMWF_MODELS + +# FIX: au lieu de muter les classes originales, on crée des sous-classes +# locales avec groups_ tronqué. Les classes originales restent intactes. +def _make_limited(cls, n_groups: int = 2): + """Return a subclass of *cls* with groups_ limited to the first *n_groups* entries.""" + return type( + f"{cls.__name__}Limited", + (cls,), + {"groups_": cls.groups_[:n_groups]}, + ) + + +# Variantes limitées pour les tests (2 groupes au lieu de tous) +_LIMITED_MF_MODELS = [_make_limited(m) for m in _METEOFRANCE_MODELS] +_LIMITED_IFS = _make_limited(Ifs) +_LIMITED_AIFS = _make_limited(Aifs) -# Liste des configurations GRIB à tester GRIB_DEFS = ["eccodes", "meteofrance"] -# Fixture pour les modèles -@pytest.fixture(params=MODELS) -def model(request): +@pytest.fixture(params=_LIMITED_MF_MODELS, ids=[m.__name__ for m in _METEOFRANCE_MODELS]) +def mf_model(request): return request.param -# Fixture pour les configurations GRIB @pytest.fixture(params=GRIB_DEFS) def grib_def(request): return request.param def test_aifs(): - datasets = Aifs.get_latest_forecast() + datasets = _LIMITED_AIFS.get_latest_forecast() for field in datasets: print(f"\t{field} - {datasets[field].units}") ds = datasets[field] @@ -69,7 +85,7 @@ def test_aifs(): def test_ifs(): - datasets = Ifs.get_latest_forecast() + datasets = _LIMITED_IFS.get_latest_forecast() for field in datasets: print(f"\t{field} - {datasets[field].units}") ds = datasets[field] @@ -80,15 +96,14 @@ def test_ifs(): collect() -def test_meteo_france_models_with_grib_defs(grib_def, model): - # Configurer les définitions GRIB +def test_meteo_france_models_with_grib_defs(grib_def, mf_model): set_grib_defs(grib_def) - print(f"\nTesting {model.__name__} with {grib_def} definitions") - print(model.availability()) + print(f"\nTesting {mf_model.__name__} with {grib_def} definitions") + print(mf_model.availability()) - for paquet in model.paquets_: - print(f"\nModel: {model.__name__}, GRIB defs: {grib_def}, Paquet: {paquet}") - datasets = model.get_latest_forecast(paquet=paquet) + for paquet in mf_model.paquets_: + print(f"\nModel: {mf_model.__name__}, GRIB defs: {grib_def}, Paquet: {paquet}") + datasets = mf_model.get_latest_forecast(paquet=paquet) assert len(datasets) > 0, f"{paquet} : aucun dataset n'a été récupéré." for field in datasets: From d1367a53eae1a5f3d41eb52d2984230024795b71 Mon Sep 17 00:00:00 2001 From: CyrilJl Date: Sun, 28 Jun 2026 13:38:28 +0200 Subject: [PATCH 2/2] fix: avoid persistent test model mutation --- tests/test_models.py | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/tests/test_models.py b/tests/test_models.py index 955aa6a..d3309f4 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -40,31 +40,17 @@ MFWAM01, ) -_ECMWF_MODELS = (Ifs, Aifs) -_ALL_MODELS = _METEOFRANCE_MODELS + _ECMWF_MODELS - -# FIX: au lieu de muter les classes originales, on crée des sous-classes -# locales avec groups_ tronqué. Les classes originales restent intactes. -def _make_limited(cls, n_groups: int = 2): - """Return a subclass of *cls* with groups_ limited to the first *n_groups* entries.""" - return type( - f"{cls.__name__}Limited", - (cls,), - {"groups_": cls.groups_[:n_groups]}, - ) - - -# Variantes limitées pour les tests (2 groupes au lieu de tous) -_LIMITED_MF_MODELS = [_make_limited(m) for m in _METEOFRANCE_MODELS] -_LIMITED_IFS = _make_limited(Ifs) -_LIMITED_AIFS = _make_limited(Aifs) +def _limit_model_groups(monkeypatch, model, n_groups: int = 2): + """Temporarily limit a model to the first *n_groups* groups for one test.""" + monkeypatch.setattr(model, "groups_", model.groups_[:n_groups]) + return model GRIB_DEFS = ["eccodes", "meteofrance"] -@pytest.fixture(params=_LIMITED_MF_MODELS, ids=[m.__name__ for m in _METEOFRANCE_MODELS]) -def mf_model(request): - return request.param +@pytest.fixture(params=_METEOFRANCE_MODELS, ids=[m.__name__ for m in _METEOFRANCE_MODELS]) +def mf_model(request, monkeypatch): + return _limit_model_groups(monkeypatch, request.param) @pytest.fixture(params=GRIB_DEFS) @@ -72,8 +58,9 @@ def grib_def(request): return request.param -def test_aifs(): - datasets = _LIMITED_AIFS.get_latest_forecast() +def test_aifs(monkeypatch): + model = _limit_model_groups(monkeypatch, Aifs) + datasets = model.get_latest_forecast() for field in datasets: print(f"\t{field} - {datasets[field].units}") ds = datasets[field] @@ -84,8 +71,9 @@ def test_aifs(): collect() -def test_ifs(): - datasets = _LIMITED_IFS.get_latest_forecast() +def test_ifs(monkeypatch): + model = _limit_model_groups(monkeypatch, Ifs) + datasets = model.get_latest_forecast() for field in datasets: print(f"\t{field} - {datasets[field].units}") ds = datasets[field]