Compare commits

..
18 Commits
Author SHA1 Message Date
Dave Halter ab89beb06c Make sure parso is the latest version 2026-10-12 17:54:53 +02:00
Dave Halter b7009b938a Create release for 0.20.1 2026-10-12 17:48:29 +02:00
Dave Halter 142bd1d0be Fix a small typo 2026-10-12 17:40:32 +02:00
Dave Halter 575c6ab378 Don't save properties to project.json that are dropped anyway 2026-10-12 17:34:14 +02:00
Dave Halter c71dead915 Change a bit of typing information 2026-10-12 17:06:43 +02:00
Dave Halter 091735802d Add to the Changelog 2026-10-12 17:01:24 +02:00
Dave Halter 102700116c Fix py__path__ availability in #2118 2026-10-12 16:43:09 +02:00
Dave Halter 79d9764bce Merge pull request #2119 from abdull-ah-med/codex/async-pytest-fixtures
Recognize asynchronous pytest fixtures
2026-10-12 11:05:00 +00:00
Abdullah AhmedandClaude Sonnet 5.5 3f7e8adddc Honor return annotations of async pytest fixtures
Async fixtures skipped the annotation branch, so a `-> T` annotation was
ignored and only the body was inferred. Infer the annotation directly:
the awaited value for coroutines, and the yielded value for annotated
async generators. Add cases where the body is not inferable.

