Merge branch 'master' into deprecations

This commit is contained in:
Dave Halter
2020-12-12 12:17:25 +01:00
79 changed files with 593 additions and 204 deletions

14
.editorconfig Normal file
View File

@@ -0,0 +1,14 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.py]
indent_size = 4
[*.md]
indent_size = 2

1
.gitignore vendored
View File

@@ -12,4 +12,5 @@ jedi.egg-info/
record.json record.json
/.cache/ /.cache/
/.pytest_cache /.pytest_cache
/.mypy_cache
/venv/ /venv/

2
.readthedocs.yml Normal file
View File

@@ -0,0 +1,2 @@
python:
pip_install: true

View File

@@ -8,6 +8,7 @@ python:
env: env:
- JEDI_TEST_ENVIRONMENT=38 - JEDI_TEST_ENVIRONMENT=38
- JEDI_TEST_ENVIRONMENT=39
- JEDI_TEST_ENVIRONMENT=37 - JEDI_TEST_ENVIRONMENT=37
- JEDI_TEST_ENVIRONMENT=36 - JEDI_TEST_ENVIRONMENT=36
- JEDI_TEST_ENVIRONMENT=interpreter - JEDI_TEST_ENVIRONMENT=interpreter
@@ -30,8 +31,8 @@ matrix:
install: install:
- 'pip install .[qa]' - 'pip install .[qa]'
script: script:
# Ignore F401, which are unused imports. flake8 is a primitive tool and is sometimes wrong. - 'flake8 jedi setup.py'
- 'flake8 --extend-ignore F401 {posargs:jedi}' - 'mypy jedi sith.py'
install: install:
- sudo apt-get -y install python3-venv - sudo apt-get -y install python3-venv
- pip install .[testing] - pip install .[testing]
@@ -47,9 +48,15 @@ script:
# Only required for JEDI_TEST_ENVIRONMENT=38, because it's not always # Only required for JEDI_TEST_ENVIRONMENT=38, because it's not always
# available. # available.
download_name=python-$test_env_version download_name=python-$test_env_version
wget https://s3.amazonaws.com/travis-python-archives/binaries/ubuntu/16.04/x86_64/$download_name.tar.bz2 if [ "$JEDI_TEST_ENVIRONMENT" == "39" ]; then
sudo tar xjf $download_name.tar.bz2 --directory / opt/python wget https://storage.googleapis.com/travis-ci-language-archives/python/binaries/ubuntu/16.04/x86_64/python-3.9-dev.tar.bz2
ln -s "/opt/python/${test_env_version}/bin/python" /home/travis/bin/$python_bin sudo tar xjf python-3.9-dev.tar.bz2 --directory / opt/python
ln -s "/opt/python/3.9-dev/bin/python" /home/travis/bin/python3.9
else
wget https://s3.amazonaws.com/travis-python-archives/binaries/ubuntu/16.04/x86_64/$download_name.tar.bz2
sudo tar xjf $download_name.tar.bz2 --directory / opt/python
ln -s "/opt/python/${test_env_version}/bin/python" /home/travis/bin/$python_bin
fi
elif [ "${python_path#/opt/pyenv/shims}" != "$python_path" ]; then elif [ "${python_path#/opt/pyenv/shims}" != "$python_path" ]; then
# Activate pyenv version (required with JEDI_TEST_ENVIRONMENT=36). # Activate pyenv version (required with JEDI_TEST_ENVIRONMENT=36).
pyenv_bin="$(pyenv whence --path "$python_bin" | head -n1)" pyenv_bin="$(pyenv whence --path "$python_bin" | head -n1)"

View File

@@ -16,6 +16,8 @@ Unreleased
- Functions with ``@property`` now return ``property`` instead of ``function`` - Functions with ``@property`` now return ``property`` instead of ``function``
in ``Name().type`` in ``Name().type``
- Started using annotations - Started using annotations
- Better support for the walrus operator
- Project attributes are now read accessible
This is likely going to be the last minor release before 1.0. This is likely going to be the last minor release before 1.0.

View File

@@ -52,9 +52,16 @@ Jedi can currently be used with the following editors/projects:
- wdb_ - Web Debugger - wdb_ - Web Debugger
- `Eric IDE`_ (Available as a plugin) - `Eric IDE`_ (Available as a plugin)
- `IPython 6.0.0+ <https://ipython.readthedocs.io/en/stable/whatsnew/version6.html>`_ - `IPython 6.0.0+ <https://ipython.readthedocs.io/en/stable/whatsnew/version6.html>`_
- `xonsh shell <https://xon.sh/contents.html>`_ has `jedi extension <https://xon.sh/xontribs.html#jedi>`_
and many more! and many more!
There are a few language servers that use Jedi:
- `jedi-language-server <https://github.com/pappasam/jedi-language-server>`_
- `python-language-server <https://github.com/palantir/python-language-server>`_
- `anakin-language-server <https://github.com/muffinmad/anakin-language-server>`_
Here are some pictures taken from jedi-vim_: Here are some pictures taken from jedi-vim_:
.. image:: https://github.com/davidhalter/jedi/raw/master/docs/_screenshots/screenshot_complete.png .. image:: https://github.com/davidhalter/jedi/raw/master/docs/_screenshots/screenshot_complete.png

View File

@@ -3,11 +3,21 @@
Using Jedi Using Jedi
========== ==========
|jedi| is can be used with a variety of plugins and software. It is also possible |jedi| is can be used with a variety of :ref:`plugins <editor-plugins>`,
to use |jedi| in the :ref:`Python shell or with IPython <repl-completion>`. `language servers <language-servers>` and other software.
It is also possible to use |jedi| in the :ref:`Python shell or with IPython
<repl-completion>`.
Below you can also find a list of :ref:`recipes for type hinting <recipes>`. Below you can also find a list of :ref:`recipes for type hinting <recipes>`.
.. _language-servers:
Language Servers
--------------
- `jedi-language-server <https://github.com/pappasam/jedi-language-server>`_
- `python-language-server <https://github.com/palantir/python-language-server>`_
- `anakin-language-server <https://github.com/muffinmad/anakin-language-server>`_
.. _editor-plugins: .. _editor-plugins:
@@ -83,6 +93,16 @@ Web Debugger
- wdb_ - wdb_
xonsh shell
~~~~~~~~~~~
Jedi is a preinstalled extension in `xonsh shell <https://xon.sh/contents.html>`_.
Run the following command to enable:
::
xontrib load jedi
and many more! and many more!
.. _repl-completion: .. _repl-completion:

View File

@@ -49,7 +49,7 @@ from jedi.inference.utils import to_list
sys.setrecursionlimit(3000) sys.setrecursionlimit(3000)
class Script(object): class Script:
""" """
A Script is the base for completions, goto or whatever you want to do with A Script is the base for completions, goto or whatever you want to do with
Jedi. The counter part of this class is :class:`Interpreter`, which works Jedi. The counter part of this class is :class:`Interpreter`, which works
@@ -122,7 +122,7 @@ class Script(object):
self._module_node, code = self._inference_state.parse_and_get_code( self._module_node, code = self._inference_state.parse_and_get_code(
code=code, code=code,
path=self.path, path=self.path,
use_latest_grammar=path and path.suffix == 'pyi', use_latest_grammar=path and path.suffix == '.pyi',
cache=False, # No disk cache, because the current script often changes. cache=False, # No disk cache, because the current script often changes.
diff_cache=settings.fast_parser, diff_cache=settings.fast_parser,
cache_path=settings.cache_directory, cache_path=settings.cache_directory,
@@ -157,6 +157,7 @@ class Script(object):
# We are in a stub file. Try to load the stub properly. # We are in a stub file. Try to load the stub properly.
stub_module = load_proper_stub_module( stub_module = load_proper_stub_module(
self._inference_state, self._inference_state,
self._inference_state.latest_grammar,
file_io, file_io,
names, names,
self._module_node self._module_node
@@ -234,6 +235,11 @@ class Script(object):
leaf = self._module_node.get_leaf_for_position(pos) leaf = self._module_node.get_leaf_for_position(pos)
if leaf is None or leaf.type == 'string': if leaf is None or leaf.type == 'string':
return [] return []
if leaf.end_pos == (line, column) and leaf.type == 'operator':
next_ = leaf.get_next_leaf()
if next_.start_pos == leaf.end_pos \
and next_.type in ('number', 'string', 'keyword'):
leaf = next_
context = self._get_module_context().create_context(leaf) context = self._get_module_context().create_context(leaf)

View File

@@ -14,18 +14,18 @@ These classes are the much biggest part of the API, because they contain
the interesting information about all operations. the interesting information about all operations.
""" """
import re import re
from pathlib import Path
from typing import Optional from typing import Optional
from parso.python.tree import search_ancestor from parso.tree import search_ancestor
from jedi import settings from jedi import settings
from jedi import debug from jedi import debug
from jedi.inference.utils import unite from jedi.inference.utils import unite
from jedi.cache import memoize_method from jedi.cache import memoize_method
from jedi.inference import imports
from jedi.inference.imports import ImportName
from jedi.inference.compiled.mixed import MixedName from jedi.inference.compiled.mixed import MixedName
from jedi.inference.gradual.typeshed import StubModuleValue from jedi.inference.names import ImportName, SubModuleName
from jedi.inference.gradual.stub_value import StubModuleValue
from jedi.inference.gradual.conversion import convert_names, convert_values from jedi.inference.gradual.conversion import convert_names, convert_values
from jedi.inference.base_value import ValueSet from jedi.inference.base_value import ValueSet
from jedi.api.keywords import KeywordName from jedi.api.keywords import KeywordName
@@ -53,7 +53,7 @@ def _values_to_definitions(values):
return [Name(c.inference_state, c.name) for c in values] return [Name(c.inference_state, c.name) for c in values]
class BaseName(object): class BaseName:
""" """
The base class for all definitions, completions and signatures. The base class for all definitions, completions and signatures.
""" """
@@ -92,17 +92,15 @@ class BaseName(object):
return self._name.get_root_context() return self._name.get_root_context()
@property @property
def module_path(self) -> Optional[str]: def module_path(self) -> Optional[Path]:
""" """
Shows the file path of a module. e.g. ``/usr/lib/python3.9/os.py`` Shows the file path of a module. e.g. ``/usr/lib/python3.9/os.py``
:rtype: str or None
""" """
module = self._get_module_context() module = self._get_module_context()
if module.is_stub() or not module.is_compiled(): if module.is_stub() or not module.is_compiled():
# Compiled modules should not return a module path even if they # Compiled modules should not return a module path even if they
# have one. # have one.
path = self._get_module_context().py__file__() path: Optional[Path] = self._get_module_context().py__file__()
if path is not None: if path is not None:
return path return path
@@ -185,7 +183,7 @@ class BaseName(object):
tree_name.is_definition(): tree_name.is_definition():
resolve = True resolve = True
if isinstance(self._name, imports.SubModuleName) or resolve: if isinstance(self._name, SubModuleName) or resolve:
for value in self._name.infer(): for value in self._name.infer():
return value.api_type return value.api_type
return self._name.api_type return self._name.api_type
@@ -720,6 +718,24 @@ class Completion(BaseName):
return super().type return super().type
def get_completion_prefix_length(self):
"""
Returns the length of the prefix being completed.
For example, completing ``isinstance``::
isinstan# <-- Cursor is here
would return 8, because len('isinstan') == 8.
Assuming the following function definition::
def foo(param=0):
pass
completing ``foo(par`` would return 3.
"""
return self._like_name_length
def __repr__(self): def __repr__(self):
return '<%s: %s>' % (type(self).__name__, self._name.get_public_name()) return '<%s: %s>' % (type(self).__name__, self._name.get_public_name())

View File

@@ -1,7 +1,13 @@
_cache = {} from typing import Dict, Tuple, Callable
CacheValues = Tuple[str, str, str]
CacheValuesCallback = Callable[[], CacheValues]
def save_entry(module_name, name, cache): _cache: Dict[str, Dict[str, CacheValues]] = {}
def save_entry(module_name: str, name: str, cache: CacheValues) -> None:
try: try:
module_cache = _cache[module_name] module_cache = _cache[module_name]
except KeyError: except KeyError:
@@ -9,8 +15,8 @@ def save_entry(module_name, name, cache):
module_cache[name] = cache module_cache[name] = cache
def _create_get_from_cache(number): def _create_get_from_cache(number: int) -> Callable[[str, str, CacheValuesCallback], str]:
def _get_from_cache(module_name, name, get_cache_values): def _get_from_cache(module_name: str, name: str, get_cache_values: CacheValuesCallback) -> str:
try: try:
return _cache[module_name][name][number] return _cache[module_name][name][number]
except KeyError: except KeyError:

View File

@@ -30,7 +30,7 @@ class InvalidPythonEnvironment(Exception):
""" """
class _BaseEnvironment(object): class _BaseEnvironment:
@memoize_method @memoize_method
def get_grammar(self): def get_grammar(self):
version_string = '%s.%s' % (self.version_info.major, self.version_info.minor) version_string = '%s.%s' % (self.version_info.major, self.version_info.minor)
@@ -121,7 +121,7 @@ class Environment(_BaseEnvironment):
return self._get_subprocess().get_sys_path() return self._get_subprocess().get_sys_path()
class _SameEnvironmentMixin(object): class _SameEnvironmentMixin:
def __init__(self): def __init__(self):
self._start_executable = self.executable = sys.executable self._start_executable = self.executable = sys.executable
self.path = sys.prefix self.path = sys.prefix
@@ -384,7 +384,8 @@ def _get_executable_path(path, safe=True):
def _get_executables_from_windows_registry(version): def _get_executables_from_windows_registry(version):
import winreg # https://github.com/python/typeshed/pull/3794 adds winreg
import winreg # type: ignore[import]
# TODO: support Python Anaconda. # TODO: support Python Anaconda.
sub_keys = [ sub_keys = [

View File

@@ -8,7 +8,7 @@ def parso_to_jedi_errors(grammar, module_node):
return [SyntaxError(e) for e in grammar.iter_errors(module_node)] return [SyntaxError(e) for e in grammar.iter_errors(module_node)]
class SyntaxError(object): class SyntaxError:
""" """
Syntax errors are generated by :meth:`.Script.get_syntax_errors`. Syntax errors are generated by :meth:`.Script.get_syntax_errors`.
""" """

View File

@@ -203,7 +203,7 @@ def filter_follow_imports(names, follow_builtin_imports=False):
yield name yield name
class CallDetails(object): class CallDetails:
def __init__(self, bracket_leaf, children, position): def __init__(self, bracket_leaf, children, position):
['bracket_leaf', 'call_index', 'keyword_name_str'] ['bracket_leaf', 'call_index', 'keyword_name_str']
self.bracket_leaf = bracket_leaf self.bracket_leaf = bracket_leaf

View File

@@ -17,7 +17,7 @@ def _create(inference_state, obj):
) )
class NamespaceObject(object): class NamespaceObject:
def __init__(self, dct): def __init__(self, dct):
self.__dict__ = dct self.__dict__ = dct

View File

@@ -1,9 +1,16 @@
import pydoc import pydoc
from contextlib import suppress from contextlib import suppress
from typing import Dict, Optional
from jedi.inference.names import AbstractArbitraryName from jedi.inference.names import AbstractArbitraryName
from pydoc_data import topics as pydoc_topics try:
# https://github.com/python/typeshed/pull/4351 adds pydoc_data
from pydoc_data import topics # type: ignore[import]
pydoc_topics: Optional[Dict[str, str]] = topics.topics
except ImportError:
# Python 3.6.8 embeddable does not have pydoc_data.
pydoc_topics = None
class KeywordName(AbstractArbitraryName): class KeywordName(AbstractArbitraryName):
@@ -40,6 +47,6 @@ def imitate_pydoc(string):
return '' return ''
try: try:
return pydoc_topics.topics[label].strip() if pydoc_topics else '' return pydoc_topics[label].strip() if pydoc_topics else ''
except KeyError: except KeyError:
return '' return ''

View File

@@ -56,7 +56,7 @@ def _remove_duplicates_from_path(path):
yield p yield p
class Project(object): class Project:
""" """
Projects are a simple way to manage Python folders and define how Jedi does Projects are a simple way to manage Python folders and define how Jedi does
import resolution. It is mostly used as a parameter to :class:`.Script`. import resolution. It is mostly used as a parameter to :class:`.Script`.
@@ -152,6 +152,29 @@ class Project(object):
""" """
return self._path return self._path
@property
def sys_path(self):
"""
The sys path provided to this project. This can be None and in that
case will be auto generated.
"""
return self._sys_path
@property
def smart_sys_path(self):
"""
If the sys path is going to be calculated in a smart way, where
additional paths are added.
"""
return self._smart_sys_path
@property
def load_unsafe_extensions(self):
"""
Wheter the project loads unsafe extensions.
"""
return self._load_unsafe_extensions
@inference_state_as_method_param_cache() @inference_state_as_method_param_cache()
def _get_base_sys_path(self, inference_state): def _get_base_sys_path(self, inference_state):
# The sys path has not been set explicitly. # The sys path has not been set explicitly.
@@ -343,8 +366,11 @@ class Project(object):
def _is_potential_project(path): def _is_potential_project(path):
for name in _CONTAINS_POTENTIAL_PROJECT: for name in _CONTAINS_POTENTIAL_PROJECT:
if path.joinpath(name).exists(): try:
return True if path.joinpath(name).exists():
return True
except OSError:
continue
return False return False

View File

@@ -12,7 +12,7 @@ EXPRESSION_PARTS = (
).split() ).split()
class ChangedFile(object): class ChangedFile:
def __init__(self, inference_state, from_path, to_path, def __init__(self, inference_state, from_path, to_path,
module_node, node_to_str_map): module_node, node_to_str_map):
self._inference_state = inference_state self._inference_state = inference_state
@@ -72,7 +72,7 @@ class ChangedFile(object):
return '<%s: %s>' % (self.__class__.__name__, self._from_path) return '<%s: %s>' % (self.__class__.__name__, self._from_path)
class Refactoring(object): class Refactoring:
def __init__(self, inference_state, file_to_node_changes, renames=()): def __init__(self, inference_state, file_to_node_changes, renames=()):
self._inference_state = inference_state self._inference_state = inference_state
self._renames = renames self._renames = renames

View File

@@ -13,14 +13,15 @@ these variables are being cleaned after every API usage.
""" """
import time import time
from functools import wraps from functools import wraps
from typing import Any, Dict, Tuple
from jedi import settings from jedi import settings
from parso.cache import parser_cache from parso.cache import parser_cache
_time_caches = {} _time_caches: Dict[str, Dict[Any, Tuple[float, Any]]] = {}
def clear_time_caches(delete_all=False): def clear_time_caches(delete_all: bool = False) -> None:
""" Jedi caches many things, that should be completed after each completion """ Jedi caches many things, that should be completed after each completion
finishes. finishes.

View File

@@ -1,6 +1,7 @@
import os import os
import time import time
from contextlib import contextmanager from contextlib import contextmanager
from typing import Callable, Optional
_inited = False _inited = False
@@ -20,7 +21,7 @@ try:
raise ImportError raise ImportError
else: else:
# Use colorama for nicer console output. # Use colorama for nicer console output.
from colorama import Fore, init from colorama import Fore, init # type: ignore[import]
from colorama import initialise from colorama import initialise
def _lazy_colorama_init(): # noqa: F811 def _lazy_colorama_init(): # noqa: F811
@@ -45,7 +46,7 @@ try:
_inited = True _inited = True
except ImportError: except ImportError:
class Fore(object): class Fore: # type: ignore[no-redef]
RED = '' RED = ''
GREEN = '' GREEN = ''
YELLOW = '' YELLOW = ''
@@ -62,7 +63,7 @@ enable_warning = False
enable_notice = False enable_notice = False
# callback, interface: level, str # callback, interface: level, str
debug_function = None debug_function: Optional[Callable[[str, str], None]] = None
_debug_indent = 0 _debug_indent = 0
_start_time = time.time() _start_time = time.time()

View File

@@ -3,7 +3,7 @@ import os
from parso import file_io from parso import file_io
class AbstractFolderIO(object): class AbstractFolderIO:
def __init__(self, path): def __init__(self, path):
self.path = path self.path = path
@@ -57,7 +57,7 @@ class FolderIO(AbstractFolderIO):
del dirs[i] del dirs[i]
class FileIOFolderMixin(object): class FileIOFolderMixin:
def get_parent_folder(self): def get_parent_folder(self):
return FolderIO(os.path.dirname(self.path)) return FolderIO(os.path.dirname(self.path))

View File

@@ -81,7 +81,7 @@ from jedi.inference.imports import follow_error_node_imports_if_possible
from jedi.plugins import plugin_manager from jedi.plugins import plugin_manager
class InferenceState(object): class InferenceState:
def __init__(self, project, environment=None, script_path=None): def __init__(self, project, environment=None, script_path=None):
if environment is None: if environment is None:
environment = project.get_environment() environment = project.get_environment()
@@ -120,14 +120,15 @@ class InferenceState(object):
debug.dbg('execute result: %s in %s', value_set, value) debug.dbg('execute result: %s in %s', value_set, value)
return value_set return value_set
@property # mypy doesn't suppport decorated propeties (https://github.com/python/mypy/issues/1362)
@property # type: ignore[misc]
@inference_state_function_cache() @inference_state_function_cache()
def builtins_module(self): def builtins_module(self):
module_name = 'builtins' module_name = 'builtins'
builtins_module, = self.import_module((module_name,), sys_path=()) builtins_module, = self.import_module((module_name,), sys_path=())
return builtins_module return builtins_module
@property @property # type: ignore[misc]
@inference_state_function_cache() @inference_state_function_cache()
def typing_module(self): def typing_module(self):
typing_module, = self.import_module(('typing',)) typing_module, = self.import_module(('typing',))
@@ -169,6 +170,8 @@ class InferenceState(object):
return tree_name_to_values(self, context, name) return tree_name_to_values(self, context, name)
elif type_ == 'param': elif type_ == 'param':
return context.py__getattribute__(name.value, position=name.end_pos) return context.py__getattribute__(name.value, position=name.end_pos)
elif type_ == 'namedexpr_test':
return context.infer_node(def_)
else: else:
result = follow_error_node_imports_if_possible(context, name) result = follow_error_node_imports_if_possible(context, name)
if result is not None: if result is not None:

View File

@@ -26,7 +26,7 @@ CODES = {
} }
class Error(object): class Error:
def __init__(self, name, module_path, start_pos, message=None): def __init__(self, name, module_path, start_pos, message=None):
self.path = module_path self.path = module_path
self._start_pos = start_pos self._start_pos = start_pos

View File

@@ -124,7 +124,7 @@ def _parse_argument_clinic(string):
allow_kwargs = True allow_kwargs = True
class _AbstractArgumentsMixin(object): class _AbstractArgumentsMixin:
def unpack(self, funcdef=None): def unpack(self, funcdef=None):
raise NotImplementedError raise NotImplementedError

View File

@@ -22,7 +22,7 @@ from jedi.cache import memoize_method
sentinel = object() sentinel = object()
class HelperValueMixin(object): class HelperValueMixin:
def get_root_context(self): def get_root_context(self):
value = self value = self
if value.parent_context is None: if value.parent_context is None:
@@ -111,7 +111,7 @@ class HelperValueMixin(object):
.py__getattribute__('__anext__').execute_with_values() .py__getattribute__('__anext__').execute_with_values()
.py__getattribute__('__await__').execute_with_values() .py__getattribute__('__await__').execute_with_values()
.py__stop_iteration_returns() .py__stop_iteration_returns()
) # noqa ) # noqa: E124
]) ])
return self.py__iter__(contextualized_node) return self.py__iter__(contextualized_node)
@@ -363,7 +363,7 @@ class TreeValue(Value):
return '<%s: %s>' % (self.__class__.__name__, self.tree_node) return '<%s: %s>' % (self.__class__.__name__, self.tree_node)
class ContextualizedNode(object): class ContextualizedNode:
def __init__(self, context, node): def __init__(self, context, node):
self.context = context self.context = context
self.node = node self.node = node
@@ -405,7 +405,7 @@ def _getitem(value, index_values, contextualized_node):
return result return result
class ValueSet(object): class ValueSet:
def __init__(self, iterable): def __init__(self, iterable):
self._set = frozenset(iterable) self._set = frozenset(iterable)
for value in iterable: for value in iterable:

