Skip to content

Commit e2a9ee5

Browse files
authored
Sepcify the encoding (#2515)
1 parent 0398e5b commit e2a9ee5

File tree

24 files changed

+60
-38
lines changed

24 files changed

+60
-38
lines changed

docs/changelog/2515.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Do not assume the default encoding.

src/virtualenv/app_data/via_disk_folder.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def exists(self):
125125
def read(self):
126126
data, bad_format = None, False
127127
try:
128-
data = json.loads(self.file.read_text())
128+
data = json.loads(self.file.read_text(encoding="utf-8"))
129129
logging.debug(f"got {self.msg} from %s", *self.msg_args)
130130
return data
131131
except ValueError:
@@ -151,7 +151,7 @@ def locked(self):
151151
def write(self, content):
152152
folder = self.file.parent
153153
folder.mkdir(parents=True, exist_ok=True)
154-
self.file.write_text(json.dumps(content, sort_keys=True, indent=2))
154+
self.file.write_text(json.dumps(content, sort_keys=True, indent=2), encoding="utf-8")
155155
logging.debug(f"wrote {self.msg} at %s", *self.msg_args)
156156

157157

src/virtualenv/config/ini.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def __init__(self, env=None):
4444
logging.error("failed to read config file %s because %r", config_file, exception)
4545

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

5050
def get(self, key, as_type):

src/virtualenv/create/creator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def setup_ignore_vcs(self):
163163
# mark this folder to be ignored by VCS, handle https://www.python.org/dev/peps/pep-0610/#registered-vcs
164164
git_ignore = self.dest / ".gitignore"
165165
if not git_ignore.exists():
166-
git_ignore.write_text("# created by virtualenv automatically\n*\n")
166+
git_ignore.write_text("# created by virtualenv automatically\n*\n", encoding="utf-8")
167167
# Mercurial - does not support the .hgignore file inside a subdirectory directly, but only if included via the
168168
# subinclude directive from root, at which point on might as well ignore the directory itself, see
169169
# https://www.selenic.com/mercurial/hgignore.5.html for more details

src/virtualenv/create/via_global_ref/api.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,15 @@ def install_patch(self):
8787
if text:
8888
pth = self.purelib / "_virtualenv.pth"
8989
logging.debug("create virtualenv import hook file %s", pth)
90-
pth.write_text("import _virtualenv")
90+
pth.write_text("import _virtualenv", encoding="utf-8")
9191
dest_path = self.purelib / "_virtualenv.py"
9292
logging.debug("create %s", dest_path)
93-
dest_path.write_text(text)
93+
dest_path.write_text(text, encoding="utf-8")
9494

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

101101
def _args(self):

src/virtualenv/create/via_global_ref/builtin/python2/python2.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def create(self):
3131
if IS_ZIPAPP:
3232
custom_site_text = read_from_zipapp(custom_site)
3333
else:
34-
custom_site_text = custom_site.read_text()
34+
custom_site_text = custom_site.read_text(encoding="utf-8")
3535
expected = json.dumps([os.path.relpath(str(i), str(site_py)) for i in self.libs])
3636

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

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

4747
@property
4848
def reload_code(self):

src/virtualenv/discovery/cached_py_info.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ def _run_subprocess(cls, exe, app_data, env):
114114
stderr=subprocess.PIPE,
115115
stdout=subprocess.PIPE,
116116
env=env,
117+
encoding="utf-8",
117118
)
118119
out, err = process.communicate()
119120
code = process.returncode

