gh-58032: Deprecate the argparse.FileType type converter (GH-124664)

This commit is contained in:
Serhiy Storchaka 2024-10-23 10:50:29 +03:00 committed by GitHub
parent c75ff2ef8e
commit 834ba5aaf2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 83 additions and 45 deletions

View file

@ -4,16 +4,6 @@ Pending removal in future versions
The following APIs will be removed in the future, The following APIs will be removed in the future,
although there is currently no date scheduled for their removal. although there is currently no date scheduled for their removal.
* :mod:`argparse`:
* Nesting argument groups and nesting mutually exclusive
groups are deprecated.
* Passing the undocumented keyword argument *prefix_chars* to
:meth:`~argparse.ArgumentParser.add_argument_group` is now
deprecated.
* :mod:`array`'s ``'u'`` format code (:gh:`57281`)
* :mod:`builtins`: * :mod:`builtins`:
* ``bool(NotImplemented)``. * ``bool(NotImplemented)``.
@ -43,6 +33,17 @@ although there is currently no date scheduled for their removal.
as a single positional argument. as a single positional argument.
(Contributed by Serhiy Storchaka in :gh:`109218`.) (Contributed by Serhiy Storchaka in :gh:`109218`.)
* :mod:`argparse`:
* Nesting argument groups and nesting mutually exclusive
groups are deprecated.
* Passing the undocumented keyword argument *prefix_chars* to
:meth:`~argparse.ArgumentParser.add_argument_group` is now
deprecated.
* The :class:`argparse.FileType` type converter is deprecated.
* :mod:`array`'s ``'u'`` format code (:gh:`57281`)
* :mod:`calendar`: ``calendar.January`` and ``calendar.February`` constants are * :mod:`calendar`: ``calendar.January`` and ``calendar.February`` constants are
deprecated and replaced by :data:`calendar.JANUARY` and deprecated and replaced by :data:`calendar.JANUARY` and
:data:`calendar.FEBRUARY`. :data:`calendar.FEBRUARY`.

View file

@ -865,16 +865,14 @@ See also :ref:`specifying-ambiguous-arguments`. The supported values are:
output files:: output files::
>>> parser = argparse.ArgumentParser() >>> parser = argparse.ArgumentParser()
>>> parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), >>> parser.add_argument('infile', nargs='?')
... default=sys.stdin) >>> parser.add_argument('outfile', nargs='?')
>>> parser.add_argument('outfile', nargs='?', type=argparse.FileType('w'),
... default=sys.stdout)
>>> parser.parse_args(['input.txt', 'output.txt']) >>> parser.parse_args(['input.txt', 'output.txt'])
Namespace(infile=<_io.TextIOWrapper name='input.txt' encoding='UTF-8'>, Namespace(infile='input.txt', outfile='output.txt')
outfile=<_io.TextIOWrapper name='output.txt' encoding='UTF-8'>) >>> parser.parse_args(['input.txt'])
Namespace(infile='input.txt', outfile=None)
>>> parser.parse_args([]) >>> parser.parse_args([])
Namespace(infile=<_io.TextIOWrapper name='<stdin>' encoding='UTF-8'>, Namespace(infile=None, outfile=None)
outfile=<_io.TextIOWrapper name='<stdout>' encoding='UTF-8'>)
.. index:: single: * (asterisk); in argparse module .. index:: single: * (asterisk); in argparse module
@ -1033,7 +1031,6 @@ Common built-in types and functions can be used as type converters:
parser.add_argument('distance', type=float) parser.add_argument('distance', type=float)
parser.add_argument('street', type=ascii) parser.add_argument('street', type=ascii)
parser.add_argument('code_point', type=ord) parser.add_argument('code_point', type=ord)
parser.add_argument('dest_file', type=argparse.FileType('w', encoding='latin-1'))
parser.add_argument('datapath', type=pathlib.Path) parser.add_argument('datapath', type=pathlib.Path)
User defined functions can be used as well: User defined functions can be used as well:
@ -1827,9 +1824,19 @@ FileType objects
>>> parser.parse_args(['-']) >>> parser.parse_args(['-'])
Namespace(infile=<_io.TextIOWrapper name='<stdin>' encoding='UTF-8'>) Namespace(infile=<_io.TextIOWrapper name='<stdin>' encoding='UTF-8'>)
.. note::
If one argument uses *FileType* and then a subsequent argument fails,
an error is reported but the file is not automatically closed.
This can also clobber the output files.
In this case, it would be better to wait until after the parser has
run and then use the :keyword:`with`-statement to manage the files.
.. versionchanged:: 3.4 .. versionchanged:: 3.4
Added the *encodings* and *errors* parameters. Added the *encodings* and *errors* parameters.
.. deprecated:: 3.14
Argument groups Argument groups
^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^

View file

@ -464,6 +464,12 @@ Deprecated
as a single positional argument. as a single positional argument.
(Contributed by Serhiy Storchaka in :gh:`109218`.) (Contributed by Serhiy Storchaka in :gh:`109218`.)
* :mod:`argparse`:
Deprecated the :class:`argparse.FileType` type converter.
Anything with resource management should be done downstream after the
arguments are parsed.
(Contributed by Serhiy Storchaka in :gh:`58032`.)
* :mod:`multiprocessing` and :mod:`concurrent.futures`: * :mod:`multiprocessing` and :mod:`concurrent.futures`:
The default start method (see :ref:`multiprocessing-start-methods`) changed The default start method (see :ref:`multiprocessing-start-methods`) changed
away from *fork* to *forkserver* on platforms where it was not already away from *fork* to *forkserver* on platforms where it was not already

View file

@ -18,11 +18,12 @@ command-line and writes the result to a file::
'integers', metavar='int', nargs='+', type=int, 'integers', metavar='int', nargs='+', type=int,
help='an integer to be summed') help='an integer to be summed')
parser.add_argument( parser.add_argument(
'--log', default=sys.stdout, type=argparse.FileType('w'), '--log',
help='the file where the sum should be written') help='the file where the sum should be written')
args = parser.parse_args() args = parser.parse_args()
args.log.write('%s' % sum(args.integers)) with (open(args.log, 'w') if args.log is not None
args.log.close() else contextlib.nullcontext(sys.stdout)) as log:
log.write('%s' % sum(args.integers))
The module contains the following public classes: The module contains the following public classes:
@ -39,7 +40,8 @@ The module contains the following public classes:
- FileType -- A factory for defining types of files to be created. As the - FileType -- A factory for defining types of files to be created. As the
example above shows, instances of FileType are typically passed as example above shows, instances of FileType are typically passed as
the type= argument of add_argument() calls. the type= argument of add_argument() calls. Deprecated since
Python 3.14.
- Action -- The base class for parser actions. Typically actions are - Action -- The base class for parser actions. Typically actions are
selected by passing strings like 'store_true' or 'append_const' to selected by passing strings like 'store_true' or 'append_const' to
@ -1252,7 +1254,7 @@ class _ExtendAction(_AppendAction):
# ============== # ==============
class FileType(object): class FileType(object):
"""Factory for creating file object types """Deprecated factory for creating file object types
Instances of FileType are typically passed as type= arguments to the Instances of FileType are typically passed as type= arguments to the
ArgumentParser add_argument() method. ArgumentParser add_argument() method.
@ -1269,6 +1271,12 @@ class FileType(object):
""" """
def __init__(self, mode='r', bufsize=-1, encoding=None, errors=None): def __init__(self, mode='r', bufsize=-1, encoding=None, errors=None):
import warnings
warnings.warn(
"FileType is deprecated. Simply open files after parsing arguments.",
category=PendingDeprecationWarning,
stacklevel=2
)
self._mode = mode self._mode = mode
self._bufsize = bufsize self._bufsize = bufsize
self._encoding = encoding self._encoding = encoding

View file

