Coverage for python/lsst/ctrl/bps/parsl/configuration.py: 35%

43 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-06 12:42 +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/>. 

27 

28import logging 

29import os 

30from typing import Any, Literal, TypeVar, overload 

31 

32from lsst.ctrl.bps import BpsConfig 

33 

34__all__ = ( 

35 "get_bps_config_value", 

36 "get_workflow_name", 

37 "get_workflow_filename", 

38 "set_parsl_logging", 

39) 

40 

41 

42T = TypeVar("T") 

43 

44 

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 ... 

54 

55 

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 ... 

67 

68 

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 ... 

78 

79 

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. 

89 

90 I find this more useful than ``BpsConfig.__getitem__`` or 

91 ``BpsConfig.get``. 

92 

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`. 

107 

108 Returns 

109 ------- 

110 value 

111 Value for ``key`` in the `config`` if it exists, otherwise ``default``, 

112 if provided. 

113 

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 

133 

134 

135def get_workflow_name(config: BpsConfig) -> str: 

136 """Get name of this workflow. 

137 

138 The workflow name is constructed by joining the ``project`` and 

139 ``campaign`` (if set; otherwise ``operator``) entries in the BPS 

140 configuration. 

141 

142 Parameters 

143 ---------- 

144 config : `BpsConfig` 

145 BPS configuration. 

146 

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}" 

157 

158 

159def get_workflow_filename(out_prefix: str) -> str: 

160 """Get filename for persisting workflow. 

161 

162 Parameters 

163 ---------- 

164 out_prefix : `str` 

165 Directory which should contain workflow file. 

166 

167 Returns 

168 ------- 

169 filename : `str` 

170 Filename for persisting workflow. 

171 """ 

172 return os.path.join(out_prefix, "parsl_workflow.pickle") 

173 

174 

175def set_parsl_logging(config: BpsConfig) -> int: 

176 """Set parsl logging levels. 

177 

178 The logging level is set by the ``parsl.log_level`` entry in the BPS 

179 configuration. 

180 

181 Parameters 

182 ---------- 

183 config : `BpsConfig` 

184 BPS configuration. 

185 

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