Git Product home page Git Product logo

click-extra's Introduction

Click Extra

Last release Python versions Downloads Unittests status Coverage status Documentation status DOI

What is Click Extra?

A ready-to-use wrapper for Click, the Python CLI framework.

It is a drop-in replacement with good defaults that saves lots of boilerplate code and frustration. It also comes with workarounds and patches that have not reached upstream yet (or are unlikely to).

Example

It transforms this vanilla click CLI:

click CLI help screen

Into this:

click-extra CLI help screen

To undestrand how we ended up with the result above, go read the tutorial.

Features

Used in

Check these projects to get real-life examples of click-extra usage:

Feel free to send a PR to add your project in this list if you are relying on Click Extra in any way.

Development

Development guidelines are the same as parent project mpm, from which click-extra originated.

click-extra's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

click-extra's Issues

`--version`, `--config`, and `--verbosity` options appear twice in help output in 3.0.1

Using click_extra 3.0.1, this code:

import logging

import click_extra as click

@click.extra_command()
@click.version_option(version="0.1")
@click.config.config_option(default="ex.yml")
@click.logging.verbosity_option(default="DEBUG")
def cli():
  pass

if __name__ == "__main__":
    cli()

and running

$ python issue232.py -h

produces:

Usage: issue232.py [OPTIONS]

Options:
  --time / --no-time        Measure and print elapsed execution time.  [default:
                            no-time]
  --color, --ansi / --no-color, --no-ansi
                            Strip out all colors and all ANSI codes from output.
                            [default: color]
  -C, --config CONFIG_PATH  Location of the configuration file. Supports both
                            local path and remote URL.  [default: C:\Users\ross\
                            AppData\Roaming\issue232.py\config.{toml,yaml,yml,js
                            on,ini,xml}]
  -v, --verbosity LEVEL     Either CRITICAL, ERROR, WARNING, INFO, DEBUG.
                            [default: INFO]
  --version                 Show the version and exit.
  -h, --help                Show this message and exit.
  --version                 Show the version and exit.
  -C, --config CONFIG_PATH  Location of the configuration file. Supports both
                            local path and remote URL.  [default: ex.yml]
  -v, --verbosity LEVEL     Either CRITICAL, ERROR, WARNING, INFO, DEBUG.
                            [default: DEBUG]

Tweak callback evaluation order of eagerness options

Eager parameters are evaluated in the order as they were provided on the command line by the user as explained in: https://click.palletsprojects.com/en/8.0.x/advanced/#callback-evaluation-order

This means a call to:

  • --no-color --version will output a plain, uncoloured version string, while
  • --version --no-color will output a coloured string.

This is highlighted by a test at:

@skip_windows_colors
def test_color_option_precedence(invoke):
"""--no-color has an effect on --version, if placed in the right order.
Eager parameters are evaluated in the order as they were provided on the command
line by the user as expleined in:
https://click.palletsprojects.com/en/8.0.x/advanced/#callback-evaluation-order
..todo:
Maybe have the possibility to tweak CLI callback evaluation order so we can
let the user to have the NO_COLOR env set to allow for color-less --version output.
"""
@click.command()
@color_option()
@version_option(version="2.1.9")
def dummy_cli():
click.echo(Style(fg="yellow")("It works!"))
result = invoke(dummy_cli, "--no-color", "--version", "command1", color=True)
assert result.exit_code == 0
assert result.output == "dummy-cli, version 2.1.9\n"
assert not result.stderr
result = invoke(dummy_cli, "--version", "--no-color", "command1", color=True)
assert result.exit_code == 0
assert result.output == "\x1b[97mdummy-cli\x1b[0m, version \x1b[32m2.1.9\x1b[0m\n"
assert not result.stderr

It could be great to have --no-color (and its NO_COLOR env var) be respected whatever its order.

Required options help text is not colored consistently

Using click-extra v4.6.4

Steps to reproduce

Here's a minimal script to reproduce the bug:

from click_extra import extra_command, option


@extra_command(params=[])
@option('-a', required=False)
@option('-b', required=False, default='b')
@option('-c', required=True)
@option('-d', required=True, default='d')
def greet(a, b, c, d):
    pass


if __name__ == '__main__':
    greet()

Actual --help output

image

Expected output

Either both should be white, or both should be italic green.
It appears that the default value style is bleeding into the required section in the help message for the -d option.

Option leaks between subcommands

When using the add_command method along with extra_command in click_extra, the wrong options are displayed for subcommands in --help. Instead of displaying the options specific to the subcommand, all the options defined for the previous subcommands are displayed.

