ophyd-async 0.8.0a5__py3-none-any.whl → 0.9.0__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 (116) hide show
  1. ophyd_async/_version.py +2 -2
  2. ophyd_async/core/__init__.py +17 -46
  3. ophyd_async/core/_detector.py +68 -44
  4. ophyd_async/core/_device.py +120 -79
  5. ophyd_async/core/_device_filler.py +17 -8
  6. ophyd_async/core/_flyer.py +2 -2
  7. ophyd_async/core/_protocol.py +0 -28
  8. ophyd_async/core/_readable.py +30 -23
  9. ophyd_async/core/_settings.py +104 -0
  10. ophyd_async/core/_signal.py +164 -151
  11. ophyd_async/core/_signal_backend.py +4 -1
  12. ophyd_async/core/_soft_signal_backend.py +2 -1
  13. ophyd_async/core/_table.py +27 -14
  14. ophyd_async/core/_utils.py +30 -5
  15. ophyd_async/core/_yaml_settings.py +64 -0
  16. ophyd_async/epics/adandor/__init__.py +9 -0
  17. ophyd_async/epics/adandor/_andor.py +45 -0
  18. ophyd_async/epics/adandor/_andor_controller.py +49 -0
  19. ophyd_async/epics/adandor/_andor_io.py +36 -0
  20. ophyd_async/epics/adaravis/__init__.py +3 -1
  21. ophyd_async/epics/adaravis/_aravis.py +23 -37
  22. ophyd_async/epics/adaravis/_aravis_controller.py +21 -30
  23. ophyd_async/epics/adaravis/_aravis_io.py +4 -4
  24. ophyd_async/epics/adcore/__init__.py +15 -8
  25. ophyd_async/epics/adcore/_core_detector.py +41 -0
  26. ophyd_async/epics/adcore/_core_io.py +56 -31
  27. ophyd_async/epics/adcore/_core_logic.py +99 -84
  28. ophyd_async/epics/adcore/_core_writer.py +219 -0
  29. ophyd_async/epics/adcore/_hdf_writer.py +33 -59
  30. ophyd_async/epics/adcore/_jpeg_writer.py +26 -0
  31. ophyd_async/epics/adcore/_single_trigger.py +5 -4
  32. ophyd_async/epics/adcore/_tiff_writer.py +26 -0
  33. ophyd_async/epics/adcore/_utils.py +37 -36
  34. ophyd_async/epics/adkinetix/_kinetix.py +29 -24
  35. ophyd_async/epics/adkinetix/_kinetix_controller.py +15 -27
  36. ophyd_async/epics/adkinetix/_kinetix_io.py +7 -7
  37. ophyd_async/epics/adpilatus/__init__.py +2 -2
  38. ophyd_async/epics/adpilatus/_pilatus.py +28 -40
  39. ophyd_async/epics/adpilatus/_pilatus_controller.py +47 -25
  40. ophyd_async/epics/adpilatus/_pilatus_io.py +5 -5
  41. ophyd_async/epics/adsimdetector/__init__.py +3 -3
  42. ophyd_async/epics/adsimdetector/_sim.py +33 -17
  43. ophyd_async/epics/advimba/_vimba.py +23 -23
  44. ophyd_async/epics/advimba/_vimba_controller.py +21 -35
  45. ophyd_async/epics/advimba/_vimba_io.py +23 -23
  46. ophyd_async/epics/core/_aioca.py +52 -21
  47. ophyd_async/epics/core/_p4p.py +59 -16
  48. ophyd_async/epics/core/_pvi_connector.py +4 -2
  49. ophyd_async/epics/core/_signal.py +9 -2
  50. ophyd_async/epics/core/_util.py +10 -1
  51. ophyd_async/epics/eiger/_eiger_controller.py +10 -5
  52. ophyd_async/epics/eiger/_eiger_io.py +3 -3
  53. ophyd_async/epics/motor.py +26 -15
  54. ophyd_async/epics/sim/_ioc.py +29 -0
  55. ophyd_async/epics/{demo → sim}/_mover.py +12 -6
  56. ophyd_async/epics/{demo → sim}/_sensor.py +2 -2
  57. ophyd_async/epics/testing/__init__.py +24 -0
  58. ophyd_async/epics/testing/_example_ioc.py +91 -0
  59. ophyd_async/epics/testing/_utils.py +50 -0
  60. ophyd_async/epics/testing/test_records.db +174 -0
  61. ophyd_async/epics/testing/test_records_pva.db +177 -0
  62. ophyd_async/fastcs/core.py +2 -2
  63. ophyd_async/fastcs/panda/__init__.py +0 -2
  64. ophyd_async/fastcs/panda/_block.py +9 -9
  65. ophyd_async/fastcs/panda/_control.py +9 -4
  66. ophyd_async/fastcs/panda/_hdf_panda.py +7 -2
  67. ophyd_async/fastcs/panda/_table.py +4 -1
  68. ophyd_async/fastcs/panda/_trigger.py +7 -7
  69. ophyd_async/plan_stubs/__init__.py +14 -0
  70. ophyd_async/plan_stubs/_ensure_connected.py +11 -17
  71. ophyd_async/plan_stubs/_fly.py +2 -2
  72. ophyd_async/plan_stubs/_nd_attributes.py +7 -5
  73. ophyd_async/plan_stubs/_panda.py +13 -0
  74. ophyd_async/plan_stubs/_settings.py +125 -0
  75. ophyd_async/plan_stubs/_wait_for_awaitable.py +13 -0
  76. ophyd_async/sim/__init__.py +19 -0
  77. ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_detector_controller.py +9 -2
  78. ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_generator.py +13 -6
  79. ophyd_async/sim/{demo/_sim_motor.py → _sim_motor.py} +34 -32
  80. ophyd_async/tango/__init__.py +0 -43
  81. ophyd_async/tango/{signal → core}/__init__.py +7 -2
  82. ophyd_async/tango/{base_devices → core}/_base_device.py +38 -64
  83. ophyd_async/tango/{signal → core}/_signal.py +16 -4
  84. ophyd_async/tango/{base_devices → core}/_tango_readable.py +3 -4
  85. ophyd_async/tango/{signal → core}/_tango_transport.py +13 -15
  86. ophyd_async/tango/{demo → sim}/_counter.py +6 -7
  87. ophyd_async/tango/{demo → sim}/_mover.py +13 -9
  88. ophyd_async/testing/__init__.py +52 -0
  89. ophyd_async/testing/__pytest_assert_rewrite.py +4 -0
  90. ophyd_async/testing/_assert.py +176 -0
  91. ophyd_async/{core → testing}/_mock_signal_utils.py +15 -11
  92. ophyd_async/testing/_one_of_everything.py +126 -0
  93. ophyd_async/testing/_wait_for_pending.py +22 -0
  94. {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0.dist-info}/METADATA +50 -48
  95. ophyd_async-0.9.0.dist-info/RECORD +129 -0
  96. {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0.dist-info}/WHEEL +1 -1
  97. ophyd_async/core/_device_save_loader.py +0 -274
  98. ophyd_async/epics/adsimdetector/_sim_controller.py +0 -51
  99. ophyd_async/fastcs/panda/_utils.py +0 -16
  100. ophyd_async/sim/demo/__init__.py +0 -19
  101. ophyd_async/sim/testing/__init__.py +0 -0
  102. ophyd_async/tango/base_devices/__init__.py +0 -4
  103. ophyd_async-0.8.0a5.dist-info/RECORD +0 -112
  104. ophyd_async-0.8.0a5.dist-info/entry_points.txt +0 -2
  105. /ophyd_async/epics/{demo → sim}/__init__.py +0 -0
  106. /ophyd_async/epics/{demo → sim}/mover.db +0 -0
  107. /ophyd_async/epics/{demo → sim}/sensor.db +0 -0
  108. /ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/__init__.py +0 -0
  109. /ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_detector.py +0 -0
  110. /ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_detector_writer.py +0 -0
  111. /ophyd_async/tango/{demo → sim}/__init__.py +0 -0
  112. /ophyd_async/tango/{demo → sim}/_detector.py +0 -0
  113. /ophyd_async/tango/{demo → sim}/_tango/__init__.py +0 -0
  114. /ophyd_async/tango/{demo → sim}/_tango/_servers.py +0 -0
  115. {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0.dist-info}/LICENSE +0 -0
  116. {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0.dist-info}/top_level.txt +0 -0
