ophyd-async 0.2.0__py3-none-any.whl → 0.3.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 (79) hide show
  1. ophyd_async/__init__.py +1 -4
  2. ophyd_async/_version.py +2 -2
  3. ophyd_async/core/__init__.py +52 -19
  4. ophyd_async/core/_providers.py +38 -5
  5. ophyd_async/core/async_status.py +86 -40
  6. ophyd_async/core/detector.py +214 -72
  7. ophyd_async/core/device.py +91 -50
  8. ophyd_async/core/device_save_loader.py +96 -23
  9. ophyd_async/core/flyer.py +32 -246
  10. ophyd_async/core/mock_signal_backend.py +82 -0
  11. ophyd_async/core/mock_signal_utils.py +145 -0
  12. ophyd_async/core/signal.py +225 -58
  13. ophyd_async/core/signal_backend.py +8 -5
  14. ophyd_async/core/{sim_signal_backend.py → soft_signal_backend.py} +51 -49
  15. ophyd_async/core/standard_readable.py +212 -23
  16. ophyd_async/core/utils.py +123 -30
  17. ophyd_async/epics/_backend/_aioca.py +42 -44
  18. ophyd_async/epics/_backend/_p4p.py +96 -52
  19. ophyd_async/epics/_backend/common.py +25 -0
  20. ophyd_async/epics/areadetector/__init__.py +8 -4
  21. ophyd_async/epics/areadetector/aravis.py +63 -0
  22. ophyd_async/epics/areadetector/controllers/__init__.py +2 -1
  23. ophyd_async/epics/areadetector/controllers/ad_sim_controller.py +1 -1
  24. ophyd_async/epics/areadetector/controllers/aravis_controller.py +78 -0
  25. ophyd_async/epics/areadetector/controllers/kinetix_controller.py +49 -0
  26. ophyd_async/epics/areadetector/controllers/pilatus_controller.py +37 -25
  27. ophyd_async/epics/areadetector/controllers/vimba_controller.py +66 -0
  28. ophyd_async/epics/areadetector/drivers/__init__.py +6 -0
  29. ophyd_async/epics/areadetector/drivers/ad_base.py +8 -12
  30. ophyd_async/epics/areadetector/drivers/aravis_driver.py +38 -0
  31. ophyd_async/epics/areadetector/drivers/kinetix_driver.py +27 -0
  32. ophyd_async/epics/areadetector/drivers/pilatus_driver.py +8 -5
  33. ophyd_async/epics/areadetector/drivers/vimba_driver.py +63 -0
  34. ophyd_async/epics/areadetector/kinetix.py +46 -0
  35. ophyd_async/epics/areadetector/pilatus.py +45 -0
  36. ophyd_async/epics/areadetector/single_trigger_det.py +14 -6
  37. ophyd_async/epics/areadetector/utils.py +2 -12
  38. ophyd_async/epics/areadetector/vimba.py +43 -0
  39. ophyd_async/epics/areadetector/writers/_hdffile.py +21 -7
  40. ophyd_async/epics/areadetector/writers/hdf_writer.py +32 -17
  41. ophyd_async/epics/areadetector/writers/nd_file_hdf.py +19 -18
  42. ophyd_async/epics/areadetector/writers/nd_plugin.py +15 -7
  43. ophyd_async/epics/demo/__init__.py +75 -49
  44. ophyd_async/epics/motion/motor.py +67 -53
  45. ophyd_async/epics/pvi/__init__.py +3 -0
  46. ophyd_async/epics/pvi/pvi.py +318 -0
  47. ophyd_async/epics/signal/__init__.py +8 -3
  48. ophyd_async/epics/signal/signal.py +26 -9
  49. ophyd_async/log.py +130 -0
  50. ophyd_async/panda/__init__.py +21 -5
  51. ophyd_async/panda/_common_blocks.py +49 -0
  52. ophyd_async/panda/_hdf_panda.py +48 -0
  53. ophyd_async/panda/_panda_controller.py +37 -0
  54. ophyd_async/panda/_trigger.py +39 -0
  55. ophyd_async/panda/_utils.py +15 -0
  56. ophyd_async/panda/writers/__init__.py +3 -0
  57. ophyd_async/panda/writers/_hdf_writer.py +220 -0
  58. ophyd_async/panda/writers/_panda_hdf_file.py +58 -0
  59. ophyd_async/plan_stubs/__init__.py +13 -0
  60. ophyd_async/plan_stubs/ensure_connected.py +22 -0
  61. ophyd_async/plan_stubs/fly.py +149 -0
  62. ophyd_async/protocols.py +126 -0
  63. ophyd_async/sim/__init__.py +11 -0
  64. ophyd_async/sim/demo/__init__.py +3 -0
  65. ophyd_async/sim/demo/sim_motor.py +103 -0
  66. ophyd_async/sim/pattern_generator.py +318 -0
  67. ophyd_async/sim/sim_pattern_detector_control.py +55 -0
  68. ophyd_async/sim/sim_pattern_detector_writer.py +34 -0
  69. ophyd_async/sim/sim_pattern_generator.py +37 -0
  70. {ophyd_async-0.2.0.dist-info → ophyd_async-0.3.0.dist-info}/METADATA +31 -70
  71. ophyd_async-0.3.0.dist-info/RECORD +86 -0
  72. {ophyd_async-0.2.0.dist-info → ophyd_async-0.3.0.dist-info}/WHEEL +1 -1
  73. ophyd_async/epics/signal/pvi_get.py +0 -22
  74. ophyd_async/panda/panda.py +0 -294
  75. ophyd_async-0.2.0.dist-info/RECORD +0 -53
  76. /ophyd_async/panda/{table.py → _table.py} +0 -0
  77. {ophyd_async-0.2.0.dist-info → ophyd_async-0.3.0.dist-info}/LICENSE +0 -0
  78. {ophyd_async-0.2.0.dist-info → ophyd_async-0.3.0.dist-info}/entry_points.txt +0 -0
  79. {ophyd_async-0.2.0.dist-info → ophyd_async-0.3.0.dist-info}/top_level.txt +0 -0
