Coverage for python/lsst/daf/butler/cli/opt/options.py: 80%

45 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-28 10:10 +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 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/>. 

21from __future__ import annotations 

22 

23__all__ = ( 

24 "CollectionTypeCallback", 

25 "collection_type_option", 

26 "collections_option", 

27 "components_option", 

28 "config_option", 

29 "config_file_option", 

30 "confirm_option", 

31 "dataset_type_option", 

32 "datasets_option", 

33 "log_level_option", 

34 "long_log_option", 

35 "log_file_option", 

36 "log_label_option", 

37 "log_tty_option", 

38 "options_file_option", 

39 "processes_option", 

40 "regex_option", 

41 "register_dataset_types_option", 

42 "run_option", 

43 "transfer_dimensions_option", 

44 "transfer_option", 

45 "verbose_option", 

46 "where_option", 

47 "order_by_option", 

48 "limit_option", 

49 "offset_option", 

50) 

51 

52from functools import partial 

53from typing import Any 

54 

55import click 

56from lsst.daf.butler.registry import CollectionType 

57 

58from ..cliLog import CliLog 

59from ..utils import MWOptionDecorator, MWPath, split_commas, split_kv, unwrap, yaml_presets 

60 

61 

62class CollectionTypeCallback: 

63 """Helper class for handling different collection types.""" 

64 

65 collectionTypes = tuple(collectionType.name for collectionType in CollectionType.all()) 

66 

67 @staticmethod 

68 def makeCollectionTypes( 

69 context: click.Context, param: click.Option, value: tuple[str, ...] | str 

70 ) -> tuple[CollectionType, ...]: 

71 if not value: 

72 # Click seems to demand that the default be an empty tuple, rather 

73 # than a sentinal like None. The behavior that we want is that 

74 # not passing this option at all passes all collection types, while 

75 # passing it uses only the passed collection types. That works 

76 # fine for now, since there's no command-line option to subtract 

77 # collection types, and hence the only way to get an empty tuple 

78 # is as the default. 

79 return tuple(CollectionType.all()) 

80 

81 return tuple(CollectionType.from_name(item) for item in split_commas(context, param, value)) 

82 

83 

84collection_type_option = MWOptionDecorator( 

85 "--collection-type", 

86 callback=CollectionTypeCallback.makeCollectionTypes, 

87 multiple=True, 

88 help="If provided, only list collections of this type.", 

89 type=click.Choice(choices=CollectionTypeCallback.collectionTypes, case_sensitive=False), 

90) 

91 

92 

93collections_option = MWOptionDecorator( 

94 "--collections", 

95 help=unwrap( 

96 """One or more expressions that fully or partially identify 

97 the collections to search for datasets. If not provided all 

98 datasets are returned.""" 

99 ), 

100 multiple=True, 

101 callback=split_commas, 

102) 

103 

104 

105components_option = MWOptionDecorator( 

106 "--components/--no-components", 

107 default=None, 

108 help=unwrap( 

109 """For --components, apply all expression patterns to 

110 component dataset type names as well. For --no-components, 

111 never apply patterns to components. Default (where neither 

112 is specified) is to apply patterns to components only if 

113 their parent datasets were not matched by the expression. 

114 Fully-specified component datasets (`str` or `DatasetType` 

115 instances) are always included.""" 

116 ), 

117) 

118 

119 

120def _config_split(*args: Any) -> dict[str, str]: 

121 # Config values might include commas so disable comma-splitting. 

122 result = split_kv(*args, multiple=False) 

123 assert isinstance(result, dict), "For mypy check that we get the expected result" 

124 return result 

125 

126 

127config_option = MWOptionDecorator( 

128 "-c", 

129 "--config", 

130 callback=_config_split, 

131 help="Config override, as a key-value pair.", 

132 metavar="TEXT=TEXT", 

133 multiple=True, 

134) 

135 

136 

137config_file_option = MWOptionDecorator( 

138 "-C", 

139 "--config-file", 

140 help=unwrap( 

141 """Path to a pex config override to be included after the 

142 Instrument config overrides are applied.""" 

143 ), 

144) 

145 

146 

147confirm_option = MWOptionDecorator( 

148 "--confirm/--no-confirm", 

149 default=True, 

150 help="Print expected action and a confirmation prompt before executing. Default is --confirm.", 

151) 

152 

153 

154dataset_type_option = MWOptionDecorator( 

155 "-d", "--dataset-type", callback=split_commas, help="Specific DatasetType(s) to validate.", multiple=True 

156) 

157 

158 

159datasets_option = MWOptionDecorator("--datasets") 

160 

161 

162logLevelChoices = ["CRITICAL", "ERROR", "WARNING", "INFO", "VERBOSE", "DEBUG", "TRACE"] 

