Argparse using subcommands/subparsers -- AttributeError: 'Namespace' object has no attribute - python

I need to use a python script having the following usages:
script.py ( commands ) ( options )
My problem is how i add arguments for "commands" and "options"?
What i did now is this:
parser = argparse.ArgumentParser()
parser._optionals.title = "Options"
parser.add_argument('-help','--help', action="store_true", dest="help", help='help')
subparsers = parser.add_subparsers(help="All available commands", title="Commands")
parser_start = subparsers.add_parser('start', help='Starts the script', add_help=False)
parser_start._optionals.title = "Options"
parser_start.add_argument('--help', action="store_true", dest="help_start")
parser_start.add_argument('-f', type=str, dest="file", help='simulation file to start')
parser_ls = subparsers.add_parser('ls', help='Lists running simulations', add_help=False)
parser_ls._optionals.title = "Options"
parser_ls.add_argument('--help', action="store_true", dest="help_ls")
parser_ls.add_argument('--all', action="store_true", help='Display all simulations')
parser_stop = subparsers.add_parser('stop', help='Stops simulation', add_help=False)
parser_stop._optionals.title = "Options"
parser_stop.add_argument('--help', action="store_true", dest="help_down")
parser_stop.add_argument('--sim-name', type=str, dest="sim_name")
args = parser.parse_args()
If i try to access args.help_up i receive: AttributeError: 'Namespace' object has no attribute 'help_start'
How do i pass the parser_up, parser_stop and parser_ls to the parse_args?
And how do i access them afterwards?
Objective is to have custom help messages ( which i have atm that is why i disabled the help ) and to run the script like this:
script.py start -f (name of file)
script.py stop --sim-name (name of simulation)
EDIT:
If i add args2 = parser_start.parse_args() i am able to get a read on args2.help_start, but i am not able to find any of the start, ls or down arguments!

I've made a few changes to your code; hopefully it will clarify what's going on:
import argparse
parser = argparse.ArgumentParser()
parser._optionals.title = "Options"
#parser.add_argument('-help','--help', action="store_true", dest="help", help='help')
# conflicts with original help
subparsers = parser.add_subparsers(help="All available commands", title="Commands",
dest='cmd') # NEW
parser_start = subparsers.add_parser('start', help='Starts the script', add_help=False)
parser_start._optionals.title = "Options"
parser_start.add_argument('--help', action="store_true", dest="help_start")
parser_start.add_argument('-f', type=str, dest="file", help='simulation file to start')
parser_ls = subparsers.add_parser('ls', help='Lists running simulations', add_help=False)
parser_ls._optionals.title = "Options"
parser_ls.add_argument('--help', action="store_true", dest="help_ls")
parser_ls.add_argument('--all', action="store_true", help='Display all simulations')
parser_stop = subparsers.add_parser('stop', help='Stops simulation', add_help=False)
parser_stop._optionals.title = "Options"
parser_stop.add_argument('--help', action="store_true", dest="help_down")
parser_stop.add_argument('--sim-name', type=str, dest="sim_name")
args = parser.parse_args()
print(args) # NEW
and sample runs:
0939:~/mypy$ python3 stack62716530.py
Namespace(cmd=None)
0939:~/mypy$ python3 stack62716530.py --help
usage: stack62716530.py [-h] {start,ls,stop} ...
Options:
-h, --help show this help message and exit
Commands:
{start,ls,stop} All available commands
start Starts the script
ls Lists running simulations
stop Stops simulation
0939:~/mypy$ python3 stack62716530.py start
Namespace(cmd='start', file=None, help_start=False)
0939:~/mypy$ python3 stack62716530.py start --help
Namespace(cmd='start', file=None, help_start=True)
and if I add:
if getattr(args, 'help_start',False):
parser_start.print_help()
I get
0940:~/mypy$ python3 stack62716530.py start --help
Namespace(cmd='start', file=None, help_start=True)
usage: stack62716530.py start [--help] [-f FILE]
Options:
--help
-f FILE simulation file to start
The key is that help_start is an attribute only if the start subparser is invoked.
In first read(s) of your code I missed the dest='help_start' parameters. Thus I couldn't tell why you expected to see such an attribute in args.

Related

Adding sub-subcommands to a command