View File

@@ -1,3 +1,6 @@
# This file also re-exports symbols for wider use. We configure mypy and flake8
# to be aware that this file does this.
from jedi.inference.compiled.value import CompiledValue, CompiledName, \ from jedi.inference.compiled.value import CompiledValue, CompiledName, \
CompiledValueFilter, CompiledValueName, create_from_access_path CompiledValueFilter, CompiledValueName, create_from_access_path
from jedi.inference.base_value import LazyValueWrapper from jedi.inference.base_value import LazyValueWrapper

View File

@@ -1,5 +1,6 @@
import inspect import inspect
import types import types
import traceback
import sys import sys
import operator as op import operator as op
from collections import namedtuple from collections import namedtuple
@@ -117,13 +118,18 @@ def load_module(inference_state, dotted_name, sys_path):
__import__(dotted_name) __import__(dotted_name)
except ImportError: except ImportError:
# If a module is "corrupt" or not really a Python module or whatever. # If a module is "corrupt" or not really a Python module or whatever.
print('Module %s not importable in path %s.' % (dotted_name, sys_path), file=sys.stderr) warnings.warn(
"Module %s not importable in path %s." % (dotted_name, sys_path),
UserWarning,
stacklevel=2,
)
return None return None
except Exception: except Exception:
# Since __import__ pretty much makes code execution possible, just # Since __import__ pretty much makes code execution possible, just
# catch any error here and print it. # catch any error here and print it.
import traceback warnings.warn(
print("Cannot import:\n%s" % traceback.format_exc(), file=sys.stderr) "Cannot import:\n%s" % traceback.format_exc(), UserWarning, stacklevel=2
)
return None return None
finally: finally:
sys.path = temp sys.path = temp
@@ -134,7 +140,7 @@ def load_module(inference_state, dotted_name, sys_path):
return create_access_path(inference_state, module) return create_access_path(inference_state, module)
class AccessPath(object): class AccessPath:
def __init__(self, accesses): def __init__(self, accesses):
self.accesses = accesses self.accesses = accesses
@@ -156,7 +162,7 @@ def get_api_type(obj):
return 'instance' return 'instance'
class DirectObjectAccess(object): class DirectObjectAccess:
def __init__(self, inference_state, obj): def __init__(self, inference_state, obj):
self._inference_state = inference_state self._inference_state = inference_state
self._obj = obj self._obj = obj

View File

@@ -3,7 +3,7 @@ Used only for REPL Completion.
""" """
import inspect import inspect
import os from pathlib import Path
from jedi.parser_utils import get_cached_code_lines from jedi.parser_utils import get_cached_code_lines
@@ -190,8 +190,16 @@ def _find_syntax_node_name(inference_state, python_object):
except TypeError: except TypeError:
# The type might not be known (e.g. class_with_dict.__weakref__) # The type might not be known (e.g. class_with_dict.__weakref__)
return None return None
if path is None or not os.path.exists(path): path = None if path is None else Path(path)
# The path might not exist or be e.g. <stdin>. try:
if path is None or not path.exists():
# The path might not exist or be e.g. <stdin>.
return None
except OSError:
# Might raise an OSError on Windows:
#
# [WinError 123] The filename, directory name, or volume label
# syntax is incorrect: '<string>'
return None return None
file_io = FileIO(path) file_io = FileIO(path)

View File

@@ -29,19 +29,19 @@ _MAIN_PATH = os.path.join(os.path.dirname(__file__), '__main__.py')
PICKLE_PROTOCOL = 4 PICKLE_PROTOCOL = 4
class _GeneralizedPopen(subprocess.Popen): def _GeneralizedPopen(*args, **kwargs):
def __init__(self, *args, **kwargs): if os.name == 'nt':
if os.name == 'nt': try:
try: # Was introduced in Python 3.7.
# Was introduced in Python 3.7. CREATE_NO_WINDOW = subprocess.CREATE_NO_WINDOW
CREATE_NO_WINDOW = subprocess.CREATE_NO_WINDOW except AttributeError:
except AttributeError: CREATE_NO_WINDOW = 0x08000000
CREATE_NO_WINDOW = 0x08000000 kwargs['creationflags'] = CREATE_NO_WINDOW
kwargs['creationflags'] = CREATE_NO_WINDOW # The child process doesn't need file descriptors except 0, 1, 2.
# The child process doesn't need file descriptors except 0, 1, 2. # This is unix only.
# This is unix only. kwargs['close_fds'] = 'posix' in sys.builtin_module_names
kwargs['close_fds'] = 'posix' in sys.builtin_module_names
super().__init__(*args, **kwargs) return subprocess.Popen(*args, **kwargs)
def _enqueue_output(out, queue_): def _enqueue_output(out, queue_):
@@ -81,7 +81,7 @@ def _cleanup_process(process, thread):
pass pass
class _InferenceStateProcess(object): class _InferenceStateProcess:
def __init__(self, inference_state): def __init__(self, inference_state):
self._inference_state_weakref = weakref.ref(inference_state) self._inference_state_weakref = weakref.ref(inference_state)
self._inference_state_id = id(inference_state) self._inference_state_id = id(inference_state)
@@ -162,7 +162,7 @@ class InferenceStateSubprocess(_InferenceStateProcess):
self._compiled_subprocess.delete_inference_state(self._inference_state_id) self._compiled_subprocess.delete_inference_state(self._inference_state_id)
class CompiledSubprocess(object): class CompiledSubprocess:
is_crashed = False is_crashed = False
def __init__(self, executable, env_vars=None): def __init__(self, executable, env_vars=None):
@@ -280,7 +280,7 @@ class CompiledSubprocess(object):
self._inference_state_deletion_queue.append(inference_state_id) self._inference_state_deletion_queue.append(inference_state_id)
class Listener(object): class Listener:
def __init__(self): def __init__(self):
self._inference_states = {} self._inference_states = {}
# TODO refactor so we don't need to process anymore just handle # TODO refactor so we don't need to process anymore just handle
@@ -346,7 +346,7 @@ class Listener(object):
pickle_dump(result, stdout, PICKLE_PROTOCOL) pickle_dump(result, stdout, PICKLE_PROTOCOL)
class AccessHandle(object): class AccessHandle:
def __init__(self, subprocess, access, id_): def __init__(self, subprocess, access, id_):
self.access = access self.access = access
self._subprocess = subprocess self._subprocess = subprocess

View File

