kdeldycke / click-extra Goto Github PK
View Code? Open in Web Editor NEW๐ Extra colorization and configuration loading for Click.
Home Page: https://kdeldycke.github.io/click-extra
License: GNU General Public License v2.0
๐ Extra colorization and configuration loading for Click.
Home Page: https://kdeldycke.github.io/click-extra
License: GNU General Public License v2.0
According to the XDG Base Directory Specification, config files should be stored under $XDG_CONFIG_HOME/<application>
(which defaults to ~/.config/application
when $XDG_CONFIG_HOME
is not set) and it would good to change the default config file location to match this.
Using click-extra v4.6.4
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()
--help
outputEither 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.
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:
click-extra/click_extra/config.py
Lines 119 to 120 in 6af02e8
Use cases: mono-repos (and reused workflows?).
Hello, I searched but couldn't figured out. Is there a way remove extra options from extra_command
? I just want to use colorization feature of the package but do not want to add extra options to the app. Thanks.
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
Status | Count |
---|---|
๐ Total | 201 |
โ Successful | 199 |
โณ Timeouts | 2 |
๐ Redirected | 0 |
๐ป Excluded | 0 |
โ Unknown | 0 |
๐ซ Errors | 0 |
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,
)
Status | Count |
---|---|
๐ Total | 175 |
โ Successful | 173 |
โณ Timeouts | 2 |
๐ Redirected | 0 |
๐ป Excluded | 0 |
โ Unknown | 0 |
๐ซ Errors | 0 |
Status | Count |
---|---|
๐ Total | 186 |
โ Successful | 185 |
โณ Timeouts | 0 |
๐ Redirected | 0 |
๐ป Excluded | 0 |
โ Unknown | 0 |
๐ซ Errors | 1 |
As proposed by @janluke (cloup
maintainer), it might be great to let the --config
option parse YAML configuration file as well as TOML.
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.
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!
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?
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`
@my_cli.command(
formatter_settings=click_extra.colorize.HelpExtraFormatter.settings()
)
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:
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'),
)
)
)
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:
click-extra/click_extra/tests/test_colorize.py
Lines 318 to 346 in d589907
It could be great to have --no-color
(and its NO_COLOR
env var) be respected whatever its order.
With the --help
option being eager, it always displays the help screen of the main subcommand.
Context: this as been discovered at callowayproject/bump-my-version#23 (comment)
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?
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.
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.
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!
Status | Count |
---|---|
๐ Total | 201 |
โ Successful | 199 |
โณ Timeouts | 0 |
๐ Redirected | 0 |
๐ป Excluded | 0 |
โ Unknown | 0 |
๐ซ Errors | 2 |
This might simplify the management of regexes in the help screen highlight code.
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'
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.
In click_extra/config.py", line 235, in map_option_type, I got the error
ValueError: Can't guess the target configuration data type of <Option verbose> prameter.
With this option
@click.option('-v', '--verbose', count=True)
If I change it to
@click.option('-v', '--verbose', count=True, type=int)
it works.
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.
Status | Count |
---|---|
๐ Total | 188 |
โ Successful | 186 |
โณ Timeouts | 2 |
๐ Redirected | 0 |
๐ป Excluded | 0 |
โ Unknown | 0 |
๐ซ Errors | 0 |
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:
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()
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
Status | Count |
---|---|
๐ Total | 201 |
โ Successful | 198 |
โณ Timeouts | 3 |
๐ Redirected | 0 |
๐ป Excluded | 0 |
โ Unknown | 0 |
๐ซ Errors | 0 |
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! :)
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]
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.
Status | Count |
---|---|
๐ Total | 176 |
โ Successful | 171 |
โณ Timeouts | 0 |
๐ Redirected | 0 |
๐ป Excluded | 0 |
โ Unknown | 0 |
๐ซ Errors | 5 |
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.
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).
Status | Count |
---|---|
๐ Total | 231 |
โ Successful | 227 |
โณ Timeouts | 2 |
๐ Redirected | 0 |
๐ป Excluded | 2 |
โ Unknown | 0 |
๐ซ Errors | 0 |
Status | Count |
---|---|
๐ Total | 201 |
โ Successful | 199 |
โณ Timeouts | 0 |
๐ Redirected | 0 |
๐ป Excluded | 0 |
โ Unknown | 0 |
๐ซ Errors | 2 |
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.
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.
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
Status | Count |
---|---|
๐ Total | 231 |
โ Successful | 226 |
โณ Timeouts | 3 |
๐ Redirected | 0 |
๐ป Excluded | 2 |
โ Unknown | 0 |
๐ซ Errors | 0 |
An alternative to TOML, YAML, JSON and the likes would be a kind of plain-text format for configuration files. In which we can dump CLI parameters and flags as we would have use them on a console prompt.
See for instance mpv
and yt-dlp
configuration files:
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!
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?
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 ?
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.
Commit 1877dc might be the culprit.
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.
Status | Count |
---|---|
๐ Total | 190 |
โ Successful | 188 |
โณ Timeouts | 2 |
๐ Redirected | 0 |
๐ป Excluded | 0 |
โ Unknown | 0 |
๐ซ Errors | 0 |
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
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.