app is not working out of the box
luiscachog opened this issue · comments
Hi,
Excellent idea, just it is not working out of the box.
I've installed the pyapp
running:
cd pyapp
pip install .
Then the error comes when I run the app with no arguments:
pyapp
Traceback (most recent call last):
File "/Users/luis/.virtualenvs/ca-env/bin/pyapp", line 8, in <module>
sys.exit(main())
File "/Users/luis/.virtualenvs/ca-env/lib/python3.7/site-packages/pyapp/cli.py", line 31, in main
command = args.command
AttributeError: 'Namespace' object has no attribute 'command'
However, if I run the app with the correct command hello
, I believe the app works:
pyapp hello
echo $?
0
Now, if I added command as an argument on the parent parser:
parser.add_argument("command", action="store",help="test command")
The app runs as expected with no arguments:
pyapp
usage: pyapp [-h] [-c CONFIG] [-v] [-w WARN] command {hello} ...
pyapp: error: the following arguments are required: command
But generates errors with the command argument hello
:
pyapp hello
Traceback (most recent call last):
File "/usr/local/Cellar/python/3.7.6_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/inspect.py", line 1126, in getfullargspec
sigcls=Signature)
File "/usr/local/Cellar/python/3.7.6_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/inspect.py", line 2208, in _signature_from_callable
raise TypeError('{!r} is not a callable object'.format(obj))
TypeError: 'hello' is not a callable object
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/Users/luis/.virtualenvs/ca-env/bin/pyapp", line 8, in <module>
sys.exit(main())
File "/Users/luis/.virtualenvs/ca-env/lib/python3.7/site-packages/pyapp/cli.py", line 33, in main
spec = getfullargspec(command)
File "/usr/local/Cellar/python/3.7.6_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/inspect.py", line 1132, in getfullargspec
raise TypeError('unsupported callable') from ex
TypeError: unsupported callable
I'm new at programming and don't know how to fix it.
Could you take a look, please?
@k4ch0
This template will give you an example of an application that has subcommands. It is not designed to be used without subcommands. Arguably, running it without a subcommand should give you the help message instead of exception, but I guess argparse
doesn't work that way by default. ¯_(ツ)_/¯
If you don't want subcommands, you can simplify cli.py
quite a bit. The examples in the argparse tutorial should be enough.
If you need subcommands and a no-command default action, something like Click might work better for you than argparse
.
@k4ch0
Here's a quick untested example if your application has a single action (no subcommands).
def main(argv=None) -> int:
""" Execute the application CLI.
:param argv: argument list to parse (sys.argv by default)
:return: exit status
"""
args = _args(argv)
logger.start(args.warn or "DEBUG") # can't use default from config yet
logger.debug("starting execution")
config.load(args.config)
config.core.config = args.config
if args.warn:
config.core.logging = args.warn
logger.stop() # clear handlers to prevent duplicate records
logger.start(config.core.logging)
try:
hello(args.name)
except RuntimeError as err:
logger.critical(err)
return 1
logger.debug("successful completion")
return 0
def _args(argv):
""" Parse command line arguments.
:param argv: argument list to parse
"""
parser = ArgumentParser()
parser.add_argument("-c", "--config", action="append",
help="config file [etc/config.yml]")
parser.add_argument("-v", "--version", action="version",
version="{{ cookiecutter.app_name }} {:s}".format(__version__),
help="print version and exit")
parser.add_argument("-w", "--warn", default="WARN",
help="logger warning level [WARN]")
parser.add_argument("name", nargs="?", default="World", help="name for greeting message")
args = parser.parse_args(argv)
if not args.config:
# Don't specify this as an argument default or else it will always be
# included in the list.
args.config = "etc/config.yml"
return args
Thank you for your answer @mdklatt. I need the subcommands but also when I run the app with no command I need to show the help message and not an exception.
@k4ch0
For starters, if you run myapp --help
without a subcommand you will get the help message.
If you want the exact behavior you describe, I think this will work (again, untested); see ArgumentParser.print_help():
def _args(argv):
""" Parse command line arguments.
:param argv: argument list to parse
"""
parser = ArgumentParser()
parser.add_argument("-c", "--config", action="append",
help="config file [etc/config.yml]")
parser.add_argument("-v", "--version", action="version",
version="{{ cookiecutter.app_name }} {:s}".format(__version__),
help="print version and exit")
parser.add_argument("-w", "--warn", default="WARN",
help="logger warning level [WARN]")
common = ArgumentParser(add_help=False) # common subcommand arguments
common.add_argument("--name", "-n", default="World", help="greeting name")
subparsers = parser.add_subparsers(title="subcommands")
_hello(subparsers, common)
args = parser.parse_args(argv)
if not args.command:
# User didn't enter a subcommand, display help message.
parser.print_help()
if not args.config:
# Don't specify this as an argument default or else it will always be
# included in the list.
args.config = "etc/config.yml"
return args
Hi @mdklatt,
Thank you for the update, after some debugging, I come with the next solution:
def _args(argv):
""" Parse command line arguments.
:param argv: argument list to parse
"""
parser = ArgumentParser()
parser.add_argument("-c", "--config", action="append",
help="config file [etc/config.yml]")
parser.add_argument("-v", "--version", action="version",
version="{{ cookiecutter.app_name }} {:s}".format(__version__),
help="print version and exit")
parser.add_argument("-w", "--warn", default="WARN",
help="logger warning level [WARN]")
common = ArgumentParser(add_help=False) # common subcommand arguments
common.add_argument("--name", "-n", default="World", help="greeting name")
subparsers = parser.add_subparsers(title="subcommands")
_hello(subparsers, common)
args = parser.parse_args(argv)
if not hasattr(args, 'command'):
parser.print_help()
exit(1)
if not args.config:
# Don't specify this as an argument default or else it will always be
# included in the list.
args.config = "etc/config.yml"
return args
Issue resolved by commit bbaf7b2.