Skip to content
Draft
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
5 changes: 5 additions & 0 deletions src/ni/datastore/data/_grpc_conversion.py
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Ideally we should update the hightime_datetime_to_protobuf method in protobuf library to ensure UTC. This way each and every Python client which uses hightime_datetime_to_protobuf should not need to implement _ensure_utc.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good long-term suggestion. However, ni-protobuf-types is a separate externally-published package (ni.protobuf.types v1.1.0) installed as a dependency — it lives in its own repository and would require its own release cycle to update. Since we can't change it from here, keeping _ensure_utc() in this library is the right approach. If the upstream fix is desired, it should be tracked as a separate issue/PR in the ni-protobuf-types repository.

Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@
_logger = logging.getLogger(__name__)


def _ensure_utc(dt: ht.datetime) -> ht.datetime:
"""Convert a datetime to UTC, treating naive datetimes as local time."""
return dt.astimezone(std_datetime.timezone.utc)


def populate_publish_condition_request_value(
publish_request: PublishConditionRequest, value: object
) -> None:
Expand Down
7 changes: 5 additions & 2 deletions src/ni/datastore/data/_types/_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import Mapping, MutableMapping

import hightime as ht
from ni.datastore.data._grpc_conversion import _ensure_utc
from ni.datastore.data._types._error_information import ErrorInformation
from ni.datastore.data._types._outcome import Outcome
from ni.datastore.metadata._grpc_conversion import (
Expand Down Expand Up @@ -147,12 +148,14 @@ def to_protobuf(self) -> StepProto:
step_type=self.step_type,
notes=self.notes,
start_date_time=(
hightime_datetime_to_protobuf(self.start_date_time)
hightime_datetime_to_protobuf(_ensure_utc(self.start_date_time))
if self.start_date_time
else None
),
end_date_time=(
hightime_datetime_to_protobuf(self.end_date_time) if self.end_date_time else None
hightime_datetime_to_protobuf(_ensure_utc(self.end_date_time))
if self.end_date_time
else None
),
link=self.link,
schema_id=self.schema_id,
Expand Down
7 changes: 5 additions & 2 deletions src/ni/datastore/data/_types/_test_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import Iterable, Mapping, MutableMapping, MutableSequence

import hightime as ht
from ni.datastore.data._grpc_conversion import _ensure_utc
from ni.datastore.data._types._error_information import ErrorInformation
from ni.datastore.data._types._outcome import Outcome
from ni.datastore.metadata._grpc_conversion import (
Expand Down Expand Up @@ -182,12 +183,14 @@ def to_protobuf(self) -> TestResultProto:
test_adapter_ids=self.test_adapter_ids,
name=self.name,
start_date_time=(
hightime_datetime_to_protobuf(self.start_date_time)
hightime_datetime_to_protobuf(_ensure_utc(self.start_date_time))
if self.start_date_time
else None
),
end_date_time=(
hightime_datetime_to_protobuf(self.end_date_time) if self.end_date_time else None
hightime_datetime_to_protobuf(_ensure_utc(self.end_date_time))
if self.end_date_time
else None
),
outcome=self.outcome.to_protobuf(),
link=self.link,
Expand Down
78 changes: 78 additions & 0 deletions tests/unit/data/test_create_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@

from __future__ import annotations

import datetime as std_datetime
from typing import cast
from unittest.mock import NonCallableMock

import hightime as ht
from ni.datastore.data import (
DataStoreClient,
Step,
TestResult,
)
from ni.datastore.data._types._outcome import Outcome
from ni.measurements.data.v1.data_store_service_pb2 import (
CreateStepRequest,
CreateStepResponse,
CreateTestResultRequest,
CreateTestResultResponse,
)
from ni.protobuf.types.precision_timestamp_conversion import hightime_datetime_to_protobuf


def test___create_step___calls_data_store_service_client(
Expand Down Expand Up @@ -66,3 +70,77 @@ def test___create_test_result___calls_data_store_service_client(
request = cast(CreateTestResultRequest, args[0])
assert request.test_result == test_result.to_protobuf()
assert result == "response_id"


def test___test_result_to_protobuf___naive_datetime___converts_to_utc() -> None:
naive_start = ht.datetime(2024, 6, 15, 10, 30, 0)
naive_end = ht.datetime(2024, 6, 15, 11, 0, 0)
test_result = TestResult(
name="test",
start_date_time=naive_start,
end_date_time=naive_end,
outcome=Outcome.PASSED,
)

proto = test_result.to_protobuf()

expected_start = hightime_datetime_to_protobuf(
naive_start.astimezone(std_datetime.timezone.utc)
)
expected_end = hightime_datetime_to_protobuf(naive_end.astimezone(std_datetime.timezone.utc))
assert proto.start_date_time == expected_start
assert proto.end_date_time == expected_end


def test___test_result_to_protobuf___utc_datetime___preserved() -> None:
utc_start = ht.datetime(2024, 6, 15, 10, 30, 0, tzinfo=std_datetime.timezone.utc)
utc_end = ht.datetime(2024, 6, 15, 11, 0, 0, tzinfo=std_datetime.timezone.utc)
test_result = TestResult(
name="test",
start_date_time=utc_start,
end_date_time=utc_end,
outcome=Outcome.PASSED,
)

proto = test_result.to_protobuf()

expected_start = hightime_datetime_to_protobuf(utc_start)
expected_end = hightime_datetime_to_protobuf(utc_end)
assert proto.start_date_time == expected_start
assert proto.end_date_time == expected_end


def test___step_to_protobuf___naive_datetime___converts_to_utc() -> None:
naive_start = ht.datetime(2024, 6, 15, 10, 30, 0)
naive_end = ht.datetime(2024, 6, 15, 11, 0, 0)
step = Step(
name="step",
start_date_time=naive_start,
end_date_time=naive_end,
)

proto = step.to_protobuf()

expected_start = hightime_datetime_to_protobuf(
naive_start.astimezone(std_datetime.timezone.utc)
)
expected_end = hightime_datetime_to_protobuf(naive_end.astimezone(std_datetime.timezone.utc))
assert proto.start_date_time == expected_start
assert proto.end_date_time == expected_end


def test___step_to_protobuf___utc_datetime___preserved() -> None:
utc_start = ht.datetime(2024, 6, 15, 10, 30, 0, tzinfo=std_datetime.timezone.utc)
utc_end = ht.datetime(2024, 6, 15, 11, 0, 0, tzinfo=std_datetime.timezone.utc)
step = Step(
name="step",
start_date_time=utc_start,
end_date_time=utc_end,
)

proto = step.to_protobuf()

expected_start = hightime_datetime_to_protobuf(utc_start)
expected_end = hightime_datetime_to_protobuf(utc_end)
assert proto.start_date_time == expected_start
assert proto.end_date_time == expected_end