@@ -2,18 +2,17 @@ from typing import Annotated as A
2
2
 
3
3
  from ophyd_async.core import DEFAULT_TIMEOUT, AsyncStatus, SignalR, SignalRW, SignalX
4
4
  from ophyd_async.core import StandardReadableFormat as Format
5
- from ophyd_async.tango import TangoReadable, tango_polling
5
+ from ophyd_async.tango.core import TangoPolling, TangoReadable
6
6
 
7
7
 
8
- # Enable device level polling, useful for servers that do not support events
9
- # Polling for individual signal can be enabled with a dict
10
- @tango_polling({"counts": (1.0, 0.1, 0.1), "sample_time": (0.1, 0.1, 0.1)})
11
8
  class TangoCounter(TangoReadable):
12
9
  # Enter the name and type of the signals you want to use
13
- # If type is None or Signal, the type will be inferred from the Tango device
14
- counts: A[SignalR[int], Format.HINTED_SIGNAL]
15
- sample_time: A[SignalRW[float], Format.CONFIG_SIGNAL]
10
+ # If the server doesn't support events, the TangoPolling annotation gives
11
+ # the parameters for ophyd to poll instead
12
+ counts: A[SignalR[int], Format.HINTED_SIGNAL, TangoPolling(1.0, 0.1, 0.1)]
13
+ sample_time: A[SignalRW[float], Format.CONFIG_SIGNAL, TangoPolling(0.1, 0.1, 0.1)]
16
14
  start: SignalX
