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
@@ -17,25 +17,34 @@ from ._utils import merge_gathered_dicts
17
17
  class StandardReadableFormat(Enum):
18
18
  """Declare how a `Device` should contribute to the `StandardReadable` verbs."""
19
19
 
20
- #: Detect which verbs the child supports and contribute to:
21
- #:
22
- #: - ``read()``, ``describe()`` if it is `bluesky.protocols.Readable`
23
- #: - ``read_configuration()``, ``describe_configuration()`` if it is
24
- #: `bluesky.protocols.Configurable`
25
- #: - ``stage()``, ``unstage()`` if it is `bluesky.protocols.Stageable`
26
- #: - ``hints`` if it `bluesky.protocols.HasHints`
27
20
  CHILD = "CHILD"
28
- #: Contribute the `Signal` value to ``read_configuration()`` and
29
- #: ``describe_configuration()``
21
+ """Detect which verbs the child supports and contribute to:
22
+
23
+ - `read()`, `describe()` if it is [](#bluesky.protocols.Readable)
24
+ - `read_configuration()`, `describe_configuration()` if it is
25
+ [](#bluesky.protocols.Configurable)
26
+ - `stage()`, `unstage()` if it is [](#bluesky.protocols.Stageable)
27
+ - `hints` if it [](#bluesky.protocols.HasHints)
28
+ """
29
+
30
30
  CONFIG_SIGNAL = "CONFIG_SIGNAL"
31
- #: Contribute the monitored `Signal` value to ``read()`` and ``describe()``` and
32
- #: put the signal name in ``hints``
31
+ """Contribute the `Signal` value to `read_configuration()` and
32
+ `describe_configuration()`
33
+ """
34
+
33
35
  HINTED_SIGNAL = "HINTED_SIGNAL"
34
- #: Contribute the uncached `Signal` value to ``read()`` and ``describe()```
36
+ """Contribute the monitored `Signal` value to `read()` and `describe()` and
37
+ put the signal name in `hints`
38
+ """
39
+
35
40
  UNCACHED_SIGNAL = "UNCACHED_SIGNAL"
36
- #: Contribute the uncached `Signal` value to ``read()`` and ``describe()``` and
37
- #: put the signal name in ``hints``
41
+ """Contribute the uncached `Signal` value to `read()` and `describe()`
42
+ """
43
+
38
44
  HINTED_UNCACHED_SIGNAL = "HINTED_UNCACHED_SIGNAL"
45
+ """Contribute the uncached `Signal` value to `read()` and `describe()` and
46
+ put the signal name in `hints`
47
+ """
39
48
 
40
49
  def __call__(self, parent: Device, child: Device):
41
50
  if not isinstance(parent, StandardReadable):
