Coverage for python/lsst/ctrl/bps/parsl/configuration.py: 35%
43 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-01 11:22 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-01 11:22 +0000
1# This file is part of ctrl_bps_parsl.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org) and the LSST DESC (https://www.lsstdesc.org/).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This software is dual licensed under the GNU General Public License and also
10# under a 3-clause BSD license. Recipients may choose which of these licenses
11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
12# respectively. If you choose the GPL option then the following text applies
13# (but note that there is still no warranty even if you opt for BSD instead):
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation, either version 3 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program. If not, see <https://www.gnu.org/licenses/>.
28import logging
29import os
30from typing import Any, Literal, TypeVar, overload
32from lsst.ctrl.bps import BpsConfig
34__all__ = (
35 "get_bps_config_value",
36 "get_workflow_name",
37 "get_workflow_filename",
38 "set_parsl_logging",
39)
42T = TypeVar("T")
45# Default provided, not required
46@overload
47def get_bps_config_value(
48 config: BpsConfig,
49 key: str,
50 dataType: type[T],
51 default: T,
52) -> T:
53 ...
56# No default, but required
57@overload
58def get_bps_config_value(
59 config: BpsConfig,
60 key: str,
61 dataType: type[T],
62 default: T | None = None,
63 *,
64 required: Literal[True],
65) -> T:
66 ...
69# No default, not required
70@overload
71def get_bps_config_value(
72 config: BpsConfig,
73 key: str,
74 dataType: type[T],
75 default: T | None = None,
76) -> T | None:
77 ...
80def get_bps_config_value(
81 config: BpsConfig,
82 key: str,
83 dataType: type[T],
84 default: T | None = None,
85 *,
86 required: bool = False,
87) -> T | None:
88 """Get a value from the BPS configuration
90 I find this more useful than ``BpsConfig.__getitem__`` or
91 ``BpsConfig.get``.
93 Parameters
94 ----------
95 config : `BpsConfig`
96 Configuration from which to retrieve value.
97 key : `str`
98 Key name.
99 dataType : `type`
100 We require that the returned value have this type.
101 default : optional
102 Default value to be provided if ``key`` doesn't exist in the
103 ``config``. A default value of `None` means that there is no default.
104 required : `bool`, optional
105 If ``True``, the returned value may come from the configuration or from
106 the default, but it may not be `None`.
108 Returns
109 -------
110 value
111 Value for ``key`` in the `config`` if it exists, otherwise ``default``,
112 if provided.
114 Raises
115 ------
116 KeyError
117 If ``key`` is not in ``config`` and no default is provided but a value
118 is ``required``.
119 RuntimeError
120 If the value is not set or is of the wrong type.
121 """
122 options: dict[str, Any] = dict(expandEnvVars=True, replaceVars=True, required=required)
123 if default is not None:
124 options["default"] = default
125 found, value = config.search(key, options)
126 if not found and default is None:
127 if required:
128 raise KeyError(f"No value found for {key} and no default provided")
129 return None
130 if not isinstance(value, dataType):
131 raise RuntimeError(f"Configuration value {key}={value} is not of type {dataType}")
132 return value
135def get_workflow_name(config: BpsConfig) -> str:
136 """Get name of this workflow
138 The workflow name is constructed by joining the ``project`` and
139 ``campaign`` (if set; otherwise ``operator``) entries in the BPS
140 configuration.
142 Parameters
143 ----------
144 config : `BpsConfig`
145 BPS configuration.
147 Returns
148 -------
149 name : `str`
150 Workflow name.
151 """
152 project = get_bps_config_value(config, "project", str, "bps")
153 campaign = get_bps_config_value(
154 config, "campaign", str, get_bps_config_value(config, "operator", str, required=True)
155 )
156 return f"{project}.{campaign}"
159def get_workflow_filename(out_prefix: str) -> str:
160 """Get filename for persisting workflow
162 Parameters
163 ----------
164 out_prefix : `str`
165 Directory which should contain workflow file.
167 Returns
168 -------
169 filename : `str`
170 Filename for persisting workflow.
171 """
172 return os.path.join(out_prefix, "parsl_workflow.pickle")
175def set_parsl_logging(config: BpsConfig) -> int:
176 """Set parsl logging levels
178 The logging level is set by the ``parsl.log_level`` entry in the BPS
179 configuration.
181 Parameters
182 ----------
183 config : `BpsConfig`
184 BPS configuration.
186 Returns
187 -------
188 level : `int`
189 Logging level applied to ``parsl`` loggers.
190 """
191 level_name = get_bps_config_value(config, ".parsl.log_level", str, "INFO")
192 if level_name not in ("CRITICAL", "DEBUG", "ERROR", "FATAL", "INFO", "WARN"):
193 raise RuntimeError(f"Unrecognised parsl.log_level: {level_name}")
194 level: int = getattr(logging, level_name)
195 for name in logging.root.manager.loggerDict:
196 if name.startswith("parsl"):
197 logging.getLogger(name).setLevel(level)
198 logging.getLogger("database_manager").setLevel(logging.INFO)
199 return level