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,10 +1,9 @@
1
- from enum import Enum
2
-
1
+ from ophyd_async.core import StrictEnum
3
2
  from ophyd_async.epics import adcore
4
3
  from ophyd_async.epics.signal import epics_signal_rw_rbv
5
4
 
6
5
 
7
- class VimbaPixelFormat(str, Enum):
6
+ class VimbaPixelFormat(StrictEnum):
8
7
  internal = "Mono8"
9
8
  ext_enable = "Mono12"
10
9
  ext_trigger = "Ext. Trigger"
@@ -12,7 +11,7 @@ class VimbaPixelFormat(str, Enum):
12
11
  alignment = "Alignment"
13
12
 
14
13
 
15
- class VimbaConvertFormat(str, Enum):
14
+ class VimbaConvertFormat(StrictEnum):
16
15
  none = "None"
17
16
  mono8 = "Mono8"
18
17
  mono16 = "Mono16"
@@ -20,7 +19,7 @@ class VimbaConvertFormat(str, Enum):
20
19
  rgb16 = "RGB16"
21
20
 
22
21
 
23
- class VimbaTriggerSource(str, Enum):
22
+ class VimbaTriggerSource(StrictEnum):
24
23
  freerun = "Freerun"
25
24
  line1 = "Line1"
26
25
  line2 = "Line2"
@@ -30,17 +29,17 @@ class VimbaTriggerSource(str, Enum):
30
29
  action1 = "Action1"
31
30
 
32
31
 
33
- class VimbaOverlap(str, Enum):
32
+ class VimbaOverlap(StrictEnum):
34
33
  off = "Off"
35
34
  prev_frame = "PreviousFrame"
36
35
 
37
36
 
38
- class VimbaOnOff(str, Enum):
37
+ class VimbaOnOff(StrictEnum):
39
38
  on = "On"
40
39
  off = "Off"
41
40
 
42
41
 
43
- class VimbaExposeOutMode(str, Enum):
42
+ class VimbaExposeOutMode(StrictEnum):
44
43
  timed = "Timed" # Use ExposureTime PV
45
44
  trigger_width = "TriggerWidth" # Expose for length of high signal
46
45
 
@@ -1,10 +1,14 @@
1
- from enum import Enum
2
-
3
- from ophyd_async.core import ConfigSignal, DeviceVector, HintedSignal, StandardReadable
1
+ from ophyd_async.core import (
2
+ ConfigSignal,
3
+ DeviceVector,
4
+ HintedSignal,
5
+ StandardReadable,
6
+ StrictEnum,
7
+ )
4
8
  from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw
5
9
 
6
10
 
7
- class EnergyMode(str, Enum):
11
+ class EnergyMode(StrictEnum):
8
12
  """Energy mode for `Sensor`"""
9
13
 
10
14
  #: Low energy mode
@@ -1,7 +1,6 @@
1
1
  from pydantic import Field
2
2
 
3
- from ophyd_async.core import AsyncStatus, PathProvider, StandardDetector
4
- from ophyd_async.core._detector import TriggerInfo
3
+ from ophyd_async.core import AsyncStatus, PathProvider, StandardDetector, TriggerInfo
5
4
 
6
5
  from ._eiger_controller import EigerController
7
6
  from ._eiger_io import EigerDriverIO
@@ -4,9 +4,9 @@ from ophyd_async.core import (
4
4
  DEFAULT_TIMEOUT,
5
5
  DetectorController,
6
6
  DetectorTrigger,
7
+ TriggerInfo,
7
8
  set_and_wait_for_other_value,
8
9
  )
9
- from ophyd_async.core._detector import TriggerInfo
10
10
 
11
11
  from ._eiger_io import EigerDriverIO, EigerTriggerMode
12
12
 
@@ -1,10 +1,8 @@
1
- from enum import Enum
2
-
3
- from ophyd_async.core import Device
1
+ from ophyd_async.core import Device, StrictEnum
4
2
  from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw_rbv, epics_signal_w
5
3
 
6
4
 
7
- class EigerTriggerMode(str, Enum):
5
+ class EigerTriggerMode(StrictEnum):
8
6
  internal = "ints"
9
7
  edge = "exts"
10
8
  gate = "exte"
@@ -1,6 +1,5 @@
1
1
  import asyncio
2
2
  from collections.abc import AsyncGenerator, AsyncIterator
3
- from enum import Enum
4
3
 
5
4
  from bluesky.protocols import StreamAsset
6
5
  from event_model import DataKey
@@ -12,6 +11,7 @@ from ophyd_async.core import (
12
11
  DeviceVector,
13
12
  NameProvider,
14
13
  PathProvider,
14
+ StrictEnum,
15
15
  observe_value,
16
16
  set_and_wait_for_value,
17
17
  )
@@ -22,7 +22,7 @@ from ophyd_async.epics.signal import (
22
22
  )
23
23
 
24
24
 
25
- class Writing(str, Enum):
25
+ class Writing(StrictEnum):
26
26
  ON = "ON"
27
27
  OFF = "OFF"
28
28
 
