Mutually Exclusive Groups¶
1. Problem Definition¶
In argparse you can specify mutually exclusive groups of arguments. This allows the programmer to specify that the user may not configure one item if another item is also being configured.
A common case where this is used is when a program takes a --debug flag to output additional information, but also takes a --quiet flag to suppress any non-error output. These two flags are mutually exclusive, since it makes no sense to both output additional information and suppress non-error output.
cfgclasses provides a way to specify this same relationship between configuration items.
1.1. Proposed solutions¶
- There were two considered solutions to this problem:
The use of the
Uniontype to allow one of two subgroups to be specified.A simpler specification via a decorator that a
dataclassis itself a mutually exclusive group.
These two solutions are discussed in the following subsections. However, if is the latter than has been selected for implementation.
1.1.1. Union type¶
The following example shows how the Union type could be used to specify a mutually exclusive group of configuration items.
from dataclasses import dataclass
@dataclass
class DebugConfig:
debug: bool
@dataclass
class QuietConfig:
quiet: bool
@dataclass
class MyConfig:
verbosity: Union[DebugConfig, QuietConfig]
This solution has the advantage that it is very flexible and even allows for groups of items to be mutually exclusive to one another (e.g. A and B can both be specified together but neither can be specified if C is).
Unfortunately there are a number of downfalls:
1. It is considered that the above is too verbose for the common case of a single mutually exclusive group.
2. argparse does not support the nesting of groups within mutually exclusive groups, so the feature of A and B being mutually exclusive to C cannot be supported.
1.1.2. dataclass as mutually exclusive group¶
This solution is much simpler and more in line with the common case of a single mutually exclusive group.
This is the solution that has been implemented.
The following example shows how the use of the mutually_exclusive decorator on the dataclass could be used to specify a mutually exclusive group of configuration items.
import cfgclasses
from dataclasses import dataclass
@cfgclasses.mutually_exclusive
@dataclass
class MyConfig:
debug: bool
quiet: bool
Here all the members of MyConfig are mutually exclusive to one another. The use of nesting is crucial to this solution to define which items are mutually exclusive to one another, and which are not.
A more complex example is shown below:
import cfgclasses
from dataclasses import dataclass
@cfgclasses.mutually_exclusive
@dataclass
class LoggingConfig:
debug: bool
quiet: bool
@dataclass
class MyConfig:
name: str
logging: LoggingConfig
In this case --debug and --quiet are mutually exclusive, but --name is not mutually exclusive to either of them.
2. Implementation¶
A new @mutually_exclusive decorator is added that accepts a class and sets a marker attribute on that class.
This marker attribute is then checked for when adding argument groups to argparse, using add_mutually_exclusive_group() instead of add_argument_group() if the attribute is set.
Programmers then simply need to use the decorator on their dataclass to specify that it is a mutually exclusive group.