lsst-ctrl-bps 29.1.0rc5__tar.gz → 30.0.0rc3__tar.gz
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.
- {lsst_ctrl_bps-29.1.0rc5/python/lsst_ctrl_bps.egg-info → lsst_ctrl_bps-30.0.0rc3}/PKG-INFO +3 -3
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/doc/lsst.ctrl.bps/CHANGES.rst +30 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/doc/lsst.ctrl.bps/quickstart.rst +103 -24
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/pyproject.toml +3 -3
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/bps_config.py +61 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/bps_reports.py +131 -43
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/bps_utils.py +35 -1
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/clustered_quantum_graph.py +48 -61
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/construct.py +108 -5
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/drivers.py +32 -17
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/etc/bps_defaults.yaml +9 -3
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/generic_workflow.py +12 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/initialize.py +3 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/pre_transform.py +18 -8
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/quantum_clustering_funcs.py +96 -83
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/report.py +45 -24
- lsst_ctrl_bps-30.0.0rc3/python/lsst/ctrl/bps/tests/config_test_utils.py +114 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/transform.py +7 -25
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/version.py +1 -1
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3/python/lsst_ctrl_bps.egg-info}/PKG-INFO +3 -3
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst_ctrl_bps.egg-info/SOURCES.txt +3 -0
- lsst_ctrl_bps-29.1.0rc5/tests/test_report.py → lsst_ctrl_bps-30.0.0rc3/tests/test_bps_reports.py +118 -39
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/tests/test_bps_utils.py +12 -1
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/tests/test_bpsconfig.py +133 -1
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/tests/test_clustered_quantum_graph.py +18 -22
- lsst_ctrl_bps-30.0.0rc3/tests/test_construct.py +371 -0
- lsst_ctrl_bps-30.0.0rc3/tests/test_drivers.py +329 -0
- lsst_ctrl_bps-30.0.0rc3/tests/test_initialize.py +180 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/tests/test_pre_transform.py +8 -7
- lsst_ctrl_bps-30.0.0rc3/tests/test_report.py +72 -0
- lsst_ctrl_bps-29.1.0rc5/tests/test_construct.py +0 -122
- lsst_ctrl_bps-29.1.0rc5/tests/test_drivers.py +0 -146
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/COPYRIGHT +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/LICENSE +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/MANIFEST.in +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/README.md +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/bsd_license.txt +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/doc/lsst.ctrl.bps/index.rst +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/gpl-v3.0.txt +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/__init__.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/__init__.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/__init__.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/_exceptions.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/bps_draw.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/cancel.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/cli/__init__.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/cli/bps.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/cli/cmd/__init__.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/cli/cmd/commands.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/cli/opt/__init__.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/cli/opt/arguments.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/cli/opt/option_groups.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/cli/opt/options.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/constants.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/ping.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/prepare.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/restart.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/status.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/submit.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/tests/gw_test_utils.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst/ctrl/bps/wms_service.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst_ctrl_bps.egg-info/dependency_links.txt +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst_ctrl_bps.egg-info/entry_points.txt +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst_ctrl_bps.egg-info/requires.txt +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst_ctrl_bps.egg-info/top_level.txt +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/python/lsst_ctrl_bps.egg-info/zip-safe +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/setup.cfg +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/tests/test_cli_commands.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/tests/test_generic_workflow.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/tests/test_ping.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/tests/test_quantum_clustering_funcs.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/tests/test_status.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/tests/test_transform.py +0 -0
- {lsst_ctrl_bps-29.1.0rc5 → lsst_ctrl_bps-30.0.0rc3}/tests/test_wms_service.py +0 -0
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lsst-ctrl-bps
|
|
3
|
-
Version:
|
|
3
|
+
Version: 30.0.0rc3
|
|
4
4
|
Summary: Pluggable execution of workflow graphs from Rubin pipelines.
|
|
5
5
|
Author-email: Rubin Observatory Data Management <dm-admin@lists.lsst.org>
|
|
6
|
-
License: BSD
|
|
6
|
+
License-Expression: BSD-3-Clause OR GPL-3.0-or-later
|
|
7
7
|
Project-URL: Homepage, https://github.com/lsst/ctrl_bps
|
|
8
8
|
Keywords: lsst
|
|
9
9
|
Classifier: Intended Audience :: Science/Research
|
|
10
|
-
Classifier: License :: OSI Approved :: BSD License
|
|
11
10
|
Classifier: Operating System :: OS Independent
|
|
12
11
|
Classifier: Programming Language :: Python :: 3
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
16
|
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
17
17
|
Requires-Python: >=3.11.0
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
lsst-ctrl-bps v30.0.0 (2026-01-16)
|
|
2
|
+
==================================
|
|
3
|
+
|
|
4
|
+
New Features
|
|
5
|
+
------------
|
|
6
|
+
|
|
7
|
+
- Added support for transferring input files to the execution site and bringing the produced output files back to the submit site when using ``bps submitcmd``. (`DM-48479 <https://rubinobs.atlassian.net/browse/DM-48479>`_)
|
|
8
|
+
- Added ``bpsGenerateConfig`` and ``bpsEval`` to run functions to produce config values. ``bpsGenerateConfig`` is used when inserting or updating one or more key/value pairs. ``bpsEval`` is for replacing part of a string value. (`DM-50616 <https://rubinobs.atlassian.net/browse/DM-50616>`_)
|
|
9
|
+
|
|
10
|
+
Bug Fixes
|
|
11
|
+
---------
|
|
12
|
+
|
|
13
|
+
- Fixed bug where the ``return_exit_codes`` command line value was not passed to the plugin's report function. While it was correctly used when displaying the report, not having the value didn't allow the plugin to optimize its report function. (`DM-52791 <https://rubinobs.atlassian.net/browse/DM-52791>`_)
|
|
14
|
+
|
|
15
|
+
Performance Enhancement
|
|
16
|
+
-----------------------
|
|
17
|
+
|
|
18
|
+
- Switched the default ``finalJob`` implementation to the new ``aggregate-graph`` command, which makes use of multiple cores much more effectively than ``transfer-from-graph``. (`DM-52360 <https://rubinobs.atlassian.net/browse/DM-52360>`_)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
Other Changes and Additions
|
|
22
|
+
---------------------------
|
|
23
|
+
|
|
24
|
+
- Removed exit code 1 from the default for ``finalJob``\ 's ``retryUnlessExit`` as the majority of these are related to system issues which could be transient. (`DM-51313 <https://rubinobs.atlassian.net/browse/DM-51313>`_)
|
|
25
|
+
- Modified the BPS report driver so it compiles exit code summary only when necessary, i.e., when ``--return-exit-codes`` option was used with ``bps report``. (`DM-52898 <https://rubinobs.atlassian.net/browse/DM-52898>`_)
|
|
26
|
+
- Added a custom __new__ method to the GenericWorkflow class so **ctrl_bps** can work when using NetworkX 3.6. (`DM-53492 <https://rubinobs.atlassian.net/browse/DM-53492>`_)
|
|
27
|
+
- Made the BPS reporting mechanism a bit more robust. If the BPS plugin does not explicitly includes jobs labels for which there are no failures in the run's exit code summary, it will try to use the run's job summary to do that. (`DM-51261 <https://rubinobs.atlassian.net/browse/DM-51261>`_)
|
|
28
|
+
- Used the new ``PredictedQuantumGraph`` class internally and default to the new QG file format via the "``.qg``" extension. (`DM-52339 <https://rubinobs.atlassian.net/browse/DM-52339>`_)
|
|
29
|
+
|
|
30
|
+
|
|
1
31
|
lsst-ctrl-bps v29.1.0 (2025-06-13)
|
|
2
32
|
==================================
|
|
3
33
|
|
|
@@ -157,7 +157,7 @@ or a pre-made file containing a serialized QuantumGraph, for example
|
|
|
157
157
|
|
|
158
158
|
.. code-block:: YAML
|
|
159
159
|
|
|
160
|
-
qgraphFile: pipelines_check_w_2020_45.
|
|
160
|
+
qgraphFile: pipelines_check_w_2020_45.qg
|
|
161
161
|
|
|
162
162
|
.. warning::
|
|
163
163
|
|
|
@@ -265,7 +265,7 @@ arguments, e.g.:
|
|
|
265
265
|
.. code-block:: yaml
|
|
266
266
|
|
|
267
267
|
customJob:
|
|
268
|
-
executable: "${HOME}/scripts/
|
|
268
|
+
executable: "${HOME}/scripts/do_stuff.sh"
|
|
269
269
|
arguments: "2"
|
|
270
270
|
|
|
271
271
|
# Uncomment settings below to disable automatic memory scaling and retries
|
|
@@ -276,16 +276,62 @@ arguments, e.g.:
|
|
|
276
276
|
|
|
277
277
|
where ``executable`` specifies the path to the executable to run and
|
|
278
278
|
``arguments`` is a list of arguments to be supplied to the executable as part
|
|
279
|
-
of the command line.
|
|
279
|
+
of the command line. If your executable does not take any command line
|
|
280
|
+
arguments set ``arguments`` to an empty string.
|
|
280
281
|
|
|
281
282
|
.. note::
|
|
282
283
|
|
|
283
|
-
|
|
284
|
-
|
|
284
|
+
The script specified by ``customJob.executable`` is copied to the run's
|
|
285
|
+
submit directory and this copy (not the original script) is being submitted
|
|
286
|
+
for execution. As a result, making any changes to the original script after
|
|
287
|
+
the run has been submitted will have no effect even if the run is still
|
|
288
|
+
in the WMS work queue waiting for execution.
|
|
289
|
+
|
|
290
|
+
If the script requires any input files that should be transferred to the
|
|
291
|
+
execution site as well and/or produces output files that should be brought back
|
|
292
|
+
specify them as follows:
|
|
293
|
+
|
|
294
|
+
.. code-block:: yaml
|
|
295
|
+
|
|
296
|
+
customJob:
|
|
297
|
+
executable: "${HOME}/scripts/do_stuff.sh"
|
|
298
|
+
arguments: "-o {outfile} {infile}"
|
|
299
|
+
inputs:
|
|
300
|
+
infile: path/to/input/file
|
|
301
|
+
outputs:
|
|
302
|
+
outfile: path/to/output/file
|
|
303
|
+
|
|
304
|
+
# Uncomment settings below to disable automatic memory scaling and retries
|
|
305
|
+
# which BPS enables by default.
|
|
306
|
+
#
|
|
307
|
+
# memoryMultiplier: 1
|
|
308
|
+
# numberOfRetries: 1
|
|
309
|
+
|
|
310
|
+
The paths in ``inputs`` specify files as they are accessed on the submit site.
|
|
311
|
+
They can be absolute or relative to the current working directory as the run is
|
|
312
|
+
submitted. The input files will be copied to the run's submit directory.
|
|
313
|
+
These copies (not the original files) will be submitted along with the script's
|
|
314
|
+
copy for execution. During the execution BPS will transfer these copies into a
|
|
315
|
+
single flat directory -- job's scratch directory on the execute machine.
|
|
316
|
+
|
|
317
|
+
The paths in ``outputs`` specifies the paths on the submit site the output
|
|
318
|
+
files will be copied to after job's completion. As input paths they can be
|
|
319
|
+
either absolute or relative. However, on the execution site, the script is
|
|
320
|
+
expected to write all its output files directly to job's scratch directory
|
|
321
|
+
(assumed to be the current working directory when the job starts unless stated
|
|
322
|
+
otherwise in the WMS-specific documentation).
|
|
323
|
+
|
|
324
|
+
As a result, both input and output files base names *must* be unique.
|
|
325
|
+
|
|
326
|
+
.. note::
|
|
327
|
+
|
|
328
|
+
Currently, BPS itself doesn't verify if the file declared in ``outputs`` was
|
|
329
|
+
produced by the script. Whether a missing output file will be considered an
|
|
330
|
+
error depends entirely on WMS in use.
|
|
285
331
|
|
|
286
|
-
|
|
287
|
-
run your script. That workflow will be submitted for execution
|
|
288
|
-
workflow.
|
|
332
|
+
The config files shown above will instruct BPS to create a special single-job
|
|
333
|
+
*workflow* to run your script. That workflow will be submitted for execution
|
|
334
|
+
as any other workflow.
|
|
289
335
|
|
|
290
336
|
As a result, the submission process for a custom script looks quite similar
|
|
291
337
|
to the submission process of regular payload jobs (i.e. jobs running
|
|
@@ -305,15 +351,6 @@ There are few things you need to keep in mind though:
|
|
|
305
351
|
instructions exist in the submit YAML. If you need the quantum graph, use
|
|
306
352
|
``bps submit``.
|
|
307
353
|
|
|
308
|
-
#. At the moment, the mechanism does not support transferring files other than
|
|
309
|
-
executable.
|
|
310
|
-
|
|
311
|
-
#. The script specified by ``customJob.executable`` is copied to the run's
|
|
312
|
-
submit directory and this copy (not the original script) is being submitted
|
|
313
|
-
for execution. As a result, making any changes to the original script after
|
|
314
|
-
the run has been submitted will have no effect even if the run is still in
|
|
315
|
-
the WMS work queue waiting for execution.
|
|
316
|
-
|
|
317
354
|
#. Some BPS plugins may require inclusion of plugin-specific settings for this
|
|
318
355
|
mechanism to work. Consult the documentation of the plugin you use for
|
|
319
356
|
details.
|
|
@@ -809,7 +846,7 @@ Supported settings
|
|
|
809
846
|
When to output job QuantumGraph files (default = TRANSFORM).
|
|
810
847
|
|
|
811
848
|
* NEVER = all jobs will use full QuantumGraph file. (Warning: make sure
|
|
812
|
-
runQuantumCommand has ``--qgraph-
|
|
849
|
+
runQuantumCommand has ``--qgraph-node-id {qgraphNodeId}``.)
|
|
813
850
|
* TRANSFORM = Output QuantumGraph files after creating GenericWorkflow.
|
|
814
851
|
* PREPARE = QuantumGraph files are output after creating WMS submission.
|
|
815
852
|
|
|
@@ -867,7 +904,7 @@ Reserved keywords
|
|
|
867
904
|
However, contrary to YAML specification, it is currently not portable.
|
|
868
905
|
|
|
869
906
|
**qgraphId**
|
|
870
|
-
|
|
907
|
+
Ignored; accepted for backwards compatibility.
|
|
871
908
|
|
|
872
909
|
**qgraphNodeId**
|
|
873
910
|
Comma-separated list of internal QuantumGraph node numbers to be
|
|
@@ -1042,13 +1079,12 @@ single full QuantumGraph file plus node numbers for each job. The default is
|
|
|
1042
1079
|
using per-job QuantumGraph files.
|
|
1043
1080
|
|
|
1044
1081
|
To use full QuantumGraph file, the submit YAML must set ``whenSaveJobQgraph`` to
|
|
1045
|
-
"NEVER" and the ``pipetask run`` command must include ``--qgraph-id {
|
|
1046
|
-
--qgraph-node-id {qgraphNodeId}``. For example:
|
|
1082
|
+
"NEVER" and the ``pipetask run`` command must include ``--qgraph-node-id {qgraphNodeId}``. For example:
|
|
1047
1083
|
|
|
1048
1084
|
.. code::
|
|
1049
1085
|
|
|
1050
1086
|
whenSaveJobQgraph: "NEVER"
|
|
1051
|
-
runQuantumCommand: "${CTRL_MPEXEC_DIR}/bin/pipetask --long-log run -b {butlerConfig} --output {output} --output-run {outputRun} --qgraph {qgraphFile} --qgraph-
|
|
1087
|
+
runQuantumCommand: "${CTRL_MPEXEC_DIR}/bin/pipetask --long-log run -b {butlerConfig} --output {output} --output-run {outputRun} --qgraph {qgraphFile} --qgraph-node-id {qgraphNodeId} --skip-init-writes --extend-run --clobber-outputs --skip-existing"
|
|
1052
1088
|
|
|
1053
1089
|
|
|
1054
1090
|
.. warning::
|
|
@@ -1125,7 +1161,7 @@ New YAML Section
|
|
|
1125
1161
|
implementation: JOB
|
|
1126
1162
|
concurrencyLimit: db_limit
|
|
1127
1163
|
command1: >-
|
|
1128
|
-
${DAF_BUTLER_DIR}/bin/butler
|
|
1164
|
+
${DAF_BUTLER_DIR}/bin/butler aggregate-graph
|
|
1129
1165
|
{fileDistributionEndPoint}{qgraphFile}
|
|
1130
1166
|
{butlerConfig}
|
|
1131
1167
|
--register-dataset-types
|
|
@@ -1207,7 +1243,7 @@ The major differences to users are:
|
|
|
1207
1243
|
the output run in the provided pre-existing quantum graph.
|
|
1208
1244
|
- ``final_post_finalJob.out``: An internal file for debugging incorrect
|
|
1209
1245
|
reporting of final run status.
|
|
1210
|
-
- ``<qgraph_filename>_orig.
|
|
1246
|
+
- ``<qgraph_filename>_orig.qg``: A backup copy of the original
|
|
1211
1247
|
pre-existing quantum graph file that was used for submitting the run. Note
|
|
1212
1248
|
that this file will *not* be present in the submit directory if the
|
|
1213
1249
|
pipeline YAML specification was used during the submission instead.
|
|
@@ -1497,6 +1533,49 @@ invisible to the user. ``bps report`` will still show same labels and
|
|
|
1497
1533
|
total counts as without ordering. ``cancel`` and ``restart`` will still
|
|
1498
1534
|
work the same.
|
|
1499
1535
|
|
|
1536
|
+
.. _bps-config-generation:
|
|
1537
|
+
|
|
1538
|
+
Config Generation
|
|
1539
|
+
-----------------
|
|
1540
|
+
|
|
1541
|
+
In some rare use cases, the submit yaml depends upon what happened in
|
|
1542
|
+
previous runs (e.g., passing pipeline configuration values to the HiPS
|
|
1543
|
+
QuantumGraph generation command depending upon colors of generated outputs
|
|
1544
|
+
of previous run). One can wait until a run finishes, query the results,
|
|
1545
|
+
and then manually modify the submit yaml for the next run. To help make
|
|
1546
|
+
this easier to automate, two special mechanisms, ``bpsGenerateConfig`` and
|
|
1547
|
+
``bpsEval`` have been added to ``bps``. While different syntax, both take
|
|
1548
|
+
two pieces of information. The first piece describes what to import and
|
|
1549
|
+
execute and the second the parameters to pass which typically will
|
|
1550
|
+
be config variables (e.g., "{butlerConfig}").
|
|
1551
|
+
|
|
1552
|
+
``bpsGenerateConfig`` is a key/value pair where the function returns a
|
|
1553
|
+
Mapping to update the config. It can be used at the root level and can
|
|
1554
|
+
return nested dictionaries to replace values across multiple sections.
|
|
1555
|
+
It can also be used inside sections (e.g., inside a specific pipetask
|
|
1556
|
+
section). The function is not run when loading the config. Instead it
|
|
1557
|
+
is run during the initialization in the ``bps submit`` (before saving
|
|
1558
|
+
the config yaml to the submit directory and before running QuantumGraph
|
|
1559
|
+
generation). Example:
|
|
1560
|
+
|
|
1561
|
+
..code::
|
|
1562
|
+
|
|
1563
|
+
bpsGenerateConfig: "lsst.my.package.my_func_1('{butlerConfig}', param3='{output}')"
|
|
1564
|
+
|
|
1565
|
+
|
|
1566
|
+
``bpsEval`` is a placeholder in an submit yaml value. It is executed when
|
|
1567
|
+
the corresponding key is requested from the config. Its function needs
|
|
1568
|
+
to return a value whose string representation can replace ``bpsEval``. Example:
|
|
1569
|
+
|
|
1570
|
+
..code::
|
|
1571
|
+
|
|
1572
|
+
extraQgraphOptions: "--dataset-query-constraint off bpsEval(lsst.my.package.my_func_2, '{butlerConfig}', '{output}')
|
|
1573
|
+
|
|
1574
|
+
.. warning::
|
|
1575
|
+
|
|
1576
|
+
Quotes must be placed around variables that return strings (e.g., '{butlerConfig}').
|
|
1577
|
+
Forgetting the quotes typically results in an invalid syntax error.
|
|
1578
|
+
|
|
1500
1579
|
.. _bps-softlink:
|
|
1501
1580
|
|
|
1502
1581
|
WMS-id softlink
|
|
@@ -6,19 +6,20 @@ build-backend = "setuptools.build_meta"
|
|
|
6
6
|
name = "lsst-ctrl-bps"
|
|
7
7
|
requires-python = ">=3.11.0"
|
|
8
8
|
description = "Pluggable execution of workflow graphs from Rubin pipelines."
|
|
9
|
-
license =
|
|
9
|
+
license = "BSD-3-Clause OR GPL-3.0-or-later"
|
|
10
|
+
license-files = ["COPYRIGHT", "LICENSE", "bsd_license.txt", "gpl-v3.0.txt"]
|
|
10
11
|
readme = "README.md"
|
|
11
12
|
authors = [
|
|
12
13
|
{name="Rubin Observatory Data Management", email="dm-admin@lists.lsst.org"},
|
|
13
14
|
]
|
|
14
15
|
classifiers = [
|
|
15
16
|
"Intended Audience :: Science/Research",
|
|
16
|
-
"License :: OSI Approved :: BSD License",
|
|
17
17
|
"Operating System :: OS Independent",
|
|
18
18
|
"Programming Language :: Python :: 3",
|
|
19
19
|
"Programming Language :: Python :: 3.11",
|
|
20
20
|
"Programming Language :: Python :: 3.12",
|
|
21
21
|
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Programming Language :: Python :: 3.14",
|
|
22
23
|
"Topic :: Scientific/Engineering :: Astronomy",
|
|
23
24
|
]
|
|
24
25
|
keywords = ["lsst"]
|
|
@@ -49,7 +50,6 @@ where = ["python"]
|
|
|
49
50
|
|
|
50
51
|
[tool.setuptools]
|
|
51
52
|
zip-safe = true
|
|
52
|
-
license-files = ["COPYRIGHT", "LICENSE", "bsd_license.txt", "gpl-v3.0.txt"]
|
|
53
53
|
|
|
54
54
|
[tool.setuptools.package-data]
|
|
55
55
|
"lsst.ctrl.bps" = ["etc/*.yaml"]
|
|
@@ -44,6 +44,8 @@ from lsst.daf.butler import Config
|
|
|
44
44
|
from lsst.resources import ResourcePath
|
|
45
45
|
from lsst.utils import doImport
|
|
46
46
|
|
|
47
|
+
from .bps_utils import bps_eval
|
|
48
|
+
|
|
47
49
|
_LOG = logging.getLogger(__name__)
|
|
48
50
|
|
|
49
51
|
# Using lsst.daf.butler.Config to resolve possible includes.
|
|
@@ -417,4 +419,63 @@ class BpsConfig(Config):
|
|
|
417
419
|
if default != _NO_SEARCH_DEFAULT_VALUE:
|
|
418
420
|
opt["default"] = default
|
|
419
421
|
|
|
422
|
+
# check for bpsEval
|
|
423
|
+
value = re.sub(
|
|
424
|
+
r"bpsEval\(([^,)]+), ([^)]+)\)", lambda m: str(bps_eval(m.group(1), m.group(2))), value
|
|
425
|
+
)
|
|
426
|
+
if "bpsEval" in value:
|
|
427
|
+
raise ValueError(f"Unparsable bpsEval in '{value}'")
|
|
428
|
+
|
|
420
429
|
return value
|
|
430
|
+
|
|
431
|
+
def generate_config(self) -> None:
|
|
432
|
+
"""Update config with values generated by bpsGenerateConfig
|
|
433
|
+
entries.
|
|
434
|
+
"""
|
|
435
|
+
_LOG.debug("generate_config before: %s", self)
|
|
436
|
+
self._recursive_generate_config("", self)
|
|
437
|
+
_LOG.debug("generate_config after: %s", self)
|
|
438
|
+
|
|
439
|
+
def _recursive_generate_config(self, recursive_key: str, sub_config: Config) -> None:
|
|
440
|
+
"""Update config with values generated by bpsGenerateConfig
|
|
441
|
+
entries.
|
|
442
|
+
|
|
443
|
+
Parameters
|
|
444
|
+
----------
|
|
445
|
+
recursive_key : `str`
|
|
446
|
+
Corresponds to a new subconfig in which to search
|
|
447
|
+
and replace bpsGenerateConfig.
|
|
448
|
+
|
|
449
|
+
sub_config : `lsst.daf.butler.Config`
|
|
450
|
+
The nested config corresponding to the recursive_key.
|
|
451
|
+
|
|
452
|
+
Raises
|
|
453
|
+
------
|
|
454
|
+
ValueError
|
|
455
|
+
If bpsGenerateConfig value isn't parseable.
|
|
456
|
+
ImportError
|
|
457
|
+
If problems importing bpsGenerateConfig's method.
|
|
458
|
+
"""
|
|
459
|
+
_LOG.debug("recursive_key = '%s'", recursive_key)
|
|
460
|
+
genkey = "bpsGenerateConfig" # to make it easier to change
|
|
461
|
+
|
|
462
|
+
# Save to avoid dictionary changed size during iteration error.
|
|
463
|
+
orig_keys = list(sub_config)
|
|
464
|
+
for key in orig_keys:
|
|
465
|
+
value = Config.__getitem__(sub_config, key)
|
|
466
|
+
_LOG.debug("key = %s, type(value) = %s", key, type(value))
|
|
467
|
+
if isinstance(value, Config):
|
|
468
|
+
self._recursive_generate_config(f"{recursive_key}.{key}", value)
|
|
469
|
+
elif key == genkey:
|
|
470
|
+
value = self.replace_vars(value, {"searchobj": sub_config})
|
|
471
|
+
|
|
472
|
+
m = re.match(r"(\S+)\((.+)\)", value)
|
|
473
|
+
if m:
|
|
474
|
+
results = bps_eval(m.group(1), m.group(2))
|
|
475
|
+
del sub_config[genkey]
|
|
476
|
+
sub_config.update(results)
|
|
477
|
+
if recursive_key:
|
|
478
|
+
self[recursive_key] = sub_config
|
|
479
|
+
_LOG.debug("After config = %s", self)
|
|
480
|
+
else:
|
|
481
|
+
raise ValueError(f"Unparsable {genkey} value='{value}'")
|
|
@@ -27,7 +27,14 @@
|
|
|
27
27
|
|
|
28
28
|
"""Classes and functions used in reporting run status."""
|
|
29
29
|
|
|
30
|
-
__all__ = [
|
|
30
|
+
__all__ = [
|
|
31
|
+
"BaseRunReport",
|
|
32
|
+
"DetailedRunReport",
|
|
33
|
+
"ExitCodesReport",
|
|
34
|
+
"SummaryRunReport",
|
|
35
|
+
"compile_code_summary",
|
|
36
|
+
"compile_job_summary",
|
|
37
|
+
]
|
|
31
38
|
|
|
32
39
|
import abc
|
|
33
40
|
import logging
|
|
@@ -55,7 +62,7 @@ class BaseRunReport(abc.ABC):
|
|
|
55
62
|
|
|
56
63
|
def __eq__(self, other):
|
|
57
64
|
if isinstance(other, BaseRunReport):
|
|
58
|
-
return
|
|
65
|
+
return self._table.pformat() == other._table.pformat()
|
|
59
66
|
return False
|
|
60
67
|
|
|
61
68
|
def __len__(self):
|
|
@@ -195,7 +202,7 @@ class DetailedRunReport(BaseRunReport):
|
|
|
195
202
|
job_summary = run_report.job_summary
|
|
196
203
|
if job_summary is None:
|
|
197
204
|
id_ = run_report.global_wms_id if use_global_id else run_report.wms_id
|
|
198
|
-
self._msg = f"WARNING: Job summary for run '{id_}' not available, report
|
|
205
|
+
self._msg = f"WARNING: Job summary for run '{id_}' not available, report may be incomplete."
|
|
199
206
|
return
|
|
200
207
|
|
|
201
208
|
if by_label_expected:
|
|
@@ -231,44 +238,60 @@ class ExitCodesReport(BaseRunReport):
|
|
|
231
238
|
error handling from the wms service.
|
|
232
239
|
"""
|
|
233
240
|
|
|
234
|
-
def add(self, run_report, use_global_id=False):
|
|
241
|
+
def add(self, run_report: WmsRunReport, use_global_id: bool = False) -> None:
|
|
235
242
|
# Docstring inherited from the base class.
|
|
236
243
|
|
|
237
|
-
|
|
238
|
-
|
|
244
|
+
exit_code_summary = run_report.exit_code_summary
|
|
245
|
+
if not exit_code_summary:
|
|
246
|
+
id_ = run_report.global_wms_id if use_global_id else run_report.wms_id
|
|
247
|
+
self._msg = f"WARNING: Exit code summary for run '{id_}' not available, report may be incomplete."
|
|
248
|
+
return
|
|
249
|
+
|
|
250
|
+
warnings = []
|
|
251
|
+
|
|
252
|
+
# If available, use label ordering from the run summary as it should
|
|
253
|
+
# reflect the ordering of the pipetasks in the pipeline.
|
|
239
254
|
labels = []
|
|
240
255
|
if run_report.run_summary:
|
|
241
256
|
for part in run_report.run_summary.split(";"):
|
|
242
257
|
label, _ = part.split(":")
|
|
243
258
|
labels.append(label)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
return
|
|
259
|
+
if not labels:
|
|
260
|
+
labels = sorted(exit_code_summary)
|
|
261
|
+
warnings.append("WARNING: Could not determine order of pipeline, instead sorted alphabetically.")
|
|
248
262
|
|
|
249
263
|
# Payload (e.g. pipetask) error codes:
|
|
250
264
|
# * 1: general failure,
|
|
251
265
|
# * 2: command line error (e.g. unknown command and/or option).
|
|
252
266
|
pyld_error_codes = {1, 2}
|
|
253
267
|
|
|
254
|
-
|
|
268
|
+
missing_labels = set()
|
|
255
269
|
for label in labels:
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
270
|
+
try:
|
|
271
|
+
exit_codes = exit_code_summary[label]
|
|
272
|
+
except KeyError:
|
|
273
|
+
missing_labels.add(label)
|
|
274
|
+
else:
|
|
275
|
+
pyld_errors = [code for code in exit_codes if code in pyld_error_codes]
|
|
276
|
+
pyld_error_count = len(pyld_errors)
|
|
277
|
+
pyld_error_summary = (
|
|
278
|
+
", ".join(sorted(str(code) for code in set(pyld_errors))) if pyld_errors else "None"
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
infra_errors = [code for code in exit_codes if code not in pyld_error_codes]
|
|
282
|
+
infra_error_count = len(infra_errors)
|
|
283
|
+
infra_error_summary = (
|
|
284
|
+
", ".join(sorted(str(code) for code in set(infra_errors))) if infra_errors else "None"
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
run = [label, pyld_error_count, pyld_error_summary, infra_error_count, infra_error_summary]
|
|
288
|
+
self._table.add_row(run)
|
|
289
|
+
if missing_labels:
|
|
290
|
+
warnings.append(
|
|
291
|
+
f"WARNING: Exit code summary was not available for job labels: {', '.join(missing_labels)}"
|
|
268
292
|
)
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
self._table.add_row(run)
|
|
293
|
+
if warnings:
|
|
294
|
+
self._msg = "\n".join(warnings)
|
|
272
295
|
|
|
273
296
|
def __str__(self):
|
|
274
297
|
alignments = ["<"] + [">"] * (len(self._table.colnames) - 1)
|
|
@@ -276,7 +299,7 @@ class ExitCodesReport(BaseRunReport):
|
|
|
276
299
|
return str("\n".join(lines))
|
|
277
300
|
|
|
278
301
|
|
|
279
|
-
def compile_job_summary(report: WmsRunReport) ->
|
|
302
|
+
def compile_job_summary(report: WmsRunReport) -> list[str]:
|
|
280
303
|
"""Add a job summary to the run report if necessary.
|
|
281
304
|
|
|
282
305
|
If the job summary is not provided, the function will attempt to compile
|
|
@@ -289,24 +312,89 @@ def compile_job_summary(report: WmsRunReport) -> None:
|
|
|
289
312
|
report : `lsst.ctrl.bps.WmsRunReport`
|
|
290
313
|
Information about a single run.
|
|
291
314
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
315
|
+
Returns
|
|
316
|
+
-------
|
|
317
|
+
warnings : `list` [`str`]
|
|
318
|
+
List of messages describing any non-critical issues encountered during
|
|
319
|
+
processing. Empty if none.
|
|
297
320
|
"""
|
|
321
|
+
warnings: list[str] = []
|
|
322
|
+
|
|
323
|
+
# If the job summary already exists, exit early.
|
|
298
324
|
if report.job_summary:
|
|
299
|
-
return
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
325
|
+
return warnings
|
|
326
|
+
|
|
327
|
+
if report.jobs:
|
|
328
|
+
job_summary = {}
|
|
329
|
+
by_label = group_jobs_by_label(report.jobs)
|
|
330
|
+
for label, job_group in by_label.items():
|
|
331
|
+
by_label_state = group_jobs_by_state(job_group)
|
|
332
|
+
_LOG.debug("by_label_state = %s", by_label_state)
|
|
333
|
+
counts = {state: len(jobs) for state, jobs in by_label_state.items()}
|
|
334
|
+
job_summary[label] = counts
|
|
335
|
+
report.job_summary = job_summary
|
|
336
|
+
else:
|
|
337
|
+
warnings.append("information about individual jobs not available")
|
|
338
|
+
|
|
339
|
+
return warnings
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def compile_code_summary(report: WmsRunReport) -> list[str]:
|
|
343
|
+
"""Add missing entries to the exit code summary if necessary.
|
|
344
|
+
|
|
345
|
+
A WMS plugin may exclude job labels for which there are no failures from
|
|
346
|
+
the exit code summary. The function will attempt to use the job summary,
|
|
347
|
+
if available, to add missing entries for these labels.
|
|
348
|
+
|
|
349
|
+
Parameters
|
|
350
|
+
----------
|
|
351
|
+
report : `lsst.ctrl.bps.WmsRunReport`
|
|
352
|
+
Information about a single run.
|
|
353
|
+
|
|
354
|
+
Returns
|
|
355
|
+
-------
|
|
356
|
+
warnings : `list` [`str`]
|
|
357
|
+
List of messages describing any non-critical issues encountered during
|
|
358
|
+
processing. Empty if none.
|
|
359
|
+
"""
|
|
360
|
+
warnings: list[str] = []
|
|
361
|
+
|
|
362
|
+
# If the job summary is not available, exit early.
|
|
363
|
+
if not report.job_summary:
|
|
364
|
+
return warnings
|
|
365
|
+
|
|
366
|
+
# A shallow copy is enough here because we won't be modifying the existing
|
|
367
|
+
# entries, only adding new ones if necessary.
|
|
368
|
+
exit_code_summary = dict(report.exit_code_summary) if report.exit_code_summary else {}
|
|
369
|
+
|
|
370
|
+
# Use the job summary to add the entries for labels with no failures
|
|
371
|
+
# *without* modifying already existing entries.
|
|
372
|
+
failure_summary = {label: states[WmsStates.FAILED] for label, states in report.job_summary.items()}
|
|
373
|
+
for label, count in failure_summary.items():
|
|
374
|
+
if count == 0:
|
|
375
|
+
exit_code_summary.setdefault(label, [])
|
|
376
|
+
|
|
377
|
+
# Check if there are any discrepancies between the data in the exit code
|
|
378
|
+
# summary and the job summary.
|
|
379
|
+
code_summary_labels = set(exit_code_summary)
|
|
380
|
+
failure_summary_labels = set(failure_summary)
|
|
381
|
+
mismatches = {
|
|
382
|
+
label
|
|
383
|
+
for label in failure_summary_labels & code_summary_labels
|
|
384
|
+
if len(exit_code_summary[label]) != failure_summary[label]
|
|
385
|
+
}
|
|
386
|
+
if mismatches:
|
|
387
|
+
warnings.append(
|
|
388
|
+
f"number of exit codes differs from number of failures for job labels: {', '.join(mismatches)}"
|
|
389
|
+
)
|
|
390
|
+
missing = failure_summary_labels - code_summary_labels
|
|
391
|
+
if missing:
|
|
392
|
+
warnings.append(f"exit codes not available for job labels: {', '.join(missing)}")
|
|
393
|
+
|
|
394
|
+
if exit_code_summary:
|
|
395
|
+
report.exit_code_summary = exit_code_summary
|
|
396
|
+
|
|
397
|
+
return warnings
|
|
310
398
|
|
|
311
399
|
|
|
312
400
|
def group_jobs_by_state(jobs):
|
|
@@ -31,6 +31,7 @@ __all__ = [
|
|
|
31
31
|
"_dump_env_info",
|
|
32
32
|
"_dump_pkg_info",
|
|
33
33
|
"_make_id_link",
|
|
34
|
+
"bps_eval",
|
|
34
35
|
"chdir",
|
|
35
36
|
"create_count_summary",
|
|
36
37
|
"create_job_quantum_graph_filename",
|
|
@@ -52,6 +53,7 @@ from typing import Any
|
|
|
52
53
|
|
|
53
54
|
import yaml
|
|
54
55
|
|
|
56
|
+
from lsst.utils import doImport
|
|
55
57
|
from lsst.utils.packages import Packages
|
|
56
58
|
|
|
57
59
|
_LOG = logging.getLogger(__name__)
|
|
@@ -144,7 +146,7 @@ def create_job_quantum_graph_filename(config, job, out_prefix=None):
|
|
|
144
146
|
found, subdir = config.search("subDirTemplate", opt={"curvals": curvals})
|
|
145
147
|
if not found:
|
|
146
148
|
subdir = "{job.label}"
|
|
147
|
-
full_filename = Path("inputs") / subdir / f"quantum_{job.name}.
|
|
149
|
+
full_filename = Path("inputs") / subdir / f"quantum_{job.name}.qg"
|
|
148
150
|
|
|
149
151
|
if out_prefix is not None:
|
|
150
152
|
full_filename = Path(out_prefix) / full_filename
|
|
@@ -353,3 +355,35 @@ def subset_dimension_values(
|
|
|
353
355
|
f"{desc_what} missing dimensions ({', '.join(sorted(missing_dims))}) required for {desc_for}"
|
|
354
356
|
)
|
|
355
357
|
return dim_values
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def bps_eval(func: str, args: str) -> Any:
|
|
361
|
+
"""Evaluate user provided expression/function.
|
|
362
|
+
|
|
363
|
+
Parameters
|
|
364
|
+
----------
|
|
365
|
+
func : `str`
|
|
366
|
+
Importable string or built-in function name.
|
|
367
|
+
args : `str`
|
|
368
|
+
Parameters to pass to the function.
|
|
369
|
+
|
|
370
|
+
Returns
|
|
371
|
+
-------
|
|
372
|
+
results : `~typing.Any`
|
|
373
|
+
Results of running eval.
|
|
374
|
+
|
|
375
|
+
Raises
|
|
376
|
+
------
|
|
377
|
+
ImportError
|
|
378
|
+
If problems importing.
|
|
379
|
+
"""
|
|
380
|
+
if "." in func:
|
|
381
|
+
genfunc = doImport(func) # noqa: F841
|
|
382
|
+
func_reference = "genfunc"
|
|
383
|
+
else:
|
|
384
|
+
func_reference = func
|
|
385
|
+
eval_str = f"{func_reference}({args})"
|
|
386
|
+
_LOG.debug("String passed to eval: '%s'", eval_str)
|
|
387
|
+
results = eval(eval_str)
|
|
388
|
+
|
|
389
|
+
return results
|