ophyd-async 0.9.0a1__py3-none-any.whl → 0.10.0a1__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 (157) hide show
  1. ophyd_async/__init__.py +5 -8
  2. ophyd_async/_docs_parser.py +12 -0
  3. ophyd_async/_version.py +9 -4
  4. ophyd_async/core/__init__.py +102 -74
  5. ophyd_async/core/_derived_signal.py +271 -0
  6. ophyd_async/core/_derived_signal_backend.py +300 -0
  7. ophyd_async/core/_detector.py +158 -153
  8. ophyd_async/core/_device.py +143 -115
  9. ophyd_async/core/_device_filler.py +82 -9
  10. ophyd_async/core/_flyer.py +16 -7
  11. ophyd_async/core/_hdf_dataset.py +29 -22
  12. ophyd_async/core/_log.py +14 -23
  13. ophyd_async/core/_mock_signal_backend.py +11 -3
  14. ophyd_async/core/_protocol.py +65 -45
  15. ophyd_async/core/_providers.py +28 -9
  16. ophyd_async/core/_readable.py +74 -58
  17. ophyd_async/core/_settings.py +113 -0
  18. ophyd_async/core/_signal.py +304 -174
  19. ophyd_async/core/_signal_backend.py +60 -14
  20. ophyd_async/core/_soft_signal_backend.py +18 -12
  21. ophyd_async/core/_status.py +72 -24
  22. ophyd_async/core/_table.py +54 -17
  23. ophyd_async/core/_utils.py +101 -52
  24. ophyd_async/core/_yaml_settings.py +66 -0
  25. ophyd_async/epics/__init__.py +1 -0
  26. ophyd_async/epics/adandor/__init__.py +9 -0
  27. ophyd_async/epics/adandor/_andor.py +45 -0
  28. ophyd_async/epics/adandor/_andor_controller.py +51 -0
  29. ophyd_async/epics/adandor/_andor_io.py +34 -0
  30. ophyd_async/epics/adaravis/__init__.py +8 -1
  31. ophyd_async/epics/adaravis/_aravis.py +23 -41
  32. ophyd_async/epics/adaravis/_aravis_controller.py +23 -55
  33. ophyd_async/epics/adaravis/_aravis_io.py +13 -28
  34. ophyd_async/epics/adcore/__init__.py +36 -14
  35. ophyd_async/epics/adcore/_core_detector.py +81 -0
  36. ophyd_async/epics/adcore/_core_io.py +145 -95
  37. ophyd_async/epics/adcore/_core_logic.py +179 -88
  38. ophyd_async/epics/adcore/_core_writer.py +223 -0
  39. ophyd_async/epics/adcore/_hdf_writer.py +51 -92
  40. ophyd_async/epics/adcore/_jpeg_writer.py +26 -0
  41. ophyd_async/epics/adcore/_single_trigger.py +6 -5
  42. ophyd_async/epics/adcore/_tiff_writer.py +26 -0
  43. ophyd_async/epics/adcore/_utils.py +3 -2
  44. ophyd_async/epics/adkinetix/__init__.py +2 -1
  45. ophyd_async/epics/adkinetix/_kinetix.py +32 -27
  46. ophyd_async/epics/adkinetix/_kinetix_controller.py +11 -21
  47. ophyd_async/epics/adkinetix/_kinetix_io.py +12 -13
  48. ophyd_async/epics/adpilatus/__init__.py +7 -2
  49. ophyd_async/epics/adpilatus/_pilatus.py +28 -40
  50. ophyd_async/epics/adpilatus/_pilatus_controller.py +25 -22
  51. ophyd_async/epics/adpilatus/_pilatus_io.py +11 -9
  52. ophyd_async/epics/adsimdetector/__init__.py +8 -1
  53. ophyd_async/epics/adsimdetector/_sim.py +22 -16
  54. ophyd_async/epics/adsimdetector/_sim_controller.py +9 -43
  55. ophyd_async/epics/adsimdetector/_sim_io.py +10 -0
  56. ophyd_async/epics/advimba/__init__.py +10 -1
  57. ophyd_async/epics/advimba/_vimba.py +26 -25
  58. ophyd_async/epics/advimba/_vimba_controller.py +12 -24
  59. ophyd_async/epics/advimba/_vimba_io.py +23 -28
  60. ophyd_async/epics/core/_aioca.py +66 -30
  61. ophyd_async/epics/core/_epics_connector.py +4 -0
  62. ophyd_async/epics/core/_epics_device.py +2 -0
  63. ophyd_async/epics/core/_p4p.py +50 -18
  64. ophyd_async/epics/core/_pvi_connector.py +65 -8
  65. ophyd_async/epics/core/_signal.py +51 -51
  66. ophyd_async/epics/core/_util.py +5 -5
  67. ophyd_async/epics/demo/__init__.py +11 -49
  68. ophyd_async/epics/demo/__main__.py +31 -0
  69. ophyd_async/epics/demo/_ioc.py +32 -0
  70. ophyd_async/epics/demo/_motor.py +82 -0
  71. ophyd_async/epics/demo/_point_detector.py +42 -0
  72. ophyd_async/epics/demo/_point_detector_channel.py +22 -0
  73. ophyd_async/epics/demo/_stage.py +15 -0
  74. ophyd_async/epics/demo/{mover.db → motor.db} +2 -1
  75. ophyd_async/epics/demo/point_detector.db +59 -0
  76. ophyd_async/epics/demo/point_detector_channel.db +21 -0
  77. ophyd_async/epics/eiger/_eiger.py +1 -3
  78. ophyd_async/epics/eiger/_eiger_controller.py +11 -4
  79. ophyd_async/epics/eiger/_eiger_io.py +2 -0
  80. ophyd_async/epics/eiger/_odin_io.py +1 -2
  81. ophyd_async/epics/motor.py +83 -38
  82. ophyd_async/epics/signal.py +4 -1
  83. ophyd_async/epics/testing/__init__.py +14 -14
  84. ophyd_async/epics/testing/_example_ioc.py +68 -73
  85. ophyd_async/epics/testing/_utils.py +19 -44
  86. ophyd_async/epics/testing/test_records.db +16 -0
  87. ophyd_async/epics/testing/test_records_pva.db +17 -16
  88. ophyd_async/fastcs/__init__.py +1 -0
  89. ophyd_async/fastcs/core.py +6 -0
  90. ophyd_async/fastcs/odin/__init__.py +1 -0
  91. ophyd_async/fastcs/panda/__init__.py +8 -8
  92. ophyd_async/fastcs/panda/_block.py +29 -9
  93. ophyd_async/fastcs/panda/_control.py +12 -2
  94. ophyd_async/fastcs/panda/_hdf_panda.py +5 -1
  95. ophyd_async/fastcs/panda/_table.py +13 -7
  96. ophyd_async/fastcs/panda/_trigger.py +23 -9
  97. ophyd_async/fastcs/panda/_writer.py +27 -30
  98. ophyd_async/plan_stubs/__init__.py +16 -0
  99. ophyd_async/plan_stubs/_ensure_connected.py +12 -17
  100. ophyd_async/plan_stubs/_fly.py +3 -5
  101. ophyd_async/plan_stubs/_nd_attributes.py +9 -5
  102. ophyd_async/plan_stubs/_panda.py +14 -0
  103. ophyd_async/plan_stubs/_settings.py +152 -0
  104. ophyd_async/plan_stubs/_utils.py +3 -0
  105. ophyd_async/plan_stubs/_wait_for_awaitable.py +13 -0
  106. ophyd_async/sim/__init__.py +29 -0
  107. ophyd_async/sim/__main__.py +43 -0
  108. ophyd_async/sim/_blob_detector.py +33 -0
  109. ophyd_async/sim/_blob_detector_controller.py +48 -0
  110. ophyd_async/sim/_blob_detector_writer.py +105 -0
  111. ophyd_async/sim/_mirror_horizontal.py +46 -0
  112. ophyd_async/sim/_mirror_vertical.py +74 -0
  113. ophyd_async/sim/_motor.py +233 -0
  114. ophyd_async/sim/_pattern_generator.py +124 -0
  115. ophyd_async/sim/_point_detector.py +86 -0
  116. ophyd_async/sim/_stage.py +19 -0
  117. ophyd_async/tango/__init__.py +1 -0
  118. ophyd_async/tango/core/__init__.py +6 -1
  119. ophyd_async/tango/core/_base_device.py +41 -33
  120. ophyd_async/tango/core/_converters.py +81 -0
  121. ophyd_async/tango/core/_signal.py +21 -33
  122. ophyd_async/tango/core/_tango_readable.py +2 -19
  123. ophyd_async/tango/core/_tango_transport.py +148 -74
  124. ophyd_async/tango/core/_utils.py +47 -0
  125. ophyd_async/tango/demo/_counter.py +2 -0
  126. ophyd_async/tango/demo/_detector.py +2 -0
  127. ophyd_async/tango/demo/_mover.py +10 -6
  128. ophyd_async/tango/demo/_tango/_servers.py +4 -0
  129. ophyd_async/tango/testing/__init__.py +6 -0
  130. ophyd_async/tango/testing/_one_of_everything.py +200 -0
  131. ophyd_async/testing/__init__.py +48 -7
  132. ophyd_async/testing/__pytest_assert_rewrite.py +4 -0
  133. ophyd_async/testing/_assert.py +200 -96
  134. ophyd_async/testing/_mock_signal_utils.py +59 -73
  135. ophyd_async/testing/_one_of_everything.py +146 -0
  136. ophyd_async/testing/_single_derived.py +87 -0
  137. ophyd_async/testing/_utils.py +3 -0
  138. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info}/METADATA +25 -26
  139. ophyd_async-0.10.0a1.dist-info/RECORD +149 -0
  140. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info}/WHEEL +1 -1
  141. ophyd_async/core/_device_save_loader.py +0 -274
  142. ophyd_async/epics/demo/_mover.py +0 -95
  143. ophyd_async/epics/demo/_sensor.py +0 -37
  144. ophyd_async/epics/demo/sensor.db +0 -19
  145. ophyd_async/fastcs/panda/_utils.py +0 -16
  146. ophyd_async/sim/demo/__init__.py +0 -19
  147. ophyd_async/sim/demo/_pattern_detector/__init__.py +0 -13
  148. ophyd_async/sim/demo/_pattern_detector/_pattern_detector.py +0 -42
  149. ophyd_async/sim/demo/_pattern_detector/_pattern_detector_controller.py +0 -62
  150. ophyd_async/sim/demo/_pattern_detector/_pattern_detector_writer.py +0 -41
  151. ophyd_async/sim/demo/_pattern_detector/_pattern_generator.py +0 -207
  152. ophyd_async/sim/demo/_sim_motor.py +0 -107
  153. ophyd_async/sim/testing/__init__.py +0 -0
  154. ophyd_async-0.9.0a1.dist-info/RECORD +0 -119
  155. ophyd_async-0.9.0a1.dist-info/entry_points.txt +0 -2
  156. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info/licenses}/LICENSE +0 -0
  157. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info}/top_level.txt +0 -0
