ophyd-async 0.7.0__py3-none-any.whl → 0.8.0a2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ophyd_async/_version.py +2 -2
- ophyd_async/core/__init__.py +23 -8
- ophyd_async/core/_detector.py +5 -10
- ophyd_async/core/_device.py +139 -66
- ophyd_async/core/_device_filler.py +191 -0
- ophyd_async/core/_device_save_loader.py +6 -7
- ophyd_async/core/_mock_signal_backend.py +32 -40
- ophyd_async/core/_mock_signal_utils.py +22 -16
- ophyd_async/core/_protocol.py +28 -8
- ophyd_async/core/_readable.py +5 -5
- ophyd_async/core/_signal.py +140 -152
- ophyd_async/core/_signal_backend.py +131 -64
- ophyd_async/core/_soft_signal_backend.py +125 -194
- ophyd_async/core/_status.py +22 -6
- ophyd_async/core/_table.py +97 -100
- ophyd_async/core/_utils.py +71 -18
- ophyd_async/epics/adaravis/_aravis_controller.py +2 -2
- ophyd_async/epics/adaravis/_aravis_io.py +7 -5
- ophyd_async/epics/adcore/_core_io.py +4 -6
- ophyd_async/epics/adcore/_hdf_writer.py +2 -2
- ophyd_async/epics/adcore/_utils.py +15 -10
- ophyd_async/epics/adkinetix/__init__.py +2 -1
- ophyd_async/epics/adkinetix/_kinetix_controller.py +6 -3
- ophyd_async/epics/adkinetix/_kinetix_io.py +3 -4
- ophyd_async/epics/adpilatus/_pilatus_controller.py +2 -2
- ophyd_async/epics/adpilatus/_pilatus_io.py +2 -3
- ophyd_async/epics/adsimdetector/_sim_controller.py +2 -2
- ophyd_async/epics/advimba/__init__.py +4 -1
- ophyd_async/epics/advimba/_vimba_controller.py +6 -3
- ophyd_async/epics/advimba/_vimba_io.py +7 -8
- ophyd_async/epics/demo/_sensor.py +8 -4
- ophyd_async/epics/eiger/_eiger.py +1 -2
- ophyd_async/epics/eiger/_eiger_controller.py +1 -1
- ophyd_async/epics/eiger/_eiger_io.py +2 -4
- ophyd_async/epics/eiger/_odin_io.py +4 -4
- ophyd_async/epics/pvi/__init__.py +2 -2
- ophyd_async/epics/pvi/_pvi.py +56 -321
- ophyd_async/epics/signal/__init__.py +3 -4
- ophyd_async/epics/signal/_aioca.py +184 -236
- ophyd_async/epics/signal/_common.py +35 -49
- ophyd_async/epics/signal/_p4p.py +254 -387
- ophyd_async/epics/signal/_signal.py +63 -21
- ophyd_async/fastcs/core.py +9 -0
- ophyd_async/fastcs/panda/__init__.py +4 -4
- ophyd_async/fastcs/panda/_block.py +18 -13
- ophyd_async/fastcs/panda/_control.py +3 -5
- ophyd_async/fastcs/panda/_hdf_panda.py +5 -19
- ophyd_async/fastcs/panda/_table.py +29 -51
- ophyd_async/fastcs/panda/_trigger.py +8 -8
- ophyd_async/fastcs/panda/_writer.py +2 -5
- ophyd_async/plan_stubs/_ensure_connected.py +3 -1
- ophyd_async/plan_stubs/_fly.py +2 -2
- ophyd_async/plan_stubs/_nd_attributes.py +5 -4
- ophyd_async/py.typed +0 -0
- ophyd_async/sim/demo/_pattern_detector/_pattern_detector_controller.py +1 -2
- ophyd_async/tango/__init__.py +2 -4
- ophyd_async/tango/base_devices/_base_device.py +76 -143
- ophyd_async/tango/demo/_counter.py +2 -2
- ophyd_async/tango/demo/_mover.py +2 -2
- ophyd_async/tango/signal/__init__.py +2 -4
- ophyd_async/tango/signal/_signal.py +29 -50
- ophyd_async/tango/signal/_tango_transport.py +38 -40
- {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0a2.dist-info}/METADATA +8 -12
- ophyd_async-0.8.0a2.dist-info/RECORD +110 -0
- {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0a2.dist-info}/WHEEL +1 -1
- ophyd_async/epics/signal/_epics_transport.py +0 -34
- ophyd_async-0.7.0.dist-info/RECORD +0 -108
- {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0a2.dist-info}/LICENSE +0 -0
- {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0a2.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0a2.dist-info}/top_level.txt +0 -0
|
@@ -1,84 +1,76 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from collections.abc import Callable
|
|
3
3
|
from functools import cached_property
|
|
4
|
-
from unittest.mock import AsyncMock
|
|
4
|
+
from unittest.mock import AsyncMock, Mock
|
|
5
5
|
|
|
6
6
|
from bluesky.protocols import Descriptor, Reading
|
|
7
7
|
|
|
8
|
-
from ._signal_backend import SignalBackend
|
|
8
|
+
from ._signal_backend import SignalBackend, SignalDatatypeT
|
|
9
9
|
from ._soft_signal_backend import SoftSignalBackend
|
|
10
|
-
from ._utils import
|
|
10
|
+
from ._utils import Callback
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
class MockSignalBackend(SignalBackend[
|
|
13
|
+
class MockSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
14
14
|
"""Signal backend for testing, created by ``Device.connect(mock=True)``."""
|
|
15
15
|
|
|
16
16
|
def __init__(
|
|
17
17
|
self,
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
initial_backend: SignalBackend[SignalDatatypeT],
|
|
19
|
+
mock: Mock,
|
|
20
20
|
) -> None:
|
|
21
21
|
if isinstance(initial_backend, MockSignalBackend):
|
|
22
|
-
raise ValueError("Cannot make a MockSignalBackend for a
|
|
22
|
+
raise ValueError("Cannot make a MockSignalBackend for a MockSignalBackend")
|
|
23
23
|
|
|
24
24
|
self.initial_backend = initial_backend
|
|
25
25
|
|
|
26
|
-
if
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
if isinstance(self.initial_backend, SoftSignalBackend):
|
|
27
|
+
# Backend is already a SoftSignalBackend, so use it
|
|
28
|
+
self.soft_backend = self.initial_backend
|
|
29
|
+
else:
|
|
30
|
+
# Backend is not a SoftSignalBackend, so create one to mimic it
|
|
31
|
+
self.soft_backend = SoftSignalBackend(
|
|
32
|
+
datatype=self.initial_backend.datatype
|
|
33
|
+
)
|
|
31
34
|
|
|
32
|
-
|
|
35
|
+
# use existing Mock if provided
|
|
36
|
+
self.mock = mock
|
|
37
|
+
self.put_mock = AsyncMock(name="put", spec=Callable)
|
|
38
|
+
self.mock.attach_mock(self.put_mock, "put")
|
|
33
39
|
|
|
34
|
-
|
|
35
|
-
# If the backend is a hard signal backend, or not provided,
|
|
36
|
-
# then we create a soft signal to mimic it
|
|
40
|
+
super().__init__(datatype=self.initial_backend.datatype)
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
self.soft_backend = self.initial_backend
|
|
42
|
+
def set_value(self, value: SignalDatatypeT):
|
|
43
|
+
self.soft_backend.set_value(value)
|
|
41
44
|
|
|
42
|
-
def source(self, name: str) -> str:
|
|
43
|
-
|
|
44
|
-
return f"mock+{self.initial_backend.source(name)}"
|
|
45
|
-
return f"mock+{name}"
|
|
45
|
+
def source(self, name: str, read: bool) -> str:
|
|
46
|
+
return f"mock+{self.initial_backend.source(name, read)}"
|
|
46
47
|
|
|
47
|
-
async def connect(self, timeout: float
|
|
48
|
+
async def connect(self, timeout: float) -> None:
|
|
48
49
|
pass
|
|
49
50
|
|
|
50
|
-
@cached_property
|
|
51
|
-
def put_mock(self) -> AsyncMock:
|
|
52
|
-
return AsyncMock(name="put", spec=Callable)
|
|
53
|
-
|
|
54
51
|
@cached_property
|
|
55
52
|
def put_proceeds(self) -> asyncio.Event:
|
|
56
53
|
put_proceeds = asyncio.Event()
|
|
57
54
|
put_proceeds.set()
|
|
58
55
|
return put_proceeds
|
|
59
56
|
|
|
60
|
-
async def put(self, value:
|
|
61
|
-
await self.put_mock(value, wait=wait
|
|
62
|
-
await self.soft_backend.put(value, wait=wait
|
|
63
|
-
|
|
57
|
+
async def put(self, value: SignalDatatypeT | None, wait: bool):
|
|
58
|
+
await self.put_mock(value, wait=wait)
|
|
59
|
+
await self.soft_backend.put(value, wait=wait)
|
|
64
60
|
if wait:
|
|
65
|
-
await
|
|
66
|
-
|
|
67
|
-
def set_value(self, value: T):
|
|
68
|
-
self.soft_backend.set_value(value)
|
|
61
|
+
await self.put_proceeds.wait()
|
|
69
62
|
|
|
70
63
|
async def get_reading(self) -> Reading:
|
|
71
64
|
return await self.soft_backend.get_reading()
|
|
72
65
|
|
|
73
|
-
async def get_value(self) ->
|
|
66
|
+
async def get_value(self) -> SignalDatatypeT:
|
|
74
67
|
return await self.soft_backend.get_value()
|
|
75
68
|
|
|
76
|
-
async def get_setpoint(self) ->
|
|
77
|
-
"""For a soft signal, the setpoint and readback values are the same."""
|
|
69
|
+
async def get_setpoint(self) -> SignalDatatypeT:
|
|
78
70
|
return await self.soft_backend.get_setpoint()
|
|
79
71
|
|
|
80
72
|
async def get_datakey(self, source: str) -> Descriptor:
|
|
81
73
|
return await self.soft_backend.get_datakey(source)
|
|
82
74
|
|
|
83
|
-
def set_callback(self, callback:
|
|
75
|
+
def set_callback(self, callback: Callback[Reading[SignalDatatypeT]] | None) -> None:
|
|
84
76
|
self.soft_backend.set_callback(callback)
|
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
from collections.abc import Awaitable, Callable, Iterable
|
|
2
2
|
from contextlib import asynccontextmanager, contextmanager
|
|
3
|
-
from
|
|
4
|
-
from unittest.mock import AsyncMock
|
|
3
|
+
from unittest.mock import AsyncMock, Mock
|
|
5
4
|
|
|
5
|
+
from ._device import Device, _device_mocks
|
|
6
6
|
from ._mock_signal_backend import MockSignalBackend
|
|
7
|
-
from ._signal import Signal
|
|
8
|
-
from .
|
|
7
|
+
from ._signal import Signal, SignalR, _mock_signal_backends
|
|
8
|
+
from ._soft_signal_backend import SignalDatatypeT
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def _get_mock_signal_backend(signal: Signal) -> MockSignalBackend:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
)
|
|
17
|
-
return backend
|
|
12
|
+
assert (
|
|
13
|
+
signal in _mock_signal_backends
|
|
14
|
+
), f"Signal {signal} not connected in mock mode"
|
|
15
|
+
return _mock_signal_backends[signal]
|
|
18
16
|
|
|
19
17
|
|
|
20
|
-
def set_mock_value(signal: Signal[
|
|
18
|
+
def set_mock_value(signal: Signal[SignalDatatypeT], value: SignalDatatypeT):
|
|
21
19
|
"""Set the value of a signal that is in mock mode."""
|
|
22
20
|
backend = _get_mock_signal_backend(signal)
|
|
23
21
|
backend.set_value(value)
|
|
@@ -47,6 +45,12 @@ def get_mock_put(signal: Signal) -> AsyncMock:
|
|
|
47
45
|
return _get_mock_signal_backend(signal).put_mock
|
|
48
46
|
|
|
49
47
|
|
|
48
|
+
def get_mock(device: Device | Signal) -> Mock:
|
|
49
|
+
if isinstance(device, Signal):
|
|
50
|
+
return _get_mock_signal_backend(device).mock
|
|
51
|
+
return _device_mocks[device]
|
|
52
|
+
|
|
53
|
+
|
|
50
54
|
def reset_mock_put_calls(signal: Signal):
|
|
51
55
|
backend = _get_mock_signal_backend(signal)
|
|
52
56
|
backend.put_mock.reset_mock()
|
|
@@ -59,8 +63,8 @@ class _SetValuesIterator:
|
|
|
59
63
|
|
|
60
64
|
def __init__(
|
|
61
65
|
self,
|
|
62
|
-
signal:
|
|
63
|
-
values: Iterable[
|
|
66
|
+
signal: SignalR[SignalDatatypeT],
|
|
67
|
+
values: Iterable[SignalDatatypeT],
|
|
64
68
|
require_all_consumed: bool = False,
|
|
65
69
|
):
|
|
66
70
|
self.signal = signal
|
|
@@ -99,8 +103,8 @@ class _SetValuesIterator:
|
|
|
99
103
|
|
|
100
104
|
|
|
101
105
|
def set_mock_values(
|
|
102
|
-
signal:
|
|
103
|
-
values: Iterable[
|
|
106
|
+
signal: SignalR[SignalDatatypeT],
|
|
107
|
+
values: Iterable[SignalDatatypeT],
|
|
104
108
|
require_all_consumed: bool = False,
|
|
105
109
|
) -> _SetValuesIterator:
|
|
106
110
|
"""Iterator to set a signal to a sequence of values, optionally repeating the
|
|
@@ -143,7 +147,9 @@ def _unset_side_effect_cm(put_mock: AsyncMock):
|
|
|
143
147
|
|
|
144
148
|
|
|
145
149
|
def callback_on_mock_put(
|
|
146
|
-
signal: Signal[
|
|
150
|
+
signal: Signal[SignalDatatypeT],
|
|
151
|
+
callback: Callable[[SignalDatatypeT, bool], None]
|
|
152
|
+
| Callable[[SignalDatatypeT, bool], Awaitable[None]],
|
|
147
153
|
):
|
|
148
154
|
"""For setting a callback when a backend is put to.
|
|
149
155
|
|
ophyd_async/core/_protocol.py
CHANGED
|
@@ -13,10 +13,38 @@ from typing import (
|
|
|
13
13
|
from bluesky.protocols import HasName, Reading
|
|
14
14
|
from event_model import DataKey
|
|
15
15
|
|
|
16
|
+
from ._utils import DEFAULT_TIMEOUT
|
|
17
|
+
|
|
16
18
|
if TYPE_CHECKING:
|
|
19
|
+
from unittest.mock import Mock
|
|
20
|
+
|
|
17
21
|
from ._status import AsyncStatus
|
|
18
22
|
|
|
19
23
|
|
|
24
|
+
@runtime_checkable
|
|
25
|
+
class Connectable(Protocol):
|
|
26
|
+
@abstractmethod
|
|
27
|
+
async def connect(
|
|
28
|
+
self,
|
|
29
|
+
mock: bool | Mock = False,
|
|
30
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
31
|
+
force_reconnect: bool = False,
|
|
32
|
+
):
|
|
33
|
+
"""Connect self and all child Devices.
|
|
34
|
+
|
|
35
|
+
Contains a timeout that gets propagated to child.connect methods.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
mock:
|
|
40
|
+
If True then use ``MockSignalBackend`` for all Signals
|
|
41
|
+
timeout:
|
|
42
|
+
Time to wait before failing with a TimeoutError.
|
|
43
|
+
force_reconnect:
|
|
44
|
+
Reconnect even if previous connect was successful.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
|
|
20
48
|
@runtime_checkable
|
|
21
49
|
class AsyncReadable(HasName, Protocol):
|
|
22
50
|
@abstractmethod
|
|
@@ -33,7 +61,6 @@ class AsyncReadable(HasName, Protocol):
|
|
|
33
61
|
('channel2',
|
|
34
62
|
{'value': 16, 'timestamp': 1472493713.539238}))
|
|
35
63
|
"""
|
|
36
|
-
...
|
|
37
64
|
|
|
38
65
|
@abstractmethod
|
|
39
66
|
async def describe(self) -> dict[str, DataKey]:
|
|
@@ -53,7 +80,6 @@ class AsyncReadable(HasName, Protocol):
|
|
|
53
80
|
'dtype': 'number',
|
|
54
81
|
'shape': []}))
|
|
55
82
|
"""
|
|
56
|
-
...
|
|
57
83
|
|
|
58
84
|
|
|
59
85
|
@runtime_checkable
|
|
@@ -63,14 +89,12 @@ class AsyncConfigurable(HasName, Protocol):
|
|
|
63
89
|
"""Same API as ``read`` but for slow-changing fields related to configuration.
|
|
64
90
|
e.g., exposure time. These will typically be read only once per run.
|
|
65
91
|
"""
|
|
66
|
-
...
|
|
67
92
|
|
|
68
93
|
@abstractmethod
|
|
69
94
|
async def describe_configuration(self) -> dict[str, DataKey]:
|
|
70
95
|
"""Same API as ``describe``, but corresponding to the keys in
|
|
71
96
|
``read_configuration``.
|
|
72
97
|
"""
|
|
73
|
-
...
|
|
74
98
|
|
|
75
99
|
|
|
76
100
|
@runtime_checkable
|
|
@@ -78,12 +102,10 @@ class AsyncPausable(Protocol):
|
|
|
78
102
|
@abstractmethod
|
|
79
103
|
async def pause(self) -> None:
|
|
80
104
|
"""Perform device-specific work when the RunEngine pauses."""
|
|
81
|
-
...
|
|
82
105
|
|
|
83
106
|
@abstractmethod
|
|
84
107
|
async def resume(self) -> None:
|
|
85
108
|
"""Perform device-specific work when the RunEngine resumes after a pause."""
|
|
86
|
-
...
|
|
87
109
|
|
|
88
110
|
|
|
89
111
|
@runtime_checkable
|
|
@@ -95,7 +117,6 @@ class AsyncStageable(Protocol):
|
|
|
95
117
|
It should return a ``Status`` that is marked done when the device is
|
|
96
118
|
done staging.
|
|
97
119
|
"""
|
|
98
|
-
...
|
|
99
120
|
|
|
100
121
|
@abstractmethod
|
|
101
122
|
def unstage(self) -> AsyncStatus:
|
|
@@ -104,7 +125,6 @@ class AsyncStageable(Protocol):
|
|
|
104
125
|
It should return a ``Status`` that is marked done when the device is finished
|
|
105
126
|
unstaging.
|
|
106
127
|
"""
|
|
107
|
-
...
|
|
108
128
|
|
|
109
129
|
|
|
110
130
|
C = TypeVar("C", contravariant=True)
|
ophyd_async/core/_readable.py
CHANGED
|
@@ -150,19 +150,19 @@ class StandardReadable(
|
|
|
150
150
|
:meth:`HintedSignal.uncached`
|
|
151
151
|
"""
|
|
152
152
|
|
|
153
|
-
dict_copy = self.
|
|
153
|
+
dict_copy = dict(self.children())
|
|
154
154
|
|
|
155
155
|
yield
|
|
156
156
|
|
|
157
157
|
# Set symmetric difference operator gives all newly added keys
|
|
158
|
-
|
|
159
|
-
|
|
158
|
+
new_dict = dict(self.children())
|
|
159
|
+
new_keys = dict_copy.keys() ^ new_dict.keys()
|
|
160
|
+
new_values = [new_dict[key] for key in new_keys]
|
|
160
161
|
|
|
161
162
|
flattened_values = []
|
|
162
163
|
for value in new_values:
|
|
163
164
|
if isinstance(value, DeviceVector):
|
|
164
|
-
|
|
165
|
-
flattened_values.extend([x[1] for x in children])
|
|
165
|
+
flattened_values.extend(value.values())
|
|
166
166
|
else:
|
|
167
167
|
flattened_values.append(value)
|
|
168
168
|
|