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
24 changes: 19 additions & 5 deletions src/nipanel/_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 = [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
python_typename = _create_python_typename(candidate, container_types)
if python_typename not in _SUPPORTED_PYTHON_TYPES:
continue
best_matching_type = python_typename
Expand Down Expand Up @@ -142,3 +141,18 @@ def is_supported_type(value: object) -> bool:
return True
except TypeError:
return False


def _get_candidate_strings(candidates: Iterable[type]) -> list[str]:
candidate_names = []
for candidate in candidates:
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
62 changes: 61 additions & 1 deletion src/nipanel/converters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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]:
Expand Down Expand Up @@ -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__}",
)
60 changes: 30 additions & 30 deletions src/nipanel/converters/builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 bool.__name__
return bool

@property
def protobuf_message(self) -> Type[wrappers_pb2.BoolValue]:
Expand All @@ -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 bytes.__name__
return bytes

@property
def protobuf_message(self) -> Type[wrappers_pb2.BytesValue]:
Expand All @@ -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 float.__name__
return float

@property
def protobuf_message(self) -> Type[wrappers_pb2.DoubleValue]:
Expand All @@ -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 int.__name__
return int

@property
def protobuf_message(self) -> Type[wrappers_pb2.Int64Value]:
Expand All @@ -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 str.__name__
return str

@property
def protobuf_message(self) -> Type[wrappers_pb2.StringValue]:
Expand All @@ -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 dt.datetime.__name__
return dt.datetime

@property
def protobuf_message(self) -> Type[timestamp_pb2.Timestamp]:
Expand All @@ -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 dt.timedelta.__name__
return dt.timedelta

@property
def protobuf_message(self) -> Type[duration_pb2.Duration]:
Expand All @@ -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.__name__}"
return bool

@property
def protobuf_message(self) -> Type[panel_types_pb2.BoolCollection]:
Expand All @@ -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.__name__}"
return bytes

@property
def protobuf_message(self) -> Type[panel_types_pb2.ByteStringCollection]:
Expand All @@ -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.__name__}"
return float

@property
def protobuf_message(self) -> Type[panel_types_pb2.FloatCollection]:
Expand All @@ -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.__name__}"
return int

@property
def protobuf_message(self) -> Type[panel_types_pb2.IntCollection]:
Expand All @@ -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.__name__}"
return str

@property
def protobuf_message(self) -> Type[panel_types_pb2.StringCollection]:
Expand Down
22 changes: 11 additions & 11 deletions src/nipanel/converters/protobuf_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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.__name__}"
def item_type(self) -> type:
"""The Python item type that this converter handles."""
return float

@property
def protobuf_message(self) -> Type[Double2DArray]:
Expand Down Expand Up @@ -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 AnalogWaveform.__name__
return AnalogWaveform

@property
def protobuf_message(self) -> Type[DoubleAnalogWaveform]:
Expand Down Expand Up @@ -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 bt.DateTime.__name__
return bt.DateTime

@property
def protobuf_message(self) -> Type[PrecisionTimestamp]:
Expand All @@ -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 Scalar.__name__
return Scalar

@property
def protobuf_message(self) -> Type[scalar_pb2.ScalarData]:
Expand Down
Loading
Loading