15
+ # If a tango name clashes with a bluesky verb, add a trailing underscore
17
16
  reset_: SignalX
18
17
 
19
18
  @AsyncStatus.wrap
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ from typing import Annotated as A
2
3
 
3
4
  from bluesky.protocols import Movable, Stoppable
4
5
 
@@ -16,18 +17,18 @@ from ophyd_async.core import (
16
17
  wait_for_value,
17
18
  )
18
19
  from ophyd_async.core import StandardReadableFormat as Format
19
- from ophyd_async.tango import TangoReadable, tango_polling
20
+ from ophyd_async.tango.core import TangoPolling, TangoReadable
20
21
  from tango import DevState
21
22
 
22
23
 
23
- # Enable device level polling, useful for servers that do not support events
24
- @tango_polling((0.1, 0.1, 0.1))
25
24
  class TangoMover(TangoReadable, Movable, Stoppable):
26
25
  # Enter the name and type of the signals you want to use
27
- # If type is None or Signal, the type will be inferred from the Tango device
28
- position: SignalRW[float]
29
- velocity: SignalRW[float]
30
- state: SignalR[DevState]
26
+ # If the server doesn't support events, the TangoPolling annotation gives
27
+ # the parameters for ophyd to poll instead
28
+ position: A[SignalRW[float], TangoPolling(0.1, 0.1, 0.1)]
29
+ velocity: A[SignalRW[float], TangoPolling(0.1, 0.1, 0.1)]
30
+ state: A[SignalR[DevState], TangoPolling(0.1)]
31
+ # If a tango name clashes with a bluesky verb, add a trailing underscore
31
32
  stop_: SignalX
32
33
 
33
34
  def __init__(self, trl: str | None = "", name=""):
@@ -42,8 +43,11 @@ class TangoMover(TangoReadable, Movable, Stoppable):
42
43
  (old_position, velocity) = await asyncio.gather(
43
44
  self.position.get_value(), self.velocity.get_value()
44
45
  )
45
- if timeout is CALCULATE_TIMEOUT:
46
- assert velocity > 0, "Motor has zero velocity"
46
+ # TODO: check whether Tango does work with negative velocity
47
+ if timeout is CALCULATE_TIMEOUT and velocity == 0:
48
+ msg = "Motor has zero velocity"
49
+ raise ValueError(msg)
50
+ else:
47
51
  timeout = abs(value - old_position) / velocity + DEFAULT_TIMEOUT
48
52
 
49
53
  if not (isinstance(timeout, float) or timeout is None):
