diff --git a/Lib/tempfile.py b/Lib/tempfile.py index cadb0bed3cce3b..4da3e9af7de7d6 100644 --- a/Lib/tempfile.py +++ b/Lib/tempfile.py @@ -57,6 +57,12 @@ if hasattr(_os, 'O_BINARY'): _bin_openflags |= _os.O_BINARY +def _get_bin_openflags(mode): + if "r" in mode or "+" in mode: + return _bin_openflags + else: + return (_bin_openflags & ~_os.O_RDWR) | _os.O_WRONLY + if hasattr(_os, 'TMP_MAX'): TMP_MAX = _os.TMP_MAX else: @@ -583,7 +589,7 @@ def NamedTemporaryFile(mode='w+b', buffering=-1, encoding=None, prefix, suffix, dir, output_type = _sanitize_params(prefix, suffix, dir) - flags = _bin_openflags + flags = _get_bin_openflags(mode) # Setting O_TEMPORARY in the flags causes the OS to delete # the file when it is closed. This is only supported by Windows. @@ -650,7 +656,7 @@ def TemporaryFile(mode='w+b', buffering=-1, encoding=None, prefix, suffix, dir, output_type = _sanitize_params(prefix, suffix, dir) - flags = _bin_openflags + flags = _get_bin_openflags(mode) if _O_TMPFILE_WORKS: fd = None def opener(*args): diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py index d46d3c0f040601..5bd02ba4fe04f2 100644 --- a/Lib/test/test_tempfile.py +++ b/Lib/test/test_tempfile.py @@ -940,14 +940,30 @@ def test_many(self): # We test _TemporaryFileWrapper by testing NamedTemporaryFile. -class TestNamedTemporaryFile(BaseTestCase): +class BaseTemporaryFileTestCases: + def test_wronly_mode(self): + with mock.patch('os.open', wraps=os.open) as spy: + with self.do_create(mode='wb') as fileobj: + fileobj.write(b'abc') + fileobj.seek(0) + self.assertRaises(io.UnsupportedOperation, fileobj.read) + with self.assertRaises(OSError): + os.read(fileobj.fileno(), 1) + + flags = spy.call_args[0][1] + self.assertEqual(flags & os.O_RDONLY, 0x0) + self.assertNotEqual(flags & os.O_RDWR, os.O_RDWR) + self.assertEqual(flags & os.O_WRONLY, os.O_WRONLY) + self.assertEqual(flags & os.O_EXCL, os.O_EXCL) + +class TestNamedTemporaryFile(BaseTestCase, BaseTemporaryFileTestCases): """Test NamedTemporaryFile().""" - def do_create(self, dir=None, pre="", suf="", delete=True): + def do_create(self, dir=None, pre="", suf="", delete=True, mode='w+b'): if dir is None: dir = tempfile.gettempdir() file = tempfile.NamedTemporaryFile(dir=dir, prefix=pre, suffix=suf, - delete=delete) + delete=delete, mode=mode) self.nameCheck(file.name, dir, pre, suf) return file @@ -1519,9 +1535,12 @@ def test_class_getitem(self): if tempfile.NamedTemporaryFile is not tempfile.TemporaryFile: - class TestTemporaryFile(BaseTestCase): + class TestTemporaryFile(BaseTestCase, BaseTemporaryFileTestCases): """Test TemporaryFile().""" + def do_create(self, *args, **kwargs): + return tempfile.TemporaryFile(*args, **kwargs) + def test_basic(self): # TemporaryFile can create files # No point in testing the name params - the file has no name. diff --git a/Misc/NEWS.d/next/Library/2025-05-01-16-10-47.gh-issue-96531.4WHMzW.rst b/Misc/NEWS.d/next/Library/2025-05-01-16-10-47.gh-issue-96531.4WHMzW.rst new file mode 100644 index 00000000000000..5cf6dc57f28f6f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-05-01-16-10-47.gh-issue-96531.4WHMzW.rst @@ -0,0 +1,3 @@ +Use the ``O_WRONLY`` flag instead of ``O_RDWR`` when opening file descriptors +for :class:`tempfile.TemporaryFile` and :class:`tempfile.NamedTemporaryFile` +objects created with a write-only mode).