@@ -1,5 +1,6 @@
import os import os
import sys import sys
from importlib.abc import MetaPathFinder
from importlib.machinery import PathFinder from importlib.machinery import PathFinder
# Remove the first entry, because it's simply a directory entry that equals # Remove the first entry, because it's simply a directory entry that equals
@@ -16,7 +17,7 @@ def _get_paths():
return {'jedi': _jedi_path, 'parso': _parso_path} return {'jedi': _jedi_path, 'parso': _parso_path}
class _ExactImporter(object): class _ExactImporter(MetaPathFinder):
def __init__(self, path_dct): def __init__(self, path_dct):
self._path_dct = path_dct self._path_dct = path_dct

View File

@@ -3,6 +3,7 @@ import os
import inspect import inspect
import importlib import importlib
import warnings import warnings
from pathlib import Path
from zipimport import zipimporter from zipimport import zipimporter
from importlib.machinery import all_suffixes from importlib.machinery import all_suffixes
@@ -211,7 +212,7 @@ def _from_loader(loader, string):
if code is None: if code is None:
return None, is_package return None, is_package
if isinstance(loader, zipimporter): if isinstance(loader, zipimporter):
return ZipFileIO(module_path, code, cast_path(loader.archive)), is_package return ZipFileIO(module_path, code, Path(cast_path(loader.archive))), is_package
return KnownContentFileIO(module_path, code), is_package return KnownContentFileIO(module_path, code), is_package
@@ -229,7 +230,7 @@ def _get_source(loader, fullname):
name=fullname) name=fullname)
class ImplicitNSInfo(object): class ImplicitNSInfo:
"""Stores information returned from an implicit namespace spec""" """Stores information returned from an implicit namespace spec"""
def __init__(self, name, paths): def __init__(self, name, paths):
self.name = name self.name = name

View File

@@ -22,7 +22,7 @@ from jedi.inference.signature import BuiltinSignature
from jedi.inference.context import CompiledContext, CompiledModuleContext from jedi.inference.context import CompiledContext, CompiledModuleContext
class CheckAttribute(object): class CheckAttribute:
"""Raises :exc:`AttributeError` if the attribute X is not available.""" """Raises :exc:`AttributeError` if the attribute X is not available."""
def __init__(self, check_name=None): def __init__(self, check_name=None):
# Remove the py in front of e.g. py__call__. # Remove the py in front of e.g. py__call__.
@@ -324,8 +324,7 @@ class CompiledName(AbstractNameDefinition):
self.string_name = name self.string_name = name
def py__doc__(self): def py__doc__(self):
value, = self.infer() return self.infer_compiled_value().py__doc__()
return value.py__doc__()
def _get_qualified_names(self): def _get_qualified_names(self):
parent_qualified_names = self.parent_context.get_qualified_names() parent_qualified_names = self.parent_context.get_qualified_names()
@@ -349,16 +348,12 @@ class CompiledName(AbstractNameDefinition):
@property @property
def api_type(self): def api_type(self):
api = self.infer() return self.infer_compiled_value().api_type
# If we can't find the type, assume it is an instance variable
if not api:
return "instance"
return next(iter(api)).api_type
@memoize_method
def infer(self): def infer(self):
return ValueSet([self.infer_compiled_value()]) return ValueSet([self.infer_compiled_value()])
@memoize_method
def infer_compiled_value(self): def infer_compiled_value(self):
return create_from_name(self._inference_state, self._parent_value, self.string_name) return create_from_name(self._inference_state, self._parent_value, self.string_name)

View File

@@ -13,7 +13,7 @@ from jedi import debug
from jedi import parser_utils from jedi import parser_utils
class AbstractContext(object): class AbstractContext:
# Must be defined: inference_state and tree_node and parent_context as an attribute/property # Must be defined: inference_state and tree_node and parent_context as an attribute/property
def __init__(self, inference_state): def __init__(self, inference_state):
@@ -216,7 +216,7 @@ class ValueContext(AbstractContext):
return '%s(%s)' % (self.__class__.__name__, self._value) return '%s(%s)' % (self.__class__.__name__, self._value)
class TreeContextMixin(object): class TreeContextMixin:
def infer_node(self, node): def infer_node(self, node):
from jedi.inference.syntax_tree import infer_node from jedi.inference.syntax_tree import infer_node
return infer_node(self, node) return infer_node(self, node)

View File

@@ -50,7 +50,7 @@ def _get_numpy_doc_string_cls():
global _numpy_doc_string_cache global _numpy_doc_string_cache
if isinstance(_numpy_doc_string_cache, (ImportError, SyntaxError)): if isinstance(_numpy_doc_string_cache, (ImportError, SyntaxError)):
raise _numpy_doc_string_cache raise _numpy_doc_string_cache
from numpydoc.docscrape import NumpyDocString from numpydoc.docscrape import NumpyDocString # type: ignore[import]
_numpy_doc_string_cache = NumpyDocString _numpy_doc_string_cache = NumpyDocString
return _numpy_doc_string_cache return _numpy_doc_string_cache
@@ -113,7 +113,7 @@ def _expand_typestr(type_str):
elif type_str.startswith('{'): elif type_str.startswith('{'):
node = parse(type_str, version='3.7').children[0] node = parse(type_str, version='3.7').children[0]
if node.type == 'atom': if node.type == 'atom':
for leaf in node.children[1].children: for leaf in getattr(node.children[1], "children", []):
if leaf.type == 'number': if leaf.type == 'number':
if '.' in leaf.value: if '.' in leaf.value:
yield 'float' yield 'float'

View File

@@ -3,9 +3,11 @@ Filters are objects that you can use to filter names in different scopes. They
are needed for name resolution. are needed for name resolution.
""" """
from abc import abstractmethod from abc import abstractmethod
from typing import List, MutableMapping, Type
import weakref import weakref
from parso.tree import search_ancestor from parso.tree import search_ancestor
from parso.python.tree import Name, UsedNamesMapping
from jedi.inference import flow_analysis from jedi.inference import flow_analysis
from jedi.inference.base_value import ValueSet, ValueWrapper, \ from jedi.inference.base_value import ValueSet, ValueWrapper, \
@@ -13,12 +15,13 @@ from jedi.inference.base_value import ValueSet, ValueWrapper, \
from jedi.parser_utils import get_cached_parent_scope from jedi.parser_utils import get_cached_parent_scope
from jedi.inference.utils import to_list from jedi.inference.utils import to_list
from jedi.inference.names import TreeNameDefinition, ParamName, \ from jedi.inference.names import TreeNameDefinition, ParamName, \
AnonymousParamName, AbstractNameDefinition AnonymousParamName, AbstractNameDefinition, NameWrapper
_definition_name_cache: MutableMapping[UsedNamesMapping, List[Name]]
_definition_name_cache = weakref.WeakKeyDictionary() _definition_name_cache = weakref.WeakKeyDictionary()
class AbstractFilter(object): class AbstractFilter:
_until_position = None _until_position = None
def _filter(self, names): def _filter(self, names):
@@ -35,8 +38,8 @@ class AbstractFilter(object):
raise NotImplementedError raise NotImplementedError
class FilterWrapper(object): class FilterWrapper:
name_wrapper_class = None name_wrapper_class: Type[NameWrapper]
def __init__(self, wrapped_filter): def __init__(self, wrapped_filter):
self._wrapped_filter = wrapped_filter self._wrapped_filter = wrapped_filter
@@ -229,7 +232,7 @@ class DictFilter(AbstractFilter):
return '<%s: for {%s}>' % (self.__class__.__name__, keys) return '<%s: for {%s}>' % (self.__class__.__name__, keys)
class MergedFilter(object): class MergedFilter:
def __init__(self, *filters): def __init__(self, *filters):
self._filters = filters self._filters = filters
@@ -320,7 +323,7 @@ class _OverwriteMeta(type):
cls.overwritten_methods = base_dct cls.overwritten_methods = base_dct
class _AttributeOverwriteMixin(object): class _AttributeOverwriteMixin:
def get_filters(self, *args, **kwargs): def get_filters(self, *args, **kwargs):
yield SpecialMethodFilter(self, self.overwritten_methods, self._wrapped_value) yield SpecialMethodFilter(self, self.overwritten_methods, self._wrapped_value)
yield from self._wrapped_value.get_filters(*args, **kwargs) yield from self._wrapped_value.get_filters(*args, **kwargs)

View File

@@ -1,12 +1,14 @@
from typing import Dict, Optional
from jedi.parser_utils import get_flow_branch_keyword, is_scope, get_parent_scope from jedi.parser_utils import get_flow_branch_keyword, is_scope, get_parent_scope
from jedi.inference.recursion import execution_allowed from jedi.inference.recursion import execution_allowed
from jedi.inference.helpers import is_big_annoying_library from jedi.inference.helpers import is_big_annoying_library
class Status(object): class Status:
lookup_table = {} lookup_table: Dict[Optional[bool], 'Status'] = {}
def __init__(self, value, name): def __init__(self, value: Optional[bool], name: str) -> None:
self._value = value self._value = value
self._name = name self._name = name
Status.lookup_table[value] = self Status.lookup_table[value] = self

View File

@@ -53,8 +53,10 @@ def _infer_annotation_string(context, string, index=None):
value_set = context.infer_node(node) value_set = context.infer_node(node)
if index is not None: if index is not None:
value_set = value_set.filter( value_set = value_set.filter(
lambda value: value.array_type == 'tuple' # noqa lambda value: (
and len(list(value.py__iter__())) >= index value.array_type == 'tuple'
and len(list(value.py__iter__())) >= index
)
).py__simple_getitem__(index) ).py__simple_getitem__(index)
return value_set return value_set

View File

@@ -37,7 +37,7 @@ class _BoundTypeVarName(AbstractNameDefinition):
return '<%s %s -> %s>' % (self.__class__.__name__, self.py__name__(), self._value_set) return '<%s %s -> %s>' % (self.__class__.__name__, self.py__name__(), self._value_set)
class _TypeVarFilter(object): class _TypeVarFilter:
""" """
A filter for all given variables in a class. A filter for all given variables in a class.
@@ -246,7 +246,7 @@ class GenericClass(DefineGenericBaseClass, ClassMixin):
return type_var_dict return type_var_dict
class _LazyGenericBaseClass(object): class _LazyGenericBaseClass:
def __init__(self, class_value, lazy_base_class, generics_manager): def __init__(self, class_value, lazy_base_class, generics_manager):
self._class_value = class_value self._class_value = class_value
self._lazy_base_class = lazy_base_class self._lazy_base_class = lazy_base_class

