From 9b24443787c24e35cffecca9a081b30736698072 Mon Sep 17 00:00:00 2001 From: Lumir Balhar Date: Mon, 9 Mar 2026 15:38:12 +0100 Subject: [PATCH 01/10] Fix Python 3.14 compatibility for typing.Union annotations In Python 3.14, typing.Union changed its repr from 'typing.Union[X, Y]' to 'X | Y' (PEP 604), breaking annotation inference. Changes: - Use getattr() instead of safe_getattr() for __module__ retrieval (getattr_static fails on Union types in Python 3.14) - Add fallback to typing.get_origin() when regex fails to match - Normalize Union display back to 'Union[X, Y]' format for consistency - Update test expectations for invalid annotation edge case in 3.14 Fixes: https://github.com/davidhalter/jedi/issues/2064 --- jedi/inference/compiled/access.py | 39 ++++++++++++++++++++++++++----- test/test_api/test_interpreter.py | 2 +- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/jedi/inference/compiled/access.py b/jedi/inference/compiled/access.py index 5a8e68fa..7b1fc5b0 100644 --- a/jedi/inference/compiled/access.py +++ b/jedi/inference/compiled/access.py @@ -477,22 +477,49 @@ class DirectObjectAccess: """ name = None args = () - if safe_getattr(self._obj, '__module__', default='') == 'typing': + # Use getattr instead of safe_getattr for __module__ as getattr_static + # fails on typing types in Python 3.14+ + module = getattr(self._obj, '__module__', '') + if module == 'typing': + import typing + # Try regex first (works for most types) m = re.match(r'typing.(\w+)\[', repr(self._obj)) if m is not None: name = m.group(1) + elif sys.version_info >= (3, 8): + # Fallback to get_origin() for Python 3.8+ when regex fails + # In Python 3.14+, Union/Optional repr changed to use | syntax + origin = typing.get_origin(self._obj) + if origin is typing.Union: + name = 'Union' - import typing - if sys.version_info >= (3, 8): - args = typing.get_args(self._obj) - else: - args = safe_getattr(self._obj, '__args__', default=None) + # Get args + if sys.version_info >= (3, 8): + args = typing.get_args(self._obj) + else: + args = safe_getattr(self._obj, '__args__', default=None) + if args is None: + args = () return name, tuple(self._create_access_path(arg) for arg in args) def needs_type_completions(self): return inspect.isclass(self._obj) and self._obj != type 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 Union[X, Y] for consistency + if sys.version_info >= (3, 8): + 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) + formatted_args = ', '.join( + self._annotation_to_str(arg) if hasattr(arg, '__origin__') + else getattr(arg, '__name__', str(arg)) + for arg in args + ) + return f'Union[{formatted_args}]' return inspect.formatannotation(annotation) def get_signature_params(self): diff --git a/test/test_api/test_interpreter.py b/test/test_api/test_interpreter.py index 1aa027bf..42193482 100644 --- a/test/test_api/test_interpreter.py +++ b/test/test_api/test_interpreter.py @@ -664,7 +664,7 @@ def bar(): ({'return': 'typing.Union["str", int]'}, ['int', 'str'] if sys.version_info >= (3, 9) else ['int'], ''), ({'return': 'typing.Union["str", 1]'}, - ['str'] if sys.version_info >= (3, 11) else [], ''), + [] if sys.version_info >= (3, 14) else (['str'] if sys.version_info >= (3, 11) else []), ''), ({'return': 'typing.Optional[str]'}, ['NoneType', 'str'], ''), ({'return': 'typing.Optional[str, int]'}, [], ''), # Takes only one arg ({'return': 'typing.Any'}, From 04737b26372722b19292e82b167123ff29182496 Mon Sep 17 00:00:00 2001 From: Lumir Balhar Date: Tue, 10 Mar 2026 09:12:32 +0100 Subject: [PATCH 02/10] Fix Python 3.15 compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix getattr_static for Python 3.15 __dict__ GetSetDescriptorType - Accept abs() parameter name change ('x' → 'number') - Add Python 3.15 os module constants to test expectations Fixes instance attribute introspection and stdlib changes in Python 3.15. --- jedi/inference/compiled/getattr_static.py | 5 ++++- test/test_api/test_interpreter.py | 3 ++- test/test_utils.py | 3 +++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/jedi/inference/compiled/getattr_static.py b/jedi/inference/compiled/getattr_static.py index 03c199ef..770a471e 100644 --- a/jedi/inference/compiled/getattr_static.py +++ b/jedi/inference/compiled/getattr_static.py @@ -90,7 +90,10 @@ def getattr_static(obj, attr, default=_sentinel): if not _is_type(obj): klass = type(obj) 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) else: klass = obj diff --git a/test/test_api/test_interpreter.py b/test/test_api/test_interpreter.py index 42193482..b87b0410 100644 --- a/test/test_api/test_interpreter.py +++ b/test/test_api/test_interpreter.py @@ -746,7 +746,8 @@ def test_complete_not_findable_class_source(): def test_param_infer_default(): abs_sig, = jedi.Interpreter('abs(', [{'abs': abs}]).get_signatures() 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() == [] diff --git a/test/test_utils.py b/test/test_utils.py index 4fc19878..a212eec5 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -81,6 +81,9 @@ class TestSetupReadline(unittest.TestCase): '_', 'O_', 'EX_', 'EFD_', 'MFD_', 'TFD_', 'SF_', 'ST_', 'CLD_', 'POSIX_SPAWN_', 'P_', 'RWF_', 'CLONE_', 'SCHED_', 'SPLICE_', + # Python 3.15+ new constants + 'AT_', 'PIDFD_', 'STATX_', 'GRND_', 'XATTR_', + 'RTLD_', 'PRIO_', 'F_', 'SEEK_', 'NODEV', ] difference = { x for x in difference From 375dd1bacc22a805c86a85fedf00c64f78543087 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Mon, 27 Apr 2026 14:01:39 +0200 Subject: [PATCH 03/10] Add 3.14 to the supported environments --- jedi/api/environment.py | 2 +- test/test_api/test_environment.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jedi/api/environment.py b/jedi/api/environment.py index f03bdc12..7a6e26b8 100644 --- a/jedi/api/environment.py +++ b/jedi/api/environment.py @@ -22,7 +22,7 @@ if TYPE_CHECKING: _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'] _CONDA_VAR = 'CONDA_PREFIX' _CURRENT_VERSION = '%s.%s' % (sys.version_info.major, sys.version_info.minor) diff --git a/test/test_api/test_environment.py b/test/test_api/test_environment.py index 8deb9500..2f459646 100644 --- a/test/test_api/test_environment.py +++ b/test/test_api/test_environment.py @@ -26,7 +26,7 @@ def test_find_system_environments(): @pytest.mark.parametrize( 'version', - ['3.10', '3.11', '3.12', '3.13'] + jedi.api.environment._SUPPORTED_PYTHONS, ) def test_versions(version): try: From 74fb7ff27936151c7e9540dd2ddd6f7f65b572fd Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Mon, 27 Apr 2026 14:18:17 +0200 Subject: [PATCH 04/10] Enable tests for 3.14 in CI, fixes #2070 --- .github/workflows/ci.yml | 4 ++-- setup.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 39044aa1..32a5462f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,8 +7,8 @@ jobs: strategy: matrix: os: [ubuntu-24.04, windows-2022] - python-version: ["3.13", "3.12", "3.11", "3.10"] - environment: ['3.13', '3.12', '3.11', '3.10', 'interpreter'] + python-version: ["3.14", "3.13", "3.12", "3.11", "3.10"] + environment: ['3.14', '3.13', '3.12', '3.11', '3.10', 'interpreter'] steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/setup.py b/setup.py index 4e772d48..c2ea2f88 100755 --- a/setup.py +++ b/setup.py @@ -99,6 +99,7 @@ setup(name='jedi', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: 3.14', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Text Editors :: Integrated Development Environments (IDE)', 'Topic :: Utilities', From d4233732be20fc4a6f4185490f55d6a85fdcb73e Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Mon, 27 Apr 2026 14:24:23 +0200 Subject: [PATCH 05/10] Format unions with | consistently --- jedi/inference/compiled/access.py | 24 +++++++++++------------- test/test_inference/test_mixed.py | 2 +- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/jedi/inference/compiled/access.py b/jedi/inference/compiled/access.py index 7b1fc5b0..8fc2d832 100644 --- a/jedi/inference/compiled/access.py +++ b/jedi/inference/compiled/access.py @@ -507,19 +507,17 @@ class DirectObjectAccess: 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 Union[X, Y] for consistency - if sys.version_info >= (3, 8): - 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) - formatted_args = ', '.join( - self._annotation_to_str(arg) if hasattr(arg, '__origin__') - else getattr(arg, '__name__', str(arg)) - for arg in args - ) - return f'Union[{formatted_args}]' + # 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) def get_signature_params(self): diff --git a/test/test_inference/test_mixed.py b/test/test_inference/test_mixed.py index 479a4bbf..08199187 100644 --- a/test/test_inference/test_mixed.py +++ b/test/test_inference/test_mixed.py @@ -111,4 +111,4 @@ def test_compiled_signature_annotation_string(): s, = jedi.Interpreter('func()', [locals()]).get_signatures(1, 5) 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' From 7bac12c125a1a590760fbf29db4870ab286a49bc Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Mon, 27 Apr 2026 14:35:10 +0200 Subject: [PATCH 06/10] Remove some 3.7/3.6 artifacts --- test/test_api/test_interpreter.py | 3 +-- test/test_inference/test_gradual/test_stubs.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/test/test_api/test_interpreter.py b/test/test_api/test_interpreter.py index 38650fee..bd3eecca 100644 --- a/test/test_api/test_interpreter.py +++ b/test/test_api/test_interpreter.py @@ -670,8 +670,7 @@ def bar(): ({'return': 'typing.Any'}, ['_AnyMeta'] if sys.version_info >= (3, 11) else [], ''), - ({'return': 'typing.Tuple[int, str]'}, - ['Tuple' if sys.version_info[:2] == (3, 6) else 'tuple'], ''), + ({'return': 'typing.Tuple[int, str]'}, ['tuple'], ''), ({'return': 'typing.Tuple[int, str]'}, ['int'], 'x()[0]'), ({'return': 'typing.Tuple[int, str]'}, ['str'], 'x()[1]'), ({'return': 'typing.Tuple[int, str]'}, [], 'x()[2]'), diff --git a/test/test_inference/test_gradual/test_stubs.py b/test/test_inference/test_gradual/test_stubs.py index b3a042c7..cbf23476 100644 --- a/test/test_inference/test_gradual/test_stubs.py +++ b/test/test_inference/test_gradual/test_stubs.py @@ -43,8 +43,8 @@ from test.helpers import root_dir ]) def test_infer_and_goto(Script, code, full_name, has_stub, has_python, way, kwargs, type_, options, environment): - if type_ == 'infer' and full_name == 'typing.Sequence' and environment.version_info >= (3, 7): - # In Python 3.7+ there's not really a sequence definition, there's just + if type_ == 'infer' and full_name == 'typing.Sequence': + # Since Python 3.7+ there's not really a sequence definition, there's just # a name that leads nowhere. has_python = False From f1ab9d95394d11841f1a8b24017c198767a10481 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Mon, 27 Apr 2026 14:41:58 +0200 Subject: [PATCH 07/10] Remove some 3.8 artifacts --- test/run.py | 5 +--- test/test_api/test_call_signatures.py | 13 +++------ test/test_inference/test_signature.py | 39 +++++++-------------------- test/test_integration.py | 34 +++++++++-------------- 4 files changed, 27 insertions(+), 64 deletions(-) diff --git a/test/run.py b/test/run.py index 86ca19ca..5d1132ea 100755 --- a/test/run.py +++ b/test/run.py @@ -104,10 +104,7 @@ import os import re import sys import operator -if sys.version_info < (3, 8): - literal_eval = eval -else: - from ast import literal_eval +from ast import literal_eval from io import StringIO from functools import reduce from unittest.mock import ANY diff --git a/test/test_api/test_call_signatures.py b/test/test_api/test_call_signatures.py index e8741671..a9a979bc 100644 --- a/test/test_api/test_call_signatures.py +++ b/test/test_api/test_call_signatures.py @@ -420,7 +420,7 @@ _calls = [ (code1, 'f(a,b,xy', 4), (code1, 'f(a,b,xyz=', 4), (code1, 'f(a,b,xy=', None), - (code1, 'f(u=', (0, None)), + (code1, 'f(u=', None), (code1, 'f(v=', 1), # **kwargs @@ -438,7 +438,7 @@ _calls = [ (code2, 'g(a,b,abc=1,abd=4,abd=', 5), (code2, 'g(a,b,kw', 5), (code2, 'g(a,b,kwargs=', 5), - (code2, 'g(u=', (0, 5)), + (code2, 'g(u=', 5), (code2, 'g(v=', 1), # *args @@ -450,7 +450,7 @@ _calls = [ (code3, 'h(a,b,c,(3,)', 2), (code3, 'h(a,b,args=', None), (code3, 'h(u,v=', 1), - (code3, 'h(u=', (0, None)), + (code3, 'h(u=', None), (code3, 'h(u,*xxx', 1), (code3, 'h(u,*xxx,*yyy', 1), (code3, 'h(u,*[]', 1), @@ -483,7 +483,7 @@ _calls = [ (code4, 'i(1, [a?b,*', 2), (code4, 'i(?b,*r,c', 1), (code4, 'i(?*', 0), - (code4, 'i(?**', (0, 1)), + (code4, 'i(?**', 1), # Random (code4, 'i(()', 0), @@ -497,11 +497,6 @@ _calls = [ @pytest.mark.parametrize('ending', ['', ')']) @pytest.mark.parametrize('code, call, expected_index', _calls) 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)) index = sig.index assert expected_index == index diff --git a/test/test_inference/test_signature.py b/test/test_inference/test_signature.py index edac7f26..bbba69c6 100644 --- a/test/test_inference/test_signature.py +++ b/test/test_inference/test_signature.py @@ -123,10 +123,6 @@ class X: ] ) 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: assert not Script(code).get_signatures() else: @@ -249,18 +245,11 @@ def test_pow_signature(Script, environment): # See github #1357 sigs = Script('pow(').get_signatures() 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', - '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'} + 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'} @pytest.mark.parametrize( @@ -408,13 +397,8 @@ def test_wraps_signature(Script, code, signature): def test_dataclass_signature( 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_infer = "object" + price_type = "Final[float]" + price_type_infer = "object" code = dedent( f""" @@ -731,13 +715,8 @@ def test_extensions_dataclass_transform_signature( if not has_typing_ext: 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_infer = "object" + price_type = "Final[float]" + price_type_infer = "object" code = dedent( f""" diff --git a/test/test_integration.py b/test/test_integration.py index 0364c05b..4415d3a8 100644 --- a/test/test_integration.py +++ b/test/test_integration.py @@ -53,30 +53,22 @@ def test_completion(case, monkeypatch, environment, has_django): # ... and mock the entry points to include it # 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): - import importlib.metadata - entries = [importlib.metadata.EntryPoint( - name=None, - value="pytest_plugin.plugin", - group="pytest11", - )] + def mock_entry_points(*, group=None): + import importlib.metadata + entries = [importlib.metadata.EntryPoint( + name=None, + value="pytest_plugin.plugin", + group="pytest11", + )] - if sys.version_info >= (3, 10): - assert group == "pytest11" - return entries - else: - assert group is None - return {"pytest11": entries} - - monkeypatch.setattr("importlib.metadata.entry_points", mock_entry_points) - else: - def mock_iter_entry_points(group): + if sys.version_info >= (3, 10): assert group == "pytest11" - EntryPoint = namedtuple("EntryPoint", ["module_name"]) - return [EntryPoint("pytest_plugin.plugin")] + return entries + else: + assert group is None + return {"pytest11": entries} - monkeypatch.setattr("pkg_resources.iter_entry_points", mock_iter_entry_points) + monkeypatch.setattr("importlib.metadata.entry_points", mock_entry_points) repo_root = helpers.root_dir monkeypatch.chdir(os.path.join(repo_root, 'jedi')) From 04d45a8e1e9a9bbbd8798279aa148ae1212f6e89 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Mon, 27 Apr 2026 14:42:24 +0200 Subject: [PATCH 08/10] Remove a 3, 9 artifact --- test/test_api/test_interpreter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_api/test_interpreter.py b/test/test_api/test_interpreter.py index bd3eecca..ee71b39c 100644 --- a/test/test_api/test_interpreter.py +++ b/test/test_api/test_interpreter.py @@ -662,7 +662,7 @@ def bar(): # typing is available via globals. ({'return': 'typing.Union[str, int]'}, ['int', 'str'], ''), ({'return': 'typing.Union["str", int]'}, - ['int', 'str'] if sys.version_info >= (3, 9) else ['int'], ''), + ['int', 'str'], ''), ({'return': 'typing.Union["str", 1]'}, [] if sys.version_info >= (3, 14) else (['str'] if sys.version_info >= (3, 11) else []), ''), ({'return': 'typing.Optional[str]'}, ['NoneType', 'str'], ''), From 44600ea194c29531a1000d7b296b26970f6a4f78 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Mon, 27 Apr 2026 15:21:26 +0200 Subject: [PATCH 09/10] Fix union acess for 3.14 --- jedi/inference/compiled/access.py | 31 ++++++++++++------------------- test/test_api/test_interpreter.py | 3 +-- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/jedi/inference/compiled/access.py b/jedi/inference/compiled/access.py index 8fc2d832..86d34cc1 100644 --- a/jedi/inference/compiled/access.py +++ b/jedi/inference/compiled/access.py @@ -471,35 +471,28 @@ class DirectObjectAccess: op = _OPERATORS[operator] 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, ...]] """ name = None args = () - # Use getattr instead of safe_getattr for __module__ as getattr_static - # fails on typing types in Python 3.14+ - module = getattr(self._obj, '__module__', '') - if module == 'typing': - import typing + module = getattr_static(self._obj, '__module__', '') + 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)) if m is not None: name = m.group(1) - elif sys.version_info >= (3, 8): - # Fallback to get_origin() for Python 3.8+ when regex fails - # In Python 3.14+, Union/Optional repr changed to use | syntax - origin = typing.get_origin(self._obj) - if origin is typing.Union: - name = 'Union' - # Get args - if sys.version_info >= (3, 8): - args = typing.get_args(self._obj) - else: - args = safe_getattr(self._obj, '__args__', default=None) - if args is None: - args = () + if sys.version_info >= (3, 8): + args = typing.get_args(self._obj) + else: + args = safe_getattr(self._obj, '__args__', default=None) return name, tuple(self._create_access_path(arg) for arg in args) def needs_type_completions(self): diff --git a/test/test_api/test_interpreter.py b/test/test_api/test_interpreter.py index ee71b39c..ce6f282f 100644 --- a/test/test_api/test_interpreter.py +++ b/test/test_api/test_interpreter.py @@ -661,8 +661,7 @@ def bar(): # typing is available via globals. ({'return': 'typing.Union[str, int]'}, ['int', 'str'], ''), - ({'return': 'typing.Union["str", int]'}, - ['int', 'str'], ''), + ({'return': 'typing.Union["str", int]'}, ['int', 'str'], ''), ({'return': 'typing.Union["str", 1]'}, [] if sys.version_info >= (3, 14) else (['str'] if sys.version_info >= (3, 11) else []), ''), ({'return': 'typing.Optional[str]'}, ['NoneType', 'str'], ''), From 6e17c85a57055c60a27bf5d67019fcc55514f849 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Mon, 27 Apr 2026 15:29:08 +0200 Subject: [PATCH 10/10] Avoid flake8 issues --- jedi/inference/compiled/access.py | 1 - test/test_api/test_interpreter.py | 2 +- test/test_integration.py | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/jedi/inference/compiled/access.py b/jedi/inference/compiled/access.py index 86d34cc1..277c9b1d 100644 --- a/jedi/inference/compiled/access.py +++ b/jedi/inference/compiled/access.py @@ -477,7 +477,6 @@ class DirectObjectAccess: """ name = None args = () - module = getattr_static(self._obj, '__module__', '') 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. diff --git a/test/test_api/test_interpreter.py b/test/test_api/test_interpreter.py index ce6f282f..64da73de 100644 --- a/test/test_api/test_interpreter.py +++ b/test/test_api/test_interpreter.py @@ -663,7 +663,7 @@ def bar(): ({'return': 'typing.Union[str, int]'}, ['int', 'str'], ''), ({'return': 'typing.Union["str", int]'}, ['int', 'str'], ''), ({'return': 'typing.Union["str", 1]'}, - [] if sys.version_info >= (3, 14) else (['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, int]'}, [], ''), # Takes only one arg ({'return': 'typing.Any'}, diff --git a/test/test_integration.py b/test/test_integration.py index 4415d3a8..da97465c 100644 --- a/test/test_integration.py +++ b/test/test_integration.py @@ -1,6 +1,5 @@ import os import sys -from collections import namedtuple import pytest