Skip to content

GH-128520: pathlib ABCs: raise text encoding warnings at correct stack level #133051

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 28, 2025
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
12 changes: 8 additions & 4 deletions Lib/pathlib/_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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}")
Expand Down
7 changes: 7 additions & 0 deletions Lib/pathlib/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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__)
Expand Down
22 changes: 22 additions & 0 deletions Lib/test/test_pathlib/test_read.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import collections.abc
import io
import sys
import unittest

from .support import is_pypi
Expand Down Expand Up @@ -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:
Expand All @@ -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')
Expand Down
22 changes: 22 additions & 0 deletions Lib/test/test_pathlib/test_write.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import io
import os
import sys
import unittest

from .support import is_pypi
Expand Down Expand Up @@ -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:
Expand All @@ -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'
Expand Down
Loading