aliles / begins

Command line programs for busy developers.
https://pypi.python.org/pypi/begins
Other
130 stars 27 forks source link

Subcommands ignore name attributes #49

Open jgat opened 10 years ago

jgat commented 10 years ago

When subcommands are created with custom names, the application doesn't respect those names. I've deduced that what's actually happening is the custom name is being registered, but when the subcommand is actually registered in argparse, it uses name (possibly in other places as well, I've only had a cursory look through the source).

An example will demonstrate:

#!/usr/bin/env python
import begin

@begin.subcommand(name='a')
def z():
    print "z"

@begin.subcommand(name='b')
def y():
    print "y"

@begin.start
def run():
    pass

Then, running as eg.py,

$ ./eg.py -h
usage: eg.py [-h] {z,y} ...

optional arguments:
  -h, --help  show this help message and exit

Available subcommands:
  {z,y}
    z
    y

Note that the commands are being sorted by the custom name (as shown here).

$ ./eg.py a
usage: eg.py [-h] {z,y} ...
eg.py: error: argument _subcommand: invalid choice: 'a' (choose from 'z', 'y')

A hacky workaround is to just modify func.__name__ attribute in subcommands.Collector.register; this would only cause problems if the function (and in particular, the function's __name__) was going to be used for something else in addition to being a subcommand. (Which to be honest, is most likely an obscure scenario.)

And then there's also this, which I suspect is an entirely separate issue, probably related to #44; this error does not occur when the custom name is removed:

$ ./eg.py z
Traceback (most recent call last):
  File "./eg.py", line 12, in <module>
    @begin.start
  File "/usr/local/lib/python2.7/site-packages/begin/main.py", line 135, in start
    return _start(func)
  File "/usr/local/lib/python2.7/site-packages/begin/main.py", line 115, in _start
    prog.start()
  File "/usr/local/lib/python2.7/site-packages/begin/main.py", line 54, in start
    collector=self._collector)
  File "/usr/local/lib/python2.7/site-packages/begin/cmdline.py", line 258, in apply_options
    return_value = call_function(subfunc, signature(subfunc), opts)
  File "/usr/local/lib/python2.7/site-packages/funcsigs/__init__.py", line 59, in signature
    raise TypeError('{0!r} is not a callable object'.format(obj))
TypeError: None is not a callable object

Edit: I now believe this is very much related: because the rest of the system is using the function's name instead of the name it's registered with, calls to .get will return None, and then that None gets passed to signature, which causes the TypeError.

jgat commented 10 years ago

The following is a workaround (but maybe not a nice one):

#!/usr/bin/env python
import begin

@begin.subcommand(name='a')
def z():
    print "z"
z.__name__ = 'a'

@begin.subcommand(name='b')
def y():
    print "y"
y.__name__ = 'b'

@begin.start
def run():
    pass

Note that both the name=??? and __name__ = ??? must be the same.