lebrice / SimpleParsing

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

Show possible values of a list field, if the amount of values is finite #16

Closed Wooza closed 4 years ago

Wooza commented 4 years ago

Is your feature request related to a problem? Please describe. I'd like to be able to pass a list of flags to my program. The flags are all listed in an enum and the variable in the dataclass is defined as flags: List[MyEnum] = ... Argument parsing works, but no proper help text is generated.

Describe the solution you'd like If the argument of the List annotation has a finite amount of possible values (e.g. enum values), it would be nice to list those. This is already done, when a field is directly annotated as an enum.

Possible output:

  --my_config [MyEnum [MyEnum ...]]
                        Configure some values here
                        (default: [SomeValue, OneMore])
                        (possible: {SomeValue, AnotherValue, YetAnotherValue, OneMore})

Although I'm not sure, where/how the values should be listed

Describe alternatives you've considered I could manually update the help text when the enum values change, but that violates the DRY principle.

lebrice commented 4 years ago

Thanks a lot for opening this issue! Let me test things out and think about it for a few days and I'll get back to you.

lebrice commented 4 years ago

Hey @Wooza, out of curiosity, in your context is the number of flags used completely arbitrary? Could you give me a bit more detail about some scenarios where you think this could be useful?

For instance this is what is currently supported:

import enum
from dataclasses import dataclass
from typing import List, Tuple
from simple_parsing import ArgumentParser, list_field, field

class Color(enum.Enum):
    RED = "RED"
    ORANGE = "ORANGE"
    BLUE = "BLUE"
    MAGENTA = "MAGENTA"

@dataclass
class MyPreferences:
    """You can use Enums"""
    color: Color = Color.BLUE # my favorite colour
    # A list of colors I hate.
    hated_colors: List[Color] = list_field(Color.MAGENTA, Color.ORANGE)

parser = ArgumentParser()
parser.add_arguments(MyPreferences, "my_preferences")
args = parser.parse_args()
prefs: MyPreferences = args.my_preferences
print(prefs)

Executing this with the "--help flag":

python .\examples\enums\enums_example.py --help
usage: enums_example.py [-h] [--color {RED,ORANGE,BLUE,MAGENTA}] [--hated_colors [Color [Color ...]]]

optional arguments:
  -h, --help            show this help message and exit

MyPreferences ['my_preferences']:
  You can use Enums

  --color {RED,ORANGE,BLUE,MAGENTA}
                        my favorite colour (default: BLUE)
  --hated_colors [Color [Color ...]]
                        A list of colors I hate. (default: [<Color.MAGENTA: 'MAGENTA'>, <Color.ORANGE: 'ORANGE'>])

As you can see, the help string defaults for enums are quite ugly. I was able to achieve the kind of formatting I think you're describing with this:

@dataclass
class MyPreferences:
    """You can use Enums"""
    color: Color = Color.BLUE # my favorite colour
    # A list of colors I hate.
    hated_colors: List[Color] = list_field(
        Color.MAGENTA, Color.ORANGE, # the default values
        choices=[e.name for e in Color], # the available values
    )

which gives:

python .\examples\enums\enums_example.py --help
usage: enums_example.py [-h] [--color {RED,ORANGE,BLUE,MAGENTA}] [--hated_colors [{RED,ORANGE,BLUE,MAGENTA} [{RED,ORANGE,BLUE,MAGENTA} ...]]]

optional arguments:
  -h, --help            show this help message and exit

MyPreferences ['my_preferences']:
  You can use Enums

  --color {RED,ORANGE,BLUE,MAGENTA}
                        my favorite colour (default: BLUE)
  --hated_colors [{RED,ORANGE,BLUE,MAGENTA} [{RED,ORANGE,BLUE,MAGENTA} ...]]
                        A list of colors I hate. (default: [<Color.MAGENTA: 'MAGENTA'>, <Color.ORANGE: 'ORANGE'>])

However this doesn't currently work (apart from the "--help" option), as its impossible to parse the values:

python .\examples\enums\enums_example.py --hated_colors ORANGE RED
usage: enums_example.py [-h] [--color {RED,ORANGE,BLUE,MAGENTA}] [--hated_colors [{RED,ORANGE,BLUE,MAGENTA} [{RED,ORANGE,BLUE,MAGENTA} ...]]]
enums_example.py: error: argument --hated_colors: invalid choice: <Color.ORANGE: 'ORANGE'> (choose from 'RED', 'ORANGE', 'BLUE', 'MAGENTA')

Also, for the help string formatting, you could also easily do this, which would totally work! (you'd lose the auto-generated help strings and would have to add it manually to the help argument though):

@dataclass
class MyPreferences:
    """You can use Enums"""
    color: Color = Color.BLUE # my favorite colour
    # A list of colors I hate.
    hated_colors: List[Color] = list_field(
        Color.MAGENTA, Color.ORANGE, # the default values, if any.
        help=f"Some Colors I hate. (Possible: {set(e.name for e in Color)})",
    )

Which would get you what you want, in the end:

(...)
  --hated_colors [Color [Color ...]]
                        Some Colors I hate. (Possible: {'BLUE', 'RED', 'MAGENTA', 'ORANGE'}) (default: [<Color.MAGENTA: 'MAGENTA'>, <Color.ORANGE: 'ORANGE'>])

This sounds like the best option, in my opinion. Let me know what you think.

Wooza commented 4 years ago

Hey @lebrice, thanks for your fast feedback and effort. The amount of flags is around 20 to 30. I think your solution with manually specifying the help string is ok, since it can be automatically generated. I only have to work out how to deal with the line breaks, to avoid a single long line. I'll close this issue and might reopen it, if I stumble into any further problems 😄

lebrice commented 4 years ago

What are those 20-30 flags used for? Also, you can use the backslash \ to spread your command onto many lines.

Wooza commented 4 years ago

We have a program with different genetic algortihms and an enum which defines different aspects that we want to track while an algorithm is running, e.g. best current fitness or number of generations. When we run an evaluation, we don't always need all of those values, so we specify the values which should be tracked/output by using a field with List[Enum] .... Thanks for the \ tip.