Steps to reproduce:

  1. Create a file test.py with following contents:

    from click_extra import extra_command
    from click import group, option
    
    @group()
    def cli():
        pass
    
    @extra_command()
    @option('--one')
    def foo():
        pass
    
    @extra_command()
    @option('--two')
    def bar():
        pass
    
    if __name__ == '__main__':
        cli.add_command(foo)
        cli.add_command(bar)
        cli()
  2. Run the file using

    $ python test.py bar --help
    

Expected behavior:

Usage: test.py bar [OPTIONS]

Options:
  --two TEXT
  --time / --no-time        Measure and print elapsed execution time.  [default:
                            no-time]
[rest of the options]

Actual behavior:

Usage: test.py bar [OPTIONS]

Options:
  --one TEXT
  --two TEXT
  --time / --no-time        Measure and print elapsed execution time.  [default:
                            no-time]
[rest of the options]

Here, the help text prints options added for foo command. If a third command baz is added after bar, it prints options for both foo and bar before printing baz's options

Expose configuration file path in context

Hi!

I'm not sure I understand why this issue was closed. The OP asked for a way to programmatically get the path to the resolved config files, and the provided solution only lets you access explicitly passed args. I'm actually looking for a way to get implicitly determined config file path. This seems very useful to me, my program needs to write back to config files.

For example, if I call my_cli command and it reads a value from ~/.my_cli/config.yaml, how do I fetch the path ~/.my_cli/config.yaml without re-implementing click_extra's load function?

Thanks, and sorry if it was already answered.

Merge cascading/hierarchical configuration files

This depends on #651 to implement the configuration file hierarchy traversal.

This is about merging configuration files at different position in the file system hierarchy.

See:

Use cases: mono-repos (and reused workflows?).

`version_option` displays click-extra version

Using click-extra 2.1.1, when I run:

import click

@click.command()
@click.version_option(version="0.1")
def cli():
    pass

if __name__ == "__main__":
    cli()

I get:

test.py, version 0.1

When I run:

import click_extra as click

@click.command()
@click.version_option(version="0.1")
def cli():
    pass

if __name__ == "__main__":
    cli()

I get:

test-extra-version.py, version 2.1.1

But hey, it's in color! :)

Set custom logger or overwrite verbosity option

Hey, I'm trying out your library mainly to add some colors to the help page and use the configuration file mechanism.

Before I used rich for logging and general printing purposes.
Now with your library the logging configuration used is overwritten.

I was looking for a way to restore the old behavior but I didn't find a way to provide my own logging configuration.

Is there a way that I missed or if there is not could we add it?

`+B` short option fails: `re.error: nothing to repeat`

With click-extra v3.4.0,

from click_extra import extra_command, echo, option
@extra_command()
@option("--bool/--no-bool", "-b/-B", is_flag=True, default=False)
def hello(bool):
    echo(f"bool={bool}!")
hello()

works fine, but changing -B to +B:

from click_extra import extra_command, echo, option
@extra_command()
@option("--bool/--no-bool", "-b/+B", is_flag=True, default=False)
def hello(bool):
    echo(f"bool={bool}!")
hello()

