diff options
author | robot-piglet <robot-piglet@yandex-team.com> | 2025-01-08 16:55:48 +0300 |
---|---|---|
committer | robot-piglet <robot-piglet@yandex-team.com> | 2025-01-08 17:08:25 +0300 |
commit | 9b668f4d12c7d6fc709ffc4ab3b87a7e8aef8e2f (patch) | |
tree | 2795579b13b11329bc8f9dc82b48d04a04023616 /contrib/python | |
parent | 5487449624c89f51c526f9e6de269590a273ebec (diff) | |
download | ydb-9b668f4d12c7d6fc709ffc4ab3b87a7e8aef8e2f.tar.gz |
Intermediate changes
commit_hash:f15cd769f87762d4807f0d4ae628b5dfb830bef5
Diffstat (limited to 'contrib/python')
32 files changed, 556 insertions, 327 deletions
diff --git a/contrib/python/click/py3/.dist-info/METADATA b/contrib/python/click/py3/.dist-info/METADATA index 7a6bbb24b5..366d1a7e4f 100644 --- a/contrib/python/click/py3/.dist-info/METADATA +++ b/contrib/python/click/py3/.dist-info/METADATA @@ -1,30 +1,25 @@ -Metadata-Version: 2.1 +Metadata-Version: 2.3 Name: click -Version: 8.1.7 +Version: 8.1.8 Summary: Composable command line interface toolkit -Home-page: https://palletsprojects.com/p/click/ -Maintainer: Pallets -Maintainer-email: contact@palletsprojects.com -License: BSD-3-Clause -Project-URL: Donate, https://palletsprojects.com/donate -Project-URL: Documentation, https://click.palletsprojects.com/ -Project-URL: Changes, https://click.palletsprojects.com/changes/ -Project-URL: Source Code, https://github.com/pallets/click/ -Project-URL: Issue Tracker, https://github.com/pallets/click/issues/ -Project-URL: Chat, https://discord.gg/pallets +Maintainer-email: Pallets <contact@palletsprojects.com> +Requires-Python: >=3.7 +Description-Content-Type: text/markdown Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python -Requires-Python: >=3.7 -Description-Content-Type: text/x-rst -License-File: LICENSE.rst -Requires-Dist: colorama ; platform_system == "Windows" -Requires-Dist: importlib-metadata ; python_version < "3.8" +Classifier: Typing :: Typed +Requires-Dist: colorama; platform_system == 'Windows' +Requires-Dist: importlib-metadata; python_version < '3.8' +Project-URL: Changes, https://click.palletsprojects.com/changes/ +Project-URL: Chat, https://discord.gg/pallets +Project-URL: Documentation, https://click.palletsprojects.com/ +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Source, https://github.com/pallets/click/ -\$ click\_ -========== +# $ click_ Click is a Python package for creating beautiful command line interfaces in a composable way with as little code as necessary. It's the "Command @@ -42,62 +37,38 @@ Click in three points: - Supports lazy loading of subcommands at runtime -Installing ----------- +## A Simple Example -Install and update using `pip`_: +```python +import click -.. code-block:: text +@click.command() +@click.option("--count", default=1, help="Number of greetings.") +@click.option("--name", prompt="Your name", help="The person to greet.") +def hello(count, name): + """Simple program that greets NAME for a total of COUNT times.""" + for _ in range(count): + click.echo(f"Hello, {name}!") - $ pip install -U click +if __name__ == '__main__': + hello() +``` -.. _pip: https://pip.pypa.io/en/stable/getting-started/ +``` +$ python hello.py --count=3 +Your name: Click +Hello, Click! +Hello, Click! +Hello, Click! +``` -A Simple Example ----------------- - -.. code-block:: python - - import click - - @click.command() - @click.option("--count", default=1, help="Number of greetings.") - @click.option("--name", prompt="Your name", help="The person to greet.") - def hello(count, name): - """Simple program that greets NAME for a total of COUNT times.""" - for _ in range(count): - click.echo(f"Hello, {name}!") - - if __name__ == '__main__': - hello() - -.. code-block:: text - - $ python hello.py --count=3 - Your name: Click - Hello, Click! - Hello, Click! - Hello, Click! - - -Donate ------- +## Donate The Pallets organization develops and supports Click and other popular packages. In order to grow the community of contributors and users, and -allow the maintainers to devote more time to the projects, `please -donate today`_. - -.. _please donate today: https://palletsprojects.com/donate - +allow the maintainers to devote more time to the projects, [please +donate today][]. -Links ------ +[please donate today]: https://palletsprojects.com/donate -- Documentation: https://click.palletsprojects.com/ -- Changes: https://click.palletsprojects.com/changes/ -- PyPI Releases: https://pypi.org/project/click/ -- Source Code: https://github.com/pallets/click -- Issue Tracker: https://github.com/pallets/click/issues -- Chat: https://discord.gg/pallets diff --git a/contrib/python/click/py3/LICENSE.rst b/contrib/python/click/py3/LICENSE.txt index d12a849186..d12a849186 100644 --- a/contrib/python/click/py3/LICENSE.rst +++ b/contrib/python/click/py3/LICENSE.txt diff --git a/contrib/python/click/py3/README.md b/contrib/python/click/py3/README.md new file mode 100644 index 0000000000..1aa055dc04 --- /dev/null +++ b/contrib/python/click/py3/README.md @@ -0,0 +1,52 @@ +# $ click_ + +Click is a Python package for creating beautiful command line interfaces +in a composable way with as little code as necessary. It's the "Command +Line Interface Creation Kit". It's highly configurable but comes with +sensible defaults out of the box. + +It aims to make the process of writing command line tools quick and fun +while also preventing any frustration caused by the inability to +implement an intended CLI API. + +Click in three points: + +- Arbitrary nesting of commands +- Automatic help page generation +- Supports lazy loading of subcommands at runtime + + +## A Simple Example + +```python +import click + +@click.command() +@click.option("--count", default=1, help="Number of greetings.") +@click.option("--name", prompt="Your name", help="The person to greet.") +def hello(count, name): + """Simple program that greets NAME for a total of COUNT times.""" + for _ in range(count): + click.echo(f"Hello, {name}!") + +if __name__ == '__main__': + hello() +``` + +``` +$ python hello.py --count=3 +Your name: Click +Hello, Click! +Hello, Click! +Hello, Click! +``` + + +## Donate + +The Pallets organization develops and supports Click and other popular +packages. In order to grow the community of contributors and users, and +allow the maintainers to devote more time to the projects, [please +donate today][]. + +[please donate today]: https://palletsprojects.com/donate diff --git a/contrib/python/click/py3/README.rst b/contrib/python/click/py3/README.rst deleted file mode 100644 index 76f26978a6..0000000000 --- a/contrib/python/click/py3/README.rst +++ /dev/null @@ -1,78 +0,0 @@ -\$ click\_ -========== - -Click is a Python package for creating beautiful command line interfaces -in a composable way with as little code as necessary. It's the "Command -Line Interface Creation Kit". It's highly configurable but comes with -sensible defaults out of the box. - -It aims to make the process of writing command line tools quick and fun -while also preventing any frustration caused by the inability to -implement an intended CLI API. - -Click in three points: - -- Arbitrary nesting of commands -- Automatic help page generation -- Supports lazy loading of subcommands at runtime - - -Installing ----------- - -Install and update using `pip`_: - -.. code-block:: text - - $ pip install -U click - -.. _pip: https://pip.pypa.io/en/stable/getting-started/ - - -A Simple Example ----------------- - -.. code-block:: python - - import click - - @click.command() - @click.option("--count", default=1, help="Number of greetings.") - @click.option("--name", prompt="Your name", help="The person to greet.") - def hello(count, name): - """Simple program that greets NAME for a total of COUNT times.""" - for _ in range(count): - click.echo(f"Hello, {name}!") - - if __name__ == '__main__': - hello() - -.. code-block:: text - - $ python hello.py --count=3 - Your name: Click - Hello, Click! - Hello, Click! - Hello, Click! - - -Donate ------- - -The Pallets organization develops and supports Click and other popular -packages. In order to grow the community of contributors and users, and -allow the maintainers to devote more time to the projects, `please -donate today`_. - -.. _please donate today: https://palletsprojects.com/donate - - -Links ------ - -- Documentation: https://click.palletsprojects.com/ -- Changes: https://click.palletsprojects.com/changes/ -- PyPI Releases: https://pypi.org/project/click/ -- Source Code: https://github.com/pallets/click -- Issue Tracker: https://github.com/pallets/click/issues -- Chat: https://discord.gg/pallets diff --git a/contrib/python/click/py3/click/__init__.py b/contrib/python/click/py3/click/__init__.py index 9a1dab0489..2610d0e142 100644 --- a/contrib/python/click/py3/click/__init__.py +++ b/contrib/python/click/py3/click/__init__.py @@ -4,6 +4,7 @@ writing command line scripts fun. Unlike other modules, it's based around a simple API that does not come with too much magic and is composable. """ + from .core import Argument as Argument from .core import BaseCommand as BaseCommand from .core import Command as Command @@ -18,6 +19,7 @@ from .decorators import command as command from .decorators import confirmation_option as confirmation_option from .decorators import group as group from .decorators import help_option as help_option +from .decorators import HelpOption as HelpOption from .decorators import make_pass_decorator as make_pass_decorator from .decorators import option as option from .decorators import pass_context as pass_context @@ -70,4 +72,4 @@ from .utils import get_binary_stream as get_binary_stream from .utils import get_text_stream as get_text_stream from .utils import open_file as open_file -__version__ = "8.1.7" +__version__ = "8.1.8" diff --git a/contrib/python/click/py3/click/_compat.py b/contrib/python/click/py3/click/_compat.py index 23f8866598..9153d150ce 100644 --- a/contrib/python/click/py3/click/_compat.py +++ b/contrib/python/click/py3/click/_compat.py @@ -516,7 +516,7 @@ if sys.platform.startswith("win") and WIN: _ansi_stream_wrappers: t.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary() - def auto_wrap_for_ansi( # noqa: F811 + def auto_wrap_for_ansi( stream: t.TextIO, color: t.Optional[bool] = None ) -> t.TextIO: """Support ANSI color and style codes on Windows by wrapping a diff --git a/contrib/python/click/py3/click/_termui_impl.py b/contrib/python/click/py3/click/_termui_impl.py index f744657753..ad9f8f6c93 100644 --- a/contrib/python/click/py3/click/_termui_impl.py +++ b/contrib/python/click/py3/click/_termui_impl.py @@ -3,6 +3,7 @@ This module contains implementations for the termui module. To keep the import time of Click down, some infrequently used functionality is placed in this module and only imported as needed. """ + import contextlib import math import os @@ -11,6 +12,7 @@ import time import typing as t from gettext import gettext as _ from io import StringIO +from shutil import which from types import TracebackType from ._compat import _default_text_stdout @@ -371,31 +373,42 @@ def pager(generator: t.Iterable[str], color: t.Optional[bool] = None) -> None: pager_cmd = (os.environ.get("PAGER", None) or "").strip() if pager_cmd: if WIN: - return _tempfilepager(generator, pager_cmd, color) - return _pipepager(generator, pager_cmd, color) + if _tempfilepager(generator, pager_cmd, color): + return + elif _pipepager(generator, pager_cmd, color): + return if os.environ.get("TERM") in ("dumb", "emacs"): return _nullpager(stdout, generator, color) - if WIN or sys.platform.startswith("os2"): - return _tempfilepager(generator, "more <", color) - if hasattr(os, "system") and os.system("(less) 2>/dev/null") == 0: - return _pipepager(generator, "less", color) + if (WIN or sys.platform.startswith("os2")) and _tempfilepager( + generator, "more", color + ): + return + if _pipepager(generator, "less", color): + return import tempfile fd, filename = tempfile.mkstemp() os.close(fd) try: - if hasattr(os, "system") and os.system(f'more "{filename}"') == 0: - return _pipepager(generator, "more", color) + if _pipepager(generator, "more", color): + return return _nullpager(stdout, generator, color) finally: os.unlink(filename) -def _pipepager(generator: t.Iterable[str], cmd: str, color: t.Optional[bool]) -> None: +def _pipepager(generator: t.Iterable[str], cmd: str, color: t.Optional[bool]) -> bool: """Page through text by feeding it to another program. Invoking a pager through this might support colors. + + Returns True if the command was found, False otherwise and thus another + pager should be attempted. """ + cmd_absolute = which(cmd) + if cmd_absolute is None: + return False + import subprocess env = dict(os.environ) @@ -411,19 +424,25 @@ def _pipepager(generator: t.Iterable[str], cmd: str, color: t.Optional[bool]) -> elif "r" in less_flags or "R" in less_flags: color = True - c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, env=env) - stdin = t.cast(t.BinaryIO, c.stdin) - encoding = get_best_encoding(stdin) + c = subprocess.Popen( + [cmd_absolute], + shell=True, + stdin=subprocess.PIPE, + env=env, + errors="replace", + text=True, + ) + assert c.stdin is not None try: for text in generator: if not color: text = strip_ansi(text) - stdin.write(text.encode(encoding, "replace")) + c.stdin.write(text) except (OSError, KeyboardInterrupt): pass else: - stdin.close() + c.stdin.close() # Less doesn't respect ^C, but catches it for its own UI purposes (aborting # search or other commands inside less). @@ -441,11 +460,25 @@ def _pipepager(generator: t.Iterable[str], cmd: str, color: t.Optional[bool]) -> else: break + return True + def _tempfilepager( - generator: t.Iterable[str], cmd: str, color: t.Optional[bool] -) -> None: - """Page through text by invoking a program on a temporary file.""" + generator: t.Iterable[str], + cmd: str, + color: t.Optional[bool], +) -> bool: + """Page through text by invoking a program on a temporary file. + + Returns True if the command was found, False otherwise and thus another + pager should be attempted. + """ + # Which is necessary for Windows, it is also recommended in the Popen docs. + cmd_absolute = which(cmd) + if cmd_absolute is None: + return False + + import subprocess import tempfile fd, filename = tempfile.mkstemp() @@ -457,11 +490,16 @@ def _tempfilepager( with open_stream(filename, "wb")[0] as f: f.write(text.encode(encoding)) try: - os.system(f'{cmd} "{filename}"') + subprocess.call([cmd_absolute, filename]) + except OSError: + # Command not found + pass finally: os.close(fd) os.unlink(filename) + return True + def _nullpager( stream: t.TextIO, generator: t.Iterable[str], color: t.Optional[bool] @@ -496,7 +534,7 @@ class Editor: if WIN: return "notepad" for editor in "sensible-editor", "vim", "nano": - if os.system(f"which {editor} >/dev/null 2>&1") == 0: + if which(editor) is not None: return editor return "vi" @@ -595,22 +633,33 @@ def open_url(url: str, wait: bool = False, locate: bool = False) -> int: null.close() elif WIN: if locate: - url = _unquote_file(url.replace('"', "")) - args = f'explorer /select,"{url}"' + url = _unquote_file(url) + args = ["explorer", f"/select,{url}"] else: - url = url.replace('"', "") - wait_str = "/WAIT" if wait else "" - args = f'start {wait_str} "" "{url}"' - return os.system(args) + args = ["start"] + if wait: + args.append("/WAIT") + args.append("") + args.append(url) + try: + return subprocess.call(args) + except OSError: + # Command not found + return 127 elif CYGWIN: if locate: - url = os.path.dirname(_unquote_file(url).replace('"', "")) - args = f'cygstart "{url}"' + url = _unquote_file(url) + args = ["cygstart", os.path.dirname(url)] else: - url = url.replace('"', "") - wait_str = "-w" if wait else "" - args = f'cygstart {wait_str} "{url}"' - return os.system(args) + args = ["cygstart"] + if wait: + args.append("-w") + args.append(url) + try: + return subprocess.call(args) + except OSError: + # Command not found + return 127 try: if locate: @@ -698,8 +747,8 @@ if WIN: return rv else: - import tty import termios + import tty @contextlib.contextmanager def raw_terminal() -> t.Iterator[int]: diff --git a/contrib/python/click/py3/click/core.py b/contrib/python/click/py3/click/core.py index cc65e896bf..e6305011ad 100644 --- a/contrib/python/click/py3/click/core.py +++ b/contrib/python/click/py3/click/core.py @@ -39,6 +39,8 @@ from .utils import PacifyFlushWrapper if t.TYPE_CHECKING: import typing_extensions as te + + from .decorators import HelpOption from .shell_completion import CompletionItem F = t.TypeVar("F", bound=t.Callable[..., t.Any]) @@ -115,9 +117,16 @@ def iter_params_for_processing( invocation_order: t.Sequence["Parameter"], declaration_order: t.Sequence["Parameter"], ) -> t.List["Parameter"]: - """Given a sequence of parameters in the order as should be considered - for processing and an iterable of parameters that exist, this returns - a list in the correct order as they should be processed. + """Returns all declared parameters in the order they should be processed. + + The declared parameters are re-shuffled depending on the order in which + they were invoked, as well as the eagerness of each parameters. + + The invocation order takes precedence over the declaration order. I.e. the + order in which the user provided them to the CLI is respected. + + This behavior and its effect on callback evaluation is detailed at: + https://click.palletsprojects.com/en/stable/advanced/#callback-evaluation-order """ def sort_key(item: "Parameter") -> t.Tuple[bool, float]: @@ -383,9 +392,9 @@ class Context: #: An optional normalization function for tokens. This is #: options, choices, commands etc. - self.token_normalize_func: t.Optional[ - t.Callable[[str], str] - ] = token_normalize_func + self.token_normalize_func: t.Optional[t.Callable[[str], str]] = ( + token_normalize_func + ) #: Indicates if resilient parsing is enabled. In that case Click #: will do its best to not cause any failures and default values @@ -624,7 +633,7 @@ class Context: def find_object(self, object_type: t.Type[V]) -> t.Optional[V]: """Finds the closest object of a given type.""" - node: t.Optional["Context"] = self + node: t.Optional[Context] = self while node is not None: if isinstance(node.obj, object_type): @@ -646,14 +655,12 @@ class Context: @t.overload def lookup_default( self, name: str, call: "te.Literal[True]" = True - ) -> t.Optional[t.Any]: - ... + ) -> t.Optional[t.Any]: ... @t.overload def lookup_default( self, name: str, call: "te.Literal[False]" = ... - ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: - ... + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: ... def lookup_default(self, name: str, call: bool = True) -> t.Optional[t.Any]: """Get the default for a parameter from :attr:`default_map`. @@ -713,24 +720,22 @@ class Context: @t.overload def invoke( - __self, # noqa: B902 + __self, __callback: "t.Callable[..., V]", *args: t.Any, **kwargs: t.Any, - ) -> V: - ... + ) -> V: ... @t.overload def invoke( - __self, # noqa: B902 + __self, __callback: "Command", *args: t.Any, **kwargs: t.Any, - ) -> t.Any: - ... + ) -> t.Any: ... def invoke( - __self, # noqa: B902 + __self, __callback: t.Union["Command", "t.Callable[..., V]"], *args: t.Any, **kwargs: t.Any, @@ -782,9 +787,7 @@ class Context: with ctx: return __callback(*args, **kwargs) - def forward( - __self, __cmd: "Command", *args: t.Any, **kwargs: t.Any # noqa: B902 - ) -> t.Any: + def forward(__self, __cmd: "Command", *args: t.Any, **kwargs: t.Any) -> t.Any: """Similar to :meth:`invoke` but fills in default keyword arguments from the current context if the other command expects it. This cannot invoke callbacks directly, only other commands. @@ -936,7 +939,10 @@ class BaseCommand: extra[key] = value ctx = self.context_class( - self, info_name=info_name, parent=parent, **extra # type: ignore + self, # type: ignore[arg-type] + info_name=info_name, + parent=parent, + **extra, ) with ctx.scope(cleanup=False): @@ -971,7 +977,7 @@ class BaseCommand: """ from click.shell_completion import CompletionItem - results: t.List["CompletionItem"] = [] + results: t.List[CompletionItem] = [] while ctx.parent is not None: ctx = ctx.parent @@ -993,8 +999,7 @@ class BaseCommand: complete_var: t.Optional[str] = None, standalone_mode: "te.Literal[True]" = True, **extra: t.Any, - ) -> "te.NoReturn": - ... + ) -> "te.NoReturn": ... @t.overload def main( @@ -1004,8 +1009,7 @@ class BaseCommand: complete_var: t.Optional[str] = None, standalone_mode: bool = ..., **extra: t.Any, - ) -> t.Any: - ... + ) -> t.Any: ... def main( self, @@ -1221,12 +1225,13 @@ class Command(BaseCommand): #: the list of parameters for this command in the order they #: should show up in the help page and execute. Eager parameters #: will automatically be handled before non eager ones. - self.params: t.List["Parameter"] = params or [] + self.params: t.List[Parameter] = params or [] self.help = help self.epilog = epilog self.options_metavar = options_metavar self.short_help = short_help self.add_help_option = add_help_option + self._help_option: t.Optional[HelpOption] = None self.no_args_is_help = no_args_is_help self.hidden = hidden self.deprecated = deprecated @@ -1289,25 +1294,29 @@ class Command(BaseCommand): return list(all_names) def get_help_option(self, ctx: Context) -> t.Optional["Option"]: - """Returns the help option object.""" + """Returns the help option object. + + Unless ``add_help_option`` is ``False``. + + .. versionchanged:: 8.1.8 + The help option is now cached to avoid creating it multiple times. + """ help_options = self.get_help_option_names(ctx) if not help_options or not self.add_help_option: return None - def show_help(ctx: Context, param: "Parameter", value: str) -> None: - if value and not ctx.resilient_parsing: - echo(ctx.get_help(), color=ctx.color) - ctx.exit() - - return Option( - help_options, - is_flag=True, - is_eager=True, - expose_value=False, - callback=show_help, - help=_("Show this message and exit."), - ) + # Cache the help option object in private _help_option attribute to + # avoid creating it multiple times. Not doing this will break the + # callback odering by iter_params_for_processing(), which relies on + # object comparison. + if self._help_option is None: + # Avoid circular import. + from .decorators import HelpOption + + self._help_option = HelpOption(help_options) + + return self._help_option def make_parser(self, ctx: Context) -> OptionParser: """Creates the underlying option parser for this command.""" @@ -1444,7 +1453,7 @@ class Command(BaseCommand): """ from click.shell_completion import CompletionItem - results: t.List["CompletionItem"] = [] + results: t.List[CompletionItem] = [] if incomplete and not incomplete[0].isalnum(): for param in self.get_params(ctx): @@ -1604,7 +1613,7 @@ class MultiCommand(Command): return f(inner, *args, **kwargs) self._result_callback = rv = update_wrapper(t.cast(F, function), f) - return rv + return rv # type: ignore[return-value] return decorator @@ -1843,14 +1852,12 @@ class Group(MultiCommand): self.commands[name] = cmd @t.overload - def command(self, __func: t.Callable[..., t.Any]) -> Command: - ... + def command(self, __func: t.Callable[..., t.Any]) -> Command: ... @t.overload def command( self, *args: t.Any, **kwargs: t.Any - ) -> t.Callable[[t.Callable[..., t.Any]], Command]: - ... + ) -> t.Callable[[t.Callable[..., t.Any]], Command]: ... def command( self, *args: t.Any, **kwargs: t.Any @@ -1894,14 +1901,12 @@ class Group(MultiCommand): return decorator @t.overload - def group(self, __func: t.Callable[..., t.Any]) -> "Group": - ... + def group(self, __func: t.Callable[..., t.Any]) -> "Group": ... @t.overload def group( self, *args: t.Any, **kwargs: t.Any - ) -> t.Callable[[t.Callable[..., t.Any]], "Group"]: - ... + ) -> t.Callable[[t.Callable[..., t.Any]], "Group"]: ... def group( self, *args: t.Any, **kwargs: t.Any @@ -2227,14 +2232,12 @@ class Parameter: @t.overload def get_default( self, ctx: Context, call: "te.Literal[True]" = True - ) -> t.Optional[t.Any]: - ... + ) -> t.Optional[t.Any]: ... @t.overload def get_default( self, ctx: Context, call: bool = ... - ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: - ... + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: ... def get_default( self, ctx: Context, call: bool = True @@ -2681,7 +2684,9 @@ class Option(Parameter): if name is None: if not expose_value: return None, opts, secondary_opts - raise TypeError("Could not determine name for option") + raise TypeError( + f"Could not determine name for option with declarations {decls!r}" + ) if not opts and not secondary_opts: raise TypeError( @@ -2810,10 +2815,12 @@ class Option(Parameter): # For boolean flags that have distinct True/False opts, # use the opt without prefix instead of the value. default_string = split_opt( - (self.opts if self.default else self.secondary_opts)[0] + (self.opts if default_value else self.secondary_opts)[0] )[1] elif self.is_bool_flag and not self.secondary_opts and not default_value: default_string = "" + elif default_value == "": + default_string = '""' else: default_string = str(default_value) @@ -2842,14 +2849,12 @@ class Option(Parameter): @t.overload def get_default( self, ctx: Context, call: "te.Literal[True]" = True - ) -> t.Optional[t.Any]: - ... + ) -> t.Optional[t.Any]: ... @t.overload def get_default( self, ctx: Context, call: bool = ... - ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: - ... + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: ... def get_default( self, ctx: Context, call: bool = True @@ -3021,7 +3026,7 @@ class Argument(Parameter): if not decls: if not expose_value: return None, [], [] - raise TypeError("Could not determine name for argument") + raise TypeError("Argument is marked as exposed, but does not have a name.") if len(decls) == 1: name = arg = decls[0] name = name.replace("-", "_").lower() diff --git a/contrib/python/click/py3/click/decorators.py b/contrib/python/click/py3/click/decorators.py index d9bba9502c..bcf8906e70 100644 --- a/contrib/python/click/py3/click/decorators.py +++ b/contrib/python/click/py3/click/decorators.py @@ -93,7 +93,7 @@ def make_pass_decorator( return update_wrapper(new_func, f) - return decorator # type: ignore[return-value] + return decorator def pass_meta_key( @@ -126,7 +126,7 @@ def pass_meta_key( f"Decorator that passes {doc_description} as the first argument" " to the decorated function." ) - return decorator # type: ignore[return-value] + return decorator CmdType = t.TypeVar("CmdType", bound=Command) @@ -134,8 +134,7 @@ CmdType = t.TypeVar("CmdType", bound=Command) # variant: no call, directly as decorator for a function. @t.overload -def command(name: _AnyCallable) -> Command: - ... +def command(name: _AnyCallable) -> Command: ... # variant: with positional name and with positional or keyword cls argument: @@ -145,8 +144,7 @@ def command( name: t.Optional[str], cls: t.Type[CmdType], **attrs: t.Any, -) -> t.Callable[[_AnyCallable], CmdType]: - ... +) -> t.Callable[[_AnyCallable], CmdType]: ... # variant: name omitted, cls _must_ be a keyword argument, @command(cls=CommandCls, ...) @@ -156,16 +154,14 @@ def command( *, cls: t.Type[CmdType], **attrs: t.Any, -) -> t.Callable[[_AnyCallable], CmdType]: - ... +) -> t.Callable[[_AnyCallable], CmdType]: ... # variant: with optional string name, no cls argument provided. @t.overload def command( name: t.Optional[str] = ..., cls: None = None, **attrs: t.Any -) -> t.Callable[[_AnyCallable], Command]: - ... +) -> t.Callable[[_AnyCallable], Command]: ... def command( @@ -255,8 +251,7 @@ GrpType = t.TypeVar("GrpType", bound=Group) # variant: no call, directly as decorator for a function. @t.overload -def group(name: _AnyCallable) -> Group: - ... +def group(name: _AnyCallable) -> Group: ... # variant: with positional name and with positional or keyword cls argument: @@ -266,8 +261,7 @@ def group( name: t.Optional[str], cls: t.Type[GrpType], **attrs: t.Any, -) -> t.Callable[[_AnyCallable], GrpType]: - ... +) -> t.Callable[[_AnyCallable], GrpType]: ... # variant: name omitted, cls _must_ be a keyword argument, @group(cmd=GroupCls, ...) @@ -277,16 +271,14 @@ def group( *, cls: t.Type[GrpType], **attrs: t.Any, -) -> t.Callable[[_AnyCallable], GrpType]: - ... +) -> t.Callable[[_AnyCallable], GrpType]: ... # variant: with optional string name, no cls argument provided. @t.overload def group( name: t.Optional[str] = ..., cls: None = None, **attrs: t.Any -) -> t.Callable[[_AnyCallable], Group]: - ... +) -> t.Callable[[_AnyCallable], Group]: ... def group( @@ -495,7 +487,7 @@ def version_option( metadata: t.Optional[types.ModuleType] try: - from importlib import metadata # type: ignore + from importlib import metadata except ImportError: # Python < 3.8 import importlib_metadata as metadata # type: ignore @@ -530,32 +522,41 @@ def version_option( return option(*param_decls, **kwargs) -def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: - """Add a ``--help`` option which immediately prints the help page +class HelpOption(Option): + """Pre-configured ``--help`` option which immediately prints the help page and exits the program. + """ - This is usually unnecessary, as the ``--help`` option is added to - each command automatically unless ``add_help_option=False`` is - passed. + def __init__( + self, + param_decls: t.Optional[t.Sequence[str]] = None, + **kwargs: t.Any, + ) -> None: + if not param_decls: + param_decls = ("--help",) - :param param_decls: One or more option names. Defaults to the single - value ``"--help"``. - :param kwargs: Extra arguments are passed to :func:`option`. - """ + kwargs.setdefault("is_flag", True) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("is_eager", True) + kwargs.setdefault("help", _("Show this message and exit.")) + kwargs.setdefault("callback", self.show_help) - def callback(ctx: Context, param: Parameter, value: bool) -> None: - if not value or ctx.resilient_parsing: - return + super().__init__(param_decls, **kwargs) - echo(ctx.get_help(), color=ctx.color) - ctx.exit() + @staticmethod + def show_help(ctx: Context, param: Parameter, value: bool) -> None: + """Callback that print the help page on ``<stdout>`` and exits.""" + if value and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() - if not param_decls: - param_decls = ("--help",) - kwargs.setdefault("is_flag", True) - kwargs.setdefault("expose_value", False) - kwargs.setdefault("is_eager", True) - kwargs.setdefault("help", _("Show this message and exit.")) - kwargs["callback"] = callback +def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Decorator for the pre-configured ``--help`` option defined above. + + :param param_decls: One or more option names. Defaults to the single + value ``"--help"``. + :param kwargs: Extra arguments are passed to :func:`option`. + """ + kwargs.setdefault("cls", HelpOption) return option(*param_decls, **kwargs) diff --git a/contrib/python/click/py3/click/exceptions.py b/contrib/python/click/py3/click/exceptions.py index fe68a3613f..0b8315166e 100644 --- a/contrib/python/click/py3/click/exceptions.py +++ b/contrib/python/click/py3/click/exceptions.py @@ -3,6 +3,7 @@ from gettext import gettext as _ from gettext import ngettext from ._compat import get_text_stderr +from .globals import resolve_color_default from .utils import echo from .utils import format_filename @@ -13,7 +14,7 @@ if t.TYPE_CHECKING: def _join_param_hints( - param_hint: t.Optional[t.Union[t.Sequence[str], str]] + param_hint: t.Optional[t.Union[t.Sequence[str], str]], ) -> t.Optional[str]: if param_hint is not None and not isinstance(param_hint, str): return " / ".join(repr(x) for x in param_hint) @@ -29,6 +30,9 @@ class ClickException(Exception): def __init__(self, message: str) -> None: super().__init__(message) + # The context will be removed by the time we print the message, so cache + # the color settings here to be used later on (in `show`) + self.show_color: t.Optional[bool] = resolve_color_default() self.message = message def format_message(self) -> str: @@ -41,7 +45,11 @@ class ClickException(Exception): if file is None: file = get_text_stderr() - echo(_("Error: {message}").format(message=self.format_message()), file=file) + echo( + _("Error: {message}").format(message=self.format_message()), + file=file, + color=self.show_color, + ) class UsageError(ClickException): @@ -58,7 +66,7 @@ class UsageError(ClickException): def __init__(self, message: str, ctx: t.Optional["Context"] = None) -> None: super().__init__(message) self.ctx = ctx - self.cmd: t.Optional["Command"] = self.ctx.command if self.ctx else None + self.cmd: t.Optional[Command] = self.ctx.command if self.ctx else None def show(self, file: t.Optional[t.IO[t.Any]] = None) -> None: if file is None: diff --git a/contrib/python/click/py3/click/globals.py b/contrib/python/click/py3/click/globals.py index 480058f10d..191e712dbd 100644 --- a/contrib/python/click/py3/click/globals.py +++ b/contrib/python/click/py3/click/globals.py @@ -3,19 +3,18 @@ from threading import local if t.TYPE_CHECKING: import typing_extensions as te + from .core import Context _local = local() @t.overload -def get_current_context(silent: "te.Literal[False]" = False) -> "Context": - ... +def get_current_context(silent: "te.Literal[False]" = False) -> "Context": ... @t.overload -def get_current_context(silent: bool = ...) -> t.Optional["Context"]: - ... +def get_current_context(silent: bool = ...) -> t.Optional["Context"]: ... def get_current_context(silent: bool = False) -> t.Optional["Context"]: diff --git a/contrib/python/click/py3/click/parser.py b/contrib/python/click/py3/click/parser.py index 5fa7adfac8..600b8436d7 100644 --- a/contrib/python/click/py3/click/parser.py +++ b/contrib/python/click/py3/click/parser.py @@ -17,6 +17,7 @@ by the Python Software Foundation. This is limited to code in parser.py. Copyright 2001-2006 Gregory P. Ward. All rights reserved. Copyright 2002-2006 Python Software Foundation. All rights reserved. """ + # This code uses parts of optparse written by Gregory P. Ward and # maintained by the Python Software Foundation. # Copyright 2001-2006 Gregory P. Ward @@ -33,6 +34,7 @@ from .exceptions import UsageError if t.TYPE_CHECKING: import typing_extensions as te + from .core import Argument as CoreArgument from .core import Context from .core import Option as CoreOption @@ -247,7 +249,7 @@ class ParsingState: self.opts: t.Dict[str, t.Any] = {} self.largs: t.List[str] = [] self.rargs = rargs - self.order: t.List["CoreParameter"] = [] + self.order: t.List[CoreParameter] = [] class OptionParser: diff --git a/contrib/python/click/py3/click/shell_completion.py b/contrib/python/click/py3/click/shell_completion.py index dc9e00b9b0..07d0f09bac 100644 --- a/contrib/python/click/py3/click/shell_completion.py +++ b/contrib/python/click/py3/click/shell_completion.py @@ -303,12 +303,19 @@ class BashComplete(ShellComplete): @staticmethod def _check_version() -> None: + import shutil import subprocess - output = subprocess.run( - ["bash", "-c", 'echo "${BASH_VERSION}"'], stdout=subprocess.PIPE - ) - match = re.search(r"^(\d+)\.(\d+)\.\d+", output.stdout.decode()) + bash_exe = shutil.which("bash") + + if bash_exe is None: + match = None + else: + output = subprocess.run( + [bash_exe, "--norc", "-c", 'echo "${BASH_VERSION}"'], + stdout=subprocess.PIPE, + ) + match = re.search(r"^(\d+)\.(\d+)\.\d+", output.stdout.decode()) if match is not None: major, minor = match.groups() diff --git a/contrib/python/click/py3/click/termui.py b/contrib/python/click/py3/click/termui.py index db7a4b2861..c084f19652 100644 --- a/contrib/python/click/py3/click/termui.py +++ b/contrib/python/click/py3/click/termui.py @@ -173,7 +173,7 @@ def prompt( if hide_input: echo(_("Error: The value you entered was invalid."), err=err) else: - echo(_("Error: {e.message}").format(e=e), err=err) # noqa: B306 + echo(_("Error: {e.message}").format(e=e), err=err) continue if not confirmation_prompt: return result diff --git a/contrib/python/click/py3/click/testing.py b/contrib/python/click/py3/click/testing.py index e0df0d2a65..772b2159cc 100644 --- a/contrib/python/click/py3/click/testing.py +++ b/contrib/python/click/py3/click/testing.py @@ -8,6 +8,7 @@ import tempfile import typing as t from types import TracebackType +from . import _compat from . import formatting from . import termui from . import utils @@ -311,10 +312,12 @@ class CliRunner: old_hidden_prompt_func = termui.hidden_prompt_func old__getchar_func = termui._getchar old_should_strip_ansi = utils.should_strip_ansi # type: ignore + old__compat_should_strip_ansi = _compat.should_strip_ansi termui.visible_prompt_func = visible_input termui.hidden_prompt_func = hidden_input termui._getchar = _getchar utils.should_strip_ansi = should_strip_ansi # type: ignore + _compat.should_strip_ansi = should_strip_ansi old_env = {} try: @@ -344,6 +347,7 @@ class CliRunner: termui.hidden_prompt_func = old_hidden_prompt_func termui._getchar = old__getchar_func utils.should_strip_ansi = old_should_strip_ansi # type: ignore + _compat.should_strip_ansi = old__compat_should_strip_ansi formatting.FORCED_WIDTH = old_forced_width def invoke( @@ -475,5 +479,5 @@ class CliRunner: if temp_dir is None: try: shutil.rmtree(dt) - except OSError: # noqa: B014 + except OSError: pass diff --git a/contrib/python/click/py3/click/types.py b/contrib/python/click/py3/click/types.py index 2b1d1797f2..a70fd58ce2 100644 --- a/contrib/python/click/py3/click/types.py +++ b/contrib/python/click/py3/click/types.py @@ -15,6 +15,7 @@ from .utils import safecall if t.TYPE_CHECKING: import typing_extensions as te + from .core import Context from .core import Parameter from .shell_completion import CompletionItem @@ -658,12 +659,15 @@ class File(ParamType): will not be held open until first IO. lazy is mainly useful when opening for writing to avoid creating the file until it is needed. - Starting with Click 2.0, files can also be opened atomically in which - case all writes go into a separate file in the same folder and upon - completion the file will be moved over to the original location. This - is useful if a file regularly read by other users is modified. + Files can also be opened atomically in which case all writes go into a + separate file in the same folder and upon completion the file will + be moved over to the original location. This is useful if a file + regularly read by other users is modified. See :ref:`file-args` for more information. + + .. versionchanged:: 2.0 + Added the ``atomic`` parameter. """ name = "filename" @@ -737,7 +741,7 @@ class File(ParamType): ctx.call_on_close(safecall(f.flush)) return f - except OSError as e: # noqa: B014 + except OSError as e: self.fail(f"'{format_filename(value)}': {e.strerror}", param, ctx) def shell_complete( @@ -891,7 +895,7 @@ class Path(ParamType): ) if not self.dir_okay and stat.S_ISDIR(st.st_mode): self.fail( - _("{name} '{filename}' is a directory.").format( + _("{name} {filename!r} is a directory.").format( name=self.name.title(), filename=format_filename(value) ), param, diff --git a/contrib/python/click/py3/click/utils.py b/contrib/python/click/py3/click/utils.py index d536434f0b..836c6f21a0 100644 --- a/contrib/python/click/py3/click/utils.py +++ b/contrib/python/click/py3/click/utils.py @@ -156,7 +156,7 @@ class LazyFile: rv, self.should_close = open_stream( self.name, self.mode, self.encoding, self.errors, atomic=self.atomic ) - except OSError as e: # noqa: E402 + except OSError as e: from .exceptions import FileError raise FileError(self.name, hint=e.strerror) from e @@ -311,7 +311,7 @@ def echo( out = strip_ansi(out) elif WIN: if auto_wrap_for_ansi is not None: - file = auto_wrap_for_ansi(file) # type: ignore + file = auto_wrap_for_ansi(file, color) # type: ignore elif not color: out = strip_ansi(out) @@ -353,7 +353,7 @@ def get_text_stream( def open_file( - filename: str, + filename: t.Union[str, "os.PathLike[str]"], mode: str = "r", encoding: t.Optional[str] = None, errors: t.Optional[str] = "strict", @@ -374,7 +374,7 @@ def open_file( with open_file(filename) as f: ... - :param filename: The name of the file to open, or ``'-'`` for + :param filename: The name or Path of the file to open, or ``'-'`` for ``stdin``/``stdout``. :param mode: The mode in which to open the file. :param encoding: The encoding to decode or encode a file opened in @@ -410,7 +410,7 @@ def format_filename( with the replacement character ``�``. Invalid bytes or surrogate escapes will raise an error when written to a - stream with ``errors="strict". This will typically happen with ``stdout`` + stream with ``errors="strict"``. This will typically happen with ``stdout`` when the locale is something like ``en_GB.UTF-8``. Many scenarios *are* safe to write surrogates though, due to PEP 538 and diff --git a/contrib/python/click/py3/patches/01-fix-tests.patch b/contrib/python/click/py3/patches/01-fix-tests.patch index 5851660555..826b504434 100644 --- a/contrib/python/click/py3/patches/01-fix-tests.patch +++ b/contrib/python/click/py3/patches/01-fix-tests.patch @@ -36,7 +36,7 @@ --- contrib/python/click/py3/tests/test_utils.py (index) +++ contrib/python/click/py3/tests/test_utils.py (working tree) @@ -460,2 +460,2 @@ def test_expand_args(monkeypatch): -- assert "setup.cfg" in click.utils._expand_args(["*.cfg"]) +- assert "pyproject.toml" in click.utils._expand_args(["*.toml"]) - assert os.path.join("docs", "conf.py") in click.utils._expand_args(["**/conf.py"]) -+ #assert "setup.cfg" in click.utils._expand_args(["*.cfg"]) ++ #assert "pyproject.toml" in click.utils._expand_args(["*.toml"]) + #assert os.path.join("docs", "conf.py") in click.utils._expand_args(["**/conf.py"]) diff --git a/contrib/python/click/py3/tests/test_commands.py b/contrib/python/click/py3/tests/test_commands.py index ed9d96f3c5..dcf66acefb 100644 --- a/contrib/python/click/py3/tests/test_commands.py +++ b/contrib/python/click/py3/tests/test_commands.py @@ -305,6 +305,138 @@ def test_group_add_command_name(runner): assert result.exit_code == 0 +@pytest.mark.parametrize( + ("invocation_order", "declaration_order", "expected_order"), + [ + # Non-eager options. + ([], ["-a"], ["-a"]), + (["-a"], ["-a"], ["-a"]), + ([], ["-a", "-c"], ["-a", "-c"]), + (["-a"], ["-a", "-c"], ["-a", "-c"]), + (["-c"], ["-a", "-c"], ["-c", "-a"]), + ([], ["-c", "-a"], ["-c", "-a"]), + (["-a"], ["-c", "-a"], ["-a", "-c"]), + (["-c"], ["-c", "-a"], ["-c", "-a"]), + (["-a", "-c"], ["-a", "-c"], ["-a", "-c"]), + (["-c", "-a"], ["-a", "-c"], ["-c", "-a"]), + # Eager options. + ([], ["-b"], ["-b"]), + (["-b"], ["-b"], ["-b"]), + ([], ["-b", "-d"], ["-b", "-d"]), + (["-b"], ["-b", "-d"], ["-b", "-d"]), + (["-d"], ["-b", "-d"], ["-d", "-b"]), + ([], ["-d", "-b"], ["-d", "-b"]), + (["-b"], ["-d", "-b"], ["-b", "-d"]), + (["-d"], ["-d", "-b"], ["-d", "-b"]), + (["-b", "-d"], ["-b", "-d"], ["-b", "-d"]), + (["-d", "-b"], ["-b", "-d"], ["-d", "-b"]), + # Mixed options. + ([], ["-a", "-b", "-c", "-d"], ["-b", "-d", "-a", "-c"]), + (["-a"], ["-a", "-b", "-c", "-d"], ["-b", "-d", "-a", "-c"]), + (["-b"], ["-a", "-b", "-c", "-d"], ["-b", "-d", "-a", "-c"]), + (["-c"], ["-a", "-b", "-c", "-d"], ["-b", "-d", "-c", "-a"]), + (["-d"], ["-a", "-b", "-c", "-d"], ["-d", "-b", "-a", "-c"]), + (["-a", "-b"], ["-a", "-b", "-c", "-d"], ["-b", "-d", "-a", "-c"]), + (["-b", "-a"], ["-a", "-b", "-c", "-d"], ["-b", "-d", "-a", "-c"]), + (["-d", "-c"], ["-a", "-b", "-c", "-d"], ["-d", "-b", "-c", "-a"]), + (["-c", "-d"], ["-a", "-b", "-c", "-d"], ["-d", "-b", "-c", "-a"]), + (["-a", "-b", "-c", "-d"], ["-a", "-b", "-c", "-d"], ["-b", "-d", "-a", "-c"]), + (["-b", "-d", "-a", "-c"], ["-a", "-b", "-c", "-d"], ["-b", "-d", "-a", "-c"]), + ([], ["-b", "-d", "-e", "-a", "-c"], ["-b", "-d", "-e", "-a", "-c"]), + (["-a", "-d"], ["-b", "-d", "-e", "-a", "-c"], ["-d", "-b", "-e", "-a", "-c"]), + (["-c", "-d"], ["-b", "-d", "-e", "-a", "-c"], ["-d", "-b", "-e", "-c", "-a"]), + ], +) +def test_iter_params_for_processing( + invocation_order, declaration_order, expected_order +): + parameters = { + "-a": click.Option(["-a"]), + "-b": click.Option(["-b"], is_eager=True), + "-c": click.Option(["-c"]), + "-d": click.Option(["-d"], is_eager=True), + "-e": click.Option(["-e"], is_eager=True), + } + + invocation_params = [parameters[opt_id] for opt_id in invocation_order] + declaration_params = [parameters[opt_id] for opt_id in declaration_order] + expected_params = [parameters[opt_id] for opt_id in expected_order] + + assert ( + click.core.iter_params_for_processing(invocation_params, declaration_params) + == expected_params + ) + + +def test_help_param_priority(runner): + """Cover the edge-case in which the eagerness of help option was not + respected, because it was internally generated multiple times. + + See: https://github.com/pallets/click/pull/2811 + """ + + def print_and_exit(ctx, param, value): + if value: + click.echo(f"Value of {param.name} is: {value}") + ctx.exit() + + @click.command(context_settings={"help_option_names": ("--my-help",)}) + @click.option("-a", is_flag=True, expose_value=False, callback=print_and_exit) + @click.option( + "-b", is_flag=True, expose_value=False, callback=print_and_exit, is_eager=True + ) + def cli(): + pass + + # --my-help is properly called and stop execution. + result = runner.invoke(cli, ["--my-help"]) + assert "Value of a is: True" not in result.stdout + assert "Value of b is: True" not in result.stdout + assert "--my-help" in result.stdout + assert result.exit_code == 0 + + # -a is properly called and stop execution. + result = runner.invoke(cli, ["-a"]) + assert "Value of a is: True" in result.stdout + assert "Value of b is: True" not in result.stdout + assert "--my-help" not in result.stdout + assert result.exit_code == 0 + + # -a takes precedence over -b and stop execution. + result = runner.invoke(cli, ["-a", "-b"]) + assert "Value of a is: True" not in result.stdout + assert "Value of b is: True" in result.stdout + assert "--my-help" not in result.stdout + assert result.exit_code == 0 + + # --my-help is eager by default so takes precedence over -a and stop + # execution, whatever the order. + for args in [["-a", "--my-help"], ["--my-help", "-a"]]: + result = runner.invoke(cli, args) + assert "Value of a is: True" not in result.stdout + assert "Value of b is: True" not in result.stdout + assert "--my-help" in result.stdout + assert result.exit_code == 0 + + # Both -b and --my-help are eager so they're called in the order they're + # invoked by the user. + result = runner.invoke(cli, ["-b", "--my-help"]) + assert "Value of a is: True" not in result.stdout + assert "Value of b is: True" in result.stdout + assert "--my-help" not in result.stdout + assert result.exit_code == 0 + + # But there was a bug when --my-help is called before -b, because the + # --my-help option created by click via help_option_names is internally + # created twice and is not the same object, breaking the priority order + # produced by iter_params_for_processing. + result = runner.invoke(cli, ["--my-help", "-b"]) + assert "Value of a is: True" not in result.stdout + assert "Value of b is: True" not in result.stdout + assert "--my-help" in result.stdout + assert result.exit_code == 0 + + def test_unprocessed_options(runner): @click.command(context_settings=dict(ignore_unknown_options=True)) @click.argument("args", nargs=-1, type=click.UNPROCESSED) diff --git a/contrib/python/click/py3/tests/test_defaults.py b/contrib/python/click/py3/tests/test_defaults.py index 8ef5ea2922..5c5e168ab6 100644 --- a/contrib/python/click/py3/tests/test_defaults.py +++ b/contrib/python/click/py3/tests/test_defaults.py @@ -5,7 +5,7 @@ def test_basic_defaults(runner): @click.command() @click.option("--foo", default=42, type=click.FLOAT) def cli(foo): - assert type(foo) is float + assert type(foo) is float # noqa E721 click.echo(f"FOO:[{foo}]") result = runner.invoke(cli, []) @@ -18,7 +18,7 @@ def test_multiple_defaults(runner): @click.option("--foo", default=[23, 42], type=click.FLOAT, multiple=True) def cli(foo): for item in foo: - assert type(item) is float + assert type(item) is float # noqa E721 click.echo(item) result = runner.invoke(cli, []) @@ -59,3 +59,28 @@ def test_multiple_flag_default(runner): result = runner.invoke(cli, ["-y", "-n", "-f", "-v", "-q"], standalone_mode=False) assert result.return_value == ((True, False), (True,), (1, -1)) + + +def test_flag_default_map(runner): + """test flag with default map""" + + @click.group() + def cli(): + pass + + @cli.command() + @click.option("--name/--no-name", is_flag=True, show_default=True, help="name flag") + def foo(name): + click.echo(name) + + result = runner.invoke(cli, ["foo"]) + assert "False" in result.output + + result = runner.invoke(cli, ["foo", "--help"]) + assert "default: no-name" in result.output + + result = runner.invoke(cli, ["foo"], default_map={"foo": {"name": True}}) + assert "True" in result.output + + result = runner.invoke(cli, ["foo", "--help"], default_map={"foo": {"name": True}}) + assert "default: name" in result.output diff --git a/contrib/python/click/py3/tests/test_imports.py b/contrib/python/click/py3/tests/test_imports.py index 38a214c873..d6724059d7 100644 --- a/contrib/python/click/py3/tests/test_imports.py +++ b/contrib/python/click/py3/tests/test_imports.py @@ -5,7 +5,6 @@ import sys from click._compat import WIN - IMPORT_TEST = b"""\ import builtins @@ -49,6 +48,7 @@ ALLOWED_IMPORTS = { "typing", "types", "gettext", + "shutil", } if WIN: diff --git a/contrib/python/click/py3/tests/test_normalization.py b/contrib/python/click/py3/tests/test_normalization.py index 32df098e9b..502e654a37 100644 --- a/contrib/python/click/py3/tests/test_normalization.py +++ b/contrib/python/click/py3/tests/test_normalization.py @@ -1,6 +1,5 @@ import click - CONTEXT_SETTINGS = dict(token_normalize_func=lambda x: x.lower()) diff --git a/contrib/python/click/py3/tests/test_options.py b/contrib/python/click/py3/tests/test_options.py index 91249b234f..7397f36676 100644 --- a/contrib/python/click/py3/tests/test_options.py +++ b/contrib/python/click/py3/tests/test_options.py @@ -786,6 +786,14 @@ def test_show_default_string(runner): assert "[default: (unlimited)]" in message +def test_show_default_with_empty_string(runner): + """When show_default is True and default is set to an empty string.""" + opt = click.Option(["--limit"], default="", show_default=True) + ctx = click.Context(click.Command("cli")) + message = opt.get_help_record(ctx)[1] + assert '[default: ""]' in message + + def test_do_not_show_no_default(runner): """When show_default is True and no default is set do not show None.""" opt = click.Option(["--limit"], show_default=True) diff --git a/contrib/python/click/py3/tests/test_testing.py b/contrib/python/click/py3/tests/test_testing.py index 9f294b3a13..0d227f2a0a 100644 --- a/contrib/python/click/py3/tests/test_testing.py +++ b/contrib/python/click/py3/tests/test_testing.py @@ -5,7 +5,7 @@ from io import BytesIO import pytest import click -from click._compat import WIN +from click.exceptions import ClickException from click.testing import CliRunner @@ -184,7 +184,6 @@ def test_catch_exceptions(): assert result.exit_code == 1 -@pytest.mark.skipif(WIN, reason="Test does not make sense on Windows.") def test_with_color(): @click.command() def cli(): @@ -201,6 +200,26 @@ def test_with_color(): assert not result.exception +def test_with_color_errors(): + class CLIError(ClickException): + def format_message(self) -> str: + return click.style(self.message, fg="red") + + @click.command() + def cli(): + raise CLIError("Red error") + + runner = CliRunner() + + result = runner.invoke(cli) + assert result.output == "Error: Red error\n" + assert result.exception + + result = runner.invoke(cli, color=True) + assert result.output == f"Error: {click.style('Red error', fg='red')}\n" + assert result.exception + + def test_with_color_but_pause_not_blocking(): @click.command() def cli(): @@ -321,7 +340,7 @@ def test_stderr(): assert result_mix.stdout == "stdout\nstderr\n" with pytest.raises(ValueError): - result_mix.stderr + result_mix.stderr # noqa B018 @click.command() def cli_empty_stderr(): diff --git a/contrib/python/click/py3/tests/test_types.py b/contrib/python/click/py3/tests/test_types.py index 0a2508882a..79068e1896 100644 --- a/contrib/python/click/py3/tests/test_types.py +++ b/contrib/python/click/py3/tests/test_types.py @@ -1,5 +1,6 @@ import os.path import pathlib +import platform import tempfile import pytest @@ -232,3 +233,14 @@ def test_file_surrogates(type, tmp_path): def test_file_error_surrogates(): message = FileError(filename="\udcff").format_message() assert message == "Could not open file '�': unknown error" + + +@pytest.mark.skipif( + platform.system() == "Windows", reason="Filepath syntax differences." +) +def test_invalid_path_with_esc_sequence(): + with pytest.raises(click.BadParameter) as exc_info: + with tempfile.TemporaryDirectory(prefix="my\ndir") as tempdir: + click.Path(dir_okay=False).convert(tempdir, None, None) + + assert "my\\ndir" in exc_info.value.message diff --git a/contrib/python/click/py3/tests/test_utils.py b/contrib/python/click/py3/tests/test_utils.py index 656d2f9e53..45433c6790 100644 --- a/contrib/python/click/py3/tests/test_utils.py +++ b/contrib/python/click/py3/tests/test_utils.py @@ -36,9 +36,7 @@ def test_echo(runner): def test_echo_custom_file(): - import io - - f = io.StringIO() + f = StringIO() click.echo("hello", file=f) assert f.getvalue() == "hello\n" @@ -181,7 +179,6 @@ def _test_gen_func(): yield "abc" -@pytest.mark.skipif(WIN, reason="Different behavior on windows.") @pytest.mark.parametrize("cat", ["cat", "cat ", "cat "]) @pytest.mark.parametrize( "test", @@ -209,7 +206,6 @@ def test_echo_via_pager(monkeypatch, capfd, cat, test): assert out == expected_output -@pytest.mark.skipif(WIN, reason="Test does not make sense on Windows.") def test_echo_color_flag(monkeypatch, capfd): isatty = True monkeypatch.setattr(click._compat, "isatty", lambda x: isatty) @@ -232,16 +228,23 @@ def test_echo_color_flag(monkeypatch, capfd): assert out == f"{styled_text}\n" isatty = False - click.echo(styled_text) - out, err = capfd.readouterr() - assert out == f"{text}\n" + # Faking isatty() is not enough on Windows; + # the implementation caches the colorama wrapped stream + # so we have to use a new stream for each test + stream = StringIO() + click.echo(styled_text, file=stream) + assert stream.getvalue() == f"{text}\n" + + stream = StringIO() + click.echo(styled_text, file=stream, color=True) + assert stream.getvalue() == f"{styled_text}\n" def test_prompt_cast_default(capfd, monkeypatch): monkeypatch.setattr(sys, "stdin", StringIO("\n")) value = click.prompt("value", default="100", type=int) capfd.readouterr() - assert type(value) is int + assert type(value) is int # noqa E721 @pytest.mark.skipif(WIN, reason="Test too complex to make work windows.") @@ -464,7 +467,7 @@ def test_expand_args(monkeypatch): assert user in click.utils._expand_args(["~"]) monkeypatch.setenv("CLICK_TEST", "hello") assert "hello" in click.utils._expand_args(["$CLICK_TEST"]) - #assert "setup.cfg" in click.utils._expand_args(["*.cfg"]) + #assert "pyproject.toml" in click.utils._expand_args(["*.toml"]) #assert os.path.join("docs", "conf.py") in click.utils._expand_args(["**/conf.py"]) assert "*.not-found" in click.utils._expand_args(["*.not-found"]) # a bad glob pattern, such as a pytest identifier, should return itself diff --git a/contrib/python/click/py3/tests/typing/typing_aliased_group.py b/contrib/python/click/py3/tests/typing/typing_aliased_group.py index ccc706bc53..a1fdac4b22 100644 --- a/contrib/python/click/py3/tests/typing/typing_aliased_group.py +++ b/contrib/python/click/py3/tests/typing/typing_aliased_group.py @@ -1,4 +1,5 @@ """Example from https://click.palletsprojects.com/en/8.1.x/advanced/#command-aliases""" + from __future__ import annotations from typing_extensions import assert_type diff --git a/contrib/python/click/py3/tests/typing/typing_confirmation_option.py b/contrib/python/click/py3/tests/typing/typing_confirmation_option.py index 09ecaa96da..a568a6ad06 100644 --- a/contrib/python/click/py3/tests/typing/typing_confirmation_option.py +++ b/contrib/python/click/py3/tests/typing/typing_confirmation_option.py @@ -1,4 +1,5 @@ """From https://click.palletsprojects.com/en/8.1.x/options/#yes-parameters""" + from typing_extensions import assert_type import click diff --git a/contrib/python/click/py3/tests/typing/typing_options.py b/contrib/python/click/py3/tests/typing/typing_options.py index 6a6611928e..613e8d43de 100644 --- a/contrib/python/click/py3/tests/typing/typing_options.py +++ b/contrib/python/click/py3/tests/typing/typing_options.py @@ -1,4 +1,5 @@ """From https://click.palletsprojects.com/en/8.1.x/quickstart/#adding-parameters""" + from typing_extensions import assert_type import click diff --git a/contrib/python/click/py3/tests/typing/typing_simple_example.py b/contrib/python/click/py3/tests/typing/typing_simple_example.py index 0a94b8cb78..641937c5dc 100644 --- a/contrib/python/click/py3/tests/typing/typing_simple_example.py +++ b/contrib/python/click/py3/tests/typing/typing_simple_example.py @@ -1,4 +1,5 @@ """The simple example from https://github.com/pallets/click#a-simple-example.""" + from typing_extensions import assert_type import click diff --git a/contrib/python/click/py3/tests/typing/typing_version_option.py b/contrib/python/click/py3/tests/typing/typing_version_option.py index fd473a001b..7ff37a75fb 100644 --- a/contrib/python/click/py3/tests/typing/typing_version_option.py +++ b/contrib/python/click/py3/tests/typing/typing_version_option.py @@ -1,6 +1,7 @@ """ From https://click.palletsprojects.com/en/8.1.x/options/#callbacks-and-eager-options. """ + from typing_extensions import assert_type import click diff --git a/contrib/python/click/py3/ya.make b/contrib/python/click/py3/ya.make index 2b33af1eb4..a71cf09a2c 100644 --- a/contrib/python/click/py3/ya.make +++ b/contrib/python/click/py3/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(8.1.7) +VERSION(8.1.8) LICENSE(BSD-3-Clause) |