mdign002
mdign002

Reputation: 79

Running argparse with optional arguments that won't require specifying output filename

I have read through a bunch of the questions already answered, but I don't see this covered--at least not that I recognized.

I am using argparse to take a file and convert it to a different type. The input filename is required. The output filename is NOT required as the optional argument should handle that. Here is the code so far:

import sys
import argparse
parser = argparse.ArgumentParser(description='Convert file to new type')
parser.add_argument('--json', type=str, help='Converts to json format')
parser.add_argument('--bibtex', type=str, help='Converts to bibtex format')
parser.add_argument('--html', type=str, help='Converts to html format')
parser.add_argument('inputfilename', type=str, help='enter the original filename')
args = parser.parse_args()
filename=args.filename
if args.json:
    print('Converting to json ...')
    #conversion code here
elif args.bibtex:
    print('Converting to bibtex ...')
    #conversion code here
elif args.html:
    print('Converting to html ...')
    #conversion code here
else:
    print('No conversion type indicated')

The problem is that whenever I use one of these flags. If I do

$ ./orsconvert.py --json inputfilename

I get an error saying

orsconvert.py: error: the following arguments are required: inputfilename

It does this because it interprets the provided inputfilename as an outputfilename connected to the --json. It is trying to force me to actually state the output filename after the optional argument and before the input filename, similar to this:

$ ./orsconvert.py --json outputfilename inputfileneame

However, I do not want to do that if there is a way around it. I want it to accept --json as the indication of what to do and then use inputfilename as the input and automatically save it as outputfilename according to what the code will specify.

Yes, I am looking at making the 3 optional arguments a group to simplify the code ... but that still doesn't explain how to get it to not require another argument after the optional one and before the final inputfilename required argument.

Is there something else I can put in the parser.add_argument('...') field that will stop requiring me to specify an outputfilename? I would like to change the code as little as possible because I am already pushing the limits of my coding comprehension.

Upvotes: 1

Views: 887

Answers (4)

chepner
chepner

Reputation: 530960

The problem is that you defined --json to require an argument, and it's taking the input file name as that argument. Your three converters should use the store_true action, not the default store action.

parser = argparse.ArgumentParser(description='Convert file to new type')
parser.add_argument('--json', action='store_true', help='Converts to json format')
parser.add_argument('--bibtex', action='store_true', help='Converts to bibtex format')
parser.add_argument('--html', action='store_true', help='Converts to html format')
parser.add_argument('inputfilename', help='enter the original filename')
args = parser.parse_args()

With this change, args.json et all are the Boolean values you expect them to be. The store_true action takes care of defining the type to be bool and the default value to be false.


Better yet, though, would be a single required positional argument that must take the value json, bibtex, or html. Then parse_args itself would detect if an incorrect or missing conversion type was given.

parser = argparse.ArgumentParser(description='Convert file to new type')
parser.add_argument('inputfilename', help='enter the original filename')
parser.add_argument('conversion_type', choices=['json', 'bibtex', 'html'])
args = parser.parse_args()
filename = args.filename
conversion_type = args.conversion_type  # Guaranteed to be json, html, or bibtex
if conversion_type == "json":
    ...
elif conversion_type == "bibtex":
    ...
elif conversion_type == "html":

If you would like an option with a default value, buckle up :) We're going to add 4 options: one that lets specify the output type explicitly, and three shortcuts for setting it.

p = argparse.ArgumentParser()
p.add_argument('inputfilename')
p.add_argument('--type', choices=['json', 'bibtex', 'html'], default='json')
p.add_argument('--json', action='store_const', const='json', dest='type')
p.add_argument('--html', action='store_const', const='html', dest='type')
p.add_argument('--bibtex', action='store_const', const='bibtex', dest='type')

--json, --bibtex, and --html have the same effect as --type json, --type bibtex, and --type html, respectively. As before, --type can only take those three values as its argument. If more than one of these, the last one used takes effect.

Upvotes: 5

MisterMiyagi
MisterMiyagi

Reputation: 50076

argparse already ships with everything you need to actually have the API you want, not the one you think you need.

  • The type should be one of several choices, not three separate flags.
  • The inputfilename should be required.
  • The outputfilename should be optional.
import argparse
parser = argparse.ArgumentParser(description='Convert file to new type')
parser.add_argument('format', choices=["json", "bibtex", "html"])
parser.add_argument('inputfilename', type=str)
parser.add_argument('outputfilename', type=str, nargs="?")
args = parser.parse_args()
print(args)

This automatically provides the desired usage, without misusing optionals but gaining error handling instead.

$ ./converter json in.txt
Namespace(format='json', inputfilename='in.txt', outputfilename=None)
$ ./converter html in.txt out.html
Namespace(format='html', inputfilename='in.txt', outputfilename='out.html')
$ ./converter yaml in.txt         
usage: converter [-h] {json,bibtex,html} inputfilename [outputfilename]
converter: error: argument format: invalid choice: 'yaml' (choose from 'json', 'bibtex', 'html')

Upvotes: 1

Teejay Bruno
Teejay Bruno

Reputation: 2159

Your issue is that argparse is expecting an input, especially since you have type=str

Try replacing the json arg with this instead

parser.add_argument('--json', action="store_true", help='Converts to json format')

Upvotes: 0

Ankit Gupta
Ankit Gupta

Reputation: 175

So the problem seems as if every argparse.arg requires an argument to be specified, be it anything. So you could set it to a boolean maybe. But I think maybe this approach could be better:

import sys
import argparse
parser = argparse.ArgumentParser(description='Convert file to new type')
parser.add_argument('--type', type=str, help='Converts to any of the following formats: 1 for json, 2 for bibtex and 3 for html')
parser.add_argument('--inputfilename', type=str, help='enter the original filename')
args = parser.parse_args()
filename=args.inputfilename
if args.type:
    conversion_type = int(args.type)
    if conversion_type == int(1):
        print('Converting to json ...')
        #conversion code here
    elif conversion_type == 2:
        print('Converting to bibtex ...')
        #conversion code here
    elif conversion_type == 3:
        print('Converting to html ...')
        #conversion code here
else:
    print('No conversion type indicated')

Just add an arg for specifying the type. Based on that, convert or dont as per the conditionals.

Upvotes: -2

Related Questions