From c27baacb3b8910289c0f0546ba594741c3dba285 Mon Sep 17 00:00:00 2001 From: Ajithkumar003-dev Date: Mon, 6 Jul 2026 19:55:29 +0530 Subject: [PATCH] fix(stubgen): add --include-vendor flag to generate stubs for vendor dirs stubgen silently skipped directories named vendor, vendored, _vendor, and _vendored_packages because they appeared in BLACKLIST -- a constant originally intended for the type-checker, not the stub generator. This caused stubs to be silently missing for any package whose subdirectory happened to be named 'vendor', with no warning to the user. Changes: - Extract VENDOR_BLACKLIST constant with only the vendor-related entries - Add include_vendor: bool = False field to the Options class - Update remove_blacklisted_modules() to subtract VENDOR_BLACKLIST when include_vendor=True, leaving the rest of BLACKLIST intact - Update is_blacklisted_path() to accept an explicit blacklist argument - Wire include_vendor through collect_build_targets() - Add --include-vendor CLI flag to parse_options() Default behaviour is unchanged (vendor dirs are still skipped). Pass --include-vendor to opt in. Fixes python/mypy#9599 --- mypy/stubgen.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/mypy/stubgen.py b/mypy/stubgen.py index fe90c3d256b7a..50e8720072430 100755 --- a/mypy/stubgen.py +++ b/mypy/stubgen.py @@ -177,6 +177,10 @@ "/_vendored_packages/", ] +# The subset of BLACKLIST entries that filter out vendor directories. +# Separated so they can be selectively re-enabled via --include-vendor. +VENDOR_BLACKLIST: Final = ["/vendored/", "/vendor/", "/_vendor/", "/_vendored_packages/"] + # These methods are expected to always return a non-trivial value. METHODS_WITH_RETURN_VALUE: Final = { "__ne__", @@ -215,6 +219,7 @@ def __init__( quiet: bool, export_less: bool, include_docstrings: bool, + include_vendor: bool = False, ) -> None: # See parse_options for descriptions of the flags. self.pyversion = pyversion @@ -235,6 +240,7 @@ def __init__( self.quiet = quiet self.export_less = export_less self.include_docstrings = include_docstrings + self.include_vendor = include_vendor class StubSource: @@ -1556,9 +1562,16 @@ def get_qualified_name(o: Expression) -> str: return ERROR_MARKER -def remove_blacklisted_modules(modules: list[StubSource]) -> list[StubSource]: +def remove_blacklisted_modules( + modules: list[StubSource], include_vendor: bool = False +) -> list[StubSource]: + active_blacklist = ( + [e for e in BLACKLIST if e not in VENDOR_BLACKLIST] if include_vendor else BLACKLIST + ) return [ - module for module in modules if module.path is None or not is_blacklisted_path(module.path) + module + for module in modules + if module.path is None or not is_blacklisted_path(module.path, active_blacklist) ] @@ -1573,8 +1586,8 @@ def split_pyc_from_py(modules: list[StubSource]) -> tuple[list[StubSource], list return pyc_modules, py_modules -def is_blacklisted_path(path: str) -> bool: - return any(substr in (normalize_path_separators(path) + "\n") for substr in BLACKLIST) +def is_blacklisted_path(path: str, blacklist: list[str] = list(BLACKLIST)) -> bool: + return any(substr in (normalize_path_separators(path) + "\n") for substr in blacklist) def normalize_path_separators(path: str) -> str: @@ -1608,7 +1621,7 @@ def collect_build_targets( py_modules = [StubSource(m.module, m.path) for m in source_list] c_modules = [] - py_modules = remove_blacklisted_modules(py_modules) + py_modules = remove_blacklisted_modules(py_modules, include_vendor=options.include_vendor) pyc_mod, py_mod = split_pyc_from_py(py_modules) return py_mod, pyc_mod, c_modules @@ -1960,6 +1973,11 @@ def parse_options(args: list[str]) -> Options: action="store_true", help="include existing docstrings with the stubs", ) + parser.add_argument( + "--include-vendor", + action="store_true", + help="generate stubs for vendor/vendored directories (skipped by default)", + ) parser.add_argument("-v", "--verbose", action="store_true", help="show more verbose messages") parser.add_argument("-q", "--quiet", action="store_true", help="show fewer messages") parser.add_argument( @@ -2046,6 +2064,7 @@ def parse_options(args: list[str]) -> Options: quiet=ns.quiet, export_less=ns.export_less, include_docstrings=ns.include_docstrings, + include_vendor=ns.include_vendor, )