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

40 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-05-03 10:15 +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( 47 ↛ exitline 47 didn't jump to the function exit

48 config: BpsConfig, 

49 key: str, 

50 dataType: type[T], 

51 default: T, 

52) -> T: ... 

53 

54 

55# No default, but required 

56@overload 

57def get_bps_config_value( 57 ↛ exitline 57 didn't jump to the function exit

58 config: BpsConfig, 

59 key: str, 

60 dataType: type[T], 

61 default: T | None = None, 

62 *, 

63 required: Literal[True], 

64) -> T: ... 

65 

66 

67# No default, not required 

68@overload 

69def get_bps_config_value( 69 ↛ exitline 69 didn't jump to the function exit

70 config: BpsConfig, 

71 key: str, 

72 dataType: type[T], 

73 default: T | None = None, 

74) -> T | None: ... 

75 

76 

77def get_bps_config_value( 

78 config: BpsConfig, 

79 key: str, 

80 dataType: type[T], 

81 default: T | None = None, 

82 *, 

83 required: bool = False, 

84) -> T | None: 

85 """Get a value from the BPS configuration. 

86 

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

88 ``BpsConfig.get``. 

89 

90 Parameters 

91 ---------- 

92 config : `BpsConfig` 

93 Configuration from which to retrieve value. 

94 key : `str` 

95 Key name. 

96 dataType : `type` 

97 We require that the returned value have this type. 

98 default : optional 

99 Default value to be provided if ``key`` doesn't exist in the 

100 ``config``. A default value of `None` means that there is no default. 

101 required : `bool`, optional 

102 If ``True``, the returned value may come from the configuration or from 

103 the default, but it may not be `None`. 

104 

105 Returns 

106 ------- 

107 value 

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

109 if provided. 

110 

111 Raises 

112 ------ 

113 KeyError 

114 If ``key`` is not in ``config`` and no default is provided but a value 

115 is ``required``. 

116 RuntimeError 

117 If the value is not set or is of the wrong type. 

118 """ 

119 options: dict[str, Any] = dict(expandEnvVars=True, replaceVars=True, required=required) 

120 if default is not None: 

121 options["default"] = default 

122 found, value = config.search(key, options) 

123 if not found and default is None: 

124 if required: 

125 raise KeyError(f"No value found for {key} and no default provided") 

126 return None 

127 if not isinstance(value, dataType): 

128 raise RuntimeError(f"Configuration value {key}={value} is not of type {dataType}") 

129 return value 

130 

131 

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

133 """Get name of this workflow. 

134 

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

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

137 configuration. 

138 

139 Parameters 

140 ---------- 

141 config : `BpsConfig` 

142 BPS configuration. 

143 

144 Returns 

145 ------- 

146 name : `str` 

147 Workflow name. 

148 """ 

149 project = get_bps_config_value(config, "project", str, "bps") 

150 campaign = get_bps_config_value( 

151 config, "campaign", str, get_bps_config_value(config, "operator", str, required=True) 

152 ) 

153 return f"{project}.{campaign}" 

154 

155 

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

157 """Get filename for persisting workflow. 

158 

159 Parameters 

160 ---------- 

161 out_prefix : `str` 

162 Directory which should contain workflow file. 

163 

164 Returns 

165 ------- 

166 filename : `str` 

167 Filename for persisting workflow. 

168 """ 

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

170 

171 

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

173 """Set parsl logging levels. 

174 

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

176 configuration. 

177 

178 Parameters 

179 ---------- 

180 config : `BpsConfig` 

181 BPS configuration. 

182 

183 Returns 

184 ------- 

185 level : `int` 

186 Logging level applied to ``parsl`` loggers. 

187 """ 

188 level_name = get_bps_config_value(config, ".parsl.log_level", str, "INFO") 

189 if level_name not in ("CRITICAL", "DEBUG", "ERROR", "FATAL", "INFO", "WARN"): 

190 raise RuntimeError(f"Unrecognised parsl.log_level: {level_name}") 

191 level: int = getattr(logging, level_name) 

192 for name in logging.root.manager.loggerDict: 

193 if name.startswith("parsl"): 

194 logging.getLogger(name).setLevel(level) 

195 logging.getLogger("database_manager").setLevel(logging.INFO) 

196 return level