This is possible since Click 4.0. You need to tell click to ignore the unknown option, and that you want the options gathered up and passed in. Here is your example with the relevant changes made.

Code:

@click.group()
@click.option('--arg')
def cli(arg):
    click.echo('cli arg: %s' % arg)
    pass

@cli.command(context_settings=dict(
    ignore_unknown_options=True,
    help_option_names=[],
))
@click.argument('script')
@click.argument('args', nargs=-1, type=click.UNPROCESSED)
def run(script, args):
    click.echo('script: %s' % script)
    click.echo(args)

Test:

cli('--arg 3 run foo.py --arg 5 arg1 --arg2 --arg 4 --help'.split())

Produces:

cli arg: 3
script: foo.py
('--arg', '5', 'arg1', '--arg2', '"--arg', '4"', '--help')

(Source)

Answer from Stephen Rauch on Stack Overflow
๐ŸŒ
ZetCode
zetcode.com โ€บ python โ€บ click
Python click - creating command line interfaces
January 29, 2024 - The commands are basic building blocks of an application. Click defines commands through decorators. They are created with the click.command decorator. Values are passed to the commands via options or arguments. Options are added with the click.option decorator, arguments with the click.argument.
Discussions

command line interface - Optional CLI arguments with python Click library option - Stack Overflow
I'm having a conundrum with the Python Click library when parsing some CLI options. I would like an option to act as a flag by itself, but optionally accept string values. E.g.: $ myscript โ‡’ optio... More on stackoverflow.com
๐ŸŒ stackoverflow.com
python - Optional argument in command with click - Stack Overflow
I am trying to accomplish something not very standard for CLI parsing with Click and it only works partially: main CLI has multiple sub-commands (in sample below 'show' and 'check') both those comm... More on stackoverflow.com
๐ŸŒ stackoverflow.com
python - Click command default arguments and/or options - Stack Overflow
The simplest solution for single arguments or options seems to be to just create a variable ARG = click.option(...) and then use @ARG as a decorator of the command function. Applying multiple opts/args at once involves creating a decorator factory or a callable class. This is the cleanest solution I found, credits go to @altendky and @SnoopJ from #python ... More on stackoverflow.com
๐ŸŒ stackoverflow.com
python - How to pass several list of arguments to @click.option - Stack Overflow
This response does answer your question about "how to pass several lists of arguments to @click.option," just that the lists need to be given without brackets or commas on the command line. A Python list could be formatted this way as follows, using shlex.join to add quotation marks when necessary: More on stackoverflow.com
๐ŸŒ stackoverflow.com
๐ŸŒ
Click Documentation
click.palletsprojects.com โ€บ en โ€บ stable โ€บ options
Options โ€” Click Documentation (8.5.x)
To get the argument name, the chosen positional argument is converted to lower case, a leading - or -- is removed if found, and any remaining - characters are replaced with _. A simple click.Option takes one option name. By default, itโ€™s assumed that the decorated function argument is not required and the expected type is str.
๐ŸŒ
Click Documentation
click.palletsprojects.com โ€บ en โ€บ stable โ€บ parameters
Parameters โ€” Click Documentation (8.5.x)
In the example, the argumentโ€™s name is filename. The name must match the python arg name. To provide a different name for use in help text, see Placeholder / Meta Variable. The optionโ€™s names are -t and --times. More names are available for options and are covered in Options. @click.command() @click.argument('filename') @click.option('-t', '--times', type=int) def multi_echo(filename, times): """Print value filename multiple times.""" for x in range(times): click.echo(filename)
Top answer
1 of 2
5

One way that I have managed to achieve this behaviour was by actually using arguments as below. I'll post this as a workaround, while I try to see if it could be done with an option, and I'll update my post accordingly

@click.command(context_settings={"ignore_unknown_options": True})
@click.argument("options", nargs=-1)
def myscript(options):
    option = False
    if options is ():
        option = False
    if '-o' in options or '--option' in options:
        option = True
    if len(options) > 1:
        option = options[1]
    print(option)

Later Edit Using an option, I have managed to achieve this by adding an argument to the command definition.

