Repository: pallets/click (python)

Issue:
With argument autocompletion, context is not passed properly
I used the current master revision:

    $ pip3 install git+https://github.com/pallets/click.git@55682f6f5348f5220a557f89c3a796321a52aebf

and an executable file `testclick`:

```python
#!/usr/bin/env python3
import click

def _complete(ctx, args, incomplete):
    return ctx.obj['completions']

@click.group()
@click.pass_context
def entrypoint(ctx):
    pass

@entrypoint.command()
@click.argument('arg', type=click.STRING, autocompletion=_complete)
@click.pass_context
def subcommand(ctx, arg):
    print('arg={}'.format(arg))

entrypoint(obj={'completions': ['abc', 'def', 'ghi', ]})
```

and enabled bash completion with 

    eval "$(_TESTCLICK_COMPLETE=source ./testclick)"

Now I am getting an error when trying to use autocompletion:

```
    $ ./testclick subcommand <TAB>Traceback (most recent call last):
  File "./testclick", line 23, in <module>
    entrypoint(obj={'completions': ['abc', 'def', 'ghi', ]})
  File "/home/user/testclick/venv/lib/python3.6/site-packages/click/core.py", line 731, in __call__
    return self.main(*args, **kwargs)
  File "/home/user/testclick/venv/lib/python3.6/site-packages/click/core.py", line 701, in main
    _bashcomplete(self, prog_name, complete_var)
  File "/home/user/testclick/venv/lib/python3.6/site-packages/click/core.py", line 46, in _bashcomplete
    if bashcomplete(cmd, prog_name, complete_var, complete_instr):
  File "/home/user/testclick/venv/lib/python3.6/site-packages/click/_bashcomplete.py", line 216, in bashcomplete
    return do_complete(cli, prog_name)
  File "/home/user/testclick/venv/lib/python3.6/site-packages/click/_bashcomplete.py", line 205, in do_complete
    for item in get_choices(cli, prog_name, args, incomplete):
  File "/home/user/testclick/venv/lib/python3.6/site-packages/click/_bashcomplete.py", line 186, in get_choices
    ctx, all_args, incomplete, param))
  File "/home/user/testclick/venv/lib/python3.6/site-packages/click/_bashcomplete.py", line 124, in get_user_autocompletions
    incomplete=incomplete)
  File "./testclick", line 7, in _complete
    return ctx.obj['completions']
TypeError: 'NoneType' object is not subscriptable
```

Expected behavior was to get a list of completions `abc def ghi`.

I am using Python 3.6.4

Pull request:
pass extra context settings to completion
The completion system created a context that ignored any extra context settings passed in like `cli(obj={...}, color=False)`. These are now passed to the completion system, so they're reflected in the context that is built for completion.

fixes #942

Checklist:

- [x] Add tests that demonstrate the correct behavior of the change. Tests should fail without the change.
- [x] Add or update relevant docs, in the docs folder and in code.
- [x] Add an entry in `CHANGES.rst` summarizing the change and linking to the issue.
- [x] Add `.. versionchanged::` entries in any relevant code docs.
- [x] Run `pre-commit` hooks and fix any issues.
- [x] Run `pytest` and `tox`, no tests failed.

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 7f5bb5f..f7dd405 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -12,7 +12,6 @@ Unreleased
     parameter. :issue:`1264`, :pr:`1329`
 -   Add an optional parameter to ``ProgressBar.update`` to set the
     ``current_item``. :issue:`1226`, :pr:`1332`
--   Include ``--help`` option in completion. :pr:`1504`
 -   ``version_option`` uses ``importlib.metadata`` (or the
     ``importlib_metadata`` backport) instead of ``pkg_resources``.
     :issue:`1582`
@@ -105,6 +104,10 @@ Unreleased
         ``ShellComplete``. The old name and behavior is deprecated and
         will be removed in 8.1.
 
+-   Extra context settings (``obj=...``, etc.) are passed on to the
+    completion system. :issue:`942`
+-   Include ``--help`` option in completion. :pr:`1504`
+
 
 Version 7.1.2
 -------------
diff --git a/src/click/core.py b/src/click/core.py
index b2ccaa8..3a24f44 100644
--- a/src/click/core.py
+++ b/src/click/core.py
@@ -950,7 +950,7 @@ class BaseCommand:
             prog_name = _detect_program_name()
 
         # Process shell completion requests and exit early.
-        self._main_shell_completion(prog_name, complete_var)
+        self._main_shell_completion(extra, prog_name, complete_var)
 
         try:
             try:
@@ -1000,7 +1000,7 @@ class BaseCommand:
             echo("Aborted!", file=sys.stderr)
             sys.exit(1)
 
