[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