Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/jedi/inference/gradual/typeshed.py: 20%

163 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 06:09 +0000

1import os 

2import re 

3from functools import wraps 

4from collections import namedtuple 

5from typing import Dict, Mapping, Tuple 

6from pathlib import Path 

7 

8from jedi import settings 

9from jedi.file_io import FileIO 

10from jedi.parser_utils import get_cached_code_lines 

11from jedi.inference.base_value import ValueSet, NO_VALUES 

12from jedi.inference.gradual.stub_value import TypingModuleWrapper, StubModuleValue 

13from jedi.inference.value import ModuleValue 

14 

15_jedi_path = Path(__file__).parent.parent.parent 

16TYPESHED_PATH = _jedi_path.joinpath('third_party', 'typeshed') 

17DJANGO_INIT_PATH = _jedi_path.joinpath('third_party', 'django-stubs', 

18 'django-stubs', '__init__.pyi') 

19 

20_IMPORT_MAP = dict( 

21 _collections='collections', 

22 _socket='socket', 

23) 

24 

25PathInfo = namedtuple('PathInfo', 'path is_third_party') 

26 

27 

28def _merge_create_stub_map(path_infos): 

29 map_ = {} 

30 for directory_path_info in path_infos: 

31 map_.update(_create_stub_map(directory_path_info)) 

32 return map_ 

33 

34 

35def _create_stub_map(directory_path_info): 

36 """ 

37 Create a mapping of an importable name in Python to a stub file. 

38 """ 

39 def generate(): 

40 try: 

41 listed = os.listdir(directory_path_info.path) 

42 except (FileNotFoundError, NotADirectoryError): 

43 return 

44 

45 for entry in listed: 

46 path = os.path.join(directory_path_info.path, entry) 

47 if os.path.isdir(path): 

48 init = os.path.join(path, '__init__.pyi') 

49 if os.path.isfile(init): 

50 yield entry, PathInfo(init, directory_path_info.is_third_party) 

51 elif entry.endswith('.pyi') and os.path.isfile(path): 

52 name = entry[:-4] 

53 if name != '__init__': 

54 yield name, PathInfo(path, directory_path_info.is_third_party) 

55 

56 # Create a dictionary from the tuple generator. 

57 return dict(generate()) 

58 

59 

60def _get_typeshed_directories(version_info): 

61 check_version_list = ['2and3', '3'] 

62 for base in ['stdlib', 'third_party']: 

63 base_path = TYPESHED_PATH.joinpath(base) 

64 base_list = os.listdir(base_path) 

65 for base_list_entry in base_list: 

66 match = re.match(r'(\d+)\.(\d+)$', base_list_entry) 

67 if match is not None: 

68 if match.group(1) == '3' and int(match.group(2)) <= version_info.minor: 

69 check_version_list.append(base_list_entry) 

70 

71 for check_version in check_version_list: 

72 is_third_party = base != 'stdlib' 

73 yield PathInfo(str(base_path.joinpath(check_version)), is_third_party) 

74 

75 

76_version_cache: Dict[Tuple[int, int], Mapping[str, PathInfo]] = {} 

77 

78 

79def _cache_stub_file_map(version_info): 

80 """ 

81 Returns a map of an importable name in Python to a stub file. 

82 """ 

83 # TODO this caches the stub files indefinitely, maybe use a time cache 

84 # for that? 

85 version = version_info[:2] 

86 try: 

87 return _version_cache[version] 

88 except KeyError: 

89 pass 

90 

91 _version_cache[version] = file_set = \ 

92 _merge_create_stub_map(_get_typeshed_directories(version_info)) 

93 return file_set 

94 

95 

96def import_module_decorator(func): 

97 @wraps(func) 

98 def wrapper(inference_state, import_names, parent_module_value, sys_path, prefer_stubs): 

99 python_value_set = inference_state.module_cache.get(import_names) 

100 if python_value_set is None: 

