Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 28 additions & 27 deletions Lib/_pyio.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,8 @@
# Rebind for compatibility
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
_CHECK_ERRORS = (hasattr(sys, "gettotalrefcount") or sys.flags.dev_mode)


def text_encoding(encoding, stacklevel=2):
Expand Down Expand Up @@ -416,18 +413,12 @@ def __del__(self):
if closed:
return

if _IOBASE_EMITS_UNRAISABLE:
self.close()
else:
# The try/except block is in case this is called at program
# exit time, when it's possible that globals have already been
# deleted, and then the close() call might fail. Since
# there's nothing we can do about such failures and they annoy
# the end users, we suppress the traceback.
try:
self.close()
except:
pass
if dealloc_warn := getattr(self, "_dealloc_warn", None):
dealloc_warn(self)

# If close() fails, the caller logs the exception with
# sys.unraisablehook. close() must be called at the end at __del__().
self.close()

### Inquiries ###

Expand Down Expand Up @@ -632,16 +623,15 @@ def read(self, size=-1):
n = self.readinto(b)
if n is None:
return None
if n < 0 or n > len(b):
raise ValueError(f"readinto returned {n} outside buffer size {len(b)}")
del b[n:]
return bytes(b)

def readall(self):
"""Read until EOF, using multiple read() call."""
res = bytearray()
while True:
data = self.read(DEFAULT_BUFFER_SIZE)
if not data:
break
while data := self.read(DEFAULT_BUFFER_SIZE):
res += data
if res:
return bytes(res)
Expand All @@ -666,8 +656,6 @@ def write(self, b):
self._unsupported("write")

io.RawIOBase.register(RawIOBase)
from _io import FileIO
RawIOBase.register(FileIO)


class BufferedIOBase(IOBase):
Expand Down Expand Up @@ -874,6 +862,10 @@ def __repr__(self):
else:
return "<{}.{} name={!r}>".format(modname, clsname, name)

def _dealloc_warn(self, source):
if dealloc_warn := getattr(self.raw, "_dealloc_warn", None):
dealloc_warn(source)

### Lower-level APIs ###

def fileno(self):
Expand Down Expand Up @@ -1511,6 +1503,11 @@ def __init__(self, file, mode='r', closefd=True, opener=None):
if isinstance(file, float):
raise TypeError('integer argument expected, got float')
if isinstance(file, int):
if isinstance(file, bool):
import warnings
warnings.warn("bool is used as a file descriptor",
RuntimeWarning, stacklevel=2)
file = int(file)
fd = file
if fd < 0:
raise ValueError('negative file descriptor')
Expand Down Expand Up @@ -1569,7 +1566,8 @@ def __init__(self, file, mode='r', closefd=True, opener=None):
if not isinstance(fd, int):
raise TypeError('expected integer from opener')
if fd < 0:
raise OSError('Negative file descriptor')
# bpo-27066: Raise a ValueError for bad value.
raise ValueError(f'opener returned {fd}')
owned_fd = fd
if not noinherit_flag:
os.set_inheritable(fd, False)
Expand Down Expand Up @@ -1608,12 +1606,11 @@ def __init__(self, file, mode='r', closefd=True, opener=None):
raise
self._fd = fd