ophyd_async/__init__.py CHANGED
@@ -1,6 +1,3 @@
1
- from importlib.metadata import version # noqa
2
-
3
- __version__ = version("ophyd-async")
4
- del version
1
+ from ._version import __version__
5
2
 
6
3
  __all__ = ["__version__"]
ophyd_async/_version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.2.0'
16
- __version_tuple__ = version_tuple = (0, 2, 0)
15
+ __version__ = version = '0.3.0'
16
+ __version_tuple__ = version_tuple = (0, 3, 0)
@@ -5,22 +5,34 @@ from ._providers import (
5
5
  ShapeProvider,
6
6
  StaticDirectoryProvider,
7
7
  )
8
- from .async_status import AsyncStatus
9
- from .detector import DetectorControl, DetectorTrigger, DetectorWriter, StandardDetector
8
+ from .async_status import AsyncStatus, WatchableAsyncStatus
9
+ from .detector import (
10
+ DetectorControl,
11
+ DetectorTrigger,
12
+ DetectorWriter,
13
+ StandardDetector,
14
+ TriggerInfo,
15
+ )
10
16
  from .device import Device, DeviceCollector, DeviceVector
11
17
  from .device_save_loader import (
12
18
  get_signal_values,
19
+ load_device,
13
20
  load_from_yaml,
21
+ save_device,
14
22
  save_to_yaml,
15
23
  set_signal_values,
16
24
  walk_rw_signals,
17
25
  )
