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

43 statements  

« prev     ^ index     » next       coverage.py v7.3.0, created at 2023-08-31 09:56 +0000

1import logging 

2import os 

3from typing import Any, Literal, TypeVar, overload 

4 

5from lsst.ctrl.bps import BpsConfig 

6 

7__all__ = ( 

8 "get_bps_config_value", 

9 "get_workflow_name", 

10 "get_workflow_filename", 

11 "set_parsl_logging", 

12) 

13 

14 

15T = TypeVar("T") 

16 

17 

18# Default provided, not required 

19@overload 

20def get_bps_config_value( 

21 config: BpsConfig, 

22 key: str, 

23 dataType: type[T], 

24 default: T, 

25) -> T: 

26 ... 

27 

28 

29# No default, but required 

30@overload 

31def get_bps_config_value( 

32 config: BpsConfig, 

33 key: str, 

34 dataType: type[T], 

35 default: T | None = None, 

36 *, 

37 required: Literal[True], 

38) -> T: 

39 ... 

40 

41 

42# No default, not required 

43@overload 

44def get_bps_config_value( 

45 config: BpsConfig, 

46 key: str, 

47 dataType: type[T], 

48 default: T | None = None, 

49) -> T | None: 

50 ... 

51 

52 

53def get_bps_config_value( 

54 config: BpsConfig, 

55 key: str, 

56 dataType: type[T], 

57 default: T | None = None, 

58 *, 

59 required: bool = False, 

60) -> T | None: 

61 """Get a value from the BPS configuration 

62 

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

64 ``BpsConfig.get``. 

65 

66 Parameters 

67 ---------- 

68 config : `BpsConfig` 

69 Configuration from which to retrieve value. 

70 key : `str` 

71 Key name. 

72 dataType : `type` 

73 We require that the returned value have this type. 

74 default : optional 

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

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

77 required : `bool`, optional 

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

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

80 

81 Returns 

82 ------- 

83 value 

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

85 if provided. 

86 

87 Raises 

88 ------ 

89 KeyError 

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

91 is ``required``. 

92 RuntimeError 

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

94 """ 

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

96 if default is not None: 

97 options["default"] = default 

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

99 if not found and default is None: 

100 if required: 

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

102 return None 

103 if not isinstance(value, dataType): 

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

105 return value 

106 

107 

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

109 """Get name of this workflow 

110 

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

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

113 configuration. 

114 

115 Parameters 

116 ---------- 

117 config : `BpsConfig` 

118 BPS configuration. 

119 

120 Returns 

121 ------- 

122 name : `str` 

123 Workflow name. 

124 """ 

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

126 campaign = get_bps_config_value( 

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

128 ) 

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

130 

131 

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

133 """Get filename for persisting workflow 

134 

135 Parameters 

136 ---------- 

137 out_prefix : `str` 

138 Directory which should contain workflow file. 

139 

140 Returns 

141 ------- 

142 filename : `str` 

143 Filename for persisting workflow. 

144 """ 

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

146 

147 

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

149 """Set parsl logging levels 

150 

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

152 configuration. 

153 

154 Parameters 

155 ---------- 

156 config : `BpsConfig` 

157 BPS configuration. 

158 

159 Returns 

160 ------- 

161 level : `int` 

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

163 """ 

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

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

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

167 level: int = getattr(logging, level_name) 

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

169 if name.startswith("parsl"): 

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

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

172 return level