Reputation: 179
So I'm writing this very small program to do http get and post requests. The requests are as follows:
requestApp.py help
requestApp.py help get
requestApp.py help post
requestApp.py get [-v] [-h key:value] URL
requestApp.py post [-v] [-h key:value] [-d inline-data] [-f file] URL
As you can see, the -v, -h, -d, -f, URL arguments are optional. The get and post arguments are non-optional. I'll show you the snippet of my program that is relevant to this situation:
parser = argparse.ArgumentParser(description='httpc is a curl-like application but supports HTTP protocol only.')
parser.add_argument('command', type=str, help=help_output())
parser.add_argument('url', action='store_true', help='The URL that will be provided to perform the requested command.')
parser.add_argument('-v', '--verbose', action='store_true')
The command
argument will be help, get, or post, and the url
argument is self explanatory. My question is related to the second and third commands above, namely:
requestApp.py help get
requestApp.py help post
How can I make sure that when typing help get
, the get
will not be registered in the URL (same for help post
). In addition, when I do include a URL, I want it to be stored inside of the URL argument. Would I have to manually evaluate the arguments passed through if statements? Or there is a better way to do it?
Upvotes: 1
Views: 2029
Reputation: 231355
Perhaps the closest an argparse
solution can come, at least without going the subparser route, is:
import argparse
import sys
print(sys.argv)
parser = argparse.ArgumentParser()
parser.add_argument('-k', '--keyvalue')
parser.add_argument('-v', '--verbose', action='store_true')
parser.add_argument('-d', '--data')
parser.add_argument('-f', '--file')
parser.add_argument('pos1', choices = ['help', 'get', 'post'])
parser.add_argument('pos2')
args = parser.parse_args()
print(args)
The resulting help is:
1744:~/mypy$ python stack54383659.py get aurl -h
['stack54383659.py', 'get', 'aurl', '-h']
usage: stack54383659.py [-h] [-k KEYVALUE] [-v] [-d DATA] [-f FILE]
{help,get,post} pos2
positional arguments:
{help,get,post}
pos2
optional arguments:
-h, --help show this help message and exit
-k KEYVALUE, --keyvalue KEYVALUE
-v, --verbose
-d DATA, --data DATA
-f FILE, --file FILE
The fit isn't perfect. For example you can give just help
, but you can provide just -h
. The 2nd positional value can be any string, 'get', a valid url or something else. Your own code will have to valid that. The key:value
bit requires your own parsing.
In the argparse
way of parsing the optionals
can occur in any order. The two positionals have to occur in the given order (relative to each other).
In newer Pythons I can change the last positional to be 'optional', and use the new intermixed
parser. That would allow me to give just 'help' (or just 'get'):
parser.add_argument('pos2', nargs='?')
args = parser.parse_intermixed_args()
intermixed
is needed if the two positional values are separated by flags. For some complex reasons, the regular parsing may consume the '?' argument prematurely leaving you with an extra unrecognized string.
Another approach is to define all the flagged arguments, and use parse_known_args
. The non-flag values will be in the extras
list, which you can parse as you like. Older parsers like optparse
did essentially that. argparse
added a limited ability to handle positional arguments as well, but strictly by position, not by value.
Upvotes: 2
Reputation: 3265
It is quite complicated to do this using argparse
here is how to do it using docopt
, docopt
parses the usage pattern and returns a dictionary :
"""
Usage:
requestApp help [get|post]
requestApp get [-v] [-k=key:value] <URL>
requestApp post [-v] [-k=key:value] [-d=data] [-f file] <URL>
Options:
-v --verbose This is verbose mode
-d=data This option does this
-k=key:value This one does that
-f file This one is magic
"""
from docopt import docopt
ARGS = docopt(__doc__)
For example with requestApp.py post -k hello:world -f myfile.txt google.com
docopt will return:
{
"--verbose": false,
"-d": None,
"-f": "myfile.txt",
"-k": "hello:world",
"<URL>": "google.com",
"get": false,
"help": false,
"post": true
}
Then you can do:
if ARGS['help']:
if ARGS['get']: pass # requestApp help get
else if ARGS['post']: pass # requestApp help post
else: pass # requestApp help
exit()
if ARGS['get']: pass # requestApp get
else if ARGS['post']: pass # requestApp post
if ARGS['--verbose']: print("this is just the beginning")
-h
is a reserved option by default (for help) that makes docopt returns the usage pattern and exit.
docopt
will return the usage pattern to stdout and exit if you try illegal commands such as requestApp help unicorn
Upvotes: 1