38

I would like to make these invocations of myprog work, and no others.

$ python3 myprog.py -i infile -o outfile
$ python3 myprog.py -o outfile
$ python3 myprog.py -o
$ python3 myprog.py 

In particular I want to make it illegal to specify the infile but not the outfile.

In the third case, a default name for the outfile is assumed, "out.json." In the second, third and fourth cases, a default name for the input file is assumed, "file.n.json", where n is an integer version number. In the fourth case the output file would be "file.n+1.json" where n+1 is a version number one larger than the one on the input file. The relevant section of my code is:

import argparse

parser = argparse.ArgumentParser(description="first python version")
parser.add_argument('-i', '--infile', nargs=1, type=argparse.FileType('r'), help='input file, in JSON format')
parser.add_argument('-o', '--outfile', nargs='?', type=argparse.FileType('w'), default='out.json', help='output file, in JSON format')

args = parser.parse_args()

print("Here's what we saw on the command line: ")
print("args.infile",args.infile)
print("args.outfile",args.outfile)

if args.infile and not args.outfile:
    parser.error("dont specify an infile without specifying an outfile")
elif not args.infile:
    print("fetching infile")
else: # neither was specified on the command line
    print("fetching both infile and outfile")

Problem is, when I run

$ python3 myprog.py -i infile.json

instead of the parser error I hoped for, I get:

Here's what we saw on the command line: 
args.infile [<_io.TextIOWrapper name='infile.json' mode='r' encoding='UTF-8'>]
args.outfile <_io.TextIOWrapper name='out.json' mode='w' encoding='UTF-8'>
fetching both infile and outfile

...which suggests that even though there was no "-o" on the command line it acted as if there was.

user1416227
  • 636
  • 2
  • 11
  • 20
  • What's the difference between the 3rd and 4th case? what does the -o stand for? – Ionut Hulub Apr 02 '13 at 00:40
  • The fourth case will use default infile and outfile names (specifically file.n.json and file.n+1.json, i.e. files with version numbers embedded). These are different from "out.json" which is what the third case with the "-o" option would cause. I have modified the text above to indicate this. – user1416227 Apr 02 '13 at 04:05
  • How is the 4th case supposed to work as the `--input` option doesn't have a default? – maxschlepzig Oct 02 '16 at 09:32

2 Answers2

28

As an add-on to the selected answer:

The option to run -o without specifying a file, can be done using const combined with nargs='?'.

From the docs:

When add_argument() is called with option strings (like -f or --foo) and nargs='?'. This creates an optional argument that can be followed by zero or one command-line arguments. When parsing the command line, if the option string is encountered with no command-line argument following it, the value of const will be assumed instead. See the nargs description for examples.

Example (with type string):

parser.add_argument('-o', '--outfile', nargs='?', const='arg_was_not_given', help='output file, in JSON format')

args = parser.parse_args()

if args.outfile is None:
    print('Option not given at all')
elif args.outfile == 'arg_was_not_given':
    print('Option given, but no command-line argument: "-o"')
elif:
    print('Option and command-line argument given: "-o <file>"')
vimaier
  • 23
  • 5
mhristache
  • 2,038
  • 3
  • 20
  • 19
23

You specified a default argument for the outfile.

parser.add_argument('-o', '--outfile', nargs='?', type=argparse.FileType('w'), default='out.json', help='output file, in JSON format')

If the -o option isn't specified at the command line, the arg parser inserts the default argument.

Change this to:

parser.add_argument('-o', '--outfile', nargs='?', type=argparse.FileType('w'), help='output file, in JSON format')

and things should work as you expect.


If you want to be able to specify -o, without a filename, you probably want something like:

out_file = args.out if args.out is not None else 'json.out'

I'm not sure if the relevant parameter will be None or '' (i.e., empty string) if you specify the -o without a parameter--I suspect it's None, but I don't know for sure. You'll have to test it out to be sure.

I don't know how to do this without extra logic with argparse.

foxyblue
  • 2,859
  • 2
  • 21
  • 29
BenDundee
  • 4,389
  • 3
  • 28
  • 34
  • 1
    It's almost exactly what I want. The remaining problem is that I want `$ python3 myprog.py -o` to act as if I had run `$ python3 myprog.py -o out.json`. Can you suggest how to make that happen? – user1416227 Apr 02 '13 at 04:37
  • Thanks! I had thought I could do it with argparse. – user1416227 Apr 02 '13 at 17:10
  • This answer ignores the requirements of the 3rd case from the question. See maximi's answer for how the 3rd case can be implemented with argparse. The last assignment isn't even valid Python syntax. – maxschlepzig Oct 02 '16 at 09:30