apsbits 1.0.0rc2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. apsbits/__init__.py +18 -0
  2. apsbits/_version.py +21 -0
  3. apsbits/core/__init__.py +11 -0
  4. apsbits/core/best_effort_init.py +38 -0
  5. apsbits/core/catalog_init.py +29 -0
  6. apsbits/core/run_engine_init.py +71 -0
  7. apsbits/demo_instrument/README.md +1 -0
  8. apsbits/demo_instrument/__init__.py +3 -0
  9. apsbits/demo_instrument/callbacks/__init__.py +1 -0
  10. apsbits/demo_instrument/callbacks/nexus_data_file_writer.py +60 -0
  11. apsbits/demo_instrument/callbacks/spec_data_file_writer.py +93 -0
  12. apsbits/demo_instrument/configs/__init__.py +1 -0
  13. apsbits/demo_instrument/configs/devices.yml +52 -0
  14. apsbits/demo_instrument/configs/devices_aps_only.yml +6 -0
  15. apsbits/demo_instrument/configs/iconfig.yml +81 -0
  16. apsbits/demo_instrument/configs/logging.yml +41 -0
  17. apsbits/demo_instrument/devices/__init__.py +1 -0
  18. apsbits/demo_instrument/plans/__init__.py +8 -0
  19. apsbits/demo_instrument/plans/dm_plans.py +111 -0
  20. apsbits/demo_instrument/plans/sim_plans.py +69 -0
  21. apsbits/demo_instrument/startup.py +63 -0
  22. apsbits/tests/__init__.py +1 -0
  23. apsbits/tests/conftest.py +32 -0
  24. apsbits/tests/test_device_factories.py +44 -0
  25. apsbits/tests/test_general.py +83 -0
  26. apsbits/tests/test_stored_dict.py +139 -0
  27. apsbits/utils/__init__.py +1 -0
  28. apsbits/utils/aps_functions.py +67 -0
  29. apsbits/utils/config_loaders.py +61 -0
  30. apsbits/utils/context_aware.py +187 -0
  31. apsbits/utils/controls_setup.py +113 -0
  32. apsbits/utils/create_new_instrument.py +144 -0
  33. apsbits/utils/helper_functions.py +113 -0
  34. apsbits/utils/logging_setup.py +211 -0
  35. apsbits/utils/make_devices_yaml.py +127 -0
  36. apsbits/utils/metadata.py +101 -0
  37. apsbits/utils/sim_creator.py +199 -0
  38. apsbits/utils/stored_dict.py +192 -0
  39. apsbits-1.0.0rc2.dist-info/LICENSE +48 -0
  40. apsbits-1.0.0rc2.dist-info/METADATA +349 -0
  41. apsbits-1.0.0rc2.dist-info/RECORD +44 -0
  42. apsbits-1.0.0rc2.dist-info/WHEEL +5 -0
  43. apsbits-1.0.0rc2.dist-info/entry_points.txt +2 -0
  44. apsbits-1.0.0rc2.dist-info/top_level.txt +1 -0
