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

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

221 statements  

1# Copyright 2017 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"""Contains Flag class - information about single command-line flag. 

16 

17Do NOT import this module directly. Import the flags package and use the 

18aliases defined at the package level instead. 

19""" 

20 

21from collections import abc 

22import copy 

23import enum 

24import functools 

25from typing import Any, Dict, Generic, Iterable, List, Optional, Type, TypeVar, Union 

26from xml.dom import minidom 

27 

28from absl.flags import _argument_parser 

29from absl.flags import _exceptions 

30from absl.flags import _helpers 

31 

32_T = TypeVar('_T') 

33_ET = TypeVar('_ET', bound=enum.Enum) 

34 

35 

36@functools.total_ordering 

37class Flag(Generic[_T]): 

38 """Information about a command-line flag. 

39 

40 Attributes: 

41 name: the name for this flag 

42 default: the default value for this flag 

43 default_unparsed: the unparsed default value for this flag. 

44 default_as_str: default value as repr'd string, e.g., "'true'" 

45 (or None) 

46 value: the most recent parsed value of this flag set by :meth:`parse` 

47 help: a help string or None if no help is available 

48 short_name: the single letter alias for this flag (or None) 

49 boolean: if 'true', this flag does not accept arguments 

50 present: true if this flag was parsed from command line flags 

51 parser: an :class:`~absl.flags.ArgumentParser` object 

52 serializer: an ArgumentSerializer object 

53 allow_override: the flag may be redefined without raising an error, 

54 and newly defined flag overrides the old one. 

55 allow_override_cpp: use the flag from C++ if available the flag 

56 definition is replaced by the C++ flag after init 

57 allow_hide_cpp: use the Python flag despite having a C++ flag with 

58 the same name (ignore the C++ flag) 

59 using_default_value: the flag value has not been set by user 

60 allow_overwrite: the flag may be parsed more than once without 

61 raising an error, the last set value will be used 

62 allow_using_method_names: whether this flag can be defined even if 

63 it has a name that conflicts with a FlagValues method. 

64 validators: list of the flag validators. 

65 

66 The only public method of a ``Flag`` object is :meth:`parse`, but it is 

67 typically only called by a :class:`~absl.flags.FlagValues` object. The 

68 :meth:`parse` method is a thin wrapper around the 

69 :meth:`ArgumentParser.parse()<absl.flags.ArgumentParser.parse>` method. The 

70 parsed value is saved in ``.value``, and the ``.present`` attribute is 

71 updated. If this flag was already present, an Error is raised. 

72 

73 :meth:`parse` is also called during ``__init__`` to parse the default value 

74 and initialize the ``.value`` attribute. This enables other python modules to 

75 safely use flags even if the ``__main__`` module neglects to parse the 

76 command line arguments. The ``.present`` attribute is cleared after 

77 ``__init__`` parsing. If the default value is set to ``None``, then the 

78 ``__init__`` parsing step is skipped and the ``.value`` attribute is 

79 initialized to None. 

80 

81 Note: The default value is also presented to the user in the help 

82 string, so it is important that it be a legal value for this flag. 