163log_level_option = MWOptionDecorator( 

164 "--log-level", 

165 callback=partial( 

166 split_kv, 

167 choice=click.Choice(choices=logLevelChoices, case_sensitive=False), 

168 normalize=True, 

169 unseparated_okay=True, 

170 add_to_default=True, 

171 default_key=None, # No separator 

172 ), 

173 help=f"The logging level. Without an explicit logger name, will only affect the default root loggers " 

174 f"({', '.join(CliLog.root_loggers())}). To modify the root logger use '.=LEVEL'. " 

175 f"Supported levels are [{'|'.join(logLevelChoices)}]", 

176 is_eager=True, 

177 metavar="LEVEL|COMPONENT=LEVEL", 

178 multiple=True, 

179) 

180 

181 

182long_log_option = MWOptionDecorator( 

183 "--long-log", help="Make log messages appear in long format.", is_flag=True 

184) 

185 

186log_file_option = MWOptionDecorator( 

187 "--log-file", 

188 default=None, 

189 multiple=True, 

190 callback=split_commas, 

191 type=MWPath(file_okay=True, dir_okay=False, writable=True), 

192 help="File(s) to write log messages. If the path ends with '.json' then" 

193 " JSON log records will be written, else formatted text log records" 

194 " will be written. This file can exist and records will be appended.", 

195) 

196 

197log_label_option = MWOptionDecorator( 

198 "--log-label", 

199 default=None, 

200 multiple=True, 

201 callback=split_kv, 

202 type=str, 

203 help="Keyword=value pairs to add to MDC of log records.", 

204) 

205 

206log_tty_option = MWOptionDecorator( 

207 "--log-tty/--no-log-tty", 

208 default=True, 

209 help="Log to terminal (default). If false logging to terminal is disabled.", 

210) 

211 

212options_file_option = MWOptionDecorator( 

213 "--options-file", 

214 "-@", 

215 expose_value=False, # This option should not be forwarded 

216 help=unwrap( 

217 """URI to YAML file containing overrides 

218 of command line options. The YAML should be organized 

219 as a hierarchy with subcommand names at the top 

220 level options for that subcommand below.""" 

221 ), 

222 callback=yaml_presets, 

223) 

224 

225 

226processes_option = MWOptionDecorator( 

227 "-j", "--processes", default=1, help="Number of processes to use.", type=click.IntRange(min=1) 

228) 

229 

230 

231regex_option = MWOptionDecorator("--regex") 

232 

233 

234register_dataset_types_option = MWOptionDecorator( 

235 "--register-dataset-types", 

236 help="Register DatasetTypes that do not already exist in the Registry.", 

237 is_flag=True, 

238) 

239 

240run_option = MWOptionDecorator("--output-run", help="The name of the run datasets should be output to.") 

241 

242 

243transfer_option = MWOptionDecorator( 

244 "-t", 

245 "--transfer", 

246 default="auto", # set to `None` if using `required=True` 

247 help="The external data transfer mode.", 

248 type=click.Choice( 

249 choices=["auto", "link", "symlink", "hardlink", "copy", "move", "relsymlink", "direct"], 

250 case_sensitive=False, 

251 ), 

252) 

253 

254 

255transfer_dimensions_option = MWOptionDecorator( 

256 "--transfer-dimensions/--no-transfer-dimensions", 

257 is_flag=True, 

258 default=True, 

259 help=unwrap( 

260 """If true, also copy dimension records along with datasets. 

261 If the dmensions are already present in the destination butler it 

262 can be more efficient to disable this. The default is to transfer 

263 dimensions.""" 

264 ), 

265) 

266 

267 

268verbose_option = MWOptionDecorator("-v", "--verbose", help="Increase verbosity.", is_flag=True) 

269 

270 

271where_option = MWOptionDecorator( 

272 "--where", default="", help="A string expression similar to a SQL WHERE clause." 

273) 

274 

275 

276order_by_option = MWOptionDecorator( 

277 "--order-by", 

278 help=unwrap( 

279 """One or more comma-separated names used to order records. Names can be dimension names, 

280 metadata names optionally prefixed by a dimension name and dot, or 

281 timestamp_begin/timestamp_end (with optional dimension name). To reverse ordering for a name 

282 prefix it with a minus sign.""" 

283 ), 

284 multiple=True, 

285 callback=split_commas, 

286) 

287 

288 

289limit_option = MWOptionDecorator( 

290 "--limit", 

291 help=unwrap("Limit the number of records, by default all records are shown."), 

292 type=int, 

293 default=0, 

294) 

295 

296offset_option = MWOptionDecorator( 

297 "--offset", 

298 help=unwrap("Skip initial number of records, only used when --limit is specified."), 

299 type=int, 

300 default=0, 

301)