ophyd-async 0.3a2__py3-none-any.whl → 0.3a4__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 +1 -1
- ophyd_async/core/__init__.py +35 -11
- ophyd_async/core/async_status.py +2 -0
- ophyd_async/core/detector.py +8 -9
- ophyd_async/core/device.py +22 -9
- ophyd_async/core/flyer.py +2 -2
- ophyd_async/core/mock_signal_backend.py +86 -0
- ophyd_async/core/mock_signal_utils.py +149 -0
- ophyd_async/core/signal.py +140 -49
- ophyd_async/core/signal_backend.py +2 -2
- ophyd_async/core/{sim_signal_backend.py → soft_signal_backend.py} +29 -39
- ophyd_async/core/standard_readable.py +211 -24
- ophyd_async/epics/_backend/_aioca.py +17 -13
- ophyd_async/epics/_backend/_p4p.py +28 -18
- ophyd_async/epics/_backend/common.py +17 -17
- ophyd_async/epics/areadetector/__init__.py +4 -4
- ophyd_async/epics/areadetector/aravis.py +7 -9
- ophyd_async/epics/areadetector/controllers/__init__.py +2 -1
- ophyd_async/epics/areadetector/controllers/kinetix_controller.py +49 -0
- ophyd_async/epics/areadetector/controllers/vimba_controller.py +66 -0
- ophyd_async/epics/areadetector/drivers/__init__.py +6 -0
- ophyd_async/epics/areadetector/drivers/ad_base.py +12 -10
- ophyd_async/epics/areadetector/drivers/aravis_driver.py +7 -5
- ophyd_async/epics/areadetector/drivers/kinetix_driver.py +27 -0
- ophyd_async/epics/areadetector/drivers/pilatus_driver.py +5 -2
- ophyd_async/epics/areadetector/drivers/vimba_driver.py +63 -0
- ophyd_async/epics/areadetector/kinetix.py +46 -0
- ophyd_async/epics/areadetector/pilatus.py +7 -12
- ophyd_async/epics/areadetector/single_trigger_det.py +14 -6
- ophyd_async/epics/areadetector/utils.py +2 -12
- ophyd_async/epics/areadetector/vimba.py +43 -0
- ophyd_async/epics/areadetector/writers/hdf_writer.py +6 -3
- ophyd_async/epics/areadetector/writers/nd_file_hdf.py +21 -18
- ophyd_async/epics/areadetector/writers/nd_plugin.py +6 -7
- ophyd_async/epics/demo/__init__.py +19 -22
- ophyd_async/epics/motion/motor.py +16 -13
- ophyd_async/epics/pvi/pvi.py +11 -11
- ophyd_async/epics/signal/signal.py +1 -1
- ophyd_async/log.py +130 -0
- ophyd_async/panda/_hdf_panda.py +3 -3
- ophyd_async/panda/writers/_hdf_writer.py +3 -3
- ophyd_async/protocols.py +26 -3
- ophyd_async/sim/demo/sim_motor.py +14 -12
- ophyd_async/sim/pattern_generator.py +9 -9
- ophyd_async/sim/sim_pattern_detector_writer.py +2 -2
- ophyd_async/sim/sim_pattern_generator.py +2 -2
- {ophyd_async-0.3a2.dist-info → ophyd_async-0.3a4.dist-info}/METADATA +20 -3
- ophyd_async-0.3a4.dist-info/RECORD +85 -0
- ophyd_async-0.3a2.dist-info/RECORD +0 -76
- {ophyd_async-0.3a2.dist-info → ophyd_async-0.3a4.dist-info}/LICENSE +0 -0
- {ophyd_async-0.3a2.dist-info → ophyd_async-0.3a4.dist-info}/WHEEL +0 -0
- {ophyd_async-0.3a2.dist-info → ophyd_async-0.3a4.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.3a2.dist-info → ophyd_async-0.3a4.dist-info}/top_level.txt +0 -0
ophyd_async/_version.py
CHANGED
ophyd_async/core/__init__.py
CHANGED
|
@@ -24,24 +24,37 @@ from .device_save_loader import (
|
|
|
24
24
|
walk_rw_signals,
|
|
25
25
|
)
|
|
26
26
|
from .flyer import HardwareTriggeredFlyable, TriggerLogic
|
|
27
|
+
from .mock_signal_backend import (
|
|
28
|
+
MockSignalBackend,
|
|
29
|
+
)
|
|
30
|
+
from .mock_signal_utils import (
|
|
31
|
+
assert_mock_put_called_with,
|
|
32
|
+
callback_on_mock_put,
|
|
33
|
+
mock_puts_blocked,
|
|
34
|
+
reset_mock_put_calls,
|
|
35
|
+
set_mock_put_proceeds,
|
|
36
|
+
set_mock_value,
|
|
37
|
+
set_mock_values,
|
|
38
|
+
)
|
|
27
39
|
from .signal import (
|
|
28
40
|
Signal,
|
|
29
41
|
SignalR,
|
|
30
42
|
SignalRW,
|
|
31
43
|
SignalW,
|
|
32
44
|
SignalX,
|
|
45
|
+
assert_configuration,
|
|
46
|
+
assert_emitted,
|
|
47
|
+
assert_reading,
|
|
48
|
+
assert_value,
|
|
33
49
|
observe_value,
|
|
34
50
|
set_and_wait_for_value,
|
|
35
|
-
|
|
36
|
-
set_sim_put_proceeds,
|
|
37
|
-
set_sim_value,
|
|
38
|
-
soft_signal_r_and_backend,
|
|
51
|
+
soft_signal_r_and_setter,
|
|
39
52
|
soft_signal_rw,
|
|
40
53
|
wait_for_value,
|
|
41
54
|
)
|
|
42
55
|
from .signal_backend import SignalBackend
|
|
43
|
-
from .
|
|
44
|
-
from .standard_readable import StandardReadable
|
|
56
|
+
from .soft_signal_backend import SoftSignalBackend
|
|
57
|
+
from .standard_readable import ConfigSignal, HintedSignal, StandardReadable
|
|
45
58
|
from .utils import (
|
|
46
59
|
DEFAULT_TIMEOUT,
|
|
47
60
|
Callback,
|
|
@@ -55,9 +68,15 @@ from .utils import (
|
|
|
55
68
|
)
|
|
56
69
|
|
|
57
70
|
__all__ = [
|
|
71
|
+
"assert_mock_put_called_with",
|
|
72
|
+
"callback_on_mock_put",
|
|
73
|
+
"mock_puts_blocked",
|
|
74
|
+
"set_mock_values",
|
|
75
|
+
"reset_mock_put_calls",
|
|
58
76
|
"SignalBackend",
|
|
59
|
-
"
|
|
77
|
+
"SoftSignalBackend",
|
|
60
78
|
"DetectorControl",
|
|
79
|
+
"MockSignalBackend",
|
|
61
80
|
"DetectorTrigger",
|
|
62
81
|
"DetectorWriter",
|
|
63
82
|
"StandardDetector",
|
|
@@ -69,13 +88,12 @@ __all__ = [
|
|
|
69
88
|
"SignalW",
|
|
70
89
|
"SignalRW",
|
|
71
90
|
"SignalX",
|
|
72
|
-
"
|
|
91
|
+
"soft_signal_r_and_setter",
|
|
73
92
|
"soft_signal_rw",
|
|
74
93
|
"observe_value",
|
|
75
94
|
"set_and_wait_for_value",
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"set_sim_value",
|
|
95
|
+
"set_mock_put_proceeds",
|
|
96
|
+
"set_mock_value",
|
|
79
97
|
"wait_for_value",
|
|
80
98
|
"AsyncStatus",
|
|
81
99
|
"DirectoryInfo",
|
|
@@ -84,6 +102,8 @@ __all__ = [
|
|
|
84
102
|
"ShapeProvider",
|
|
85
103
|
"StaticDirectoryProvider",
|
|
86
104
|
"StandardReadable",
|
|
105
|
+
"ConfigSignal",
|
|
106
|
+
"HintedSignal",
|
|
87
107
|
"TriggerInfo",
|
|
88
108
|
"TriggerLogic",
|
|
89
109
|
"HardwareTriggeredFlyable",
|
|
@@ -103,4 +123,8 @@ __all__ = [
|
|
|
103
123
|
"walk_rw_signals",
|
|
104
124
|
"load_device",
|
|
105
125
|
"save_device",
|
|
126
|
+
"assert_reading",
|
|
127
|
+
"assert_value",
|
|
128
|
+
"assert_configuration",
|
|
129
|
+
"assert_emitted",
|
|
106
130
|
]
|
ophyd_async/core/async_status.py
CHANGED
|
@@ -21,7 +21,9 @@ class AsyncStatus(Status):
|
|
|
21
21
|
self.task = awaitable
|
|
22
22
|
else:
|
|
23
23
|
self.task = asyncio.create_task(awaitable) # type: ignore
|
|
24
|
+
|
|
24
25
|
self.task.add_done_callback(self._run_callbacks)
|
|
26
|
+
|
|
25
27
|
self._callbacks = cast(List[Callback[Status]], [])
|
|
26
28
|
self._watchers = watchers
|
|
27
29
|
|
ophyd_async/core/detector.py
CHANGED
|
@@ -19,7 +19,7 @@ from typing import (
|
|
|
19
19
|
|
|
20
20
|
from bluesky.protocols import (
|
|
21
21
|
Collectable,
|
|
22
|
-
|
|
22
|
+
DataKey,
|
|
23
23
|
Flyable,
|
|
24
24
|
Preparable,
|
|
25
25
|
Reading,
|
|
@@ -33,7 +33,6 @@ from ophyd_async.protocols import AsyncConfigurable, AsyncReadable
|
|
|
33
33
|
|
|
34
34
|
from .async_status import AsyncStatus
|
|
35
35
|
from .device import Device
|
|
36
|
-
from .signal import SignalR
|
|
37
36
|
from .utils import DEFAULT_TIMEOUT, merge_gathered_dicts
|
|
38
37
|
|
|
39
38
|
T = TypeVar("T")
|
|
@@ -110,7 +109,7 @@ class DetectorWriter(ABC):
|
|
|
110
109
|
(e.g. an HDF5 file)"""
|
|
111
110
|
|
|
112
111
|
@abstractmethod
|
|
113
|
-
async def open(self, multiplier: int = 1) -> Dict[str,
|
|
112
|
+
async def open(self, multiplier: int = 1) -> Dict[str, DataKey]:
|
|
114
113
|
"""Open writer and wait for it to be ready for data.
|
|
115
114
|
|
|
116
115
|
Args:
|
|
@@ -161,7 +160,7 @@ class StandardDetector(
|
|
|
161
160
|
self,
|
|
162
161
|
controller: DetectorControl,
|
|
163
162
|
writer: DetectorWriter,
|
|
164
|
-
config_sigs: Sequence[
|
|
163
|
+
config_sigs: Sequence[AsyncReadable] = (),
|
|
165
164
|
name: str = "",
|
|
166
165
|
writer_timeout: float = DEFAULT_TIMEOUT,
|
|
167
166
|
) -> None:
|
|
@@ -181,7 +180,7 @@ class StandardDetector(
|
|
|
181
180
|
"""
|
|
182
181
|
self._controller = controller
|
|
183
182
|
self._writer = writer
|
|
184
|
-
self._describe: Dict[str,
|
|
183
|
+
self._describe: Dict[str, DataKey] = {}
|
|
185
184
|
self._config_sigs = list(config_sigs)
|
|
186
185
|
self._frame_writing_timeout = writer_timeout
|
|
187
186
|
# For prepare
|
|
@@ -214,7 +213,7 @@ class StandardDetector(
|
|
|
214
213
|
async def _check_config_sigs(self):
|
|
215
214
|
"""Checks configuration signals are named and connected."""
|
|
216
215
|
for signal in self._config_sigs:
|
|
217
|
-
if signal.
|
|
216
|
+
if signal.name == "":
|
|
218
217
|
raise Exception(
|
|
219
218
|
"config signal must be named before it is passed to the detector"
|
|
220
219
|
)
|
|
@@ -234,14 +233,14 @@ class StandardDetector(
|
|
|
234
233
|
async def read_configuration(self) -> Dict[str, Reading]:
|
|
235
234
|
return await merge_gathered_dicts(sig.read() for sig in self._config_sigs)
|
|
236
235
|
|
|
237
|
-
async def describe_configuration(self) -> Dict[str,
|
|
236
|
+
async def describe_configuration(self) -> Dict[str, DataKey]:
|
|
238
237
|
return await merge_gathered_dicts(sig.describe() for sig in self._config_sigs)
|
|
239
238
|
|
|
240
239
|
async def read(self) -> Dict[str, Reading]:
|
|
241
240
|
# All data is in StreamResources, not Events, so nothing to output here
|
|
242
241
|
return {}
|
|
243
242
|
|
|
244
|
-
async def describe(self) -> Dict[str,
|
|
243
|
+
async def describe(self) -> Dict[str, DataKey]:
|
|
245
244
|
return self._describe
|
|
246
245
|
|
|
247
246
|
@AsyncStatus.wrap
|
|
@@ -330,7 +329,7 @@ class StandardDetector(
|
|
|
330
329
|
assert self._fly_status, "Kickoff not run"
|
|
331
330
|
return await self._fly_status
|
|
332
331
|
|
|
333
|
-
async def describe_collect(self) -> Dict[str,
|
|
332
|
+
async def describe_collect(self) -> Dict[str, DataKey]:
|
|
334
333
|
return self._describe
|
|
335
334
|
|
|
336
335
|
async def collect_asset_docs(
|
ophyd_async/core/device.py
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import sys
|
|
6
|
+
from functools import cached_property
|
|
7
|
+
from logging import LoggerAdapter, getLogger
|
|
6
8
|
from typing import (
|
|
7
9
|
Any,
|
|
8
10
|
Coroutine,
|
|
@@ -39,6 +41,12 @@ class Device(HasName):
|
|
|
39
41
|
"""Return the name of the Device"""
|
|
40
42
|
return self._name
|
|
41
43
|
|
|
44
|
+
@cached_property
|
|
45
|
+
def log(self):
|
|
46
|
+
return LoggerAdapter(
|
|
47
|
+
getLogger("ophyd_async.devices"), {"ophyd_async_device_name": self.name}
|
|
48
|
+
)
|
|
49
|
+
|
|
42
50
|
def children(self) -> Iterator[Tuple[str, Device]]:
|
|
43
51
|
for attr_name, attr in self.__dict__.items():
|
|
44
52
|
if attr_name != "parent" and isinstance(attr, Device):
|
|
@@ -52,26 +60,31 @@ class Device(HasName):
|
|
|
52
60
|
name:
|
|
53
61
|
New name to set
|
|
54
62
|
"""
|
|
63
|
+
|
|
64
|
+
# Ensure self.log is recreated after a name change
|
|
65
|
+
if hasattr(self, "log"):
|
|
66
|
+
del self.log
|
|
67
|
+
|
|
55
68
|
self._name = name
|
|
56
69
|
for attr_name, child in self.children():
|
|
57
70
|
child_name = f"{name}-{attr_name.rstrip('_')}" if name else ""
|
|
58
71
|
child.set_name(child_name)
|
|
59
72
|
child.parent = self
|
|
60
73
|
|
|
61
|
-
async def connect(self,
|
|
74
|
+
async def connect(self, mock: bool = False, timeout: float = DEFAULT_TIMEOUT):
|
|
62
75
|
"""Connect self and all child Devices.
|
|
63
76
|
|
|
64
77
|
Contains a timeout that gets propagated to child.connect methods.
|
|
65
78
|
|
|
66
79
|
Parameters
|
|
67
80
|
----------
|
|
68
|
-
|
|
69
|
-
If True then
|
|
81
|
+
mock:
|
|
82
|
+
If True then use ``MockSignalBackend`` for all Signals
|
|
70
83
|
timeout:
|
|
71
84
|
Time to wait before failing with a TimeoutError.
|
|
72
85
|
"""
|
|
73
86
|
coros = {
|
|
74
|
-
name: child_device.connect(
|
|
87
|
+
name: child_device.connect(mock=mock, timeout=timeout)
|
|
75
88
|
for name, child_device in self.children()
|
|
76
89
|
}
|
|
77
90
|
if coros:
|
|
@@ -105,9 +118,9 @@ class DeviceCollector:
|
|
|
105
118
|
If True, call ``device.set_name(variable_name)`` on all collected
|
|
106
119
|
Devices
|
|
107
120
|
connect:
|
|
108
|
-
If True, call ``device.connect(
|
|
121
|
+
If True, call ``device.connect(mock)`` in parallel on all
|
|
109
122
|
collected Devices
|
|
110
|
-
|
|
123
|
+
mock:
|
|
111
124
|
If True, connect Signals in simulation mode
|
|
112
125
|
timeout:
|
|
113
126
|
How long to wait for connect before logging an exception
|
|
@@ -129,12 +142,12 @@ class DeviceCollector:
|
|
|
129
142
|
self,
|
|
130
143
|
set_name=True,
|
|
131
144
|
connect=True,
|
|
132
|
-
|
|
145
|
+
mock=False,
|
|
133
146
|
timeout: float = 10.0,
|
|
134
147
|
):
|
|
135
148
|
self._set_name = set_name
|
|
136
149
|
self._connect = connect
|
|
137
|
-
self.
|
|
150
|
+
self._mock = mock
|
|
138
151
|
self._timeout = timeout
|
|
139
152
|
self._names_on_enter: Set[str] = set()
|
|
140
153
|
self._objects_on_exit: Dict[str, Any] = {}
|
|
@@ -168,7 +181,7 @@ class DeviceCollector:
|
|
|
168
181
|
obj.set_name(name)
|
|
169
182
|
if self._connect:
|
|
170
183
|
connect_coroutines[name] = obj.connect(
|
|
171
|
-
self.
|
|
184
|
+
self._mock, timeout=self._timeout
|
|
172
185
|
)
|
|
173
186
|
|
|
174
187
|
# Connect to all the devices
|
ophyd_async/core/flyer.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from typing import Dict, Generic, Sequence, TypeVar
|
|
3
3
|
|
|
4
|
-
from bluesky.protocols import
|
|
4
|
+
from bluesky.protocols import DataKey, Flyable, Preparable, Reading, Stageable
|
|
5
5
|
|
|
6
6
|
from .async_status import AsyncStatus
|
|
7
7
|
from .device import Device
|
|
@@ -74,7 +74,7 @@ class HardwareTriggeredFlyable(
|
|
|
74
74
|
async def complete(self) -> None:
|
|
75
75
|
await self._trigger_logic.complete()
|
|
76
76
|
|
|
77
|
-
async def describe_configuration(self) -> Dict[str,
|
|
77
|
+
async def describe_configuration(self) -> Dict[str, DataKey]:
|
|
78
78
|
return await merge_gathered_dicts(
|
|
79
79
|
[sig.describe() for sig in self._configuration_signals]
|
|
80
80
|
)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Optional, Type
|
|
3
|
+
from unittest.mock import MagicMock
|
|
4
|
+
|
|
5
|
+
from bluesky.protocols import Descriptor, Reading
|
|
6
|
+
|
|
7
|
+
from ophyd_async.core.signal_backend import SignalBackend
|
|
8
|
+
from ophyd_async.core.soft_signal_backend import SoftSignalBackend
|
|
9
|
+
from ophyd_async.core.utils import DEFAULT_TIMEOUT, ReadingValueCallback, T
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MockSignalBackend(SignalBackend):
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
datatype: Optional[Type[T]] = None,
|
|
16
|
+
initial_backend: Optional[SignalBackend[T]] = None,
|
|
17
|
+
) -> None:
|
|
18
|
+
if isinstance(initial_backend, MockSignalBackend):
|
|
19
|
+
raise ValueError("Cannot make a MockSignalBackend for a MockSignalBackends")
|
|
20
|
+
|
|
21
|
+
self.initial_backend = initial_backend
|
|
22
|
+
|
|
23
|
+
if datatype is None:
|
|
24
|
+
assert (
|
|
25
|
+
self.initial_backend
|
|
26
|
+
), "Must supply either initial_backend or datatype"
|
|
27
|
+
datatype = self.initial_backend.datatype
|
|
28
|
+
|
|
29
|
+
self.datatype = datatype
|
|
30
|
+
|
|
31
|
+
if not isinstance(self.initial_backend, SoftSignalBackend):
|
|
32
|
+
# If the backend is a hard signal backend, or not provided,
|
|
33
|
+
# then we create a soft signal to mimick it
|
|
34
|
+
|
|
35
|
+
self.soft_backend = SoftSignalBackend(datatype=datatype)
|
|
36
|
+
else:
|
|
37
|
+
self.soft_backend = initial_backend
|
|
38
|
+
|
|
39
|
+
self.mock = MagicMock()
|
|
40
|
+
|
|
41
|
+
self.put_proceeds = asyncio.Event()
|
|
42
|
+
self.put_proceeds.set()
|
|
43
|
+
|
|
44
|
+
def source(self, name: str) -> str:
|
|
45
|
+
self.mock.source(name)
|
|
46
|
+
if self.initial_backend:
|
|
47
|
+
return f"mock+{self.initial_backend.source(name)}"
|
|
48
|
+
return f"mock+{name}"
|
|
49
|
+
|
|
50
|
+
async def connect(self, timeout: float = DEFAULT_TIMEOUT) -> None:
|
|
51
|
+
self.mock.connect(timeout=timeout)
|
|
52
|
+
|
|
53
|
+
async def put(self, value: Optional[T], wait=True, timeout=None):
|
|
54
|
+
self.mock.put(value, wait=wait, timeout=timeout)
|
|
55
|
+
await self.soft_backend.put(value, wait=wait, timeout=timeout)
|
|
56
|
+
|
|
57
|
+
if wait:
|
|
58
|
+
await asyncio.wait_for(self.put_proceeds.wait(), timeout=timeout)
|
|
59
|
+
|
|
60
|
+
def set_value(self, value: T):
|
|
61
|
+
self.mock.set_value(value)
|
|
62
|
+
self.soft_backend.set_value(value)
|
|
63
|
+
|
|
64
|
+
async def get_descriptor(self, source: str) -> Descriptor:
|
|
65
|
+
self.mock.get_descriptor(source)
|
|
66
|
+
return await self.soft_backend.get_descriptor(source)
|
|
67
|
+
|
|
68
|
+
async def get_reading(self) -> Reading:
|
|
69
|
+
self.mock.get_reading()
|
|
70
|
+
return await self.soft_backend.get_reading()
|
|
71
|
+
|
|
72
|
+
async def get_value(self) -> T:
|
|
73
|
+
self.mock.get_value()
|
|
74
|
+
return await self.soft_backend.get_value()
|
|
75
|
+
|
|
76
|
+
async def get_setpoint(self) -> T:
|
|
77
|
+
"""For a soft signal, the setpoint and readback values are the same."""
|
|
78
|
+
self.mock.get_setpoint()
|
|
79
|
+
return await self.soft_backend.get_setpoint()
|
|
80
|
+
|
|
81
|
+
async def get_datakey(self, source: str) -> Descriptor:
|
|
82
|
+
return await self.soft_backend.get_datakey(source)
|
|
83
|
+
|
|
84
|
+
def set_callback(self, callback: Optional[ReadingValueCallback[T]]) -> None:
|
|
85
|
+
self.mock.set_callback(callback)
|
|
86
|
+
self.soft_backend.set_callback(callback)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from contextlib import asynccontextmanager, contextmanager
|
|
2
|
+
from typing import Any, Callable, Generator, Iterable, Iterator, List
|
|
3
|
+
from unittest.mock import ANY
|
|
4
|
+
|
|
5
|
+
from ophyd_async.core.signal import Signal
|
|
6
|
+
from ophyd_async.core.utils import T
|
|
7
|
+
|
|
8
|
+
from .mock_signal_backend import MockSignalBackend
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _get_mock_signal_backend(signal: Signal) -> MockSignalBackend:
|
|
12
|
+
assert isinstance(signal._backend, MockSignalBackend), (
|
|
13
|
+
"Expected to receive a `MockSignalBackend`, instead "
|
|
14
|
+
f" received {type(signal._backend)}. "
|
|
15
|
+
)
|
|
16
|
+
return signal._backend
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def set_mock_value(signal: Signal[T], value: T):
|
|
20
|
+
"""Set the value of a signal that is in mock mode."""
|
|
21
|
+
backend = _get_mock_signal_backend(signal)
|
|
22
|
+
backend.set_value(value)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def set_mock_put_proceeds(signal: Signal[T], proceeds: bool):
|
|
26
|
+
"""Allow or block a put with wait=True from proceeding"""
|
|
27
|
+
backend = _get_mock_signal_backend(signal)
|
|
28
|
+
|
|
29
|
+
if proceeds:
|
|
30
|
+
backend.put_proceeds.set()
|
|
31
|
+
else:
|
|
32
|
+
backend.put_proceeds.clear()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@asynccontextmanager
|
|
36
|
+
async def mock_puts_blocked(*signals: List[Signal]):
|
|
37
|
+
for signal in signals:
|
|
38
|
+
set_mock_put_proceeds(signal, False)
|
|
39
|
+
yield
|
|
40
|
+
for signal in signals:
|
|
41
|
+
set_mock_put_proceeds(signal, True)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def assert_mock_put_called_with(signal: Signal, value: Any, wait=ANY, timeout=ANY):
|
|
45
|
+
backend = _get_mock_signal_backend(signal)
|
|
46
|
+
backend.mock.put.assert_called_with(value, wait=wait, timeout=timeout)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def reset_mock_put_calls(signal: Signal):
|
|
50
|
+
backend = _get_mock_signal_backend(signal)
|
|
51
|
+
backend.mock.put.reset_mock()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class _SetValuesIterator:
|
|
55
|
+
# Garbage collected by the time __del__ is called unless we put it as a
|
|
56
|
+
# global attrbute here.
|
|
57
|
+
require_all_consumed: bool = False
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
signal: Signal,
|
|
62
|
+
values: Iterable[Any],
|
|
63
|
+
require_all_consumed: bool = False,
|
|
64
|
+
):
|
|
65
|
+
self.signal = signal
|
|
66
|
+
self.values = values
|
|
67
|
+
self.require_all_consumed = require_all_consumed
|
|
68
|
+
self.index = 0
|
|
69
|
+
|
|
70
|
+
self.iterator = enumerate(values, start=1)
|
|
71
|
+
|
|
72
|
+
def __iter__(self):
|
|
73
|
+
return self
|
|
74
|
+
|
|
75
|
+
def __next__(self):
|
|
76
|
+
# Will propogate StopIteration
|
|
77
|
+
self.index, next_value = next(self.iterator)
|
|
78
|
+
set_mock_value(self.signal, next_value)
|
|
79
|
+
return next_value
|
|
80
|
+
|
|
81
|
+
def __del__(self):
|
|
82
|
+
if self.require_all_consumed and self.index != len(self.values):
|
|
83
|
+
raise AssertionError("Not all values have been consumed.")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def set_mock_values(
|
|
87
|
+
signal: Signal,
|
|
88
|
+
values: Iterable[Any],
|
|
89
|
+
require_all_consumed: bool = False,
|
|
90
|
+
) -> Iterator[Any]:
|
|
91
|
+
"""Iterator to set a signal to a sequence of values, optionally repeating the
|
|
92
|
+
sequence.
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
signal:
|
|
97
|
+
A signal with a `MockSignalBackend` backend.
|
|
98
|
+
values:
|
|
99
|
+
An iterable of the values to set the signal to, on each iteration
|
|
100
|
+
the value will be set.
|
|
101
|
+
require_all_consumed:
|
|
102
|
+
If True, an AssertionError will be raised if the iterator is deleted before
|
|
103
|
+
all values have been consumed.
|
|
104
|
+
|
|
105
|
+
Notes
|
|
106
|
+
-----
|
|
107
|
+
Example usage::
|
|
108
|
+
|
|
109
|
+
for value_set in set_mock_values(signal, [1, 2, 3]):
|
|
110
|
+
# do something
|
|
111
|
+
|
|
112
|
+
cm = set_mock_values(signal, 1, 2, 3, require_all_consumed=True):
|
|
113
|
+
next(cm)
|
|
114
|
+
# do something
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
return _SetValuesIterator(
|
|
118
|
+
signal,
|
|
119
|
+
values,
|
|
120
|
+
require_all_consumed=require_all_consumed,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@contextmanager
|
|
125
|
+
def _unset_side_effect_cm(mock):
|
|
126
|
+
yield
|
|
127
|
+
mock.put.side_effect = None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# linting isn't smart enought to realize @contextmanager will give use a
|
|
131
|
+
# ContextManager[None]
|
|
132
|
+
def callback_on_mock_put(
|
|
133
|
+
signal: Signal, callback: Callable[[T], None]
|
|
134
|
+
) -> Generator[None, None, None]:
|
|
135
|
+
"""For setting a callback when a backend is put to.
|
|
136
|
+
|
|
137
|
+
Can either be used in a context, with the callback being
|
|
138
|
+
unset on exit, or as an ordinary function.
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
signal:
|
|
143
|
+
A signal with a `MockSignalBackend` backend.
|
|
144
|
+
callback:
|
|
145
|
+
The callback to call when the backend is put to during the context.
|
|
146
|
+
"""
|
|
147
|
+
backend = _get_mock_signal_backend(signal)
|
|
148
|
+
backend.mock.put.side_effect = callback
|
|
149
|
+
return _unset_side_effect_cm(backend.mock)
|