From c621a41c1ea5ab04204dd0ec789d7fedfef5d323 Mon Sep 17 00:00:00 2001 From: Michael Johansen Date: Sun, 3 Aug 2025 14:29:11 -0500 Subject: [PATCH 1/3] Disambiguate datetime and timedelta types when converting. Signed-off-by: Michael Johansen --- src/nipanel/_convert.py | 22 ++++++++++++++++++++-- src/nipanel/converters/builtin.py | 2 +- tests/unit/test_convert.py | 2 +- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/nipanel/_convert.py b/src/nipanel/_convert.py index 6de5eff..d14457f 100644 --- a/src/nipanel/_convert.py +++ b/src/nipanel/_convert.py @@ -4,7 +4,7 @@ import logging from collections.abc import Collection -from typing import Any +from typing import Any, Iterable from google.protobuf import any_pb2 @@ -102,7 +102,7 @@ def _get_best_matching_type(python_value: object) -> str: container_types.append(Collection.__name__) best_matching_type = None - candidates = [parent.__name__ for parent in underlying_parents] + candidates = _get_candidate_strings(underlying_parents) for candidate in candidates: containers_str = ".".join(container_types) python_typename = f"{containers_str}.{candidate}" if containers_str else candidate @@ -140,3 +140,21 @@ def is_supported_type(value: object) -> bool: return True except TypeError: return False + + +def _get_candidate_strings(candidates: Iterable[type]) -> list[str]: + names_to_disambiguate = [ + "datetime", + "DateTime", + "timedelta", + "TimeDelta", + ] + + candidate_names = [] + for candidate in candidates: + if candidate.__name__ in names_to_disambiguate: + candidate_names.append(f"{candidate.__module__}.{candidate.__name__}") + else: + candidate_names.append(candidate.__name__) + + return candidate_names diff --git a/src/nipanel/converters/builtin.py b/src/nipanel/converters/builtin.py index 50c9b93..29ce579 100644 --- a/src/nipanel/converters/builtin.py +++ b/src/nipanel/converters/builtin.py @@ -126,7 +126,7 @@ class DTDateTimeConverter(Converter[dt.datetime, timestamp_pb2.Timestamp]): @property def python_typename(self) -> str: """The Python type that this converter handles.""" - return dt.datetime.__name__ + return f"{dt.datetime.__module__}.{dt.datetime.__name__}" @property def protobuf_message(self) -> Type[timestamp_pb2.Timestamp]: diff --git a/tests/unit/test_convert.py b/tests/unit/test_convert.py index ea20bef..ed08e67 100644 --- a/tests/unit/test_convert.py +++ b/tests/unit/test_convert.py @@ -54,7 +54,7 @@ (tests.types.MixinIntEnum.VALUE11, "int"), (tests.types.MyStrEnum.VALUE1, "str"), (tests.types.MixinStrEnum.VALUE11, "str"), - (dt.datetime.now(), "datetime"), + (dt.datetime.now(), "datetime.datetime"), ([False, False], "Collection.bool"), ([b"mystr", b"mystr"], "Collection.bytes"), ([456.2, 1.0], "Collection.float"), From 189791ba40976b826b7ed08bf4f76957e83233b9 Mon Sep 17 00:00:00 2001 From: Michael Johansen Date: Mon, 4 Aug 2025 14:18:17 -0500 Subject: [PATCH 2/3] Add the module string for all converters, not just time-based ones. Signed-off-by: Michael Johansen --- src/nipanel/_convert.py | 12 +-- src/nipanel/converters/builtin.py | 20 ++--- src/nipanel/converters/protobuf_types.py | 8 +- tests/unit/test_convert.py | 100 ++++++++++++----------- 4 files changed, 68 insertions(+), 72 deletions(-) diff --git a/src/nipanel/_convert.py b/src/nipanel/_convert.py index 819fdcf..006176e 100644 --- a/src/nipanel/_convert.py +++ b/src/nipanel/_convert.py @@ -145,18 +145,8 @@ def is_supported_type(value: object) -> bool: def _get_candidate_strings(candidates: Iterable[type]) -> list[str]: - names_to_disambiguate = [ - "datetime", - "DateTime", - "timedelta", - "TimeDelta", - ] - candidate_names = [] for candidate in candidates: - if candidate.__name__ in names_to_disambiguate: - candidate_names.append(f"{candidate.__module__}.{candidate.__name__}") - else: - candidate_names.append(candidate.__name__) + candidate_names.append(f"{candidate.__module__}.{candidate.__name__}") return candidate_names diff --git a/src/nipanel/converters/builtin.py b/src/nipanel/converters/builtin.py index 0c752c1..0a426b4 100644 --- a/src/nipanel/converters/builtin.py +++ b/src/nipanel/converters/builtin.py @@ -16,7 +16,7 @@ class BoolConverter(Converter[bool, wrappers_pb2.BoolValue]): @property def python_typename(self) -> str: """The Python type that this converter handles.""" - return bool.__name__ + return f"{bool.__module__}.{bool.__name__}" @property def protobuf_message(self) -> Type[wrappers_pb2.BoolValue]: @@ -38,7 +38,7 @@ class BytesConverter(Converter[bytes, wrappers_pb2.BytesValue]): @property def python_typename(self) -> str: """The Python type that this converter handles.""" - return bytes.__name__ + return f"{bytes.__module__}.{bytes.__name__}" @property def protobuf_message(self) -> Type[wrappers_pb2.BytesValue]: @@ -60,7 +60,7 @@ class FloatConverter(Converter[float, wrappers_pb2.DoubleValue]): @property def python_typename(self) -> str: """The Python type that this converter handles.""" - return float.__name__ + return f"{float.__module__}.{float.__name__}" @property def protobuf_message(self) -> Type[wrappers_pb2.DoubleValue]: @@ -82,7 +82,7 @@ class IntConverter(Converter[int, wrappers_pb2.Int64Value]): @property def python_typename(self) -> str: """The Python type that this converter handles.""" - return int.__name__ + return f"{int.__module__}.{int.__name__}" @property def protobuf_message(self) -> Type[wrappers_pb2.Int64Value]: @@ -104,7 +104,7 @@ class StrConverter(Converter[str, wrappers_pb2.StringValue]): @property def python_typename(self) -> str: """The Python type that this converter handles.""" - return str.__name__ + return f"{str.__module__}.{str.__name__}" @property def protobuf_message(self) -> Type[wrappers_pb2.StringValue]: @@ -174,7 +174,7 @@ class BoolCollectionConverter(Converter[Collection[bool], panel_types_pb2.BoolCo @property def python_typename(self) -> str: """The Python type that this converter handles.""" - return f"{Collection.__name__}.{bool.__name__}" + return f"{Collection.__name__}.{bool.__module__}.{bool.__name__}" @property def protobuf_message(self) -> Type[panel_types_pb2.BoolCollection]: @@ -196,7 +196,7 @@ class BytesCollectionConverter(Converter[Collection[bytes], panel_types_pb2.Byte @property def python_typename(self) -> str: """The Python type that this converter handles.""" - return f"{Collection.__name__}.{bytes.__name__}" + return f"{Collection.__name__}.{bytes.__module__}.{bytes.__name__}" @property def protobuf_message(self) -> Type[panel_types_pb2.ByteStringCollection]: @@ -222,7 +222,7 @@ class FloatCollectionConverter(Converter[Collection[float], panel_types_pb2.Floa @property def python_typename(self) -> str: """The Python type that this converter handles.""" - return f"{Collection.__name__}.{float.__name__}" + return f"{Collection.__name__}.{float.__module__}.{float.__name__}" @property def protobuf_message(self) -> Type[panel_types_pb2.FloatCollection]: @@ -248,7 +248,7 @@ class IntCollectionConverter(Converter[Collection[int], panel_types_pb2.IntColle @property def python_typename(self) -> str: """The Python type that this converter handles.""" - return f"{Collection.__name__}.{int.__name__}" + return f"{Collection.__name__}.{int.__module__}.{int.__name__}" @property def protobuf_message(self) -> Type[panel_types_pb2.IntCollection]: @@ -270,7 +270,7 @@ class StrCollectionConverter(Converter[Collection[str], panel_types_pb2.StringCo @property def python_typename(self) -> str: """The Python type that this converter handles.""" - return f"{Collection.__name__}.{str.__name__}" + return f"{Collection.__name__}.{str.__module__}.{str.__name__}" @property def protobuf_message(self) -> Type[panel_types_pb2.StringCollection]: diff --git a/src/nipanel/converters/protobuf_types.py b/src/nipanel/converters/protobuf_types.py index 2cef62f..49fde4a 100644 --- a/src/nipanel/converters/protobuf_types.py +++ b/src/nipanel/converters/protobuf_types.py @@ -48,7 +48,7 @@ class Double2DArrayConverter(Converter[Collection[Collection[float]], Double2DAr @property def python_typename(self) -> str: """The Python type that this converter handles.""" - return f"{Collection.__name__}.{Collection.__name__}.{float.__name__}" + return f"{Collection.__name__}.{Collection.__name__}.{float.__module__}.{float.__name__}" @property def protobuf_message(self) -> Type[Double2DArray]: @@ -97,7 +97,7 @@ def __init__(self) -> None: @property def python_typename(self) -> str: """The Python type that this converter handles.""" - return AnalogWaveform.__name__ + return f"{AnalogWaveform.__module__}.{AnalogWaveform.__name__}" @property def protobuf_message(self) -> Type[DoubleAnalogWaveform]: @@ -194,7 +194,7 @@ class PrecisionTimestampConverter(Converter[bt.DateTime, PrecisionTimestamp]): @property def python_typename(self) -> str: """The Python type that this converter handles.""" - return bt.DateTime.__name__ + return f"{bt.DateTime.__module__}.{bt.DateTime.__name__}" @property def protobuf_message(self) -> Type[PrecisionTimestamp]: @@ -220,7 +220,7 @@ class ScalarConverter(Converter[Scalar[_AnyScalarType], scalar_pb2.ScalarData]): @property def python_typename(self) -> str: """The Python type that this converter handles.""" - return Scalar.__name__ + return f"{Scalar.__module__}.{Scalar.__name__}" @property def protobuf_message(self) -> Type[scalar_pb2.ScalarData]: diff --git a/tests/unit/test_convert.py b/tests/unit/test_convert.py index e62a711..77fc54f 100644 --- a/tests/unit/test_convert.py +++ b/tests/unit/test_convert.py @@ -44,55 +44,61 @@ @pytest.mark.parametrize( "python_object, expected_type_string", [ - (False, "bool"), - (b"mystr", "bytes"), - (456.2, "float"), - (123, "int"), - ("mystr", "str"), - (tests.types.MyIntFlags.VALUE1, "int"), - (tests.types.MyIntEnum.VALUE10, "int"), - (tests.types.MixinIntEnum.VALUE11, "int"), - (tests.types.MyStrEnum.VALUE1, "str"), - (tests.types.MixinStrEnum.VALUE11, "str"), + (False, "builtins.bool"), + (b"mystr", "builtins.bytes"), + (456.2, "builtins.float"), + (123, "builtins.int"), + ("mystr", "builtins.str"), + (tests.types.MyIntFlags.VALUE1, "builtins.int"), + (tests.types.MyIntEnum.VALUE10, "builtins.int"), + (tests.types.MixinIntEnum.VALUE11, "builtins.int"), + (tests.types.MyStrEnum.VALUE1, "builtins.str"), + (tests.types.MixinStrEnum.VALUE11, "builtins.str"), (dt.datetime.now(), "datetime.datetime"), (dt.timedelta(days=1), "datetime.timedelta"), - ([False, False], "Collection.bool"), - ([b"mystr", b"mystr"], "Collection.bytes"), - ([456.2, 1.0], "Collection.float"), - ([123, 456], "Collection.int"), - (["mystr", "mystr"], "Collection.str"), - ((False, False), "Collection.bool"), - ((b"mystr", b"mystr"), "Collection.bytes"), - ((456.2, 1.0), "Collection.float"), - ((123, 456), "Collection.int"), - (("mystr", "mystr"), "Collection.str"), - ((False, False), "Collection.bool"), - ((b"mystr", b"mystr"), "Collection.bytes"), - ((456.2, 1.0), "Collection.float"), - ((123, 456), "Collection.int"), - (("mystr", "mystr"), "Collection.str"), - (set([False, True]), "Collection.bool"), - (set([b"mystr", b"mystr2"]), "Collection.bytes"), - (set([456.2, 1.0]), "Collection.float"), - (set([123, 456]), "Collection.int"), - (set(["mystr", "mystr2"]), "Collection.str"), - (frozenset([False, True]), "Collection.bool"), - (frozenset([b"mystr", b"mystr2"]), "Collection.bytes"), - (frozenset([456.2, 1.0]), "Collection.float"), - (frozenset([123, 456]), "Collection.int"), - (frozenset(["mystr", "mystr2"]), "Collection.str"), - ([[1.0, 2.0], [1.0, 2.0]], "Collection.Collection.float"), - ([(1.0, 2.0), (3.0, 4.0)], "Collection.Collection.float"), - ([set([1.0, 2.0]), set([3.0, 4.0])], "Collection.Collection.float"), - ([frozenset([1.0, 2.0]), frozenset([3.0, 4.0])], "Collection.Collection.float"), - (([1.0, 2.0], [3.0, 4.0]), "Collection.Collection.float"), - (((1.0, 2.0), (3.0, 4.0)), "Collection.Collection.float"), - ((set([1.0, 2.0]), set([3.0, 4.0])), "Collection.Collection.float"), - ((frozenset([1.0, 2.0]), frozenset([3.0, 4.0])), "Collection.Collection.float"), - (set([(1.0, 2.0), (3.0, 4.0)]), "Collection.Collection.float"), - (set([frozenset([1.0, 2.0]), frozenset([3.0, 4.0])]), "Collection.Collection.float"), - (frozenset([(1.0, 2.0), (3.0, 4.0)]), "Collection.Collection.float"), - (frozenset([frozenset([1.0, 2.0]), frozenset([3.0, 4.0])]), "Collection.Collection.float"), + ([False, False], "Collection.builtins.bool"), + ([b"mystr", b"mystr"], "Collection.builtins.bytes"), + ([456.2, 1.0], "Collection.builtins.float"), + ([123, 456], "Collection.builtins.int"), + (["mystr", "mystr"], "Collection.builtins.str"), + ((False, False), "Collection.builtins.bool"), + ((b"mystr", b"mystr"), "Collection.builtins.bytes"), + ((456.2, 1.0), "Collection.builtins.float"), + ((123, 456), "Collection.builtins.int"), + (("mystr", "mystr"), "Collection.builtins.str"), + ((False, False), "Collection.builtins.bool"), + ((b"mystr", b"mystr"), "Collection.builtins.bytes"), + ((456.2, 1.0), "Collection.builtins.float"), + ((123, 456), "Collection.builtins.int"), + (("mystr", "mystr"), "Collection.builtins.str"), + (set([False, True]), "Collection.builtins.bool"), + (set([b"mystr", b"mystr2"]), "Collection.builtins.bytes"), + (set([456.2, 1.0]), "Collection.builtins.float"), + (set([123, 456]), "Collection.builtins.int"), + (set(["mystr", "mystr2"]), "Collection.builtins.str"), + (frozenset([False, True]), "Collection.builtins.bool"), + (frozenset([b"mystr", b"mystr2"]), "Collection.builtins.bytes"), + (frozenset([456.2, 1.0]), "Collection.builtins.float"), + (frozenset([123, 456]), "Collection.builtins.int"), + (frozenset(["mystr", "mystr2"]), "Collection.builtins.str"), + ([[1.0, 2.0], [1.0, 2.0]], "Collection.Collection.builtins.float"), + ([(1.0, 2.0), (3.0, 4.0)], "Collection.Collection.builtins.float"), + ([set([1.0, 2.0]), set([3.0, 4.0])], "Collection.Collection.builtins.float"), + ([frozenset([1.0, 2.0]), frozenset([3.0, 4.0])], "Collection.Collection.builtins.float"), + (([1.0, 2.0], [3.0, 4.0]), "Collection.Collection.builtins.float"), + (((1.0, 2.0), (3.0, 4.0)), "Collection.Collection.builtins.float"), + ((set([1.0, 2.0]), set([3.0, 4.0])), "Collection.Collection.builtins.float"), + ((frozenset([1.0, 2.0]), frozenset([3.0, 4.0])), "Collection.Collection.builtins.float"), + (set([(1.0, 2.0), (3.0, 4.0)]), "Collection.Collection.builtins.float"), + ( + set([frozenset([1.0, 2.0]), frozenset([3.0, 4.0])]), + "Collection.Collection.builtins.float", + ), + (frozenset([(1.0, 2.0), (3.0, 4.0)]), "Collection.Collection.builtins.float"), + ( + frozenset([frozenset([1.0, 2.0]), frozenset([3.0, 4.0])]), + "Collection.Collection.builtins.float", + ), ], ) def test___various_python_objects___get_best_matching_type___returns_correct_type_string( From 1a61b7ef015bcf934426875b68c416647afba374 Mon Sep 17 00:00:00 2001 From: Michael Johansen Date: Thu, 7 Aug 2025 10:19:56 -0500 Subject: [PATCH 3/3] Refactor python type names to use brackets and include module names for Collection. Type name logic moved into base class. Signed-off-by: Michael Johansen --- src/nipanel/_convert.py | 12 ++- src/nipanel/converters/__init__.py | 62 +++++++++++++- src/nipanel/converters/builtin.py | 60 ++++++------- src/nipanel/converters/protobuf_types.py | 22 ++--- tests/unit/test_convert.py | 104 +++++++++++++++-------- 5 files changed, 178 insertions(+), 82 deletions(-) diff --git a/src/nipanel/_convert.py b/src/nipanel/_convert.py index 006176e..3a1ef18 100644 --- a/src/nipanel/_convert.py +++ b/src/nipanel/_convert.py @@ -101,13 +101,12 @@ def _get_best_matching_type(python_value: object) -> str: value_is_collection = any( _CONVERTIBLE_COLLECTION_TYPES.intersection(underlying_parents) ) - container_types.append(Collection.__name__) + container_types.append(Collection) best_matching_type = None candidates = _get_candidate_strings(underlying_parents) for candidate in candidates: - containers_str = ".".join(container_types) - python_typename = f"{containers_str}.{candidate}" if containers_str else candidate + python_typename = _create_python_typename(candidate, container_types) if python_typename not in _SUPPORTED_PYTHON_TYPES: continue best_matching_type = python_typename @@ -150,3 +149,10 @@ def _get_candidate_strings(candidates: Iterable[type]) -> list[str]: candidate_names.append(f"{candidate.__module__}.{candidate.__name__}") return candidate_names + + +def _create_python_typename(candidate_name: str, container_types: Iterable[type]) -> str: + name = candidate_name + for container_type in container_types: + name = f"{container_type.__module__}.{container_type.__name__}[{name}]" + return name diff --git a/src/nipanel/converters/__init__.py b/src/nipanel/converters/__init__.py index 4c613cf..92b189d 100644 --- a/src/nipanel/converters/__init__.py +++ b/src/nipanel/converters/__init__.py @@ -3,11 +3,13 @@ from __future__ import annotations from abc import ABC, abstractmethod +from collections.abc import Collection from typing import Generic, Type, TypeVar from google.protobuf import any_pb2 from google.protobuf.message import Message +_TItemType = TypeVar("_TItemType") _TPythonType = TypeVar("_TPythonType") _TProtobufType = TypeVar("_TProtobufType", bound=Message) @@ -17,9 +19,14 @@ class Converter(Generic[_TPythonType, _TProtobufType], ABC): @property @abstractmethod - def python_typename(self) -> str: + def python_type(self) -> type: """The Python type that this converter handles.""" + @property + def python_typename(self) -> str: + """The Python type name that this converter handles.""" + return f"{self.python_type.__module__}.{self.python_type.__name__}" + @property @abstractmethod def protobuf_message(self) -> Type[_TProtobufType]: @@ -52,3 +59,56 @@ def to_python(self, protobuf_value: any_pb2.Any) -> _TPythonType: @abstractmethod def to_python_value(self, protobuf_message: _TProtobufType) -> _TPythonType: """Convert the protobuf wrapper message to its matching Python type.""" + + +class CollectionConverter( + Generic[_TItemType, _TProtobufType], + Converter[Collection[_TItemType], _TProtobufType], + ABC, +): + """A converter between a collection of Python objects and protobuf Any messages.""" + + @property + @abstractmethod + def item_type(self) -> type: + """The Python item type that this converter handles.""" + + @property + def python_type(self) -> type: + """The Python type that this converter handles.""" + return Collection + + @property + def python_typename(self) -> str: + """The Python type name that this converter handles.""" + return "{}[{}]".format( + f"{Collection.__module__}.{Collection.__name__}", + f"{self.item_type.__module__}.{self.item_type.__name__}", + ) + + +class CollectionConverter2D( + Generic[_TItemType, _TProtobufType], + Converter[Collection[Collection[_TItemType]], _TProtobufType], + ABC, +): + """A converter between a 2D collection of Python objects and protobuf Any messages.""" + + @property + @abstractmethod + def item_type(self) -> type: + """The Python item type that this converter handles.""" + + @property + def python_type(self) -> type: + """The Python type that this converter handles.""" + return Collection + + @property + def python_typename(self) -> str: + """The Python type name that this converter handles.""" + return "{}[{}[{}]]".format( + f"{Collection.__module__}.{Collection.__name__}", + f"{Collection.__module__}.{Collection.__name__}", + f"{self.item_type.__module__}.{self.item_type.__name__}", + ) diff --git a/src/nipanel/converters/builtin.py b/src/nipanel/converters/builtin.py index 0a426b4..72d0077 100644 --- a/src/nipanel/converters/builtin.py +++ b/src/nipanel/converters/builtin.py @@ -7,16 +7,16 @@ from google.protobuf import duration_pb2, timestamp_pb2, wrappers_pb2 from ni.panels.v1 import panel_types_pb2 -from nipanel.converters import Converter +from nipanel.converters import Converter, CollectionConverter class BoolConverter(Converter[bool, wrappers_pb2.BoolValue]): """A converter for boolean types.""" @property - def python_typename(self) -> str: + def python_type(self) -> type: """The Python type that this converter handles.""" - return f"{bool.__module__}.{bool.__name__}" + return bool @property def protobuf_message(self) -> Type[wrappers_pb2.BoolValue]: @@ -36,9 +36,9 @@ class BytesConverter(Converter[bytes, wrappers_pb2.BytesValue]): """A converter for byte string types.""" @property - def python_typename(self) -> str: + def python_type(self) -> type: """The Python type that this converter handles.""" - return f"{bytes.__module__}.{bytes.__name__}" + return bytes @property def protobuf_message(self) -> Type[wrappers_pb2.BytesValue]: @@ -58,9 +58,9 @@ class FloatConverter(Converter[float, wrappers_pb2.DoubleValue]): """A converter for floating point types.""" @property - def python_typename(self) -> str: + def python_type(self) -> type: """The Python type that this converter handles.""" - return f"{float.__module__}.{float.__name__}" + return float @property def protobuf_message(self) -> Type[wrappers_pb2.DoubleValue]: @@ -80,9 +80,9 @@ class IntConverter(Converter[int, wrappers_pb2.Int64Value]): """A converter for integer types.""" @property - def python_typename(self) -> str: + def python_type(self) -> type: """The Python type that this converter handles.""" - return f"{int.__module__}.{int.__name__}" + return int @property def protobuf_message(self) -> Type[wrappers_pb2.Int64Value]: @@ -102,9 +102,9 @@ class StrConverter(Converter[str, wrappers_pb2.StringValue]): """A converter for text string types.""" @property - def python_typename(self) -> str: + def python_type(self) -> type: """The Python type that this converter handles.""" - return f"{str.__module__}.{str.__name__}" + return str @property def protobuf_message(self) -> Type[wrappers_pb2.StringValue]: @@ -124,9 +124,9 @@ class DTDateTimeConverter(Converter[dt.datetime, timestamp_pb2.Timestamp]): """A converter for datetime.datetime types.""" @property - def python_typename(self) -> str: + def python_type(self) -> type: """The Python type that this converter handles.""" - return f"{dt.datetime.__module__}.{dt.datetime.__name__}" + return dt.datetime @property def protobuf_message(self) -> Type[timestamp_pb2.Timestamp]: @@ -148,9 +148,9 @@ class DTTimeDeltaConverter(Converter[dt.timedelta, duration_pb2.Duration]): """A converter for datetime.timedelta types.""" @property - def python_typename(self) -> str: + def python_type(self) -> type: """The Python type that this converter handles.""" - return f"{dt.timedelta.__module__}.{dt.timedelta.__name__}" + return dt.timedelta @property def protobuf_message(self) -> Type[duration_pb2.Duration]: @@ -168,13 +168,13 @@ def to_python_value(self, protobuf_message: duration_pb2.Duration) -> dt.timedel return protobuf_message.ToTimedelta() -class BoolCollectionConverter(Converter[Collection[bool], panel_types_pb2.BoolCollection]): +class BoolCollectionConverter(CollectionConverter[bool, panel_types_pb2.BoolCollection]): """A converter for a Collection of bools.""" @property - def python_typename(self) -> str: + def item_type(self) -> type: """The Python type that this converter handles.""" - return f"{Collection.__name__}.{bool.__module__}.{bool.__name__}" + return bool @property def protobuf_message(self) -> Type[panel_types_pb2.BoolCollection]: @@ -190,13 +190,13 @@ def to_python_value(self, protobuf_message: panel_types_pb2.BoolCollection) -> C return list(protobuf_message.values) -class BytesCollectionConverter(Converter[Collection[bytes], panel_types_pb2.ByteStringCollection]): +class BytesCollectionConverter(CollectionConverter[bytes, panel_types_pb2.ByteStringCollection]): """A converter for a Collection of byte strings.""" @property - def python_typename(self) -> str: + def item_type(self) -> type: """The Python type that this converter handles.""" - return f"{Collection.__name__}.{bytes.__module__}.{bytes.__name__}" + return bytes @property def protobuf_message(self) -> Type[panel_types_pb2.ByteStringCollection]: @@ -216,13 +216,13 @@ def to_python_value( return list(protobuf_message.values) -class FloatCollectionConverter(Converter[Collection[float], panel_types_pb2.FloatCollection]): +class FloatCollectionConverter(CollectionConverter[float, panel_types_pb2.FloatCollection]): """A converter for a Collection of floats.""" @property - def python_typename(self) -> str: + def item_type(self) -> type: """The Python type that this converter handles.""" - return f"{Collection.__name__}.{float.__module__}.{float.__name__}" + return float @property def protobuf_message(self) -> Type[panel_types_pb2.FloatCollection]: @@ -242,13 +242,13 @@ def to_python_value( return list(protobuf_message.values) -class IntCollectionConverter(Converter[Collection[int], panel_types_pb2.IntCollection]): +class IntCollectionConverter(CollectionConverter[int, panel_types_pb2.IntCollection]): """A converter for a Collection of integers.""" @property - def python_typename(self) -> str: + def item_type(self) -> type: """The Python type that this converter handles.""" - return f"{Collection.__name__}.{int.__module__}.{int.__name__}" + return int @property def protobuf_message(self) -> Type[panel_types_pb2.IntCollection]: @@ -264,13 +264,13 @@ def to_python_value(self, protobuf_message: panel_types_pb2.IntCollection) -> Co return list(protobuf_message.values) -class StrCollectionConverter(Converter[Collection[str], panel_types_pb2.StringCollection]): +class StrCollectionConverter(CollectionConverter[str, panel_types_pb2.StringCollection]): """A converter for a Collection of strings.""" @property - def python_typename(self) -> str: + def item_type(self) -> type: """The Python type that this converter handles.""" - return f"{Collection.__name__}.{str.__module__}.{str.__name__}" + return str @property def protobuf_message(self) -> Type[panel_types_pb2.StringCollection]: diff --git a/src/nipanel/converters/protobuf_types.py b/src/nipanel/converters/protobuf_types.py index 49fde4a..d02e1f6 100644 --- a/src/nipanel/converters/protobuf_types.py +++ b/src/nipanel/converters/protobuf_types.py @@ -31,7 +31,7 @@ from nitypes.waveform.typing import ExtendedPropertyValue from typing_extensions import TypeAlias -from nipanel.converters import Converter +from nipanel.converters import Converter, CollectionConverter2D _AnyScalarType: TypeAlias = Union[bool, int, float, str] _SCALAR_TYPE_TO_PB_ATTR_MAP = { @@ -42,13 +42,13 @@ } -class Double2DArrayConverter(Converter[Collection[Collection[float]], Double2DArray]): +class Double2DArrayConverter(CollectionConverter2D[float, Double2DArray]): """A converter between Collection[Collection[float]] and Double2DArray.""" @property - def python_typename(self) -> str: - """The Python type that this converter handles.""" - return f"{Collection.__name__}.{Collection.__name__}.{float.__module__}.{float.__name__}" + def item_type(self) -> type: + """The Python item type that this converter handles.""" + return float @property def protobuf_message(self) -> Type[Double2DArray]: @@ -95,9 +95,9 @@ def __init__(self) -> None: self._pt_converter = PrecisionTimestampConverter() @property - def python_typename(self) -> str: + def python_type(self) -> type: """The Python type that this converter handles.""" - return f"{AnalogWaveform.__module__}.{AnalogWaveform.__name__}" + return AnalogWaveform @property def protobuf_message(self) -> Type[DoubleAnalogWaveform]: @@ -192,9 +192,9 @@ class PrecisionTimestampConverter(Converter[bt.DateTime, PrecisionTimestamp]): """A converter for bintime.DateTime types.""" @property - def python_typename(self) -> str: + def python_type(self) -> type: """The Python type that this converter handles.""" - return f"{bt.DateTime.__module__}.{bt.DateTime.__name__}" + return bt.DateTime @property def protobuf_message(self) -> Type[PrecisionTimestamp]: @@ -218,9 +218,9 @@ class ScalarConverter(Converter[Scalar[_AnyScalarType], scalar_pb2.ScalarData]): """A converter for Scalar objects.""" @property - def python_typename(self) -> str: + def python_type(self) -> type: """The Python type that this converter handles.""" - return f"{Scalar.__module__}.{Scalar.__name__}" + return Scalar @property def protobuf_message(self) -> Type[scalar_pb2.ScalarData]: diff --git a/tests/unit/test_convert.py b/tests/unit/test_convert.py index 77fc54f..46c5325 100644 --- a/tests/unit/test_convert.py +++ b/tests/unit/test_convert.py @@ -56,48 +56,78 @@ (tests.types.MixinStrEnum.VALUE11, "builtins.str"), (dt.datetime.now(), "datetime.datetime"), (dt.timedelta(days=1), "datetime.timedelta"), - ([False, False], "Collection.builtins.bool"), - ([b"mystr", b"mystr"], "Collection.builtins.bytes"), - ([456.2, 1.0], "Collection.builtins.float"), - ([123, 456], "Collection.builtins.int"), - (["mystr", "mystr"], "Collection.builtins.str"), - ((False, False), "Collection.builtins.bool"), - ((b"mystr", b"mystr"), "Collection.builtins.bytes"), - ((456.2, 1.0), "Collection.builtins.float"), - ((123, 456), "Collection.builtins.int"), - (("mystr", "mystr"), "Collection.builtins.str"), - ((False, False), "Collection.builtins.bool"), - ((b"mystr", b"mystr"), "Collection.builtins.bytes"), - ((456.2, 1.0), "Collection.builtins.float"), - ((123, 456), "Collection.builtins.int"), - (("mystr", "mystr"), "Collection.builtins.str"), - (set([False, True]), "Collection.builtins.bool"), - (set([b"mystr", b"mystr2"]), "Collection.builtins.bytes"), - (set([456.2, 1.0]), "Collection.builtins.float"), - (set([123, 456]), "Collection.builtins.int"), - (set(["mystr", "mystr2"]), "Collection.builtins.str"), - (frozenset([False, True]), "Collection.builtins.bool"), - (frozenset([b"mystr", b"mystr2"]), "Collection.builtins.bytes"), - (frozenset([456.2, 1.0]), "Collection.builtins.float"), - (frozenset([123, 456]), "Collection.builtins.int"), - (frozenset(["mystr", "mystr2"]), "Collection.builtins.str"), - ([[1.0, 2.0], [1.0, 2.0]], "Collection.Collection.builtins.float"), - ([(1.0, 2.0), (3.0, 4.0)], "Collection.Collection.builtins.float"), - ([set([1.0, 2.0]), set([3.0, 4.0])], "Collection.Collection.builtins.float"), - ([frozenset([1.0, 2.0]), frozenset([3.0, 4.0])], "Collection.Collection.builtins.float"), - (([1.0, 2.0], [3.0, 4.0]), "Collection.Collection.builtins.float"), - (((1.0, 2.0), (3.0, 4.0)), "Collection.Collection.builtins.float"), - ((set([1.0, 2.0]), set([3.0, 4.0])), "Collection.Collection.builtins.float"), - ((frozenset([1.0, 2.0]), frozenset([3.0, 4.0])), "Collection.Collection.builtins.float"), - (set([(1.0, 2.0), (3.0, 4.0)]), "Collection.Collection.builtins.float"), + ([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]"), + ([123, 456], "collections.abc.Collection[builtins.int]"), + (["mystr", "mystr"], "collections.abc.Collection[builtins.str]"), + ((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]"), + ((123, 456), "collections.abc.Collection[builtins.int]"), + (("mystr", "mystr"), "collections.abc.Collection[builtins.str]"), + ((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]"), + ((123, 456), "collections.abc.Collection[builtins.int]"), + (("mystr", "mystr"), "collections.abc.Collection[builtins.str]"), + (set([False, True]), "collections.abc.Collection[builtins.bool]"), + (set([b"mystr", b"mystr2"]), "collections.abc.Collection[builtins.bytes]"), + (set([456.2, 1.0]), "collections.abc.Collection[builtins.float]"), + (set([123, 456]), "collections.abc.Collection[builtins.int]"), + (set(["mystr", "mystr2"]), "collections.abc.Collection[builtins.str]"), + (frozenset([False, True]), "collections.abc.Collection[builtins.bool]"), + (frozenset([b"mystr", b"mystr2"]), "collections.abc.Collection[builtins.bytes]"), + (frozenset([456.2, 1.0]), "collections.abc.Collection[builtins.float]"), + (frozenset([123, 456]), "collections.abc.Collection[builtins.int]"), + (frozenset(["mystr", "mystr2"]), "collections.abc.Collection[builtins.str]"), + ( + [[1.0, 2.0], [1.0, 2.0]], + "collections.abc.Collection[collections.abc.Collection[builtins.float]]", + ), + ( + [(1.0, 2.0), (3.0, 4.0)], + "collections.abc.Collection[collections.abc.Collection[builtins.float]]", + ), + ( + [set([1.0, 2.0]), set([3.0, 4.0])], + "collections.abc.Collection[collections.abc.Collection[builtins.float]]", + ), + ( + [frozenset([1.0, 2.0]), frozenset([3.0, 4.0])], + "collections.abc.Collection[collections.abc.Collection[builtins.float]]", + ), + ( + ([1.0, 2.0], [3.0, 4.0]), + "collections.abc.Collection[collections.abc.Collection[builtins.float]]", + ), + ( + ((1.0, 2.0), (3.0, 4.0)), + "collections.abc.Collection[collections.abc.Collection[builtins.float]]", + ), + ( + (set([1.0, 2.0]), set([3.0, 4.0])), + "collections.abc.Collection[collections.abc.Collection[builtins.float]]", + ), + ( + (frozenset([1.0, 2.0]), frozenset([3.0, 4.0])), + "collections.abc.Collection[collections.abc.Collection[builtins.float]]", + ), + ( + set([(1.0, 2.0), (3.0, 4.0)]), + "collections.abc.Collection[collections.abc.Collection[builtins.float]]", + ), ( set([frozenset([1.0, 2.0]), frozenset([3.0, 4.0])]), - "Collection.Collection.builtins.float", + "collections.abc.Collection[collections.abc.Collection[builtins.float]]", + ), + ( + frozenset([(1.0, 2.0), (3.0, 4.0)]), + "collections.abc.Collection[collections.abc.Collection[builtins.float]]", ), - (frozenset([(1.0, 2.0), (3.0, 4.0)]), "Collection.Collection.builtins.float"), ( frozenset([frozenset([1.0, 2.0]), frozenset([3.0, 4.0])]), - "Collection.Collection.builtins.float", + "collections.abc.Collection[collections.abc.Collection[builtins.float]]", ), ], )