Skip to content
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

Sepcify the encoding #2515

Merged
merged 5 commits into from
Mar 12, 2023
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
1 change: 1 addition & 0 deletions docs/changelog/2515.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Do not assume the default encoding.
4 changes: 2 additions & 2 deletions src/virtualenv/app_data/via_disk_folder.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def exists(self):
def read(self):
data, bad_format = None, False
try:
data = json.loads(self.file.read_text())
data = json.loads(self.file.read_text(encoding="utf-8"))
logging.debug(f"got {self.msg} from %s", *self.msg_args)
return data
except ValueError:
Expand All @@ -151,7 +151,7 @@ def locked(self):
def write(self, content):
folder = self.file.parent
folder.mkdir(parents=True, exist_ok=True)
self.file.write_text(json.dumps(content, sort_keys=True, indent=2))
self.file.write_text(json.dumps(content, sort_keys=True, indent=2), encoding="utf-8")
logging.debug(f"wrote {self.msg} at %s", *self.msg_args)


Expand Down
2 changes: 1 addition & 1 deletion src/virtualenv/config/ini.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def __init__(self, env=None):
logging.error("failed to read config file %s because %r", config_file, exception)

def _load(self):
with self.config_file.open("rt") as file_handler:
with self.config_file.open("rt", encoding="utf-8") as file_handler:
return self.config_parser.read_file(file_handler)

def get(self, key, as_type):
Expand Down
2 changes: 1 addition & 1 deletion src/virtualenv/create/creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def setup_ignore_vcs(self):
# mark this folder to be ignored by VCS, handle https://www.python.org/dev/peps/pep-0610/#registered-vcs
git_ignore = self.dest / ".gitignore"
if not git_ignore.exists():
git_ignore.write_text("# created by virtualenv automatically\n*\n")
git_ignore.write_text("# created by virtualenv automatically\n*\n", encoding="utf-8")
# Mercurial - does not support the .hgignore file inside a subdirectory directly, but only if included via the
# subinclude directive from root, at which point on might as well ignore the directory itself, see
# https://www.selenic.com/mercurial/hgignore.5.html for more details
Expand Down
6 changes: 3 additions & 3 deletions src/virtualenv/create/via_global_ref/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,15 @@ def install_patch(self):
if text:
pth = self.purelib / "_virtualenv.pth"
logging.debug("create virtualenv import hook file %s", pth)
pth.write_text("import _virtualenv")
pth.write_text("import _virtualenv", encoding="utf-8")
dest_path = self.purelib / "_virtualenv.py"
logging.debug("create %s", dest_path)
dest_path.write_text(text)
dest_path.write_text(text, encoding="utf-8")

def env_patch_text(self):
"""Patch the distutils package to not be derailed by its configuration files"""
with self.app_data.ensure_extracted(Path(__file__).parent / "_virtualenv.py") as resolved_path:
text = resolved_path.read_text()
text = resolved_path.read_text(encoding="utf-8")
return text.replace('"__SCRIPT_DIR__"', repr(os.path.relpath(str(self.script_dir), str(self.purelib))))

def _args(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def create(self):
if IS_ZIPAPP:
custom_site_text = read_from_zipapp(custom_site)
else:
custom_site_text = custom_site.read_text()
custom_site_text = custom_site.read_text(encoding="utf-8")
expected = json.dumps([os.path.relpath(str(i), str(site_py)) for i in self.libs])

custom_site_text = custom_site_text.replace("___EXPECTED_SITE_PACKAGES___", expected)
Expand All @@ -42,7 +42,7 @@ def create(self):
skip_rewrite = os.linesep.join(f" {i}" for i in self.skip_rewrite.splitlines()).lstrip()
custom_site_text = custom_site_text.replace("# ___SKIP_REWRITE____", skip_rewrite)

site_py.write_text(custom_site_text)
site_py.write_text(custom_site_text, encoding="utf-8")

@property
def reload_code(self):
Expand Down
1 change: 1 addition & 0 deletions src/virtualenv/discovery/cached_py_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ def _run_subprocess(cls, exe, app_data, env):
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
env=env,
encoding="utf-8",
)
out, err = process.communicate()
code = process.returncode
Expand Down
16 changes: 11 additions & 5 deletions src/virtualenv/seed/embed/via_app_data/pip_install/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ def _records_text(self, files):
def _generate_new_files(self):
new_files = set()
installer = self._dist_info / "INSTALLER"
installer.write_text("pip\n")
installer.write_text("pip\n", encoding="utf-8")
new_files.add(installer)
# inject a no-op root element, as workaround for bug in https://github.com/pypa/pip/issues/7226
marker = self._image_dir / f"{self._dist_info.stem}.virtualenv"
marker.write_text("")
marker.write_text("", encoding="utf-8")
new_files.add(marker)
folder = mkdtemp()
try:
Expand Down Expand Up @@ -120,7 +120,7 @@ def _console_scripts(self):
entry_points = self._dist_info / "entry_points.txt"
if entry_points.exists():
parser = ConfigParser()
with entry_points.open() as file_handler:
with entry_points.open(encoding="utf-8") as file_handler:
parser.read_file(file_handler)
if "console_scripts" in parser.sections():
for name, value in parser.items("console_scripts"):
Expand Down Expand Up @@ -152,11 +152,17 @@ def _uninstall_dist(dist):
logging.debug("uninstall existing distribution %s from %s", dist.stem, dist_base)

