Panels - Overview¶
rich-click lets you configure grouping and sorting of command options and subcommands. The containers which contain grouped options and subcommands are called panels:
By default, RichCommand
s have a single panel for options named "Options", and RichGroup
s have an additional panel for commands named "Commands".
rich-click allows you to control and customize everything about these panels:
- The default panels can be renamed and stylized.
- Options and commands can be split up across multiple panels.
- Positional arguments can be given a separate panel, or included in options panels.
- The styles of these panels can be modified.
Info
Panels are a replacement of rich-click "groups," which are deprecated as of version 1.9.0. We will support the old groups API for the foreseeable future, but its use is discouraged.
Although groups technically can be combined with panels, doing so can lead to unpredictable sorting behavior.
You can read the rich-click v1.8 docs to learn more about groups API.
Introduction to API¶
The high-level API for defining panels is with the @click.command_panel()
and @click.option_panel()
decorators.
Under the hood, these decorators create RichPanel
objects that are attached to the command.
Options¶
Options panels handle parameters for your command:
# /// script
# dependencies = ["rich-click>=1.9"]
# ///
import rich_click as click
@click.command()
@click.option("--src", help="Source")
@click.option("--dest", help="Destination")
@click.option("--env", help="Environment")
@click.option("--log-level", help="Log level")
@click.version_option("1.2.3")
@click.option_panel("Main",
options=["--src", "--dest"])
@click.option_panel("Extra",
options=["--env", "--log-level", "--help", "--version"])
@click.rich_config({"style_options_panel_border": "dim blue"})
def move_item(src, dest, env, log_level):
"""Move an item from a src location to a dest location"""
pass
if __name__ == "__main__":
move_item()
Alternatively, you can configure panels within the option itself. If the panel is not created in a decorator, then one is created on the fly.
The following code generates the same output as the example above:
# /// script
# dependencies = ["rich-click>=1.9"]
# ///
import rich_click as click
@click.command()
@click.option("--src", panel="Main", help="Source")
@click.option("--dest", panel="Main", help="Destination")
@click.option("--env", panel="Extra", help="Environment")
@click.option("--log-level", panel="Extra", help="Log level")
@click.help_option(panel="Extra")
@click.version_option("1.2.3", panel="Extra")
@click.rich_config({"style_options_panel_border": "dim blue"})
def move_item(src, dest, env, log_level):
"""Move an item from a src location to a dest location"""
pass
if __name__ == "__main__":
move_item()
Output
Note that this output is the same as the previous example, even though it was defined differently.
Arguments¶
Despite the name, options panels handle more than just options; they can also handle positional arguments.
Arguments can be given their own panel with the show_arguments
config option:
# /// script
# dependencies = ["rich-click>=1.9"]
# ///
import rich_click as click
@click.command()
@click.argument("src")
@click.argument("dest")
@click.option("--env", help="Environment")
@click.option("--log-level", help="Log level")
@click.rich_config({"show_arguments": True})
def move_item(src, dest, env, log_level):
"""Move an item from a src location to a dest location"""
pass
if __name__ == "__main__":
move_item()
Arguments can also be included in the options panel with the group_arguments_options
config option (the show_arguments
config option does not need to be set).
# /// script
# dependencies = ["rich-click>=1.9"]
# ///
import rich_click as click
@click.command()
@click.argument("src")
@click.argument("dest")
@click.option("--env", help="Environment")
@click.option("--log-level", help="Log level")
@click.rich_config({"group_arguments_options": True})
def move_item(src, dest, env, log_level):
"""Move an item from a src location to a dest location"""
pass
if __name__ == "__main__":
move_item()
In rich-click, unlike base Click, arguments can have help
text.
If help=
if set for arguments, then the argument panel is automatically shown:
# /// script
# dependencies = ["rich-click>=1.9"]
# ///
import rich_click as click
@click.command()
@click.argument("src", help="Source")
@click.argument("dest", help="Destination")
@click.option("--env", help="Environment")
@click.option("--log-level", help="Log level")
def move_item(src, dest, env, log_level):
"""Move an item from a src location to a dest location"""
pass
if __name__ == "__main__":
move_item()
Commands¶
Sub-commands also have panels that are defined similarly to option panels:
# /// script
# dependencies = ["rich-click>=1.9"]
# ///
import rich_click as click
@click.group()
@click.command_panel("Items",
commands=["move-item", "update-item"])
@click.command_panel("Users",
commands=["create-user", "update-user"],
help="User management commands")
def cli():
"""CLI"""
pass
@cli.command()
def move_item():
"""Move an item"""
pass
@cli.command()
def update_item():
"""Update an item"""
pass
@cli.command()
def create_user():
"""Create a user"""
pass
@cli.command()
def update_user():
"""Update a user"""
pass
if __name__ == "__main__":
cli()
Styles & Panel Help¶
RichPanel
objects inherit their base style behaviors from the rich config by default, but this can be set on a per-panel basis.
Panels can also have help text.
The below example shows both of these things:
# /// script
# dependencies = ["rich-click>=1.9"]
# ///
import rich_click as click
@click.command()
@click.option("--src")
@click.option("--dest")
@click.option("--env")
@click.option("--log-level")
@click.option_panel("Options",
help="All of the options available",
help_style="green",
panel_styles={"box": "DOUBLE"},
table_styles={
"row_styles": ["dim on rgb(16,16,32)", "on rgb(32,32,72)"],
"caption": "The arguments are optional"
})
@click.rich_config({"color_system": "truecolor"})
def move_item(src, dest, env, log_level):
"""Move an item from a src location to a dest location"""
pass
if __name__ == "__main__":
move_item()
The panel_styles
is passed into the outer rich.panel.Panel()
, and the table_styles
dict is pass as kwargs into the inner rich.table.Table()
.
See the available arguments for the rich library Table
and Panel
objects for more information:
Overriding defaults¶
Default panel titles can be overridden with the config. Renamed panels can still have their panel-level configurations modified.
# /// script
# dependencies = ["rich-click>=1.9"]
# ///
import rich_click as click
@click.group()
@click.option("--env", help="Environment")
@click.option("--log-level", help="Log level")
@click.option_panel("Some Additional Options",
panel_styles={"border_style": "dim blue"})
@click.command_panel("My Tool's Subcommands",
panel_styles={"border_style": "dim magenta"})
@click.rich_config({
"arguments_panel_title": "Very Important Required Args",
"options_panel_title": "Some Additional Options",
"commands_panel_title": "My Tool's Subcommands",
"show_arguments": True
})
def cli(env, log_level):
"""My CLI"""
@cli.command()
@click.argument("src")
@click.argument("dest")
def move_item(src, dest):
"""Move an item from a src location to a dest location"""
pass
if __name__ == "__main__":
cli()
Note that the rich config passes to subcommands, but panels are defined at the command level.
So running move-item --help
from the above example will rename the children's panels (because that's set in the parent's config), but it does not pass the panel_styles=
to the subcommand:
Default panel styles are also handled by the config, and will be overridden when conflicting options are defined at the panel level.
# /// script
# dependencies = ["rich-click>=1.9"]
# ///
import rich_click as click
# 'Main' panel: uses default title style "bright_red"
# 'Extra' panel: title style is overridden to use "blue"
@click.command()
@click.argument("src", help="Source", panel="Main")
@click.argument("dest", help="Destination", panel="Main")
@click.option("--env", help="Environment", panel="Extra")
@click.option("--log-level", help="Log level", panel="Extra")
@click.help_option(panel="Extra")
@click.option_panel("Main")
@click.option_panel("Extra", title_style="blue")
@click.rich_config({
"theme": "plain",
"style_options_panel_title_style": "bright_red" # <- default title style
})
def move_item(src, dest, env, log_level):
"""Move an item from a src location to a dest location"""
pass
if __name__ == "__main__":
move_item()