Compare commits

..
16 Commits
Author SHA1 Message Date
Dave Halter b91f57bec2 Disallow LLM usage
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
2026-11-02 12:12:44 +01:00
Dave Halter c7ceec72f1 Merge pull request #2124 from Don-Yin/fix-comprehension-full-name
Preserve comprehension scopes in full_name
2026-10-25 10:21:19 +00:00
Don Yin 11e1a965be Preserve comprehension scopes in full_name
Signed-off-by: Don Yin <Don_Yin@outlook.com>
2026-10-24 05:32:56 +01:00
Dave Halter 65112c72c7 Merge pull request #2122 from spicysharkco/fix/extract-function-no-output
Fix extract_function when the extracted code defines no variable
2026-10-18 07:40:13 +00:00
spicysharkco 897cbff748 Fix extract_function when the extracted code defines no variable
If the extracted lines define no name, the new function ended with a
bare "return " and the call site became " = func(v1)". Both are
invalid Python.

Only add the final return line and the assignment when there is at
least one variable to return. Otherwise call the function as a
statement.
2026-10-18 02:07:55 +00:00
Dave Halter 2af1a0f394 Fix tests 2026-10-13 12:23:01 +02:00
Dave Halter 3fd1c87716 Avoid recursions in signatures, fixes #2085 2026-10-13 11:30:30 +02:00
Dave Halter e7843ba82c Fix a test issue 2026-10-13 09:30:21 +02:00
Dave Halter 5d3295e0f9 Implement pyrepl completions, fixes #2069 2026-10-13 03:13:55 +02:00
Dave Halter 91206f4c65 Allow typing.overload and typing_extensions.overload, fixes #1840, fixes #2117 2026-10-12 23:03:31 +02:00
Dave Halter 8a56168d9a Upgrade zuban 2026-10-12 23:01:49 +02:00
Dave Halter a94b899c13 Avoid an issue around lambdas, fixes #1978 2026-10-12 22:37:24 +02:00
Dave Halter dd7d4a92f4 Avoid a crash, fixes #1990 2026-10-12 22:33:34 +02:00
Dave Halter ea1b96ff10 Add better typing for completions 2026-10-12 21:08:40 +02:00
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
24 changed files with 278 additions and 51 deletions

No files matched your search

