Coverage for python/lsst/daf/butler/cli/opt/options.py: 82%
47 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-05 11:07 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-05 11:07 +0000
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=False,
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. Default is False.
119 Fully-specified component datasets (`str` or `DatasetType`
120 instances) are always included."""
121 ),
122)
125def _config_split(*args: Any) -> dict[str, str]:
126 # Config values might include commas so disable comma-splitting.
127 result = split_kv(*args, multiple=False)
128 assert isinstance(result, dict), "For mypy check that we get the expected result"
129 return result
132config_option = MWOptionDecorator(
133 "-c",
134 "--config",
135 callback=_config_split,
136 help="Config override, as a key-value pair.",
137 metavar="TEXT=TEXT",
138 multiple=True,
139)
142config_file_option = MWOptionDecorator(
143 "-C",
144 "--config-file",
145 help=unwrap(
146 """Path to a pex config override to be included after the
147 Instrument config overrides are applied."""
148 ),
149)
152confirm_option = MWOptionDecorator(
153 "--confirm/--no-confirm",
154 default=True,
155 help="Print expected action and a confirmation prompt before executing. Default is --confirm.",
156)
159dataset_type_option = MWOptionDecorator(
160 "-d", "--dataset-type", callback=split_commas, help="Specific DatasetType(s) to validate.", multiple=True
161)
164datasets_option = MWOptionDecorator("--datasets")
167logLevelChoices = ["CRITICAL", "ERROR", "WARNING", "INFO", "VERBOSE", "DEBUG", "TRACE"]
168log_level_option = MWOptionDecorator(
169 "--log-level",
170 callback=partial(
171 split_kv,
172 choice=click.Choice(choices=logLevelChoices, case_sensitive=False),
173 normalize=True,
174 unseparated_okay=True,
175 add_to_default=True,
176 default_key=None, # No separator
177 ),
178 help=f"The logging level. Without an explicit logger name, will only affect the default root loggers "
179 f"({', '.join(CliLog.root_loggers())}). To modify the root logger use '.=LEVEL'. "
180 f"Supported levels are [{'|'.join(logLevelChoices)}]",
181 is_eager=True,
182 metavar="LEVEL|COMPONENT=LEVEL",
183 multiple=True,
184)
187long_log_option = MWOptionDecorator(
188 "--long-log", help="Make log messages appear in long format.", is_flag=True
189)
191log_file_option = MWOptionDecorator(
192 "--log-file",
193 default=None,
194 multiple=True,
195 callback=split_commas,
196 type=MWPath(file_okay=True, dir_okay=False, writable=True),
197 help="File(s) to write log messages. If the path ends with '.json' then"
198 " JSON log records will be written, else formatted text log records"
199 " will be written. This file can exist and records will be appended.",
200)
202log_label_option = MWOptionDecorator(
203 "--log-label",
204 default=None,
205 multiple=True,
206 callback=split_kv,
207 type=str,
208 help="Keyword=value pairs to add to MDC of log records.",
209)
211log_tty_option = MWOptionDecorator(
212 "--log-tty/--no-log-tty",
213 default=True,
214 help="Log to terminal (default). If false logging to terminal is disabled.",
215)
217options_file_option = MWOptionDecorator(
218 "--options-file",
219 "-@",
220 expose_value=False, # This option should not be forwarded
221 help=unwrap(
222 """URI to YAML file containing overrides
223 of command line options. The YAML should be organized
224 as a hierarchy with subcommand names at the top
225 level options for that subcommand below."""
226 ),
227 callback=yaml_presets,
228)
231processes_option = MWOptionDecorator(
232 "-j", "--processes", default=1, help="Number of processes to use.", type=click.IntRange(min=1)
233)
236regex_option = MWOptionDecorator("--regex")
239register_dataset_types_option = MWOptionDecorator(
240 "--register-dataset-types",
241 help="Register DatasetTypes that do not already exist in the Registry.",
242 is_flag=True,
243)
245run_option = MWOptionDecorator("--output-run", help="The name of the run datasets should be output to.")
247_transfer_params = dict(
248 default="auto", # set to `None` if using `required=True`
249 help="The external data transfer mode.",
250 type=click.Choice(
251 choices=["auto", "link", "symlink", "hardlink", "copy", "move", "relsymlink", "direct"],
252 case_sensitive=False,
253 ),
254)
256transfer_option_no_short = MWOptionDecorator(
257 "--transfer",
258 **_transfer_params,
259)
261transfer_option = MWOptionDecorator(
262 "-t",
263 "--transfer",
264 **_transfer_params,
265)
268transfer_dimensions_option = MWOptionDecorator(
269 "--transfer-dimensions/--no-transfer-dimensions",
270 is_flag=True,
271 default=True,
272 help=unwrap(
273 """If true, also copy dimension records along with datasets.
274 If the dmensions are already present in the destination butler it
275 can be more efficient to disable this. The default is to transfer
276 dimensions."""
277 ),
278)
281verbose_option = MWOptionDecorator("-v", "--verbose", help="Increase verbosity.", is_flag=True)
284where_option = MWOptionDecorator(
285 "--where", default="", help="A string expression similar to a SQL WHERE clause."
286)
289order_by_option = MWOptionDecorator(
290 "--order-by",
291 help=unwrap(
292 """One or more comma-separated names used to order records. Names can be dimension names,
293 metadata field names, or "timespan.begin" / "timespan.end" for temporal dimensions.
294 In some cases the dimension for a metadata field or timespan bound can be inferred, but usually
295 qualifying these with "<dimension>.<field>" is necessary.
296 To reverse ordering for a name, prefix it with a minus sign.
297 """
298 ),
299 multiple=True,
300 callback=split_commas,
301)
304limit_option = MWOptionDecorator(
305 "--limit",
306 help=unwrap("Limit the number of records, by default all records are shown."),
307 type=int,
308 default=0,
309)
311offset_option = MWOptionDecorator(
312 "--offset",
313 help=unwrap("Skip initial number of records, only used when --limit is specified."),
314 type=int,
315 default=0,
316)