1

I have a program, for which a argument can take values from a defined list. Every value can only be passed once. At least one value must be chosen. If no values are chosen, the default is that all values were chosen. The following code exhibits the desired behavior.

import argparse

############################# Argument parsing
all_targets = ["bar", "baz", "buzz"]
p = argparse.ArgumentParser()
p.add_argument(
    "--targets", default=all_targets, choices=all_targets, nargs="+",
)
args = p.parse_args()
targets = args.targets
if len(targets) > len(set(targets)):
    raise ValueError("You may only foo every target once!")

############################## Actual program
for target in targets:
    print(f"You just foo'ed the {target}!")

However, now I am dealing with argument validation after parsing, which seems like a anti-pattern, since argparse typically do type validation etc in the parsing step.

Can I get the same behavior as above from the ArgumentParser instead of making post-checks on the arguments?

LudvigH
  • 3,662
  • 5
  • 31
  • 49
  • 1
    If you are using Python 3.8 or later, you might want to look at the `extend` action. Otherwise, you might want to use an `append` action (which would have you use `--target` multiple times, once per target, rather than a single `--targets` option). Finally, you might consider defining a custom action that handles removing duplicates during parsing. – chepner Feb 08 '21 at 20:43
  • Nice. I didn't really understand the Actions-part of the documentation, but your suggestion made me look closer. I'll provide that as an answer, but I leave the question un-answered for some time in hope of other answers. – LudvigH Feb 08 '21 at 21:23

1 Answers1

1

@chepner provided some alternatives. The one that felt most natural to me was the use of Actions. Incorporating the unicity-check can be done as below:

import argparse


class CheckUniqueStore(argparse.Action):
    """Checks that the list of arguments contains no duplicates, then stores"""

    def __call__(self, parser, namespace, values, option_string=None):
        if len(values) > len(set(values)):
            raise argparse.ArgumentError(
                self,
                "You cannot specify the same value multiple times. "
                + f"You provided {values}",
            )
        setattr(namespace, self.dest, values)


############################# Argument parsing
all_targets = ["bar", "baz", "buzz"]
p = argparse.ArgumentParser()
p.add_argument(
    "--targets",
    default=all_targets,
    choices=all_targets,
    nargs="+",
    action=CheckUniqueStore,
)
args = p.parse_args()

############################## Actual program
for target in args.targets:
    print(f"You just foo'ed a {target}!")

LudvigH
  • 3,662
  • 5
  • 31
  • 49