DeviceCollector is an context manager that can be used to connect one or more devices to their backends in an easy-to-write way:
# Outside a running event loop
with DeviceCollector():
x = MyMotor(...)
y = MyMotor(...)
det = MyDetector(...)
# Inside a running event loop
async with DeviceCollector():
x = MyMotor(...)
y = MyMotor(...)
det = MyDetector(...)
Because it is ophyd-async, a running event loop is actually needed in both cases. The outside case is for when there is already an event loop running in the background, which will have been created by the RunEngine constructor when, for example, you have a RunEngine in an IPython terminal.
This introduces two potential failure cases:
If you call with DeviceCollector() before RE = RunEngine() the call will fail with a cryptic error message about being unable to find the event loop, which will confuse anyone who does not know about this hidden functionality.
If you call async with DeviceCollector() inside a function invoked by asyncio.run (as we do in the ophyd-async tests) it will work without a RunEngine. However if you then create a RunEngine and try to use the devices you created inside it it will fail because the RunEngine uses its own background event loop by default and the devices are using the main event loop. The RunEngine can be set to use the same event loop as the devices by passing an additional argument to the constructor but again, the user has to know that.
Currently we are providing a totally RunEngine-independent route to making and controlling ophyd-async devices (currently async with) and an async-free route which annoyingly requires a RunEngine (with).
Finally, in the interest of modularity, it is desirable to minimize or even remove ophyd-async's dependency on bluesky.
First Steps
The first step is to improve the error message in the first failure case so the user is at least informed that they need to make a RunEngine first. Ideally the message should point to a docs page that explains these nuances and what the two possible context managers give you.
Other Solutions Discussed
Make DeviceCollector and RunEngine both do the same thing in their constructors: use the bluesky event loop if it exists, if not, create one. That way the order in which they are called does not matter
Make the RunEngine a required argument to DeviceCollector() so the user has to create it first. This breaks the RunEngine-free, async with case and forces a RunEngine to exist in all cases, increasing the dependency on bluesky.
Background
DeviceCollector
is an context manager that can be used to connect one or more devices to their backends in an easy-to-write way:Because it is ophyd-async, a running event loop is actually needed in both cases. The outside case is for when there is already an event loop running in the background, which will have been created by the
RunEngine
constructor when, for example, you have aRunEngine
in an IPython terminal.This introduces two potential failure cases:
with DeviceCollector()
beforeRE = RunEngine()
the call will fail with a cryptic error message about being unable to find the event loop, which will confuse anyone who does not know about this hidden functionality.async with DeviceCollector()
inside a function invoked byasyncio.run
(as we do in the ophyd-async tests) it will work without aRunEngine
. However if you then create aRunEngine
and try to use the devices you created inside it it will fail because theRunEngine
uses its own background event loop by default and the devices are using the main event loop. TheRunEngine
can be set to use the same event loop as the devices by passing an additional argument to the constructor but again, the user has to know that.Currently we are providing a totally
RunEngine
-independent route to making and controlling ophyd-async devices (currentlyasync with
) and an async-free route which annoyingly requires aRunEngine
(with
).Finally, in the interest of modularity, it is desirable to minimize or even remove ophyd-async's dependency on bluesky.
First Steps
The first step is to improve the error message in the first failure case so the user is at least informed that they need to make a
RunEngine
first. Ideally the message should point to a docs page that explains these nuances and what the two possible context managers give you.Other Solutions Discussed
DeviceCollector
andRunEngine
both do the same thing in their constructors: use the bluesky event loop if it exists, if not, create one. That way the order in which they are called does not matterRunEngine
a required argument toDeviceCollector()
so the user has to create it first. This breaks theRunEngine
-free,async with
case and forces aRunEngine
to exist in all cases, increasing the dependency on bluesky.