ophyd_async/__init__.py CHANGED
@@ -1,12 +1,9 @@
1
- """Top level API.
2
-
3
- .. data:: __version__
4
- :type: str
5
-
6
- Version number as calculated by https://github.com/pypa/setuptools_scm
7
- """
1
+ """Top level API."""
8
2
 
9
3
  from . import core
10
- from ._version import __version__
4
+ from ._version import version
5
+
6
+ __version__ = version
7
+ """Version number as calculated by https://github.com/pypa/setuptools_scm"""
11
8
 
12
9
  __all__ = ["__version__", "core"]
@@ -0,0 +1,12 @@
1
+ import re
2
+
3
+ from autodoc2.render.myst_ import MystRenderer
4
+
5
+ DOTTED_MODULE = re.compile(r"((?:[a-zA-Z_][a-zA-Z_0-9]+\.)+)")
6
+
7
+
8
+ class ShortenedNamesRenderer(MystRenderer):
9
+ def format_annotation(self, annotation):
10
+ if annotation:
11
+ annotation = DOTTED_MODULE.sub(r"~\1", annotation)
12
+ return super().format_annotation(annotation)
ophyd_async/_version.py CHANGED
@@ -1,8 +1,13 @@
1
- # file generated by setuptools_scm
1
+ # file generated by setuptools-scm
2
2
  # don't change, don't track in version control