@@ -0,0 +1,52 @@
1
+ from . import __pytest_assert_rewrite # noqa: F401
2
+ from ._assert import (
3
+ ApproxTable,
4
+ MonitorQueue,
5
+ approx_value,
6
+ assert_configuration,
7
+ assert_describe_signal,
8
+ assert_emitted,
9
+ assert_reading,
10
+ assert_value,
11
+ )
12
+ from ._mock_signal_utils import (
13
+ callback_on_mock_put,
14
+ get_mock,
15
+ get_mock_put,
16
+ mock_puts_blocked,
17
+ reset_mock_put_calls,
18
+ set_mock_put_proceeds,
19
+ set_mock_value,
20
+ set_mock_values,
21
+ )
22
+ from ._one_of_everything import (
23
+ ExampleEnum,
24
+ ExampleTable,
25
+ OneOfEverythingDevice,
26
+ ParentOfEverythingDevice,
27
+ )
28
+ from ._wait_for_pending import wait_for_pending_wakeups
29
+
30
+ __all__ = [
31
+ "approx_value",
32
+ "assert_configuration",
33
+ "assert_describe_signal",
34
+ "assert_emitted",
35
+ "assert_reading",
36
+ "assert_value",
37
+ "callback_on_mock_put",
38
+ "get_mock",
39
+ "get_mock_put",
40
+ "mock_puts_blocked",
41
+ "reset_mock_put_calls",
42
+ "set_mock_put_proceeds",
43
+ "set_mock_value",
44
+ "set_mock_values",
45
+ "wait_for_pending_wakeups",
46
+ "ExampleEnum",
47
+ "ExampleTable",
48
+ "OneOfEverythingDevice",
49
+ "ParentOfEverythingDevice",
50
+ "MonitorQueue",
51
+ "ApproxTable",
52
+ ]
@@ -0,0 +1,4 @@
1
+ import pytest
2
+
3
+ # So that bare asserts give a nice pytest traceback
4
+ pytest.register_assert_rewrite("ophyd_async.testing._assert")
@@ -0,0 +1,176 @@
1
+ import asyncio
2
+ import time
3
+ from contextlib import AbstractContextManager
4
+ from typing import Any
5
+
6
+ import pytest
7
+ from bluesky.protocols import Reading
8
+ from event_model import DataKey
9
+
10
+ from ophyd_async.core import (
11
+ AsyncConfigurable,
12
+ AsyncReadable,
13
+ SignalDatatypeT,
14
+ SignalR,
15
+ Table,
16
+ )
17
+
18
+
19
+ def approx_value(value: Any):
20
+ return ApproxTable(value) if isinstance(value, Table) else pytest.approx(value)
21
+
22
+
23
+ async def assert_value(signal: SignalR[SignalDatatypeT], value: Any) -> None:
24
+ """Assert a signal's value and compare it an expected signal.
25
+
26
+ Parameters
27
+ ----------
28
+ signal:
29
+ signal with get_value.
30
+ value:
31
+ The expected value from the signal.
32
+
33
+ Notes
34
+ -----
35
+ Example usage::
36
+ await assert_value(signal, value)
37
+
38
+ """
39
+ actual_value = await signal.get_value()
40
+ assert approx_value(value) == actual_value
41
+
42
+
43
+ async def assert_reading(
44
+ readable: AsyncReadable, expected_reading: dict[str, Reading]
45
+ ) -> None:
46
+ """Assert readings from readable.
47
+
48
+ Parameters
49
+ ----------
50
+ readable:
51
+ Callable with readable.read function that generate readings.
52
+
53
+ reading:
54
+ The expected readings from the readable.
55
+
56
+ Notes
57
+ -----
58
+ Example usage::
59
+ await assert_reading(readable, reading)
60
+
61
+ """
62
+ actual_reading = await readable.read()
63
+ approx_expected_reading = {
64
+ k: dict(v, value=approx_value(expected_reading[k]["value"]))
65
+ for k, v in expected_reading.items()
66
+ }
67
+ assert actual_reading == approx_expected_reading
68
+
69
+
70
+ async def assert_configuration(
71
+ configurable: AsyncConfigurable,
72
+ configuration: dict[str, Reading],
73
+ ) -> None:
74
+ """Assert readings from Configurable.
75
+
76
+ Parameters
77
+ ----------
78
+ configurable:
79
+ Configurable with Configurable.read function that generate readings.
80
+
81
+ configuration:
82
+ The expected readings from configurable.
83
+
84
+ Notes
85
+ -----
86
+ Example usage::
87
+ await assert_configuration(configurable configuration)
88
+
89
+ """
90
+ actual_configuration = await configurable.read_configuration()
91
+ approx_expected_configuration = {
92
+ k: dict(v, value=approx_value(configuration[k]["value"]))
93
+ for k, v in configuration.items()
94
+ }
95
+ assert actual_configuration == approx_expected_configuration
96
+
97
+
98
+ async def assert_describe_signal(signal: SignalR, /, **metadata):
99
+ actual_describe = await signal.describe()
100
+ assert list(actual_describe) == [signal.name]
101
+ (actual_datakey,) = actual_describe.values()
102
+ expected_datakey = DataKey(source=signal.source, **metadata)
103
+ assert actual_datakey == expected_datakey
104
+
105
+
106
+ def assert_emitted(docs: dict[str, list[dict]], **numbers: int):
107
+ """Assert emitted document generated by running a Bluesky plan
108
+
109
+ Parameters
110
+ ----------
111
+ Doc:
112
+ A dictionary
113
+
114
+ numbers:
115
+ expected emission in kwarg from
116
+
117
+ Notes
118
+ -----
119
+ Example usage::
120
+ docs = defaultdict(list)
121
+ RE.subscribe(lambda name, doc: docs[name].append(doc))
122
+ RE(my_plan())
123
+ assert_emitted(docs, start=1, descriptor=1, event=1, stop=1)
124
+ """
125
+ assert list(docs) == list(numbers)
126
+ actual_numbers = {name: len(d) for name, d in docs.items()}
127
+ assert actual_numbers == numbers
128
+
129
+
130
+ class ApproxTable:
131
+ def __init__(self, expected: Table, rel=None, abs=None, nan_ok: bool = False):
132
+ self.expected = expected
133
+ self.rel = rel
134
+ self.abs = abs
135
+ self.nan_ok = nan_ok
136
+
137
+ def __eq__(self, value):
138
+ approx_fields = {
139
+ k: pytest.approx(v, self.rel, self.abs, self.nan_ok)
140
+ for k, v in self.expected
141
+ }
142
+ expected = type(self.expected).model_construct(**approx_fields) # type: ignore
143
+ return expected == value
144
+
145
+
146
+ class MonitorQueue(AbstractContextManager):
147
+ def __init__(self, signal: SignalR):
148
+ self.signal = signal
149
+ self.updates: asyncio.Queue[dict[str, Reading]] = asyncio.Queue()
150
+ self.signal.subscribe(self.updates.put_nowait)
151
+
152
+ async def assert_updates(self, expected_value):
153
+ # Get an update, value and reading
154
+ expected_type = type(expected_value)
155
+ expected_value = approx_value(expected_value)
156
+ update = await self.updates.get()
157
+ value = await self.signal.get_value()
158
+ reading = await self.signal.read()
159
+ # Check they match what we expected
160
+ assert value == expected_value
161
+ assert type(value) is expected_type
162
+ expected_reading = {
163
+ self.signal.name: {
164
+ "value": expected_value,
165
+ "timestamp": pytest.approx(time.time(), rel=0.1),
166
+ "alarm_severity": 0,
167
+ }
168
+ }
169
+ assert reading == update == expected_reading
170
+
171
+ def __enter__(self):
172
+ self.signal.subscribe(self.updates.put_nowait)
173
+ return self
174
+
175
+ def __exit__(self, exc_type, exc_value, traceback):
176
+ self.signal.clear_sub(self.updates.put_nowait)
@@ -1,12 +1,16 @@
1
1
  from collections.abc import Awaitable, Callable, Iterable
