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

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 

22import click 

23import os 

24 

25from ..core.utils import iterable 

26 

27 

28# DAF_BUTLER_MOCK is set by some tests as an environment variable and indicates 

29# to the cli_handle_exception function that instead of executing the command 

30# implementation function it should print details about the called command to 

31# stdout. These details are then used to verify the command function was loaded 

32# and received expected inputs. 

33DAF_BUTLER_MOCK = {"DAF_BUTLER_MOCK": ""} 

34 

35 

36def split_commas(context, param, values): 

37 """Process a tuple of values, where each value may contain comma-separated 

38 values, and return a single list of all the passed-in values. 

39 

40 This function can be passed to the 'callback' argument of a click.option to 

41 allow it to process comma-separated values (e.g. "--my-opt a,b,c"). 

42 

43 Parameters 

44 ---------- 

45 context : `click.Context` or `None` 

46 The current execution context. Unused, but Click always passes it to 

47 callbacks. 

48 param : `click.core.Option` or `None` 

49 The parameter being handled. Unused, but Click always passes it to 

50 callbacks. 

51 values : [`str`] 

52 All the values passed for this option. Strings may contain commas, 

53 which will be treated as delimiters for separate values. 

54 

55 Returns 

56 ------- 

57 list of string 

58 The passed in values separated by commas and combined into a single 

59 list. 

60 """ 

61 valueList = [] 

62 for value in iterable(values): 

63 valueList.extend(value.split(",")) 

64 return valueList 

65 

66 

67def split_kv(context, param, values, separator="="): 

68 """Process a tuple of values that are key-value pairs separated by a given 

69 separator. Multiple pairs may be comma separated. Return a dictionary of 

70 all the passed-in values. 

71 

72 This function can be passed to the 'callback' argument of a click.option to 

73 allow it to process comma-separated values (e.g. "--my-opt a=1,b=2"). 

74 

75 Parameters 

76 ---------- 

77 context : `click.Context` or `None` 

78 The current execution context. Unused, but Click always passes it to 

79 callbacks. 

80 param : `click.core.Option` or `None` 

81 The parameter being handled. Unused, but Click always passes it to 

82 callbacks. 

83 values : [`str`] 

84 All the values passed for this option. Strings may contain commas, 

85 which will be treated as delimiters for separate values. 

86 separator : str, optional 

87 The character that separates key-value pairs. May not be a comma or an 

88 empty space (for space separators use Click's default implementation 

89 for tuples; `type=(str, str)`). By default "=". 

90 

91 Returns 

92 ------- 

93 `dict` : [`str`, `str`] 

94 The passed-in values in dict form. 

95 

96 Raises 

97 ------ 

98 `click.ClickException` 

99 Raised if the separator is not found in an entry, or if duplicate keys 

100 are encountered. 

101 """ 

102 if "," == separator or " " == separator: 

103 raise RuntimeError(f"'{separator}' is not a supported separator for key-value pairs.") 

104 vals = split_commas(context, param, values) 

105 ret = {} 

106 for val in vals: 

107 try: 

108 k, v = val.split(separator) 

109 except ValueError: 

110 raise click.ClickException(f"Missing or invalid key-value separator in value '{val}'") 

111 if k in ret: 

112 raise click.ClickException(f"Duplicate entries for '{k}' in '{values}'") 

113 ret[k] = v 

114 return ret 

115 

116 

117def to_upper(context, param, value): 

118 """Convert a value to upper case. 

119 

120 Parameters 

121 ---------- 

122 context : click.Context 

123 

124 values : string 

125 The value to be converted. 

126 

127 Returns 

128 ------- 

129 string 

130 A copy of the passed-in value, converted to upper case. 

131 """ 

132 return value.upper() 

133 

134 

135def printFunctionInfo(func, *args, **kwargs): 

136 """For unit testing butler subcommand call execution, write a dict to 

137 stdout that formats information about a funciton call into a dict that can 

138 be evaluated by `verifyFunctionInfo` 

139 

140 Parameters 

141 ---------- 

142 func : function 

143 The function that has been called, whose name should be written. 

144 args : [`str`] 

145 The values of the arguments the function was called with. 

146 kwags : `dict` [`str`, `str`] 

147 The names and values of the kwargs the function was called with. 

148 """ 

149 print(dict(function=func.__name__, 

150 args=args, 

151 kwargs=kwargs)) 

152 

153 

154def verifyFunctionInfo(testSuite, output, function, expectedArgs, expectedKwargs): 

155 """For unit testing butler subcommand call execution, compare a dict that 

156 has been printed to stdout to expected data. 

157 

158 Parameters 

159 ---------- 

160 testSuite : `unittest.Testsuite` 

161 The test suite that is executing a unit test. 

162 output : `str` 

163 The dict that has been printed to stdout. It should be formatted to 

164 re-instantiate by calling `eval`. 

165 function : `str` 

166 The name of the function that was was expected to have been called. 

167 expectedArgs : [`str`] 

168 The values of the arguments that should have been passed to the 

169 function. 

170 expectedKwargs : `dict` [`str`, `str`] 

171 The names and values of the kwargs that should have been passsed to the 

172 funciton. 

173 """ 

174 calledWith = eval(output) 

175 testSuite.assertEqual(calledWith['function'], function) 

176 testSuite.assertEqual(calledWith['args'], expectedArgs) 

177 testSuite.assertEqual(calledWith['kwargs'], expectedKwargs) 

178 

179 

180def cli_handle_exception(func, *args, **kwargs): 

181 """Wrap a function call in an exception handler that raises a 

182 ClickException if there is an Exception. 

183 

184 Also provides support for unit testing by testing for an environment 

185 variable, and if it is present prints the function name, args, and kwargs 

186 to stdout so they can be read and verified by the unit test code. 

187 

188 Parameters 

189 ---------- 

190 func : function 

191 A function to be called and exceptions handled. Will pass args & kwargs 

192 to the function. 

193 

194 Returns 

195 ------- 

196 The result of calling func. 

197 

198 Raises 

199 ------ 

200 click.ClickException 

201 An exception to be handled by the Click CLI tool. 

202 """ 

203 # "DAF_BUTLER_MOCK" matches the key in the variable DAF_BUTLER_MOCK, 

204 # defined in the top of this file. 

205 if "DAF_BUTLER_MOCK" in os.environ: 

206 printFunctionInfo(func, *args, **kwargs) 

207 return 

208 try: 

209 return func(*args, **kwargs) 

210 except Exception as err: 

211 raise click.ClickException(err)