mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-06 22:14:27 +08:00
Typeshed third party libraries should not be loaded if they don't actually exist in the environment, fixes #1620
This commit is contained in:
@@ -102,7 +102,7 @@ class TypeVar(BaseTypingValue):
|
|||||||
else:
|
else:
|
||||||
if found:
|
if found:
|
||||||
return found
|
return found
|
||||||
return self._get_classes() or ValueSet({self})
|
return ValueSet({self})
|
||||||
|
|
||||||
def execute_annotation(self):
|
def execute_annotation(self):
|
||||||
return self._get_classes().execute_annotation()
|
return self._get_classes().execute_annotation()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
from jedi import settings
|
from jedi import settings
|
||||||
from jedi.file_io import FileIO
|
from jedi.file_io import FileIO
|
||||||
@@ -20,36 +21,38 @@ _IMPORT_MAP = dict(
|
|||||||
_socket='socket',
|
_socket='socket',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
PathInfo = namedtuple('PathInfo', 'path is_third_party')
|
||||||
|
|
||||||
def _merge_create_stub_map(directories):
|
|
||||||
|
def _merge_create_stub_map(path_infos):
|
||||||
map_ = {}
|
map_ = {}
|
||||||
for directory in directories:
|
for directory_path_info in path_infos:
|
||||||
map_.update(_create_stub_map(directory))
|
map_.update(_create_stub_map(directory_path_info))
|
||||||
return map_
|
return map_
|
||||||
|
|
||||||
|
|
||||||
def _create_stub_map(directory):
|
def _create_stub_map(directory_path_info):
|
||||||
"""
|
"""
|
||||||
Create a mapping of an importable name in Python to a stub file.
|
Create a mapping of an importable name in Python to a stub file.
|
||||||
"""
|
"""
|
||||||
def generate():
|
def generate():
|
||||||
try:
|
try:
|
||||||
listed = os.listdir(directory)
|
listed = os.listdir(directory_path_info.path)
|
||||||
except (FileNotFoundError, OSError):
|
except (FileNotFoundError, OSError):
|
||||||
# OSError is Python 2
|
# OSError is Python 2
|
||||||
return
|
return
|
||||||
|
|
||||||
for entry in listed:
|
for entry in listed:
|
||||||
entry = cast_path(entry)
|
entry = cast_path(entry)
|
||||||
path = os.path.join(directory, entry)
|
path = os.path.join(directory_path_info.path, entry)
|
||||||
if os.path.isdir(path):
|
if os.path.isdir(path):
|
||||||
init = os.path.join(path, '__init__.pyi')
|
init = os.path.join(path, '__init__.pyi')
|
||||||
if os.path.isfile(init):
|
if os.path.isfile(init):
|
||||||
yield entry, init
|
yield entry, PathInfo(init, directory_path_info.is_third_party)
|
||||||
elif entry.endswith('.pyi') and os.path.isfile(path):
|
elif entry.endswith('.pyi') and os.path.isfile(path):
|
||||||
name = entry[:-4]
|
name = entry[:-4]
|
||||||
if name != '__init__':
|
if name != '__init__':
|
||||||
yield name, path
|
yield name, PathInfo(path, directory_path_info.is_third_party)
|
||||||
|
|
||||||
# Create a dictionary from the tuple generator.
|
# Create a dictionary from the tuple generator.
|
||||||
return dict(generate())
|
return dict(generate())
|
||||||
@@ -58,8 +61,8 @@ def _create_stub_map(directory):
|
|||||||
def _get_typeshed_directories(version_info):
|
def _get_typeshed_directories(version_info):
|
||||||
check_version_list = ['2and3', str(version_info.major)]
|
check_version_list = ['2and3', str(version_info.major)]
|
||||||
for base in ['stdlib', 'third_party']:
|
for base in ['stdlib', 'third_party']:
|
||||||
base = os.path.join(TYPESHED_PATH, base)
|
base_path = os.path.join(TYPESHED_PATH, base)
|
||||||
base_list = os.listdir(base)
|
base_list = os.listdir(base_path)
|
||||||
for base_list_entry in base_list:
|
for base_list_entry in base_list:
|
||||||
match = re.match(r'(\d+)\.(\d+)$', base_list_entry)
|
match = re.match(r'(\d+)\.(\d+)$', base_list_entry)
|
||||||
if match is not None:
|
if match is not None:
|
||||||
@@ -68,7 +71,8 @@ def _get_typeshed_directories(version_info):
|
|||||||
check_version_list.append(base_list_entry)
|
check_version_list.append(base_list_entry)
|
||||||
|
|
||||||
for check_version in check_version_list:
|
for check_version in check_version_list:
|
||||||
yield os.path.join(base, check_version)
|
is_third_party = base != 'stdlib'
|
||||||
|
yield PathInfo(os.path.join(base_path, check_version), is_third_party)
|
||||||
|
|
||||||
|
|
||||||
_version_cache = {}
|
_version_cache = {}
|
||||||
@@ -175,7 +179,7 @@ def _try_to_load_stub(inference_state, import_names, python_value_set,
|
|||||||
)
|
)
|
||||||
if m is not None:
|
if m is not None:
|
||||||
return m
|
return m
|
||||||
if import_names[0] == 'django':
|
if import_names[0] == 'django' and python_value_set:
|
||||||
return _try_to_load_stub_from_file(
|
return _try_to_load_stub_from_file(
|
||||||
inference_state,
|
inference_state,
|
||||||
python_value_set,
|
python_value_set,
|
||||||
@@ -249,16 +253,21 @@ def _load_from_typeshed(inference_state, python_value_set, parent_module_value,
|
|||||||
# Only if it's a package (= a folder) something can be
|
# Only if it's a package (= a folder) something can be
|
||||||
# imported.
|
# imported.
|
||||||
return None
|
return None
|
||||||
path = parent_module_value.py__path__()
|
paths = parent_module_value.py__path__()
|
||||||
map_ = _merge_create_stub_map(path)
|
# Once the initial package has been loaded, the sub packages will
|
||||||
|
# always be loaded, regardless if they are there or not. This makes
|
||||||
|
# sense, IMO, because stubs take preference, even if the original
|
||||||
|
# library doesn't provide a module (it could be dynamic). ~dave
|
||||||
|
map_ = _merge_create_stub_map([PathInfo(p, is_third_party=False) for p in paths])
|
||||||
|
|
||||||
if map_ is not None:
|
if map_ is not None:
|
||||||
path = map_.get(import_name)
|
path_info = map_.get(import_name)
|
||||||
if path is not None:
|
print(path_info)
|
||||||
|
if path_info is not None and (not path_info.is_third_party or python_value_set):
|
||||||
return _try_to_load_stub_from_file(
|
return _try_to_load_stub_from_file(
|
||||||
inference_state,
|
inference_state,
|
||||||
python_value_set,
|
python_value_set,
|
||||||
file_io=FileIO(path),
|
file_io=FileIO(path_info.path),
|
||||||
import_names=import_names,
|
import_names=import_names,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ TYPESHED_PYTHON3 = os.path.join(typeshed.TYPESHED_PATH, 'stdlib', '3')
|
|||||||
def test_get_typeshed_directories():
|
def test_get_typeshed_directories():
|
||||||
def get_dirs(version_info):
|
def get_dirs(version_info):
|
||||||
return {
|
return {
|
||||||
d.replace(typeshed.TYPESHED_PATH, '').lstrip(os.path.sep)
|
p.path.replace(typeshed.TYPESHED_PATH, '').lstrip(os.path.sep)
|
||||||
for d in typeshed._get_typeshed_directories(version_info)
|
for p in typeshed._get_typeshed_directories(version_info)
|
||||||
}
|
}
|
||||||
|
|
||||||
def transform(set_):
|
def transform(set_):
|
||||||
@@ -35,11 +35,8 @@ def test_get_typeshed_directories():
|
|||||||
|
|
||||||
|
|
||||||
def test_get_stub_files():
|
def test_get_stub_files():
|
||||||
def get_map(version_info):
|
map_ = typeshed._create_stub_map(typeshed.PathInfo(TYPESHED_PYTHON3, is_third_party=False))
|
||||||
return typeshed._create_stub_map(version_info)
|
assert map_['functools'].path == os.path.join(TYPESHED_PYTHON3, 'functools.pyi')
|
||||||
|
|
||||||
map_ = typeshed._create_stub_map(TYPESHED_PYTHON3)
|
|
||||||
assert map_['functools'] == os.path.join(TYPESHED_PYTHON3, 'functools.pyi')
|
|
||||||
|
|
||||||
|
|
||||||
def test_function(Script, environment):
|
def test_function(Script, environment):
|
||||||
@@ -227,3 +224,25 @@ def test_goto_stubs_on_itself(Script, code, type_):
|
|||||||
|
|
||||||
_assert_is_same(same_definition, definition)
|
_assert_is_same(same_definition, definition)
|
||||||
_assert_is_same(same_definition, same_definition2)
|
_assert_is_same(same_definition, same_definition2)
|
||||||
|
|
||||||
|
|
||||||
|
def test_module_exists_only_as_stub(Script):
|
||||||
|
try:
|
||||||
|
import redis
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
pytest.skip('redis is already installed, it should only exist as a stub for this test')
|
||||||
|
redis_path = os.path.join(typeshed.TYPESHED_PATH, 'third_party', '2and3', 'redis')
|
||||||
|
assert os.path.isdir(redis_path)
|
||||||
|
assert not Script('import redis').infer()
|
||||||
|
|
||||||
|
|
||||||
|
def test_django_exists_only_as_stub(Script):
|
||||||
|
try:
|
||||||
|
import django
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
pytest.skip('django is already installed, it should only exist as a stub for this test')
|
||||||
|
assert not Script('import django').infer()
|
||||||
|
|||||||
Reference in New Issue
Block a user