diff --git a/mypy/build.py b/mypy/build.py index a4817d1866c7..0afa348ef1c7 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -17,6 +17,7 @@ import errno import gc import json +import locale import os import platform import re @@ -1194,7 +1195,7 @@ def add_catch_all_gitignore(target_dir: str) -> None: """ gitignore = os.path.join(target_dir, ".gitignore") try: - with open(gitignore, "x") as f: + with open(gitignore, "x", encoding=locale.getpreferredencoding(False)) as f: print("# Automatically created by mypy", file=f) print("*", file=f) except FileExistsError: @@ -1208,7 +1209,7 @@ def exclude_from_backups(target_dir: str) -> None: """ cachedir_tag = os.path.join(target_dir, "CACHEDIR.TAG") try: - with open(cachedir_tag, "x") as f: + with open(cachedir_tag, "x", encoding=locale.getpreferredencoding(False)) as f: f.write( """Signature: 8a477f597d28d172789f06886806bc55 # This file is a cache directory tag automatically created by mypy. @@ -3600,7 +3601,7 @@ def record_missing_stub_packages(cache_dir: str, missing_stub_packages: set[str] """ fnam = missing_stubs_file(cache_dir) if missing_stub_packages: - with open(fnam, "w") as f: + with open(fnam, "w", encoding=locale.getpreferredencoding(False)) as f: for pkg in sorted(missing_stub_packages): f.write(f"{pkg}\n") else: diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 190782a3bded..b7bfa6d0966f 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -3,6 +3,7 @@ import argparse import configparser import glob as fileglob +import locale import os import re import sys @@ -236,7 +237,7 @@ def parse_config_file( parser: MutableMapping[str, Any] = destructure_overrides(toml_data) config_types = toml_config_types else: - config_parser.read(config_file) + config_parser.read(config_file, encoding=locale.getpreferredencoding(False)) parser = config_parser config_types = ini_config_types except (tomllib.TOMLDecodeError, configparser.Error, ConfigTOMLValueError) as err: diff --git a/mypy/metastore.py b/mypy/metastore.py index 16cbd5adc9c8..7b74fb22a56f 100644 --- a/mypy/metastore.py +++ b/mypy/metastore.py @@ -11,6 +11,7 @@ from __future__ import annotations import binascii +import locale import os import time from abc import abstractmethod @@ -93,7 +94,9 @@ def read(self, name: str) -> str: if not self.cache_dir_prefix: raise FileNotFoundError() - with open(os.path.join(self.cache_dir_prefix, name)) as f: + with open( + os.path.join(self.cache_dir_prefix, name), encoding=locale.getpreferredencoding(False) + ) as f: return f.read() def write(self, name: str, data: str, mtime: float | None = None) -> bool: @@ -106,7 +109,7 @@ def write(self, name: str, data: str, mtime: float | None = None) -> bool: tmp_filename = path + "." + random_string() try: os.makedirs(os.path.dirname(path), exist_ok=True) - with open(tmp_filename, "w") as f: + with open(tmp_filename, "w", encoding=locale.getpreferredencoding(False)) as f: f.write(data) os.replace(tmp_filename, path) if mtime is not None: diff --git a/mypy/modulefinder.py b/mypy/modulefinder.py index 265d76ed5bb6..a46223ad8c99 100644 --- a/mypy/modulefinder.py +++ b/mypy/modulefinder.py @@ -8,6 +8,7 @@ import ast import collections import functools +import locale import os import re import subprocess @@ -866,7 +867,7 @@ def load_stdlib_py_versions(custom_typeshed_dir: str | None) -> StdlibVersions: versions_path = os.path.join(stdlib_dir, "VERSIONS") assert os.path.isfile(versions_path), (custom_typeshed_dir, versions_path, __file__) - with open(versions_path) as f: + with open(versions_path, encoding=locale.getpreferredencoding(False)) as f: for line in f: line = line.split("#")[0].strip() if line == "": diff --git a/mypy/test/testapi.py b/mypy/test/testapi.py index 95bd95ece785..97d9c3d1e189 100644 --- a/mypy/test/testapi.py +++ b/mypy/test/testapi.py @@ -1,7 +1,11 @@ from __future__ import annotations +import shutil +import subprocess import sys from io import StringIO +from pathlib import Path +from tempfile import NamedTemporaryFile, mkdtemp import mypy.api from mypy.test.helpers import Suite @@ -13,12 +17,18 @@ def setUp(self) -> None: self.sys_stderr = sys.stderr sys.stdout = self.stdout = StringIO() sys.stderr = self.stderr = StringIO() + with NamedTemporaryFile(delete=False) as tmp: + tmp.write(b"x: int = 5\n") + self.tmp_path = Path(tmp.name) + self.tmp_cache_dir = Path(mkdtemp()) def tearDown(self) -> None: sys.stdout = self.sys_stdout sys.stderr = self.sys_stderr assert self.stdout.getvalue() == "" assert self.stderr.getvalue() == "" + self.tmp_path.unlink() + shutil.rmtree(self.tmp_cache_dir) def test_capture_bad_opt(self) -> None: """stderr should be captured when a bad option is passed.""" @@ -43,3 +53,20 @@ def test_capture_version(self) -> None: stdout, _, _ = mypy.api.run(["--version"]) assert isinstance(stdout, str) assert stdout != "" + + def test_default_encoding_warnings(self) -> None: + """No EncodingWarnings should be emitted.""" + for empty_cache in [True, False]: + res = subprocess.run( + [ + sys.executable, + "-c", + "import mypy.api;" + "mypy.api.run(" + f"['--cache-dir', '{self.tmp_cache_dir}', '{self.tmp_path}']" + ")", + ], + capture_output=True, + env={"PYTHONWARNDEFAULTENCODING": "1"}, + ) + assert b"EncodingWarning" not in res.stderr