83 """ 

84 

85 # NOTE: pytype doesn't find defaults without this. 

86 default: Optional[_T] 

87 default_as_str: Optional[str] 

88 default_unparsed: Union[Optional[_T], str] 

89 

90 parser: _argument_parser.ArgumentParser[_T] 

91 

92 def __init__( 

93 self, 

94 parser: _argument_parser.ArgumentParser[_T], 

95 serializer: Optional[_argument_parser.ArgumentSerializer[_T]], 

96 name: str, 

97 default: Union[Optional[_T], str], 

98 help_string: Optional[str], 

99 short_name: Optional[str] = None, 

100 boolean: bool = False, 

101 allow_override: bool = False, 

102 allow_override_cpp: bool = False, 

103 allow_hide_cpp: bool = False, 

104 allow_overwrite: bool = True, 

105 allow_using_method_names: bool = False, 

106 ) -> None: 

107 self.name = name 

108 

109 if not help_string: 

110 help_string = '(no help available)' 

111 

112 self.help = help_string 

113 self.short_name = short_name 

114 self.boolean = boolean 

115 self.present = 0 

116 self.parser = parser # type: ignore[annotation-type-mismatch] 

117 self.serializer = serializer 

118 self.allow_override = allow_override 

119 self.allow_override_cpp = allow_override_cpp 

120 self.allow_hide_cpp = allow_hide_cpp 

121 self.allow_overwrite = allow_overwrite 

122 self.allow_using_method_names = allow_using_method_names 

123 

124 self.using_default_value = True 

125 self._value: Optional[_T] = None 

126 self.validators: List[Any] = [] 

127 if self.allow_hide_cpp and self.allow_override_cpp: 

128 raise _exceptions.Error( 

129 "Can't have both allow_hide_cpp (means use Python flag) and " 

130 'allow_override_cpp (means use C++ flag after InitGoogle)') 

131 

132 self._set_default(default) 

133 

134 @property 

135 def value(self) -> Optional[_T]: 

136 return self._value 

137 

138 @value.setter 

139 def value(self, value: Optional[_T]): 

140 self._value = value 

141 

142 def __hash__(self): 

143 return hash(id(self)) 

144 

145 def __eq__(self, other): 

146 return self is other 

147 

148 def __lt__(self, other): 

149 if isinstance(other, Flag): 

150 return id(self) < id(other) 

151 return NotImplemented 

152 

153 def __bool__(self): 

154 raise TypeError('A Flag instance would always be True. ' 

155 'Did you mean to test the `.value` attribute?') 

156 

157 def __getstate__(self): 

158 raise TypeError("can't pickle Flag objects") 

159 

160 def __copy__(self): 

161 raise TypeError('%s does not support shallow copies. ' 

162 'Use copy.deepcopy instead.' % type(self).__name__) 

163 

164 def __deepcopy__(self, memo: Dict[int, Any]) -> 'Flag[_T]': 

165 result = object.__new__(type(self)) 

166 result.__dict__ = copy.deepcopy(self.__dict__, memo) 

167 return result 

168 

169 def _get_parsed_value_as_string(self, value: Optional[_T]) -> Optional[str]: 

170 """Returns parsed flag value as string.""" 

171 if value is None: 

172 return None 

173 if self.serializer: 

174 return repr(self.serializer.serialize(value)) 

175 if self.boolean: 

176 if value: 

177 return repr('true') 

178 else: 

179 return repr('false') 

180 return repr(str(value)) 

181 

182 def parse(self, argument: Union[str, _T]) -> None: 

183 """Parses string and sets flag value. 

184 

185 Args: 

186 argument: str or the correct flag value type, argument to be parsed. 

187 """ 

188 if self.present and not self.allow_overwrite: 

189 raise _exceptions.IllegalFlagValueError( 

190 'flag --%s=%s: already defined as %s' % ( 

191 self.name, argument, self.value)) 

192 self.value = self._parse(argument) 

193 self.present += 1 

194 

195 def _parse(self, argument: Union[str, _T]) -> Optional[_T]: 

196 """Internal parse function. 

197 

198 It returns the parsed value, and does not modify class states. 

199 

200 Args: 

201 argument: str or the correct flag value type, argument to be parsed. 

202 

203 Returns: 

204 The parsed value. 

205 """ 

206 try: 

207 return self.parser.parse(argument) # type: ignore[arg-type] 

208 except (TypeError, ValueError, OverflowError) as e: 

209 # Recast as IllegalFlagValueError. 

210 raise _exceptions.IllegalFlagValueError( 

211 'flag --%s=%s: %s' % (self.name, argument, e)) 

212 

213 def unparse(self) -> None: 

214 self.value = self.default 

215 self.using_default_value = True 

216 self.present = 0 

217 

218 def serialize(self) -> str: 

219 """Serializes the flag.""" 

220 return self._serialize(self.value) 

221 

222 def _serialize(self, value: Optional[_T]) -> str: 

223 """Internal serialize function.""" 

224 if value is None: 

225 return '' 

226 if self.boolean: 

227 if value: 

228 return '--%s' % self.name 

229 else: 

230 return '--no%s' % self.name 

231 else: 

232 if not self.serializer: 

233 raise _exceptions.Error( 

234 'Serializer not present for flag %s' % self.name) 

235 return '--%s=%s' % (self.name, self.serializer.serialize(value)) 

236 

237 def _set_default(self, value: Union[Optional[_T], str]) -> None: 

238 """Changes the default value (and current value too) for this Flag.""" 

239 self.default_unparsed = value 

240 if value is None: 

241 self.default = None 

242 else: 

243 self.default = self._parse_from_default(value) 

244 self.default_as_str = self._get_parsed_value_as_string(self.default) 

245 if self.using_default_value: 

246 self.value = self.default 

247 

248 # This is split out so that aliases can skip regular parsing of the default 

249 # value. 

250 def _parse_from_default(self, value: Union[str, _T]) -> Optional[_T]: 

251 return self._parse(value) 

252 

253 def flag_type(self) -> str: 

254 """Returns a str that describes the type of the flag. 

