From 1297fd3f99a3aed9725799571bc964529f94dc00 Mon Sep 17 00:00:00 2001 From: Tom Cobb Date: Fri, 17 Jan 2025 11:27:26 +0000 Subject: [PATCH 1/2] Widen Array1D to fix changes to numpy shape https://github.com/numpy/numpy/issues/28077#issuecomment-2566485178 --- src/ophyd_async/core/_signal_backend.py | 5 ++++- src/ophyd_async/core/_table.py | 2 +- tests/core/test_soft_signal_backend.py | 4 +++- tests/core/test_table.py | 4 ++++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/ophyd_async/core/_signal_backend.py b/src/ophyd_async/core/_signal_backend.py index d6b04b3d84..a0ad517b7c 100644 --- a/src/ophyd_async/core/_signal_backend.py +++ b/src/ophyd_async/core/_signal_backend.py @@ -10,7 +10,10 @@ from ._utils import Callback, StrictEnum, T DTypeScalar_co = TypeVar("DTypeScalar_co", covariant=True, bound=np.generic) -Array1D = np.ndarray[tuple[int], np.dtype[DTypeScalar_co]] +# To be a 1D array shape should really be tuple[int], but np.array() +# currently produces tuple[int, ...] even when it has 1D input args +# https://github.com/numpy/numpy/issues/28077#issuecomment-2566485178 +Array1D = np.ndarray[tuple[int, ...], np.dtype[DTypeScalar_co]] Primitive = bool | int | float | str # NOTE: if you change this union then update the docs to match SignalDatatype = ( diff --git a/src/ophyd_async/core/_table.py b/src/ophyd_async/core/_table.py index fd3439a996..7fbeb5b9c7 100644 --- a/src/ophyd_async/core/_table.py +++ b/src/ophyd_async/core/_table.py @@ -101,7 +101,7 @@ def numpy_table(self, selection: slice | None = None) -> np.ndarray: v = v[selection] if array is None: array = np.empty(v.shape, dtype=self.numpy_dtype()) - array[k] = v + array[k] = v # type: ignore if array is None: msg = "No arrays found in table" raise ValueError(msg) diff --git a/tests/core/test_soft_signal_backend.py b/tests/core/test_soft_signal_backend.py index 8a1ea6015d..399631db54 100644 --- a/tests/core/test_soft_signal_backend.py +++ b/tests/core/test_soft_signal_backend.py @@ -148,8 +148,10 @@ async def test_soft_signal_backend_set_callback(): async def test_soft_signal_backend_with_numpy_typing(): soft_backend = SoftSignalBackend(Array1D[np.float64]) await soft_backend.connect(timeout=1) + await soft_backend.put(np.array([1, 2]), wait=True) array = await soft_backend.get_value() - assert array.shape == (0,) + assert array.shape == (2,) + assert array[0] == 1 async def test_soft_signal_descriptor_fails_for_invalid_class(): diff --git a/tests/core/test_table.py b/tests/core/test_table.py index dd05af9edb..4ac55be8a2 100644 --- a/tests/core/test_table.py +++ b/tests/core/test_table.py @@ -33,6 +33,10 @@ class MyTable(Table): {"bool": [0, 1], "uint": [3, 4], "str": [44, ""]}, "Input should be a valid string [type=string_type, input_value=44,", ), + ( + {"bool": [0, 1], "uint": [[3], [4]], "str": ["", ""]}, + "Array 2-dimensional; the target dimensions is 1", + ), ], ) def test_table_wrong_types(kwargs, error_msg): From d113534c8c99a0520d802f91230a5e0886835c77 Mon Sep 17 00:00:00 2001 From: Tom Cobb Date: Fri, 17 Jan 2025 11:45:13 +0000 Subject: [PATCH 2/2] Fix error message output --- src/ophyd_async/epics/core/_util.py | 2 +- tests/epics/signal/test_signals.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/ophyd_async/epics/core/_util.py b/src/ophyd_async/epics/core/_util.py index a40d794b45..7b5c923948 100644 --- a/src/ophyd_async/epics/core/_util.py +++ b/src/ophyd_async/epics/core/_util.py @@ -47,7 +47,7 @@ def get_supported_values( def format_datatype(datatype: Any) -> str: - if get_origin(datatype) is np.ndarray and get_args(datatype)[0] == tuple[int]: + if get_origin(datatype) is np.ndarray and get_args(datatype): dtype = get_dtype(datatype) return f"Array1D[np.{dtype.name}]" elif get_origin(datatype) is Sequence: diff --git a/tests/epics/signal/test_signals.py b/tests/epics/signal/test_signals.py index fdb7f44a76..4d6324486e 100644 --- a/tests/epics/signal/test_signals.py +++ b/tests/epics/signal/test_signals.py @@ -8,6 +8,7 @@ import bluesky.plan_stubs as bps import numpy as np +import numpy.typing as npt import pytest import yaml from aioca import purge_channel_caches @@ -38,6 +39,7 @@ epics_signal_w, epics_signal_x, ) +from ophyd_async.epics.core._util import format_datatype # noqa: PLC2701 from ophyd_async.epics.testing import ( EpicsTestEnum, EpicsTestIocAndDevices, @@ -567,6 +569,18 @@ async def test_non_existent_errors( await signal.connect(timeout=0.1) +@pytest.mark.parametrize( + "dt,expected", + [ + (Array1D[np.int32], "Array1D[np.int32]"), + (np.ndarray, "ndarray"), + (npt.NDArray[np.float64], "Array1D[np.float64]"), + ], +) +def test_format_error_message(dt, expected): + assert format_datatype(dt) == expected + + def test_make_backend_fails_for_different_transports(): read_pv = "test" write_pv = "pva://test"