I am using the argparse package of Python 2.7 to write some option-parsing logic for a command-line tool. The tool should accept one of the following arguments:
"ON": Turn a function on.
"OFF": Turn a function off.
[No arguments provided]: Echo the current state of the function.
Looking at the argparse documentation led me to believe that I wanted two--possibly three--subcommands to be defined, since these three states are mutually exclusive and represent different conceptual activities. This is my current attempt at the code:
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
parser.set_defaults(func=print_state) # I think this line is wrong.
parser_on = subparsers.add_parser('ON')
parser_on.set_defaults(func=set_state, newstate='ON')
parser_off = subparsers.add_parser('OFF')
parser_off.set_defaults(func=set_state, newstate='OFF')
args = parser.parse_args()
if(args.func == set_state):
set_state(args.newstate)
elif(args.func == print_state):
print_state()
else:
args.func() # Catchall in case I add more functions later
I was under the impression that if I provided 0 arguments, the main parser would set func=print_state, and if I provided 1 argument, the main parser would use the appropriate subcommand's defaults and call func=set_state. Instead, I get the following error with 0 arguments:
usage: cvsSecure.py [-h] {ON,OFF} ...
cvsSecure.py: error: too few arguments
And if I provide "OFF" or "ON", print_state gets called instead of set_state. If I comment out the parser.set_defaults line, set_state is called correctly.
I'm a journeyman-level programmer, but a rank beginner to Python. Any suggestions about how I can get this working?
Edit: Another reason I was looking at subcommands was a potential fourth function that I am considering for the future:
"FORCE txtval": Set the function's state to txtval.
The defaults of the top-level parser override the defaults on the sub-parsers, so setting the default value of func on the sub-parsers is ignored, but the value of newstate from the sub-parser defaults is correct.
I don't think you want to use subcommands. Subcommands are used when the available options and positional arguments change depending on which subcommand is chosen. However, you have no other options or positional arguments.
The following code seems to do what you require:
import argparse
def print_state():
print "Print state"
def set_state(s):
print "Setting state to " + s
parser = argparse.ArgumentParser()
parser.add_argument('state', choices = ['ON', 'OFF'], nargs='?')
args = parser.parse_args()
if args.state is None:
print_state()
elif args.state in ('ON', 'OFF'):
set_state(args.state)
Note the optional parameters to parser.add_argument. The "choices" parameter specifies the allowable options, while setting "nargs" to "?" specifies that 1 argument should be consumed if available, otherwise none should be consumed.
Edit: If you want to add a FORCE command with an argument and have separate help text for the ON and OFF command then you do need to use subcommands. Unfortunately there doesn't seem to be a way of specifying a default subcommand. However, you can work around the problem by checking for an empty argument list and supplying your own. Here's some sample code illustrating what I mean:
import argparse
import sys
def print_state(ignored):
print "Print state"
def set_state(s):
print "Setting state to " + s
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
on = subparsers.add_parser('ON', help = 'On help here.')
on.set_defaults(func = set_state, newstate = 'ON')
off = subparsers.add_parser('OFF', help = 'Off help here.')
off.set_defaults(func = set_state, newstate = 'OFF')
prt = subparsers.add_parser('PRINT')
prt.set_defaults(func = print_state, newstate = 'N/A')
force = subparsers.add_parser('FORCE' , help = 'Force help here.')
force.add_argument('newstate', choices = [ 'ON', 'OFF' ])
force.set_defaults(func = set_state)
if (len(sys.argv) < 2):
args = parser.parse_args(['PRINT'])
else:
args = parser.parse_args(sys.argv[1:])
args.func(args.newstate)
There are two problems with your approach.
First you probably already noticed that newstate is not some sub_value of the sub parser and needs to be addressed at the top level of args as args.newstate. That should explain that assigning a default to newstate twice will result in the first value being overwritten. Whether you call your programm with 'ON' or 'OFF' as a parameter, each time set_state() will be called with OFF. If you just want to be able to do python cvsSecure ON and
python cvsSecure OFF the following would work:
from __future__ import print_function
import sys
import argparse
def set_state(state):
print("set_state", state)
def do_on(args):
set_state('ON')
def do_off(args):
set_state('OFF')
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
parser_on = subparsers.add_parser('ON')
parser_on.set_defaults(func=do_on)
parser_on.add_argument('--fast', action='store_true')
parser_off = subparsers.add_parser('OFF')
parser_off.set_defaults(func=do_off)
args = parser.parse_args()
args.func(args)
The second problem is that argparse does handle subparsers as single value arguments, so you have to specify one before invoking parser.parse_args(). You can automate insertion of a lacking argument by adding a extra subparser 'PRINT' and automatically inserting
that using set_default_subparser added to argparse.ArgumentParser() (that code is part
of the package ruamel.std.argparse
from __future__ import print_function
import sys
import argparse
def set_default_subparser(self, name, args=None):
"""default subparser selection. Call after setup, just before parse_args()
name: is the name of the subparser to call by default
args: if set is the argument list handed to parse_args()
, tested with 2.7, 3.2, 3.3, 3.4
it works with 2.6 assuming argparse is installed
"""
subparser_found = False
for arg in sys.argv[1:]:
if arg in ['-h', '--help']: # global help if no subparser
break
else:
for x in self._subparsers._actions:
if not isinstance(x, argparse._SubParsersAction):
continue
for sp_name in x._name_parser_map.keys():
if sp_name in sys.argv[1:]:
subparser_found = True
if not subparser_found:
# insert default in first position, this implies no
# global options without a sub_parsers specified
if args is None:
sys.argv.insert(1, name)
else:
args.insert(0, name)
argparse.ArgumentParser.set_default_subparser = set_default_subparser
def print_state(args):
print("print_state")
def set_state(state):
print("set_state", state)
def do_on(args):
set_state('ON')
def do_off(args):
set_state('OFF')
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
parser_print = subparsers.add_parser('PRINT', help='default action')
parser_print.set_defaults(func=print_state)
parser_on = subparsers.add_parser('ON')
parser_on.set_defaults(func=do_on)
parser_on.add_argument('--fast', action='store_true')
parser_off = subparsers.add_parser('OFF')
parser_off.set_defaults(func=do_off)
parser.set_default_subparser('PRINT')
args = parser.parse_args()
args.func(args)
You don't need to handle in args to do_on(), etc., but it comes in handy if you start specifying options to the different subparsers.
Related
I have quite a big program which has a CLI interaction based on argparse, with several sub parsers. The list of supported choices for the subparsers arguments are determined based on DB queries, parsing different xml files, making different calculations etc, so it is quite IO intensive and time consuming.
The problem is that argparse seems to fetch choices for all sub parser when I run the script, which adds a considerable and annoying startup delay.
Is there a way to make argparse only fetch and validate choices for the currently used sub parser?
One solution could be to move all the validation logic deeper inside the code but that would mean quite a lot of work which I would like to avoid, if possible.
Thank you
To delay the fetching of choices, you could parse the command-line in two stages: In the first stage, you find only the subparser, and in the second stage, the subparser is used to parse the rest of the arguments:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('subparser', choices=['foo','bar'])
def foo_parser():
parser = argparse.ArgumentParser()
parser.add_argument('fooval', choices='123')
return parser
def bar_parser():
parser = argparse.ArgumentParser()
parser.add_argument('barval', choices='ABC')
return parser
dispatch = {'foo':foo_parser, 'bar':bar_parser}
args, unknown = parser.parse_known_args()
args = dispatch[args.subparser]().parse_args(unknown)
print(args)
It could be used like this:
% script.py foo 2
Namespace(fooval='2')
% script.py bar A
Namespace(barval='A')
Note that the top-level help message will be less friendly, since it can only tell you about the subparser choices:
% script.py -h
usage: script.py [-h] {foo,bar}
...
To find information about the choices in each subparser, the user would have to select the subparser and pass the -h to it:
% script.py bar -- -h
usage: script.py [-h] {A,B,C}
All arguments after the -- are considered non-options (to script.py) and are thus parsed by the bar_parser.
Here's a quick and dirty example of a 'lazy' choices. In this case choices are a range of integers. I think a case that requires expensive DB lookups could implemented in a similar fashion.
# argparse with lazy choices
class LazyChoice(object):
# large range
def __init__(self, argmax):
self.argmax=argmax
def __contains__(self, item):
# a 'lazy' test that does not enumerate all choices
return item<=self.argmax
def __iter__(self):
# iterable for display in error message
# use is in:
# tup = value, ', '.join(map(repr, action.choices))
# metavar bypasses this when formatting help/usage
return iter(['integers less than %s'%self.argmax])
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--regular','-r',choices=['one','two'])
larg = parser.add_argument('--lazy','-l', choices=LazyChoice(10))
larg.type = int
print parser.parse_args()
Implementing the testing part (__contains__) is easy. The help/usage can be customized with help and metavar attributes. Customizing the error message is harder. http://bugs.python.org/issue16468 discusses alternatives when choices are not iterable. (also on long list choices: http://bugs.python.org/issue16418)
I've also shown how the type can be changed after the initial setup. That doesn't solve the problem of setting type based on subparser choice. But it isn't hard to write a custom type, one that does some sort of Db lookup. All a type function needs to do is take a string, return the correct converted value, and raise ValueError if there's a problem.
I have solved the issue by creating a simple ArgumentParser subclass:
import argparse
class ArgumentParser(argparse.ArgumentParser):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.lazy_init = None
def parse_known_args(self, args=None, namespace=None):
if self.lazy_init is not None:
self.lazy_init()
self.lazy_init = None
return super().parse_known_args(args, namespace)
Then I can use it as following:
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='command', title='commands', parser_class=ArgumentParser)
subparsers.required = True
subparser = subparsers.add_parser(
'do-something', help="do something",
description="Do something great.",
)
def lazy_init():
from my_database import data
subparser.add_argument(
'-o', '--option', choices=data.expensive_fetch(), action='save',
)
subparser.lazy_init = lazy_init
This will really initialize a sub-parser only when parent parser tries to parse arguments for the sub-parser. So if you do program -h it will not initialize the sub-parser, but if you do program do-something -h it will.
This is a script that tests the idea of delaying the creation of a subparser until it is actually needed. In theory it might save start up time, by only creating the subparser that's actually needed.
I use the nargs=argparse.PARSER to replicate the subparser behavior in the main parser. help behavior is similar.
# lazy subparsers test
# lazy behaves much like a regular subparser case, but only creates one subparser
# for N=5 time differences do not rise above the noise
import argparse
def regular(N):
parser = argparse.ArgumentParser()
sp = parser.add_subparsers(dest='cmd')
for i in range(N):
spp = sp.add_parser('cmd%s'%i)
spp.set_defaults(func='cmd%s'%(10*i))
spp.add_argument('-f','--foo')
spp.add_argument('pos', nargs='*')
return parser
def lazy(N):
parser = argparse.ArgumentParser()
sp = parser.add_argument('cmd', nargs=argparse.PARSER, choices=[])
for i in range(N):
sp.choices.append('cmd%s'%i)
return parser
def subpar(cmd):
cmd, argv = cmd[0], cmd[1:]
parser = argparse.ArgumentParser(prog=cmd)
parser.add_argument('-f','--foo')
parser.add_argument('pos', nargs='*')
parser.set_defaults(func=cmd)
args = parser.parse_args(argv)
return args
N = 5
mode = True #False
argv = 'cmd1 -f1 a b c'.split()
if mode:
args = regular(N).parse_args(argv)
print(args)
else:
args = lazy(N).parse_args(argv)
print(args)
if isinstance(args.cmd, list):
sargs = subpar(args.cmd)
print(sargs)
test runs with different values of mode (and N=5)
1004:~/mypy$ time python3 stack44315696.py
Namespace(cmd='cmd1', foo='1', func='cmd10', pos=['a', 'b', 'c'])
real 0m0.052s
user 0m0.044s
sys 0m0.008s
1011:~/mypy$ time python3 stack44315696.py
Namespace(cmd=['cmd1', '-f1', 'a', 'b', 'c'])
Namespace(foo='1', func='cmd1', pos=['a', 'b', 'c'])
real 0m0.051s
user 0m0.048s
sys 0m0.000s
N has to be much larger to start seeing a effect.
I cant see m to figure out how to iterate over the accepted args of argparse. I get I can iterate over the parsed_args result, but what I want is to iterate over the arguments the parser is configured with ( ie with optparse you can iterate over the args ).
for example:
parser = argparse.ArgumentParser( prog = 'myapp' )
parser.add_argument( '--a', .. )
parser.add_argument( '--b', ...)
parser.add_argument( '--c', ... )
for arg in parser.args():
print arg
would result in
--a
--b
--c
You'll probably want to getattr from the args:
args = parser.parse_args()
for arg in vars(args):
print arg, getattr(args, arg)
Result:
a None
c None
b None
If you want to list the optionals you can do it this way:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--foo')
parser.add_argument('--bar')
parser.add_argument('--baz')
for option in parser._optionals._actions:
print(option.option_strings)
I don't see a practical reason to iterate over them however. You can always see the options via --help.
A bit late to the game here, but I found a way to do this without reading from private variables by using a custom help formatter that collects the arguments it is asked to format.
The following program will print ['-h', '--help', '--a', '--b', '--c']
import argparse
class ArgCollector(argparse.HelpFormatter):
# Will store the arguments in a class variable since argparse uses a class
# name, not an instance of a class
args = []
def add_argument(self, action):
# Just remember the options
self.args.extend(action.option_strings)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--a')
parser.add_argument('--b')
parser.add_argument('--c')
# Install our new help formatter, use it, then restore the original
# formatter
original_formatter_class = parser.formatter_class
parser.formatter_class = ArgCollector
parser.format_help()
parser.formatter_class = original_formatter_class
# Print the args that argparse would accept
print(ArgCollector.args)
if __name__ == '__main__':
main()
I'm new to python and currently playing with it.
I have a script which does some API Calls to an appliance. I would like to extend the functionality and call different functions based on the arguments given when calling the script.
Currently I have the following:
parser = argparse.ArgumentParser()
parser.add_argument("--showtop20", help="list top 20 by app",
action="store_true")
parser.add_argument("--listapps", help="list all available apps",
action="store_true")
args = parser.parse_args()
I also have a
def showtop20():
.....
and
def listapps():
....
How can I call the function (and only this) based on the argument given?
I don't want to run
if args.showtop20:
#code here
if args.listapps:
#code here
as I want to move the different functions to a module later on keeping the main executable file clean and tidy.
Since it seems like you want to run one, and only one, function depending on the arguments given, I would suggest you use a mandatory positional argument ./prog command, instead of optional arguments (./prog --command1 or ./prog --command2).
so, something like this should do it:
FUNCTION_MAP = {'top20' : my_top20_func,
'listapps' : my_listapps_func }
parser.add_argument('command', choices=FUNCTION_MAP.keys())
args = parser.parse_args()
func = FUNCTION_MAP[args.command]
func()
At least from what you have described, --showtop20 and --listapps sound more like sub-commands than options. Assuming this is the case, we can use subparsers to achieve your desired result. Here is a proof of concept:
import argparse
import sys
def showtop20():
print('running showtop20')
def listapps():
print('running listapps')
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
# Create a showtop20 subcommand
parser_showtop20 = subparsers.add_parser('showtop20', help='list top 20 by app')
parser_showtop20.set_defaults(func=showtop20)
# Create a listapps subcommand
parser_listapps = subparsers.add_parser('listapps', help='list all available apps')
parser_listapps.set_defaults(func=listapps)
# Print usage message if no args are supplied.
# NOTE: Python 2 will error 'too few arguments' if no subcommand is supplied.
# No such error occurs in Python 3, which makes it feasible to check
# whether a subcommand was provided (displaying a help message if not).
# argparse internals vary significantly over the major versions, so it's
# much easier to just override the args passed to it.
if len(sys.argv) <= 1:
sys.argv.append('--help')
options = parser.parse_args()
# Run the appropriate function (in this case showtop20 or listapps)
options.func()
# If you add command-line options, consider passing them to the function,
# e.g. `options.func(options)`
There are lots of ways of skinning this cat. Here's one using action='store_const' (inspired by the documented subparser example):
p=argparse.ArgumentParser()
p.add_argument('--cmd1', action='store_const', const=lambda:'cmd1', dest='cmd')
p.add_argument('--cmd2', action='store_const', const=lambda:'cmd2', dest='cmd')
args = p.parse_args(['--cmd1'])
# Out[21]: Namespace(cmd=<function <lambda> at 0x9abf994>)
p.parse_args(['--cmd2']).cmd()
# Out[19]: 'cmd2'
p.parse_args(['--cmd1']).cmd()
# Out[20]: 'cmd1'
With a shared dest, each action puts its function (const) in the same Namespace attribute. The function is invoked by args.cmd().
And as in the documented subparsers example, those functions could be written so as to use other values from Namespace.
args = parse_args()
args.cmd(args)
For sake of comparison, here's the equivalent subparsers case:
p = argparse.ArgumentParser()
sp = p.add_subparsers(dest='cmdstr')
sp1 = sp.add_parser('cmd1')
sp1.set_defaults(cmd=lambda:'cmd1')
sp2 = sp.add_parser('cmd2')
sp2.set_defaults(cmd=lambda:'cmd2')
p.parse_args(['cmd1']).cmd()
# Out[25]: 'cmd1'
As illustrated in the documentation, subparsers lets you define different parameter arguments for each of the commands.
And of course all of these add argument or parser statements could be created in a loop over some list or dictionary that pairs a key with a function.
Another important consideration - what kind of usage and help do you want? The different approaches generate very different help messages.
If your functions are "simple enough" take adventage of type parameter https://docs.python.org/2.7/library/argparse.html#type
type= can take any callable that takes a single string argument and
returns the converted value:
In your example (even if you don't need a converted value):
parser.add_argument("--listapps", help="list all available apps",
type=showtop20,
action="store")
This simple script:
import argparse
def showtop20(dummy):
print "{0}\n".format(dummy) * 5
parser = argparse.ArgumentParser()
parser.add_argument("--listapps", help="list all available apps",
type=showtop20,
action="store")
args = parser.parse_args()
Will give:
# ./test.py --listapps test
test
test
test
test
test
test
Instead of using your code as your_script --showtop20, make it into a sub-command your_script showtop20 and use the click library instead of argparse. You define functions that are the name of your subcommand and use decorators to specify the arguments:
import click
#click.group()
#click.option('--debug/--no-debug', default=False)
def cli(debug):
print(f'Debug mode is {"on" if debug else "off"}')
#cli.command() # #cli, not #click!
def showtop20():
# ...
#cli.command()
def listapps():
# ...
See https://click.palletsprojects.com/en/master/commands/
# based on parser input to invoke either regression/classification plus other params
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--path", type=str)
parser.add_argument("--target", type=str)
parser.add_argument("--type", type=str)
parser.add_argument("--deviceType", type=str)
args = parser.parse_args()
df = pd.read_csv(args.path)
df = df.loc[:, ~df.columns.str.contains('^Unnamed')]
if args.type == "classification":
classify = AutoML(df, args.target, args.type, args.deviceType)
classify.class_dist()
classify.classification()
elif args.type == "regression":
reg = AutoML(df, args.target, args.type, args.deviceType)
reg.regression()
else:
ValueError("Invalid argument passed")
# Values passed as : python app.py --path C:\Users\Abhishek\Downloads\adult.csv --target income --type classification --deviceType GPU
You can evaluate using evalwhether your argument value is callable:
import argparse
def list_showtop20():
print("Calling from showtop20")
def list_apps():
print("Calling from listapps")
my_funcs = [x for x in dir() if x.startswith('list_')]
parser = argparse.ArgumentParser()
parser.add_argument("-f", "--function", required=True,
choices=my_funcs,
help="function to call", metavar="")
args = parser.parse_args()
eval(args.function)()
I'm writing a wrapper around the ssh command line client. After the first positional argument that's part of command, all further options should also be treated as positional arguments.
Under optparse, I believe this would be done with disable_interspersed_args.
Presently I have something like this:
parser = argparse.ArgumentParser()
parser.add_argument('--parallel', default=False, action='store_true')
# maybe allow no command? this would ssh interactively into each machine...
parser.add_argument('command', nargs='+')
args = parser.parse_args()
But if options are passed as part of the command (such as my_wrapper ls -l), they're instead interpreted by ArgumentParser as unknown options. error: unrecognized arguments: -l
If I use parse_known_args(), the options may be taken out of order.
p = argparse.ArgumentParser()
p.add_argument('-a', action='store_true')
p.add_argument('command', nargs='+')
print(p.parse_known_args())
$ python3 bah.py -b ls -l -a
(Namespace(a=True, command=['ls']), ['-b', '-l'])
Here you can see that -b's position before ls has been lost, and -a has been parsed out from the command, which is not desired.
How can I:
Prevent arguments from being parsed after a certain point?
Disable parsing of interspersed arguments?
Allow arguments with a prefix to be consumed as positional arguments?
I had the same problem. I found the solution on the argparse bug tracker: http://code.google.com/p/argparse/issues/detail?id=52
The solution is simple: replace nargs='+' (or '*') with nargs=argparse.REMAINDER. This special value is not documented, but it does what you want.
I think your best bet to start solving these issues is to try out -- after all your optional args. -- is a pseudo-arg that tells ArgumentParser that everything after is a positional argument. Docs are here
As for prevent arguments from being parsed after a certain point, you can pass part of argv to parse_args. That combined with some introspection can be used to limit what is parsed.
What #dcolish suggested is the universal approach. Here is a sample implementation which also supports the standard -- separator, but its usage is not required for correct parsing.
Result:
# ./parse-pos.py -h
usage: parse-pos.py [-h] [-qa] [-qb] COMMAND [ARGS...]
# ./parse-pos.py -qa ls -q -h aa /bb
try_argv = ['-qa', 'ls']
cmd_rest_argv = ['-q', '-h', 'aa', '/bb']
parsed_args = Namespace(command='ls', qa=True, qb=False)
The code:
#!/usr/bin/python3
import argparse
import sys
from pprint import pprint
class CustomParserError(Exception):
pass
class CustomArgumentParser(argparse.ArgumentParser):
def error(self, message):
raise CustomParserError(message)
def original_error(self, message):
super().error(message)
def parse_argv():
parser = CustomArgumentParser(description='Example')
parser.add_argument('command', metavar='COMMAND [ARGS...]', help='the command to be executed')
parser.add_argument('-qa', action='store_true') # "ambiguous option" if you specify just "-q"
parser.add_argument('-qb', action='store_true') # "ambiguous option" if you specify just "-q"
def parse_until_positional(parser, _sys_argv = None):
if _sys_argv is None:
_sys_argv = sys.argv[1:] # skip the program name
for i in range(0, len(_sys_argv) + 1):
try_argv = _sys_argv[0:i]
try:
parsed_args = parser.parse_args(try_argv)
except CustomParserError as ex:
if len(try_argv) == len(_sys_argv):
# this is our last try and we still couldn't parse anything
parser.original_error(str(ex)) # sys.exit()
continue
# if we are here, we parsed our known optional & dash-prefixed parameters and the COMMAND
cmd_rest_argv = _sys_argv[i:]
break
return (parsed_args, cmd_rest_argv, try_argv)
(parsed_args, cmd_rest_argv, try_argv) = parse_until_positional(parser)
# debug
pprint(try_argv)
pprint(cmd_rest_argv)
pprint(parsed_args)
return (parsed_args, cmd_rest_argv)
def main():
parse_argv()
main()
Another option is to use parse_known_args, which stops parsing when an unknown argument is encountered.
my python version is 2.4.3.
Now I am developing a CLI with cmd module from python for a CD player. I have some classes like CDContainer (with method like addCD, removeCD, etc), CD (with method like play, stop, pause). Now, I want to add some options for the commands and also if the options inputs are not correct, the CLI could return proper information about either wrong input type or wrong values. e.g., I want to have "addcd --track 3 --cdname thriller". what I am doing now is to get all the arguments via , split it, and assign it to the relevant variables, as follows.
my question is in python, is there some module that is handy for my case to parse and analyse the options or arguments ?
REVISION: OK, I edit it, thanks to gclj5 comments.
import cmd
class CDContainerCLI(cmd.Cmd):
def do_addcd(self, line):
args=line.split()
parser = OptionParser()
parser.add_option("-t", "--track", dest="track_number", type="int",
help="track number")
parser.add_option("-n", "--cdname", dest="cd_name", type="string",
help="CD name")
(options, positional_args) = parser.parse_args(args)
cd_obj= CD()
cd_obj.addCD(options.track_number, options.cd_name)
If possible, could you write some code samples, just to show how to do it?
Thank you very much!!
Depending on your Python version, you should take a look at either optparse (since version 2.3, deprecated since version 2.7) or argparse (since version 2.7).
Some sample code using optparse (line is the string you read from stdin in your CLI):
from optparse import OptionParser
args = line.split()
parser = OptionParser()
parser.add_option("-t", "--track", dest="track_number", type="int",
help="track number")
parser.add_option("-n", "--cdname", dest="cd_name", type="string",
help="CD name")
# args[0] contains the actual command ("addcd" in this example).
(options, positional_args) = add_cd_parser.parse_args(args[1:])
if options.track_number != None and options.cd_name != None:
cd_obj= CD()
cd_obj.addCD(options.track_number, options.cd_name)
print "add CD (track %d, name %s)" % (options.track_number, options.cd_name)
This parser only handles your "addcd" command. For more commands you could use several OptionParser objects in a dictionary with the command name as the key, for instance. You could parse the options like this then:
(options, args) = parsers[args[0]].parse_args(args[1:])
Take a look at the documentation for optparse for more information. It's very easy to output usage information, for instance. There is also a tutorial available.
my question is in python, is there some module that is handy for my case to parse and analyse the options or arguments ?
Yes, the argparse module.
If you're already familiar with the getopt library from C, that's also available as a python module - though less easy to use, if you're not already used to it.
Here's a demo script I wrote a few months ago when my co-workers and I were learning the argparse module. It illustrates several of the module's behaviors and features:
import sys
import argparse
def parse_command_line():
# Define our argument parser.
ap = argparse.ArgumentParser(
description = 'This is a demo app for the argparse module.',
epilog = 'This text will appear after options.',
usage = '%(prog)s [options]', # Auto-generated by default.
add_help = False, # Default is True.
)
# A grouping of options in the help text.
gr = ap.add_argument_group('Required arguments')
# A positional argument. This is indicated by the absense
# of leading minus signs.
gr.add_argument(
'task',
choices = ['get', 'put'],
help = 'Task to be performed.', # Help text about an option.
metavar = 'TASK', # Placeholder to be used in an option's help text.
# The default in this case would be "{get,put}".
)
# Another group.
gr = ap.add_argument_group('Common options')
# A basic option.
gr.add_argument(
'-s', '--subtask',
action = 'store', # This is the default.
# One value will be stored, as a string,
# in opt.subtask
)
# A required option, with type conversion.
gr.add_argument(
'-u', '--user',
required = True, # Options can be made mandatory.
# However, positional arguments can't be made optional.
type = int, # Convert opt.user to an integer.
# By default, it would be a string.
)
# A flag option.
gr.add_argument(
'--overwrite',
dest = 'clobber', # Store in opt.clobber rather than opt.overwrite.
action = 'store_true', # If option is supplied, opt.clobber == True.
)
# Another group.
gr = ap.add_argument_group('Some other options')
# An option with multiple values.
gr.add_argument(
'--datasets',
metavar = 'DATASET', # Default would be DATASETS.
nargs = '+', # If option is used, it takes 1 or more arguments.
# Will be stored as a list in opt.datasets.
help = "The datasets to use for frobnication.",
)
# An option with a specific N of values.
gr.add_argument(
'--bar',
nargs = 1, # Takes exactly one argument. Differs from a basic
# option because opt.bar will be a list rather
# than a string.
default = [], # Default would be None.
)
# A file option.
gr.add_argument(
'--log',
type = argparse.FileType('w'), # Will open a file for writing.
default = sys.stdout,
help = 'Log file (default: STDOUT)',
)
# Another group.
gr = ap.add_argument_group('Program information')
# A version option.
gr.add_argument(
'-v', '--version',
action = 'version', # Will display version text and exit.
version = 'argparse_demo v1.2.0', # The version text.
)
# A help option.
gr.add_argument(
'-h', '--help',
action = 'help', # Will display help text and exit.
)
# Parse the options.
# If given no arguments, parse_args() works with sys.argv[1:].
# And the object it returns will be of type Namespace.
opt = ap.parse_args()
return opt
command_lines = [
'argparse_demo.py put -u 1',
'argparse_demo.py get -u 234 --over --data a b c --bar XYZ -s munch --log _log.txt',
'argparse_demo.py -h', # Will exit() here.
]
for c in command_lines:
sys.argv = c.split()
opt = parse_command_line()
print opt