255 

256 NOTE: we use strings, and not the types.*Type constants because 

257 our flags can have more exotic types, e.g., 'comma separated list 

258 of strings', 'whitespace separated list of strings', etc. 

259 """ 

260 return self.parser.flag_type() 

261 

262 def _create_xml_dom_element( 

263 self, doc: minidom.Document, module_name: str, is_key: bool = False 

264 ) -> minidom.Element: 

265 """Returns an XML element that contains this flag's information. 

266 

267 This is information that is relevant to all flags (e.g., name, 

268 meaning, etc.). If you defined a flag that has some other pieces of 

269 info, then please override _ExtraXMLInfo. 

270 

271 Please do NOT override this method. 

272 

273 Args: 

274 doc: minidom.Document, the DOM document it should create nodes from. 

275 module_name: str,, the name of the module that defines this flag. 

276 is_key: boolean, True iff this flag is key for main module. 

277 

278 Returns: 

279 A minidom.Element instance. 

280 """ 

281 element = doc.createElement('flag') 

282 if is_key: 

283 element.appendChild(_helpers.create_xml_dom_element(doc, 'key', 'yes')) 

284 element.appendChild(_helpers.create_xml_dom_element( 

285 doc, 'file', module_name)) 

286 # Adds flag features that are relevant for all flags. 

287 element.appendChild(_helpers.create_xml_dom_element(doc, 'name', self.name)) 

288 if self.short_name: 

289 element.appendChild(_helpers.create_xml_dom_element( 

290 doc, 'short_name', self.short_name)) 

291 if self.help: 

292 element.appendChild(_helpers.create_xml_dom_element( 

293 doc, 'meaning', self.help)) 

294 # The default flag value can either be represented as a string like on the 

295 # command line, or as a Python object. We serialize this value in the 

296 # latter case in order to remain consistent. 

297 if self.serializer and not isinstance(self.default, str): 

298 if self.default is not None: 

299 default_serialized = self.serializer.serialize(self.default) 

300 else: 

301 default_serialized = '' 

302 else: 

303 default_serialized = self.default # type: ignore[assignment] 

304 element.appendChild(_helpers.create_xml_dom_element( 

305 doc, 'default', default_serialized)) 

306 value_serialized = self._serialize_value_for_xml(self.value) 

307 element.appendChild(_helpers.create_xml_dom_element( 

308 doc, 'current', value_serialized)) 

309 element.appendChild(_helpers.create_xml_dom_element( 

310 doc, 'type', self.flag_type())) 

311 # Adds extra flag features this flag may have. 

312 for e in self._extra_xml_dom_elements(doc): 

313 element.appendChild(e) 

314 return element 

315 

316 def _serialize_value_for_xml(self, value: Optional[_T]) -> Any: 

317 """Returns the serialized value, for use in an XML help text.""" 

318 return value 

319 

320 def _extra_xml_dom_elements( 

321 self, doc: minidom.Document 

322 ) -> List[minidom.Element]: 

323 """Returns extra info about this flag in XML. 

324 

325 "Extra" means "not already included by _create_xml_dom_element above." 

326 

327 Args: 

328 doc: minidom.Document, the DOM document it should create nodes from. 

329 

330 Returns: 

331 A list of minidom.Element. 

332 """ 

333 # Usually, the parser knows the extra details about the flag, so 

334 # we just forward the call to it. 

335 return self.parser._custom_xml_dom_elements(doc) # pylint: disable=protected-access 

336 

337 

338class BooleanFlag(Flag[bool]): 

339 """Basic boolean flag. 

340 

341 Boolean flags do not take any arguments, and their value is either 

342 ``True`` (1) or ``False`` (0). The false value is specified on the command 

