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 io 

24import os 

25import traceback 

26from unittest.mock import MagicMock 

27 

28from ..core.utils import iterable 

29 

30 

31# CLI_BUTLER_MOCK_ENV is set by some tests as an environment variable, it 

32# indicates to the cli_handle_exception function that instead of executing the 

33# command implementation function it should use the Mocker class for unit test 

34# verification. 

35mockEnvVarKey = "CLI_BUTLER_MOCK_ENV" 

36mockEnvVar = {mockEnvVarKey: "1"} 

37 

38 

39class Mocker: 

40 

41 mock = MagicMock() 

42 

43 def __init__(self, *args, **kwargs): 

44 """Mocker is a helper class for unit tests. It can be imported and 

45 called and later imported again and call can be verified. 

46 

47 For convenience, constructor arguments are forwarded to the call 

48 function. 

49 """ 

50 self.__call__(*args, **kwargs) 

51 

52 def __call__(self, *args, **kwargs): 

53 """Creates a MagicMock and stores it in a static variable that can 

54 later be verified. 

55 """ 

56 Mocker.mock(*args, **kwargs) 

57 

58 

59def split_commas(context, param, values): 

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

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

62 

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

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

65 

66 Parameters 

67 ---------- 

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

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

70 callbacks. 

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

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

73 callbacks. 

74 values : [`str`] 

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

76 which will be treated as delimiters for separate values. 

77 

78 Returns 

79 ------- 

80 list of string 

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

82 list. 

83 """ 

84 valueList = [] 

85 for value in iterable(values): 

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

87 return valueList 

88 

89 

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

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

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

93 all the passed-in values. 

94 

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

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

97 

98 Parameters 

99 ---------- 

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

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

102 callbacks. 

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

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

105 callbacks. 

106 values : [`str`] 

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

108 which will be treated as delimiters for separate values. 

109 separator : str, optional 

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

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

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

113 

114 Returns 

115 ------- 

116 `dict` : [`str`, `str`] 

117 The passed-in values in dict form. 

118 

119 Raises 

120 ------ 

121 `click.ClickException` 

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

123 are encountered. 

124 """ 

125 if separator in (",", " "): 

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

127 vals = split_commas(context, param, values) 

128 ret = {} 

129 for val in vals: 

130 try: 

131 k, v = val.split(separator) 

132 except ValueError: 

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

134 if k in ret: 

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

136 ret[k] = v 

137 return ret 

138 

139 

140def to_upper(context, param, value): 

141 """Convert a value to upper case. 

142 

143 Parameters 

144 ---------- 

145 context : click.Context 

146 

147 values : string 

148 The value to be converted. 

149 

150 Returns 

151 ------- 

152 string 

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

154 """ 

155 return value.upper() 

156 

157 

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

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

160 ClickException if there is an Exception. 

161 

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

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

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

165 

166 Parameters 

167 ---------- 

168 func : function 

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

170 to the function. 

171 

172 Returns 

173 ------- 

174 The result of calling func. 

175 

176 Raises 

177 ------ 

178 click.ClickException 

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

180 """ 

181 if mockEnvVarKey in os.environ: 

182 Mocker(*args, **kwargs) 

183 return 

184 try: 

185 return func(*args, **kwargs) 

186 except Exception: 

187 msg = io.StringIO() 

188 msg.write("An error occurred during command execution:\n") 

189 traceback.print_exc(file=msg) 

190 msg.seek(0) 

191 raise click.ClickException(msg.read())