@click.command()
@click.option('-o', '--option', is_flag=True, default=False, is_eager=True)
@click.argument('value', nargs=-1)
def myscript(option, value):
    if option and value != ():
        option = value[0]
    print(option)

The nargs can be removed if you only expect at most one argument to follow, and can be treated as not required.

@click.command()
@click.option('-o', '--option', is_flag=True, default=False, is_eager=True)
@click.argument('value', required=False)
def myscript(option, value=None):
    if option and value is not None:
        option = value
    print(option)

It might also be possible by putting together a context generator and storing some state, but that seems the least desirable solution, since you would be relying on the context storing your state.

2 of 2
5

FYI, if anybody bumps into this problem in 2024, it seems that click has made something specific for this use case, see: https://click.palletsprojects.com/en/8.1.x/options/#optional-value

Setting is_flag=False, flag_value=value tells Click that the option can still be passed a value, but if only the flag is given the flag_value is used.

@click.command()
@click.option("--name", is_flag=False, flag_value="Flag", default="Default")
def hello(name):
    click.echo(f"Hello, {name}!")

>> hello
Hello, Default!
>> hello --name Value
Hello, Value!
>> hello --name
Hello, Flag!
Find elsewhere
Top answer
1 of 6
93

If you don't insist on passing something that looks like a list, but simply want to pass multiple variadic arguments, you can use the multiple option.

From the click documentation

@click.command()
@click.option('--message', '-m', multiple=True)
def commit(message):
    click.echo('\n'.join(message))
$ commit -m foo -m bar
foo
bar
2 of 6
41

You can coerce click into taking multiple list arguments, if the lists are formatted as a string literals of python lists by using a custom option class like:

Custom Class:

import click
import ast

class PythonLiteralOption(click.Option):

    def type_cast_value(self, ctx, value):
        try:
            return ast.literal_eval(value)
        except:
            raise click.BadParameter(value)

This class will use Python's Abstract Syntax Tree module to parse the parameter as a python literal.

Custom Class Usage:

To use the custom class, pass the cls parameter to @click.option() decorator like:

@click.option('--option1', cls=PythonLiteralOption, default=[])

How does this work?

This works because click is a well designed OO framework. The @click.option() decorator usually instantiates a click.Option object but allows this behavior to be over ridden with the cls parameter. So it is a relatively easy matter to inherit from click.Option in our own class and over ride the desired methods.

In this case we over ride click.Option.type_cast_value() and then call ast.literal_eval() to parse the list.

Test Code:

@click.command(context_settings=dict(help_option_names=['-h', '--help']))
@click.option('--option1', cls=PythonLiteralOption, default=[])
@click.option('--option2', cls=PythonLiteralOption, default=[])
def cli(option1, option2):
    click.echo("Option 1, type: {}  value: {}".format(
        type(option1), option1))
    click.echo("Option 2, type: {}  value: {}".format(
        type(option2), option2))

# do stuff
if __name__ == '__main__':
    import shlex
    cli(shlex.split(
        '''--option1 '["o11", "o12", "o13"]' 
        --option2 '["o21", "o22", "o23"]' '''))

Test Results:

Option 1, type: <type 'list'>  value: ['o11', 'o12', 'o13']
Option 2, type: <type 'list'>  value: ['o21', 'o22', 'o23']
๐ŸŒ
Click Documentation
click.palletsprojects.com โ€บ en โ€บ stable โ€บ arguments
Arguments โ€” Click Documentation (8.5.x)
@click.command() @click.argument('files', nargs=-1, type=click.Path()) def touch(files): """Print all FILES file names.""" for filename in files: click.echo(filename) ... If you donโ€™t like the -- marker, you can set ignore_unknown_options to True to avoid checking unknown options:
Top answer
1 of 2
1
import sys
import click
import pandas as pd
from sklearn import metrics
@click.command()

# @click.option('--filename')
## @click.argument('result.csv', type=click.Path(exists=True))
@click.argument('filename', type=click.Path(exists=True))
def main(filename):

    df = pd.read_csv(filename)
    y_test = df["actual"].values
    y_pred = df["predicted"].values

    print('R Squared: ' +  str(metrics.r2_score(y_test, y_pred)))
    
