Click: With argument autocompletion, context is not passed properly

Created on 1 Mar 2018  路  8Comments  路  Source: pallets/click

I used the current master revision:

$ pip3 install git+https://github.com/pallets/click.git@55682f6f5348f5220a557f89c3a796321a52aebf

and an executable file testclick:

#!/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

bug completion

Most helpful comment

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.

All 8 comments

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鈥檛 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鈥檛 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.

# 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()
Was this page helpful?
0 / 5 - 0 ratings