-    def _main_shell_completion(self, prog_name, complete_var=None):
+    def _main_shell_completion(self, ctx_args, prog_name, complete_var=None):
         """Check if the shell is asking for tab completion, process
         that, then exit early. Called from :meth:`main` before the
         program is invoked.
@@ -1020,7 +1020,7 @@ class BaseCommand:
 
         from .shell_completion import shell_complete
 
-        rv = shell_complete(self, prog_name, complete_var, instruction)
+        rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction)
         _fast_exit(rv)
 
     def __call__(self, *args, **kwargs):
diff --git a/src/click/shell_completion.py b/src/click/shell_completion.py
index 2ce3eb9..c9664ad 100644
--- a/src/click/shell_completion.py
+++ b/src/click/shell_completion.py
@@ -8,10 +8,12 @@ from .parser import split_arg_string
 from .utils import echo
 
 
-def shell_complete(cli, prog_name, complete_var, instruction):
+def shell_complete(cli, ctx_args, prog_name, complete_var, instruction):
     """Perform shell completion for the given CLI program.
 
     :param cli: Command being called.
+    :param ctx_args: Extra arguments to pass to
+        ``cli.make_context``.
     :param prog_name: Name of the executable in the shell.
     :param complete_var: Name of the environment variable that holds
         the completion instruction.
@@ -25,7 +27,7 @@ def shell_complete(cli, prog_name, complete_var, instruction):
     if comp_cls is None:
         return 1
 
-    comp = comp_cls(cli, prog_name, complete_var)
+    comp = comp_cls(cli, ctx_args, prog_name, complete_var)
 
     if instruction == "source":
         echo(comp.source())
@@ -190,8 +192,9 @@ class ShellComplete:
     be provided by subclasses.
     """
 
-    def __init__(self, cli, prog_name, complete_var):
+    def __init__(self, cli, ctx_args, prog_name, complete_var):
         self.cli = cli
+        self.ctx_args = ctx_args
         self.prog_name = prog_name
         self.complete_var = complete_var
 
@@ -238,7 +241,7 @@ class ShellComplete:
         :param args: List of complete args before the incomplete value.
         :param incomplete: Value being completed. May be empty.
         """
-        ctx = _resolve_context(self.cli, self.prog_name, args)
+        ctx = _resolve_context(self.cli, self.ctx_args, self.prog_name, args)
 
         if ctx is None:
             return []
@@ -446,7 +449,7 @@ def _is_incomplete_option(args, param):
     return last_option is not None and last_option in param.opts
 
 
-def _resolve_context(cli, prog_name, args):
+def _resolve_context(cli, ctx_args, prog_name, args):
     """Produce the context hierarchy starting with the command and
     traversing the complete arguments. This only follows the commands,
     it doesn't trigger input prompts or callbacks.
@@ -455,7 +458,8 @@ def _resolve_context(cli, prog_name, args):
     :param prog_name: Name of the executable in the shell.
     :param args: List of complete args before the incomplete value.
     """
-    ctx = cli.make_context(prog_name, args.copy(), resilient_parsing=True)
+    ctx_args["resilient_parsing"] = True
+    ctx = cli.make_context(prog_name, args.copy(), **ctx_args)
     args = ctx.protected_args + ctx.args
 
     while args:


Test patch:
diff --git a/tests/test_shell_completion.py b/tests/test_shell_completion.py
index 2357cb7..39c0161 100644
--- a/tests/test_shell_completion.py
+++ b/tests/test_shell_completion.py
@@ -14,7 +14,7 @@ from click.types import Path
 
 
 def _get_completions(cli, args, incomplete):
-    comp = ShellComplete(cli, cli.name, "_CLICK_COMPLETE")
+    comp = ShellComplete(cli, {}, cli.name, "_CLICK_COMPLETE")
     return comp.get_completions(args, incomplete)
 
 
@@ -275,3 +275,17 @@ def test_full_complete(runner, shell, env, expect):
     env["_CLI_COMPLETE"] = f"complete_{shell}"
     result = runner.invoke(cli, env=env)
     assert result.output == expect
+
+
+@pytest.mark.usefixtures("_patch_for_completion")
+def test_context_settings(runner):
+    def complete(ctx, param, incomplete):
+        return ctx.obj["choices"]
+
+    cli = Command("cli", params=[Argument("x", shell_complete=complete)])
+    result = runner.invoke(
+        cli,
+        obj={"choices": ["a", "b"]},
+        env={"COMP_WORDS": "", "COMP_CWORD": "0", "_CLI_COMPLETE": "complete_bash"},
+    )
+    assert result.output == "plain,a\nplain,b\n"