Skip to content

Commit 834ba5a

Browse files
gh-58032: Deprecate the argparse.FileType type converter (GH-124664)
1 parent c75ff2e commit 834ba5a

File tree

6 files changed

+83
-45
lines changed

6 files changed

+83
-45
lines changed

Doc/deprecations/pending-removal-in-future.rst

+11-10
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,6 @@ Pending removal in future versions
44
The following APIs will be removed in the future,
55
although there is currently no date scheduled for their removal.
66

7-
* :mod:`argparse`:
8-
9-
* Nesting argument groups and nesting mutually exclusive
10-
groups are deprecated.
11-
* Passing the undocumented keyword argument *prefix_chars* to
12-
:meth:`~argparse.ArgumentParser.add_argument_group` is now
13-
deprecated.
14-
15-
* :mod:`array`'s ``'u'`` format code (:gh:`57281`)
16-
177
* :mod:`builtins`:
188

199
* ``bool(NotImplemented)``.
@@ -43,6 +33,17 @@ although there is currently no date scheduled for their removal.
4333
as a single positional argument.
4434
(Contributed by Serhiy Storchaka in :gh:`109218`.)
4535

36+
* :mod:`argparse`:
37+
38+
* Nesting argument groups and nesting mutually exclusive
39+
groups are deprecated.
40+
* Passing the undocumented keyword argument *prefix_chars* to
41+
:meth:`~argparse.ArgumentParser.add_argument_group` is now
42+
deprecated.
43+
* The :class:`argparse.FileType` type converter is deprecated.
44+
45+
* :mod:`array`'s ``'u'`` format code (:gh:`57281`)
46+
4647
* :mod:`calendar`: ``calendar.January`` and ``calendar.February`` constants are
4748
deprecated and replaced by :data:`calendar.JANUARY` and
4849
:data:`calendar.FEBRUARY`.

Doc/library/argparse.rst

+16-9
Original file line numberDiff line numberDiff line change
@@ -865,16 +865,14 @@ See also :ref:`specifying-ambiguous-arguments`. The supported values are:
865865
output files::
866866

867867
>>> parser = argparse.ArgumentParser()
868-
>>> parser.add_argument('infile', nargs='?', type=argparse.FileType('r'),
869-
... default=sys.stdin)
870-
>>> parser.add_argument('outfile', nargs='?', type=argparse.FileType('w'),
871-
... default=sys.stdout)
868+
>>> parser.add_argument('infile', nargs='?')
869+
>>> parser.add_argument('outfile', nargs='?')
872870
>>> parser.parse_args(['input.txt', 'output.txt'])
873-
Namespace(infile=<_io.TextIOWrapper name='input.txt' encoding='UTF-8'>,
874-
outfile=<_io.TextIOWrapper name='output.txt' encoding='UTF-8'>)
871+
Namespace(infile='input.txt', outfile='output.txt')
872+
>>> parser.parse_args(['input.txt'])
873+
Namespace(infile='input.txt', outfile=None)
875874
>>> parser.parse_args([])
876-
Namespace(infile=<_io.TextIOWrapper name='<stdin>' encoding='UTF-8'>,
877-
outfile=<_io.TextIOWrapper name='<stdout>' encoding='UTF-8'>)
875+
Namespace(infile=None, outfile=None)
878876

879877
.. index:: single: * (asterisk); in argparse module
880878

@@ -1033,7 +1031,6 @@ Common built-in types and functions can be used as type converters:
10331031
parser.add_argument('distance', type=float)
10341032
parser.add_argument('street', type=ascii)
10351033
parser.add_argument('code_point', type=ord)
1036-
parser.add_argument('dest_file', type=argparse.FileType('w', encoding='latin-1'))
10371034
parser.add_argument('datapath', type=pathlib.Path)
10381035

10391036
User defined functions can be used as well:
@@ -1827,9 +1824,19 @@ FileType objects
18271824
>>> parser.parse_args(['-'])
18281825
Namespace(infile=<_io.TextIOWrapper name='<stdin>' encoding='UTF-8'>)
18291826

1827+
.. note::
1828+
1829+
If one argument uses *FileType* and then a subsequent argument fails,
1830+
an error is reported but the file is not automatically closed.
1831+
This can also clobber the output files.
1832+
In this case, it would be better to wait until after the parser has
1833+
run and then use the :keyword:`with`-statement to manage the files.
1834+
18301835
.. versionchanged:: 3.4
18311836
Added the *encodings* and *errors* parameters.
18321837

1838+
.. deprecated:: 3.14
1839+
18331840

18341841
Argument groups
18351842
^^^^^^^^^^^^^^^

Doc/whatsnew/3.14.rst