src/virtualenv/seed/embed/via_app_data/pip_install/base.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,11 @@ def _records_text(self, files):
7272
def _generate_new_files(self):
7373
new_files = set()
7474
installer = self._dist_info / "INSTALLER"
75-
installer.write_text("pip\n")
75+
installer.write_text("pip\n", encoding="utf-8")
7676
new_files.add(installer)
7777
# inject a no-op root element, as workaround for bug in https://github.com/pypa/pip/issues/7226
7878
marker = self._image_dir / f"{self._dist_info.stem}.virtualenv"
79-
marker.write_text("")
79+
marker.write_text("", encoding="utf-8")
8080
new_files.add(marker)
8181
folder = mkdtemp()
8282
try:
@@ -120,7 +120,7 @@ def _console_scripts(self):
120120
entry_points = self._dist_info / "entry_points.txt"
121121
if entry_points.exists():
122122
parser = ConfigParser()
123-
with entry_points.open() as file_handler:
123+
with entry_points.open(encoding="utf-8") as file_handler:
124124
parser.read_file(file_handler)
125125
if "console_scripts" in parser.sections():
126126
for name, value in parser.items("console_scripts"):
@@ -152,11 +152,17 @@ def _uninstall_dist(dist):
152152
logging.debug("uninstall existing distribution %s from %s", dist.stem, dist_base)
153153

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

158162
base_dirs, record = paths.copy(), dist / "RECORD" # collect entries in record that we did not register yet
159-
for name in (i.split(",")[0] for i in record.read_text().splitlines()) if record.exists() else ():
163+
for name in (
164+
(i.split(",")[0] for i in record.read_text(encoding="utf-8").splitlines()) if record.exists() else ()
165+
):
160166
path = dist_base / name
161167
if not any(p in base_dirs for p in path.parents): # only add if not already added as a base dir
162168
paths.add(path)

src/virtualenv/seed/wheels/acquire.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def download_wheel(distribution, version_spec, for_py_version, search_dirs, app_
6060
]
6161
# pip has no interface in python - must be a new sub-process
6262
env = pip_wheel_env_run(search_dirs, app_data, env)
63-
process = Popen(cmd, env=env, stdout=PIPE, stderr=PIPE, universal_newlines=True)
63+
process = Popen(cmd, env=env, stdout=PIPE, stderr=PIPE, universal_newlines=True, encoding="utf-8")
6464
out, err = process.communicate()
6565
if process.returncode != 0:
6666
kwargs = {"output": out, "stderr": err}

src/virtualenv/util/subprocess/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ def run_cmd(cmd):
1111
stdin=subprocess.PIPE,
1212
stderr=subprocess.PIPE,
1313
stdout=subprocess.PIPE,
14+
encoding="utf-8",
1415
)
1516
out, err = process.communicate() # input disabled
1617
code = process.returncode

