Groups
Groups offer a way of organizing parameters and commands on the help-page. They also provide an additional abstraction layer that converters and validators can operate on.
Groups can be created in 2 ways:
Creating an instance of the
Group
object.Implicitly with string title name. This is short for
Group(my_group_name)
. If there exists aGroup
object with the same name within the command/parameter context, it will join that group.
Every command and parameter belongs to one (or more) groups.
Group(s) can be provided to the group
keyword argument of @app.command
and Parameter
.
The Group
class itself only marks objects with metadata and doesn't contain a set of it's members.
This means that groups can be re-used across commands.
Command Groups
An example of using groups with commands:
from cyclopts import App, Group, Parameter
app = App()
# Change the group of "--help" and "--version" to the implicit "Admin" group.
app["--help"].group = "Admin"
app["--version"].group = "Admin"
@app.command(group="Admin")
def info():
"""Print debugging system information."""
print("Displaying system info.")
@app.command
def download(path, url):
"""Download a file."""
print(f"Downloading {url} to {path}.")
@app.command
def upload(path, url):
"""Upload a file."""
print(f"Downloading {url} to {path}.")
app()
$ python my-script.py --help
Usage: my-script.py COMMAND
╭─ Admin ──────────────────────────────────────────────────────────────────────╮
│ info Print debugging system information. │
│ --help,-h Display this message and exit. │
│ --version Display application version. │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ───────────────────────────────────────────────────────────────────╮
│ download Download a file. │
│ upload Upload a file. │
╰──────────────────────────────────────────────────────────────────────────────╯
The default group is defined by the registering app's App.group_command
, which defaults to a group named "Commands"
.
Parameter Groups
An example of using groups with parameters:
from cyclopts import App, Group, Parameter, validators
from typing_extensions import Annotated
app = App()
vehicle_type_group = Group(
"Vehicle (choose one)",
default_parameter=Parameter(negative=""), # Disable "--no-" flags
validator=validators.LimitedChoice(), # Mutually Exclusive Options
)
@app.command
def create(
*,
# Using an explicitly created group object.
car: Annotated[bool, Parameter(group=vehicle_type_group)] = False,
truck: Annotated[bool, Parameter(group=vehicle_type_group)] = False,
# Implicitly creating an "Engine" group.
hp: Annotated[float, Parameter(group="Engine")] = 200,
cylinders: Annotated[int, Parameter(group="Engine")] = 6,
# You can explicitly create groups in-line.
wheel_diameter: Annotated[float, Parameter(group=Group("Wheels"))] = 18,
# Groups within the function signature can always be referenced with a string.
rims: Annotated[bool, Parameter(group="Wheels")] = False,
):
pass
app()
$ python my-script.py create --help
Usage: my-script.py create [OPTIONS]
╭─ Vehicle (choose one) ───────────────────────────────────────────────────────╮
│ --car [default: False] │
│ --truck [default: False] │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Engine ─────────────────────────────────────────────────────────────────────╮
│ --hp [default: 200] │
│ --cylinders [default: 6] │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Wheels ─────────────────────────────────────────────────────────────────────╮
│ --wheel-diameter [default: 18] │
│ --rims,--no-rims [default: False] │
╰──────────────────────────────────────────────────────────────────────────────╯
$ python my-script.py create --car --truck
╭─ Error ──────────────────────────────────────────────────────────────────────╮
│ Mutually exclusive arguments: {--car, --truck} │
╰──────────────────────────────────────────────────────────────────────────────╯
The default groups are defined by the registering app:
App.group_arguments
for positional-only arguments, which defaults to a group named"Arguments"
.App.group_parameters
for all other parameters, which defaults to a group named"Parameters"
.
Converters
Converters offer a way of having parameters within a group interact during processing.
Groups with an empty name, or with show=False
, are a way of using converters without impacting the help-page.
See Group.converter
for details.
Validators
Group validators offer a way of jointly validating group parameter members of CLI-provided values.
Groups with an empty name, or with show=False
, are a way of using validators without impacting the help-page.
mutually_exclusive = Group(validator=LimitedChoice(), default_parameter=Parameter(show_default=False, negative=""))
@app.command
def foo(
car: Annotated[bool, Parameter(group=(app.group_parameters, mutually_exclusive))],
truck: Annotated[bool, Parameter(group=(app.group_parameters, mutually_exclusive))],
):
pass
$ python demo.py foo --help
Usage: demo.py foo [ARGS] [OPTIONS]
╭─ Parameters ──────────────────────────────────────────────────────╮
│ CAR,--car │
│ TRUCK,--truck │
╰───────────────────────────────────────────────────────────────────╯
See Group.validator
for details.
Cyclopts has some builtin group-validators for common use-cases.
Help Page
Groups form titled panels on the help-page.
Groups with an empty name, or with show=False
, are not shown on the help-page.
This is useful for applying additional grouping logic (such as applying a LimitedChoice
validator) without impacting the help-page.
By default, the ordering of panels is alphabetical.
However, the sorting can be manipulated by Group.sort_key
. See it's documentation for usage.
The Group.create_ordered()
convenience classmethod creates a Group
with a sort_key
value drawn drawn from a global monotonically increasing counter.
This means that the order in the help-page will match the order that the groups were instantiated.
from cyclopts import App, Group
app = App()
g_plants = Group.create_ordered("Plants")
g_animals = Group.create_ordered("Animals")
g_mushrooms = Group.create_ordered("Mushrooms")
@app.command(group=g_animals)
def zebra():
pass
@app.command(group=g_plants)
def daisy():
pass
@app.command(group=g_mushrooms)
def portobello():
pass
app()
╭─ Plants ───────────────────────────────────────────────────────────╮
│ daisy │
╰────────────────────────────────────────────────────────────────────╯
╭─ Animals ──────────────────────────────────────────────────────────╮
│ zebra │
╰────────────────────────────────────────────────────────────────────╯
╭─ Mushrooms ────────────────────────────────────────────────────────╮
│ portobello │
╰────────────────────────────────────────────────────────────────────╯
╭─ Commands ─────────────────────────────────────────────────────────╮
│ --help,-h Display this message and exit. │
│ --version Display application version. │
╰────────────────────────────────────────────────────────────────────╯
A sort_key
can still be supplied; the global counter will only be used to break sorting ties.