Skip to content

Commit 21901aa

Browse files
mjohanse-emrMichael Johansen
andauthored
Create HTDateTimeConverter and rename PrecisionTimestampConverter (#138)
* Create HighTimeDateTimeConverter and rename PrecisionTimestampConverter Signed-off-by: Michael Johansen <[email protected]> * Revert changes to raised TypeError. Signed-off-by: Michael Johansen <[email protected]> * Update get_value docstring based on PR feedback. Signed-off-by: Michael Johansen <[email protected]> --------- Signed-off-by: Michael Johansen <[email protected]> Co-authored-by: Michael Johansen <[email protected]>
1 parent cfea4c1 commit 21901aa

File tree

7 files changed

+132
-13
lines changed

7 files changed

+132
-13
lines changed

examples/all_types/all_types_panel.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import datetime as dt
44
from enum import Enum, Flag
55

6+
import hightime as ht
67
import streamlit as st
78
from define_types import all_types_with_values
89

@@ -121,11 +122,20 @@
121122
st.number_input(label=name, value=default_value, key=name, format="%.2f")
122123
elif isinstance(default_value, str):
123124
st.text_input(label=name, value=default_value, key=name)
125+
elif isinstance(default_value, ht.datetime):
126+
# In order to avoid multiple date_input elements with the same ID,
127+
# specify a unique key for both ht.datetime and dt.datetime.
128+
date = st.date_input(label="date", key="ht1", value=default_value)
129+
time = st.time_input(label="time", key="ht2", value=default_value)
130+
ht_datetime = ht.datetime.combine(date, time, tzinfo=dt.timezone.utc)
131+
panel.set_value(name, ht_datetime)
124132
elif isinstance(default_value, dt.datetime):
125-
date = st.date_input(label="date", value=default_value)
126-
time = st.time_input(label="time", value=default_value)
127-
datetime = dt.datetime.combine(date, time)
128-
panel.set_value(name, datetime)
133+
# In order to avoid multiple date_input elements with the same ID,
134+
# specify a unique key for both ht.datetime and dt.datetime.
135+
date = st.date_input(label="date", key="dt1", value=default_value)
136+
time = st.time_input(label="time", key="dt2", value=default_value)
137+
dt_datetime = dt.datetime.combine(date, time)
138+
panel.set_value(name, dt_datetime)
129139

130140
with col3:
131141
st.write(panel.get_value(name, default_value=default_value))

examples/all_types/define_types.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import datetime as dt
44
import enum
55

6+
import hightime as ht
7+
import nitypes.bintime as bt
68
import numpy as np
79
from nitypes.complex import ComplexInt32DType
810
from nitypes.scalar import Scalar
@@ -74,6 +76,8 @@ class MyMixedEnum(enum.Enum):
7476
"int": 42,
7577
"str": "sample string",
7678
"dt_datetime": dt.datetime.now(),
79+
"ht_datetime": ht.datetime.now(tz=dt.timezone.utc),
80+
"bt_datetime": bt.DateTime.now(tz=dt.timezone.utc),
7781
"dt_timedelta": dt.timedelta(weeks=2, days=5, minutes=12, milliseconds=75),
7882
# supported enum and flag types
7983
"intflags": MyIntFlags.VALUE1 | MyIntFlags.VALUE4,

src/nipanel/_convert.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
StrConverter,
2121
)
2222
from nipanel.converters.protobuf_types import (
23+
BTDateTimeConverter,
2324
BoolCollectionConverter,
2425
BytesCollectionConverter,
2526
DigitalWaveformConverter,
@@ -28,6 +29,7 @@
2829
DoubleComplexWaveformConverter,
2930
DoubleSpectrumConverter,
3031
FloatCollectionConverter,
32+
HTDateTimeConverter,
3133
Int16AnalogWaveformConverter,
3234
Int16ComplexWaveformConverter,
3335
IntCollectionConverter,
@@ -49,6 +51,7 @@
4951
DTDateTimeConverter(),
5052
DTTimeDeltaConverter(),
5153
# Protobuf Types
54+
BTDateTimeConverter(),
5255
BoolCollectionConverter(),
5356
BytesCollectionConverter(),
5457
DigitalWaveformConverter(),
@@ -57,6 +60,7 @@
5760
DoubleComplexWaveformConverter(),
5861
DoubleSpectrumConverter(),
5962
FloatCollectionConverter(),
63+
HTDateTimeConverter(),
6064
Int16AnalogWaveformConverter(),
6165
Int16ComplexWaveformConverter(),
6266
IntCollectionConverter(),

src/nipanel/_panel_value_accessor.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
from typing import TypeVar, overload
77

88
import grpc
9+
import hightime as ht
10+
import nitypes.bintime as bt
911
from ni_measurement_plugin_sdk_service.discovery import DiscoveryClient
1012
from ni_measurement_plugin_sdk_service.grpc.channelpool import GrpcChannelPool
13+
from nitypes.time import convert_datetime
1114

1215
from nipanel._panel_client import _PanelClient
1316

@@ -65,7 +68,8 @@ def get_value(self, value_id: str, default_value: _T | None = None) -> _T | obje
6568
default_value: The default value to return if the value is not set
6669
6770
Returns:
68-
The value, or the default value if not set
71+
The value, or the default value if not set. The returned value will
72+
be the same as default_value, if one was provided.
6973
7074
Raises:
7175
KeyError: If the value is not set and no default value is provided
@@ -81,6 +85,12 @@ def get_value(self, value_id: str, default_value: _T | None = None) -> _T | obje
8185
enum_type = type(default_value)
8286
return enum_type(value)
8387

88+
# The grpc converter always converts PrecisionTimestamp into bt.DateTime, so
89+
# we need to handle the case where they provide an ht.datetime default by
90+
# converting to hightime.
91+
if isinstance(default_value, ht.datetime) and isinstance(value, bt.DateTime):
92+
return convert_datetime(ht.datetime, value)
93+
8494
# lists are allowed to not match, since sets and tuples are converted to lists
8595
if not isinstance(value, list):
8696
raise TypeError(

src/nipanel/converters/protobuf_types.py

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from collections.abc import Collection
66
from typing import Any, Type, Union
77

8+
import hightime as ht
89
import nitypes.bintime as bt
910
import numpy as np
1011
from ni.protobuf.types import (
@@ -377,9 +378,7 @@ def to_python_value(
377378
return waveform_conversion.float64_spectrum_from_protobuf(protobuf_message)
378379

379380

380-
class PrecisionTimestampConverter(
381-
Converter[bt.DateTime, precision_timestamp_pb2.PrecisionTimestamp]
382-
):
381+
class BTDateTimeConverter(Converter[bt.DateTime, precision_timestamp_pb2.PrecisionTimestamp]):
383382
"""A converter for bintime.DateTime types."""
384383

385384
@property
@@ -405,6 +404,44 @@ def to_python_value(
405404
return precision_timestamp_conversion.bintime_datetime_from_protobuf(protobuf_message)
406405

407406

407+
class HTDateTimeConverter(Converter[ht.datetime, precision_timestamp_pb2.PrecisionTimestamp]):
408+
"""A converter for hightime.datetime objects."""
409+
410+
@property
411+
def python_type(self) -> type:
412+
"""The Python type that this converter handles."""
413+
return ht.datetime
414+
415+
@property
416+
def protobuf_message(self) -> Type[precision_timestamp_pb2.PrecisionTimestamp]:
417+
"""The type-specific protobuf message for the Python type."""
418+
return precision_timestamp_pb2.PrecisionTimestamp
419+
420+
@property
421+
def protobuf_typename(self) -> str:
422+
"""The protobuf name for the type.
423+
424+
Override the base class here because there can only be one converter that
425+
converts ``PrecisionTimestamp`` objects. Since there are two converters that convert
426+
to ``PrecisionTimestamp``, we have to choose one to handle conversion from protobuf.
427+
For the purposes of nipanel, we'll convert ``PrecisionTimestamp`` messages to
428+
bintime.DateTime. See ``BTDateTimeConverter``.
429+
"""
430+
return "PrecisionTimestamp_Placeholder"
431+
432+
def to_protobuf_message(
433+
self, python_value: ht.datetime
434+
) -> precision_timestamp_pb2.PrecisionTimestamp:
435+
"""Convert the Python DateTime to a protobuf PrecisionTimestamp."""
436+
return precision_timestamp_conversion.hightime_datetime_to_protobuf(python_value)
437+
438+
def to_python_value(
439+
self, protobuf_message: precision_timestamp_pb2.PrecisionTimestamp
440+
) -> ht.datetime:
441+
"""Convert the protobuf PrecisionTimestamp to a Python DateTime."""
442+
return precision_timestamp_conversion.hightime_datetime_from_protobuf(protobuf_message)
443+
444+
408445
class ScalarConverter(Converter[Scalar[_AnyScalarType], scalar_pb2.Scalar]):
409446
"""A converter for Scalar objects."""
410447

tests/unit/test_convert.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
import datetime as dt
22
from typing import Any, Collection, Union
33

4+
import hightime as ht
5+
import nitypes.bintime as bt
46
import numpy as np
57
import pytest
68
from google.protobuf import any_pb2, duration_pb2, timestamp_pb2, wrappers_pb2
79
from google.protobuf.message import Message
8-
from ni.protobuf.types import array_pb2, attribute_value_pb2, scalar_pb2, vector_pb2, waveform_pb2
10+
from ni.protobuf.types import (
11+
array_pb2,
12+
attribute_value_pb2,
13+
precision_timestamp_pb2,
14+
scalar_pb2,
15+
vector_pb2,
16+
waveform_pb2,
17+
)
918
from nitypes.complex import ComplexInt32DType
1019
from nitypes.scalar import Scalar
20+
from nitypes.time import convert_datetime
1121
from nitypes.vector import Vector
1222
from nitypes.waveform import AnalogWaveform, ComplexWaveform, DigitalWaveform, Spectrum
1323
from typing_extensions import TypeAlias
@@ -50,6 +60,8 @@
5060
(tests.types.MixinStrEnum.VALUE11, "builtins.str"),
5161
(dt.datetime.now(), "datetime.datetime"),
5262
(dt.timedelta(days=1), "datetime.timedelta"),
63+
(bt.DateTime.now(tz=dt.timezone.utc), "nitypes.bintime.DateTime"),
64+
(ht.datetime.now(), "hightime.datetime"),
5365
([False, False], "collections.abc.Collection[builtins.bool]"),
5466
([b"mystr", b"mystr"], "collections.abc.Collection[builtins.bytes]"),
5567
([456.2, 1.0], "collections.abc.Collection[builtins.float]"),
@@ -369,6 +381,31 @@ def test___python_float64_spectrum___to_any___valid_double_spectrum_proto() -> N
369381
assert unpack_dest.frequency_increment == 10.0
370382

371383

384+
def test___python_bintime_datetime__to_any___valid_precision_timestamp_proto() -> None:
385+
python_value = bt.DateTime(year=2020, month=1, day=10, second=45, tzinfo=dt.timezone.utc)
386+
387+
result = nipanel._convert.to_any(python_value)
388+
unpack_dest = precision_timestamp_pb2.PrecisionTimestamp()
389+
_assert_any_and_unpack(result, unpack_dest)
390+
391+
expected_tuple = python_value.to_tuple()
392+
assert unpack_dest.seconds == expected_tuple.whole_seconds
393+
assert unpack_dest.fractional_seconds == expected_tuple.fractional_seconds
394+
395+
396+
def test___python_hightime_datetime__to_any___valid_precision_timestamp_proto() -> None:
397+
python_value = ht.datetime(year=2020, month=1, day=10, second=45, tzinfo=dt.timezone.utc)
398+
399+
result = nipanel._convert.to_any(python_value)
400+
unpack_dest = precision_timestamp_pb2.PrecisionTimestamp()
401+
_assert_any_and_unpack(result, unpack_dest)
402+
403+
expected_bt_datetime = convert_datetime(bt.DateTime, python_value)
404+
expected_tuple = expected_bt_datetime.to_tuple()
405+
assert unpack_dest.seconds == expected_tuple.whole_seconds
406+
assert unpack_dest.fractional_seconds == expected_tuple.fractional_seconds
407+
408+
372409
@pytest.mark.parametrize(
373410
"python_value",
374411
[
@@ -564,6 +601,21 @@ def test___double_spectrum_proto___from_any___valid_python_spectrum() -> None:
564601
assert result.frequency_increment == 10.0
565602

566603

604+
def test___precision_timestamp_proto__from_any___valid_bintime_datetime() -> None:
605+
expected_bt_dt = bt.DateTime(year=2020, month=1, day=10, second=45, tzinfo=dt.timezone.utc)
606+
expected_tuple = expected_bt_dt.to_tuple()
607+
pb_value = precision_timestamp_pb2.PrecisionTimestamp(
608+
seconds=expected_tuple.whole_seconds,
609+
fractional_seconds=expected_tuple.fractional_seconds,
610+
)
611+
packed_any = _pack_into_any(pb_value)
612+
613+
result = nipanel._convert.from_any(packed_any)
614+
615+
assert isinstance(result, bt.DateTime)
616+
assert result == expected_bt_dt
617+
618+
567619
def test___double2darray___from_any___valid_python_2dcollection() -> None:
568620
pb_value = array_pb2.Double2DArray(data=[1.0, 2.0, 3.0, 4.0], rows=2, columns=2)
569621
packed_any = _pack_into_any(pb_value)

tests/unit/test_waveform_conversion.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44

55
import nitypes.bintime as bt
66
import numpy as np
7-
from ni.protobuf.types import precision_timestamp_pb2, waveform_pb2
7+
from ni.protobuf.types import (
8+
precision_timestamp_conversion,
9+
precision_timestamp_pb2,
10+
waveform_pb2,
11+
)
812
from nitypes.complex import ComplexInt32DType
913
from nitypes.waveform import (
1014
AnalogWaveform,
@@ -23,7 +27,6 @@
2327
DoubleSpectrumConverter,
2428
Int16AnalogWaveformConverter,
2529
Int16ComplexWaveformConverter,
26-
PrecisionTimestampConverter,
2730
)
2831

2932
EXPECTED_SAMPLE_INTERVAL = 0.1
@@ -284,8 +287,7 @@ def test___double_spectrum___convert___valid_python_object() -> None:
284287
# Helpers
285288
# ========================================================
286289
def _get_t0_pt() -> precision_timestamp_pb2.PrecisionTimestamp:
287-
pt_converter = PrecisionTimestampConverter()
288-
return pt_converter.to_protobuf_message(EXPECTED_T0_BT)
290+
return precision_timestamp_conversion.bintime_datetime_to_protobuf(EXPECTED_T0_BT)
289291

290292

291293
def _set_python_attributes(

0 commit comments

Comments
 (0)