There are many utilities that have one option to be an alias for others, like the following:
$ python3 ./1.py --help
Usage: 1.py [OPTIONS]
Options:
--format TEXT
--long TEXT Alias to --format=long_format
--help Show this message and exit.
How would I implement --long option in click so that click automatically sets --long=long_format when the option is used in an easy matter?
I ended up using a global variable to override locally the arguments:
import argparse
from typing import Any, Optional
import click
PARAMS = {}
def set_param(
opt: str, val: Any, ctx: click.Context, param: Optional[click.Parameter], value: Any
):
if value:
PARAMS[opt] = val
@click.command()
@click.option("--format", default="default_format")
@click.option(
"--long",
is_flag=True,
help="Alias to --format=long_format",
callback=lambda *args: set_param("format", "long_format", *args),
)
def cli(**kwargs):
args = argparse.Namespace(**{**kwargs, **PARAMS})
print(args.format)
cli()
But inside @click.option(callback=<here>) there is already ctx available. I tried setting ctx.params["format"] = "long_format", but it does not work. Is there a better way to set @click.option alias automatically set different option? I tried reading click source code and documentation, but did not find anything relevant.
In other words, how to implement the following @click.option callback:
import argparse
from typing import Any, Optional
import click
@click.command()
@click.option("--format", default="default_format")
@click.option(
"--long",
is_flag=True,
help="Alias to --format=long_format",
callback=lambda *args: "How to set --format=long_format from here?",
)
def cli(**kwargs):
args = argparse.Namespace(**{**kwargs, **PARAMS})
print(args.format)
cli()
Based on the amazing answer below, I created this:
def __alias_option_callback(
aliased: Dict[str, Any],
ctx: click.Context,
param: click.Parameter,
value: Any,
):
"""Callback called from alias_option option."""
if value:
for paramname, val in aliased.items():
param = next(p for p in ctx.command.params if p.name == paramname)
param.default = val
def alias_option(
*param_decls: str,
aliased: Dict[str, Any],
**attrs: Any,
):
"""Add this to click options to have an alias for other options"""
aliasedhelp = " ".join(
"--"
+ k.replace("_", "-")
+ ("" if v is True else f"={v}" if isinstance(v, int) else f"={v!r}")
for k, v in aliased.items()
)
return click.option(
*param_decls,
is_flag=True,
help=f"Alias to {aliasedhelp}",
callback=lambda *args: __alias_option_callback(aliased, *args),
**attrs,
)
@click.command()
# Auto generates help= and sets --format when used.
@alias_option("--long", aliased=dict(format="long_format"))
def cli():
pass
It's not perfect, as it does not handle False properly, but works for my limited use case.
The above code with further fixes got included in my clickdc pip package: https://github.com/Kamilcuk/clickdc/blob/main/src/clickdc.py#L460 although not documented.
@click.pass_context?@click.pass_context. Note that some context is passed to@click.option(callback=lambda ctx <here>), but I do no see how it can influence the context further.