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
« 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
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
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')
20_IMPORT_MAP = dict(
21 _collections='collections',
22 _socket='socket',
23)
25PathInfo = namedtuple('PathInfo', 'path is_third_party')
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_
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
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)
56 # Create a dictionary from the tuple generator.
57 return dict(generate())
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)
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)
76_version_cache: Dict[Tuple[int, int], Mapping[str, PathInfo]] = {}
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
91 _version_cache[version] = file_set = \
92 _merge_create_stub_map(_get_typeshed_directories(version_info))
93 return file_set
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)
121 if not prefer_stubs or import_names[0] in settings.auto_import_modules:
122 return python_value_set
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
130 return wrapper
133def try_to_load_stub_cached(inference_state, import_names, *args, **kwargs):
134 if import_names is None:
135 return None
137 try:
138 return inference_state.stub_module_cache[import_names]
139 except KeyError:
140 pass
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
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.
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
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 )
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']
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
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
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
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
237 # If no stub is found, that's fine, the calling function has to deal with
238 # it.
239 return None
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])
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 )
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 )
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 )
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