@@ -101,10 +101,10 @@ class OdinWriter(DetectorWriter):
101
101
  return {
102
102
  "data": DataKey(
103
103
  source=self._drv.file_name.source,
104
- shape=data_shape,
104
+ shape=list(data_shape),
105
105
  dtype="array",
106
106
  # TODO: Use correct type based on eiger https://github.com/bluesky/ophyd-async/issues/529
107
- dtype_numpy="<u2", # type: ignore
107
+ dtype_numpy="<u2",
108
108
  external="STREAM:",
109
109
  )
110
110
  }
@@ -1,3 +1,3 @@
1
- from ._pvi import create_children_from_annotations, fill_pvi_entries
1
+ from ._pvi import PviDeviceConnector
2
2
 
3
- __all__ = ["fill_pvi_entries", "create_children_from_annotations"]
3
+ __all__ = ["PviDeviceConnector"]
@@ -1,338 +1,73 @@
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
- )
1
+ from __future__ import annotations
2
+
3
+ from unittest.mock import Mock
14
4
 
15
5
  from ophyd_async.core import (
16
- DEFAULT_TIMEOUT,
17
6
  Device,
18
- DeviceVector,
7
+ DeviceConnector,
8
+ DeviceFiller,
19
9
  Signal,
20
- SoftSignalBackend,
21
- T,
10
+ SignalR,
11
+ SignalRW,
12
+ SignalX,
22
13
  )
23
14
  from ophyd_async.epics.signal import (
24
15
  PvaSignalBackend,
25
- epics_signal_r,
26
- epics_signal_rw,
27
- epics_signal_w,
28
- epics_signal_x,
16
+ pvget_with_timeout,
29
17
  )
30
18
 
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
19
 
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
- """
20
+ def _get_signal_details(entry: dict[str, str]) -> tuple[type[Signal], str, str]:
21
+ match entry:
22
+ case {"r": read_pv}:
23
+ return SignalR, read_pv, read_pv
24
+ case {"r": read_pv, "w": write_pv}:
25
+ return SignalRW, read_pv, write_pv
26
+ case {"rw": read_write_pv}:
27
+ return SignalRW, read_write_pv, read_write_pv
28
+ case {"x": execute_pv}:
29
+ return SignalX, execute_pv, execute_pv
30
+ case _:
31
+ raise TypeError(f"Can't process entry {entry}")
82
32
 
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
33
 
34
+ class PviDeviceConnector(DeviceConnector):
35
+ def __init__(self, pvi_pv: str = "") -> None:
36
+ self.pvi_pv = pvi_pv
88
37
 
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,
38
+ def create_children_from_annotations(self, device: Device):
39
+ self._filler = DeviceFiller(
294
40
  device=device,
295
- common_device_type=type(device),
296
- sub_entries={},
41
+ signal_backend_factory=PvaSignalBackend,
42
+ device_connector_factory=PviDeviceConnector,
297
43
  )
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
44
 
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
- )
45
+ async def connect(
46
+ self, device: Device, mock: bool | Mock, timeout: float, force_reconnect: bool
47
+ ) -> None:
48
+ if mock:
49
+ # Make 2 entries for each DeviceVector
50
+ self._filler.make_soft_device_vector_entries(2)
335
51
  else:
336
- sub_device = device_type()
337
- setattr(device, name, sub_device)
338
- create_children_from_annotations(sub_device, device_vectors=device_vectors)
52
+ pvi_structure = await pvget_with_timeout(self.pvi_pv, timeout)
53
+ entries: dict[str, dict[str, str]] = pvi_structure["value"].todict()
54
+ # Ensure we have device vectors for everything that should be there
55
+ self._filler.make_device_vectors(list(entries))
56
+ for name, entry in entries.items():
57
+ if set(entry) == {"d"}:
58
+ connector = self._filler.make_child_device(name)
59
+ connector.pvi_pv = entry["d"]
60
+ else:
61
+ signal_type, read_pv, write_pv = _get_signal_details(entry)
62
+ backend = self._filler.make_child_signal(name, signal_type)
63
+ backend.read_pv = read_pv
64
+ backend.write_pv = write_pv
65
+ # Check that all the requested children have been created
66
+ if unfilled := self._filler.unfilled():
67
+ raise RuntimeError(
68
+ f"{device.name}: cannot provision {unfilled} from "
69
+ f"{self.pvi_pv}: {entries}"
70
+ )
71
+ # Set the name of the device to name all children
72
+ device.set_name(device.name)
73
+ return await super().connect(device, mock, timeout, force_reconnect)
@@ -1,5 +1,5 @@
1
- from ._common import LimitPair, Limits, get_supported_values
2
- from ._p4p import PvaSignalBackend
1
+ from ._common import get_supported_values
2
+ from ._p4p import PvaSignalBackend, pvget_with_timeout
3
3
  from ._signal import (
4
4
  epics_signal_r,
5
5
  epics_signal_rw,
@@ -10,9 +10,8 @@ from ._signal import (
10
10
 
11
11
  __all__ = [
12
12
  "get_supported_values",
13
- "LimitPair",
14
- "Limits",
15
13
  "PvaSignalBackend",
14
+ "pvget_with_timeout",
16
15
  "epics_signal_r",
17
16
  "epics_signal_rw",
18
17
  "epics_signal_rw_rbv",