Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/absl_py-2.1.0-py3.8.egg/absl/flags/argparse_flags.py: 17%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

121 statements  

1# Copyright 2018 The Abseil Authors. 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14 

15"""This module provides argparse integration with absl.flags. 

16 

17``argparse_flags.ArgumentParser`` is a drop-in replacement for 

18:class:`argparse.ArgumentParser`. It takes care of collecting and defining absl 

19flags in :mod:`argparse`. 

20 

21Here is a simple example:: 

22 

23 # Assume the following absl.flags is defined in another module: 

24 # 

25 # from absl import flags 

26 # flags.DEFINE_string('echo', None, 'The echo message.') 

27 # 

28 parser = argparse_flags.ArgumentParser( 

29 description='A demo of absl.flags and argparse integration.') 

30 parser.add_argument('--header', help='Header message to print.') 

31 

32 # The parser will also accept the absl flag `--echo`. 

33 # The `header` value is available as `args.header` just like a regular 

34 # argparse flag. The absl flag `--echo` continues to be available via 

35 # `absl.flags.FLAGS` if you want to access it. 

36 args = parser.parse_args() 

37 

38 # Example usages: 

39 # ./program --echo='A message.' --header='A header' 

40 # ./program --header 'A header' --echo 'A message.' 

41 

42 

43Here is another example demonstrates subparsers:: 

44 

45 parser = argparse_flags.ArgumentParser(description='A subcommands demo.') 

46 parser.add_argument('--header', help='The header message to print.') 

47 

48 subparsers = parser.add_subparsers(help='The command to execute.') 

49 

50 roll_dice_parser = subparsers.add_parser( 

51 'roll_dice', help='Roll a dice.', 

52 # By default, absl flags can also be specified after the sub-command. 

53 # To only allow them before sub-command, pass 

54 # `inherited_absl_flags=None`. 

55 inherited_absl_flags=None) 

56 roll_dice_parser.add_argument('--num_faces', type=int, default=6) 

57 roll_dice_parser.set_defaults(command=roll_dice) 

58 

59 shuffle_parser = subparsers.add_parser('shuffle', help='Shuffle inputs.') 

60 shuffle_parser.add_argument( 

61 'inputs', metavar='I', nargs='+', help='Inputs to shuffle.') 

62 shuffle_parser.set_defaults(command=shuffle) 

63 

64 args = parser.parse_args(argv[1:]) 

65 args.command(args) 

66 

67 # Example usages: 

68 # ./program --echo='A message.' roll_dice --num_faces=6 

69 # ./program shuffle --echo='A message.' 1 2 3 4 

70 

71 

72There are several differences between :mod:`absl.flags` and 

73:mod:`~absl.flags.argparse_flags`: 

74 

751. Flags defined with absl.flags are parsed differently when using the 

76 argparse parser. Notably: 

77 

78 1) absl.flags allows both single-dash and double-dash for any flag, and 

79 doesn't distinguish them; argparse_flags only allows double-dash for 

80 flag's regular name, and single-dash for flag's ``short_name``. 

81 2) Boolean flags in absl.flags can be specified with ``--bool``, 

82 ``--nobool``, as well as ``--bool=true/false`` (though not recommended); 

83 in argparse_flags, it only allows ``--bool``, ``--nobool``. 

84 

852. Help related flag differences: 

86 

87 1) absl.flags does not define help flags, absl.app does that; argparse_flags 

88 defines help flags unless passed with ``add_help=False``. 

89 2) absl.app supports ``--helpxml``; argparse_flags does not. 

90 3) argparse_flags supports ``-h``; absl.app does not. 

91""" 

92 

93import argparse 

94import sys 

95 

96from absl import flags 

97 

98 

99_BUILT_IN_FLAGS = frozenset({ 

100 'help', 

101 'helpshort', 

102 'helpfull', 

103 'helpxml', 

104 'flagfile', 

105 'undefok', 

106}) 

107 

108 

