Skip to content

Commit 3fe2fec

Browse files
BilchreisPeter Braun
andauthored
Migrate ophyd async 0.10 (#33)
* set new typevar * added 3.12 to testing * add bson dependenc * tests run * migrate to v0.10 * test fix --------- Co-authored-by: Peter Braun <peter.braun@helmholtz-berlin.de>
1 parent 9096d8a commit 3fe2fec

File tree

8 files changed

+87
-52
lines changed

8 files changed

+87
-52
lines changed

.github/workflows/code.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
strategy:
4444
fail-fast: false
4545
matrix:
46-
python-version: ["3.10", "3.11"]
46+
python-version: ["3.10", "3.11", "3.12"]
4747

4848
steps:
4949
- uses: actions/checkout@v4

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ dynamic = ["version"]
1010
description = "An Interface between bluesky and SECoP, using ophyd and frappy-client"
1111

1212
dependencies = [
13-
'ophyd-async >= 0.9.0',
13+
'ophyd-async >= 0.10.0',
1414
'frappy-core == 0.20.4'
1515

1616
]
@@ -37,7 +37,7 @@ dev = [
3737
'pytest-xprocess',
3838
'pre-commit',
3939
'ipykernel',
40-
'databroker',
40+
'databroker[all]==2.0.0b64',
4141
'ophyd',
4242
'tox',
4343
'mlzlog',
@@ -60,7 +60,7 @@ minversion = "6.0"
6060
addopts = "-v"
6161
asyncio_mode="auto"
6262
filterwarnings = "ignore::PendingDeprecationWarning"
63-
norecursedirs=["frappy","epics-testing","build"]
63+
6464

6565
[tool.isort]
6666
float_to_top = true

src/secop_ophyd/SECoPDevices.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,13 @@
1010

1111
import bluesky.plan_stubs as bps
1212
from bluesky.protocols import (
13-
Descriptor,
1413
Flyable,
1514
Locatable,
1615
Location,
1716
PartialEvent,
1817
Reading,
1918
Stoppable,
2019
Subscribable,
21-
SyncOrAsync,
2220
Triggerable,
2321
)
2422
from frappy.datatypes import (
@@ -222,9 +220,6 @@ def collect(self) -> Iterator[PartialEvent]:
222220
time=self._start_time, timestamps={self.name: []}, data={self.name: []}
223221
)
224222

225-
async def describe_collect(self) -> SyncOrAsync[Dict[str, Dict[str, Descriptor]]]:
226-
return await self.describe()
227-
228223

229224
class SECoPBaseDevice(StandardReadable):
230225
"""Base Class for generating Opyd devices from SEC Node modules,

src/secop_ophyd/SECoPSignal.py

Lines changed: 49 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import asyncio
22
import warnings
33
from functools import wraps
4-
from typing import Any, Callable, Dict, Optional
4+
from typing import Any, Callable, Dict
55

66
from bluesky.protocols import DataKey, Reading
77
from frappy.client import CacheItem
@@ -17,10 +17,10 @@
1717
StructOf,
1818
TupleOf,
1919
)
20-
from ophyd_async.core import Callback, SignalBackend, T
20+
from ophyd_async.core import Callback, SignalBackend, SignalDatatypeT
2121

2222
from secop_ophyd.AsyncFrappyClient import AsyncFrappyClient
23-
from secop_ophyd.util import Path, SECoPdtype, SECoPReading, deep_get
23+
from secop_ophyd.util import Path, SECoPDataKey, SECoPdtype, SECoPReading, deep_get
2424

2525
atomic_dtypes = (
2626
StringType,
@@ -105,18 +105,18 @@ async def put(self, value: Any | None, wait=True):
105105

106106
async def get_datakey(self, source: str) -> DataKey:
107107
"""Metadata like source, dtype, shape, precision, units"""
108-
return self.describe_dict
108+
return describedict_to_datakey(self.describe_dict)
109109

110-
async def get_reading(self) -> Reading:
110+
async def get_reading(self) -> Reading[SignalDatatypeT]:
111111
return self.reading.get_reading()
112112

113-
async def get_value(self) -> T:
113+
async def get_value(self) -> SignalDatatypeT:
114114
return self.reading.get_value()
115115

116-
async def get_setpoint(self) -> T:
116+
async def get_setpoint(self) -> SignalDatatypeT:
117117
return await self.get_value()
118118

119-
def set_callback(self, callback: Callback[T] | None) -> None:
119+
def set_callback(self, callback: Callback[Reading[SignalDatatypeT]] | None) -> None:
120120
self.callback = callback # type: ignore[assignment]
121121

122122

@@ -191,36 +191,25 @@ async def put(self, value: Any | None, wait=True):
191191

192192
async def get_datakey(self, source: str) -> DataKey:
193193
"""Metadata like source, dtype, shape, precision, units"""
194-
res = {}
195194

196-
res["source"] = self.source("", True)
195+
return DataKey(shape=[], dtype="string", source=self.source("", True))
197196

198-
# ophyd datatype (some SECoP datatypeshaveto be converted)
199-
# signalx has no datatype and is never read
200-
res["dtype"] = "None"
201-
202-
# get shape from datainfo and SECoPtype
203-
204-
res["shape"] = [] # type: ignore
205-
206-
return res
207-
208-
async def get_reading(self) -> Reading:
197+
async def get_reading(self) -> Reading[SignalDatatypeT]:
209198
raise Exception(
210199
"Cannot read _x Signal, it has no value and is only"
211200
+ " used to trigger Command execution"
212201
)
213202

214-
async def get_value(self) -> T:
203+
async def get_value(self) -> SignalDatatypeT:
215204
raise Exception(
216205
"Cannot read _x Signal, it has no value and is only"
217206
+ " used to trigger Command execution"
218207
)
219208

220-
def set_callback(self, callback: Callback[T] | None) -> None:
209+
def set_callback(self, callback: Callback[Reading[SignalDatatypeT]] | None) -> None:
221210
pass
222211

223-
async def get_setpoint(self) -> T:
212+
async def get_setpoint(self) -> SignalDatatypeT:
224213
raise Exception(
225214
"Cannot read _x Signal, it has no value and is only"
226215
+ " used to trigger Command execution"
@@ -334,9 +323,9 @@ async def get_datakey(self, source: str) -> DataKey:
334323
SECoPReading(entry=dataset, secop_dt=self.SECoP_type_info)
335324
self.describe_dict.update(self.SECoP_type_info.get_datakey())
336325

337-
return self.describe_dict
326+
return describedict_to_datakey(self.describe_dict)
338327

339-
async def get_reading(self) -> Reading:
328+
async def get_reading(self) -> Reading[SignalDatatypeT]:
340329
dataset = await self._secclient.get_parameter(
341330
**self.get_param_path(), trycache=False
342331
)
@@ -345,15 +334,15 @@ async def get_reading(self) -> Reading:
345334

346335
return sec_reading.get_reading()
347336

348-
async def get_value(self) -> T:
337+
async def get_value(self) -> SignalDatatypeT:
349338
dataset: Reading = await self.get_reading()
350339

351340
return dataset["value"] # type: ignore
352341

353-
async def get_setpoint(self) -> T:
342+
async def get_setpoint(self) -> SignalDatatypeT:
354343
return await self.get_value()
355344

356-
def set_callback(self, callback: Callback[T] | None) -> None:
345+
def set_callback(self, callback: Callback[Reading[SignalDatatypeT]] | None) -> None:
357346
def awaitify(sync_func):
358347
"""Wrap a synchronous callable to allow ``await``'ing it"""
359348

@@ -403,7 +392,7 @@ class PropertyBackend(SignalBackend):
403392
"""Readonly backend for static SECoP Properties of Nodes/Modules"""
404393

405394
def __init__(
406-
self, prop_key: str, property_dict: Dict[str, T], secclient: AsyncFrappyClient
395+
self, prop_key: str, property_dict: Dict[str, Any], secclient: AsyncFrappyClient
407396
) -> None:
408397
"""Initializes PropertyBackend
409398
@@ -450,16 +439,16 @@ async def connect(self, timeout: float):
450439
"""Connect to underlying hardware"""
451440
pass
452441

453-
async def put(self, value: Optional[T], wait=True):
442+
async def put(self, value: SignalDatatypeT | None, wait=True):
454443
"""Put a value to the PV, if wait then wait for completion for up to timeout"""
455444
# Properties are readonly
456445
pass
457446

458447
async def get_datakey(self, source: str) -> DataKey:
459448
"""Metadata like source, dtype, shape, precision, units"""
460-
return self.describe_dict
449+
return describedict_to_datakey(self.describe_dict)
461450

462-
async def get_reading(self) -> Reading:
451+
async def get_reading(self) -> Reading[SignalDatatypeT]:
463452
dataset = CacheItem(
464453
value=self._prop_value, timestamp=self._secclient.conn_timestamp
465454
)
@@ -468,15 +457,15 @@ async def get_reading(self) -> Reading:
468457

469458
return sec_reading.get_reading()
470459

471-
async def get_value(self) -> T:
460+
async def get_value(self) -> SignalDatatypeT:
472461
dataset: Reading = await self.get_reading()
473462

474463
return dataset["value"] # type: ignore
475464

476-
async def get_setpoint(self) -> T:
465+
async def get_setpoint(self) -> SignalDatatypeT:
477466
return await self.get_value()
478467

479-
def set_callback(self, callback: Callback[T] | None) -> None:
468+
def set_callback(self, callback: Callback[Reading[SignalDatatypeT]] | None) -> None:
480469
pass
481470

482471

@@ -515,3 +504,27 @@ def secop_dtype_obj_from_json(prop_val):
515504
f"""unsupported datatype in Property: {str(prop_val.__class__.__name__)}\n
516505
propval: {prop_val}"""
517506
)
507+
508+
509+
def describedict_to_datakey(describe_dict: dict) -> SECoPDataKey:
510+
"""Convert a DataKey to a SECoPDataKey"""
511+
datakey = SECoPDataKey(
512+
dtype=describe_dict["dtype"],
513+
shape=describe_dict["shape"],
514+
source=describe_dict["source"],
515+
SECOP_datainfo=describe_dict["SECOP_datainfo"],
516+
)
517+
518+
if "units" in describe_dict:
519+
datakey["units"] = describe_dict["units"]
520+
521+
if "dtype_str" in describe_dict:
522+
datakey["dtype_str"] = describe_dict["dtype_str"]
523+
524+
if "dtype_descr" in describe_dict:
525+
datakey["dtype_descr"] = describe_dict["dtype_descr"]
526+
527+
if "dtype_numpy" in describe_dict:
528+
datakey["dtype_numpy"] = describe_dict["dtype_numpy"]
529+
530+
return datakey

src/secop_ophyd/util.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
TupleOf,
2727
)
2828
from ophyd_async.core._utils import StrictEnum
29+
from typing_extensions import NotRequired
2930

3031
SCALAR_DATATYPES = (
3132
IntRange,
@@ -37,6 +38,32 @@
3738
)
3839

3940

41+
class SECoPDataKey(DataKey):
42+
"""
43+
A DataKey that is used to describe the SECoP Datatype.
44+
"""
45+
46+
dtype_str: NotRequired[str]
47+
"""
48+
The array-protocol typestring of the data-type object.
49+
"""
50+
51+
dtype_descr: NotRequired[list]
52+
"""
53+
String representation of the numpy structured array dtype.
54+
"""
55+
56+
SECOP_datainfo: str
57+
"""
58+
String representation of the original secop datatype.
59+
"""
60+
61+
SECoP_dtype: NotRequired[str]
62+
"""
63+
The SECoP datatype of the data.
64+
"""
65+
66+
4067
class NestedRaggedArray(Exception):
4168
"""The Datatype contains nested ragged arrays"""
4269

@@ -552,7 +579,7 @@ def __init__(self, datatype: DataType) -> None:
552579
self.dtype_descr: list
553580

554581
# string representation of the original secop datatype
555-
self.secop_dtype_str = datatype.export_datatype()
582+
self.secop_dtype_str = str(datatype.export_datatype())
556583

557584
# Shape of Data
558585
self.shape = []
@@ -612,7 +639,8 @@ def __init__(self, datatype: DataType) -> None:
612639
self.np_datatype = SECOP2DTYPE[datatype.__class__][0]
613640
self.dtype = SECOP2DTYPE[datatype.__class__][1]
614641

615-
def get_datakey(self) -> DataKey:
642+
def get_datakey(self) -> dict:
643+
616644
describe_dict: dict = {}
617645
# Composite Datatypes & Arrays of COmposite Datatypes
618646
if self._is_composite:

tests/test_RE.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ async def test_run_engine_count(cryo_sim, run_engine, cryo_node: SECoPNodeDevice
1717

1818
run = db[p]
1919

20-
data = run.primary.read()
20+
print(run[0])
21+
22+
data = run[0].primary.read()
2123

2224
cryo_dat = data[cryo_node.cryo.value.name]
2325

tests/test_nested.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ async def test_nested_dtype_str_signal_generation(
6161

6262
assert isinstance(val, np.ndarray)
6363
assert descr["dtype"] == "array"
64-
assert descr["SECoP_dtype"] == "struct"
64+
assert "struct" in descr["SECOP_datainfo"]
6565
await nested_node.disconnect_async()
6666

6767

tests/test_primitive_arrays.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from bluesky import RunEngine
55
from bluesky.callbacks.best_effort import BestEffortCallback
66
from bluesky.plans import count
7-
from bluesky.utils import ProgressBarManager
87
from ophyd_async.core import SignalR
98

109
from secop_ophyd.SECoPDevices import SECoPNodeDevice, SECoPReadableDevice
@@ -15,7 +14,6 @@ async def test_primitive_arrays(
1514
):
1615
bec = BestEffortCallback()
1716
run_engine.subscribe(bec)
18-
run_engine.waiting_hook = ProgressBarManager()
1917
run_engine.ignore_callback_exceptions = False
2018

2119
prim_arr: SECoPReadableDevice = nested_node_re.primitive_arrays
@@ -57,7 +55,6 @@ async def test_primitive_float_array(
5755
):
5856
bec = BestEffortCallback()
5957
run_engine.subscribe(bec)
60-
run_engine.waiting_hook = ProgressBarManager()
6158
run_engine.ignore_callback_exceptions = False
6259

6360
prim_arr: SECoPReadableDevice = nested_node_re.primitive_arrays

0 commit comments

Comments
 (0)