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.
IIUC, you can set the default value of the --format
option programatically:
import argparse
from typing import Any, Optional
import click
def set_long(ctx, *_):
for p in ctx.command.params:
if p.name == "format":
p.default = "long_format"
@click.command()
@click.option("--format", default="default_format")
@click.option(
"--long", is_flag=True, help="Alias to --format=long_format", callback=set_long
)
def cli(**kwargs):
args = argparse.Namespace(**kwargs)
print(args.format)
cli()
Invoking python3 script.py --long
will print:
long_format