Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# This file is part of pipe_base. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (http://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <http://www.gnu.org/licenses/>. 

21 

22"""Module which defines ConfigOverrides class and related methods. 

23""" 

24 

25__all__ = ["ConfigOverrides"] 

26 

27import ast 

28 

29import lsst.pex.exceptions as pexExceptions 

30from lsst.utils import doImport 

31 

32from enum import Enum 

33 

34OverrideTypes = Enum("OverrideTypes", "Value File Python Instrument") 

35 

36 

37class ConfigOverrides: 

38 """Defines a set of overrides to be applied to a task config. 

39 

40 Overrides for task configuration need to be applied by activator when 

41 creating task instances. This class represents an ordered set of such 

42 overrides which activator receives from some source (e.g. command line 

43 or some other configuration). 

44 

45 Methods 

46 ---------- 

47 addFileOverride(filename) 

48 Add overrides from a specified file. 

49 addValueOverride(field, value) 

50 Add override for a specific field. 

51 applyTo(config) 

52 Apply all overrides to a `config` instance. 

53 

54 Notes 

55 ----- 

56 Serialization support for this class may be needed, will add later if 

57 necessary. 

58 """ 

59 

60 def __init__(self): 

61 self._overrides = [] 

62 

63 def addFileOverride(self, filename): 

64 """Add overrides from a specified file. 

65 

66 Parameters 

67 ---------- 

68 filename : str 

69 Path to the override file. 

70 """ 

71 self._overrides.append((OverrideTypes.File, filename)) 

72 

73 def addValueOverride(self, field, value): 

74 """Add override for a specific field. 

75 

76 This method is not very type-safe as it is designed to support 

77 use cases where input is given as string, e.g. command line 

78 activators. If `value` has a string type and setting of the field 

79 fails with `TypeError` the we'll attempt `eval()` the value and 

80 set the field with that value instead. 

81 

82 Parameters 

83 ---------- 

84 field : str 

85 Fully-qualified field name. 

86 value : 

87 Value to be given to a filed. 

88 """ 

89 self._overrides.append((OverrideTypes.Value, (field, value))) 

90 

91 def addPythonOverride(self, python_snippet: str): 

92 """Add Overrides by running a snippit of python code against a config. 

93 

94 Parameters 

95 ---------- 

96 python_snippet: str 

97 A string which is valid python code to be executed. This is done 

98 with config as the only local accessible value. 

99 """ 

100 self._overrides.append((OverrideTypes.Python, python_snippet)) 

101 

102 def addInstrumentOverride(self, instrument: str, task_name: str): 

103 """Apply any overrides that an instrument has for a task 

104 

105 Parameters 

106 ---------- 

107 instrument: str 

108 A string containing the fully qualified name of an instrument from 

109 which configs should be loaded and applied 

110 task_name: str 

111 The _DefaultName of a task associated with a config, used to look 

112 up overrides from the instrument. 

113 """ 

114 instrument_lib = doImport(instrument)() 

115 self._overrides.append((OverrideTypes.Instrument, (instrument_lib, task_name))) 

116 

117 def applyTo(self, config): 

118 """Apply all overrides to a task configuration object. 

119 

120 Parameters 

121 ---------- 

122 config : `pex.Config` 

123 

124 Raises 

125 ------ 

126 `Exception` is raised if operations on configuration object fail. 

127 """ 

128 for otype, override in self._overrides: 

129 if otype is OverrideTypes.File: 

130 config.load(override) 

131 elif otype is OverrideTypes.Value: 

132 field, value = override 

133 field = field.split('.') 

134 # find object with attribute to set, throws if we name is wrong 

135 obj = config 

136 for attr in field[:-1]: 

137 obj = getattr(obj, attr) 

138 # If input is a string and field type is not a string then we 

139 # have to convert string to an expected type. Implementing 

140 # full string parser is non-trivial so we take a shortcut here 

141 # and `eval` the string and assign the resulting value to a 

142 # field. Type erroes can happen during both `eval` and field 

143 # assignment. 

144 if isinstance(value, str) and obj._fields[field[-1]].dtype is not str: 

145 try: 

146 # use safer ast.literal_eval, it only supports literals 

147 value = ast.literal_eval(value) 

148 except Exception: 

149 # eval failed, wrap exception with more user-friendly 

150 # message 

151 raise pexExceptions.RuntimeError(f"Unable to parse `{value}' into a Python object") 

152 

153 # this can throw in case of type mismatch 

154 setattr(obj, field[-1], value) 

155 elif otype is OverrideTypes.Python: 

156 exec(override, None, {"config": config}) 

157 elif otype is OverrideTypes.Instrument: 

158 instrument, name = override 

159 instrument.applyConfigOverrides(name, config)