I have a python script that takes input from CLI, parses it and runs the appropriate function:
command subcommand subsubcommand1 --arg1 <value1>
When user doesn't provide subsubcommand I want the script to return an error, saying that at least one of the supported subcommand must be provided, instead it returns:
Namespace object has no attribute func
This is the code:
parser = argparse.ArgumentParser(description='The highlevel command')
parser.add_argument("-v", help="some help text", action='store_true', default=False)
subparsers = parser.add_subparsers(dest='action')
subparsers.required = True
subcommand_parser = subparsers.add_parser('subcommand', help='some help text for the subcommand')
subsub_subparser = subcommand_parser.add_subparsers()
subsubparser1 = subsub_subparser.add_parser('subsubcommand1', help='some help text for the subsubcommand1')
subsubparser1.set_defaults(action='subsubcommand_action', func=mylib.subsub1)
subsubparser1.add_argument('--arg1', required=True, help='arg1')
subsubparser1.add_argument('--arg2', required=False, default='hello', help='arg2')
How can I fix the current error and make the new error show up?
Correcting the omissions mentioned in my comments:
import argparse
parser = argparse.ArgumentParser(description='The highlevel command')
parser.add_argument("-v", help="some help text", action='store_true', default=False)
subparsers = parser.add_subparsers(dest='action')
subparsers.required = True
subcommand_parser = subparsers.add_parser('subcommand', help='some help text for the subcommand')
subsub_subparser = subcommand_parser.add_subparsers(dest='subaction') # EDIT
subsub_subparser.required = True # EDIT
subsubparser1 = subsub_subparser.add_parser('subsubcommand1', help='some help text for the subsubcommand1')
subsubparser1.set_defaults(func="mylib.subsub1") # EDIT
subsubparser1.add_argument('--arg1', required=True, help='arg1')
subsubparser1.add_argument('--arg2', required=False, default='hello', help='arg2')
subsubparser2 = subsub_subparser.add_parser('subsubcommand2') # EDIT
args = parser.parse_args()
print(args) # EDIT
if hasattr(args, 'func'):
print('func: ',args.func)
else:
print('func not defined')
sample runs:
2020:~/mypy$ python3 stack56435945.py
usage: stack56435945.py [-h] [-v] {subcommand} ...
stack56435945.py: error: the following arguments are required: action
2021:~/mypy$ python3 stack56435945.py subcommand
usage: stack56435945.py subcommand [-h] {subsubcommand1,subsubcommand2} ...
stack56435945.py subcommand: error: the following arguments are required: subaction
2021:~/mypy$ python3 stack56435945.py subcommand subsubcommand1 --arg1 foobar
Namespace(action='subcommand', arg1='foobar', arg2='hello', func='mylib.subsub1', subaction='subsubcommand1', v=False)
func: mylib.subsub1
2022:~/mypy$ python3 stack56435945.py subcommand subsubcommand2
Namespace(action='subcommand', subaction='subsubcommand2', v=False)
func not defined

python-daemon + argparse?

I made a script that takes a few arguments to run. Originally it could be run automatically with the argument 'auto', but I'm trying to daemonize it so it will run the run the script with the specified arguments as a daemon. The problem is that python-daemon and argparse don't seem to get along when it comes to deciding who parses what.
parser = argparse.ArgumentParser(usage='pyfilter.py <file> <options> <actions>')
parser.add_argument('file', help='blacklist file containing IPs', type=str)
subparsers = parser.add_subparsers(help='help', dest='action')
parser_update = subparsers.add_parser('update', help='update help')
parser_update.add_argument('-c', '--check', help="check IPs for abuse reports", dest="check", type=str, nargs=1)
parser_blacklist = subparsers.add_parser('blacklist', help='create iptables rules for malicious IPs specified'
'in the provided file')
parser_clear = subparsers.add_parser('clear', help='clear iptables')
parser_auto = subparsers.add_parser('auto', help='automatically run update and blacklist on a loop')
parser_auto.add_argument('-i', '--interval', help='specify the loop interval', dest='interval', type=int, nargs=1)
parser_auto.add_argument('-c', '--check', help="check IPs for abuse reports", dest="check", type=str, nargs=1)
parser.add_argument('-p', '--port', help='specify the port to block', type=int)
parser.add_argument('-v', '--verbose', help='write output to screen', nargs=1)
args = parser.parse_args()
. . .
class pyfilterDaemon():
def __init__(self):
self.stdin_path = '/dev/null'
self.stdout_path = '/dev/tty'
self.stderr_path = '/dev/tty'
self.pidfile_path = '/tmp/pyfilter.pid'
self.pidfile_timeout = 5
def run(self):
while True:
update()
blacklist()
time.sleep(interval)
. . .
def main():
#handle()
d = pyfilterDaemon()
daemon_runner = runner.DaemonRunner(d)
daemon_runner.start()
Here's the commands I've tried to make this work:
root#tfof:~# ./pyfilter.py start
ERROR: File 'start' not found # argparse parsed 'start' accidentally
root#tfof:~# ./pyfilter.py /etc/blacklist.lst -v yes auto
usage: checkfilter.py stop|restart|start # now the argparse arguments are satisfied, but python-daemon is looking for its argument
root#tfof:~# ./pyfilter.py /etc/blacklist.lst -v yes auto start
usage: pyfilter.py <file> <options> <actions>
pyfilter.py: error: unrecognized arguments: start # argparse is trying to parse 'start'
Would it be possible to pass off the 'start' argument to python-daemon or something? Or if I could just get rid of the argparsing it'd be fine, but the 'file' argument is a must.
Argparse takes arguments from sys.argv per default (see here).
It is not surprising that the behaviour you see here is happening,
as you just call the parse_args function with the default arguments.
You can just pass whatever you want to parse to it, instead of sys.argv.
See this question for an example.
So consume whatever you need for python-deamon and then parse the remaining args with argparse.

