ophyd-async 0.9.0a2__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 (151) 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 +97 -62
  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 +106 -125
  8. ophyd_async/core/_device.py +69 -63
  9. ophyd_async/core/_device_filler.py +65 -1
  10. ophyd_async/core/_flyer.py +14 -5
  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 +44 -35
  17. ophyd_async/core/_settings.py +36 -27
  18. ophyd_async/core/_signal.py +262 -170
  19. ophyd_async/core/_signal_backend.py +56 -13
  20. ophyd_async/core/_soft_signal_backend.py +16 -11
  21. ophyd_async/core/_status.py +72 -24
  22. ophyd_async/core/_table.py +37 -8
  23. ophyd_async/core/_utils.py +96 -49
  24. ophyd_async/core/_yaml_settings.py +2 -0
  25. ophyd_async/epics/__init__.py +1 -0
  26. ophyd_async/epics/adandor/_andor.py +2 -2
  27. ophyd_async/epics/adandor/_andor_controller.py +4 -2
  28. ophyd_async/epics/adandor/_andor_io.py +2 -4
  29. ophyd_async/epics/adaravis/__init__.py +5 -0
  30. ophyd_async/epics/adaravis/_aravis.py +4 -8
  31. ophyd_async/epics/adaravis/_aravis_controller.py +20 -43
  32. ophyd_async/epics/adaravis/_aravis_io.py +13 -28
  33. ophyd_async/epics/adcore/__init__.py +23 -8
  34. ophyd_async/epics/adcore/_core_detector.py +42 -2
  35. ophyd_async/epics/adcore/_core_io.py +124 -99
  36. ophyd_async/epics/adcore/_core_logic.py +106 -27
  37. ophyd_async/epics/adcore/_core_writer.py +12 -8
  38. ophyd_async/epics/adcore/_hdf_writer.py +21 -38
  39. ophyd_async/epics/adcore/_single_trigger.py +2 -2
  40. ophyd_async/epics/adcore/_utils.py +2 -2
  41. ophyd_async/epics/adkinetix/__init__.py +2 -1
  42. ophyd_async/epics/adkinetix/_kinetix.py +3 -3
  43. ophyd_async/epics/adkinetix/_kinetix_controller.py +4 -2
  44. ophyd_async/epics/adkinetix/_kinetix_io.py +12 -13
  45. ophyd_async/epics/adpilatus/__init__.py +5 -0
  46. ophyd_async/epics/adpilatus/_pilatus.py +1 -1
  47. ophyd_async/epics/adpilatus/_pilatus_controller.py +5 -24
  48. ophyd_async/epics/adpilatus/_pilatus_io.py +11 -9
  49. ophyd_async/epics/adsimdetector/__init__.py +8 -1
  50. ophyd_async/epics/adsimdetector/_sim.py +4 -14
  51. ophyd_async/epics/adsimdetector/_sim_controller.py +17 -0
  52. ophyd_async/epics/adsimdetector/_sim_io.py +10 -0
  53. ophyd_async/epics/advimba/__init__.py +10 -1
  54. ophyd_async/epics/advimba/_vimba.py +3 -2
  55. ophyd_async/epics/advimba/_vimba_controller.py +4 -2
  56. ophyd_async/epics/advimba/_vimba_io.py +23 -28
  57. ophyd_async/epics/core/_aioca.py +35 -16
  58. ophyd_async/epics/core/_epics_connector.py +4 -0
  59. ophyd_async/epics/core/_epics_device.py +2 -0
  60. ophyd_async/epics/core/_p4p.py +10 -2
  61. ophyd_async/epics/core/_pvi_connector.py +65 -8
  62. ophyd_async/epics/core/_signal.py +51 -51
  63. ophyd_async/epics/core/_util.py +4 -4
  64. ophyd_async/epics/demo/__init__.py +16 -0
  65. ophyd_async/epics/demo/__main__.py +31 -0
  66. ophyd_async/epics/demo/_ioc.py +32 -0
  67. ophyd_async/epics/demo/_motor.py +82 -0
  68. ophyd_async/epics/demo/_point_detector.py +42 -0
  69. ophyd_async/epics/demo/_point_detector_channel.py +22 -0
  70. ophyd_async/epics/demo/_stage.py +15 -0
  71. ophyd_async/epics/{sim/mover.db → demo/motor.db} +2 -1
  72. ophyd_async/epics/demo/point_detector.db +59 -0
  73. ophyd_async/epics/demo/point_detector_channel.db +21 -0
  74. ophyd_async/epics/eiger/_eiger.py +1 -3
  75. ophyd_async/epics/eiger/_eiger_controller.py +11 -4
  76. ophyd_async/epics/eiger/_eiger_io.py +2 -0
  77. ophyd_async/epics/eiger/_odin_io.py +1 -2
  78. ophyd_async/epics/motor.py +65 -28
  79. ophyd_async/epics/signal.py +4 -1
  80. ophyd_async/epics/testing/_example_ioc.py +21 -9
  81. ophyd_async/epics/testing/_utils.py +3 -0
  82. ophyd_async/epics/testing/test_records.db +8 -0
  83. ophyd_async/epics/testing/test_records_pva.db +17 -16
  84. ophyd_async/fastcs/__init__.py +1 -0
  85. ophyd_async/fastcs/core.py +6 -0
  86. ophyd_async/fastcs/odin/__init__.py +1 -0
  87. ophyd_async/fastcs/panda/__init__.py +8 -6
  88. ophyd_async/fastcs/panda/_block.py +29 -9
  89. ophyd_async/fastcs/panda/_control.py +5 -0
  90. ophyd_async/fastcs/panda/_hdf_panda.py +2 -0
  91. ophyd_async/fastcs/panda/_table.py +9 -6
  92. ophyd_async/fastcs/panda/_trigger.py +23 -9
  93. ophyd_async/fastcs/panda/_writer.py +27 -30
  94. ophyd_async/plan_stubs/__init__.py +2 -0
  95. ophyd_async/plan_stubs/_ensure_connected.py +1 -0
  96. ophyd_async/plan_stubs/_fly.py +2 -4
  97. ophyd_async/plan_stubs/_nd_attributes.py +2 -0
  98. ophyd_async/plan_stubs/_panda.py +1 -0
  99. ophyd_async/plan_stubs/_settings.py +43 -16
  100. ophyd_async/plan_stubs/_utils.py +3 -0
  101. ophyd_async/plan_stubs/_wait_for_awaitable.py +1 -1
  102. ophyd_async/sim/__init__.py +24 -14
  103. ophyd_async/sim/__main__.py +43 -0
  104. ophyd_async/sim/_blob_detector.py +33 -0
  105. ophyd_async/sim/_blob_detector_controller.py +48 -0
  106. ophyd_async/sim/_blob_detector_writer.py +105 -0
  107. ophyd_async/sim/_mirror_horizontal.py +46 -0
  108. ophyd_async/sim/_mirror_vertical.py +74 -0
  109. ophyd_async/sim/_motor.py +233 -0
  110. ophyd_async/sim/_pattern_generator.py +124 -0
  111. ophyd_async/sim/_point_detector.py +86 -0
  112. ophyd_async/sim/_stage.py +19 -0
  113. ophyd_async/tango/__init__.py +1 -0
  114. ophyd_async/tango/core/__init__.py +6 -1
  115. ophyd_async/tango/core/_base_device.py +41 -33
  116. ophyd_async/tango/core/_converters.py +81 -0
  117. ophyd_async/tango/core/_signal.py +18 -32
  118. ophyd_async/tango/core/_tango_readable.py +2 -19
  119. ophyd_async/tango/core/_tango_transport.py +136 -60
  120. ophyd_async/tango/core/_utils.py +47 -0
  121. ophyd_async/tango/{sim → demo}/_counter.py +2 -0
  122. ophyd_async/tango/{sim → demo}/_detector.py +2 -0
  123. ophyd_async/tango/{sim → demo}/_mover.py +5 -4
  124. ophyd_async/tango/{sim → demo}/_tango/_servers.py +4 -0
  125. ophyd_async/tango/testing/__init__.py +6 -0
  126. ophyd_async/tango/testing/_one_of_everything.py +200 -0
  127. ophyd_async/testing/__init__.py +29 -7
  128. ophyd_async/testing/_assert.py +137 -81
  129. ophyd_async/testing/_mock_signal_utils.py +56 -70
  130. ophyd_async/testing/_one_of_everything.py +41 -21
  131. ophyd_async/testing/_single_derived.py +87 -0
  132. ophyd_async/testing/_utils.py +3 -0
  133. {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a1.dist-info}/METADATA +25 -26
  134. ophyd_async-0.10.0a1.dist-info/RECORD +149 -0
  135. {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a1.dist-info}/WHEEL +1 -1
  136. ophyd_async/epics/sim/__init__.py +0 -54
  137. ophyd_async/epics/sim/_ioc.py +0 -29
  138. ophyd_async/epics/sim/_mover.py +0 -101
  139. ophyd_async/epics/sim/_sensor.py +0 -37
  140. ophyd_async/epics/sim/sensor.db +0 -19
  141. ophyd_async/sim/_pattern_detector/__init__.py +0 -13
  142. ophyd_async/sim/_pattern_detector/_pattern_detector.py +0 -42
  143. ophyd_async/sim/_pattern_detector/_pattern_detector_controller.py +0 -69
  144. ophyd_async/sim/_pattern_detector/_pattern_detector_writer.py +0 -41
  145. ophyd_async/sim/_pattern_detector/_pattern_generator.py +0 -214
  146. ophyd_async/sim/_sim_motor.py +0 -107
  147. ophyd_async-0.9.0a2.dist-info/RECORD +0 -129
  148. /ophyd_async/tango/{sim → demo}/__init__.py +0 -0
  149. /ophyd_async/tango/{sim → demo}/_tango/__init__.py +0 -0
  150. {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a1.dist-info/licenses}/LICENSE +0 -0
  151. {ophyd_async-0.9.0a2.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.0a2'
16
- __version_tuple__ = version_tuple = (0, 9, 0)
20
+ __version__ = version = '0.10.0a1'
21
+ __version_tuple__ = version_tuple = (0, 10, 0)
@@ -1,6 +1,14 @@
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
- DetectorControllerT,
4
12
  DetectorTrigger,
5
13
  DetectorWriter,
6
14
  StandardDetector,
@@ -9,10 +17,10 @@ from ._detector import (
9
17
  from ._device import Device, DeviceConnector, DeviceVector, init_devices
10
18
  from ._device_filler import DeviceFiller
11
19
  from ._flyer import FlyerController, StandardFlyer
12
- from ._hdf_dataset import HDFDataset, HDFFile
20
+ from ._hdf_dataset import HDFDatasetDescription, HDFDocumentComposer
13
21
  from ._log import config_ophyd_async_logging
14
22
  from ._mock_signal_backend import MockSignalBackend
15
- from ._protocol import AsyncConfigurable, AsyncReadable, AsyncStageable
23
+ from ._protocol import AsyncConfigurable, AsyncReadable, AsyncStageable, Watcher
16
24
  from ._providers import (
17
25
  AutoIncrementFilenameProvider,
18
26
  AutoIncrementingPathProvider,
@@ -34,6 +42,7 @@ from ._readable import (
34
42
  )
35
43
  from ._settings import Settings, SettingsProvider
36
44
  from ._signal import (
45
+ Ignore,
37
46
  Signal,
38
47
  SignalConnector,
39
48
  SignalR,
@@ -47,6 +56,7 @@ from ._signal import (
47
56
  soft_signal_r_and_setter,
48
57
  soft_signal_rw,
49
58
  wait_for_value,
59
+ walk_config_signals,
50
60
  walk_rw_signals,
51
61
  )
52
62
  from ._signal_backend import (
@@ -55,9 +65,10 @@ from ._signal_backend import (
55
65
  SignalBackend,
56
66
  SignalDatatype,
57
67
  SignalDatatypeT,
68
+ SignalMetadata,
58
69
  make_datakey,
59
70
  )
60
- from ._soft_signal_backend import SignalMetadata, SoftSignalBackend
71
+ from ._soft_signal_backend import SoftSignalBackend
61
72
  from ._status import AsyncStatus, WatchableAsyncStatus, completed_status
62
73
  from ._table import Table
63
74
  from ._utils import (
@@ -70,8 +81,8 @@ from ._utils import (
70
81
  Reference,
71
82
  StrictEnum,
72
83
  SubsetEnum,
73
- T,
74
84
  WatcherUpdate,
85
+ gather_dict,
75
86
  get_dtype,
76
87
  get_enum_cls,
77
88
  get_unique,
@@ -81,84 +92,108 @@ from ._utils import (
81
92
  from ._yaml_settings import YamlSettingsProvider
82
93
 
83
94
  __all__ = [
84
- "DetectorController",
85
- "DetectorControllerT",
86
- "DetectorTrigger",
87
- "DetectorWriter",
88
- "StandardDetector",
89
- "TriggerInfo",
95
+ # Device
90
96
  "Device",
91
97
  "DeviceConnector",
92
- "init_devices",
93
- "DeviceVector",
94
98
  "DeviceFiller",
95
- "StandardFlyer",
96
- "FlyerController",
97
- "HDFDataset",
98
- "HDFFile",
99
- "config_ophyd_async_logging",
100
- "MockSignalBackend",
101
- "AsyncConfigurable",
99
+ "DeviceVector",
100
+ "init_devices",
101
+ # Protocols
102
102
  "AsyncReadable",
103
+ "AsyncConfigurable",
103
104
  "AsyncStageable",
104
- "AutoIncrementFilenameProvider",
105
- "AutoIncrementingPathProvider",
106
- "FilenameProvider",
107
- "NameProvider",
108
- "PathInfo",
109
- "PathProvider",
110
- "DatasetDescriber",
111
- "StaticFilenameProvider",
112
- "StaticPathProvider",
113
- "UUIDFilenameProvider",
114
- "YMDPathProvider",
115
- "ConfigSignal",
116
- "HintedSignal",
117
- "StandardReadable",
118
- "StandardReadableFormat",
119
- "Settings",
120
- "SettingsProvider",
105
+ "Watcher",
106
+ # Status
107
+ "AsyncStatus",
108
+ "WatchableAsyncStatus",
109
+ "WatcherUpdate",
110
+ "completed_status",
111
+ # Signal
121
112
  "Signal",
122
- "SignalConnector",
123
113
  "SignalR",
124
- "SignalRW",
125
114
  "SignalW",
115
+ "SignalRW",
126
116
  "SignalX",
127
- "observe_value",
128
- "observe_signals_value",
129
- "set_and_wait_for_value",
130
- "set_and_wait_for_other_value",
131
- "soft_signal_r_and_setter",
132
- "soft_signal_rw",
133
- "wait_for_value",
134
- "walk_rw_signals",
135
- "Array1D",
136
- "DTypeScalar_co",
137
117
  "SignalBackend",
138
- "make_datakey",
139
- "StrictEnum",
140
- "SubsetEnum",
118
+ "SignalConnector",
119
+ # Signal Types
141
120
  "SignalDatatype",
142
121
  "SignalDatatypeT",
122
+ "DTypeScalar_co",
123
+ "Array1D",
124
+ "StrictEnum",
125
+ "SubsetEnum",
126
+ "Table",
143
127
  "SignalMetadata",
128
+ # Soft signal
144
129
  "SoftSignalBackend",
145
- "AsyncStatus",
146
- "WatchableAsyncStatus",
147
- "DEFAULT_TIMEOUT",
148
- "CalculatableTimeout",
149
- "Callback",
130
+ "soft_signal_r_and_setter",
131
+ "soft_signal_rw",
132
+ # Mock signal
150
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",
151
176
  "CALCULATE_TIMEOUT",
177
+ "CalculatableTimeout",
178
+ "DEFAULT_TIMEOUT",
179
+ "Callback",
152
180
  "NotConnected",
153
181
  "Reference",
154
- "Table",
155
- "T",
156
- "WatcherUpdate",
182
+ "gather_dict",
157
183
  "get_dtype",
158
184
  "get_enum_cls",
159
185
  "get_unique",
160
186
  "in_micros",
187
+ "make_datakey",
161
188
  "wait_for_connection",
162
- "completed_status",
163
- "YamlSettingsProvider",
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",
164
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
+ )