@ -1773,27 +1773,43 @@ class TestArgumentsFromFileConverter(TempDirMixin, ParserTestCase):
# Type conversion tests # Type conversion tests
# ===================== # =====================
def FileType(*args, **kwargs):
with warnings.catch_warnings():
warnings.filterwarnings('ignore', 'FileType is deprecated',
PendingDeprecationWarning, __name__)
return argparse.FileType(*args, **kwargs)
class TestFileTypeDeprecation(TestCase):
def test(self):
with self.assertWarns(PendingDeprecationWarning) as cm:
argparse.FileType()
self.assertIn('FileType is deprecated', str(cm.warning))
self.assertEqual(cm.filename, __file__)
class TestFileTypeRepr(TestCase): class TestFileTypeRepr(TestCase):
def test_r(self): def test_r(self):
type = argparse.FileType('r') type = FileType('r')
self.assertEqual("FileType('r')", repr(type)) self.assertEqual("FileType('r')", repr(type))
def test_wb_1(self): def test_wb_1(self):
type = argparse.FileType('wb', 1) type = FileType('wb', 1)
self.assertEqual("FileType('wb', 1)", repr(type)) self.assertEqual("FileType('wb', 1)", repr(type))
def test_r_latin(self): def test_r_latin(self):
type = argparse.FileType('r', encoding='latin_1') type = FileType('r', encoding='latin_1')
self.assertEqual("FileType('r', encoding='latin_1')", repr(type)) self.assertEqual("FileType('r', encoding='latin_1')", repr(type))
def test_w_big5_ignore(self): def test_w_big5_ignore(self):
type = argparse.FileType('w', encoding='big5', errors='ignore') type = FileType('w', encoding='big5', errors='ignore')
self.assertEqual("FileType('w', encoding='big5', errors='ignore')", self.assertEqual("FileType('w', encoding='big5', errors='ignore')",
repr(type)) repr(type))
def test_r_1_replace(self): def test_r_1_replace(self):
type = argparse.FileType('r', 1, errors='replace') type = FileType('r', 1, errors='replace')
self.assertEqual("FileType('r', 1, errors='replace')", repr(type)) self.assertEqual("FileType('r', 1, errors='replace')", repr(type))
@ -1847,7 +1863,6 @@ class RFile(object):
text = text.decode('ascii') text = text.decode('ascii')
return self.name == other.name == text return self.name == other.name == text
class TestFileTypeR(TempDirMixin, ParserTestCase): class TestFileTypeR(TempDirMixin, ParserTestCase):
"""Test the FileType option/argument type for reading files""" """Test the FileType option/argument type for reading files"""
@ -1860,8 +1875,8 @@ class TestFileTypeR(TempDirMixin, ParserTestCase):
self.create_readonly_file('readonly') self.create_readonly_file('readonly')
argument_signatures = [ argument_signatures = [
Sig('-x', type=argparse.FileType()), Sig('-x', type=FileType()),
Sig('spam', type=argparse.FileType('r')), Sig('spam', type=FileType('r')),
] ]
failures = ['-x', '', 'non-existent-file.txt'] failures = ['-x', '', 'non-existent-file.txt']
successes = [ successes = [
@ -1881,7 +1896,7 @@ class TestFileTypeDefaults(TempDirMixin, ParserTestCase):
file.close() file.close()
argument_signatures = [ argument_signatures = [
Sig('-c', type=argparse.FileType('r'), default='no-file.txt'), Sig('-c', type=FileType('r'), default='no-file.txt'),
] ]
# should provoke no such file error # should provoke no such file error
failures = [''] failures = ['']
@ -1900,8 +1915,8 @@ class TestFileTypeRB(TempDirMixin, ParserTestCase):
file.write(file_name) file.write(file_name)
argument_signatures = [ argument_signatures = [
Sig('-x', type=argparse.FileType('rb')), Sig('-x', type=FileType('rb')),
Sig('spam', type=argparse.FileType('rb')), Sig('spam', type=FileType('rb')),
] ]
failures = ['-x', ''] failures = ['-x', '']
successes = [ successes = [
@ -1939,8 +1954,8 @@ class TestFileTypeW(TempDirMixin, ParserTestCase):
self.create_writable_file('writable') self.create_writable_file('writable')
argument_signatures = [ argument_signatures = [
Sig('-x', type=argparse.FileType('w')), Sig('-x', type=FileType('w')),
Sig('spam', type=argparse.FileType('w')), Sig('spam', type=FileType('w')),
] ]
failures = ['-x', '', 'readonly'] failures = ['-x', '', 'readonly']
successes = [ successes = [
@ -1962,8 +1977,8 @@ class TestFileTypeX(TempDirMixin, ParserTestCase):
self.create_writable_file('writable') self.create_writable_file('writable')
argument_signatures = [ argument_signatures = [
Sig('-x', type=argparse.FileType('x')), Sig('-x', type=FileType('x')),
Sig('spam', type=argparse.FileType('x')), Sig('spam', type=FileType('x')),
] ]
failures = ['-x', '', 'readonly', 'writable'] failures = ['-x', '', 'readonly', 'writable']
successes = [ successes = [
@ -1977,8 +1992,8 @@ class TestFileTypeWB(TempDirMixin, ParserTestCase):
"""Test the FileType option/argument type for writing binary files""" """Test the FileType option/argument type for writing binary files"""
argument_signatures = [ argument_signatures = [
Sig('-x', type=argparse.FileType('wb')), Sig('-x', type=FileType('wb')),
Sig('spam', type=argparse.FileType('wb')), Sig('spam', type=FileType('wb')),
] ]
failures = ['-x', ''] failures = ['-x', '']
successes = [ successes = [
@ -1994,8 +2009,8 @@ class TestFileTypeXB(TestFileTypeX):
"Test the FileType option/argument type for writing new binary files only" "Test the FileType option/argument type for writing new binary files only"
argument_signatures = [ argument_signatures = [
Sig('-x', type=argparse.FileType('xb')), Sig('-x', type=FileType('xb')),
Sig('spam', type=argparse.FileType('xb')), Sig('spam', type=FileType('xb')),
] ]
successes = [ successes = [
('-x foo bar', NS(x=WFile('foo'), spam=WFile('bar'))), ('-x foo bar', NS(x=WFile('foo'), spam=WFile('bar'))),
@ -2007,7 +2022,7 @@ class TestFileTypeOpenArgs(TestCase):
"""Test that open (the builtin) is correctly called""" """Test that open (the builtin) is correctly called"""
def test_open_args(self): def test_open_args(self):
FT = argparse.FileType FT = FileType
cases = [ cases = [
(FT('rb'), ('rb', -1, None, None)), (FT('rb'), ('rb', -1, None, None)),
(FT('w', 1), ('w', 1, None, None)), (FT('w', 1), ('w', 1, None, None)),
@ -2022,7 +2037,7 @@ class TestFileTypeOpenArgs(TestCase):
def test_invalid_file_type(self): def test_invalid_file_type(self):
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
argparse.FileType('b')('-test') FileType('b')('-test')
class TestFileTypeMissingInitialization(TestCase): class TestFileTypeMissingInitialization(TestCase):

View file

@ -0,0 +1 @@
Deprecate the :class:`argparse.FileType` type converter.