From 96528b1038b8694e267cf8d186fa5a284df07e21 Mon Sep 17 00:00:00 2001 From: KevinRK29 Date: Fri, 30 Jan 2026 02:57:06 -0500 Subject: [PATCH 1/4] added known list of modules to use for fuzzy matching --- mypy/known_modules.py | 270 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 mypy/known_modules.py diff --git a/mypy/known_modules.py b/mypy/known_modules.py new file mode 100644 index 000000000000..79be48f33622 --- /dev/null +++ b/mypy/known_modules.py @@ -0,0 +1,270 @@ +"""Known Python module names for fuzzy matching import suggestions. + +This module provides a curated list of popular Python package import names +for suggesting corrections when a user mistypes an import statement. + +Sources: +- Python standard library (typeshed/stdlib/VERSIONS) +- Top 200 PyPI packages by downloads (https://github.com/hugovk/top-pypi-packages) + +Note: These are import names, not PyPI package names. +""" + +from __future__ import annotations + +from typing import Final + +from mypy.modulefinder import StdlibVersions + +POPULAR_THIRD_PARTY_MODULES: Final[frozenset[str]] = frozenset({ + # Cloud + "boto3", + "botocore", + "aiobotocore", + "s3transfer", + "s3fs", + "awscli", + + # HTTP / Networking + "urllib3", + "requests", + "certifi", + "idna", + "charset_normalizer", + "httpx", + "httpcore", + "aiohttp", + "yarl", + "multidict", + "requests_oauthlib", + "oauthlib", + "websocket", + "websockets", + "h11", + "sniffio", + "requests_toolbelt", + "httplib2", + + # Typing / Extensions + "typing_extensions", + "mypy_extensions", + "annotated_types", + "typing_inspection", + + # Core Utilities + "setuptools", + "packaging", + "pip", + "wheel", + "virtualenv", + "platformdirs", + "filelock", + "zipp", + "importlib_metadata", + "importlib_resources", + "distlib", + "distro", + "appdirs", + + # Data Science / Numerical + "numpy", + "pandas", + "scipy", + "sklearn", + "matplotlib", + "pyarrow", + "networkx", + "joblib", + "threadpoolctl", + "kiwisolver", + "fontTools", + "dill", + "cloudpickle", + + # Serialization / Config + "yaml", + "pydantic", + "pydantic_core", + "pydantic_settings", + "attrs", + "tomli", + "tomlkit", + "jsonschema", + "jsonschema_specifications", + "jsonpointer", + "jmespath", + "msgpack", + "isodate", + "ruamel", + + # Cryptography / Security + "cryptography", + "cffi", + "pycparser", + "rsa", + "pyjwt", + "jwt", + "pyasn1", + "pyasn1_modules", + "OpenSSL", + "nacl", + "bcrypt", + "asn1crypto", + "paramiko", + "secretstorage", + "msal", + "msal_extensions", + "keyring", + + # Date / Time + "dateutil", + "pytz", + "tzdata", + "tzlocal", + + # Google + "google", + "google_auth_oauthlib", + "google_auth_httplib2", + "google_crc32c", + "googleapiclient", + "grpc", + "grpc_status", + "grpc_tools", + "protobuf", + "proto", + "googleapis_common_protos", + + # Testing + "pytest", + "pluggy", + "iniconfig", + "coverage", + "exceptiongroup", + + # CLI / Terminal + "click", + "typer", + "colorama", + "rich", + "tqdm", + "tabulate", + "prompt_toolkit", + "shellingham", + "wcwidth", + + # Web Frameworks + "flask", + "werkzeug", + "itsdangerous", + "blinker", + "fastapi", + "starlette", + "uvicorn", + + # Templates / Markup + "jinja2", + "markupsafe", + "pygments", + "markdown_it", + "mdurl", + "docutils", + + # Async + "anyio", + "greenlet", + "aiosignal", + "aiohappyeyeballs", + "async_timeout", + + # Database + "sqlalchemy", + "alembic", + "redis", + "psycopg2", + + # Parsing / XML + "lxml", + "bs4", + "soupsieve", + "pyparsing", + "regex", + "et_xmlfile", + + # OpenTelemetry + "opentelemetry", + + # Azure + "azure", + + # Other Popular Modules + "six", + "fsspec", + "wrapt", + "propcache", + "rpds", + "pathspec", + "PIL", + "pillow", + "psutil", + "referencing", + "trove_classifiers", + "openpyxl", + "tenacity", + "more_itertools", + "sortedcontainers", + "decorator", + "ptyprocess", + "pexpect", + "hatchling", + "dotenv", + "python_dotenv", + "huggingface_hub", + "transformers", + "openai", + "langsmith", + "dns", + "dnspython", + "git", + "gitdb", + "smmap", + "deprecated", + "chardet", + "backoff", + "ruff", + "setuptools_scm", + "pyproject_hooks", + "jiter", + "yandexcloud", + "aliyunsdkcore", + "uritemplate", + "kubernetes", + "snowflake", + "multipart", +}) + + +def get_stdlib_modules( + stdlib_versions: StdlibVersions, + python_version: tuple[int, int] | None = None, +) -> frozenset[str]: + modules: set[str] = set() + for module, (min_ver, max_ver) in stdlib_versions.items(): + if python_version is not None: + if python_version < min_ver: + continue + if max_ver is not None and python_version > max_ver: + continue + top_level = module.split(".")[0] + modules.add(top_level) + return frozenset(modules) + + +def get_known_modules( + stdlib_versions: StdlibVersions | None = None, + python_version: tuple[int, int] | None = None, +) -> frozenset[str]: + modules: set[str] = set(POPULAR_THIRD_PARTY_MODULES) + if stdlib_versions is not None: + modules = modules.union(get_stdlib_modules(stdlib_versions, python_version)) + return frozenset(modules) From 2bf3bb4495d10ab4cde2b598dbec4ca7090c1540 Mon Sep 17 00:00:00 2001 From: KevinRK29 Date: Fri, 30 Jan 2026 02:58:59 -0500 Subject: [PATCH 2/4] added module not found note with suggestions --- mypy/build.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/mypy/build.py b/mypy/build.py index 7eee0f343c45..86cb149398ec 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -150,6 +150,8 @@ from mypy.renaming import LimitedVariableRenameVisitor, VariableRenameVisitor from mypy.stats import dump_type_stats from mypy.stubinfo import is_module_from_legacy_bundled_package, stub_distribution_name +from mypy.known_modules import get_known_modules +from mypy.messages import best_matches, pretty_seq from mypy.types import Type, instance_cache from mypy.typestate import reset_global_state, type_state from mypy.util import json_dumps, json_loads @@ -3189,6 +3191,23 @@ def module_not_found( code = codes.IMPORT errors.report(line, 0, msg.format(module=target), code=code) + if reason == ModuleNotFoundReason.NOT_FOUND: + top_level_target = target.split(".")[0] + known_modules = get_known_modules( + manager.find_module_cache.stdlib_py_versions, + manager.options.python_version, + ) + matches = best_matches(top_level_target, known_modules, n=3) + matches = [m for m in matches if m.lower() != top_level_target.lower()] + if matches: + errors.report( + line, + 0, + f'Did you mean {pretty_seq(matches, "or")}?', + severity="note", + code=code, + ) + dist = stub_distribution_name(target) for note in notes: if "{stub_dist}" in note: From dba232ad9a95322a8ff36cc14ae3a5950a37888a Mon Sep 17 00:00:00 2001 From: KevinRK29 Date: Fri, 30 Jan 2026 03:00:49 -0500 Subject: [PATCH 3/4] added test for missing module --- test-data/unit/semanal-errors.test | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index b69d35ce030e..b77305bf8306 100644 --- a/test-data/unit/semanal-errors.test +++ b/test-data/unit/semanal-errors.test @@ -296,6 +296,26 @@ main:2: error: Cannot find implementation or library stub for module named "m.n" main:2: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports main:3: error: Cannot find implementation or library stub for module named "a.b" +[case testMissingModuleFuzzyMatchThirdParty] +import numpyy +[out] +main:1: error: Cannot find implementation or library stub for module named "numpyy" +main:1: note: Did you mean "numpy"? +main:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports + +[case testMissingModuleFuzzyMatchStdlib] +import ittertools +[out] +main:1: error: Cannot find implementation or library stub for module named "ittertools" +main:1: note: Did you mean "itertools"? +main:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports + +[case testMissingModuleFuzzyMatchNoSuggestion] +import xyzabc123 +[out] +main:1: error: Cannot find implementation or library stub for module named "xyzabc123" +main:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports + [case testErrorInImportedModule] import m [file m.py] From 78b3ce44a53bcdd1af3421de41225f46d3efcb0d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 30 Jan 2026 08:06:03 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/build.py | 7 +- mypy/known_modules.py | 442 ++++++++++++++++++++---------------------- 2 files changed, 215 insertions(+), 234 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 86cb149398ec..ea43546767b6 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -132,6 +132,8 @@ from mypy.fixup import fixup_module from mypy.freetree import free_tree from mypy.fscache import FileSystemCache +from mypy.known_modules import get_known_modules +from mypy.messages import best_matches, pretty_seq from mypy.metastore import FilesystemMetadataStore, MetadataStore, SqliteMetadataStore from mypy.modulefinder import ( BuildSource as BuildSource, @@ -150,8 +152,6 @@ from mypy.renaming import LimitedVariableRenameVisitor, VariableRenameVisitor from mypy.stats import dump_type_stats from mypy.stubinfo import is_module_from_legacy_bundled_package, stub_distribution_name -from mypy.known_modules import get_known_modules -from mypy.messages import best_matches, pretty_seq from mypy.types import Type, instance_cache from mypy.typestate import reset_global_state, type_state from mypy.util import json_dumps, json_loads @@ -3194,8 +3194,7 @@ def module_not_found( if reason == ModuleNotFoundReason.NOT_FOUND: top_level_target = target.split(".")[0] known_modules = get_known_modules( - manager.find_module_cache.stdlib_py_versions, - manager.options.python_version, + manager.find_module_cache.stdlib_py_versions, manager.options.python_version ) matches = best_matches(top_level_target, known_modules, n=3) matches = [m for m in matches if m.lower() != top_level_target.lower()] diff --git a/mypy/known_modules.py b/mypy/known_modules.py index 79be48f33622..b2662ea72513 100644 --- a/mypy/known_modules.py +++ b/mypy/known_modules.py @@ -16,237 +16,220 @@ from mypy.modulefinder import StdlibVersions -POPULAR_THIRD_PARTY_MODULES: Final[frozenset[str]] = frozenset({ - # Cloud - "boto3", - "botocore", - "aiobotocore", - "s3transfer", - "s3fs", - "awscli", - - # HTTP / Networking - "urllib3", - "requests", - "certifi", - "idna", - "charset_normalizer", - "httpx", - "httpcore", - "aiohttp", - "yarl", - "multidict", - "requests_oauthlib", - "oauthlib", - "websocket", - "websockets", - "h11", - "sniffio", - "requests_toolbelt", - "httplib2", - - # Typing / Extensions - "typing_extensions", - "mypy_extensions", - "annotated_types", - "typing_inspection", - - # Core Utilities - "setuptools", - "packaging", - "pip", - "wheel", - "virtualenv", - "platformdirs", - "filelock", - "zipp", - "importlib_metadata", - "importlib_resources", - "distlib", - "distro", - "appdirs", - - # Data Science / Numerical - "numpy", - "pandas", - "scipy", - "sklearn", - "matplotlib", - "pyarrow", - "networkx", - "joblib", - "threadpoolctl", - "kiwisolver", - "fontTools", - "dill", - "cloudpickle", - - # Serialization / Config - "yaml", - "pydantic", - "pydantic_core", - "pydantic_settings", - "attrs", - "tomli", - "tomlkit", - "jsonschema", - "jsonschema_specifications", - "jsonpointer", - "jmespath", - "msgpack", - "isodate", - "ruamel", - - # Cryptography / Security - "cryptography", - "cffi", - "pycparser", - "rsa", - "pyjwt", - "jwt", - "pyasn1", - "pyasn1_modules", - "OpenSSL", - "nacl", - "bcrypt", - "asn1crypto", - "paramiko", - "secretstorage", - "msal", - "msal_extensions", - "keyring", - - # Date / Time - "dateutil", - "pytz", - "tzdata", - "tzlocal", - - # Google - "google", - "google_auth_oauthlib", - "google_auth_httplib2", - "google_crc32c", - "googleapiclient", - "grpc", - "grpc_status", - "grpc_tools", - "protobuf", - "proto", - "googleapis_common_protos", - - # Testing - "pytest", - "pluggy", - "iniconfig", - "coverage", - "exceptiongroup", - - # CLI / Terminal - "click", - "typer", - "colorama", - "rich", - "tqdm", - "tabulate", - "prompt_toolkit", - "shellingham", - "wcwidth", - - # Web Frameworks - "flask", - "werkzeug", - "itsdangerous", - "blinker", - "fastapi", - "starlette", - "uvicorn", - - # Templates / Markup - "jinja2", - "markupsafe", - "pygments", - "markdown_it", - "mdurl", - "docutils", - - # Async - "anyio", - "greenlet", - "aiosignal", - "aiohappyeyeballs", - "async_timeout", - - # Database - "sqlalchemy", - "alembic", - "redis", - "psycopg2", - - # Parsing / XML - "lxml", - "bs4", - "soupsieve", - "pyparsing", - "regex", - "et_xmlfile", - - # OpenTelemetry - "opentelemetry", - - # Azure - "azure", - - # Other Popular Modules - "six", - "fsspec", - "wrapt", - "propcache", - "rpds", - "pathspec", - "PIL", - "pillow", - "psutil", - "referencing", - "trove_classifiers", - "openpyxl", - "tenacity", - "more_itertools", - "sortedcontainers", - "decorator", - "ptyprocess", - "pexpect", - "hatchling", - "dotenv", - "python_dotenv", - "huggingface_hub", - "transformers", - "openai", - "langsmith", - "dns", - "dnspython", - "git", - "gitdb", - "smmap", - "deprecated", - "chardet", - "backoff", - "ruff", - "setuptools_scm", - "pyproject_hooks", - "jiter", - "yandexcloud", - "aliyunsdkcore", - "uritemplate", - "kubernetes", - "snowflake", - "multipart", -}) +POPULAR_THIRD_PARTY_MODULES: Final[frozenset[str]] = frozenset( + { + # Cloud + "boto3", + "botocore", + "aiobotocore", + "s3transfer", + "s3fs", + "awscli", + # HTTP / Networking + "urllib3", + "requests", + "certifi", + "idna", + "charset_normalizer", + "httpx", + "httpcore", + "aiohttp", + "yarl", + "multidict", + "requests_oauthlib", + "oauthlib", + "websocket", + "websockets", + "h11", + "sniffio", + "requests_toolbelt", + "httplib2", + # Typing / Extensions + "typing_extensions", + "mypy_extensions", + "annotated_types", + "typing_inspection", + # Core Utilities + "setuptools", + "packaging", + "pip", + "wheel", + "virtualenv", + "platformdirs", + "filelock", + "zipp", + "importlib_metadata", + "importlib_resources", + "distlib", + "distro", + "appdirs", + # Data Science / Numerical + "numpy", + "pandas", + "scipy", + "sklearn", + "matplotlib", + "pyarrow", + "networkx", + "joblib", + "threadpoolctl", + "kiwisolver", + "fontTools", + "dill", + "cloudpickle", + # Serialization / Config + "yaml", + "pydantic", + "pydantic_core", + "pydantic_settings", + "attrs", + "tomli", + "tomlkit", + "jsonschema", + "jsonschema_specifications", + "jsonpointer", + "jmespath", + "msgpack", + "isodate", + "ruamel", + # Cryptography / Security + "cryptography", + "cffi", + "pycparser", + "rsa", + "pyjwt", + "jwt", + "pyasn1", + "pyasn1_modules", + "OpenSSL", + "nacl", + "bcrypt", + "asn1crypto", + "paramiko", + "secretstorage", + "msal", + "msal_extensions", + "keyring", + # Date / Time + "dateutil", + "pytz", + "tzdata", + "tzlocal", + # Google + "google", + "google_auth_oauthlib", + "google_auth_httplib2", + "google_crc32c", + "googleapiclient", + "grpc", + "grpc_status", + "grpc_tools", + "protobuf", + "proto", + "googleapis_common_protos", + # Testing + "pytest", + "pluggy", + "iniconfig", + "coverage", + "exceptiongroup", + # CLI / Terminal + "click", + "typer", + "colorama", + "rich", + "tqdm", + "tabulate", + "prompt_toolkit", + "shellingham", + "wcwidth", + # Web Frameworks + "flask", + "werkzeug", + "itsdangerous", + "blinker", + "fastapi", + "starlette", + "uvicorn", + # Templates / Markup + "jinja2", + "markupsafe", + "pygments", + "markdown_it", + "mdurl", + "docutils", + # Async + "anyio", + "greenlet", + "aiosignal", + "aiohappyeyeballs", + "async_timeout", + # Database + "sqlalchemy", + "alembic", + "redis", + "psycopg2", + # Parsing / XML + "lxml", + "bs4", + "soupsieve", + "pyparsing", + "regex", + "et_xmlfile", + # OpenTelemetry + "opentelemetry", + # Azure + "azure", + # Other Popular Modules + "six", + "fsspec", + "wrapt", + "propcache", + "rpds", + "pathspec", + "PIL", + "pillow", + "psutil", + "referencing", + "trove_classifiers", + "openpyxl", + "tenacity", + "more_itertools", + "sortedcontainers", + "decorator", + "ptyprocess", + "pexpect", + "hatchling", + "dotenv", + "python_dotenv", + "huggingface_hub", + "transformers", + "openai", + "langsmith", + "dns", + "dnspython", + "git", + "gitdb", + "smmap", + "deprecated", + "chardet", + "backoff", + "ruff", + "setuptools_scm", + "pyproject_hooks", + "jiter", + "yandexcloud", + "aliyunsdkcore", + "uritemplate", + "kubernetes", + "snowflake", + "multipart", + } +) def get_stdlib_modules( - stdlib_versions: StdlibVersions, - python_version: tuple[int, int] | None = None, + stdlib_versions: StdlibVersions, python_version: tuple[int, int] | None = None ) -> frozenset[str]: modules: set[str] = set() for module, (min_ver, max_ver) in stdlib_versions.items(): @@ -261,8 +244,7 @@ def get_stdlib_modules( def get_known_modules( - stdlib_versions: StdlibVersions | None = None, - python_version: tuple[int, int] | None = None, + stdlib_versions: StdlibVersions | None = None, python_version: tuple[int, int] | None = None ) -> frozenset[str]: modules: set[str] = set(POPULAR_THIRD_PARTY_MODULES) if stdlib_versions is not None: