Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 25 additions & 9 deletions src/flexmeasures_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down Expand Up @@ -1378,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).
Expand All @@ -1396,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
Expand Down Expand Up @@ -1440,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",
Expand Down
72 changes: 72 additions & 0 deletions tests/client/test_schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,78 @@ 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_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."""
Expand Down
Loading