Converting an existing beamline to use a DeviceManager#
This guide assumes you have a beamline module file (eg dodal/beamlines/ixx.py)
with a number of functions defined and decorated with @device_factory
(dodal.common.beamlines.beamline_utils).
Create a DeviceManager#
By convention this should be named devices but can be custom if there is good
reason.
from dodal.device_manager import DeviceManager
devices = DeviceManager()
This device manager keeps track of all device factories for a beamline making it easier to determine which are present and the order in which to build them to ensure dependencies are available before their dependents.
Replace the @device_factory decorators#
Each factory function should be decorated with @devices.factory (where
devices is the name of the DeviceManager created above) instead of
@device_factory. If no arguments are required, the parentheses can be omitted,
eg
@devices.factory()
def device() -> Device:
...
is equivalent to
@devices.factory
def device() -> Device:
...
If arguments (eg skip or mock) were passed to device_factory, these can
continue to be passed to devices.factory in the same way.
Replace get_path_provider with fixture#
Previously, the path_provider used by detectors was set and accessed as a global.
Where device factory functions used this global (via get_path_provider) they
should be updated to accept path_provider as a parameter. This can then be
passed in when creating the devices.
# Previous approach using `get_path_provider`...
@device_factory()
def panda() -> HDFPanda:
return HDFPanda(
f"{PREFIX.beamline_prefix}-EA-PANDA-01:",
path_provider=get_path_provider(),
)
# ... should be replaced with
@devices.factory()
def panda(path_provider: PathProvider) -> HDFPanda:
return HDFPanda(
f"{PREFIX.beamline_prefix}-EA-PANDA-01:",
path_provider=path_provider,
)
Where a beamline was setting the path provider via set_path_provider, a
fixture should be created which will provide a fallback instance to be used if
one is not passed in.
# The previous approach...
set_path_provider(PathProviderImplementation(...))
# ...should be removed and replaced with
@devices.fixture
def path_provider() -> PathProvider:
return PathProviderImplementation(...)
Replace factory calls with parameters#
Where one function previously called another to access another device, this should be replaced with a parameter of the same name. This is to prevent multiple instances of devices being created as functions no longer cache the devices they create.
# Previous example using a device factory function
@device_factory()
def beamsize() -> Beamsize:
return Beamsize(aperture_scatterguard())
# New version with `aperture_scatterguard` passed as a parameter
@devices.factory
def beamsize(aperture_scatterguard: ApertureScatterguard) -> Beamsize:
return Beamsize(aperture_scatterguard)
Merge multiple device managers#
If devices need to be shared between multiple beamlines, eg branch beamlines sharing optics components, an external device manager can be imported and merged into another.
# in ixx_shared.py
devices = DeviceManager()
@devices.factory
def common_device() -> CommonDevice:
...
# in ixx.py
from ixx_shared import devices as shared_devices
devices = DeviceManager()
devices.include(shared_devices)
# devices defined in the common beamline module can then be used as dependencies
# in the beamline module
@devices.factory
def beamline_device(common_device: CommonDevice) -> BeamlineDevice:
...