From 59663afb241137f71f9c95cc64f3d56dc8c6b575 Mon Sep 17 00:00:00 2001 From: "Mathias L. Baumann" Date: Mon, 23 Feb 2026 13:31:14 +0100 Subject: [PATCH 1/2] Raise ValueError for invalid start_time in create() Passing a non-datetime, non-'NOW' value (e.g. numpy.datetime64) previously silently produced a dispatch with an epoch timestamp (1970-01-01). It now raises ValueError immediately, making the bug surface at the call site. Signed-off-by: Mathias L. Baumann --- RELEASE_NOTES.md | 4 ++-- src/frequenz/client/dispatch/_client.py | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 72e44a3..9f0393b 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,7 +2,7 @@ ## Summary - +Raise `ValueError` when `start_time` passed to `create()` is neither a `datetime` nor `"NOW"`. ## Upgrading @@ -14,4 +14,4 @@ ## Bug Fixes - +- `DispatchApiClient.create()`: Passing an invalid `start_time` (not a `datetime` or `"NOW"`) previously silently created a dispatch with an epoch timestamp (1970-01-01). It now raises `ValueError` immediately. diff --git a/src/frequenz/client/dispatch/_client.py b/src/frequenz/client/dispatch/_client.py index b09ba35..9381c4e 100644 --- a/src/frequenz/client/dispatch/_client.py +++ b/src/frequenz/client/dispatch/_client.py @@ -2,6 +2,7 @@ # Copyright © 2024 Frequenz Energy-as-a-Service GmbH """Dispatch API client for Python.""" + from __future__ import annotations import warnings @@ -344,6 +345,8 @@ async def create( # pylint: disable=too-many-positional-arguments or start_time.tzinfo.utcoffset(start_time) is None ): raise ValueError("start_time must be timezone aware") + elif start_time != "NOW": + raise ValueError("start_time must be a datetime or 'NOW'") request = DispatchCreateRequest( microgrid_id=microgrid_id, From e0ae50d7ed1c97772d4be5aba0e4660ad3c3c658 Mon Sep 17 00:00:00 2001 From: "Mathias L. Baumann" Date: Mon, 23 Feb 2026 15:08:07 +0100 Subject: [PATCH 2/2] tests: Add tests for invalid start_time in create() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Covers: non-datetime types (int, str, object), naive datetime, and past datetime — all of which should raise ValueError. Signed-off-by: Mathias L. Baumann --- tests/test_client.py | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/tests/test_client.py b/tests/test_client.py index ca4289b..62153b2 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -6,7 +6,7 @@ import asyncio import random from dataclasses import replace -from datetime import timedelta +from datetime import datetime, timedelta, timezone from functools import partial import grpc @@ -471,3 +471,36 @@ async def expect(dispatch: Dispatch, event: Event) -> None: # Expect the first dispatch deletion await expect(dispatches[0], Event.DELETED) + + +async def test_create_invalid_start_time(client: FakeClient, sample: Dispatch) -> None: + """Test that create() raises ValueError for invalid start_time types.""" + microgrid_id = MicrogridId(random.randint(1, 100)) + params = to_create_params(microgrid_id, sample) + + for invalid in [0, 1234567890, "2026-01-01T00:00:00", object()]: + params["start_time"] = invalid + with raises(ValueError): + await client.create(**params) + + +async def test_create_naive_datetime_raises( + client: FakeClient, sample: Dispatch +) -> None: + """Test that create() raises ValueError for a timezone-naive datetime.""" + microgrid_id = MicrogridId(random.randint(1, 100)) + params = to_create_params(microgrid_id, sample) + params["start_time"] = datetime(2099, 1, 1, 0, 0, 0) # naive, no tzinfo + with raises(ValueError): + await client.create(**params) + + +async def test_create_past_datetime_raises( + client: FakeClient, sample: Dispatch +) -> None: + """Test that create() raises ValueError for a start_time in the past.""" + microgrid_id = MicrogridId(random.randint(1, 100)) + params = to_create_params(microgrid_id, sample) + params["start_time"] = datetime(2000, 1, 1, tzinfo=timezone.utc) + with raises(ValueError): + await client.create(**params)