Skip to content

Commit

Permalink
Close contexts created during shell completion pallets#2644. (pallets…
Browse files Browse the repository at this point in the history
…#2800)

Co-authored-by: akhil-vempali <[email protected]>
  • Loading branch information
AndreasBackx and akhil-vempali authored Nov 3, 2024
1 parent d8763b9 commit 0ef55fd
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 33 deletions.
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 comments on commit 0ef55fd

Please sign in to comment.