Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
10 changes: 10 additions & 0 deletions examples/all_types/all_types_panel.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""A Streamlit visualization panel for the all_types.py example script."""

import datetime as dt
from enum import Enum, Flag

import streamlit as st
Expand Down Expand Up @@ -35,6 +36,15 @@
st.number_input(label=name, value=default_value, key=name, format="%.2f")
elif isinstance(default_value, str):
st.text_input(label=name, value=default_value, key=name)
elif isinstance(default_value, dt.datetime):
date = st.date_input(label="date", value=default_value)
time = st.time_input(label="time", value=default_value)
datetime = dt.datetime.combine(date, time)
panel.set_value(name, datetime)
elif isinstance(default_value, dt.date):
st.date_input(label=name, value=default_value, key=name)
elif isinstance(default_value, dt.time):
st.time_input(label=name, value=default_value, key=name)

with col3:
st.write(panel.get_value(name, default_value=default_value))
2 changes: 2 additions & 0 deletions examples/all_types/define_types.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Define types."""

import datetime as dt
import enum

import numpy as np
Expand Down Expand Up @@ -70,6 +71,7 @@ class MyMixedEnum(enum.Enum):
"float": 13.12,
"int": 42,
"str": "sample string",
"dt_datetime": dt.datetime.now(),
# 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 @@ -12,6 +12,7 @@
from nipanel.converters.builtin import (
BoolConverter,
BytesConverter,
DTDateTimeConverter,
FloatConverter,
IntConverter,
StrConverter,
Expand All @@ -37,6 +38,7 @@
FloatConverter(),
IntConverter(),
StrConverter(),
DTDateTimeConverter(),
# Containers next
BoolCollectionConverter(),
BytesCollectionConverter(),
Expand Down
27 changes: 26 additions & 1 deletion src/nipanel/converters/builtin.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"""Classes to convert between builtin Python scalars and containers."""

import datetime as dt
from collections.abc import Collection
from typing import Type

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

from nipanel.converters import Converter
Expand Down Expand Up @@ -119,6 +120,30 @@ def to_python_value(self, protobuf_message: wrappers_pb2.StringValue) -> str:
return protobuf_message.value


class DTDateTimeConverter(Converter[dt.datetime, timestamp_pb2.Timestamp]):
"""A converter for datetime.datetime types."""

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

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

def to_protobuf_message(self, python_value: dt.datetime) -> timestamp_pb2.Timestamp:
"""Convert the Python dt.datetime to a protobuf timestamp_pb2.Timestamp."""
ts = self.protobuf_message()
ts.FromDatetime(python_value)
return ts

def to_python_value(self, protobuf_message: timestamp_pb2.Timestamp) -> dt.datetime:
"""Convert the protobuf timestamp_pb2.Timestamp to a Python dt.datetime."""
return protobuf_message.ToDatetime()


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

Expand Down
26 changes: 25 additions & 1 deletion tests/unit/test_convert.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import datetime as dt
from typing import Any, Collection, Union

import numpy as np
import pytest
from google.protobuf import any_pb2, wrappers_pb2
from google.protobuf import any_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 @@ -53,6 +54,7 @@
(tests.types.MixinIntEnum.VALUE11, "int"),
(tests.types.MyStrEnum.VALUE1, "str"),
(tests.types.MixinStrEnum.VALUE11, "str"),
(dt.datetime.now(), "datetime"),
([False, False], "Collection.bool"),
([b"mystr", b"mystr"], "Collection.bytes"),
([456.2, 1.0], "Collection.float"),
Expand Down Expand Up @@ -123,6 +125,16 @@ def test___python_builtin_scalar___to_any___valid_wrapperpb2_value(
assert unpack_dest.value == expected_value


def test___python_datetime_datetime___to_any___valid_timestamppb2_value() -> None:
expected_value = dt.datetime.now()
result = nipanel._convert.to_any(expected_value)
unpack_dest = timestamp_pb2.Timestamp()
_assert_any_and_unpack(result, unpack_dest)

assert isinstance(unpack_dest, timestamp_pb2.Timestamp)
assert unpack_dest.ToDatetime() == expected_value


@pytest.mark.parametrize(
"proto_type, default_value, expected_value",
[
Expand Down Expand Up @@ -175,6 +187,18 @@ def test___wrapperpb2_value___from_any___valid_python_value(
assert result == expected_value


def test___timestamppb2_timestamp___from_any___valid_python_value() -> None:
expected_value = dt.datetime.now()
pb_value = timestamp_pb2.Timestamp()
pb_value.FromDatetime(expected_value)
packed_any = _pack_into_any(pb_value)

result = nipanel._convert.from_any(packed_any)

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


@pytest.mark.parametrize(
"proto_type, expected_value",
[
Expand Down
2 changes: 0 additions & 2 deletions tests/unit/test_streamlit_panel.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import enum
from datetime import datetime

import grpc
import pytest
Expand Down Expand Up @@ -312,7 +311,6 @@ def test___enum_type___set_value___gets_same_value(
@pytest.mark.parametrize(
"value_payload",
[
datetime.now(),
lambda x: x + 1,
[1, "string"],
["string", []],
Expand Down