From 2a638c784ff63ab869626d23ce95856dece31a9b Mon Sep 17 00:00:00 2001 From: Peter Law Date: Sat, 6 Jul 2024 11:39:06 +0100 Subject: [PATCH 2/2] Merge branch 'python-3.13' (cherry picked from commit 82d1902f382ddac5b0e6647646b72f28a3181ec3) --- .github/workflows/ci.yml | 4 ++-- CHANGELOG.rst | 2 ++ jedi/_compatibility.py | 15 ++++++++++++++- jedi/api/environment.py | 2 +- jedi/inference/__init__.py | 2 +- setup.py | 5 +++-- test/test_api/test_interpreter.py | 13 +++++++++---- test/test_inference/test_signature.py | 5 +++-- test/test_utils.py | 14 ++++++++++---- 9 files changed, 45 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a565425d..13b744c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,8 +7,8 @@ jobs: strategy: matrix: os: [ubuntu-20.04, windows-2019] - python-version: ["3.12", "3.11", "3.10", "3.9", "3.8", "3.7", "3.6"] - environment: ['3.8', '3.12', '3.11', '3.10', '3.9', '3.7', '3.6', 'interpreter'] + python-version: ["3.13", "3.12", "3.11", "3.10", "3.9", "3.8", "3.7", "3.6"] + environment: ['3.8', '3.13', '3.12', '3.11', '3.10', '3.9', '3.7', '3.6', 'interpreter'] steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fca94429..cf6810fa 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,8 @@ Changelog Unreleased ++++++++++ +- Python 3.13 support + 0.19.1 (2023-10-02) +++++++++++++++++++ diff --git a/jedi/_compatibility.py b/jedi/_compatibility.py index 13a74b7b..48563d00 100644 --- a/jedi/_compatibility.py +++ b/jedi/_compatibility.py @@ -5,11 +5,24 @@ different Python versions. import errno import sys import pickle +from typing import Any + + +class Unpickler(pickle.Unpickler): + def find_class(self, module: str, name: str) -> Any: + # Python 3.13 moved pathlib implementation out of __init__.py as part of + # generalising its implementation. Ensure that we support loading + # pickles from 3.13 on older version of Python. Since 3.13 maintained a + # compatible API, pickles from older Python work natively on the newer + # version. + if module == 'pathlib._local': + module = 'pathlib' + return super().find_class(module, name) def pickle_load(file): try: - return pickle.load(file) + return Unpickler(file).load() # Python on Windows don't throw EOF errors for pipes. So reraise them with # the correct type, which is caught upwards. except OSError: diff --git a/jedi/api/environment.py b/jedi/api/environment.py index cfe8cfe3..a2134110 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.12', '3.11', '3.10', '3.9', '3.8', '3.7', '3.6'] +_SUPPORTED_PYTHONS = ['3.13', '3.12', '3.11', '3.10', '3.9', '3.8', '3.7', '3.6'] _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/jedi/inference/__init__.py b/jedi/inference/__init__.py index aadfeba9..bd31cbd3 100644 --- a/jedi/inference/__init__.py +++ b/jedi/inference/__init__.py @@ -90,7 +90,7 @@ class InferenceState: self.compiled_subprocess = environment.get_inference_state_subprocess(self) self.grammar = environment.get_grammar() - self.latest_grammar = parso.load_grammar(version='3.12') + self.latest_grammar = parso.load_grammar(version='3.13') self.memoize_cache = {} # for memoize decorators self.module_cache = imports.ModuleCache() # does the job of `sys.modules`. self.stub_module_cache = {} # Dict[Tuple[str, ...], Optional[ModuleValue]] diff --git a/setup.py b/setup.py index 68210ef2..ed1e67a4 100755 --- a/setup.py +++ b/setup.py @@ -35,8 +35,8 @@ setup(name='jedi', long_description=readme, packages=find_packages(exclude=['test', 'test.*']), python_requires='>=3.6', - # Python 3.11 & 3.12 grammars are added to parso in 0.8.3 - install_requires=['parso>=0.8.3,<0.9.0'], + # Python 3.13 grammars are added to parso in 0.8.4 + install_requires=['parso>=0.8.4,<0.9.0'], extras_require={ 'testing': [ 'pytest<7.0.0', @@ -101,6 +101,7 @@ setup(name='jedi', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Text Editors :: Integrated Development Environments (IDE)', 'Topic :: Utilities', diff --git a/test/test_api/test_interpreter.py b/test/test_api/test_interpreter.py index 74f066b8..efff7c5b 100644 --- a/test/test_api/test_interpreter.py +++ b/test/test_api/test_interpreter.py @@ -310,8 +310,9 @@ def test_completion_param_annotations(): # Need to define this function not directly in Python. Otherwise Jedi is too # clever and uses the Python code instead of the signature object. code = 'def foo(a: 1, b: str, c: int = 1.0) -> bytes: pass' - exec(code, locals()) - script = jedi.Interpreter('foo', [locals()]) + exec_locals = {} + exec(code, exec_locals) + script = jedi.Interpreter('foo', [exec_locals]) c, = script.complete() sig, = c.get_signatures() a, b, c = sig.params @@ -323,7 +324,7 @@ def test_completion_param_annotations(): assert b.description == 'param b: str' assert c.description == 'param c: int=1.0' - d, = jedi.Interpreter('foo()', [locals()]).infer() + d, = jedi.Interpreter('foo()', [exec_locals]).infer() assert d.name == 'bytes' @@ -525,10 +526,14 @@ def test_partial_signatures(code, expected, index): c = functools.partial(func, 1, c=2) sig, = jedi.Interpreter(code, [locals()]).get_signatures() - assert sig.name == 'partial' assert [p.name for p in sig.params] == expected assert index == sig.index + if sys.version_info < (3, 13): + # Python 3.13.0b3 makes functools.partial be a descriptor, which breaks + # Jedi's `py__name__` detection; see https://github.com/davidhalter/jedi/issues/2012 + assert sig.name == 'partial' + def test_type_var(): """This was an issue before, see Github #1369""" diff --git a/test/test_inference/test_signature.py b/test/test_inference/test_signature.py index f8f71581..4a1fcb62 100644 --- a/test/test_inference/test_signature.py +++ b/test/test_inference/test_signature.py @@ -1,5 +1,5 @@ from textwrap import dedent -from operator import ge, lt +from operator import eq, ge, lt import re import os @@ -14,7 +14,8 @@ from ..helpers import get_example_dir ('import math; math.cos', 'cos(x, /)', ['x'], ge, (3, 6)), ('next', 'next(iterator, default=None, /)', ['iterator', 'default'], lt, (3, 12)), - ('next', 'next()', [], ge, (3, 12)), + ('next', 'next()', [], eq, (3, 12)), + ('next', 'next(iterator, default=None, /)', ['iterator', 'default'], ge, (3, 13)), ('str', "str(object='', /) -> str", ['object'], ge, (3, 6)), diff --git a/test/test_utils.py b/test/test_utils.py index f17fc246..13786d38 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -73,15 +73,21 @@ class TestSetupReadline(unittest.TestCase): import os s = 'from os import ' goal = {s + el for el in dir(os)} + # There are minor differences, e.g. the dir doesn't include deleted # items as well as items that are not only available on linux. difference = set(self.complete(s)).symmetric_difference(goal) + ACCEPTED_DIFFERENCE_PREFIXES = [ + '_', 'O_', 'EX_', 'EFD_', 'MFD_', 'TFD_', + 'SF_', 'ST_', 'CLD_', 'POSIX_SPAWN_', 'P_', + 'RWF_', 'CLONE_', 'SCHED_', 'SPLICE_', + ] difference = { x for x in difference - if all(not x.startswith('from os import ' + s) - for s in ['_', 'O_', 'EX_', 'MFD_', 'SF_', 'ST_', - 'CLD_', 'POSIX_SPAWN_', 'P_', 'RWF_', - 'CLONE_', 'SCHED_']) + if not any( + x.startswith('from os import ' + prefix) + for prefix in ACCEPTED_DIFFERENCE_PREFIXES + ) } # There are quite a few differences, because both Windows and Linux # (posix and nt) libraries are included. -- 2.45.2