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
2 changes: 2 additions & 0 deletions examples/all_types/define_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import numpy as np
from nitypes.complex import ComplexInt32DType
from nitypes.scalar import Scalar
from nitypes.vector import Vector
from nitypes.waveform import AnalogWaveform, ComplexWaveform, DigitalWaveform, Spectrum


Expand Down Expand Up @@ -84,6 +85,7 @@ class MyMixedEnum(enum.Enum):
"mixedenum": MyMixedEnum.VALUE2,
# NI types
"nitypes_Scalar": Scalar(42, "m"),
"nitypes_Vector": Vector([1, 2, 3], "volts"),
"nitypes_DoubleAnalogWaveform": AnalogWaveform.from_array_1d(np.array([1.0, 2.0, 3.0])),
"nitypes_I16AnalogWaveform": AnalogWaveform.from_array_1d(np.array([1, 2, 3]), dtype=np.int16),
"nitypes_DoubleComplexWaveform": ComplexWaveform(2, np.complex128),
Expand Down
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 @@ -16,7 +16,7 @@ streamlit = ">=1.24"
nitypes = {version=">=0.1.0dev8", allow-prereleases=true}
numpy = ">=1.22"
debugpy = ">=1.8.1"
ni-protobuf-types = { version = ">=0.1.0dev2", allow-prereleases = true }
ni-protobuf-types = { version = ">=0.1.0dev3", allow-prereleases = true }
ni-panels-v1-proto = { version = ">=0.1.0dev1", allow-prereleases = true }

[tool.poetry.group.dev.dependencies]
Expand Down
2 changes: 2 additions & 0 deletions src/nipanel/_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
IntCollectionConverter,
ScalarConverter,
StrCollectionConverter,
VectorConverter,
)

_logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -61,6 +62,7 @@
IntCollectionConverter(),
StrCollectionConverter(),
ScalarConverter(),
VectorConverter(),
]

_CONVERTIBLE_COLLECTION_TYPES = {
Expand Down
25 changes: 25 additions & 0 deletions src/nipanel/converters/protobuf_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@
precision_timestamp_conversion,
scalar_conversion,
scalar_pb2,
vector_pb2,
vector_conversion,
waveform_conversion,
waveform_pb2,
)
from nitypes.complex import ComplexInt32Base
from nitypes.scalar import Scalar
from nitypes.vector import Vector
from nitypes.waveform import AnalogWaveform, ComplexWaveform, DigitalWaveform, Spectrum
from typing_extensions import TypeAlias

Expand Down Expand Up @@ -422,3 +425,25 @@ def to_protobuf_message(self, python_value: Scalar[_AnyScalarType]) -> scalar_pb
def to_python_value(self, protobuf_message: scalar_pb2.Scalar) -> Scalar[_AnyScalarType]:
"""Convert the protobuf message to a Python Scalar."""
return scalar_conversion.scalar_from_protobuf(protobuf_message)


class VectorConverter(Converter[Vector[_AnyScalarType], vector_pb2.Vector]):
"""A converter for Vector objects."""

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

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

def to_protobuf_message(self, python_value: Vector[Any]) -> vector_pb2.Vector:
"""Convert the Python Vector to a protobuf vector_pb2.Vector."""
return vector_conversion.vector_to_protobuf(python_value)

def to_python_value(self, protobuf_message: vector_pb2.Vector) -> Vector[_AnyScalarType]:
"""Convert the protobuf message to a Python Vector."""
return vector_conversion.vector_from_protobuf(protobuf_message)
31 changes: 30 additions & 1 deletion tests/unit/test_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
import pytest
from google.protobuf import any_pb2, duration_pb2, timestamp_pb2, wrappers_pb2
from google.protobuf.message import Message
from ni.protobuf.types import array_pb2, attribute_value_pb2, scalar_pb2, waveform_pb2
from ni.protobuf.types import array_pb2, attribute_value_pb2, scalar_pb2, vector_pb2, waveform_pb2
from nitypes.complex import ComplexInt32DType
from nitypes.scalar import Scalar
from nitypes.vector import Vector
from nitypes.waveform import AnalogWaveform, ComplexWaveform, DigitalWaveform, Spectrum
from typing_extensions import TypeAlias

