Argparse FileType trick

Hello, this is my first post :wave: I’d like to share a recurring frustration with the argparse library.

Sometimes I need to check whether an argument is a file or not. The simpler approach is to use os.path.isfile, but I don’t like setting up exceptions when the argument is not a file:

parser.add_argument("file")
args = parser.parse_args()
if not os.path.isfile(args.file):
    raise ValueError("NOT A FILE!")

What I like to do instead is to use argparse.FileType:

parser.add_argument("file", type=argparse.FileType("r"))
args = parser.parse_args()
args.file.close() # WHYYY!
args.file.name # This is what I need

It handles the errors for me, which is nice, but it also gives-me an open io.TextIOWrapper that I have to close manually when I don’t need the stream.

What I’d like to write is something like this:

parser.add_argument("file", type=argparse.IsFile(extension="h5"))
args = parser.parse_args()
args.file  # A nice path for an existing .h5 file!

What do you guys think? Do any of you have the same problem?

2 Likes

It’s not something I’ve ever been particularly bothered about, but argparse is designed to allow you to write your own type-checking functions exactly like this, so it certainly seems like something you could write, either for your own application, or as a common library (maybe containing various type checkers you find useful).

Whether this particular example is sufficiently useful to warrant adding to the stdlib, I honestly don’t know.

1 Like

I have several argparse type functions I accumulated from various projects, and I’m sure there are many others with the similar collections. Maybe It’d be a good idea to make a package (say argparse-types) so they can be readily accessible. Something like Path(file=Ture, exists=True) would fit your need here.

2 Likes

I’d love to see a package like that.

parser = argparse.ArgumentParser(description="do stuff")
parser.add_arguments("number", type=argparse.types.Integer(range=range(0, 10)))  # Integer between 0-10
parser.add_arguments("json", type=argparse.types.JSONString())  # JSON string
parser.add_arguments("path", type=argparse.types.Path(file=True, exists=False)))  # Non existing file

It would be helpful for documentation as well:

usage: test.py [-h] number json path

do stuff

positional arguments:
  number       Integer 0-10
  json         JSON formatted string
  path         File path (new file)

optional arguments:
  -h, --help  show this help message and exit

And also has better error handling for parsing the arguments instead of a stack trace

$ ./test.py '{key: "value"} '  # missing quotes on "key"

Argument "json" expecting property name enclosed in double quotes: line 1 column 2 (char 1)

$ ./test.py 22  # wrong range
Argument "number" should be in range 0-10
1 Like

That would be great. There is just one essential problem with paths (when we need existing ones). They must exist at the time we are opening them. The time of parsing the arguments is less important. So you would need to handle the errors related to path existence twice.