2
- from contextlib import asynccontextmanager, contextmanager
2
+ from contextlib import contextmanager
3
3
  from unittest.mock import AsyncMock, Mock
4
4
 
5
- from ._device import Device
6
- from ._mock_signal_backend import MockSignalBackend
7
- from ._signal import Signal, SignalConnector, SignalR
8
- from ._soft_signal_backend import SignalDatatypeT
9
- from ._utils import LazyMock
5
+ from ophyd_async.core import (
6
+ Device,
7
+ LazyMock,
8
+ MockSignalBackend,
9
+ Signal,
10
+ SignalConnector,
11
+ SignalDatatypeT,
12
+ SignalR,
13
+ )
10
14
 
11
15
 
12
16
  def get_mock(device: Device | Signal) -> Mock:
@@ -18,9 +22,9 @@ def get_mock(device: Device | Signal) -> Mock:
18
22
  def _get_mock_signal_backend(signal: Signal) -> MockSignalBackend:
19
23
  connector = signal._connector # noqa: SLF001
20
24
  assert isinstance(connector, SignalConnector), f"Expected Signal, got {signal}"
21
- assert isinstance(
22
- connector.backend, MockSignalBackend
23
- ), f"Signal {signal} not connected in mock mode"
25
+ assert isinstance(connector.backend, MockSignalBackend), (
26
+ f"Signal {signal} not connected in mock mode"
27
+ )
24
28
  return connector.backend
25
29
 
26
30
 
@@ -40,8 +44,8 @@ def set_mock_put_proceeds(signal: Signal, proceeds: bool):
40
44
  backend.put_proceeds.clear()
41
45
 
42
46
 
43
- @asynccontextmanager
44
- async def mock_puts_blocked(*signals: Signal):
47
+ @contextmanager
48
+ def mock_puts_blocked(*signals: Signal):
45
49
  for signal in signals:
46
50
  set_mock_put_proceeds(signal, False)
47
51
  yield
