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.
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