From 312bad8ed52fab5432ca45c9735916a873999ab6 Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Thu, 15 May 2025 23:51:20 -0700 Subject: [PATCH 01/12] improve documentation about --install-types --- docs/source/command_line.rst | 11 +++++++++++ docs/source/running_mypy.rst | 15 +++++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/docs/source/command_line.rst b/docs/source/command_line.rst index b455e287017e..c4923973adf0 100644 --- a/docs/source/command_line.rst +++ b/docs/source/command_line.rst @@ -1172,6 +1172,13 @@ Miscellaneous stubs at the end of the run, but only if any missing modules were detected. + It is not recommended to use this option in `CI/CD + `_, as it will make your + dependencies less reproducible. Instead, you should require and + install type dependencies like you would any other (test) dependency, + eg by using an dependency section in your `pyproject.toml + `_. + .. note:: This is new in mypy 0.900. Previous mypy versions included a @@ -1192,6 +1199,10 @@ Miscellaneous stub packages were found, they are installed and then another run is performed. + It is not recommended to use ``--install-types --non-interactive`` + in `CI/CD `_; see the other + flag for more details. + .. option:: --junit-xml JUNIT_XML Causes mypy to generate a JUnit XML test result document with diff --git a/docs/source/running_mypy.rst b/docs/source/running_mypy.rst index 9f7461d24f72..813df0a058db 100644 --- a/docs/source/running_mypy.rst +++ b/docs/source/running_mypy.rst @@ -382,26 +382,29 @@ the library, you will get a message like this: main.py:1: note: (or run "mypy --install-types" to install all missing stub packages) You can resolve the issue by running the suggested pip commands. + If you're running mypy in CI, you can ensure the presence of any stub packages you need the same as you would any other test dependency, e.g. by adding them to -the appropriate ``requirements.txt`` file. +the appropriate ``requirements.txt`` file or dependency section of ``pyproject.toml``. -Alternatively, add the :option:`--install-types ` -to your mypy command to install all known missing stubs: +The :option:`--install-types ` flag +makes mypy list and (after a prompt) install all known missing stubs: .. code-block:: text mypy --install-types This is slower than explicitly installing stubs, since it effectively -runs mypy twice -- the first time to find the missing stubs, and +runs mypy twice — the first time to find the missing stubs, and the second time to type check your code properly after mypy has installed the stubs. It also can make controlling stub versions harder, -resulting in less reproducible type checking. +resulting in less reproducible type checking — it might even install +incompatible versions of your project's non-type dependencies, if the +type stubs require them! By default, :option:`--install-types ` shows a confirmation prompt. Use :option:`--non-interactive ` to install all suggested -stub packages without asking for confirmation *and* type check your code: +stub packages without asking for confirmation *and* then type check your code. If you've already installed the relevant third-party libraries in an environment other than the one mypy is running in, you can use :option:`--python-executable From 050632b0abb22d1d8853b07ab250730f83c79af4 Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Sat, 17 May 2025 02:03:50 -0700 Subject: [PATCH 02/12] add import-untyped-stubs-available, and docu (but not implementation) --- docs/source/error_code_list.rst | 24 ++++++++++++++++++++++-- mypy/errorcodes.py | 3 +++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/docs/source/error_code_list.rst b/docs/source/error_code_list.rst index 49cb8a0c06c1..e47e985f6545 100644 --- a/docs/source/error_code_list.rst +++ b/docs/source/error_code_list.rst @@ -640,7 +640,8 @@ Check for an issue with imports [import] ---------------------------------------- Mypy generates an error if it can't resolve an `import` statement. -This is a parent error code of `import-not-found` and `import-untyped` +This is a parent error code of `import-not-found`, `import-untyped`, +and `import-untyped-stubs-available` See :ref:`ignore-missing-imports` for how to work around these errors. @@ -673,7 +674,7 @@ Example: .. code-block:: python - # Error: Library stubs not installed for "bs4" [import-untyped] + # Error: Library stubs not installed for "bs4" [import-untyped-stubs-available] import bs4 # Error: Skipping analyzing "no_py_typed": module is installed, but missing library stubs or py.typed marker [import-untyped] import no_py_typed @@ -681,6 +682,25 @@ Example: In some cases, these errors can be fixed by installing an appropriate stub package. See :ref:`ignore-missing-imports` for more details. +Check that import target with known stubs can be found [import-untyped-stubs-available] +-------------------------------------------------------- + +Like :ref:`code-import-untyped`, but used when mypy knows there is an appropriate +type stub package corresponding to the library, which you could install. + +Example: + +.. code-block:: python + + # Error: Library stubs not installed for "bs4" [import-untyped-stubs-available] + import bs4 + # Error: Skipping analyzing "no_py_typed": module is installed, but missing library stubs or py.typed marker [import-untyped] + import no_py_typed + +These errors can be fixed by installing the appropriate +stub package. See :ref:`ignore-missing-imports` for more details. + + .. _code-no-redef: Check that each name is defined once [no-redef] diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py index 8f650aa30605..0368734443d5 100644 --- a/mypy/errorcodes.py +++ b/mypy/errorcodes.py @@ -113,6 +113,9 @@ def __hash__(self) -> int: IMPORT_UNTYPED: Final = ErrorCode( "import-untyped", "Require that imported module has stubs", "General", sub_code_of=IMPORT ) +IMPORT_UNTYPED_STUBS_AVAILABLE: Final = ErrorCode( + "import-untyped-stubs-available", "Require that imported module (with known stubs) has stubs", "General", sub_code_of=IMPORT +) NO_REDEF: Final = ErrorCode("no-redef", "Check that each name is defined once", "General") FUNC_RETURNS_VALUE: Final = ErrorCode( "func-returns-value", "Check that called function returns a value in value context", "General" From 5e92478e861ab318f6e3a19c975482c9d5f4a299 Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Sat, 17 May 2025 02:23:05 -0700 Subject: [PATCH 03/12] implement import-untyped-stubs-available --- docs/source/running_mypy.rst | 2 +- mypy/build.py | 7 +++---- mypy/errorcodes.py | 8 +++++++- mypy/errors.py | 6 +++--- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/docs/source/running_mypy.rst b/docs/source/running_mypy.rst index 813df0a058db..2cc2983f309e 100644 --- a/docs/source/running_mypy.rst +++ b/docs/source/running_mypy.rst @@ -400,7 +400,7 @@ the second time to type check your code properly after mypy has installed the stubs. It also can make controlling stub versions harder, resulting in less reproducible type checking — it might even install incompatible versions of your project's non-type dependencies, if the -type stubs require them! +type stubs require them! By default, :option:`--install-types ` shows a confirmation prompt. Use :option:`--non-interactive ` to install all suggested diff --git a/mypy/build.py b/mypy/build.py index 355ba861385e..874c06f87147 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -2780,11 +2780,10 @@ def module_not_found( msg, notes = reason.error_message_templates(daemon) if reason == ModuleNotFoundReason.NOT_FOUND: code = codes.IMPORT_NOT_FOUND - elif ( - reason == ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS - or reason == ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED - ): + elif reason == ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS: code = codes.IMPORT_UNTYPED + elif reason == ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED: + code = codes.IMPORT_UNTYPED_STUBS_AVAILABLE else: code = codes.IMPORT errors.report(line, 0, msg.format(module=target), code=code) diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py index 0368734443d5..0d8c4eb72b94 100644 --- a/mypy/errorcodes.py +++ b/mypy/errorcodes.py @@ -34,6 +34,9 @@ def __init__( sub_code_map[sub_code_of.code].add(code) error_codes[code] = self + def is_import_related_code(self) -> bool: + return IMPORT in (self.code, self.sub_code_of) + def __str__(self) -> str: return f"" @@ -114,7 +117,10 @@ def __hash__(self) -> int: "import-untyped", "Require that imported module has stubs", "General", sub_code_of=IMPORT ) IMPORT_UNTYPED_STUBS_AVAILABLE: Final = ErrorCode( - "import-untyped-stubs-available", "Require that imported module (with known stubs) has stubs", "General", sub_code_of=IMPORT + "import-untyped-stubs-available", + "Require that imported module (with known stubs) has stubs", + "General", + sub_code_of=IMPORT, ) NO_REDEF: Final = ErrorCode("no-redef", "Check that each name is defined once", "General") FUNC_RETURNS_VALUE: Final = ErrorCode( diff --git a/mypy/errors.py b/mypy/errors.py index c9510ae5f1eb..4b69ddee6d1a 100644 --- a/mypy/errors.py +++ b/mypy/errors.py @@ -10,7 +10,7 @@ from mypy import errorcodes as codes from mypy.error_formatter import ErrorFormatter -from mypy.errorcodes import IMPORT, IMPORT_NOT_FOUND, IMPORT_UNTYPED, ErrorCode, mypy_error_codes +from mypy.errorcodes import ErrorCode, mypy_error_codes from mypy.options import Options from mypy.scope import Scope from mypy.util import DEFAULT_SOURCE_OFFSET, is_typeshed_file @@ -476,7 +476,7 @@ def _add_error_info(self, file: str, info: ErrorInfo) -> None: self.error_info_map[file].append(info) if info.blocker: self.has_blockers.add(file) - if info.code in (IMPORT, IMPORT_UNTYPED, IMPORT_NOT_FOUND): + if info.code is not None and info.code.is_import_related_code(): self.seen_import_error = True def _filter_error(self, file: str, info: ErrorInfo) -> bool: @@ -522,7 +522,7 @@ def add_error_info(self, info: ErrorInfo) -> None: self.only_once_messages.add(info.message) if ( self.seen_import_error - and info.code not in (IMPORT, IMPORT_UNTYPED, IMPORT_NOT_FOUND) + and (info.code is None or (not info.code.is_import_related_code())) and self.has_many_errors() ): # Missing stubs can easily cause thousands of errors about From 8756f9fe68bb02cb7957593267179abf82bfb4ad Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Sat, 17 May 2025 02:55:46 -0700 Subject: [PATCH 04/12] update test-requirements and remove unused ignores for lxml-stubs per the information gleaned from the new flags --- build-requirements.txt | 2 ++ mypy/report.py | 2 +- mypy/test/testcheck.py | 2 +- mypy/test/testcmdline.py | 2 +- mypy/test/testreports.py | 4 ++-- test-requirements.in | 1 + test-requirements.txt | 31 +++++++++++++++++++++++++++++-- 7 files changed, 37 insertions(+), 7 deletions(-) diff --git a/build-requirements.txt b/build-requirements.txt index aac1b95eddf7..94cd794a33e7 100644 --- a/build-requirements.txt +++ b/build-requirements.txt @@ -2,3 +2,5 @@ -r mypy-requirements.txt types-psutil types-setuptools +pip-tools # Not strictly needed for building per se, but pip-compile from this package is needed if you want to update the requirement files. (Word on the street is you can also use uv pip compile instead.) +pip<24.3 # Needed to update the requirement files correctly ay ay ay https://github.com/jazzband/pip-tools/issues/2131 diff --git a/mypy/report.py b/mypy/report.py index 39cd80ed38bf..f90ca3333732 100644 --- a/mypy/report.py +++ b/mypy/report.py @@ -26,7 +26,7 @@ from mypy.version import __version__ try: - from lxml import etree # type: ignore[import-untyped] + from lxml import etree LXML_INSTALLED = True except ImportError: diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index e6415ddff906..3719ab5998a5 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -25,7 +25,7 @@ from mypy.test.update_data import update_testcase_output try: - import lxml # type: ignore[import-untyped] + import lxml except ImportError: lxml = None diff --git a/mypy/test/testcmdline.py b/mypy/test/testcmdline.py index 9bc02d319964..d6c442020716 100644 --- a/mypy/test/testcmdline.py +++ b/mypy/test/testcmdline.py @@ -20,7 +20,7 @@ ) try: - import lxml # type: ignore[import-untyped] + import lxml except ImportError: lxml = None diff --git a/mypy/test/testreports.py b/mypy/test/testreports.py index f638756ad819..93a0f32b9982 100644 --- a/mypy/test/testreports.py +++ b/mypy/test/testreports.py @@ -8,7 +8,7 @@ from mypy.test.helpers import Suite, assert_equal try: - import lxml # type: ignore[import-untyped] + import lxml except ImportError: lxml = None @@ -23,7 +23,7 @@ def test_get_line_rate(self) -> None: @pytest.mark.skipif(lxml is None, reason="Cannot import lxml. Is it installed?") def test_as_xml(self) -> None: - import lxml.etree as etree # type: ignore[import-untyped] + import lxml.etree as etree cobertura_package = CoberturaPackage("foobar") cobertura_package.covered_lines = 21 diff --git a/test-requirements.in b/test-requirements.in index 666dd9fc082c..0c2007828c8e 100644 --- a/test-requirements.in +++ b/test-requirements.in @@ -6,6 +6,7 @@ attrs>=18.0 filelock>=3.3.0 lxml>=5.3.0; python_version<'3.14' +lxml-stubs psutil>=4.0 pytest>=8.1.0 pytest-xdist>=1.34.0 diff --git a/test-requirements.txt b/test-requirements.txt index 51281f0e4c11..efdc9758eb77 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -6,8 +6,17 @@ # attrs==25.1.0 # via -r test-requirements.in +build==1.2.2.post1 + # via pip-tools cfgv==3.4.0 # via pre-commit +click==8.2.0 + # via pip-tools +colorama==0.4.6 + # via + # build + # click + # pytest coverage==7.6.10 # via pytest-cov distlib==0.3.9 @@ -24,14 +33,20 @@ iniconfig==2.0.0 # via pytest lxml==5.3.0 ; python_version < "3.14" # via -r test-requirements.in +lxml-stubs==0.5.1 + # via -r test-requirements.in mypy-extensions==1.0.0 # via -r mypy-requirements.txt nodeenv==1.9.1 # via pre-commit packaging==24.2 - # via pytest + # via + # build + # pytest pathspec==0.12.1 # via -r mypy-requirements.txt +pip-tools==7.4.1 + # via -r build-requirements.txt platformdirs==4.3.6 # via virtualenv pluggy==1.5.0 @@ -40,6 +55,10 @@ pre-commit==4.1.0 # via -r test-requirements.in psutil==6.1.1 # via -r test-requirements.in +pyproject-hooks==1.2.0 + # via + # build + # pip-tools pytest==8.3.4 # via # -r test-requirements.in @@ -61,7 +80,15 @@ typing-extensions==4.12.2 # via -r mypy-requirements.txt virtualenv==20.29.1 # via pre-commit +wheel==0.45.1 + # via pip-tools # The following packages are considered to be unsafe in a requirements file: +pip==24.2 + # via + # -r build-requirements.txt + # pip-tools setuptools==75.8.0 - # via -r test-requirements.in + # via + # -r test-requirements.in + # pip-tools From 9d452c050acb9c6da19f96456d898f9ac1a0994f Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Sat, 17 May 2025 03:12:16 -0700 Subject: [PATCH 05/12] adapt the way lxml is imported to adapt to it not being any more --- mypy/test/testcheck.py | 4 ++-- mypy/test/testcmdline.py | 4 ++-- mypy/test/testreports.py | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 3719ab5998a5..9a0da2bd7e3f 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -27,7 +27,7 @@ try: import lxml except ImportError: - lxml = None + lxml_import_failure = True import pytest @@ -55,7 +55,7 @@ class TypeCheckSuite(DataSuite): files = typecheck_files def run_case(self, testcase: DataDrivenTestCase) -> None: - if lxml is None and os.path.basename(testcase.file) == "check-reports.test": + if lxml_import_failure and os.path.basename(testcase.file) == "check-reports.test": pytest.skip("Cannot import lxml. Is it installed?") incremental = ( "incremental" in testcase.name.lower() diff --git a/mypy/test/testcmdline.py b/mypy/test/testcmdline.py index d6c442020716..069666c58af4 100644 --- a/mypy/test/testcmdline.py +++ b/mypy/test/testcmdline.py @@ -22,7 +22,7 @@ try: import lxml except ImportError: - lxml = None + lxml_import_failure = True import pytest @@ -38,7 +38,7 @@ class PythonCmdlineSuite(DataSuite): native_sep = True def run_case(self, testcase: DataDrivenTestCase) -> None: - if lxml is None and os.path.basename(testcase.file) == "reports.test": + if lxml_import_failure and os.path.basename(testcase.file) == "reports.test": pytest.skip("Cannot import lxml. Is it installed?") for step in [1] + sorted(testcase.output2): test_python_cmdline(testcase, step) diff --git a/mypy/test/testreports.py b/mypy/test/testreports.py index 93a0f32b9982..11a66687d7b2 100644 --- a/mypy/test/testreports.py +++ b/mypy/test/testreports.py @@ -10,18 +10,18 @@ try: import lxml except ImportError: - lxml = None + lxml_import_failure = True import pytest class CoberturaReportSuite(Suite): - @pytest.mark.skipif(lxml is None, reason="Cannot import lxml. Is it installed?") + @pytest.mark.skipif(lxml_import_failure, reason="Cannot import lxml. Is it installed?") def test_get_line_rate(self) -> None: assert_equal("1.0", get_line_rate(0, 0)) assert_equal("0.3333", get_line_rate(1, 3)) - @pytest.mark.skipif(lxml is None, reason="Cannot import lxml. Is it installed?") + @pytest.mark.skipif(lxml_import_failure, reason="Cannot import lxml. Is it installed?") def test_as_xml(self) -> None: import lxml.etree as etree From 1e2da7835129cb3d2db0ece48ce3bfafa39bc9b4 Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Sat, 17 May 2025 03:23:42 -0700 Subject: [PATCH 06/12] deal with 'unused import' ruff errors although it might be smarter to use importlib.util.find_spec, as ruff suggests --- mypy/test/testcheck.py | 2 +- mypy/test/testcmdline.py | 2 +- mypy/test/testreports.py | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 9a0da2bd7e3f..90d3a00c8595 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -25,7 +25,7 @@ from mypy.test.update_data import update_testcase_output try: - import lxml + pass # import lxml #try just passing for now... except ImportError: lxml_import_failure = True diff --git a/mypy/test/testcmdline.py b/mypy/test/testcmdline.py index 069666c58af4..3697ee698a3c 100644 --- a/mypy/test/testcmdline.py +++ b/mypy/test/testcmdline.py @@ -20,7 +20,7 @@ ) try: - import lxml + pass # import lxml #try just passing for now... except ImportError: lxml_import_failure = True diff --git a/mypy/test/testreports.py b/mypy/test/testreports.py index 11a66687d7b2..98a63b8004e0 100644 --- a/mypy/test/testreports.py +++ b/mypy/test/testreports.py @@ -8,7 +8,7 @@ from mypy.test.helpers import Suite, assert_equal try: - import lxml + import lxml.etree as etree except ImportError: lxml_import_failure = True @@ -23,8 +23,6 @@ def test_get_line_rate(self) -> None: @pytest.mark.skipif(lxml_import_failure, reason="Cannot import lxml. Is it installed?") def test_as_xml(self) -> None: - import lxml.etree as etree - cobertura_package = CoberturaPackage("foobar") cobertura_package.covered_lines = 21 cobertura_package.total_lines = 42 From 68ec62bb01d07cd25732e09cf85582da390ffbff Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Sat, 17 May 2025 03:37:18 -0700 Subject: [PATCH 07/12] do this checking for lxml the right way --- mypy/test/testcheck.py | 13 ++++--------- mypy/test/testcmdline.py | 12 ++++-------- mypy/test/testreports.py | 16 +++++++--------- 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 90d3a00c8595..500de8ce1baa 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -5,6 +5,9 @@ import os import re import sys +from importlib.util import find_spec as import_exists + +import pytest from mypy import build from mypy.build import Graph @@ -24,14 +27,6 @@ ) from mypy.test.update_data import update_testcase_output -try: - pass # import lxml #try just passing for now... -except ImportError: - lxml_import_failure = True - - -import pytest - # List of files that contain test case descriptions. # Includes all check-* files with the .test extension in the test-data/unit directory typecheck_files = find_test_files(pattern="check-*.test") @@ -55,7 +50,7 @@ class TypeCheckSuite(DataSuite): files = typecheck_files def run_case(self, testcase: DataDrivenTestCase) -> None: - if lxml_import_failure and os.path.basename(testcase.file) == "check-reports.test": + if import_exists("lmxl") and os.path.basename(testcase.file) == "check-reports.test": pytest.skip("Cannot import lxml. Is it installed?") incremental = ( "incremental" in testcase.name.lower() diff --git a/mypy/test/testcmdline.py b/mypy/test/testcmdline.py index 3697ee698a3c..eea6ed4519fc 100644 --- a/mypy/test/testcmdline.py +++ b/mypy/test/testcmdline.py @@ -10,6 +10,9 @@ import re import subprocess import sys +from importlib.util import find_spec as import_exists + +import pytest from mypy.test.config import PREFIX, test_temp_dir from mypy.test.data import DataDrivenTestCase, DataSuite @@ -19,13 +22,6 @@ normalize_error_messages, ) -try: - pass # import lxml #try just passing for now... -except ImportError: - lxml_import_failure = True - -import pytest - # Path to Python 3 interpreter python3_path = sys.executable @@ -38,7 +34,7 @@ class PythonCmdlineSuite(DataSuite): native_sep = True def run_case(self, testcase: DataDrivenTestCase) -> None: - if lxml_import_failure and os.path.basename(testcase.file) == "reports.test": + if not import_exists("lxml") and os.path.basename(testcase.file) == "reports.test": pytest.skip("Cannot import lxml. Is it installed?") for step in [1] + sorted(testcase.output2): test_python_cmdline(testcase, step) diff --git a/mypy/test/testreports.py b/mypy/test/testreports.py index 98a63b8004e0..7ea8cb1cc7fc 100644 --- a/mypy/test/testreports.py +++ b/mypy/test/testreports.py @@ -3,26 +3,24 @@ from __future__ import annotations import textwrap +from importlib.util import find_spec as import_exists + +import pytest from mypy.report import CoberturaPackage, get_line_rate from mypy.test.helpers import Suite, assert_equal -try: - import lxml.etree as etree -except ImportError: - lxml_import_failure = True - -import pytest - class CoberturaReportSuite(Suite): - @pytest.mark.skipif(lxml_import_failure, reason="Cannot import lxml. Is it installed?") + @pytest.mark.skipif(not import_exists("lxml"), reason="Cannot import lxml. Is it installed?") def test_get_line_rate(self) -> None: assert_equal("1.0", get_line_rate(0, 0)) assert_equal("0.3333", get_line_rate(1, 3)) - @pytest.mark.skipif(lxml_import_failure, reason="Cannot import lxml. Is it installed?") + @pytest.mark.skipif(not import_exists("lxml"), reason="Cannot import lxml. Is it installed?") def test_as_xml(self) -> None: + import lxml.etree as etree + cobertura_package = CoberturaPackage("foobar") cobertura_package.covered_lines = 21 cobertura_package.total_lines = 42 From fc76c900bf012f1fdd0d8ee329952d5f502d4c45 Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Sat, 17 May 2025 04:16:08 -0700 Subject: [PATCH 08/12] fix test --- test-data/unit/check-errorcodes.test | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index 21112b7d85a2..af302a1755a3 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -535,7 +535,10 @@ if int() is str(): # E: Non-overlapping identity check (left operand type: "int [builtins fixtures/primitives.pyi] [case testErrorCodeMissingModule] -from defusedxml import xyz # E: Library stubs not installed for "defusedxml" [import-untyped] \ +# Note: it was too difficult for me to figure out how to test [import-untyped] here, +# (ideally, it would!) +# but testNamespacePkgWStubs does test that, anyway. +from defusedxml import xyz # E: Library stubs not installed for "defusedxml" [import-untyped-stubs-available] \ # N: Hint: "python3 -m pip install types-defusedxml" \ # N: (or run "mypy --install-types" to install all missing stub packages) from nonexistent import foobar # E: Cannot find implementation or library stub for module named "nonexistent" [import-not-found] From 34b20528552eef16d90e7950480c18d00fe698f1 Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Sat, 17 May 2025 04:25:30 -0700 Subject: [PATCH 09/12] add an explicit ref/anchor/whatever for import-untyped-stubs-available --- docs/source/error_code_list.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/error_code_list.rst b/docs/source/error_code_list.rst index e47e985f6545..136f7fa2a526 100644 --- a/docs/source/error_code_list.rst +++ b/docs/source/error_code_list.rst @@ -682,6 +682,8 @@ Example: In some cases, these errors can be fixed by installing an appropriate stub package. See :ref:`ignore-missing-imports` for more details. +.. _code-import-untyped-stubs-available: + Check that import target with known stubs can be found [import-untyped-stubs-available] -------------------------------------------------------- From fe76f96d73a64f7f07ad3e40b71321b19409c54a Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Sat, 17 May 2025 04:35:48 -0700 Subject: [PATCH 10/12] move the meta requirements into a new 'extra', 'dev' --- build-requirements.txt | 2 -- pyproject.toml | 4 ++++ test-requirements.txt | 29 +++-------------------------- 3 files changed, 7 insertions(+), 28 deletions(-) diff --git a/build-requirements.txt b/build-requirements.txt index 94cd794a33e7..aac1b95eddf7 100644 --- a/build-requirements.txt +++ b/build-requirements.txt @@ -2,5 +2,3 @@ -r mypy-requirements.txt types-psutil types-setuptools -pip-tools # Not strictly needed for building per se, but pip-compile from this package is needed if you want to update the requirement files. (Word on the street is you can also use uv pip compile instead.) -pip<24.3 # Needed to update the requirement files correctly ay ay ay https://github.com/jazzband/pip-tools/issues/2131 diff --git a/pyproject.toml b/pyproject.toml index 8a1177f60009..2dc5be78d71f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,6 +62,10 @@ python2 = [] reports = ["lxml"] install-types = ["pip"] faster-cache = ["orjson"] +dev = [ + "pip-tools", # Not strictly needed for building per se, but pip-compile from this package is needed if you want to update the requirement files. (Word on the street is you can also use uv pip compile instead.) + "pip<24.3" # Needed to update the requirement files correctly with pip-tools ay ay ay https://github.com/jazzband/pip-tools/issues/2131 +] [project.urls] Homepage = "https://www.mypy-lang.org/" diff --git a/test-requirements.txt b/test-requirements.txt index efdc9758eb77..6dae2b676c83 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -6,17 +6,10 @@ # attrs==25.1.0 # via -r test-requirements.in -build==1.2.2.post1 - # via pip-tools cfgv==3.4.0 # via pre-commit -click==8.2.0 - # via pip-tools colorama==0.4.6 - # via - # build - # click - # pytest + # via pytest coverage==7.6.10 # via pytest-cov distlib==0.3.9 @@ -40,13 +33,9 @@ mypy-extensions==1.0.0 nodeenv==1.9.1 # via pre-commit packaging==24.2 - # via - # build - # pytest + # via pytest pathspec==0.12.1 # via -r mypy-requirements.txt -pip-tools==7.4.1 - # via -r build-requirements.txt platformdirs==4.3.6 # via virtualenv pluggy==1.5.0 @@ -55,10 +44,6 @@ pre-commit==4.1.0 # via -r test-requirements.in psutil==6.1.1 # via -r test-requirements.in -pyproject-hooks==1.2.0 - # via - # build - # pip-tools pytest==8.3.4 # via # -r test-requirements.in @@ -80,15 +65,7 @@ typing-extensions==4.12.2 # via -r mypy-requirements.txt virtualenv==20.29.1 # via pre-commit -wheel==0.45.1 - # via pip-tools # The following packages are considered to be unsafe in a requirements file: -pip==24.2 - # via - # -r build-requirements.txt - # pip-tools setuptools==75.8.0 - # via - # -r test-requirements.in - # pip-tools + # via -r test-requirements.in From 2d1e4910d3115481caa3c97f75120576552949fa Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Sat, 17 May 2025 04:40:19 -0700 Subject: [PATCH 11/12] adjust title underline to pass sphinx docs generation without warnings lmao --- docs/source/error_code_list.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/error_code_list.rst b/docs/source/error_code_list.rst index 136f7fa2a526..b4c8442f855b 100644 --- a/docs/source/error_code_list.rst +++ b/docs/source/error_code_list.rst @@ -665,7 +665,7 @@ See :ref:`ignore-missing-imports` for how to work around these errors. .. _code-import-untyped: Check that import target can be found [import-untyped] --------------------------------------------------------- +------------------------------------------------------ Mypy generates an error if it can find the source code for an imported module, but that module does not provide type annotations (via :ref:`PEP 561 `). @@ -685,7 +685,7 @@ stub package. See :ref:`ignore-missing-imports` for more details. .. _code-import-untyped-stubs-available: Check that import target with known stubs can be found [import-untyped-stubs-available] --------------------------------------------------------- +--------------------------------------------------------------------------------------- Like :ref:`code-import-untyped`, but used when mypy knows there is an appropriate type stub package corresponding to the library, which you could install. From e9797429ed6ce79f0fb3ae591db5e77511cc75b4 Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Sat, 17 May 2025 05:00:57 -0700 Subject: [PATCH 12/12] cleanup --- docs/source/command_line.rst | 2 +- mypy/test/testcheck.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/command_line.rst b/docs/source/command_line.rst index c4923973adf0..9d84531984b9 100644 --- a/docs/source/command_line.rst +++ b/docs/source/command_line.rst @@ -1176,7 +1176,7 @@ Miscellaneous `_, as it will make your dependencies less reproducible. Instead, you should require and install type dependencies like you would any other (test) dependency, - eg by using an dependency section in your `pyproject.toml + such as by using a dependency section in your `pyproject.toml `_. .. note:: diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 500de8ce1baa..27d9311a1255 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -50,7 +50,7 @@ class TypeCheckSuite(DataSuite): files = typecheck_files def run_case(self, testcase: DataDrivenTestCase) -> None: - if import_exists("lmxl") and os.path.basename(testcase.file) == "check-reports.test": + if not import_exists("lxml") and os.path.basename(testcase.file) == "check-reports.test": pytest.skip("Cannot import lxml. Is it installed?") incremental = ( "incremental" in testcase.name.lower()