@@ -0,0 +1,126 @@
1
+ from collections.abc import Sequence
2
+ from typing import Any
3
+
4
+ import numpy as np
5
+
6
+ from ophyd_async.core import (
7
+ Array1D,
8
+ Device,
9
+ DTypeScalar_co,
10
+ SignalRW,
11
+ StandardReadable,
12
+ StrictEnum,
13
+ Table,
14
+ soft_signal_r_and_setter,
15
+ soft_signal_rw,
16
+ )
17
+ from ophyd_async.core import StandardReadableFormat as Format
18
+ from ophyd_async.core._device import DeviceVector
19
+
20
+
21
+ class ExampleEnum(StrictEnum):
22
+ A = "Aaa"
23
+ B = "Bbb"
24
+ C = "Ccc"
25
+
26
+
27
+ class ExampleTable(Table):
28
+ bool: Array1D[np.bool_]
29
+ int: Array1D[np.int32]
30
+ float: Array1D[np.float64]
31
+ str: Sequence[str]
32
+ enum: Sequence[ExampleEnum]
33
+
34
+
35
+ def int_array_signal(
36
+ dtype: type[DTypeScalar_co], name: str = ""
37
+ ) -> SignalRW[Array1D[DTypeScalar_co]]:
38
+ iinfo = np.iinfo(dtype) # type: ignore
39
+ value = np.array([iinfo.min, iinfo.max, 0, 1, 2, 3, 4], dtype=dtype)
40
+ return soft_signal_rw(Array1D[dtype], value, name)
41
+
42
+
43
+ def float_array_signal(
44
+ dtype: type[DTypeScalar_co], name: str = ""
45
+ ) -> SignalRW[Array1D[DTypeScalar_co]]:
46
+ finfo = np.finfo(dtype) # type: ignore
47
+ value = np.array(
48
+ [
49
+ finfo.min,
50
+ finfo.max,
51
+ finfo.smallest_normal,
52
+ finfo.smallest_subnormal,
53
+ 0,
54
+ 1.234,
55
+ 2.34e5,
56
+ 3.45e-6,
57
+ ],
58
+ dtype=dtype,
59
+ )
60
+ return soft_signal_rw(Array1D[dtype], value, name)
61
+
62
+
63
+ class OneOfEverythingDevice(StandardReadable):
64
+ # make a detector to test assert_configuration
65
+ def __init__(self, name=""):
66
+ # add all signals to configuration
67
+ with self.add_children_as_readables(Format.CONFIG_SIGNAL):
68
+ self.int = soft_signal_rw(int, 1)
69
+ self.float = soft_signal_rw(float, 1.234)
70
+ self.str = soft_signal_rw(str, "test_string")
71
+ self.bool = soft_signal_rw(bool, True)
72
+ self.enum = soft_signal_rw(ExampleEnum, ExampleEnum.B)
73
+ self.int8a = int_array_signal(np.int8)
74
+ self.uint8a = int_array_signal(np.uint8)
75
+ self.int16a = int_array_signal(np.int16)
76
+ self.uint16a = int_array_signal(np.uint16)
77
+ self.int32a = int_array_signal(np.int32)
78
+ self.uint32a = int_array_signal(np.uint32)
79
+ self.int64a = int_array_signal(np.int64)
80
+ self.uint64a = int_array_signal(np.uint64)
81
+ self.float32a = float_array_signal(np.float32)
82
+ self.float64a = float_array_signal(np.float64)
83
+ self.stra = soft_signal_rw(
84
+ Sequence[str],
85
+ ["one", "two", "three"],
86
+ )
87
+ self.enuma = soft_signal_rw(
88
+ Sequence[ExampleEnum],
89
+ [ExampleEnum.A, ExampleEnum.C],
90
+ )
91
+ self.table = soft_signal_rw(
92
+ ExampleTable,
93
+ ExampleTable(
94
+ bool=np.array([False, False, True, True], np.bool_),
95
+ int=np.array([1, 8, -9, 32], np.int32),
96
+ float=np.array([1.8, 8.2, -6, 32.9887], np.float64),
97
+ str=["Hello", "World", "Foo", "Bar"],
98
+ enum=[ExampleEnum.A, ExampleEnum.B, ExampleEnum.A, ExampleEnum.C],
99
+ ),
100
+ )
101
+ self.ndarray = soft_signal_rw(np.ndarray, np.array(([1, 2, 3], [4, 5, 6])))
102
+ super().__init__(name)
103
+
104
+
105
+ async def _get_signal_values(child: Device) -> dict[SignalRW, Any]:
106
+ if isinstance(child, SignalRW):
107
+ return {child: await child.get_value()}
108
+ ret = {}
109
+ for _, c in child.children():
110
+ ret.update(await _get_signal_values(c))
111
+ return ret
112
+
113
+
114
+ class ParentOfEverythingDevice(Device):
115
+ def __init__(self, name=""):
116
+ self.child = OneOfEverythingDevice()
117
+ self.vector = DeviceVector(
118
+ {1: OneOfEverythingDevice(), 3: OneOfEverythingDevice()}
119
+ )
120
+ self.sig_rw = soft_signal_rw(str, "Top level SignalRW")
121
+ self.sig_r, _ = soft_signal_r_and_setter(str, "Top level SignalR")
122
+ self._sig_rw = soft_signal_rw(str, "Top level private SignalRW")
123
+ super().__init__(name=name)
124
+
125
+ async def get_signal_values(self):
126
+ return await _get_signal_values(self)
@@ -0,0 +1,22 @@
1
+ import asyncio
2
+
3
+
4
+ async def wait_for_pending_wakeups(max_yields=20, raise_if_exceeded=True):
5
+ """Allow any ready asyncio tasks to be woken up.
6
+
7
+ Used in:
8
+
9
+ - Tests to allow tasks like ``set()`` to start so that signal
10
+ puts can be tested
11
+ - `observe_value` to allow it to be wrapped in `asyncio.wait_for`
12
+ with a timeout
13
+ """
14
+ loop = asyncio.get_event_loop()
15
+ # If anything has called loop.call_soon or is scheduled a wakeup
16
+ # then let it run
17
+ for _ in range(max_yields):
18
+ await asyncio.sleep(0)
19
+ if not loop._ready: # type: ignore # noqa: SLF001
20
+ return
21
+ if raise_if_exceeded:
22
+ raise RuntimeError(f"Tasks still scheduling wakeups after {max_yields} yields")
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: ophyd-async
3
- Version: 0.8.0a5
3
+ Version: 0.9.0
4
4
  Summary: Asynchronous Bluesky hardware abstraction code, compatible with control systems like EPICS and Tango
