From 7ab00b7bc968acc817710f53f3acd42ebe3727a9 Mon Sep 17 00:00:00 2001 From: Hasier Date: Fri, 23 May 2025 11:18:14 +0100 Subject: [PATCH 01/14] Add config key to extend config files --- mypy/config_parser.py | 93 ++++++++++++++++++++++++++++++--- mypy/test/test_config_parser.py | 83 ++++++++++++++++++++++++++++- 2 files changed, 167 insertions(+), 9 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 0e033471d2e9..aefc3b8cd1e9 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -292,7 +292,7 @@ def parse_config_file( stdout: TextIO | None = None, stderr: TextIO | None = None, ) -> None: - """Parse a config file into an Options object. + """Parse a config file into an Options object, following config extend arguments. Errors are written to stderr but are not fatal. @@ -301,30 +301,104 @@ def parse_config_file( stdout = stdout or sys.stdout stderr = stderr or sys.stderr + ret = _parse_and_extend_config_file( + options=options, + set_strict_flags=set_strict_flags, + filename=filename, + stdout=stdout, + stderr=stderr, + visited=set(), + ) + + if ret is None: + return + + file_read, mypy_updates, mypy_report_dirs, module_updates = ret + + options.config_file = file_read + os.environ["MYPY_CONFIG_FILE_DIR"] = os.path.dirname(os.path.abspath(file_read)) + + for k, v in mypy_updates.items(): + setattr(options, k, v) + + options.report_dirs.update(mypy_report_dirs) + + for glob, updates in module_updates.items(): + options.per_module_options[glob] = updates + + +def _merge_updates(existing: dict[str, object], new: dict[str, object]) -> None: + existing["disable_error_code"] = list( + set(existing.get("disable_error_code", []) + new.pop("disable_error_code")) + ) + existing["enable_error_code"] = list( + set(existing.get("enable_error_code", []) + new.pop("enable_error_code")) + ) + existing.update(new) + + +def _parse_and_extend_config_file( + options: Options, + set_strict_flags: Callable[[], None], + filename: str | None, + stdout: TextIO, + stderr: TextIO, + visited: set[str], +) -> tuple[str, dict[str, object], dict[str, str], dict[str, dict[str, object]]] | None: ret = ( _parse_individual_file(filename, stderr) if filename is not None else _find_config_file(stderr) ) if ret is None: - return + return None parser, config_types, file_read = ret - options.config_file = file_read - os.environ["MYPY_CONFIG_FILE_DIR"] = os.path.dirname(os.path.abspath(file_read)) + abs_file_read = os.path.abspath(file_read) + if abs_file_read in visited: + print(f"Circular extend detected: {abs_file_read}", file=stderr) + return None + visited.add(abs_file_read) + + mypy_updates: dict[str, object] = {} + mypy_report_dirs: dict[str, str] = {} + module_updates: dict[str, dict[str, object]] = {} if "mypy" not in parser: if filename or os.path.basename(file_read) not in defaults.SHARED_CONFIG_NAMES: print(f"{file_read}: No [mypy] section in config file", file=stderr) else: section = parser["mypy"] + + extend = parser["mypy"].pop("extend", None) + if extend: + cwd = os.getcwd() + try: + # process extend relative to the directory where we found current config + os.chdir(os.path.dirname(abs_file_read)) + parse_ret = _parse_and_extend_config_file( + options=options, + set_strict_flags=set_strict_flags, + filename=os.path.abspath(expand_path(extend)), + stdout=stdout, + stderr=stderr, + visited=visited, + ) + finally: + os.chdir(cwd) + + if parse_ret is None: + print(f"{extend} is not a valid path to extend from {abs_file_read}", file=stderr) + else: + _, mypy_updates, mypy_report_dirs, module_updates = parse_ret + prefix = f"{file_read}: [mypy]: " updates, report_dirs = parse_section( prefix, options, set_strict_flags, section, config_types, stderr ) - for k, v in updates.items(): - setattr(options, k, v) - options.report_dirs.update(report_dirs) + # extend and overwrite existing values with new ones + _merge_updates(mypy_updates, updates) + mypy_report_dirs.update(report_dirs) for name, section in parser.items(): if name.startswith("mypy-"): @@ -367,7 +441,10 @@ def parse_config_file( file=stderr, ) else: - options.per_module_options[glob] = updates + # extend and overwrite existing values with new ones + _merge_updates(module_updates.setdefault(glob, {}), updates) + + return file_read, mypy_updates, mypy_report_dirs, module_updates def get_prefix(file_read: str, name: str) -> str: diff --git a/mypy/test/test_config_parser.py b/mypy/test/test_config_parser.py index 597143738f23..bdff78cd4a92 100644 --- a/mypy/test/test_config_parser.py +++ b/mypy/test/test_config_parser.py @@ -1,14 +1,16 @@ from __future__ import annotations import contextlib +import io import os import tempfile import unittest from collections.abc import Iterator from pathlib import Path -from mypy.config_parser import _find_config_file +from mypy.config_parser import _find_config_file, parse_config_file from mypy.defaults import CONFIG_NAMES, SHARED_CONFIG_NAMES +from mypy.options import Options @contextlib.contextmanager @@ -128,3 +130,82 @@ def test_precedence_missing_section(self) -> None: result = _find_config_file() assert result is not None assert Path(result[2]).resolve() == parent_mypy.resolve() + + +class ExtendConfigFileSuite(unittest.TestCase): + + def test_extend_success(self) -> None: + with tempfile.TemporaryDirectory() as _tmpdir: + tmpdir = Path(_tmpdir) + with chdir(tmpdir): + pyproject = tmpdir / "pyproject.toml" + write_config( + pyproject, + "[tool.mypy]\n" + 'extend = "./folder/mypy.ini"\n' + "strict = false\n" + "[[tool.mypy.overrides]]\n" + 'module = "c"\n' + 'enable_error_code = ["explicit-override"]\n' + "disallow_untyped_defs = true", + ) + folder = tmpdir / "folder" + folder.mkdir() + write_config( + folder / "mypy.ini", + "[mypy]\n" + "strict = True\n" + "ignore_missing_imports_per_module = True\n" + "[mypy-c]\n" + "disallow_incomplete_defs = True", + ) + + options = Options() + strict_option_set = False + + def set_strict_flags() -> None: + nonlocal strict_option_set + strict_option_set = True + + stdout = io.StringIO() + stderr = io.StringIO() + parse_config_file(options, set_strict_flags, None, stdout, stderr) + + assert strict_option_set is True + assert options.ignore_missing_imports_per_module is True + assert options.config_file == str(pyproject.name) + os.environ["MYPY_CONFIG_FILE_DIR"] = str(pyproject.parent) + + assert options.per_module_options["c"] == { + "disable_error_code": [], + "enable_error_code": ["explicit-override"], + "disallow_untyped_defs": True, + "disallow_incomplete_defs": True, + } + + assert stdout.getvalue() == "" + assert stderr.getvalue() == "" + + def test_extend_cyclic(self) -> None: + with tempfile.TemporaryDirectory() as _tmpdir: + tmpdir = Path(_tmpdir) + with chdir(tmpdir): + pyproject = tmpdir / "pyproject.toml" + write_config(pyproject, '[tool.mypy]\nextend = "./folder/mypy.ini"\n') + + folder = tmpdir / "folder" + folder.mkdir() + ini = folder / "mypy.ini" + write_config(ini, "[mypy]\nextend = ../pyproject.toml\n") + + options = Options() + + stdout = io.StringIO() + stderr = io.StringIO() + parse_config_file(options, lambda: None, None, stdout, stderr) + + assert stdout.getvalue() == "" + assert stderr.getvalue() == ( + f"Circular extend detected: /private{pyproject}\n" + f"../pyproject.toml is not a valid path to extend from /private{ini}\n" + ) From 31702dd0a5ef0e78950379349cc1058a606204f6 Mon Sep 17 00:00:00 2001 From: Hasier Date: Fri, 23 May 2025 15:47:33 +0100 Subject: [PATCH 02/14] Fix pop default and appease linter --- mypy/config_parser.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index aefc3b8cd1e9..e431ae1a5846 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -16,7 +16,7 @@ import tomli as tomllib from collections.abc import Mapping, MutableMapping, Sequence -from typing import Any, Callable, Final, TextIO, Union +from typing import Any, Callable, Final, TextIO, Union, cast from typing_extensions import TypeAlias as _TypeAlias from mypy import defaults @@ -329,10 +329,16 @@ def parse_config_file( def _merge_updates(existing: dict[str, object], new: dict[str, object]) -> None: existing["disable_error_code"] = list( - set(existing.get("disable_error_code", []) + new.pop("disable_error_code")) + set( + cast(list[str], existing.get("disable_error_code", [])) + + cast(list[str], new.pop("disable_error_code", [])) + ) ) existing["enable_error_code"] = list( - set(existing.get("enable_error_code", []) + new.pop("enable_error_code")) + set( + cast(list[str], existing.get("enable_error_code", [])) + + cast(list[str], new.pop("enable_error_code", [])) + ) ) existing.update(new) From 027d2e58f1ad6b5221a9888509b9eb3c558ec661 Mon Sep 17 00:00:00 2001 From: Hasier Date: Fri, 23 May 2025 16:11:13 +0100 Subject: [PATCH 03/14] Update test expectation --- mypy/test/test_config_parser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/test/test_config_parser.py b/mypy/test/test_config_parser.py index bdff78cd4a92..1cd098a17251 100644 --- a/mypy/test/test_config_parser.py +++ b/mypy/test/test_config_parser.py @@ -206,6 +206,6 @@ def test_extend_cyclic(self) -> None: assert stdout.getvalue() == "" assert stderr.getvalue() == ( - f"Circular extend detected: /private{pyproject}\n" - f"../pyproject.toml is not a valid path to extend from /private{ini}\n" + f"Circular extend detected: {pyproject}\n" + f"../pyproject.toml is not a valid path to extend from {ini}\n" ) From b3df3efd5bb92c249814fe744a5faff231396c42 Mon Sep 17 00:00:00 2001 From: Hasier Date: Tue, 27 May 2025 10:54:20 +0100 Subject: [PATCH 04/14] Fix test assertion --- mypy/test/test_config_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/test/test_config_parser.py b/mypy/test/test_config_parser.py index 1cd098a17251..c876b27f6cff 100644 --- a/mypy/test/test_config_parser.py +++ b/mypy/test/test_config_parser.py @@ -174,7 +174,7 @@ def set_strict_flags() -> None: assert strict_option_set is True assert options.ignore_missing_imports_per_module is True assert options.config_file == str(pyproject.name) - os.environ["MYPY_CONFIG_FILE_DIR"] = str(pyproject.parent) + assert os.environ["MYPY_CONFIG_FILE_DIR"] == str(pyproject.parent) assert options.per_module_options["c"] == { "disable_error_code": [], From 67a5303575e2c7f82609cf36de29e04a26ca985c Mon Sep 17 00:00:00 2001 From: Hasier Date: Tue, 27 May 2025 11:51:35 +0100 Subject: [PATCH 05/14] Build relative filename rather than changing directory --- mypy/config_parser.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index e431ae1a5846..2096a3e1cffb 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -378,20 +378,19 @@ def _parse_and_extend_config_file( extend = parser["mypy"].pop("extend", None) if extend: - cwd = os.getcwd() - try: - # process extend relative to the directory where we found current config - os.chdir(os.path.dirname(abs_file_read)) - parse_ret = _parse_and_extend_config_file( - options=options, - set_strict_flags=set_strict_flags, - filename=os.path.abspath(expand_path(extend)), - stdout=stdout, - stderr=stderr, - visited=visited, - ) - finally: - os.chdir(cwd) + parse_ret = _parse_and_extend_config_file( + options=options, + set_strict_flags=set_strict_flags, + # refer to extend relative to directory where we found current config + filename=os.path.relpath( + os.path.normpath( + os.path.join(os.path.dirname(abs_file_read), expand_path(extend)) + ) + ), + stdout=stdout, + stderr=stderr, + visited=visited, + ) if parse_ret is None: print(f"{extend} is not a valid path to extend from {abs_file_read}", file=stderr) From 436227ce7a16ebb118ea0966763c894120c71c27 Mon Sep 17 00:00:00 2001 From: Hasier Date: Tue, 27 May 2025 12:32:05 +0100 Subject: [PATCH 06/14] Rename template argument --- mypy/config_parser.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 2096a3e1cffb..4c885eb94c56 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -302,7 +302,7 @@ def parse_config_file( stderr = stderr or sys.stderr ret = _parse_and_extend_config_file( - options=options, + template=options, set_strict_flags=set_strict_flags, filename=filename, stdout=stdout, @@ -344,7 +344,7 @@ def _merge_updates(existing: dict[str, object], new: dict[str, object]) -> None: def _parse_and_extend_config_file( - options: Options, + template: Options, set_strict_flags: Callable[[], None], filename: str | None, stdout: TextIO, @@ -379,7 +379,7 @@ def _parse_and_extend_config_file( extend = parser["mypy"].pop("extend", None) if extend: parse_ret = _parse_and_extend_config_file( - options=options, + template=template, set_strict_flags=set_strict_flags, # refer to extend relative to directory where we found current config filename=os.path.relpath( @@ -399,7 +399,7 @@ def _parse_and_extend_config_file( prefix = f"{file_read}: [mypy]: " updates, report_dirs = parse_section( - prefix, options, set_strict_flags, section, config_types, stderr + prefix, template, set_strict_flags, section, config_types, stderr ) # extend and overwrite existing values with new ones _merge_updates(mypy_updates, updates) @@ -409,7 +409,7 @@ def _parse_and_extend_config_file( if name.startswith("mypy-"): prefix = get_prefix(file_read, name) updates, report_dirs = parse_section( - prefix, options, set_strict_flags, section, config_types, stderr + prefix, template, set_strict_flags, section, config_types, stderr ) if report_dirs: print( From a563040810544d02e86c6a97bdefd6a0a07b7d33 Mon Sep 17 00:00:00 2001 From: Hasier Date: Tue, 27 May 2025 16:53:05 +0100 Subject: [PATCH 07/14] Set envvar early --- mypy/config_parser.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 4c885eb94c56..bb81cbebc383 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -316,7 +316,6 @@ def parse_config_file( file_read, mypy_updates, mypy_report_dirs, module_updates = ret options.config_file = file_read - os.environ["MYPY_CONFIG_FILE_DIR"] = os.path.dirname(os.path.abspath(file_read)) for k, v in mypy_updates.items(): setattr(options, k, v) @@ -366,6 +365,11 @@ def _parse_and_extend_config_file( return None visited.add(abs_file_read) + if not os.environ.get("MYPY_CONFIG_FILE_DIR"): + # set it only if unset to allow for path variable expansions when parsing below, + # so recursive calls for config extend references won't overwrite it + os.environ["MYPY_CONFIG_FILE_DIR"] = os.path.dirname(abs_file_read) + mypy_updates: dict[str, object] = {} mypy_report_dirs: dict[str, str] = {} module_updates: dict[str, dict[str, object]] = {} From e8d6771e28e5ff096a7e9acccd9bff58754741ff Mon Sep 17 00:00:00 2001 From: Hasier Date: Tue, 27 May 2025 16:55:02 +0100 Subject: [PATCH 08/14] Reuse section variable --- mypy/config_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index bb81cbebc383..19bbd799d9ef 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -380,7 +380,7 @@ def _parse_and_extend_config_file( else: section = parser["mypy"] - extend = parser["mypy"].pop("extend", None) + extend = section.pop("extend", None) if extend: parse_ret = _parse_and_extend_config_file( template=template, From 91c5f741b92a9ac5ccce328523e3001401f8a4ca Mon Sep 17 00:00:00 2001 From: Hasier Date: Tue, 27 May 2025 17:33:42 +0100 Subject: [PATCH 09/14] Skip envvar check as xdist overwrites it --- mypy/test/test_config_parser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypy/test/test_config_parser.py b/mypy/test/test_config_parser.py index c876b27f6cff..67bc4a7092d0 100644 --- a/mypy/test/test_config_parser.py +++ b/mypy/test/test_config_parser.py @@ -174,7 +174,6 @@ def set_strict_flags() -> None: assert strict_option_set is True assert options.ignore_missing_imports_per_module is True assert options.config_file == str(pyproject.name) - assert os.environ["MYPY_CONFIG_FILE_DIR"] == str(pyproject.parent) assert options.per_module_options["c"] == { "disable_error_code": [], From ed4158337d03c7fd91a488c4e4118c10b90f783e Mon Sep 17 00:00:00 2001 From: Hasier Date: Tue, 27 May 2025 18:08:31 +0100 Subject: [PATCH 10/14] Set envvar based on visited files --- mypy/config_parser.py | 6 +++--- mypy/test/test_config_parser.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 19bbd799d9ef..7034999d0634 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -365,9 +365,9 @@ def _parse_and_extend_config_file( return None visited.add(abs_file_read) - if not os.environ.get("MYPY_CONFIG_FILE_DIR"): - # set it only if unset to allow for path variable expansions when parsing below, - # so recursive calls for config extend references won't overwrite it + if len(visited) == 1: + # set it only after the first config file is visited to allow for path variable expansions + # when parsing below, so recursive calls for config extend references won't overwrite it os.environ["MYPY_CONFIG_FILE_DIR"] = os.path.dirname(abs_file_read) mypy_updates: dict[str, object] = {} diff --git a/mypy/test/test_config_parser.py b/mypy/test/test_config_parser.py index 67bc4a7092d0..c876b27f6cff 100644 --- a/mypy/test/test_config_parser.py +++ b/mypy/test/test_config_parser.py @@ -174,6 +174,7 @@ def set_strict_flags() -> None: assert strict_option_set is True assert options.ignore_missing_imports_per_module is True assert options.config_file == str(pyproject.name) + assert os.environ["MYPY_CONFIG_FILE_DIR"] == str(pyproject.parent) assert options.per_module_options["c"] == { "disable_error_code": [], From a0c4d6c862c270c6033003a6fe23a6b46f9fc99e Mon Sep 17 00:00:00 2001 From: Hasier Date: Wed, 28 May 2025 11:46:31 +0100 Subject: [PATCH 11/14] Account for strict flag override --- mypy/config_parser.py | 30 +++++++++++++++++++----------- mypy/test/test_config_parser.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 7034999d0634..d6836b096aac 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -301,9 +301,15 @@ def parse_config_file( stdout = stdout or sys.stdout stderr = stderr or sys.stderr + strict_found = False + + def set_strict(value: bool) -> None: + nonlocal strict_found + strict_found = value + ret = _parse_and_extend_config_file( template=options, - set_strict_flags=set_strict_flags, + set_strict=set_strict, filename=filename, stdout=stdout, stderr=stderr, @@ -315,6 +321,9 @@ def parse_config_file( file_read, mypy_updates, mypy_report_dirs, module_updates = ret + if strict_found: + set_strict_flags() + options.config_file = file_read for k, v in mypy_updates.items(): @@ -344,7 +353,7 @@ def _merge_updates(existing: dict[str, object], new: dict[str, object]) -> None: def _parse_and_extend_config_file( template: Options, - set_strict_flags: Callable[[], None], + set_strict: Callable[[bool], None], filename: str | None, stdout: TextIO, stderr: TextIO, @@ -384,7 +393,7 @@ def _parse_and_extend_config_file( if extend: parse_ret = _parse_and_extend_config_file( template=template, - set_strict_flags=set_strict_flags, + set_strict=set_strict, # refer to extend relative to directory where we found current config filename=os.path.relpath( os.path.normpath( @@ -403,7 +412,7 @@ def _parse_and_extend_config_file( prefix = f"{file_read}: [mypy]: " updates, report_dirs = parse_section( - prefix, template, set_strict_flags, section, config_types, stderr + prefix, template, set_strict, section, config_types, stderr ) # extend and overwrite existing values with new ones _merge_updates(mypy_updates, updates) @@ -413,7 +422,7 @@ def _parse_and_extend_config_file( if name.startswith("mypy-"): prefix = get_prefix(file_read, name) updates, report_dirs = parse_section( - prefix, template, set_strict_flags, section, config_types, stderr + prefix, template, set_strict, section, config_types, stderr ) if report_dirs: print( @@ -555,7 +564,7 @@ def destructure_overrides(toml_data: dict[str, Any]) -> dict[str, Any]: def parse_section( prefix: str, template: Options, - set_strict_flags: Callable[[], None], + set_strict: Callable[[bool], None], section: Mapping[str, Any], config_types: dict[str, Any], stderr: TextIO = sys.stderr, @@ -644,8 +653,7 @@ def parse_section( print(f"{prefix}{key}: {err}", file=stderr) continue if key == "strict": - if v: - set_strict_flags() + set_strict(v) continue results[options_key] = v @@ -746,12 +754,12 @@ def parse_mypy_comments( stderr = StringIO() strict_found = False - def set_strict_flags() -> None: + def set_strict(value: bool) -> None: nonlocal strict_found - strict_found = True + strict_found = value new_sections, reports = parse_section( - "", template, set_strict_flags, parser["dummy"], ini_config_types, stderr=stderr + "", template, set_strict, parser["dummy"], ini_config_types, stderr=stderr ) errors.extend((lineno, x) for x in stderr.getvalue().strip().split("\n") if x) if reports: diff --git a/mypy/test/test_config_parser.py b/mypy/test/test_config_parser.py index c876b27f6cff..bc75b840d42a 100644 --- a/mypy/test/test_config_parser.py +++ b/mypy/test/test_config_parser.py @@ -209,3 +209,32 @@ def test_extend_cyclic(self) -> None: f"Circular extend detected: {pyproject}\n" f"../pyproject.toml is not a valid path to extend from {ini}\n" ) + + def test_extend_strict_override(self) -> None: + with tempfile.TemporaryDirectory() as _tmpdir: + tmpdir = Path(_tmpdir) + with chdir(tmpdir): + pyproject = tmpdir / "pyproject.toml" + write_config( + pyproject, '[tool.mypy]\nextend = "./folder/mypy.ini"\nstrict = True\n' + ) + + folder = tmpdir / "folder" + folder.mkdir() + ini = folder / "mypy.ini" + write_config(ini, "[mypy]\nstrict = false\n") + + options = Options() + + stdout = io.StringIO() + stderr = io.StringIO() + + strict_called = False + + def set_strict_flags() -> None: + nonlocal strict_called + strict_called = True + + parse_config_file(options, set_strict_flags, None, stdout, stderr) + + assert strict_called is False From 78c5b167a5bf0a5b348fc3bd4608311a726b2cdc Mon Sep 17 00:00:00 2001 From: Hasier Date: Wed, 28 May 2025 12:05:38 +0100 Subject: [PATCH 12/14] Update success test case --- mypy/test/test_config_parser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/test/test_config_parser.py b/mypy/test/test_config_parser.py index bc75b840d42a..5e776bdb1bc1 100644 --- a/mypy/test/test_config_parser.py +++ b/mypy/test/test_config_parser.py @@ -143,7 +143,7 @@ def test_extend_success(self) -> None: pyproject, "[tool.mypy]\n" 'extend = "./folder/mypy.ini"\n' - "strict = false\n" + "strict = true\n" "[[tool.mypy.overrides]]\n" 'module = "c"\n' 'enable_error_code = ["explicit-override"]\n' @@ -154,7 +154,7 @@ def test_extend_success(self) -> None: write_config( folder / "mypy.ini", "[mypy]\n" - "strict = True\n" + "strict = False\n" "ignore_missing_imports_per_module = True\n" "[mypy-c]\n" "disallow_incomplete_defs = True", From 8e6125f9ad9065adabe51df2b14fd9c76b95e02e Mon Sep 17 00:00:00 2001 From: Hasier Date: Wed, 28 May 2025 17:23:28 +0100 Subject: [PATCH 13/14] Fix MacOS tmp path compatibility --- mypy/test/test_config_parser.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mypy/test/test_config_parser.py b/mypy/test/test_config_parser.py index 5e776bdb1bc1..ea8c8da6d381 100644 --- a/mypy/test/test_config_parser.py +++ b/mypy/test/test_config_parser.py @@ -174,7 +174,8 @@ def set_strict_flags() -> None: assert strict_option_set is True assert options.ignore_missing_imports_per_module is True assert options.config_file == str(pyproject.name) - assert os.environ["MYPY_CONFIG_FILE_DIR"] == str(pyproject.parent) + # MacOS has some odd symlinks for tmp folder, resolve them to get the actual values + assert os.environ["MYPY_CONFIG_FILE_DIR"] == os.path.realpath(pyproject.parent) assert options.per_module_options["c"] == { "disable_error_code": [], @@ -204,10 +205,11 @@ def test_extend_cyclic(self) -> None: stderr = io.StringIO() parse_config_file(options, lambda: None, None, stdout, stderr) + # MacOS has some odd symlinks for tmp folder, resolve them to get the actual values assert stdout.getvalue() == "" assert stderr.getvalue() == ( - f"Circular extend detected: {pyproject}\n" - f"../pyproject.toml is not a valid path to extend from {ini}\n" + f"Circular extend detected: {os.path.realpath(pyproject)}\n" + f"../pyproject.toml is not a valid path to extend from {os.path.realpath(ini)}\n" ) def test_extend_strict_override(self) -> None: From 282b5798692ae5998186aeaae76c4202712fd232 Mon Sep 17 00:00:00 2001 From: Hasier Date: Wed, 28 May 2025 18:00:54 +0100 Subject: [PATCH 14/14] Conditionally fix MacOS tmp path --- mypy/test/test_config_parser.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/mypy/test/test_config_parser.py b/mypy/test/test_config_parser.py index ea8c8da6d381..e6f3af97bd42 100644 --- a/mypy/test/test_config_parser.py +++ b/mypy/test/test_config_parser.py @@ -174,8 +174,12 @@ def set_strict_flags() -> None: assert strict_option_set is True assert options.ignore_missing_imports_per_module is True assert options.config_file == str(pyproject.name) - # MacOS has some odd symlinks for tmp folder, resolve them to get the actual values - assert os.environ["MYPY_CONFIG_FILE_DIR"] == os.path.realpath(pyproject.parent) + if os.path.realpath(pyproject.parent).startswith("/private"): + # MacOS has some odd symlinks for tmp folder, resolve them to get the actual values + expected_path = os.path.realpath(pyproject.parent) + else: + expected_path = str(pyproject.parent) + assert os.environ["MYPY_CONFIG_FILE_DIR"] == expected_path assert options.per_module_options["c"] == { "disable_error_code": [], @@ -205,11 +209,18 @@ def test_extend_cyclic(self) -> None: stderr = io.StringIO() parse_config_file(options, lambda: None, None, stdout, stderr) - # MacOS has some odd symlinks for tmp folder, resolve them to get the actual values + if os.path.realpath(pyproject).startswith("/private"): + # MacOS has some odd symlinks for tmp folder, resolve them to get the actual values + expected_pyproject = os.path.realpath(pyproject) + expected_ini = os.path.realpath(ini) + else: + expected_pyproject = str(pyproject) + expected_ini = str(ini) + assert stdout.getvalue() == "" assert stderr.getvalue() == ( - f"Circular extend detected: {os.path.realpath(pyproject)}\n" - f"../pyproject.toml is not a valid path to extend from {os.path.realpath(ini)}\n" + f"Circular extend detected: {expected_pyproject}\n" + f"../pyproject.toml is not a valid path to extend from {expected_ini}\n" ) def test_extend_strict_override(self) -> None: