ophyd-async 0.7.0__py3-none-any.whl → 0.8.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 (92) hide show
  1. ophyd_async/_version.py +2 -2
  2. ophyd_async/core/__init__.py +34 -9
  3. ophyd_async/core/_detector.py +5 -10
  4. ophyd_async/core/_device.py +170 -68
  5. ophyd_async/core/_device_filler.py +269 -0
  6. ophyd_async/core/_device_save_loader.py +6 -7
  7. ophyd_async/core/_mock_signal_backend.py +35 -40
  8. ophyd_async/core/_mock_signal_utils.py +25 -16
  9. ophyd_async/core/_protocol.py +28 -8
  10. ophyd_async/core/_readable.py +133 -134
  11. ophyd_async/core/_signal.py +219 -163
  12. ophyd_async/core/_signal_backend.py +131 -64
  13. ophyd_async/core/_soft_signal_backend.py +131 -194
  14. ophyd_async/core/_status.py +22 -6
  15. ophyd_async/core/_table.py +102 -100
  16. ophyd_async/core/_utils.py +143 -32
  17. ophyd_async/epics/adaravis/_aravis_controller.py +2 -2
  18. ophyd_async/epics/adaravis/_aravis_io.py +8 -6
  19. ophyd_async/epics/adcore/_core_io.py +5 -7
  20. ophyd_async/epics/adcore/_core_logic.py +3 -1
  21. ophyd_async/epics/adcore/_hdf_writer.py +2 -2
  22. ophyd_async/epics/adcore/_single_trigger.py +6 -10
  23. ophyd_async/epics/adcore/_utils.py +15 -10
  24. ophyd_async/epics/adkinetix/__init__.py +2 -1
  25. ophyd_async/epics/adkinetix/_kinetix_controller.py +6 -3
  26. ophyd_async/epics/adkinetix/_kinetix_io.py +4 -5
  27. ophyd_async/epics/adpilatus/_pilatus_controller.py +2 -2
  28. ophyd_async/epics/adpilatus/_pilatus_io.py +3 -4
  29. ophyd_async/epics/adsimdetector/_sim_controller.py +2 -2
  30. ophyd_async/epics/advimba/__init__.py +4 -1
  31. ophyd_async/epics/advimba/_vimba_controller.py +6 -3
  32. ophyd_async/epics/advimba/_vimba_io.py +8 -9
  33. ophyd_async/epics/core/__init__.py +26 -0
  34. ophyd_async/epics/core/_aioca.py +323 -0
  35. ophyd_async/epics/core/_epics_connector.py +53 -0
  36. ophyd_async/epics/core/_epics_device.py +13 -0
  37. ophyd_async/epics/core/_p4p.py +383 -0
  38. ophyd_async/epics/core/_pvi_connector.py +91 -0
  39. ophyd_async/epics/core/_signal.py +171 -0
  40. ophyd_async/epics/core/_util.py +61 -0
  41. ophyd_async/epics/demo/_mover.py +4 -5
  42. ophyd_async/epics/demo/_sensor.py +14 -13
  43. ophyd_async/epics/eiger/_eiger.py +1 -2
  44. ophyd_async/epics/eiger/_eiger_controller.py +7 -2
  45. ophyd_async/epics/eiger/_eiger_io.py +3 -5
  46. ophyd_async/epics/eiger/_odin_io.py +5 -5
  47. ophyd_async/epics/motor.py +4 -5
  48. ophyd_async/epics/signal.py +11 -0
  49. ophyd_async/epics/testing/__init__.py +24 -0
  50. ophyd_async/epics/testing/_example_ioc.py +105 -0
  51. ophyd_async/epics/testing/_utils.py +78 -0
  52. ophyd_async/epics/testing/test_records.db +152 -0
  53. ophyd_async/epics/testing/test_records_pva.db +177 -0
  54. ophyd_async/fastcs/core.py +9 -0
  55. ophyd_async/fastcs/panda/__init__.py +4 -4
  56. ophyd_async/fastcs/panda/_block.py +18 -13
  57. ophyd_async/fastcs/panda/_control.py +3 -5
  58. ophyd_async/fastcs/panda/_hdf_panda.py +5 -19
  59. ophyd_async/fastcs/panda/_table.py +30 -52
  60. ophyd_async/fastcs/panda/_trigger.py +8 -8
  61. ophyd_async/fastcs/panda/_writer.py +2 -5
  62. ophyd_async/plan_stubs/_ensure_connected.py +20 -13
  63. ophyd_async/plan_stubs/_fly.py +2 -2
  64. ophyd_async/plan_stubs/_nd_attributes.py +5 -4
  65. ophyd_async/py.typed +0 -0
  66. ophyd_async/sim/demo/_pattern_detector/_pattern_detector_controller.py +1 -2
  67. ophyd_async/sim/demo/_sim_motor.py +3 -4
  68. ophyd_async/tango/__init__.py +0 -45
  69. ophyd_async/tango/{signal → core}/__init__.py +9 -6
  70. ophyd_async/tango/core/_base_device.py +132 -0
  71. ophyd_async/tango/{signal → core}/_signal.py +42 -53
  72. ophyd_async/tango/{base_devices → core}/_tango_readable.py +3 -4
  73. ophyd_async/tango/{signal → core}/_tango_transport.py +38 -40
  74. ophyd_async/tango/demo/_counter.py +12 -23
  75. ophyd_async/tango/demo/_mover.py +13 -13
  76. {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0.dist-info}/METADATA +52 -55
  77. ophyd_async-0.8.0.dist-info/RECORD +116 -0
  78. {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0.dist-info}/WHEEL +1 -1
  79. ophyd_async/epics/pvi/__init__.py +0 -3
  80. ophyd_async/epics/pvi/_pvi.py +0 -338
  81. ophyd_async/epics/signal/__init__.py +0 -21
  82. ophyd_async/epics/signal/_aioca.py +0 -378
  83. ophyd_async/epics/signal/_common.py +0 -57
  84. ophyd_async/epics/signal/_epics_transport.py +0 -34
  85. ophyd_async/epics/signal/_p4p.py +0 -518
  86. ophyd_async/epics/signal/_signal.py +0 -114
  87. ophyd_async/tango/base_devices/__init__.py +0 -4
  88. ophyd_async/tango/base_devices/_base_device.py +0 -225
  89. ophyd_async-0.7.0.dist-info/RECORD +0 -108
  90. {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0.dist-info}/LICENSE +0 -0
  91. {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0.dist-info}/entry_points.txt +0 -0
  92. {ophyd_async-0.7.0.dist-info → ophyd_async-0.8.0.dist-info}/top_level.txt +0 -0
