Repository: pallets/click

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


Discussion:
Im currently not able to test my idea, so please excuse me if for just thinking out loud. :wink:

Instead of passing the settings dict to `entrypoint` you could try passing it to the `context_settings` kwarg of the `.group` or `.command` function.

Refer to the documentation for details:
 http://click.pocoo.org/5/commands/#context-defaults

@LuckyJosh: Thanks for the hint, but that won’t suffice as a workaround. In the above example, I provided completions as a static dictionary just for the sake of simplicity. In my case, a network connection object would be passed via the context, and the `_complete` function would use it to retrieve possible completions. I don’t think `context_settings` can be used for that.
Alright, in that case I have another idea:
Are you sure that the kwarg `obj` to `entrypoint` should get passed to the context?

After having a glimps at the implementation of `pass_context` I would suspect, that `obj={...}` just get passed to the wrapped function and is not used any further. The context is passed as the first argument to the decorated function without any interaction with the other arguments it would seem.
This would explain the missing (`None`) `ctx.obj` the ErrorMessage is about. 

The source code I am refering to is in `decorators.py`. 
I don't understand what LuckyJosh is trying to say.

The custom context with `obj` attached is normally created in BaseCommand.main(). The problem is that in autocomplete mode, _bashcomplete() gets called before the context-creation code executes, and creates its own context (without `obj`) instead.

It looks to me like we want BaseCommand.main() to create its context in all circumstances, and pass it to _bashcomplete(). Would there be any drawbacks to this?

Possibly related: #930

+1 on this issue. I, too, am trying to get autocomplete working with completions that come from network calls.
To be clear, this issue limits the utility of #755. Until it is fixed, autocompletion callbacks have no clean way to access shared network resources.
anyone found a workaround on this ? 

for now i'm doing things like this to at least init state based on env variables before calling out:

```
def get_entities(ctx, args, incomplete):
    if not hasattr(ctx, 'server'):
        ctx.server = os.environ.get('HASS_SERVER', const.DEFAULT_SERVER)
    
    if not hasattr(ctx, 'token'):
        ctx.token = os.environ.get('HASS_TOKEN')

    if not hasattr(ctx, "timeout"):
        ctx.timeout = os.environ.get('HASS_TIMEOUT', const.DEFAULT_TIMEOUT)

    try:
        x = req(ctx, "get", "states")
    except HTTPError:
        x = None

    entities = []

    if x is not None:
        for entity in x:
            entities.append((entity["entity_id"], ''))

        return [c for c in entities if incomplete in c[0]]
    else:
        return entities
```

this lets the autocompletion work but its unforunate I have to relist the defaults and env parameters here. would be so much nicer if there was a way click could pass the right context or i at least could call something to generate the proper config/context.

thx!
Is there any update on this issue?
I'm facing this issue too.

For now, my workaround is to factor out the `ctx.obj` creation code and call it from both `cli()` and the corresponding autocomplete methods.
```python
# cli() function
@click.group
@click.pass_context
def cli(ctx):
    ctx.obj = create_context_object()

# example autocomplete function
def get_completion(ctx, args, incomplete):
    if ctx.obj is None:
        ctx.obj = create_context_object()
```

Resolving patch:
diff --git a/src/click/core.py b/src/click/core.py
--- a/src/click/core.py
+++ b/src/click/core.py
@@ -950,7 +950,7 @@ def main(
             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 @@ def main(
             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 @@ def _main_shell_completion(self, prog_name, complete_var=None):
 
         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
--- a/src/click/shell_completion.py
+++ b/src/click/shell_completion.py
@@ -8,10 +8,12 @@
 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 @@ def get_completions(self, args, incomplete):
         :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
--- a/tests/test_shell_completion.py
+++ b/tests/test_shell_completion.py
@@ -14,7 +14,7 @@
 
 
 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"