Arg parse: parse file name as string (python)
<p>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.</p>

<p>Here is a sample code, <code>test.py</code>:</p>

<pre><code>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
</code></pre>

<p><code>testfile</code> is a <code>.txt</code> file containing: <code>1,2,3,4</code></p>

<p>In principal would like <code>print args.testfile</code> to return the invoked name of <code>testfile</code> as a string:</p>

<pre><code>$ python test.py -test test.txt
&gt;&gt; "test.txt"
</code></pre>

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

<p>Many thanks!</p>

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

<pre><code>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=&lt;_io.TextIOWrapper name='test.txt' mode='r' encoding='UTF-8'&gt;)
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()
</code></pre>

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

<p>If the file doesn't exist it exits with usage and a <code>cant open</code> message.</p>

<pre><code>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'
</code></pre>

<p><code>FileType</code> <code>__call__</code> handles the error with an <code>argparse.ArgumentTypeError</code></p>

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

<p>Using this error mechanism, and omitting your <code>open</code> I'd suggest:</p>

<pre><code>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
</code></pre>

<p>Which could be used as:</p>

<pre><code>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
</code></pre>

<p><a href="http://bugs.python.org/issue13824" rel="nofollow">http://bugs.python.org/issue13824</a> worries about <code>FileType</code> opening a file but not closing it.  I proposed a <code>FileContext</code>, modeled on <code>FileType</code>, but instead of opening the file, returns an object that can be use as:</p>

<pre><code>with arg.file() as f:
    f.read()
</code></pre>

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

python string filenames argparse