Invoke: Positional arguments not described in help

Created on 30 May 2017  ·  6Comments  ·  Source: pyinvoke/invoke

$ cat tasks.py
from invoke import task

@task
def hi(ctx, name):
    print("Hi {0}!".format(name))


$ invoke --help hi
Usage: inv[oke] [--core-opts] hi [--options] [other tasks here ...]

Docstring:
  none

Options:
  -n STRING, --name=STRING


$ invoke hi
'hi' did not receive all required positional arguments!

The auto-generated usage does not indicate that a positional argument is required. hi [--options] [other tasks here ...] seems to imply the opposite - that all arguments are optional.

In addition, we should be able to print the required positional arguments when we do not receive all of them.

Bug Core CLI behavior Low-hanging Fruit Needs investigation Needs patch

Most helpful comment

I don't think the above commit fully fixes this bug. If you don't supply sufficient positional arguments, the help flag won't work properly, either. Here's an example:

If you have a task, something like this that was written by one of my coworkers, @ofek:

@task(help={
    'package': 'The package to upgrade throughout the integrations',
    'version': 'The version of the package to pin',
    'verbose': 'Whether or not to produce output',
})
def upgrade(ctx, package, version, verbose=False):
    """
    Do Something
    """
    ...

And you do not supply the positional arguments, the task will just fail:

❯❯❯ invoke upgrade -h
'upgrade' did not receive all required positional arguments!

If you put help before the task, it will print out normally:

❯❯❯ invoke -h upgrade
Usage: inv[oke] [--core-opts] upgrade [--options] [other tasks here ...]

Docstring:
  Do Something

Options:
  -e, --verbose                 Whether or not to produce output
  -p STRING, --package=STRING   The package to upgrade throughout the integrations
  -v STRING, --version=STRING   The version of the package to pin

All 6 comments

Good catch, this feels like a bug to me. Thanks!

+1

To clarify, I think this change should both:

  • add more info to --help output re: positional arguments
  • display --help output in the situation causing us to complain about positional arguments, automatically (this comes up a lot actually.)

I've posted a pull request for this issue at #504

I don't think the above commit fully fixes this bug. If you don't supply sufficient positional arguments, the help flag won't work properly, either. Here's an example:

If you have a task, something like this that was written by one of my coworkers, @ofek:

@task(help={
    'package': 'The package to upgrade throughout the integrations',
    'version': 'The version of the package to pin',
    'verbose': 'Whether or not to produce output',
})
def upgrade(ctx, package, version, verbose=False):
    """
    Do Something
    """
    ...

And you do not supply the positional arguments, the task will just fail:

❯❯❯ invoke upgrade -h
'upgrade' did not receive all required positional arguments!

If you put help before the task, it will print out normally:

❯❯❯ invoke -h upgrade
Usage: inv[oke] [--core-opts] upgrade [--options] [other tasks here ...]

Docstring:
  Do Something

Options:
  -e, --verbose                 Whether or not to produce output
  -p STRING, --package=STRING   The package to upgrade throughout the integrations
  -v STRING, --version=STRING   The version of the package to pin

May I ask is this one bug too? (i mean if there not have args, but there will raise error?)

code (invoke-0.22.1-py2.7.egg\invoke\tasks.py function: def argspec(self, body)):
...
print "+++arg_names", arg_names # add for debug
matched_args = [reversed(x) for x in [spec.args, spec.defaults or []]]
spec_dict = dict(zip_longest(*matched_args, fillvalue=NO_DEFAULT))
# Pop context argument
try:
context_arg = arg_names.pop(0)
except IndexError:
# TODO: see TODO under __call__, this should be same type
raise TypeError("Tasks must have an initial Context argument!")
del spec_dict[context_arg]
return arg_names, spec_dict

output:
E:\github\RIDE>inv --list
+++arg_names ['args']
+++context_arg args
+++arg_names ['test_filter']
+++context_arg test_filter
+++arg_names ['upgrade']
+++context_arg upgrade
+++arg_names []
Traceback (most recent call last):
File "C:\Python27\Scripts\inv-script.py", line 11, in
load_entry_point('invoke==0.22.1', 'console_scripts', 'inv')()
File "C:\Python27\lib\site-packages\invoke-0.22.1-py2.7.egg\invoke\program.py"
, line 282, in run
self.parse_collection()
File "C:\Python27\lib\site-packages\invoke-0.22.1-py2.7.egg\invoke\program.py"
, line 356, in parse_collection
self.load_collection()
File "C:\Python27\lib\site-packages\invoke-0.22.1-py2.7.egg\invoke\program.py"
, line 508, in load_collection
module, parent = loader.load(coll_name)
File "C:\Python27\lib\site-packages\invoke-0.22.1-py2.7.egg\invoke\loader.py",
line 69, in load
module = imp.load_module(name, fd, path, desc)
File "E:\github\RIDE\tasks.py", line 63, in
@task
File "C:\Python27\lib\site-packages\invoke-0.22.1-py2.7.egg\invoke\tasks.py",
line 292, in task
return Task(args[0], **kwargs)
File "C:\Python27\lib\site-packages\invoke-0.22.1-py2.7.egg\invoke\tasks.py",
line 63, in __init__
self.positional = self.fill_implicit_positionals(positional)
File "C:\Python27\lib\site-packages\invoke-0.22.1-py2.7.egg\invoke\tasks.py",
line 155, in fill_implicit_positionals
args, spec_dict = self.argspec(self.body)
File "C:\Python27\lib\site-packages\invoke-0.22.1-py2.7.egg\invoke\tasks.py",
line 149, in argspec
raise TypeError("Tasks must have an initial Context argument!")
TypeError: Tasks must have an initial Context argument!

Was this page helpful?
0 / 5 - 0 ratings