+6
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,12 @@ Deprecated
464464
as a single positional argument.
465465
(Contributed by Serhiy Storchaka in :gh:`109218`.)
466466

467+
* :mod:`argparse`:
468+
Deprecated the :class:`argparse.FileType` type converter.
469+
Anything with resource management should be done downstream after the
470+
arguments are parsed.
471+
(Contributed by Serhiy Storchaka in :gh:`58032`.)
472+
467473
* :mod:`multiprocessing` and :mod:`concurrent.futures`:
468474
The default start method (see :ref:`multiprocessing-start-methods`) changed
469475
away from *fork* to *forkserver* on platforms where it was not already

Lib/argparse.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@
1818
'integers', metavar='int', nargs='+', type=int,
1919
help='an integer to be summed')
2020
parser.add_argument(
21-
'--log', default=sys.stdout, type=argparse.FileType('w'),
21+
'--log',
2222
help='the file where the sum should be written')
2323
args = parser.parse_args()
24-
args.log.write('%s' % sum(args.integers))
25-
args.log.close()
24+
with (open(args.log, 'w') if args.log is not None
25+
else contextlib.nullcontext(sys.stdout)) as log:
26+
log.write('%s' % sum(args.integers))
2627
2728
The module contains the following public classes:
2829
@@ -39,7 +40,8 @@
3940
4041
- FileType -- A factory for defining types of files to be created. As the
4142
example above shows, instances of FileType are typically passed as
42-
the type= argument of add_argument() calls.
43+
the type= argument of add_argument() calls. Deprecated since
44+
Python 3.14.
4345
4446
- Action -- The base class for parser actions. Typically actions are
4547
selected by passing strings like 'store_true' or 'append_const' to
@@ -1252,7 +1254,7 @@ def __call__(self, parser, namespace, values, option_string=None):
12521254
# ==============
12531255