apsbits/__init__.py ADDED
@@ -0,0 +1,18 @@
1
+ # -*- coding: iso-8859-1 -*-
2
+
3
+ """Model Bluesky Data Acquisition Instrument."""
4
+
5
+ from apsbits.utils.logging_setup import configure_logging
6
+
7
+ configure_logging()
8
+
9
+ __package__ = "apsbits"
10
+ try:
11
+ from setuptools_scm import get_version
12
+
13
+ __version__ = get_version(root="..", relative_to=__file__)
14
+ del get_version
15
+ except (LookupError, ModuleNotFoundError):
16
+ from importlib.metadata import version
17
+
18
+ __version__ = version(__package__)
apsbits/_version.py ADDED
@@ -0,0 +1,21 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
5
+
6
+ TYPE_CHECKING = False
7
+ if TYPE_CHECKING:
8
+ from typing import Tuple
9
+ from typing import Union
10
+
11
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
12
+ else:
13
+ VERSION_TUPLE = object
14
+
15
+ version: str
16
+ __version__: str
17
+ __version_tuple__: VERSION_TUPLE
18
+ version_tuple: VERSION_TUPLE
19
+
20
+ __version__ = version = '1.0.0rc2'
21
+ __version_tuple__ = version_tuple = (1, 0, 0, 'rc2')
@@ -0,0 +1,11 @@
1
+ """
2
+ Utility support to start bluesky sessions.
3
+
4
+ Also contains setup code that MUST run before other code in this directory.
5
+ """
6
+
7
+ from apsbits.utils.helper_functions import debug_python
8
+ from apsbits.utils.helper_functions import mpl_setup
9
+
10
+ debug_python()
11
+ mpl_setup()
@@ -0,0 +1,38 @@
1
+ """
2
+ BestEffortCallback: simple real-time visualizations, provides ``bec``.
3
+ ======================================================================
4
+
5
+ .. autosummary::
6
+ ~bec
7
+ ~peaks
8
+ """
9
+
10
+ import logging
11
+
12
+ from bluesky.callbacks.best_effort import BestEffortCallback
13
+
14
+ from apsbits.utils.config_loaders import iconfig
15
+ from apsbits.utils.helper_functions import running_in_queueserver
16
+
17
+ logger = logging.getLogger(__name__)
18
+ logger.bsdev(__file__)
19
+
20
+ bec = BestEffortCallback()
21
+ """BestEffortCallback object, creates live tables and plots."""
22
+
23
+ bec_config = iconfig.get("BEC", {})
24
+
25
+ if not bec_config.get("BASELINE", True):
26
+ bec.disable_baseline()
27
+
28
+ if not bec_config.get("HEADING", True):
29
+ bec.disable_heading()
30
+
31
+ if not bec_config.get("PLOTS", True) or running_in_queueserver():
32
+ bec.disable_plots()
33
+
34
+ if not bec_config.get("TABLE", True):
35
+ bec.disable_table()
36
+
37
+ peaks = bec.peaks
38
+ """Dictionary with statistical analysis of LivePlots."""
@@ -0,0 +1,29 @@
1
+ """
2
+ Databroker catalog, provides ``cat``
3
+ ====================================
4
+
5
+ .. autosummary::
6
+ ~cat
7
+ """
8
+
9
+ import logging
10
+
11
+ import databroker
12
+
13
+ from apsbits.utils.config_loaders import iconfig
14
+
15
+ logger = logging.getLogger(__name__)
16
+ logger.bsdev(__file__)
17
+
18
+ TEMPORARY_CATALOG_NAME = "temp"
19
+
20
+ catalog_name = iconfig.get("DATABROKER_CATALOG", TEMPORARY_CATALOG_NAME)
21
+ try:
22
+ _cat = databroker.catalog[catalog_name].v2
23
+ except KeyError:
24
+ _cat = databroker.temp().v2
25
+
26
+ cat = _cat
27
+ """Databroker catalog object, receives new data from ``RE``."""
28
+
29
+ logger.info("Databroker catalog: %s", cat.name)
@@ -0,0 +1,71 @@
1
+ """
2
+ Setup the Bluesky RunEngine, provides ``RE`` and ``sd``.
3
+ ========================================================
4
+
5
+ .. autosummary::
6
+ ~RE
7
+ ~sd
8
+ """
9
+
10
+ import logging
11
+
12
+ import bluesky
13
+ from bluesky.utils import ProgressBarManager
14
+
15
+ from apsbits.core.best_effort_init import bec
16
+ from apsbits.core.catalog_init import cat
17
+ from apsbits.utils.config_loaders import iconfig
18
+ from apsbits.utils.controls_setup import connect_scan_id_pv
19
+ from apsbits.utils.controls_setup import set_control_layer
20
+ from apsbits.utils.controls_setup import set_timeouts
21
+ from apsbits.utils.metadata import MD_PATH
22
+ from apsbits.utils.stored_dict import StoredDict
23
+
24
+ logger = logging.getLogger(__name__)
25
+ logger.bsdev(__file__)
26
+
27
+ re_config = iconfig.get("RUN_ENGINE", {})
28
+
29
+ RE = bluesky.RunEngine()
30
+ """The bluesky RunEngine object."""
31
+
32
+ # Save/restore RE.md dictionary, in this precise order.
33
+ if MD_PATH is not None:
34
+ handler_name = re_config.get("MD_STORAGE_HANDLER", "StoredDict")
35
+ logger.debug(
36
+ "Select %r to store 'RE.md' dictionary in %s.",
37
+ handler_name,
38
+ MD_PATH,
39
+ )
40
+ try:
41
+ if handler_name == "PersistentDict":
42
+ RE.md = bluesky.utils.PersistentDict(MD_PATH)
43
+ else:
44
+ RE.md = StoredDict(MD_PATH)
45
+ except Exception as error:
46
+ print(
47
+ "\n"
48
+ f"Could not create {handler_name} for RE metadata. Continuing"
49
+ f" without saving metadata to disk. {error=}\n"
50
+ )
51
+ logger.warning("%s('%s') error:%s", handler_name, MD_PATH, error)
52
+
53
+ # RE.md.update(re_metadata(cat)) # programmatic metadata
54
+ # RE.md.update(re_config.get("DEFAULT_METADATA", {}))
55
+
56
+ sd = bluesky.SupplementalData()
57
+ """Baselines & monitors for ``RE``."""
58
+
59
+ RE.subscribe(cat.v1.insert)
60
+ RE.subscribe(bec)
61
+ RE.preprocessors.append(sd)
62
+
63
+ set_control_layer()
64
+ set_timeouts() # MUST happen before ANY EpicsSignalBase (or subclass) is created.
65
+
66
+ connect_scan_id_pv(RE) # if configured
67
+
68
+ if re_config.get("USE_PROGRESS_BAR", True):
69
+ # Add a progress bar.
70
+ pbar_manager = ProgressBarManager()
71
+ RE.waiting_hook = pbar_manager
@@ -0,0 +1 @@
1
+ ## Demo Instrument
@@ -0,0 +1,3 @@
1
+ """
2
+ Custom folder to store all beamline specific implementations
3
+ """
@@ -0,0 +1 @@
1
+ """RunEngine callbacks, mostly."""
@@ -0,0 +1,60 @@
1
+ """
2
+
3
+ NeXus Writer
4
+ ============
5
+
6
+ Write scan(s) to a NeXus/HDF5 file.
7
+
8
+ .. autosummary::
9
+ :nosignatures:
10
+
11
+ ~MyNXWriter
12
+ ~nxwriter
13
+ """
14
+
15
+ import logging
16
+
17
+ from apsbits.core.run_engine_init import RE
18
+ from apsbits.utils.aps_functions import host_on_aps_subnet
19
+ from apsbits.utils.config_loaders import iconfig
20
+
21
+ logger = logging.getLogger(__name__)
22
+ logger.bsdev(__file__)
23
+
24
+
25
+ if host_on_aps_subnet():
26
+ from apstools.callbacks import NXWriterAPS as NXWriter
27
+ else:
28
+ from apstools.callbacks import NXWriter
29
+
30
+
31
+ class MyNXWriter(NXWriter):
32
+ """Patch to get sample title from metadata, if available."""
33
+
34
+ def get_sample_title(self):
35
+ """
36
+ Get the title from the metadata or modify the default.
37
+
38
+ default title: S{scan_id}-{plan_name}-{short_uid}
39
+ """
40
+ try:
41
+ title = self.metadata["title"]
42
+ except KeyError:
43
+ # title = super().get_sample_title() # the default title
44
+ title = f"S{self.scan_id:05d}-{self.plan_name}-{self.uid[:7]}"
45
+ return title
46
+
47
+
48
+ nxwriter = MyNXWriter() # create the callback instance
49
+ """The NeXus file writer object."""
50
+
51
+ if iconfig.get("NEXUS_DATA_FILES", {}).get("ENABLE", False):
52
+ RE.subscribe(nxwriter.receiver) # write data to NeXus files
53
+
54
+ nxwriter.file_extension = iconfig.get("NEXUS_DATA_FILES", {}).get(
55
+ "FILE_EXTENSION", "hdf"
56
+ )
57
+ print("\n\n\n")
58
+ print(nxwriter.file_extension)
59
+ warn_missing = iconfig.get("NEXUS_DATA_FILES", {}).get("WARN_MISSING", False)
60
+ nxwriter.warn_on_missing_content = warn_missing
@@ -0,0 +1,93 @@
1
+ """
2
+ custom callbacks
3
+ ================
4
+
5
+ .. autosummary::
6
+ :nosignatures:
7
+
8
+ ~newSpecFile
9
+ ~spec_comment
10
+ ~specwriter
11
+ """
12
+
13
+ import datetime
14
+ import logging
15
+ import pathlib
16
+
17
+ import apstools.callbacks
18
+ import apstools.utils
19
+
20
+ from apsbits.core.run_engine_init import RE
21
+ from apsbits.utils.config_loaders import iconfig
22
+
23
+ logger = logging.getLogger(__name__)
24
+ logger.bsdev(__file__)
25
+
26
+
27
+ file_extension = iconfig.get("SPEC_DATA_FILES", {}).get("FILE_EXTENSION", "dat")
28
+
29
+
30
+ def spec_comment(comment, doc=None):
31
+ """Make it easy for user to add comments to the data file."""
32
+ apstools.callbacks.spec_comment(comment, doc, specwriter)
33
+
34
+
35
+ def newSpecFile(title, scan_id=None, RE=None):
36
+ """
37
+ User choice of the SPEC file name.
38
+
39
+ Cleans up title, prepends month and day and appends file extension.
40
+ If ``RE`` is passed, then resets ``RE.md["scan_id"] = scan_id``.
41
+
42
+ If the SPEC file already exists, then ``scan_id`` is ignored and
43
+ ``RE.md["scan_id"]`` is set to the last scan number in the file.
44
+ """
45
+ kwargs = {}
46
+ if RE is not None:
47
+ kwargs["RE"] = RE
48
+
49
+ mmdd = str(datetime.datetime.now()).split()[0][5:].replace("-", "_")
50
+ clean = apstools.utils.cleanupText(title)
51
+ fname = pathlib.Path(f"{mmdd}_{clean}.{file_extension}")
52
+ if fname.exists():
53
+ logger.warning(f">>> file already exists: {fname} <<<")
54
+ handled = "appended"
55
+ else:
56
+ kwargs["scan_id"] = scan_id or 1
57
+ handled = "created"
58
+
59
+ specwriter.newfile(fname, **kwargs)
60
+
61
+ logger.info(f"SPEC file name : {specwriter.spec_filename}")
62
+ logger.info(f"File will be {handled} at end of next bluesky scan.")
63
+
64
+
65
+ # write scans to SPEC data file
66
+ try:
67
+ # apstools >=1.6.21
68
+ _specwriter = apstools.callbacks.SpecWriterCallback2()
69
+ except AttributeError:
70
+ # apstools <1.6.21
71
+ _specwriter = apstools.callbacks.SpecWriterCallback()
72
+
73
+ specwriter = _specwriter
74
+ """The SPEC file writer object."""
75
+
76
+ # make the SPEC file in current working directory (assumes is writable)
77
+ specwriter.newfile(specwriter.spec_filename)
78
+
79
+ if iconfig.get("SPEC_DATA_FILES", {}).get("ENABLE", False):
80
+ RE.subscribe(specwriter.receiver) # write data to SPEC files
81
+ logger.info("SPEC data file: %s", specwriter.spec_filename.resolve())
82
+
83
+ try:
84
+ # feature new in apstools 1.6.14
85
+ from apstools.plans import label_stream_wrapper
86
+
87
+ def motor_start_preprocessor(plan):
88
+ """Record motor positions at start of each run."""
89
+ return label_stream_wrapper(plan, "motor", when="start")
90
+
91
+ RE.preprocessors.append(motor_start_preprocessor)
92
+ except Exception:
93
+ logger.warning("Could load support to log motors positions.")
@@ -0,0 +1 @@
1
+ """Configs required to set up user package"""
@@ -0,0 +1,52 @@
1
+ # Guarneri-style device YAML configuration
2
+
3
+ apsbits.utils.sim_creator.predefined_device:
4
+ - {creator: ophyd.sim.motor, name: sim_motor}
5
+ - {creator: ophyd.sim.noisy_det, name: sim_det}
6
+
7
+ apstools.devices.SimulatedApsPssShutterWithStatus:
8
+ - name: shutter
9
+ labels: ["shutters"]
10
+
11
+ # ophyd.Signal:
12
+ # - name: test
13
+ # value: 50.7
14
+ # - name: t2
15
+ # value: 2
16
+
17
+ # apstools.synApps.Optics2Slit2D_HV:
18
+ # - name: slit1
19
+ # prefix: ioc:Slit1
20
+ # labels: ["slits"]
21
+
22
+ # hkl.SimulatedE4CV:
23
+ # - name: sim4c
24
+ # prefix: ""
25
+ # labels: ["diffractometer"]
26
+
27
+ # ophyd.scaler.ScalerCH:
28
+ # - name: scaler1
29
+ # prefix: vme:scaler1
30
+ # labels: ["scalers", "detectors"]
31
+
32
+ # ophyd.EpicsMotor:
33
+ # - {name: m1, prefix: gp:m1, labels: ["motor"]}
34
+ # - {name: m2, prefix: gp:m2, labels: ["motor"]}
35
+ # - {name: m3, prefix: gp:m3, labels: ["motor"]}
36
+ # - {name: m4, prefix: gp:m4, labels: ["motor"]}
37
+
38
+ # apstools.devices.ad_creator:
39
+ # - name: adsimdet
40
+ # prefix: "ad:"
41
+ # labels: ["area_detector", "detectors"]
42
+ # plugins:
43
+ # - cam:
44
+ # class: apstools.devices.SimDetectorCam_V34
45
+ # - image
46
+ # - pva
47
+ # - hdf1:
48
+ # class: apstools.devices.AD_EpicsFileNameHDF5Plugin
49
+ # read_path_template: "/path/to/bluesky/tmp/"
50
+ # write_path_template: "/path/to/ioc/tmp/"
51
+ # - roi1
52
+ # - stats1
@@ -0,0 +1,6 @@
1
+ # Guarneri-style device YAML configuration
2
+
3
+ # Objects only available at APS
4
+
5
+ apstools.devices.ApsMachineParametersDevice:
6
+ - name: aps
@@ -0,0 +1,81 @@
1
+ # Configuration for the Bluesky instrument package.
2
+
3
+ # identify the version of this iconfig.yml file
4
+ ICONFIG_VERSION: 2.0.0
5
+
6
+ # Add additional configuration for use with your instrument.
7
+
8
+ ### The short name for the databroker catalog.
9
+ DATABROKER_CATALOG: &databroker_catalog temp
10
+
11
+ ### RunEngine configuration
12
+ RUN_ENGINE:
13
+ DEFAULT_METADATA:
14
+ beamline_id: demo_instrument
15
+ instrument_name: Most Glorious Scientific Instrument
16
+ proposal_id: commissioning
17
+ databroker_catalog: *databroker_catalog
18
+
19
+ ### EPICS PV to use for the `scan_id`.
20
+ ### Default: `RE.md["scan_id"]` (not using an EPICS PV)
21
+ # SCAN_ID_PV: "IOC:bluesky_scan_id"
22
+
23
+ ### Where to "autosave" the RE.md dictionary.
24
+ ### Defaults:
25
+ MD_STORAGE_HANDLER: StoredDict
26
+ MD_PATH: .re_md_dict.yml
27
+
28
+ ### The progress bar is nice to see,
29
+ ### except when it clutters the output in Jupyter notebooks.
30
+ ### Default: False
31
+ USE_PROGRESS_BAR: false
32
+
33
+ # Command-line tools, such as %wa, %ct, ...
34
+ USE_BLUESKY_MAGICS: true
35
+
36
+ ### Best Effort Callback Configurations
37
+ ### Defaults: all true
38
+ ### except no plots in queueserver
39
+ BEC:
40
+ BASELINE: true
41
+ HEADING: true
42
+ PLOTS: false
43
+ TABLE: true
44
+
45
+ ### Support for known output file formats.
46
+ ### Uncomment to use. If undefined, will not write that type of file.
47
+ ### Each callback should apply its configuration from here.
48
+ NEXUS_DATA_FILES:
49
+ ENABLE: false
50
+ FILE_EXTENSION: hdf
51
+ WARN_MISSING_CONTENT: true
52
+
53
+ SPEC_DATA_FILES:
54
+ ENABLE: true
55
+ FILE_EXTENSION: dat
56
+
57
+ ### APS Data Management
58
+ ### Use bash shell, deactivate all conda environments, source this file:
59
+ DM_SETUP_FILE: "/home/dm/etc/dm.setup.sh"
60
+
61
+ ### Local OPHYD Device Control Yaml
62
+ DEVICES_FILE: devices.yml
63
+ APS_DEVICES_FILE: devices_aps_only.yml
64
+
65
+ # ----------------------------------
66
+
67
+ OPHYD:
68
+ ### Control layer for ophyd to communicate with EPICS.
69
+ ### Default: PyEpics
70
+ ### Choices: "PyEpics" or "caproto" # caproto is not yet supported
71
+ CONTROL_LAYER: PyEpics
72
+
73
+ ### default timeouts (seconds)
74
+ TIMEOUTS:
75
+ PV_READ: &TIMEOUT 5
76
+ PV_WRITE: *TIMEOUT
77
+ PV_CONNECTION: *TIMEOUT
78
+
79
+ # Control detail of exception traces in IPython (console and notebook).
80
+ # Options are: Minimal, Plain, Verbose
81
+ XMODE_DEBUG_LEVEL: Plain
@@ -0,0 +1,41 @@
1
+ # Bluesky Session Logging Configuration
2
+
3
+ console_logs:
4
+ date_format: "%a-%H:%M:%S"
5
+ log_format: "%(levelname)-.1s %(asctime)s.%(msecs)03d: %(message)s"
6
+ level: info
7
+ root_level: bsdev
8
+
9
+ file_logs:
10
+ date_format: "%Y-%m-%d %H:%M:%S"
11
+ log_directory: .logs
12
+ log_filename_base: logging.log
13
+ log_format: "|\
14
+ %(asctime)s.%(msecs)03d|\
15
+ %(levelname)s|\
16
+ %(process)d|\
17
+ %(name)s|\
18
+ %(module)s|\
19
+ %(lineno)d|\
20
+ %(threadName)s| - \
21
+ %(message)s"
22
+ maxBytes: 1_000_000
23
+ backupCount: 9
24
+ level: info
25
+ rotate_on_startup: true
26
+
27
+ ipython_logs:
28
+ log_directory: .logs
29
+ log_filename_base: ipython_log.py
30
+ log_mode: rotate
31
+ options: -o -t
32
+
33
+ modules:
34
+ apstools: warning
35
+ bluesky-queueserver: warning
36
+ bluesky: warning
37
+ bluesky.RE: warning
38
+ caproto: warning
39
+ databroker: warning
40
+ instrument: bsdev
41
+ ophyd: warning
@@ -0,0 +1 @@
1
+ """Ophyd-style devices."""
@@ -0,0 +1,8 @@
1
+ """Bluesky plans."""
2
+
3
+ from .dm_plans import dm_kickoff_workflow # noqa: F401
4
+ from .dm_plans import dm_list_processing_jobs # noqa: F401
5
+ from .dm_plans import dm_submit_workflow_job # noqa: F401
6
+ from .sim_plans import sim_count_plan # noqa: F401
7
+ from .sim_plans import sim_print_plan # noqa: F401
8
+ from .sim_plans import sim_rel_scan_plan # noqa: F401
@@ -0,0 +1,111 @@
1
+ """
2
+ Plans in support of APS Data Management
3
+ =======================================
4
+
5
+ .. autosummary::
6
+
7
+ ~dm_kickoff_workflow
8
+ ~dm_list_processing_jobs
9
+ ~dm_submit_workflow_job
10
+ """
11
+
12
+ import logging
13
+
14
+ from apstools.devices import DM_WorkflowConnector
15
+ from apstools.utils import dm_api_proc
16
+ from apstools.utils import share_bluesky_metadata_with_dm
17
+ from bluesky import plan_stubs as bps
18
+
19
+ logger = logging.getLogger(__name__)
20
+ logger.bsdev(__file__)
21
+
22
+
23
+ def dm_kickoff_workflow(run, argsDict, timeout=None, wait=False):
24
+ """
25
+ Start a DM workflow for this bluesky run and share run's metadata with DM.
26
+
27
+ PARAMETERS:
28
+
29
+ run (*obj*): Bluesky run object (such as 'run = cat[uid]').
30
+
31
+ argsDict (*dict*): Dictionary of parameters needed by 'workflowName'.
32
+ At minimum, most workflows expect these keys: 'filePath' and
33
+ 'experimentName'. Consult the workflow for the expected
34
+ content of 'argsDict'.
35
+
36
+ timeout (*number*): When should bluesky stop reporting on this
37
+ DM workflow job (if it has not ended). Units are seconds.
38
+ Default is forever.
39
+
40
+ wait (*bool*): Should this plan stub wait for the job to end?
41
+ Default is 'False'.
42
+ """
43
+ dm_workflow = DM_WorkflowConnector(name="dm_workflow")
44
+
45
+ if timeout is None:
46
+ # Disable periodic reports, use a long time (s).
47
+ timeout = 999_999_999_999
48
+
49
+ yield from bps.mv(dm_workflow.concise_reporting, True)
50
+ yield from bps.mv(dm_workflow.reporting_period, timeout)
51
+
52
+ workflow_name = argsDict.pop["workflowName"]
53
+ yield from dm_workflow.run_as_plan(
54
+ workflow=workflow_name,
55
+ wait=wait,
56
+ timeout=timeout,
57
+ **argsDict,
58
+ )
59
+
60
+ # Upload bluesky run metadata to APS DM.
61
+ share_bluesky_metadata_with_dm(argsDict["experimentName"], workflow_name, run)
62
+
63
+ # Users requested the DM workflow job ID be printed to the console.
64
+ dm_workflow._update_processing_data()
65
+ job_id = dm_workflow.job_id.get()
66
+ job_stage = dm_workflow.stage_id.get()
67
+ job_status = dm_workflow.status.get()
68
+ print(f"DM workflow id: {job_id!r} status: {job_status} stage: {job_stage}")
69
+
70
+
71
+ def dm_list_processing_jobs(exclude=None):
72
+ """
73
+ Show all the DM jobs with status not excluded.
74
+
75
+ Excluded status (default): 'done', 'failed'
76
+ """
77
+ yield from bps.null() # make this a plan stub
78
+ api = dm_api_proc()
79
+ if exclude is None:
80
+ exclude = ("done", "failed")
81
+
82
+ for j in api.listProcessingJobs():
83
+ if j["status"] not in exclude:
84
+ print(
85
+ f"id={j['id']!r}"
86
+ f" submitted={j.get('submissionTimestamp')}"
87
+ f" status={j['status']!r}"
88
+ )
89
+
90
+
91
+ def dm_submit_workflow_job(workflowName, argsDict):
92
+ """
93
+ Low-level plan stub to submit a job to a DM workflow.
94
+
95
+ It is recommended to use dm_kickoff_workflow() instead.
96
+ This plan does not share run metadata with DM.
97
+
98
+ PARAMETERS:
99
+
100
+ workflowName (*str*): Name of the DM workflow to be run.
101
+
102
+ argsDict (*dict*): Dictionary of parameters needed by 'workflowName'.
103
+ At minimum, most workflows expect these keys: 'filePath' and
104
+ 'experimentName'. Consult the workflow for the expected
105
+ content of 'argsDict'.
106
+ """
107
+ yield from bps.null() # make this a plan stub
108
+ api = dm_api_proc()
109
+
110
+ job = api.startProcessingJob(api.username, workflowName, argsDict)
111
+ print(f"workflow={workflowName!r} id={job['id']!r}")