View File

@@ -23,7 +23,7 @@ def _resolve_forward_references(context, value_set):
yield value yield value
class _AbstractGenericManager(object): class _AbstractGenericManager:
def get_index_and_execute(self, index): def get_index_and_execute(self, index):
try: try:
return self[index].execute_annotation() return self[index].execute_annotation()

View File

@@ -2,6 +2,7 @@ import os
import re import re
from functools import wraps from functools import wraps
from collections import namedtuple from collections import namedtuple
from typing import Dict, Mapping, Tuple
from pathlib import Path from pathlib import Path
from jedi import settings from jedi import settings
@@ -74,7 +75,7 @@ def _get_typeshed_directories(version_info):
yield PathInfo(str(base_path.joinpath(check_version)), is_third_party) yield PathInfo(str(base_path.joinpath(check_version)), is_third_party)
_version_cache = {} _version_cache: Dict[Tuple[int, int], Mapping[str, PathInfo]] = {}
def _cache_stub_file_map(version_info): def _cache_stub_file_map(version_info):
@@ -278,8 +279,8 @@ def _try_to_load_stub_from_file(inference_state, python_value_set, file_io, impo
return None return None
else: else:
return create_stub_module( return create_stub_module(
inference_state, python_value_set, stub_module_node, file_io, inference_state, inference_state.latest_grammar, python_value_set,
import_names stub_module_node, file_io, import_names
) )
@@ -293,7 +294,8 @@ def parse_stub_module(inference_state, file_io):
) )
def create_stub_module(inference_state, python_value_set, stub_module_node, file_io, import_names): def create_stub_module(inference_state, grammar, python_value_set,
stub_module_node, file_io, import_names):
if import_names == ('typing',): if import_names == ('typing',):
module_cls = TypingModuleWrapper module_cls = TypingModuleWrapper
else: else:
@@ -305,7 +307,7 @@ def create_stub_module(inference_state, python_value_set, stub_module_node, file
string_names=import_names, string_names=import_names,
# The code was loaded with latest_grammar, so use # The code was loaded with latest_grammar, so use
# that. # that.
code_lines=get_cached_code_lines(inference_state.latest_grammar, file_io.path), code_lines=get_cached_code_lines(grammar, file_io.path),
is_package=file_name == '__init__.pyi', is_package=file_name == '__init__.pyi',
) )
return stub_module_value return stub_module_value

View File

@@ -1,10 +1,9 @@
import os
from pathlib import Path from pathlib import Path
from jedi.inference.gradual.typeshed import TYPESHED_PATH, create_stub_module from jedi.inference.gradual.typeshed import TYPESHED_PATH, create_stub_module
def load_proper_stub_module(inference_state, file_io, import_names, module_node): def load_proper_stub_module(inference_state, grammar, file_io, import_names, module_node):
""" """
This function is given a random .pyi file and should return the proper This function is given a random .pyi file and should return the proper
module. module.
@@ -28,7 +27,8 @@ def load_proper_stub_module(inference_state, file_io, import_names, module_node)
actual_value_set = inference_state.import_module(import_names, prefer_stubs=False) actual_value_set = inference_state.import_module(import_names, prefer_stubs=False)
stub = create_stub_module( stub = create_stub_module(
inference_state, actual_value_set, module_node, file_io, import_names inference_state, grammar, actual_value_set,
module_node, file_io, import_names
) )
inference_state.stub_module_cache[import_names] = stub inference_state.stub_module_cache[import_names] = stub
return stub return stub

View File

