Group Validators

Group validators operate on a set of parameters, ensuring that their values are mutually compatible. Validator(s) for a group can be set via the Group.validator attribute. An individual validator is a callable object/function with signature:

def validator(argument_collection: ArgumentCollection):
    "Raise an exception if something is invalid."

Cyclopts has some builtin common group validators in the cyclopts.validators module.

LimitedChoice

Limits the number of specified arguments within the group. Most commonly used for mutually-exclusive arguments (default behavior).

from cyclopts import App, Group, Parameter, validators
from typing import Annotated

app = App()

vehicle = Group(
    "Vehicle (choose one)",
    default_parameter=Parameter(negative=""),  # Disable "--no-" flags
    validator=validators.LimitedChoice(),  # Mutually Exclusive Options
)

@app.default
def main(
    *,
    car: Annotated[bool, Parameter(group=vehicle)] = False,
    truck: Annotated[bool, Parameter(group=vehicle)] = False,
):
    if car:
        print("I'm driving a car.")
    if truck:
        print("I'm driving a truck.")

app()
$ python drive.py --help
Usage: main COMMAND [OPTIONS]

╭─ Commands ─────────────────────────────────────────────────────────╮
│ --help -h  Display this message and exit.                          │
│ --version  Display application version.                            │
╰────────────────────────────────────────────────────────────────────╯
╭─ Vehicle (choose one) ─────────────────────────────────────────────╮
│ --car    [default: False]                                          │
│ --truck  [default: False]                                          │
╰────────────────────────────────────────────────────────────────────╯

$ python drive.py --car
I'm driving a car.

$ python drive.py --car --truck
╭─ Error ────────────────────────────────────────────────────────────╮
│ Invalid values for group "Vehicle (choose one)". Mutually          │
│ exclusive arguments: {--car, --truck}                              │
╰────────────────────────────────────────────────────────────────────╯

See the LimitedChoice docs for more info.

MutuallyExclusive

Alias for LimitedChoice with default arguments. Exists primarily because the usage/implication will be more directly obvious and searchable to developers than LimitedChoice. Since this class takes no arguments, an already instantiated version mutually_exclusive is also provided for convenience.

all_or_none

Group validator that enforces that either all parameters in the group must be supplied an argument, or none of them.

from typing import Annotated

from cyclopts import App, Group, Parameter
from cyclopts.validators import all_or_none

app = App()

group_1 = Group(validator=all_or_none)
group_2 = Group(validator=all_or_none)


@app.default
def default(
    foo: Annotated[bool, Parameter(group=group_1)] = False,
    bar: Annotated[bool, Parameter(group=group_1)] = False,
    fizz: Annotated[bool, Parameter(group=group_2)] = False,
    buzz: Annotated[bool, Parameter(group=group_2)] = False,
):
    print(f"{foo=} {bar=}")
    print(f"{fizz=} {buzz=}")


if __name__ == "__main__":
    app()
$ python all_or_none.py
foo=False bar=False
fizz=False buzz=False

$ python all_or_none.py --foo
╭─ Error ──────────────────────────────────────────────────────────╮
│ Missing argument: --bar                                          │
╰──────────────────────────────────────────────────────────────────╯

$ python all_or_none.py --foo --bar
foo=True bar=True
fizz=False buzz=False

$ python all_or_none.py --foo --bar --fizz
╭─ Error ────────────────────────────────────────────────────────────╮
│ Missing argument: --buzz                                           │
╰────────────────────────────────────────────────────────────────────╯

$ python all_or_none.py --foo --bar --fizz --buzz
foo=True bar=True
fizz=True buzz=True

See the all_or_none docs for more info.