Argparse positional arguments with prefix - python

My python3 script works with the input and the output file which is specified on the command line.
The usage should looks like this
xxxx.py [-h] --input=file --output=file
In code I am using
parser.add_argument("input", help='Input file');
parser.add_argument("output", help='Output file');
but the arguments are without the necessary prefix. Is there a way to specify the prefix for each argument?

Simply include the double-dash:
parser.add_argument("--input", help='Input file');
parser.add_argument("--output", help='Output file');
Arguments are either positional or optional; arguments starting with -- are always optional. You cannot create positional arguments with a -- prefix and you really should not. The -- prefix is a user interface convention you really do not want to break.

Related

How to accept positional argument in main parser as well as subparser?

I am making a CLI that will accept Fortran code file and some functions in the following manner:-
prog -m module_name example.f only: add sub
How can I accept values after only: as separate positional arguments?
Currently argparse is parsing all arguments after module_name as Fortran files. My code is as follows.
parser = argparse.ArgumentParser(prog="prog", help = "Help text)
parser.add_argument(
"Fortran Files",
metavar="<fortran files>",
action="extend", # List storage
nargs="*",
type=check_fortran, # Returns a pathlib.Path
help="""Paths to fortranfiles that will be scanned for
<fortran functions>.""",
)
parser.add_argument(
"Keep functions",
metavar="only:",
action="extend",
type=str,
nargs="*",
help="Use only fortran functions that follow.",
)
parser.add_argument(
"-m",
"--module",
metavar="<modulename>",
type=str,
nargs=1,
help="""Name of the module""",
)
I don't see any use of subparsers.
Use of phases like "Keep functions" as the dest is awkward and unnecessary when defining positionals. They can't be used as
args."Keep functions"
Simple names like "keep" can be used as args.keep.
action="extend"
has no value for positionals. A positional cannot be reused. Stick with the default "store".
Using two "nargs='*'" positionals sequentially doesn't work. The first gets all the strings, leaving none for the second. They have to be separated by an optional like your '-m'.
I'd suggest using
parser.add_argument('--only', nargs='+', ....)
to specify the functions. Then args.only will be list of function names.

Python argparse Optional argument only works when it's entered in the right positions

In my script I have 3 positional arguments and 1 optional argument. One of the three positional arguments is required and the rest is optional (as specified using narg='?').
My optional argument doesn't pass any other arguments (action ='store_true') and is just there to enable sorting which will be implemented at a later time.
However, my problem is that my optional argument only works when it is the first or last argument in the script call.
Below is my script so far:
parser = argparse.ArgumentParser()
parser.add_argument("-s", "--sort", help="option to sort the ip addresses", action='store_true')
parser.add_argument("file_name", help="Name of the file containing the tcpdump")
parser.add_argument("source_IP", nargs='?', type=str, help="Source ip to search")
parser.add_argument("dest_IP", nargs='?', type=str, help="Destination ip to search")
args = parser.parse_args()
If I enter my -s between any of the other positional arguments I get an error.
Ex: ./my_script file.txt -s 192.168.152.32 192.168.0.25
usage: 1 [-h] [-s] file_name [source_IP] [dest_IP]
1: error: unrecognized arguments: 192.168.152.32 192.168.0.25
My goal is to be able to enter my optional argument (-s) anywhere in the script call and have it working.
You have three positional arguments, but two of them are also optional thanks to nargs='?'. argparse is getting screwed up because it sees the positional filename, and then has to choose arbitrarily between interpreting the -s as the optional positional source, or as the switch. Either interpretation is valid (it's not doing complicated backtracking parsing to try to find some legal interpretation of the arguments that would allow it to complete; doing so with some argument types could lead to very bad behavior, like opening a file, then backtracking, closing it, and opening something else).
Short answer: In general, optional arguments should be either all positional, or all switches. Mixing and matching introduces complications that would make parsing a complicated recursive process that could only heuristically guess at the correct parsing (particularly with nargs='*' and nargs='+', but even '?' causes problems as you see). Removing the nargs qualifier from source and dest, or leaving them optional and converting to switches will allow -s to be passed in whatever order you like.
This problem is a subtile one, and requires a good understanding of how argparse parses postionals and optionals.
If all the positionals took one argument (the default nargs), then the -s could occur anywhere - start, end, or between any positional.
What happens with:
./my_script file.txt -s 192.168.152.32 192.168.0.25
is that source_IP,dest_IP are both consumed (and set to their defaults) when file_name is parsed. It then handles -s. Now there are 2 strings left, but no positionals to consume them, hence the unrecognized arguments error. Note that ./my_script file.txt runs fine, as does ./my_script file.txt -s. In all 3 cases, the 2 IP arguments are consumed at the same time as file_name.
parse_args alternates between consuming positionals and optionals. It will consume as many positionals at time as there strings (think of a greedy regex expression). Since source_IP and dest_IP are ok with 0 args, all three are consumed the first time it handles positionals.
There isn't a neat fix for the user, except to be wary of using nargs='?' positionals.
There is a bug/issue that tries to fix this. But the fix isn't trivial. The parser has to 'look ahead', noting that it could delay parsing these '?' arguments.
http://bugs.python.org/issue15112
argparse: nargs='*' positional argument doesn't accept any items
if preceded by an option and another positional
Your parser runs fine with the argparse patched as proposed in that issue. But there's quite a backlog of potential patches for argparse.

Python argparse : how to detect duplicated optional argument?

I'm using argparse with optional parameter, but I want to avoid having something like this : script.py -a 1 -b -a 2
Here we have twice the optional parameter 'a', and only the second parameter is returned. I want either to get both values or get an error message.
How should I define the argument ?
[Edit]
This is the code:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-a', dest='alpha', action='store', nargs='?')
parser.add_argument('-b', dest='beta', action='store', nargs='?')
params, undefParams = self.parser.parse_known_args()
append action will collect the values from repeated use in a list
parser.add_argument('-a', '--alpha', action='append')
producing an args namespace like:
namespace(alpha=['1','3'], b='4')
After parsing you can check args.alpha, and accept or complain about the number of values. parser.error('repeated -a') can be used to issue an argparse style error message.
You could implement similar functionality in a custom Action class, but that requires understanding the basic structure and operation of such a class. I can't think anything that can be done in an Action that can't just as well be done in the appended list after.
https://stackoverflow.com/a/23032953/901925 is an answer with a no-repeats custom Action.
Why are you using nargs='?' with flagged arguments like this? Without a const parameter this is nearly useless (see the nargs=? section in the docs).
Another similar SO: Python argparse with nargs behaviour incorrect

Define the order of argparse argument in python

I am trying to use argparse with subparser to switch between 3 fonctionnalities whereas one positional argument should be common to all subparser. Moreover, and it is the key point, i want to put the positional argument as the last argument provided as this one is an output file path. It makes no sense to me to put it at the beginning (as first argument)
import sys,argparse,os
files = argparse.ArgumentParser(add_help=False)
files.add_argument('outfile', help='output mesh file name')
parser = argparse.ArgumentParser(description="A data interpolation program.",prog='data_interpolate.py', parents=[files])
subparsers = parser.add_subparsers(help='Mode command.')
command_parser = subparsers.add_parser('cmd',help='Pass all argument in command line.',parents=[files])
command_parser.add_argument('-min', dest='MINFILE',help='Input file with min values', required=True)
command_parser.add_argument('-max', dest='MAXFILE',help='Input file with min values', required=True)
command_parser.add_argument('u', help='Interpolation parameter. Float between 0 and 1. Out of bound values are limited to 0 or 1.')
subparsers.add_parser('py',help='Pass all argument in python file.',parents=[files])
subparsers.add_parser('json',help='Pass all argument in json file.',parents=[files])
Which gives:
data_interpolation.py -h
usage: data_interpolation.py [-h] outfile {cmd,py,json}
But, to my opinion, the outfile should be given at the end following:
data_interpolation.py [-h] {cmd,py,json} outfile
This has even more sense when using the cmd command as I need to pass other parameter values. For intance:
data_interpolation.py cmd -min minfile.txt -max maxfile.txt 0.6 outfile.txt
How can I set up argparse to have such behaviour?
(note - this is an old question).
The order of positionals is determined by the order in which they are defined. That includes the subparsers argument (which is a positional with choices and a special action).
Defining outfile as an argument to both the main parser and the subparsers is redundant.
Positionals defined via parents will be placed first. So if 'outfile' must be last, it has to be defined separately for each subparser.
It could also be specified last as postional for the main parser (after the subparser definitions).
In [2]: p=argparse.ArgumentParser()
In [5]: sp=p.add_subparsers(dest='cmd')
In [6]: spp=sp.add_parser('cmd1')
In [7]: spp.add_argument('test')
In [8]: p.add_argument('out')
In [9]: p.print_help()
usage: ipython [-h] {cmd1} ... out
...
In [11]: spp.print_help()
usage: ipython cmd1 [-h] test
...
In [15]: p.parse_args('cmd1 test out'.split())
Out[15]: Namespace(cmd='cmd1', out='out', test='test')
cmd1 is interpreted as a subparser choice. test is interpreted by the subparser as a positional. out is left over, and returned to the main parser to use as it sees fit. This parsing could be messed up if the subparser does not return any extras. So I'd be wary of specifying a final positional like this.
You don't need to specify files as a parent of parser, just for each of the subparsers.

Override the positional and optional arguments with another argument in command line (argparse python module)

I am using argparser to parse the command line arguments.
Now, I have something like
./script.py 1112323 0 --salary 100000 -- age 34
Here first two are positional arguments and rest are optional.
Now, I want to have a feature such that when the user gives a filename as input in command line, then it should override these above arguments and take the arguments from header of the file. I meam when user gives sth like
id|sequence|age|name|........... (header of the file with first two cols as positional arguments and rest positional)
On giving this in command line:
./script.py -f filename
it should not complain of above positional arguments.
Is this feasible over my current implementation?
You will most likely need to implement this check yourself. Make both arguments (positional and -f) optional (required=False and nargs="*") and then implement your custom check and use the error method of ArgumentParser. To make it easier for user mention the correct usage in help string.
Something like this:
parser = ArgumentParser()
parser.add_argument("positional", nargs="*", help="If you don't provide positional arguments you need use -f")
parser.add_argument("-f", "--file", required=False, help="...")
args = parser.parse_args()
if not args.file and not args.positional:
parser.error('You must use either -f or positional argument')

Categories