8000 Close contexts created during shell completion #2644. by AndreasBackx · Pull Request #2800 · pallets/click · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Close contexts created during shell completion #2644. #2800

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ Unreleased
- If help is shown because ``no_args_is_help`` is enabled (defaults to ``True``
for groups, ``False`` for commands), the exit code is 2 instead of 0.
:issue:`1489` :pr:`1489`
- Contexts created during shell completion are closed properly, fixing
``ResourceWarning``s when using ``click.File``. :issue:`2644` :pr:`2800`
:pr:`2767`


Version 8.1.8
Expand Down
4 changes: 2 additions & 2 deletions src/click/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -704,8 +704,8 @@ def exit(self, code: int = 0) -> t.NoReturn:
"""Exits the application with a given exit code.

.. versionchanged:: 8.2
Force closing of callbacks registered with
:meth:`call_on_close` before exiting the CLI.
Callbacks and context managers registered with :meth:`call_on_close`
and :meth:`with_resource` are closed before exiting.
"""
self.close()
raise Exit(code)
Expand Down
66 changes: 35 additions & 31 deletions src/click/shell_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,44 +544,48 @@ def _resolve_context(
:param args: List of complete args before the incomplete value.
"""
ctx_args["resilient_parsing"] = True
ctx = cli.make_context(prog_name, args.copy(), **ctx_args)
args = ctx._protected_args + ctx.args
with cli.make_context(prog_name, args.copy(), **ctx_args) as ctx:
args = ctx._protected_args + ctx.args

while args:
command = ctx.command
while args:
command = ctx.command

if isinstance(command, Group):
if not command.chain:
name, cmd, args = command.resolve_command(ctx, args)

if cmd is None:
return ctx

ctx = cmd.make_context(name, args, parent=ctx, resilient_parsing=True)
args = ctx._protected_args + ctx.args
else:
sub_ctx = ctx

while args:
if isinstance(command, Group):
if not command.chain:
name, cmd, args = command.resolve_command(ctx, args)

if cmd is None:
return ctx

sub_ctx = cmd.make_context(
name,
args,
parent=ctx,
allow_extra_args=True,
allow_interspersed_args=False,
resilient_parsing=True,
)
args = sub_ctx.args

ctx = sub_ctx
args = [*sub_ctx._protected_args, *sub_ctx.args]
else:
break
with cmd.make_context(
name, args, parent=ctx, resilient_parsing=True
) as sub_ctx:
args = ctx._protected_args + ctx.args
ctx = sub_ctx
else:
sub_ctx = ctx

while args:
name, cmd, args = command.resolve_command(ctx, args)

if cmd is None:
return ctx

with cmd.make_context(
name,
args,
parent=ctx,
allow_extra_args=True,
allow_interspersed_args=False,
resilient_parsing=True,
) as sub_sub_ctx:
args = sub_ctx.args
sub_ctx = sub_sub_ctx

ctx = sub_ctx
args = [*sub_ctx._protected_args, *sub_ctx.args]
else:
break

return ctx

Expand Down
26 changes: 26 additions & 0 deletions tests/test_shell_completion.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import warnings

import pytest

import click.shell_completion
Expand Down Expand Up @@ -414,3 +416,27 @@ class MyshComplete(ShellComplete):
# Using `add_completion_class` as a decorator adds the new shell immediately
assert "mysh" in click.shell_completion._available_shells
assert click.shell_completion._available_shells["mysh"] is MyshComplete


# Don't make the ResourceWarning give an error
@pytest.mark.filterwarnings("default")
def test_files_closed(runner) -> None:
with runner.isolated_filesystem():
config_file = "foo.txt"
with open(config_file, "w") as f:
f.write("bar")

@click.group()
@click.option(
"--config-file",
default=config_file,
type=click.File(mode="r"),
)
@click.pass_context
def cli(ctx, config_file):
pass

with warnings.catch_warnings(record=True) as current_warnings:
assert not current_warnings, "There should be no warnings to start"
_get_completions(cli, args=[], incomplete="")
assert not current_warnings, "There should be no warnings after either"
0