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

121 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 06:13 +0000

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(ArgumentParser, self).__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(ArgumentParser, self).parse_known_args( 

167 args, namespace) 

168 

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

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

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

172 if undefok is not undefok_missing: 

173 namespace.undefok = undefok 

174 

175 if self._inherited_absl_flags is not None: 

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

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

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

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

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

181 if hasattr(namespace, 'undefok'): 

182 args = _strip_undefok_args(namespace.undefok, args) 

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

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

185 del namespace.undefok 

186 self._inherited_absl_flags.mark_as_parsed() 

187 try: 

188 self._inherited_absl_flags.validate_all_flags() 

189 except flags.IllegalFlagValueError as e: 

190 self.error(str(e)) 

191 

192 return namespace, args 

193 

194 def _define_absl_flags(self, absl_flags): 

195 """Defines flags from absl_flags.""" 

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

197 for name in absl_flags: 

198 if name in _BUILT_IN_FLAGS: 

199 # Do not inherit built-in flags. 

200 continue 

201 flag_instance = absl_flags[name] 

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

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

204 if name == flag_instance.name: 

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

206 # module's key flag. 

207 suppress = flag_instance not in key_flags 

208 self._define_absl_flag(flag_instance, suppress) 

209 

210 def _define_absl_flag(self, flag_instance, suppress): 

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

212 flag_name = flag_instance.name 

213 short_name = flag_instance.short_name 

214 argument_names = ['--' + flag_name] 

215 if short_name: 

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

217 if suppress: 

218 helptext = argparse.SUPPRESS 

219 else: 

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

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

222 if flag_instance.boolean: 

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

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

225 self.add_argument( 

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

227 metavar=flag_instance.name.upper(), 

228 flag_instance=flag_instance) 

229 else: 

230 self.add_argument( 

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

232 metavar=flag_instance.name.upper(), 

233 flag_instance=flag_instance) 

234 

235 

236class _FlagAction(argparse.Action): 

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

238 

239 def __init__( 

240 self, 

241 option_strings, 

242 dest, 

243 help, # pylint: disable=redefined-builtin 

244 metavar, 

245 flag_instance, 

246 default=argparse.SUPPRESS): 

247 """Initializes _FlagAction. 

248 

249 Args: 

250 option_strings: See argparse.Action. 

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

252 help: See argparse.Action. 

253 metavar: See argparse.Action. 

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

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

256 doesn't affect the parsing result. 

257 """ 

258 del dest 

259 self._flag_instance = flag_instance 

260 super(_FlagAction, self).__init__( 

261 option_strings=option_strings, 

262 dest=argparse.SUPPRESS, 

263 help=help, 

264 metavar=metavar) 

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(_BooleanFlagAction, self).__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 def __call__(self, parser, namespace, values, option_string=None): 

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

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

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

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

312 option = option_string[2:] 

313 else: 

314 option = option_string[1:] 

315 if option in self._flag_names: 

316 self._flag_instance.parse('true') 

317 else: 

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

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

320 self._flag_instance.parse('false') 

321 self._flag_instance.using_default_value = False 

322 

323 

324class _HelpFullAction(argparse.Action): 

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

326 

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

328 """Initializes _HelpFullAction. 

329 

330 Args: 

331 option_strings: See argparse.Action. 

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

333 default: Ignored. 

334 help: See argparse.Action. 

335 """ 

336 del dest, default 

337 super(_HelpFullAction, self).__init__( 

338 option_strings=option_strings, 

339 dest=argparse.SUPPRESS, 

340 default=argparse.SUPPRESS, 

341 nargs=0, 

342 help=help) 

343 

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

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

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

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

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

349 # printed here. 

350 parser.print_help() 

351 

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

353 if absl_flags is not None: 

354 modules = sorted(absl_flags.flags_by_module_dict()) 

355 main_module = sys.argv[0] 

356 if main_module in modules: 

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

358 modules.remove(main_module) 

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

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

361 parser.exit() 

362 

363 

364def _strip_undefok_args(undefok, args): 

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

366 if undefok: 

367 undefok_names = set(name.strip() for name in undefok.split(',')) 

368 undefok_names |= set('no' + name for name in undefok_names) 

369 # Remove undefok flags. 

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

371 return args 

372 

373 

374def _is_undefok(arg, undefok_names): 

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

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

377 return False 

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

379 arg_without_dash = arg[2:] 

380 else: 

381 arg_without_dash = arg[1:] 

382 if '=' in arg_without_dash: 

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

384 else: 

385 name = arg_without_dash 

386 if name in undefok_names: 

387 return True 

388 return False