def __del__(self):
def _dealloc_warn(self, source):
if self._fd >= 0 and self._closefd and not self.closed:
import warnings
warnings.warn('unclosed file %r' % (self,), ResourceWarning,
warnings.warn(f'unclosed file {source!r}', ResourceWarning,
stacklevel=2, source=self)
self.close()

def __getstate__(self):
raise TypeError(f"cannot pickle {self.__class__.__name__!r} object")
Expand Down Expand Up @@ -1757,7 +1754,7 @@ def close(self):
"""
if not self.closed:
try:
if self._closefd:
if self._closefd and self._fd >= 0:
os.close(self._fd)
finally:
super().close()
Expand Down Expand Up @@ -2649,6 +2646,10 @@ def readline(self, size=None):
def newlines(self):
return self._decoder.newlines if self._decoder else None

def _dealloc_warn(self, source):
if dealloc_warn := getattr(self.buffer, "_dealloc_warn", None):
dealloc_warn(source)


class StringIO(TextIOWrapper):
"""Text I/O implementation using an in-memory buffer.
Expand Down
36 changes: 1 addition & 35 deletions Lib/test/test_file_eintr.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,15 +186,7 @@ def test_readall(self):
class CTestFileIOSignalInterrupt(TestFileIOSignalInterrupt, unittest.TestCase):
modname = '_io'

# TODO: RUSTPYTHON - _io module uses _pyio internally, signal handling differs
@unittest.expectedFailure
def test_readline(self):
super().test_readline()

@unittest.expectedFailure
def test_readlines(self):
super().test_readlines()

# TODO: RUSTPYTHON - _io.FileIO.readall uses read_to_end which differs from _pyio.FileIO.readall
@unittest.expectedFailure
def test_readall(self):
super().test_readall()
Expand All @@ -221,19 +213,6 @@ def test_readall(self):
class CTestBufferedIOSignalInterrupt(TestBufferedIOSignalInterrupt, unittest.TestCase):
modname = '_io'

# TODO: RUSTPYTHON - _io module uses _pyio internally, signal handling differs
@unittest.expectedFailure
def test_readline(self):
super().test_readline()

@unittest.expectedFailure
def test_readlines(self):
super().test_readlines()

@unittest.expectedFailure
def test_readall(self):
super().test_readall()

class PyTestBufferedIOSignalInterrupt(TestBufferedIOSignalInterrupt, unittest.TestCase):
modname = '_pyio'

Expand Down Expand Up @@ -273,19 +252,6 @@ def test_readall(self):
class CTestTextIOSignalInterrupt(TestTextIOSignalInterrupt, unittest.TestCase):
modname = '_io'

# TODO: RUSTPYTHON - _io module uses _pyio internally, signal handling differs
@unittest.expectedFailure
def test_readline(self):
super().test_readline()

@unittest.expectedFailure
def test_readlines(self):
super().test_readlines()

@unittest.expectedFailure
def test_readall(self):
super().test_readall()

class PyTestTextIOSignalInterrupt(TestTextIOSignalInterrupt, unittest.TestCase):
modname = '_pyio'

Expand Down
26 changes: 0 additions & 26 deletions Lib/test/test_fileio.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,27 +363,6 @@ class CAutoFileTests(AutoFileTests, unittest.TestCase):
FileIO = _io.FileIO
modulename = '_io'

@unittest.expectedFailure # TODO: RUSTPYTHON
def testBlksize(self):
return super().testBlksize()

@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def testErrnoOnClosedTruncate(self):
return super().testErrnoOnClosedTruncate()

@unittest.expectedFailure # TODO: RUSTPYTHON
def testMethods(self):
return super().testMethods()

@unittest.expectedFailure # TODO: RUSTPYTHON
def testOpenDirFD(self):
return super().testOpenDirFD()

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_subclass_repr(self):
return super().test_subclass_repr()

@unittest.skipIf(sys.platform == "win32", "TODO: RUSTPYTHON, test setUp errors on Windows")
class PyAutoFileTests(AutoFileTests, unittest.TestCase):
FileIO = _pyio.FileIO
modulename = '_pyio'
Expand Down Expand Up @@ -506,7 +485,6 @@ def testInvalidFd(self):
import msvcrt
self.assertRaises(OSError, msvcrt.get_osfhandle, make_bad_fd())

@unittest.expectedFailure # TODO: RUSTPYTHON
def testBooleanFd(self):
for fd in False, True:
with self.assertWarnsRegex(RuntimeWarning,
Expand Down Expand Up @@ -634,10 +612,6 @@ def test_open_code(self):
actual = f.read()
self.assertEqual(expected, actual)

@unittest.expectedFailure # TODO: RUSTPYTHON
def testUnclosedFDOnException(self):
return super().testUnclosedFDOnException()


class PyOtherFileTests(OtherFileTests, unittest.TestCase):
FileIO = _pyio.FileIO
Expand Down
Loading
Loading