Replies: 6 comments 3 replies
-
|
Thanks @tpoliaw for this RFC. I am new comer in this system. I wonder if there is a way to set up a separate dedicated RFC channel or tracker in our current used system so RFC requests will no longer being buried inside normal developement issues? |
Beta Was this translation helpful? Give feedback.
-
|
It's not explicitly stated above but after discussing with @tpoliaw I think this means that we can get an un-connected, real device (which will be a singleton owned by the manager) by doing: from dodal.beamline.i03 import stage
my_stage = stage()But if I want to pass arguments (so want a mock or a connected device or if the device needs arguments to build) you need to do: from dodal.beamline.i03 import stage
my_stage = stage.build(...)I think what this will mean for beamline users is that we will want to teach them to do We also discussed that we can remove caching and just treat calls to
It was decided, with @abbiemery also in the convo, that the above was rare enough (and caused enough complexity in the building) to warrant no caching. We also discussed that devices that depend on other devices should hold them as Together I think these decisions answer all the questions above (but please add anything I've forgotten or we can rediscuss) |
Beta Was this translation helpful? Give feedback.
-
|
Thanks @DominicOram, I think the main takeaway is that there will no longer be any caching. Calling Within a single call to I will get a PR together that implements the above (partial still-work-in-progress proof of concept in #1549) For clarity against the original questions
Both yes, building a single device should use the same build arguments for all dependencies
With no caching involved, a new instance will always be returned when calling
Yes. Connecting is still useful and the complexity is avoided by recreating devices each time |
Beta Was this translation helpful? Give feedback.
-
It would be good to hear from anyone with any ideas that would be make things more intuitive |
Beta Was this translation helpful? Give feedback.
-
|
Does it mean that original idea of introducing DeviceManager is not needed anymore? If yes maybe we could still have it as a, well, rather device tracker such that we can always find out which instances of which class do we have currently initiated? |
Beta Was this translation helpful? Give feedback.
-
|
Alternative to having to call # ixx.py
devices = DeviceManager()
def m1() -> Motor:
...
def det() -> Detector:
...
# in cli
from ixx import *
devices.auto_connect(connect=True, mock=True)
motor = m1() # motor is mocked and connectedThoughts, @DominicOram? |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Current system
The current
@device_factorydecorator takes a function and wraps it in aDeviceInitializationController. This controller is callable, taking a handful of its own arguments and passing the rest to the underlying function so that a device function such asdef stage() -> Stage: ...can end up being called asstage(mock=True, connection_timeout=42)which isn't obviously possible from the signature. If the function itself takes a parameter that clashes with one of the wrapper's parameters, it will not be passed through.To build these devices, the factories are found by checking all attributes of a module to see if they are instances of the wrapper (including special handling to avoid imports counting as functions again), then the dependencies between them are found and they are built in the correct order - and then connected.
The problems
blueapiwhen it imports a module to load devices. This is the main motivation for this change and is mentioned in Move util functions out of dodal repo #1189 and Remove dependency on dodal blueapi#1203Proposed new approach
Introduce a new device manager class that can keep track of all registered device factory functions. The idea would take a similar approach to fastAPI when an
appis created and then endpoint functions are decorated using a method on the app. In our case, that would look something likeThe
factorydecorator would take the same arguments as the currentdevice_factory(or none). Thedevicesinstance would track the factories it is aware of so a singlebuild_allmethod would be enough to build all devices in a module without having to do any kind of runtime inspection of types. It would also be able to build subsets of devices and provide dependencies as required in a similar way to the current system (egdef stage(stage_x, stage_y): ...would automatically havestage_xandstage_ybuilt and passed in).In addition to dependencies on other devices, external dependencies could be required, eg if a detector requires a
path_providerit could expect one via a parameter and the device manager could forward a user provided instance via an approach similar to fixtures in pytest. egThe functions returned by the decorator would maintain a reference to the manager allowing it to build its own dependencies if required allowing users to do something like
The factories would cache the devices in a similar way to the existing decorator so that calling
stage.build()twice would return the same device.Questions and/or remaining decisions to be made
Most of the ambiguity is around handling connecting the devices after creation.
The current factory decorator can optionally connect the device as well as build it when called (via its new
connect_immediatelymagic parameter).With the new approach where there are no new methods to pass directly to the factory but we could do something similar via arguments to
buildso thatstage.build(connect_immediately=True)would connect the newly created device.In this case, should the dependencies also be connected? Presumably yes?
If
mock=Trueis passed tostage.build, should the dependencies also be mocked?If a dependency is mocked as part of building a parent device, should building it directly via its own build method, return the same (mocked) instance even if
mock=Trueis not set? egin this scenario is it more surprising for
childto be mocked or not connected?Should passing
mock=Trueto a dependency after building the parent devicewithout mock
Should we support calling factories multiple times?
The current version raises an exception if a device factory is called twice.
Should it handle connecting things at all or just concern itself with building things?
Related reading
https://github.com/DiamondLightSource/dodal/blob/main/docs/explanations/decisions/0004-make-devices-factory.md
#878 and #881
Beta Was this translation helpful? Give feedback.
All reactions