11
11
import copy
12
12
import enum
13
13
import importlib
14
+ import importlib .machinery
14
15
import inspect
15
16
import os
16
17
import pkgutil
25
26
from contextlib import redirect_stderr , redirect_stdout
26
27
from functools import singledispatch
27
28
from pathlib import Path
28
- from typing import Any , Generic , Iterator , TypeVar , Union
29
+ from typing import AbstractSet , Any , Generic , Iterator , TypeVar , Union
29
30
from typing_extensions import get_origin , is_typeddict
30
31
31
32
import mypy .build
@@ -1639,7 +1640,7 @@ def get_stub(module: str) -> nodes.MypyFile | None:
1639
1640
1640
1641
def get_typeshed_stdlib_modules (
1641
1642
custom_typeshed_dir : str | None , version_info : tuple [int , int ] | None = None
1642
- ) -> list [str ]:
1643
+ ) -> set [str ]:
1643
1644
"""Returns a list of stdlib modules in typeshed (for current Python version)."""
1644
1645
stdlib_py_versions = mypy .modulefinder .load_stdlib_py_versions (custom_typeshed_dir )
1645
1646
if version_info is None :
@@ -1661,14 +1662,75 @@ def exists_in_version(module: str) -> bool:
1661
1662
typeshed_dir = Path (mypy .build .default_data_dir ()) / "typeshed"
1662
1663
stdlib_dir = typeshed_dir / "stdlib"
1663
1664
1664
- modules = []
1665
+ modules : set [ str ] = set ()
1665
1666
for path in stdlib_dir .rglob ("*.pyi" ):
1666
1667
if path .stem == "__init__" :
1667
1668
path = path .parent
1668
1669
module = "." .join (path .relative_to (stdlib_dir ).parts [:- 1 ] + (path .stem ,))
1669
1670
if exists_in_version (module ):
1670
- modules .append (module )
1671
- return sorted (modules )
1671
+ modules .add (module )
1672
+ return modules
1673
+
1674
+
1675
+ def get_importable_stdlib_modules () -> set [str ]:
1676
+ """Return all importable stdlib modules at runtime."""
1677
+ all_stdlib_modules : AbstractSet [str ]
1678
+ if sys .version_info >= (3 , 10 ):
1679
+ all_stdlib_modules = sys .stdlib_module_names
1680
+ else :
1681
+ all_stdlib_modules = set (sys .builtin_module_names )
1682
+ python_exe_dir = Path (sys .executable ).parent
1683
+ for m in pkgutil .iter_modules ():
1684
+ finder = m .module_finder
1685
+ if isinstance (finder , importlib .machinery .FileFinder ):
1686
+ finder_path = Path (finder .path )
1687
+ if (
1688
+ python_exe_dir in finder_path .parents
1689
+ and "site-packages" not in finder_path .parts
1690
+ ):
1691
+ all_stdlib_modules .add (m .name )
1692
+
1693
+ importable_stdlib_modules : set [str ] = set ()
1694
+ for module_name in all_stdlib_modules :
1695
+ if module_name in ANNOYING_STDLIB_MODULES :
1696
+ continue
1697
+
1698
+ try :
1699
+ runtime = silent_import_module (module_name )
1700
+ except ImportError :
1701
+ continue
1702
+ else :
1703
+ importable_stdlib_modules .add (module_name )
1704
+
1705
+ try :
1706
+ # some stdlib modules (e.g. `nt`) don't have __path__ set...
1707
+ runtime_path = runtime .__path__
1708
+ runtime_name = runtime .__name__
1709
+ except AttributeError :
1710
+ continue
1711
+
1712
+ for submodule in pkgutil .walk_packages (runtime_path , runtime_name + "." ):
1713
+ submodule_name = submodule .name
1714
+
1715
+ # There are many annoying *.__main__ stdlib modules,
1716
+ # and including stubs for them isn't really that useful anyway:
1717
+ # tkinter.__main__ opens a tkinter windows; unittest.__main__ raises SystemExit; etc.
1718
+ #
1719
+ # The idlelib.* submodules are similarly annoying in opening random tkinter windows,
1720
+ # and we're unlikely to ever add stubs for idlelib in typeshed
1721
+ # (see discussion in https://github.com/python/typeshed/pull/9193)
1722
+ if submodule_name .endswith (".__main__" ) or submodule_name .startswith ("idlelib." ):
1723
+ continue
1724
+
1725
+ try :
1726
+ silent_import_module (submodule_name )
1727
+ # importing multiprocessing.popen_forkserver on Windows raises AttributeError...
1728
+ except Exception :
1729
+ continue
1730
+ else :
1731
+ importable_stdlib_modules .add (submodule_name )
1732
+
1733
+ return importable_stdlib_modules
1672
1734
1673
1735
1674
1736
def get_allowlist_entries (allowlist_file : str ) -> Iterator [str ]:
@@ -1699,6 +1761,10 @@ class _Arguments:
1699
1761
version : str
1700
1762
1701
1763
1764
+ # typeshed added a stub for __main__, but that causes stubtest to check itself
1765
+ ANNOYING_STDLIB_MODULES : typing_extensions .Final = frozenset ({"antigravity" , "this" , "__main__" })
1766
+
1767
+
1702
1768
def test_stubs (args : _Arguments , use_builtins_fixtures : bool = False ) -> int :
1703
1769
"""This is stubtest! It's time to test the stubs!"""
1704
1770
# Load the allowlist. This is a series of strings corresponding to Error.object_desc
@@ -1721,10 +1787,9 @@ def test_stubs(args: _Arguments, use_builtins_fixtures: bool = False) -> int:
1721
1787
"cannot pass both --check-typeshed and a list of modules" ,
1722
1788
)
1723
1789
return 1
1724
- modules = get_typeshed_stdlib_modules (args .custom_typeshed_dir )
1725
- # typeshed added a stub for __main__, but that causes stubtest to check itself
1726
- annoying_modules = {"antigravity" , "this" , "__main__" }
1727
- modules = [m for m in modules if m not in annoying_modules ]
1790
+ typeshed_modules = get_typeshed_stdlib_modules (args .custom_typeshed_dir )
1791
+ runtime_modules = get_importable_stdlib_modules ()
1792
+ modules = sorted ((typeshed_modules | runtime_modules ) - ANNOYING_STDLIB_MODULES )
1728
1793
1729
1794
if not modules :
1730
1795
print (_style ("error:" , color = "red" , bold = True ), "no modules to check" )
0 commit comments