lebrice / SimpleParsing

Simple, Elegant, Typed Argument Parsing with argparse
MIT License
431 stars 52 forks source link

Help behavior inconsistent after calling parse_known_args #277

Open pfrwilson opened 1 year ago

pfrwilson commented 1 year ago

Describe the bug The behavior of parser.print_helpis different after calling parser.parse_known_args(), but it behaves correctly when calling the superclass (argparse.ArgumentParser) version of the function.

To Reproduce

# issue.py 

INTENDED_BEHAVIOR = False

from dataclasses import dataclass
from simple_parsing import ArgumentParser
parser = ArgumentParser(add_help=False)
parser.add_argument('-h', action='store_true')

@dataclass
class UseCase1Config: 
    name: str = None 
    a: int = 1 
    b: int = 2

@dataclass
class UseCase2Config: 
    c: int = 3
    b: int = 4

parser.add_argument('--use_case', choices=['A', 'B'], required=True)    
if INTENDED_BEHAVIOR:   
    # behaves correctly if calling the argparse.ArgumentParser version of the method 
    args, _ = super(type(parser), parser).parse_known_args() 
else: 
    args, _ = parser.parse_known_args()

# print(args)
if args.use_case == 'A':
    parser.add_arguments(UseCase1Config, dest='use_case_config') 
elif args.use_case == 'B':
    parser.add_arguments(UseCase2Config, dest='use_case_config')

args = parser.parse_args()
if args.h: 
    parser.print_help()
print(args)

Expected behavior The console should print a different message depending on which use case is specified:

$ python issue.py --use_case B -h
usage: test.py [-h] --use_case {A,B} [-c int] [-b int]

options:
  -h
  --use_case {A,B}

UseCase2Config ['use_case_config']:
  UseCase2Config(c: int = 3, b: int = 4)

  -c int, --c int   (default: 3)
  -b int, --b int   (default: 4)
Namespace(h=True, use_case='B', use_case_config=UseCase2Config(c=3, b=4))

$ python issue.py --use_case A -h
usage: test.py [-h] --use_case {A,B} [--name str] [-a int] [-b int]

options:
  -h
  --use_case {A,B}

UseCase1Config ['use_case_config']:
  UseCase1Config(name: str = None, a: int = 1, b: int = 2)

  --name str
  -a int, --a int   (default: 1)
  -b int, --b int   (default: 2)
Namespace(h=True, use_case='A', use_case_config=UseCase1Config(name=None, a=1, b=2))

Actual behavior The help message ignores the added dataclass, even though the correct dataclass is added to the config, with default values

$ python issue.py --use_case A -h
usage: test.py [-h] --use_case {A,B}

options:
  -h
  --use_case {A,B}
Namespace(h=True, use_case='A', use_case_config=UseCase1Config(name=None, a=1, b=2))

$ python issue.py --use_case B -h
usage: test.py [-h] --use_case {A,B}

options:
  -h
  --use_case {A,B}
Namespace(h=True, use_case='B', use_case_config=UseCase2Config(c=3, b=4))

Desktop (please complete the following information):

Additional context I am developing a system for dynamically added configuration groups with appropriate help message for the user e.g.

$ python test.py -h 
usage: test.py [-h] [--use_case {A,B}]

options:
  -h
  --use_case {A,B}
Namespace(h=True, use_case=None)
$ usage: test.py [-h] [--use_case {A,B}] [--name str] [-a int] [-b int]

options:
  -h
  --use_case {A,B}

UseCase1Config ['use_case_config']:
  UseCase1Config(name: str = None, a: int = 1, b: int = 2)

  --name str
  -a int, --a int   (default: 1)
  -b int, --b int   (default: 2)
Namespace(h=True, use_case='A', use_case_config=UseCase1Config(name=None, a=1, b=2))

I believe this will be useful in alot of use cases, in particular one I had in mind was specifying a machine learning model (e.g resnet, inception) then having the correct constructor args dynamically added to the configuration and being given a more detailed help message as you go.

lebrice commented 1 year ago

Hello there @pfrwilson, thanks for posting!

Good catch, there does seem to be some statefullness going on with the parse_known_args. I'll take a look.

By the way, are you familiar with the subgroups feature of simple-parsing? There is even an example that looks quite a bit like your use-case, here: https://github.com/lebrice/SimpleParsing/tree/master/examples/subgroups

As for the loading of config files for each subgroup, note that there is currently a bug with the dynamic selection of the subgroup dataclass (#276 ) but I'm working on it ;)

Thanks again for posting, I'll try to take a look at this soon.

pfrwilson commented 1 year ago

Hi @lebrice, thanks for your response! No I was not previously aware of the subgroups feature, but i'm excited to check it out as it does appear to address the same use-case already.

Love this project by the way!

Thanks again.