From 884cf4212cc2f0e48a9ce9b409c84f0df584cc6e Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Sun, 5 Jul 2026 14:05:26 +0100 Subject: [PATCH 1/4] feat: add server version helper Signed-off-by: Mohamed Belhsan Hmida --- src/flexmeasures_client/client.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/flexmeasures_client/client.py b/src/flexmeasures_client/client.py index 2757b618..4d4d123d 100644 --- a/src/flexmeasures_client/client.py +++ b/src/flexmeasures_client/client.py @@ -70,6 +70,18 @@ def _parse_sensor_json_fields(sensor: dict) -> None: _parse_json_field(sensor, "attributes") +def _server_version_at_least(server_version: str | None, minimum: str) -> bool: + """Compare server versions by release generation. + + FlexMeasures reports development versions such as 0.33.0.dev26 while the + server is already exposing the 0.33 API shape. Comparing base versions keeps + these development builds on the right side of feature checks. + """ + if server_version is None: + return False + return Version(Version(server_version).base_version) >= Version(minimum) + + def convert_units( values: list[int | float], from_unit: str, to_unit: str ) -> list[int | float]: From 36ea53ae8a6fc4286917fb591b5ada467a2d7a02 Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Sun, 5 Jul 2026 14:05:42 +0100 Subject: [PATCH 2/4] fix: use server version helper for schedule force field Signed-off-by: Mohamed Belhsan Hmida --- src/flexmeasures_client/client.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/flexmeasures_client/client.py b/src/flexmeasures_client/client.py index 4d4d123d..fb1c70ab 100644 --- a/src/flexmeasures_client/client.py +++ b/src/flexmeasures_client/client.py @@ -1390,14 +1390,10 @@ async def trigger_schedule( if flex_context is not None: message["flex-context"] = flex_context + force_new_job_creation_requested = False if prior is not None: message["prior"] = pd.Timestamp(prior).isoformat() - if self.server_version is not None and Version( - self.server_version - ) < Version("0.33.0"): - message["force_new_job_creation"] = True - else: - message["force-new-job-creation"] = True + force_new_job_creation_requested = True # For a sensor_id, try to resolve to the sensor's asset_id so we can use # the asset scheduling endpoint (preferred over the sensor endpoint). @@ -1408,10 +1404,10 @@ async def trigger_schedule( use_sensor_endpoint = False if sensor_id is not None: if self.server_version is not None and ( - Version(self.server_version) < Version("0.27.0") + not _server_version_at_least(self.server_version, "0.27.0") or ( - Version(self.server_version) < Version("0.33.0") - and "force_new_job_creation" in message + not _server_version_at_least(self.server_version, "0.33.0") + and force_new_job_creation_requested ) ): use_sensor_endpoint = True @@ -1452,6 +1448,14 @@ async def trigger_schedule( asset_id=asset_id, updates=dict(attributes=asset_attributes) ) + if force_new_job_creation_requested: + if use_sensor_endpoint and not _server_version_at_least( + self.server_version, "0.33.0" + ): + message["force_new_job_creation"] = True + else: + message["force-new-job-creation"] = True + if use_sensor_endpoint: response, status = await self.request( uri=f"sensors/{sensor_id}/schedules/trigger", From a4693f07c4c678cc300123f5ff033410f4b6451c Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Sun, 5 Jul 2026 14:06:14 +0100 Subject: [PATCH 3/4] test: cover dev server schedule force field Signed-off-by: Mohamed Belhsan Hmida --- tests/client/test_schedule.py | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/client/test_schedule.py b/tests/client/test_schedule.py index 0599207b..49fb02c8 100644 --- a/tests/client/test_schedule.py +++ b/tests/client/test_schedule.py @@ -567,6 +567,42 @@ async def test_trigger_schedule_with_prior(): await client.close() +@pytest.mark.asyncio +async def test_trigger_schedule_with_prior_on_dev_033_uses_asset_field_name(): + """0.33.0.dev servers already expose the 0.33 asset scheduling API shape.""" + with aioresponses() as m: + client = FlexMeasuresClient(email="test@test.test", password="test") + client.access_token = "test-token" + client.server_version = "0.33.0.dev26+gda5eae592" + m.post( + "http://localhost:5000/api/v3_0/assets/10/schedules/trigger", + status=200, + payload={"schedule": "sched-uuid"}, + ) + schedule_id = await client.trigger_schedule( + asset_id=10, + start="2023-01-01T00:00+00:00", + duration="PT1H", + prior="2023-01-01T00:00+00:00", + ) + assert schedule_id == "sched-uuid" + m.assert_called_with( + method="POST", + headers={"Content-Type": "application/json", "Authorization": "test-token"}, + json={ + "start": "2023-01-01T00:00:00+00:00", + "duration": "P0DT1H0M0S", + "prior": "2023-01-01T00:00:00+00:00", + "force-new-job-creation": True, + }, + url="http://localhost:5000/api/v3_0/assets/10/schedules/trigger", + params=None, + ssl=False, + allow_redirects=False, + ) + await client.close() + + @pytest.mark.asyncio async def test_trigger_schedule_scheduler_with_sensor_id_error(): """scheduler set with sensor_id on a server older than v0.27.0 raises ValueError.""" From 1e1014dceeda48131ce38f149ec00a36dc565d03 Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Sun, 5 Jul 2026 14:06:29 +0100 Subject: [PATCH 4/4] test: cover legacy schedule force field Signed-off-by: Mohamed Belhsan Hmida --- tests/client/test_schedule.py | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/client/test_schedule.py b/tests/client/test_schedule.py index 49fb02c8..5a49b9c2 100644 --- a/tests/client/test_schedule.py +++ b/tests/client/test_schedule.py @@ -603,6 +603,42 @@ async def test_trigger_schedule_with_prior_on_dev_033_uses_asset_field_name(): await client.close() +@pytest.mark.asyncio +async def test_trigger_schedule_with_prior_on_old_server_uses_sensor_field_name(): + """Older servers use the sensor endpoint with the legacy force field name.""" + with aioresponses() as m: + client = FlexMeasuresClient(email="test@test.test", password="test") + client.access_token = "test-token" + client.server_version = "0.32.0" + m.post( + "http://localhost:5000/api/v3_0/sensors/1/schedules/trigger", + status=200, + payload={"schedule": "sched-uuid"}, + ) + schedule_id = await client.trigger_schedule( + sensor_id=1, + start="2023-01-01T00:00+00:00", + duration="PT1H", + prior="2023-01-01T00:00+00:00", + ) + assert schedule_id == "sched-uuid" + m.assert_called_with( + method="POST", + headers={"Content-Type": "application/json", "Authorization": "test-token"}, + json={ + "start": "2023-01-01T00:00:00+00:00", + "duration": "P0DT1H0M0S", + "prior": "2023-01-01T00:00:00+00:00", + "force_new_job_creation": True, + }, + url="http://localhost:5000/api/v3_0/sensors/1/schedules/trigger", + params=None, + ssl=False, + allow_redirects=False, + ) + await client.close() + + @pytest.mark.asyncio async def test_trigger_schedule_scheduler_with_sensor_id_error(): """scheduler set with sensor_id on a server older than v0.27.0 raises ValueError."""