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.
Files changed (70) hide show
  1. ophyd_async/_version.py +2 -2
  2. ophyd_async/core/__init__.py +23 -8
  3. ophyd_async/core/_detector.py +5 -10
  4. ophyd_async/core/_device.py +139 -66
  5. ophyd_async/core/_device_filler.py +191 -0
  6. ophyd_async/core/_device_save_loader.py +6 -7
  7. ophyd_async/core/_mock_signal_backend.py +32 -40
  8. ophyd_async/core/_mock_signal_utils.py +22 -16
  9. ophyd_async/core/_protocol.py +28 -8
  10. ophyd_async/core/_readable.py +5 -5
  11. ophyd_async/core/_signal.py +140 -152
  12. ophyd_async/core/_signal_backend.py +131 -64
  13. ophyd_async/core/_soft_signal_backend.py +125 -194
  14. ophyd_async/core/_status.py +22 -6
  15. ophyd_async/core/_table.py +97 -100
  16. ophyd_async/core/_utils.py +71 -18
  17. ophyd_async/epics/adaravis/_aravis_controller.py +2 -2
  18. ophyd_async/epics/adaravis/_aravis_io.py +7 -5
  19. ophyd_async/epics/adcore/_core_io.py +4 -6
  20. ophyd_async/epics/adcore/_hdf_writer.py +2 -2
  21. ophyd_async/epics/adcore/_utils.py +15 -10
  22. ophyd_async/epics/adkinetix/__init__.py +2 -1
  23. ophyd_async/epics/adkinetix/_kinetix_controller.py +6 -3
  24. ophyd_async/epics/adkinetix/_kinetix_io.py +3 -4
  25. ophyd_async/epics/adpilatus/_pilatus_controller.py +2 -2
  26. ophyd_async/epics/adpilatus/_pilatus_io.py +2 -3
  27. ophyd_async/epics/adsimdetector/_sim_controller.py +2 -2
  28. ophyd_async/epics/advimba/__init__.py +4 -1
  29. ophyd_async/epics/advimba/_vimba_controller.py +6 -3
  30. ophyd_async/epics/advimba/_vimba_io.py +7 -8
  31. ophyd_async/epics/demo/_sensor.py +8 -4
  32. ophyd_async/epics/eiger/_eiger.py +1 -2
  33. ophyd_async/epics/eiger/_eiger_controller.py +1 -1
  34. ophyd_async/epics/eiger/_eiger_io.py +2 -4
  35. ophyd_async/epics/eiger/_odin_io.py +4 -4
  36. ophyd_async/epics/pvi/__init__.py +2 -2
  37. ophyd_async/epics/pvi/_pvi.py +56 -321
  38. ophyd_async/epics/signal/__init__.py +3 -4
  39. ophyd_async/epics/signal/_aioca.py +184 -236
  40. ophyd_async/epics/signal/_common.py +35 -49
  41. ophyd_async/epics/signal/_p4p.py +254 -387
  42. ophyd_async/epics/signal/_signal.py +63 -21
  43. ophyd_async/fastcs/core.py +9 -0
  44. ophyd_async/fastcs/panda/__init__.py +4 -4
  45. ophyd_async/fastcs/panda/_block.py +18 -13
  46. ophyd_async/fastcs/panda/_control.py +3 -5
  47. ophyd_async/fastcs/panda/_hdf_panda.py +5 -19
  48. ophyd_async/fastcs/panda/_table.py +29 -51
  49. ophyd_async/fastcs/panda/_trigger.py +8 -8
  50. ophyd_async/fastcs/panda/_writer.py +2 -5
  51. ophyd_async/plan_stubs/_ensure_connected.py +3 -1
  52. ophyd_async/plan_stubs/_fly.py +2 -2
  53. ophyd_async/plan_stubs/_nd_attributes.py +5 -4
  54. ophyd_async/py.typed +0 -0
  55. ophyd_async/sim/demo/_pattern_detector/_pattern_detector_controller.py +1 -2
  56. ophyd_async/tango/__init__.py +2 -4
  57. ophyd_async/tango/base_devices/_base_device.py +76 -143
  58. ophyd_async/tango/demo/_counter.py +2 -2
  59. ophyd_async/tango/demo/_mover.py +2 -2
  60. ophyd_async/tango/signal/__init__.py +2 -4
  61. ophyd_async/tango/signal/_signal.py +29 -50
  62. ophyd_async/tango/signal/_tango_transport.py +38 -40
  63. {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0a2.dist-info}/METADATA +8 -12
  64. ophyd_async-0.8.0a2.dist-info/RECORD +110 -0
  65. {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0a2.dist-info}/WHEEL +1 -1
  66. ophyd_async/epics/signal/_epics_transport.py +0 -34
  67. ophyd_async-0.7.0.dist-info/RECORD +0 -108
  68. {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0a2.dist-info}/LICENSE +0 -0
  69. {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0a2.dist-info}/entry_points.txt +0 -0
  70. {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 DEFAULT_TIMEOUT, ReadingValueCallback, T
10
+ from ._utils import Callback
11
11
 
12
12
 
13
- class MockSignalBackend(SignalBackend[T]):
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
- datatype: type[T] | None = None,
19
- initial_backend: SignalBackend[T] | None = None,
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 MockSignalBackends")
22
+ raise ValueError("Cannot make a MockSignalBackend for a MockSignalBackend")
23
23
 
24
24
  self.initial_backend = initial_backend
25
25
 
26
- if datatype is None:
27
- assert (
28
- self.initial_backend
29
- ), "Must supply either initial_backend or datatype"
30
- datatype = self.initial_backend.datatype
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
- self.datatype = datatype
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
- if not isinstance(self.initial_backend, SoftSignalBackend):
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
- self.soft_backend = SoftSignalBackend(datatype=datatype)
39
- else:
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
- if self.initial_backend:
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 = DEFAULT_TIMEOUT) -> None:
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: T | None, wait=True, timeout=None):
61
- await self.put_mock(value, wait=wait, timeout=timeout)
62
- await self.soft_backend.put(value, wait=wait, timeout=timeout)
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 asyncio.wait_for(self.put_proceeds.wait(), timeout=timeout)
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) -> T:
66
+ async def get_value(self) -> SignalDatatypeT:
74
67
  return await self.soft_backend.get_value()
75
68
 
76
- async def get_setpoint(self) -> T:
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: ReadingValueCallback[T] | None) -> None:
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 typing import Any
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 ._utils import T
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
- backend = signal._backend # noqa:SLF001
13
- assert isinstance(backend, MockSignalBackend), (
14
- "Expected to receive a `MockSignalBackend`, instead "
15
- f" received {type(backend)}. "
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[T], value: T):
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: Signal,
63
- values: Iterable[Any],
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: Signal,
103
- values: Iterable[Any],
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[T], callback: Callable[[T], None] | Callable[[T], Awaitable[None]]
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
 
@@ -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)
@@ -150,19 +150,19 @@ class StandardReadable(
150
150
  :meth:`HintedSignal.uncached`
151
151
  """
152
152
 
153
- dict_copy = self.__dict__.copy()
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
- new_keys = dict_copy.keys() ^ self.__dict__.keys()
159
- new_values = [self.__dict__[key] for key in new_keys]
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
- children = value.children()
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