Skip to content

Commit f4cc423

Browse files
committed
Remove device_factory and supporting methods
1 parent 0beeb31 commit f4cc423

19 files changed

Lines changed: 66 additions & 1537 deletions

src/dodal/cli.py

Lines changed: 6 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
import importlib
22
import os
33
from collections.abc import Mapping
4-
from pathlib import Path
54

65
import click
76
from bluesky.run_engine import RunEngine
8-
from ophyd_async.core import NotConnectedError, StaticPathProvider, UUIDFilenameProvider
9-
from ophyd_async.plan_stubs import ensure_connected
7+
from click.exceptions import ClickException
8+
from ophyd_async.core import NotConnectedError
109

1110
from dodal.beamlines import all_beamline_names, module_name_for_beamline
12-
from dodal.common.beamlines.beamline_utils import set_path_provider
1311
from dodal.device_manager import DeviceManager
14-
from dodal.utils import AnyDevice, filter_ophyd_devices, make_all_devices
12+
from dodal.utils import AnyDevice
1513

1614
from . import __version__
1715

@@ -103,7 +101,7 @@ def connect(
103101

104102
# We need to make a RunEngine to allow ophyd-async devices to connect.
105103
# See https://blueskyproject.io/ophyd-async/main/explanations/event-loop-choice.html
106-
run_engine = RunEngine(call_returns_result=True)
104+
_run_engine = RunEngine(call_returns_result=True)
107105

108106
print(f"Attempting connection to {beamline} (using {full_module_path})")
109107

@@ -121,15 +119,9 @@ def connect(
121119
timeout=timeout,
122120
)
123121
else:
124-
print(f"No device manager named '{device_manager}' found in {mod}")
125-
_spoof_path_provider()
126-
devices, instance_exceptions = make_all_devices(
127-
full_module_path,
128-
include_skipped=all,
129-
fake_with_ophyd_sim=sim_backend,
130-
wait_for_connection=False,
122+
raise ClickException(
123+
f"No device manager named '{device_manager}' found in {mod}"
131124
)
132-
devices, connect_exceptions = _connect_devices(run_engine, devices, sim_backend)
133125

134126
# Inform user of successful connections
135127
_report_successful_devices(devices, sim_backend)
@@ -151,35 +143,3 @@ def _report_successful_devices(
151143

152144
print(f"{len(devices)} devices connected{sim_statement}:")
153145
print(connected_devices)
154-
155-
156-
def _connect_devices(
157-
run_engine: RunEngine,
158-
devices: Mapping[str, AnyDevice],
159-
sim_backend: bool,
160-
) -> tuple[Mapping[str, AnyDevice], Mapping[str, Exception]]:
161-
ophyd_devices, ophyd_async_devices = filter_ophyd_devices(devices)
162-
exceptions = {}
163-
164-
# Connect ophyd devices
165-
for name, device in ophyd_devices.items():
166-
try:
167-
device.wait_for_connection()
168-
except Exception as ex:
169-
exceptions[name] = ex
170-
171-
# Connect ophyd-async devices
172-
try:
173-
run_engine(ensure_connected(*ophyd_async_devices.values(), mock=sim_backend))
174-
except NotConnectedError as ex:
175-
exceptions = {**exceptions, **ex.sub_errors}
176-
177-
# Only return the subset of devices that haven't raised an exception
178-
successful_devices = {
179-
name: device for name, device in devices.items() if name not in exceptions
180-
}
181-
return successful_devices, exceptions
182-
183-
184-
def _spoof_path_provider() -> None:
185-
set_path_provider(StaticPathProvider(UUIDFilenameProvider(), Path("/tmp")))

src/dodal/common/beamlines/beamline_utils.py

Lines changed: 1 addition & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,17 @@
11
import inspect
22
from collections.abc import Callable
3-
from typing import Annotated, Final, TypeVar, cast
3+
from typing import TypeVar
44

5-
from bluesky.run_engine import call_in_bluesky_event_loop
65
from daq_config_server import ConfigClient
7-
from ophyd import Device as OphydV1Device
8-
from ophyd.sim import make_fake_device
96
from ophyd_async.core import (
10-
DEFAULT_TIMEOUT,
117
PathProvider,
128
)
13-
from ophyd_async.core import Device as OphydV2Device
14-
from ophyd_async.core import wait_for_connection as v2_device_wait_for_connection
159

1610
from dodal.log import LOGGER
1711
from dodal.utils import (
1812
AnyDevice,
19-
BeamlinePrefix,
20-
DeviceInitializationController,
21-
SkipType,
22-
skip_device,
2313
)
2414

25-
DEFAULT_CONNECTION_TIMEOUT: Final[float] = 5.0
26-
27-
ACTIVE_DEVICES: dict[str, AnyDevice] = {}
2815
BL = ""
2916

3017

@@ -33,134 +20,15 @@ def set_beamline(beamline: str):
3320
BL = beamline
3421

3522

36-
def clear_devices():
37-
global ACTIVE_DEVICES
38-
for name in list(ACTIVE_DEVICES):
39-
clear_device(name)
40-
41-
42-
def clear_device(name: str):
43-
global ACTIVE_DEVICES
44-
device = ACTIVE_DEVICES[name]
45-
if isinstance(device, OphydV1Device):
46-
device.destroy()
47-
del ACTIVE_DEVICES[name]
48-
49-
50-
def list_active_devices() -> list[str]:
51-
global ACTIVE_DEVICES
52-
return list(ACTIVE_DEVICES.keys())
53-
54-
5523
def active_device_is_same_type(
5624
active_device: AnyDevice, device: Callable[..., AnyDevice]
5725
) -> bool:
5826
return inspect.isclass(device) and isinstance(active_device, device)
5927

6028

61-
def wait_for_connection(
62-
device: AnyDevice,
63-
timeout: float = DEFAULT_CONNECTION_TIMEOUT,
64-
mock: bool = False,
65-
) -> None:
66-
if isinstance(device, OphydV1Device):
67-
device.wait_for_connection(timeout=timeout)
68-
elif isinstance(device, OphydV2Device):
69-
call_in_bluesky_event_loop(
70-
v2_device_wait_for_connection(
71-
coros=device.connect(mock=mock, timeout=timeout)
72-
),
73-
)
74-
else:
75-
raise TypeError(
76-
f"Invalid type {device.__class__.__name__} in _wait_for_connection"
77-
)
78-
79-
8029
T = TypeVar("T", bound=AnyDevice)
8130

8231

83-
@skip_device()
84-
def device_instantiation(
85-
device_factory: Callable[..., T],
86-
name: str,
87-
prefix: str,
88-
wait: bool,
89-
fake: bool,
90-
post_create: Callable[[T], None] | None = None,
91-
bl_prefix: bool = True,
92-
**kwargs,
93-
) -> T:
94-
"""Method to allow generic creation of singleton devices. Meant to be used to easily
95-
define lists of devices in beamline files. Additional keyword arguments are passed
96-
directly to the device constructor.
97-
98-
Args:
99-
device_factory (Callable): The device class.
100-
name (str): The name for ophyd.
101-
prefix (str): The PV prefix for the most (usually all) components.
102-
wait (bool): Whether to run .wait_for_connection().
103-
fake (bool): Whether to fake with ophyd.sim.
104-
post_create (Callable): (optional) a function to be run on the device after
105-
creation.
106-
bl_prefix (bool): If true, add the beamline prefix when instantiating.
107-
**kwargs: Arguments passed on to every device factory.
108-
109-
Returns:
110-
The instance of the device.
111-
"""
112-
already_existing_device: AnyDevice | None = ACTIVE_DEVICES.get(name)
113-
if fake:
114-
device_factory = cast(Callable[..., T], make_fake_device(device_factory))
115-
if already_existing_device is None:
116-
device_instance = device_factory(
117-
name=name,
118-
prefix=(
119-
f"{(BeamlinePrefix(BL).beamline_prefix)}{prefix}"
120-
if bl_prefix
121-
else prefix
122-
),
123-
**kwargs,
124-
)
125-
ACTIVE_DEVICES[name] = device_instance
126-
if wait:
127-
wait_for_connection(device_instance, mock=fake)
128-
129-
else:
130-
if not active_device_is_same_type(already_existing_device, device_factory):
131-
raise TypeError(
132-
f"Can't instantiate device of type {device_factory} with the same "
133-
f"name as an existing device. Device name '{name}' already used for "
134-
f"a(n) {type(already_existing_device)}."
135-
)
136-
device_instance = cast(T, already_existing_device)
137-
if post_create:
138-
post_create(device_instance)
139-
return device_instance
140-
141-
142-
def device_factory(
143-
*,
144-
use_factory_name: Annotated[bool, "Use factory name as name of device"] = True,
145-
timeout: Annotated[float, "Timeout for connecting to the device"] = DEFAULT_TIMEOUT,
146-
mock: Annotated[bool, "Use Signals with mock backends for device"] = False,
147-
skip: Annotated[
148-
SkipType,
149-
"mark the factory to be (conditionally) skipped when beamline is imported by external program",
150-
] = False,
151-
) -> Callable[[Callable[[], T]], DeviceInitializationController[T]]:
152-
def decorator(factory: Callable[[], T]) -> DeviceInitializationController[T]:
153-
return DeviceInitializationController(
154-
factory,
155-
use_factory_name,
156-
timeout,
157-
mock,
158-
skip,
159-
)
160-
161-
return decorator
162-
163-
16432
def set_path_provider(provider: PathProvider):
16533
global PATH_PROVIDER
16634

src/dodal/device_manager.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,26 @@
1414
NamedTuple,
1515
ParamSpec,
1616
Self,
17+
TypeAlias,
1718
TypeVar,
1819
)
1920

2021
from bluesky.run_engine import get_bluesky_event_loop
22+
from ophyd.device import Device as OphydV1Device
2123
from ophyd.sim import make_fake_device
22-
23-
from dodal.common.beamlines.beamline_utils import wait_for_connection
24-
from dodal.utils import (
25-
AnyDevice,
26-
OphydV1Device,
27-
OphydV2Device,
28-
SkipType,
29-
)
24+
from ophyd_async.core import Device as OphydV2Device
3025

3126
DEFAULT_TIMEOUT = 30
3227
NO_DOCS = "No documentation available."
3328

3429
T = TypeVar("T")
3530
Args = ParamSpec("Args")
3631

32+
SkipType = bool | Callable[[], bool]
33+
3734
V1 = TypeVar("V1", bound=OphydV1Device)
3835
V2 = TypeVar("V2", bound=OphydV2Device)
36+
AnyDevice: TypeAlias = OphydV1Device | OphydV2Device
3937

4038
DeviceFactoryDecorator = Callable[[Callable[Args, V2]], "DeviceFactory[Args, V2]"]
4139
OphydInitialiser = Callable[Concatenate[V1, Args], V1 | None]
@@ -249,7 +247,7 @@ def __call__(self, dev: V1, *args: Args.args, **kwargs: Args.kwargs):
249247
def create(self, *args: Args.args, **kwargs: Args.kwargs) -> V1:
250248
device = self.factory(name=self.name, prefix=self.prefix)
251249
if self.wait:
252-
wait_for_connection(device, timeout=self.timeout)
250+
device.wait_for_connection(timeout=self.timeout)
253251
self.post_create(device, *args, **kwargs)
254252
return device
255253

src/dodal/plans/save_panda.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import argparse
2+
import importlib
23
import os
34
import sys
45
from argparse import ArgumentParser
@@ -12,7 +13,7 @@
1213
)
1314

1415
from dodal.beamlines import module_name_for_beamline
15-
from dodal.utils import make_device
16+
from dodal.device_manager import DeviceFactory, DeviceManager
1617

1718

1819
def main(argv: list[str] | None = None):
@@ -70,19 +71,25 @@ def main(argv: list[str] | None = None):
7071
return 0
7172

7273

74+
def _build_panda(beamline, device_name) -> Device:
75+
print(f"Building {device_name} for beamline {beamline}")
76+
module_name = module_name_for_beamline(beamline)
77+
mod = importlib.import_module("dodal.beamlines." + module_name)
78+
device_manager: DeviceManager = mod.devices
79+
return cast(DeviceFactory, device_manager[device_name]).build(
80+
connect_immediately=True
81+
)
82+
83+
7384
def _save_panda(beamline, device_name, output_directory, file_name):
7485
run_engine = RunEngine()
75-
print("Creating devices...")
76-
module_name = module_name_for_beamline(beamline)
86+
7787
try:
78-
devices = make_device(
79-
f"dodal.beamlines.{module_name}", device_name, connect_immediately=True
80-
)
88+
panda = _build_panda(beamline, device_name)
8189
except Exception as error:
8290
sys.stderr.write(f"Couldn't create device {device_name}: {error}\n")
8391
sys.exit(1)
8492

85-
panda = devices[device_name]
8693
print(
8794
f"Saving to {output_directory}/{file_name} from {device_name} on {beamline}..."
8895
)

0 commit comments

Comments
 (0)