239
239
Dict ,
240
240
Any ,
241
241
Generic ,
242
+ ClassVar ,
242
243
)
243
244
from dataclasses import (
244
245
Field ,
245
246
is_dataclass ,
246
247
fields ,
247
248
MISSING ,
249
+ InitVar ,
248
250
dataclass as real_dataclass ,
249
251
)
250
252
from importlib .metadata import version
251
253
254
+ # This is `typing._GenericAlias` but don't use non-public type names
255
+ _ClassVarType = type (ClassVar [object ])
256
+
252
257
if hasattr (argparse , "BooleanOptionalAction" ):
253
258
# BooleanOptionalAction was added in Python 3.9
254
259
BooleanOptionalAction = argparse .BooleanOptionalAction
@@ -333,14 +338,17 @@ def _add_dataclass_options(
333
338
if not is_dataclass (options_class ):
334
339
raise TypeError ("cls must be a dataclass" )
335
340
336
- for field in fields (options_class ):
341
+ for field in _fields (options_class ):
337
342
if not field .init :
338
343
continue # Ignore fields not in __init__
344
+ f_type = field .type
345
+ if _is_initvar (f_type ):
346
+ f_type = f_type .type
339
347
340
348
args = field .metadata .get ("args" , [f"--{ _get_arg_name (field )} " ])
341
349
positional = not args [0 ].startswith ("-" )
342
350
kwargs = {
343
- "type" : field .metadata .get ("type" , field . type ),
351
+ "type" : field .metadata .get ("type" , f_type ),
344
352
"help" : field .metadata .get ("help" , None ),
345
353
}
346
354
@@ -353,7 +361,7 @@ def _add_dataclass_options(
353
361
kwargs ["choices" ] = field .metadata ["choices" ]
354
362
355
363
# Support Literal types as an alternative means of specifying choices.
356
- if get_origin (field . type ) is Literal :
364
+ if get_origin (f_type ) is Literal :
357
365
# Prohibit a potential collision with the choices field
358
366
if field .metadata .get ("choices" ) is not None :
359
367
raise ValueError (
@@ -363,7 +371,7 @@ def _add_dataclass_options(
363
371
)
364
372
365
373
# Get the types of the arguments of the Literal
366
- types = [type (arg ) for arg in get_args (field . type )]
374
+ types = [type (arg ) for arg in get_args (f_type )]
367
375
368
376
# Make sure just a single type has been used
369
377
if len (set (types )) > 1 :
@@ -378,7 +386,7 @@ def _add_dataclass_options(
378
386
# Overwrite the type kwarg
379
387
kwargs ["type" ] = types [0 ]
380
388
# Use the literal arguments as choices
381
- kwargs ["choices" ] = get_args (field . type )
389
+ kwargs ["choices" ] = get_args (f_type )
382
390
383
391
if field .metadata .get ("metavar" ) is not None :
384
392
kwargs ["metavar" ] = field .metadata ["metavar" ]
@@ -392,7 +400,7 @@ def _add_dataclass_options(
392
400
# did not specify the type of the elements within the list, we
393
401
# try to infer it:
394
402
try :
395
- kwargs ["type" ] = get_args (field . type )[0 ] # get_args returns a tuple
403
+ kwargs ["type" ] = get_args (f_type )[0 ] # get_args returns a tuple
396
404
except IndexError :
397
405
# get_args returned an empty tuple, type cannot be inferred
398
406
raise ValueError (
@@ -406,12 +414,12 @@ def _add_dataclass_options(
406
414
else :
407
415
kwargs ["default" ] = MISSING
408
416
409
- if field . type is bool :
417
+ if f_type is bool :
410
418
_handle_bool_type (field , args , kwargs )
411
- elif get_origin (field . type ) is Union :
419
+ elif get_origin (f_type ) is Union :
412
420
if field .metadata .get ("type" ) is None :
413
421
# Optional[X] is equivalent to Union[X, None].
414
- f_args = get_args (field . type )
422
+ f_args = get_args (f_type )
415
423
if len (f_args ) == 2 and NoneType in f_args :
416
424
arg = next (a for a in f_args if a is not NoneType )
417
425
kwargs ["type" ] = arg
@@ -488,6 +496,24 @@ def _get_arg_name(field: Field):
488
496
return field .name .replace ("_" , "-" )
489
497
490
498
499
+ def _fields (dataclass ) -> Tuple [Field ]:
500
+ # dataclass.fields does not return fields that are of type InitVar
501
+ dc_fields = getattr (dataclass , "__dataclass_fields__" , None )
502
+ if dc_fields is None :
503
+ return fields (dataclass )
504
+ return tuple (f for f in dc_fields .values () if not _is_classvar (f .type ))
505
+
506
+
507
+ def _is_classvar (a_type ):
508
+ return a_type is ClassVar or (
509
+ type (a_type ) is _ClassVarType and a_type .__origin__ is ClassVar
510
+ )
511
+
512
+
513
+ def _is_initvar (a_type ):
514
+ return a_type is InitVar or type (a_type ) is InitVar
515
+
516
+
491
517
class ArgumentParser (argparse .ArgumentParser , Generic [OptionsType ]):
492
518
"""Command line argument parser that derives its options from a dataclass.
493
519
0 commit comments