5
5
  Author-email: Tom Cobb <tom.cobb@diamond.ac.uk>
6
6
  License: BSD 3-Clause License
@@ -37,65 +37,67 @@ Classifier: Development Status :: 3 - Alpha
37
37
  Classifier: License :: OSI Approved :: BSD License
38
38
  Classifier: Programming Language :: Python :: 3.10
39
39
  Classifier: Programming Language :: Python :: 3.11
40
+ Classifier: Programming Language :: Python :: 3.12
40
41
  Requires-Python: >=3.10
41
42
  Description-Content-Type: text/markdown
42
43
  License-File: LICENSE
43
- Requires-Dist: networkx >=2.0
44
+ Requires-Dist: networkx>=2.0
44
45
  Requires-Dist: numpy
45
46
  Requires-Dist: packaging
46
47
  Requires-Dist: pint
47
- Requires-Dist: bluesky >=1.13
48
- Requires-Dist: event-model >=1.22.1
49
- Requires-Dist: p4p >=4.2.0a3
48
+ Requires-Dist: bluesky>=1.13
49
+ Requires-Dist: event-model>=1.22.1
50
+ Requires-Dist: p4p>=4.2.0a3
50
51
  Requires-Dist: pyyaml
51
52
  Requires-Dist: colorlog
52
- Requires-Dist: pydantic >=2.0
53
+ Requires-Dist: pydantic>=2.0
53
54
  Requires-Dist: pydantic-numpy
54
55
  Provides-Extra: ca
55
- Requires-Dist: aioca >=1.6 ; extra == 'ca'
56
- Provides-Extra: dev
57
- Requires-Dist: ophyd-async[pva] ; extra == 'dev'
58
- Requires-Dist: ophyd-async[sim] ; extra == 'dev'
59
- Requires-Dist: ophyd-async[ca] ; extra == 'dev'
60
- Requires-Dist: ophyd-async[tango] ; extra == 'dev'
61
- Requires-Dist: inflection ; extra == 'dev'
62
- Requires-Dist: ipython ; extra == 'dev'
63
- Requires-Dist: ipywidgets ; extra == 'dev'
64
- Requires-Dist: matplotlib ; extra == 'dev'
65
- Requires-Dist: myst-parser ; extra == 'dev'
66
- Requires-Dist: numpydoc ; extra == 'dev'
67
- Requires-Dist: ophyd ; extra == 'dev'
68
- Requires-Dist: pickleshare ; extra == 'dev'
69
- Requires-Dist: pipdeptree ; extra == 'dev'
70
- Requires-Dist: pre-commit ; extra == 'dev'
71
- Requires-Dist: pydata-sphinx-theme >=0.12 ; extra == 'dev'
72
- Requires-Dist: pyepics >=3.4.2 ; extra == 'dev'
73
- Requires-Dist: pyright ; extra == 'dev'
74
- Requires-Dist: pyside6 ==6.7.0 ; extra == 'dev'
75
- Requires-Dist: pytest ; extra == 'dev'
76
- Requires-Dist: pytest-asyncio ; extra == 'dev'
77
- Requires-Dist: pytest-cov ; extra == 'dev'
78
- Requires-Dist: pytest-faulthandler ; extra == 'dev'
79
- Requires-Dist: pytest-forked ; extra == 'dev'
80
- Requires-Dist: pytest-rerunfailures ; extra == 'dev'
81
- Requires-Dist: pytest-timeout ; extra == 'dev'
82
- Requires-Dist: ruff ; extra == 'dev'
83
- Requires-Dist: sphinx <7.4.0 ; extra == 'dev'
84
- Requires-Dist: sphinx-autobuild ; extra == 'dev'
85
- Requires-Dist: autodoc-pydantic ; extra == 'dev'
86
- Requires-Dist: sphinxcontrib-mermaid ; extra == 'dev'
87
- Requires-Dist: sphinx-copybutton ; extra == 'dev'
88
- Requires-Dist: sphinx-design ; extra == 'dev'
89
- Requires-Dist: super-state-machine ; extra == 'dev'
90
- Requires-Dist: tox-direct ; extra == 'dev'
91
- Requires-Dist: types-mock ; extra == 'dev'
92
- Requires-Dist: types-pyyaml ; extra == 'dev'
56
+ Requires-Dist: aioca>=1.6; extra == "ca"
93
57
  Provides-Extra: pva
