forked from VimPlug/jedi
Merge pull request #2097 from davidhalter/py314
Python 3.14; fixes #2093, fixes #2070, will fix #2064 after release.
This commit is contained in:
@@ -7,8 +7,8 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-24.04, windows-2022]
|
os: [ubuntu-24.04, windows-2022]
|
||||||
python-version: ["3.13", "3.12", "3.11", "3.10"]
|
python-version: ["3.14", "3.13", "3.12", "3.11", "3.10"]
|
||||||
environment: ['3.13', '3.12', '3.11', '3.10', 'interpreter']
|
environment: ['3.14', '3.13', '3.12', '3.11', '3.10', 'interpreter']
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
_VersionInfo = namedtuple('VersionInfo', 'major minor micro') # type: ignore[name-match]
|
_VersionInfo = namedtuple('VersionInfo', 'major minor micro') # type: ignore[name-match]
|
||||||
|
|
||||||
_SUPPORTED_PYTHONS = ['3.13', '3.12', '3.11', '3.10']
|
_SUPPORTED_PYTHONS = ['3.14', '3.13', '3.12', '3.11', '3.10']
|
||||||
_SAFE_PATHS = ['/usr/bin', '/usr/local/bin']
|
_SAFE_PATHS = ['/usr/bin', '/usr/local/bin']
|
||||||
_CONDA_VAR = 'CONDA_PREFIX'
|
_CONDA_VAR = 'CONDA_PREFIX'
|
||||||
_CURRENT_VERSION = '%s.%s' % (sys.version_info.major, sys.version_info.minor)
|
_CURRENT_VERSION = '%s.%s' % (sys.version_info.major, sys.version_info.minor)
|
||||||
|
|||||||
@@ -471,18 +471,23 @@ class DirectObjectAccess:
|
|||||||
op = _OPERATORS[operator]
|
op = _OPERATORS[operator]
|
||||||
return self._create_access_path(op(self._obj, other_access._obj))
|
return self._create_access_path(op(self._obj, other_access._obj))
|
||||||
|
|
||||||
def get_annotation_name_and_args(self):
|
def get_annotation_name_and_args(self) -> tuple[str | None, tuple[AccessPath, ...]]:
|
||||||
"""
|
"""
|
||||||
Returns Tuple[Optional[str], Tuple[AccessPath, ...]]
|
Returns Tuple[Optional[str], Tuple[AccessPath, ...]]
|
||||||
"""
|
"""
|
||||||
name = None
|
name = None
|
||||||
args = ()
|
args = ()
|
||||||
if safe_getattr(self._obj, '__module__', default='') == 'typing':
|
if type(self._obj) is typing.Union: # zuban: ignore[comparison-overlap] # TODO zuban
|
||||||
|
# This is mostly formatted like `int | str` and we therefor need to
|
||||||
|
# check the type.
|
||||||
|
args = typing.get_args(self._obj)
|
||||||
|
name = "Union"
|
||||||
|
elif safe_getattr(self._obj, '__module__', default='') == 'typing':
|
||||||
|
# Try regex first (works for most types)
|
||||||
m = re.match(r'typing.(\w+)\[', repr(self._obj))
|
m = re.match(r'typing.(\w+)\[', repr(self._obj))
|
||||||
if m is not None:
|
if m is not None:
|
||||||
name = m.group(1)
|
name = m.group(1)
|
||||||
|
|
||||||
import typing
|
|
||||||
if sys.version_info >= (3, 8):
|
if sys.version_info >= (3, 8):
|
||||||
args = typing.get_args(self._obj)
|
args = typing.get_args(self._obj)
|
||||||
else:
|
else:
|
||||||
@@ -493,6 +498,18 @@ class DirectObjectAccess:
|
|||||||
return inspect.isclass(self._obj) and self._obj != type
|
return inspect.isclass(self._obj) and self._obj != type
|
||||||
|
|
||||||
def _annotation_to_str(self, annotation):
|
def _annotation_to_str(self, annotation):
|
||||||
|
# In Python 3.14+, Union types are displayed as X | Y instead of Union[X, Y]
|
||||||
|
# We normalize to that for consistency
|
||||||
|
import typing
|
||||||
|
origin = typing.get_origin(annotation)
|
||||||
|
if origin is typing.Union:
|
||||||
|
# Get the args and format them as Union[...]
|
||||||
|
args = typing.get_args(annotation)
|
||||||
|
return ' | '.join(
|
||||||
|
self._annotation_to_str(arg) if hasattr(arg, '__origin__')
|
||||||
|
else getattr(arg, '__name__', str(arg))
|
||||||
|
for arg in args
|
||||||
|
)
|
||||||
return inspect.formatannotation(annotation)
|
return inspect.formatannotation(annotation)
|
||||||
|
|
||||||
def get_signature_params(self):
|
def get_signature_params(self):
|
||||||
|
|||||||
@@ -90,7 +90,10 @@ def getattr_static(obj, attr, default=_sentinel):
|
|||||||
if not _is_type(obj):
|
if not _is_type(obj):
|
||||||
klass = type(obj)
|
klass = type(obj)
|
||||||
dict_attr = _shadowed_dict(klass)
|
dict_attr = _shadowed_dict(klass)
|
||||||
if (dict_attr is _sentinel or type(dict_attr) is types.MemberDescriptorType):
|
# In Python 3.15+, __dict__ is a GetSetDescriptorType instead of being _sentinel
|
||||||
|
if (dict_attr is _sentinel
|
||||||
|
or type(dict_attr) is types.MemberDescriptorType
|
||||||
|
or type(dict_attr) is types.GetSetDescriptorType):
|
||||||
instance_result = _check_instance(obj, attr)
|
instance_result = _check_instance(obj, attr)
|
||||||
else:
|
else:
|
||||||
klass = obj
|
klass = obj
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ setup(name='jedi',
|
|||||||
'Programming Language :: Python :: 3.11',
|
'Programming Language :: Python :: 3.11',
|
||||||
'Programming Language :: Python :: 3.12',
|
'Programming Language :: Python :: 3.12',
|
||||||
'Programming Language :: Python :: 3.13',
|
'Programming Language :: Python :: 3.13',
|
||||||
|
'Programming Language :: Python :: 3.14',
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||||
'Topic :: Text Editors :: Integrated Development Environments (IDE)',
|
'Topic :: Text Editors :: Integrated Development Environments (IDE)',
|
||||||
'Topic :: Utilities',
|
'Topic :: Utilities',
|
||||||
|
|||||||
+1
-4
@@ -104,10 +104,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import operator
|
import operator
|
||||||
if sys.version_info < (3, 8):
|
from ast import literal_eval
|
||||||
literal_eval = eval
|
|
||||||
else:
|
|
||||||
from ast import literal_eval
|
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from unittest.mock import ANY
|
from unittest.mock import ANY
|
||||||
|
|||||||
@@ -420,7 +420,7 @@ _calls = [
|
|||||||
(code1, 'f(a,b,xy', 4),
|
(code1, 'f(a,b,xy', 4),
|
||||||
(code1, 'f(a,b,xyz=', 4),
|
(code1, 'f(a,b,xyz=', 4),
|
||||||
(code1, 'f(a,b,xy=', None),
|
(code1, 'f(a,b,xy=', None),
|
||||||
(code1, 'f(u=', (0, None)),
|
(code1, 'f(u=', None),
|
||||||
(code1, 'f(v=', 1),
|
(code1, 'f(v=', 1),
|
||||||
|
|
||||||
# **kwargs
|
# **kwargs
|
||||||
@@ -438,7 +438,7 @@ _calls = [
|
|||||||
(code2, 'g(a,b,abc=1,abd=4,abd=', 5),
|
(code2, 'g(a,b,abc=1,abd=4,abd=', 5),
|
||||||
(code2, 'g(a,b,kw', 5),
|
(code2, 'g(a,b,kw', 5),
|
||||||
(code2, 'g(a,b,kwargs=', 5),
|
(code2, 'g(a,b,kwargs=', 5),
|
||||||
(code2, 'g(u=', (0, 5)),
|
(code2, 'g(u=', 5),
|
||||||
(code2, 'g(v=', 1),
|
(code2, 'g(v=', 1),
|
||||||
|
|
||||||
# *args
|
# *args
|
||||||
@@ -450,7 +450,7 @@ _calls = [
|
|||||||
(code3, 'h(a,b,c,(3,)', 2),
|
(code3, 'h(a,b,c,(3,)', 2),
|
||||||
(code3, 'h(a,b,args=', None),
|
(code3, 'h(a,b,args=', None),
|
||||||
(code3, 'h(u,v=', 1),
|
(code3, 'h(u,v=', 1),
|
||||||
(code3, 'h(u=', (0, None)),
|
(code3, 'h(u=', None),
|
||||||
(code3, 'h(u,*xxx', 1),
|
(code3, 'h(u,*xxx', 1),
|
||||||
(code3, 'h(u,*xxx,*yyy', 1),
|
(code3, 'h(u,*xxx,*yyy', 1),
|
||||||
(code3, 'h(u,*[]', 1),
|
(code3, 'h(u,*[]', 1),
|
||||||
@@ -483,7 +483,7 @@ _calls = [
|
|||||||
(code4, 'i(1, [a?b,*', 2),
|
(code4, 'i(1, [a?b,*', 2),
|
||||||
(code4, 'i(?b,*r,c', 1),
|
(code4, 'i(?b,*r,c', 1),
|
||||||
(code4, 'i(?*', 0),
|
(code4, 'i(?*', 0),
|
||||||
(code4, 'i(?**', (0, 1)),
|
(code4, 'i(?**', 1),
|
||||||
|
|
||||||
# Random
|
# Random
|
||||||
(code4, 'i(()', 0),
|
(code4, 'i(()', 0),
|
||||||
@@ -497,11 +497,6 @@ _calls = [
|
|||||||
@pytest.mark.parametrize('ending', ['', ')'])
|
@pytest.mark.parametrize('ending', ['', ')'])
|
||||||
@pytest.mark.parametrize('code, call, expected_index', _calls)
|
@pytest.mark.parametrize('code, call, expected_index', _calls)
|
||||||
def test_signature_index(Script, environment, code, call, expected_index, ending):
|
def test_signature_index(Script, environment, code, call, expected_index, ending):
|
||||||
if isinstance(expected_index, tuple):
|
|
||||||
expected_index = expected_index[environment.version_info > (3, 8)]
|
|
||||||
if environment.version_info < (3, 8):
|
|
||||||
code = code.replace('/,', '')
|
|
||||||
|
|
||||||
sig, = Script(code + '\n' + call + ending).get_signatures(column=len(call))
|
sig, = Script(code + '\n' + call + ending).get_signatures(column=len(call))
|
||||||
index = sig.index
|
index = sig.index
|
||||||
assert expected_index == index
|
assert expected_index == index
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ def test_find_system_environments():
|
|||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'version',
|
'version',
|
||||||
['3.10', '3.11', '3.12', '3.13']
|
jedi.api.environment._SUPPORTED_PYTHONS,
|
||||||
)
|
)
|
||||||
def test_versions(version):
|
def test_versions(version):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -661,17 +661,15 @@ def bar():
|
|||||||
|
|
||||||
# typing is available via globals.
|
# typing is available via globals.
|
||||||
({'return': 'typing.Union[str, int]'}, ['int', 'str'], ''),
|
({'return': 'typing.Union[str, int]'}, ['int', 'str'], ''),
|
||||||
({'return': 'typing.Union["str", int]'},
|
({'return': 'typing.Union["str", int]'}, ['int', 'str'], ''),
|
||||||
['int', 'str'] if sys.version_info >= (3, 9) else ['int'], ''),
|
|
||||||
({'return': 'typing.Union["str", 1]'},
|
({'return': 'typing.Union["str", 1]'},
|
||||||
['str'] if sys.version_info >= (3, 11) else [], ''),
|
(['str'] if (3, 14) > sys.version_info >= (3, 11) else []), ''),
|
||||||
({'return': 'typing.Optional[str]'}, ['NoneType', 'str'], ''),
|
({'return': 'typing.Optional[str]'}, ['NoneType', 'str'], ''),
|
||||||
({'return': 'typing.Optional[str, int]'}, [], ''), # Takes only one arg
|
({'return': 'typing.Optional[str, int]'}, [], ''), # Takes only one arg
|
||||||
({'return': 'typing.Any'},
|
({'return': 'typing.Any'},
|
||||||
['_AnyMeta'] if sys.version_info >= (3, 11) else [], ''),
|
['_AnyMeta'] if sys.version_info >= (3, 11) else [], ''),
|
||||||
|
|
||||||
({'return': 'typing.Tuple[int, str]'},
|
({'return': 'typing.Tuple[int, str]'}, ['tuple'], ''),
|
||||||
['Tuple' if sys.version_info[:2] == (3, 6) else 'tuple'], ''),
|
|
||||||
({'return': 'typing.Tuple[int, str]'}, ['int'], 'x()[0]'),
|
({'return': 'typing.Tuple[int, str]'}, ['int'], 'x()[0]'),
|
||||||
({'return': 'typing.Tuple[int, str]'}, ['str'], 'x()[1]'),
|
({'return': 'typing.Tuple[int, str]'}, ['str'], 'x()[1]'),
|
||||||
({'return': 'typing.Tuple[int, str]'}, [], 'x()[2]'),
|
({'return': 'typing.Tuple[int, str]'}, [], 'x()[2]'),
|
||||||
@@ -746,7 +744,8 @@ def test_complete_not_findable_class_source():
|
|||||||
def test_param_infer_default():
|
def test_param_infer_default():
|
||||||
abs_sig, = jedi.Interpreter('abs(', [{'abs': abs}]).get_signatures()
|
abs_sig, = jedi.Interpreter('abs(', [{'abs': abs}]).get_signatures()
|
||||||
param, = abs_sig.params
|
param, = abs_sig.params
|
||||||
assert param.name == 'x'
|
# Parameter name changed from 'x' to 'number' in Python 3.15
|
||||||
|
assert param.name in ('x', 'number')
|
||||||
assert param.infer_default() == []
|
assert param.infer_default() == []
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ from test.helpers import root_dir
|
|||||||
])
|
])
|
||||||
def test_infer_and_goto(Script, code, full_name, has_stub, has_python, way,
|
def test_infer_and_goto(Script, code, full_name, has_stub, has_python, way,
|
||||||
kwargs, type_, options, environment):
|
kwargs, type_, options, environment):
|
||||||
if type_ == 'infer' and full_name == 'typing.Sequence' and environment.version_info >= (3, 7):
|
if type_ == 'infer' and full_name == 'typing.Sequence':
|
||||||
# In Python 3.7+ there's not really a sequence definition, there's just
|
# Since Python 3.7+ there's not really a sequence definition, there's just
|
||||||
# a name that leads nowhere.
|
# a name that leads nowhere.
|
||||||
has_python = False
|
has_python = False
|
||||||
|
|
||||||
|
|||||||
@@ -111,4 +111,4 @@ def test_compiled_signature_annotation_string():
|
|||||||
|
|
||||||
s, = jedi.Interpreter('func()', [locals()]).get_signatures(1, 5)
|
s, = jedi.Interpreter('func()', [locals()]).get_signatures(1, 5)
|
||||||
assert s.params[0].description == 'param x: Type'
|
assert s.params[0].description == 'param x: Type'
|
||||||
assert s.params[1].description == 'param y: Union[Type, int]'
|
assert s.params[1].description == 'param y: Type | int'
|
||||||
|
|||||||
@@ -123,10 +123,6 @@ class X:
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
def test_tree_signature(Script, environment, code, expected):
|
def test_tree_signature(Script, environment, code, expected):
|
||||||
# Only test this in the latest version, because of /
|
|
||||||
if environment.version_info < (3, 8):
|
|
||||||
pytest.skip()
|
|
||||||
|
|
||||||
if expected is None:
|
if expected is None:
|
||||||
assert not Script(code).get_signatures()
|
assert not Script(code).get_signatures()
|
||||||
else:
|
else:
|
||||||
@@ -249,13 +245,6 @@ def test_pow_signature(Script, environment):
|
|||||||
# See github #1357
|
# See github #1357
|
||||||
sigs = Script('pow(').get_signatures()
|
sigs = Script('pow(').get_signatures()
|
||||||
strings = {sig.to_string() for sig in sigs}
|
strings = {sig.to_string() for sig in sigs}
|
||||||
if environment.version_info < (3, 8):
|
|
||||||
assert strings == {'pow(base: _SupportsPow2[_E, _T_co], exp: _E, /) -> _T_co',
|
|
||||||
'pow(base: _SupportsPow3[_E, _M, _T_co], exp: _E, mod: _M, /) -> _T_co',
|
|
||||||
'pow(base: float, exp: float, mod: None=..., /) -> float',
|
|
||||||
'pow(base: int, exp: int, mod: None=..., /) -> Any',
|
|
||||||
'pow(base: int, exp: int, mod: int, /) -> int'}
|
|
||||||
else:
|
|
||||||
assert strings == {'pow(base: _SupportsPow2[_E, _T_co], exp: _E) -> _T_co',
|
assert strings == {'pow(base: _SupportsPow2[_E, _T_co], exp: _E) -> _T_co',
|
||||||
'pow(base: _SupportsPow3[_E, _M, _T_co], exp: _E, mod: _M) -> _T_co',
|
'pow(base: _SupportsPow3[_E, _M, _T_co], exp: _E, mod: _M) -> _T_co',
|
||||||
'pow(base: float, exp: float, mod: None=...) -> float',
|
'pow(base: float, exp: float, mod: None=...) -> float',
|
||||||
@@ -408,11 +397,6 @@ def test_wraps_signature(Script, code, signature):
|
|||||||
def test_dataclass_signature(
|
def test_dataclass_signature(
|
||||||
Script, start, start_params, include_params, environment
|
Script, start, start_params, include_params, environment
|
||||||
):
|
):
|
||||||
if environment.version_info < (3, 8):
|
|
||||||
# Final is not yet supported
|
|
||||||
price_type = "float"
|
|
||||||
price_type_infer = "float"
|
|
||||||
else:
|
|
||||||
price_type = "Final[float]"
|
price_type = "Final[float]"
|
||||||
price_type_infer = "object"
|
price_type_infer = "object"
|
||||||
|
|
||||||
@@ -731,11 +715,6 @@ def test_extensions_dataclass_transform_signature(
|
|||||||
if not has_typing_ext:
|
if not has_typing_ext:
|
||||||
raise pytest.skip("typing_extensions needed in target environment to run this test")
|
raise pytest.skip("typing_extensions needed in target environment to run this test")
|
||||||
|
|
||||||
if environment.version_info < (3, 8):
|
|
||||||
# Final is not yet supported
|
|
||||||
price_type = "float"
|
|
||||||
price_type_infer = "float"
|
|
||||||
else:
|
|
||||||
price_type = "Final[float]"
|
price_type = "Final[float]"
|
||||||
price_type_infer = "object"
|
price_type_infer = "object"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from collections import namedtuple
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -53,7 +52,6 @@ def test_completion(case, monkeypatch, environment, has_django):
|
|||||||
|
|
||||||
# ... and mock the entry points to include it
|
# ... and mock the entry points to include it
|
||||||
# see https://docs.pytest.org/en/stable/how-to/writing_plugins.html#setuptools-entry-points
|
# see https://docs.pytest.org/en/stable/how-to/writing_plugins.html#setuptools-entry-points
|
||||||
if sys.version_info >= (3, 8):
|
|
||||||
def mock_entry_points(*, group=None):
|
def mock_entry_points(*, group=None):
|
||||||
import importlib.metadata
|
import importlib.metadata
|
||||||
entries = [importlib.metadata.EntryPoint(
|
entries = [importlib.metadata.EntryPoint(
|
||||||
@@ -70,13 +68,6 @@ def test_completion(case, monkeypatch, environment, has_django):
|
|||||||
return {"pytest11": entries}
|
return {"pytest11": entries}
|
||||||
|
|
||||||
monkeypatch.setattr("importlib.metadata.entry_points", mock_entry_points)
|
monkeypatch.setattr("importlib.metadata.entry_points", mock_entry_points)
|
||||||
else:
|
|
||||||
def mock_iter_entry_points(group):
|
|
||||||
assert group == "pytest11"
|
|
||||||
EntryPoint = namedtuple("EntryPoint", ["module_name"])
|
|
||||||
return [EntryPoint("pytest_plugin.plugin")]
|
|
||||||
|
|
||||||
monkeypatch.setattr("pkg_resources.iter_entry_points", mock_iter_entry_points)
|
|
||||||
|
|
||||||
repo_root = helpers.root_dir
|
repo_root = helpers.root_dir
|
||||||
monkeypatch.chdir(os.path.join(repo_root, 'jedi'))
|
monkeypatch.chdir(os.path.join(repo_root, 'jedi'))
|
||||||
|
|||||||
@@ -83,6 +83,9 @@ class TestSetupReadline(unittest.TestCase):
|
|||||||
'_', 'O_', 'EX_', 'EFD_', 'MFD_', 'TFD_',
|
'_', 'O_', 'EX_', 'EFD_', 'MFD_', 'TFD_',
|
||||||
'SF_', 'ST_', 'CLD_', 'POSIX_SPAWN_', 'P_',
|
'SF_', 'ST_', 'CLD_', 'POSIX_SPAWN_', 'P_',
|
||||||
'RWF_', 'CLONE_', 'SCHED_', 'SPLICE_',
|
'RWF_', 'CLONE_', 'SCHED_', 'SPLICE_',
|
||||||
|
# Python 3.15+ new constants
|
||||||
|
'AT_', 'PIDFD_', 'STATX_', 'GRND_', 'XATTR_',
|
||||||
|
'RTLD_', 'PRIO_', 'F_', 'SEEK_', 'NODEV',
|
||||||
]
|
]
|
||||||
difference = {
|
difference = {
|
||||||
x for x in difference
|
x for x in difference
|
||||||
|
|||||||
Reference in New Issue
Block a user