3
+
4
+ __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
5
+
3
6
  TYPE_CHECKING = False
4
7
  if TYPE_CHECKING:
5
- from typing import Tuple, Union
8
+ from typing import Tuple
9
+ from typing import Union
10
+
6
11
  VERSION_TUPLE = Tuple[Union[int, str], ...]
7
12
  else:
8
13
  VERSION_TUPLE = object
@@ -12,5 +17,5 @@ __version__: str
12
17
  __version_tuple__: VERSION_TUPLE
13
18
  version_tuple: VERSION_TUPLE
14
19
 
15
- __version__ = version = '0.9.0a1'
16
- __version_tuple__ = version_tuple = (0, 9, 0)
20
+ __version__ = version = '0.10.0a1'
21
+ __version_tuple__ = version_tuple = (0, 10, 0)
@@ -1,3 +1,12 @@
1
+ """The building blocks for making devices."""
2
+
3
+ from ._derived_signal import (
4
+ DerivedSignalFactory,
5
+ derived_signal_r,
6
+ derived_signal_rw,
7
+ derived_signal_w,
8
+ )
9
+ from ._derived_signal_backend import Transform
1
10
  from ._detector import (
2
11
  DetectorController,
3
12
  DetectorTrigger,
@@ -5,23 +14,13 @@ from ._detector import (
5
14
  StandardDetector,
6
15
  TriggerInfo,
7
16
  )
8
- from ._device import Device, DeviceCollector, DeviceConnector, DeviceVector
17
+ from ._device import Device, DeviceConnector, DeviceVector, init_devices
9
18
  from ._device_filler import DeviceFiller
10
- from ._device_save_loader import (
11
- all_at_once,
12
- get_signal_values,
13
- load_device,
14
- load_from_yaml,
15
- save_device,
16
- save_to_yaml,
17
- set_signal_values,
18
- walk_rw_signals,
19
- )
20
19
  from ._flyer import FlyerController, StandardFlyer
21
- from ._hdf_dataset import HDFDataset, HDFFile
20
+ from ._hdf_dataset import HDFDatasetDescription, HDFDocumentComposer
22
21
  from ._log import config_ophyd_async_logging
23
22
  from ._mock_signal_backend import MockSignalBackend
24
- from ._protocol import AsyncConfigurable, AsyncReadable, AsyncStageable
23
+ from ._protocol import AsyncConfigurable, AsyncReadable, AsyncStageable, Watcher
25
24
  from ._providers import (
26
25
  AutoIncrementFilenameProvider,
27
26
  AutoIncrementingPathProvider,
@@ -41,7 +40,9 @@ from ._readable import (
41
40
  StandardReadable,
42
41
  StandardReadableFormat,
43
42
  )
43
+ from ._settings import Settings, SettingsProvider
44
44
  from ._signal import (
45
+ Ignore,
45
46
  Signal,
46
47
  SignalConnector,
47
48
  SignalR,
@@ -55,15 +56,19 @@ from ._signal import (
55
56
  soft_signal_r_and_setter,
56
57
  soft_signal_rw,
57
58
  wait_for_value,
59
+ walk_config_signals,
60
+ walk_rw_signals,
58
61
  )
59
62
  from ._signal_backend import (
60
63
  Array1D,
64
+ DTypeScalar_co,
61
65
  SignalBackend,
62
66
  SignalDatatype,
63
67
  SignalDatatypeT,
68
+ SignalMetadata,
64
69
  make_datakey,
65
70
  )
66
- from ._soft_signal_backend import SignalMetadata, SoftSignalBackend
71
+ from ._soft_signal_backend import SoftSignalBackend
67
72
  from ._status import AsyncStatus, WatchableAsyncStatus, completed_status
68
73
  from ._table import Table
69
74
  from ._utils import (
@@ -76,96 +81,119 @@ from ._utils import (
76
81
  Reference,
77
82
  StrictEnum,
78
83
  SubsetEnum,
79
- T,
80
84
  WatcherUpdate,
85
+ gather_dict,
81
86
  get_dtype,
82
87
  get_enum_cls,
83
88
  get_unique,
84
89
  in_micros,
85
90
  wait_for_connection,
86
91
  )
92
+ from ._yaml_settings import YamlSettingsProvider
87
93
 
88
94
  __all__ = [
89
- "DetectorController",
90
- "DetectorTrigger",
91
- "DetectorWriter",
92
- "StandardDetector",
93
- "TriggerInfo",
95
+ # Device
94
96
  "Device",
95
97
  "DeviceConnector",
96
- "DeviceCollector",
97
- "DeviceVector",
98
98
  "DeviceFiller",
99
- "all_at_once",
100
- "get_signal_values",
101
- "load_device",
102
- "load_from_yaml",
103
- "save_device",
104
- "save_to_yaml",
105
- "set_signal_values",
106
- "walk_rw_signals",
107
- "StandardFlyer",
108
- "FlyerController",
109
- "HDFDataset",
110
- "HDFFile",
111
- "config_ophyd_async_logging",
112
- "MockSignalBackend",
113
- "AsyncConfigurable",
99
+ "DeviceVector",
100
+ "init_devices",
101
+ # Protocols
114
102
  "AsyncReadable",
103
+ "AsyncConfigurable",
115
104
  "AsyncStageable",
116
- "AutoIncrementFilenameProvider",
117
- "AutoIncrementingPathProvider",
118
- "FilenameProvider",
119
- "NameProvider",
120
- "PathInfo",
121
- "PathProvider",
122
- "DatasetDescriber",
123
- "StaticFilenameProvider",
124
- "StaticPathProvider",
125
- "UUIDFilenameProvider",
126
- "YMDPathProvider",
127
- "ConfigSignal",
128
- "HintedSignal",
129
- "StandardReadable",
130
- "StandardReadableFormat",
105
+ "Watcher",
106
+ # Status
107
+ "AsyncStatus",
108
+ "WatchableAsyncStatus",
109
+ "WatcherUpdate",
110
+ "completed_status",
111
+ # Signal
131
112
  "Signal",
132
- "SignalConnector",
133
113
  "SignalR",
134
- "SignalRW",
135
114
  "SignalW",
115
+ "SignalRW",
136
116
  "SignalX",
137
- "observe_value",
138
- "observe_signals_value",
139
- "set_and_wait_for_value",
140
- "set_and_wait_for_other_value",
141
- "soft_signal_r_and_setter",
142
- "soft_signal_rw",
143
- "wait_for_value",
144
- "Array1D",
145
117
  "SignalBackend",
146
- "make_datakey",
147
- "StrictEnum",
148
- "SubsetEnum",
118
+ "SignalConnector",
119
+ # Signal Types
149
120
  "SignalDatatype",
150
121
  "SignalDatatypeT",
122
+ "DTypeScalar_co",
123
+ "Array1D",
124
+ "StrictEnum",
125
+ "SubsetEnum",
126
+ "Table",
151
127
  "SignalMetadata",
128
+ # Soft signal
152
129
  "SoftSignalBackend",
153
- "AsyncStatus",
154
- "WatchableAsyncStatus",
155
- "DEFAULT_TIMEOUT",
156
- "CalculatableTimeout",
157
- "Callback",
130
+ "soft_signal_r_and_setter",
131
+ "soft_signal_rw",
132
+ # Mock signal
158
133
  "LazyMock",
134
+ "MockSignalBackend",
135
+ # Signal utilities
136
+ "observe_value",
137
+ "observe_signals_value",
138
+ "wait_for_value",
139
+ "set_and_wait_for_value",
140
+ "set_and_wait_for_other_value",
141
+ "walk_rw_signals",
142
+ "walk_config_signals",
143
+ # Readable
144
+ "StandardReadable",
145
+ "StandardReadableFormat",
146
+ # Detector
147
+ "StandardDetector",
148
+ "TriggerInfo",
149
+ "DetectorTrigger",
150
+ "DetectorController",
151
+ "DetectorWriter",
152
+ # Path
153
+ "PathInfo",
154
+ "PathProvider",
155
+ "StaticPathProvider",
156
+ "AutoIncrementingPathProvider",
157
+ "YMDPathProvider",
158
+ "FilenameProvider",
159
+ "StaticFilenameProvider",
160
+ "AutoIncrementFilenameProvider",
161
+ "UUIDFilenameProvider",
162
+ # Datatset
163
+ "NameProvider",
164
+ "DatasetDescriber",
165
+ "HDFDatasetDescription",
166
+ "HDFDocumentComposer",
167
+ # Flyer
168
+ "StandardFlyer",
169
+ "FlyerController",
170
+ # Settings
171
+ "Settings",
172
+ "SettingsProvider",
173
+ "YamlSettingsProvider",
174
+ # Utils
175
+ "config_ophyd_async_logging",
159
176
  "CALCULATE_TIMEOUT",
177
+ "CalculatableTimeout",
178
+ "DEFAULT_TIMEOUT",
179
+ "Callback",
160
180
  "NotConnected",
161
181
  "Reference",
162
- "Table",
163
- "T",
164
- "WatcherUpdate",
182
+ "gather_dict",
165
183
  "get_dtype",
166
184
  "get_enum_cls",
167
185
  "get_unique",
168
186
  "in_micros",
187
+ "make_datakey",
169
188
  "wait_for_connection",
170
- "completed_status",
189
+ "Ignore",
190
+ # Derived signal
191
+ "derived_signal_r",
192
+ "derived_signal_rw",
193
+ "derived_signal_w",
194
+ "Transform",
195
+ "DerivedSignalFactory",
196
+ # Back compat - delete before 1.0
197
+ "ConfigSignal",
198
+ "HintedSignal",
171
199
  ]
@@ -0,0 +1,271 @@
1
+ from collections.abc import Awaitable, Callable
2
+ from typing import Any, Generic, get_type_hints
3
+
4
+ from ._derived_signal_backend import (
5
+ DerivedSignalBackend,
6
+ SignalTransformer,
7
+ Transform,
8
+ TransformT,
9
+ )
10
+ from ._device import Device
11
+ from ._signal import SignalR, SignalRW, SignalT, SignalW
12
+ from ._signal_backend import SignalDatatypeT
13
+
14
+
15
+ class DerivedSignalFactory(Generic[TransformT]):
16
+ """A factory that makes Derived Signals using a many-many Transform.
17
+
18
+ :param transform_cls:
19
+ The Transform subclass that will be filled in with parameters in order to
20
+ transform raw to derived and derived to raw.
21
+ :param set_derived:
22
+ An optional async function that takes the output of
23
+ `transform_cls.raw_to_derived` and applies it to the raw devices.
24
+ :param raw_and_transform_devices:
25
+ Devices whose values will be passed as parameters to the `transform_cls`,
26
+ and as arguments to `transform_cls.raw_to_derived`.
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ transform_cls: type[TransformT],
32
+ set_derived: Callable[..., Awaitable[None]] | None = None,
33
+ **raw_and_transform_devices,
34
+ ):
35
+ self._set_derived = set_derived
36
+ # Check the raw and transform devices match the input arguments of the Transform
37
+ if transform_cls is not Transform:
38
+ expected = list(transform_cls.model_fields) + [
39
+ x
40
+ for x in get_type_hints(transform_cls.raw_to_derived)
41
+ if x not in ["self", "return"]
42
+ ]
43
+ if set(expected) != set(raw_and_transform_devices):
44
+ msg = (
45
+ f"Expected devices to be passed as keyword arguments {expected}, "
46
+ f"got {list(raw_and_transform_devices)}"
47
+ )
48
+ raise TypeError(msg)
49
+ set_derived_datatype = (
50
+ _get_first_arg_datatype(set_derived) if set_derived else None
51
+ )
52
+ self._transformer = SignalTransformer(
53
+ transform_cls,
54
+ set_derived,
55
+ set_derived_datatype,
56
+ **raw_and_transform_devices,
57
+ )
58
+
59
+ def _make_signal(
60
+ self,
61
+ signal_cls: type[SignalT],
62
+ datatype: type[SignalDatatypeT],
63
+ name: str,
64
+ units: str | None = None,
65
+ precision: int | None = None,
66
+ ) -> SignalT:
67
+ # Check up front the raw_devices are of the right type for what the signal_cls
68
+ # supports
69
+ if issubclass(signal_cls, SignalR):
70
+ self._transformer.raw_and_transform_readables # noqa: B018
71
+ self._transformer.raw_and_transform_subscribables # noqa: B018
72
+ if issubclass(signal_cls, SignalW) and not self._set_derived:
73
+ msg = (
74
+ f"Must define a set_derived method to support derived "
75
+ f"{signal_cls.__name__}s"
76
+ )
77
+ raise ValueError(msg)
78
+ if issubclass(signal_cls, SignalRW):
79
+ self._transformer.raw_locatables # noqa: B018
80
+ backend = DerivedSignalBackend(
81
+ datatype, name, self._transformer, units, precision
82
+ )
83
+ return signal_cls(backend)
84
+
85
+ def derived_signal_r(
86
+ self,
87
+ datatype: type[SignalDatatypeT],
88
+ name: str,
89
+ units: str | None = None,
90
+ precision: int | None = None,
91
+ ) -> SignalR[SignalDatatypeT]:
92
+ """Create a read only derived signal.
93
+
94
+ :param datatype: The datatype of the derived signal value
95
+ :param name:
96
+ The name of the derived signal. Should be a key within the
97
+ return value of `transform_cls.raw_to_derived()`.
98
+ :param units: Engineering units for the derived signal
99
+ :param precision: Number of digits after the decimal place to display
100
+ """
101
+ return self._make_signal(SignalR, datatype, name, units, precision)
102
+
103
+ def derived_signal_w(
104
+ self,
105
+ datatype: type[SignalDatatypeT],
106
+ name: str,
107
+ units: str | None = None,
108
+ precision: int | None = None,
109
+ ) -> SignalW[SignalDatatypeT]:
110
+ """Create a write only derived signal.
111
+
112
+ :param datatype: The datatype of the derived signal value
113
+ :param name:
114
+ The name of the derived signal. Should be a key within the
115
+ return value of `transform_cls.raw_to_derived()`.
116
+ :param units: Engineering units for the derived signal
117
+ :param precision: Number of digits after the decimal place to display
118
+ """
119
+ return self._make_signal(SignalW, datatype, name, units, precision)
120
+
121
+ def derived_signal_rw(
122
+ self,
123
+ datatype: type[SignalDatatypeT],
124
+ name: str,
125
+ units: str | None = None,
126
+ precision: int | None = None,
127
+ ) -> SignalRW[SignalDatatypeT]:
128
+ """Create a read-write derived signal.
129
+
130
+ :param datatype: The datatype of the derived signal value
131
+ :param name:
132
+ The name of the derived signal. Should be a key within the
133
+ return value of `transform_cls.raw_to_derived()`.
134
+ :param units: Engineering units for the derived signal
135
+ :param precision: Number of digits after the decimal place to display
136
+ """
137
+ return self._make_signal(SignalRW, datatype, name, units, precision)
138
+
139
+ async def transform(self) -> TransformT:
140
+ """Return an instance of `transform_cls` with all the parameters filled in."""
141
+ return await self._transformer.get_transform()
142
+
143
+
144
+ def _get_return_datatype(func: Callable[..., SignalDatatypeT]) -> type[SignalDatatypeT]:
145
+ args = get_type_hints(func)
146
+ if "return" not in args:
147
+ msg = f"{func} does not have a type hint for it's return value"
148
+ raise TypeError(msg)
149
+ return args["return"]
150
+
151
+
152
+ def _get_first_arg_datatype(
153
+ func: Callable[[SignalDatatypeT], Any],
154
+ ) -> type[SignalDatatypeT]:
155
+ args = get_type_hints(func)
156
+ args.pop("return", None)
157
+ if not args:
158
+ msg = f"{func} does not have a type hinted argument"
159
+ raise TypeError(msg)
160
+ return list(args.values())[0]
161
+
162
+
163
+ def _make_factory(
164
+ raw_to_derived: Callable[..., SignalDatatypeT] | None = None,
165
+ set_derived: Callable[[SignalDatatypeT], Awaitable[None]] | None = None,
166
+ raw_devices: dict[str, Device] | None = None,
167
+ ) -> DerivedSignalFactory:
168
+ if raw_to_derived:
169
+
170
+ class DerivedTransform(Transform):
171
+ def raw_to_derived(self, **kwargs) -> dict[str, SignalDatatypeT]:
172
+ return {"value": raw_to_derived(**kwargs)}
173
+
174
+ # Update the signature for raw_to_derived to match what we are passed as this
175
+ # will be checked in DerivedSignalFactory
176
+ DerivedTransform.raw_to_derived.__annotations__ = raw_to_derived.__annotations__
177
+
178
+ return DerivedSignalFactory(
179
+ DerivedTransform, set_derived=set_derived, **(raw_devices or {})
180
+ )
181
+ else:
182
+ return DerivedSignalFactory(Transform, set_derived=set_derived)
183
+
184
+
185
+ def derived_signal_r(
186
+ raw_to_derived: Callable[..., SignalDatatypeT],
187
+ derived_units: str | None = None,
188
+ derived_precision: int | None = None,
189
+ **raw_devices: Device,
190
+ ) -> SignalR[SignalDatatypeT]:
191
+ """Create a read only derived signal.
192
+
193
+ :param raw_to_derived:
194
+ A function that takes the raw values as individual keyword arguments and
195
+ returns the derived value.
196
+ :param derived_units: Engineering units for the derived signal
197
+ :param derived_precision: Number of digits after the decimal place to display
198
+ :param raw_devices:
199
+ A dictionary of Devices to provide the values for raw_to_derived. The names
200
+ of these arguments must match the arguments of raw_to_derived.
201
+ """
202
+ factory = _make_factory(raw_to_derived=raw_to_derived, raw_devices=raw_devices)
203
+ return factory.derived_signal_r(
204
+ datatype=_get_return_datatype(raw_to_derived),
205
+ name="value",
206
+ units=derived_units,
207
+ precision=derived_precision,
208
+ )
209
+
210
+
211
+ def derived_signal_rw(
212
+ raw_to_derived: Callable[..., SignalDatatypeT],
213
+ set_derived: Callable[[SignalDatatypeT], Awaitable[None]],
214
+ derived_units: str | None = None,
215
+ derived_precision: int | None = None,
216
+ **raw_devices: Device,
217
+ ) -> SignalRW[SignalDatatypeT]:
218
+ """Create a read-write derived signal.
219
+
220
+ :param raw_to_derived:
221
+ A function that takes the raw values as individual keyword arguments and
222
+ returns the derived value.
223
+ :param set_derived:
224
+ A function that takes the derived value and sets the raw signals. It can
225
+ either be an async function, or return an [](#AsyncStatus)
226
+ :param derived_units: Engineering units for the derived signal
227
+ :param derived_precision: Number of digits after the decimal place to display
228
+ :param raw_devices:
229
+ A dictionary of Devices to provide the values for raw_to_derived. The names
230
+ of these arguments must match the arguments of raw_to_derived.
231
+ """
232
+ raw_to_derived_datatype = _get_return_datatype(raw_to_derived)
233
+ set_derived_datatype = _get_first_arg_datatype(set_derived)
234
+ if raw_to_derived_datatype != set_derived_datatype:
235
+ msg = (
236
+ f"{raw_to_derived} has datatype {raw_to_derived_datatype} "
237
+ f"!= {set_derived_datatype} dataype {set_derived_datatype}"
238
+ )
239
+ raise TypeError(msg)
240
+
241
+ factory = _make_factory(
242
+ raw_to_derived=raw_to_derived, set_derived=set_derived, raw_devices=raw_devices
243
+ )
244
+ return factory.derived_signal_rw(
245
+ datatype=raw_to_derived_datatype,
246
+ name="value",
247
+ units=derived_units,
248
+ precision=derived_precision,
249
+ )
250
+
251
+
252
+ def derived_signal_w(
253
+ set_derived: Callable[[SignalDatatypeT], Awaitable[None]],
254
+ derived_units: str | None = None,
255
+ derived_precision: int | None = None,
256
+ ) -> SignalW[SignalDatatypeT]:
257
+ """Create a write only derived signal.
258
+
259
+ :param set_derived:
260
+ A function that takes the derived value and sets the raw signals. It can
261
+ either be an async function, or return an [](#AsyncStatus)
262
+ :param derived_units: Engineering units for the derived signal
263
+ :param derived_precision: Number of digits after the decimal place to display
264
+ """
265
+ factory = _make_factory(set_derived=set_derived)
266
+ return factory.derived_signal_w(
267
+ datatype=_get_first_arg_datatype(set_derived),
268
+ name="value",
269
+ units=derived_units,
270
+ precision=derived_precision,
271
+ )