33
44import argparse
55from collections .abc import Callable
6- from collections .abc import Mapping
76from collections .abc import Sequence
87import os
98import sys
@@ -280,103 +279,37 @@ def get_ini_default_for_type(
280279 return ""
281280
282281
283- class ArgumentError (Exception ):
284- """Raised if an Argument instance is created with invalid or
285- inconsistent arguments."""
286-
287- def __init__ (self , msg : str , option : Argument | str ) -> None :
288- self .msg = msg
289- self .option_id = str (option )
290-
291- def __str__ (self ) -> str :
292- if self .option_id :
293- return f"option { self .option_id } : { self .msg } "
294- else :
295- return self .msg
296-
297-
298282class Argument :
299- """Class that mimics the necessary behaviour of optparse.Option.
283+ """An option defined in an OptionGroup."""
300284
301- It's currently a least effort implementation and ignoring choices
302- and integer prefixes.
285+ def __init__ ( self , action : argparse . Action ) -> None :
286+ self . _action = action
303287
304- https://docs.python.org/3/library/optparse.html#optparse-standard-option-types
305- """
288+ def attrs ( self ) -> dict [ str , Any ]:
289+ return self . _action . __dict__
306290
307- def __init__ (self , * names : str , ** attrs : Any ) -> None :
308- """Store params in private vars for use in add_argument."""
309- self ._attrs = attrs
310- self ._short_opts : list [str ] = []
311- self ._long_opts : list [str ] = []
312- try :
313- self .type = attrs ["type" ]
314- except KeyError :
315- pass
316- try :
317- # Attribute existence is tested in Config._processopt.
318- self .default = attrs ["default" ]
319- except KeyError :
320- pass
321- self ._set_opt_strings (names )
322- dest : str | None = attrs .get ("dest" )
323- if dest :
324- self .dest = dest
325- elif self ._long_opts :
326- self .dest = self ._long_opts [0 ][2 :].replace ("-" , "_" )
327- else :
328- try :
329- self .dest = self ._short_opts [0 ][1 :]
330- except IndexError as e :
331- self .dest = "???" # Needed for the error repr.
332- raise ArgumentError ("need a long or short option" , self ) from e
291+ def names (self ) -> Sequence [str ]:
292+ return self ._action .option_strings
333293
334- def names (self ) -> list [str ]:
335- return self ._short_opts + self ._long_opts
336-
337- def attrs (self ) -> Mapping [str , Any ]:
338- return self ._attrs
294+ @property
295+ def dest (self ) -> str :
296+ return self ._action .dest
339297
340- def _set_opt_strings (self , opts : Sequence [str ]) -> None :
341- """Directly from optparse.
298+ @property
299+ def default (self ) -> Any :
300+ return self ._action .default
342301
343- Might not be necessary as this is passed to argparse later on.
344- """
345- for opt in opts :
346- if len (opt ) < 2 :
347- raise ArgumentError (
348- f"invalid option string { opt !r} : "
349- "must be at least two characters long" ,
350- self ,
351- )
352- elif len (opt ) == 2 :
353- if not (opt [0 ] == "-" and opt [1 ] != "-" ):
354- raise ArgumentError (
355- f"invalid short option string { opt !r} : "
356- "must be of the form -x, (x any non-dash char)" ,
357- self ,
358- )
359- self ._short_opts .append (opt )
360- else :
361- if not (opt [0 :2 ] == "--" and opt [2 ] != "-" ):
362- raise ArgumentError (
363- f"invalid long option string { opt !r} : "
364- "must start with --, followed by non-dash" ,
365- self ,
366- )
367- self ._long_opts .append (opt )
302+ @property
303+ def type (self ) -> Any | None :
304+ return self ._action .type
368305
369306 def __repr__ (self ) -> str :
370307 args : list [str ] = []
371- if self ._short_opts :
372- args += ["_short_opts: " + repr (self ._short_opts )]
373- if self ._long_opts :
374- args += ["_long_opts: " + repr (self ._long_opts )]
308+ args += ["opts: " + repr (self .names ())]
375309 args += ["dest: " + repr (self .dest )]
376- if hasattr ( self , " type" ) :
310+ if self . _action . type :
377311 args += ["type: " + repr (self .type )]
378- if hasattr (self , "default" ):
379- args += ["default: " + repr (self .default )]
312+ args += ["default: " + repr (self .default )]
380313 return "Argument({})" .format (", " .join (args ))
381314
382315
@@ -406,6 +339,7 @@ def addoption(self, *opts: str, **attrs: Any) -> None:
406339
407340 :param opts:
408341 Option names, can be short or long options.
342+ Note that lower-case short options (e.g. `-x`) are reserved.
409343 :param attrs:
410344 Same attributes as the argparse library's :meth:`add_argument()
411345 <argparse.ArgumentParser.add_argument>` function accepts.
@@ -415,25 +349,27 @@ def addoption(self, *opts: str, **attrs: Any) -> None:
415349 )
416350 if conflict :
417351 raise ValueError (f"option names { conflict } already added" )
418- option = Argument (* opts , ** attrs )
419- self ._addoption_instance (option , shortupper = False )
352+ self ._addoption_inner (opts , attrs , allow_reserved = False )
420353
421354 def _addoption (self , * opts : str , ** attrs : Any ) -> None :
422- option = Argument (* opts , ** attrs )
423- self ._addoption_instance (option , shortupper = True )
355+ """Like addoption(), but also allows registering short lower case options (e.g. -x),
356+ which are reserved for pytest core."""
357+ self ._addoption_inner (opts , attrs , allow_reserved = True )
424358
425- def _addoption_instance (self , option : Argument , shortupper : bool = False ) -> None :
426- if not shortupper :
427- for opt in option ._short_opts :
428- if opt [0 ] == "-" and opt [1 ].islower ():
429- raise ValueError ("lowercase shortoptions reserved" )
359+ def _addoption_inner (
360+ self , opts : tuple [str , ...], attrs : dict [str , Any ], allow_reserved : bool
361+ ) -> None :
362+ if not allow_reserved :
363+ for opt in opts :
364+ if len (opt ) >= 2 and opt [0 ] == "-" and opt [1 ].islower ():
365+ raise ValueError ("lowercase short options are reserved" )
430366
367+ action = self ._arggroup .add_argument (* opts , ** attrs )
368+ option = Argument (action )
369+ self .options .append (option )
431370 if self .parser :
432371 self .parser .processoption (option )
433372
434- self ._arggroup .add_argument (* option .names (), ** option .attrs ())
435- self .options .append (option )
436-
437373
438374class PytestArgumentParser (argparse .ArgumentParser ):
439375 def __init__ (
@@ -495,10 +431,9 @@ def _format_action_invocation(self, action: argparse.Action) -> str:
495431 for option in options :
496432 if len (option ) == 2 or option [2 ] == " " :
497433 continue
498- if not option .startswith ("--" ):
499- raise ArgumentError (
500- f'long optional argument without "--": [{ option } ]' , option
501- )
434+ assert option .startswith ("--" ), (
435+ f'long optional argument without "--": [{ option } ]'
436+ )
502437 xxoption = option [2 :]
503438 shortened = xxoption .replace ("-" , "" )
504439 if shortened not in short_long or len (short_long [shortened ]) < len (
0 commit comments