gh-133036: Deprecate codecs.open (#133038)

Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Co-authored-by: Victor Stinner <vstinner@python.org>
This commit is contained in:
Inada Naoki 2025-04-30 10:11:09 +09:00 committed by GitHub
parent 732d1b0241
commit 4e294f6feb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 58 additions and 37 deletions

View File

@ -47,6 +47,8 @@ although there is currently no date scheduled for their removal.
:data:`calendar.FEBRUARY`. :data:`calendar.FEBRUARY`.
(Contributed by Prince Roshan in :gh:`103636`.) (Contributed by Prince Roshan in :gh:`103636`.)
* :mod:`codecs`: use :func:`open` instead of :func:`codecs.open`. (:gh:`133038`)
* :attr:`codeobject.co_lnotab`: use the :meth:`codeobject.co_lines` method * :attr:`codeobject.co_lnotab`: use the :meth:`codeobject.co_lines` method
instead. instead.

View File

@ -208,6 +208,10 @@ wider range of codecs when working with binary files:
.. versionchanged:: 3.11 .. versionchanged:: 3.11
The ``'U'`` mode has been removed. The ``'U'`` mode has been removed.
.. deprecated:: next
:func:`codecs.open` has been superseded by :func:`open`.
.. function:: EncodedFile(file, data_encoding, file_encoding=None, errors='strict') .. function:: EncodedFile(file, data_encoding, file_encoding=None, errors='strict')

View File

@ -1597,6 +1597,10 @@ 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:`codecs`:
:func:`codecs.open` is now deprecated. Use :func:`open` instead.
(Contributed by Inada Naoki in :gh:`133036`.)
* :mod:`functools`: * :mod:`functools`:
Calling the Python implementation of :func:`functools.reduce` with *function* Calling the Python implementation of :func:`functools.reduce` with *function*
or *sequence* as keyword arguments is now deprecated. or *sequence* as keyword arguments is now deprecated.

View File

@ -2056,8 +2056,7 @@ def __init__(self, buffer, encoding=None, errors=None, newline=None,
raise ValueError("invalid encoding: %r" % encoding) raise ValueError("invalid encoding: %r" % encoding)
if not codecs.lookup(encoding)._is_text_encoding: if not codecs.lookup(encoding)._is_text_encoding:
msg = ("%r is not a text encoding; " msg = "%r is not a text encoding"
"use codecs.open() to handle arbitrary codecs")
raise LookupError(msg % encoding) raise LookupError(msg % encoding)
if errors is None: if errors is None:

View File

@ -884,7 +884,6 @@ def __reduce_ex__(self, proto):
### Shortcuts ### Shortcuts
def open(filename, mode='r', encoding=None, errors='strict', buffering=-1): def open(filename, mode='r', encoding=None, errors='strict', buffering=-1):
""" Open an encoded file using the given mode and return """ Open an encoded file using the given mode and return
a wrapped version providing transparent encoding/decoding. a wrapped version providing transparent encoding/decoding.
@ -912,8 +911,11 @@ def open(filename, mode='r', encoding=None, errors='strict', buffering=-1):
.encoding which allows querying the used encoding. This .encoding which allows querying the used encoding. This
attribute is only available if an encoding was specified as attribute is only available if an encoding was specified as
parameter. parameter.
""" """
import warnings
warnings.warn("codecs.open() is deprecated. Use open() instead.",
DeprecationWarning, stacklevel=2)
if encoding is not None and \ if encoding is not None and \
'b' not in mode: 'b' not in mode:
# Force opening of the file in binary mode # Force opening of the file in binary mode

View File

@ -7,6 +7,7 @@
import unittest import unittest
import encodings import encodings
from unittest import mock from unittest import mock
import warnings
from test import support from test import support
from test.support import os_helper from test.support import os_helper
@ -20,13 +21,12 @@
except ImportError: except ImportError:
_testinternalcapi = None _testinternalcapi = None
try:
import ctypes def codecs_open_no_warn(*args, **kwargs):
except ImportError: """Call codecs.open(*args, **kwargs) ignoring DeprecationWarning."""
ctypes = None with warnings.catch_warnings():
SIZEOF_WCHAR_T = -1 warnings.simplefilter("ignore")
else: return codecs.open(*args, **kwargs)
SIZEOF_WCHAR_T = ctypes.sizeof(ctypes.c_wchar)
def coding_checker(self, coder): def coding_checker(self, coder):
def check(input, expect): def check(input, expect):
@ -35,13 +35,13 @@ def check(input, expect):
# On small versions of Windows like Windows IoT or Windows Nano Server not all codepages are present # On small versions of Windows like Windows IoT or Windows Nano Server not all codepages are present
def is_code_page_present(cp): def is_code_page_present(cp):
from ctypes import POINTER, WINFUNCTYPE, WinDLL from ctypes import POINTER, WINFUNCTYPE, WinDLL, Structure
from ctypes.wintypes import BOOL, BYTE, WCHAR, UINT, DWORD from ctypes.wintypes import BOOL, BYTE, WCHAR, UINT, DWORD
MAX_LEADBYTES = 12 # 5 ranges, 2 bytes ea., 0 term. MAX_LEADBYTES = 12 # 5 ranges, 2 bytes ea., 0 term.
MAX_DEFAULTCHAR = 2 # single or double byte MAX_DEFAULTCHAR = 2 # single or double byte
MAX_PATH = 260 MAX_PATH = 260
class CPINFOEXW(ctypes.Structure): class CPINFOEXW(Structure):
_fields_ = [("MaxCharSize", UINT), _fields_ = [("MaxCharSize", UINT),
("DefaultChar", BYTE*MAX_DEFAULTCHAR), ("DefaultChar", BYTE*MAX_DEFAULTCHAR),
("LeadByte", BYTE*MAX_LEADBYTES), ("LeadByte", BYTE*MAX_LEADBYTES),
@ -719,19 +719,19 @@ def test_bug691291(self):
self.addCleanup(os_helper.unlink, os_helper.TESTFN) self.addCleanup(os_helper.unlink, os_helper.TESTFN)
with open(os_helper.TESTFN, 'wb') as fp: with open(os_helper.TESTFN, 'wb') as fp:
fp.write(s) fp.write(s)
with codecs.open(os_helper.TESTFN, 'r', with codecs_open_no_warn(os_helper.TESTFN, 'r',
encoding=self.encoding) as reader: encoding=self.encoding) as reader:
self.assertEqual(reader.read(), s1) self.assertEqual(reader.read(), s1)
def test_invalid_modes(self): def test_invalid_modes(self):
for mode in ('U', 'rU', 'r+U'): for mode in ('U', 'rU', 'r+U'):
with self.assertRaises(ValueError) as cm: with self.assertRaises(ValueError) as cm:
codecs.open(os_helper.TESTFN, mode, encoding=self.encoding) codecs_open_no_warn(os_helper.TESTFN, mode, encoding=self.encoding)
self.assertIn('invalid mode', str(cm.exception)) self.assertIn('invalid mode', str(cm.exception))
for mode in ('rt', 'wt', 'at', 'r+t'): for mode in ('rt', 'wt', 'at', 'r+t'):
with self.assertRaises(ValueError) as cm: with self.assertRaises(ValueError) as cm:
codecs.open(os_helper.TESTFN, mode, encoding=self.encoding) codecs_open_no_warn(os_helper.TESTFN, mode, encoding=self.encoding)
self.assertIn("can't have text and binary mode at once", self.assertIn("can't have text and binary mode at once",
str(cm.exception)) str(cm.exception))
@ -1844,8 +1844,8 @@ def test_all(self):
def test_open(self): def test_open(self):
self.addCleanup(os_helper.unlink, os_helper.TESTFN) self.addCleanup(os_helper.unlink, os_helper.TESTFN)
for mode in ('w', 'r', 'r+', 'w+', 'a', 'a+'): for mode in ('w', 'r', 'r+', 'w+', 'a', 'a+'):
with self.subTest(mode), \ with self.subTest(mode), self.assertWarns(DeprecationWarning):
codecs.open(os_helper.TESTFN, mode, 'ascii') as file: with codecs.open(os_helper.TESTFN, mode, 'ascii') as file:
self.assertIsInstance(file, codecs.StreamReaderWriter) self.assertIsInstance(file, codecs.StreamReaderWriter)
def test_undefined(self): def test_undefined(self):
@ -1863,7 +1863,7 @@ def test_file_closes_if_lookup_error_raised(self):
mock_open = mock.mock_open() mock_open = mock.mock_open()
with mock.patch('builtins.open', mock_open) as file: with mock.patch('builtins.open', mock_open) as file:
with self.assertRaises(LookupError): with self.assertRaises(LookupError):
codecs.open(os_helper.TESTFN, 'wt', 'invalid-encoding') codecs_open_no_warn(os_helper.TESTFN, 'wt', 'invalid-encoding')
file().close.assert_called() file().close.assert_called()
@ -2883,7 +2883,7 @@ def test_seek0(self):
self.addCleanup(os_helper.unlink, os_helper.TESTFN) self.addCleanup(os_helper.unlink, os_helper.TESTFN)
for encoding in tests: for encoding in tests:
# Check if the BOM is written only once # Check if the BOM is written only once
with codecs.open(os_helper.TESTFN, 'w+', encoding=encoding) as f: with codecs_open_no_warn(os_helper.TESTFN, 'w+', encoding=encoding) as f:
f.write(data) f.write(data)
f.write(data) f.write(data)
f.seek(0) f.seek(0)
@ -2892,7 +2892,7 @@ def test_seek0(self):
self.assertEqual(f.read(), data * 2) self.assertEqual(f.read(), data * 2)
# Check that the BOM is written after a seek(0) # Check that the BOM is written after a seek(0)
with codecs.open(os_helper.TESTFN, 'w+', encoding=encoding) as f: with codecs_open_no_warn(os_helper.TESTFN, 'w+', encoding=encoding) as f:
f.write(data[0]) f.write(data[0])
self.assertNotEqual(f.tell(), 0) self.assertNotEqual(f.tell(), 0)
f.seek(0) f.seek(0)
@ -2901,7 +2901,7 @@ def test_seek0(self):
self.assertEqual(f.read(), data) self.assertEqual(f.read(), data)
# (StreamWriter) Check that the BOM is written after a seek(0) # (StreamWriter) Check that the BOM is written after a seek(0)
with codecs.open(os_helper.TESTFN, 'w+', encoding=encoding) as f: with codecs_open_no_warn(os_helper.TESTFN, 'w+', encoding=encoding) as f:
f.writer.write(data[0]) f.writer.write(data[0])
self.assertNotEqual(f.writer.tell(), 0) self.assertNotEqual(f.writer.tell(), 0)
f.writer.seek(0) f.writer.seek(0)
@ -2911,7 +2911,7 @@ def test_seek0(self):
# Check that the BOM is not written after a seek() at a position # Check that the BOM is not written after a seek() at a position
# different than the start # different than the start
with codecs.open(os_helper.TESTFN, 'w+', encoding=encoding) as f: with codecs_open_no_warn(os_helper.TESTFN, 'w+', encoding=encoding) as f:
f.write(data) f.write(data)
f.seek(f.tell()) f.seek(f.tell())
f.write(data) f.write(data)
@ -2920,7 +2920,7 @@ def test_seek0(self):
# (StreamWriter) Check that the BOM is not written after a seek() # (StreamWriter) Check that the BOM is not written after a seek()
# at a position different than the start # at a position different than the start
with codecs.open(os_helper.TESTFN, 'w+', encoding=encoding) as f: with codecs_open_no_warn(os_helper.TESTFN, 'w+', encoding=encoding) as f:
f.writer.write(data) f.writer.write(data)
f.writer.seek(f.writer.tell()) f.writer.seek(f.writer.tell())
f.writer.write(data) f.writer.write(data)

View File

@ -314,6 +314,7 @@ def test_bug1728403(self):
f.write(b'\xa1') f.write(b'\xa1')
finally: finally:
f.close() f.close()
with self.assertWarns(DeprecationWarning):
f = codecs.open(TESTFN, encoding='cp949') f = codecs.open(TESTFN, encoding='cp949')
try: try:
self.assertRaises(UnicodeDecodeError, f.read, 2) self.assertRaises(UnicodeDecodeError, f.read, 2)

View File

@ -1,5 +1,4 @@
# regression test for SAX 2.0 # regression test for SAX 2.0
# $Id$
from xml.sax import make_parser, ContentHandler, \ from xml.sax import make_parser, ContentHandler, \
SAXException, SAXReaderNotAvailable, SAXParseException SAXException, SAXReaderNotAvailable, SAXParseException
@ -832,6 +831,7 @@ class StreamReaderWriterXmlgenTest(XmlgenTest, unittest.TestCase):
fname = os_helper.TESTFN + '-codecs' fname = os_helper.TESTFN + '-codecs'
def ioclass(self): def ioclass(self):
with self.assertWarns(DeprecationWarning):
writer = codecs.open(self.fname, 'w', encoding='ascii', writer = codecs.open(self.fname, 'w', encoding='ascii',
errors='xmlcharrefreplace', buffering=0) errors='xmlcharrefreplace', buffering=0)
def cleanup(): def cleanup():

View File

@ -0,0 +1,2 @@
:func:`codecs.open` is now deprecated. Use :func:`open` instead. Contributed
by Inada Naoki.

View File

@ -1185,7 +1185,7 @@ _io_TextIOWrapper___init___impl(textio *self, PyObject *buffer,
} }
/* Check we have been asked for a real text encoding */ /* Check we have been asked for a real text encoding */
codec_info = _PyCodec_LookupTextEncoding(encoding, "codecs.open()"); codec_info = _PyCodec_LookupTextEncoding(encoding, NULL);
if (codec_info == NULL) { if (codec_info == NULL) {
Py_CLEAR(self->encoding); Py_CLEAR(self->encoding);
goto error; goto error;
@ -1324,8 +1324,7 @@ textiowrapper_change_encoding(textio *self, PyObject *encoding,
} }
// Create new encoder & decoder // Create new encoder & decoder
PyObject *codec_info = _PyCodec_LookupTextEncoding( PyObject *codec_info = _PyCodec_LookupTextEncoding(c_encoding, NULL);
c_encoding, "codecs.open()");
if (codec_info == NULL) { if (codec_info == NULL) {
Py_DECREF(encoding); Py_DECREF(encoding);
Py_DECREF(errors); Py_DECREF(errors);

View File

@ -540,11 +540,19 @@ PyObject * _PyCodec_LookupTextEncoding(const char *encoding,
Py_DECREF(attr); Py_DECREF(attr);
if (is_text_codec <= 0) { if (is_text_codec <= 0) {
Py_DECREF(codec); Py_DECREF(codec);
if (!is_text_codec) if (!is_text_codec) {
if (alternate_command != NULL) {
PyErr_Format(PyExc_LookupError, PyErr_Format(PyExc_LookupError,
"'%.400s' is not a text encoding; " "'%.400s' is not a text encoding; "
"use %s to handle arbitrary codecs", "use %s to handle arbitrary codecs",
encoding, alternate_command); encoding, alternate_command);
}
else {
PyErr_Format(PyExc_LookupError,
"'%.400s' is not a text encoding",
encoding);
}
}
return NULL; return NULL;
} }
} }