18
- from .flyer import (
19
- DetectorGroupLogic,
20
- HardwareTriggeredFlyable,
21
- SameTriggerDetectorGroupLogic,
22
- TriggerInfo,
23
- TriggerLogic,
26
+ from .flyer import HardwareTriggeredFlyable, TriggerLogic
27
+ from .mock_signal_backend import MockSignalBackend
28
+ from .mock_signal_utils import (
29
+ callback_on_mock_put,
30
+ get_mock_put,
31
+ mock_puts_blocked,
32
+ reset_mock_put_calls,
33
+ set_mock_put_proceeds,
34
+ set_mock_value,
35
+ set_mock_values,
24
36
  )
25
37
  from .signal import (
26
38
  Signal,
@@ -28,18 +40,23 @@ from .signal import (
28
40
  SignalRW,
29
41
  SignalW,
30
42
  SignalX,
43
+ assert_configuration,
44
+ assert_emitted,
45
+ assert_reading,
46
+ assert_value,
31
47
  observe_value,
32
48
  set_and_wait_for_value,
33
- set_sim_callback,
34
- set_sim_put_proceeds,
35
- set_sim_value,
49
+ soft_signal_r_and_setter,
50
+ soft_signal_rw,
36
51
  wait_for_value,
37
52
  )
38
53
  from .signal_backend import SignalBackend
39
- from .sim_signal_backend import SimSignalBackend
40
- from .standard_readable import StandardReadable
54
+ from .soft_signal_backend import SoftSignalBackend
55
+ from .standard_readable import ConfigSignal, HintedSignal, StandardReadable
41
56
  from .utils import (
42
57
  DEFAULT_TIMEOUT,
58
+ CalculatableTimeout,
59
+ CalculateTimeout,
43
60
  Callback,
44
61
  NotConnected,
45
62
  ReadingValueCallback,
@@ -51,9 +68,15 @@ from .utils import (
51
68
  )
52
69
 
53
70
  __all__ = [
71
+ "get_mock_put",
72
+ "callback_on_mock_put",
73
+ "mock_puts_blocked",
74
+ "set_mock_values",
75
+ "reset_mock_put_calls",
54
76
  "SignalBackend",
55
- "SimSignalBackend",
77
+ "SoftSignalBackend",
56
78
  "DetectorControl",
79
+ "MockSignalBackend",
57
80
  "DetectorTrigger",
58
81
  "DetectorWriter",
59
82
  "StandardDetector",
@@ -65,24 +88,28 @@ __all__ = [
65
88
  "SignalW",
66
89
  "SignalRW",
67
90
  "SignalX",
91
+ "soft_signal_r_and_setter",
92
+ "soft_signal_rw",
68
93
  "observe_value",
69
94
  "set_and_wait_for_value",
70
- "set_sim_callback",
71
- "set_sim_put_proceeds",
72
- "set_sim_value",
95
+ "set_mock_put_proceeds",
96
+ "set_mock_value",
73
97
  "wait_for_value",
74
98
  "AsyncStatus",
99
+ "WatchableAsyncStatus",
75
100
  "DirectoryInfo",
76
101
  "DirectoryProvider",
77
102
  "NameProvider",
78
103
  "ShapeProvider",
79
104
  "StaticDirectoryProvider",
80
105
  "StandardReadable",
106
+ "ConfigSignal",
107
+ "HintedSignal",
81
108
  "TriggerInfo",
82
- "DetectorGroupLogic",
83
- "SameTriggerDetectorGroupLogic",
84
109
  "TriggerLogic",
85
110
  "HardwareTriggeredFlyable",
111
+ "CalculateTimeout",
112
+ "CalculatableTimeout",
86
113
  "DEFAULT_TIMEOUT",
87
114
  "Callback",
88
115
  "NotConnected",
@@ -97,4 +124,10 @@ __all__ = [
97
124
  "save_to_yaml",
98
125
  "set_signal_values",
99
126
  "walk_rw_signals",
127
+ "load_device",
128
+ "save_device",
129
+ "assert_reading",
130
+ "assert_value",
131
+ "assert_configuration",
132
+ "assert_emitted",
100
133
  ]
@@ -1,12 +1,30 @@
1
1
  from abc import abstractmethod
2
2
  from dataclasses import dataclass
3
- from typing import Protocol, Sequence
3
+ from pathlib import Path
4
+ from typing import Optional, Protocol, Sequence, Union
4
5
 
5
6
 
6
7
  @dataclass
7
8
  class DirectoryInfo:
8
- directory_path: str
9
- filename_prefix: str
9
+ """
10
+ Information about where and how to write a file.
11
+
12
+ The bluesky event model splits the URI for a resource into two segments to aid in
13
+ different applications mounting filesystems at different mount points.
14
+ The portion of this path which is relevant only for the writer is the 'root',
15
+ while the path from an agreed upon mutual mounting is the resource_path.
16
+ The resource_dir is used with the filename to construct the resource_path.
17
+
18
+ :param root: Path of a root directory, relevant only for the file writer
19
+ :param resource_dir: Directory into which files should be written, relative to root
20
+ :param prefix: Optional filename prefix to add to all files
21
+ :param suffix: Optional filename suffix to add to all files
22
+ """
23
+
24
+ root: Path
25
+ resource_dir: Path
26
+ prefix: Optional[str] = ""
27
+ suffix: Optional[str] = ""
10
28
 
11
29
 
12
30
  class DirectoryProvider(Protocol):
@@ -16,8 +34,23 @@ class DirectoryProvider(Protocol):
16
34
 
17
35
 
18
36
  class StaticDirectoryProvider(DirectoryProvider):
19
- def __init__(self, directory_path: str, filename_prefix: str) -> None:
20
- self._directory_info = DirectoryInfo(directory_path, filename_prefix)
37
+ def __init__(
38
+ self,
39
+ directory_path: Union[str, Path],
40
+ filename_prefix: str = "",
41
+ filename_suffix: str = "",
42
+ resource_dir: Optional[Path] = None,
43
+ ) -> None:
44
+ if resource_dir is None:
45
+ resource_dir = Path(".")
46
+ if isinstance(directory_path, str):
47
+ directory_path = Path(directory_path)
48
+ self._directory_info = DirectoryInfo(
49
+ root=directory_path,
50
+ resource_dir=resource_dir,
51
+ prefix=filename_prefix,
52
+ suffix=filename_suffix,
53
+ )
21
54
 
22
55
  def __call__(self) -> DirectoryInfo:
23
56
  return self._directory_info
@@ -2,28 +2,37 @@
2
2
 
3
3
  import asyncio
4
4
  import functools
5
- from typing import Awaitable, Callable, Coroutine, List, Optional, cast
5
+ import time
6
+ from dataclasses import asdict, replace
7
+ from typing import (
8
+ AsyncIterator,
9
+ Awaitable,
10
+ Callable,
11
+ Generic,
12
+ Type,
13
+ TypeVar,
14
+ cast,
15
+ )
6
16
 
7
17
  from bluesky.protocols import Status
8
18
 
9
- from .utils import Callback, T
19
+ from ..protocols import Watcher
20
+ from .utils import Callback, P, T, WatcherUpdate
10
21
 
22
+ AS = TypeVar("AS", bound="AsyncStatus")
23
+ WAS = TypeVar("WAS", bound="WatchableAsyncStatus")
11
24
 
12
- class AsyncStatus(Status):
25
+
26
+ class AsyncStatusBase(Status):
13
27
  """Convert asyncio awaitable to bluesky Status interface"""
14
28
 
15
- def __init__(
16
- self,
17
- awaitable: Awaitable,
18
- watchers: Optional[List[Callable]] = None,
19
- ):
29
+ def __init__(self, awaitable: Awaitable):
20
30
  if isinstance(awaitable, asyncio.Task):
21
31
  self.task = awaitable
22
32
  else:
23
- self.task = asyncio.create_task(awaitable) # type: ignore
33
+ self.task = asyncio.create_task(awaitable)
24
34
  self.task.add_done_callback(self._run_callbacks)
25
- self._callbacks = cast(List[Callback[Status]], [])
26
- self._watchers = watchers
35
+ self._callbacks: list[Callback[Status]] = []
27
36
 
28
37
  def __await__(self):
29
38
  return self.task.__await__()
@@ -35,19 +44,14 @@ class AsyncStatus(Status):
35
44
  self._callbacks.append(callback)
36
45
 
37
46
  def _run_callbacks(self, task: asyncio.Task):
38
- if not task.cancelled():
39
- for callback in self._callbacks:
40
- callback(self)
41
-
42
- # TODO: remove ignore and bump min version when bluesky v1.12.0 is released
43
- def exception( # type: ignore
44
- self, timeout: Optional[float] = 0.0
45
- ) -> Optional[BaseException]:
47
+ for callback in self._callbacks:
48
+ callback(self)
49
+
50
+ def exception(self, timeout: float | None = 0.0) -> BaseException | None:
46
51
  if timeout != 0.0:
47
- raise Exception(
52
+ raise ValueError(
48
53
  "cannot honour any timeout other than 0 in an asynchronous function"
49
54
  )
50
-
51
55
  if self.task.done():
52
56
  try:
53
57
  return self.task.exception()
@@ -67,30 +71,72 @@ class AsyncStatus(Status):
67
71
  and self.task.exception() is None
68
72
  )
69
73
 
70
- def watch(self, watcher: Callable):
71
- """Add watcher to the list of interested parties.
72
-
73
- Arguments as per Bluesky :external+bluesky:meth:`watch` protocol.
74
- """
75
- if self._watchers is not None:
76
- self._watchers.append(watcher)
77
-
78
- @classmethod
79
- def wrap(cls, f: Callable[[T], Coroutine]) -> Callable[[T], "AsyncStatus"]:
80
- @functools.wraps(f)
81
- def wrap_f(self) -> AsyncStatus:
82
- return AsyncStatus(f(self))
83
-
84
- return wrap_f
85
-
86
74
  def __repr__(self) -> str:
87
75
  if self.done:
88
- if self.exception() is not None:
89
- status = "errored"
76
+ if e := self.exception():
77
+ status = f"errored: {repr(e)}"
90
78
  else:
91
79
  status = "done"
92
80
  else:
93
81
  status = "pending"
94
- return f"<{type(self).__name__} {status}>"
82
+ return f"<{type(self).__name__}, task: {self.task.get_coro()}, {status}>"
95
83
 
96
84
  __str__ = __repr__
85
+
86
+
87
+ class AsyncStatus(AsyncStatusBase):
88
+ @classmethod
89
+ def wrap(cls: Type[AS], f: Callable[P, Awaitable]) -> Callable[P, AS]:
90
+ """Wrap an async function in an AsyncStatus."""
91
+
92
+ @functools.wraps(f)
93
+ def wrap_f(*args: P.args, **kwargs: P.kwargs) -> AS:
94
+ return cls(f(*args, **kwargs))
95
+
96
+ # type is actually functools._Wrapped[P, Awaitable, P, AS]
97
+ # but functools._Wrapped is not necessarily available
98
+ return cast(Callable[P, AS], wrap_f)
99
+
100
+
101
+ class WatchableAsyncStatus(AsyncStatusBase, Generic[T]):
102
+ """Convert AsyncIterator of WatcherUpdates to bluesky Status interface."""
103
+
104
+ def __init__(self, iterator: AsyncIterator[WatcherUpdate[T]]):
105
+ self._watchers: list[Watcher] = []
106
+ self._start = time.monotonic()
107
+ self._last_update: WatcherUpdate[T] | None = None
108
+ super().__init__(self._notify_watchers_from(iterator))
109
+
110
+ async def _notify_watchers_from(self, iterator: AsyncIterator[WatcherUpdate[T]]):
111
+ async for update in iterator:
112
+ self._last_update = (
113
+ update
114
+ if update.time_elapsed is not None
115
+ else replace(update, time_elapsed=time.monotonic() - self._start)
116
+ )
117
+ for watcher in self._watchers:
118
+ self._update_watcher(watcher, self._last_update)
119
+
120
+ def _update_watcher(self, watcher: Watcher, update: WatcherUpdate[T]):
121
+ vals = asdict(
122
+ update, dict_factory=lambda d: {k: v for k, v in d if v is not None}
123
+ )
124
+ watcher(**vals)
125
+
126
+ def watch(self, watcher: Watcher):
127
+ self._watchers.append(watcher)
128
+ if self._last_update:
129
+ self._update_watcher(watcher, self._last_update)
130
+
131
+ @classmethod
132
+ def wrap(
133
+ cls: Type[WAS],
134
+ f: Callable[P, AsyncIterator[WatcherUpdate[T]]],
135
+ ) -> Callable[P, WAS]:
136
+ """Wrap an AsyncIterator in a WatchableAsyncStatus."""
137
+
138
+ @functools.wraps(f)
139
+ def wrap_f(*args: P.args, **kwargs: P.kwargs) -> WAS:
140
+ return cls(f(*args, **kwargs))
141
+
142
+ return cast(Callable[P, WAS], wrap_f)