109class ArgumentParser(argparse.ArgumentParser): 

110 """Custom ArgumentParser class to support special absl flags.""" 

111 

112 def __init__(self, **kwargs): 

113 """Initializes ArgumentParser. 

114 

115 Args: 

116 **kwargs: same as argparse.ArgumentParser, except: 

117 1. It also accepts `inherited_absl_flags`: the absl flags to inherit. 

118 The default is the global absl.flags.FLAGS instance. Pass None to 

119 ignore absl flags. 

120 2. The `prefix_chars` argument must be the default value '-'. 

121 

122 Raises: 

123 ValueError: Raised when prefix_chars is not '-'. 

124 """ 

125 prefix_chars = kwargs.get('prefix_chars', '-') 

126 if prefix_chars != '-': 

127 raise ValueError( 

128 'argparse_flags.ArgumentParser only supports "-" as the prefix ' 

129 'character, found "{}".'.format(prefix_chars)) 

130 

131 # Remove inherited_absl_flags before calling super. 

132 self._inherited_absl_flags = kwargs.pop('inherited_absl_flags', flags.FLAGS) 

133 # Now call super to initialize argparse.ArgumentParser before calling 

134 # add_argument in _define_absl_flags. 

135 super().__init__(**kwargs) 

136 

137 if self.add_help: 

138 # -h and --help are defined in super. 

139 # Also add the --helpshort and --helpfull flags. 

140 self.add_argument( 

141 # Action 'help' defines a similar flag to -h/--help. 

142 '--helpshort', action='help', 

143 default=argparse.SUPPRESS, help=argparse.SUPPRESS) 

144 self.add_argument( 

145 '--helpfull', action=_HelpFullAction, 

146 default=argparse.SUPPRESS, help='show full help message and exit') 

147 

148 if self._inherited_absl_flags is not None: 

149 self.add_argument( 

150 '--undefok', default=argparse.SUPPRESS, help=argparse.SUPPRESS) 

151 self._define_absl_flags(self._inherited_absl_flags) 

152 

153 def parse_known_args(self, args=None, namespace=None): 

154 if args is None: 

155 args = sys.argv[1:] 

156 if self._inherited_absl_flags is not None: 

157 # Handle --flagfile. 

158 # Explicitly specify force_gnu=True, since argparse behaves like 

159 # gnu_getopt: flags can be specified after positional arguments. 

160 args = self._inherited_absl_flags.read_flags_from_files( 

161 args, force_gnu=True) 

162 

163 undefok_missing = object() 

164 undefok = getattr(namespace, 'undefok', undefok_missing) 

165 

166 namespace, args = super().parse_known_args(args, namespace) 

167 

168 # For Python <= 2.7.8: https://bugs.python.org/issue9351, a bug where 

169 # sub-parsers don't preserve existing namespace attributes. 

170 # Restore the undefok attribute if a sub-parser dropped it. 

171 if undefok is not undefok_missing: 

172 namespace.undefok = undefok 

173 

174 if self._inherited_absl_flags is not None: 

175 # Handle --undefok. At this point, `args` only contains unknown flags, 

176 # so it won't strip defined flags that are also specified with --undefok. 

177 # For Python <= 2.7.8: https://bugs.python.org/issue9351, a bug where 

178 # sub-parsers don't preserve existing namespace attributes. The undefok 

179 # attribute might not exist because a subparser dropped it. 

180 if hasattr(namespace, 'undefok'): 

181 args = _strip_undefok_args(namespace.undefok, args) 

182 # absl flags are not exposed in the Namespace object. See Namespace: 

183 # https://docs.python.org/3/library/argparse.html#argparse.Namespace. 

184 del namespace.undefok 

185 self._inherited_absl_flags.mark_as_parsed() 

186 try: 

187 self._inherited_absl_flags.validate_all_flags() 

188 except flags.IllegalFlagValueError as e: 

189 self.error(str(e)) 

190 

191 return namespace, args 

