mirror of
https://github.com/davidhalter/jedi.git
synced 2026-11-05 16:42:52 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab89beb06c | ||
|
|
b7009b938a | ||
|
|
142bd1d0be | ||
|
|
575c6ab378 | ||
|
|
c71dead915 | ||
|
|
091735802d | ||
|
|
102700116c | ||
|
|
79d9764bce | ||
|
|
3f7e8adddc | ||
|
|
d8efff8e30 | ||
|
|
2e3e905707 | ||
|
|
558135e8f3 | ||
|
|
f37a9f9feb | ||
|
|
ed47f89162 | ||
|
|
112d255414 | ||
|
|
5c37b736a3 | ||
|
|
19236b472b | ||
|
|
ef5d361121 |
No files matched your search
@@ -66,6 +66,11 @@ Code Contributors
|
|||||||
- Martin Vielsmaier (@moser) <martin@vielsmaier.net>
|
- Martin Vielsmaier (@moser) <martin@vielsmaier.net>
|
||||||
- TingJia Wu (@WutingjiaX) <wutingjia@bytedance.com>
|
- TingJia Wu (@WutingjiaX) <wutingjia@bytedance.com>
|
||||||
- Nguyễn Hồng Quân <ng.hong.quan@gmail.com>
|
- Nguyễn Hồng Quân <ng.hong.quan@gmail.com>
|
||||||
|
- haoran3160-afk (@haoran3160-afk)
|
||||||
|
- Eric3-jp (@Eric3-jp) (with OpenAI Codex assistance)
|
||||||
|
- Dhanraj E (@Dhanu-dynamic) <dhanraj@labs.digiscrypt.com>
|
||||||
|
- Charan Rathore (@charan-rathore)
|
||||||
|
- Abdullah Ahmed (@abdull-ah-med)
|
||||||
|
|
||||||
And a few more "anonymous" contributors.
|
And a few more "anonymous" contributors.
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,14 @@ Changelog
|
|||||||
Unreleased
|
Unreleased
|
||||||
++++++++++
|
++++++++++
|
||||||
|
|
||||||
|
0.20.1 (2026-10-12)
|
||||||
|
+++++++++++++++++++
|
||||||
|
|
||||||
|
- Avoid loading an executable path from `project.json`
|
||||||
|
- Avoid changing the global recursion limit
|
||||||
|
- Support for async pytest fixtures
|
||||||
|
- Work better with PEP 695 type parameter syntax
|
||||||
|
|
||||||
0.20.0 (2026-05-02)
|
0.20.0 (2026-05-02)
|
||||||
+++++++++++++++++++
|
+++++++++++++++++++
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ ad
|
|||||||
load
|
load
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = '0.20.0'
|
__version__ = '0.20.1'
|
||||||
|
|
||||||
from jedi.api import Script, Interpreter, set_debug_function, preload_module
|
from jedi.api import Script, Interpreter, set_debug_function, preload_module
|
||||||
from jedi import settings
|
from jedi import settings
|
||||||
|
|||||||
+17
-6
@@ -7,7 +7,6 @@ Alternatively, if you don't need a custom function and are happy with printing
|
|||||||
debug messages to stdout, simply call :func:`set_debug_function` without
|
debug messages to stdout, simply call :func:`set_debug_function` without
|
||||||
arguments.
|
arguments.
|
||||||
"""
|
"""
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import parso
|
import parso
|
||||||
@@ -21,7 +20,7 @@ from jedi.file_io import KnownContentFileIO
|
|||||||
from jedi.api import classes
|
from jedi.api import classes
|
||||||
from jedi.api import interpreter
|
from jedi.api import interpreter
|
||||||
from jedi.api import helpers
|
from jedi.api import helpers
|
||||||
from jedi.api.helpers import validate_line_column
|
from jedi.api.helpers import validate_line_column, recursion_limit
|
||||||
from jedi.api.completion import Completion, search_in_module
|
from jedi.api.completion import Completion, search_in_module
|
||||||
from jedi.api.keywords import KeywordName
|
from jedi.api.keywords import KeywordName
|
||||||
from jedi.api.environment import InterpreterEnvironment
|
from jedi.api.environment import InterpreterEnvironment
|
||||||
@@ -43,10 +42,6 @@ from jedi.inference.gradual.conversion import convert_names, convert_values
|
|||||||
from jedi.inference.gradual.utils import load_proper_stub_module
|
from jedi.inference.gradual.utils import load_proper_stub_module
|
||||||
from jedi.inference.utils import to_list
|
from jedi.inference.utils import to_list
|
||||||
|
|
||||||
# Jedi uses lots and lots of recursion. By setting this a little bit higher, we
|
|
||||||
# can remove some "maximum recursion depth" errors.
|
|
||||||
sys.setrecursionlimit(3000)
|
|
||||||
|
|
||||||
|
|
||||||
class Script:
|
class Script:
|
||||||
"""
|
"""
|
||||||
@@ -97,6 +92,7 @@ class Script:
|
|||||||
references works well, because the right folder is searched. There are
|
references works well, because the right folder is searched. There are
|
||||||
also ways to modify the sys path and other things.
|
also ways to modify the sys path and other things.
|
||||||
"""
|
"""
|
||||||
|
@recursion_limit
|
||||||
def __init__(self, code=None, *, path=None, environment=None, project=None):
|
def __init__(self, code=None, *, path=None, environment=None, project=None):
|
||||||
self._orig_path = path
|
self._orig_path = path
|
||||||
if isinstance(path, str):
|
if isinstance(path, str):
|
||||||
@@ -191,6 +187,7 @@ class Script:
|
|||||||
self._inference_state.environment,
|
self._inference_state.environment,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def complete(self, line=None, column=None, *, fuzzy=False):
|
def complete(self, line=None, column=None, *, fuzzy=False):
|
||||||
"""
|
"""
|
||||||
@@ -214,6 +211,7 @@ class Script:
|
|||||||
)
|
)
|
||||||
return completion.complete()
|
return completion.complete()
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def infer(self, line=None, column=None, *, only_stubs=False, prefer_stubs=False):
|
def infer(self, line=None, column=None, *, only_stubs=False, prefer_stubs=False):
|
||||||
"""
|
"""
|
||||||
@@ -259,6 +257,7 @@ class Script:
|
|||||||
# the API.
|
# the API.
|
||||||
return helpers.sorted_definitions(set(defs))
|
return helpers.sorted_definitions(set(defs))
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def goto(self, line=None, column=None, *, follow_imports=False, follow_builtin_imports=False,
|
def goto(self, line=None, column=None, *, follow_imports=False, follow_builtin_imports=False,
|
||||||
only_stubs=False, prefer_stubs=False):
|
only_stubs=False, prefer_stubs=False):
|
||||||
@@ -324,6 +323,7 @@ class Script:
|
|||||||
"""
|
"""
|
||||||
return self._search_func(string, all_scopes=all_scopes)
|
return self._search_func(string, all_scopes=all_scopes)
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
@to_list
|
@to_list
|
||||||
def _search_func(self, string, all_scopes=False, complete=False, fuzzy=False):
|
def _search_func(self, string, all_scopes=False, complete=False, fuzzy=False):
|
||||||
names = self._names(all_scopes=all_scopes)
|
names = self._names(all_scopes=all_scopes)
|
||||||
@@ -353,6 +353,7 @@ class Script:
|
|||||||
"""
|
"""
|
||||||
return self._search_func(string, complete=True, **kwargs)
|
return self._search_func(string, complete=True, **kwargs)
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def help(self, line=None, column=None):
|
def help(self, line=None, column=None):
|
||||||
"""
|
"""
|
||||||
@@ -397,6 +398,7 @@ class Script:
|
|||||||
return [classes.Name(self._inference_state, name)]
|
return [classes.Name(self._inference_state, name)]
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def get_references(self, line=None, column=None, **kwargs):
|
def get_references(self, line=None, column=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
@@ -428,6 +430,7 @@ class Script:
|
|||||||
return helpers.sorted_definitions(definitions)
|
return helpers.sorted_definitions(definitions)
|
||||||
return _references(**kwargs)
|
return _references(**kwargs)
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def get_signatures(self, line=None, column=None):
|
def get_signatures(self, line=None, column=None):
|
||||||
"""
|
"""
|
||||||
@@ -466,6 +469,7 @@ class Script:
|
|||||||
return [classes.Signature(self._inference_state, signature, call_details)
|
return [classes.Signature(self._inference_state, signature, call_details)
|
||||||
for signature in definitions.get_signatures()]
|
for signature in definitions.get_signatures()]
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def get_context(self, line=None, column=None):
|
def get_context(self, line=None, column=None):
|
||||||
"""
|
"""
|
||||||
@@ -502,6 +506,8 @@ class Script:
|
|||||||
tree_name = name.tree_name
|
tree_name = name.tree_name
|
||||||
if tree_name is not None: # Happens with lambdas.
|
if tree_name is not None: # Happens with lambdas.
|
||||||
scope = tree_name.get_definition()
|
scope = tree_name.get_definition()
|
||||||
|
if scope.parent.type in ('async_stmt', 'async_funcdef'):
|
||||||
|
scope = scope.parent
|
||||||
if scope.start_pos[1] < column:
|
if scope.start_pos[1] < column:
|
||||||
break
|
break
|
||||||
definition = definition.parent()
|
definition = definition.parent()
|
||||||
@@ -541,6 +547,7 @@ class Script:
|
|||||||
finally:
|
finally:
|
||||||
self._inference_state.is_analysis = False
|
self._inference_state.is_analysis = False
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
def get_names(self, **kwargs):
|
def get_names(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Returns names defined in the current file.
|
Returns names defined in the current file.
|
||||||
@@ -579,6 +586,7 @@ class Script:
|
|||||||
]
|
]
|
||||||
return sorted(defs, key=lambda x: x.start_pos)
|
return sorted(defs, key=lambda x: x.start_pos)
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
def rename(self, line=None, column=None, *, new_name):
|
def rename(self, line=None, column=None, *, new_name):
|
||||||
"""
|
"""
|
||||||
Renames all references of the variable under the cursor.
|
Renames all references of the variable under the cursor.
|
||||||
@@ -591,6 +599,7 @@ class Script:
|
|||||||
definitions = self.get_references(line, column, include_builtins=False)
|
definitions = self.get_references(line, column, include_builtins=False)
|
||||||
return refactoring.rename(self._inference_state, definitions, new_name)
|
return refactoring.rename(self._inference_state, definitions, new_name)
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def extract_variable(self, line, column, *, new_name, until_line=None, until_column=None):
|
def extract_variable(self, line, column, *, new_name, until_line=None, until_column=None):
|
||||||
"""
|
"""
|
||||||
@@ -630,6 +639,7 @@ class Script:
|
|||||||
new_name, (line, column), until_pos
|
new_name, (line, column), until_pos
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
@validate_line_column
|
@validate_line_column
|
||||||
def extract_function(self, line, column, *, new_name, until_line=None, until_column=None):
|
def extract_function(self, line, column, *, new_name, until_line=None, until_column=None):
|
||||||
"""
|
"""
|
||||||
@@ -677,6 +687,7 @@ class Script:
|
|||||||
new_name, (line, column), until_pos
|
new_name, (line, column), until_pos
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
def inline(self, line=None, column=None):
|
def inline(self, line=None, column=None):
|
||||||
"""
|
"""
|
||||||
Inlines a variable under the cursor. This is basically the opposite of
|
Inlines a variable under the cursor. This is basically the opposite of
|
||||||
|
|||||||
+16
-3
@@ -28,7 +28,7 @@ from jedi.inference.gradual.conversion import convert_names, convert_values
|
|||||||
from jedi.inference.base_value import ValueSet, HasNoContext
|
from jedi.inference.base_value import ValueSet, HasNoContext
|
||||||
from jedi.api.keywords import KeywordName
|
from jedi.api.keywords import KeywordName
|
||||||
from jedi.api import completion_cache
|
from jedi.api import completion_cache
|
||||||
from jedi.api.helpers import filter_follow_imports
|
from jedi.api.helpers import filter_follow_imports, recursion_limit
|
||||||
|
|
||||||
|
|
||||||
def _sort_names_by_start_pos(names):
|
def _sort_names_by_start_pos(names):
|
||||||
@@ -262,6 +262,7 @@ class BaseName:
|
|||||||
return last_leaf.end_pos
|
return last_leaf.end_pos
|
||||||
return definition.end_pos
|
return definition.end_pos
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
def docstring(self, raw=False, fast=True):
|
def docstring(self, raw=False, fast=True):
|
||||||
r"""
|
r"""
|
||||||
Return a document string for this completion object.
|
Return a document string for this completion object.
|
||||||
@@ -422,6 +423,7 @@ class BaseName:
|
|||||||
return tree_name.is_definition() and tree_name.parent.type == 'trailer'
|
return tree_name.is_definition() and tree_name.parent.type == 'trailer'
|
||||||
|
|
||||||
@debug.increase_indent_cm('goto on name')
|
@debug.increase_indent_cm('goto on name')
|
||||||
|
@recursion_limit
|
||||||
def goto(self, *, follow_imports=False, follow_builtin_imports=False,
|
def goto(self, *, follow_imports=False, follow_builtin_imports=False,
|
||||||
only_stubs=False, prefer_stubs=False):
|
only_stubs=False, prefer_stubs=False):
|
||||||
|
|
||||||
@@ -452,6 +454,7 @@ class BaseName:
|
|||||||
for n in names]
|
for n in names]
|
||||||
|
|
||||||
@debug.increase_indent_cm('infer on name')
|
@debug.increase_indent_cm('infer on name')
|
||||||
|
@recursion_limit
|
||||||
def infer(self, *, only_stubs=False, prefer_stubs=False):
|
def infer(self, *, only_stubs=False, prefer_stubs=False):
|
||||||
"""
|
"""
|
||||||
Like :meth:`.Script.infer`, it can be useful to understand which type
|
Like :meth:`.Script.infer`, it can be useful to understand which type
|
||||||
@@ -551,7 +554,7 @@ class BaseName:
|
|||||||
return ''.join(lines[start_index:index + after + 1])
|
return ''.join(lines[start_index:index + after + 1])
|
||||||
|
|
||||||
def _get_signatures(self, for_docstring=False):
|
def _get_signatures(self, for_docstring=False):
|
||||||
if self._name.api_type == 'property':
|
if self._name.api_type in ('property', 'param'):
|
||||||
return []
|
return []
|
||||||
if for_docstring and self._name.api_type == 'statement' and not self.is_stub():
|
if for_docstring and self._name.api_type == 'statement' and not self.is_stub():
|
||||||
# For docstrings we don't resolve signatures if they are simple
|
# For docstrings we don't resolve signatures if they are simple
|
||||||
@@ -567,6 +570,7 @@ class BaseName:
|
|||||||
names = convert_names([self._name], prefer_stubs=True)
|
names = convert_names([self._name], prefer_stubs=True)
|
||||||
return [sig for name in names for sig in name.infer().get_signatures()]
|
return [sig for name in names for sig in name.infer().get_signatures()]
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
def get_signatures(self):
|
def get_signatures(self):
|
||||||
"""
|
"""
|
||||||
Returns all potential signatures for a function or a class. Multiple
|
Returns all potential signatures for a function or a class. Multiple
|
||||||
@@ -579,6 +583,7 @@ class BaseName:
|
|||||||
for s in self._get_signatures()
|
for s in self._get_signatures()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
def execute(self):
|
def execute(self):
|
||||||
"""
|
"""
|
||||||
Uses type inference to "execute" this identifier and returns the
|
Uses type inference to "execute" this identifier and returns the
|
||||||
@@ -588,6 +593,7 @@ class BaseName:
|
|||||||
"""
|
"""
|
||||||
return _values_to_definitions(self._name.infer().execute_with_values())
|
return _values_to_definitions(self._name.infer().execute_with_values())
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
def get_type_hint(self):
|
def get_type_hint(self):
|
||||||
"""
|
"""
|
||||||
Returns type hints like ``Iterable[int]`` or ``Union[int, str]``.
|
Returns type hints like ``Iterable[int]`` or ``Union[int, str]``.
|
||||||
@@ -670,6 +676,7 @@ class Completion(BaseName):
|
|||||||
"""
|
"""
|
||||||
return self._complete(False)
|
return self._complete(False)
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
def docstring(self, raw=False, fast=True):
|
def docstring(self, raw=False, fast=True):
|
||||||
"""
|
"""
|
||||||
Documented under :meth:`BaseName.docstring`.
|
Documented under :meth:`BaseName.docstring`.
|
||||||
@@ -752,6 +759,7 @@ class Name(BaseName):
|
|||||||
super().__init__(inference_state, definition)
|
super().__init__(inference_state, definition)
|
||||||
|
|
||||||
@memoize_method
|
@memoize_method
|
||||||
|
@recursion_limit
|
||||||
def defined_names(self):
|
def defined_names(self):
|
||||||
"""
|
"""
|
||||||
List sub-definitions (e.g., methods in class).
|
List sub-definitions (e.g., methods in class).
|
||||||
@@ -797,6 +805,7 @@ class BaseSignature(Name):
|
|||||||
self._signature = signature
|
self._signature = signature
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@recursion_limit
|
||||||
def params(self):
|
def params(self):
|
||||||
"""
|
"""
|
||||||
Returns definitions for all parameters that a signature defines.
|
Returns definitions for all parameters that a signature defines.
|
||||||
@@ -807,6 +816,7 @@ class BaseSignature(Name):
|
|||||||
return [ParamName(self._inference_state, n)
|
return [ParamName(self._inference_state, n)
|
||||||
for n in self._signature.get_param_names(resolve_stars=True)]
|
for n in self._signature.get_param_names(resolve_stars=True)]
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
def to_string(self):
|
def to_string(self):
|
||||||
"""
|
"""
|
||||||
Returns a text representation of the signature. This could for example
|
Returns a text representation of the signature. This could for example
|
||||||
@@ -831,7 +841,7 @@ class Signature(BaseSignature):
|
|||||||
def index(self):
|
def index(self):
|
||||||
"""
|
"""
|
||||||
Returns the param index of the current cursor position.
|
Returns the param index of the current cursor position.
|
||||||
Returns None if the index cannot be found in the curent call.
|
Returns None if the index cannot be found in the current call.
|
||||||
|
|
||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
@@ -858,6 +868,7 @@ class Signature(BaseSignature):
|
|||||||
|
|
||||||
|
|
||||||
class ParamName(Name):
|
class ParamName(Name):
|
||||||
|
@recursion_limit
|
||||||
def infer_default(self):
|
def infer_default(self):
|
||||||
"""
|
"""
|
||||||
Returns default values like the ``1`` of ``def foo(x=1):``.
|
Returns default values like the ``1`` of ``def foo(x=1):``.
|
||||||
@@ -866,6 +877,7 @@ class ParamName(Name):
|
|||||||
"""
|
"""
|
||||||
return _values_to_definitions(self._name.infer_default())
|
return _values_to_definitions(self._name.infer_default())
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
def infer_annotation(self, **kwargs):
|
def infer_annotation(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
:param execute_annotation: Default True; If False, values are not
|
:param execute_annotation: Default True; If False, values are not
|
||||||
@@ -874,6 +886,7 @@ class ParamName(Name):
|
|||||||
"""
|
"""
|
||||||
return _values_to_definitions(self._name.infer_annotation(ignore_stars=True, **kwargs))
|
return _values_to_definitions(self._name.infer_annotation(ignore_stars=True, **kwargs))
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
def to_string(self):
|
def to_string(self):
|
||||||
"""
|
"""
|
||||||
Returns a simple representation of a param, like
|
Returns a simple representation of a param, like
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
Helpers for the API
|
Helpers for the API
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from contextlib import contextmanager
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
@@ -21,6 +23,48 @@ from jedi.parser_utils import get_parent_scope
|
|||||||
|
|
||||||
CompletionParts = namedtuple('CompletionParts', ['path', 'has_dot', 'name'])
|
CompletionParts = namedtuple('CompletionParts', ['path', 'has_dot', 'name'])
|
||||||
|
|
||||||
|
RECURSION_LIMIT = 3000
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def higher_recursion_limit():
|
||||||
|
"""Jedi needs a higher recursion limit for parsing and inference. Raise it
|
||||||
|
only temporarily, so importing Jedi does not change global interpreter
|
||||||
|
state and a user-defined higher limit is never lowered."""
|
||||||
|
old_limit = sys.getrecursionlimit()
|
||||||
|
if old_limit >= RECURSION_LIMIT:
|
||||||
|
yield
|
||||||
|
else:
|
||||||
|
sys.setrecursionlimit(RECURSION_LIMIT)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
sys.setrecursionlimit(old_limit)
|
||||||
|
|
||||||
|
|
||||||
|
def recursion_limit(func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
with higher_recursion_limit():
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def recursion_limit_generator(func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
with higher_recursion_limit():
|
||||||
|
iterator = iter(func(*args, **kwargs))
|
||||||
|
while True:
|
||||||
|
with higher_recursion_limit():
|
||||||
|
try:
|
||||||
|
value = next(iterator)
|
||||||
|
except StopIteration:
|
||||||
|
return
|
||||||
|
# Restore the caller's limit before yielding a result.
|
||||||
|
yield value
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
def _start_match(string, like_name):
|
def _start_match(string, like_name):
|
||||||
return string.startswith(like_name)
|
return string.startswith(like_name)
|
||||||
|
|||||||
+19
-2
@@ -15,7 +15,7 @@ from jedi import debug
|
|||||||
from jedi.api.environment import get_cached_default_environment, create_environment
|
from jedi.api.environment import get_cached_default_environment, create_environment
|
||||||
from jedi.api.exceptions import WrongVersion
|
from jedi.api.exceptions import WrongVersion
|
||||||
from jedi.api.completion import search_in_module
|
from jedi.api.completion import search_in_module
|
||||||
from jedi.api.helpers import split_search_string, get_module_names
|
from jedi.api.helpers import split_search_string, get_module_names, recursion_limit_generator
|
||||||
from jedi.inference.imports import load_module_from_path, \
|
from jedi.inference.imports import load_module_from_path, \
|
||||||
load_namespace_from_path, iter_module_names
|
load_namespace_from_path, iter_module_names
|
||||||
from jedi.inference.sys_path import discover_buildout_paths
|
from jedi.inference.sys_path import discover_buildout_paths
|
||||||
@@ -86,6 +86,14 @@ class Project:
|
|||||||
version, data = json.load(f)
|
version, data = json.load(f)
|
||||||
|
|
||||||
if version == 1:
|
if version == 1:
|
||||||
|
# A code base must not be able to enable the loading of its own
|
||||||
|
# extensions or point Jedi at a binary to execute, see the docs
|
||||||
|
# about security. The project file can be part of a checked out
|
||||||
|
# repository, so these settings are ignored for loaded projects.
|
||||||
|
if data.pop('load_unsafe_extensions', False):
|
||||||
|
debug.warning('load_unsafe_extensions is ignored for loaded projects')
|
||||||
|
if data.pop('environment_path', None) is not None:
|
||||||
|
debug.warning('environment_path is ignored for loaded projects')
|
||||||
return cls(**data)
|
return cls(**data)
|
||||||
else:
|
else:
|
||||||
raise WrongVersion(
|
raise WrongVersion(
|
||||||
@@ -99,6 +107,11 @@ class Project:
|
|||||||
data = dict(self.__dict__)
|
data = dict(self.__dict__)
|
||||||
data.pop('_environment', None)
|
data.pop('_environment', None)
|
||||||
data.pop('_django', None) # TODO make django setting public?
|
data.pop('_django', None) # TODO make django setting public?
|
||||||
|
|
||||||
|
# These are dropped anyway
|
||||||
|
data.pop('_load_unsafe_extensions', None)
|
||||||
|
data.pop('_environment_path', None)
|
||||||
|
|
||||||
data = {k.lstrip('_'): v for k, v in data.items()}
|
data = {k.lstrip('_'): v for k, v in data.items()}
|
||||||
data['path'] = str(data['path'])
|
data['path'] = str(data['path'])
|
||||||
|
|
||||||
@@ -242,7 +255,10 @@ class Project:
|
|||||||
def get_environment(self):
|
def get_environment(self):
|
||||||
if self._environment is None:
|
if self._environment is None:
|
||||||
if self._environment_path is not None:
|
if self._environment_path is not None:
|
||||||
self._environment = create_environment(self._environment_path, safe=False)
|
# environment_path can only be set by the user directly, it is
|
||||||
|
# never loaded from a project file, so it is trusted.
|
||||||
|
self._environment = create_environment(
|
||||||
|
self._environment_path, safe=False)
|
||||||
else:
|
else:
|
||||||
self._environment = get_cached_default_environment()
|
self._environment = get_cached_default_environment()
|
||||||
return self._environment
|
return self._environment
|
||||||
@@ -282,6 +298,7 @@ class Project:
|
|||||||
"""
|
"""
|
||||||
return self._search_func(string, complete=True, **kwargs)
|
return self._search_func(string, complete=True, **kwargs)
|
||||||
|
|
||||||
|
@recursion_limit_generator
|
||||||
@_try_to_skip_duplicates
|
@_try_to_skip_duplicates
|
||||||
def _search_func(self, string, complete=False, all_scopes=False):
|
def _search_func(self, string, complete=False, all_scopes=False):
|
||||||
# Using a Script is they easiest way to get an empty module context.
|
# Using a Script is they easiest way to get an empty module context.
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ class InferenceState:
|
|||||||
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
|
||||||
|
|
||||||
# mypy doesn't suppport decorated propeties (https://github.com/python/mypy/issues/1362)
|
# mypy doesn't support decorated properties (https://github.com/python/mypy/issues/1362)
|
||||||
@property
|
@property
|
||||||
@inference_state_function_cache()
|
@inference_state_function_cache()
|
||||||
def builtins_module(self):
|
def builtins_module(self):
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ def _check_for_setattr(instance):
|
|||||||
|
|
||||||
def add_attribute_error(name_context, lookup_value, name):
|
def add_attribute_error(name_context, lookup_value, name):
|
||||||
message = ('AttributeError: %s has no attribute %s.' % (lookup_value, name))
|
message = ('AttributeError: %s has no attribute %s.' % (lookup_value, name))
|
||||||
# Check for __getattr__/__getattribute__ existance and issue a warning
|
# Check for __getattr__/__getattribute__ existence and issue a warning
|
||||||
# instead of an error, if that happens.
|
# instead of an error, if that happens.
|
||||||
typ = Error
|
typ = Error
|
||||||
if lookup_value.is_instance() and not lookup_value.is_compiled():
|
if lookup_value.is_instance() and not lookup_value.is_compiled():
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import re
|
|||||||
import builtins
|
import builtins
|
||||||
import typing
|
import typing
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple, Any
|
||||||
|
|
||||||
from jedi.inference.compiled.getattr_static import getattr_static
|
from jedi.inference.compiled.getattr_static import getattr_static
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ ClassMethodDescriptorType = type(object_class_dict['__subclasshook__'])
|
|||||||
_sentinel = object()
|
_sentinel = object()
|
||||||
|
|
||||||
# Maps Python syntax to the operator module.
|
# Maps Python syntax to the operator module.
|
||||||
COMPARISON_OPERATORS = {
|
COMPARISON_OPERATORS: dict[str, Any] = {
|
||||||
'==': op.eq,
|
'==': op.eq,
|
||||||
'!=': op.ne,
|
'!=': op.ne,
|
||||||
'is': op.is_,
|
'is': op.is_,
|
||||||
|
|||||||
@@ -416,10 +416,18 @@ def import_module(inference_state, import_names, parent_module_value, sys_path):
|
|||||||
if is_pkg is None:
|
if is_pkg is None:
|
||||||
return NO_VALUES
|
return NO_VALUES
|
||||||
else:
|
else:
|
||||||
paths = parent_module_value.py__path__()
|
try:
|
||||||
if paths is None:
|
py__path__ = parent_module_value.py__path__
|
||||||
# The module might not be a package.
|
except AttributeError:
|
||||||
|
# Due to meta_path, the value can be pretty much any compiled value
|
||||||
|
# and we're not guaranteed that the attribute is available, see
|
||||||
|
# also https://github.com/davidhalter/jedi/issues/2118
|
||||||
return NO_VALUES
|
return NO_VALUES
|
||||||
|
else:
|
||||||
|
paths = py__path__()
|
||||||
|
if paths is None:
|
||||||
|
# The module might not be a package.
|
||||||
|
return NO_VALUES
|
||||||
|
|
||||||
file_io_or_ns, is_pkg = inference_state.compiled_subprocess.get_module_info(
|
file_io_or_ns, is_pkg = inference_state.compiled_subprocess.get_module_info(
|
||||||
string=import_names[-1],
|
string=import_names[-1],
|
||||||
|
|||||||
@@ -170,6 +170,8 @@ class AbstractTreeName(AbstractNameDefinition):
|
|||||||
trailer = par.parent
|
trailer = par.parent
|
||||||
if trailer.type == 'arglist':
|
if trailer.type == 'arglist':
|
||||||
trailer = trailer.parent
|
trailer = trailer.parent
|
||||||
|
if trailer.type == 'error_node':
|
||||||
|
return []
|
||||||
if trailer.type != 'classdef':
|
if trailer.type != 'classdef':
|
||||||
if trailer.type == 'decorator':
|
if trailer.type == 'decorator':
|
||||||
value_set = context.infer_node(trailer.children[1])
|
value_set = context.infer_node(trailer.children[1])
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ class _BaseTreeInstance(AbstractInstanceValue):
|
|||||||
elif isinstance(f, CompiledValueFilter):
|
elif isinstance(f, CompiledValueFilter):
|
||||||
yield CompiledInstanceClassFilter(self, f)
|
yield CompiledInstanceClassFilter(self, f)
|
||||||
else:
|
else:
|
||||||
# Propably from the metaclass.
|
# Probably from the metaclass.
|
||||||
yield f
|
yield f
|
||||||
|
|
||||||
@inference_state_method_cache()
|
@inference_state_method_cache()
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import sys
|
|||||||
from typing import List
|
from typing import List
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from jedi.inference.arguments import ValuesArguments
|
||||||
from jedi.inference.cache import inference_state_method_cache
|
from jedi.inference.cache import inference_state_method_cache
|
||||||
|
from jedi.inference.gradual.annotation import infer_return_types
|
||||||
from jedi.inference.imports import goto_import, load_module_from_path
|
from jedi.inference.imports import goto_import, load_module_from_path
|
||||||
from jedi.inference.filters import ParserTreeFilter
|
from jedi.inference.filters import ParserTreeFilter
|
||||||
from jedi.inference.base_value import NO_VALUES, ValueSet
|
from jedi.inference.base_value import NO_VALUES, ValueSet
|
||||||
@@ -32,6 +34,19 @@ def execute(callback):
|
|||||||
def infer_anonymous_param(func):
|
def infer_anonymous_param(func):
|
||||||
def get_returns(value):
|
def get_returns(value):
|
||||||
if value.tree_node.annotation is not None:
|
if value.tree_node.annotation is not None:
|
||||||
|
if value.tree_node.parent.type == 'async_funcdef':
|
||||||
|
# Async fixtures provide the awaited value, rather than a
|
||||||
|
# coroutine. The annotation of an async generator describes
|
||||||
|
# the iterator, so the value is what that iterator yields.
|
||||||
|
result = infer_return_types(value, ValuesArguments([]))
|
||||||
|
if value.as_context().is_generator():
|
||||||
|
return ValueSet.from_sets(
|
||||||
|
lazy_value.infer()
|
||||||
|
for v in result
|
||||||
|
for lazy_value in v.iterate(is_async=True)
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
result = value.execute_with_values()
|
result = value.execute_with_values()
|
||||||
if any(v.name.get_qualified_names(include_module_names=True)
|
if any(v.name.get_qualified_names(include_module_names=True)
|
||||||
== ('typing', 'Generator')
|
== ('typing', 'Generator')
|
||||||
@@ -223,6 +238,8 @@ class FixtureFilter(ParserTreeFilter):
|
|||||||
if funcdef.type != "funcdef":
|
if funcdef.type != "funcdef":
|
||||||
return False
|
return False
|
||||||
decorated = funcdef.parent
|
decorated = funcdef.parent
|
||||||
|
if decorated.type == "async_funcdef":
|
||||||
|
decorated = decorated.parent
|
||||||
if decorated.type != "decorated":
|
if decorated.type != "decorated":
|
||||||
return False
|
return False
|
||||||
decorators = decorated.children[0]
|
decorators = decorated.children[0]
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ setup(name='jedi',
|
|||||||
packages=find_packages(exclude=['test', 'test.*']),
|
packages=find_packages(exclude=['test', 'test.*']),
|
||||||
python_requires='>=3.10',
|
python_requires='>=3.10',
|
||||||
# Python 3.13 grammars are added to parso in 0.8.4
|
# Python 3.13 grammars are added to parso in 0.8.4
|
||||||
install_requires=['parso>=0.8.6,<0.9.0'],
|
install_requires=['parso>=0.8.7,<0.9.0'],
|
||||||
extras_require={
|
extras_require={
|
||||||
'dev': [
|
'dev': [
|
||||||
'pytest<9.0.0',
|
'pytest<9.0.0',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Generator
|
from typing import AsyncGenerator, Generator
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pytest import fixture
|
from pytest import fixture
|
||||||
@@ -208,3 +208,58 @@ def bla(admin_u
|
|||||||
def bla(admin_user):
|
def bla(admin_user):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------
|
||||||
|
# asynchronous fixtures
|
||||||
|
# -----------------
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def async_fixture():
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='module')
|
||||||
|
async def async_annotated_fixture() -> str:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
@fixture
|
||||||
|
async def async_yield_fixture():
|
||||||
|
yield 1.0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def async_annotated_opaque() -> int:
|
||||||
|
return opaque_value
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def async_generator_annotated() -> AsyncGenerator[str, None]:
|
||||||
|
yield opaque_value
|
||||||
|
|
||||||
|
|
||||||
|
#? ['async_fixture']
|
||||||
|
def test_async_fixture_completion(async_fixt
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_async_fixture_inference(async_fixture, async_annotated_fixture, async_yield_fixture):
|
||||||
|
#? int()
|
||||||
|
async_fixture
|
||||||
|
#? str()
|
||||||
|
async_annotated_fixture
|
||||||
|
#? float()
|
||||||
|
async_yield_fixture
|
||||||
|
|
||||||
|
|
||||||
|
def test_async_fixture_annotation(async_annotated_opaque, async_generator_annotated):
|
||||||
|
#? int()
|
||||||
|
async_annotated_opaque
|
||||||
|
#? str()
|
||||||
|
async_generator_annotated
|
||||||
|
|
||||||
|
|
||||||
|
#! 30 ['def async_fixture']
|
||||||
|
def test_async_fixture_goto(async_fixture):
|
||||||
|
pass
|
||||||
@@ -3,6 +3,8 @@ Test all things related to the ``jedi.api`` module.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -231,6 +233,19 @@ def test_goto_follow_imports(Script):
|
|||||||
assert d.name == 'a'
|
assert d.name == 'a'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('prefix', ['', 'def foo(bar): pass\n', 'def f():\n '])
|
||||||
|
@pytest.mark.parametrize('call', ['foo(bar=1', 'foo(bar=1,', 'foo(foo(bar=1'])
|
||||||
|
def test_goto_incomplete_named_argument(Script, prefix, call):
|
||||||
|
code = prefix + call
|
||||||
|
column = code.splitlines()[-1].index('bar')
|
||||||
|
assert Script(code).goto(column=column) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_goto_named_argument_in_complete_inner_call(Script):
|
||||||
|
param, = Script('def foo(bar): pass\nfoo(foo(bar=1)').goto(2, 8)
|
||||||
|
assert (param.name, param.line, param.column) == ('bar', 1, 8)
|
||||||
|
|
||||||
|
|
||||||
def test_goto_module(Script):
|
def test_goto_module(Script):
|
||||||
def check(line, expected, follow_imports=False):
|
def check(line, expected, follow_imports=False):
|
||||||
script = Script(path=path)
|
script = Script(path=path)
|
||||||
@@ -412,3 +427,90 @@ def test_infer_after_parentheses(Script, code, column, expected):
|
|||||||
assert completions == []
|
assert completions == []
|
||||||
else:
|
else:
|
||||||
assert [c.name for c in completions] == [expected]
|
assert [c.name for c in completions] == [expected]
|
||||||
|
|
||||||
|
|
||||||
|
def test_import_does_not_change_recursion_limit():
|
||||||
|
# Regression test for #1925: importing jedi must not lower a higher
|
||||||
|
# user-defined limit, nor raise a lower one.
|
||||||
|
for limit in (1200, 5000):
|
||||||
|
code = ('import sys; sys.setrecursionlimit(%d); import jedi; '
|
||||||
|
'print(sys.getrecursionlimit())' % limit)
|
||||||
|
output = subprocess.check_output([sys.executable, '-c', code])
|
||||||
|
assert output.strip() == str(limit).encode()
|
||||||
|
|
||||||
|
|
||||||
|
def test_recursion_limit_is_scoped_and_restored():
|
||||||
|
from jedi.api.helpers import higher_recursion_limit, RECURSION_LIMIT
|
||||||
|
|
||||||
|
old_limit = sys.getrecursionlimit()
|
||||||
|
sys.setrecursionlimit(1000)
|
||||||
|
try:
|
||||||
|
with higher_recursion_limit():
|
||||||
|
assert sys.getrecursionlimit() == RECURSION_LIMIT
|
||||||
|
assert sys.getrecursionlimit() == 1000
|
||||||
|
sys.setrecursionlimit(5000)
|
||||||
|
with higher_recursion_limit():
|
||||||
|
assert sys.getrecursionlimit() == 5000
|
||||||
|
assert sys.getrecursionlimit() == 5000
|
||||||
|
finally:
|
||||||
|
sys.setrecursionlimit(old_limit)
|
||||||
|
|
||||||
|
|
||||||
|
def test_deep_code_works_with_low_recursion_limit(Script):
|
||||||
|
old_limit = sys.getrecursionlimit()
|
||||||
|
sys.setrecursionlimit(1000)
|
||||||
|
try:
|
||||||
|
code = 'x = ' + '(' * 200 + '1' + ')' * 200 + '\nx.'
|
||||||
|
assert Script(code).complete(2, 2)
|
||||||
|
assert sys.getrecursionlimit() == 1000
|
||||||
|
finally:
|
||||||
|
sys.setrecursionlimit(old_limit)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('method', [
|
||||||
|
'infer', 'get_type_hint', 'get_signatures', 'execute', 'defined_names',
|
||||||
|
])
|
||||||
|
def test_name_inference_recursion_limit(Script, method):
|
||||||
|
old_limit = sys.getrecursionlimit()
|
||||||
|
sys.setrecursionlimit(1000)
|
||||||
|
try:
|
||||||
|
name = Script('x = ' + '(' * 200 + '1' + ')' * 200).get_names()[0]
|
||||||
|
getattr(name, method)()
|
||||||
|
assert sys.getrecursionlimit() == 1000
|
||||||
|
finally:
|
||||||
|
sys.setrecursionlimit(old_limit)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('method', ['search', 'complete_search'])
|
||||||
|
def test_project_search_recursion_limit(tmp_path, method):
|
||||||
|
from jedi import Project
|
||||||
|
|
||||||
|
(tmp_path / 'sample.py').write_text('x = ' + '(' * 200 + '1' + ')' * 200)
|
||||||
|
old_limit = sys.getrecursionlimit()
|
||||||
|
sys.setrecursionlimit(1000)
|
||||||
|
try:
|
||||||
|
results = getattr(Project(tmp_path), method)('sample.x.real')
|
||||||
|
assert sys.getrecursionlimit() == 1000
|
||||||
|
assert next(results).name == 'real'
|
||||||
|
assert sys.getrecursionlimit() == 1000
|
||||||
|
results.close()
|
||||||
|
assert sys.getrecursionlimit() == 1000
|
||||||
|
finally:
|
||||||
|
sys.setrecursionlimit(old_limit)
|
||||||
|
|
||||||
|
|
||||||
|
def test_recursion_limit_restored_after_error():
|
||||||
|
from jedi.api.helpers import recursion_limit
|
||||||
|
|
||||||
|
@recursion_limit
|
||||||
|
def fail():
|
||||||
|
raise ValueError('test')
|
||||||
|
|
||||||
|
old_limit = sys.getrecursionlimit()
|
||||||
|
sys.setrecursionlimit(1000)
|
||||||
|
try:
|
||||||
|
with pytest.raises(ValueError, match='test'):
|
||||||
|
fail()
|
||||||
|
assert sys.getrecursionlimit() == 1000
|
||||||
|
finally:
|
||||||
|
sys.setrecursionlimit(old_limit)
|
||||||
@@ -462,3 +462,21 @@ def test_module_completions(Script, module):
|
|||||||
|
|
||||||
def test_whitespace_at_end_after_dot(Script):
|
def test_whitespace_at_end_after_dot(Script):
|
||||||
assert 'strip' in [c.name for c in Script('str. ').complete()]
|
assert 'strip' in [c.name for c in Script('str. ').complete()]
|
||||||
|
|
||||||
|
|
||||||
|
def test_param_docstring_in_completion(Script):
|
||||||
|
# From Github #2063
|
||||||
|
code = '''\
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
def func(arg: Optional[int]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
func(arg'''
|
||||||
|
script = Script(code=code)
|
||||||
|
completions = script.complete(
|
||||||
|
line=len(code.splitlines()),
|
||||||
|
column=len(code.splitlines()[-1])
|
||||||
|
)
|
||||||
|
c, = completions
|
||||||
|
assert c.docstring() == ''
|
||||||
@@ -53,6 +53,21 @@ def x():
|
|||||||
]
|
]
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
async_code = '''\
|
||||||
|
async def coro():
|
||||||
|
return None
|
||||||
|
'''
|
||||||
|
async_method = '''\
|
||||||
|
class C:
|
||||||
|
async def coro(self):
|
||||||
|
return None
|
||||||
|
'''
|
||||||
|
async_nested = '''\
|
||||||
|
async def outer():
|
||||||
|
async def inner():
|
||||||
|
return None
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'code, line, column, full_name, expected_parents', [
|
'code, line, column, full_name, expected_parents', [
|
||||||
@@ -106,6 +121,21 @@ def x():
|
|||||||
(with_brackets, 3, None, 'myfile', []),
|
(with_brackets, 3, None, 'myfile', []),
|
||||||
(with_brackets, 4, 4, 'myfile.x', ['x']),
|
(with_brackets, 4, 4, 'myfile.x', ['x']),
|
||||||
(with_brackets, 4, 5, 'myfile.x', ['x']),
|
(with_brackets, 4, 5, 'myfile.x', ['x']),
|
||||||
|
|
||||||
|
(async_code, 2, 0, 'myfile', []),
|
||||||
|
(async_code, 2, 4, 'myfile.coro', ['coro']),
|
||||||
|
(async_code, 2, 5, 'myfile.coro', ['coro']),
|
||||||
|
(async_code, 2, None, 'myfile.coro', ['coro']),
|
||||||
|
('@decorator\n' + async_code, 3, 0, 'myfile', []),
|
||||||
|
('@decorator\n' + async_code, 3, 4, 'myfile.coro', ['coro']),
|
||||||
|
('@decorator\n' + async_code, 3, 5, 'myfile.coro', ['coro']),
|
||||||
|
(async_method, 3, 4, 'myfile.C', ['C']),
|
||||||
|
(async_method, 3, 8, 'myfile.C.coro', ['C', 'coro']),
|
||||||
|
(async_method, 3, 9, 'myfile.C.coro', ['C', 'coro']),
|
||||||
|
(async_nested, 3, 0, 'myfile', []),
|
||||||
|
(async_nested, 3, 4, 'myfile.outer', ['outer']),
|
||||||
|
(async_nested, 3, 8, None, ['outer', 'inner']),
|
||||||
|
(async_nested, 3, 9, None, ['outer', 'inner']),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
def test_context(Script, code, line, column, full_name, expected_parents):
|
def test_context(Script, code, line, column, full_name, expected_parents):
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -54,6 +55,24 @@ def test_load_save_project(tmpdir):
|
|||||||
assert loaded.added_sys_path == ['/foo']
|
assert loaded.added_sys_path == ['/foo']
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_project_ignores_environment_path(tmpdir, monkeypatch):
|
||||||
|
# The project file can be part of a checked out repository, so it must not
|
||||||
|
# be able to point Jedi at a binary to execute.
|
||||||
|
def _get_subprocess(self):
|
||||||
|
raise RuntimeError('Should not get called!')
|
||||||
|
|
||||||
|
monkeypatch.setattr('jedi.api.environment.Environment._get_subprocess',
|
||||||
|
_get_subprocess)
|
||||||
|
|
||||||
|
Project(tmpdir.strpath, environment_path=sys.executable).save()
|
||||||
|
assert Project.load(tmpdir.strpath)._environment_path is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_project_ignores_unsafe_extensions(tmpdir):
|
||||||
|
Project(tmpdir.strpath, load_unsafe_extensions=True).save()
|
||||||
|
assert Project.load(tmpdir.strpath).load_unsafe_extensions is False
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'string, full_names, kwargs', [
|
'string, full_names, kwargs', [
|
||||||
('test_load_save_project', ['test_api.test_project.test_load_save_project'], {}),
|
('test_load_save_project', ['test_api.test_project.test_load_save_project'], {}),
|
||||||
|
|||||||
Reference in new issue
Block a user