1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
|
.. _Groups:
======
Groups
======
Groups offer a way of organizing parameters and commands on the help-page; for example:
.. code-block:: console
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] │
╰──────────────────────────────────────────────────────────────────────────────╯
They also provide an additional abstraction layer that :ref:`validators <API Validators>` can operate on.
Groups can be created in two ways:
1. Explicitly creating a :class:`.Group` object.
2. Implicitly with a **string**.
This will implicitly create a group, ``Group(my_str_group_name)``, if it doesn't exist.
If there exists a :class:`.Group` object with the same name within the command/parameter context, it will join that group.
.. warning::
While convenient and terse, mistyping a group name as a string will unintentionally create a new group!
Every command and parameter belongs to at least one group.
Group(s) can be provided to the ``group`` keyword argument of :meth:`app.command <cyclopts.App.command>` and :class:`.Parameter`.
Like :class:`.Parameter`, the :class:`.Group` class itself only marks objects with metadata; the group does **not** contain direct references to it's members.
This means that groups can be re-used across commands.
--------------
Command Groups
--------------
An example of using groups to organize commands:
.. code-block:: python
from cyclopts import App
app = App()
# Change the group of "--help" and "--version" to the implicitly created "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()
.. code-block:: console
$ 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 :attr:`.App.group_commands`, which defaults to a group named ``"Commands"``.
.. _Parameter Groups:
----------------
Parameter Groups
----------------
Like commands above, parameter groups allow us to organize parameters on the help page.
They also allow us to add additional inter-parameter validators (e.g. mutually-exclusive parameters).
An example of using groups with parameters:
.. code-block:: python
from cyclopts import App, Group, Parameter, validators
from typing import Annotated
app = App()
vehicle_type_group = Group(
"Vehicle (choose one)",
default_parameter=Parameter(negative=""), # Disable "--no-" flags
validator=validators.MutuallyExclusive(), # Only one option is allowed to be selected.
)
@app.command
def create(
*, # force all subsequent variables to be keyword-only
# 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()
.. code-block:: console
$ python my-script.py create --help
Usage: my-script.py create [OPTIONS]
╭─ Engine ──────────────────────────────────────────────────────╮
│ --hp [default: 200] │
│ --cylinders [default: 6] │
╰───────────────────────────────────────────────────────────────╯
╭─ Vehicle (choose one) ────────────────────────────────────────╮
│ --car [default: False] │
│ --truck [default: False] │
╰───────────────────────────────────────────────────────────────╯
╭─ Wheels ──────────────────────────────────────────────────────╮
│ --wheel-diameter [default: 18] │
│ --rims --no-rims [default: False] │
╰───────────────────────────────────────────────────────────────╯
$ python my-script.py create --car --truck
╭─ Error ───────────────────────────────────────────────────────╮
│ Invalid values for group "Vehicle (choose one)". Mutually │
│ exclusive arguments: {--car, --truck} │
╰───────────────────────────────────────────────────────────────╯
In this example, we use the :class:`~.validators.MutuallyExclusive` validator to make it so the user can only specify ``--car`` or ``--truck``.
The default groups are defined by the registering app:
* :attr:`.App.group_arguments` for positional-only arguments, which defaults to a group named ``"Arguments"``.
* :attr:`.App.group_parameters` for all other parameters, which defaults to a group named ``"Parameters"``.
----------
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 group validators without impacting the help-page.
.. code-block:: python
from cyclopts import App, Group, Parameter, validators
from typing import Annotated
app = App()
mutually_exclusive = Group(
# This Group has no name, so it won't impact the help page.
validator=validators.MutuallyExclusive(),
# show_default=False - Showing "[default: False]" isn't too meaningful for mutually-exclusive options.
# negative="" - Don't create a "--no-" flag
default_parameter=Parameter(show_default=False, negative=""),
)
@app.command
def foo(
car: Annotated[bool, Parameter(group=(app.group_parameters, mutually_exclusive))] = False,
truck: Annotated[bool, Parameter(group=(app.group_parameters, mutually_exclusive))] = False,
):
print(f"{car=} {truck=}")
app()
.. code-block:: console
$ python demo.py foo --help
Usage: demo.py foo [ARGS] [OPTIONS]
╭─ Parameters ──────────────────────────────────────────────────────╮
│ CAR,--car │
│ TRUCK,--truck │
╰───────────────────────────────────────────────────────────────────╯
$ python demo.py foo --car
car=True truck=False
$ python demo.py foo --truck
car=False truck=True
$ python demo.py foo --car --truck
╭─ Error ───────────────────────────────────────────────────────────╮
│ Mutually exclusive arguments: {--car, --truck} │
╰───────────────────────────────────────────────────────────────────╯
See :attr:`.Group.validator` for details.
Cyclopts has some :ref:`builtin group-validators for common use-cases.<Group Validators>`
---------
Help Page
---------
Groups form titled panels on the help-page.
Groups with an empty name, or with :attr:`show=False <.Group.show>`, are **not** shown on the help-page.
This is useful for applying additional grouping logic (such as applying a :class:`.LimitedChoice` validator) without impacting the help-page.
By default, the ordering of panels is **alphabetical**.
However, the sorting can be manipulated by :attr:`.Group.sort_key`. See it's documentation for usage.
The :meth:`.Group.create_ordered` convenience classmethod creates a :class:`.Group` with a :attr:`~.Group.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.
.. code-block:: python
from cyclopts import App, Group
app = App()
plants = Group.create_ordered("Plants")
animals = Group.create_ordered("Animals")
fungi = Group.create_ordered("Fungi")
@app.command(group=animals)
def zebra():
pass
@app.command(group=plants)
def daisy():
pass
@app.command(group=fungi)
def portobello():
pass
app()
.. code-block:: console
$ my-script --help
Usage: scratch.py COMMAND
╭─ Plants ───────────────────────────────────────────────────────────╮
│ daisy │
╰────────────────────────────────────────────────────────────────────╯
╭─ Animals ──────────────────────────────────────────────────────────╮
│ zebra │
╰────────────────────────────────────────────────────────────────────╯
╭─ Fungi ────────────────────────────────────────────────────────────╮
│ portobello │
╰────────────────────────────────────────────────────────────────────╯
╭─ Commands ─────────────────────────────────────────────────────────╮
│ --help -h Display this message and exit. │
│ --version Display application version. │
╰────────────────────────────────────────────────────────────────────╯
Even when using :meth:`.Group.create_ordered`, a :attr:`~.Group.sort_key` can still be supplied; the global counter will only be used to break sorting ties.
|