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

47 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-05 10:00 +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 

28 

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) 

58 

59from functools import partial 

60from typing import Any 

61 

62import click 

63from lsst.daf.butler.registry import CollectionType 

64 

65from ..cliLog import CliLog 

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

67 

68 

69class CollectionTypeCallback: 

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

71 

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

73 

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()) 

87 

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

89 

90 

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) 

98 

99 

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) 

110 

111 

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) 

122 

123 

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 

129 

130 

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) 

139 

140 

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) 

149 

150 

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) 

156 

157 

158dataset_type_option = MWOptionDecorator( 

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

160) 

161 

162 

163datasets_option = MWOptionDecorator("--datasets") 

164 

165 

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) 

184 

185 

186long_log_option = MWOptionDecorator( 

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

188) 

189 

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) 

200 

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) 

209 

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) 

215 

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) 

228 

229 

230processes_option = MWOptionDecorator( 

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

232) 

233 

234 

235regex_option = MWOptionDecorator("--regex") 

236 

237 

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) 

243 

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

245 

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) 

254 

255transfer_option_no_short = MWOptionDecorator( 

256 "--transfer", 

257 **_transfer_params, 

258) 

259 

260transfer_option = MWOptionDecorator( 

261 "-t", 

262 "--transfer", 

263 **_transfer_params, 

264) 

265 

266 

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) 

278 

279 

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

281 

282 

283where_option = MWOptionDecorator( 

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

285) 

286 

287 

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) 

301 

302 

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) 

309 

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)