@@ -1,338 +0,0 @@
1
- import re
2
- import types
3
- from collections.abc import Callable
4
- from dataclasses import dataclass
5
- from inspect import isclass
6
- from typing import (
7
- Any,
8
- Literal,
9
- Union,
10
- get_args,
11
- get_origin,
12
- get_type_hints,
13
- )
14
-
15
- from ophyd_async.core import (
16
- DEFAULT_TIMEOUT,
17
- Device,
18
- DeviceVector,
19
- Signal,
20
- SoftSignalBackend,
21
- T,
22
- )
23
- from ophyd_async.epics.signal import (
24
- PvaSignalBackend,
25
- epics_signal_r,
26
- epics_signal_rw,
27
- epics_signal_w,
28
- epics_signal_x,
29
- )
30
-
31
- Access = frozenset[
32
- Literal["r"] | Literal["w"] | Literal["rw"] | Literal["x"] | Literal["d"]
33
- ]
34
-
35
-
36
- def _strip_number_from_string(string: str) -> tuple[str, int | None]:
37
- match = re.match(r"(.*?)(\d*)$", string)
38
- assert match
39
-
40
- name = match.group(1)
41
- number = match.group(2) or None
42
- if number is None:
43
- return name, None
44
- else:
45
- return name, int(number)
46
-
47
-
48
- def _split_subscript(tp: T) -> tuple[Any, tuple[Any]] | tuple[T, None]:
49
- """Split a subscripted type into the its origin and args.
50
-
51
- If `tp` is not a subscripted type, then just return the type and None as args.
52
-
53
- """
54
- if get_origin(tp) is not None:
55
- return get_origin(tp), get_args(tp)
56
-
57
- return tp, None
58
-
59
-
60
- def _strip_union(field: T | T) -> tuple[T, bool]:
61
- if get_origin(field) in [Union, types.UnionType]:
62
- args = get_args(field)
63
- is_optional = type(None) in args
64
- for arg in args:
65
- if arg is not type(None):
66
- return arg, is_optional
67
- return field, False
68
-
69
-
70
- def _strip_device_vector(field: type[Device]) -> tuple[bool, type[Device]]:
71
- if get_origin(field) is DeviceVector:
72
- return True, get_args(field)[0]
73
- return False, field
74
-
75
-
76
- @dataclass
77
- class _PVIEntry:
78
- """
79
- A dataclass to represent a single entry in the PVI table.
80
- This could either be a signal or a sub-table.
81
- """
82
-
83
- sub_entries: dict[str, Union[dict[int, "_PVIEntry"], "_PVIEntry"]]
84
- pvi_pv: str | None = None
85
- device: Device | None = None
86
- common_device_type: type[Device] | None = None
87
-
88
-
89
- def _verify_common_blocks(entry: _PVIEntry, common_device: type[Device]):
90
- if not entry.sub_entries:
91
- return
92
- common_sub_devices = get_type_hints(common_device)
93
- for sub_name, sub_device in common_sub_devices.items():
94
- if sub_name.startswith("_") or sub_name == "parent":
95
- continue
96
- assert entry.sub_entries
97
- device_t, is_optional = _strip_union(sub_device)
98
- if sub_name not in entry.sub_entries and not is_optional:
99
- raise RuntimeError(
100
- f"sub device `{sub_name}:{type(sub_device)}` " "was not provided by pvi"
101
- )
102
- if isinstance(entry.sub_entries[sub_name], dict):
103
- for sub_sub_entry in entry.sub_entries[sub_name].values(): # type: ignore
104
- _verify_common_blocks(sub_sub_entry, sub_device) # type: ignore
105
- else:
106
- _verify_common_blocks(
107
- entry.sub_entries[sub_name], # type: ignore
108
- sub_device, # type: ignore
109
- )
110
-
111
-
112
- _pvi_mapping: dict[frozenset[str], Callable[..., Signal]] = {
113
- frozenset({"r", "w"}): lambda dtype, read_pv, write_pv: epics_signal_rw(
114
- dtype, "pva://" + read_pv, "pva://" + write_pv
115
- ),
116
- frozenset({"rw"}): lambda dtype, read_write_pv: epics_signal_rw(
117
- dtype, "pva://" + read_write_pv, write_pv="pva://" + read_write_pv
118
- ),
119
- frozenset({"r"}): lambda dtype, read_pv: epics_signal_r(dtype, "pva://" + read_pv),
120
- frozenset({"w"}): lambda dtype, write_pv: epics_signal_w(
121
- dtype, "pva://" + write_pv
122
- ),
123
- frozenset({"x"}): lambda _, write_pv: epics_signal_x("pva://" + write_pv),
124
- }
125
-
126
-
127
- def _parse_type(
128
- is_pvi_table: bool,
129
- number_suffix: int | None,
130
- common_device_type: type[Device] | None,
131
- ):
132
- if common_device_type:
133
- # pre-defined type
134
- device_cls, _ = _strip_union(common_device_type)
135
- is_device_vector, device_cls = _strip_device_vector(device_cls)
136
- device_cls, device_args = _split_subscript(device_cls)
137
- assert issubclass(device_cls, Device)
138
-
139
- is_signal = issubclass(device_cls, Signal)
140
- signal_dtype = device_args[0] if device_args is not None else None
141
-
142
- elif is_pvi_table:
143
- # is a block, we can make it a DeviceVector if it ends in a number
144
- is_device_vector = number_suffix is not None
145
- is_signal = False
146
- signal_dtype = None
147
- device_cls = Device
148
- else:
149
- # is a signal, signals aren't stored in DeviceVectors unless
150
- # they're defined as such in the common_device_type
151
- is_device_vector = False
152
- is_signal = True
153
- signal_dtype = None
154
- device_cls = Signal
155
-
156
- return is_device_vector, is_signal, signal_dtype, device_cls
157
-
158
-
159
- def _mock_common_blocks(device: Device, stripped_type: type | None = None):
160
- device_t = stripped_type or type(device)
161
- sub_devices = (
162
- (field, field_type)
163
- for field, field_type in get_type_hints(device_t).items()
164
- if not field.startswith("_") and field != "parent"
165
- )
166
-
167
- for device_name, device_cls in sub_devices:
168
- device_cls, _ = _strip_union(device_cls)
169
- is_device_vector, device_cls = _strip_device_vector(device_cls)
170
- device_cls, device_args = _split_subscript(device_cls)
171
- assert issubclass(device_cls, Device)
172
-
173
- signal_dtype = device_args[0] if device_args is not None else None
174
-
175
- if is_device_vector:
176
- if issubclass(device_cls, Signal):
177
- sub_device_1 = device_cls(SoftSignalBackend(signal_dtype))
178
- sub_device_2 = device_cls(SoftSignalBackend(signal_dtype))
179
- sub_device = DeviceVector({1: sub_device_1, 2: sub_device_2})
180
- else:
181
- if hasattr(device, device_name):
182
- sub_device = getattr(device, device_name)
183
- else:
184
- sub_device = DeviceVector(
185
- {
186
- 1: device_cls(),
187
- 2: device_cls(),
188
- }
189
- )
190
-
191
- for sub_device_in_vector in sub_device.values():
192
- _mock_common_blocks(sub_device_in_vector, stripped_type=device_cls)
193
-
194
- for value in sub_device.values():
195
- value.parent = sub_device
196
- else:
197
- if issubclass(device_cls, Signal):
198
- sub_device = device_cls(SoftSignalBackend(signal_dtype))
199
- else:
200
- sub_device = getattr(device, device_name, device_cls())
201
- _mock_common_blocks(sub_device, stripped_type=device_cls)
202
-
203
- setattr(device, device_name, sub_device)
204
- sub_device.parent = device
205
-
206
-
207
- async def _get_pvi_entries(entry: _PVIEntry, timeout=DEFAULT_TIMEOUT):
208
- if not entry.pvi_pv or not entry.pvi_pv.endswith(":PVI"):
209
- raise RuntimeError("Top level entry must be a pvi table")
210
-
211
- pvi_table_signal_backend: PvaSignalBackend = PvaSignalBackend(
212
- None, entry.pvi_pv, entry.pvi_pv
213
- )
214
- await pvi_table_signal_backend.connect(
215
- timeout=timeout
216
- ) # create table signal backend
217
-
218
- pva_table = (await pvi_table_signal_backend.get_value())["pvi"]
219
- common_device_type_hints = (
220
- get_type_hints(entry.common_device_type) if entry.common_device_type else {}
221
- )
222
-
223
- for sub_name, pva_entries in pva_table.items():
224
- pvs = list(pva_entries.values())
225
- is_pvi_table = len(pvs) == 1 and pvs[0].endswith(":PVI")
226
- sub_name_split, sub_number_split = _strip_number_from_string(sub_name)
227
- is_device_vector, is_signal, signal_dtype, device_type = _parse_type(
228
- is_pvi_table,
229
- sub_number_split,
230
- common_device_type_hints.get(sub_name_split),
231
- )
232
- if is_signal:
233
- device = _pvi_mapping[frozenset(pva_entries.keys())](signal_dtype, *pvs)
234
- else:
235
- device = getattr(entry.device, sub_name, device_type())
236
-
237
- sub_entry = _PVIEntry(
238
- device=device, common_device_type=device_type, sub_entries={}
239
- )
240
-
241
- if is_device_vector:
242
- # If device vector then we store sub_name -> {sub_number -> sub_entry}
243
- # and aggregate into `DeviceVector` in `_set_device_attributes`
244
- sub_number_split = 1 if sub_number_split is None else sub_number_split
245
- if sub_name_split not in entry.sub_entries:
246
- entry.sub_entries[sub_name_split] = {}
247
- entry.sub_entries[sub_name_split][sub_number_split] = sub_entry # type: ignore
248
- else:
249
- entry.sub_entries[sub_name] = sub_entry
250
-
251
- if is_pvi_table:
252
- sub_entry.pvi_pv = pvs[0]
253
- await _get_pvi_entries(sub_entry)
254
-
255
- if entry.common_device_type:
256
- _verify_common_blocks(entry, entry.common_device_type)
257
-
258
-
259
- def _set_device_attributes(entry: _PVIEntry):
260
- for sub_name, sub_entry in entry.sub_entries.items():
261
- if isinstance(sub_entry, dict):
262
- sub_device = DeviceVector() # type: ignore
263
- for key, device_vector_sub_entry in sub_entry.items():
264
- sub_device[key] = device_vector_sub_entry.device
265
- if device_vector_sub_entry.pvi_pv:
266
- _set_device_attributes(device_vector_sub_entry)
267
- # Set the device vector entry to have the device vector as a parent
268
- device_vector_sub_entry.device.parent = sub_device # type: ignore
269
- else:
270
- sub_device = sub_entry.device
271
- assert sub_device, f"Device of {sub_entry} is None"
272
- if sub_entry.pvi_pv:
273
- _set_device_attributes(sub_entry)
274
-
275
- sub_device.parent = entry.device
276
- setattr(entry.device, sub_name, sub_device)
277
-
278
-
279
- async def fill_pvi_entries(
280
- device: Device, root_pv: str, timeout=DEFAULT_TIMEOUT, mock=False
281
- ):
282
- """
283
- Fills a ``device`` with signals from a the ``root_pvi:PVI`` table.
284
-
285
- If the device names match with parent devices of ``device`` then types are used.
286
- """
287
- if mock:
288
- # set up mock signals for the common annotations
289
- _mock_common_blocks(device)
290
- else:
291
- # check the pvi table for devices and fill the device with them
292
- root_entry = _PVIEntry(
293
- pvi_pv=root_pv,
294
- device=device,
295
- common_device_type=type(device),
296
- sub_entries={},
297
- )
298
- await _get_pvi_entries(root_entry, timeout=timeout)
299
- _set_device_attributes(root_entry)
300
-
301
- # We call set name now the parent field has been set in all of the
302
- # introspect-initialized devices. This will recursively set the names.
303
- device.set_name(device.name)
304
-
305
-
306
- def create_children_from_annotations(
307
- device: Device,
308
- included_optional_fields: tuple[str, ...] = (),
309
- device_vectors: dict[str, int] | None = None,
310
- ):
311
- """For intializing blocks at __init__ of ``device``."""
312
- for name, device_type in get_type_hints(type(device)).items():
313
- if name in ("_name", "parent"):
314
- continue
315
- device_type, is_optional = _strip_union(device_type)
316
- if is_optional and name not in included_optional_fields:
317
- continue
318
- is_device_vector, device_type = _strip_device_vector(device_type)
319
- if (
320
- (is_device_vector and (not device_vectors or name not in device_vectors))
321
- or ((origin := get_origin(device_type)) and issubclass(origin, Signal))
322
- or (isclass(device_type) and issubclass(device_type, Signal))
323
- ):
324
- continue
325
-
326
- if is_device_vector:
327
- n_device_vector = DeviceVector(
328
- {i: device_type() for i in range(1, device_vectors[name] + 1)} # type: ignore
329
- )
330
- setattr(device, name, n_device_vector)
331
- for sub_device in n_device_vector.values():
332
- create_children_from_annotations(
333
- sub_device, device_vectors=device_vectors
334
- )
335
- else:
336
- sub_device = device_type()
337
- setattr(device, name, sub_device)
338
- create_children_from_annotations(sub_device, device_vectors=device_vectors)
@@ -1,21 +0,0 @@
1
- from ._common import LimitPair, Limits, get_supported_values
2
- from ._p4p import PvaSignalBackend
3
- from ._signal import (
4
- epics_signal_r,
5
- epics_signal_rw,
6
- epics_signal_rw_rbv,
7
- epics_signal_w,
8
- epics_signal_x,
9
- )
10
-
11
- __all__ = [
12
- "get_supported_values",
13
- "LimitPair",
14
- "Limits",
15
- "PvaSignalBackend",
16
- "epics_signal_r",
17
- "epics_signal_rw",
18
- "epics_signal_rw_rbv",
19
- "epics_signal_w",
20
- "epics_signal_x",
21
- ]