@@ -32,7 +32,7 @@ from jedi.inference.compiled.subprocess.functions import ImplicitNSInfo
from jedi.plugins import plugin_manager from jedi.plugins import plugin_manager
class ModuleCache(object): class ModuleCache:
def __init__(self): def __init__(self):
self._name_cache = {} self._name_cache = {}
@@ -150,7 +150,7 @@ def _level_to_base_import_path(project_path, directory, level):
return None, directory return None, directory
class Importer(object): class Importer:
def __init__(self, inference_state, import_path, module_context, level=0): def __init__(self, inference_state, import_path, module_context, level=0):
""" """
An implementation similar to ``__import__``. Use `follow` An implementation similar to ``__import__``. Use `follow`
@@ -498,8 +498,8 @@ def load_module_from_path(inference_state, file_io, import_names=None, is_packag
values = NO_VALUES values = NO_VALUES
return create_stub_module( return create_stub_module(
inference_state, values, parse_stub_module(inference_state, file_io), inference_state, inference_state.latest_grammar, values,
file_io, import_names parse_stub_module(inference_state, file_io), file_io, import_names
) )
else: else:
module = _load_python_module( module = _load_python_module(

View File

@@ -2,7 +2,7 @@ from jedi.inference.base_value import ValueSet, NO_VALUES
from jedi.common import monkeypatch from jedi.common import monkeypatch
class AbstractLazyValue(object): class AbstractLazyValue:
def __init__(self, data, min=1, max=1): def __init__(self, data, min=1, max=1):
self.data = data self.data = data
self.min = min self.min = min

View File

@@ -1,11 +1,13 @@
from abc import abstractmethod from abc import abstractmethod
from inspect import Parameter from inspect import Parameter
from typing import Optional, Tuple
from parso.tree import search_ancestor from parso.tree import search_ancestor
from jedi.parser_utils import find_statement_documentation, clean_scope_docstring from jedi.parser_utils import find_statement_documentation, clean_scope_docstring
from jedi.inference.utils import unite from jedi.inference.utils import unite
from jedi.inference.base_value import ValueSet, NO_VALUES from jedi.inference.base_value import ValueSet, NO_VALUES
from jedi.inference.cache import inference_state_method_cache
from jedi.inference import docstrings from jedi.inference import docstrings
from jedi.cache import memoize_method from jedi.cache import memoize_method
from jedi.inference.helpers import deep_ast_copy, infer_call_of_leaf from jedi.inference.helpers import deep_ast_copy, infer_call_of_leaf
@@ -23,9 +25,9 @@ def _merge_name_docs(names):
return doc return doc
class AbstractNameDefinition(object): class AbstractNameDefinition:
start_pos = None start_pos: Optional[Tuple[int, int]] = None
string_name = None string_name: str
parent_context = None parent_context = None
tree_name = None tree_name = None
is_value_name = True is_value_name = True
@@ -223,7 +225,7 @@ class AbstractTreeName(AbstractNameDefinition):
return self.tree_name.start_pos return self.tree_name.start_pos
class ValueNameMixin(object): class ValueNameMixin:
def infer(self): def infer(self):
return ValueSet([self._value]) return ValueSet([self._value])
@@ -330,6 +332,12 @@ class TreeNameDefinition(AbstractTreeName):
node = node.parent node = node.parent
return indexes return indexes
@property
def inference_state(self):
# Used by the cache function below
return self.parent_context.inference_state
@inference_state_method_cache(default='')
def py__doc__(self): def py__doc__(self):
api_type = self.api_type api_type = self.api_type
if api_type in ('function', 'class'): if api_type in ('function', 'class'):
@@ -346,7 +354,7 @@ class TreeNameDefinition(AbstractTreeName):
return '' return ''
class _ParamMixin(object): class _ParamMixin:
def maybe_positional_argument(self, include_star=True): def maybe_positional_argument(self, include_star=True):
options = [Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD] options = [Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
if include_star: if include_star:
@@ -604,7 +612,7 @@ class SubModuleName(ImportName):
_level = 1 _level = 1
class NameWrapper(object): class NameWrapper:
def __init__(self, wrapped_name): def __init__(self, wrapped_name):
self._wrapped_name = wrapped_name self._wrapped_name = wrapped_name
@@ -615,7 +623,7 @@ class NameWrapper(object):
return '%s(%s)' % (self.__class__.__name__, self._wrapped_name) return '%s(%s)' % (self.__class__.__name__, self._wrapped_name)
class StubNameMixin(object): class StubNameMixin:
def py__doc__(self): def py__doc__(self):
from jedi.inference.gradual.conversion import convert_names from jedi.inference.gradual.conversion import convert_names
# Stubs are not complicated and we can just follow simple statements # Stubs are not complicated and we can just follow simple statements

View File

@@ -50,7 +50,7 @@ A function may not be executed more than this number of times recursively.
""" """
class RecursionDetector(object): class RecursionDetector:
def __init__(self): def __init__(self):
self.pushed_nodes = [] self.pushed_nodes = []
@@ -92,7 +92,7 @@ def execution_recursion_decorator(default=NO_VALUES):
return decorator return decorator
class ExecutionRecursionDetector(object): class ExecutionRecursionDetector:
""" """
Catches recursions of executions. Catches recursions of executions.
""" """

View File

@@ -5,7 +5,8 @@ from parso import python_bytes_to_unicode
from jedi.debug import dbg from jedi.debug import dbg
from jedi.file_io import KnownContentFileIO from jedi.file_io import KnownContentFileIO
from jedi.inference.imports import SubModuleName, load_module_from_path from jedi.inference.names import SubModuleName
from jedi.inference.imports import load_module_from_path
from jedi.inference.filters import ParserTreeFilter from jedi.inference.filters import ParserTreeFilter
from jedi.inference.gradual.conversion import convert_names from jedi.inference.gradual.conversion import convert_names
@@ -203,11 +204,11 @@ def recurse_find_python_folders_and_files(folder_io, except_paths=()):
# Delete folders that we don't want to iterate over. # Delete folders that we don't want to iterate over.
for file_io in file_ios: for file_io in file_ios:
path = file_io.path path = file_io.path
if path.endswith('.py') or path.endswith('.pyi'): if path.suffix in ('.py', '.pyi'):
if path not in except_paths: if path not in except_paths:
yield None, file_io yield None, file_io
if path.endswith('.gitignore'): if path.name == '.gitignore':
ignored_paths, ignored_names = \ ignored_paths, ignored_names = \
gitignored_lines(root_folder_io, file_io) gitignored_lines(root_folder_io, file_io)
except_paths |= ignored_paths except_paths |= ignored_paths

View File

@@ -5,7 +5,7 @@ from jedi import debug
from jedi import parser_utils from jedi import parser_utils
class _SignatureMixin(object): class _SignatureMixin:
def to_string(self): def to_string(self):
def param_strings(): def param_strings():
is_positional = False is_positional = False

View File

@@ -287,10 +287,11 @@ def infer_atom(context, atom):
state = context.inference_state state = context.inference_state
if atom.type == 'name': if atom.type == 'name':
# This is the first global lookup. # This is the first global lookup.
stmt = tree.search_ancestor( stmt = tree.search_ancestor(atom, 'expr_stmt', 'lambdef', 'if_stmt') or atom
atom, 'expr_stmt', 'lambdef' if stmt.type == 'if_stmt':
) or atom if not any(n.start_pos <= atom.start_pos < n.end_pos for n in stmt.get_test_nodes()):
if stmt.type == 'lambdef': stmt = atom
elif stmt.type == 'lambdef':
stmt = atom stmt = atom
position = stmt.start_pos position = stmt.start_pos
if _is_annotation_name(atom): if _is_annotation_name(atom):
@@ -753,6 +754,8 @@ def tree_name_to_values(inference_state, context, tree_name):
types = NO_VALUES types = NO_VALUES
elif typ == 'del_stmt': elif typ == 'del_stmt':
types = NO_VALUES types = NO_VALUES
elif typ == 'namedexpr_test':
types = infer_node(context, node)
else: else:
raise ValueError("Should not happen. type: %s" % typ) raise ValueError("Should not happen. type: %s" % typ)
return types return types

View File

@@ -14,8 +14,8 @@ from jedi import debug
_BUILDOUT_PATH_INSERTION_LIMIT = 10 _BUILDOUT_PATH_INSERTION_LIMIT = 10
def _abs_path(module_context, path: str): def _abs_path(module_context, str_path: str):
path = Path(path) path = Path(str_path)
if path.is_absolute(): if path.is_absolute():
return path return path
@@ -164,15 +164,18 @@ def _get_paths_from_buildout_script(inference_state, buildout_script_path):
inference_state, module_node, inference_state, module_node,
file_io=file_io, file_io=file_io,
string_names=None, string_names=None,
code_lines=get_cached_code_lines(inference_state.grammar, str(buildout_script_path)), code_lines=get_cached_code_lines(inference_state.grammar, buildout_script_path),
).as_context() ).as_context()
yield from check_sys_path_modifications(module_context) yield from check_sys_path_modifications(module_context)
def _get_parent_dir_with_file(path: Path, filename): def _get_parent_dir_with_file(path: Path, filename):
for parent in path.parents: for parent in path.parents:
if parent.joinpath(filename).is_file(): try:
return parent if parent.joinpath(filename).is_file():
return parent
except OSError:
continue
return None return None

View File

@@ -70,7 +70,7 @@ def reraise_uncaught(func):
return wrapper return wrapper
class PushBackIterator(object): class PushBackIterator:
def __init__(self, iterator): def __init__(self, iterator):
self.pushes = [] self.pushes = []
self.iterator = iterator self.iterator = iterator

View File

@@ -1,3 +1,6 @@
# Re-export symbols for wider use. We configure mypy and flake8 to be aware that
# this file does this.
from jedi.inference.value.module import ModuleValue from jedi.inference.value.module import ModuleValue
from jedi.inference.value.klass import ClassValue from jedi.inference.value.klass import ClassValue
from jedi.inference.value.function import FunctionValue, \ from jedi.inference.value.function import FunctionValue, \

View File

@@ -53,7 +53,7 @@ class FunctionAndClassBase(TreeValue):
return None return None
class FunctionMixin(object): class FunctionMixin:
api_type = 'function' api_type = 'function'
def get_filters(self, origin_scope=None): def get_filters(self, origin_scope=None):

View File

@@ -1,6 +1,6 @@
from abc import abstractproperty from abc import abstractproperty
from parso.python.tree import search_ancestor from parso.tree import search_ancestor
from jedi import debug from jedi import debug
from jedi import settings from jedi import settings
@@ -401,21 +401,10 @@ class AnonymousInstance(_BaseTreeInstance):
_arguments = None _arguments = None
class CompiledInstanceName(compiled.CompiledName): class CompiledInstanceName(NameWrapper):
def __init__(self, inference_state, instance, klass, name):
parent_value = klass.parent_context.get_value()
assert parent_value is not None, "How? Please reproduce and report"
super().__init__(
inference_state,
parent_value,
name.string_name
)
self._instance = instance
self._class_member_name = name
@iterator_to_value_set @iterator_to_value_set
def infer(self): def infer(self):
for result_value in self._class_member_name.infer(): for result_value in self._wrapped_name.infer():
if result_value.api_type == 'function': if result_value.api_type == 'function':
yield CompiledBoundMethod(result_value) yield CompiledBoundMethod(result_value)
else: else:
@@ -434,11 +423,7 @@ class CompiledInstanceClassFilter(AbstractFilter):
return self._convert(self._class_filter.values()) return self._convert(self._class_filter.values())
def _convert(self, names): def _convert(self, names):
klass = self._class_filter.compiled_value return [CompiledInstanceName(n) for n in names]
return [
CompiledInstanceName(self._instance.inference_state, self._instance, klass, n)
for n in names
]
class BoundMethod(FunctionMixin, ValueWrapper): class BoundMethod(FunctionMixin, ValueWrapper):
@@ -516,6 +501,18 @@ class SelfName(TreeNameDefinition):
def get_defining_qualified_value(self): def get_defining_qualified_value(self):
return self._instance return self._instance
def infer(self):
stmt = search_ancestor(self.tree_name, 'expr_stmt')
if stmt is not None:
if stmt.children[1].type == "annassign":
from jedi.inference.gradual.annotation import infer_annotation
values = infer_annotation(
self.parent_context, stmt.children[1].children[1]
).execute_annotation()
if values:
return values
return super().infer()
class LazyInstanceClassName(NameWrapper): class LazyInstanceClassName(NameWrapper):
def __init__(self, instance, class_member_name): def __init__(self, instance, class_member_name):

View File

@@ -19,7 +19,7 @@ from jedi.inference.context import CompForContext
from jedi.inference.value.dynamic_arrays import check_array_additions from jedi.inference.value.dynamic_arrays import check_array_additions
class IterableMixin(object): class IterableMixin:
def py__next__(self, contextualized_node=None): def py__next__(self, contextualized_node=None):
return self.py__iter__(contextualized_node) return self.py__iter__(contextualized_node)
@@ -127,7 +127,7 @@ def comprehension_from_atom(inference_state, value, atom):
) )
class ComprehensionMixin(object): class ComprehensionMixin:
@inference_state_method_cache() @inference_state_method_cache()
def _get_comp_for_context(self, parent_context, comp_for): def _get_comp_for_context(self, parent_context, comp_for):
return CompForContext(parent_context, comp_for) return CompForContext(parent_context, comp_for)
@@ -175,7 +175,7 @@ class ComprehensionMixin(object):
return "<%s of %s>" % (type(self).__name__, self._sync_comp_for_node) return "<%s of %s>" % (type(self).__name__, self._sync_comp_for_node)
class _DictMixin(object): class _DictMixin:
def _get_generics(self): def _get_generics(self):
return tuple(c_set.py__class__() for c_set in self.get_mapping_item_values()) return tuple(c_set.py__class__() for c_set in self.get_mapping_item_values())
@@ -247,7 +247,7 @@ class GeneratorComprehension(_BaseComprehension, GeneratorBase):
pass pass
class _DictKeyMixin(object): class _DictKeyMixin:
# TODO merge with _DictMixin? # TODO merge with _DictMixin?
def get_mapping_item_values(self): def get_mapping_item_values(self):
return self._dict_keys(), self._dict_values() return self._dict_keys(), self._dict_values()

View File

@@ -142,7 +142,7 @@ class ClassFilter(ParserTreeFilter):
return [name for name in names if self._access_possible(name)] return [name for name in names if self._access_possible(name)]
class ClassMixin(object): class ClassMixin:
def is_class(self): def is_class(self):
return True return True

View File

@@ -1,5 +1,6 @@
import os import os
from pathlib import Path from pathlib import Path
from typing import Optional
from jedi.inference.cache import inference_state_method_cache from jedi.inference.cache import inference_state_method_cache
from jedi.inference.names import AbstractNameDefinition, ModuleName from jedi.inference.names import AbstractNameDefinition, ModuleName
@@ -33,7 +34,7 @@ class _ModuleAttributeName(AbstractNameDefinition):
return compiled.get_string_value_set(self.parent_context.inference_state) return compiled.get_string_value_set(self.parent_context.inference_state)
class SubModuleDictMixin(object): class SubModuleDictMixin:
@inference_state_method_cache() @inference_state_method_cache()
def sub_modules_dict(self): def sub_modules_dict(self):
""" """
@@ -79,7 +80,7 @@ class ModuleMixin(SubModuleDictMixin):
def is_stub(self): def is_stub(self):
return False return False
@property @property # type: ignore[misc]
@inference_state_method_cache() @inference_state_method_cache()
def name(self): def name(self):
return self._module_name_class(self, self.string_names[-1]) return self._module_name_class(self, self.string_names[-1])
@@ -145,7 +146,7 @@ class ModuleValue(ModuleMixin, TreeValue):
) )
self.file_io = file_io self.file_io = file_io
if file_io is None: if file_io is None:
self._path = None self._path: Optional[Path] = None
else: else:
self._path = Path(file_io.path) self._path = Path(file_io.path)
self.string_names = string_names # Optional[Tuple[str, ...]] self.string_names = string_names # Optional[Tuple[str, ...]]
@@ -165,7 +166,7 @@ class ModuleValue(ModuleMixin, TreeValue):
return None return None
return '.'.join(self.string_names) return '.'.join(self.string_names)
def py__file__(self) -> Path: def py__file__(self) -> Optional[Path]:
""" """
In contrast to Python's __file__ can be None. In contrast to Python's __file__ can be None.
""" """

View File

@@ -38,7 +38,7 @@ class ImplicitNamespaceValue(Value, SubModuleDictMixin):
def get_qualified_names(self): def get_qualified_names(self):
return () return ()
@property @property # type: ignore[misc]
@inference_state_method_cache() @inference_state_method_cache()
def name(self): def name(self):
string_name = self.py__package__()[-1] string_name = self.py__package__()[-1]

View File