Expand Down Expand Up @@ -131,6 +132,8 @@
),
(DigitalWaveform(10, 2, np.bool, False), "nitypes.waveform.DigitalWaveform"),
(Spectrum(10, np.float64), "nitypes.waveform.Spectrum"),
(Scalar("one"), "nitypes.scalar.Scalar"),
(Vector([1, 2, 3]), "nitypes.vector.Vector"),
],
)
def test___various_python_objects___get_best_matching_type___returns_correct_type_string(
Expand Down Expand Up @@ -274,6 +277,17 @@ def test___python_scalar_object___to_any___valid_scalar_proto() -> None:
assert unpack_dest.attributes["NI_UnitDescription"].string_value == "amps"


def test___python_vector_object___to_any___valid_vector_proto() -> None:
vector_obj = Vector([1.0, 2.0, 3.0], "amps")
result = nipanel._convert.to_any(vector_obj)
unpack_dest = vector_pb2.Vector()
_assert_any_and_unpack(result, unpack_dest)

assert isinstance(unpack_dest, vector_pb2.Vector)
assert list(unpack_dest.double_array.values) == [1.0, 2.0, 3.0]
assert unpack_dest.attributes["NI_UnitDescription"].string_value == "amps"


def test___python_float64_analog_waveform___to_any___valid_double_analog_waveform_proto() -> None:
wfm_obj = AnalogWaveform(3, np.float64)
result = nipanel._convert.to_any(wfm_obj)
Expand Down Expand Up @@ -449,6 +463,21 @@ def test___scalar_proto___from_any___valid_python_scalar() -> None:
assert result.units == "amps"


def test___vector_proto___from_any___valid_python_vector() -> None:
attrs = {"NI_UnitDescription": attribute_value_pb2.AttributeValue(string_value="amps")}
pb_value = vector_pb2.Vector(
attributes=attrs,
double_array=array_pb2.DoubleArray(values=[1.0, 2.0, 3.0]),
)
packed_any = _pack_into_any(pb_value)

result = nipanel._convert.from_any(packed_any)

assert isinstance(result, Vector)
assert list(result) == [1.0, 2.0, 3.0]
assert result.units == "amps"


def test___double_analog_waveform_proto___from_any___valid_python_float64_analog_waveform() -> None:
pb_value = waveform_pb2.DoubleAnalogWaveform(y_data=[0.0, 0.0, 0.0])
packed_any = _pack_into_any(pb_value)
Expand Down
142 changes: 37 additions & 105 deletions tests/unit/test_protobuf_type_conversion.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import pytest
from ni.protobuf.types import array_pb2, attribute_value_pb2, scalar_pb2
from ni.protobuf.types import array_pb2, attribute_value_pb2, scalar_pb2, vector_pb2
from nitypes.scalar import Scalar
from nitypes.vector import Vector
from typing_extensions import Mapping

from nipanel.converters.protobuf_types import Double2DArrayConverter, ScalarConverter
from nipanel.converters.protobuf_types import (
Double2DArrayConverter,
ScalarConverter,
VectorConverter,
)


# ========================================================
Expand Down Expand Up @@ -84,47 +89,8 @@ def test___double2darray_empty_data___convert___returns_empty_list() -> None:
# ========================================================
# Scalar: Protobuf to Python
# ========================================================
def test___bool_scalar_protobuf___convert___valid_bool_scalar() -> None:
attrs = _units_to_scalar_attribute_map("volts")
protobuf_value = scalar_pb2.Scalar(attributes=attrs)
protobuf_value.bool_value = True

converter = ScalarConverter()
python_value = converter.to_python_value(protobuf_value)

assert isinstance(python_value.value, bool)
assert python_value.value is True
assert python_value.units == "volts"


def test___int32_scalar_protobuf___convert___valid_int_scalar() -> None:
attrs = _units_to_scalar_attribute_map("volts")
protobuf_value = scalar_pb2.Scalar(attributes=attrs)
protobuf_value.sint32_value = 10

converter = ScalarConverter()
python_value = converter.to_python_value(protobuf_value)

assert isinstance(python_value.value, int)
assert python_value.value == 10
assert python_value.units == "volts"


def test___double_scalar_protobuf___convert___valid_float_scalar() -> None:
attrs = _units_to_scalar_attribute_map("volts")
protobuf_value = scalar_pb2.Scalar(attributes=attrs)
protobuf_value.double_value = 20.0

converter = ScalarConverter()
python_value = converter.to_python_value(protobuf_value)

assert isinstance(python_value.value, float)
assert python_value.value == 20.0
assert python_value.units == "volts"


def test___string_scalar_protobuf___convert___valid_str_scalar() -> None:
attrs = _units_to_scalar_attribute_map("volts")
attrs = _units_to_attribute_map("volts")
protobuf_value = scalar_pb2.Scalar(attributes=attrs)
protobuf_value.string_value = "value"

Expand All @@ -136,65 +102,9 @@ def test___string_scalar_protobuf___convert___valid_str_scalar() -> None:
assert python_value.units == "volts"


def test___scalar_protobuf_value_unset___convert___throws_type_error() -> None:
attrs = _units_to_scalar_attribute_map("volts")
protobuf_value = scalar_pb2.Scalar(attributes=attrs)

converter = ScalarConverter()
with pytest.raises(ValueError) as exc:
_ = converter.to_python_value(protobuf_value)

assert exc.value.args[0].startswith("Could not determine the data type of 'value'.")


def test___scalar_protobuf_units_unset___convert___python_units_blank() -> None:
protobuf_value = scalar_pb2.Scalar()
protobuf_value.bool_value = True

converter = ScalarConverter()
python_value = converter.to_python_value(protobuf_value)

assert isinstance(python_value.value, bool)
assert python_value.value is True
assert python_value.units == ""


# ========================================================
# Scalar: Python to Protobuf
# ========================================================
def test___bool_scalar___convert___valid_bool_scalar_protobuf() -> None:
python_value = Scalar(True, "volts")

converter = ScalarConverter()
protobuf_value = converter.to_protobuf_message(python_value)

assert protobuf_value.WhichOneof("value") == "bool_value"
assert protobuf_value.bool_value is True
assert protobuf_value.attributes["NI_UnitDescription"].string_value == "volts"


def test___int_scalar___convert___valid_int32_scalar_protobuf() -> None:
python_value = Scalar(10, "volts")

converter = ScalarConverter()
protobuf_value = converter.to_protobuf_message(python_value)

assert protobuf_value.WhichOneof("value") == "sint32_value"
assert protobuf_value.sint32_value == 10
assert protobuf_value.attributes["NI_UnitDescription"].string_value == "volts"


def test___float_scalar___convert___valid_double_scalar_protobuf() -> None:
python_value = Scalar(20.0, "volts")

converter = ScalarConverter()
protobuf_value = converter.to_protobuf_message(python_value)

assert protobuf_value.WhichOneof("value") == "double_value"
assert protobuf_value.double_value == 20.0
assert protobuf_value.attributes["NI_UnitDescription"].string_value == "volts"


def test___str_scalar___convert___valid_string_scalar_protobuf() -> None:
python_value = Scalar("value", "volts")

Expand All @@ -206,17 +116,39 @@ def test___str_scalar___convert___valid_string_scalar_protobuf() -> None:
assert protobuf_value.attributes["NI_UnitDescription"].string_value == "volts"


def test___scalar_units_unset___convert___protobuf_units_blank() -> None:
python_value = Scalar(10)
# ========================================================
# Vector: Protobuf to Python
# ========================================================
def test___string_vector_protobuf___convert___valid_str_vector() -> None:
attrs = _units_to_attribute_map("volts")
protobuf_value = vector_pb2.Vector(
attributes=attrs,
string_array=array_pb2.StringArray(values=["one", "two", "three"]),
)

converter = VectorConverter()
python_value = converter.to_python_value(protobuf_value)

converter = ScalarConverter()
assert isinstance(python_value, Vector)
assert list(python_value) == ["one", "two", "three"]
assert python_value.units == "volts"


# ========================================================
# Vector: Python to Protobuf
# ========================================================
def test___str_vector___convert___valid_string_vector_protobuf() -> None:
python_value = Vector(["one", "two", "three"], "volts")

converter = VectorConverter()
protobuf_value = converter.to_protobuf_message(python_value)

assert protobuf_value.WhichOneof("value") == "sint32_value"
assert protobuf_value.sint32_value == 10
assert protobuf_value.attributes["NI_UnitDescription"].string_value == ""
assert isinstance(protobuf_value, vector_pb2.Vector)
assert protobuf_value.WhichOneof("value") == "string_array"
assert list(protobuf_value.string_array.values) == ["one", "two", "three"]
assert protobuf_value.attributes["NI_UnitDescription"].string_value == "volts"


def _units_to_scalar_attribute_map(units: str) -> Mapping[str, attribute_value_pb2.AttributeValue]:
def _units_to_attribute_map(units: str) -> Mapping[str, attribute_value_pb2.AttributeValue]:
value = attribute_value_pb2.AttributeValue(string_value=units)
return {"NI_UnitDescription": value}
Loading