+2
View File
@@ -71,6 +71,8 @@ Code Contributors
- Dhanraj E (@Dhanu-dynamic) <dhanraj@labs.digiscrypt.com>
- Charan Rathore (@charan-rathore)
- Abdullah Ahmed (@abdull-ah-med)
- spicysharkco (@spicysharkco)
- Don Yin (@Don-Yin) <Don_Yin@outlook.com>
And a few more "anonymous" contributors.
+1
View File
@@ -12,6 +12,7 @@ Unreleased
- 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)
+++++++++++++++++++
+4
View File
@@ -6,3 +6,7 @@ Pull Requests are great.
4. Push to your fork and submit a pull request.
**Try to use the PEP8 style guide** (and it's ok to have a line length of 100 characters).
# LLM usage
LLM usage is prohibited unless done by maintainers or people who understand what they do.
+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
+3 -2
View File
@@ -8,6 +8,7 @@ debug messages to stdout, simply call :func:`set_debug_function` without
arguments.
"""
from pathlib import Path
from typing import Any
import parso
from parso.python import tree
@@ -189,7 +190,7 @@ class Script:
@recursion_limit
@validate_line_column
def complete(self, line=None, column=None, *, fuzzy=False):
def complete(self, line=None, column=None, *, fuzzy=False) -> list[classes.Completion]:
"""
Completes objects under the cursor.
@@ -338,7 +339,7 @@ class Script:
fuzzy=fuzzy,
)
def complete_search(self, string, **kwargs):
def complete_search(self, string, **kwargs) -> list[Any]:
"""
Like :meth:`.Script.search`, but completes that string. If you want to
have all possible definitions in a file you can also provide an empty
+8 -5
View File
@@ -9,6 +9,7 @@ from textwrap import dedent
from itertools import chain
from functools import wraps
from inspect import Parameter
from typing import Callable, TypeVar, Any, ParamSpec
from parso.python.parser import Parser
from parso.python import tree
@@ -20,6 +21,8 @@ from jedi.inference.compiled import get_string_value_set
from jedi.cache import signature_time_cache, memoize_method
from jedi.parser_utils import get_parent_scope
T = TypeVar("T")
P = ParamSpec("P")
CompletionParts = namedtuple('CompletionParts', ['path', 'has_dot', 'name'])
@@ -42,7 +45,7 @@ def higher_recursion_limit():
sys.setrecursionlimit(old_limit)
def recursion_limit(func):
def recursion_limit(func: Callable[P, T]) -> Callable[P, T]:
@wraps(func)
def wrapper(*args, **kwargs):
with higher_recursion_limit():
@@ -509,9 +512,9 @@ def cache_signatures(inference_state, context, bracket_leaf, code_lines, user_po
)
def validate_line_column(func):
def validate_line_column(func: Callable[P, T]) -> Callable[P, T]:
@wraps(func)
def wrapper(self, line=None, column=None, *args, **kwargs):
def wrapper(self, line=None, column=None, *args, **kwargs) -> Any:
line = max(len(self._code_lines), 1) if line is None else line
if not (0 < line <= len(self._code_lines)):
raise ValueError('`line` parameter is not in a valid range.')
@@ -528,8 +531,8 @@ def validate_line_column(func):
raise ValueError('`column` parameter (%d) is not in a valid range '
'(0-%d) for line %d (%r).' % (
column, line_len, line, line_string))
return func(self, line, column, *args, **kwargs)
return wrapper
return func(self, line, column, *args, **kwargs) # type: ignore[arg-type]
return wrapper # type: ignore[return-value]
def get_module_names(module, all_scopes, definitions=True, references=False):
+5 -2
View File
@@ -247,7 +247,8 @@ def extract_function(inference_state, path, module_context, name, pos, until_pos
code_block = dedent(code_block)
if not has_ending_return_stmt:
output_var_str = ', '.join(return_variables)
code_block += 'return ' + output_var_str + '\n'
if return_variables:
code_block += 'return ' + output_var_str + '\n'
# Check if we have to raise RefactoringError
_check_for_non_extractables(nodes[:-1] if has_ending_return_stmt else nodes)
@@ -282,8 +283,10 @@ def extract_function(inference_state, path, module_context, name, pos, until_pos
else:
if has_ending_return_stmt:
replacement = 'return ' + function_call + '\n'
else:
elif return_variables:
replacement = output_var_str + ' = ' + function_call + '\n'
else:
replacement = function_call + '\n'
replacement_dct = _replace(nodes, replacement, function_code, pos,
insert_before_leaf, remaining_prefix)
+3 -3
View File
@@ -1,8 +1,8 @@
"""
To use Jedi completion in Python interpreter, add the following in your shell
setup (e.g., ``.bashrc``). This works only on Linux/Mac, because readline is
not available on Windows. If you still want Jedi autocompletion in your REPL,
just use IPython instead::
setup (e.g., ``.bashrc``). Jedi configures the classic readline shell and
Python 3.13+'s new ``_pyrepl`` shell when those hooks are available.
You might also want to try IPython instead, which also uses Jedi::
export PYTHONSTARTUP="$(python -m jedi repl)"
+1 -1
View File
@@ -216,7 +216,7 @@ class Value(HelperValueMixin):
def is_namespace(self):
return False
def is_compiled(self):
def is_compiled(self) -> bool:
return False
def is_bound_method(self):
+2 -3
View File
@@ -256,12 +256,11 @@ class CompiledValue(Value):
arg.execute_annotation(context)
for arg in arguments)
elif name:
# While with_generics only exists on very specific objects, we
# should probably be fine, because we control all the typing
# objects.
# These are typically typing module objects. Try to use that to create generics.
return ValueSet([
v.with_generics(arguments)
for v in self.inference_state.typing_module.py__getattribute__(name)
if hasattr(v, "with_generics")
]).execute_annotation(context)
return super().execute_annotation(context)
+6
View File
@@ -395,6 +395,12 @@ class CompForContext(TreeContextMixin, AbstractContext):
def py__name__(self):
return '<comprehension context>'
def get_qualified_names(self) -> Any:
names = self.parent_context.get_qualified_names()
if names is None:
return None
return names + ('<comprehension>',)
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self.tree_node)
+1 -1
View File
@@ -254,4 +254,4 @@ def _error_argument_count(funcdef, actual_count):
else:
before = 'from %s to ' % (len(params) - default_arguments)
return ('TypeError: %s() takes %s%s arguments (%s given).'
% (funcdef.name, before, len(params), actual_count))
% (getattr(funcdef, "name", "<lambda>"), before, len(params), actual_count))
+31 -15
View File
@@ -11,9 +11,10 @@ This means for example in this case::
The signature here for bar should be `bar(b, c)` instead of bar(*args).
"""
from inspect import Parameter
from typing import Sequence
from jedi.inference.utils import to_list
from jedi.inference.names import ParamNameWrapper
from jedi.inference.names import ParamNameWrapper, AnonymousParamName
from jedi.inference.helpers import is_big_annoying_library
@@ -93,8 +94,24 @@ def _remove_given_params(arguments, param_names):
yield p
class ParamNameFixedKind(ParamNameWrapper):
def __init__(self, param_name, new_kind):
super().__init__(param_name)
self._new_kind = new_kind
def get_kind(self):
return self._new_kind
@to_list
def process_params(param_names, star_count=3): # default means both * and **
def process_params(
param_names: Sequence[AnonymousParamName | ParamNameFixedKind],
star_count=3,
already_used=None):
# start_count=3 means both * and **
if already_used is None:
already_used = set()
if param_names:
if is_big_annoying_library(param_names[0].parent_context):
# At first this feature can look innocent, but it does a lot of
@@ -112,14 +129,22 @@ def process_params(param_names, star_count=3): # default means both * and **
original_arg_name = None
original_kwarg_name = None
for p in param_names:
def is_recursion():
is_recursion = p.tree_name in already_used
if not is_recursion:
already_used.add(p.tree_name)
return is_recursion
kind = p.get_kind()
if kind == Parameter.VAR_POSITIONAL:
if star_count & 1:
arg_callables = _iter_nodes_for_param(p)
if not is_recursion():
arg_callables = _iter_nodes_for_param(p)
original_arg_name = p
elif p.get_kind() == Parameter.VAR_KEYWORD:
if star_count & 2:
kwarg_callables = list(_iter_nodes_for_param(p))
if not is_recursion():
kwarg_callables = list(_iter_nodes_for_param(p))
original_kwarg_name = p
elif kind == Parameter.KEYWORD_ONLY:
if star_count & 2:
@@ -157,7 +182,7 @@ def process_params(param_names, star_count=3): # default means both * and **
list(_remove_given_params(
arguments,
signature.get_param_names(resolve_stars=False)
)), new_star_count):
)), new_star_count, already_used):
if p.get_kind() == Parameter.VAR_KEYWORD:
kwarg_names.append(p)
elif p.get_kind() == Parameter.VAR_POSITIONAL:
@@ -190,7 +215,7 @@ def process_params(param_names, star_count=3): # default means both * and **
list(_remove_given_params(
arguments,
signature.get_param_names(resolve_stars=False)
)), star_count=2):
)), star_count=2, already_used=already_used):
if p.get_kind() == Parameter.VAR_KEYWORD:
kwarg_names.append(p)
elif p.get_kind() == Parameter.KEYWORD_ONLY:
@@ -206,12 +231,3 @@ def process_params(param_names, star_count=3): # default means both * and **
yield original_kwarg_name
elif kwarg_names:
yield kwarg_names[0]
class ParamNameFixedKind(ParamNameWrapper):
def __init__(self, param_name, new_kind):
super().__init__(param_name)
self._new_kind = new_kind
def get_kind(self):
return self._new_kind
+5 -1
View File
@@ -2,6 +2,7 @@
import functools
import re
import os
from typing import ParamSpec, TypeVar, Callable, Iterable
_sep = os.path.sep
@@ -10,8 +11,11 @@ if os.path.altsep is not None:
_path_re = re.compile(r'(?:\.[^{0}]+|[{0}]__init__\.py)$'.format(re.escape(_sep)))
del _sep
P = ParamSpec("P")
T = TypeVar("T")
def to_list(func):
def to_list(func: Callable[P, Iterable[T]]) -> Callable[P, list[T]]:
def wrapper(*args, **kwargs):
return list(func(*args, **kwargs))
return wrapper
+4
View File
@@ -435,6 +435,10 @@ def _find_overload_functions(context, tree_node):
if dotted_name.type == 'name' and dotted_name.value == 'overload':
# TODO check with values if it's the right overload
return True
if dotted_name.type == 'atom_expr':
trailer = dotted_name.children[-1]
if trailer.children[0] == "." and trailer.children[1].value == "overload":
return True
return False
if tree_node.type == 'lambdef':
+41 -11
View File
@@ -100,17 +100,10 @@ def setup_readline(namespace_module=__main__, fuzzy=False):
except IndexError:
return None
try:
# Need to import this one as well to make sure it's executed before
# this code. This didn't use to be an issue until 3.3. Starting with
# 3.4 this is different, it always overwrites the completer if it's not
# already imported here.
import rlcompleter # noqa: F401
import readline
except ImportError:
print("Jedi: Module readline not available.")
else:
readline.set_completer(JediRL().complete)
jedi_rl = JediRL()
def configure_readline(readline):
readline.set_completer(jedi_rl.complete)
readline.parse_and_bind("tab: complete")
# jedi itself does the case matching
readline.parse_and_bind("set completion-ignore-case on")
@@ -122,6 +115,43 @@ def setup_readline(namespace_module=__main__, fuzzy=False):
# No delimiters, Jedi handles that.
readline.set_completer_delims('')
try:
import _pyrepl.readline as pyrepl_readline # type: ignore
except ImportError:
pyrepl_readline = None
else:
original_setup = getattr(pyrepl_readline, '_setup', None)
if original_setup is not None and not getattr(original_setup, '__jedi_wrapped__', False):
def _jedi_pyrepl_setup(namespace):
original_setup(namespace)
configure_readline(pyrepl_readline)
wrapper = getattr(pyrepl_readline, '_wrapper', None)
if wrapper is not None:
# Reset the original completer to None so Jedi's readline
# completer is preferred.
wrapper.config.module_completer = _NoPyreplModuleCompleter()
_jedi_pyrepl_setup.__jedi_wrapped__ = True # type: ignore[attr-defined]
pyrepl_readline._setup = _jedi_pyrepl_setup
try:
# Need to import this one as well to make sure it's executed before
# this code. This didn't use to be an issue until 3.3. Starting with
# 3.4 this is different, it always overwrites the completer if it's not
# already imported here.
import rlcompleter # noqa: F401
import readline
except ImportError:
if pyrepl_readline is None:
print("Jedi: Module readline not available.")
else:
configure_readline(readline)
class _NoPyreplModuleCompleter:
def get_completions(self, line, include_values=False):
return None
def version_info():
"""
+2 -2
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',
@@ -50,7 +50,7 @@ setup(name='jedi',
'typing_extensions',
# latest version on 2025-06-16
'flake8==7.1.2',
'zuban==0.7.0',
'zuban==0.10.1',
# Arbitrary pins, latest at the time of pinning
'types-setuptools==80.9.0.20250529',
],
+25
View File
@@ -86,3 +86,28 @@ def broken_f1(value: Tuple[T]) -> Tuple[T]: ...
tup: Tuple[float]
#? float()
broken_f1(broken_f1(tup))[0]
# -------------------------
# Typing Extensions
# -------------------------
import typing_extensions
import typing
@typing_extensions.overload
def extensions(x: int) -> int: ...
@typing_extensions.overload
def extensions(x: str) -> str: ...
#? int()
extensions(1)
#? str()
extensions("")
@typing.overload
def typing_direct(x: int) -> int: ...
@typing.overload
def typing_direct(x: str) -> str: ...
#? int()
typing_direct(1)
#? str()
typing_direct("")
+16
View File
@@ -314,6 +314,22 @@ def x(v1):
#bar
return x
x
# -------------------------------------------------- with-range-func-no-output
def x(v1):
#? 2 text {'new_name': 'func', 'until_line': 4}
print(v1)
print(v1 + 1)
return v1
# ++++++++++++++++++++++++++++++++++++++++++++++++++
def func(v1):
print(v1)
print(v1 + 1)
def x(v1):
#? 2 text {'new_name': 'func', 'until_line': 4}
func(v1)
return v1
# -------------------------------------------------- in-class-range-1
class X1:
#? 9 text {'new_name': 'f', 'until_line': 4}
+42
View File
@@ -128,3 +128,45 @@ def test_variable_in_func(Script):
x = names[-1]
assert x.name == 'x'
assert x.full_name == '__main__.f.x'
def test_comprehension_full_name_issue_1944(Script):
source = textwrap.dedent('''\
def pig_latin(text):
translated_words = [translate(word) for word in words]
''')
names = Script(source).get_names(all_scopes=True, references=True)
word_names = [name for name in names if name.name == 'word']
assert len(word_names) == 2
assert {name.full_name for name in word_names} == {
'__main__.pig_latin.<comprehension>.word'
}
@pytest.mark.parametrize('expression', [
'[word for word in words]',
'{word for word in words}',
'{word: word for word in words}',
'(word for word in words)',
'[word for row in rows for word in row]',
'[word for row in rows if row for word in row]',
])
def test_comprehension_full_name(Script, expression):
source = 'def f():\n return ' + expression
names = Script(source).get_names(all_scopes=True, references=True)
word_names = [name for name in names if name.name == 'word']
assert len(word_names) >= 2
assert {name.full_name for name in word_names} == {'__main__.f.<comprehension>.word'}
@pytest.mark.parametrize('source, expected', [
('[word for word in words]', '__main__.<comprehension>.word'),
('def f():\n return [[word for word in words] for words in rows]',
'__main__.f.<comprehension>.<comprehension>.word'),
('def outer():\n def f():\n return [word for word in words]', None),
])
def test_comprehension_parent_full_name(Script, source, expected):
names = Script(source).get_names(all_scopes=True, references=True)
word_names = [name for name in names if name.name == 'word']
assert len(word_names) == 2
assert {name.full_name for name in word_names} == {expected}
+17
View File
@@ -873,3 +873,20 @@ def test_whitespace_after_dot_completion(Script):
# From #1954
completions = jedi.Interpreter("object. \n", []).complete(1, 8)
assert "mro" in [c.name for c in completions]
def test_interpreter_with_literals_no_crash():
# From #1990
class A:
@property
def value(self) -> typing.Literal["a"]:
return "a"
a = A()
interpreter = jedi.Interpreter("a.", [{"a": a}])
value = next(filter(lambda c: c.name == "value", interpreter.complete()))
assert value.name == "value"
interpreter = jedi.Interpreter("a.value.", [{"a": a}])
assert "upper" in [c.name for c in interpreter.complete()]
+27
View File
@@ -1,3 +1,5 @@
from textwrap import dedent
import pytest
_tuple_code = 'from typing import Tuple\ndef f(x: Tuple[int]): ...\nf'
@@ -69,3 +71,28 @@ def test_param_kind_and_name(code, index, param_code, kind, Script):
def test_staticmethod(Script):
s, = Script('staticmethod(').get_signatures()
assert s.to_string() == 'staticmethod(f: Callable[_P, _R_co], /)'
def test_recursive_star_kwargs_do_not_recurse(Script):
# From #2085
code = dedent('''
def test(x, **kwargs):
if isinstance(x, int):
return test((x,), **kwargs)
test(
''')
script = Script(code)
script.complete()
signature, = script.get_signatures()
assert signature.to_string() == 'test(x, **kwargs)'
code = dedent('''
def test(x, *args, **kwargs):
if isinstance(x, int):
return test((x,), *args, **kwargs)
test(
''')
script = Script(code)
script.complete()
signature, = script.get_signatures()
assert signature.to_string() == 'test(x, *args, **kwargs)'
+4 -4
View File
@@ -165,7 +165,7 @@ def test_tree_signature(Script, environment, code, expected):
('two_kwargs_redirects(kw, kw)', '*args, a, b, c, **kwargs'),
('two_kwargs_redirects(simple, kw)', '*args, a, b, c, **kwargs'),
('two_kwargs_redirects(simple2, two_kwargs_redirects(simple, simple))',
'*args, x, a, b, c'),
'*args, x, **kwargs'),
('combined_redirect(simple, simple2)', 'a, b, /, *, x'),
('combined_redirect(simple, simple3)', 'a, b, /, *, a, x: int'),
@@ -180,11 +180,11 @@ def test_tree_signature(Script, environment, code, expected):
('combined_lot_of_args(simple4, kw)', '*, b, c, **kwargs'),
('combined_redirect(combined_redirect(simple2, simple4), combined_redirect(kw, simple5))',
'x, /, *, y'),
'*args, **kwargs'),
('combined_redirect(combined_redirect(simple4, simple2), combined_redirect(simple5, kw))',
'a, b, x: int, /, *, a, b, c, **kwargs'),
'*args, **kwargs'),
('combined_redirect(combined_redirect(a, kw), combined_redirect(kw, simple5))',
'a, b, /, *args, y'),
'*args, **kwargs'),
('no_redirect(kw)', '*args, **kwargs'),
('no_redirect(akw)', '*args, **kwargs'),
+27
View File
@@ -1,4 +1,5 @@
import sys
import types
from typing import Any
try:
@@ -136,3 +137,29 @@ class TestSetupReadline(unittest.TestCase):
def test_version_info():
assert utils.version_info()[:2] > (0, 7)
def test_pyrepl():
try:
from _pyrepl import readline # zuban: ignore[import-not-found]
except ImportError:
return # This is typical before Python 3.13
utils.setup_readline(types.SimpleNamespace())
readline._setup({})
def complete(text):
completer = readline.get_completer()
i = 0
completions = []
while True:
completion = completer(text, i)
if completion is None:
break
completions.append(completion)
i += 1
return completions
assert complete("str") == ["str"]
assert complete("str().upp") == ["str().upper"]
assert complete("import multiproc") == ["import multiprocessing"]