user 123342
user 123342

Reputation: 483

Arg parse: parse file name as string (python)

I would like to parse the name of a file into my script as a string, rather than directly converting the file into an object.

Here is a sample code, test.py:

import argparse
import os.path
def is_valid_file(parser, arg):
      if not os.path.exists(arg):
           parser.error("The file %s does not exist! Use the --help flag for input options." % arg)
      else:
           return open(arg, 'r')

parser = argparse.ArgumentParser(description='test') 
parser.add_argument("-test", dest="testfile", required=True,
                    help="test", type=lambda x: is_valid_file(parser, x))
args = parser.parse_args()    
print args.testfile

testfile is a .txt file containing: 1,2,3,4

In principal would like print args.testfile to return the invoked name of testfile as a string:

$ python test.py -test test.txt
>> "test.txt"

To achieve this I need to prevent argparser from converting test.txt into an object. How can I do this?

Many thanks!

Upvotes: 0

Views: 3876

Answers (2)

hpaulj
hpaulj

Reputation: 231395

The FileType type factory does most of what your code does, with a slightly different message mechanism:

In [16]: parser=argparse.ArgumentParser()
In [17]: parser.add_argument('-f',type=argparse.FileType('r'))

In [18]: args=parser.parse_args(['-f','test.txt'])
In [19]: args
Out[19]: Namespace(f=<_io.TextIOWrapper name='test.txt' mode='r' encoding='UTF-8'>)
In [20]: args.f.read()
Out[20]: '  0.000000,  3.333333,  6.666667, 10.000000, 13.333333, 16.666667, 20.000000, 23.333333, 26.666667, 30.000000\n'
In [21]: args.f.close()

For a valid name it opens the file, which you can use and close. But you can't use it in a with context.

If the file doesn't exist it exits with usage and a cant open message.

In [22]: args=parser.parse_args(['-f','test11.txt'])
usage: ipython3 [-h] [-f F]
ipython3: error: argument -f: can't open 'test11.txt': [Errno 2] No such file or directory: 'test11.txt'

FileType __call__ handles the error with an argparse.ArgumentTypeError

   except OSError as e:
        message = _("can't open '%s': %s")
        raise ArgumentTypeError(message % (string, e))

Using this error mechanism, and omitting your open I'd suggest:

def valid_file(astring):
    if not os.path.exists(astring):
        msg = "The file %s does not exist! Use the --help flag for input options." % astring
        raise argparse.ArgumentTypeError(msg)
    else:
        return astring

Which could be used as:

In [32]: parser=argparse.ArgumentParser()
In [33]: parser.add_argument('-f',type=valid_file)

In [34]: args=parser.parse_args(['-f','test11.txt'])
usage: ipython3 [-h] [-f F]
ipython3: error: argument -f: The file test11.txt does not exist! Use the --help flag for input options.
An exception has occurred, use %tb to see the full traceback.

SystemExit: 2

In [35]: args=parser.parse_args(['-f','test.txt'])
In [36]: args
Out[36]: Namespace(f='test.txt')
In [37]: with open(args.f) as f:print(f.read())
  0.000000,  3.333333,  6.666667, 10.000000, 13.333333, 16.666667, 20.000000, 23.333333, 26.666667, 30.000000

http://bugs.python.org/issue13824 worries about FileType opening a file but not closing it. I proposed a FileContext, modeled on FileType, but instead of opening the file, returns an object that can be use as:

with arg.file() as f:
    f.read()

It would do the file existence or creatablity testing, without actually opening or creating the file. It's a more complicated solution.

Upvotes: 1

Jean-Fran&#231;ois Fabre
Jean-Fran&#231;ois Fabre

Reputation: 140196

you can modify your function as follows to return the string after having checked it exists:

def is_valid_file(parser, arg):
      if not os.path.exists(arg):
           parser.error("The file %s does not exist! Use the --help flag for input options." % arg)
      else:
           return arg

There's also a more direct method:

parser.add_argument("-test", dest="testfile", required=True,
                    help="test", type=file)  # file exists in python 2.x only

parser.add_argument("-test", dest="testfile", required=True,
                    help="test", type=lambda f: open(f))  # python 3.x

args = parser.parse_args()    
print(args.testfile.name)  # name of the file from the file handle

actually args.testfile is the file handle, opened by argparser (exception if not found). You can read from it directly.

Upvotes: 2

Related Questions