Coverage for python/lsst/daf/butler/cli/opt/options.py: 82%
47 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-02 03:16 -0700
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-02 03:16 -0700
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 software is dual licensed under the GNU General Public License and also
10# under a 3-clause BSD license. Recipients may choose which of these licenses
11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
12# respectively. If you choose the GPL option then the following text applies
13# (but note that there is still no warranty even if you opt for BSD instead):
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation, either version 3 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program. If not, see <http://www.gnu.org/licenses/>.
27from __future__ import annotations
29__all__ = (
30 "CollectionTypeCallback",
31 "collection_type_option",
32 "collections_option",
33 "components_option",
34 "config_option",
35 "config_file_option",
36 "confirm_option",
37 "dataset_type_option",
38 "datasets_option",
39 "log_level_option",
40 "long_log_option",
41 "log_file_option",
42 "log_label_option",
43 "log_tty_option",
44 "options_file_option",
45 "processes_option",
46 "regex_option",
47 "register_dataset_types_option",
48 "run_option",
49 "transfer_dimensions_option",
50 "transfer_option",
51 "transfer_option_no_short",
52 "verbose_option",
53 "where_option",
54 "order_by_option",
55 "limit_option",
56 "offset_option",
57)
59from functools import partial
60from typing import Any
62import click
63from lsst.daf.butler.registry import CollectionType
65from ..cliLog import CliLog
66from ..utils import MWOptionDecorator, MWPath, split_commas, split_kv, unwrap, yaml_presets
69class CollectionTypeCallback:
70 """Helper class for handling different collection types."""
72 collectionTypes = tuple(collectionType.name for collectionType in CollectionType.all())
74 @staticmethod
75 def makeCollectionTypes(
76 context: click.Context, param: click.Option, value: tuple[str, ...] | str
77 ) -> tuple[CollectionType, ...]:
78 if not value:
79 # Click seems to demand that the default be an empty tuple, rather
80 # than a sentinal like None. The behavior that we want is that
81 # not passing this option at all passes all collection types, while
82 # passing it uses only the passed collection types. That works
83 # fine for now, since there's no command-line option to subtract
84 # collection types, and hence the only way to get an empty tuple
85 # is as the default.
86 return tuple(CollectionType.all())
88 return tuple(CollectionType.from_name(item) for item in split_commas(context, param, value))
91collection_type_option = MWOptionDecorator(
92 "--collection-type",
93 callback=CollectionTypeCallback.makeCollectionTypes,
94 multiple=True,
95 help="If provided, only list collections of this type.",
96 type=click.Choice(choices=CollectionTypeCallback.collectionTypes, case_sensitive=False),
97)
100collections_option = MWOptionDecorator(
101 "--collections",
102 help=unwrap(
103 """One or more expressions that fully or partially identify
104 the collections to search for datasets. If not provided all
105 datasets are returned."""
106 ),
107 multiple=True,
108 callback=split_commas,
109)
112components_option = MWOptionDecorator(
113 "--components/--no-components",
114 default=None,
115 help=unwrap(
116 """For --components, apply all expression patterns to
117 component dataset type names as well. For --no-components,
118 never apply patterns to components. Only --no-components
119 is now supported. Option will be removed after v27."""
120 ),
121)
124def _config_split(*args: Any) -> dict[str, str]:
125 # Config values might include commas so disable comma-splitting.
126 result = split_kv(*args, multiple=False)
127 assert isinstance(result, dict), "For mypy check that we get the expected result"
128 return result
131config_option = MWOptionDecorator(
132 "-c",
133 "--config",
134 callback=_config_split,
135 help="Config override, as a key-value pair.",
136 metavar="TEXT=TEXT",
137 multiple=True,
138)
141config_file_option = MWOptionDecorator(
142 "-C",
143 "--config-file",
144 help=unwrap(
145 """Path to a pex config override to be included after the
146 Instrument config overrides are applied."""
147 ),
148)
151confirm_option = MWOptionDecorator(
152 "--confirm/--no-confirm",
153 default=True,
154 help="Print expected action and a confirmation prompt before executing. Default is --confirm.",
155)
158dataset_type_option = MWOptionDecorator(
159 "-d", "--dataset-type", callback=split_commas, help="Specific DatasetType(s) to validate.", multiple=True
160)
163datasets_option = MWOptionDecorator("--datasets")
166logLevelChoices = ["CRITICAL", "ERROR", "WARNING", "INFO", "VERBOSE", "DEBUG", "TRACE"]
167log_level_option = MWOptionDecorator(
168 "--log-level",
169 callback=partial(
170 split_kv,
171 choice=click.Choice(choices=logLevelChoices, case_sensitive=False),
172 normalize=True,
173 unseparated_okay=True,
174 add_to_default=True,
175 default_key=None, # No separator
176 ),
177 help=f"The logging level. Without an explicit logger name, will only affect the default root loggers "
178 f"({', '.join(CliLog.root_loggers())}). To modify the root logger use '.=LEVEL'. "
179 f"Supported levels are [{'|'.join(logLevelChoices)}]",
180 is_eager=True,
181 metavar="LEVEL|COMPONENT=LEVEL",
182 multiple=True,
183)
186long_log_option = MWOptionDecorator(
187 "--long-log", help="Make log messages appear in long format.", is_flag=True
188)
190log_file_option = MWOptionDecorator(
191 "--log-file",
192 default=None,
193 multiple=True,
194 callback=split_commas,
195 type=MWPath(file_okay=True, dir_okay=False, writable=True),
196 help="File(s) to write log messages. If the path ends with '.json' then"
197 " JSON log records will be written, else formatted text log records"
198 " will be written. This file can exist and records will be appended.",
199)
201log_label_option = MWOptionDecorator(
202 "--log-label",
203 default=None,
204 multiple=True,
205 callback=split_kv,
206 type=str,
207 help="Keyword=value pairs to add to MDC of log records.",
208)
210log_tty_option = MWOptionDecorator(
211 "--log-tty/--no-log-tty",
212 default=True,
213 help="Log to terminal (default). If false logging to terminal is disabled.",
214)
216options_file_option = MWOptionDecorator(
217 "--options-file",
218 "-@",
219 expose_value=False, # This option should not be forwarded
220 help=unwrap(
221 """URI to YAML file containing overrides
222 of command line options. The YAML should be organized
223 as a hierarchy with subcommand names at the top
224 level options for that subcommand below."""
225 ),
226 callback=yaml_presets,
227)
230processes_option = MWOptionDecorator(
231 "-j", "--processes", default=1, help="Number of processes to use.", type=click.IntRange(min=1)
232)
235regex_option = MWOptionDecorator("--regex")
238register_dataset_types_option = MWOptionDecorator(
239 "--register-dataset-types",
240 help="Register DatasetTypes that do not already exist in the Registry.",
241 is_flag=True,
242)
244run_option = MWOptionDecorator("--output-run", help="The name of the run datasets should be output to.")
246_transfer_params = dict(
247 default="auto", # set to `None` if using `required=True`
248 help="The external data transfer mode.",
249 type=click.Choice(
250 choices=["auto", "link", "symlink", "hardlink", "copy", "move", "relsymlink", "direct"],
251 case_sensitive=False,
252 ),
253)
255transfer_option_no_short = MWOptionDecorator(
256 "--transfer",
257 **_transfer_params,
258)
260transfer_option = MWOptionDecorator(
261 "-t",
262 "--transfer",
263 **_transfer_params,
264)
267transfer_dimensions_option = MWOptionDecorator(
268 "--transfer-dimensions/--no-transfer-dimensions",
269 is_flag=True,
270 default=True,
271 help=unwrap(
272 """If true, also copy dimension records along with datasets.
273 If the dmensions are already present in the destination butler it
274 can be more efficient to disable this. The default is to transfer
275 dimensions."""
276 ),
277)
280verbose_option = MWOptionDecorator("-v", "--verbose", help="Increase verbosity.", is_flag=True)
283where_option = MWOptionDecorator(
284 "--where", default="", help="A string expression similar to a SQL WHERE clause."
285)
288order_by_option = MWOptionDecorator(
289 "--order-by",
290 help=unwrap(
291 """One or more comma-separated names used to order records. Names can be dimension names,
292 metadata field names, or "timespan.begin" / "timespan.end" for temporal dimensions.
293 In some cases the dimension for a metadata field or timespan bound can be inferred, but usually
294 qualifying these with "<dimension>.<field>" is necessary.
295 To reverse ordering for a name, prefix it with a minus sign.
296 """
297 ),
298 multiple=True,
299 callback=split_commas,
300)
303limit_option = MWOptionDecorator(
304 "--limit",
305 help=unwrap("Limit the number of records, by default all records are shown."),
306 type=int,
307 default=0,
308)
310offset_option = MWOptionDecorator(
311 "--offset",
312 help=unwrap("Skip initial number of records, only used when --limit is specified."),
313 type=int,
314 default=0,
315)