Skip to content

Commit 6a0419b

Browse files
burkedscoretl
andauthored
Adding ConfigSignal version of get_current_settings and retrieve_settings. (#803)
* Variation of store_settings which only handles configuration attributes in StandardReadable devices * Moved walk_config_signals to _readable * defined config_names before loop * walk_config_signals now checks for bluesky.protocols.Configurable so I moved it back to _signal.py. The test should work now. * Ran ruff * Clearing pre-commit,typechecking with tox * Update src/ophyd_async/core/_signal.py Co-authored-by: Tom C (DLS) <101418278+coretl@users.noreply.github.com> * Reverted suggested change * Added config signal only plans to handle settings including get_current_config_settings and retrieve_config_settings. * Added only_config=False kwarg to store_config, get_current_settings, and retrieve_settings to optionally only handle configuration signals instead of all SignalRWs. --------- Co-authored-by: Tom C (DLS) <101418278+coretl@users.noreply.github.com>
1 parent e926a60 commit 6a0419b

File tree

5 files changed

+143
-31
lines changed

5 files changed

+143
-31
lines changed

src/ophyd_async/plan_stubs/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
apply_settings_if_different,
1414
get_current_settings,
1515
retrieve_settings,
16-
store_config_settings,
1716
store_settings,
1817
)
1918

@@ -30,5 +29,4 @@
3029
"get_current_settings",
3130
"retrieve_settings",
3231
"store_settings",
33-
"store_config_settings",
3432
]

