Search code examples
python-3.xargparse

Making a flag in argparse require 2 arguments with different types


I'd like to make a parser for a program like follows program --serve some/path /file/to/serve.html

Looking at the argparse documentation https://docs.python.org/3/library/argparse.html#type

I cannot for the life of me figure out how I could parse the first argument of --serve as a string and the second as argparse.FileType('r')

I'd like to do something like

parser.add_argument('--serve', nargs=2, type=(str, argparse.FileType('r')), action='append', help='...')

Is there a way to do this with argparse?


Solution

  • If you implement a custom type and instead of using --nargs=2 you use a delimiter to separate the two arguments, you could do something like this:

    import os
    import stat
    import argparse
    
    def mytype(v):
        dirpath, filepath = v.split(':')
        try:
            res = os.stat(filepath)
        except FileNotFoundError:
            raise ValueError(f'{filepath} does not exist')
        else:
            if not stat.S_ISREG(res.st_mode):
                raise ValueError(f'{filepath} is not a regular file')
        return dirpath, filepath
    
    p = argparse.ArgumentParser()
    p.add_argument('--serve', type=mytype, action='append', help='...')
    
    args = p.parse_args()
    print(args)
    

    If I run this in a directory that contains the file foo.txt, we see:

    # with a file that does not exist
    $ python argtest.py --serve somedir:bar.txt
    usage: argtest.py [-h] [--serve SERVE]
    argtest.py: error: argument --serve: invalid mytype value: 'somedir:bar.txt'
    
    # with a file that *does* exist
    $ python argtest.py --serve somedir:foo.txt
    Namespace(serve=[('somedir', 'foo.txt')])
    

    This isn't opening the file; it's testing that the second argument points to a regular file. You could instead of the file and return a file object instead of the path if you want.