diff --git a/poetry.lock b/poetry.lock index 833711f..2adc8d0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -828,6 +828,16 @@ grpcio = ">=1.67.0" protobuf = ">=5.26.1,<6.0dev" setuptools = "*" +[[package]] +name = "hightime" +version = "0.2.2" +description = "Hightime Python API" +optional = false +python-versions = "*" +files = [ + {file = "hightime-0.2.2-py3-none-any.whl", hash = "sha256:5109a449bb3a75dbf305147777de71634c91b943d47cfbee18ed2f34a8307e0b"}, +] + [[package]] name = "idna" version = "3.10" @@ -1290,6 +1300,26 @@ pycodestyle = [ ] toml = ">=0.10.1" +[[package]] +name = "nitypes" +version = "0.1.0.dev1" +description = "Data types for NI Python APIs" +optional = false +python-versions = "<4.0,>=3.9" +files = [ + {file = "nitypes-0.1.0.dev1-py3-none-any.whl", hash = "sha256:1b763497686a605d0071951b389f2a8a6aa36bfa6d812215f3fcd6c5ffb5b54a"}, + {file = "nitypes-0.1.0.dev1.tar.gz", hash = "sha256:727d1b63316b150dbba98c5aed9c5c9650bd4368c22c3b3e2d4d798f51f1f068"}, +] + +[package.dependencies] +hightime = ">=0.2.2" +numpy = [ + {version = ">=1.22", markers = "python_version >= \"3.9\" and python_version < \"3.12\""}, + {version = ">=1.26", markers = "python_version >= \"3.12\" and python_version < \"3.13\""}, + {version = ">=2.1", markers = "python_version >= \"3.13\" and python_version < \"4.0\""}, +] +typing-extensions = ">=4.13.2" + [[package]] name = "numpy" version = "2.0.2" @@ -1344,6 +1374,66 @@ files = [ {file = "numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78"}, ] +[[package]] +name = "numpy" +version = "2.3.0" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.11" +files = [ + {file = "numpy-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c3c9fdde0fa18afa1099d6257eb82890ea4f3102847e692193b54e00312a9ae9"}, + {file = "numpy-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46d16f72c2192da7b83984aa5455baee640e33a9f1e61e656f29adf55e406c2b"}, + {file = "numpy-2.3.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a0be278be9307c4ab06b788f2a077f05e180aea817b3e41cebbd5aaf7bd85ed3"}, + {file = "numpy-2.3.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:99224862d1412d2562248d4710126355d3a8db7672170a39d6909ac47687a8a4"}, + {file = "numpy-2.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2393a914db64b0ead0ab80c962e42d09d5f385802006a6c87835acb1f58adb96"}, + {file = "numpy-2.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:7729c8008d55e80784bd113787ce876ca117185c579c0d626f59b87d433ea779"}, + {file = "numpy-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:06d4fb37a8d383b769281714897420c5cc3545c79dc427df57fc9b852ee0bf58"}, + {file = "numpy-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c39ec392b5db5088259c68250e342612db82dc80ce044cf16496cf14cf6bc6f8"}, + {file = "numpy-2.3.0-cp311-cp311-win32.whl", hash = "sha256:ee9d3ee70d62827bc91f3ea5eee33153212c41f639918550ac0475e3588da59f"}, + {file = "numpy-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:43c55b6a860b0eb44d42341438b03513cf3879cb3617afb749ad49307e164edd"}, + {file = "numpy-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:2e6a1409eee0cb0316cb64640a49a49ca44deb1a537e6b1121dc7c458a1299a8"}, + {file = "numpy-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:389b85335838155a9076e9ad7f8fdba0827496ec2d2dc32ce69ce7898bde03ba"}, + {file = "numpy-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9498f60cd6bb8238d8eaf468a3d5bb031d34cd12556af53510f05fcf581c1b7e"}, + {file = "numpy-2.3.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:622a65d40d8eb427d8e722fd410ac3ad4958002f109230bc714fa551044ebae2"}, + {file = "numpy-2.3.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b9446d9d8505aadadb686d51d838f2b6688c9e85636a0c3abaeb55ed54756459"}, + {file = "numpy-2.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:50080245365d75137a2bf46151e975de63146ae6d79f7e6bd5c0e85c9931d06a"}, + {file = "numpy-2.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c24bb4113c66936eeaa0dc1e47c74770453d34f46ee07ae4efd853a2ed1ad10a"}, + {file = "numpy-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4d8d294287fdf685281e671886c6dcdf0291a7c19db3e5cb4178d07ccf6ecc67"}, + {file = "numpy-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6295f81f093b7f5769d1728a6bd8bf7466de2adfa771ede944ce6711382b89dc"}, + {file = "numpy-2.3.0-cp312-cp312-win32.whl", hash = "sha256:e6648078bdd974ef5d15cecc31b0c410e2e24178a6e10bf511e0557eed0f2570"}, + {file = "numpy-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:0898c67a58cdaaf29994bc0e2c65230fd4de0ac40afaf1584ed0b02cd74c6fdd"}, + {file = "numpy-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:bd8df082b6c4695753ad6193018c05aac465d634834dca47a3ae06d4bb22d9ea"}, + {file = "numpy-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5754ab5595bfa2c2387d241296e0381c21f44a4b90a776c3c1d39eede13a746a"}, + {file = "numpy-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d11fa02f77752d8099573d64e5fe33de3229b6632036ec08f7080f46b6649959"}, + {file = "numpy-2.3.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:aba48d17e87688a765ab1cd557882052f238e2f36545dfa8e29e6a91aef77afe"}, + {file = "numpy-2.3.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4dc58865623023b63b10d52f18abaac3729346a7a46a778381e0e3af4b7f3beb"}, + {file = "numpy-2.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:df470d376f54e052c76517393fa443758fefcdd634645bc9c1f84eafc67087f0"}, + {file = "numpy-2.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:87717eb24d4a8a64683b7a4e91ace04e2f5c7c77872f823f02a94feee186168f"}, + {file = "numpy-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fa264d56882b59dcb5ea4d6ab6f31d0c58a57b41aec605848b6eb2ef4a43e8"}, + {file = "numpy-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e651756066a0eaf900916497e20e02fe1ae544187cb0fe88de981671ee7f6270"}, + {file = "numpy-2.3.0-cp313-cp313-win32.whl", hash = "sha256:e43c3cce3b6ae5f94696669ff2a6eafd9a6b9332008bafa4117af70f4b88be6f"}, + {file = "numpy-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:81ae0bf2564cf475f94be4a27ef7bcf8af0c3e28da46770fc904da9abd5279b5"}, + {file = "numpy-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:c8738baa52505fa6e82778580b23f945e3578412554d937093eac9205e845e6e"}, + {file = "numpy-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:39b27d8b38942a647f048b675f134dd5a567f95bfff481f9109ec308515c51d8"}, + {file = "numpy-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0eba4a1ea88f9a6f30f56fdafdeb8da3774349eacddab9581a21234b8535d3d3"}, + {file = "numpy-2.3.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:b0f1f11d0a1da54927436505a5a7670b154eac27f5672afc389661013dfe3d4f"}, + {file = "numpy-2.3.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:690d0a5b60a47e1f9dcec7b77750a4854c0d690e9058b7bef3106e3ae9117808"}, + {file = "numpy-2.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:8b51ead2b258284458e570942137155978583e407babc22e3d0ed7af33ce06f8"}, + {file = "numpy-2.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:aaf81c7b82c73bd9b45e79cfb9476cb9c29e937494bfe9092c26aece812818ad"}, + {file = "numpy-2.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f420033a20b4f6a2a11f585f93c843ac40686a7c3fa514060a97d9de93e5e72b"}, + {file = "numpy-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d344ca32ab482bcf8735d8f95091ad081f97120546f3d250240868430ce52555"}, + {file = "numpy-2.3.0-cp313-cp313t-win32.whl", hash = "sha256:48a2e8eaf76364c32a1feaa60d6925eaf32ed7a040183b807e02674305beef61"}, + {file = "numpy-2.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ba17f93a94e503551f154de210e4d50c5e3ee20f7e7a1b5f6ce3f22d419b93bb"}, + {file = "numpy-2.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f14e016d9409680959691c109be98c436c6249eaf7f118b424679793607b5944"}, + {file = "numpy-2.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80b46117c7359de8167cc00a2c7d823bdd505e8c7727ae0871025a86d668283b"}, + {file = "numpy-2.3.0-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:5814a0f43e70c061f47abd5857d120179609ddc32a613138cbb6c4e9e2dbdda5"}, + {file = "numpy-2.3.0-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ef6c1e88fd6b81ac6d215ed71dc8cd027e54d4bf1d2682d362449097156267a2"}, + {file = "numpy-2.3.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33a5a12a45bb82d9997e2c0b12adae97507ad7c347546190a18ff14c28bbca12"}, + {file = "numpy-2.3.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:54dfc8681c1906d239e95ab1508d0a533c4a9505e52ee2d71a5472b04437ef97"}, + {file = "numpy-2.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e017a8a251ff4d18d71f139e28bdc7c31edba7a507f72b1414ed902cbe48c74d"}, + {file = "numpy-2.3.0.tar.gz", hash = "sha256:581f87f9e9e9db2cba2141400e160e9dd644ee248788d6f90636eeb8fd9260a6"}, +] + [[package]] name = "packaging" version = "24.2" @@ -2696,4 +2786,4 @@ watchmedo = ["PyYAML (>=3.10)"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0,!=3.9.7" -content-hash = "f52be166ca9ddc9507e71fd843237d20630eea582a4bcf85d599dcd73cc784fa" +content-hash = "2405bfc23e77ee98c4fe58845fa73632b884c4bf6acded29ece6c9380a168675" diff --git a/protos/ni/protobuf/types/scalar.proto b/protos/ni/protobuf/types/scalar.proto new file mode 100644 index 0000000..67493e5 --- /dev/null +++ b/protos/ni/protobuf/types/scalar.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package ni.protobuf.types; + +option csharp_namespace = "NationalInstruments.Protobuf.Types"; +option go_package = "types"; +option java_multiple_files = true; +option java_outer_classname = "ScalarProto"; +option java_package = "com.ni.protobuf.types"; +option objc_class_prefix = "NIPT"; +option php_namespace = "NI\\PROTOBUF\\TYPES"; +option ruby_package = "NI::Protobuf::Types"; + +message ScalarData { + string units = 1; + + oneof value { + double double_value = 2; + int32 int32_value = 3; + bool bool_value = 4; + string string_value = 5; + } +} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index e6ac2fb..11ecccc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ protobuf = {version=">=4.21"} ni-measurement-plugin-sdk = {version=">=2.3"} typing-extensions = ">=4.13.2" streamlit = ">=1.24" +nitypes = {version=">=0.1.0dev1", allow-prereleases=true} [tool.poetry.group.dev.dependencies] types-grpcio = ">=1.0" @@ -52,10 +53,10 @@ requires = ["poetry-core>=1.8.0"] build-backend = "poetry.core.masonry.api" [tool.ni-python-styleguide] -extend_exclude = ".tox,docs,src/ni/pythonpanel/v1" +extend_exclude = ".tox,docs,src/ni/pythonpanel/v1,src/ni/protobuf/types/" [tool.black] -extend-exclude = '\.tox/|docs/|src/ni/pythonpanel/v1/' +extend-exclude = '\.tox/|docs/|src/ni/pythonpanel/v1/|src/ni/protobuf/types/' line-length = 100 [tool.mypy] diff --git a/src/ni/protobuf/types/scalar_pb2.py b/src/ni/protobuf/types/scalar_pb2.py new file mode 100644 index 0000000..927798c --- /dev/null +++ b/src/ni/protobuf/types/scalar_pb2.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: ni/protobuf/types/scalar.proto +"""Generated protocol buffer code.""" +from google.protobuf.internal import builder as _builder +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1eni/protobuf/types/scalar.proto\x12\x11ni.protobuf.types\"\x81\x01\n\nScalarData\x12\r\n\x05units\x18\x01 \x01(\t\x12\x16\n\x0c\x64ouble_value\x18\x02 \x01(\x01H\x00\x12\x15\n\x0bint32_value\x18\x03 \x01(\x05H\x00\x12\x14\n\nbool_value\x18\x04 \x01(\x08H\x00\x12\x16\n\x0cstring_value\x18\x05 \x01(\tH\x00\x42\x07\n\x05valueB\x83\x01\n\x15\x63om.ni.protobuf.typesB\x0bScalarProtoP\x01Z\x05types\xa2\x02\x04NIPT\xaa\x02\"NationalInstruments.Protobuf.Types\xca\x02\x11NI\\PROTOBUF\\TYPES\xea\x02\x13NI::Protobuf::Typesb\x06proto3') + +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'ni.protobuf.types.scalar_pb2', globals()) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + DESCRIPTOR._serialized_options = b'\n\025com.ni.protobuf.typesB\013ScalarProtoP\001Z\005types\242\002\004NIPT\252\002\"NationalInstruments.Protobuf.Types\312\002\021NI\\PROTOBUF\\TYPES\352\002\023NI::Protobuf::Types' + _SCALARDATA._serialized_start=54 + _SCALARDATA._serialized_end=183 +# @@protoc_insertion_point(module_scope) diff --git a/src/ni/protobuf/types/scalar_pb2.pyi b/src/ni/protobuf/types/scalar_pb2.pyi new file mode 100644 index 0000000..f2018eb --- /dev/null +++ b/src/ni/protobuf/types/scalar_pb2.pyi @@ -0,0 +1,40 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" + +import builtins +import google.protobuf.descriptor +import google.protobuf.message +import typing + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +@typing.final +class ScalarData(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + UNITS_FIELD_NUMBER: builtins.int + DOUBLE_VALUE_FIELD_NUMBER: builtins.int + INT32_VALUE_FIELD_NUMBER: builtins.int + BOOL_VALUE_FIELD_NUMBER: builtins.int + STRING_VALUE_FIELD_NUMBER: builtins.int + units: builtins.str + double_value: builtins.float + int32_value: builtins.int + bool_value: builtins.bool + string_value: builtins.str + def __init__( + self, + *, + units: builtins.str = ..., + double_value: builtins.float = ..., + int32_value: builtins.int = ..., + bool_value: builtins.bool = ..., + string_value: builtins.str = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["bool_value", b"bool_value", "double_value", b"double_value", "int32_value", b"int32_value", "string_value", b"string_value", "value", b"value"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["bool_value", b"bool_value", "double_value", b"double_value", "int32_value", b"int32_value", "string_value", b"string_value", "units", b"units", "value", b"value"]) -> None: ... + def WhichOneof(self, oneof_group: typing.Literal["value", b"value"]) -> typing.Literal["double_value", "int32_value", "bool_value", "string_value"] | None: ... + +global___ScalarData = ScalarData diff --git a/src/ni/protobuf/types/scalar_pb2_grpc.py b/src/ni/protobuf/types/scalar_pb2_grpc.py new file mode 100644 index 0000000..2daafff --- /dev/null +++ b/src/ni/protobuf/types/scalar_pb2_grpc.py @@ -0,0 +1,4 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + diff --git a/src/ni/protobuf/types/scalar_pb2_grpc.pyi b/src/ni/protobuf/types/scalar_pb2_grpc.pyi new file mode 100644 index 0000000..a6a9cff --- /dev/null +++ b/src/ni/protobuf/types/scalar_pb2_grpc.pyi @@ -0,0 +1,17 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" + +import abc +import collections.abc +import grpc +import grpc.aio +import typing + +_T = typing.TypeVar("_T") + +class _MaybeAsyncIterator(collections.abc.AsyncIterator[_T], collections.abc.Iterator[_T], metaclass=abc.ABCMeta): ... + +class _ServicerContext(grpc.ServicerContext, grpc.aio.ServicerContext): # type: ignore[misc, type-arg] + ... diff --git a/src/nipanel/converters/protobuf_types.py b/src/nipanel/converters/protobuf_types.py new file mode 100644 index 0000000..21eb99c --- /dev/null +++ b/src/nipanel/converters/protobuf_types.py @@ -0,0 +1,55 @@ +"""Classes to convert between measurement specific protobuf types and containers.""" + +from typing import Type, Union + +from ni.protobuf.types import scalar_pb2 +from nitypes.scalar import Scalar +from typing_extensions import TypeAlias + +from nipanel.converters import Converter + +_AnyScalarType: TypeAlias = Union[bool, int, float, str] +_SCALAR_TYPE_TO_PB_ATTR_MAP = { + bool: "bool_value", + int: "int32_value", + float: "double_value", + str: "string_value", +} + + +class ScalarConverter(Converter[Scalar[_AnyScalarType], scalar_pb2.ScalarData]): + """A converter for Scalar objects.""" + + @property + def python_typename(self) -> str: + """The Python type that this converter handles.""" + return Scalar.__name__ + + @property + def protobuf_message(self) -> Type[scalar_pb2.ScalarData]: + """The type-specific protobuf message for the Python type.""" + return scalar_pb2.ScalarData + + def to_protobuf_message(self, python_value: Scalar[_AnyScalarType]) -> scalar_pb2.ScalarData: + """Convert the Python Scalar to a protobuf scalar_pb2.ScalarData.""" + message = self.protobuf_message() + message.units = python_value.units + + value_attr = _SCALAR_TYPE_TO_PB_ATTR_MAP.get(type(python_value.value), None) + if not value_attr: + raise TypeError(f"Unexpected type for python_value.value: {type(python_value.value)}") + setattr(message, value_attr, python_value.value) + + return message + + def to_python_value(self, protobuf_value: scalar_pb2.ScalarData) -> Scalar[_AnyScalarType]: + """Convert the protobuf message to a Python Scalar.""" + if protobuf_value.units is None: + raise ValueError("protobuf.units cannot be None.") + + pb_type = str(protobuf_value.WhichOneof("value")) + if pb_type not in _SCALAR_TYPE_TO_PB_ATTR_MAP.values(): + raise ValueError(f"Unexpected value for protobuf_value.WhichOneOf: {pb_type}") + + value = getattr(protobuf_value, pb_type) + return Scalar(value, protobuf_value.units) diff --git a/tests/unit/test_protobuf_type_conversion.py b/tests/unit/test_protobuf_type_conversion.py new file mode 100644 index 0000000..5dc841f --- /dev/null +++ b/tests/unit/test_protobuf_type_conversion.py @@ -0,0 +1,141 @@ +import pytest +from ni.protobuf.types.scalar_pb2 import ScalarData +from nitypes.scalar import Scalar + +from nipanel.converters.protobuf_types import ScalarConverter + + +# ======================================================== +# Protobuf to Python +# ======================================================== +def test___bool_scalar_protobuf___convert___valid_bool_scalar() -> None: + protobuf_value = ScalarData() + protobuf_value.units = "volts" + 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: + protobuf_value = ScalarData() + protobuf_value.units = "volts" + protobuf_value.int32_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: + protobuf_value = ScalarData() + protobuf_value.units = "volts" + 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: + protobuf_value = ScalarData() + protobuf_value.units = "volts" + protobuf_value.string_value = "value" + + converter = ScalarConverter() + python_value = converter.to_python_value(protobuf_value) + + assert isinstance(python_value.value, str) + assert python_value.value == "value" + assert python_value.units == "volts" + + +def test___scalar_protobuf_value_unset___convert___throws_type_error() -> None: + protobuf_value = ScalarData() + protobuf_value.units = "volts" + + converter = ScalarConverter() + with pytest.raises(ValueError) as exc: + _ = converter.to_python_value(protobuf_value) + + assert exc.value.args[0].startswith("Unexpected value for protobuf_value.WhichOneOf") + + +def test___scalar_protobuf_units_unset___convert___python_units_blank() -> None: + protobuf_value = ScalarData() + 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 == "" + + +# ======================================================== +# 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.units == "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") == "int32_value" + assert protobuf_value.int32_value == 10 + assert protobuf_value.units == "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.units == "volts" + + +def test___str_scalar___convert___valid_string_scalar_protobuf() -> None: + python_value = Scalar("value", "volts") + + converter = ScalarConverter() + protobuf_value = converter.to_protobuf_message(python_value) + + assert protobuf_value.WhichOneof("value") == "string_value" + assert protobuf_value.string_value == "value" + assert protobuf_value.units == "volts" + + +def test___scalar_units_unset___convert___protobuf_units_blank() -> None: + python_value = Scalar(10) + + converter = ScalarConverter() + protobuf_value = converter.to_protobuf_message(python_value) + + assert protobuf_value.WhichOneof("value") == "int32_value" + assert protobuf_value.int32_value == 10 + assert protobuf_value.units == ""