Python argparse - adding argument depending on existence of other argument [duplicate] - python-2.7

I'd like to have a program that takes a --action= flag, where the valid choices are dump and upload, with upload being the default. If (and only if) dump is selected, I'd like there to also be a --dump-format= option. Is there a way to express this using argparse, or do I need to just accept all the arguments and do the logic myself.

The argparse module offers a way to do this without implementing your own
requiredness checks. The example below uses "subparsers" or "sub commands".
I've implemented a subparser for "dump" and one for "format".
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('file', help='The file you want to act on.')
subparsers = parser.add_subparsers(dest='subcommand')
subparsers.required = True # required since 3.7
# subparser for dump
parser_dump = subparsers.add_parser('dump')
# add a required argument
parser_dump.add_argument(
'format',
choices=['csv', 'json'],
help='Dump the file in this format.')
# subparser for upload
parser_upload = subparsers.add_parser('upload')
# add a required argument
parser_upload.add_argument(
'server',
choices=['amazon', 'imgur'],
help='Upload the file to this service.')
args = parser.parse_args()
print args
if args.subcommand == 'dump':
print 'I will now dump "%s" in the %s format' % (args.file, args.format)
if args.subcommand == 'upload':
print 'I will now upload "%s" to %s' % (args.file, args.server)
That looks like this on the command line:
$ python ap.py
usage: ap.py [-h] file {upload,dump} ...
ap.py: error: too few arguments
$ python ap.py tmp.txt
usage: ap.py [-h] file {upload,dump} ...
ap.py: error: too few arguments
Upload:
$ python ap.py tmp.txt upload
usage: ap.py file upload [-h] {amazon,imgur}
ap.py file upload: error: too few arguments
$ python ap.py tmp.txt upload amazo
usage: ap.py file upload [-h] {amazon,imgur}
ap.py file upload: error: argument server: invalid choice: 'amazo' (choose from 'amazon', 'imgur')
$ python ap.py tmp.txt upload amazon
Namespace(file='tmp.txt', server='amazon', subcommand='upload')
I will now upload "tmp.txt" to amazon
$ python ap.py tmp.txt upload imgur
Namespace(file='tmp.txt', server='imgur', subcommand='upload')
I will now upload "tmp.txt" to imgur
Dump:
$ python ap.py tmp.txt dump
usage: ap.py file dump [-h] {csv,json}
ap.py file dump: error: too few arguments
$ python ap.py tmp.txt dump csv
Namespace(file='tmp.txt', format='csv', subcommand='dump')
I will now dump "tmp.txt" in the csv format
$ python ap.py tmp.txt dump json
Namespace(file='tmp.txt', format='json', subcommand='dump')
I will now dump "tmp.txt" in the json format
More info: ArgumentParser.add_subparsers()

