Skip to content
Merged
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
1 change: 1 addition & 0 deletions examples/all_types/define_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class MyMixedEnum(enum.Enum):
"int": 42,
"str": "sample string",
"dt_datetime": dt.datetime.now(),
"dt_timedelta": dt.timedelta(weeks=2, days=5, minutes=12, milliseconds=75),
# supported enum and flag types
"intflags": MyIntFlags.VALUE1 | MyIntFlags.VALUE4,
"intenum": MyIntEnum.VALUE20,
Expand Down
2 changes: 2 additions & 0 deletions src/nipanel/_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
BoolConverter,
BytesConverter,
DTDateTimeConverter,
DTTimeDeltaConverter,
FloatConverter,
IntConverter,
StrConverter,
Expand All @@ -39,6 +40,7 @@
IntConverter(),
StrConverter(),
DTDateTimeConverter(),
DTTimeDeltaConverter(),
# Containers next
BoolCollectionConverter(),
BytesCollectionConverter(),
Expand Down
26 changes: 25 additions & 1 deletion src/nipanel/converters/builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from collections.abc import Collection
from typing import Type

from google.protobuf import timestamp_pb2, wrappers_pb2
from google.protobuf import duration_pb2, timestamp_pb2, wrappers_pb2
from ni.panels.v1 import panel_types_pb2

from nipanel.converters import Converter
Expand Down Expand Up @@ -144,6 +144,30 @@ def to_python_value(self, protobuf_message: timestamp_pb2.Timestamp) -> dt.datet
return protobuf_message.ToDatetime()


class DTTimeDeltaConverter(Converter[dt.timedelta, duration_pb2.Duration]):
"""A converter for datetime.timedelta types."""

@property
def python_typename(self) -> str:
"""The Python type that this converter handles."""
return dt.timedelta.__name__

@property
def protobuf_message(self) -> Type[duration_pb2.Duration]:
"""The type-specific protobuf message for the Python type."""
return duration_pb2.Duration

def to_protobuf_message(self, python_value: dt.timedelta) -> duration_pb2.Duration:
"""Convert the Python dt.timedelta to a protobuf duration_pb2.Duration."""
dur = self.protobuf_message()
dur.FromTimedelta(python_value)
return dur

def to_python_value(self, protobuf_message: duration_pb2.Duration) -> dt.timedelta:
"""Convert the protobuf timestamp_pb2.Timestamp to a Python dt.timedelta."""
return protobuf_message.ToTimedelta()


class BoolCollectionConverter(Converter[Collection[bool], panel_types_pb2.BoolCollection]):
"""A converter for a Collection of bools."""

Expand Down
25 changes: 24 additions & 1 deletion tests/unit/test_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import numpy as np
import pytest
from google.protobuf import any_pb2, timestamp_pb2, wrappers_pb2
from google.protobuf import any_pb2, duration_pb2, timestamp_pb2, wrappers_pb2
from google.protobuf.message import Message
from ni.panels.v1 import panel_types_pb2
from ni.protobuf.types.scalar_pb2 import ScalarData
Expand Down Expand Up @@ -55,6 +55,7 @@
(tests.types.MyStrEnum.VALUE1, "str"),
(tests.types.MixinStrEnum.VALUE11, "str"),
(dt.datetime.now(), "datetime"),
(dt.timedelta(days=1), "timedelta"),
([False, False], "Collection.bool"),
([b"mystr", b"mystr"], "Collection.bytes"),
([456.2, 1.0], "Collection.float"),
Expand Down Expand Up @@ -135,6 +136,16 @@ def test___python_datetime_datetime___to_any___valid_timestamppb2_value() -> Non
assert unpack_dest.ToDatetime() == expected_value


def test___python_datetime_timedelta___to_any___valid_durationpb2_value() -> None:
expected_value = dt.timedelta(days=1, seconds=2, microseconds=3)
result = nipanel._convert.to_any(expected_value)
unpack_dest = duration_pb2.Duration()
_assert_any_and_unpack(result, unpack_dest)

assert isinstance(unpack_dest, duration_pb2.Duration)
assert unpack_dest.ToTimedelta() == expected_value


@pytest.mark.parametrize(
"proto_type, default_value, expected_value",
[
Expand Down Expand Up @@ -199,6 +210,18 @@ def test___timestamppb2_timestamp___from_any___valid_python_value() -> None:
assert result == expected_value


def test___durationpb2_timestamp___from_any___valid_python_value() -> None:
expected_value = dt.timedelta(weeks=1, hours=2, minutes=3)
pb_value = duration_pb2.Duration()
pb_value.FromTimedelta(expected_value)
packed_any = _pack_into_any(pb_value)

result = nipanel._convert.from_any(packed_any)

assert isinstance(result, dt.timedelta)
assert result == expected_value


@pytest.mark.parametrize(
"proto_type, expected_value",
[
Expand Down
Loading