tests/unit/activation/conftest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ def get_version(self, raise_on_fail):
3636
universal_newlines=True,
3737
stdout=subprocess.PIPE,
3838
stderr=subprocess.PIPE,
39+
encoding="utf-8",
3940
)
4041
out, err = process.communicate()
4142
result = out if out else err
@@ -220,7 +221,7 @@ def activation_python(request, tmp_path_factory, special_char_name, current_fast
220221
cmd += ["--prompt", special_char_name]
221222
session = cli_run(cmd)
222223
pydoc_test = session.creator.purelib / "pydoc_test.py"
223-
pydoc_test.write_text('"""This is pydoc_test.py"""')
224+
pydoc_test.write_text('"""This is pydoc_test.py"""', encoding="utf-8")
224225
return session
225226

226227

tests/unit/activation/test_activate_this.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@ def test_python_activator_cross(session_app_data, cross_python, special_name_dir
2121
results = activator.generate(session.creator)
2222
assert len(results) == 1
2323
result = results[0]
24-
content = result.read_text()
24+
content = result.read_text(encoding="utf-8")
2525
# check that the repr strings have been correctly stripped
2626
assert "\"'" not in content

tests/unit/activation/test_batch.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
@pytest.mark.usefixtures("activation_python")
99
def test_batch(activation_tester_class, activation_tester, tmp_path):
1010
version_script = tmp_path / "version.bat"
11-
version_script.write_text("ver")
11+
version_script.write_text("ver", encoding="utf-8")
1212

1313
class Batch(activation_tester_class):
1414
def __init__(self, session):

tests/unit/activation/test_fish.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ def test_fish(activation_tester_class, activation_tester, monkeypatch, tmp_path)
99
monkeypatch.setenv("HOME", str(tmp_path))
1010
fish_conf_dir = tmp_path / ".config" / "fish"
1111
fish_conf_dir.mkdir(parents=True)
12-
(fish_conf_dir / "config.fish").write_text("")
12+
(fish_conf_dir / "config.fish").write_text("", encoding="utf-8")
1313

1414
class Fish(activation_tester_class):
1515
def __init__(self, session):

tests/unit/activation/test_python_activator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def print_r(value):
4444
4545
file_at = {str(activate_script)!r}
4646
# CPython 2 requires non-ascii path open to be unicode
47-
with open(file_at, "r") as file_handler:
47+
with open(file_at, "r", encoding='utf-8') as file_handler:
4848
content = file_handler.read()
4949
exec(content, {{"__file__": file_at}})
5050

tests/unit/config/test___main__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@
99

1010

1111
def test_main():
12-
process = Popen([sys.executable, "-m", "virtualenv", "--help"], universal_newlines=True, stdout=PIPE)
12+
process = Popen(
13+
[sys.executable, "-m", "virtualenv", "--help"],
14+
universal_newlines=True,
15+
stdout=PIPE,
16+
encoding="utf-8",
17+
)
1318
out, _ = process.communicate()
1419
assert not process.returncode
1520
assert out
@@ -92,6 +97,7 @@ def test_session_report_subprocess(tmp_path):
9297
out = check_output(
9398
[sys.executable, "-m", "virtualenv", str(tmp_path), "--activators", "powershell", "--without-pip"],
9499
text=True,
100+
encoding="utf-8",
95101
)
96102
lines = out.split("\n")
97103
regexes = [

tests/unit/config/test_env_var.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
def _empty_conf(tmp_path, monkeypatch):
1313
conf = tmp_path / "conf.ini"
1414
monkeypatch.setenv(IniConfig.VIRTUALENV_CONFIG_FILE_ENV_VAR, str(conf))
15-
conf.write_text("[virtualenv]")
15+
conf.write_text("[virtualenv]", encoding="utf-8")
1616

1717

1818
@pytest.mark.usefixtures("_empty_conf")

tests/unit/config/test_ini.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def test_ini_can_be_overwritten_by_flag(tmp_path, monkeypatch):
1616
copies = True
1717
""",
1818
),
19+
encoding="utf-8",
1920
)
2021
monkeypatch.setenv("VIRTUALENV_CONFIG_FILE", str(custom_ini))
2122

tests/unit/create/test_creator.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def _non_success_exit_code(capsys, target):
5353

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

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

236236

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

243243

244244
def test_create_vcs_ignore_override(tmp_path):
@@ -249,9 +249,9 @@ def test_create_vcs_ignore_override(tmp_path):
249249

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

256256

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

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

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

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

@@ -509,7 +509,7 @@ def test_pth_in_site_vs_python_path(tmp_path):
509509
session = cli_run([str(tmp_path)])
510510
site_packages = str(session.creator.purelib)
511511
# install test.pth that sets sys.testpth='ok'
512-
with open(os.path.join(site_packages, "test.pth"), "w") as f:
512+
with open(os.path.join(site_packages, "test.pth"), "w", encoding="utf-8") as f:
513513
f.write('import sys; sys.testpth="ok"\n')
514514
# verify that test.pth is activated when interpreter is run
515515
out = subprocess.check_output(
@@ -595,7 +595,10 @@ def test_debug_bad_virtualenv(tmp_path):
595595
result = cli_run(cmd)
596596
# if the site.py is removed/altered the debug should fail as no one is around to fix the paths
597597
cust = result.creator.purelib / "_a.pth"
598-
cust.write_text('import sys; sys.stdout.write("std-out"); sys.stderr.write("std-err"); raise SystemExit(1)')
598+
cust.write_text(
599+
'import sys; sys.stdout.write("std-out"); sys.stderr.write("std-err"); raise SystemExit(1)',
600+
encoding="utf-8",
601+
)
599602
debug_info = result.creator.debug
600603
assert debug_info["returncode"] == 1
601604
assert "std-err" in debug_info["err"]

tests/unit/create/via_global_ref/builtin/testing/py_info.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ def fixture_file(fixture_name):
1515

1616

1717
def read_fixture(fixture_name):
18-
fixture_json = fixture_file(fixture_name).read_text()
18+
fixture_json = fixture_file(fixture_name).read_text(encoding="utf-8")
1919
return PythonInfo._from_json(fixture_json)

tests/unit/create/via_global_ref/test_build_c_ext.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def test_can_build_c_extensions(creator, tmp_path, coverage_env):
5656
[str(session.creator.exe), "-c", "import greet; greet.greet('World')"],
5757
universal_newlines=True,
5858
stdout=subprocess.PIPE,
59+
encoding="utf-8",
5960
)
6061
out, _ = process.communicate()
6162
assert process.returncode == 0

tests/unit/discovery/py_info/test_py_info.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ def test_py_info_cached_symlink(mocker, tmp_path, session_app_data):
156156
new_exe.symlink_to(sys.executable)
157157
pyvenv = Path(sys.executable).parents[1] / "pyvenv.cfg"
158158
if pyvenv.exists():
159-
(tmp_path / pyvenv.name).write_text(pyvenv.read_text())
159+
(tmp_path / pyvenv.name).write_text(pyvenv.read_text(encoding="utf-8"), encoding="utf-8")
160160
new_exe_str = str(new_exe)
161161
second_result = PythonInfo.from_exe(new_exe_str, session_app_data)
162162
assert second_result.executable == new_exe_str
@@ -211,7 +211,7 @@ def _make_py_info(of):
211211
selected = None
212212
for pos, i in enumerate(discovered):
213213
path = tmp_path / str(pos)
214-
path.write_text("")
214+
path.write_text("", encoding="utf-8")
215215
py_info = _make_py_info(i)
216216
py_info.system_executable = CURRENT.system_executable
217217
py_info.executable = CURRENT.system_executable
@@ -259,7 +259,7 @@ def test_py_info_ignores_distutils_config(monkeypatch, tmp_path):
259259
install_scripts={tmp_path}{os.sep}scripts
260260
install_data={tmp_path}{os.sep}data
261261
"""
262-
(tmp_path / "setup.cfg").write_text(dedent(raw))
262+
(tmp_path / "setup.cfg").write_text(dedent(raw), encoding="utf-8")
263263
monkeypatch.chdir(tmp_path)
264264
py_info = PythonInfo.from_exe(sys.executable)
265265
distutils = py_info.distutils_install

tests/unit/discovery/py_info/test_py_info_exe_based_of.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def test_discover_ok(tmp_path, suffix, impl, version, arch, into, caplog, sessio
3636
os.symlink(CURRENT.executable, str(dest))
3737
pyvenv = Path(CURRENT.executable).parents[1] / "pyvenv.cfg"
3838
if pyvenv.exists():
39-
(folder / pyvenv.name).write_text(pyvenv.read_text())
39+
(folder / pyvenv.name).write_text(pyvenv.read_text(encoding="utf-8"), encoding="utf-8")
4040
inside_folder = str(tmp_path)
4141
base = CURRENT.discover_exe(session_app_data, inside_folder)
4242
found = base.executable

tox.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ set_env =
3434
_COVERAGE_SRC = {envsitepackagesdir}/virtualenv
3535
COVERAGE_FILE = {toxworkdir}/.coverage.{envname}
3636
COVERAGE_PROCESS_START = {toxinidir}/pyproject.toml
37+
PYTHONWARNDEFAULTENCODING = 1
3738
wheel_build_env = .pkg
3839

3940
[testenv:fix]

0 commit comments

Comments
 (0)