343 line by prepending the word ``'no'`` to either the long or the short flag 

344 name. 

345 

346 For example, if a Boolean flag was created whose long name was 

347 ``'update'`` and whose short name was ``'x'``, then this flag could be 

348 explicitly unset through either ``--noupdate`` or ``--nox``. 

349 """ 

350 

351 def __init__( 

352 self, 

353 name: str, 

354 default: Union[Optional[bool], str], 

355 help: Optional[str], # pylint: disable=redefined-builtin 

356 short_name: Optional[str] = None, 

357 **args 

358 ) -> None: 

359 p = _argument_parser.BooleanParser() 

360 super().__init__(p, None, name, default, help, short_name, True, **args) 

361 

362 

363class EnumFlag(Flag[str]): 

364 """Basic enum flag; its value can be any string from list of enum_values.""" 

365 

366 parser: _argument_parser.EnumParser 

367 

368 def __init__( 

369 self, 

370 name: str, 

371 default: Optional[str], 

372 help: Optional[str], # pylint: disable=redefined-builtin 

373 enum_values: Iterable[str], 

374 short_name: Optional[str] = None, 

375 case_sensitive: bool = True, 

376 **args 

377 ): 

378 p = _argument_parser.EnumParser(enum_values, case_sensitive) 

379 g: _argument_parser.ArgumentSerializer[str] 

380 g = _argument_parser.ArgumentSerializer() 

381 super().__init__(p, g, name, default, help, short_name, **args) 

382 self.parser = p 

383 self.help = '<%s>: %s' % ('|'.join(p.enum_values), self.help) 

384 

385 def _extra_xml_dom_elements( 

386 self, doc: minidom.Document 

387 ) -> List[minidom.Element]: 

388 elements = [] 

389 for enum_value in self.parser.enum_values: 

390 elements.append(_helpers.create_xml_dom_element( 

391 doc, 'enum_value', enum_value)) 

392 return elements 

393 

394 

395class EnumClassFlag(Flag[_ET]): 

396 """Basic enum flag; its value is an enum class's member.""" 

397 

398 parser: _argument_parser.EnumClassParser 

399 

400 def __init__( 

401 self, 

402 name: str, 

403 default: Union[Optional[_ET], str], 

404 help: Optional[str], # pylint: disable=redefined-builtin 

405 enum_class: Type[_ET], 

406 short_name: Optional[str] = None, 

407 case_sensitive: bool = False, 

408 **args 

409 ): 

410 p = _argument_parser.EnumClassParser( 

411 enum_class, case_sensitive=case_sensitive 

412 ) 

413 g: _argument_parser.EnumClassSerializer[_ET] 

414 g = _argument_parser.EnumClassSerializer(lowercase=not case_sensitive) 

415 super().__init__(p, g, name, default, help, short_name, **args) 

416 self.parser = p 

417 self.help = '<%s>: %s' % ('|'.join(p.member_names), self.help) 

418 

419 def _extra_xml_dom_elements( 

420 self, doc: minidom.Document 

421 ) -> List[minidom.Element]: 

422 elements = [] 

423 for enum_value in self.parser.enum_class.__members__.keys(): 

424 elements.append(_helpers.create_xml_dom_element( 

425 doc, 'enum_value', enum_value)) 

426 return elements 

427 

428 

429class MultiFlag(Generic[_T], Flag[List[_T]]): 

430 """A flag that can appear multiple time on the command-line. 

431 

432 The value of such a flag is a list that contains the individual values 

433 from all the appearances of that flag on the command-line. 

434 

435 See the __doc__ for Flag for most behavior of this class. Only 

436 differences in behavior are described here: 

437 

438 * The default value may be either a single value or an iterable of values. 

439 A single value is transformed into a single-item list of that value. 

440 

441 * The value of the flag is always a list, even if the option was 

442 only supplied once, and even if the default value is a single 

443 value 

444 """ 

445 

446 def __init__(self, *args, **kwargs): 

447 super().__init__(*args, **kwargs) 

448 self.help += ';\n repeat this option to specify a list of values' 

449 

450 def parse(self, arguments: Union[str, _T, Iterable[_T]]): # pylint: disable=arguments-renamed 

451 """Parses one or more arguments with the installed parser. 

452 

453 Args: 

454 arguments: a single argument or a list of arguments (typically a 

455 list of default values); a single argument is converted 

456 internally into a list containing one item. 

457 """ 

