Skip to content

Commit 965636e

Browse files
authored
Explicitly use "locale" encoding for .pth files (#4265)
2 parents bac21fd + 1dd135c commit 965636e

File tree

6 files changed

+40
-17
lines changed

6 files changed

+40
-17
lines changed

docs/conf.py

+4
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@
5555
pattern=r'(Python #|bpo-)(?P<python>\d+)',
5656
url='https://bugs.python.org/issue{python}',
5757
),
58+
dict(
59+
pattern=r'\bpython/cpython#(?P<cpython>\d+)',
60+
url='{GH}/python/cpython/issues/{cpython}',
61+
),
5862
dict(
5963
pattern=r'Interop #(?P<interop>\d+)',
6064
url='{GH}/pypa/interoperability-peps/issues/{interop}',

newsfragments/4265.feature.rst

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Explicitly use ``encoding="locale"`` for ``.pth`` files whenever possible,
2+
to reduce ``EncodingWarnings``.
3+
This avoid errors with UTF-8 (see discussion in python/cpython#77102).

setuptools/command/easy_install.py

+18-13
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
DEVELOP_DIST,
7575
)
7676
import pkg_resources
77-
from ..compat import py311
77+
from ..compat import py39, py311
7878
from .._path import ensure_directory
7979
from ..extern.jaraco.text import yield_lines
8080

@@ -491,7 +491,7 @@ def check_site_dir(self): # noqa: C901 # is too complex (12) # FIXME
491491
try:
492492
if test_exists:
493493
os.unlink(testfile)
494-
open(testfile, 'w').close()
494+
open(testfile, 'wb').close()
495495
os.unlink(testfile)
496496
except OSError:
497497
self.cant_write_to_target()
@@ -576,7 +576,7 @@ def check_pth_processing(self):
576576
_one_liner(
577577
"""
578578
import os
579-
f = open({ok_file!r}, 'w')
579+
f = open({ok_file!r}, 'w', encoding="utf-8")
580580
f.write('OK')
581581
f.close()
582582
"""
@@ -588,7 +588,8 @@ def check_pth_processing(self):
588588
os.unlink(ok_file)
589589
dirname = os.path.dirname(ok_file)
590590
os.makedirs(dirname, exist_ok=True)
591-
f = open(pth_file, 'w')
591+
f = open(pth_file, 'w', encoding=py39.LOCALE_ENCODING)
592+
# ^-- Requires encoding="locale" instead of "utf-8" (python/cpython#77102).
592593
except OSError:
593594
self.cant_write_to_target()
594595
else:
@@ -872,7 +873,7 @@ def write_script(self, script_name, contents, mode="t", blockers=()):
872873
ensure_directory(target)
873874
if os.path.exists(target):
874875
os.unlink(target)
875-
with open(target, "w" + mode) as f:
876+
with open(target, "w" + mode) as f: # TODO: is it safe to use utf-8?
876877
f.write(contents)
877878
chmod(target, 0o777 - mask)
878879

@@ -1016,7 +1017,7 @@ def install_exe(self, dist_filename, tmpdir):
10161017

10171018
# Write EGG-INFO/PKG-INFO
10181019
if not os.path.exists(pkg_inf):
1019-
f = open(pkg_inf, 'w')
1020+
f = open(pkg_inf, 'w') # TODO: probably it is safe to use utf-8
10201021
f.write('Metadata-Version: 1.0\n')
10211022
for k, v in cfg.items('metadata'):
10221023
if k != 'target_version':
@@ -1087,7 +1088,7 @@ def process(src, dst):
10871088
if locals()[name]:
10881089
txt = os.path.join(egg_tmp, 'EGG-INFO', name + '.txt')
10891090
if not os.path.exists(txt):
1090-
f = open(txt, 'w')
1091+
f = open(txt, 'w') # TODO: probably it is safe to use utf-8
10911092
f.write('\n'.join(locals()[name]) + '\n')
10921093
f.close()
10931094

@@ -1277,7 +1278,9 @@ def update_pth(self, dist): # noqa: C901 # is too complex (11) # FIXME
12771278
filename = os.path.join(self.install_dir, 'setuptools.pth')
12781279
if os.path.islink(filename):
12791280
os.unlink(filename)
1280-
with open(filename, 'wt') as f:
1281+
1282+
with open(filename, 'wt', encoding=py39.LOCALE_ENCODING) as f:
1283+
# Requires encoding="locale" instead of "utf-8" (python/cpython#77102).
12811284
f.write(self.pth_file.make_relative(dist.location) + '\n')
12821285

12831286
def unpack_progress(self, src, dst):
@@ -1503,9 +1506,9 @@ def expand_paths(inputs): # noqa: C901 # is too complex (11) # FIXME
15031506
continue
15041507

15051508
# Read the .pth file
1506-
f = open(os.path.join(dirname, name))
1507-
lines = list(yield_lines(f))
1508-
f.close()
1509+
with open(os.path.join(dirname, name), encoding=py39.LOCALE_ENCODING) as f:
1510+
# Requires encoding="locale" instead of "utf-8" (python/cpython#77102).
1511+
lines = list(yield_lines(f))
15091512

15101513
# Yield existing non-dupe, non-import directory lines from it
15111514
for line in lines:
@@ -1619,7 +1622,8 @@ def _load_raw(self):
16191622
paths = []
16201623
dirty = saw_import = False
16211624
seen = dict.fromkeys(self.sitedirs)
1622-
f = open(self.filename, 'rt')
1625+
f = open(self.filename, 'rt', encoding=py39.LOCALE_ENCODING)
1626+
# ^-- Requires encoding="locale" instead of "utf-8" (python/cpython#77102).
16231627
for line in f:
16241628
path = line.rstrip()
16251629
# still keep imports and empty/commented lines for formatting
@@ -1690,7 +1694,8 @@ def save(self):
16901694
data = '\n'.join(lines) + '\n'
16911695
if os.path.islink(self.filename):
16921696
os.unlink(self.filename)
1693-
with open(self.filename, 'wt') as f:
1697+
with open(self.filename, 'wt', encoding=py39.LOCALE_ENCODING) as f:
1698+
# Requires encoding="locale" instead of "utf-8" (python/cpython#77102).
16941699
f.write(data)
16951700
elif os.path.exists(self.filename):
16961701
log.debug("Deleting empty %s", self.filename)

setuptools/command/editable_wheel.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import io
1515
import os
1616
import shutil
17-
import sys
1817
import traceback
1918
from contextlib import suppress
2019
from enum import Enum
@@ -44,6 +43,7 @@
4443
namespaces,
4544
)
4645
from .._path import StrPath
46+
from ..compat import py39
4747
from ..discovery import find_package_path
4848
from ..dist import Distribution
4949
from ..warnings import (
@@ -558,9 +558,8 @@ def _encode_pth(content: str) -> bytes:
558558
(There seems to be some variety in the way different version of Python handle
559559
``encoding=None``, not all of them use ``locale.getpreferredencoding(False)``).
560560
"""
561-
encoding = "locale" if sys.version_info >= (3, 10) else None
562561
with io.BytesIO() as buffer:
563-
wrapper = io.TextIOWrapper(buffer, encoding)
562+
wrapper = io.TextIOWrapper(buffer, encoding=py39.LOCALE_ENCODING)
564563
wrapper.write(content)
565564
wrapper.flush()
566565
buffer.seek(0)

setuptools/compat/py39.py

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import sys
2+
3+
# Explicitly use the ``"locale"`` encoding in versions that support it,
4+
# otherwise just rely on the implicit handling of ``encoding=None``.
5+
# Since all platforms that support ``EncodingWarning`` also support
6+
# ``encoding="locale"``, this can be used to suppress the warning.
7+
# However, please try to use UTF-8 when possible
8+
# (.pth files are the notorious exception: python/cpython#77102, pypa/setuptools#3937).
9+
LOCALE_ENCODING = "locale" if sys.version_info >= (3, 10) else None

setuptools/namespaces.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from distutils import log
33
import itertools
44

5+
from .compat import py39
6+
57

68
flatten = itertools.chain.from_iterable
79

@@ -23,7 +25,8 @@ def install_namespaces(self):
2325
list(lines)
2426
return
2527

26-
with open(filename, 'wt') as f:
28+
with open(filename, 'wt', encoding=py39.LOCALE_ENCODING) as f:
29+
# Requires encoding="locale" instead of "utf-8" (python/cpython#77102).
2730
f.writelines(lines)
2831

2932
def uninstall_namespaces(self):

0 commit comments

Comments
 (0)