[med-svn] [Git][python-team/packages/mypy][upstream] New upstream version 1.15~rc0.2

Michael R. Crusoe (@crusoe) gitlab at salsa.debian.org
Sat Jan 25 15:01:02 GMT 2025



Michael R. Crusoe pushed to branch upstream at Debian Python Team / packages / mypy


Commits:
4eba324a by Michael R. Crusoe at 2025-01-24T11:14:46+01:00
New upstream version 1.15~rc0.2
- - - - -


13 changed files:

- CHANGELOG.md
- PKG-INFO
- docs/source/config_file.rst
- mypy.egg-info/PKG-INFO
- mypy.egg-info/SOURCES.txt
- mypy/config_parser.py
- mypy/defaults.py
- mypy/main.py
- mypy/semanal.py
- + mypy/test/test_config_parser.py
- test-data/unit/check-type-aliases.test
- test-data/unit/cmdline.pyproject.test
- test-data/unit/diff.test


Changes:

=====================================
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/)).


=====================================
PKG-INFO
=====================================
@@ -1,6 +1,6 @@
 Metadata-Version: 2.2
 Name: mypy
-Version: 1.15.0+dev.68cffa7afe03d2b663aced9a70254e58704857db
+Version: 1.15.0+dev.23d862dd6fbb905a69bcb31e88746dc7a1eb4a43
 Summary: Optional static typing for Python
 Author-email: Jukka Lehtosalo <jukka.lehtosalo at iki.fi>
 License: MIT


=====================================
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 <mypy --config-file>` command-line flag has the
+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.
 
 It is important to understand that there is no merging of configuration
-files, as it would lead to ambiguity. The :option:`--config-file <mypy --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
 <command-line>` but there are some differences in flag names and some


=====================================
mypy.egg-info/PKG-INFO
=====================================
@@ -1,6 +1,6 @@
 Metadata-Version: 2.2
 Name: mypy
-Version: 1.15.0+dev.68cffa7afe03d2b663aced9a70254e58704857db
+Version: 1.15.0+dev.23d862dd6fbb905a69bcb31e88746dc7a1eb4a43
 Summary: Optional static typing for Python
 Author-email: Jukka Lehtosalo <jukka.lehtosalo at iki.fi>
 License: MIT


=====================================
mypy.egg-info/SOURCES.txt
=====================================
@@ -200,6 +200,7 @@ mypy/test/__init__.py
 mypy/test/config.py
 mypy/test/data.py
 mypy/test/helpers.py
+mypy/test/test_config_parser.py
 mypy/test/test_find_sources.py
 mypy/test/test_ref_info.py
 mypy/test/testapi.py


=====================================
mypy/config_parser.py
=====================================
@@ -15,7 +15,7 @@ if sys.version_info >= (3, 11):
 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
 
@@ -217,6 +217,72 @@ toml_config_types.update(
 )
 
 
+def _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
+
+    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:
+            parser = configparser.RawConfigParser()
+            parser.read(config_file)
+            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 = _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 = _parse_individual_file(config_file, stderr)
+        if ret is None:
+            continue
+        return ret
+
+    return None
+
+
 def parse_config_file(
     options: Options,
     set_strict_flags: Callable[[], None],
@@ -233,47 +299,20 @@ def parse_config_file(
     stdout = stdout or sys.stdout
     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
-    else:
+    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
 
-    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"]


=====================================
mypy/defaults.py
=====================================
@@ -12,50 +12,15 @@ PYTHON3_VERSION: Final = (3, 9)
 # 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.


=====================================
mypy/main.py
=====================================
@@ -564,7 +564,7 @@ def process_options(
         "--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(


=====================================
mypy/semanal.py
=====================================
@@ -4022,7 +4022,6 @@ class SemanticAnalyzer(
             and not res.args
             and not empty_tuple_index
             and not pep_695
-            and not pep_613
         )
         if isinstance(res, ProperType) and isinstance(res, Instance):
             if not validate_instance(res, self.fail, empty_tuple_index):


=====================================
mypy/test/test_config_parser.py
=====================================
@@ -0,0 +1,130 @@
+from __future__ import annotations
+
+import contextlib
+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.defaults import CONFIG_NAMES, SHARED_CONFIG_NAMES
+
+
+ at 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 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()
+
+                    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
+
+    def test_precedence(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"
+
+            child = tmpdir / "child"
+            child.mkdir()
+
+            for cwd in [tmpdir, child]:
+                write_config(pyproject)
+                write_config(setup_cfg)
+                write_config(mypy_ini)
+                write_config(dot_mypy)
+
+                with chdir(cwd):
+                    result = _find_config_file()
+                    assert result is not None
+                    assert os.path.basename(result[2]) == "mypy.ini"
+
+                    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:
+            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 chdir(child):
+                result = _find_config_file()
+                assert result is not None
+                assert Path(result[2]).resolve() == parent_mypy.resolve()


=====================================
test-data/unit/check-type-aliases.test
=====================================
@@ -1243,31 +1243,22 @@ A = Union[int, List[A]]
 def func(x: A) -> int: ...
 [builtins fixtures/tuple.pyi]
 
-[case testAliasExplicitNoArgsBasic]
-from typing import Any, List, assert_type
+[case testAliasNonGeneric]
 from typing_extensions import TypeAlias
+class Foo: ...
 
-Implicit = List
-Explicit: TypeAlias = List
+ImplicitFoo = Foo
+ExplicitFoo: TypeAlias = Foo
 
-x1: Implicit[str]
-x2: Explicit[str]  # E: Bad number of arguments for type alias, expected 0, given 1
-assert_type(x1, List[str])
-assert_type(x2, List[Any])
-[builtins fixtures/tuple.pyi]
-
-[case testAliasExplicitNoArgsGenericClass]
-# flags: --python-version 3.9
-from typing import Any, assert_type
-from typing_extensions import TypeAlias
+x1: ImplicitFoo[str]  # E: "Foo" expects no type arguments, but 1 given
+x2: ExplicitFoo[str]  # E: "Foo" expects no type arguments, but 1 given
 
-Implicit = list
-Explicit: TypeAlias = list
+def is_foo(x: object):
+    if isinstance(x, ImplicitFoo):
+        pass
+    if isinstance(x, ExplicitFoo):
+        pass
 
-x1: Implicit[str]
-x2: Explicit[str]  # E: Bad number of arguments for type alias, expected 0, given 1
-assert_type(x1, list[str])
-assert_type(x2, list[Any])
 [builtins fixtures/tuple.pyi]
 
 [case testAliasExplicitNoArgsTuple]


=====================================
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


=====================================
test-data/unit/diff.test
=====================================
@@ -1563,6 +1563,7 @@ type H[T] = int
 __main__.A
 __main__.C
 __main__.D
+__main__.E
 __main__.G
 __main__.H
 



View it on GitLab: https://salsa.debian.org/python-team/packages/mypy/-/commit/4eba324ad5cd15ac8cced21e77d916846d0426a2

-- 
View it on GitLab: https://salsa.debian.org/python-team/packages/mypy/-/commit/4eba324ad5cd15ac8cced21e77d916846d0426a2
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/debian-med-commit/attachments/20250125/6aa3a12c/attachment-0001.htm>


More information about the debian-med-commit mailing list