@@ -345,21 +345,22 @@ def _format_usage(self, usage, actions, groups, prefix):
345345 def get_lines (parts , indent , prefix = None ):
346346 lines = []
347347 line = []
348+ indent_length = len (indent )
348349 if prefix is not None :
349350 line_len = len (prefix ) - 1
350351 else :
351- line_len = len ( indent ) - 1
352+ line_len = indent_length - 1
352353 for part in parts :
353354 if line_len + 1 + len (part ) > text_width and line :
354355 lines .append (indent + ' ' .join (line ))
355356 line = []
356- line_len = len ( indent ) - 1
357+ line_len = indent_length - 1
357358 line .append (part )
358359 line_len += len (part ) + 1
359360 if line :
360361 lines .append (indent + ' ' .join (line ))
361362 if prefix is not None :
362- lines [0 ] = lines [0 ][len ( indent ) :]
363+ lines [0 ] = lines [0 ][indent_length :]
363364 return lines
364365
365366 # if prog is short, follow it with optionals or positionals
@@ -403,10 +404,18 @@ def _format_actions_usage(self, actions, groups):
403404 except ValueError :
404405 continue
405406 else :
406- end = start + len (group ._group_actions )
407+ group_action_count = len (group ._group_actions )
408+ end = start + group_action_count
407409 if actions [start :end ] == group ._group_actions :
410+
411+ suppressed_actions_count = 0
408412 for action in group ._group_actions :
409413 group_actions .add (action )
414+ if action .help is SUPPRESS :
415+ suppressed_actions_count += 1
416+
417+ exposed_actions_count = group_action_count - suppressed_actions_count
418+
410419 if not group .required :
411420 if start in inserts :
412421 inserts [start ] += ' ['
@@ -416,7 +425,7 @@ def _format_actions_usage(self, actions, groups):
416425 inserts [end ] += ']'
417426 else :
418427 inserts [end ] = ']'
419- else :
428+ elif exposed_actions_count > 1 :
420429 if start in inserts :
421430 inserts [start ] += ' ('
422431 else :
@@ -490,7 +499,6 @@ def _format_actions_usage(self, actions, groups):
490499 text = _re .sub (r'(%s) ' % open , r'\1' , text )
491500 text = _re .sub (r' (%s)' % close , r'\1' , text )
492501 text = _re .sub (r'%s *%s' % (open , close ), r'' , text )
493- text = _re .sub (r'\(([^|]*)\)' , r'\1' , text )
494502 text = text .strip ()
495503
496504 # return the text
@@ -875,16 +883,19 @@ def __call__(self, parser, namespace, values, option_string=None):
875883 raise NotImplementedError (_ ('.__call__() not defined' ))
876884
877885
886+ # FIXME: remove together with `BooleanOptionalAction` deprecated arguments.
887+ _deprecated_default = object ()
888+
878889class BooleanOptionalAction (Action ):
879890 def __init__ (self ,
880891 option_strings ,
881892 dest ,
882893 default = None ,
883- type = None ,
884- choices = None ,
894+ type = _deprecated_default ,
895+ choices = _deprecated_default ,
885896 required = False ,
886897 help = None ,
887- metavar = None ):
898+ metavar = _deprecated_default ):
888899
889900 _option_strings = []
890901 for option_string in option_strings :
@@ -894,6 +905,24 @@ def __init__(self,
894905 option_string = '--no-' + option_string [2 :]
895906 _option_strings .append (option_string )
896907
908+ # We need `_deprecated` special value to ban explicit arguments that
909+ # match default value. Like:
910+ # parser.add_argument('-f', action=BooleanOptionalAction, type=int)
911+ for field_name in ('type' , 'choices' , 'metavar' ):
912+ if locals ()[field_name ] is not _deprecated_default :
913+ warnings ._deprecated (
914+ field_name ,
915+ "{name!r} is deprecated as of Python 3.12 and will be "
916+ "removed in Python {remove}." ,
917+ remove = (3 , 14 ))
918+
919+ if type is _deprecated_default :
920+ type = None
921+ if choices is _deprecated_default :
922+ choices = None
923+ if metavar is _deprecated_default :
924+ metavar = None
925+
897926 super ().__init__ (
898927 option_strings = _option_strings ,
899928 dest = dest ,
@@ -2165,7 +2194,9 @@ def _read_args_from_files(self, arg_strings):
21652194 # replace arguments referencing files with the file content
21662195 else :
21672196 try :
2168- with open (arg_string [1 :]) as args_file :
2197+ with open (arg_string [1 :],
2198+ encoding = _sys .getfilesystemencoding (),
2199+ errors = _sys .getfilesystemencodeerrors ()) as args_file :
21692200 arg_strings = []
21702201 for arg_line in args_file .read ().splitlines ():
21712202 for arg in self .convert_arg_line_to_args (arg_line ):
@@ -2479,9 +2510,11 @@ def _get_values(self, action, arg_strings):
24792510 not action .option_strings ):
24802511 if action .default is not None :
24812512 value = action .default
2513+ self ._check_value (action , value )
24822514 else :
2515+ # since arg_strings is always [] at this point
2516+ # there is no need to use self._check_value(action, value)
24832517 value = arg_strings
2484- self ._check_value (action , value )
24852518
24862519 # single argument or optional argument produces a single value
24872520 elif len (arg_strings ) == 1 and action .nargs in [None , OPTIONAL ]:
@@ -2523,7 +2556,6 @@ def _get_value(self, action, arg_string):
25232556
25242557 # ArgumentTypeErrors indicate errors
25252558 except ArgumentTypeError as err :
2526- name = getattr (action .type , '__name__' , repr (action .type ))
25272559 msg = str (err )
25282560 raise ArgumentError (action , msg )
25292561
@@ -2595,9 +2627,11 @@ def print_help(self, file=None):
25952627
25962628 def _print_message (self , message , file = None ):
25972629 if message :
2598- if file is None :
2599- file = _sys .stderr
2600- file .write (message )
2630+ file = file or _sys .stderr
2631+ try :
2632+ file .write (message )
2633+ except (AttributeError , OSError ):
2634+ pass
26012635
26022636 # ===============
26032637 # Exiting methods
0 commit comments