top_txt = dist / "top_level.txt" # add top level packages at folder level
paths = {dist.parent / i.strip() for i in top_txt.read_text().splitlines()} if top_txt.exists() else set()
paths = (
{dist.parent / i.strip() for i in top_txt.read_text(encoding="utf-8").splitlines()}
if top_txt.exists()
else set()
)
paths.add(dist) # add the dist-info folder itself

base_dirs, record = paths.copy(), dist / "RECORD" # collect entries in record that we did not register yet
for name in (i.split(",")[0] for i in record.read_text().splitlines()) if record.exists() else ():
for name in (
(i.split(",")[0] for i in record.read_text(encoding="utf-8").splitlines()) if record.exists() else ()
):
path = dist_base / name
if not any(p in base_dirs for p in path.parents): # only add if not already added as a base dir
paths.add(path)
Expand Down
2 changes: 1 addition & 1 deletion src/virtualenv/seed/wheels/acquire.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def download_wheel(distribution, version_spec, for_py_version, search_dirs, app_
]
# pip has no interface in python - must be a new sub-process
env = pip_wheel_env_run(search_dirs, app_data, env)
process = Popen(cmd, env=env, stdout=PIPE, stderr=PIPE, universal_newlines=True)
process = Popen(cmd, env=env, stdout=PIPE, stderr=PIPE, universal_newlines=True, encoding="utf-8")
out, err = process.communicate()
if process.returncode != 0:
kwargs = {"output": out, "stderr": err}
Expand Down
1 change: 1 addition & 0 deletions src/virtualenv/util/subprocess/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def run_cmd(cmd):
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
encoding="utf-8",
)
out, err = process.communicate() # input disabled
code = process.returncode
Expand Down
3 changes: 2 additions & 1 deletion tests/unit/activation/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def get_version(self, raise_on_fail):
universal_newlines=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
)
out, err = process.communicate()
result = out if out else err
Expand Down Expand Up @@ -220,7 +221,7 @@ def activation_python(request, tmp_path_factory, special_char_name, current_fast
cmd += ["--prompt", special_char_name]
session = cli_run(cmd)
pydoc_test = session.creator.purelib / "pydoc_test.py"
pydoc_test.write_text('"""This is pydoc_test.py"""')
pydoc_test.write_text('"""This is pydoc_test.py"""', encoding="utf-8")
return session


Expand Down
2 changes: 1 addition & 1 deletion tests/unit/activation/test_activate_this.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ def test_python_activator_cross(session_app_data, cross_python, special_name_dir
results = activator.generate(session.creator)
assert len(results) == 1
result = results[0]
content = result.read_text()
content = result.read_text(encoding="utf-8")
# check that the repr strings have been correctly stripped
assert "\"'" not in content
2 changes: 1 addition & 1 deletion tests/unit/activation/test_batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
@pytest.mark.usefixtures("activation_python")
def test_batch(activation_tester_class, activation_tester, tmp_path):
version_script = tmp_path / "version.bat"
version_script.write_text("ver")
version_script.write_text("ver", encoding="utf-8")

class Batch(activation_tester_class):
def __init__(self, session):
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/activation/test_fish.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def test_fish(activation_tester_class, activation_tester, monkeypatch, tmp_path)
monkeypatch.setenv("HOME", str(tmp_path))
fish_conf_dir = tmp_path / ".config" / "fish"
fish_conf_dir.mkdir(parents=True)
(fish_conf_dir / "config.fish").write_text("")
(fish_conf_dir / "config.fish").write_text("", encoding="utf-8")

class Fish(activation_tester_class):
def __init__(self, session):
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/activation/test_python_activator.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def print_r(value):

file_at = {str(activate_script)!r}
# CPython 2 requires non-ascii path open to be unicode
with open(file_at, "r") as file_handler:
with open(file_at, "r", encoding='utf-8') as file_handler:
content = file_handler.read()
exec(content, {{"__file__": file_at}})

Expand Down
8 changes: 7 additions & 1 deletion tests/unit/config/test___main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@


def test_main():
process = Popen([sys.executable, "-m", "virtualenv", "--help"], universal_newlines=True, stdout=PIPE)
process = Popen(
[sys.executable, "-m", "virtualenv", "--help"],
universal_newlines=True,
stdout=PIPE,
encoding="utf-8",
)
out, _ = process.communicate()
assert not process.returncode
assert out
Expand Down Expand Up @@ -92,6 +97,7 @@ def test_session_report_subprocess(tmp_path):
out = check_output(
[sys.executable, "-m", "virtualenv", str(tmp_path), "--activators", "powershell", "--without-pip"],
text=True,
encoding="utf-8",
)
lines = out.split("\n")
regexes = [
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/config/test_env_var.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
def _empty_conf(tmp_path, monkeypatch):
conf = tmp_path / "conf.ini"
monkeypatch.setenv(IniConfig.VIRTUALENV_CONFIG_FILE_ENV_VAR, str(conf))
conf.write_text("[virtualenv]")
conf.write_text("[virtualenv]", encoding="utf-8")


@pytest.mark.usefixtures("_empty_conf")
Expand Down
1 change: 1 addition & 0 deletions tests/unit/config/test_ini.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def test_ini_can_be_overwritten_by_flag(tmp_path, monkeypatch):
copies = True
""",
),
encoding="utf-8",
)
monkeypatch.setenv("VIRTUALENV_CONFIG_FILE", str(custom_ini))

Expand Down
25 changes: 14 additions & 11 deletions tests/unit/create/test_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def _non_success_exit_code(capsys, target):

def test_destination_exists_file(tmp_path, capsys):
target = tmp_path / "out"
target.write_text("")
target.write_text("", encoding="utf-8")
err = _non_success_exit_code(capsys, str(target))
msg = f"the destination {str(target)} already exists and is a file"
assert msg in err, err
Expand Down Expand Up @@ -230,15 +230,15 @@ def list_to_str(iterable):
make_file = debug["makefile_filename"]
assert os.path.exists(make_file)

git_ignore = (dest / ".gitignore").read_text()
git_ignore = (dest / ".gitignore").read_text(encoding="utf-8")
assert git_ignore.splitlines() == ["# created by virtualenv automatically", "*"]


def test_create_vcs_ignore_exists(tmp_path):
git_ignore = tmp_path / ".gitignore"
git_ignore.write_text("magic")
git_ignore.write_text("magic", encoding="utf-8")
cli_run([str(tmp_path), "--without-pip", "--activators", ""])
assert git_ignore.read_text() == "magic"
assert git_ignore.read_text(encoding="utf-8") == "magic"


def test_create_vcs_ignore_override(tmp_path):
Expand All @@ -249,9 +249,9 @@ def test_create_vcs_ignore_override(tmp_path):

def test_create_vcs_ignore_exists_override(tmp_path):
git_ignore = tmp_path / ".gitignore"
git_ignore.write_text("magic")
git_ignore.write_text("magic", encoding="utf-8")
cli_run([str(tmp_path), "--without-pip", "--no-vcs-ignore", "--activators", ""])
assert git_ignore.read_text() == "magic"
assert git_ignore.read_text(encoding="utf-8") == "magic"


@pytest.mark.skipif(not CURRENT.has_venv, reason="requires interpreter with venv")
Expand All @@ -268,7 +268,7 @@ def _session_via_cli(args, options=None, setup_logging=True, env=None):
mocker.patch("virtualenv.run.session_via_cli", side_effect=_session_via_cli)
before = tmp_path.stat().st_mode
cfg_path = tmp_path / "pyvenv.cfg"
cfg_path.write_text("")
cfg_path.write_text("", encoding="utf-8")
cfg = str(cfg_path)
try:
os.chmod(cfg, stat.S_IREAD | stat.S_IRGRP | stat.S_IROTH)
Expand All @@ -293,7 +293,7 @@ def test_create_clear_resets(tmp_path, creator, clear, caplog):
cmd = [str(tmp_path), "--seeder", "app-data", "--without-pip", "--creator", creator, "-vvv"]
cli_run(cmd)

marker.write_text("") # if we a marker file this should be gone on a clear run, remain otherwise
marker.write_text("", encoding="utf-8") # if we a marker file this should be gone on a clear run, remain otherwise
assert marker.exists()

cli_run(cmd + (["--clear"] if clear else []))
Expand Down Expand Up @@ -430,7 +430,7 @@ def test_create_distutils_cfg(creator, tmp_path, monkeypatch):
install_data={tmp_path}{os.sep}data
""",
)
setup_cfg.write_text(setup_cfg.read_text() + conf)
setup_cfg.write_text(setup_cfg.read_text(encoding="utf-8") + conf, encoding="utf-8")

monkeypatch.chdir(dest) # distutils will read the setup.cfg from the cwd, so change to that

Expand Down Expand Up @@ -509,7 +509,7 @@ def test_pth_in_site_vs_python_path(tmp_path):
session = cli_run([str(tmp_path)])
site_packages = str(session.creator.purelib)
# install test.pth that sets sys.testpth='ok'
with open(os.path.join(site_packages, "test.pth"), "w") as f:
with open(os.path.join(site_packages, "test.pth"), "w", encoding="utf-8") as f:
f.write('import sys; sys.testpth="ok"\n')
# verify that test.pth is activated when interpreter is run
out = subprocess.check_output(
Expand Down Expand Up @@ -595,7 +595,10 @@ def test_debug_bad_virtualenv(tmp_path):
result = cli_run(cmd)
# if the site.py is removed/altered the debug should fail as no one is around to fix the paths
cust = result.creator.purelib / "_a.pth"
cust.write_text('import sys; sys.stdout.write("std-out"); sys.stderr.write("std-err"); raise SystemExit(1)')
cust.write_text(
'import sys; sys.stdout.write("std-out"); sys.stderr.write("std-err"); raise SystemExit(1)',
encoding="utf-8",
)
debug_info = result.creator.debug
assert debug_info["returncode"] == 1
assert "std-err" in debug_info["err"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ def fixture_file(fixture_name):


def read_fixture(fixture_name):
fixture_json = fixture_file(fixture_name).read_text()
fixture_json = fixture_file(fixture_name).read_text(encoding="utf-8")
return PythonInfo._from_json(fixture_json)
1 change: 1 addition & 0 deletions tests/unit/create/via_global_ref/test_build_c_ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def test_can_build_c_extensions(creator, tmp_path, coverage_env):
[str(session.creator.exe), "-c", "import greet; greet.greet('World')"],
universal_newlines=True,
stdout=subprocess.PIPE,
encoding="utf-8",
)
out, _ = process.communicate()
assert process.returncode == 0
Expand Down
6 changes: 3 additions & 3 deletions tests/unit/discovery/py_info/test_py_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def test_py_info_cached_symlink(mocker, tmp_path, session_app_data):
new_exe.symlink_to(sys.executable)
pyvenv = Path(sys.executable).parents[1] / "pyvenv.cfg"
if pyvenv.exists():
(tmp_path / pyvenv.name).write_text(pyvenv.read_text())
(tmp_path / pyvenv.name).write_text(pyvenv.read_text(encoding="utf-8"), encoding="utf-8")
new_exe_str = str(new_exe)
second_result = PythonInfo.from_exe(new_exe_str, session_app_data)
assert second_result.executable == new_exe_str
Expand Down Expand Up @@ -211,7 +211,7 @@ def _make_py_info(of):
selected = None
for pos, i in enumerate(discovered):
path = tmp_path / str(pos)
path.write_text("")
path.write_text("", encoding="utf-8")
py_info = _make_py_info(i)
py_info.system_executable = CURRENT.system_executable
py_info.executable = CURRENT.system_executable
Expand Down Expand Up @@ -259,7 +259,7 @@ def test_py_info_ignores_distutils_config(monkeypatch, tmp_path):
install_scripts={tmp_path}{os.sep}scripts
install_data={tmp_path}{os.sep}data
"""
(tmp_path / "setup.cfg").write_text(dedent(raw))
(tmp_path / "setup.cfg").write_text(dedent(raw), encoding="utf-8")
monkeypatch.chdir(tmp_path)
py_info = PythonInfo.from_exe(sys.executable)
distutils = py_info.distutils_install
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/discovery/py_info/test_py_info_exe_based_of.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def test_discover_ok(tmp_path, suffix, impl, version, arch, into, caplog, sessio
os.symlink(CURRENT.executable, str(dest))
pyvenv = Path(CURRENT.executable).parents[1] / "pyvenv.cfg"
if pyvenv.exists():
(folder / pyvenv.name).write_text(pyvenv.read_text())
(folder / pyvenv.name).write_text(pyvenv.read_text(encoding="utf-8"), encoding="utf-8")
inside_folder = str(tmp_path)
base = CURRENT.discover_exe(session_app_data, inside_folder)
found = base.executable
Expand Down
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ set_env =
_COVERAGE_SRC = {envsitepackagesdir}/virtualenv
COVERAGE_FILE = {toxworkdir}/.coverage.{envname}
COVERAGE_PROCESS_START = {toxinidir}/pyproject.toml
PYTHONWARNDEFAULTENCODING = 1
wheel_build_env = .pkg

[testenv:fix]
Expand Down