101 if parent_module_value is not None and parent_module_value.is_stub(): 

102 parent_module_values = parent_module_value.non_stub_value_set 

103 else: 

104 parent_module_values = [parent_module_value] 

105 if import_names == ('os', 'path'): 

106 # This is a huge exception, we follow a nested import 

107 # ``os.path``, because it's a very important one in Python 

108 # that is being achieved by messing with ``sys.modules`` in 

109 # ``os``. 

110 python_value_set = ValueSet.from_sets( 

111 func(inference_state, (n,), None, sys_path,) 

112 for n in ['posixpath', 'ntpath', 'macpath', 'os2emxpath'] 

113 ) 

114 else: 

115 python_value_set = ValueSet.from_sets( 

116 func(inference_state, import_names, p, sys_path,) 

117 for p in parent_module_values 

118 ) 

119 inference_state.module_cache.add(import_names, python_value_set) 

120 

121 if not prefer_stubs or import_names[0] in settings.auto_import_modules: 

122 return python_value_set 

123 

124 stub = try_to_load_stub_cached(inference_state, import_names, python_value_set, 

125 parent_module_value, sys_path) 

126 if stub is not None: 

127 return ValueSet([stub]) 

128 return python_value_set 

129 

130 return wrapper 

131 

132 

133def try_to_load_stub_cached(inference_state, import_names, *args, **kwargs): 

134 if import_names is None: 

135 return None 

136 

137 try: 

138 return inference_state.stub_module_cache[import_names] 

139 except KeyError: 

140 pass 

141 

142 # TODO is this needed? where are the exceptions coming from that make this 

143 # necessary? Just remove this line. 

144 inference_state.stub_module_cache[import_names] = None 

145 inference_state.stub_module_cache[import_names] = result = \ 

146 _try_to_load_stub(inference_state, import_names, *args, **kwargs) 

147 return result 

148 

149 

150def _try_to_load_stub(inference_state, import_names, python_value_set, 

151 parent_module_value, sys_path): 

152 """ 

153 Trying to load a stub for a set of import_names. 

154 

155 This is modelled to work like "PEP 561 -- Distributing and Packaging Type 

156 Information", see https://www.python.org/dev/peps/pep-0561. 

157 """ 

158 if parent_module_value is None and len(import_names) > 1: 

159 try: 

160 parent_module_value = try_to_load_stub_cached( 

161 inference_state, import_names[:-1], NO_VALUES, 

162 parent_module_value=None, sys_path=sys_path) 

163 except KeyError: 

164 pass 

165 

166 # 1. Try to load foo-stubs folders on path for import name foo. 

167 if len(import_names) == 1: 

168 # foo-stubs 

169 for p in sys_path: 

170 init = os.path.join(p, *import_names) + '-stubs' + os.path.sep + '__init__.pyi' 

171 m = _try_to_load_stub_from_file( 

172 inference_state, 

173 python_value_set, 

174 file_io=FileIO(init), 

175 import_names=import_names, 

176 ) 

177 if m is not None: 

178 return m 

179 if import_names[0] == 'django' and python_value_set: 

180 return _try_to_load_stub_from_file( 

181 inference_state, 

182 python_value_set, 

183 file_io=FileIO(str(DJANGO_INIT_PATH)), 

184 import_names=import_names, 

185 ) 

186 

187 # 2. Try to load pyi files next to py files. 

188 for c in python_value_set: 

189 try: 

190 method = c.py__file__ 

191 except AttributeError: 

192 pass 

193 else: 

194 file_path = method() 

195 file_paths = [] 

196 if c.is_namespace(): 

197 file_paths = [os.path.join(p, '__init__.pyi') for p in c.py__path__()] 

198 elif file_path is not None and file_path.suffix == '.py': 

199 file_paths = [str(file_path) + 'i'] 

200 

201 for file_path in file_paths: 

202 m = _try_to_load_stub_from_file( 

203 inference_state, 

204 python_value_set, 

205 # The file path should end with .pyi 

206 file_io=FileIO(file_path), 

207 import_names=import_names, 

208 ) 

209 if m is not None: 

210 return m 

211 

212 # 3. Try to load typeshed 

213 m = _load_from_typeshed(inference_state, python_value_set, parent_module_value, import_names) 

214 if m is not None: 

215 return m 

216 

217 # 4. Try to load pyi file somewhere if python_value_set was not defined. 

218 if not python_value_set: 

219 if parent_module_value is not None: 

220 check_path = parent_module_value.py__path__() or [] 

221 # In case import_names 

222 names_for_path = (import_names[-1],) 

223 else: 

224 check_path = sys_path 

225 names_for_path = import_names 

226 

227 for p in check_path: 

228 m = _try_to_load_stub_from_file( 

229 inference_state, 

230 python_value_set, 

231 file_io=FileIO(os.path.join(p, *names_for_path) + '.pyi'), 

232 import_names=import_names, 

233 ) 

234 if m is not None: 

235 return m 

236 

237 # If no stub is found, that's fine, the calling function has to deal with 

238 # it. 

239 return None 

240 

241 

242def _load_from_typeshed(inference_state, python_value_set, parent_module_value, import_names): 

243 import_name = import_names[-1] 

244 map_ = None 

245 if len(import_names) == 1: 

246 map_ = _cache_stub_file_map(inference_state.grammar.version_info) 

247 import_name = _IMPORT_MAP.get(import_name, import_name) 

248 elif isinstance(parent_module_value, ModuleValue): 

249 if not parent_module_value.is_package(): 

250 # Only if it's a package (= a folder) something can be 

251 # imported. 

252 return None 

253 paths = parent_module_value.py__path__() 

254 # Once the initial package has been loaded, the sub packages will 

255 # always be loaded, regardless if they are there or not. This makes 

256 # sense, IMO, because stubs take preference, even if the original 

257 # library doesn't provide a module (it could be dynamic). ~dave 

258 map_ = _merge_create_stub_map([PathInfo(p, is_third_party=False) for p in paths]) 

259 

260 if map_ is not None: 

261 path_info = map_.get(import_name) 

262 if path_info is not None and (not path_info.is_third_party or python_value_set): 

263 return _try_to_load_stub_from_file( 

264 inference_state, 

265 python_value_set, 

266 file_io=FileIO(path_info.path), 

267 import_names=import_names, 

268 ) 

269 

270 

271def _try_to_load_stub_from_file(inference_state, python_value_set, file_io, import_names): 

272 try: 

273 stub_module_node = parse_stub_module(inference_state, file_io) 

274 except OSError: 

275 # The file that you're looking for doesn't exist (anymore). 

276 return None 

277 else: 

278 return create_stub_module( 

279 inference_state, inference_state.latest_grammar, python_value_set, 

280 stub_module_node, file_io, import_names 

281 ) 

282 

283 

284def parse_stub_module(inference_state, file_io): 

285 return inference_state.parse( 

286 file_io=file_io, 

287 cache=True, 

288 diff_cache=settings.fast_parser, 

289 cache_path=settings.cache_directory, 

290 use_latest_grammar=True 

291 ) 

292 

293 

294def create_stub_module(inference_state, grammar, python_value_set, 

295 stub_module_node, file_io, import_names): 

296 if import_names == ('typing',): 

297 module_cls = TypingModuleWrapper 

298 else: 

299 module_cls = StubModuleValue 

300 file_name = os.path.basename(file_io.path) 

301 stub_module_value = module_cls( 

302 python_value_set, inference_state, stub_module_node, 

303 file_io=file_io, 

304 string_names=import_names, 

305 # The code was loaded with latest_grammar, so use 

306 # that. 

307 code_lines=get_cached_code_lines(grammar, file_io.path), 

308 is_package=file_name == '__init__.pyi', 

309 ) 

310 return stub_module_value