if __name__ == '__main__':
    # sys.argv = ['',  '--filename', 'result.csv']
    sys.argv = ['',  'result.csv']
    main()
2 of 2
0

Instead of a named option @click.option('--filename') you can use the argument decorator and specifying:

  1. the parameter-name of your function like here filename and
  2. the type of the parameter, e.g. a file-path that is validated for existence: click.Path(exists=True)

Click Argument for file-paths

You can define File Path Arguments as follows with click:

@click.command()
@click.argument('filename', type=click.Path(exists=True))
def main(filename):
    print(f"{filename=}")
    df = pd.read_csv(filename)

However this means changing your command-line, e.g. by removing the option '--filename' element from the array:

if __name__ == '__main__':
    sys.argv = ['',  'result.csv']
    main()

Attribute Error

The runtime error mentioned in a comment to guolei's answer:

AttributeError: 'tuple' object has no attribute 'tb_frame'

is most likely not related to your presented Python script (incl. click, pandas or scikit-learn). It rather comes from your runtime-environment like IPython or Jupyter Notebook. See AttributeError: 'TypeError' object has no attribute 'tb_frame' bug ยท Issue #12877 ยท ipython/ipython.

๐ŸŒ
Click Documentation
click.palletsprojects.com โ€บ en โ€บ stable โ€บ documentation
Help Pages โ€” Click Documentation (8.5.x)
import click @click.group( help="Example Click app with subcommands, options, and positional arguments.", epilog=("Examples:\n\n\b\n python hello.py greet Alice --count 2\n"), ) def cli() -> None: """Run the example CLI.""" @cli.command() @click.argument("name") @click.option( "--count", default=1, show_default=True, type=click.IntRange(1, None), help="Number of greetings to print.", ) @click.option( "--greeting", default="Hello", show_default=True, help="Greeting prefix to use.", ) def greet(name: str, count: int, greeting: str) -> None: """Greet NAME one or more times.""" for _ in range(count): click.echo(f"{greeting} {name}!")
๐ŸŒ
Digitalk
digitalk.tech โ€บ blog โ€บ python-click-or-how-to-write-professional-cli-applications
Mndwrk Blog - Python click or how to write professional CLI applications
June 17, 2022 - $ python3 myscript.py --option ... the previously demonstrated @click.option decorators, while arg1, arg2, and arg3 are arguments that are defined with @click.argument....
Top answer
1 of 2
42

This can be done by over riding the click.Command.invoke() method like:

Custom Class:

def CommandWithConfigFile(config_file_param_name):

    class CustomCommandClass(click.Command):

        def invoke(self, ctx):
            config_file = ctx.params[config_file_param_name]
            if config_file is not None:
                with open(config_file) as f:
                    config_data = yaml.safe_load(f)
                    for param, value in ctx.params.items():
                        if value is None and param in config_data:
                            ctx.params[param] = config_data[param]

            return super(CustomCommandClass, self).invoke(ctx)

    return CustomCommandClass

Using Custom Class:

Then to use the custom class, pass it as the cls argument to the command decorator like:

@click.command(cls=CommandWithConfigFile('config_file'))
@click.argument("arg")
@click.option("--opt")
@click.option("--config_file", type=click.Path())
def main(arg, opt, config_file):

Test Code:

# !/usr/bin/env python
import click
import yaml

@click.command(cls=CommandWithConfigFile('config_file'))
@click.argument("arg")
@click.option("--opt")
@click.option("--config_file", type=click.Path())
def main(arg, opt, config_file):
    print("arg: {}".format(arg))
    print("opt: {}".format(opt))
    print("config_file: {}".format(config_file))


main('my_arg --config_file config_file'.split())

Test Results:

arg: my_arg
opt: my_opt
config_file: config_file
2 of 2
11

I realize that this is way old, but since Click 2.0, there's a more simple solution. The following is a slight modification of the example from the docs.

