@@ -862,6 +862,10 @@ def _get_site_packages_dirs(python_executable: Optional[str]) -> List[str]:
862
862
stderr = subprocess .PIPE ).decode ())
863
863
864
864
865
+ # Search paths are a two-tuple of path and whether to verify the module
866
+ SearchPaths = List [Tuple [str , bool ]]
867
+
868
+
865
869
class FindModuleCache :
866
870
"""Module finder with integrated cache.
867
871
@@ -875,16 +879,16 @@ class FindModuleCache:
875
879
876
880
def __init__ (self , fscache : Optional [FileSystemCache ] = None ) -> None :
877
881
self .fscache = fscache or FileSystemCache ()
878
- # Cache find_lib_path_dirs: (dir_chain, lib_path)
879
- self .dirs = {} # type: Dict[Tuple[str, Tuple[str, ...]], List[str] ]
882
+ # Cache find_lib_path_dirs: (dir_chain, lib_path) -> list of (package_path, should_verify)
883
+ self .dirs = {} # type: Dict[Tuple[str, Tuple[str, ...]], SearchPaths ]
880
884
# Cache find_module: (id, lib_path, python_version) -> result.
881
885
self .results = {} # type: Dict[Tuple[str, Tuple[str, ...], Optional[str]], Optional[str]]
882
886
883
887
def clear (self ) -> None :
884
888
self .results .clear ()
885
889
self .dirs .clear ()
886
890
887
- def find_lib_path_dirs (self , dir_chain : str , lib_path : Tuple [str , ...]) -> List [ str ] :
891
+ def find_lib_path_dirs (self , dir_chain : str , lib_path : Tuple [str , ...]) -> SearchPaths :
888
892
# Cache some repeated work within distinct find_module calls: finding which
889
893
# elements of lib_path have even the subdirectory they'd need for the module
890
894
# to exist. This is shared among different module ids when they differ only
@@ -894,13 +898,13 @@ def find_lib_path_dirs(self, dir_chain: str, lib_path: Tuple[str, ...]) -> List[
894
898
self .dirs [key ] = self ._find_lib_path_dirs (dir_chain , lib_path )
895
899
return self .dirs [key ]
896
900
897
- def _find_lib_path_dirs (self , dir_chain : str , lib_path : Tuple [str , ...]) -> List [ str ] :
901
+ def _find_lib_path_dirs (self , dir_chain : str , lib_path : Tuple [str , ...]) -> SearchPaths :
898
902
dirs = []
899
903
for pathitem in lib_path :
900
904
# e.g., '/usr/lib/python3.4/foo/bar'
901
905
dir = os .path .normpath (os .path .join (pathitem , dir_chain ))
902
906
if self .fscache .isdir (dir ):
903
- dirs .append (dir )
907
+ dirs .append (( dir , True ) )
904
908
return dirs
905
909
906
910
def find_module (self , id : str , lib_path : Tuple [str , ...],
@@ -933,13 +937,26 @@ def _find_module(self, id: str, lib_path: Tuple[str, ...],
933
937
typed_file = os .path .join (pkg_dir , components [0 ], 'py.typed' )
934
938
stub_dir = os .path .join (pkg_dir , stub_name )
935
939
if fscache .isdir (stub_dir ):
940
+ stub_typed_file = os .path .join (stub_dir , 'py.typed' )
936
941
stub_components = [stub_name ] + components [1 :]
937
942
path = os .path .join (pkg_dir , * stub_components [:- 1 ])
938
943
if fscache .isdir (path ):
939
- third_party_stubs_dirs .append (path )
944
+ if fscache .isfile (stub_typed_file ):
945
+ # Stub packages can have a py.typed file, which must include
946
+ # 'partial\n' to make the package partial
947
+ # Partial here means that mypy should look at the runtime
948
+ # package if installed.
949
+ if fscache .read (stub_typed_file ).decode ().strip () == 'partial' :
950
+ runtime_path = os .path .join (pkg_dir , dir_chain )
951
+ third_party_inline_dirs .append ((runtime_path , True ))
952
+ # if the package is partial, we don't verify the module, as
953
+ # the partial stub package may not have a __init__.pyi
954
+ third_party_stubs_dirs .append ((path , False ))
955
+ else :
956
+ third_party_stubs_dirs .append ((path , True ))
940
957
elif fscache .isfile (typed_file ):
941
958
path = os .path .join (pkg_dir , dir_chain )
942
- third_party_inline_dirs .append (path )
959
+ third_party_inline_dirs .append (( path , True ) )
943
960
candidate_base_dirs = self .find_lib_path_dirs (dir_chain , lib_path ) + \
944
961
third_party_stubs_dirs + third_party_inline_dirs
945
962
@@ -949,20 +966,26 @@ def _find_module(self, id: str, lib_path: Tuple[str, ...],
949
966
# Now just look for 'baz.pyi', 'baz/__init__.py', etc., inside those directories.
950
967
seplast = os .sep + components [- 1 ] # so e.g. '/baz'
951
968
sepinit = os .sep + '__init__'
952
- for base_dir in candidate_base_dirs :
969
+ for base_dir , verify in candidate_base_dirs :
953
970
base_path = base_dir + seplast # so e.g. '/usr/lib/python3.4/foo/bar/baz'
954
971
# Prefer package over module, i.e. baz/__init__.py* over baz.py*.
955
972
for extension in PYTHON_EXTENSIONS :
956
973
path = base_path + sepinit + extension
957
974
path_stubs = base_path + '-stubs' + sepinit + extension
958
- if fscache .isfile_case (path ) and verify_module (fscache , id , path ):
975
+ if fscache .isfile_case (path ):
976
+ if verify and not verify_module (fscache , id , path ):
977
+ continue
959
978
return path
960
- elif fscache .isfile_case (path_stubs ) and verify_module (fscache , id , path_stubs ):
979
+ elif fscache .isfile_case (path_stubs ):
980
+ if verify and not verify_module (fscache , id , path_stubs ):
981
+ continue
961
982
return path_stubs
962
983
# No package, look for module.
963
984
for extension in PYTHON_EXTENSIONS :
964
985
path = base_path + extension
965
- if fscache .isfile_case (path ) and verify_module (fscache , id , path ):
986
+ if fscache .isfile_case (path ):
987
+ if verify and not verify_module (fscache , id , path ):
988
+ continue
966
989
return path
967
990
return None
968
991
0 commit comments