Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parameterize Devices #18

Closed
callumforrester opened this issue Jan 24, 2023 · 8 comments
Closed

Parameterize Devices #18

callumforrester opened this issue Jan 24, 2023 · 8 comments
Labels
python Pull requests that update Python code question Further information is requested

Comments

@callumforrester
Copy link
Contributor

@DominicOram and @noemifrisina

Relates to #6, #3 and DiamondLightSource/hyperion#200

It seems, to avoid verbosity, we want module level devices so we can say

from i03 import dcm

and immediately use dcm as oppose to having a make_dcm() function that only instantiates the object when called.

However, we may want the device to do slightly different things, depending on context, when we import it, for example:

  • Use a different PV prefix, for example, if it's a simulated beamline vs. real (Find a good way to keep beamline and simulator devices in step #6) or if there are macros in the IOCs themselves that change the prefix (e.g. for the soft motors available in Diamond).
  • Test that the devices connect on import (in Ophyd v1, this is done by calling wait_for_connection()) so that it doesn't unexpectedly cause an error when first used
  • Explicitly not test that devices connect on import, for example because of long startup times, or because only a subset of devices are active on the beamline at any one time
  • Run custom checks on the devices (e.g. detector.hdf5.warmup()) or not, or sometimes, as above

If we take parameterization as a possible solution for these use cases, we should think about how to achieve it. We want to maximize granularity and flexibility while minimizing boilerplate. The two are likely to work in mutual contravention so we would need to find a good compromise. Also, depending on exactly how much flexibility we feel we need, we should keep in mind the possibility that the make_dcm() function above may actually be the least worst option.

@callumforrester
Copy link
Contributor Author

One immediate thought I had (and I'm not sure I like it) was using environment variables. Something like:

# <beamline>.py
import socket

from pydantic import BaseSettings

from .devices.simpledet import SimpleAreaDetector
from .devices.simstage import SimStage


class Settings(BaseSettings):
   # $HOSTNAME
    hostname: str = socket.gethostname().split(".")[0]
   # $CHECK_CONNECTION
    check_connection: bool = True
   # $WARMUP_DETECTOR
    warmup_detector: bool = True


stage = SimStage(name="sim_stage", prefix=f"{Settings.hostname}-MO-SIM-01:")
det = SimpleAreaDetector(name="adsim", prefix=f"{Settings.hostname}-AD-SIM-01:")

if Settings.check_connection:
    stage.wait_for_connection()
    det.wait_for_connection()

if Settings.warmup_detector:
    det.hdf.warmup()


__all__ = ["stage", "det"]

# startup.py

from dodal.<beamline> import det

This is fairly clunky and feels like it can be refined. It does present some level of flexibility but the path to changing settings is not obvious. We can't currently customize what happens depending on which devices are imported, I think that would require delving into the depths of importlib. Finally, there is lots of duplication between modules. We can move some of the settings to a global location but that adds complexity and more hidden magic.

@callumforrester
Copy link
Contributor Author

Another thought is to implement some form of lazy loading with the help of PEP 562 (https://peps.python.org/pep-0562/), that needs more thought.

@DominicOram
Copy link
Contributor

It seems, to avoid verbosity, we want module level devices so we can say
from i03 import dcm
and immediately use dcm as oppose to having a make_dcm() function that only instantiates the object when called.

Is this a requirement? In DiamondLightSource/hyperion#200 (comment) we explicitly don't have this, instead requiring that you call dcm() (which could be called make_dcm() I guess). I generally don't like doing too much work on import as this is a real PIA to test, I certainly don't want to have to have devices available for something to be importable. If we want further customisation we could just have the convention of dcm() being an initialized device, connected_dcm() one that's connected, warmed_detector() one that's warmed etc.

@callumforrester
Copy link
Contributor Author

Honestly, I would prefer dcm() or make_dcm() because it feels like we're just reinventing the constructor otherwise, so if we're all happy with that then I would say problem largely solved. Unless we still want to find a way to avoid supplying a prefix to each make_<device>() function, which sounds like a worthy pursuit.

@DominicOram
Copy link
Contributor

I think mostly solved except I still don't see a neat way to do #6.

@stan-dot stan-dot added question Further information is requested python Pull requests that update Python code labels Sep 25, 2024
@stan-dot
Copy link
Contributor

this feels like an echo from the distant early days of dodal, might be solved with all that we have in #483 etc

@DominicOram what do you think?

@callumforrester
Copy link
Contributor Author

Yeah, I think this is no longer relevant and can be closed

@stan-dot
Copy link
Contributor

excellent ❗

@stan-dot stan-dot closed this as not planned Won't fix, can't repro, duplicate, stale Oct 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
python Pull requests that update Python code question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants