Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
8 changes: 4 additions & 4 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ numpy = ">=1.22"
debugpy = ">=1.8.1"
ni-grpc-extensions = { version = ">=0.1.0.dev1", allow-prereleases = true }
ni-measurementlink-discovery-v1-client = { version = ">=0.1.0dev0", allow-prereleases = true }
ni-protobuf-types = { version = ">=0.1.0dev3", allow-prereleases = true }
ni-protobuf-types = { version = ">=0.1.0dev4", allow-prereleases = true }
ni-panels-v1-proto = { version = ">=0.1.0dev1", allow-prereleases = true }

[tool.poetry.group.dev.dependencies]
Expand Down
4 changes: 4 additions & 0 deletions src/nipanel/_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
)
from nipanel.converters.protobuf_types import (
BTDateTimeConverter,
BTTimeDeltaConverter,
BoolCollectionConverter,
BytesCollectionConverter,
DigitalWaveformConverter,
Expand All @@ -30,6 +31,7 @@
DoubleSpectrumConverter,
FloatCollectionConverter,
HTDateTimeConverter,
HTTimeDeltaConverter,
Int16AnalogWaveformConverter,
Int16ComplexWaveformConverter,
IntCollectionConverter,
Expand All @@ -52,6 +54,7 @@
DTTimeDeltaConverter(),
# Protobuf Types
BTDateTimeConverter(),
BTTimeDeltaConverter(),
BoolCollectionConverter(),
BytesCollectionConverter(),
DigitalWaveformConverter(),
Expand All @@ -61,6 +64,7 @@
DoubleSpectrumConverter(),
FloatCollectionConverter(),
HTDateTimeConverter(),
HTTimeDeltaConverter(),
Int16AnalogWaveformConverter(),
Int16ComplexWaveformConverter(),
IntCollectionConverter(),
Expand Down
70 changes: 70 additions & 0 deletions src/nipanel/converters/protobuf_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import numpy as np
from ni.protobuf.types import (
array_pb2,
precision_duration_pb2,
precision_duration_conversion,
precision_timestamp_pb2,
precision_timestamp_conversion,
scalar_conversion,
Expand Down Expand Up @@ -404,6 +406,32 @@ def to_python_value(
return precision_timestamp_conversion.bintime_datetime_from_protobuf(protobuf_message)


class BTTimeDeltaConverter(Converter[bt.TimeDelta, precision_duration_pb2.PrecisionDuration]):
"""A converter for bintime.TimeDelta types."""

@property
def python_type(self) -> type:
"""The Python type that this converter handles."""
return bt.TimeDelta

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

def to_protobuf_message(
self, python_value: bt.TimeDelta
) -> precision_duration_pb2.PrecisionDuration:
"""Convert the Python TimeDelta to a protobuf PrecisionDuration."""
return precision_duration_conversion.bintime_timedelta_to_protobuf(python_value)

def to_python_value(
self, protobuf_message: precision_duration_pb2.PrecisionDuration
) -> bt.TimeDelta:
"""Convert the protobuf PrecisionDuration to a Python TimeDelta."""
return precision_duration_conversion.bintime_timedelta_from_protobuf(protobuf_message)


class HTDateTimeConverter(Converter[ht.datetime, precision_timestamp_pb2.PrecisionTimestamp]):
"""A converter for hightime.datetime objects.

Expand Down Expand Up @@ -446,6 +474,48 @@ def to_python_value(
return precision_timestamp_conversion.hightime_datetime_from_protobuf(protobuf_message)


class HTTimeDeltaConverter(Converter[ht.timedelta, precision_duration_pb2.PrecisionDuration]):
"""A converter for hightime.timedelta objects.

.. note:: The nipanel package will always convert PrecisionDuration messages to
bintime.TimeDelta objects using BTTimeDeltaConverter. To use hightime.timedelta
values in a panel, you must pass a hightime.datetime value for the default_value
parameter of the get_value() method on the panel.
"""

@property
def python_type(self) -> type:
"""The Python type that this converter handles."""
return ht.timedelta

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

@property
def protobuf_typename(self) -> str:
"""The protobuf name for the type."""
# Override the base class here because there can only be one converter that
# converts PrecisionDuration objects. Since there are two converters that convert
# to PrecisionDuration, we have to choose one to handle conversion from protobuf.
# For the purposes of nipanel, we'll convert PrecisionDuration messages to
# bintime.TimeDelta. See BTTimeDeltaConverter.
return "PrecisionDuration_Placeholder"

def to_protobuf_message(
self, python_value: ht.timedelta
) -> precision_duration_pb2.PrecisionDuration:
"""Convert the Python timedelta to a protobuf PrecisionDuration."""
return precision_duration_conversion.hightime_timedelta_to_protobuf(python_value)

def to_python_value(
self, protobuf_message: precision_duration_pb2.PrecisionDuration
) -> ht.timedelta:
"""Convert the protobuf PrecisionDuration to a Python timedelta."""
return precision_duration_conversion.hightime_timedelta_from_protobuf(protobuf_message)


class ScalarConverter(Converter[Scalar[_AnyScalarType], scalar_pb2.Scalar]):
"""A converter for Scalar objects."""

Expand Down
45 changes: 44 additions & 1 deletion tests/unit/test_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@
from ni.protobuf.types import (
array_pb2,
attribute_value_pb2,
precision_duration_pb2,
precision_timestamp_pb2,
scalar_pb2,
vector_pb2,
waveform_pb2,
)
from nitypes.complex import ComplexInt32DType
from nitypes.scalar import Scalar
from nitypes.time import convert_datetime
from nitypes.time import convert_datetime, convert_timedelta
from nitypes.vector import Vector
from nitypes.waveform import AnalogWaveform, ComplexWaveform, DigitalWaveform, Spectrum
from typing_extensions import TypeAlias
Expand Down Expand Up @@ -61,7 +62,9 @@
(dt.datetime.now(), "datetime.datetime"),
(dt.timedelta(days=1), "datetime.timedelta"),
(bt.DateTime.now(tz=dt.timezone.utc), "nitypes.bintime.DateTime"),
(bt.TimeDelta(seconds=1), "nitypes.bintime.TimeDelta"),
(ht.datetime.now(), "hightime.datetime"),
(ht.timedelta(days=1), "hightime.timedelta"),
([False, False], "collections.abc.Collection[builtins.bool]"),
([b"mystr", b"mystr"], "collections.abc.Collection[builtins.bytes]"),
([456.2, 1.0], "collections.abc.Collection[builtins.float]"),
Expand Down Expand Up @@ -393,6 +396,18 @@ def test___python_bintime_datetime__to_any___valid_precision_timestamp_proto() -
assert unpack_dest.fractional_seconds == expected_tuple.fractional_seconds


def test___python_bintime_timedelta__to_any___valid_precision_duration_proto() -> None:
python_value = bt.TimeDelta(seconds=12.345)

result = nipanel._convert.to_any(python_value)
unpack_dest = precision_duration_pb2.PrecisionDuration()
_assert_any_and_unpack(result, unpack_dest)

expected_tuple = python_value.to_tuple()
assert unpack_dest.seconds == expected_tuple.whole_seconds
assert unpack_dest.fractional_seconds == expected_tuple.fractional_seconds


def test___python_hightime_datetime__to_any___valid_precision_timestamp_proto() -> None:
python_value = ht.datetime(year=2020, month=1, day=10, second=45, tzinfo=dt.timezone.utc)

Expand All @@ -406,6 +421,19 @@ def test___python_hightime_datetime__to_any___valid_precision_timestamp_proto()
assert unpack_dest.fractional_seconds == expected_tuple.fractional_seconds


def test___python_hightime_timedelta__to_any___valid_precision_duration_proto() -> None:
python_value = ht.timedelta(days=10, seconds=45, picoseconds=60)

result = nipanel._convert.to_any(python_value)
unpack_dest = precision_duration_pb2.PrecisionDuration()
_assert_any_and_unpack(result, unpack_dest)

expected_bt_timedelta = convert_timedelta(bt.TimeDelta, python_value)
expected_tuple = expected_bt_timedelta.to_tuple()
assert unpack_dest.seconds == expected_tuple.whole_seconds
assert unpack_dest.fractional_seconds == expected_tuple.fractional_seconds


@pytest.mark.parametrize(
"python_value",
[
Expand Down Expand Up @@ -616,6 +644,21 @@ def test___precision_timestamp_proto__from_any___valid_bintime_datetime() -> Non
assert result == expected_bt_dt


def test___precision_duration_proto__from_any___valid_bintime_timedelta() -> None:
expected_bt_td = bt.TimeDelta(seconds=45.678)
expected_tuple = expected_bt_td.to_tuple()
pb_value = precision_duration_pb2.PrecisionDuration(
seconds=expected_tuple.whole_seconds,
fractional_seconds=expected_tuple.fractional_seconds,
)
packed_any = _pack_into_any(pb_value)

result = nipanel._convert.from_any(packed_any)

assert isinstance(result, bt.TimeDelta)
assert result == expected_bt_td


def test___double2darray___from_any___valid_python_2dcollection() -> None:
pb_value = array_pb2.Double2DArray(data=[1.0, 2.0, 3.0, 4.0], rows=2, columns=2)
packed_any = _pack_into_any(pb_value)
Expand Down
Loading