]> granicus.if.org Git - python/commitdiff
#11175: argparse.FileType now accepts encoding and errors arguments.
authorPetri Lehtinen <petri@digip.org>
Sat, 15 Dec 2012 20:39:32 +0000 (22:39 +0200)
committerPetri Lehtinen <petri@digip.org>
Sat, 15 Dec 2012 20:42:47 +0000 (22:42 +0200)
Patch by Lucas Maystre.

Doc/library/argparse.rst
Lib/argparse.py
Lib/test/test_argparse.py
Misc/ACKS
Misc/NEWS

index 9f6a1eaf9d943eea6e653b42e6cdbc52d0c14f7b..584f4f779224c74f25ebcb316ce13eb185f43e88 100644 (file)
@@ -976,9 +976,9 @@ See the section on the default_ keyword argument for information on when the
 ``type`` argument is applied to default arguments.
 
 To ease the use of various types of files, the argparse module provides the
-factory FileType which takes the ``mode=`` and ``bufsize=`` arguments of the
-:func:`open` function.  For example, ``FileType('w')`` can be used to create a
-writable file::
+factory FileType which takes the ``mode=``, ``bufsize=``, ``encoding=`` and
+``errors=`` arguments of the :func:`open` function.  For example,
+``FileType('w')`` can be used to create a writable file::
 
    >>> parser = argparse.ArgumentParser()
    >>> parser.add_argument('bar', type=argparse.FileType('w'))
@@ -1617,17 +1617,19 @@ Sub-commands
 FileType objects
 ^^^^^^^^^^^^^^^^
 
-.. class:: FileType(mode='r', bufsize=None)
+.. class:: FileType(mode='r', bufsize=-1, encoding=None, errors=None)
 
    The :class:`FileType` factory creates objects that can be passed to the type
    argument of :meth:`ArgumentParser.add_argument`.  Arguments that have
-   :class:`FileType` objects as their type will open command-line arguments as files
-   with the requested modes and buffer sizes::
+   :class:`FileType` objects as their type will open command-line arguments as
+   files with the requested modes, buffer sizes, encodings and error handling
+   (see the :func:`open` function for more details)::
 
       >>> parser = argparse.ArgumentParser()
-      >>> parser.add_argument('--output', type=argparse.FileType('wb', 0))
-      >>> parser.parse_args(['--output', 'out'])
-      Namespace(output=<_io.BufferedWriter name='out'>)
+      >>> parser.add_argument('--raw', type=argparse.FileType('wb', 0))
+      >>> parser.add_argument('out', type=argparse.FileType('w', encoding='UTF-8'))
+      >>> parser.parse_args(['--raw', 'raw.dat', 'file.txt'])
+      Namespace(out=<_io.TextIOWrapper name='file.txt' mode='w' encoding='UTF-8'>, raw=<_io.FileIO name='raw.dat' mode='wb'>)
 
    FileType objects understand the pseudo-argument ``'-'`` and automatically
    convert this into ``sys.stdin`` for readable :class:`FileType` objects and
index 202f2cb972784f1c5b3f376f982d558560d9efdb..3b1a0795522361f31f25f6aefdcaeadbbdd3cd78 100644 (file)
@@ -1140,11 +1140,17 @@ class FileType(object):
             same values as the builtin open() function.
         - bufsize -- The file's desired buffer size. Accepts the same values as
             the builtin open() function.