94
- Requires-Dist: p4p ; extra == 'pva'
58
+ Requires-Dist: p4p; extra == "pva"
95
59
  Provides-Extra: sim
96
- Requires-Dist: h5py ; extra == 'sim'
60
+ Requires-Dist: h5py; extra == "sim"
97
61
  Provides-Extra: tango
98
- Requires-Dist: pytango >=10.0.0 ; extra == 'tango'
62
+ Requires-Dist: pytango>=10.0.0; extra == "tango"
63
+ Provides-Extra: dev
64
+ Requires-Dist: ophyd_async[pva]; extra == "dev"
65
+ Requires-Dist: ophyd_async[sim]; extra == "dev"
66
+ Requires-Dist: ophyd_async[ca]; extra == "dev"
67
+ Requires-Dist: ophyd_async[tango]; extra == "dev"
68
+ Requires-Dist: inflection; extra == "dev"
69
+ Requires-Dist: ipython; extra == "dev"
70
+ Requires-Dist: ipywidgets; extra == "dev"
71
+ Requires-Dist: import-linter; extra == "dev"
72
+ Requires-Dist: matplotlib; extra == "dev"
73
+ Requires-Dist: myst-parser; extra == "dev"
74
+ Requires-Dist: numpydoc; extra == "dev"
75
+ Requires-Dist: ophyd; extra == "dev"
76
+ Requires-Dist: pickleshare; extra == "dev"
77
+ Requires-Dist: pipdeptree; extra == "dev"
78
+ Requires-Dist: pre-commit; extra == "dev"
79
+ Requires-Dist: pydata-sphinx-theme>=0.12; extra == "dev"
80
+ Requires-Dist: pyepics>=3.4.2; extra == "dev"
81
+ Requires-Dist: pyright; extra == "dev"
82
+ Requires-Dist: pyside6==6.7.0; extra == "dev"
83
+ Requires-Dist: pytest; extra == "dev"
84
+ Requires-Dist: pytest-asyncio; extra == "dev"
85
+ Requires-Dist: pytest-cov; extra == "dev"
86
+ Requires-Dist: pytest-faulthandler; extra == "dev"
87
+ Requires-Dist: pytest-forked; extra == "dev"
88
+ Requires-Dist: pytest-rerunfailures; extra == "dev"
89
+ Requires-Dist: pytest-timeout; extra == "dev"
90
+ Requires-Dist: ruff; extra == "dev"
91
+ Requires-Dist: sphinx<7.4.0; extra == "dev"
92
+ Requires-Dist: sphinx-autobuild; extra == "dev"
93
+ Requires-Dist: autodoc-pydantic; extra == "dev"
94
+ Requires-Dist: sphinxcontrib-mermaid; extra == "dev"
95
+ Requires-Dist: sphinx-copybutton; extra == "dev"
96
+ Requires-Dist: sphinx-design; extra == "dev"
97
+ Requires-Dist: super_state_machine; extra == "dev"
98
+ Requires-Dist: tox-direct; extra == "dev"
99
+ Requires-Dist: types-mock; extra == "dev"
100
+ Requires-Dist: types-pyyaml; extra == "dev"
99
101
 
100
102
  [![CI](https://github.com/bluesky/ophyd-async/actions/workflows/ci.yml/badge.svg)](https://github.com/bluesky/ophyd-async/actions/workflows/ci.yml)
101
103
  [![Coverage](https://codecov.io/gh/bluesky/ophyd-async/branch/main/graph/badge.svg)](https://codecov.io/gh/bluesky/ophyd-async)