458 new_values = self._parse(arguments) 

459 if self.present: 

460 assert self.value is not None 

461 self.value.extend(new_values) 

462 else: 

463 self.value = new_values 

464 self.present += len(new_values) 

465 

466 def _parse(self, arguments: Union[str, _T, Iterable[_T]]) -> List[_T]: # pylint: disable=arguments-renamed 

467 arguments_list: List[Union[str, _T]] 

468 

469 if isinstance(arguments, str): 

470 arguments_list = [arguments] 

471 

472 elif isinstance(arguments, abc.Iterable): 

473 arguments_list = list(arguments) 

474 

475 else: 

476 # Default value may be a list of values. Most other arguments 

477 # will not be, so convert them into a single-item list to make 

478 # processing simpler below. 

479 arguments_list = [arguments] 

480 

481 return [super(MultiFlag, self)._parse(item) for item in arguments_list] # type: ignore 

482 

483 def _serialize(self, value: Optional[List[_T]]) -> str: 

484 """See base class.""" 

485 if not self.serializer: 

486 raise _exceptions.Error( 

487 'Serializer not present for flag %s' % self.name) 

488 if value is None: 

489 return '' 

490 

491 serialized_items = [ 

492 super(MultiFlag, self)._serialize(value_item) # type: ignore[arg-type] 

493 for value_item in value 

494 ] 

495 

496 return '\n'.join(serialized_items) 

497 

498 def flag_type(self): 

499 """See base class.""" 

500 return 'multi ' + self.parser.flag_type() 

501 

502 def _extra_xml_dom_elements( 

503 self, doc: minidom.Document 

504 ) -> List[minidom.Element]: 

505 elements = [] 

506 if hasattr(self.parser, 'enum_values'): 

507 for enum_value in self.parser.enum_values: # pytype: disable=attribute-error 

508 elements.append(_helpers.create_xml_dom_element( 

509 doc, 'enum_value', enum_value)) 

510 return elements 

511 

512 

513class MultiEnumClassFlag(MultiFlag[_ET]): # pytype: disable=not-indexable 

514 """A multi_enum_class flag. 

515 

516 See the __doc__ for MultiFlag for most behaviors of this class. In addition, 

517 this class knows how to handle enum.Enum instances as values for this flag 

518 type. 

519 """ 

520 

521 parser: _argument_parser.EnumClassParser[_ET] # type: ignore[assignment] 

522 

523 def __init__( 

524 self, 

525 name: str, 

526 default: Union[None, Iterable[_ET], _ET, Iterable[str], str], 

527 help_string: str, 

528 enum_class: Type[_ET], 

529 case_sensitive: bool = False, 

530 **args 

531 ): 

532 p = _argument_parser.EnumClassParser( 

533 enum_class, case_sensitive=case_sensitive) 

534 g: _argument_parser.EnumClassListSerializer 

535 g = _argument_parser.EnumClassListSerializer( 

536 list_sep=',', lowercase=not case_sensitive) 

537 super().__init__(p, g, name, default, help_string, **args) 

538 # NOTE: parser should be typed EnumClassParser[_ET] but the constructor 

539 # restricts the available interface to ArgumentParser[str]. 

540 self.parser = p 

541 # NOTE: serializer should be non-Optional but this isn't inferred. 

542 self.serializer = g 

543 self.help = ( 

544 '<%s>: %s;\n repeat this option to specify a list of values' % 

545 ('|'.join(p.member_names), help_string or '(no help available)')) 

546 

547 def _extra_xml_dom_elements( 

548 self, doc: minidom.Document 

549 ) -> List[minidom.Element]: 

550 elements = [] 

551 for enum_value in self.parser.enum_class.__members__.keys(): # pytype: disable=attribute-error 

552 elements.append(_helpers.create_xml_dom_element( 

553 doc, 'enum_value', enum_value)) 

554 return elements 

555 

556 def _serialize_value_for_xml(self, value): 

557 """See base class.""" 

558 if value is not None: 

559 if not self.serializer: 

560 raise _exceptions.Error( 

561 'Serializer not present for flag %s' % self.name 

562 ) 

563 value_serialized = self.serializer.serialize(value) 

564 else: 

565 value_serialized = '' 

566 return value_serialized