ophyd-async 0.8.0a3__py3-none-any.whl → 0.8.0a5__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 +2 -0
- ophyd_async/core/_device.py +77 -49
- ophyd_async/core/_mock_signal_backend.py +10 -7
- ophyd_async/core/_mock_signal_utils.py +14 -11
- ophyd_async/core/_signal.py +26 -31
- ophyd_async/core/_soft_signal_backend.py +8 -2
- ophyd_async/core/_utils.py +74 -24
- ophyd_async/epics/adcore/_single_trigger.py +2 -1
- ophyd_async/epics/core/_aioca.py +1 -1
- ophyd_async/epics/core/_p4p.py +1 -0
- ophyd_async/epics/core/_pvi_connector.py +24 -25
- ophyd_async/fastcs/panda/_table.py +1 -1
- ophyd_async/plan_stubs/_ensure_connected.py +19 -14
- ophyd_async/tango/base_devices/_base_device.py +37 -36
- {ophyd_async-0.8.0a3.dist-info → ophyd_async-0.8.0a5.dist-info}/METADATA +1 -1
- {ophyd_async-0.8.0a3.dist-info → ophyd_async-0.8.0a5.dist-info}/RECORD +21 -21
- {ophyd_async-0.8.0a3.dist-info → ophyd_async-0.8.0a5.dist-info}/WHEEL +1 -1
- {ophyd_async-0.8.0a3.dist-info → ophyd_async-0.8.0a5.dist-info}/LICENSE +0 -0
- {ophyd_async-0.8.0a3.dist-info → ophyd_async-0.8.0a5.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.8.0a3.dist-info → ophyd_async-0.8.0a5.dist-info}/top_level.txt +0 -0
ophyd_async/_version.py
CHANGED
ophyd_async/core/__init__.py
CHANGED
|
@@ -83,6 +83,7 @@ from ._utils import (
|
|
|
83
83
|
DEFAULT_TIMEOUT,
|
|
84
84
|
CalculatableTimeout,
|
|
85
85
|
Callback,
|
|
86
|
+
LazyMock,
|
|
86
87
|
NotConnected,
|
|
87
88
|
Reference,
|
|
88
89
|
StrictEnum,
|
|
@@ -176,6 +177,7 @@ __all__ = [
|
|
|
176
177
|
"DEFAULT_TIMEOUT",
|
|
177
178
|
"CalculatableTimeout",
|
|
178
179
|
"Callback",
|
|
180
|
+
"LazyMock",
|
|
179
181
|
"CALCULATE_TIMEOUT",
|
|
180
182
|
"NotConnected",
|
|
181
183
|
"Reference",
|
ophyd_async/core/_device.py
CHANGED
|
@@ -3,17 +3,15 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import sys
|
|
5
5
|
from collections.abc import Coroutine, Iterator, Mapping, MutableMapping
|
|
6
|
+
from functools import cached_property
|
|
6
7
|
from logging import LoggerAdapter, getLogger
|
|
7
8
|
from typing import Any, TypeVar
|
|
8
|
-
from unittest.mock import Mock
|
|
9
9
|
|
|
10
10
|
from bluesky.protocols import HasName
|
|
11
11
|
from bluesky.run_engine import call_in_bluesky_event_loop, in_bluesky_event_loop
|
|
12
12
|
|
|
13
13
|
from ._protocol import Connectable
|
|
14
|
-
from ._utils import DEFAULT_TIMEOUT, NotConnected, wait_for_connection
|
|
15
|
-
|
|
16
|
-
_device_mocks: dict[Device, Mock] = {}
|
|
14
|
+
from ._utils import DEFAULT_TIMEOUT, LazyMock, NotConnected, wait_for_connection
|
|
17
15
|
|
|
18
16
|
|
|
19
17
|
class DeviceConnector:
|
|
@@ -37,25 +35,29 @@ class DeviceConnector:
|
|
|
37
35
|
during ``__init__``.
|
|
38
36
|
"""
|
|
39
37
|
|
|
40
|
-
async def
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
38
|
+
async def connect_mock(self, device: Device, mock: LazyMock):
|
|
39
|
+
# Connect serially, no errors to gather up as in mock mode
|
|
40
|
+
exceptions: dict[str, Exception] = {}
|
|
41
|
+
for name, child_device in device.children():
|
|
42
|
+
try:
|
|
43
|
+
await child_device.connect(mock=mock.child(name))
|
|
44
|
+
except Exception as e:
|
|
45
|
+
exceptions[name] = e
|
|
46
|
+
if exceptions:
|
|
47
|
+
raise NotConnected.with_other_exceptions_logged(exceptions)
|
|
48
|
+
|
|
49
|
+
async def connect_real(self, device: Device, timeout: float, force_reconnect: bool):
|
|
47
50
|
"""Used during ``Device.connect``.
|
|
48
51
|
|
|
49
52
|
This is called when a previous connect has not been done, or has been
|
|
50
53
|
done in a different mock more. It should connect the Device and all its
|
|
51
54
|
children.
|
|
52
55
|
"""
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
)
|
|
56
|
+
# Connect in parallel, gathering up NotConnected errors
|
|
57
|
+
coros = {
|
|
58
|
+
name: child_device.connect(timeout=timeout, force_reconnect=force_reconnect)
|
|
59
|
+
for name, child_device in device.children()
|
|
60
|
+
}
|
|
59
61
|
await wait_for_connection(**coros)
|
|
60
62
|
|
|
61
63
|
|
|
@@ -67,9 +69,8 @@ class Device(HasName, Connectable):
|
|
|
67
69
|
parent: Device | None = None
|
|
68
70
|
# None if connect hasn't started, a Task if it has
|
|
69
71
|
_connect_task: asyncio.Task | None = None
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
_connect_mock_arg: bool | None = None
|
|
72
|
+
# The mock if we have connected in mock mode
|
|
73
|
+
_mock: LazyMock | None = None
|
|
73
74
|
|
|
74
75
|
def __init__(
|
|
75
76
|
self, name: str = "", connector: DeviceConnector | None = None
|
|
@@ -83,10 +84,18 @@ class Device(HasName, Connectable):
|
|
|
83
84
|
"""Return the name of the Device"""
|
|
84
85
|
return self._name
|
|
85
86
|
|
|
87
|
+
@cached_property
|
|
88
|
+
def _child_devices(self) -> dict[str, Device]:
|
|
89
|
+
return {}
|
|
90
|
+
|
|
86
91
|
def children(self) -> Iterator[tuple[str, Device]]:
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
92
|
+
yield from self._child_devices.items()
|
|
93
|
+
|
|
94
|
+
@cached_property
|
|
95
|
+
def log(self) -> LoggerAdapter:
|
|
96
|
+
return LoggerAdapter(
|
|
97
|
+
getLogger("ophyd_async.devices"), {"ophyd_async_device_name": self.name}
|
|
98
|
+
)
|
|
90
99
|
|
|
91
100
|
def set_name(self, name: str):
|
|
92
101
|
"""Set ``self.name=name`` and each ``self.child.name=name+"-child"``.
|
|
@@ -97,28 +106,33 @@ class Device(HasName, Connectable):
|
|
|
97
106
|
New name to set
|
|
98
107
|
"""
|
|
99
108
|
self._name = name
|
|
100
|
-
# Ensure
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
)
|
|
109
|
+
# Ensure logger is recreated after a name change
|
|
110
|
+
if "log" in self.__dict__:
|
|
111
|
+
del self.log
|
|
104
112
|
for child_name, child in self.children():
|
|
105
113
|
child_name = f"{self.name}-{child_name.strip('_')}" if self.name else ""
|
|
106
114
|
child.set_name(child_name)
|
|
107
115
|
|
|
108
116
|
def __setattr__(self, name: str, value: Any) -> None:
|
|
117
|
+
# Bear in mind that this function is called *a lot*, so
|
|
118
|
+
# we need to make sure nothing expensive happens in it...
|
|
109
119
|
if name == "parent":
|
|
110
120
|
if self.parent not in (value, None):
|
|
111
121
|
raise TypeError(
|
|
112
122
|
f"Cannot set the parent of {self} to be {value}: "
|
|
113
123
|
f"it is already a child of {self.parent}"
|
|
114
124
|
)
|
|
115
|
-
|
|
125
|
+
# ...hence not doing an isinstance check for attributes we
|
|
126
|
+
# know not to be Devices
|
|
127
|
+
elif name not in _not_device_attrs and isinstance(value, Device):
|
|
116
128
|
value.parent = self
|
|
117
|
-
|
|
129
|
+
self._child_devices[name] = value
|
|
130
|
+
# ...and avoiding the super call as we know it resolves to `object`
|
|
131
|
+
return object.__setattr__(self, name, value)
|
|
118
132
|
|
|
119
133
|
async def connect(
|
|
120
134
|
self,
|
|
121
|
-
mock: bool |
|
|
135
|
+
mock: bool | LazyMock = False,
|
|
122
136
|
timeout: float = DEFAULT_TIMEOUT,
|
|
123
137
|
force_reconnect: bool = False,
|
|
124
138
|
) -> None:
|
|
@@ -133,26 +147,39 @@ class Device(HasName, Connectable):
|
|
|
133
147
|
timeout:
|
|
134
148
|
Time to wait before failing with a TimeoutError.
|
|
135
149
|
"""
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
if mock:
|
|
151
|
+
# Always connect in mock mode serially
|
|
152
|
+
if isinstance(mock, LazyMock):
|
|
153
|
+
# Use the provided mock
|
|
154
|
+
self._mock = mock
|
|
155
|
+
elif not self._mock:
|
|
156
|
+
# Make one
|
|
157
|
+
self._mock = LazyMock()
|
|
158
|
+
await self._connector.connect_mock(self, self._mock)
|
|
159
|
+
else:
|
|
160
|
+
# Try to cache the connect in real mode
|
|
161
|
+
can_use_previous_connect = (
|
|
162
|
+
self._mock is None
|
|
163
|
+
and self._connect_task
|
|
164
|
+
and not (self._connect_task.done() and self._connect_task.exception())
|
|
150
165
|
)
|
|
151
|
-
|
|
166
|
+
if force_reconnect or not can_use_previous_connect:
|
|
167
|
+
self._mock = None
|
|
168
|
+
coro = self._connector.connect_real(self, timeout, force_reconnect)
|
|
169
|
+
self._connect_task = asyncio.create_task(coro)
|
|
170
|
+
assert self._connect_task, "Connect task not created, this shouldn't happen"
|
|
171
|
+
# Wait for it to complete
|
|
172
|
+
await self._connect_task
|
|
173
|
+
|
|
152
174
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
175
|
+
_not_device_attrs = {
|
|
176
|
+
"_name",
|
|
177
|
+
"_children",
|
|
178
|
+
"_connector",
|
|
179
|
+
"_timeout",
|
|
180
|
+
"_mock",
|
|
181
|
+
"_connect_task",
|
|
182
|
+
}
|
|
156
183
|
|
|
157
184
|
|
|
158
185
|
DeviceT = TypeVar("DeviceT", bound=Device)
|
|
@@ -172,7 +199,8 @@ class DeviceVector(MutableMapping[int, DeviceT], Device):
|
|
|
172
199
|
children: Mapping[int, DeviceT],
|
|
173
200
|
name: str = "",
|
|
174
201
|
) -> None:
|
|
175
|
-
self._children =
|
|
202
|
+
self._children: dict[int, DeviceT] = {}
|
|
203
|
+
self.update(children)
|
|
176
204
|
super().__init__(name=name)
|
|
177
205
|
|
|
178
206
|
def __setattr__(self, name: str, child: Any) -> None:
|
|
@@ -1,13 +1,13 @@
|
|
|
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
|
|
5
5
|
|
|
6
6
|
from bluesky.protocols import Descriptor, Reading
|
|
7
7
|
|
|
8
8
|
from ._signal_backend import SignalBackend, SignalDatatypeT
|
|
9
9
|
from ._soft_signal_backend import SoftSignalBackend
|
|
10
|
-
from ._utils import Callback
|
|
10
|
+
from ._utils import Callback, LazyMock
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class MockSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
@@ -16,7 +16,7 @@ class MockSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
16
16
|
def __init__(
|
|
17
17
|
self,
|
|
18
18
|
initial_backend: SignalBackend[SignalDatatypeT],
|
|
19
|
-
mock:
|
|
19
|
+
mock: LazyMock,
|
|
20
20
|
) -> None:
|
|
21
21
|
if isinstance(initial_backend, MockSignalBackend):
|
|
22
22
|
raise ValueError("Cannot make a MockSignalBackend for a MockSignalBackend")
|
|
@@ -34,11 +34,14 @@ class MockSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
34
34
|
|
|
35
35
|
# use existing Mock if provided
|
|
36
36
|
self.mock = mock
|
|
37
|
-
self.put_mock = AsyncMock(name="put", spec=Callable)
|
|
38
|
-
self.mock.attach_mock(self.put_mock, "put")
|
|
39
|
-
|
|
40
37
|
super().__init__(datatype=self.initial_backend.datatype)
|
|
41
38
|
|
|
39
|
+
@cached_property
|
|
40
|
+
def put_mock(self) -> AsyncMock:
|
|
41
|
+
put_mock = AsyncMock(name="put", spec=Callable)
|
|
42
|
+
self.mock().attach_mock(put_mock, "put")
|
|
43
|
+
return put_mock
|
|
44
|
+
|
|
42
45
|
def set_value(self, value: SignalDatatypeT):
|
|
43
46
|
self.soft_backend.set_value(value)
|
|
44
47
|
|
|
@@ -46,7 +49,7 @@ class MockSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
|
46
49
|
return f"mock+{self.initial_backend.source(name, read)}"
|
|
47
50
|
|
|
48
51
|
async def connect(self, timeout: float) -> None:
|
|
49
|
-
|
|
52
|
+
raise RuntimeError("It is not possible to connect a MockSignalBackend")
|
|
50
53
|
|
|
51
54
|
@cached_property
|
|
52
55
|
def put_proceeds(self) -> asyncio.Event:
|
|
@@ -2,17 +2,26 @@ from collections.abc import Awaitable, Callable, Iterable
|
|
|
2
2
|
from contextlib import asynccontextmanager, contextmanager
|
|
3
3
|
from unittest.mock import AsyncMock, Mock
|
|
4
4
|
|
|
5
|
-
from ._device import Device
|
|
5
|
+
from ._device import Device
|
|
6
6
|
from ._mock_signal_backend import MockSignalBackend
|
|
7
|
-
from ._signal import Signal,
|
|
7
|
+
from ._signal import Signal, SignalConnector, SignalR
|
|
8
8
|
from ._soft_signal_backend import SignalDatatypeT
|
|
9
|
+
from ._utils import LazyMock
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_mock(device: Device | Signal) -> Mock:
|
|
13
|
+
mock = device._mock # noqa: SLF001
|
|
14
|
+
assert isinstance(mock, LazyMock), f"Device {device} not connected in mock mode"
|
|
15
|
+
return mock()
|
|
9
16
|
|
|
10
17
|
|
|
11
18
|
def _get_mock_signal_backend(signal: Signal) -> MockSignalBackend:
|
|
12
|
-
|
|
13
|
-
|
|
19
|
+
connector = signal._connector # noqa: SLF001
|
|
20
|
+
assert isinstance(connector, SignalConnector), f"Expected Signal, got {signal}"
|
|
21
|
+
assert isinstance(
|
|
22
|
+
connector.backend, MockSignalBackend
|
|
14
23
|
), f"Signal {signal} not connected in mock mode"
|
|
15
|
-
return
|
|
24
|
+
return connector.backend
|
|
16
25
|
|
|
17
26
|
|
|
18
27
|
def set_mock_value(signal: Signal[SignalDatatypeT], value: SignalDatatypeT):
|
|
@@ -45,12 +54,6 @@ def get_mock_put(signal: Signal) -> AsyncMock:
|
|
|
45
54
|
return _get_mock_signal_backend(signal).put_mock
|
|
46
55
|
|
|
47
56
|
|
|
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
|
-
|
|
54
57
|
def reset_mock_put_calls(signal: Signal):
|
|
55
58
|
backend = _get_mock_signal_backend(signal)
|
|
56
59
|
backend.put_mock.reset_mock()
|
ophyd_async/core/_signal.py
CHANGED
|
@@ -4,7 +4,6 @@ import asyncio
|
|
|
4
4
|
import functools
|
|
5
5
|
from collections.abc import AsyncGenerator, Awaitable, Callable, Mapping
|
|
6
6
|
from typing import Any, Generic, cast
|
|
7
|
-
from unittest.mock import Mock
|
|
8
7
|
|
|
9
8
|
from bluesky.protocols import (
|
|
10
9
|
Locatable,
|
|
@@ -30,9 +29,14 @@ from ._signal_backend import (
|
|
|
30
29
|
)
|
|
31
30
|
from ._soft_signal_backend import SoftSignalBackend
|
|
32
31
|
from ._status import AsyncStatus
|
|
33
|
-
from ._utils import
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
from ._utils import (
|
|
33
|
+
CALCULATE_TIMEOUT,
|
|
34
|
+
DEFAULT_TIMEOUT,
|
|
35
|
+
CalculatableTimeout,
|
|
36
|
+
Callback,
|
|
37
|
+
LazyMock,
|
|
38
|
+
T,
|
|
39
|
+
)
|
|
36
40
|
|
|
37
41
|
|
|
38
42
|
async def _wait_for(coro: Awaitable[T], timeout: float | None, source: str) -> T:
|
|
@@ -54,26 +58,28 @@ class SignalConnector(DeviceConnector):
|
|
|
54
58
|
def __init__(self, backend: SignalBackend):
|
|
55
59
|
self.backend = self._init_backend = backend
|
|
56
60
|
|
|
57
|
-
async def
|
|
58
|
-
self,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
force_reconnect: bool,
|
|
63
|
-
):
|
|
64
|
-
if mock:
|
|
65
|
-
self.backend = MockSignalBackend(self._init_backend, mock)
|
|
66
|
-
_mock_signal_backends[device] = self.backend
|
|
67
|
-
else:
|
|
68
|
-
self.backend = self._init_backend
|
|
61
|
+
async def connect_mock(self, device: Device, mock: LazyMock):
|
|
62
|
+
self.backend = MockSignalBackend(self._init_backend, mock)
|
|
63
|
+
|
|
64
|
+
async def connect_real(self, device: Device, timeout: float, force_reconnect: bool):
|
|
65
|
+
self.backend = self._init_backend
|
|
69
66
|
device.log.debug(f"Connecting to {self.backend.source(device.name, read=True)}")
|
|
70
67
|
await self.backend.connect(timeout)
|
|
71
68
|
|
|
72
69
|
|
|
70
|
+
class _ChildrenNotAllowed(dict[str, Device]):
|
|
71
|
+
def __setitem__(self, key: str, value: Device) -> None:
|
|
72
|
+
raise AttributeError(
|
|
73
|
+
f"Cannot add Device or Signal child {key}={value} of Signal, "
|
|
74
|
+
"make a subclass of Device instead"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
73
78
|
class Signal(Device, Generic[SignalDatatypeT]):
|
|
74
79
|
"""A Device with the concept of a value, with R, RW, W and X flavours"""
|
|
75
80
|
|
|
76
81
|
_connector: SignalConnector
|
|
82
|
+
_child_devices = _ChildrenNotAllowed() # type: ignore
|
|
77
83
|
|
|
78
84
|
def __init__(
|
|
79
85
|
self,
|
|
@@ -89,14 +95,6 @@ class Signal(Device, Generic[SignalDatatypeT]):
|
|
|
89
95
|
"""Like ca://PV_PREFIX:SIGNAL, or "" if not set"""
|
|
90
96
|
return self._connector.backend.source(self.name, read=True)
|
|
91
97
|
|
|
92
|
-
def __setattr__(self, name: str, value: Any) -> None:
|
|
93
|
-
if name != "parent" and isinstance(value, Device):
|
|
94
|
-
raise AttributeError(
|
|
95
|
-
f"Cannot add Device or Signal {value} as a child of Signal {self}, "
|
|
96
|
-
"make a subclass of Device instead"
|
|
97
|
-
)
|
|
98
|
-
return super().__setattr__(name, value)
|
|
99
|
-
|
|
100
98
|
|
|
101
99
|
class _SignalCache(Generic[SignalDatatypeT]):
|
|
102
100
|
def __init__(self, backend: SignalBackend[SignalDatatypeT], signal: Signal):
|
|
@@ -451,12 +449,6 @@ async def observe_value(
|
|
|
451
449
|
"""
|
|
452
450
|
|
|
453
451
|
q: asyncio.Queue[SignalDatatypeT | Status] = asyncio.Queue()
|
|
454
|
-
if timeout is None:
|
|
455
|
-
get_value = q.get
|
|
456
|
-
else:
|
|
457
|
-
|
|
458
|
-
async def get_value():
|
|
459
|
-
return await asyncio.wait_for(q.get(), timeout)
|
|
460
452
|
|
|
461
453
|
if done_status is not None:
|
|
462
454
|
done_status.add_callback(q.put_nowait)
|
|
@@ -464,7 +456,10 @@ async def observe_value(
|
|
|
464
456
|
signal.subscribe_value(q.put_nowait)
|
|
465
457
|
try:
|
|
466
458
|
while True:
|
|
467
|
-
|
|
459
|
+
# yield here in case something else is filling the queue
|
|
460
|
+
# like in test_observe_value_times_out_with_no_external_task()
|
|
461
|
+
await asyncio.sleep(0)
|
|
462
|
+
item = await asyncio.wait_for(q.get(), timeout)
|
|
468
463
|
if done_status and item is done_status:
|
|
469
464
|
if exc := done_status.exception():
|
|
470
465
|
raise exc
|
|
@@ -4,7 +4,8 @@ import time
|
|
|
4
4
|
from abc import abstractmethod
|
|
5
5
|
from collections.abc import Sequence
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
-
from
|
|
7
|
+
from functools import lru_cache
|
|
8
|
+
from typing import Any, Generic, get_args, get_origin
|
|
8
9
|
|
|
9
10
|
import numpy as np
|
|
10
11
|
from bluesky.protocols import Reading
|
|
@@ -57,7 +58,7 @@ class SequenceEnumSoftConverter(SoftConverter[Sequence[EnumT]]):
|
|
|
57
58
|
|
|
58
59
|
@dataclass
|
|
59
60
|
class NDArraySoftConverter(SoftConverter[Array1D]):
|
|
60
|
-
datatype: np.dtype
|
|
61
|
+
datatype: np.dtype | None = None
|
|
61
62
|
|
|
62
63
|
def write_value(self, value: Any) -> Array1D:
|
|
63
64
|
return np.array(() if value is None else value, dtype=self.datatype)
|
|
@@ -90,13 +91,18 @@ class TableSoftConverter(SoftConverter[TableT]):
|
|
|
90
91
|
raise TypeError(f"Cannot convert {value} to {self.datatype}")
|
|
91
92
|
|
|
92
93
|
|
|
94
|
+
@lru_cache
|
|
93
95
|
def make_converter(datatype: type[SignalDatatype]) -> SoftConverter:
|
|
94
96
|
enum_cls = get_enum_cls(datatype)
|
|
95
97
|
if datatype == Sequence[str]:
|
|
96
98
|
return SequenceStrSoftConverter()
|
|
97
99
|
elif get_origin(datatype) == Sequence and enum_cls:
|
|
98
100
|
return SequenceEnumSoftConverter(enum_cls)
|
|
101
|
+
elif datatype is np.ndarray:
|
|
102
|
+
return NDArraySoftConverter()
|
|
99
103
|
elif get_origin(datatype) == np.ndarray:
|
|
104
|
+
if datatype not in get_args(SignalDatatype):
|
|
105
|
+
raise TypeError(f"Expected Array1D[dtype], got {datatype}")
|
|
100
106
|
return NDArraySoftConverter(get_dtype(datatype))
|
|
101
107
|
elif enum_cls:
|
|
102
108
|
return EnumSoftConverter(enum_cls)
|
ophyd_async/core/_utils.py
CHANGED
|
@@ -2,18 +2,11 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
-
from collections.abc import Awaitable, Callable, Iterable, Sequence
|
|
5
|
+
from collections.abc import Awaitable, Callable, Iterable, Mapping, Sequence
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from enum import Enum, EnumMeta
|
|
8
|
-
from typing import
|
|
9
|
-
|
|
10
|
-
Generic,
|
|
11
|
-
Literal,
|
|
12
|
-
ParamSpec,
|
|
13
|
-
TypeVar,
|
|
14
|
-
get_args,
|
|
15
|
-
get_origin,
|
|
16
|
-
)
|
|
8
|
+
from typing import Any, Generic, Literal, ParamSpec, TypeVar, get_args, get_origin
|
|
9
|
+
from unittest.mock import Mock
|
|
17
10
|
|
|
18
11
|
import numpy as np
|
|
19
12
|
|
|
@@ -21,7 +14,7 @@ T = TypeVar("T")
|
|
|
21
14
|
P = ParamSpec("P")
|
|
22
15
|
Callback = Callable[[T], None]
|
|
23
16
|
DEFAULT_TIMEOUT = 10.0
|
|
24
|
-
ErrorText = str |
|
|
17
|
+
ErrorText = str | Mapping[str, Exception]
|
|
25
18
|
|
|
26
19
|
|
|
27
20
|
class StrictEnum(str, Enum):
|
|
@@ -69,6 +62,13 @@ class NotConnected(Exception):
|
|
|
69
62
|
|
|
70
63
|
self._errors = errors
|
|
71
64
|
|
|
65
|
+
@property
|
|
66
|
+
def sub_errors(self) -> Mapping[str, Exception]:
|
|
67
|
+
if isinstance(self._errors, dict):
|
|
68
|
+
return self._errors.copy()
|
|
69
|
+
else:
|
|
70
|
+
return {}
|
|
71
|
+
|
|
72
72
|
def _format_sub_errors(self, name: str, error: Exception, indent="") -> str:
|
|
73
73
|
if isinstance(error, NotConnected):
|
|
74
74
|
error_txt = ":" + error.format_error_string(indent + self._indent_width)
|
|
@@ -99,6 +99,19 @@ class NotConnected(Exception):
|
|
|
99
99
|
def __str__(self) -> str:
|
|
100
100
|
return self.format_error_string(indent="")
|
|
101
101
|
|
|
102
|
+
@classmethod
|
|
103
|
+
def with_other_exceptions_logged(
|
|
104
|
+
cls, exceptions: Mapping[str, Exception]
|
|
105
|
+
) -> NotConnected:
|
|
106
|
+
for name, exception in exceptions.items():
|
|
107
|
+
if not isinstance(exception, NotConnected):
|
|
108
|
+
logging.exception(
|
|
109
|
+
f"device `{name}` raised unexpected exception "
|
|
110
|
+
f"{type(exception).__name__}",
|
|
111
|
+
exc_info=exception,
|
|
112
|
+
)
|
|
113
|
+
return NotConnected(exceptions)
|
|
114
|
+
|
|
102
115
|
|
|
103
116
|
@dataclass(frozen=True)
|
|
104
117
|
class WatcherUpdate(Generic[T]):
|
|
@@ -120,21 +133,23 @@ async def wait_for_connection(**coros: Awaitable[None]):
|
|
|
120
133
|
|
|
121
134
|
Expected kwargs should be a mapping of names to coroutine tasks to execute.
|
|
122
135
|
"""
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
136
|
+
exceptions: dict[str, Exception] = {}
|
|
137
|
+
if len(coros) == 1:
|
|
138
|
+
# Single device optimization
|
|
139
|
+
name, coro = coros.popitem()
|
|
140
|
+
try:
|
|
141
|
+
await coro
|
|
142
|
+
except Exception as e:
|
|
143
|
+
exceptions[name] = e
|
|
144
|
+
else:
|
|
145
|
+
# Use gather to connect in parallel
|
|
146
|
+
results = await asyncio.gather(*coros.values(), return_exceptions=True)
|
|
147
|
+
for name, result in zip(coros, results, strict=False):
|
|
148
|
+
if isinstance(result, Exception):
|
|
149
|
+
exceptions[name] = result
|
|
135
150
|
|
|
136
151
|
if exceptions:
|
|
137
|
-
raise NotConnected(exceptions)
|
|
152
|
+
raise NotConnected.with_other_exceptions_logged(exceptions)
|
|
138
153
|
|
|
139
154
|
|
|
140
155
|
def get_dtype(datatype: type) -> np.dtype:
|
|
@@ -252,3 +267,38 @@ class Reference(Generic[T]):
|
|
|
252
267
|
|
|
253
268
|
def __call__(self) -> T:
|
|
254
269
|
return self._obj
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class LazyMock:
|
|
273
|
+
"""A lazily created Mock to be used when connecting in mock mode.
|
|
274
|
+
|
|
275
|
+
Creating Mocks is reasonably expensive when each Device (and Signal)
|
|
276
|
+
requires its own, and the tree is only used when ``Signal.set()`` is
|
|
277
|
+
called. This class allows a tree of lazily connected Mocks to be
|
|
278
|
+
constructed so that when the leaf is created, so are its parents.
|
|
279
|
+
Any calls to the child are then accessible from the parent mock.
|
|
280
|
+
|
|
281
|
+
>>> parent = LazyMock()
|
|
282
|
+
>>> child = parent.child("child")
|
|
283
|
+
>>> child_mock = child()
|
|
284
|
+
>>> child_mock() # doctest: +ELLIPSIS
|
|
285
|
+
<Mock name='mock.child()' id='...'>
|
|
286
|
+
>>> parent_mock = parent()
|
|
287
|
+
>>> parent_mock.mock_calls
|
|
288
|
+
[call.child()]
|
|
289
|
+
"""
|
|
290
|
+
|
|
291
|
+
def __init__(self, name: str = "", parent: LazyMock | None = None) -> None:
|
|
292
|
+
self.parent = parent
|
|
293
|
+
self.name = name
|
|
294
|
+
self._mock: Mock | None = None
|
|
295
|
+
|
|
296
|
+
def child(self, name: str) -> LazyMock:
|
|
297
|
+
return LazyMock(name, self)
|
|
298
|
+
|
|
299
|
+
def __call__(self) -> Mock:
|
|
300
|
+
if self._mock is None:
|
|
301
|
+
self._mock = Mock(spec=object)
|
|
302
|
+
if self.parent is not None:
|
|
303
|
+
self.parent().attach_mock(self._mock, self.name)
|
|
304
|
+
return self._mock
|
|
@@ -19,7 +19,8 @@ class SingleTriggerDetector(StandardReadable, Triggerable):
|
|
|
19
19
|
**plugins: NDPluginBaseIO,
|
|
20
20
|
) -> None:
|
|
21
21
|
self.drv = drv
|
|
22
|
-
|
|
22
|
+
for k, v in plugins.items():
|
|
23
|
+
setattr(self, k, v)
|
|
23
24
|
|
|
24
25
|
self.add_readables(
|
|
25
26
|
[self.drv.array_counter, *read_uncached],
|
ophyd_async/epics/core/_aioca.py
CHANGED
|
@@ -115,7 +115,7 @@ class CaLongStrConverter(CaConverter[str]):
|
|
|
115
115
|
def __init__(self):
|
|
116
116
|
super().__init__(str, dbr.DBR_CHAR_STR, dbr.DBR_CHAR_STR)
|
|
117
117
|
|
|
118
|
-
def
|
|
118
|
+
def write_value(self, value: Any) -> Any:
|
|
119
119
|
# Add a null in here as this is what the commandline caput does
|
|
120
120
|
# TODO: this should be in the server so check if it can be pushed to asyn
|
|
121
121
|
return value + "\0"
|
ophyd_async/epics/core/_p4p.py
CHANGED
|
@@ -188,6 +188,7 @@ _datatype_converter_from_typeid: dict[
|
|
|
188
188
|
("epics:nt/NTScalarArray:1.0", "as"): (Sequence[str], PvaConverter),
|
|
189
189
|
("epics:nt/NTTable:1.0", "S"): (Table, PvaTableConverter),
|
|
190
190
|
("epics:nt/NTNDArray:1.0", "v"): (np.ndarray, PvaNDArrayConverter),
|
|
191
|
+
("epics:nt/NTNDArray:1.0", "U"): (np.ndarray, PvaNDArrayConverter),
|
|
191
192
|
}
|
|
192
193
|
|
|
193
194
|
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from unittest.mock import Mock
|
|
4
|
-
|
|
5
3
|
from ophyd_async.core import (
|
|
6
4
|
Device,
|
|
7
5
|
DeviceConnector,
|
|
@@ -11,6 +9,7 @@ from ophyd_async.core import (
|
|
|
11
9
|
SignalRW,
|
|
12
10
|
SignalX,
|
|
13
11
|
)
|
|
12
|
+
from ophyd_async.core._utils import LazyMock
|
|
14
13
|
|
|
15
14
|
from ._epics_connector import fill_backend_with_prefix
|
|
16
15
|
from ._signal import PvaSignalBackend, pvget_with_timeout
|
|
@@ -64,29 +63,29 @@ class PviDeviceConnector(DeviceConnector):
|
|
|
64
63
|
backend.read_pv = read_pv
|
|
65
64
|
backend.write_pv = write_pv
|
|
66
65
|
|
|
67
|
-
async def
|
|
68
|
-
self
|
|
66
|
+
async def connect_mock(self, device: Device, mock: LazyMock):
|
|
67
|
+
self.filler.create_device_vector_entries_to_mock(2)
|
|
68
|
+
# Set the name of the device to name all children
|
|
69
|
+
device.set_name(device.name)
|
|
70
|
+
return await super().connect_mock(device, mock)
|
|
71
|
+
|
|
72
|
+
async def connect_real(
|
|
73
|
+
self, device: Device, timeout: float, force_reconnect: bool
|
|
69
74
|
) -> None:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
# This is a DeviceVector of children
|
|
85
|
-
for i, e in enumerate(entry):
|
|
86
|
-
if e:
|
|
87
|
-
self._fill_child(name, e, i)
|
|
88
|
-
# Check that all the requested children have been filled
|
|
89
|
-
self.filler.check_filled(f"{self.pvi_pv}: {entries}")
|
|
75
|
+
pvi_structure = await pvget_with_timeout(self.pvi_pv, timeout)
|
|
76
|
+
entries: dict[str, Entry | list[Entry | None]] = pvi_structure["value"].todict()
|
|
77
|
+
# Fill based on what PVI gives us
|
|
78
|
+
for name, entry in entries.items():
|
|
79
|
+
if isinstance(entry, dict):
|
|
80
|
+
# This is a child
|
|
81
|
+
self._fill_child(name, entry)
|
|
82
|
+
else:
|
|
83
|
+
# This is a DeviceVector of children
|
|
84
|
+
for i, e in enumerate(entry):
|
|
85
|
+
if e:
|
|
86
|
+
self._fill_child(name, e, i)
|
|
87
|
+
# Check that all the requested children have been filled
|
|
88
|
+
self.filler.check_filled(f"{self.pvi_pv}: {entries}")
|
|
90
89
|
# Set the name of the device to name all children
|
|
91
90
|
device.set_name(device.name)
|
|
92
|
-
return await super().
|
|
91
|
+
return await super().connect_real(device, timeout, force_reconnect)
|
|
@@ -1,28 +1,33 @@
|
|
|
1
|
-
from
|
|
1
|
+
from collections.abc import Awaitable
|
|
2
2
|
|
|
3
3
|
import bluesky.plan_stubs as bps
|
|
4
4
|
|
|
5
|
-
from ophyd_async.core import DEFAULT_TIMEOUT, Device, wait_for_connection
|
|
5
|
+
from ophyd_async.core import DEFAULT_TIMEOUT, Device, LazyMock, wait_for_connection
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def ensure_connected(
|
|
9
9
|
*devices: Device,
|
|
10
|
-
mock: bool |
|
|
10
|
+
mock: bool | LazyMock = False,
|
|
11
11
|
timeout: float = DEFAULT_TIMEOUT,
|
|
12
12
|
force_reconnect=False,
|
|
13
13
|
):
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
device_names = [device.name for device in devices]
|
|
15
|
+
non_unique = {
|
|
16
|
+
device: device.name for device in devices if device_names.count(device.name) > 1
|
|
17
|
+
}
|
|
18
|
+
if non_unique:
|
|
19
|
+
raise ValueError(f"Devices do not have unique names {non_unique}")
|
|
20
|
+
|
|
21
|
+
def connect_devices() -> Awaitable[None]:
|
|
22
|
+
coros = {
|
|
23
|
+
device.name: device.connect(
|
|
24
|
+
mock=mock, timeout=timeout, force_reconnect=force_reconnect
|
|
23
25
|
)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
for device in devices
|
|
27
|
+
}
|
|
28
|
+
return wait_for_connection(**coros)
|
|
29
|
+
|
|
30
|
+
(connect_task,) = yield from bps.wait_for([connect_devices])
|
|
26
31
|
|
|
27
32
|
if connect_task and connect_task.exception() is not None:
|
|
28
33
|
raise connect_task.exception()
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from typing import TypeVar
|
|
4
|
-
from unittest.mock import Mock
|
|
5
4
|
|
|
6
5
|
from ophyd_async.core import Device, DeviceConnector, DeviceFiller
|
|
6
|
+
from ophyd_async.core._utils import LazyMock
|
|
7
7
|
from ophyd_async.tango.signal import (
|
|
8
8
|
TangoSignalBackend,
|
|
9
9
|
infer_python_type,
|
|
@@ -117,41 +117,42 @@ class TangoDeviceConnector(DeviceConnector):
|
|
|
117
117
|
list(self.filler.create_signals_from_annotations(filled=False))
|
|
118
118
|
self.filler.check_created()
|
|
119
119
|
|
|
120
|
-
async def
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
120
|
+
async def connect_mock(self, device: Device, mock: LazyMock):
|
|
121
|
+
# Make 2 entries for each DeviceVector
|
|
122
|
+
self.filler.create_device_vector_entries_to_mock(2)
|
|
123
|
+
# Set the name of the device to name all children
|
|
124
|
+
device.set_name(device.name)
|
|
125
|
+
return await super().connect_mock(device, mock)
|
|
126
|
+
|
|
127
|
+
async def connect_real(self, device: Device, timeout: float, force_reconnect: bool):
|
|
128
|
+
if self.trl and self.proxy is None:
|
|
129
|
+
self.proxy = await AsyncDeviceProxy(self.trl)
|
|
130
|
+
elif self.proxy and not self.trl:
|
|
131
|
+
self.trl = self.proxy.name()
|
|
126
132
|
else:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
full_trl
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
backend
|
|
145
|
-
|
|
146
|
-
backend.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
elif self._polling[0]:
|
|
151
|
-
backend.set_polling(*self._polling)
|
|
152
|
-
backend.allow_events(False)
|
|
153
|
-
# Check that all the requested children have been filled
|
|
154
|
-
self.filler.check_filled(f"{self.trl}: {children}")
|
|
133
|
+
raise TypeError("Neither proxy nor trl supplied")
|
|
134
|
+
|
|
135
|
+
children = sorted(
|
|
136
|
+
set()
|
|
137
|
+
.union(self.proxy.get_attribute_list())
|
|
138
|
+
.union(self.proxy.get_command_list())
|
|
139
|
+
)
|
|
140
|
+
for name in children:
|
|
141
|
+
# TODO: strip attribute name
|
|
142
|
+
full_trl = f"{self.trl}/{name}"
|
|
143
|
+
signal_type = await infer_signal_type(full_trl, self.proxy)
|
|
144
|
+
if signal_type:
|
|
145
|
+
backend = self.filler.fill_child_signal(name, signal_type)
|
|
146
|
+
backend.datatype = await infer_python_type(full_trl, self.proxy)
|
|
147
|
+
backend.set_trl(full_trl)
|
|
148
|
+
if polling := self._signal_polling.get(name, ()):
|
|
149
|
+
backend.set_polling(*polling)
|
|
150
|
+
backend.allow_events(False)
|
|
151
|
+
elif self._polling[0]:
|
|
152
|
+
backend.set_polling(*self._polling)
|
|
153
|
+
backend.allow_events(False)
|
|
154
|
+
# Check that all the requested children have been filled
|
|
155
|
+
self.filler.check_filled(f"{self.trl}: {children}")
|
|
155
156
|
# Set the name of the device to name all children
|
|
156
157
|
device.set_name(device.name)
|
|
157
|
-
return await super().
|
|
158
|
+
return await super().connect_real(device, timeout, force_reconnect)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ophyd-async
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.0a5
|
|
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
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
ophyd_async/__init__.py,sha256=tEfgj45lRItQ-_u8SRFPM-mpBh3gWvHXr3emhiJJG_M,225
|
|
2
2
|
ophyd_async/__main__.py,sha256=n_U4O9bgm97OuboUB_9eK7eFiwy8BZSgXJ0OzbE0DqU,481
|
|
3
|
-
ophyd_async/_version.py,sha256=
|
|
3
|
+
ophyd_async/_version.py,sha256=SQdFsQ53whTNOERBL9cmDWrar7r4kVAXQthVKIJB7mU,413
|
|
4
4
|
ophyd_async/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
ophyd_async/core/__init__.py,sha256=
|
|
5
|
+
ophyd_async/core/__init__.py,sha256=JBKq2w50jBjxg_2KRL1jXnIzETaj8dPOD47_prgtotU,4283
|
|
6
6
|
ophyd_async/core/_detector.py,sha256=bKLekM2J3GzLXsKwe8qXQjNP_sAVsa8EtwFEWD-8MeA,14307
|
|
7
|
-
ophyd_async/core/_device.py,sha256=
|
|
7
|
+
ophyd_async/core/_device.py,sha256=ygXxDKiTO43qDnLeLzrKKyABwlrfGvSVii7PHyCIjHg,12074
|
|
8
8
|
ophyd_async/core/_device_filler.py,sha256=Nw-DUyuXYpvt4mmCAQaNVA0LFBBaPK84ubZo3bR39Ak,11407
|
|
9
9
|
ophyd_async/core/_device_save_loader.py,sha256=OViN9_LWNOLuajzrHDKYEqd5I47u5npQACdGceKcIGY,8375
|
|
10
10
|
ophyd_async/core/_flyer.py,sha256=us5z6MNGCvIfgPDTmFTxNERSP37g0WVRkRD0Z2JiMgM,1701
|
|
11
11
|
ophyd_async/core/_hdf_dataset.py,sha256=wW_OL8OYLGOsE01ny3hGaapOrxK7BzhWTxKgz8CIXK0,2492
|
|
12
12
|
ophyd_async/core/_log.py,sha256=UbL9AtnHVUg7r9LofzgmuKEtBESy03usCp7ejmDltG4,3679
|
|
13
|
-
ophyd_async/core/_mock_signal_backend.py,sha256=
|
|
14
|
-
ophyd_async/core/_mock_signal_utils.py,sha256=
|
|
13
|
+
ophyd_async/core/_mock_signal_backend.py,sha256=8Upnz6QrSigeDXemjZ-jB4sV2yIPUzid-6GOfTZ-7Io,2805
|
|
14
|
+
ophyd_async/core/_mock_signal_utils.py,sha256=YeKjStClwp1etlmHMx1tb_VV1GjeFPg83Hkq7-YPkpg,5306
|
|
15
15
|
ophyd_async/core/_protocol.py,sha256=MuYRqSfakdry9RllX7G9UTzp4lw3eDjtkdGPpnbNb34,4040
|
|
16
16
|
ophyd_async/core/_providers.py,sha256=ff9ZT5-PZ6rhTTdE-q8w9l_k9DuZqLWLebsKZLeJ0Ds,7112
|
|
17
17
|
ophyd_async/core/_readable.py,sha256=7FxqxhAT1wBQqOEivgnY731zA9QoK1Tt-ZGcH7GBOXM,10623
|
|
18
|
-
ophyd_async/core/_signal.py,sha256=
|
|
18
|
+
ophyd_async/core/_signal.py,sha256=IpOyYu1IqpX8sFs9_CLUd1LuFWgAqEaFZgA12BG5tfM,19897
|
|
19
19
|
ophyd_async/core/_signal_backend.py,sha256=YWPgLSPbfPnWIUDHvP1ArCVK8zKXJxzzbloqQe_ucCI,5040
|
|
20
|
-
ophyd_async/core/_soft_signal_backend.py,sha256=
|
|
20
|
+
ophyd_async/core/_soft_signal_backend.py,sha256=w9zzD4eoD9SsJpORXNSaFOLJrD6biYBbCSVAybLa_7k,5926
|
|
21
21
|
ophyd_async/core/_status.py,sha256=OUKhblRQ4KU5PDsWbpvYduM7G60JMk1NqeV4eqyPtKc,5131
|
|
22
22
|
ophyd_async/core/_table.py,sha256=ZToBVmAPDmhrVDgjx0f8SErxVdKhvGdGwQ-fXxGCtN8,5386
|
|
23
|
-
ophyd_async/core/_utils.py,sha256=
|
|
23
|
+
ophyd_async/core/_utils.py,sha256=wzzGL7yPAMuPueGOG1cpTgh0vho5YxI86m8SSX7Z9hw,9494
|
|
24
24
|
ophyd_async/epics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
25
|
ophyd_async/epics/motor.py,sha256=pujJXV_vslvo3AxsVySTAEoFuduuv5Hp6sz8aRvIbeQ,8792
|
|
26
26
|
ophyd_async/epics/signal.py,sha256=hJCGIIWjRVhjEHkeL1I_oPEaaN7dDFKmm7G7ZmgoTYQ,219
|
|
@@ -32,7 +32,7 @@ ophyd_async/epics/adcore/__init__.py,sha256=3wMOyFGaq1X61LqK4iY4pq-m_BjhOgYZD2-m
|
|
|
32
32
|
ophyd_async/epics/adcore/_core_io.py,sha256=ZQjRLdpFMVS9kwEm5LAh60pxiy7XWyYtc2TzEvCEVYM,6076
|
|
33
33
|
ophyd_async/epics/adcore/_core_logic.py,sha256=JjrSmKErRFSv1C98or1Upwi01k3NWDRMi2fPHVWMmWw,3561
|
|
34
34
|
ophyd_async/epics/adcore/_hdf_writer.py,sha256=eWT9SH7uegf9rpCWRmVCZTsOxF1drPaOAMmoXm99mVk,7215
|
|
35
|
-
ophyd_async/epics/adcore/_single_trigger.py,sha256=
|
|
35
|
+
ophyd_async/epics/adcore/_single_trigger.py,sha256=7SzmadatWk4zXIweRIhVX5odc__ZZKuGicL7vlW0JbY,1208
|
|
36
36
|
ophyd_async/epics/adcore/_utils.py,sha256=MZBKeSPIRzyo6f84MpzPp28KwOLa9qgrkMIFc618wOE,3932
|
|
37
37
|
ophyd_async/epics/adkinetix/__init__.py,sha256=cvnwOqbvEENf70eFp6bPGwayP0u14UTIhs3WiZEcF_Q,262
|
|
38
38
|
ophyd_async/epics/adkinetix/_kinetix.py,sha256=JsQVc4d7lRCnVObJlM775hHLfp3rYRSRgOoIyRvrSek,1184
|
|
@@ -50,11 +50,11 @@ ophyd_async/epics/advimba/_vimba.py,sha256=E_RJ0uJQt-RWAY7uFTNkHaOdGYS5sa7ZbRgAe
|
|
|
50
50
|
ophyd_async/epics/advimba/_vimba_controller.py,sha256=Ej7irxTab9cfjmqz4G4Zxv3CjhJw_eRmIb3E62YWh6g,2226
|
|
51
51
|
ophyd_async/epics/advimba/_vimba_io.py,sha256=F3KUzMN-GMe637za-iRVGGTt8v9F1a79fRcl3MCkfXA,1864
|
|
52
52
|
ophyd_async/epics/core/__init__.py,sha256=8NoQxEEc2Ny_L9nrD2fnGSf_2gJr1wCR1LwUeLNcIJo,588
|
|
53
|
-
ophyd_async/epics/core/_aioca.py,sha256=
|
|
53
|
+
ophyd_async/epics/core/_aioca.py,sha256=iQWiHWYbMJLa7qeBrCz4_e16Y8A-NYYi6oYNi8oOFVY,11617
|
|
54
54
|
ophyd_async/epics/core/_epics_connector.py,sha256=n1FlQYui8HdobPxaX3VAflrzi2UT7QCe3cFasssmVLw,1789
|
|
55
55
|
ophyd_async/epics/core/_epics_device.py,sha256=kshNiKQhevsL2OZXa-r093L_sQGvGK_0J4PWVLg3Eqw,437
|
|
56
|
-
ophyd_async/epics/core/_p4p.py,sha256=
|
|
57
|
-
ophyd_async/epics/core/_pvi_connector.py,sha256=
|
|
56
|
+
ophyd_async/epics/core/_p4p.py,sha256=S6zXXApRF0454aOcxUI_cd7Y7tXiOnss_ODhjjk0PMo,14691
|
|
57
|
+
ophyd_async/epics/core/_pvi_connector.py,sha256=Rjc8g3Rdny_O-4JxhoCpD4L7XWIRq-lnGHXKpsIUrSU,3621
|
|
58
58
|
ophyd_async/epics/core/_signal.py,sha256=jHdMXV1-0bd7PC8XV32Sso1xgubZVDhWFNsWV-UuamQ,4642
|
|
59
59
|
ophyd_async/epics/core/_util.py,sha256=6CCWDfp54WeBIJdGjg_YBVZTKoNjponWyykMmLPrj7U,1820
|
|
60
60
|
ophyd_async/epics/demo/__init__.py,sha256=wCrgemEo-zR4TTvaqCKnQ-AIUHorotV5jhftbq1tXz0,1368
|
|
@@ -74,12 +74,12 @@ ophyd_async/fastcs/panda/__init__.py,sha256=_o7n7ckoTM6hTRHpLphpL7r_9sADE59MRNM0
|
|
|
74
74
|
ophyd_async/fastcs/panda/_block.py,sha256=STQo6NJAqIVfxyMf-2pxINPyr9_nKtXSdicp92a25xo,1709
|
|
75
75
|
ophyd_async/fastcs/panda/_control.py,sha256=61vcJMjYQiUGAM5J0SfkfthFs7U28m9Pe9mgmGGf0-w,1021
|
|
76
76
|
ophyd_async/fastcs/panda/_hdf_panda.py,sha256=WdgWgdrU2yT4keH70VG-ZBVOmT-IpKVyukEuKk7QnJs,1049
|
|
77
|
-
ophyd_async/fastcs/panda/_table.py,sha256=
|
|
77
|
+
ophyd_async/fastcs/panda/_table.py,sha256=5YyAfsl3H7kxH3bDjUKHuH9DyrWQmAn9dv-v0NYzFNo,2289
|
|
78
78
|
ophyd_async/fastcs/panda/_trigger.py,sha256=forImtdnDnaZ0KKhqSxCqwHWXq13SJ4mn9wdM4yqNLY,3056
|
|
79
79
|
ophyd_async/fastcs/panda/_utils.py,sha256=NdvzdKy0SOG1eCVMQo_nwRXpBo0wyi6lM5Xw3HvssOw,508
|
|
80
80
|
ophyd_async/fastcs/panda/_writer.py,sha256=wDN6uWX1ENofmI3JBXJ7_CGooI7WsZP-JJQrRiSc6sM,6000
|
|
81
81
|
ophyd_async/plan_stubs/__init__.py,sha256=wjpEj_BoBZJ9x2fhUPY6BzWMqyYH96JrBlJvV7frdN4,524
|
|
82
|
-
ophyd_async/plan_stubs/_ensure_connected.py,sha256=
|
|
82
|
+
ophyd_async/plan_stubs/_ensure_connected.py,sha256=uoqfAzghjifdfD_JM860TvMvj9T2Y12nKPvtI5l6zZc,1021
|
|
83
83
|
ophyd_async/plan_stubs/_fly.py,sha256=WxghBAHsF-8xFrILCm44jeHIu9udLhm-tj4JXd9kZjY,6208
|
|
84
84
|
ophyd_async/plan_stubs/_nd_attributes.py,sha256=TVfy3bhnrLFBXZ6b2bREBj0LzEviEGzuGvgWK3I7tII,2198
|
|
85
85
|
ophyd_async/sim/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -93,7 +93,7 @@ ophyd_async/sim/demo/_pattern_detector/_pattern_generator.py,sha256=gP0Q1-1p_3KO
|
|
|
93
93
|
ophyd_async/sim/testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
94
94
|
ophyd_async/tango/__init__.py,sha256=2XxSpJWvvAlCs0GLPv6sEnUUD40fWq9OzKuiBEZ_MEs,861
|
|
95
95
|
ophyd_async/tango/base_devices/__init__.py,sha256=fbn1-rfK8MCErpSmjAJuQioDikbjOobd4sDvAB9Xupw,157
|
|
96
|
-
ophyd_async/tango/base_devices/_base_device.py,sha256=
|
|
96
|
+
ophyd_async/tango/base_devices/_base_device.py,sha256=3GIkU1bUyunA9uTBsEtANmlJBo4WCgQmAWYbCRcjoXM,6016
|
|
97
97
|
ophyd_async/tango/base_devices/_tango_readable.py,sha256=J-XeR2fmQU5RTdsNhRvzNPJD8xZRVJ6-qXt09vfpVtI,951
|
|
98
98
|
ophyd_async/tango/demo/__init__.py,sha256=_j-UicTnckuIBp8PnieFMOMnLFGivnaKdmo9o0hYtzc,256
|
|
99
99
|
ophyd_async/tango/demo/_counter.py,sha256=neKkuepWfpBxMOPnnHJ79SHgwepymG4gTDVacuHE6fA,1134
|
|
@@ -104,9 +104,9 @@ ophyd_async/tango/demo/_tango/_servers.py,sha256=MwkkoZWJQm_cgafCBBXeQfwyAiOgU8c
|
|
|
104
104
|
ophyd_async/tango/signal/__init__.py,sha256=-_wBvhSPb58h_XSeGVaJ6gMFOY8TQNsVYfZxQuxGB1c,750
|
|
105
105
|
ophyd_async/tango/signal/_signal.py,sha256=72iOxCt6HkyaYPgE402h5fd1KryyVUarR0exV2A3UbU,6277
|
|
106
106
|
ophyd_async/tango/signal/_tango_transport.py,sha256=DVTdLu8C19k-QzYaKUzFK2WMbaSd6dIO77k99ugD8U4,28990
|
|
107
|
-
ophyd_async-0.8.
|
|
108
|
-
ophyd_async-0.8.
|
|
109
|
-
ophyd_async-0.8.
|
|
110
|
-
ophyd_async-0.8.
|
|
111
|
-
ophyd_async-0.8.
|
|
112
|
-
ophyd_async-0.8.
|
|
107
|
+
ophyd_async-0.8.0a5.dist-info/LICENSE,sha256=pU5shZcsvWgz701EbT7yjFZ8rMvZcWgRH54CRt8ld_c,1517
|
|
108
|
+
ophyd_async-0.8.0a5.dist-info/METADATA,sha256=0uu4GmEt1a33pHw4Oa5BhF22ns92xsqC_0wNmg3nkM0,6708
|
|
109
|
+
ophyd_async-0.8.0a5.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
|
|
110
|
+
ophyd_async-0.8.0a5.dist-info/entry_points.txt,sha256=O0YNJTEufO0w9BozXi-JurTy2U1_o0ypeCgJLQ727Jk,58
|
|
111
|
+
ophyd_async-0.8.0a5.dist-info/top_level.txt,sha256=-hjorMsv5Rmjo3qrgqhjpal1N6kW5vMxZO3lD4iEaXs,12
|
|
112
|
+
ophyd_async-0.8.0a5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|