Using argparse with a specified acceptable option

I am trying to use argparse to accept required command line options. I have defined a function like so
def get_args():
parser = argparse.ArgumentParser(description='Help Desk Calendar Tool')
parser.add_argument('-s', '--start', type=str, required=True, metavar='YYYY-MM-DD')
parser.add_argument('-e','--end', type=str, required=True, metavar='YYYY-MM-DD')
parser.add_argument('-m','--mode', type=str, required=True , metavar='add|del')
args = parser.parse_args()
start = args.start
end = args.end
mode = args.mode
return start,end,mode
What I am trying to do is for the option --mode I would like it to ONLY accept an parameter of either add or del. I could do this from an if statement but was wondering if argparse has a built in way of accomplishing this task. I looked at nargs but wasn't too clear if that's the path I need to go down
I think you are asking about choices:
parser.add_argument('-m','--mode', type=str, required=True, choices=['add', 'del'])
Demo:
$ python test.py -s 10 -e 20 -m invalid
usage: test.py [-h] -m {add,del}
test.py: error: argument -m/--mode: invalid choice: 'invalid' (choose from 'add', 'del')

python's argument parser printing the argument name in upper case

I am trying to write usage/help for my python script using the argparse library.
This is my sample code:
import argparse
parser = argparse.ArgumentParser(
description='My description')
parser.add_argument(
"-r", "--remote",
help="help message")
parser.print_help()
Output:
usage: [-h] [-r REMOTE]
My description
optional arguments:
-h, --help show this help message and exit
-r REMOTE, --remote REMOTE
help message
I have no idea why it is printing REMOTE after the -r and --remote switches in the above output.
Can anyone tell me what am I doing wrong here or what should I do to get rid of it?
You are looking at the metavar; it is autogenerated from the option string to form a placeholder. It tells the user that it is there that they need to fill in a value.
You can set an explicit metavar value with the metavar keyword argument:
When ArgumentParser generates help messages, it needs some way to refer to each expected argument. By default, ArgumentParser objects use the dest value as the “name” of each object. By default, for positional argument actions, the dest value is used directly, and for optional argument actions, the dest value is uppercased.
You see it because your argument takes a value; if you expected it to be a toggle, use action='store_true'; in that case the option defaults to False unless the user specifies the switch.
Demo of the latter:
>>> import argparse
>>> parser = argparse.ArgumentParser(
... description='My description')
>>> parser.add_argument("-r", "--remote", action='store_true', help="help message")
_StoreTrueAction(option_strings=['-r', '--remote'], dest='remote', nargs=0, const=True, default=False, type=None, choices=None, help='help message', metavar=None)
>>> parser.print_help()
usage: [-h] [-r]
My description
optional arguments:
-h, --help show this help message and exit
-r, --remote help message
>>> opts = parser.parse_args([])
>>> opts.remote
False
>>> opts = parser.parse_args(['-r'])
>>> opts.remote
True
You are missing action.
import argparse
parser = argparse.ArgumentParser(
description='My description')
parser.add_argument(
"-r", "--remote", action="store_true", # add action
help="help message")
parser.print_help()
usage: -c [-h] [-r]
My description
optional arguments:
-h, --help show this help message and exit
-r, --remote help message

How to make python argparse mutually exclusive group arguments without prefix?