@@ -74,11 +83,15 @@ HintedSignal.uncached = _compat_format(
74
83
  class StandardReadable(
75
84
  Device, AsyncReadable, AsyncConfigurable, AsyncStageable, HasHints
76
85
  ):
77
- """Device that owns its children and provides useful default behavior.
86
+ """Device that provides selected child Device values in `read()`.
87
+
88
+ Provides the ability for children to be registered to:
89
+ - Participate in `stage()` and `unstage()`
90
+ - Provide their value in `read()` and `describe()
91
+ - Provide their value in `read_configuration()` and `describe_configuration()
92
+ - Select a value to appear in `hints`
78
93
 
79
- - When its name is set it renames child Devices
80
- - Signals can be registered for read() and read_configuration()
81
- - These signals will be subscribed for read() between stage() and unstage()
94
+ The behavior is customized with a [](#StandardReadableFormat)
82
95
  """
83
96
 
84
97
  # These must be immutable types to avoid accidental sharing between
@@ -119,33 +132,35 @@ class StandardReadable(
119
132
  hints: Hints = {}
120
133
  for new_hint in self._has_hints:
121
134
  # Merge the existing and new hints, based on the type of the value.
122
- # This avoids default dict merge behaviour that overrides the values;
135
+ # This avoids default dict merge behavior that overrides the values;
123
136
  # we want to combine them when they are Sequences, and ensure they are
124
137
  # identical when string values.
125
138
  for key, value in new_hint.hints.items():
139
+ # fail early for unkwon types
126
140
  if isinstance(value, str):
127
141
  if key in hints:
128
- assert (
129
- hints[key] == value # type: ignore[literal-required]
130
- ), f"Hints key {key} value may not be overridden"
142
+ if hints[key] != value:
143
+ msg = f"Hints key {key} value may not be overridden"
144
+ raise RuntimeError(msg)
131
145
  else:
132
146
  hints[key] = value # type: ignore[literal-required]
133
147
  elif isinstance(value, Sequence):
134
148
  if key in hints:
135
149
  for new_val in value:
136
- assert (
137
- new_val not in hints[key] # type: ignore[literal-required]
138
- ), f"Hint {key} {new_val} overrides existing hint"
150
+ if new_val in hints[key]:
151
+ msg = f"Hint {key} {new_val} overrides existing hint"
152
+ raise RuntimeError(msg)
139
153
  hints[key] = ( # type: ignore[literal-required]
140
154
  hints[key] + value # type: ignore[literal-required]
141
155
  )
142
156
  else:
143
157
  hints[key] = value # type: ignore[literal-required]
144
158
  else:
145
- raise TypeError(
146
- f"{new_hint.name}: Unknown type for value '{value}' "
159
+ msg = (
160
+ f"{new_hint.name}: Unknown type for value '{value}'"
147
161
  f" for key '{key}'"
148
162
  )
163
+ raise TypeError(msg)
149
164
 
150
165
  return hints
151
166
 
@@ -154,13 +169,12 @@ class StandardReadable(
154
169
  self,
155
170
  format: StandardReadableFormat = StandardReadableFormat.CHILD,
156
171
  ) -> Generator[None, None, None]:
157
- """Context manager that calls `add_readables` on child Devices added within.
172
+ """Context manager that calls [](#add_readables) on child Devices added within.
158
173
 
159
- Scans ``self.children()`` on entry and exit to context manager, and calls
160
- `add_readables` on any that are added with the provided
174
+ Scans `self.children()` on entry and exit to context manager, and calls
175
+ `add_readables()` on any that are added with the provided
161
176
  `StandardReadableFormat`.
162
177
  """
163
-
164
178
  dict_copy = dict(self.children())
165
179
 
166
180
  yield
@@ -190,20 +204,22 @@ class StandardReadable(
190
204
  Use output from the given devices to contribute to the verbs of the following
191
205
  interfaces:
192
206
 
193
- - `bluesky.protocols.Readable`
194
- - `bluesky.protocols.Configurable`
195
- - `bluesky.protocols.Stageable`
196
- - `bluesky.protocols.HasHints`
197
-
198
- Parameters
199
- ----------
200
- devices:
201
- The devices to be added
202
- format:
203
- Determines which of the devices functions are added to which verb as per the
204
- `StandardReadableFormat` documentation
207
+ - [](#bluesky.protocols.Readable)
208
+ - [](#bluesky.protocols.Configurable)
209
+ - [](#bluesky.protocols.Stageable)
210
+ - [](#bluesky.protocols.HasHints)
211
+
212
+ :param devices: The devices to be added
213
+ :param format:
214
+ Determines which of the devices functions are added to which verb as
215
+ per the [](#StandardReadableFormat) documentation
205
216
  """
206
217
 
218
+ def assert_device_is_signalr(device: Device) -> SignalR:
219
+ if not isinstance(device, SignalR):
220
+ raise TypeError(f"{device} is not a SignalR")
221
+ return device
222
+
207
223
  for device in devices:
208
224
  match format:
209
225
  case StandardReadableFormat.CHILD:
@@ -218,24 +234,24 @@ class StandardReadable(
218
234
  if isinstance(device, HasHints):
219
235
  self._has_hints += (device,)
220
236
  case StandardReadableFormat.CONFIG_SIGNAL:
221
- assert isinstance(device, SignalR), f"{device} is not a SignalR"
222
- self._describe_config_funcs += (device.describe,)
223
- self._read_config_funcs += (device.read,)
237
+ signalr_device = assert_device_is_signalr(device=device)
238
+ self._describe_config_funcs += (signalr_device.describe,)
239
+ self._read_config_funcs += (signalr_device.read,)
224
240
  case StandardReadableFormat.HINTED_SIGNAL:
225
- assert isinstance(device, SignalR), f"{device} is not a SignalR"
226
- self._describe_funcs += (device.describe,)
227
- self._read_funcs += (device.read,)
228
- self._stageables += (device,)
229
- self._has_hints += (_HintsFromName(device),)
241
+ signalr_device = assert_device_is_signalr(device=device)
242
+ self._describe_funcs += (signalr_device.describe,)
243
+ self._read_funcs += (signalr_device.read,)
244
+ self._stageables += (signalr_device,)
245
+ self._has_hints += (_HintsFromName(signalr_device),)
230
246
  case StandardReadableFormat.UNCACHED_SIGNAL:
231
- assert isinstance(device, SignalR), f"{device} is not a SignalR"
232
- self._describe_funcs += (device.describe,)
233
- self._read_funcs += (_UncachedRead(device),)
247
+ signalr_device = assert_device_is_signalr(device=device)
248
+ self._describe_funcs += (signalr_device.describe,)
249
+ self._read_funcs += (_UncachedRead(signalr_device),)
234
250
  case StandardReadableFormat.HINTED_UNCACHED_SIGNAL:
235
- assert isinstance(device, SignalR), f"{device} is not a SignalR"
236
- self._describe_funcs += (device.describe,)
237
- self._read_funcs += (_UncachedRead(device),)
238
- self._has_hints += (_HintsFromName(device),)
251
+ signalr_device = assert_device_is_signalr(device=device)
252
+ self._describe_funcs += (signalr_device.describe,)
253
+ self._read_funcs += (_UncachedRead(signalr_device),)
254
+ self._has_hints += (_HintsFromName(signalr_device),)
239
255
 
240
256
 
241
257
  class _UncachedRead:
@@ -0,0 +1,113 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import abstractmethod
4
+ from collections.abc import Callable, Iterator, MutableMapping
5
+ from typing import Any, Generic
6
+
7
+ from ._device import Device, DeviceT
8
+ from ._signal import SignalRW
9
+ from ._signal_backend import SignalDatatypeT
10
+
11
+
12
+ class Settings(MutableMapping[SignalRW[Any], Any], Generic[DeviceT]):
13
+ """Used for supplying settings to signals.
14
+
15
+ :param device: The device that the settings are for.
16
+ :param settings: A dictionary of settings to start with.
17
+
18
+ :example:
19
+ ```python
20
+ # Settings are created from a dict of signals to values
21
+ settings1 = Settings(device, {device.sig1: 1, device.sig2: 2})
22
+ settings2 = Settings(device, {device.sig1: 10, device.sig3: 3})
23
+ # They act like a dictionaries
24
+ assert settings1[device.sig1] == 1
25
+ # Including the ability to "or" two settings together
26
+ settings = settings1 | settings2
27
+ assert dict(settings) == {
28
+ device.sig1: 10,
29
+ device.sig2: 2,
30
+ device.sig3: 3,
31
+ }
32
+ ```
33
+ """
34
+
35
+ def __init__(
36
+ self, device: DeviceT, settings: MutableMapping[SignalRW, Any] | None = None
37
+ ):
38
+ self.device = device
39
+ self._settings = {}
40
+ self.update(settings or {})
41
+
42
+ def __getitem__(self, key: SignalRW[SignalDatatypeT]) -> SignalDatatypeT:
43
+ return self._settings[key]
44
+
45
+ def _is_in_device(self, device: Device) -> bool:
46
+ while device.parent and device.parent is not self.device:
47
+ # While we have a parent that is not the right device
48
+ # continue searching up the tree
49
+ device = device.parent
50
+ return device.parent is self.device
51
+
52
+ def __setitem__(
53
+ self, key: SignalRW[SignalDatatypeT], value: SignalDatatypeT | None
54
+ ) -> None:
55
+ # Check the types on entry to dict to make sure we can't accidentally
56
+ # add a non-signal type
57
+ if not isinstance(key, SignalRW):
58
+ raise TypeError(f"Expected SignalRW, got {key}")
59
+ if not self._is_in_device(key):
60
+ raise KeyError(f"Signal {key} is not a child of {self.device}")
61
+ self._settings[key] = value
62
+
63
+ def __delitem__(self, key: SignalRW) -> None:
64
+ del self._settings[key]
65
+
66
+ def __iter__(self) -> Iterator[SignalRW]:
67
+ yield from iter(self._settings)
68
+
69
+ def __len__(self) -> int:
70
+ return len(self._settings)
71
+
72
+ def __or__(self, other: MutableMapping[SignalRW, Any]) -> Settings[DeviceT]:
73
+ """Create a new Settings that is the union of self overridden by other."""
74
+ if isinstance(other, Settings) and not self._is_in_device(other.device):
75
+ raise ValueError(f"{other.device} is not a child of {self.device}")
76
+ return Settings(self.device, self._settings | dict(other))
77
+
78
+ def partition(
79
+ self, predicate: Callable[[SignalRW], bool]
80
+ ) -> tuple[Settings[DeviceT], Settings[DeviceT]]:
81
+ """Partition into two Settings based on a predicate.
82
+
83
+ :param predicate:
84
+ Callable that takes each signal, and returns a boolean to say if it
85
+ should be in the first returned Settings
86
+ :returns:
87
+ `(where_true, where_false)` where each is a Settings object.
88
+ The first contains the signals for which the predicate returned True,
89
+ and the second contains the signals for which the predicate returned False.
90
+
91
+ :example:
92
+ ```python
93
+ settings = Settings(device, {device.special: 1, device.sig: 2})
94
+ specials, others = settings.partition(lambda sig: "special" in sig.name)
95
+ ```
96
+ """
97
+ where_true, where_false = Settings(self.device), Settings(self.device)
98
+ for signal, value in self.items():
99
+ dest = where_true if predicate(signal) else where_false
100
+ dest[signal] = value
101
+ return where_true, where_false
102
+
103
+
104
+ class SettingsProvider:
105
+ """Base class for providing settings."""
106
+
107
+ @abstractmethod
108
+ async def store(self, name: str, data: dict[str, Any]):
109
+ """Store the data, associating it with the given name."""
110
+
111
+ @abstractmethod
112
+ async def retrieve(self, name: str) -> dict[str, Any]:
113
+ """Retrieve the data associated with the given name."""