@@ -90,7 +90,7 @@ def get_flow_branch_keyword(flow_node, node):
first_leaf = child.get_first_leaf() first_leaf = child.get_first_leaf()
if first_leaf in _FLOW_KEYWORDS: if first_leaf in _FLOW_KEYWORDS:
keyword = first_leaf keyword = first_leaf
return 0 return None
def clean_scope_docstring(scope_node): def clean_scope_docstring(scope_node):
@@ -239,7 +239,7 @@ def get_parent_scope(node, include_flows=False):
return None # It's a module already. return None # It's a module already.
while True: while True:
if is_scope(scope) or include_flows and isinstance(scope, tree.Flow): if is_scope(scope):
if scope.type in ('classdef', 'funcdef', 'lambdef'): if scope.type in ('classdef', 'funcdef', 'lambdef'):
index = scope.children.index(':') index = scope.children.index(':')
if scope.children[index].start_pos >= node.start_pos: if scope.children[index].start_pos >= node.start_pos:
@@ -251,6 +251,14 @@ def get_parent_scope(node, include_flows=False):
scope = scope.parent scope = scope.parent
continue continue
return scope return scope
elif include_flows and isinstance(scope, tree.Flow):
# The cursor might be on `if foo`, so the parent scope will not be
# the if, but the parent of the if.
if not (scope.type == 'if_stmt'
and any(n.start_pos <= node.start_pos < n.end_pos
for n in scope.get_test_nodes())):
return scope
scope = scope.parent scope = scope.parent

View File

@@ -1,7 +1,7 @@
from functools import wraps from functools import wraps
class _PluginManager(object): class _PluginManager:
def __init__(self): def __init__(self):
self._registered_plugins = [] self._registered_plugins = []
self._cached_base_callbacks = {} self._cached_base_callbacks = {}

View File

@@ -1,6 +1,6 @@
from pathlib import Path from pathlib import Path
from parso.python.tree import search_ancestor from parso.tree import search_ancestor
from jedi.inference.cache import inference_state_method_cache from jedi.inference.cache import inference_state_method_cache
from jedi.inference.imports import load_module_from_path from jedi.inference.imports import load_module_from_path
from jedi.inference.filters import ParserTreeFilter from jedi.inference.filters import ParserTreeFilter

View File

@@ -257,7 +257,7 @@ class ReversedObject(AttributeOverwrite):
super().__init__(reversed_obj) super().__init__(reversed_obj)
self._iter_list = iter_list self._iter_list = iter_list
def py__iter__(self, contextualized_node): def py__iter__(self, contextualized_node=None):
return self._iter_list return self._iter_list
@publish_method('__next__') @publish_method('__next__')
@@ -819,7 +819,8 @@ def get_metaclass_filters(func):
and metaclass.get_root_context().py__name__() == 'enum': and metaclass.get_root_context().py__name__() == 'enum':
filter_ = ParserTreeFilter(parent_context=cls.as_context()) filter_ = ParserTreeFilter(parent_context=cls.as_context())
return [DictFilter({ return [DictFilter({
name.string_name: EnumInstance(cls, name).name for name in filter_.values() name.string_name: EnumInstance(cls, name).name
for name in filter_.values()
})] })]
return func(cls, metaclasses, is_instance) return func(cls, metaclasses, is_instance)
return wrapper return wrapper
@@ -837,6 +838,14 @@ class EnumInstance(LazyValueWrapper):
return ValueName(self, self._name.tree_name) return ValueName(self, self._name.tree_name)
def _get_wrapped_value(self): def _get_wrapped_value(self):
n = self._name.string_name
if n.startswith('__') and n.endswith('__') or self._name.api_type == 'function':
inferred = self._name.infer()
if inferred:
return next(iter(inferred))
o, = self.inference_state.builtins_module.py__getattribute__('object')
return o
value, = self._cls.execute_with_values() value, = self._cls.execute_with_values()
return value return value

View File

@@ -69,8 +69,11 @@ Adds an opening bracket after a function for completions.
# ---------------- # ----------------
if platform.system().lower() == 'windows': if platform.system().lower() == 'windows':
_cache_directory = os.path.join(os.getenv('LOCALAPPDATA') or _cache_directory = os.path.join(
os.path.expanduser('~'), 'Jedi', 'Jedi') os.getenv('LOCALAPPDATA') or os.path.expanduser('~'),
'Jedi',
'Jedi',
)
elif platform.system().lower() == 'darwin': elif platform.system().lower() == 'darwin':
_cache_directory = os.path.join('~', 'Library', 'Caches', 'Jedi') _cache_directory = os.path.join('~', 'Library', 'Caches', 'Jedi')
else: else:
@@ -98,7 +101,7 @@ parse the parts again that have changed, while reusing the rest of the syntax
tree. tree.
""" """
_cropped_file_size = 10e6 # 1 Megabyte _cropped_file_size = int(10e6) # 1 Megabyte
""" """
Jedi gets extremely slow if the file size exceed a few thousand lines. Jedi gets extremely slow if the file size exceed a few thousand lines.
To avoid getting stuck completely Jedi crops the file at some point. To avoid getting stuck completely Jedi crops the file at some point.

View File

@@ -2,7 +2,7 @@
Utilities for end-users. Utilities for end-users.
""" """
import __main__ import __main__ # type: ignore[import]
from collections import namedtuple from collections import namedtuple
import logging import logging
import traceback import traceback
@@ -65,7 +65,7 @@ def setup_readline(namespace_module=__main__, fuzzy=False):
level=logging.DEBUG level=logging.DEBUG
) )
class JediRL(object): class JediRL:
def complete(self, text, state): def complete(self, text, state):
""" """
This complete stuff is pretty weird, a generator would make This complete stuff is pretty weird, a generator would make

View File

