diff --git a/docs/explanations/decisions/0003-make-devices-factory.md b/docs/explanations/decisions/0003-make-devices-factory.md deleted file mode 100644 index 3292453bb1..0000000000 --- a/docs/explanations/decisions/0003-make-devices-factory.md +++ /dev/null @@ -1,28 +0,0 @@ -# 3. Add device factory decorator with lazy connect support - -Date: 2024-04-26 - -## Status - -Accepted - -## Context - -Device instances should be capable of being created without necessarily connecting, so long as they are connected prior to being utilised to collect data. The current method puts requirements on the init method of device classes, and does not expose all options for connecting to ophyd-async devices. - -## Decision - -DAQ members led us to this proposal: - -- ophyd-async: make Device.connect(mock, timeout, force=False) idempotent -- ophyd-async: make ensure_connected(\*devices) plan stub -- dodal: make device_factory() decorator that may construct, name, cache and connect a device -- dodal: collect_factories() returns all device factories -- blueapi: call collect_factories(), instantiate and connect Devices appropriately, log those that fail -- blueapi: when plan is called, run ensure_connected on all plan args and defaults that are Devices - -We can then iterate on this if the parallel connect causes a broadcast storm. We could also in future add a monitor to a heartbeat PV per device in Device.connect so that it would reconnect next time it was called. - -## Consequences - -Beamlines will be converted to use the decorator, and default arguments to plans should be replaced with a non-eagerly connecting call to the initializer controlling device. diff --git a/src/dodal/beamlines/i22.py b/src/dodal/beamlines/i22.py index b739b6d4e2..4b42694a98 100644 --- a/src/dodal/beamlines/i22.py +++ b/src/dodal/beamlines/i22.py @@ -5,12 +5,12 @@ from ophyd_async.fastcs.panda import HDFPanda from dodal.common.beamlines.beamline_utils import ( - device_factory, + device_instantiation, get_path_provider, set_path_provider, ) from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline -from dodal.common.beamlines.device_helpers import HDF5_PREFIX +from dodal.common.beamlines.device_helpers import numbered_slits from dodal.common.visit import RemoteDirectoryServiceClient, StaticVisitPathProvider from dodal.devices.focusing_mirror import FocusingMirror from dodal.devices.i22.dcm import CrystalMetadata, DoubleCrystalMonochromator @@ -22,10 +22,9 @@ from dodal.devices.tetramm import TetrammDetector from dodal.devices.undulator import Undulator from dodal.log import set_beamline as set_log_beamline -from dodal.utils import BeamlinePrefix, get_beamline_name +from dodal.utils import BeamlinePrefix, get_beamline_name, skip_device BL = get_beamline_name("i22") -PREFIX = BeamlinePrefix(BL) set_log_beamline(BL) set_utils_beamline(BL) @@ -43,149 +42,243 @@ ) -@device_factory() -def saxs() -> PilatusDetector: - metadata_holder = NXSasMetadataHolder( - x_pixel_size=(1.72e-1, "mm"), - y_pixel_size=(1.72e-1, "mm"), - description="Dectris Pilatus3 2M", - type="Photon Counting Hybrid Pixel", - sensor_material="silicon", - sensor_thickness=(0.45, "mm"), - distance=(4711.833684146172, "mm"), - ) - return NXSasPilatus( - prefix=f"{PREFIX.beamline_prefix}-EA-PILAT-01:", - path_provider=get_path_provider(), +def saxs( + wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False +) -> PilatusDetector: + return device_instantiation( + NXSasPilatus, + "saxs", + "-EA-PILAT-01:", + wait_for_connection, + fake_with_ophyd_sim, drv_suffix="CAM:", - hdf_suffix=HDF5_PREFIX, - metadata_holder=metadata_holder, + hdf_suffix="HDF5:", + metadata_holder=NXSasMetadataHolder( + x_pixel_size=(1.72e-1, "mm"), + y_pixel_size=(1.72e-1, "mm"), + description="Dectris Pilatus3 2M", + type="Photon Counting Hybrid Pixel", + sensor_material="silicon", + sensor_thickness=(0.45, "mm"), + distance=(4711.833684146172, "mm"), + ), + path_provider=get_path_provider(), ) -@device_factory() -def synchrotron() -> Synchrotron: - return Synchrotron() +def synchrotron( + wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False +) -> Synchrotron: + return device_instantiation( + Synchrotron, + "synchrotron", + "", + wait_for_connection, + fake_with_ophyd_sim, + ) -@device_factory() -def waxs() -> PilatusDetector: - metadata_holder = NXSasMetadataHolder( - x_pixel_size=(1.72e-1, "mm"), - y_pixel_size=(1.72e-1, "mm"), - description="Dectris Pilatus3 2M", - type="Photon Counting Hybrid Pixel", - sensor_material="silicon", - sensor_thickness=(0.45, "mm"), - distance=(175.4199417092314, "mm"), - ) - return NXSasPilatus( - prefix=f"{PREFIX.beamline_prefix}-EA-PILAT-03:", - path_provider=get_path_provider(), +def waxs( + wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False +) -> PilatusDetector: + return device_instantiation( + NXSasPilatus, + "waxs", + "-EA-PILAT-03:", + wait_for_connection, + fake_with_ophyd_sim, drv_suffix="CAM:", - hdf_suffix=HDF5_PREFIX, - metadata_holder=metadata_holder, + hdf_suffix="HDF5:", + metadata_holder=NXSasMetadataHolder( + x_pixel_size=(1.72e-1, "mm"), + y_pixel_size=(1.72e-1, "mm"), + description="Dectris Pilatus3 2M", + type="Photon Counting Hybrid Pixel", + sensor_material="silicon", + sensor_thickness=(0.45, "mm"), + distance=(175.4199417092314, "mm"), + ), + path_provider=get_path_provider(), ) -@device_factory() -def i0() -> TetrammDetector: - return TetrammDetector( - prefix=f"{PREFIX.beamline_prefix}-EA-XBPM-02:", - path_provider=get_path_provider(), +def i0( + wait_for_connection: bool = True, + fake_with_ophyd_sim: bool = False, +) -> TetrammDetector: + return device_instantiation( + TetrammDetector, + "i0", + "-EA-XBPM-02:", + wait_for_connection, + fake_with_ophyd_sim, type="Cividec Diamond XBPM", + path_provider=get_path_provider(), ) -@device_factory() -def it() -> TetrammDetector: - return TetrammDetector( - prefix=f"{PREFIX.beamline_prefix}-EA-TTRM-02:", - path_provider=get_path_provider(), +def it( + wait_for_connection: bool = True, + fake_with_ophyd_sim: bool = False, +) -> TetrammDetector: + return device_instantiation( + TetrammDetector, + "it", + "-EA-TTRM-02:", + wait_for_connection, + fake_with_ophyd_sim, type="PIN Diode", + path_provider=get_path_provider(), ) -@device_factory() -def vfm() -> FocusingMirror: - return FocusingMirror( - prefix=f"{PREFIX.beamline_prefix}-OP-KBM-01:VFM:", +def vfm( + wait_for_connection: bool = True, + fake_with_ophyd_sim: bool = False, +) -> FocusingMirror: + return device_instantiation( + FocusingMirror, + "vfm", + "-OP-KBM-01:VFM:", + wait_for_connection, + fake_with_ophyd_sim, ) -@device_factory() -def hfm() -> FocusingMirror: - return FocusingMirror( - prefix=f"{PREFIX.beamline_prefix}|-OP-KBM-01:HFM:", +def hfm( + wait_for_connection: bool = True, + fake_with_ophyd_sim: bool = False, +) -> FocusingMirror: + return device_instantiation( + FocusingMirror, + "hfm", + "-OP-KBM-01:HFM:", + wait_for_connection, + fake_with_ophyd_sim, ) -@device_factory() -def dcm() -> DoubleCrystalMonochromator: - crystal_1_metadata = CrystalMetadata( - usage="Bragg", - type="silicon", - reflection=(1, 1, 1), - d_spacing=(3.13475, "nm"), - ) - crystal_2_metadata = CrystalMetadata( - usage="Bragg", - type="silicon", - reflection=(1, 1, 1), - d_spacing=(3.13475, "nm"), - ) - return DoubleCrystalMonochromator( - motion_prefix=f"{PREFIX.beamline_prefix}-MO-DCM-01:", - temperature_prefix=f"{PREFIX.beamline_prefix}-DI-DCM-01:", - crystal_1_metadata=crystal_1_metadata, - crystal_2_metadata=crystal_2_metadata, +def dcm( + wait_for_connection: bool = True, + fake_with_ophyd_sim: bool = False, +) -> DoubleCrystalMonochromator: + return device_instantiation( + DoubleCrystalMonochromator, + "dcm", + "", + wait_for_connection, + fake_with_ophyd_sim, + bl_prefix=False, + motion_prefix=f"{BeamlinePrefix(BL).beamline_prefix}-MO-DCM-01:", + temperature_prefix=f"{BeamlinePrefix(BL).beamline_prefix}-DI-DCM-01:", + crystal_1_metadata=CrystalMetadata( + usage="Bragg", + type="silicon", + reflection=(1, 1, 1), + d_spacing=(3.13475, "nm"), + ), + crystal_2_metadata=CrystalMetadata( + usage="Bragg", + type="silicon", + reflection=(1, 1, 1), + d_spacing=(3.13475, "nm"), + ), ) -@device_factory() -def undulator() -> Undulator: - return Undulator( - prefix=f"{PREFIX.insertion_prefix}-MO-SERVC-01:", - id_gap_lookup_table_path="/dls_sw/i22/software/daq_configuration/lookup/BeamLine_Undulator_toGap.txt", +def undulator( + wait_for_connection: bool = True, + fake_with_ophyd_sim: bool = False, +) -> Undulator: + return device_instantiation( + Undulator, + "undulator", + f"{BeamlinePrefix(BL).insertion_prefix}-MO-SERVC-01:", + wait_for_connection, + fake_with_ophyd_sim, + bl_prefix=False, poles=80, length=2.0, + id_gap_lookup_table_path="/dls_sw/i22/software/daq_configuration/lookup/BeamLine_Undulator_toGap.txt", ) -@device_factory() -def slits_1() -> Slits: - return Slits(prefix=f"{PREFIX.beamline_prefix}-AL-SLITS-01:") +def slits_1( + wait_for_connection: bool = True, + fake_with_ophyd_sim: bool = False, +) -> Slits: + return numbered_slits( + 1, + wait_for_connection, + fake_with_ophyd_sim, + ) -@device_factory() -def slits_2() -> Slits: - return Slits(prefix=f"{PREFIX.beamline_prefix}-AL-SLITS-02:") +def slits_2( + wait_for_connection: bool = True, + fake_with_ophyd_sim: bool = False, +) -> Slits: + return numbered_slits( + 2, + wait_for_connection, + fake_with_ophyd_sim, + ) -@device_factory() -def slits_3() -> Slits: - return Slits(prefix=f"{PREFIX.beamline_prefix}-AL-SLITS-03:") +def slits_3( + wait_for_connection: bool = True, + fake_with_ophyd_sim: bool = False, +) -> Slits: + return numbered_slits( + 3, + wait_for_connection, + fake_with_ophyd_sim, + ) -@device_factory() -def slits_4() -> Slits: - return Slits(prefix=f"{PREFIX.beamline_prefix}-AL-SLITS-04:") +def slits_4( + wait_for_connection: bool = True, + fake_with_ophyd_sim: bool = False, +) -> Slits: + return numbered_slits( + 4, + wait_for_connection, + fake_with_ophyd_sim, + ) -@device_factory() -def slits_5() -> Slits: - return Slits(prefix=f"{PREFIX.beamline_prefix}-AL-SLITS-05:") +def slits_5( + wait_for_connection: bool = True, + fake_with_ophyd_sim: bool = False, +) -> Slits: + return numbered_slits( + 5, + wait_for_connection, + fake_with_ophyd_sim, + ) -@device_factory() -def slits_6() -> Slits: - return Slits(prefix=f"{PREFIX.beamline_prefix}-AL-SLITS-06:") +def slits_6( + wait_for_connection: bool = True, + fake_with_ophyd_sim: bool = False, +) -> Slits: + return numbered_slits( + 6, + wait_for_connection, + fake_with_ophyd_sim, + ) -@device_factory() -def fswitch() -> FSwitch: - return FSwitch( - prefix=f"{PREFIX.beamline_prefix}-MO-FSWT-01:", +def fswitch( + wait_for_connection: bool = True, + fake_with_ophyd_sim: bool = False, +) -> FSwitch: + return device_instantiation( + FSwitch, + "fswitch", + "-MO-FSWT-01:", + wait_for_connection, + fake_with_ophyd_sim, lens_geometry="paraboloid", cylindrical=True, lens_material="Beryllium", @@ -194,55 +287,94 @@ def fswitch() -> FSwitch: # Must document what PandAs are physically connected to # See: https://github.com/bluesky/ophyd-async/issues/284 -@device_factory() -def panda1() -> HDFPanda: - return HDFPanda( - prefix=f"{PREFIX.beamline_prefix}-EA-PANDA-01:", +def panda1( + wait_for_connection: bool = True, + fake_with_ophyd_sim: bool = False, +) -> HDFPanda: + return device_instantiation( + HDFPanda, + "panda1", + "-EA-PANDA-01:", + wait_for_connection, + fake_with_ophyd_sim, path_provider=get_path_provider(), ) -@device_factory(skip=True) -def panda2() -> HDFPanda: - return HDFPanda( - prefix=f"{PREFIX.beamline_prefix}-EA-PANDA-02:", +@skip_device() +def panda2( + wait_for_connection: bool = True, + fake_with_ophyd_sim: bool = False, +) -> HDFPanda: + return device_instantiation( + HDFPanda, + "panda2", + "-EA-PANDA-02:", + wait_for_connection, + fake_with_ophyd_sim, path_provider=get_path_provider(), ) -@device_factory(skip=True) -def panda3() -> HDFPanda: - return HDFPanda( - prefix=f"{PREFIX.beamline_prefix}-EA-PANDA-03:", +@skip_device() +def panda3( + wait_for_connection: bool = True, + fake_with_ophyd_sim: bool = False, +) -> HDFPanda: + return device_instantiation( + HDFPanda, + "panda3", + "-EA-PANDA-03:", + wait_for_connection, + fake_with_ophyd_sim, path_provider=get_path_provider(), ) -@device_factory(skip=True) -def panda4() -> HDFPanda: - return HDFPanda( - prefix=f"{PREFIX.beamline_prefix}-EA-PANDA-04:", +@skip_device() +def panda4( + wait_for_connection: bool = True, + fake_with_ophyd_sim: bool = False, +) -> HDFPanda: + return device_instantiation( + HDFPanda, + "panda4", + "-EA-PANDA-04:", + wait_for_connection, + fake_with_ophyd_sim, path_provider=get_path_provider(), ) -@device_factory() -def oav() -> AravisDetector: - metadata_holder = NXSasMetadataHolder( - x_pixel_size=(3.45e-3, "mm"), # Double check this figure - y_pixel_size=(3.45e-3, "mm"), - description="AVT Mako G-507B", - distance=(-1.0, "m"), - ) - return NXSasOAV( - prefix=f"{PREFIX.beamline_prefix}-DI-OAV-01:", +def oav( + wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False +) -> AravisDetector: + return device_instantiation( + NXSasOAV, + "oav", + "-DI-OAV-01:", + wait_for_connection, + fake_with_ophyd_sim, drv_suffix="DET:", - hdf_suffix=HDF5_PREFIX, + hdf_suffix="HDF5:", + metadata_holder=NXSasMetadataHolder( + x_pixel_size=(3.45e-3, "mm"), # Double check this figure + y_pixel_size=(3.45e-3, "mm"), + description="AVT Mako G-507B", + distance=(-1.0, "m"), + ), path_provider=get_path_provider(), - metadata_holder=metadata_holder, ) -@device_factory() -def linkam() -> Linkam3: - return Linkam3(prefix=f"{PREFIX.beamline_prefix}-EA-TEMPC-05") +@skip_device() +def linkam( + wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False +) -> Linkam3: + return device_instantiation( + Linkam3, + "linkam", + "-EA-TEMPC-05", + wait_for_connection, + fake_with_ophyd_sim, + ) diff --git a/src/dodal/common/beamlines/beamline_utils.py b/src/dodal/common/beamlines/beamline_utils.py index 5a9dcac1f8..ac1418e44d 100644 --- a/src/dodal/common/beamlines/beamline_utils.py +++ b/src/dodal/common/beamlines/beamline_utils.py @@ -1,23 +1,15 @@ import inspect from collections.abc import Callable -from typing import Annotated, Final, TypeVar, cast +from typing import Final, TypeVar, cast from bluesky.run_engine import call_in_bluesky_event_loop from ophyd import Device as OphydV1Device from ophyd.sim import make_fake_device -from ophyd_async.core import DEFAULT_TIMEOUT from ophyd_async.core import Device as OphydV2Device from ophyd_async.core import wait_for_connection as v2_device_wait_for_connection from dodal.common.types import UpdatingPathProvider -from dodal.utils import ( - AnyDevice, - BeamlinePrefix, - D, - DeviceInitializationController, - SkipType, - skip_device, -) +from dodal.utils import AnyDevice, BeamlinePrefix, skip_device DEFAULT_CONNECTION_TIMEOUT: Final[float] = 5.0 @@ -133,34 +125,6 @@ def device_instantiation( return device_instance -def _cache_device(name: str) -> Callable[[OphydV2Device], None]: - def cache_device(device: OphydV2Device): - ACTIVE_DEVICES[name] = device - - return cache_device - - -def device_factory( - *, - eager_connect: Annotated[bool, "Connect or raise Exception at startup"] = True, - use_factory_name: Annotated[bool, "Use factory name as name of device"] = True, - timeout: Annotated[float, "Timeout for connecting to the device"] = DEFAULT_TIMEOUT, - mock: Annotated[bool, "Use Signals with mock backends for device"] = False, - skip: Annotated[ - SkipType, - "mark the factory to be (conditionally) skipped when beamline is imported by external program", - ] = False, -) -> Callable[[Callable[[], D]], DeviceInitializationController[D]]: - def decorator(factory: Callable[[], D]) -> DeviceInitializationController[D]: - controller = DeviceInitializationController( - factory, eager_connect, use_factory_name, timeout, mock, skip - ) - controller.add_callback(_cache_device(factory.__name__)) - return controller - - return decorator - - def set_path_provider(provider: UpdatingPathProvider): global PATH_PROVIDER diff --git a/src/dodal/common/beamlines/device_helpers.py b/src/dodal/common/beamlines/device_helpers.py index 43c3c88e4c..8e699361aa 100644 --- a/src/dodal/common/beamlines/device_helpers.py +++ b/src/dodal/common/beamlines/device_helpers.py @@ -2,8 +2,6 @@ from dodal.devices.slits import Slits from dodal.utils import skip_device -HDF5_PREFIX = "HDF5:" - @skip_device() def numbered_slits( diff --git a/src/dodal/devices/focusing_mirror.py b/src/dodal/devices/focusing_mirror.py index 3ac457bb00..b8e360ba13 100644 --- a/src/dodal/devices/focusing_mirror.py +++ b/src/dodal/devices/focusing_mirror.py @@ -132,12 +132,7 @@ class FocusingMirror(StandardReadable): """Focusing Mirror""" def __init__( - self, - prefix: str, - name: str = "", - bragg_to_lat_lut_path: str | None = None, - x_suffix: str = "X", - y_suffix: str = "Y", + self, name, prefix, bragg_to_lat_lut_path=None, x_suffix="X", y_suffix="Y" ): self.bragg_to_lat_lookup_table_path = bragg_to_lat_lut_path self.yaw_mrad = Motor(prefix + "YAW") @@ -165,12 +160,12 @@ class FocusingMirrorWithStripes(FocusingMirror): """A focusing mirror where the stripe material can be changed. This is usually done based on the energy of the beamline.""" - def __init__(self, prefix: str, name: str = "", *args, **kwargs): + def __init__(self, name, prefix, *args, **kwargs): self.stripe = epics_signal_rw(MirrorStripe, prefix + "STRP:DVAL") # apply the current set stripe setting self.apply_stripe = epics_signal_x(prefix + "CHANGE.PROC") - super().__init__(prefix, name, *args, **kwargs) + super().__init__(name, prefix, *args, **kwargs) def energy_to_stripe(self, energy_kev) -> MirrorStripe: # In future, this should be configurable per-mirror diff --git a/src/dodal/devices/i22/dcm.py b/src/dodal/devices/i22/dcm.py index 682eaa199a..6424dfc604 100644 --- a/src/dodal/devices/i22/dcm.py +++ b/src/dodal/devices/i22/dcm.py @@ -43,6 +43,7 @@ def __init__( temperature_prefix: str, crystal_1_metadata: CrystalMetadata | None = None, crystal_2_metadata: CrystalMetadata | None = None, + prefix: str = "", name: str = "", ) -> None: with self.add_children_as_readables(): diff --git a/src/dodal/devices/linkam3.py b/src/dodal/devices/linkam3.py index 7ed213c875..7f1c0b77c9 100644 --- a/src/dodal/devices/linkam3.py +++ b/src/dodal/devices/linkam3.py @@ -34,7 +34,7 @@ class Linkam3(StandardReadable): tolerance: float = 0.5 settle_time: int = 0 - def __init__(self, prefix: str, name: str = ""): + def __init__(self, prefix: str, name: str): self.temp = epics_signal_r(float, prefix + "TEMP:") self.dsc = epics_signal_r(float, prefix + "DSC:") self.start_heat = epics_signal_rw(bool, prefix + "STARTHEAT:") diff --git a/src/dodal/devices/tetramm.py b/src/dodal/devices/tetramm.py index 8021b3c221..844e932376 100644 --- a/src/dodal/devices/tetramm.py +++ b/src/dodal/devices/tetramm.py @@ -219,7 +219,7 @@ def __init__( self, prefix: str, path_provider: PathProvider, - name: str = "", + name: str, type: str | None = None, **scalar_sigs: str, ) -> None: diff --git a/src/dodal/utils.py b/src/dodal/utils.py index 7a591fa49d..99aa1f7c41 100644 --- a/src/dodal/utils.py +++ b/src/dodal/utils.py @@ -6,14 +6,13 @@ import string from collections.abc import Callable, Iterable, Mapping from dataclasses import dataclass -from functools import update_wrapper, wraps +from functools import wraps from importlib import import_module from inspect import signature from os import environ from types import ModuleType from typing import ( Any, - Generic, Protocol, TypeGuard, TypeVar, @@ -36,7 +35,6 @@ Triggerable, WritesExternalAssets, ) -from bluesky.run_engine import call_in_bluesky_event_loop from ophyd.device import Device as OphydV1Device from ophyd_async.core import Device as OphydV2Device @@ -97,8 +95,6 @@ def __post_init__(self): T = TypeVar("T", bound=AnyDevice) -D = TypeVar("D", bound=OphydV2Device) -SkipType = bool | Callable[[], bool] def skip_device(precondition=lambda: True): @@ -114,101 +110,6 @@ def wrapper(*args, **kwds) -> T: return decorator -class DeviceInitializationController(Generic[D]): - def __init__( - self, - factory: Callable[[], D], - eager_connect: bool, - use_factory_name: bool, - timeout: float, - mock: bool, - skip: SkipType, - ): - self._factory: Callable[[], D] = factory - self._cached_device: D | None = None - self._callbacks: list[Callable[[D], None]] = [] - self._eager_connect = eager_connect - self._use_factory_name = use_factory_name - self._timeout = timeout - self._mock = mock - self._skip = skip - update_wrapper(self, factory) - - @property - def skip(self) -> bool: - return self._skip() if callable(self._skip) else self._skip - - @property - def device(self) -> D | None: - return self._cached_device - - def add_callback(self, callback: Callable[[D], None]): - self._callbacks.append(callback) - - def __call__( - self, - connect_immediately: bool | None = None, - name: str | None = None, - connection_timeout: float | None = None, - mock: bool | None = None, - ) -> D: - """Returns an instance of the Device the wrapped factory produces: the same - instance will be returned if this method is called multiple times, and arguments - may be passed to override this Controller's configuration. - Once the device is connected, the value of mock must be consistent, or connect - must be False. - - - Args: - connect_immediately (bool | None, optional): whether to call connect on the - device before returning it- connect is idempotent for ophyd-async devices. - Not connecting to the device allows for the instance to be created prior - to the RunEngine event loop being configured or for connect to be called - lazily e.g. by the `ensure_connected` stub. Defaults to None, which defers - to the eager_connect parameter of this Controller. - name (str | None, optional): an override name to give the device, which is - also used to name its children. Defaults to None, which does not name the - device unless the device has no name and this Controller is configured to - use_factory_name, which propagates the name of the wrapped factory - function to the device instance. - connection_timeout (float | None, optional): an override timeout length in - seconds for the connect method, if it is called. Defaults to None, which - defers to the timeout configured for this Controller: the default uses - ophyd_async's DEFAULT_TIMEOUT. - mock (bool | None, optional): overrides whether to connect to Mock signal - backends, if connect is called. Defaults to None, which uses the mock - parameter of this Controller. This value must be used consistently when - connect is called on the Device. - - Returns: - D: a singleton instance of the Device class returned by the wrapped factory. - """ - if self.device is not None: - device = self.device - else: - device = self._factory() - - if connect_immediately or connect_immediately is None and self._eager_connect: - call_in_bluesky_event_loop( - device.connect( - timeout=connection_timeout - if connection_timeout is not None - else self._timeout, - mock=mock if mock is not None else self._mock, - ) - ) - - if name: - device.set_name(name) - elif not device.name and self._use_factory_name: - device.set_name(self._factory.__name__) - - self._cached_device = device - for callback in self._callbacks: - callback(device) - return device - - def make_device( module: str | ModuleType, device_name: str, @@ -301,13 +202,7 @@ def invoke_factories( dependent_name = leaves.pop() params = {name: devices[name] for name in dependencies[dependent_name]} try: - factory = factories[dependent_name] - if isinstance(factory, DeviceInitializationController): - # replace with an arg - # https://github.com/DiamondLightSource/dodal/issues/844 - devices[dependent_name] = factory(mock=kwargs.get("mock", False)) - else: - devices[dependent_name] = factory(**params, **kwargs) + devices[dependent_name] = factories[dependent_name](**params, **kwargs) except Exception as e: exceptions[dependent_name] = e @@ -369,8 +264,6 @@ def collect_factories( def _is_device_skipped(func: AnyDeviceFactory) -> bool: - if isinstance(func, DeviceInitializationController): - return func.skip return getattr(func, "__skip__", False) diff --git a/tests/common/beamlines/test_beamline_utils.py b/tests/common/beamlines/test_beamline_utils.py index dffc43e470..91b6b4edaf 100644 --- a/tests/common/beamlines/test_beamline_utils.py +++ b/tests/common/beamlines/test_beamline_utils.py @@ -12,12 +12,11 @@ from dodal.beamlines import i03 from dodal.common.beamlines import beamline_utils from dodal.devices.eiger import EigerDetector -from dodal.devices.focusing_mirror import FocusingMirror from dodal.devices.motors import XYZPositioner from dodal.devices.smargon import Smargon from dodal.devices.zebra import Zebra from dodal.log import LOGGER -from dodal.utils import DeviceInitializationController, make_all_devices +from dodal.utils import make_all_devices from ...conftest import mock_beamline_module_filepaths @@ -135,51 +134,3 @@ def test_wait_for_v2_device_connection_passes_through_timeout(kwargs, expected_t mock=ANY, timeout=expected_timeout, ) - - -def dummy_mirror() -> FocusingMirror: - mirror = MagicMock(spec=FocusingMirror) - connect = AsyncMock() - mirror.connect = connect - - def set_name(name: str): - mirror.name = name # type: ignore - - mirror.set_name.side_effect = set_name - mirror.set_name("") - return mirror - - -def test_device_controller_names(): - @beamline_utils.device_factory(eager_connect=False) - def device() -> FocusingMirror: - return dummy_mirror() - - mirror = device(name="foo") - assert mirror.name == "foo" - assert mirror.connect.call_count == 0 # type: ignore - - -def test_device_controller_connect(RE): - @beamline_utils.device_factory(mock=True) - def device() -> FocusingMirror: - return dummy_mirror() - - mirror = device() - assert mirror.name == "device" - assert mirror.connect.call_count == 1 # type: ignore - - -def test_skip(RE): - skip = True - - def _skip() -> bool: - return skip - - controller = beamline_utils.device_factory(skip=_skip)(dummy_mirror) - - assert isinstance(controller, DeviceInitializationController) - assert controller.skip - - skip = False - assert not controller.skip diff --git a/tests/fake_device_factory_beamline.py b/tests/fake_device_factory_beamline.py deleted file mode 100644 index 101927fab1..0000000000 --- a/tests/fake_device_factory_beamline.py +++ /dev/null @@ -1,24 +0,0 @@ -from bluesky.protocols import Readable, Reading, SyncOrAsync -from event_model.documents.event_descriptor import DataKey -from ophyd_async.core import Device - -from dodal.common.beamlines.beamline_utils import device_factory -from dodal.devices.cryostream import CryoStream - - -class ReadableDevice(Readable, Device): - def read(self) -> SyncOrAsync[dict[str, Reading]]: - return {} - - def describe(self) -> SyncOrAsync[dict[str, DataKey]]: - return {} - - -@device_factory(skip=True, eager_connect=False) -def device_a() -> ReadableDevice: - return ReadableDevice("readable") - - -@device_factory(skip=lambda: True, eager_connect=False) -def device_c() -> CryoStream: - return CryoStream("FOO:") diff --git a/tests/test_utils.py b/tests/test_utils.py index 08f6a34f54..f755c2ad75 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -131,33 +131,6 @@ def test_make_device_dependency_throws(): make_device(fake_beamline, "device_z") -def test_device_factory_skips(): - import tests.fake_device_factory_beamline as fake_beamline - - devices, exceptions = make_all_devices(fake_beamline) - assert len(devices) == 0 - assert len(exceptions) == 0 - - -def test_device_factory_can_ignore_skip(): - import tests.fake_device_factory_beamline as fake_beamline - - devices, exceptions = make_all_devices(fake_beamline, include_skipped=True) - assert len(devices) == 2 - assert len(exceptions) == 0 - - -def test_device_factory_can_rename(RE): - from tests.fake_device_factory_beamline import device_c - - cryo = device_c(mock=True, connect_immediately=True) - assert cryo.name == "device_c" - assert cryo.fine.name == "device_c-fine" - device_c(name="cryo") - assert cryo.name == "cryo" - assert cryo.fine.name == "cryo-fine" - - def device_a() -> Readable: return MagicMock()