Another way to approach the problem is by using subcommands (a'la git) with "action" as the first argument:
script dump --dump-format="foo"
script upload

You could use parser.error:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--action', choices=['upload', 'dump'], default='dump')
parser.add_argument('--dump-format')
args = parser.parse_args()
if args.action != 'dump' and args.dump_format:
parser.error('--dump-format can only be set when --action=dump.')

It depends what you consider "doing all the logic yourself". You can still use argparse and add the dump option as follows with minimal effort without resorting to sub-commands:
from argparse import ArgumentParser
from sys import argv
parser = ArgumentParser()
action_choices = ['upload', 'dump']
parser.add_argument('--action', choices=action_choices, default=action_choices[1])
parser.add_argument('--dump-format', required=(action_choices[1] in argv))
This way argparse won't care about the dump format if the dump action wasn't selected

Try this.
Suppose you have a tool that lists, adds and deletes records in a table with the following structure:
id
sitekey
response
status
1
123456
valid
a
2
234567
invalid
3
345678
invalid
c
4
456789
valid
d
And you want to have the following operations and arguments:
list
from: optional
to: optional
short-response: optional
add
sitekey: required
response: required
status: optional
remove
id: required
Then, you can have a code similar to the following:
import argparse
import sys
operation_choices = ['list', 'add', 'remove']
parser = argparse.ArgumentParser()
parser.add_argument("--operation",
choices = operation_choices,
default = operation_choices[0],
help = "Your help!",
required = True)
# list operation subarguments
if True in list(map(lambda x: operation_choices[0] in x, sys.argv)):
parser.add_argument("--from",
type = int,
default = 1,
help = "Your help!",
required = False)
parser.add_argument("--to",
type = int,
help = "Your help!",
required = False)
parser.add_argument("--short-response",
type = bool,
default = True,
help = "Your help!",
required = False)
# add operation subarguments
if True in list(map(lambda x: operation_choices[1] in x, sys.argv)):
parser.add_argument("--sitekey",
type = str,
help = "Your help!",
required = True)
parser.add_argument("--response",
type = str,
help = "Your help!",
required = True)
parser.add_argument("--status",
type = str,
help = "Your help!",
required = False)
# remove operation subarguments
if True in list(map(lambda x: operation_choices[2] in x, sys.argv)):
parser.add_argument("--id",
type = int,
help = "Your help!",
required = True)
args = parser.parse_args()
# Your operations...
So when you run:
$ python tool.py --operation=list
This run, no required arguments
$ python tool.py --operation=add
usage: tool.py [-h] --operation {list,add,remove} --sitekey SITEKEY --response RESPONSE [--status STATUS]
tool.py: error: the following arguments are required: --sitekey, --response
$ python tool.py --operation=remove
usage: tool.py [-h] --operation {list,add,remove} --id ID
tool.py: error: the following arguments are required: --id
$ python tool.py --help
usage: tool.py [-h] --operation {list,add,remove}
options:
-h, --help show this help message and exit
--operation {list,add,remove}
Your help!
$ python tool.py --operation=list --help
usage: tool.py [-h] --operation {list,add,remove} [--from FROM] [--to TO] [--short-response SHORT_RESPONSE]
options:
-h, --help show this help message and exit
--operation {list,add,remove}
Your help!
--from FROM Your help!
--to TO Your help!
--short-response SHORT_RESPONSE
Your help!
$ python tool.py --operation=add --help
usage: tool.py [-h] --operation {list,add,remove} --sitekey SITEKEY --response RESPONSE [--status STATUS]
options:
-h, --help show this help message and exit
--operation {list,add,remove}
Your help!
--sitekey SITEKEY Your help!
--response RESPONSE Your help!
--status STATUS Your help!
$ python tool.py --operation=remove --help
usage: tool.py [-h] --operation {list,add,remove} --id ID
options:
-h, --help show this help message and exit
--operation {list,add,remove}
Your help!
--id ID Your help!

Related

How to pass non-alphanumeric ids to youtube-dl?

Here's an example url I wanted to extract, as well as the error I got:
$ youtube-dl -x --get-url -8ysWJSrITc
Usage: youtube-dl [OPTIONS] URL [URL...]
youtube-dl: error: no such option: -8
$ youtube-dl -x --get-url "-8ysWJSrITc"
Usage: youtube-dl [OPTIONS] URL [URL...]
youtube-dl: error: no such option: -8

Need to convert os.system with subprocess.call

i need to convert os.system() to subprocess.call()
I have 2 complex commands executing inside os.system as (cmd1 +"&&"+cmd2), I need to convert it to subprocess.call() with shell=False(for security reasons)
example of my code :-
cmd1 ="echo paras"
cmd2 ="rm -rf paras1.txt"
os.system as (cmd1 +"&&"+cmd2)
I Tried:-
subprocess.call([cmd3,"&&",cmd4],shell=False)
subprocess.call(shlex.split(cmd3+ "&&" +cmd4),shell=False)
but failed in both cases, i need to know how can i run 2 commands inside subprocess.call() separated by "&&" with shell=False.
You need to execute the commands within a shell (for example bash):
import subprocess
shell = '/bin/bash' # you may need to adjust the path!
cmds = ['echo "foo"', '&&', 'echo "bar"']
cmd = [shell, '-c', ' '.join(cmds)]
print cmd
print subprocess.call(cmd, shell=False)
Output:
['/bin/bash', '-c', 'echo "foo" && echo "bar"']
foo
bar
0

Wanted to customize the python argparse module generated errors

import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("-f", "--filepath", help="Input the file path")
group.add_argument("-d", "--dirpath", help="Input the directory path")
For Example if I use both options -f and -d,I'm getting the following error,which is generated by python:
error: argument -d/--dirpath: expected one argument
But I want to customise the error and it should be defined by me.
2140:~/mypy$ python3 stack50552334.py
Namespace(dirpath=None, filepath=None)
These are all produced when there's a mismatch between the Action nargs and available strings. While message format is the same, it is customized to the argument's name:
2140:~/mypy$ python3 stack50552334.py -f
usage: stack50552334.py [-h] [-f FILEPATH | -d DIRPATH]
stack50552334.py: error: argument -f/--filepath: expected one argument
2140:~/mypy$ python3 stack50552334.py -d
usage: stack50552334.py [-h] [-f FILEPATH | -d DIRPATH]
stack50552334.py: error: argument -d/--dirpath: expected one argument
2140:~/mypy$ python3 stack50552334.py -f -d
usage: stack50552334.py [-h] [-f FILEPATH | -d DIRPATH]
stack50552334.py: error: argument -f/--filepath: expected one argument
2141:~/mypy$ python3 stack50552334.py -f foo -d
usage: stack50552334.py [-h] [-f FILEPATH | -d DIRPATH]
stack50552334.py: error: argument -d/--dirpath: expected one argument
But when both flags are provided with the expected arguments, then the mutually_exclusive_group test comes into play, and raises its own error.
2141:~/mypy$ python3 stack50552334.py -f foo -d dir
usage: stack50552334.py [-h] [-f FILEPATH | -d DIRPATH]
stack50552334.py: error: argument -d/--dirpath: not allowed with argument -f/--filepath
The first class of message is produced in:
def _match_argument(self, action, arg_strings_pattern):
# match the pattern for this action to the arg strings
nargs_pattern = self._get_nargs_pattern(action)
match = _re.match(nargs_pattern, arg_strings_pattern)
# raise an exception if we weren't able to find a match
if match is None:
nargs_errors = {
None: _('expected one argument'),
OPTIONAL: _('expected at most one argument'),
ONE_OR_MORE: _('expected at least one argument'),
}
default = ngettext('expected %s argument',
'expected %s arguments',
action.nargs) % action.nargs
msg = nargs_errors.get(action.nargs, default)
raise ArgumentError(action, msg)
# return the number of arguments matched
return len(match.group(1))
The _ is alias for gettext.gettext. So there is a possibility of customizing the messages with this mechanism. https://docs.python.org/3/library/gettext.html
from gettext import gettext as _, ngettext
I don't have any experience with this multlingual localization.
The mutually_exclusive error is raised in:
def take_action(action, argument_strings, option_string=None):
...
# error if this argument is not allowed with other previously
# seen arguments, assuming that actions that use the default
# value don't really count as "present"
if argument_values is not action.default:
seen_non_default_actions.add(action)
for conflict_action in action_conflicts.get(action, []):
if conflict_action in seen_non_default_actions:
msg = _('not allowed with argument %s')
action_name = _get_action_name(conflict_action)
raise ArgumentError(action, msg % action_name)
Again, there is the possibility of gettext customization.
custom argparse.py
Of course there's nothing preventing you from modifying a copy of argparse.py file, and placing in your own directory so it is loaded instead of the default one.
post parsing testing
If the wording of the mutually_exclusive error is bothering you (as opposed to the expected one argument errors, you could omit that group from the parser, and do your own tests after parsing. If the user provides non-default values for both args.filepath and args.dirpath call parser.error('your own message'), or raise your own error.

Python: invalid syntax error while using es2csv

This is the query I am trying to use in order to connect to elasticsearch, which is in (172.21.150.230) in order to pull out information in a csv format:
es2csv -u http://xxx.xx.xxx.xxx:5601/ -f _all -d doc -i test2 -r -q '{"query": {"match": {"NAME": "xxx"}}}' -o database.csv
However, I get SyntaxError: invalid syntax
Thanks
Which version are you using?
This error raised when your json query is not valid or when you forget to add -r argument.

How do I make command line arguments with a hyphen (-) show up as non-optional in python v2.7?

I have the following python script
parser = argparse.ArgumentParser(description='Process controller.py arguments')
parser.add_argument("-b", help='Build number, e.g., 1234')
args = vars(parser.parse_args())
When I run it I get...
$ python CommandLineParser.py -h
usage: CommandLineParser.py [-h] [-b B]
Process controller.py arguments
optional arguments:
-h, --help show this help message and exit
-b B Build number, e.g., 1234
How do I make the "-b" show up as a "non-optional" argument (because it's NOT!). As an added bonus, how do I rid of the uppercase "B" after it? Thanks!
You need to set required to True and metavar (it's responsible for B) to '':
parser.add_argument("-b", help='Build number, e.g., 1234', required=True, metavar='')
Actually, you will still see your required argument as optional if run your script in a help mode. This is because of a bug: argparse required arguments displayed under "optional arguments":
The argparse module lists required args as optional in the default
help message.
There are also some workarounds suggested, but I like this one more: add your own required arguments group:
required_group = parser.add_argument_group('required arguments')
required_group.add_argument("-b", help='Build number, e.g., 1234', required=True, metavar='')
Then, you will see this on a command-line:
$ python test.py -h
usage: test.py [-h] -b
Process controller.py arguments
optional arguments:
-h, --help show this help message and exit
required arguments:
-b Build number, e.g., 1234
Please use the required keyword when adding it to the argparse: http://docs.python.org/2/library/argparse.html#sub-commands
parser.add_argument("-b", help='Build number, e.g., 1234', required=True)