Python2.7 argparse only accepts optional arguments (prefixed) in mutually exclusive groups:
parser = argparse.ArgumentParser(prog='mydaemon')
action = parser.add_mutually_exclusive_group(required=True)
action.add_argument('--start', action='store_true', help='Starts %(prog)s daemon')
action.add_argument('--stop', action='store_true', help='Stops %(prog)s daemon')
action.add_argument('--restart', action='store_true', help='Restarts %(prog)s daemon')
$ mydaemon -h
usage: mydaemon [-h] (--start | --stop | --restart)
optional arguments:
-h, --help show this help message and exit
--start Starts mydaemon daemon
--stop Stops mydaemon daemon
--restart Restarts mydaemon daemon
Is there a way to make argparse arguments behaves like traditional unix daemon control:
(start | stop | restart) and not (--start | --stop | --restart) ?
from pymotw
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument('-a', action='store_true')
group.add_argument('-b', action='store_true')
print parser.parse_args()
output:
$ python argparse_mutually_exclusive.py -h
usage: argparse_mutually_exclusive.py [-h] [-a | -b]
optional arguments:
-h, --help show this help message and exit
-a
-b
$ python argparse_mutually_exclusive.py -a
Namespace(a=True, b=False)
$ python argparse_mutually_exclusive.py -b
Namespace(a=False, b=True)
$ python argparse_mutually_exclusive.py -a -b
usage: argparse_mutually_exclusive.py [-h] [-a | -b]
argparse_mutually_exclusive.py: error: argument -b: not allowed with argument -a
version2
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='commands')
# A list command
list_parser = subparsers.add_parser('list', help='List contents')
list_parser.add_argument('dirname', action='store', help='Directory to list')
# A create command
create_parser = subparsers.add_parser('create', help='Create a directory')
create_parser.add_argument('dirname', action='store', help='New directory to create')
create_parser.add_argument('--read-only', default=False, action='store_true',
help='Set permissions to prevent writing to the directory',
)
# A delete command
delete_parser = subparsers.add_parser('delete', help='Remove a directory')
delete_parser.add_argument('dirname', action='store', help='The directory to remove')
delete_parser.add_argument('--recursive', '-r', default=False, action='store_true',
help='Remove the contents of the directory, too',
)
print parser.parse_args(['list', 'a s d', ])
>>> Namespace(dirname='a s d')
print parser.parse_args(['list', 'a s d', 'create' ])
>>> error: unrecognized arguments: create
For all the abilities and options in argparse I don't think you'll ever get a "canned" usage string that looks like what you want.
That said, have you looked at sub-parsers since your original post?
Here's a barebones implementation:
import argparse
parser = argparse.ArgumentParser(prog='mydaemon')
sp = parser.add_subparsers()
sp_start = sp.add_parser('start', help='Starts %(prog)s daemon')
sp_stop = sp.add_parser('stop', help='Stops %(prog)s daemon')
sp_restart = sp.add_parser('restart', help='Restarts %(prog)s daemon')
parser.parse_args()
Running this with the -h option yields:
usage: mydaemon [-h] {start,stop,restart} ...
positional arguments:
{start,stop,restart}
start Starts mydaemon daemon
stop Stops mydaemon daemon
restart Restarts mydaemon daemon
One of the benefits of this approach is being able to use set_defaults for each sub-parser to hook up a function directly to the argument. I've also added a "graceful" option for stop and restart:
import argparse
def my_stop(args):
if args.gracefully:
print "Let's try to stop..."
else:
print 'Stop, now!'
parser = argparse.ArgumentParser(prog='mydaemon')
graceful = argparse.ArgumentParser(add_help=False)
graceful.add_argument('-g', '--gracefully', action='store_true', help='tries to terminate the process gracefully')
sp = parser.add_subparsers()
sp_start = sp.add_parser('start', help='Starts %(prog)s daemon')
sp_stop = sp.add_parser('stop', parents=[graceful],
description='Stops the daemon if it is currently running.',
help='Stops %(prog)s daemon')
sp_restart = sp.add_parser('restart', parents=[graceful], help='Restarts %(prog)s daemon')
# Hook subparsers up to functions
sp_stop.set_defaults(func=my_stop)
# Uncomment when my_start() and
# my_restart() are implemented
#
# sp_start.set_defaults(func=my_start)
# sp_restart.set_defaults(func=my_restart)
args = parser.parse_args()
args.func(args)
Showing the "help" message for stop:
$ python mydaemon.py stop -h
usage: mydaemon stop [-h] [-g]
Stops the daemon if it is currently running.
optional arguments:
-h, --help show this help message and exit
-g, --gracefully tries to terminate the process gracefully
Stopping "gracefully":
$ python mydaemon.py stop -g
Let's try to stop...
It sounds like you want a positional argument instead of mutually exclusive options. You can use 'choices' to restrict the possible acceptable options.
parser = ArgumentParser()
parser.add_argument('action', choices=('start', 'stop', 'restart'))
This produces a usage line that looks like this:
usage: foo.py [-h] {start,stop,restart}
Building on Adam's answer... if you wanted to specify a default you could always do the following so they can effectively leave it blank.
import argparse
ActionHelp = """
Start = Starts the daemon (default)
Stop = Stops the daemon
Restart = Restarts the daemon
"""
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('action', nargs = '?', choices=('start', 'stop', 'restart'),
default = 'start', help = ActionHelp)
print parser.parse_args(''.split())
print
print parser.parse_args('-h'.split())
which will print:
Namespace(action='start')
usage: program.py [-h] [{start,stop,restart}]
postional arguments:
{start,stop,restart}
Start = Starts the daemon (default)
Stop = Stops the daemon
Restart = Restarts the daemon
optional arguments:
-h, --help show this help message and exit

Categories