This example takes explicit --port args, it'll take an environment variable, or a config file (with that precedence).

Command Groups

Our code:

import os
import click
from yaml import load
try:
    from yaml import CLoader as Loader
except ImportError:
    from yaml import Loader


@click.group(context_settings={'auto_envvar_prefix': 'FOOP'})  # this allows for environment variables
@click.option('--config', default='~/config.yml', type=click.Path())  # this allows us to change config path
@click.pass_context
def foop(ctx, config):
    if os.path.exists(config):
        with open(config, 'r') as f:
            config = load(f.read(), Loader=Loader)
        ctx.default_map = config


@foop.command()
@click.option('--port', default=8000)
def runserver(port):
    click.echo(f"Serving on http://127.0.0.1:{port}/")


if __name__ == '__main__':
    foop()

Assuming our config file (~/config.yml) looks like:

runserver:
    port: 5000

and we have a second config file (at ~/config2.yml) that looks like:

runserver:
    port: 9000

Then if we call it from bash:

$ foop runserver
# ==> Serving on http://127.0.0.1:5000/
$ FOOP_RUNSERVER_PORT=23 foop runserver
# ==> Serving on http://127.0.0.1:23/
$ FOOP_RUNSERVER_PORT=23 foop runserver --port 34
# ==> Serving on http://127.0.0.1:34/
$ foop --config ~/config2.yml runserver
# ==> Serving on http://127.0.0.1:9000/

Single Commands

If you don't want to use command groups and want to have configs for a single command:

import os
import click
from yaml import load
try:
    from yaml import CLoader as Loader
except ImportError:
    from yaml import Loader


def set_default(ctx, param, value):
    if os.path.exists(value):
        with open(value, 'r') as f:
            config = load(f.read(), Loader=Loader)
        ctx.default_map = config
    return value


@click.command(context_settings={'auto_envvar_prefix': 'FOOP'})
@click.option('--config', default='config.yml', type=click.Path(),
              callback=set_default, is_eager=True, expose_value=False)
@click.option('--port')
def foop(port):
    click.echo(f"Serving on http://127.0.0.1:{port}/")

will give similar behavior.

๐ŸŒ
Zonca
zonca.dev โ€บ posts โ€บ 2022-10-26-click-commandline-class-arguments
Generate Click command line options dynamically from class arguments โ€“ Andrea Zonca
October 26, 2022 - @cli.command() @options_from_c... click-commandline-class-arguments.py aclass --help Usage: click-commandline-class-arguments.py aclass [OPTIONS] Options: --b INTEGER [required] --a TEXT [required] --help Show this message and exit....
๐ŸŒ
Devnet-academy
docs.devnet-academy.com โ€บ docs โ€บ click_v7.1.2 โ€บ options.html
Options โ€” Click Documentation (7.x)
The first name prefixed with one dash is used otherwise. To get the Python argument name, the chosen name is converted to lower case, up to two dashes are removed as the prefix, and other dashes are converted to underscores. @click.command() @click.option('-s', '--string-to-echo') def echo(string_to_echo): click.echo(string_to_echo)
๐ŸŒ
dbader.org
dbader.org โ€บ blog โ€บ mastering-click-advanced-python-command-line-apps
Mastering Click: Writing Advanced Python Command-Line Apps โ€“ dbader.org
January 2, 2018 - The most obvious ones are the builtin Python types such as str, int, float but Click also provides additional types: Path, File and more. The complete list is available in the section on Parameter Types. Ensuring that an input value is of a specific type is as easy as you can make it. You simply pass the parameter type youโ€™re expecting to the decorator as type argument when defining your parameter. Something like this: @click.option('--api-key', '-a', type=str) @click.option('--config-file', '-c', type=click.Path())
๐ŸŒ
AssemblyAI
assemblyai.com โ€บ blog โ€บ the-definitive-guide-to-python-click
The Definitive Guide to Python Click
July 28, 2021 - ... Now letโ€™s go through how to use the click.options decorator. This decorator is used when you want to pass optional parameters to your commands. It can be used to pass an argument, or to be passed as a flag.