+        - encoding -- The file's encoding. Accepts the same values as the
+            the builtin open() function.
+        - errors -- A string indicating how encoding and decoding errors are to
+            be handled. Accepts the same value as the builtin open() function.
     """
 
-    def __init__(self, mode='r', bufsize=-1):
+    def __init__(self, mode='r', bufsize=-1, encoding=None, errors=None):
         self._mode = mode
         self._bufsize = bufsize
+        self._encoding = encoding
+        self._errors = errors
 
     def __call__(self, string):
         # the special argument "-" means sys.std{in,out}
@@ -1159,14 +1165,18 @@ class FileType(object):
 
         # all other arguments are used as file names
         try:
-            return open(string, self._mode, self._bufsize)
+            return open(string, self._mode, self._bufsize, self._encoding,
+                        self._errors)
         except IOError as e:
             message = _("can't open '%s': %s")
             raise ArgumentTypeError(message % (string, e))
 
     def __repr__(self):
         args = self._mode, self._bufsize
-        args_str = ', '.join(repr(arg) for arg in args if arg != -1)
+        kwargs = [('encoding', self._encoding), ('errors', self._errors)]
+        args_str = ', '.join([repr(arg) for arg in args if arg != -1] +
+                             ['%s=%r' % (kw, arg) for kw, arg in kwargs
+                              if arg is not None])
         return '%s(%s)' % (type(self).__name__, args_str)
 
 # ===========================
index c06c940bf2d62dcccbc4bdb3de3fbf8f1192d2fe..00cde2ed5cc953c2960a22f780d49176c42c59c2 100644 (file)
@@ -14,6 +14,7 @@ import argparse
 from io import StringIO
 
 from test import support
+from unittest import mock
 class StdIOBuffer(StringIO):
     pass
 
@@ -1421,6 +1422,19 @@ class TestFileTypeRepr(TestCase):
         type = argparse.FileType('wb', 1)
         self.assertEqual("FileType('wb', 1)", repr(type))
 
+    def test_r_latin(self):
+        type = argparse.FileType('r', encoding='latin_1')
+        self.assertEqual("FileType('r', encoding='latin_1')", repr(type))
+
+    def test_w_big5_ignore(self):
+        type = argparse.FileType('w', encoding='big5', errors='ignore')
+        self.assertEqual("FileType('w', encoding='big5', errors='ignore')",
+                         repr(type))
+
+    def test_r_1_replace(self):
+        type = argparse.FileType('r', 1, errors='replace')
+        self.assertEqual("FileType('r', 1, errors='replace')", repr(type))
+
 
 class RFile(object):
     seen = {}
@@ -1557,6 +1571,24 @@ class TestFileTypeWB(TempDirMixin, ParserTestCase):
     ]
 
 
+class TestFileTypeOpenArgs(TestCase):
+    """Test that open (the builtin) is correctly called"""
+
+    def test_open_args(self):
+        FT = argparse.FileType
+        cases = [
+            (FT('rb'), ('rb', -1, None, None)),
+            (FT('w', 1), ('w', 1, None, None)),
+            (FT('w', errors='replace'), ('w', -1, None, 'replace')),
+            (FT('wb', encoding='big5'), ('wb', -1, 'big5', None)),
+            (FT('w', 0, 'l1', 'strict'), ('w', 0, 'l1', 'strict')),
+        ]
+        with mock.patch('builtins.open') as m:
+            for type, args in cases:
+                type('foo')
+                m.assert_called_with('foo', *args)
+
+
 class TestTypeCallable(ParserTestCase):
     """Test some callables as option/argument types"""
 
index ac657c35bd945eff11ae714f5344850cddb24e2f..99b34a3d2892c71adc5f512e74a42b5a103e8291 100644 (file)
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -768,6 +768,7 @@ Laura Matson
 Graham Matthews
 Dieter Maurer
 Daniel May
+Lucas Maystre
 Arnaud Mazin
 Rebecca McCreary
 Kirk McDonald
index 7b71524210a17ce502a92aa7fb89d838d3d55e91..d52a9d81599322c5d8bef15bf35bdd761aa33645 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -167,6 +167,9 @@ Core and Builtins
 Library
 -------
 
+- Issue #11175: argparse.FileType now accepts encoding and errors
+  arguments. Patch by Lucas Maystre.
+
 - Issue #16488: epoll() objects now support the `with` statement.  Patch
   by Serhiy Storchaka.