@@ -13,7 +13,42 @@ ignore =
E721, E721,
# Line break before binary operator # Line break before binary operator
W503, W503,
# Single letter loop variables are often fine
E741,
per-file-ignores =
# Ignore apparently unused imports in files where we're (implicitly)
# re-exporting them.
jedi/__init__.py:F401
jedi/inference/compiled/__init__.py:F401
jedi/inference/value/__init__.py:F401
exclude = jedi/third_party/* .tox/* exclude = jedi/third_party/* .tox/*
[pycodestyle] [pycodestyle]
max-line-length = 100 max-line-length = 100
[mypy]
# Ensure generics are explicit about what they are (e.g: `List[str]` rather than
# just `List`)
disallow_any_generics = True
disallow_subclassing_any = True
# Avoid creating future gotchas emerging from bad typing
warn_redundant_casts = True
warn_unused_ignores = True
warn_return_any = True
warn_unused_configs = True
warn_unreachable = True
# Require values to be explicitly re-exported; this makes things easier for
# Flake8 too and avoids accidentally importing thing from the "wrong" place
# (which helps avoid circular imports)
implicit_reexport = False
strict_equality = True
[mypy-jedi,jedi.inference.compiled,jedi.inference.value,parso]
# Various __init__.py files which contain re-exports we want to implicitly make.
implicit_reexport = True

View File

@@ -32,7 +32,7 @@ setup(name='jedi',
long_description=readme, long_description=readme,
packages=find_packages(exclude=['test', 'test.*']), packages=find_packages(exclude=['test', 'test.*']),
python_requires='>=3.6', python_requires='>=3.6',
install_requires=['parso>=0.7.0,<0.8.0'], install_requires=['parso>=0.8.0,<0.9.0'],
extras_require={ extras_require={
'testing': [ 'testing': [
'pytest<6.0.0', 'pytest<6.0.0',
@@ -43,7 +43,8 @@ setup(name='jedi',
'Django<3.1', # For now pin this. 'Django<3.1', # For now pin this.
], ],
'qa': [ 'qa': [
'flake8==3.7.9', 'flake8==3.8.3',
'mypy==0.782',
], ],
}, },
package_data={'jedi': ['*.pyi', 'third_party/typeshed/LICENSE', package_data={'jedi': ['*.pyi', 'third_party/typeshed/LICENSE',

View File

@@ -44,7 +44,7 @@ Options:
--pudb Launch pudb when error is raised. --pudb Launch pudb when error is raised.
""" """
from docopt import docopt from docopt import docopt # type: ignore[import]
import json import json
import os import os

View File

@@ -1,3 +1,6 @@
# For assignment expressions / named expressions / walrus operators / whatever
# they are called.
# python >= 3.8 # python >= 3.8
b = (a:=1, a) b = (a:=1, a)
@@ -11,3 +14,39 @@ b = ('':=1,)
#? int() #? int()
b[0] b[0]
def test_assignments():
match = ''
#? str()
match
#? 8 int()
if match := 1:
#? int()
match
#? int()
match
def test_assignments2():
class Foo:
match = ''
#? str()
Foo.match
#? 13 int()
if Foo.match := 1:
#? str()
Foo.match
#? str()
Foo.match
#?
y
#? 16 str()
if y := Foo.match:
#? str()
y
#? str()
y
#? 8 str()
if z := Foo.match:
pass

View File

@@ -179,3 +179,22 @@ def argskwargs(*args: int, **kwargs: float):
next(iter(kwargs.keys())) next(iter(kwargs.keys()))
#? float() #? float()
kwargs[''] kwargs['']
class NotCalledClass:
def __init__(self, x):
self.x: int = x
self.y: int = ''
#? int()
self.x
#? int()
self.y
#? int()
self.y
self.z: int
self.z = ''
#? str() int()
self.z
self.w: float
#? float()
self.w

View File

@@ -0,0 +1 @@
from .cq import selectors

View File

@@ -0,0 +1 @@
from . import selectors

View File

@@ -0,0 +1,3 @@
import cadquery_simple as cq
cq.

View File

@@ -15,7 +15,7 @@ from test.helpers import test_dir, get_example_dir
def test_preload_modules(): def test_preload_modules():
def check_loaded(*modules): def check_loaded(*module_names):
for grammar_cache in cache.parser_cache.values(): for grammar_cache in cache.parser_cache.values():
if None in grammar_cache: if None in grammar_cache:
break break
@@ -25,9 +25,9 @@ def test_preload_modules():
if path is not None and str(path).startswith(str(typeshed.TYPESHED_PATH)) if path is not None and str(path).startswith(str(typeshed.TYPESHED_PATH))
) )
# +1 for None module (currently used) # +1 for None module (currently used)
assert len(grammar_cache) - typeshed_cache_count == len(modules) + 1 assert len(grammar_cache) - typeshed_cache_count == len(module_names) + 1
for i in modules: for i in module_names:
assert [i in k for k in grammar_cache.keys() if k is not None] assert [i in str(k) for k in grammar_cache.keys() if k is not None]
old_cache = cache.parser_cache.copy() old_cache = cache.parser_cache.copy()
cache.parser_cache.clear() cache.parser_cache.clear()
@@ -370,3 +370,35 @@ def test_multi_goto(Script):
y, = script.goto(line=4) y, = script.goto(line=4)
assert x.line == 1 assert x.line == 1
assert y.line == 2 assert y.line == 2
@pytest.mark.parametrize(
'code, column, expected', [
('str() ', 3, 'str'),
('str() ', 4, 'str'),
('str() ', 5, 'str'),
('str() ', 6, None),
('str( ) ', 6, None),
(' 1', 1, None),
('str(1) ', 3, 'str'),
('str(1) ', 4, 'int'),
('str(1) ', 5, 'int'),
('str(1) ', 6, 'str'),
('str(1) ', 7, None),
('str( 1) ', 4, 'str'),
('str( 1) ', 5, 'int'),
('str(+1) ', 4, 'str'),
('str(+1) ', 5, 'int'),
('str(1, 1.) ', 3, 'str'),
('str(1, 1.) ', 4, 'int'),
('str(1, 1.) ', 5, 'int'),
('str(1, 1.) ', 6, None),
('str(1, 1.) ', 7, 'float'),
]
)
def test_infer_after_parentheses(Script, code, column, expected):
completions = Script(code).infer(column=column)
if expected is None:
assert completions == []
else:
assert [c.name for c in completions] == [expected]

View File

@@ -121,11 +121,8 @@ def test_multiple_signatures(Script):
def test_get_signatures_whitespace(Script): def test_get_signatures_whitespace(Script):
s = dedent("""\ # note: trailing space after 'abs'
abs( s = 'abs( \ndef x():\n pass\n'
def x():
pass
""") # noqa
assert_signature(Script, s, 'abs', 0, line=1, column=5) assert_signature(Script, s, 'abs', 0, line=1, column=5)

View File

@@ -513,10 +513,14 @@ def test_added_equals_to_params(Script):
assert run('foo(bar').name_with_symbols == 'bar=' assert run('foo(bar').name_with_symbols == 'bar='
assert run('foo(bar').complete == '=' assert run('foo(bar').complete == '='
assert run('foo(bar').get_completion_prefix_length() == 3
assert run('foo(bar, baz').complete == '=' assert run('foo(bar, baz').complete == '='
assert run('foo(bar, baz').get_completion_prefix_length() == 3
assert run(' bar').name_with_symbols == 'bar' assert run(' bar').name_with_symbols == 'bar'
assert run(' bar').complete == '' assert run(' bar').complete == ''
assert run(' bar').get_completion_prefix_length() == 3
x = run('foo(bar=isins').name_with_symbols x = run('foo(bar=isins').name_with_symbols
assert run('foo(bar=isins').get_completion_prefix_length() == 5
assert x == 'isinstance' assert x == 'isinstance'

View File

@@ -621,7 +621,8 @@ def bar():
# typing is available via globals. # typing is available via globals.
({'return': 'typing.Union[str, int]'}, ['int', 'str'], ''), ({'return': 'typing.Union[str, int]'}, ['int', 'str'], ''),
({'return': 'typing.Union["str", int]'}, ['int'], ''), ({'return': 'typing.Union["str", int]'},
['int', 'str'] if sys.version_info >= (3, 9) else ['int'], ''),
({'return': 'typing.Union["str", 1]'}, [], ''), ({'return': 'typing.Union["str", 1]'}, [], ''),
({'return': 'typing.Optional[str]'}, ['NoneType', 'str'], ''), ({'return': 'typing.Optional[str]'}, ['NoneType', 'str'], ''),
({'return': 'typing.Optional[str, int]'}, [], ''), # Takes only one arg ({'return': 'typing.Optional[str, int]'}, [], ''), # Takes only one arg

View File

@@ -6,6 +6,7 @@ import pytest
from ..helpers import get_example_dir, set_cwd, root_dir, test_dir from ..helpers import get_example_dir, set_cwd, root_dir, test_dir
from jedi import Interpreter from jedi import Interpreter
from jedi.api import Project, get_default_project from jedi.api import Project, get_default_project
from jedi.api.project import _is_potential_project, _CONTAINS_POTENTIAL_PROJECT
def test_django_default_project(Script): def test_django_default_project(Script):
@@ -17,7 +18,12 @@ def test_django_default_project(Script):
) )
c, = script.complete() c, = script.complete()
assert c.name == "SomeModel" assert c.name == "SomeModel"
assert script._inference_state.project._django is True
project = script._inference_state.project
assert project._django is True
assert project.sys_path is None
assert project.smart_sys_path is True
assert project.load_unsafe_extensions is False
def test_django_default_project_of_file(Script): def test_django_default_project_of_file(Script):
@@ -155,3 +161,21 @@ def test_complete_search(Script, string, completions, all_scopes):
project = Project(test_dir) project = Project(test_dir)
defs = project.complete_search(string, all_scopes=all_scopes) defs = project.complete_search(string, all_scopes=all_scopes)
assert [d.complete for d in defs] == completions assert [d.complete for d in defs] == completions
@pytest.mark.parametrize(
'path,expected', [
(Path(__file__).parents[2], True), # The path of the project
(Path(__file__).parents[1], False), # The path of the tests, not a project
(Path.home(), None)
]
)
def test_is_potential_project(path, expected):
if expected is None:
try:
expected = _CONTAINS_POTENTIAL_PROJECT in os.listdir(path)
except OSError:
expected = False
assert _is_potential_project(path) == expected

View File

@@ -206,6 +206,24 @@ def test_numpydoc_parameters_set_of_values():
assert 'capitalize' in names assert 'capitalize' in names
assert 'numerator' in names assert 'numerator' in names
@pytest.mark.skipif(numpydoc_unavailable,
reason='numpydoc module is unavailable')
def test_numpydoc_parameters_set_single_value():
"""
This is found in numpy masked-array I'm not too sure what this means but should not crash
"""
s = dedent('''
def foobar(x, y):
"""
Parameters
----------
x : {var}, optional
"""
x.''')
names = [c.name for c in jedi.Script(s).complete()]
# just don't crash
assert names == []
@pytest.mark.skipif(numpydoc_unavailable, @pytest.mark.skipif(numpydoc_unavailable,
reason='numpydoc module is unavailable') reason='numpydoc module is unavailable')

View File

@@ -1,4 +1,5 @@
import os import os
from parso.cache import parser_cache
from test.helpers import root_dir from test.helpers import root_dir
from jedi.api.project import Project from jedi.api.project import Project
@@ -64,6 +65,17 @@ def test_goto_import(Script):
assert not d.is_stub() assert not d.is_stub()
def test_stub_get_line_code(Script):
code = 'from abc import ABC; ABC'
script = Script(code)
d, = script.goto(only_stubs=True)
assert d.get_line_code() == 'class ABC(metaclass=ABCMeta): ...\n'
del parser_cache[script._inference_state.latest_grammar._hashed][d.module_path]
d, = Script(path=d.module_path).goto(d.line, d.column, only_stubs=True)
assert d.is_stub()
assert d.get_line_code() == 'class ABC(metaclass=ABCMeta): ...\n'
def test_os_stat_result(Script): def test_os_stat_result(Script):
d, = Script('import os; os.stat_result').goto() d, = Script('import os; os.stat_result').goto()
assert d.is_stub() assert d.is_stub()

View File

@@ -29,13 +29,13 @@ def test_find_module_basic():
def test_find_module_package(): def test_find_module_package():
file_io, is_package = _find_module('json') file_io, is_package = _find_module('json')
assert file_io.path.endswith(os.path.join('json', '__init__.py')) assert file_io.path.parts[-2:] == ('json', '__init__.py')
assert is_package is True assert is_package is True
def test_find_module_not_package(): def test_find_module_not_package():
file_io, is_package = _find_module('io') file_io, is_package = _find_module('io')
assert file_io.path.endswith('io.py') assert file_io.path.name == 'io.py'
assert is_package is False assert is_package is False
@@ -55,8 +55,8 @@ def test_find_module_package_zipped(Script, inference_state, environment):
full_name='pkg' full_name='pkg'
) )
assert file_io is not None assert file_io is not None
assert file_io.path.endswith(os.path.join('pkg.zip', 'pkg', '__init__.py')) assert file_io.path.parts[-3:] == ('pkg.zip', 'pkg', '__init__.py')
assert file_io._zip_path.endswith('pkg.zip') assert file_io._zip_path.name == 'pkg.zip'
assert is_package is True assert is_package is True
@@ -108,7 +108,7 @@ def test_find_module_not_package_zipped(Script, inference_state, environment):
string='not_pkg', string='not_pkg',
full_name='not_pkg' full_name='not_pkg'
) )
assert file_io.path.endswith(os.path.join('not_pkg.zip', 'not_pkg.py')) assert file_io.path.parts[-2:] == ('not_pkg.zip', 'not_pkg.py')
assert is_package is False assert is_package is False
@@ -468,3 +468,9 @@ def test_relative_import_star(Script):
script = Script(source, path='export.py') script = Script(source, path='export.py')
assert script.complete(3, len("furl.c")) assert script.complete(3, len("furl.c"))
def test_import_recursion(Script):
path = get_example_dir('import-recursion', "cq_example.py")
for c in Script(path=path).complete(3, 3):
c.docstring()

View File

@@ -340,3 +340,20 @@ def test_overload(Script, code):
x1, x2 = Script(code, path=os.path.join(dir_, 'foo.py')).get_signatures() x1, x2 = Script(code, path=os.path.join(dir_, 'foo.py')).get_signatures()
assert x1.to_string() == 'with_overload(x: int, y: int) -> float' assert x1.to_string() == 'with_overload(x: int, y: int) -> float'
assert x2.to_string() == 'with_overload(x: str, y: list) -> float' assert x2.to_string() == 'with_overload(x: str, y: list) -> float'
def test_enum(Script):
script = Script('''\
from enum import Enum
class Planet(Enum):
MERCURY = (3.303e+23, 2.4397e6)
VENUS = (4.869e+24, 6.0518e6)
def __init__(self, mass, radius):
self.mass = mass # in kilograms
self.radius = radius # in meters
Planet.MERCURY''')
completion, = script.complete()
assert not completion.get_signatures()