produces an exception:

  File "C:\issue316.py", line 6, in <module>
    hello()
  File "C:\Python310\lib\site-packages\click\core.py", line 1130, in __call__
    return self.main(*args, **kwargs)
  File "C:\Python310\lib\site-packages\click_extra\commands.py", line 161, in main
    return super().main(*args, **kwargs)
  File "C:\Python310\lib\site-packages\click\core.py", line 1054, in main
    with self.make_context(prog_name, args, **extra) as ctx:
  File "C:\Python310\lib\site-packages\click_extra\commands.py", line 180, in make_context
    ctx = super().make_context(info_name, args, parent, **extra)
  File "C:\Python310\lib\site-packages\click\core.py", line 920, in make_context
    self.parse_args(ctx, args)
  File "C:\Python310\lib\site-packages\cloup\constraints\_support.py", line 183, in parse_args
    args = super().parse_args(ctx, args)  # type: ignore
  File "C:\Python310\lib\site-packages\click\core.py", line 1378, in parse_args
    value, args = param.handle_parse_result(ctx, opts, args)
  File "C:\Python310\lib\site-packages\click\core.py", line 2360, in handle_parse_result
    value = self.process_value(ctx, value)
  File "C:\Python310\lib\site-packages\click\core.py", line 2322, in process_value
    value = self.callback(ctx, self, value)
  File "C:\Python310\lib\site-packages\click_extra\colorize.py", line 468, in print_help
    echo(ctx.get_help(), color=ctx.color)
  File "C:\Python310\lib\site-packages\click\core.py", line 699, in get_help
    return self.command.get_help(self)
  File "C:\Python310\lib\site-packages\click_extra\colorize.py", line 573, in get_help
    return super().get_help(ctx)
  File "C:\Python310\lib\site-packages\click\core.py", line 1299, in get_help
    return formatter.getvalue().rstrip("\n")
  File "C:\Python310\lib\site-packages\click_extra\colorize.py", line 797, in getvalue
    return self.highlight_extra_keywords(help_text)
  File "C:\Python310\lib\site-packages\click_extra\colorize.py", line 775, in highlight_extra_keywords
    help_text = re.sub(
  File "C:\Python310\lib\re.py", line 209, in sub
    return _compile(pattern, flags).sub(repl, string, count)
  File "C:\Python310\lib\re.py", line 303, in _compile
    p = sre_compile.compile(pattern, flags)
  File "C:\Python310\lib\sre_compile.py", line 788, in compile
    p = sre_parse.parse(p, flags)
  File "C:\Python310\lib\sre_parse.py", line 955, in parse
    p = _parse_sub(source, state, flags & SRE_FLAG_VERBOSE, 0)
  File "C:\Python310\lib\sre_parse.py", line 444, in _parse_sub
    itemsappend(_parse(source, state, verbose, nested + 1,
  File "C:\Python310\lib\sre_parse.py", line 841, in _parse
    p = _parse_sub(source, state, sub_verbose, nested + 1)
  File "C:\Python310\lib\sre_parse.py", line 444, in _parse_sub
    itemsappend(_parse(source, state, verbose, nested + 1,
  File "C:\Python310\lib\sre_parse.py", line 669, in _parse
    raise source.error("nothing to repeat",
re.error: nothing to repeat at position 424 (line 8, column 38)

But this works with click only:

from click import command, echo, option

@command()
@option("--bool/--no-bool", "-b/+B", is_flag=True, default=False)
def hello(bool):
    """Simple program that greets NAME for a total of COUNT times."""
    echo(f"bool={bool}!")
hello()

which produces

Usage: issue316b.py [OPTIONS]

  Simple program that greets NAME for a total of COUNT times.

Options:
  -b, --bool / +B, --no-bool
  --help                      Show this message and exit.

Exception: you forgot parenthesis in the command decorator for `subcommand`

With Python 3.10.11, and click-8.1.6 click-extra-4.6.3 cloup-3.0.0 and running the script found at
https://kdeldycke.github.io/click-extra/parameters.html :

#!/usr/bin/env python3

from click import option, echo, pass_context

from click_extra import config_option, extra_group

@extra_group
@option("--dummy-flag/--no-flag")
@option("--my-list", multiple=True)
@config_option
@pass_context
def my_cli(ctx, dummy_flag, my_list):
    echo(f"dummy_flag    is {dummy_flag!r}")
    echo(f"my_list       is {my_list!r}")
    echo(f"Raw parameters:            {ctx.meta.get('click_extra.raw_args', [])}")
    echo(f"Loaded, default values:    {ctx.default_map}")
    echo(f"Values passed to function: {ctx.params}")

@my_cli.command
@option("--int-param", type=int, default=10)
def subcommand(int_param):
    echo(f"int_parameter is {int_param!r}")

I get this error:

Traceback (most recent call last):
  File "click_bug_test_example.py", line 23, in <module>
    def subcommand(int_param):
...
Exception: you forgot parenthesis in the command decorator for `subcommand`. While parenthesis are optional in Click >= 8.1, they are required in Cloup.

What am I doing wrong, or does this example code need freshening?

Add a `--no-config` option

I propose to add a --no-config flag that will bypass any attempt to look for and load a configuration file, as well as ignore any passed environment variable.

This is really practical in automated workflow and debugging of CLI invokation in general. It allow the user to execute a CLI from its strict defaults and CLI options only.

`--show-params` skips nested subcommands

When using nested subcommands, --show-params skips anything deeper than subcommands.

As an example:

#!/usr/bin/env python3

import click_extra

@click_extra.extra_group(params=[click_extra.ShowParamsOption()])
@click_extra.option("--first")
def main(first):
    print(first)

@main.group(params=[])
@click_extra.option("--second")
def subcommand(second):
    print(second)

@subcommand.command(params=[])
@click_extra.option("--third")
def subsubcommand(third):
    print(third)

if __name__ == "__main__":
    main()

Option third is not shown in the param output table. As far as I can tell this is due to walk_params not recursing into subcommands at https://github.com/kdeldycke/click-extra/blob/main/click_extra/parameters.py#L356

Always compute version number and pass it in context.

Given

import click_extra
@click_extra.extra_command(params=[
  click_extra.VersionOption(version="0.1")
])
def cli():
  print("%s version %s" % (__file__, ?))

cli()

what do I need to replace ? with? Note that I scanned the code for close to an hour, before giving up.

Use default `root` logger in `VerbosityOption`

Using click-extra 3.4.0, and modifying the second script at https://kdeldycke.github.io/click-extra/commands.html#default-options:

import logging
from click_extra import extra_command, VersionOption, ConfigOption, VerbosityOption
@extra_command(params=[
   VersionOption(version="0.1"),
   ConfigOption(default="ex.yml"),
   VerbosityOption(default="DEBUG"),
])
def cli():
  logging.warning("logging at warning level")
  logging.info("logging at info level")
  logging.debug("logging at debug level")

cli()

produces:

debug: Verbosity set to DEBUG.
debug: issue318.py, version 0.1
WARNING:root:logging at warning level

which is unexpected. Perhaps the docs should be updated to pass logging.getLogger() to the VerbosityOption() function:

import logging
from click_extra import extra_command, VersionOption, ConfigOption, VerbosityOption
@extra_command(params=[
   VersionOption(version="0.1"),
   ConfigOption(default="ex.yml"),
   VerbosityOption(logging.getLogger(), default="DEBUG"),
])
def cli():
  logging.warning("logging at warning level")
  logging.info("logging at info level")
  logging.debug("logging at debug level")

cli()

which outputs the expected result:

WARNING:root:logging at warning level
INFO:root:logging at info level
DEBUG:root:logging at debug level

Also, notice that the first two lines of the output with the debug prefix do not appear with this second script. It appears click-extra has its own logger. This most likely will confuse the user (as it's confusing to me).

How can I load a dotfile as a config file?

Hello.

When I want to load a dotfile-style config file (such as .bashrc) in YAML, how can I specify the file format? It appears click-extra selects the parser from the file extension. Is there a way to handle dotfiles?

Thanks for open sourcing your project!

Remove runtime dependencies on sphinx and so on

Hello

I see click-extra has LOT of runtime dependencies when instantiating it in a simple CLI:

 click-extra >=4 
 โ”œโ”€โ”€ boltons >=23.0.0,<24.0.0 (circular dependency aborted here)
 โ”œโ”€โ”€ click >=8.1.4,<9.0.0 (circular dependency aborted here)
 โ”œโ”€โ”€ cloup >=3.0.1,<4.0.0 
 โ”‚   โ”œโ”€โ”€ click >=8.0,<9.0 (circular dependency aborted here)
 โ”‚   โ””โ”€โ”€ typing-extensions * 
 โ”œโ”€โ”€ commentjson >=0.9.0,<0.10.0 
 โ”‚   โ””โ”€โ”€ lark-parser >=0.7.1,<0.8.0 
 โ”œโ”€โ”€ mergedeep >=1.3.4,<2.0.0 
 โ”œโ”€โ”€ pallets-sphinx-themes >=2.1.1,<3.0.0 
 โ”‚   โ”œโ”€โ”€ packaging * 
 โ”‚   โ””โ”€โ”€ sphinx >=3 
 โ”‚       โ”œโ”€โ”€ alabaster >=0.7,<0.8 
 โ”‚       โ”œโ”€โ”€ babel >=2.9 
 โ”‚       โ”‚   โ””โ”€โ”€ pytz >=2015.7 
 โ”‚       โ”œโ”€โ”€ colorama >=0.4.5 (circular dependency aborted here)
 โ”‚       โ”œโ”€โ”€ docutils >=0.18.1,<0.20 
 โ”‚       โ”œโ”€โ”€ imagesize >=1.3 
 โ”‚       โ”œโ”€โ”€ importlib-metadata >=4.8 
 โ”‚       โ”‚   โ””โ”€โ”€ zipp >=0.5 
 โ”‚       โ”œโ”€โ”€ jinja2 >=3.0 
 โ”‚       โ”‚   โ””โ”€โ”€ markupsafe >=2.0 
 โ”‚       โ”œโ”€โ”€ packaging >=21.0 (circular dependency aborted here)
 โ”‚       โ”œโ”€โ”€ pygments >=2.13 
 โ”‚       โ”œโ”€โ”€ requests >=2.25.0 (circular dependency aborted here)
 โ”‚       โ”œโ”€โ”€ snowballstemmer >=2.0 
 โ”‚       โ”œโ”€โ”€ sphinxcontrib-applehelp * 
 โ”‚       โ”œโ”€โ”€ sphinxcontrib-devhelp * 
 โ”‚       โ”œโ”€โ”€ sphinxcontrib-htmlhelp >=2.0.0 
 โ”‚       โ”œโ”€โ”€ sphinxcontrib-jsmath * 
 โ”‚       โ”œโ”€โ”€ sphinxcontrib-qthelp * 
 โ”‚       โ””โ”€โ”€ sphinxcontrib-serializinghtml >=1.1.5 
 โ”œโ”€โ”€ pygments >=2.14,<3.0 (circular dependency aborted here)
 โ”œโ”€โ”€ pygments-ansi-color >=0.3.0,<0.4.0 
 โ”‚   โ””โ”€โ”€ pygments !=2.7.3 (circular dependency aborted here)
 โ”œโ”€โ”€ pyyaml >=6.0.0,<7.0.0 
 โ”œโ”€โ”€ regex >=2023.3.22,<2024.0.0 
 โ”œโ”€โ”€ requests >=2.28.2,<3.0.0 (circular dependency aborted here)
 โ”œโ”€โ”€ sphinx >=6 (circular dependency aborted here)
 โ”œโ”€โ”€ tabulate >=0.9,<0.10 
 โ”‚   โ””โ”€โ”€ wcwidth * 
 โ”œโ”€โ”€ tomli >=2.0.1,<3.0.0 
 โ”œโ”€โ”€ wcmatch >=8.4.1,<9.0.0 
 โ”‚   โ””โ”€โ”€ bracex >=2.1.1 
 โ””โ”€โ”€ xmltodict >=0.13.0,<0.14.0 

Please provide a way to avoid my CLI to depends on sphinx, pallets-sphinx-themes, and so on, just for a simple runtime function.

`extra_command` not found?

I'm sure I'm doing something wrong, but using 3.01:

from click_extra import extra_command
...
@extra_command(

produces:

ImportError: cannot import name 'extra_command' from 'click_extra'

and

import click_extra
...
@click_extra.extra_command(

produces:

AttributeError: module 'click_extra' has no attribute 'extra_command'

Duplicate `version_option` raises `AssertionError`

With click-extra 3.4.0, the script at https://kdeldycke.github.io/click-extra/commands.html#default-options :

from click_extra import extra_command, version_option, config_option, verbosity_option

@extra_command()
@version_option(version="0.1")
@config_option(default="ex.yml")
@verbosity_option(default="DEBUG")
def cli():
   pass
cli()

produces:

debug: Verbosity set to DEBUG.
Traceback (most recent call last):
  File "C:\issue317.py", line 9, in <module>
    cli()
  File "C:\Python310\lib\site-packages\click\core.py", line 1130, in __call__
    return self.main(*args, **kwargs)
  File "C:\Python310\lib\site-packages\click_extra\commands.py", line 161, in main
    return super().main(*args, **kwargs)
  File "C:\Python310\lib\site-packages\click\core.py", line 1055, in main
    rv = self.invoke(ctx)
  File "C:\Python310\lib\site-packages\click_extra\commands.py", line 203, in invoke
    version_param = self._get_param(ctx, VersionOption)
  File "C:\Python310\lib\site-packages\click_extra\commands.py", line 189, in _get_param
    assert len(params) == 1
AssertionError

Commenting out the @version_option fixes it:

from click_extra import extra_command, version_option, config_option, verbosity_option

@extra_command()
# @version_option(version="0.1")
@config_option(default="ex.yml")
@verbosity_option(default="DEBUG")
def cli():
   pass
cli()

produces:

debug: Verbosity set to DEBUG.
debug: issue317.py, version 3.4.0

Note that the script:

from click_extra import extra_command, VersionOption, ConfigOption, VerbosityOption
@extra_command(params=[
   VersionOption(version="0.1"),
   ConfigOption(default="ex.yml"),
   VerbosityOption(default="DEBUG"),
])
def cli():
   pass
cli()

works as expected:

debug: Verbosity set to DEBUG.
debug: issue317.py, version 0.1

Help text shows config option twice

Synopsis

Using click-extra, 2.1.1, when I use make_pass_decorator with config_option, the text

  -C, --config CONFIG_PATH  Location of the configuration file. Supports both
                            local path and remote URL.  [default:config.{toml,yaml,yml,json,ini,xml}]

appears twice in the help text.

Details

The code:

import click

class Ex:
    pass

pass_class = click.make_pass_decorator(Ex)

@click.group()
@click.pass_context
def cli(ctx):
    ctx.obj = Ex()

@cli.command()
@pass_class
def command():
    pass

if __name__ == "__main__":
    cli()

produces:

Usage: test.py [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  command

and replacing click with click_extra in the above code:

import click_extra as click # replaced click with click_extra

class Ex:
    pass

pass_class = click.make_pass_decorator(Ex)

@click.group()
@click.pass_context
def cli(ctx):
    ctx.obj = Ex()

@cli.command()
@pass_class
def command():
    pass

if __name__ == "__main__":
    cli()

produces:

Usage: test.py [OPTIONS] COMMAND [ARGS]...

Options:
  --time / --no-time        Measure and print elapsed execution time.  [default:
                            no-time]
  --color, --ansi / --no-color, --no-ansi
                            Strip out all colors and all ANSI codes from output.
                            [default: color]
  -C, --config CONFIG_PATH  Location of the configuration file. Supports both
                            local path and remote URL.  [default:config.{toml,yaml,yml,json,ini,xml}]
  -v, --verbosity LEVEL     Either CRITICAL, ERROR, WARNING, INFO, DEBUG.
                            [default: INFO]
  --version                 Show the version and exit.
  -h, --help                Show this message and exit.

Commands:
  command

which is unexpected, as I didn't use verbosity_option, etc, but it's fine.

But when I add config_option to the above code:

import click_extra as click

class Ex:
    pass

pass_class = click.make_pass_decorator(Ex)

@click.group()
@click.config.config_option() # added this line
@click.pass_context
def cli(ctx):
    ctx.obj = Ex()

@cli.command()
@pass_class
def command():
    pass

if __name__ == "__main__":
    cli()

The --config option now appears twice:

Usage: test.py [OPTIONS] COMMAND [ARGS]...

Options:
  -C, --config CONFIG_PATH  Location of the configuration file. Supports both
                            local path and remote URL.  [default:config.{toml,yaml,yml,json,ini,xml}]
  --time / --no-time        Measure and print elapsed execution time.  [default:
                            no-time]
  --color, --ansi / --no-color, --no-ansi
                            Strip out all colors and all ANSI codes from output.
                            [default: color]
  -C, --config CONFIG_PATH  Location of the configuration file. Supports both
                            local path and remote URL.  [default:config.{toml,yaml,yml,json,ini,xml}]
  -v, --verbosity LEVEL     Either CRITICAL, ERROR, WARNING, INFO, DEBUG.
                            [default: INFO]
  --version                 Show the version and exit.
  -h, --help                Show this message and exit.

Commands:
  command

I would try to fix, but perhaps all of these options (config/time/color/verbosity) shouldn't be showing up by default, which is a bigger change.

Question: Get loaded config file path

Hey there!

Awesome project, quick question. Is there a way for me to retrieve the resolved path to the config file specified on the command line?

I would like to load the config file in other portions of my application without having to specify all options first with click. I would like to do something like:

@config_option()
def main():
    config_path = config['path']
    do_something(config_path)

While this may seem a bit convoluted, it would be useful in my situation as I am loading the config to pull variables for FastAPI routes that don't need to be initialized immediately on runtime. Currently, I am having to hardcode the path to my config in the classes supporting other API routes in separate files throughout the app:

pwd = os.path.dirname(os.path.realpath(__file__))
with open(f"{pwd}/../../config.yaml", "r") as f:
     config = yaml.safe_load(f)

You can see the problem users will experience here if this application is deployed on a different host.

I combed through the documentation, but I'm not seeing anything that would enable me to do this. Any thoughts you have would be greatly appreciated. Thanks!

Broken links

Summary

Status Count
๐Ÿ” Total 201
โœ… Successful 199
โณ Timeouts 0
๐Ÿ”€ Redirected 0
๐Ÿ‘ป Excluded 0
โ“ Unknown 0
๐Ÿšซ Errors 2

Errors per input

Errors in docs/license.md

Errors in docs/version.md

Change order of commands

Hello,

Is there any workaround that commands not to be sorted by default? I want them to be in order as I wish. I tried this stackoverflow solution. It works for click library but not for click-extra. I feel that they are sorted at somewhere else in click-extra code, but I couldn't find. My trial is as below:

import cloup as _cloup
import click_extra as _ce
from click_extra import *  # pylint: disable=W0401,W0614 # noqa:F401,F403


def _extra_params():
    return [
        _ce.colorize.HelpOption(),
    ]


class OrderedExtraGroup(_ce.commands.ExtraGroup):  # pylint: disable=R0901
    """Ordered ExtraGroup Class."""

    def __init__(self, *args, **kwargs):
        """Initialize."""
        self.help_priorities = {}
        super().__init__(*args, **kwargs)

    def get_help(self, ctx):
        """Get Help."""
        self.list_commands = self.list_commands_for_help
        return super().get_help(ctx)

    def list_commands_for_help(self, ctx):
        """Reorder the list of commands when listing the help."""
        commands = super().list_commands(ctx)
        return list(c[1] for c in sorted(
            (self.help_priorities.get(command, 1), command)
            for command in commands))

    def command(self, *args, **kwargs):
        """
        Ordered Command.

        Behave the same as `click.Group.command()` except capture a
        priority for listing command names in help.
        """
        help_priority = kwargs.pop('help_priority', 1)
        help_priorities = self.help_priorities

        def decorator(f):
            cmd = super(OrderedExtraGroup, self).command(*args, **kwargs)(f)
            help_priorities[cmd.name] = help_priority
            return cmd

        return decorator


group = _ce.decorators.decorator_factory(
    dec=_cloup.group,
    cls=OrderedExtraGroup,
    params=_extra_params,
)

Broken links

Summary

Status Count
๐Ÿ” Total 176
โœ… Successful 171
โณ Timeouts 0
๐Ÿ”€ Redirected 0
๐Ÿ‘ป Excluded 0
โ“ Unknown 0
๐Ÿšซ Errors 5

Errors per input

Errors in docs/tutorial.md

  • [ERR] file:///home/runner/work/click-extra/click-extra/docs/index | Failed: Cannot find file

Errors in docs/config.md

  • [ERR] file:///home/runner/work/click-extra/click-extra/docs/standalone-option | Failed: Cannot find file

Errors in docs/commands.md

  • [ERR] file:///home/runner/work/click-extra/click-extra/docs/index | Failed: Cannot find file
  • [ERR] file:///home/runner/work/click-extra/click-extra/docs/click_extra.commands.default_extra_params | Failed: Cannot find file
  • [ERR] file:///home/runner/work/click-extra/click-extra/docs/tutorial | Failed: Cannot find file
    Full Github Actions output

`option(..., hidden=True)` not compatible with `--show-params`

Hi there!
Using --show-params with a hidden option throws a TypeError because param.get_help_record(ctx) returns None when the parameter is hidden.
The cause is that in print_params the result of get_help_record is subscripted and not checked for None.

An example causing this error is as follows:

from click import option, echo
from click_extra import extra_command

@extra_command
@option("--dummy-flag/--no-flag", hidden=True)
def my_cli(dummy_flag):
    echo(f"dummy_flag is {dummy_flag!r}")

if __name__ == "__main__":
    my_cli()

Running it with --show-params throws a TypeError for click-extra 4.5.0

I could prepare a patch but I am not sure what the intended behavior should be: since click doesn't generate the help record for the hidden parameters, re-implementing this in click_extra for the hidden paramters seems not clean. On the other hand not printing anything in this field for --show-params is very counterintuitive as well since the arguments do get parsed. A workaround would be to set hidden False before generating the help record and then resetting it to the previous value.

Am I missing something? Looking forward to your thoughts on this.

Passing `logging.root` to `verbosity_option()` doesn't set logging level

Using click-extra, 3.0.1, and running this script:

import logging

import click_extra as click

@click.command()
@click.logging.verbosity_option(default_logger=logging.getLogger())
def cli():
    logging.warning("logging at warning level")
    logging.info("logging at info level")
    logging.debug("logging at debug level")
    logging.getLogger().setLevel(click.logging.logger.level)
    logging.warning("logging at warning level")
    logging.info("logging at info level")
    logging.debug("logging at debug level")

if __name__ == "__main__":
    cli()

via:

python issue174.py -v DEBUG

it produces:

Traceback (most recent call last):
  File "c:\github\rasa\click-extra-tests\issue174.py", line 7, in <module>
    def cli():
  File "C:\Python310\lib\site-packages\cloup\_params.py", line 52, in decorator
    _param_memo(f, OptionClass(param_decls, **attrs))
TypeError: VerbosityOption.__init__() got multiple values for argument 'default_logger'

Passing default_logger=None or removing the parameter fixes the issue.

Coloring help for sub commands

Credit where it's due!

First off, hats off to you @kdeldycke for putting the time to implement this. I've just found this and it's saved me so much time in creating a readable help page for the cli that I'm making!

Issue

I'm seeing the behavior where the help for the subcommands isn't colored, even though the help for the main command (group) is properly colored.

Is there a way to make the coloring apply to subcommands as well?

How to recreate

This is s simple code to recreate it (small modification from the docs):

@extra_group()
@option("--dummy-flag/--no-flag")
@option("--my-list", multiple=True)
@pass_context
def my_cli(ctx, dummy_flag, my_list):
    echo(f"dummy_flag    is {dummy_flag!r}")
    echo(f"my_list       is {my_list!r}")
    echo(f"Raw parameters:            {ctx.meta.get('click_extra.raw_args', [])}")
    echo(f"Loaded, default values:    {ctx.default_map}")
    echo(f"Values passed to function: {ctx.params}")

@my_cli.command()
@pass_context
@option("--int-param", type=int, default=10)
def subcommand(ctx, int_param):
    echo(f"int_parameter is {int_param!r}")
    echo(ctx.meta.get('click_extra.raw_args', []))
    
if __name__ == '__main__':
    my_cli()

Where --help is colored, but subcommand --help is not`
image

What I've tried

  1. I tried setting the formatter settings for the command to the default from click_extra, but that had no effect:
@my_cli.command(
    formatter_settings=click_extra.colorize.HelpExtraFormatter.settings()
)
  1. Then I tried manually setting the theme to the dark() theme from cloup which did work, but it's not the click-extra theme that I've started to love!
@my_cli.command(
    formatter_settings=click_extra.colorize.HelpExtraFormatter.settings(
       theme=cloup.HelpTheme.dark()
       )
    )

outputs:
image
3. Finally, I also managed to set the colors of the theme manually, but it's not as comprehensive as the default theme of click-extra!

@my_cli.command(
    formatter_settings=click_extra.colorize.HelpExtraFormatter.settings(
       theme=click_extra.colorize.HelpExtraTheme(
                invoked_command=cloup.Style(fg='bright_yellow'),
                heading=cloup.Style(fg='bright_white', bold=True),
                constraint=cloup.Style(fg='magenta'),
                col1=cloup.Style(fg='bright_yellow'),
            )
       )
    )

Unrecognized `<Option warn_error_options>` parameter from `dbt` subcommand

Hello,

I'm trying to create a wrapper for an open-source project using click. Here is the dbt click command. I am then trying to add the dbt command as a subcommand to my CLI, like so:

from dbt.cli.main import cli

@click_extra.extra_group(params=[])
@click_extra.pass_context
def main(ctx,):
    # my root CLI group
    pass

# Here I would like to add the entirety of the `dbt` command as a sub-functionality of my CLI
main.add_command(cmd=cli, name='dbt')

This code results in:

$ mycli dbt --help
...
...
...
  File "/opt/homebrew/lib/python3.11/site-packages/click_extra/config.py", line 428, in load_conf
    conf = self.merge_conf(user_conf)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/click_extra/config.py", line 368, in merge_conf
    valid_conf = self.recursive_update(self.params_template, user_conf)
                                       ^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/[email protected]/3.11.3/Frameworks/Python.framework/Versions/3.11/lib/python3.11/functools.py", line 1001, in __get__
    val = self.func(instance)
          ^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/click_extra/parameters.py", line 479, in params_template
    self.build_param_trees()
  File "/opt/homebrew/lib/python3.11/site-packages/click_extra/parameters.py", line 465, in build_param_trees
    merge(types, self.init_tree_dict(*keys, leaf=self.get_param_type(param)))
                                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/click_extra/parameters.py", line 430, in get_param_type
    raise ValueError(msg)
ValueError: Can't guess the appropriate Python type of <Option warn_error_options> parameter.

I'm not sure if this is an issue with click_extra, or (more likely) my implementation, or (also possible) the dbt implementation of click is simply not compatible with click_extra. Any insight would be helpful, but I understand if the issue is outside of click_extra that not much could be done. Thanks

Package name search not working in `ExtraVersionOption` for standalone scripts

Commit 1877dc might be the culprit.

Minimal example:

from click_extra import extra_command


@extra_command()
def greet():
    print('hello world')


if __name__ == '__main__':
    greet()

When run using

 python main.py --version

Produces

  File ".../lib/python3.10/site-packages/click_extra/version.py", line 300, in print_and_exit
    ctx.meta["click_extra.package_name"] = self.package_name
  File ".../lib/python3.10/functools.py", line 981, in __get__
    val = self.func(instance)
  File ".../lib/python3.10/site-packages/click_extra/version.py", line 188, in package_name
    raise RuntimeError(msg)
RuntimeError: Could not determine the package name automatically from the frame stack. Try passing 'package_name' instead.

Upon debugging I found __package__ to be empty in frame.f_globals:

{'__name__': '__main__', '__doc__': None, '__package__': '', '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x102921570>, '__spec__': None, '__file__': '<file_path>/main.py', '__builtins__': <module 'builtins' (built-in)>, 'extra_command': <function decorator_factory.<locals>.decorator at 0x1057ee950>, 'greet': <ExtraCommand greet>}

frame_chain was

[('click_extra.version', 'package_name'), ('functools', '__get__'), ('click_extra.version', 'print_and_exit'), ('click.core', 'process_value'), ('click.core', 'handle_parse_result'), ('click.core', 'parse_args'), ('cloup.constraints._support', 'parse_args'), ('click.core', 'make_context'), ('click_extra.commands', 'make_context'), ('click.core', 'main'), ('click_extra.commands', 'main'), ('click.core', '__call__'), ('__main__', '<module>')]

I was able to reproduce it in versions >=4.6.1.

Hide `click:example` blocks

i managed to import my complex click app with click:example through a simple import as"

.. click:example::
    from myapp.cli import cli as myapp

.. click:run::
    invoke(myapp, args=["--help"])

how can i hide the 'click:example' in the generated doc, I only need the output ?

Broken links

Summary

Status Count
๐Ÿ” Total 201
โœ… Successful 199
โณ Timeouts 0
๐Ÿ”€ Redirected 0
๐Ÿ‘ป Excluded 0
โ“ Unknown 0
๐Ÿšซ Errors 2

Errors per input

Errors in docs/license.md

Continuous testing against Click and Cloup dev version

With its loads of workarounds and hacks, Click Extra is quite sensible to the implementation details of Click and Cloup. I propose to run the test suite in the CI against these dev versions so we can catch bugs and regression earlier.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.