How to set python click parameter to alias different parameter from option callback?

100 Views Asked by At

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.

1

There are 1 best solutions below

0
On BEST ANSWER

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