From 450de8a2799c14495be35f8028c62cce82c86e64 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 17 Jan 2025 02:38:26 -0800 Subject: [PATCH 01/11] Handle precedence correctly when searching for config file --- mypy/config_parser.py | 114 +++++++++++++++++++++++++++++------------- mypy/defaults.py | 41 ++------------- mypy/main.py | 2 +- 3 files changed, 82 insertions(+), 75 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index a0f93f663522..33e00ca00035 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -216,6 +216,73 @@ def split_commas(value: str) -> list[str]: } ) +def _maybe_parse_individual_file( + config_file: str, stderr: TextIO | None = None, +) -> tuple[MutableMapping[str, Any], dict[str, _INI_PARSER_CALLABLE], str] | None: + + if not os.path.exists(config_file): + return None + + config_parser = configparser.RawConfigParser() + parser: MutableMapping[str, Any] + try: + if is_toml(config_file): + with open(config_file, "rb") as f: + toml_data = tomllib.load(f) + # Filter down to just mypy relevant toml keys + toml_data = toml_data.get("tool", {}) + if "mypy" not in toml_data: + return None + toml_data = {"mypy": toml_data["mypy"]} + parser = destructure_overrides(toml_data) + config_types = toml_config_types + else: + config_parser.read(config_file) + parser = config_parser + config_types = ini_config_types + + except (tomllib.TOMLDecodeError, configparser.Error, ConfigTOMLValueError) as err: + print(f"{config_file}: {err}", file=stderr) + return None + + if os.path.basename(config_file) in defaults.SHARED_CONFIG_NAMES and "mypy" not in parser: + return None + + return parser, config_types, config_file + + +def _find_config_file( + stderr: TextIO | None = None, +) -> tuple[MutableMapping[str, Any], dict[str, _INI_PARSER_CALLABLE], str] | None: + + current_dir = os.path.abspath(os.getcwd()) + + while True: + for name in defaults.CONFIG_NAMES + defaults.SHARED_CONFIG_NAMES: + config_file = os.path.relpath(os.path.join(current_dir, name)) + ret = _maybe_parse_individual_file(config_file, stderr) + if ret is None: + continue + return ret + + if any( + os.path.exists(os.path.join(current_dir, cvs_root)) + for cvs_root in (".git", ".hg") + ): + break + parent_dir = os.path.dirname(current_dir) + if parent_dir == current_dir: + break + current_dir = parent_dir + + for config_file in defaults.USER_CONFIG_FILES: + ret = _maybe_parse_individual_file(config_file, stderr) + if ret is None: + continue + return ret + + return None + def parse_config_file( options: Options, @@ -234,46 +301,21 @@ def parse_config_file( stderr = stderr or sys.stderr if filename is not None: - config_files: tuple[str, ...] = (filename,) - else: - config_files_iter: Iterable[str] = map(os.path.expanduser, defaults.CONFIG_FILES) - config_files = tuple(config_files_iter) - - config_parser = configparser.RawConfigParser() - - for config_file in config_files: - if not os.path.exists(config_file): - continue - try: - if is_toml(config_file): - with open(config_file, "rb") as f: - toml_data = tomllib.load(f) - # Filter down to just mypy relevant toml keys - toml_data = toml_data.get("tool", {}) - if "mypy" not in toml_data: - continue - toml_data = {"mypy": toml_data["mypy"]} - parser: MutableMapping[str, Any] = destructure_overrides(toml_data) - config_types = toml_config_types - else: - config_parser.read(config_file) - parser = config_parser - config_types = ini_config_types - except (tomllib.TOMLDecodeError, configparser.Error, ConfigTOMLValueError) as err: - print(f"{config_file}: {err}", file=stderr) - else: - if config_file in defaults.SHARED_CONFIG_FILES and "mypy" not in parser: - continue - file_read = config_file - options.config_file = file_read - break + ret = _maybe_parse_individual_file(filename, stderr) + if ret is None: + return + parser, config_types, file_read = ret else: - return + ret = _find_config_file(stderr) + if ret is None: + return + parser, config_types, file_read = ret - os.environ["MYPY_CONFIG_FILE_DIR"] = os.path.dirname(os.path.abspath(config_file)) + options.config_file = file_read + os.environ["MYPY_CONFIG_FILE_DIR"] = os.path.dirname(os.path.abspath(file_read)) if "mypy" not in parser: - if filename or file_read not in defaults.SHARED_CONFIG_FILES: + 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"] diff --git a/mypy/defaults.py b/mypy/defaults.py index ed0b8d0dc6d9..67628d544edf 100644 --- a/mypy/defaults.py +++ b/mypy/defaults.py @@ -12,50 +12,15 @@ # mypy, at least version PYTHON3_VERSION is needed. PYTHON3_VERSION_MIN: Final = (3, 8) # Keep in sync with typeshed's python support +CACHE_DIR: Final = ".mypy_cache" -def find_pyproject() -> str: - """Search for file pyproject.toml in the parent directories recursively. - - It resolves symlinks, so if there is any symlink up in the tree, it does not respect them - - If the file is not found until the root of FS or repository, PYPROJECT_FILE is used - """ - - def is_root(current_dir: str) -> bool: - parent = os.path.join(current_dir, os.path.pardir) - return os.path.samefile(current_dir, parent) or any( - os.path.isdir(os.path.join(current_dir, cvs_root)) for cvs_root in (".git", ".hg") - ) - - # Preserve the original behavior, returning PYPROJECT_FILE if exists - if os.path.isfile(PYPROJECT_FILE) or is_root(os.path.curdir): - return PYPROJECT_FILE - - # And iterate over the tree - current_dir = os.path.pardir - while not is_root(current_dir): - config_file = os.path.join(current_dir, PYPROJECT_FILE) - if os.path.isfile(config_file): - return config_file - parent = os.path.join(current_dir, os.path.pardir) - current_dir = parent - - return PYPROJECT_FILE - +CONFIG_NAMES: Final = ["mypy.ini", ".mypy.ini"] +SHARED_CONFIG_NAMES: Final = ["pyproject.toml", "setup.cfg"] -CACHE_DIR: Final = ".mypy_cache" -CONFIG_FILE: Final = ["mypy.ini", ".mypy.ini"] -PYPROJECT_FILE: Final = "pyproject.toml" -PYPROJECT_CONFIG_FILES: Final = [find_pyproject()] -SHARED_CONFIG_FILES: Final = ["setup.cfg"] USER_CONFIG_FILES: Final = ["~/.config/mypy/config", "~/.mypy.ini"] if os.environ.get("XDG_CONFIG_HOME"): USER_CONFIG_FILES.insert(0, os.path.join(os.environ["XDG_CONFIG_HOME"], "mypy/config")) -CONFIG_FILES: Final = ( - CONFIG_FILE + PYPROJECT_CONFIG_FILES + SHARED_CONFIG_FILES + USER_CONFIG_FILES -) - # This must include all reporters defined in mypy.report. This is defined here # to make reporter names available without importing mypy.report -- this speeds # up startup. diff --git a/mypy/main.py b/mypy/main.py index ae7a3b9d5c86..79147f8bf0bd 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -564,7 +564,7 @@ def add_invertible_flag( "--config-file", help=( f"Configuration file, must have a [mypy] section " - f"(defaults to {', '.join(defaults.CONFIG_FILES)})" + f"(defaults to {', '.join(defaults.CONFIG_NAMES + defaults.SHARED_CONFIG_NAMES)})" ), ) add_invertible_flag( From 00c627459485c021915864185578753f33454168 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 10:42:47 +0000 Subject: [PATCH 02/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/config_parser.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 33e00ca00035..e1732c7994e8 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -15,7 +15,7 @@ else: import tomli as tomllib -from collections.abc import Iterable, Mapping, MutableMapping, Sequence +from collections.abc import Mapping, MutableMapping, Sequence from typing import Any, Callable, Final, TextIO, Union from typing_extensions import TypeAlias as _TypeAlias @@ -216,8 +216,9 @@ def split_commas(value: str) -> list[str]: } ) + def _maybe_parse_individual_file( - config_file: str, stderr: TextIO | None = None, + config_file: str, stderr: TextIO | None = None ) -> tuple[MutableMapping[str, Any], dict[str, _INI_PARSER_CALLABLE], str] | None: if not os.path.exists(config_file): @@ -266,8 +267,7 @@ def _find_config_file( return ret if any( - os.path.exists(os.path.join(current_dir, cvs_root)) - for cvs_root in (".git", ".hg") + os.path.exists(os.path.join(current_dir, cvs_root)) for cvs_root in (".git", ".hg") ): break parent_dir = os.path.dirname(current_dir) From ebc9b2f1ced3100e104f1058a8dfdbe8f40a10bd Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 17 Jan 2025 02:45:12 -0800 Subject: [PATCH 03/11] . --- mypy/config_parser.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index e1732c7994e8..2645537e4ca8 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -217,7 +217,7 @@ def split_commas(value: str) -> list[str]: ) -def _maybe_parse_individual_file( +def _parse_individual_file( config_file: str, stderr: TextIO | None = None ) -> tuple[MutableMapping[str, Any], dict[str, _INI_PARSER_CALLABLE], str] | None: @@ -261,7 +261,7 @@ def _find_config_file( while True: for name in defaults.CONFIG_NAMES + defaults.SHARED_CONFIG_NAMES: config_file = os.path.relpath(os.path.join(current_dir, name)) - ret = _maybe_parse_individual_file(config_file, stderr) + ret = _parse_individual_file(config_file, stderr) if ret is None: continue return ret @@ -276,7 +276,7 @@ def _find_config_file( current_dir = parent_dir for config_file in defaults.USER_CONFIG_FILES: - ret = _maybe_parse_individual_file(config_file, stderr) + ret = _parse_individual_file(config_file, stderr) if ret is None: continue return ret @@ -300,16 +300,14 @@ def parse_config_file( stdout = stdout or sys.stdout stderr = stderr or sys.stderr - if filename is not None: - ret = _maybe_parse_individual_file(filename, stderr) - if ret is None: - return - parser, config_types, file_read = ret - else: - ret = _find_config_file(stderr) - if ret is None: - return - parser, config_types, file_read = ret + ret = ( + _parse_individual_file(filename, stderr) + if filename is not None + else _find_config_file(stderr) + ) + if ret is None: + return + 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)) From 1d8fdb5c87e06773465d1e69a9ebaa6bae52670f Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 17 Jan 2025 02:47:02 -0800 Subject: [PATCH 04/11] delete a line --- mypy/config_parser.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 2645537e4ca8..4161f7e04dd3 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -224,7 +224,6 @@ def _parse_individual_file( if not os.path.exists(config_file): return None - config_parser = configparser.RawConfigParser() parser: MutableMapping[str, Any] try: if is_toml(config_file): @@ -238,8 +237,8 @@ def _parse_individual_file( parser = destructure_overrides(toml_data) config_types = toml_config_types else: - config_parser.read(config_file) - parser = config_parser + parser = configparser.RawConfigParser() + parser.read(config_file) config_types = ini_config_types except (tomllib.TOMLDecodeError, configparser.Error, ConfigTOMLValueError) as err: From f1d0d91e26198072ea0b8b7b38fccb14e1383a87 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 17 Jan 2025 20:29:19 -0800 Subject: [PATCH 05/11] . --- CHANGELOG.md | 32 ++++++++++++++++++++++---------- docs/source/config_file.rst | 34 +++++++++++++++++++++------------- 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5260104f3fe..3acec84fec5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,15 +9,6 @@ garbage collector. Contributed by Jukka Lehtosalo (PR [18306](https://github.com/python/mypy/pull/18306)). -### Drop Support for Python 3.8 - -Mypy no longer supports running with Python 3.8, which has reached end-of-life. -When running mypy with Python 3.9+, it is still possible to type check code -that needs to support Python 3.8 with the `--python-version 3.8` argument. -Support for this will be dropped in the first half of 2025! - -Contributed by Marc Mueller (PR [17492](https://github.com/python/mypy/pull/17492)). - ### Mypyc accelerated mypy wheels for aarch64 Mypy can compile itself to C extension modules using mypyc. This makes mypy 3-5x faster @@ -25,7 +16,9 @@ than if mypy is interpreted with pure Python. We now build and upload mypyc acce mypy wheels for `manylinux_aarch64` to PyPI, making it easy for users on such platforms to realise this speedup. -Contributed by Christian Bundy (PR [mypy_mypyc-wheels#76](https://github.com/mypyc/mypy_mypyc-wheels/pull/76)) +Contributed by Christian Bundy and Marc Mueller +(PR [mypy_mypyc-wheels#76](https://github.com/mypyc/mypy_mypyc-wheels/pull/76), +PR [mypy_mypyc-wheels#89](https://github.com/mypyc/mypy_mypyc-wheels/pull/89)). ### `--strict-bytes` @@ -48,6 +41,16 @@ Contributed by Christoph Tyralla (PR [18180](https://github.com/python/mypy/pull (Speaking of partial types, another reminder that mypy plans on enabling `--local-partial-types` by default in **mypy 2.0**). +### Better discovery of configuration files + +Mypy will now walk up the filesystem (up until a repository or file system root) to discover +configuration files. See the +[mypy configuration file documentation](https://mypy.readthedocs.io/en/stable/config_file.html) +for more details. + +Contributed by Mikhail Shiryaev and Shantanu Jain +(PR [16965](https://github.com/python/mypy/pull/16965), PR [18482](https://github.com/python/mypy/pull/18482) + ### Better line numbers for decorators and slice expressions Mypy now uses more correct line numbers for decorators and slice expressions. In some cases, this @@ -56,6 +59,15 @@ may necessitate changing the location of a `# type: ignore` comment. Contributed by Shantanu Jain (PR [18392](https://github.com/python/mypy/pull/18392), PR [18397](https://github.com/python/mypy/pull/18397)). +### Drop Support for Python 3.8 + +Mypy no longer supports running with Python 3.8, which has reached end-of-life. +When running mypy with Python 3.9+, it is still possible to type check code +that needs to support Python 3.8 with the `--python-version 3.8` argument. +Support for this will be dropped in the first half of 2025! + +Contributed by Marc Mueller (PR [17492](https://github.com/python/mypy/pull/17492)). + ## Mypy 1.14 We’ve just uploaded mypy 1.14 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). diff --git a/docs/source/config_file.rst b/docs/source/config_file.rst index 747ef3a9fdaa..ce7661f93f5e 100644 --- a/docs/source/config_file.rst +++ b/docs/source/config_file.rst @@ -7,22 +7,30 @@ Mypy is very configurable. This is most useful when introducing typing to an existing codebase. See :ref:`existing-code` for concrete advice for that situation. -Mypy supports reading configuration settings from a file with the following precedence order: +Mypy supports reading configuration settings from a file. By default, mypy will +discover configuration files by walking up the file system (up until the root of +a repository or the root of the filesystem). In each directory, it will look for +the following configuration files (in this order): - 1. ``./mypy.ini`` - 2. ``./.mypy.ini`` - 3. ``./pyproject.toml`` - 4. ``./setup.cfg`` - 5. ``$XDG_CONFIG_HOME/mypy/config`` - 6. ``~/.config/mypy/config`` - 7. ``~/.mypy.ini`` + 1. ``mypy.ini`` + 2. ``.mypy.ini`` + 3. ``pyproject.toml`` (containing a ``[tool.mypy]`` section) + 4. ``setup.cfg`` (containing a ``[mypy]`` section) + +If no configuration file is found by this method, mypy will then look for +configuration files in the following locations (in this order): + + 1. ``$XDG_CONFIG_HOME/mypy/config`` + 2. ``~/.config/mypy/config`` + 3. ``~/.mypy.ini`` + +The :option:`--config-file ` command-line flag has the +highest precedence and must be point towards a valid configuration file; +otherwise mypy will report an error and exit. Without the command line option, +mypy will look for configuration files in the precedence order above. It is important to understand that there is no merging of configuration -files, as it would lead to ambiguity. The :option:`--config-file ` -command-line flag has the highest precedence and -must be correct; otherwise mypy will report an error and exit. Without the -command line option, mypy will look for configuration files in the -precedence order above. +files, as it would lead to ambiguity. Most flags correspond closely to :ref:`command-line flags ` but there are some differences in flag names and some From c694ea6c83cd0af32d83bfa5475713037af64297 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 17 Jan 2025 20:40:52 -0800 Subject: [PATCH 06/11] . --- mypy/test/test_config_parser.py | 114 ++++++++++++++++++++++++++ test-data/unit/cmdline.pyproject.test | 35 -------- 2 files changed, 114 insertions(+), 35 deletions(-) create mode 100644 mypy/test/test_config_parser.py diff --git a/mypy/test/test_config_parser.py b/mypy/test/test_config_parser.py new file mode 100644 index 000000000000..b670e905fbe5 --- /dev/null +++ b/mypy/test/test_config_parser.py @@ -0,0 +1,114 @@ +from __future__ import annotations +import contextlib +import os +import tempfile +import unittest +from collections.abc import Iterator +from pathlib import Path + +from mypy.defaults import CONFIG_NAMES, SHARED_CONFIG_NAMES +from mypy.config_parser import _find_config_file + + +@contextlib.contextmanager +def chdir(target: Path) -> Iterator[None]: + # Replace with contextlib.chdir in Python 3.11 + dir = os.getcwd() + os.chdir(target) + try: + yield + finally: + os.chdir(dir) + + +def write_config(path: Path, content: str | None = None) -> None: + if path.suffix == ".toml": + if content is None: + content = "[tool.mypy]\nstrict = true" + path.write_text(content) + else: + if content is None: + content = "[mypy]\nstrict = True" + path.write_text(content) + + +class FindConfigFileSuite(unittest.TestCase): + + def test_no_config(self) -> None: + with tempfile.TemporaryDirectory() as _tmpdir: + tmpdir = Path(_tmpdir) + (tmpdir / ".git").touch() + with contextlib.chdir(tmpdir): + result = _find_config_file() + assert result is None + + def test_parent_config_with_and_without_git(self) -> None: + for name in CONFIG_NAMES + SHARED_CONFIG_NAMES: + with tempfile.TemporaryDirectory() as _tmpdir: + tmpdir = Path(_tmpdir) + + config = tmpdir / name + write_config(config) + + child = tmpdir / "child" + child.mkdir() + + with chdir(child): + result = _find_config_file() + assert result is not None + assert Path(result[2]).resolve() == config.resolve() + + (child / ".git").touch() + + result = _find_config_file() + assert result is None + + def test_precedence_basic(self) -> None: + with tempfile.TemporaryDirectory() as _tmpdir: + tmpdir = Path(_tmpdir) + + pyproject = tmpdir / "pyproject.toml" + setup_cfg = tmpdir / "setup.cfg" + mypy_ini = tmpdir / "mypy.ini" + dot_mypy = tmpdir / ".mypy.ini" + write_config(pyproject) + write_config(setup_cfg) + write_config(mypy_ini) + write_config(dot_mypy) + + with contextlib.chdir(tmpdir): + result = _find_config_file() + assert result is not None + assert result[2] == "mypy.ini" + + mypy_ini.unlink() + result = _find_config_file() + assert result is not None + assert result[2] == ".mypy.ini" + + dot_mypy.unlink() + result = _find_config_file() + assert result is not None + assert result[2] == "pyproject.toml" + + pyproject.unlink() + result = _find_config_file() + assert result is not None + assert result[2] == "setup.cfg" + + def test_precedence_missing_section(self) -> None: + with tempfile.TemporaryDirectory() as _tmpdir: + tmpdir = Path(_tmpdir) + + child = tmpdir / "child" + child.mkdir() + + parent_mypy = tmpdir / "mypy.ini" + child_pyproject = child / "pyproject.toml" + write_config(parent_mypy) + write_config(child_pyproject, content="") + + with contextlib.chdir(child): + result = _find_config_file() + assert result is not None + assert Path(result[2]).resolve() == parent_mypy.resolve() diff --git a/test-data/unit/cmdline.pyproject.test b/test-data/unit/cmdline.pyproject.test index e6e5f113a844..57e6facad032 100644 --- a/test-data/unit/cmdline.pyproject.test +++ b/test-data/unit/cmdline.pyproject.test @@ -133,38 +133,3 @@ Neither is this! description = "Factory ⸻ A code generator 🏭" \[tool.mypy] [file x.py] - -[case testSearchRecursively] -# cmd: mypy x.py -[file ../pyproject.toml] -\[tool.mypy] -\[tool.mypy.overrides] -module = "x" -disallow_untyped_defs = false -[file x.py] -pass -[out] -../pyproject.toml: tool.mypy.overrides sections must be an array. Please make sure you are using double brackets like so: [[tool.mypy.overrides]] -== Return code: 0 - -[case testSearchRecursivelyStopsGit] -# cmd: mypy x.py -[file .git/test] -[file ../pyproject.toml] -\[tool.mypy] -\[tool.mypy.overrides] -module = "x" -disallow_untyped_defs = false -[file x.py] -i: int = 0 - -[case testSearchRecursivelyStopsHg] -# cmd: mypy x.py -[file .hg/test] -[file ../pyproject.toml] -\[tool.mypy] -\[tool.mypy.overrides] -module = "x" -disallow_untyped_defs = false -[file x.py] -i: int = 0 From 58f389bdcd33d819127de69365aa46a30a3474b7 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 17 Jan 2025 20:57:31 -0800 Subject: [PATCH 07/11] . --- mypy/test/test_config_parser.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/mypy/test/test_config_parser.py b/mypy/test/test_config_parser.py index b670e905fbe5..6e5b1028f3b6 100644 --- a/mypy/test/test_config_parser.py +++ b/mypy/test/test_config_parser.py @@ -58,7 +58,17 @@ def test_parent_config_with_and_without_git(self) -> None: assert result is not None assert Path(result[2]).resolve() == config.resolve() - (child / ".git").touch() + git = child / ".git" + git.touch() + + result = _find_config_file() + assert result is None + + git.unlink() + result = _find_config_file() + assert result is not None + hg = child / ".hg" + hg.touch() result = _find_config_file() assert result is None From cb1f686e1335064219747fd553b4dba925b7a8d9 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 17 Jan 2025 20:58:57 -0800 Subject: [PATCH 08/11] . --- mypy/test/test_config_parser.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mypy/test/test_config_parser.py b/mypy/test/test_config_parser.py index 6e5b1028f3b6..297fb1bd6374 100644 --- a/mypy/test/test_config_parser.py +++ b/mypy/test/test_config_parser.py @@ -38,7 +38,7 @@ def test_no_config(self) -> None: with tempfile.TemporaryDirectory() as _tmpdir: tmpdir = Path(_tmpdir) (tmpdir / ".git").touch() - with contextlib.chdir(tmpdir): + with chdir(tmpdir): result = _find_config_file() assert result is None @@ -86,7 +86,7 @@ def test_precedence_basic(self) -> None: write_config(mypy_ini) write_config(dot_mypy) - with contextlib.chdir(tmpdir): + with chdir(tmpdir): result = _find_config_file() assert result is not None assert result[2] == "mypy.ini" @@ -118,7 +118,7 @@ def test_precedence_missing_section(self) -> None: write_config(parent_mypy) write_config(child_pyproject, content="") - with contextlib.chdir(child): + with chdir(child): result = _find_config_file() assert result is not None assert Path(result[2]).resolve() == parent_mypy.resolve() From d22046654656867df361240988a4424cbf3eba7c Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 17 Jan 2025 21:00:21 -0800 Subject: [PATCH 09/11] . --- mypy/test/test_config_parser.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/test/test_config_parser.py b/mypy/test/test_config_parser.py index 297fb1bd6374..b689166b24bb 100644 --- a/mypy/test/test_config_parser.py +++ b/mypy/test/test_config_parser.py @@ -1,4 +1,5 @@ from __future__ import annotations + import contextlib import os import tempfile @@ -6,8 +7,8 @@ from collections.abc import Iterator from pathlib import Path -from mypy.defaults import CONFIG_NAMES, SHARED_CONFIG_NAMES from mypy.config_parser import _find_config_file +from mypy.defaults import CONFIG_NAMES, SHARED_CONFIG_NAMES @contextlib.contextmanager From b09c6a43e2b27973696b06b46256e732c0743837 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 17 Jan 2025 21:03:01 -0800 Subject: [PATCH 10/11] . --- mypy/test/test_config_parser.py | 47 ++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/mypy/test/test_config_parser.py b/mypy/test/test_config_parser.py index b689166b24bb..597143738f23 100644 --- a/mypy/test/test_config_parser.py +++ b/mypy/test/test_config_parser.py @@ -74,7 +74,7 @@ def test_parent_config_with_and_without_git(self) -> None: result = _find_config_file() assert result is None - def test_precedence_basic(self) -> None: + def test_precedence(self) -> None: with tempfile.TemporaryDirectory() as _tmpdir: tmpdir = Path(_tmpdir) @@ -82,30 +82,35 @@ def test_precedence_basic(self) -> None: setup_cfg = tmpdir / "setup.cfg" mypy_ini = tmpdir / "mypy.ini" dot_mypy = tmpdir / ".mypy.ini" - write_config(pyproject) - write_config(setup_cfg) - write_config(mypy_ini) - write_config(dot_mypy) - with chdir(tmpdir): - result = _find_config_file() - assert result is not None - assert result[2] == "mypy.ini" + child = tmpdir / "child" + child.mkdir() - mypy_ini.unlink() - result = _find_config_file() - assert result is not None - assert result[2] == ".mypy.ini" + for cwd in [tmpdir, child]: + write_config(pyproject) + write_config(setup_cfg) + write_config(mypy_ini) + write_config(dot_mypy) - dot_mypy.unlink() - result = _find_config_file() - assert result is not None - assert result[2] == "pyproject.toml" + with chdir(cwd): + result = _find_config_file() + assert result is not None + assert os.path.basename(result[2]) == "mypy.ini" - pyproject.unlink() - result = _find_config_file() - assert result is not None - assert result[2] == "setup.cfg" + mypy_ini.unlink() + result = _find_config_file() + assert result is not None + assert os.path.basename(result[2]) == ".mypy.ini" + + dot_mypy.unlink() + result = _find_config_file() + assert result is not None + assert os.path.basename(result[2]) == "pyproject.toml" + + pyproject.unlink() + result = _find_config_file() + assert result is not None + assert os.path.basename(result[2]) == "setup.cfg" def test_precedence_missing_section(self) -> None: with tempfile.TemporaryDirectory() as _tmpdir: From cf3ededaf3dcfb77ebe8563bb56fee80fa0c3405 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Mon, 20 Jan 2025 13:42:15 -0800 Subject: [PATCH 11/11] Update docs/source/config_file.rst --- docs/source/config_file.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/config_file.rst b/docs/source/config_file.rst index ce7661f93f5e..41dadbe7d2a3 100644 --- a/docs/source/config_file.rst +++ b/docs/source/config_file.rst @@ -25,7 +25,7 @@ configuration files in the following locations (in this order): 3. ``~/.mypy.ini`` The :option:`--config-file ` command-line flag has the -highest precedence and must be point towards a valid configuration file; +highest precedence and must point towards a valid configuration file; otherwise mypy will report an error and exit. Without the command line option, mypy will look for configuration files in the precedence order above.