192 

193 def _define_absl_flags(self, absl_flags): 

194 """Defines flags from absl_flags.""" 

195 key_flags = set(absl_flags.get_key_flags_for_module(sys.argv[0])) 

196 for name in absl_flags: 

197 if name in _BUILT_IN_FLAGS: 

198 # Do not inherit built-in flags. 

199 continue 

200 flag_instance = absl_flags[name] 

201 # Each flags with short_name appears in FLAGS twice, so only define 

202 # when the dictionary key is equal to the regular name. 

203 if name == flag_instance.name: 

204 # Suppress the flag in the help short message if it's not a main 

205 # module's key flag. 

206 suppress = flag_instance not in key_flags 

207 self._define_absl_flag(flag_instance, suppress) 

208 

209 def _define_absl_flag(self, flag_instance, suppress): 

210 """Defines a flag from the flag_instance.""" 

211 flag_name = flag_instance.name 

212 short_name = flag_instance.short_name 

213 argument_names = ['--' + flag_name] 

214 if short_name: 

215 argument_names.insert(0, '-' + short_name) 

216 if suppress: 

217 helptext = argparse.SUPPRESS 

218 else: 

219 # argparse help string uses %-formatting. Escape the literal %'s. 

220 helptext = flag_instance.help.replace('%', '%%') 

221 if flag_instance.boolean: 

222 # Only add the `no` form to the long name. 

223 argument_names.append('--no' + flag_name) 

224 self.add_argument( 

225 *argument_names, action=_BooleanFlagAction, help=helptext, 

226 metavar=flag_instance.name.upper(), 

227 flag_instance=flag_instance) 

228 else: 

229 self.add_argument( 

230 *argument_names, action=_FlagAction, help=helptext, 

231 metavar=flag_instance.name.upper(), 

232 flag_instance=flag_instance) 

233 

234 

235class _FlagAction(argparse.Action): 

236 """Action class for Abseil non-boolean flags.""" 

237 

238 def __init__( 

239 self, 

240 option_strings, 

241 dest, 

242 help, # pylint: disable=redefined-builtin 

243 metavar, 

244 flag_instance, 

245 default=argparse.SUPPRESS): 

246 """Initializes _FlagAction. 

247 

248 Args: 

249 option_strings: See argparse.Action. 

250 dest: Ignored. The flag is always defined with dest=argparse.SUPPRESS. 

251 help: See argparse.Action. 

252 metavar: See argparse.Action. 

253 flag_instance: absl.flags.Flag, the absl flag instance. 

254 default: Ignored. The flag always uses dest=argparse.SUPPRESS so it 

255 doesn't affect the parsing result. 

256 """ 

257 del dest 

258 self._flag_instance = flag_instance 

259 super().__init__( 

260 option_strings=option_strings, 

261 dest=argparse.SUPPRESS, 

262 help=help, 

263 metavar=metavar, 

264 ) 

265 

266 def __call__(self, parser, namespace, values, option_string=None): 

267 """See https://docs.python.org/3/library/argparse.html#action-classes.""" 

268 self._flag_instance.parse(values) 

269 self._flag_instance.using_default_value = False 

270 

271 

272class _BooleanFlagAction(argparse.Action): 

273 """Action class for Abseil boolean flags.""" 

274 

275 def __init__( 

276 self, 

277 option_strings, 

278 dest, 

279 help, # pylint: disable=redefined-builtin 

280 metavar, 

281 flag_instance, 

282 default=argparse.SUPPRESS): 

283 """Initializes _BooleanFlagAction. 

284 

285 Args: 

286 option_strings: See argparse.Action. 

287 dest: Ignored. The flag is always defined with dest=argparse.SUPPRESS. 

288 help: See argparse.Action. 

289 metavar: See argparse.Action. 

290 flag_instance: absl.flags.Flag, the absl flag instance. 

291 default: Ignored. The flag always uses dest=argparse.SUPPRESS so it 

292 doesn't affect the parsing result. 

293 """ 

294 del dest, default 

295 self._flag_instance = flag_instance 

296 flag_names = [self._flag_instance.name] 

297 if self._flag_instance.short_name: 

298 flag_names.append(self._flag_instance.short_name) 

299 self._flag_names = frozenset(flag_names) 

300 super().__init__( 

301 option_strings=option_strings, 

302 dest=argparse.SUPPRESS, 

303 nargs=0, # Does not accept values, only `--bool` or `--nobool`. 

304 help=help, 

305 metavar=metavar, 

306 ) 

307 

308 def __call__(self, parser, namespace, values, option_string=None): 

309 """See https://docs.python.org/3/library/argparse.html#action-classes.""" 

310 if not isinstance(values, list) or values: 

311 raise ValueError('values must be an empty list.') 

312 if option_string.startswith('--'): 

313 option = option_string[2:] 

314 else: 

315 option = option_string[1:] 

316 if option in self._flag_names: 

317 self._flag_instance.parse('true') 

318 else: 

319 if not option.startswith('no') or option[2:] not in self._flag_names: 

320 raise ValueError('invalid option_string: ' + option_string) 

321 self._flag_instance.parse('false') 

322 self._flag_instance.using_default_value = False 

323 

324 

325class _HelpFullAction(argparse.Action): 

326 """Action class for --helpfull flag.""" 

327 

328 def __init__(self, option_strings, dest, default, help): # pylint: disable=redefined-builtin 

329 """Initializes _HelpFullAction. 

330 

331 Args: 

332 option_strings: See argparse.Action. 

333 dest: Ignored. The flag is always defined with dest=argparse.SUPPRESS. 

334 default: Ignored. 

335 help: See argparse.Action. 

336 """ 

337 del dest, default 

338 super().__init__( 

339 option_strings=option_strings, 

340 dest=argparse.SUPPRESS, 

341 default=argparse.SUPPRESS, 

342 nargs=0, 

343 help=help, 

344 ) 

345 

346 def __call__(self, parser, namespace, values, option_string=None): 

347 """See https://docs.python.org/3/library/argparse.html#action-classes.""" 

348 # This only prints flags when help is not argparse.SUPPRESS. 

349 # It includes user defined argparse flags, as well as main module's 

350 # key absl flags. Other absl flags use argparse.SUPPRESS, so they aren't 

351 # printed here. 

352 parser.print_help() 

353 

354 absl_flags = parser._inherited_absl_flags # pylint: disable=protected-access 

355 if absl_flags is not None: 

356 modules = sorted(absl_flags.flags_by_module_dict()) 

357 main_module = sys.argv[0] 

358 if main_module in modules: 

359 # The main module flags are already printed in parser.print_help(). 

360 modules.remove(main_module) 

361 print(absl_flags._get_help_for_modules( # pylint: disable=protected-access 

362 modules, prefix='', include_special_flags=True)) 

363 parser.exit() 

364 

365 

366def _strip_undefok_args(undefok, args): 

367 """Returns a new list of args after removing flags in --undefok.""" 

368 if undefok: 

369 undefok_names = {name.strip() for name in undefok.split(',')} 

370 undefok_names |= {'no' + name for name in undefok_names} 

371 # Remove undefok flags. 

372 args = [arg for arg in args if not _is_undefok(arg, undefok_names)] 

373 return args 

374 

375 

376def _is_undefok(arg, undefok_names): 

377 """Returns whether we can ignore arg based on a set of undefok flag names.""" 

378 if not arg.startswith('-'): 

379 return False 

380 if arg.startswith('--'): 

381 arg_without_dash = arg[2:] 

382 else: 

383 arg_without_dash = arg[1:] 

384 if '=' in arg_without_dash: 

385 name, _ = arg_without_dash.split('=', 1) 

386 else: 

387 name = arg_without_dash 

388 if name in undefok_names: 

389 return True 

390 return False