diff --git a/Lib/pathlib/_os.py b/Lib/pathlib/_os.py index e3751bbcb62377..039836941dd456 100644 --- a/Lib/pathlib/_os.py +++ b/Lib/pathlib/_os.py @@ -3,8 +3,8 @@ """ from errno import * +from io import TextIOWrapper, text_encoding from stat import S_ISDIR, S_ISREG, S_ISLNK, S_IMODE -import io import os import sys try: @@ -172,12 +172,16 @@ def magic_open(path, mode='r', buffering=-1, encoding=None, errors=None, Open the file pointed to by this path and return a file object, as the built-in open() function does. """ + text = 'b' not in mode + if text: + # Call io.text_encoding() here to ensure any warning is raised at an + # appropriate stack level. + encoding = text_encoding(encoding) try: - return io.open(path, mode, buffering, encoding, errors, newline) + return open(path, mode, buffering, encoding, errors, newline) except TypeError: pass cls = type(path) - text = 'b' not in mode mode = ''.join(sorted(c for c in mode if c not in 'bt')) if text: try: @@ -200,7 +204,7 @@ def magic_open(path, mode='r', buffering=-1, encoding=None, errors=None, else: stream = attr(path, buffering) if text: - stream = io.TextIOWrapper(stream, encoding, errors, newline) + stream = TextIOWrapper(stream, encoding, errors, newline) return stream raise TypeError(f"{cls.__name__} can't be opened with mode {mode!r}") diff --git a/Lib/pathlib/types.py b/Lib/pathlib/types.py index d1bb8701b887c8..d8f5c34a1a7513 100644 --- a/Lib/pathlib/types.py +++ b/Lib/pathlib/types.py @@ -12,6 +12,7 @@ from abc import ABC, abstractmethod from glob import _PathGlobber +from io import text_encoding from pathlib._os import magic_open, ensure_distinct_paths, ensure_different_files, copyfileobj from pathlib import PurePath, Path from typing import Optional, Protocol, runtime_checkable @@ -262,6 +263,9 @@ def read_text(self, encoding=None, errors=None, newline=None): """ Open the file in text mode, read it, and close the file. """ + # Call io.text_encoding() here to ensure any warning is raised at an + # appropriate stack level. + encoding = text_encoding(encoding) with magic_open(self, mode='r', encoding=encoding, errors=errors, newline=newline) as f: return f.read() @@ -391,6 +395,9 @@ def write_text(self, data, encoding=None, errors=None, newline=None): """ Open the file in text mode, write to it, and close the file. """ + # Call io.text_encoding() here to ensure any warning is raised at an + # appropriate stack level. + encoding = text_encoding(encoding) if not isinstance(data, str): raise TypeError('data must be str, not %s' % data.__class__.__name__) diff --git a/Lib/test/test_pathlib/test_read.py b/Lib/test/test_pathlib/test_read.py index 753ae5d760aceb..9bb5535a6eb310 100644 --- a/Lib/test/test_pathlib/test_read.py +++ b/Lib/test/test_pathlib/test_read.py @@ -4,6 +4,7 @@ import collections.abc import io +import sys import unittest from .support import is_pypi @@ -35,6 +36,17 @@ def test_open_r(self): self.assertIsInstance(f, io.TextIOBase) self.assertEqual(f.read(), 'this is file A\n') + @unittest.skipIf( + not getattr(sys.flags, 'warn_default_encoding', 0), + "Requires warn_default_encoding", + ) + def test_open_r_encoding_warning(self): + p = self.root / 'fileA' + with self.assertWarns(EncodingWarning) as wc: + with magic_open(p, 'r'): + pass + self.assertEqual(wc.filename, __file__) + def test_open_rb(self): p = self.root / 'fileA' with magic_open(p, 'rb') as f: @@ -55,6 +67,16 @@ def test_read_text(self): self.assertEqual(q.read_text(encoding='latin-1'), 'äbcdefg') self.assertEqual(q.read_text(encoding='utf-8', errors='ignore'), 'bcdefg') + @unittest.skipIf( + not getattr(sys.flags, 'warn_default_encoding', 0), + "Requires warn_default_encoding", + ) + def test_read_text_encoding_warning(self): + p = self.root / 'fileA' + with self.assertWarns(EncodingWarning) as wc: + p.read_text() + self.assertEqual(wc.filename, __file__) + def test_read_text_with_newlines(self): p = self.root / 'abc' self.ground.create_file(p, b'abcde\r\nfghlk\n\rmnopq') diff --git a/Lib/test/test_pathlib/test_write.py b/Lib/test/test_pathlib/test_write.py index d302e0a9caa889..2f3c06b433d224 100644 --- a/Lib/test/test_pathlib/test_write.py +++ b/Lib/test/test_pathlib/test_write.py @@ -4,6 +4,7 @@ import io import os +import sys import unittest from .support import is_pypi @@ -35,6 +36,17 @@ def test_open_w(self): f.write('this is file A\n') self.assertEqual(self.ground.readtext(p), 'this is file A\n') + @unittest.skipIf( + not getattr(sys.flags, 'warn_default_encoding', 0), + "Requires warn_default_encoding", + ) + def test_open_w_encoding_warning(self): + p = self.root / 'fileA' + with self.assertWarns(EncodingWarning) as wc: + with magic_open(p, 'w'): + pass + self.assertEqual(wc.filename, __file__) + def test_open_wb(self): p = self.root / 'fileA' with magic_open(p, 'wb') as f: @@ -61,6 +73,16 @@ def test_write_text(self): self.assertRaises(TypeError, p.write_text, b'somebytes') self.assertEqual(self.ground.readbytes(p), b'\xe4bcdefg') + @unittest.skipIf( + not getattr(sys.flags, 'warn_default_encoding', 0), + "Requires warn_default_encoding", + ) + def test_write_text_encoding_warning(self): + p = self.root / 'fileA' + with self.assertWarns(EncodingWarning) as wc: + p.write_text('abcdefg') + self.assertEqual(wc.filename, __file__) + def test_write_text_with_newlines(self): # Check that `\n` character change nothing p = self.root / 'fileA'