Also drop the tooling note from the AUTHORS entry.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 04:52:32 -04:00
Abdullah Ahmed d8efff8e30 Recognize asynchronous pytest fixtures 2026-10-12 04:05:48 -04:00
Dave Halter 2e3e905707 Add to the changelog 2026-10-01 08:42:53 +02:00
Charan Rathore 558135e8f3 Stop changing the global recursion limit on import (#2113) 2026-10-01 06:38:00 +00:00
Dhanu-dynamic f37a9f9feb Ignore environment_path in loaded project files (#2110)
A .jedi/project.json can be part of a checked out repository, so honouring its environment_path let an untrusted file point Jedi at a binary to execute. Rather than trying to gate that binary (the ownership heuristic in _is_unix_safe_simple is meaningless on Windows, where st_uid is always 0), drop environment_path from loaded project files entirely, the same way load_unsafe_extensions is already ignored. It stays available when a Project is constructed directly by the user.
2026-09-29 13:58:51 +00:00
Dave Halter ed47f89162 Fix docstrings for param completions, fixes #2063, fixes #2109 2026-09-27 11:41:13 +02:00
Dhanu-dynamic 112d255414 Do not trust code execution settings from .jedi/project.json (#2108)
get_default_project() loads the first .jedi/project.json above the analysed file, which can be part of a checked out repository. Its environment_path was executed with safe=False and its load_unsafe_extensions was honoured. Loaded projects now check the binary like find_virtualenvs and ignore load_unsafe_extensions.
2026-09-26 12:52:55 +00:00
曾楚笑 5c37b736a3 Fix get_context indentation for async functions (#2107) 2026-09-22 21:50:04 +00:00
Elias 19236b472b Avoid goto crashes on unfinished keyword calls (#2106) 2026-09-20 07:13:11 +00:00
ef5d361121 Fix typos in comments and docstrings (#2101)
ci / tests (3.10, ubuntu-24.04, 3.10) (push) Canceled after 0s
ci / tests (3.10, ubuntu-24.04, 3.11) (push) Canceled after 0s
ci / tests (3.10, ubuntu-24.04, 3.12) (push) Canceled after 0s
ci / tests (3.10, ubuntu-24.04, 3.13) (push) Canceled after 0s
ci / tests (3.10, ubuntu-24.04, 3.14) (push) Canceled after 0s
ci / tests (3.10, windows-2022, 3.10) (push) Canceled after 0s
ci / tests (3.10, windows-2022, 3.11) (push) Canceled after 0s
ci / tests (3.10, windows-2022, 3.12) (push) Canceled after 0s
ci / tests (3.10, windows-2022, 3.13) (push) Canceled after 0s
ci / tests (3.10, windows-2022, 3.14) (push) Canceled after 0s
ci / tests (3.11, ubuntu-24.04, 3.10) (push) Canceled after 0s
ci / tests (3.11, ubuntu-24.04, 3.11) (push) Canceled after 0s
ci / tests (3.11, ubuntu-24.04, 3.12) (push) Canceled after 0s
ci / tests (3.11, ubuntu-24.04, 3.13) (push) Canceled after 0s
ci / tests (3.11, ubuntu-24.04, 3.14) (push) Canceled after 0s
ci / tests (3.11, windows-2022, 3.10) (push) Canceled after 0s
ci / tests (3.11, windows-2022, 3.11) (push) Canceled after 0s
ci / tests (3.11, windows-2022, 3.12) (push) Canceled after 0s
ci / tests (3.11, windows-2022, 3.13) (push) Canceled after 0s
ci / tests (3.11, windows-2022, 3.14) (push) Canceled after 0s
ci / tests (3.12, ubuntu-24.04, 3.10) (push) Canceled after 0s
ci / tests (3.12, ubuntu-24.04, 3.11) (push) Canceled after 0s
ci / tests (3.12, ubuntu-24.04, 3.12) (push) Canceled after 0s
ci / tests (3.12, ubuntu-24.04, 3.13) (push) Canceled after 0s
ci / tests (3.12, ubuntu-24.04, 3.14) (push) Canceled after 0s
ci / tests (3.12, windows-2022, 3.10) (push) Canceled after 0s
ci / tests (3.12, windows-2022, 3.11) (push) Canceled after 0s
ci / tests (3.12, windows-2022, 3.12) (push) Canceled after 0s
ci / tests (3.12, windows-2022, 3.13) (push) Canceled after 0s
ci / tests (3.12, windows-2022, 3.14) (push) Canceled after 0s
ci / tests (3.13, ubuntu-24.04, 3.10) (push) Canceled after 0s
ci / tests (3.13, ubuntu-24.04, 3.11) (push) Canceled after 0s
ci / tests (3.13, ubuntu-24.04, 3.12) (push) Canceled after 0s
ci / tests (3.13, ubuntu-24.04, 3.13) (push) Canceled after 0s
ci / tests (3.13, ubuntu-24.04, 3.14) (push) Canceled after 0s
ci / tests (3.13, windows-2022, 3.10) (push) Canceled after 0s
ci / tests (3.13, windows-2022, 3.11) (push) Canceled after 0s
ci / tests (3.13, windows-2022, 3.12) (push) Canceled after 0s
ci / tests (3.13, windows-2022, 3.13) (push) Canceled after 0s
ci / tests (3.13, windows-2022, 3.14) (push) Canceled after 0s
ci / tests (3.14, ubuntu-24.04, 3.10) (push) Canceled after 0s
ci / tests (3.14, ubuntu-24.04, 3.11) (push) Canceled after 0s
ci / tests (3.14, ubuntu-24.04, 3.12) (push) Canceled after 0s
ci / tests (3.14, ubuntu-24.04, 3.13) (push) Canceled after 0s
ci / tests (3.14, ubuntu-24.04, 3.14) (push) Canceled after 0s
ci / tests (3.14, windows-2022, 3.10) (push) Canceled after 0s
ci / tests (3.14, windows-2022, 3.11) (push) Canceled after 0s
ci / tests (3.14, windows-2022, 3.12) (push) Canceled after 0s
ci / tests (3.14, windows-2022, 3.13) (push) Canceled after 0s
ci / tests (3.14, windows-2022, 3.14) (push) Canceled after 0s
ci / tests (interpreter, ubuntu-24.04, 3.10) (push) Canceled after 0s
ci / tests (interpreter, ubuntu-24.04, 3.11) (push) Canceled after 0s
ci / tests (interpreter, ubuntu-24.04, 3.12) (push) Canceled after 0s
ci / tests (interpreter, ubuntu-24.04, 3.13) (push) Canceled after 0s
ci / tests (interpreter, ubuntu-24.04, 3.14) (push) Canceled after 0s
ci / tests (interpreter, windows-2022, 3.10) (push) Canceled after 0s
ci / tests (interpreter, windows-2022, 3.11) (push) Canceled after 0s
ci / tests (interpreter, windows-2022, 3.12) (push) Canceled after 0s
ci / tests (interpreter, windows-2022, 3.13) (push) Canceled after 0s
ci / tests (interpreter, windows-2022, 3.14) (push) Canceled after 0s
ci / code-quality (push) Canceled after 0s
ci / coverage (push) Canceled after 0s
Found by codespell:
- curent -> current (classes.py)
- suppport -> support, propeties -> properties (inference/__init__.py)
- existance -> existence (inference/analysis.py)
- Propably -> Probably (inference/value/instance.py)

Co-authored-by: maxtaran2010 <ocotifuzo727@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-09 23:42:11 +00:00
20 changed files with 369 additions and 20 deletions

No files matched your search

+5
View File
@@ -66,6 +66,11 @@ Code Contributors
- Martin Vielsmaier (@moser) <martin@vielsmaier.net>
- TingJia Wu (@WutingjiaX) <wutingjia@bytedance.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.
+8
View File
@@ -6,6 +6,14 @@ Changelog
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)
+++++++++++++++++++
+1 -1
View File
@@ -27,7 +27,7 @@ ad
load
"""
__version__ = '0.20.0'
__version__ = '0.20.1'
from jedi.api import Script, Interpreter, set_debug_function, preload_module
from jedi import settings
+17 -6
View File
@@ -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
arguments.
"""
import sys
from pathlib import Path
import parso
@@ -21,7 +20,7 @@ from jedi.file_io import KnownContentFileIO
from jedi.api import classes
from jedi.api import interpreter
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.keywords import KeywordName
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.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:
"""
@@ -97,6 +92,7 @@ class Script:
references works well, because the right folder is searched. There are
also ways to modify the sys path and other things.
"""
@recursion_limit
def __init__(self, code=None, *, path=None, environment=None, project=None):
self._orig_path = path
if isinstance(path, str):
@@ -191,6 +187,7 @@ class Script:
self._inference_state.environment,
)
@recursion_limit
@validate_line_column
def complete(self, line=None, column=None, *, fuzzy=False):
"""
@@ -214,6 +211,7 @@ class Script:
)
return completion.complete()
@recursion_limit
@validate_line_column
def infer(self, line=None, column=None, *, only_stubs=False, prefer_stubs=False):
"""
@@ -259,6 +257,7 @@ class Script:
# the API.
return helpers.sorted_definitions(set(defs))
@recursion_limit
@validate_line_column
def goto(self, line=None, column=None, *, follow_imports=False, follow_builtin_imports=False,
only_stubs=False, prefer_stubs=False):
@@ -324,6 +323,7 @@ class Script:
"""
return self._search_func(string, all_scopes=all_scopes)
@recursion_limit
@to_list
def _search_func(self, string, all_scopes=False, complete=False, fuzzy=False):
names = self._names(all_scopes=all_scopes)
@@ -353,6 +353,7 @@ class Script:
"""
return self._search_func(string, complete=True, **kwargs)
@recursion_limit
@validate_line_column
def help(self, line=None, column=None):
"""
@@ -397,6 +398,7 @@ class Script:
return [classes.Name(self._inference_state, name)]
return []
@recursion_limit
@validate_line_column
def get_references(self, line=None, column=None, **kwargs):
"""
@@ -428,6 +430,7 @@ class Script:
return helpers.sorted_definitions(definitions)
return _references(**kwargs)
@recursion_limit
@validate_line_column
def get_signatures(self, line=None, column=None):
"""
@@ -466,6 +469,7 @@ class Script:
return [classes.Signature(self._inference_state, signature, call_details)
for signature in definitions.get_signatures()]
@recursion_limit
@validate_line_column
def get_context(self, line=None, column=None):
"""
@@ -502,6 +506,8 @@ class Script:
tree_name = name.tree_name
if tree_name is not None: # Happens with lambdas.
scope = tree_name.get_definition()
if scope.parent.type in ('async_stmt', 'async_funcdef'):
scope = scope.parent
if scope.start_pos[1] < column:
break
definition = definition.parent()
@@ -541,6 +547,7 @@ class Script:
finally:
self._inference_state.is_analysis = False
@recursion_limit
def get_names(self, **kwargs):
"""
Returns names defined in the current file.
@@ -579,6 +586,7 @@ class Script:
]
return sorted(defs, key=lambda x: x.start_pos)
@recursion_limit
def rename(self, line=None, column=None, *, new_name):
"""
Renames all references of the variable under the cursor.
@@ -591,6 +599,7 @@ class Script:
definitions = self.get_references(line, column, include_builtins=False)
return refactoring.rename(self._inference_state, definitions, new_name)
@recursion_limit
@validate_line_column
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
)
@recursion_limit
@validate_line_column
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
)
@recursion_limit
def inline(self, line=None, column=None):
"""
Inlines a variable under the cursor. This is basically the opposite of
+16 -3
View File
@@ -28,7 +28,7 @@ from jedi.inference.gradual.conversion import convert_names, convert_values
from jedi.inference.base_value import ValueSet, HasNoContext
from jedi.api.keywords import KeywordName
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):
@@ -262,6 +262,7 @@ class BaseName:
return last_leaf.end_pos
return definition.end_pos
@recursion_limit
def docstring(self, raw=False, fast=True):
r"""
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'
@debug.increase_indent_cm('goto on name')
@recursion_limit
def goto(self, *, follow_imports=False, follow_builtin_imports=False,
only_stubs=False, prefer_stubs=False):
@@ -452,6 +454,7 @@ class BaseName:
for n in names]
@debug.increase_indent_cm('infer on name')
@recursion_limit
def infer(self, *, only_stubs=False, prefer_stubs=False):
"""
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])
def _get_signatures(self, for_docstring=False):
if self._name.api_type == 'property':
if self._name.api_type in ('property', 'param'):
return []
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
@@ -567,6 +570,7 @@ class BaseName:
names = convert_names([self._name], prefer_stubs=True)
return [sig for name in names for sig in name.infer().get_signatures()]
@recursion_limit
def get_signatures(self):
"""
Returns all potential signatures for a function or a class. Multiple
@@ -579,6 +583,7 @@ class BaseName:
for s in self._get_signatures()
]
@recursion_limit
def execute(self):
"""
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())
@recursion_limit
def get_type_hint(self):
"""
Returns type hints like ``Iterable[int]`` or ``Union[int, str]``.
@@ -670,6 +676,7 @@ class Completion(BaseName):
"""
return self._complete(False)
@recursion_limit
def docstring(self, raw=False, fast=True):
"""
Documented under :meth:`BaseName.docstring`.
@@ -752,6 +759,7 @@ class Name(BaseName):
super().__init__(inference_state, definition)
@memoize_method
@recursion_limit
def defined_names(self):
"""
List sub-definitions (e.g., methods in class).
@@ -797,6 +805,7 @@ class BaseSignature(Name):
self._signature = signature
@property
@recursion_limit
def params(self):
"""
Returns definitions for all parameters that a signature defines.
@@ -807,6 +816,7 @@ class BaseSignature(Name):
return [ParamName(self._inference_state, n)
for n in self._signature.get_param_names(resolve_stars=True)]
@recursion_limit
def to_string(self):
"""
Returns a text representation of the signature. This could for example
@@ -831,7 +841,7 @@ class Signature(BaseSignature):
def index(self):
"""
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
"""
@@ -858,6 +868,7 @@ class Signature(BaseSignature):
class ParamName(Name):
@recursion_limit
def infer_default(self):
"""
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())
@recursion_limit
def infer_annotation(self, **kwargs):
"""
: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))
@recursion_limit
def to_string(self):
"""
Returns a simple representation of a param, like
+44
View File
@@ -2,7 +2,9 @@
Helpers for the API
"""
import re
import sys
from collections import namedtuple
from contextlib import contextmanager
from textwrap import dedent
from itertools import chain
from functools import wraps
@@ -21,6 +23,48 @@ from jedi.parser_utils import get_parent_scope
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):
return string.startswith(like_name)
+19 -2
View File
@@ -15,7 +15,7 @@ from jedi import debug
from jedi.api.environment import get_cached_default_environment, create_environment
from jedi.api.exceptions import WrongVersion
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, \
load_namespace_from_path, iter_module_names
from jedi.inference.sys_path import discover_buildout_paths
@@ -86,6 +86,14 @@ class Project:
version, data = json.load(f)
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)
else:
raise WrongVersion(
@@ -99,6 +107,11 @@ class Project:
data = dict(self.__dict__)
data.pop('_environment', None)
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['path'] = str(data['path'])
@@ -242,7 +255,10 @@ class Project:
def get_environment(self):
if self._environment is 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:
self._environment = get_cached_default_environment()
return self._environment
@@ -282,6 +298,7 @@ class Project:
"""
return self._search_func(string, complete=True, **kwargs)
@recursion_limit_generator
@_try_to_skip_duplicates
def _search_func(self, string, complete=False, all_scopes=False):
# Using a Script is they easiest way to get an empty module context.
+1 -1
View File
@@ -125,7 +125,7 @@ class InferenceState:
debug.dbg('execute result: %s in %s', value_set, value)
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
@inference_state_function_cache()
def builtins_module(self):
+1 -1
View File
@@ -110,7 +110,7 @@ def _check_for_setattr(instance):
def add_attribute_error(name_context, 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.
typ = Error
if lookup_value.is_instance() and not lookup_value.is_compiled():
+2 -2
View File
@@ -9,7 +9,7 @@ import re
import builtins
import typing
from pathlib import Path
from typing import Optional, Tuple
from typing import Optional, Tuple, Any
from jedi.inference.compiled.getattr_static import getattr_static
@@ -46,7 +46,7 @@ ClassMethodDescriptorType = type(object_class_dict['__subclasshook__'])
_sentinel = object()
# Maps Python syntax to the operator module.
COMPARISON_OPERATORS = {
COMPARISON_OPERATORS: dict[str, Any] = {
'==': op.eq,
'!=': op.ne,
'is': op.is_,
+9 -1
View File
@@ -416,7 +416,15 @@ def import_module(inference_state, import_names, parent_module_value, sys_path):
if is_pkg is None:
return NO_VALUES
else:
paths = parent_module_value.py__path__()
try:
py__path__ = parent_module_value.py__path__
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
else:
paths = py__path__()
if paths is None:
# The module might not be a package.
return NO_VALUES
+2
View File
@@ -170,6 +170,8 @@ class AbstractTreeName(AbstractNameDefinition):
trailer = par.parent
if trailer.type == 'arglist':
trailer = trailer.parent
if trailer.type == 'error_node':
return []
if trailer.type != 'classdef':
if trailer.type == 'decorator':
value_set = context.infer_node(trailer.children[1])
+1 -1
View File
@@ -224,7 +224,7 @@ class _BaseTreeInstance(AbstractInstanceValue):
elif isinstance(f, CompiledValueFilter):
yield CompiledInstanceClassFilter(self, f)
else:
# Propably from the metaclass.
# Probably from the metaclass.
yield f
@inference_state_method_cache()
+17
View File
@@ -2,7 +2,9 @@ import sys
from typing import List
from pathlib import Path
from jedi.inference.arguments import ValuesArguments
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.filters import ParserTreeFilter
from jedi.inference.base_value import NO_VALUES, ValueSet
@@ -32,6 +34,19 @@ def execute(callback):
def infer_anonymous_param(func):
def get_returns(value):
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()
if any(v.name.get_qualified_names(include_module_names=True)
== ('typing', 'Generator')
@@ -223,6 +238,8 @@ class FixtureFilter(ParserTreeFilter):
if funcdef.type != "funcdef":
return False
decorated = funcdef.parent
if decorated.type == "async_funcdef":
decorated = decorated.parent
if decorated.type != "decorated":
return False
decorators = decorated.children[0]
+1 -1
View File
@@ -37,7 +37,7 @@ setup(name='jedi',
packages=find_packages(exclude=['test', 'test.*']),
python_requires='>=3.10',
# 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={
'dev': [
'pytest<9.0.0',
+56 -1
View File
@@ -1,4 +1,4 @@
from typing import Generator
from typing import AsyncGenerator, Generator
import pytest
from pytest import fixture
@@ -208,3 +208,58 @@ def bla(admin_u
def bla(admin_user):
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
+102
View File
@@ -3,6 +3,8 @@ Test all things related to the ``jedi.api`` module.
"""
import os
import subprocess
import sys
from textwrap import dedent
import pytest
@@ -231,6 +233,19 @@ def test_goto_follow_imports(Script):
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 check(line, expected, follow_imports=False):
script = Script(path=path)
@@ -412,3 +427,90 @@ def test_infer_after_parentheses(Script, code, column, expected):
assert completions == []
else:
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)
+18
View File
@@ -462,3 +462,21 @@ def test_module_completions(Script, module):
def test_whitespace_at_end_after_dot(Script):
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() == ''
+30
View File
@@ -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(
'code, line, column, full_name, expected_parents', [
@@ -106,6 +121,21 @@ def x():
(with_brackets, 3, None, 'myfile', []),
(with_brackets, 4, 4, '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):
+19
View File
@@ -1,4 +1,5 @@
import os
import sys
from pathlib import Path
import pytest
@@ -54,6 +55,24 @@ def test_load_save_project(tmpdir):
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(
'string, full_names, kwargs', [
('test_load_save_project', ['test_api.test_project.test_load_save_project'], {}),