Coverage for python/lsst/daf/butler/cli/utils.py : 20%

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/>.
22import click
23import os
25from ..core.utils import iterable
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": ""}
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.
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").
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.
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
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.
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").
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 "=".
91 Returns
92 -------
93 `dict` : [`str`, `str`]
94 The passed-in values in dict form.
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
117def to_upper(context, param, value):
118 """Convert a value to upper case.
120 Parameters
121 ----------
122 context : click.Context
124 values : string
125 The value to be converted.
127 Returns
128 -------
129 string
130 A copy of the passed-in value, converted to upper case.
131 """
132 return value.upper()
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`
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))
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.
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)
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.
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.
188 Parameters
189 ----------
190 func : function
191 A function to be called and exceptions handled. Will pass args & kwargs
192 to the function.
194 Returns
195 -------
196 The result of calling func.
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)