12541256
class FileType(object):
1255-
"""Factory for creating file object types
1257+
"""Deprecated factory for creating file object types
12561258
12571259
Instances of FileType are typically passed as type= arguments to the
12581260
ArgumentParser add_argument() method.
@@ -1269,6 +1271,12 @@ class FileType(object):
12691271
"""
12701272

12711273
def __init__(self, mode='r', bufsize=-1, encoding=None, errors=None):
1274+
import warnings
1275+
warnings.warn(
1276+
"FileType is deprecated. Simply open files after parsing arguments.",
1277+
category=PendingDeprecationWarning,
1278+
stacklevel=2
1279+
)
12721280
self._mode = mode
12731281
self._bufsize = bufsize
12741282
self._encoding = encoding

Lib/test/test_argparse.py

+36-21
Original file line numberDiff line numberDiff line change
@@ -1773,27 +1773,43 @@ def convert_arg_line_to_args(self, arg_line):
17731773
# Type conversion tests
17741774
# =====================
17751775

1776+
def FileType(*args, **kwargs):
1777+
with warnings.catch_warnings():
1778+
warnings.filterwarnings('ignore', 'FileType is deprecated',
1779+
PendingDeprecationWarning, __name__)
1780+
return argparse.FileType(*args, **kwargs)
1781+
1782+
1783+
class TestFileTypeDeprecation(TestCase):
1784+
1785+
def test(self):
1786+
with self.assertWarns(PendingDeprecationWarning) as cm:
1787+
argparse.FileType()
1788+
self.assertIn('FileType is deprecated', str(cm.warning))
1789+
self.assertEqual(cm.filename, __file__)
1790+
1791+
17761792
class TestFileTypeRepr(TestCase):
17771793

17781794
def test_r(self):
1779-
type = argparse.FileType('r')
1795+
type = FileType('r')
17801796
self.assertEqual("FileType('r')", repr(type))
17811797

17821798
def test_wb_1(self):
1783-
type = argparse.FileType('wb', 1)
1799+
type = FileType('wb', 1)
17841800
self.assertEqual("FileType('wb', 1)", repr(type))
17851801

17861802
def test_r_latin(self):
1787-
type = argparse.FileType('r', encoding='latin_1')
1803+
type = FileType('r', encoding='latin_1')
17881804
self.assertEqual("FileType('r', encoding='latin_1')", repr(type))
17891805

17901806
def test_w_big5_ignore(self):
1791-
type = argparse.FileType('w', encoding='big5', errors='ignore')
1807+
type = FileType('w', encoding='big5', errors='ignore')
17921808
self.assertEqual("FileType('w', encoding='big5', errors='ignore')",
17931809
repr(type))
17941810

17951811
def test_r_1_replace(self):
1796-
type = argparse.FileType('r', 1, errors='replace')
1812+
type = FileType('r', 1, errors='replace')
17971813
self.assertEqual("FileType('r', 1, errors='replace')", repr(type))
17981814

17991815

@@ -1847,7 +1863,6 @@ def __eq__(self, other):
18471863
text = text.decode('ascii')
18481864
return self.name == other.name == text
18491865

1850-
18511866
class TestFileTypeR(TempDirMixin, ParserTestCase):
18521867
"""Test the FileType option/argument type for reading files"""
18531868

@@ -1860,8 +1875,8 @@ def setUp(self):
18601875
self.create_readonly_file('readonly')
18611876

18621877
argument_signatures = [
1863-
Sig('-x', type=argparse.FileType()),
1864-
Sig('spam', type=argparse.FileType('r')),
1878+
Sig('-x', type=FileType()),
1879+
Sig('spam', type=FileType('r')),
18651880
]
18661881
failures = ['-x', '', 'non-existent-file.txt']
18671882
successes = [
@@ -1881,7 +1896,7 @@ def setUp(self):
18811896
file.close()
18821897

18831898
argument_signatures = [
1884-
Sig('-c', type=argparse.FileType('r'), default='no-file.txt'),
1899+
Sig('-c', type=FileType('r'), default='no-file.txt'),
18851900
]
18861901
# should provoke no such file error
18871902
failures = ['']
@@ -1900,8 +1915,8 @@ def setUp(self):
19001915
file.write(file_name)
19011916

19021917
argument_signatures = [
1903-
Sig('-x', type=argparse.FileType('rb')),
1904-
Sig('spam', type=argparse.FileType('rb')),
1918+
Sig('-x', type=FileType('rb')),
1919+
Sig('spam', type=FileType('rb')),
19051920
]
19061921
failures = ['-x', '']
19071922
successes = [
@@ -1939,8 +1954,8 @@ def setUp(self):
19391954
self.create_writable_file('writable')
19401955

19411956
argument_signatures = [
1942-
Sig('-x', type=argparse.FileType('w')),
1943-
Sig('spam', type=argparse.FileType('w')),
1957+
Sig('-x', type=FileType('w')),
1958+
Sig('spam', type=FileType('w')),
19441959
]
19451960
failures = ['-x', '', 'readonly']
19461961
successes = [
@@ -1962,8 +1977,8 @@ def setUp(self):
19621977
self.create_writable_file('writable')
19631978

19641979
argument_signatures = [
1965-
Sig('-x', type=argparse.FileType('x')),
1966-
Sig('spam', type=argparse.FileType('x')),
1980+
Sig('-x', type=FileType('x')),
1981+
Sig('spam', type=FileType('x')),
19671982
]
19681983
failures = ['-x', '', 'readonly', 'writable']
19691984
successes = [
@@ -1977,8 +1992,8 @@ class TestFileTypeWB(TempDirMixin, ParserTestCase):
19771992
"""Test the FileType option/argument type for writing binary files"""
19781993

19791994
argument_signatures = [
1980-
Sig('-x', type=argparse.FileType('wb')),
1981-
Sig('spam', type=argparse.FileType('wb')),
1995+
Sig('-x', type=FileType('wb')),
1996+
Sig('spam', type=FileType('wb')),
19821997
]
19831998
failures = ['-x', '']
19841999
successes = [
@@ -1994,8 +2009,8 @@ class TestFileTypeXB(TestFileTypeX):
19942009
"Test the FileType option/argument type for writing new binary files only"
19952010

19962011
argument_signatures = [
1997-
Sig('-x', type=argparse.FileType('xb')),
1998-
Sig('spam', type=argparse.FileType('xb')),
2012+
Sig('-x', type=FileType('xb')),
2013+
Sig('spam', type=FileType('xb')),
19992014
]
20002015
successes = [
20012016
('-x foo bar', NS(x=WFile('foo'), spam=WFile('bar'))),
@@ -2007,7 +2022,7 @@ class TestFileTypeOpenArgs(TestCase):
20072022
"""Test that open (the builtin) is correctly called"""
20082023

20092024
def test_open_args(self):
2010-
FT = argparse.FileType
2025+
FT = FileType
20112026
cases = [
20122027
(FT('rb'), ('rb', -1, None, None)),
20132028
(FT('w', 1), ('w', 1, None, None)),
@@ -2022,7 +2037,7 @@ def test_open_args(self):
20222037

20232038
def test_invalid_file_type(self):
20242039
with self.assertRaises(ValueError):
2025-
argparse.FileType('b')('-test')
2040+
FileType('b')('-test')
20262041

20272042

20282043
class TestFileTypeMissingInitialization(TestCase):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Deprecate the :class:`argparse.FileType` type converter.

0 commit comments

Comments
 (0)