src/ophyd_async/plan_stubs/_settings.py

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,51 +33,61 @@ def _get_values_of_signals(
3333

3434

3535
@plan
36-
def get_current_settings(device: Device) -> MsgGenerator[Settings]:
37-
"""Get current settings on `Device`."""
38-
signals = walk_rw_signals(device)
36+
def get_current_settings(
37+
device: Device, only_config: bool = False
38+
) -> MsgGenerator[Settings]:
39+
"""Get current settings on `Device`.
40+
41+
If `only_config` is True, get current configuration settings on `Configurable`.
42+
"""
43+
if only_config:
44+
signals = yield from wait_for_awaitable(walk_config_signals(device))
45+
else:
46+
signals = walk_rw_signals(device)
3947
named_values = yield from _get_values_of_signals(signals)
4048
signal_values = {signals[name]: value for name, value in named_values.items()}
4149
return Settings(device, signal_values)
4250

4351

4452
@plan
4553
def store_settings(
46-
provider: SettingsProvider, name: str, device: Device
54+
provider: SettingsProvider, name: str, device: Device, only_config: bool = False
4755
) -> MsgGenerator[None]:
4856
"""Walk a Device for SignalRWs and store their values.
4957
50-
:param provider: The provider to store the settings with.
51-
:param name: The name to store the settings under.
52-
:param device: The Device to walk for SignalRWs.
53-
"""
54-
signals = walk_rw_signals(device)
55-
named_values = yield from _get_values_of_signals(signals)
56-
yield from wait_for_awaitable(provider.store(name, named_values))
57-
58-
59-
@plan
60-
def store_config_settings(
61-
provider: SettingsProvider, name: str, device: Device
62-
) -> MsgGenerator[None]:
63-
"""Walk a Device for SignalRWs and store their values.
58+
If `only_config` is True, store only configuration settings on `Configurable`.
6459
6560
:param provider: The provider to store the settings with.
6661
:param name: The name to store the settings under.
6762
:param device: The Device to walk for SignalRWs.
63+
:param only_config: If True, store only configuration settings.
6864
"""
69-
signals = yield from wait_for_awaitable(walk_config_signals(device))
65+
if only_config:
66+
signals = yield from wait_for_awaitable(walk_config_signals(device))
67+
else:
68+
signals = walk_rw_signals(device)
7069
named_values = yield from _get_values_of_signals(signals)
7170
yield from wait_for_awaitable(provider.store(name, named_values))
7271

7372

7473
@plan
7574
def retrieve_settings(
76-
provider: SettingsProvider, name: str, device: Device
75+
provider: SettingsProvider, name: str, device: Device, only_config: bool = False
7776
) -> MsgGenerator[Settings]:
78-
"""Retrieve named Settings for a Device from a provider."""
77+
"""Retrieve named Settings for a Device from a provider.
78+
79+
If `only_config` is True, retrieve only configuration settings on `Configurable`.
80+
81+
:param provider: The provider to retrieve the settings from.
82+
:param name: The name of the settings to retrieve.
83+
:param device: The Device to retrieve the settings for.
84+
:param only_config: If True, retrieve only configuration settings.
85+
"""
7986
named_values = yield from wait_for_awaitable(provider.retrieve(name))
80-
signals = walk_rw_signals(device)
87+
if only_config:
88+
signals = yield from wait_for_awaitable(walk_config_signals(device))
89+
else:
90+
signals = walk_rw_signals(device)
8191
unknown_names = set(named_values) - set(signals)
8292
if unknown_names:
8393
raise NameError(f"Unknown signal names {sorted(unknown_names)}")

src/ophyd_async/testing/_one_of_everything.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ def __init__(self, name=""):
105105
self.ndarray = soft_signal_rw(np.ndarray, np.array(([1, 2, 3], [4, 5, 6])))
106106
super().__init__(name)
107107

108+
async def get_signal_values(self):
109+
return await _get_signal_values(self)
110+
108111

109112
async def _get_signal_values(child: Device) -> dict[SignalRW, Any]:
110113
if isinstance(child, SignalRW):

tests/plan_stubs/test_settings.py

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from unittest.mock import call
33

44
import bluesky.plan_stubs as bps
5+
import numpy as np
56
import pytest
67
import yaml
78

@@ -11,7 +12,6 @@
1112
apply_settings_if_different,
1213
get_current_settings,
1314
retrieve_settings,
14-
store_config_settings,
1515
store_settings,
1616
)
1717
from ophyd_async.testing import (
@@ -31,6 +31,13 @@ async def parent_device() -> ParentOfEverythingDevice:
3131
return device
3232

3333

34+
@pytest.fixture
35+
async def every_parent_device() -> OneOfEverythingDevice:
36+
device = OneOfEverythingDevice("parent")
37+
await device.connect(mock=True)
38+
return device
39+
40+
3441
async def test_get_current_settings(RE, parent_device: ParentOfEverythingDevice):
3542
expected_values = await parent_device.get_signal_values()
3643

@@ -41,6 +48,25 @@ def my_plan():
4148
RE(my_plan())
4249

4350

51+
async def test_get_current_config_settings(
52+
RE, every_parent_device: OneOfEverythingDevice
53+
):
54+
expected_values = await every_parent_device.get_signal_values()
55+
56+
def my_plan():
57+
current_settings = yield from get_current_settings(
58+
every_parent_device, only_config=True
59+
)
60+
current_settings = dict(current_settings)
61+
for key, value in current_settings.items():
62+
if isinstance(value, np.ndarray):
63+
assert np.array_equal(value, expected_values[key])
64+
else:
65+
assert value == expected_values[key]
66+
67+
RE(my_plan())
68+
69+
4470
async def test_store_settings(RE, parent_device: ParentOfEverythingDevice, tmp_path):
4571
provider = YamlSettingsProvider(tmp_path)
4672

@@ -54,19 +80,18 @@ def my_plan():
5480

5581

5682
async def test_store_config_settings(
57-
RE, parent_device: OneOfEverythingDevice, tmp_path
83+
RE, every_parent_device: OneOfEverythingDevice, tmp_path
5884
):
5985
provider = YamlSettingsProvider(tmp_path)
6086

6187
def my_plan():
62-
yield from store_config_settings(provider, "test_file", parent_device)
88+
yield from store_settings(
89+
provider, "test_file", every_parent_device, only_config=True
90+
)
6391
with open(tmp_path / "test_file.yaml") as actual_file:
6492
actual_data = yaml.safe_load(actual_file)
65-
with open(TEST_DATA / "test_yaml_save.yaml") as expected_file:
93+
with open(TEST_DATA / "test_yaml_config_save.yaml") as expected_file:
6694
expected_data = yaml.safe_load(expected_file)
67-
# Remove the keys that shouldn't be expected
68-
expected_data.pop("_sig_rw", None)
69-
expected_data.pop("sig_rw", None)
7095
assert actual_data == expected_data
7196

7297
RE(my_plan())
@@ -108,6 +133,43 @@ def my_plan():
108133
RE(my_plan())
109134

110135

136+
async def test_retrieve_and_apply_config_settings(
137+
RE, every_parent_device: OneOfEverythingDevice
138+
):
139+
provider = YamlSettingsProvider(TEST_DATA)
140+
expected_values = await every_parent_device.get_signal_values()
141+
serialized_values = {}
142+
# Override the table to be the serialized version so it compares equal
143+
for sig, value in expected_values.items():
144+
if isinstance(value, ExampleTable):
145+
serialized_values[sig] = {
146+
k: pytest.approx(v) for k, v in value.model_dump().items()
147+
}
148+
else:
149+
serialized_values[sig] = pytest.approx(value)
150+
151+
def my_plan():
152+
m = get_mock(every_parent_device)
153+
settings = yield from retrieve_settings(
154+
provider, "test_yaml_config_save", every_parent_device, only_config=True
155+
)
156+
assert dict(settings) == serialized_values
157+
assert not m.mock_calls
158+
yield from apply_settings(settings)
159+
assert len(m.mock_calls) == 19
160+
m.reset_mock()
161+
assert not m.mock_calls
162+
yield from apply_settings_if_different(settings, apply_settings)
163+
assert not m.mock_calls
164+
yield from bps.abs_set(every_parent_device.a_str, "foo", wait=True)
165+
assert m.mock_calls == [call.a_str.put("foo", wait=True)]
166+
m.reset_mock()
167+
yield from apply_settings_if_different(settings, apply_settings)
168+
assert m.mock_calls == [call.a_str.put("test_string", wait=True)]
169+
170+
RE(my_plan())
171+
172+
111173
async def test_ignored_settings(RE, parent_device: ParentOfEverythingDevice):
112174
def my_plan():
113175
m = get_mock(parent_device)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
a_bool: true
2+
a_float: 1.234
3+
a_int: 1
4+
a_str: test_string
5+
enum: Bbb
6+
enuma:
7+
- Aaa
8+
- Ccc
9+
float32a: [-3.4028234663852886e+38, 3.4028234663852886e+38, 1.1754943508222875e-38,
10+
1.401298464324817e-45, 0.0, 1.2339999675750732, 234000.0, 3.4499998946557753e-06]
11+
float64a: [-1.7976931348623157e+308, 1.7976931348623157e+308, 2.2250738585072014e-308,
12+
5.0e-324, 0.0, 1.234, 234000.0, 3.45e-06]
13+
int16a: [-32768, 32767, 0, 1, 2, 3, 4]
14+
int32a: [-2147483648, 2147483647, 0, 1, 2, 3, 4]
15+
int64a: [-9223372036854775808, 9223372036854775807, 0, 1, 2, 3, 4]
16+
int8a: [-128, 127, 0, 1, 2, 3, 4]
17+
ndarray: [[1, 2, 3], [4, 5, 6]]
18+
stra:
19+
- one
20+
- two
21+
- three
22+
table:
23+
a_bool: [false, false, true, true]
24+
a_enum:
25+
- Aaa
26+
- Bbb
27+
- Aaa
28+
- Ccc
29+
a_float: [1.8, 8.2, -6.0, 32.9887]
30+
a_int: [1, 8, -9, 32]
31+
a_str:
32+
- Hello
33+
- World
34+
- Foo
35+
- Bar
36+
uint16a: [0, 65535, 0, 1, 2, 3, 4]
37+
uint32a: [0, 4294967295, 0, 1, 2, 3, 4]
38+
uint64a: [0, 18446744073709551615, 0, 1, 2, 3, 4]
39+
uint8a: [0, 255, 0, 1, 2, 3, 4]

0 commit comments

Comments
 (0)