Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions mypy/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -3189,6 +3191,22 @@ 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:
Expand Down
252 changes: 252 additions & 0 deletions mypy/known_modules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
"""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)
20 changes: 20 additions & 0 deletions test-data/unit/semanal-errors.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down