From 80080825e06c047e0c4b31cc5c24c4ff700d8be5 Mon Sep 17 00:00:00 2001 From: Zachary Lentz Date: Tue, 25 Jun 2024 00:36:50 +0000 Subject: [PATCH 1/4] FIX: metadata handling for enums --- typhos/plugins/core.py | 26 ++++++++++++++-- typhos/tests/plugins/test_core.py | 51 ++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/typhos/plugins/core.py b/typhos/plugins/core.py index 2ce42790..c292be95 100644 --- a/typhos/plugins/core.py +++ b/typhos/plugins/core.py @@ -83,6 +83,8 @@ def __init__(self, channel, address, protocol=None, parent=None): self._connection_open = True self.signal_type = None self.is_float = False + self.enum_strs = () + # Collect our signal self.signal = self.find_signal(address) # Subscribe to updates from Ophyd @@ -143,7 +145,24 @@ def cast(self, value): dtype, self.signal.name, self.signal_type) logger.debug("Casting %r to %r", value, self.signal_type) - if self.signal_type is np.ndarray: + if self.enum_strs: + # signal_type is either int or str + # use enums to cast type + if self.signal_type is int: + # Get the index + try: + value = self.enum_strs.index(value) + except (TypeError, ValueError, AttributeError): + value = int(value) + elif self.signal_type is str: + # Get the enum string + try: + value = self.enum_strs[value] + except (TypeError, ValueError): + value = str(value) + else: + raise TypeError(f"Invalid combination: {self.enum_strs=} with {self.signal_type=}") + elif self.signal_type is np.ndarray: value = np.asarray(value) else: value = self.signal_type(value) @@ -227,6 +246,7 @@ def send_new_meta( self.unit_signal.emit(units) if enum_strs is not None: self.enum_strings_signal.emit(enum_strs) + self.enum_strs = enum_strs # Special handling for severity if severity is None: @@ -266,9 +286,9 @@ def add_listener(self, channel): else: self.is_float = False - # Report new value - self.send_new_value(signal_val) + # Report new meta for context, then value self.send_new_meta(**signal_meta) + self.send_new_value(signal_val) # If the channel is used for writing to PVs, hook it up to the # 'put' methods. if channel.value_signal is not None: diff --git a/typhos/tests/plugins/test_core.py b/typhos/tests/plugins/test_core.py index 434950b4..bfff8927 100644 --- a/typhos/tests/plugins/test_core.py +++ b/typhos/tests/plugins/test_core.py @@ -5,8 +5,9 @@ import pytest from ophyd import Component as Cpt from ophyd import Device, Signal +from ophyd.sim import EnumSignal from pydm import PyDMApplication -from pydm.widgets import PyDMLineEdit +from pydm.widgets import PyDMChannel, PyDMLineEdit from pytestqt.qtbot import QtBot from typhos.plugins.core import (SignalConnection, register_signal, @@ -211,3 +212,51 @@ def test_array_signal_put_value(qapp, qtbot): widget.send_value_signal[np.ndarray].emit(np.zeros(4)) qapp.processEvents() assert all(sig.get() == np.zeros(4)) + + +def test_add_listener_order(qapp): + sig = EnumSignal(name="my_listener", value=0, enum_strings=("zero", "one", "two")) + register_signal(sig) + + order = [] + + def new_value(*args, **kwargs): + order.append("value") + + def new_meta(*args, **kwargs): + order.append("meta") + + chan = PyDMChannel(address="sig://my_listener", value_slot=new_value, enum_strings_slot=new_meta) + _ = SignalConnection(chan, "my_listener", "sig") + qapp.processEvents() + + assert order == ["meta", "value"] + + +def test_enum_casts(qapp): + sig = Signal(name="my_enum_caster", value=0) + register_signal(sig) + + chan = PyDMChannel(address="sig://my_enum_caster") + conn = SignalConnection(chan, "my_enum_caster", "sig") + qapp.processEvents() + + conn.enum_strs = ("1", "2", "5") + + # First, keep type as an int + assert conn.cast("1") == 0 + assert conn.cast("2") == 1 + assert conn.cast("5") == 2 + assert conn.cast(0) == 0 + assert conn.cast(1) == 1 + assert conn.cast(2) == 2 + + # Try str next + conn.signal_type = str + + assert conn.cast("1") == "1" + assert conn.cast("2") == "2" + assert conn.cast("5") == "5" + assert conn.cast(0) == "1" + assert conn.cast(1) == "2" + assert conn.cast(2) == "5" From 87b98d30e703e6d600852e6d4ef0ac5088a7a444 Mon Sep 17 00:00:00 2001 From: Zachary Lentz Date: Tue, 25 Jun 2024 00:51:25 +0000 Subject: [PATCH 2/4] DOC: pre-release notes --- .../616-fix_enum_handling.rst | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 docs/source/upcoming_release_notes/616-fix_enum_handling.rst diff --git a/docs/source/upcoming_release_notes/616-fix_enum_handling.rst b/docs/source/upcoming_release_notes/616-fix_enum_handling.rst new file mode 100644 index 00000000..0f48a1f9 --- /dev/null +++ b/docs/source/upcoming_release_notes/616-fix_enum_handling.rst @@ -0,0 +1,23 @@ +616 fix_enum_handling +################# + +API Breaks +---------- +- N/A + +Features +-------- +- N/A + +Bugfixes +-------- +- Fix various issues with enum handling in the SignalPlugin. + +Maintenance +----------- +- N/A + +Contributors +------------ +- canismarko +- zllentz From ee526b7d9ad8139413da49e55fae78adc678e1c6 Mon Sep 17 00:00:00 2001 From: Zachary Lentz Date: Tue, 25 Jun 2024 14:20:32 -0700 Subject: [PATCH 3/4] MAINT: init type annotations, manual/simpler error text --- typhos/plugins/core.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/typhos/plugins/core.py b/typhos/plugins/core.py index c292be95..7fe6e947 100644 --- a/typhos/plugins/core.py +++ b/typhos/plugins/core.py @@ -80,10 +80,10 @@ class SignalConnection(PyDMConnection): def __init__(self, channel, address, protocol=None, parent=None): # Create base connection super().__init__(channel, address, protocol=protocol, parent=parent) - self._connection_open = True - self.signal_type = None - self.is_float = False - self.enum_strs = () + self._connection_open: bool = True + self.signal_type: type | None = None + self.is_float: bool = False + self.enum_strs: tuple[str, ...] = () # Collect our signal self.signal = self.find_signal(address) @@ -161,7 +161,9 @@ def cast(self, value): except (TypeError, ValueError): value = str(value) else: - raise TypeError(f"Invalid combination: {self.enum_strs=} with {self.signal_type=}") + raise TypeError( + f"Invalid combination: enum_strs={self.enum_strs} with signal_type={self.signal_type}" + ) elif self.signal_type is np.ndarray: value = np.asarray(value) else: From f84cce2602f9c3afa54db8fccc0ad7b55db8a0e9 Mon Sep 17 00:00:00 2001 From: Zachary Lentz Date: Tue, 25 Jun 2024 14:21:42 -0700 Subject: [PATCH 4/4] TST: make casting test slightly less artificial --- typhos/tests/plugins/test_core.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/typhos/tests/plugins/test_core.py b/typhos/tests/plugins/test_core.py index bfff8927..74f1fd1a 100644 --- a/typhos/tests/plugins/test_core.py +++ b/typhos/tests/plugins/test_core.py @@ -252,7 +252,9 @@ def test_enum_casts(qapp): assert conn.cast(2) == 2 # Try str next - conn.signal_type = str + conn.signal_type = None + sig.put("2") + qapp.processEvents() assert conn.cast("1") == "1" assert conn.cast("2") == "2"