From 979d204af6853421c9d4df21ca9185406da998a7 Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Wed, 8 Apr 2026 23:51:09 +0100 Subject: [PATCH 01/15] feat: link weather data sources to a provider account Signed-off-by: Mohamed Belhsan Hmida --- flexmeasures_weather/utils/modeling.py | 30 ++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/flexmeasures_weather/utils/modeling.py b/flexmeasures_weather/utils/modeling.py index 33ed9c4..1caa19b 100644 --- a/flexmeasures_weather/utils/modeling.py +++ b/flexmeasures_weather/utils/modeling.py @@ -1,8 +1,9 @@ +import inspect from packaging import version from flask import current_app from flexmeasures.data.models.generic_assets import GenericAsset, GenericAssetType -from flexmeasures import Source, __version__ as flexmeasures_version +from flexmeasures import Account, Source, __version__ as flexmeasures_version from flexmeasures.data import db from flexmeasures.data.services.data_sources import get_or_create_source @@ -17,26 +18,47 @@ SOURCE_TYPE = "forecaster" +def get_or_create_weather_account() -> Account: + """Make sure we have an account for the weather provider service.""" + account_name = current_app.config.get( + "WEATHER_DATA_SOURCE_NAME", DEFAULT_DATA_SOURCE_NAME + ) + weather_account = Account.query.filter( + Account.name == account_name, + ).one_or_none() + if weather_account is None: + weather_account = Account(name=account_name) + db.session.add(weather_account) + db.session.flush() + return weather_account + + def get_or_create_owm_data_source() -> Source: """Make sure we have an data source""" - return get_or_create_source( + source_kwargs = dict( source=current_app.config.get( "WEATHER_DATA_SOURCE_NAME", DEFAULT_DATA_SOURCE_NAME ), - source_type=SOURCE_TYPE, + source_type="market", flush=False, ) + if "account" in inspect.signature(get_or_create_source).parameters: + source_kwargs["account"] = get_or_create_weather_account() + return get_or_create_source(**source_kwargs) def get_or_create_owm_data_source_for_derived_data() -> Source: owm_source_name = current_app.config.get( "WEATHER_DATA_SOURCE_NAME", DEFAULT_DATA_SOURCE_NAME ) - return get_or_create_source( + source_kwargs = dict( source=f"FlexMeasures {owm_source_name}", source_type=SOURCE_TYPE, flush=False, ) + if "account" in inspect.signature(get_or_create_source).parameters: + source_kwargs["account"] = get_or_create_weather_account() + return get_or_create_source(**source_kwargs) def get_or_create_weather_station_type() -> GenericAssetType: From 2b523c8a541d411faacd2a8927680f4b82fca527 Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Wed, 8 Apr 2026 23:51:50 +0100 Subject: [PATCH 02/15] test: cover weather provider account registration Signed-off-by: Mohamed Belhsan Hmida --- .../utils/tests/test_modeling.py | 106 +++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/flexmeasures_weather/utils/tests/test_modeling.py b/flexmeasures_weather/utils/tests/test_modeling.py index dcea357..e04dc34 100644 --- a/flexmeasures_weather/utils/tests/test_modeling.py +++ b/flexmeasures_weather/utils/tests/test_modeling.py @@ -1,10 +1,114 @@ +import inspect +from types import SimpleNamespace + from flexmeasures import Asset +from flexmeasures import Account from flexmeasures_weather import DEFAULT_WEATHER_STATION_NAME -from flexmeasures_weather.utils.modeling import get_or_create_weather_station +from flexmeasures_weather.utils.modeling import ( + get_or_create_owm_data_source, + get_or_create_owm_data_source_for_derived_data, + get_or_create_weather_account, + get_or_create_weather_station, +) def test_creating_two_weather_stations(fresh_db): get_or_create_weather_station(50, 40) get_or_create_weather_station(40, 50) assert Asset.query.filter(Asset.name == DEFAULT_WEATHER_STATION_NAME).count() == 2 + + +def test_get_or_create_weather_account(fresh_db): + weather_account = get_or_create_weather_account() + + assert weather_account.name == "Weather" + assert Account.query.filter(Account.name == weather_account.name).count() == 1 + + +def test_get_or_create_owm_data_source_registers_market_source_on_weather_account( + fresh_db, +): + data_source = get_or_create_owm_data_source() + + assert data_source.type == "market" + if ( + "account" + in inspect.signature( + get_or_create_owm_data_source.__globals__["get_or_create_source"] + ).parameters + ): + assert data_source.account is not None + assert data_source.account.name == data_source.name + else: + assert Account.query.filter(Account.name == data_source.name).count() == 0 + + +def test_get_or_create_owm_data_source_for_derived_data_uses_weather_account(fresh_db): + derived_data_source = get_or_create_owm_data_source_for_derived_data() + + assert derived_data_source.type == "forecaster" + if ( + "account" + in inspect.signature( + get_or_create_owm_data_source.__globals__["get_or_create_source"] + ).parameters + ): + assert derived_data_source.account is not None + assert derived_data_source.account.name == "Weather" + else: + assert Account.query.filter(Account.name == "Weather").count() == 0 + + +def test_get_or_create_owm_data_source_passes_weather_account_when_supported( + fresh_db, monkeypatch +): + captured_kwargs = {} + + def fake_get_or_create_source(source, source_type, account, flush): + captured_kwargs.update( + dict( + source=source, + source_type=source_type, + account=account, + flush=flush, + ) + ) + return SimpleNamespace(type=source_type, account=account) + + monkeypatch.setattr( + "flexmeasures_weather.utils.modeling.get_or_create_source", + fake_get_or_create_source, + ) + + data_source = get_or_create_owm_data_source() + + assert data_source.type == "market" + assert captured_kwargs["account"].name == "Weather" + + +def test_get_or_create_owm_derived_data_source_passes_weather_account_when_supported( + fresh_db, monkeypatch +): + captured_kwargs = {} + + def fake_get_or_create_source(source, source_type, account, flush): + captured_kwargs.update( + dict( + source=source, + source_type=source_type, + account=account, + flush=flush, + ) + ) + return SimpleNamespace(type=source_type, account=account) + + monkeypatch.setattr( + "flexmeasures_weather.utils.modeling.get_or_create_source", + fake_get_or_create_source, + ) + + data_source = get_or_create_owm_data_source_for_derived_data() + + assert data_source.type == "forecaster" + assert captured_kwargs["account"].name == "Weather" From e2890bde348f1729e09a21177a76e0569f836da8 Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Mon, 13 Apr 2026 13:09:01 +0100 Subject: [PATCH 03/15] feat: update docstring for get_or_create_owm_data_source Signed-off-by: Mohamed Belhsan Hmida --- flexmeasures_weather/utils/modeling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flexmeasures_weather/utils/modeling.py b/flexmeasures_weather/utils/modeling.py index 1caa19b..c5249df 100644 --- a/flexmeasures_weather/utils/modeling.py +++ b/flexmeasures_weather/utils/modeling.py @@ -34,7 +34,7 @@ def get_or_create_weather_account() -> Account: def get_or_create_owm_data_source() -> Source: - """Make sure we have an data source""" + """Make sure we have a raw weather provider data source of type "market".""" source_kwargs = dict( source=current_app.config.get( "WEATHER_DATA_SOURCE_NAME", DEFAULT_DATA_SOURCE_NAME From 7589dcdda851e6082b7c2fdd61c01bece1b990f3 Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Mon, 13 Apr 2026 13:21:45 +0100 Subject: [PATCH 04/15] test: use version-aware derived source type assertions Signed-off-by: Mohamed Belhsan Hmida --- flexmeasures_weather/utils/tests/test_modeling.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/flexmeasures_weather/utils/tests/test_modeling.py b/flexmeasures_weather/utils/tests/test_modeling.py index e04dc34..abce584 100644 --- a/flexmeasures_weather/utils/tests/test_modeling.py +++ b/flexmeasures_weather/utils/tests/test_modeling.py @@ -6,6 +6,7 @@ from flexmeasures_weather import DEFAULT_WEATHER_STATION_NAME from flexmeasures_weather.utils.modeling import ( + SOURCE_TYPE, get_or_create_owm_data_source, get_or_create_owm_data_source_for_derived_data, get_or_create_weather_account, @@ -47,7 +48,7 @@ def test_get_or_create_owm_data_source_registers_market_source_on_weather_accoun def test_get_or_create_owm_data_source_for_derived_data_uses_weather_account(fresh_db): derived_data_source = get_or_create_owm_data_source_for_derived_data() - assert derived_data_source.type == "forecaster" + assert derived_data_source.type == SOURCE_TYPE if ( "account" in inspect.signature( @@ -110,5 +111,5 @@ def fake_get_or_create_source(source, source_type, account, flush): data_source = get_or_create_owm_data_source_for_derived_data() - assert data_source.type == "forecaster" + assert data_source.type == SOURCE_TYPE assert captured_kwargs["account"].name == "Weather" From 4e243ec2651467b89aa25f76ee5862ef53249e86 Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Mon, 13 Apr 2026 13:28:11 +0100 Subject: [PATCH 05/15] test: inspect source support through the modeling module Signed-off-by: Mohamed Belhsan Hmida --- flexmeasures_weather/utils/tests/test_modeling.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/flexmeasures_weather/utils/tests/test_modeling.py b/flexmeasures_weather/utils/tests/test_modeling.py index abce584..0dd6ed3 100644 --- a/flexmeasures_weather/utils/tests/test_modeling.py +++ b/flexmeasures_weather/utils/tests/test_modeling.py @@ -4,6 +4,7 @@ from flexmeasures import Asset from flexmeasures import Account +import flexmeasures_weather.utils.modeling as modeling from flexmeasures_weather import DEFAULT_WEATHER_STATION_NAME from flexmeasures_weather.utils.modeling import ( SOURCE_TYPE, @@ -33,12 +34,7 @@ def test_get_or_create_owm_data_source_registers_market_source_on_weather_accoun data_source = get_or_create_owm_data_source() assert data_source.type == "market" - if ( - "account" - in inspect.signature( - get_or_create_owm_data_source.__globals__["get_or_create_source"] - ).parameters - ): + if "account" in inspect.signature(modeling.get_or_create_source).parameters: assert data_source.account is not None assert data_source.account.name == data_source.name else: @@ -49,12 +45,7 @@ def test_get_or_create_owm_data_source_for_derived_data_uses_weather_account(fre derived_data_source = get_or_create_owm_data_source_for_derived_data() assert derived_data_source.type == SOURCE_TYPE - if ( - "account" - in inspect.signature( - get_or_create_owm_data_source.__globals__["get_or_create_source"] - ).parameters - ): + if "account" in inspect.signature(modeling.get_or_create_source).parameters: assert derived_data_source.account is not None assert derived_data_source.account.name == "Weather" else: From e982238f6820910b3bf418dddc38055e05dc06b6 Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Mon, 13 Apr 2026 13:29:11 +0100 Subject: [PATCH 06/15] feat: cache source account support checks Signed-off-by: Mohamed Belhsan Hmida --- flexmeasures_weather/utils/modeling.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/flexmeasures_weather/utils/modeling.py b/flexmeasures_weather/utils/modeling.py index c5249df..9cac1bd 100644 --- a/flexmeasures_weather/utils/modeling.py +++ b/flexmeasures_weather/utils/modeling.py @@ -17,6 +17,10 @@ else: SOURCE_TYPE = "forecaster" +SUPPORTS_SOURCE_ACCOUNT = ( + "account" in inspect.signature(get_or_create_source).parameters +) + def get_or_create_weather_account() -> Account: """Make sure we have an account for the weather provider service.""" @@ -42,7 +46,7 @@ def get_or_create_owm_data_source() -> Source: source_type="market", flush=False, ) - if "account" in inspect.signature(get_or_create_source).parameters: + if SUPPORTS_SOURCE_ACCOUNT: source_kwargs["account"] = get_or_create_weather_account() return get_or_create_source(**source_kwargs) @@ -56,7 +60,7 @@ def get_or_create_owm_data_source_for_derived_data() -> Source: source_type=SOURCE_TYPE, flush=False, ) - if "account" in inspect.signature(get_or_create_source).parameters: + if SUPPORTS_SOURCE_ACCOUNT: source_kwargs["account"] = get_or_create_weather_account() return get_or_create_source(**source_kwargs) From d13a086b6150d9303e70970ef03dcb776b6ce834 Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Mon, 13 Apr 2026 13:34:20 +0100 Subject: [PATCH 07/15] test: use the default weather source name in account assertions Signed-off-by: Mohamed Belhsan Hmida --- flexmeasures_weather/utils/tests/test_modeling.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flexmeasures_weather/utils/tests/test_modeling.py b/flexmeasures_weather/utils/tests/test_modeling.py index 0dd6ed3..241b65c 100644 --- a/flexmeasures_weather/utils/tests/test_modeling.py +++ b/flexmeasures_weather/utils/tests/test_modeling.py @@ -5,7 +5,7 @@ from flexmeasures import Account import flexmeasures_weather.utils.modeling as modeling -from flexmeasures_weather import DEFAULT_WEATHER_STATION_NAME +from flexmeasures_weather import DEFAULT_DATA_SOURCE_NAME, DEFAULT_WEATHER_STATION_NAME from flexmeasures_weather.utils.modeling import ( SOURCE_TYPE, get_or_create_owm_data_source, @@ -24,7 +24,7 @@ def test_creating_two_weather_stations(fresh_db): def test_get_or_create_weather_account(fresh_db): weather_account = get_or_create_weather_account() - assert weather_account.name == "Weather" + assert weather_account.name == DEFAULT_DATA_SOURCE_NAME assert Account.query.filter(Account.name == weather_account.name).count() == 1 From 0e0aa1aa537e5f7a6e6b665cb913be2ee240c7ad Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Mon, 13 Apr 2026 13:39:43 +0100 Subject: [PATCH 08/15] style: run pre-commit Signed-off-by: Mohamed Belhsan Hmida --- flexmeasures_weather/cli/tests/test_get_forecasts.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/flexmeasures_weather/cli/tests/test_get_forecasts.py b/flexmeasures_weather/cli/tests/test_get_forecasts.py index 2f33497..6a2d2b4 100644 --- a/flexmeasures_weather/cli/tests/test_get_forecasts.py +++ b/flexmeasures_weather/cli/tests/test_get_forecasts.py @@ -60,7 +60,10 @@ def test_get_weather_forecasts_no_close_sensors( with caplog.at_level(logging.WARNING): result = runner.invoke( collect_weather_data, - ["--location", f"{weather_station.latitude-5},{weather_station.longitude}"], + [ + "--location", + f"{weather_station.latitude - 5},{weather_station.longitude}", + ], ) print(result.output) assert "Reported task get-weather-forecasts status as True" in result.output From 7efd98ec227f46988523208df63ba4f763dfafde Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Mon, 13 Apr 2026 13:42:29 +0100 Subject: [PATCH 09/15] test: force account support in monkeypatched source tests Signed-off-by: Mohamed Belhsan Hmida --- flexmeasures_weather/utils/tests/test_modeling.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/flexmeasures_weather/utils/tests/test_modeling.py b/flexmeasures_weather/utils/tests/test_modeling.py index 241b65c..9719680 100644 --- a/flexmeasures_weather/utils/tests/test_modeling.py +++ b/flexmeasures_weather/utils/tests/test_modeling.py @@ -72,6 +72,10 @@ def fake_get_or_create_source(source, source_type, account, flush): "flexmeasures_weather.utils.modeling.get_or_create_source", fake_get_or_create_source, ) + monkeypatch.setattr( + "flexmeasures_weather.utils.modeling.SUPPORTS_SOURCE_ACCOUNT", + True, + ) data_source = get_or_create_owm_data_source() @@ -99,6 +103,10 @@ def fake_get_or_create_source(source, source_type, account, flush): "flexmeasures_weather.utils.modeling.get_or_create_source", fake_get_or_create_source, ) + monkeypatch.setattr( + "flexmeasures_weather.utils.modeling.SUPPORTS_SOURCE_ACCOUNT", + True, + ) data_source = get_or_create_owm_data_source_for_derived_data() From bb797e3d451139ac73303d745f667b77e7fb7b5a Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Tue, 14 Apr 2026 11:28:22 +0100 Subject: [PATCH 10/15] fix: change source from market to forecaster (SOURCE_TYPE) Signed-off-by: Mohamed Belhsan Hmida --- flexmeasures_weather/utils/modeling.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flexmeasures_weather/utils/modeling.py b/flexmeasures_weather/utils/modeling.py index 9cac1bd..140f61e 100644 --- a/flexmeasures_weather/utils/modeling.py +++ b/flexmeasures_weather/utils/modeling.py @@ -38,12 +38,12 @@ def get_or_create_weather_account() -> Account: def get_or_create_owm_data_source() -> Source: - """Make sure we have a raw weather provider data source of type "market".""" + """Make sure we have a weather provider data source of the configured type.""" source_kwargs = dict( source=current_app.config.get( "WEATHER_DATA_SOURCE_NAME", DEFAULT_DATA_SOURCE_NAME ), - source_type="market", + source_type=SOURCE_TYPE, flush=False, ) if SUPPORTS_SOURCE_ACCOUNT: From 5dfc682c2dd1e9f2786dc0ba9cfdb4fa68ca014d Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Tue, 14 Apr 2026 11:28:30 +0100 Subject: [PATCH 11/15] test: align weather source expectations Signed-off-by: Mohamed Belhsan Hmida --- flexmeasures_weather/utils/tests/test_modeling.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/flexmeasures_weather/utils/tests/test_modeling.py b/flexmeasures_weather/utils/tests/test_modeling.py index 9719680..c3efe25 100644 --- a/flexmeasures_weather/utils/tests/test_modeling.py +++ b/flexmeasures_weather/utils/tests/test_modeling.py @@ -28,12 +28,12 @@ def test_get_or_create_weather_account(fresh_db): assert Account.query.filter(Account.name == weather_account.name).count() == 1 -def test_get_or_create_owm_data_source_registers_market_source_on_weather_account( +def test_get_or_create_owm_data_source_registers_weather_source_on_weather_account( fresh_db, ): data_source = get_or_create_owm_data_source() - assert data_source.type == "market" + assert data_source.type == SOURCE_TYPE if "account" in inspect.signature(modeling.get_or_create_source).parameters: assert data_source.account is not None assert data_source.account.name == data_source.name @@ -79,8 +79,8 @@ def fake_get_or_create_source(source, source_type, account, flush): data_source = get_or_create_owm_data_source() - assert data_source.type == "market" - assert captured_kwargs["account"].name == "Weather" + assert data_source.type == SOURCE_TYPE + assert captured_kwargs["account"].name == DEFAULT_DATA_SOURCE_NAME def test_get_or_create_owm_derived_data_source_passes_weather_account_when_supported( @@ -111,4 +111,4 @@ def fake_get_or_create_source(source, source_type, account, flush): data_source = get_or_create_owm_data_source_for_derived_data() assert data_source.type == SOURCE_TYPE - assert captured_kwargs["account"].name == "Weather" + assert captured_kwargs["account"].name == DEFAULT_DATA_SOURCE_NAME From a8a74bd8de513f825709139228d51a21497af582 Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Wed, 22 Apr 2026 14:46:39 +0100 Subject: [PATCH 12/15] feat: branch weather source setup on FM version Signed-off-by: Mohamed Belhsan Hmida --- flexmeasures_weather/utils/modeling.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/flexmeasures_weather/utils/modeling.py b/flexmeasures_weather/utils/modeling.py index 140f61e..370b2dc 100644 --- a/flexmeasures_weather/utils/modeling.py +++ b/flexmeasures_weather/utils/modeling.py @@ -1,9 +1,8 @@ -import inspect from packaging import version from flask import current_app from flexmeasures.data.models.generic_assets import GenericAsset, GenericAssetType -from flexmeasures import Account, Source, __version__ as flexmeasures_version +from flexmeasures import Source, __version__ as flexmeasures_version from flexmeasures.data import db from flexmeasures.data.services.data_sources import get_or_create_source @@ -17,13 +16,22 @@ else: SOURCE_TYPE = "forecaster" -SUPPORTS_SOURCE_ACCOUNT = ( - "account" in inspect.signature(get_or_create_source).parameters -) +FM_SUPPORTS_ACCOUNT_LINKED_SOURCES = version.parse( + flexmeasures_version +) >= version.parse("0.32") + +if FM_SUPPORTS_ACCOUNT_LINKED_SOURCES: + from flexmeasures import Account +else: + Account = None -def get_or_create_weather_account() -> Account: +def get_or_create_weather_account(): """Make sure we have an account for the weather provider service.""" + if Account is None: + raise RuntimeError( + "FlexMeasures Account model is unavailable before FlexMeasures 0.32." + ) account_name = current_app.config.get( "WEATHER_DATA_SOURCE_NAME", DEFAULT_DATA_SOURCE_NAME ) @@ -46,7 +54,7 @@ def get_or_create_owm_data_source() -> Source: source_type=SOURCE_TYPE, flush=False, ) - if SUPPORTS_SOURCE_ACCOUNT: + if FM_SUPPORTS_ACCOUNT_LINKED_SOURCES: source_kwargs["account"] = get_or_create_weather_account() return get_or_create_source(**source_kwargs) @@ -60,7 +68,7 @@ def get_or_create_owm_data_source_for_derived_data() -> Source: source_type=SOURCE_TYPE, flush=False, ) - if SUPPORTS_SOURCE_ACCOUNT: + if FM_SUPPORTS_ACCOUNT_LINKED_SOURCES: source_kwargs["account"] = get_or_create_weather_account() return get_or_create_source(**source_kwargs) From 25d73ac808c995128c36af5b422ec4253ce07707 Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Wed, 22 Apr 2026 14:47:25 +0100 Subject: [PATCH 13/15] test: align weather source tests with version branches Signed-off-by: Mohamed Belhsan Hmida --- .../utils/tests/test_modeling.py | 120 +++++++++++++++--- 1 file changed, 100 insertions(+), 20 deletions(-) diff --git a/flexmeasures_weather/utils/tests/test_modeling.py b/flexmeasures_weather/utils/tests/test_modeling.py index c3efe25..b0f35f6 100644 --- a/flexmeasures_weather/utils/tests/test_modeling.py +++ b/flexmeasures_weather/utils/tests/test_modeling.py @@ -1,12 +1,13 @@ -import inspect from types import SimpleNamespace +import pytest + from flexmeasures import Asset -from flexmeasures import Account import flexmeasures_weather.utils.modeling as modeling from flexmeasures_weather import DEFAULT_DATA_SOURCE_NAME, DEFAULT_WEATHER_STATION_NAME from flexmeasures_weather.utils.modeling import ( + FM_SUPPORTS_ACCOUNT_LINKED_SOURCES, SOURCE_TYPE, get_or_create_owm_data_source, get_or_create_owm_data_source_for_derived_data, @@ -21,37 +22,54 @@ def test_creating_two_weather_stations(fresh_db): assert Asset.query.filter(Asset.name == DEFAULT_WEATHER_STATION_NAME).count() == 2 +# The version-branch tests below still use monkeypatching to isolate source +# creation side effects without requiring multiple FlexMeasures installs. +@pytest.mark.skipif( + not FM_SUPPORTS_ACCOUNT_LINKED_SOURCES, + reason="Weather source accounts are only supported on FlexMeasures >= 0.32.", +) def test_get_or_create_weather_account(fresh_db): weather_account = get_or_create_weather_account() assert weather_account.name == DEFAULT_DATA_SOURCE_NAME - assert Account.query.filter(Account.name == weather_account.name).count() == 1 + assert ( + modeling.Account.query.filter( + modeling.Account.name == weather_account.name + ).count() + == 1 + ) +@pytest.mark.skipif( + not FM_SUPPORTS_ACCOUNT_LINKED_SOURCES, + reason="Account-linked weather sources are only supported on FlexMeasures >= 0.32.", +) def test_get_or_create_owm_data_source_registers_weather_source_on_weather_account( fresh_db, ): data_source = get_or_create_owm_data_source() assert data_source.type == SOURCE_TYPE - if "account" in inspect.signature(modeling.get_or_create_source).parameters: - assert data_source.account is not None - assert data_source.account.name == data_source.name - else: - assert Account.query.filter(Account.name == data_source.name).count() == 0 + assert data_source.account is not None + assert data_source.account.name == data_source.name +@pytest.mark.skipif( + not FM_SUPPORTS_ACCOUNT_LINKED_SOURCES, + reason="Account-linked weather sources are only supported on FlexMeasures >= 0.32.", +) def test_get_or_create_owm_data_source_for_derived_data_uses_weather_account(fresh_db): derived_data_source = get_or_create_owm_data_source_for_derived_data() assert derived_data_source.type == SOURCE_TYPE - if "account" in inspect.signature(modeling.get_or_create_source).parameters: - assert derived_data_source.account is not None - assert derived_data_source.account.name == "Weather" - else: - assert Account.query.filter(Account.name == "Weather").count() == 0 + assert derived_data_source.account is not None + assert derived_data_source.account.name == DEFAULT_DATA_SOURCE_NAME +@pytest.mark.skipif( + not FM_SUPPORTS_ACCOUNT_LINKED_SOURCES, + reason="Account-linked weather sources are only supported on FlexMeasures >= 0.32.", +) def test_get_or_create_owm_data_source_passes_weather_account_when_supported( fresh_db, monkeypatch ): @@ -72,10 +90,6 @@ def fake_get_or_create_source(source, source_type, account, flush): "flexmeasures_weather.utils.modeling.get_or_create_source", fake_get_or_create_source, ) - monkeypatch.setattr( - "flexmeasures_weather.utils.modeling.SUPPORTS_SOURCE_ACCOUNT", - True, - ) data_source = get_or_create_owm_data_source() @@ -83,6 +97,10 @@ def fake_get_or_create_source(source, source_type, account, flush): assert captured_kwargs["account"].name == DEFAULT_DATA_SOURCE_NAME +@pytest.mark.skipif( + not FM_SUPPORTS_ACCOUNT_LINKED_SOURCES, + reason="Account-linked weather sources are only supported on FlexMeasures >= 0.32.", +) def test_get_or_create_owm_derived_data_source_passes_weather_account_when_supported( fresh_db, monkeypatch ): @@ -103,12 +121,74 @@ def fake_get_or_create_source(source, source_type, account, flush): "flexmeasures_weather.utils.modeling.get_or_create_source", fake_get_or_create_source, ) + + data_source = get_or_create_owm_data_source_for_derived_data() + + assert data_source.type == SOURCE_TYPE + assert captured_kwargs["account"].name == DEFAULT_DATA_SOURCE_NAME + + +@pytest.mark.skipif( + FM_SUPPORTS_ACCOUNT_LINKED_SOURCES, + reason="Legacy source creation without accounts is only used on FlexMeasures < 0.32.", +) +def test_get_or_create_owm_data_source_omits_account_when_not_supported(monkeypatch): + captured_kwargs = {} + + def fake_get_or_create_source(source, source_type, flush): + captured_kwargs.update( + dict( + source=source, + source_type=source_type, + flush=flush, + ) + ) + return SimpleNamespace(type=source_type, name=source) + + monkeypatch.setattr( + "flexmeasures_weather.utils.modeling.get_or_create_source", + fake_get_or_create_source, + ) + + data_source = get_or_create_owm_data_source() + + assert data_source.type == SOURCE_TYPE + assert captured_kwargs == { + "source": DEFAULT_DATA_SOURCE_NAME, + "source_type": SOURCE_TYPE, + "flush": False, + } + + +@pytest.mark.skipif( + FM_SUPPORTS_ACCOUNT_LINKED_SOURCES, + reason="Legacy source creation without accounts is only used on FlexMeasures < 0.32.", +) +def test_get_or_create_owm_derived_data_source_omits_account_when_not_supported( + monkeypatch, +): + captured_kwargs = {} + + def fake_get_or_create_source(source, source_type, flush): + captured_kwargs.update( + dict( + source=source, + source_type=source_type, + flush=flush, + ) + ) + return SimpleNamespace(type=source_type, name=source) + monkeypatch.setattr( - "flexmeasures_weather.utils.modeling.SUPPORTS_SOURCE_ACCOUNT", - True, + "flexmeasures_weather.utils.modeling.get_or_create_source", + fake_get_or_create_source, ) data_source = get_or_create_owm_data_source_for_derived_data() assert data_source.type == SOURCE_TYPE - assert captured_kwargs["account"].name == DEFAULT_DATA_SOURCE_NAME + assert captured_kwargs == { + "source": f"FlexMeasures {DEFAULT_DATA_SOURCE_NAME}", + "source_type": SOURCE_TYPE, + "flush": False, + } From d7fc474896d04125ffd8ed8dc48f80e2736be44a Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Wed, 13 May 2026 09:24:22 +0200 Subject: [PATCH 14/15] fix: modernize validator Signed-off-by: F.N. Claessen --- flexmeasures_weather/cli/schemas/weather_sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flexmeasures_weather/cli/schemas/weather_sensor.py b/flexmeasures_weather/cli/schemas/weather_sensor.py index d058ce1..5c4039d 100644 --- a/flexmeasures_weather/cli/schemas/weather_sensor.py +++ b/flexmeasures_weather/cli/schemas/weather_sensor.py @@ -28,7 +28,7 @@ class WeatherSensorSchema(Schema): ) @validates("name") - def validate_name_is_supported(self, name: str): + def validate_name_is_supported(self, name: str, **kwargs): if get_supported_sensor_spec(name): return raise ValidationError( @@ -36,7 +36,7 @@ def validate_name_is_supported(self, name: str): ) @validates("timezone") - def validate_timezone(self, timezone: str): + def validate_timezone(self, timezone: str, **kwargs): try: pytz.timezone(timezone) except pytz.UnknownTimeZoneError: From 2f7c1cd7c933f0ddd5a0085ac348179dcb4a51cf Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Wed, 13 May 2026 09:28:05 +0200 Subject: [PATCH 15/15] fix: backwards compatibility against old config settings Signed-off-by: F.N. Claessen --- flexmeasures_weather/cli/commands.py | 6 +++++- flexmeasures_weather/utils/weather.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/flexmeasures_weather/cli/commands.py b/flexmeasures_weather/cli/commands.py index a95c435..a66e736 100644 --- a/flexmeasures_weather/cli/commands.py +++ b/flexmeasures_weather/cli/commands.py @@ -162,7 +162,11 @@ def collect_weather_data(location, asset_id, store_in_db, num_cells, method, reg a geometrical grid (See the --location parameter). """ - api_key = str(current_app.config.get("WEATHERAPI_KEY", "")) + api_key = str( + current_app.config.get( + "WEATHERAPI_KEY", current_app.config.get("OPENWEATHERMAP_API_KEY", "") + ) + ) if api_key == "": raise Exception("[FLEXMEASURES-WEATHER] Setting WEATHERAPI_KEY not available.") if asset_id is not None: diff --git a/flexmeasures_weather/utils/weather.py b/flexmeasures_weather/utils/weather.py index f27f91a..dfcf8bb 100644 --- a/flexmeasures_weather/utils/weather.py +++ b/flexmeasures_weather/utils/weather.py @@ -198,7 +198,7 @@ def call_api( Exception: If an invalid weather provider is configured. """ - provider = str(current_app.config.get("WEATHER_PROVIDER", "")) + provider = str(current_app.config.get("WEATHER_PROVIDER", "OWM")) if provider not in ["OWM", "WAPI"]: raise Exception( "Invalid provider name. Please set WEATHER_PROVIDER setting in config file to either OWM or WAPI, the two permissible options."