From af50fa55129f052b85604fd5543e1c8193a3df75 Mon Sep 17 00:00:00 2001 From: Padraic Fanning Date: Sun, 30 Jan 2022 19:42:13 -0500 Subject: [PATCH] Update _pyio.py to CPython 3.10 --- Lib/_pyio.py | 106 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 70 insertions(+), 36 deletions(-) diff --git a/Lib/_pyio.py b/Lib/_pyio.py index fd31b8ca9..56e9a0cb3 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -36,8 +36,40 @@ BlockingIOError = BlockingIOError # Does io.IOBase finalizer log the exception if the close() method fails? # The exception is ignored silently by default in release build. _IOBASE_EMITS_UNRAISABLE = (hasattr(sys, "gettotalrefcount") or sys.flags.dev_mode) +# Does open() check its 'errors' argument? +_CHECK_ERRORS = _IOBASE_EMITS_UNRAISABLE +def text_encoding(encoding, stacklevel=2): + """ + A helper function to choose the text encoding. + + When encoding is not None, just return it. + Otherwise, return the default text encoding (i.e. "locale"). + + This function emits an EncodingWarning if *encoding* is None and + sys.flags.warn_default_encoding is true. + + This can be used in APIs with an encoding=None parameter + that pass it to TextIOWrapper or open. + However, please consider using encoding="utf-8" for new APIs. + """ + if encoding is None: + encoding = "locale" + if sys.flags.warn_default_encoding: + import warnings + warnings.warn("'encoding' argument not specified.", + EncodingWarning, stacklevel + 1) + return encoding + + +# Wrapper for builtins.open +# +# Trick so that open() won't become a bound method when stored +# as a class variable (as dbm.dumb does). +# +# See init_set_builtins_open() in Python/pylifecycle.c. +@staticmethod def open(file, mode="r", buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None): @@ -246,6 +278,7 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None, result = buffer if binary: return result + encoding = text_encoding(encoding) text = TextIOWrapper(buffer, encoding, errors, newline, line_buffering) result = text text.mode = mode @@ -278,27 +311,20 @@ except AttributeError: open_code = _open_code_with_warning -class DocDescriptor: - """Helper for builtins.open.__doc__ - """ - def __get__(self, obj, typ=None): - return ( - "open(file, mode='r', buffering=-1, encoding=None, " - "errors=None, newline=None, closefd=True)\n\n" + - open.__doc__) - -class OpenWrapper: - """Wrapper for builtins.open - - Trick so that open won't become a bound method when stored - as a class variable (as dbm.dumb does). - - See initstdio() in Python/pylifecycle.c. - """ - __doc__ = DocDescriptor() - - def __new__(cls, *args, **kwargs): - return open(*args, **kwargs) +def __getattr__(name): + if name == "OpenWrapper": + # bpo-43680: Until Python 3.9, _pyio.open was not a static method and + # builtins.open was set to OpenWrapper to not become a bound method + # when set to a class variable. _io.open is a built-in function whereas + # _pyio.open is a Python function. In Python 3.10, _pyio.open() is now + # a static method, and builtins.open() is now io.open(). + import warnings + warnings.warn('OpenWrapper is deprecated, use open instead', + DeprecationWarning, stacklevel=2) + global OpenWrapper + OpenWrapper = open + return OpenWrapper + raise AttributeError(name) # In normal operation, both `UnsupportedOperation`s should be bound to the @@ -802,6 +828,9 @@ class _BufferedIOMixin(BufferedIOBase): return pos def truncate(self, pos=None): + self._checkClosed() + self._checkWritable() + # Flush the stream. We're mixing buffered I/O with lower-level I/O, # and a flush may be necessary to synch both views of the current # file state. @@ -1571,7 +1600,7 @@ class FileIO(RawIOBase): raise IsADirectoryError(errno.EISDIR, os.strerror(errno.EISDIR), file) except AttributeError: - # Ignore the AttribueError if stat.S_ISDIR or errno.EISDIR + # Ignore the AttributeError if stat.S_ISDIR or errno.EISDIR # don't exist. pass self._blksize = getattr(fdfstat, 'st_blksize', 0) @@ -1999,19 +2028,22 @@ class TextIOWrapper(TextIOBase): def __init__(self, buffer, encoding=None, errors=None, newline=None, line_buffering=False, write_through=False): self._check_newline(newline) - if encoding is None: + encoding = text_encoding(encoding) + + if encoding == "locale": try: - encoding = os.device_encoding(buffer.fileno()) + encoding = os.device_encoding(buffer.fileno()) or "locale" except (AttributeError, UnsupportedOperation): pass - if encoding is None: - try: - import locale - except ImportError: - # Importing locale may fail if Python is being built - encoding = "ascii" - else: - encoding = locale.getpreferredencoding(False) + + if encoding == "locale": + try: + import locale + except ImportError: + # Importing locale may fail if Python is being built + encoding = "utf-8" + else: + encoding = locale.getpreferredencoding(False) if not isinstance(encoding, str): raise ValueError("invalid encoding: %r" % encoding) @@ -2026,6 +2058,8 @@ class TextIOWrapper(TextIOBase): else: if not isinstance(errors, str): raise ValueError("invalid errors: %r" % errors) + if _CHECK_ERRORS: + codecs.lookup_error(errors) self._buffer = buffer self._decoded_chars = '' # buffer for text returned from decoder @@ -2295,7 +2329,7 @@ class TextIOWrapper(TextIOBase): return not eof def _pack_cookie(self, position, dec_flags=0, - bytes_to_feed=0, need_eof=0, chars_to_skip=0): + bytes_to_feed=0, need_eof=False, chars_to_skip=0): # The meaning of a tell() cookie is: seek to position, set the # decoder flags to dec_flags, read bytes_to_feed bytes, feed them # into the decoder with need_eof as the EOF flag, then skip @@ -2309,7 +2343,7 @@ class TextIOWrapper(TextIOBase): rest, dec_flags = divmod(rest, 1<<64) rest, bytes_to_feed = divmod(rest, 1<<64) need_eof, chars_to_skip = divmod(rest, 1<<64) - return position, dec_flags, bytes_to_feed, need_eof, chars_to_skip + return position, dec_flags, bytes_to_feed, bool(need_eof), chars_to_skip def tell(self): if not self._seekable: @@ -2383,7 +2417,7 @@ class TextIOWrapper(TextIOBase): # (a point where the decoder has nothing buffered, so seek() # can safely start from there and advance to this location). bytes_fed = 0 - need_eof = 0 + need_eof = False # Chars decoded since `start_pos` chars_decoded = 0 for i in range(skip_bytes, len(next_input)): @@ -2400,7 +2434,7 @@ class TextIOWrapper(TextIOBase): else: # We didn't get enough decoded data; signal EOF to get more. chars_decoded += len(decoder.decode(b'', final=True)) - need_eof = 1 + need_eof = True if chars_decoded < chars_to_skip: raise OSError("can't reconstruct logical file position")