Skip to content

Commit

Permalink
Use default encoding when creating .pth files in editable_wheel (#4009)
Browse files Browse the repository at this point in the history
  • Loading branch information
abravalheri committed Aug 15, 2023
2 parents 9e24163 + 8aefb2a commit db4036a
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 2 deletions.
1 change: 1 addition & 0 deletions newsfragments/4009.misc.rst
@@ -0,0 +1 @@
Use default encoding to create ``.pth`` files with ``editable_wheel``.
23 changes: 21 additions & 2 deletions setuptools/command/editable_wheel.py
Expand Up @@ -11,6 +11,7 @@
"""

import logging
import io
import os
import shutil
import sys
Expand Down Expand Up @@ -401,7 +402,7 @@ def __init__(self, dist: Distribution, name: str, path_entries: List[Path]):

def __call__(self, wheel: "WheelFile", files: List[str], mapping: Dict[str, str]):
entries = "\n".join((str(p.resolve()) for p in self.path_entries))
contents = bytes(f"{entries}\n", "utf-8")
contents = _encode_pth(f"{entries}\n")
wheel.writestr(f"__editable__.{self.name}.pth", contents)

def __enter__(self):
Expand Down Expand Up @@ -509,7 +510,7 @@ def __call__(self, wheel: "WheelFile", files: List[str], mapping: Dict[str, str]
content = bytes(_finder_template(name, roots, namespaces_), "utf-8")
wheel.writestr(f"{finder}.py", content)

content = bytes(f"import {finder}; {finder}.install()", "utf-8")
content = _encode_pth(f"import {finder}; {finder}.install()")
wheel.writestr(f"__editable__.{self.name}.pth", content)

def __enter__(self):
Expand All @@ -525,6 +526,24 @@ def __exit__(self, _exc_type, _exc_value, _traceback):
InformationOnly.emit("Editable installation.", msg)


def _encode_pth(content: str) -> bytes:
""".pth files are always read with 'locale' encoding, the recommendation
from the cpython core developers is to write them as ``open(path, "w")``
and ignore warnings (see python/cpython#77102, pypa/setuptools#3937).
This function tries to simulate this behaviour without having to create an
actual file, in a way that supports a range of active Python versions.
(There seems to be some variety in the way different version of Python handle
``encoding=None``, not all of them use ``locale.getpreferredencoding(False)``).
"""
encoding = "locale" if sys.version_info >= (3, 10) else None
with io.BytesIO() as buffer:
wrapper = io.TextIOWrapper(buffer, encoding)
wrapper.write(content)
wrapper.flush()
buffer.seek(0)
return buffer.read()


def _can_symlink_files(base_dir: Path) -> bool:
with TemporaryDirectory(dir=str(base_dir.resolve())) as tmp:
path1, path2 = Path(tmp, "file1.txt"), Path(tmp, "file2.txt")
Expand Down
8 changes: 8 additions & 0 deletions setuptools/tests/test_editable_install.py
Expand Up @@ -22,6 +22,7 @@
from setuptools.command.editable_wheel import (
_DebuggingTips,
_LinkTree,
_encode_pth,
_find_virtual_namespaces,
_find_namespaces,
_find_package_roots,
Expand Down Expand Up @@ -1107,6 +1108,13 @@ def test_debugging_tips(tmpdir_cwd, monkeypatch):
cmd.run()


@pytest.mark.filterwarnings("error")
def test_encode_pth():
"""Ensure _encode_pth function does not produce encoding warnings"""
content = _encode_pth("tkmilan_ç_utf8") # no warnings (would be turned into errors)
assert isinstance(content, bytes)


def install_project(name, venv, tmp_path, files, *opts):
project = tmp_path / name
project.mkdir()
Expand Down

0 comments on commit db4036a

Please sign in to comment.