Merge pull request #2095 from davidhalter/typing

Use Zuban instead of Mypy for subsecond type checking and editor integration
This commit is contained in:
Dave Halter
2026-04-27 00:05:43 +00:00
committed by GitHub
76 changed files with 347 additions and 313 deletions
+6 -6
View File
@@ -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", "3.9", "3.8"] python-version: ["3.13", "3.12", "3.11", "3.10"]
environment: ['3.8', '3.13', '3.12', '3.11', '3.10', '3.9', 'interpreter'] environment: ['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
@@ -27,7 +27,7 @@ jobs:
allow-prereleases: true allow-prereleases: true
- name: Install dependencies - name: Install dependencies
run: 'pip install .[testing]' run: 'pip install .[dev]'
- name: Run tests - name: Run tests
run: python -m pytest run: python -m pytest
@@ -43,12 +43,12 @@ jobs:
submodules: recursive submodules: recursive
- name: Install dependencies - name: Install dependencies
run: 'pip install .[qa]' run: 'pip install .[dev]'
- name: Run tests - name: Run tests
run: | run: |
python -m flake8 jedi test setup.py python -m flake8 jedi test setup.py
python -m mypy jedi sith.py setup.py zuban check
coverage: coverage:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
@@ -60,7 +60,7 @@ jobs:
submodules: recursive submodules: recursive
- name: Install dependencies - name: Install dependencies
run: 'pip install .[testing] coverage' run: 'pip install .[dev] coverage'
- name: Run tests - name: Run tests
run: | run: |
+2 -2
View File
@@ -14,8 +14,8 @@ sphinx:
configuration: docs/conf.py configuration: docs/conf.py
build: build:
os: ubuntu-22.04 os: ubuntu-24.04
tools: tools:
python: "3.11" python: "3.14"
apt_packages: apt_packages:
- graphviz - graphviz
+3 -3
View File
@@ -102,7 +102,7 @@ Features and Limitations
Jedi's features are listed here: Jedi's features are listed here:
`Features <https://jedi.readthedocs.org/en/latest/docs/features.html>`_. `Features <https://jedi.readthedocs.org/en/latest/docs/features.html>`_.
You can run Jedi on Python 3.8+ but it should also You can run Jedi on Python 3.10+ but it should also
understand code that is older than those versions. Additionally you should be understand code that is older than those versions. Additionally you should be
able to use `Virtualenvs <https://jedi.readthedocs.org/en/latest/docs/api.html#environments>`_ able to use `Virtualenvs <https://jedi.readthedocs.org/en/latest/docs/api.html#environments>`_
very well. very well.
@@ -183,10 +183,10 @@ The test suite uses ``pytest``::
pip install pytest pip install pytest
If you want to test only a specific Python version (e.g. Python 3.8), it is as If you want to test only a specific Python version (e.g. Python 3.14), it is as
easy as:: easy as::
python3.8 -m pytest python3.14 -m pytest
For more detailed information visit the `testing documentation For more detailed information visit the `testing documentation
<https://jedi.readthedocs.org/en/latest/docs/testing.html>`_. <https://jedi.readthedocs.org/en/latest/docs/testing.html>`_.
+7 -21
View File
@@ -42,7 +42,7 @@ def pytest_addoption(parser):
help="Warnings are treated as errors.") help="Warnings are treated as errors.")
parser.addoption("--env", action='store', parser.addoption("--env", action='store',
help="Execute the tests in that environment (e.g. 39 for python3.9).") help="Execute the tests in that environment (e.g. 314 for python3.14).")
parser.addoption("--interpreter-env", "-I", action='store_true', parser.addoption("--interpreter-env", "-I", action='store_true',
help="Don't use subprocesses to guarantee having safe " help="Don't use subprocesses to guarantee having safe "
"code execution. Useful for debugging.") "code execution. Useful for debugging.")
@@ -133,11 +133,13 @@ def goto_or_help(request, Script):
@pytest.fixture(scope='session', params=['goto', 'help', 'infer']) @pytest.fixture(scope='session', params=['goto', 'help', 'infer'])
def goto_or_help_or_infer(request, Script): def goto_or_help_or_infer(request, Script):
def do(code, *args, **kwargs): class GotoOrHelpOrInfer:
return getattr(Script(code), request.param)(*args, **kwargs) def __call__(self, code, *args, **kwargs):
return getattr(Script(code), request.param)(*args, **kwargs)
do.type = request.param type = request.param
return do
return GotoOrHelpOrInfer()
@pytest.fixture(scope='session', params=['goto', 'complete', 'help']) @pytest.fixture(scope='session', params=['goto', 'complete', 'help'])
@@ -162,19 +164,3 @@ def skip_pre_python311(environment):
# This if is just needed to avoid that tests ever skip way more than # This if is just needed to avoid that tests ever skip way more than
# they should for all Python versions. # they should for all Python versions.
pytest.skip() pytest.skip()
@pytest.fixture()
def skip_pre_python38(environment):
if environment.version_info < (3, 8):
# This if is just needed to avoid that tests ever skip way more than
# they should for all Python versions.
pytest.skip()
@pytest.fixture()
def skip_pre_python37(environment):
if environment.version_info < (3, 7):
# This if is just needed to avoid that tests ever skip way more than
# they should for all Python versions.
pytest.skip()
+1 -1
View File
@@ -107,7 +107,7 @@ Completions
>>> code = '''import json; json.l''' >>> code = '''import json; json.l'''
>>> script = jedi.Script(code, path='example.py') >>> script = jedi.Script(code, path='example.py')
>>> script >>> script
<Script: 'example.py' <SameEnvironment: 3.9.0 in /usr>> <Script: 'example.py' <SameEnvironment: 3.14.0 in /usr>>
>>> completions = script.complete(1, 19) >>> completions = script.complete(1, 19)
>>> completions >>> completions
[<Completion: load>, <Completion: loads>] [<Completion: load>, <Completion: loads>]
+1 -1
View File
@@ -16,7 +16,7 @@ Jedi's main API calls and features are:
Basic Features Basic Features
-------------- --------------
- Python 3.8+ support - Python 3.10+ support
- Ignores syntax errors and wrong indentation - Ignores syntax errors and wrong indentation
- Can deal with complex module / function / class structures - Can deal with complex module / function / class structures
- Great ``virtualenv``/``venv`` support - Great ``virtualenv``/``venv`` support
+2 -2
View File
@@ -7,10 +7,10 @@ The test suite depends on ``pytest``::
pip install pytest pip install pytest
If you want to test only a specific Python version (e.g. Python 3.8), it is as If you want to test only a specific Python version (e.g. Python 3.14), it is as
easy as:: easy as::
python3.8 -m pytest python3.14 -m pytest
Tests are also run automatically on `GitHub Actions Tests are also run automatically on `GitHub Actions
<https://github.com/davidhalter/jedi/actions>`_. <https://github.com/davidhalter/jedi/actions>`_.
+1 -1
View File
@@ -13,7 +13,7 @@ Below you can also find a list of :ref:`recipes for type hinting <recipes>`.
.. _language-servers: .. _language-servers:
Language Servers Language Servers
-------------- ----------------
- `jedi-language-server <https://github.com/pappasam/jedi-language-server>`_ - `jedi-language-server <https://github.com/pappasam/jedi-language-server>`_
- `python-language-server <https://github.com/palantir/python-language-server>`_ (currently unmaintained) - `python-language-server <https://github.com/palantir/python-language-server>`_ (currently unmaintained)
+1 -1
View File
@@ -96,7 +96,7 @@ class BaseName:
@property @property
def module_path(self) -> Optional[Path]: def module_path(self) -> Optional[Path]:
""" """
Shows the file path of a module. e.g. ``/usr/lib/python3.9/os.py`` Shows the file path of a module. e.g. ``/usr/lib/python3.14/os.py``
""" """
module = self._get_module_context() module = self._get_module_context()
if module.is_stub() or not module.is_compiled(): if module.is_stub() or not module.is_compiled():
+2 -1
View File
@@ -1,5 +1,6 @@
import re import re
from textwrap import dedent from textwrap import dedent
from typing import Any
from inspect import Parameter from inspect import Parameter
from parso.python.token import PythonTokenTypes from parso.python.token import PythonTokenTypes
@@ -265,7 +266,7 @@ class Completion:
elif type_ == 'for_stmt': elif type_ == 'for_stmt':
allowed_transitions.append('else') allowed_transitions.append('else')
completion_names = [] completion_names: list[Any] = []
kwargs_only = False kwargs_only = False
if any(t in allowed_transitions for t in (PythonTokenTypes.NAME, if any(t in allowed_transitions for t in (PythonTokenTypes.NAME,
+29 -25
View File
@@ -8,7 +8,7 @@ import hashlib
import filecmp import filecmp
from collections import namedtuple from collections import namedtuple
from shutil import which from shutil import which
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, Any
from jedi.cache import memoize_method, time_cache from jedi.cache import memoize_method, time_cache
from jedi.inference.compiled.subprocess import CompiledSubprocess, \ from jedi.inference.compiled.subprocess import CompiledSubprocess, \
@@ -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', '3.9', '3.8'] _SUPPORTED_PYTHONS = ['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)
@@ -36,6 +36,9 @@ class InvalidPythonEnvironment(Exception):
class _BaseEnvironment: class _BaseEnvironment:
version_info: Any
executable: Any
@memoize_method @memoize_method
def get_grammar(self): def get_grammar(self):
version_string = '%s.%s' % (self.version_info.major, self.version_info.minor) version_string = '%s.%s' % (self.version_info.major, self.version_info.minor)
@@ -251,7 +254,7 @@ def get_cached_default_environment():
# /path/to/env so we need to fully resolve the paths in order to # /path/to/env so we need to fully resolve the paths in order to
# compare them. # compare them.
if var and os.path.realpath(var) != os.path.realpath(environment.path): if var and os.path.realpath(var) != os.path.realpath(environment.path):
_get_cached_default_environment.clear_cache() _get_cached_default_environment.clear_cache() # type: ignore[attr-defined]
return _get_cached_default_environment() return _get_cached_default_environment()
return environment return environment
@@ -355,7 +358,7 @@ def get_system_environment(version, *, env_vars=None):
return SameEnvironment() return SameEnvironment()
return Environment(exe) return Environment(exe)
if os.name == 'nt': if sys.platform == "win32":
for exe in _get_executables_from_windows_registry(version): for exe in _get_executables_from_windows_registry(version):
try: try:
return Environment(exe, env_vars=env_vars) return Environment(exe, env_vars=env_vars)
@@ -383,7 +386,7 @@ def _get_executable_path(path, safe=True):
Returns None if it's not actually a virtual env. Returns None if it's not actually a virtual env.
""" """
if os.name == 'nt': if sys.platform == "win32":
pythons = [os.path.join(path, 'Scripts', 'python.exe'), os.path.join(path, 'python.exe')] pythons = [os.path.join(path, 'Scripts', 'python.exe'), os.path.join(path, 'python.exe')]
else: else:
pythons = [os.path.join(path, 'bin', 'python')] pythons = [os.path.join(path, 'bin', 'python')]
@@ -397,27 +400,28 @@ def _get_executable_path(path, safe=True):
return python return python
def _get_executables_from_windows_registry(version): if sys.platform == "win32":
import winreg def _get_executables_from_windows_registry(version):
import winreg
# TODO: support Python Anaconda. # TODO: support Python Anaconda.
sub_keys = [ sub_keys = [
r'SOFTWARE\Python\PythonCore\{version}\InstallPath', r'SOFTWARE\Python\PythonCore\{version}\InstallPath',
r'SOFTWARE\Wow6432Node\Python\PythonCore\{version}\InstallPath', r'SOFTWARE\Wow6432Node\Python\PythonCore\{version}\InstallPath',
r'SOFTWARE\Python\PythonCore\{version}-32\InstallPath', r'SOFTWARE\Python\PythonCore\{version}-32\InstallPath',
r'SOFTWARE\Wow6432Node\Python\PythonCore\{version}-32\InstallPath' r'SOFTWARE\Wow6432Node\Python\PythonCore\{version}-32\InstallPath'
] ]
for root_key in [winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE]: for root_key in [winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE]:
for sub_key in sub_keys: for sub_key in sub_keys:
sub_key = sub_key.format(version=version) sub_key = sub_key.format(version=version)
try: try:
with winreg.OpenKey(root_key, sub_key) as key: with winreg.OpenKey(root_key, sub_key) as key:
prefix = winreg.QueryValueEx(key, '')[0] prefix = winreg.QueryValueEx(key, '')[0]
exe = os.path.join(prefix, 'python.exe') exe = os.path.join(prefix, 'python.exe')
if os.path.isfile(exe): if os.path.isfile(exe):
yield exe yield exe
except WindowsError: except WindowsError:
pass pass
def _assert_safe(executable_path, safe): def _assert_safe(executable_path, safe):
+1 -1
View File
@@ -41,7 +41,7 @@ def imitate_pydoc(string):
try: try:
# is a tuple now # is a tuple now
label, related = string label, related = string # type: ignore[misc]
except TypeError: except TypeError:
return '' return ''
+1 -1
View File
@@ -9,7 +9,7 @@ just use IPython instead::
Then you will be able to use Jedi completer in your Python interpreter:: Then you will be able to use Jedi completer in your Python interpreter::
$ python $ python
Python 3.9.2+ (default, Jul 20 2020, 22:15:08) Python 3.14.0+ (default, Jul 20 2020, 22:15:08)
[GCC 4.6.1] on linux2 [GCC 4.6.1] on linux2
Type "help", "copyright", "credits" or "license" for more information. Type "help", "copyright", "credits" or "license" for more information.
>>> import os >>> import os
+1 -1
View File
@@ -93,7 +93,7 @@ def time_cache(seconds):
cache[key] = time.time(), result cache[key] = time.time(), result
return result return result
wrapper.clear_cache = lambda: cache.clear() wrapper.clear_cache = lambda: cache.clear() # type: ignore[attr-defined]
return wrapper return wrapper
return decorator return decorator
+1 -1
View File
@@ -36,7 +36,7 @@ try:
# pytest resets the stream at the end - causes troubles. Since # pytest resets the stream at the end - causes troubles. Since
# after every output the stream is reset automatically we don't # after every output the stream is reset automatically we don't
# need this. # need this.
initialise.atexit_done = True initialise.atexit_done = True # type: ignore[attr-defined]
try: try:
init(strip=False) init(strip=False)
except Exception: except Exception:
+3
View File
@@ -1,4 +1,5 @@
import os import os
from typing import Any
from parso import file_io from parso import file_io
@@ -58,6 +59,8 @@ class FolderIO(AbstractFolderIO):
class FileIOFolderMixin: class FileIOFolderMixin:
path: Any
def get_parent_folder(self): def get_parent_folder(self):
return FolderIO(os.path.dirname(self.path)) return FolderIO(os.path.dirname(self.path))
+4
View File
@@ -62,6 +62,8 @@ I need to mention now that lazy type inference is really good because it
only *inferes* what needs to be *inferred*. All the statements and modules only *inferes* what needs to be *inferred*. All the statements and modules
that are not used are just being ignored. that are not used are just being ignored.
""" """
from typing import Any
import parso import parso
from jedi.file_io import FileIO from jedi.file_io import FileIO
@@ -82,6 +84,8 @@ from jedi.plugins import plugin_manager
class InferenceState: class InferenceState:
analysis_modules: "list[Any]"
def __init__(self, project, environment=None, script_path=None): def __init__(self, project, environment=None, script_path=None):
if environment is None: if environment is None:
environment = project.get_environment() environment = project.get_environment()
+4 -1
View File
@@ -1,5 +1,6 @@
import re import re
from itertools import zip_longest from itertools import zip_longest
from typing import Any
from parso.python import tree from parso.python import tree
@@ -134,7 +135,7 @@ class _AbstractArgumentsMixin:
class AbstractArguments(_AbstractArgumentsMixin): class AbstractArguments(_AbstractArgumentsMixin):
context = None context = None
argument_node = None argument_node: Any = None
trailer = None trailer = None
@@ -164,6 +165,8 @@ def unpack_arglist(arglist):
class TreeArguments(AbstractArguments): class TreeArguments(AbstractArguments):
context: Any
def __init__(self, inference_state, context, argument_node, trailer=None): def __init__(self, inference_state, context, argument_node, trailer=None):
""" """
:param argument_node: May be an argument_node or a list of nodes. :param argument_node: May be an argument_node or a list of nodes.
+25 -6
View File
@@ -9,6 +9,7 @@ just one.
from functools import reduce from functools import reduce
from operator import add from operator import add
from itertools import zip_longest from itertools import zip_longest
from typing import TYPE_CHECKING, Any
from parso.python.tree import Name from parso.python.tree import Name
@@ -21,12 +22,25 @@ from jedi.cache import memoize_method
sentinel = object() sentinel = object()
if TYPE_CHECKING:
from jedi.inference import InferenceState
class HasNoContext(Exception): class HasNoContext(Exception):
pass pass
class HelperValueMixin: class HelperValueMixin:
parent_context: Any
inference_state: "InferenceState"
name: Any
get_filters: Any
is_stub: Any
py__getattribute__alternatives: Any
py__iter__: Any
py__mro__: Any
_as_context: Any
def get_root_context(self): def get_root_context(self):
value = self value = self
if value.parent_context is None: if value.parent_context is None:
@@ -337,11 +351,16 @@ class _ValueWrapperBase(HelperValueMixin):
class LazyValueWrapper(_ValueWrapperBase): class LazyValueWrapper(_ValueWrapperBase):
@safe_property if TYPE_CHECKING:
@memoize_method @property
def _wrapped_value(self): def _wrapped_value(self) -> Any:
with debug.increase_indent_cm('Resolve lazy value wrapper'): return
return self._get_wrapped_value() else:
@safe_property
@memoize_method
def _wrapped_value(self):
with debug.increase_indent_cm('Resolve lazy value wrapper'):
return self._get_wrapped_value()
def __repr__(self): def __repr__(self):
return '<%s>' % (self.__class__.__name__) return '<%s>' % (self.__class__.__name__)
@@ -499,7 +518,7 @@ class ValueSet:
return ValueSet.from_sets(_getitem(c, *args, **kwargs) for c in self._set) return ValueSet.from_sets(_getitem(c, *args, **kwargs) for c in self._set)
def try_merge(self, function_name): def try_merge(self, function_name):
value_set = self.__class__([]) value_set = ValueSet([])
for c in self._set: for c in self._set:
try: try:
method = getattr(c, function_name) method = getattr(c, function_name)
+2 -2
View File
@@ -39,7 +39,7 @@ def _is_type(obj):
def _shadowed_dict(klass): def _shadowed_dict(klass):
dict_attr = type.__dict__["__dict__"] dict_attr = type.__dict__["__dict__"] # type: ignore[index]
for entry in _static_getmro(klass): for entry in _static_getmro(klass):
try: try:
class_dict = dict_attr.__get__(entry)["__dict__"] class_dict = dict_attr.__get__(entry)["__dict__"]
@@ -54,7 +54,7 @@ def _shadowed_dict(klass):
def _static_getmro(klass): def _static_getmro(klass):
mro = type.__dict__['__mro__'].__get__(klass) mro = type.__dict__['__mro__'].__get__(klass) # type: ignore[index]
if not isinstance(mro, (tuple, list)): if not isinstance(mro, (tuple, list)):
# There are unfortunately no tests for this, I was not able to # There are unfortunately no tests for this, I was not able to
# reproduce this in pure Python. However should still solve the issue # reproduce this in pure Python. However should still solve the issue
@@ -33,7 +33,7 @@ import traceback
import weakref import weakref
from functools import partial from functools import partial
from threading import Thread from threading import Thread
from typing import Dict, TYPE_CHECKING from typing import Dict, TYPE_CHECKING, Any
from jedi._compatibility import pickle_dump, pickle_load from jedi._compatibility import pickle_dump, pickle_load
from jedi import debug from jedi import debug
@@ -52,7 +52,7 @@ PICKLE_PROTOCOL = 4
def _GeneralizedPopen(*args, **kwargs): def _GeneralizedPopen(*args, **kwargs):
if os.name == 'nt': if sys.platform == "win32":
try: try:
# Was introduced in Python 3.7. # Was introduced in Python 3.7.
CREATE_NO_WINDOW = subprocess.CREATE_NO_WINDOW CREATE_NO_WINDOW = subprocess.CREATE_NO_WINDOW
@@ -104,6 +104,8 @@ def _cleanup_process(process, thread):
class _InferenceStateProcess: class _InferenceStateProcess:
get_compiled_method_return: Any
def __init__(self, inference_state: 'InferenceState') -> None: def __init__(self, inference_state: 'InferenceState') -> None:
self._inference_state_weakref = weakref.ref(inference_state) self._inference_state_weakref = weakref.ref(inference_state)
self._handles: Dict[int, AccessHandle] = {} self._handles: Dict[int, AccessHandle] = {}
@@ -158,7 +158,10 @@ def _find_module(string, path=None, full_name=None, is_global_search=True):
if loader is None and not spec.has_location: if loader is None and not spec.has_location:
# This is a namespace package. # This is a namespace package.
full_name = string if not path else full_name full_name = string if not path else full_name
implicit_ns_info = ImplicitNSInfo(full_name, spec.submodule_search_locations._path) implicit_ns_info = ImplicitNSInfo(
full_name,
spec.submodule_search_locations._path, # type: ignore[union-attr]
)
return implicit_ns_info, True return implicit_ns_info, True
break break
+2 -2
View File
@@ -592,7 +592,7 @@ def create_from_name(inference_state, compiled_value, name):
value = create_cached_compiled_value( value = create_cached_compiled_value(
inference_state, inference_state,
access_path, access_path,
parent_context=None if value is None else value.as_context(), parent_context=None if value is None else value.as_context(), # type: ignore # TODO
) )
return value return value
@@ -610,7 +610,7 @@ def create_from_access_path(inference_state, access_path):
value = create_cached_compiled_value( value = create_cached_compiled_value(
inference_state, inference_state,
access, access,
parent_context=None if value is None else value.as_context() parent_context=None if value is None else value.as_context() # type: ignore # TODO
) )
return value return value
+10 -2
View File
@@ -1,7 +1,7 @@
from abc import abstractmethod from abc import abstractmethod
from contextlib import contextmanager from contextlib import contextmanager
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional, Any
from parso.python.tree import Name from parso.python.tree import Name
@@ -16,6 +16,8 @@ from jedi import parser_utils
class AbstractContext: class AbstractContext:
# Must be defined: inference_state and tree_node and parent_context as an attribute/property # Must be defined: inference_state and tree_node and parent_context as an attribute/property
tree_node: Any
parent_context: Any
def __init__(self, inference_state): def __init__(self, inference_state):
self.inference_state = inference_state self.inference_state = inference_state
@@ -218,6 +220,13 @@ class ValueContext(AbstractContext):
class TreeContextMixin: class TreeContextMixin:
tree_node: Any
is_module: Any
get_value: Any
inference_state: Any
is_class: Any
parent_context: Any
def infer_node(self, node): def infer_node(self, node):
from jedi.inference.syntax_tree import infer_node from jedi.inference.syntax_tree import infer_node
return infer_node(self, node) return infer_node(self, node)
@@ -300,7 +309,6 @@ class TreeContextMixin:
class FunctionContext(TreeContextMixin, ValueContext): class FunctionContext(TreeContextMixin, ValueContext):
def get_filters(self, until_position=None, origin_scope=None): def get_filters(self, until_position=None, origin_scope=None):
yield ParserTreeFilter( yield ParserTreeFilter(
self.inference_state,
parent_context=self, parent_context=self,
until_position=until_position, until_position=until_position,
origin_scope=origin_scope origin_scope=origin_scope
+1 -1
View File
@@ -16,6 +16,6 @@ class DocstringModuleContext(ModuleContext):
super().__init__(module_value) super().__init__(module_value)
self._in_module_context = in_module_context self._in_module_context = in_module_context
def get_filters(self, origin_scope=None, until_position=None): def get_filters(self, until_position=None, origin_scope=None):
yield from super().get_filters(until_position=until_position) yield from super().get_filters(until_position=until_position)
yield from self._in_module_context.get_filters() yield from self._in_module_context.get_filters()
+2 -2
View File
@@ -109,7 +109,7 @@ def _search_function_arguments(module_context, funcdef, string_name):
if string_name == '__init__': if string_name == '__init__':
cls = get_parent_scope(funcdef) cls = get_parent_scope(funcdef)
if cls.type == 'classdef': if cls.type == 'classdef':
string_name = cls.name.value string_name = cls.name.value # type: ignore[union-attr]
compare_node = cls compare_node = cls
found_arguments = False found_arguments = False
@@ -203,7 +203,7 @@ def _check_name_for_execution(inference_state, context, compare_node, name, trai
# Here we're trying to find decorators by checking the first # Here we're trying to find decorators by checking the first
# parameter. It's not very generic though. Should find a better # parameter. It's not very generic though. Should find a better
# solution that also applies to nested decorators. # solution that also applies to nested decorators.
param_names = value.parent_context.get_param_names() param_names = value.parent_context.get_param_names() # type: ignore[attr-defined]
if len(param_names) != 1: if len(param_names) != 1:
continue continue
values = param_names[0].infer() values = param_names[0].infer()
+6 -3
View File
@@ -3,7 +3,7 @@ Filters are objects that you can use to filter names in different scopes. They
are needed for name resolution. are needed for name resolution.
""" """
from abc import abstractmethod from abc import abstractmethod
from typing import List, MutableMapping, Type from typing import MutableMapping, Type, Any
import weakref import weakref
from parso.python.tree import Name, UsedNamesMapping from parso.python.tree import Name, UsedNamesMapping
@@ -16,8 +16,8 @@ from jedi.inference.utils import to_list
from jedi.inference.names import TreeNameDefinition, ParamName, \ from jedi.inference.names import TreeNameDefinition, ParamName, \
AnonymousParamName, AbstractNameDefinition, NameWrapper AnonymousParamName, AbstractNameDefinition, NameWrapper
_definition_name_cache: MutableMapping[UsedNamesMapping, List[Name]] _definition_name_cache: 'MutableMapping[UsedNamesMapping, dict[str, tuple[Name, ...]]]' \
_definition_name_cache = weakref.WeakKeyDictionary() = weakref.WeakKeyDictionary()
class AbstractFilter: class AbstractFilter:
@@ -346,6 +346,9 @@ class _OverwriteMeta(type):
class _AttributeOverwriteMixin: class _AttributeOverwriteMixin:
overwritten_methods: Any
_wrapped_value: Any
def get_filters(self, *args, **kwargs): def get_filters(self, *args, **kwargs):
yield SpecialMethodFilter(self, self.overwritten_methods, self._wrapped_value) yield SpecialMethodFilter(self, self.overwritten_methods, self._wrapped_value)
yield from self._wrapped_value.get_filters(*args, **kwargs) yield from self._wrapped_value.get_filters(*args, **kwargs)
+2 -2
View File
@@ -195,7 +195,7 @@ class GenericClass(DefineGenericBaseClass, ClassMixin):
@to_list @to_list
def py__bases__(self): def py__bases__(self):
for base in self._wrapped_value.py__bases__(): for base in self._wrapped_value.py__bases__(): # type: ignore[attr-defined]
yield _LazyGenericBaseClass(self, base, self._generics_manager) yield _LazyGenericBaseClass(self, base, self._generics_manager)
def _create_instance_with_generics(self, generics_manager): def _create_instance_with_generics(self, generics_manager):
@@ -384,7 +384,7 @@ class BaseTypingValue(LazyValueWrapper):
return _PseudoTreeNameClass(self.parent_context, self._tree_name) return _PseudoTreeNameClass(self.parent_context, self._tree_name)
def get_signatures(self): def get_signatures(self):
return self._wrapped_value.get_signatures() return self._wrapped_value.get_signatures() # type: ignore[attr-defined]
def __repr__(self): def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self._tree_name.value) return '%s(%s)' % (self.__class__.__name__, self._tree_name.value)
+9
View File
@@ -2,6 +2,7 @@
This module is about generics, like the `int` in `List[int]`. It's not about This module is about generics, like the `int` in `List[int]`. It's not about
the Generic class. the Generic class.
""" """
from abc import abstractmethod
from jedi import debug from jedi import debug
from jedi.cache import memoize_method from jedi.cache import memoize_method
@@ -24,6 +25,14 @@ def _resolve_forward_references(context, value_set):
class _AbstractGenericManager: class _AbstractGenericManager:
@abstractmethod
def __getitem__(self, index):
raise NotImplementedError
@abstractmethod
def to_tuple(self):
raise NotImplementedError
def get_index_and_execute(self, index): def get_index_and_execute(self, index):
try: try:
return self[index].execute_annotation() return self[index].execute_annotation()
+3
View File
@@ -6,6 +6,7 @@ values.
This file deals with all the typing.py cases. This file deals with all the typing.py cases.
""" """
import itertools import itertools
from typing import Any
from jedi import debug from jedi import debug
from jedi.inference.compiled import builtin_from_name, create_simple_object from jedi.inference.compiled import builtin_from_name, create_simple_object
@@ -186,6 +187,8 @@ class ProxyTypingValue(BaseTypingValue):
class _TypingClassMixin(ClassMixin): class _TypingClassMixin(ClassMixin):
_tree_name: Any
def py__bases__(self): def py__bases__(self):
return [LazyKnownValues( return [LazyKnownValues(
self.inference_state.builtins_module.py__getattribute__('object') self.inference_state.builtins_module.py__getattribute__('object')
+2 -2
View File
@@ -5,12 +5,12 @@ import os
from itertools import chain from itertools import chain
from contextlib import contextmanager from contextlib import contextmanager
from parso.python import tree from parso import tree
def is_stdlib_path(path): def is_stdlib_path(path):
# Python standard library paths look like this: # Python standard library paths look like this:
# /usr/lib/python3.9/... # /usr/lib/python3.14/...
# TODO The implementation below is probably incorrect and not complete. # TODO The implementation below is probably incorrect and not complete.
parts = path.parts parts = path.parts
if 'dist-packages' in parts or 'site-packages' in parts: if 'dist-packages' in parts or 'site-packages' in parts:
+5 -5
View File
@@ -370,16 +370,16 @@ def import_module_by_names(inference_state, import_names, sys_path=None,
i.value if isinstance(i, tree.Name) else i i.value if isinstance(i, tree.Name) else i
for i in import_names for i in import_names
) )
value_set = [None] base = [None]
for i, name in enumerate(import_names): for i, name in enumerate(import_names):
value_set = ValueSet.from_sets([ base = value_set = ValueSet.from_sets([
import_module( import_module(
inference_state, inference_state,
str_import_names[:i+1], str_import_names[:i+1],
parent_module_value, parent_module_value,
sys_path, sys_path,
prefer_stubs=prefer_stubs, prefer_stubs=prefer_stubs, # type: ignore[call-arg]
) for parent_module_value in value_set ) for parent_module_value in base
]) ])
if not value_set: if not value_set:
message = 'No module named ' + '.'.join(str_import_names) message = 'No module named ' + '.'.join(str_import_names)
@@ -474,7 +474,7 @@ def _load_python_module(inference_state, file_io,
) )
def _load_builtin_module(inference_state, import_names=None, sys_path=None): def _load_builtin_module(inference_state, import_names, sys_path):
project = inference_state.project project = inference_state.project
if sys_path is None: if sys_path is None:
sys_path = inference_state.get_sys_path() sys_path = inference_state.get_sys_path()
+21 -10
View File
@@ -1,9 +1,8 @@
from abc import abstractmethod from abc import abstractmethod
from inspect import Parameter from inspect import Parameter
from typing import Optional, Tuple from typing import Optional, Tuple, Any
from jedi.parser_utils import find_statement_documentation, clean_scope_docstring from jedi.parser_utils import find_statement_documentation, clean_scope_docstring
from jedi.inference.utils import unite
from jedi.inference.base_value import ValueSet, NO_VALUES from jedi.inference.base_value import ValueSet, NO_VALUES
from jedi.inference.cache import inference_state_method_cache from jedi.inference.cache import inference_state_method_cache
from jedi.inference import docstrings from jedi.inference import docstrings
@@ -37,7 +36,6 @@ class AbstractNameDefinition:
def infer(self): def infer(self):
raise NotImplementedError raise NotImplementedError
@abstractmethod
def goto(self): def goto(self):
# Typically names are already definitions and therefore a goto on that # Typically names are already definitions and therefore a goto on that
# name will always result on itself. # name will always result on itself.
@@ -105,6 +103,9 @@ class AbstractArbitraryName(AbstractNameDefinition):
class AbstractTreeName(AbstractNameDefinition): class AbstractTreeName(AbstractNameDefinition):
tree_name: Any
parent_context: Any
def __init__(self, parent_context, tree_name): def __init__(self, parent_context, tree_name):
self.parent_context = parent_context self.parent_context = parent_context
self.tree_name = tree_name self.tree_name = tree_name
@@ -194,10 +195,11 @@ class AbstractTreeName(AbstractNameDefinition):
new_dotted = deep_ast_copy(par) new_dotted = deep_ast_copy(par)
new_dotted.children[index - 1:] = [] new_dotted.children[index - 1:] = []
values = context.infer_node(new_dotted) values = context.infer_node(new_dotted)
return unite( return [
value.goto(name, name_context=context) n
for value in values for value in values
) for n in value.goto(name, name_context=context)
]
if node_type == 'trailer' and par.children[0] == '.': if node_type == 'trailer' and par.children[0] == '.':
values = infer_call_of_leaf(context, name, cut_own_trailer=True) values = infer_call_of_leaf(context, name, cut_own_trailer=True)
@@ -222,6 +224,9 @@ class AbstractTreeName(AbstractNameDefinition):
class ValueNameMixin: class ValueNameMixin:
_value: Any
parent_context: Any
def infer(self): def infer(self):
return ValueSet([self._value]) return ValueSet([self._value])
@@ -240,7 +245,7 @@ class ValueNameMixin:
def get_root_context(self): def get_root_context(self):
if self.parent_context is None: # A module if self.parent_context is None: # A module
return self._value.as_context() return self._value.as_context()
return super().get_root_context() return super().get_root_context() # type: ignore
def get_defining_qualified_value(self): def get_defining_qualified_value(self):
context = self.parent_context context = self.parent_context
@@ -357,14 +362,16 @@ class TreeNameDefinition(AbstractTreeName):
class _ParamMixin: class _ParamMixin:
get_kind: Any
def maybe_positional_argument(self, include_star=True): def maybe_positional_argument(self, include_star=True):
options = [Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD] options: list[int] = [Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
if include_star: if include_star:
options.append(Parameter.VAR_POSITIONAL) options.append(Parameter.VAR_POSITIONAL)
return self.get_kind() in options return self.get_kind() in options
def maybe_keyword_argument(self, include_stars=True): def maybe_keyword_argument(self, include_stars=True):
options = [Parameter.KEYWORD_ONLY, Parameter.POSITIONAL_OR_KEYWORD] options: list[int] = [Parameter.KEYWORD_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
if include_stars: if include_stars:
options.append(Parameter.VAR_KEYWORD) options.append(Parameter.VAR_KEYWORD)
return self.get_kind() in options return self.get_kind() in options
@@ -629,6 +636,10 @@ class NameWrapper:
class StubNameMixin: class StubNameMixin:
api_type: str
tree_name: Any
infer: Any
def py__doc__(self): def py__doc__(self):
from jedi.inference.gradual.conversion import convert_names from jedi.inference.gradual.conversion import convert_names
# Stubs are not complicated and we can just follow simple statements # Stubs are not complicated and we can just follow simple statements
@@ -640,7 +651,7 @@ class StubNameMixin:
names = convert_names(names, prefer_stub_to_compiled=False) names = convert_names(names, prefer_stub_to_compiled=False)
if self in names: if self in names:
return super().py__doc__() return super().py__doc__() # type: ignore
else: else:
# We have signatures ourselves in stubs, so don't use signatures # We have signatures ourselves in stubs, so don't use signatures
# from the implementation. # from the implementation.
+7
View File
@@ -1,4 +1,5 @@
from inspect import Parameter from inspect import Parameter
from typing import Any
from jedi.cache import memoize_method from jedi.cache import memoize_method
from jedi import debug from jedi import debug
@@ -6,6 +7,10 @@ from jedi import parser_utils
class _SignatureMixin: class _SignatureMixin:
get_param_names: Any
name: Any
annotation_string: Any
def to_string(self): def to_string(self):
def param_strings(): def param_strings():
is_positional = False is_positional = False
@@ -36,6 +41,8 @@ class _SignatureMixin:
class AbstractSignature(_SignatureMixin): class AbstractSignature(_SignatureMixin):
_function_value: Any
def __init__(self, value, is_bound=False): def __init__(self, value, is_bound=False):
self.value = value self.value = value
self.is_bound = is_bound self.is_bound = is_bound
+1 -1
View File
@@ -89,6 +89,7 @@ def infer_node(context, element):
if isinstance(context, CompForContext): if isinstance(context, CompForContext):
return _infer_node(context, element) return _infer_node(context, element)
name_dicts = [{}]
if_stmt = element if_stmt = element
while if_stmt is not None: while if_stmt is not None:
if_stmt = if_stmt.parent if_stmt = if_stmt.parent
@@ -104,7 +105,6 @@ def infer_node(context, element):
if predefined_if_name_dict is None and if_stmt \ if predefined_if_name_dict is None and if_stmt \
and if_stmt.type == 'if_stmt' and context.inference_state.is_analysis: and if_stmt.type == 'if_stmt' and context.inference_state.is_analysis:
if_stmt_test = if_stmt.children[1] if_stmt_test = if_stmt.children[1]
name_dicts = [{}]
# If we already did a check, we don't want to do it again -> If # If we already did a check, we don't want to do it again -> If
# value.predefined_names is filled, we stop. # value.predefined_names is filled, we stop.
# We don't want to check the if stmt itself, it's just about # We don't want to check the if stmt itself, it's just about
-1
View File
@@ -74,7 +74,6 @@ class PushBackIterator:
def __init__(self, iterator): def __init__(self, iterator):
self.pushes = [] self.pushes = []
self.iterator = iterator self.iterator = iterator
self.current = None
def push_back(self, value): def push_back(self, value):
self.pushes.append(value) self.pushes.append(value)
+6
View File
@@ -1,3 +1,5 @@
from typing import Any
from jedi import debug from jedi import debug
from jedi.inference.cache import inference_state_method_cache, CachedMetaClass from jedi.inference.cache import inference_state_method_cache, CachedMetaClass
from jedi.inference import compiled from jedi.inference import compiled
@@ -53,6 +55,10 @@ class FunctionAndClassBase(TreeValue):
class FunctionMixin: class FunctionMixin:
api_type = 'function' api_type = 'function'
tree_node: Any
py__class__: Any
as_context: Any
get_signature_functions: Any
def get_filters(self, origin_scope=None): def get_filters(self, origin_scope=None):
cls = self.py__class__() cls = self.py__class__()
+4
View File
@@ -1,4 +1,5 @@
from abc import abstractproperty from abc import abstractproperty
from typing import Any
from jedi import debug from jedi import debug
from jedi import settings from jedi import settings
@@ -187,6 +188,9 @@ class CompiledInstance(AbstractInstanceValue):
class _BaseTreeInstance(AbstractInstanceValue): class _BaseTreeInstance(AbstractInstanceValue):
get_defined_names: Any
_arguments: Any
@property @property
def array_type(self): def array_type(self):
name = self.class_value.py__name__() name = self.class_value.py__name__()
+17 -1
View File
@@ -2,6 +2,8 @@
Contains all classes and functions to deal with lists, dicts, generators and Contains all classes and functions to deal with lists, dicts, generators and
iterators in general. iterators in general.
""" """
from typing import Any
from jedi.inference import compiled from jedi.inference import compiled
from jedi.inference import analysis from jedi.inference import analysis
from jedi.inference.lazy_value import LazyKnownValue, LazyKnownValues, \ from jedi.inference.lazy_value import LazyKnownValue, LazyKnownValues, \
@@ -20,6 +22,9 @@ from jedi.inference.value.dynamic_arrays import check_array_additions
class IterableMixin: class IterableMixin:
py__iter__: Any
inference_state: Any
def py__next__(self, contextualized_node=None): def py__next__(self, contextualized_node=None):
return self.py__iter__(contextualized_node) return self.py__iter__(contextualized_node)
@@ -128,6 +133,12 @@ def comprehension_from_atom(inference_state, value, atom):
class ComprehensionMixin: class ComprehensionMixin:
_defining_context: Any
_entry_node: Any
array_type: Any
_value_node: Any
_sync_comp_for_node: Any
@inference_state_method_cache() @inference_state_method_cache()
def _get_comp_for_context(self, parent_context, comp_for): def _get_comp_for_context(self, parent_context, comp_for):
return CompForContext(parent_context, comp_for) return CompForContext(parent_context, comp_for)
@@ -176,6 +187,8 @@ class ComprehensionMixin:
class _DictMixin: class _DictMixin:
get_mapping_item_values: Any
def _get_generics(self): def _get_generics(self):
return tuple(c_set.py__class__() for c_set in self.get_mapping_item_values()) return tuple(c_set.py__class__() for c_set in self.get_mapping_item_values())
@@ -220,7 +233,7 @@ class Sequence(LazyAttributeOverwrite, IterableMixin):
class _BaseComprehension(ComprehensionMixin): class _BaseComprehension(ComprehensionMixin):
def __init__(self, inference_state, defining_context, sync_comp_for_node, entry_node): def __init__(self, inference_state, defining_context, sync_comp_for_node, entry_node):
assert sync_comp_for_node.type == 'sync_comp_for' assert sync_comp_for_node.type == 'sync_comp_for'
super().__init__(inference_state) super().__init__(inference_state) # type: ignore[call-arg]
self._defining_context = defining_context self._defining_context = defining_context
self._sync_comp_for_node = sync_comp_for_node self._sync_comp_for_node = sync_comp_for_node
self._entry_node = entry_node self._entry_node = entry_node
@@ -248,6 +261,9 @@ class GeneratorComprehension(_BaseComprehension, GeneratorBase):
class _DictKeyMixin: class _DictKeyMixin:
_dict_keys: Any
_dict_values: Any
# TODO merge with _DictMixin? # TODO merge with _DictMixin?
def get_mapping_item_values(self): def get_mapping_item_values(self):
return self._dict_keys(), self._dict_values() return self._dict_keys(), self._dict_values()
+16 -1
View File
@@ -38,7 +38,7 @@ py__doc__() Returns the docstring for a value.
""" """
from __future__ import annotations from __future__ import annotations
from typing import List, Optional, Tuple from typing import List, Optional, Tuple, TYPE_CHECKING, Any
from jedi import debug from jedi import debug
from jedi.parser_utils import get_cached_parent_scope, expr_is_dotted, \ from jedi.parser_utils import get_cached_parent_scope, expr_is_dotted, \
@@ -61,6 +61,9 @@ from inspect import Parameter
from jedi.inference.names import BaseTreeParamName from jedi.inference.names import BaseTreeParamName
from jedi.inference.signature import AbstractSignature from jedi.inference.signature import AbstractSignature
if TYPE_CHECKING:
from jedi.inference import InferenceState
class ClassName(TreeNameDefinition): class ClassName(TreeNameDefinition):
def __init__(self, class_value, tree_name, name_context, apply_decorators): def __init__(self, class_value, tree_name, name_context, apply_decorators):
@@ -197,6 +200,15 @@ def get_dataclass_param_names(cls) -> List[DataclassParamName]:
class ClassMixin: class ClassMixin:
tree_node: Any
parent_context: Any
inference_state: InferenceState
py__bases__: Any
get_metaclasses: Any
get_metaclass_filters: Any
get_metaclass_signatures: Any
list_type_vars: Any
def is_class(self): def is_class(self):
return True return True
@@ -682,6 +694,9 @@ class ClassValue(ClassMixin, FunctionAndClassBase, metaclass=CachedMetaClass):
""" """
bases_arguments = self._get_bases_arguments() bases_arguments = self._get_bases_arguments()
if bases_arguments is None:
return None
if bases_arguments.argument_node.type != "arglist": if bases_arguments.argument_node.type != "arglist":
# If it is not inheriting from the base model and having # If it is not inheriting from the base model and having
# extra parameters, then init behavior is not changed. # extra parameters, then init behavior is not changed.
+13 -1
View File
@@ -1,6 +1,6 @@
import os import os
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional, TYPE_CHECKING, Any
from jedi.inference.cache import inference_state_method_cache from jedi.inference.cache import inference_state_method_cache
from jedi.inference.names import AbstractNameDefinition, ModuleName from jedi.inference.names import AbstractNameDefinition, ModuleName
@@ -13,6 +13,9 @@ from jedi.inference.compiled import create_simple_object
from jedi.inference.base_value import ValueSet from jedi.inference.base_value import ValueSet
from jedi.inference.context import ModuleContext from jedi.inference.context import ModuleContext
if TYPE_CHECKING:
from jedi.inference import InferenceState
class _ModuleAttributeName(AbstractNameDefinition): class _ModuleAttributeName(AbstractNameDefinition):
""" """
@@ -35,6 +38,11 @@ class _ModuleAttributeName(AbstractNameDefinition):
class SubModuleDictMixin: class SubModuleDictMixin:
inference_state: "InferenceState"
is_package: Any
py__path__: Any
as_context: Any
@inference_state_method_cache() @inference_state_method_cache()
def sub_modules_dict(self): def sub_modules_dict(self):
""" """
@@ -57,6 +65,10 @@ class SubModuleDictMixin:
class ModuleMixin(SubModuleDictMixin): class ModuleMixin(SubModuleDictMixin):
_module_name_class = ModuleName _module_name_class = ModuleName
tree_node: Any
string_names: Any
sub_modules_dict: Any
py__file__: Any
def get_filters(self, origin_scope=None): def get_filters(self, origin_scope=None):
yield MergedFilter( yield MergedFilter(
+1 -1
View File
@@ -259,7 +259,7 @@ def get_parent_scope(node, include_flows=False):
# the if, but the parent of the if. # the if, but the parent of the if.
if not (scope.type == 'if_stmt' if not (scope.type == 'if_stmt'
and any(n.start_pos <= node.start_pos < n.end_pos and any(n.start_pos <= node.start_pos < n.end_pos
for n in scope.get_test_nodes())): for n in scope.get_test_nodes())): # type: ignore[attr-defined]
return scope return scope
scope = scope.parent scope = scope.parent
+2 -1
View File
@@ -2,6 +2,7 @@
Module is used to infer Django model fields. Module is used to infer Django model fields.
""" """
from inspect import Parameter from inspect import Parameter
from typing import Any
from jedi import debug from jedi import debug
from jedi.inference.cache import inference_state_function_cache from jedi.inference.cache import inference_state_function_cache
@@ -140,7 +141,7 @@ def _new_dict_filter(cls, is_instance):
include_metaclasses=False, include_metaclasses=False,
include_type_when_class=False) include_type_when_class=False)
) )
dct = { dct: dict[str, Any] = {
name.string_name: DjangoModelName(cls, name, is_instance) name.string_name: DjangoModelName(cls, name, is_instance)
for filter_ in reversed(filters) for filter_ in reversed(filters)
for name in filter_.values() for name in filter_.values()
+7 -21
View File
@@ -138,28 +138,14 @@ def _find_pytest_plugin_modules() -> List[List[str]]:
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): from importlib.metadata import entry_points
from importlib.metadata import entry_points
if sys.version_info >= (3, 10):
pytest_entry_points = entry_points(group="pytest11")
else:
pytest_entry_points = entry_points().get("pytest11", ())
if sys.version_info >= (3, 9):
return [ep.module.split(".") for ep in pytest_entry_points]
else:
# Python 3.8 doesn't have `EntryPoint.module`. Implement equivalent
# to what Python 3.9 does (with additional None check to placate `mypy`)
matches = [
ep.pattern.match(ep.value)
for ep in pytest_entry_points
]
return [x.group('module').split(".") for x in matches if x]
if sys.version_info >= (3, 10):
pytest_entry_points = entry_points(group="pytest11")
else: else:
from pkg_resources import iter_entry_points pytest_entry_points = entry_points().get("pytest11", ())
return [ep.module_name.split(".") for ep in iter_entry_points(group="pytest11")]
return [ep.module.split(".") for ep in pytest_entry_points]
@inference_state_method_cache() @inference_state_method_cache()
@@ -192,7 +178,7 @@ def _iter_pytest_modules(module_context, skip_own_module=False):
folder = folder.get_parent_folder() folder = folder.get_parent_folder()
# prevent an infinite for loop if the same parent folder is return twice # prevent an infinite for loop if the same parent folder is return twice
if last_folder is not None and folder.path == last_folder.path: if last_folder is not None and folder.path == last_folder.path: # type: ignore # TODO
break break
last_folder = folder # keep track of the last found parent name last_folder = folder # keep track of the last found parent name
+1 -1
View File
@@ -134,7 +134,7 @@ def execute(callback):
except KeyError: except KeyError:
pass pass
else: else:
return func(value, arguments=arguments, callback=call) return func(value, arguments=arguments, callback=call) # type: ignore
return call() return call()
return wrapper return wrapper
+12 -30
View File
@@ -1,32 +1,14 @@
[tool.mypy] [tool.zuban]
# Exclude our copies of external stubs strict = true
exclude = "^jedi/third_party" enable_error_code = ["ignore-without-code"]
show_error_codes = true # Revert some --strict specific flags:
enable_error_code = "ignore-without-code" allow_untyped_calls = true
allow_untyped_defs = true
# Ensure generics are explicit about what they are (e.g: `List[str]` rather than allow_incomplete_defs = true
# just `List`) allow_untyped_globals = true
disallow_any_generics = true untyped_strict_optional = false
disallow_subclassing_any = true
# Avoid creating future gotchas emerging from bad typing
warn_redundant_casts = true
warn_unused_ignores = true
warn_return_any = true
warn_unused_configs = true
warn_unreachable = true
# Require values to be explicitly re-exported; this makes things easier for
# Flake8 too and avoids accidentally importing thing from the "wrong" place
# (which helps avoid circular imports)
implicit_reexport = false
strict_equality = true
[[tool.mypy.overrides]]
# Various __init__.py files which contain re-exports we want to implicitly make.
module = ["jedi", "jedi.inference.compiled", "jedi.inference.value", "parso"]
implicit_reexport = true implicit_reexport = true
# Exclude our copies of external stubs
exclude = "^jedi/third_party|^test/(completion|refactor|static_analysis|examples)/"
-50
View File
@@ -1,50 +0,0 @@
#!/usr/bin/env python
"""
Profile a piece of Python code with ``cProfile`` that uses the diff parser.
Usage:
profile.py <file> [-d] [-s <sort>]
profile.py -h | --help
Options:
-h --help Show this screen.
-d --debug Enable Jedi internal debugging.
-s <sort> Sort the profile results, e.g. cumtime, name [default: time].
"""
import cProfile
from docopt import docopt
from jedi.parser.python import load_grammar
from jedi.parser.diff import DiffParser
from jedi.parser.python import ParserWithRecovery
from jedi.common import splitlines
import jedi
def run(parser, lines):
diff_parser = DiffParser(parser)
diff_parser.update(lines)
# Make sure used_names is loaded
parser.module.used_names
def main(args):
if args['--debug']:
jedi.set_debug_function(notices=True)
with open(args['<file>']) as f:
code = f.read()
grammar = load_grammar()
parser = ParserWithRecovery(grammar, code)
# Make sure used_names is loaded
parser.module.used_names
code = code + '\na\n' # Add something so the diff parser needs to run.
lines = splitlines(code, keepends=True)
cProfile.runctx('run(parser, lines)', globals(), locals(), sort=args['-s'])
if __name__ == '__main__':
args = docopt(__doc__)
main(args)
+1 -1
View File
@@ -13,7 +13,7 @@ Note: This requires the psutil library, available on PyPI.
import time import time
import sys import sys
import os import os
import psutil import psutil # type: ignore[import-untyped]
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/..')) sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/..'))
import jedi import jedi
+1 -1
View File
@@ -56,7 +56,7 @@ def main(args):
run(code, i, infer=infer) run(code, i, infer=infer)
if args['--precision']: if args['--precision']:
pstats.f8 = f8 pstats.f8 = f8 # type: ignore[attr-defined] # TODO this does not seem to exist?!
jedi.set_debug_function(notices=args['--debug']) jedi.set_debug_function(notices=args['--debug'])
if args['--omit']: if args['--omit']:
+2 -2
View File
@@ -17,11 +17,11 @@ import sys
try: try:
import urllib.request as urllib2 import urllib.request as urllib2
except ImportError: except ImportError:
import urllib2 import urllib2 # type: ignore[import-not-found, no-redef]
import gc import gc
from os.path import abspath, dirname from os.path import abspath, dirname
import objgraph import objgraph # type: ignore[import-untyped]
sys.path.insert(0, dirname(dirname(abspath(__file__)))) sys.path.insert(0, dirname(dirname(abspath(__file__))))
import jedi import jedi
+32 -35
View File
@@ -35,11 +35,11 @@ setup(name='jedi',
keywords='python completion refactoring vim', keywords='python completion refactoring vim',
long_description=readme, long_description=readme,
packages=find_packages(exclude=['test', 'test.*']), packages=find_packages(exclude=['test', 'test.*']),
python_requires='>=3.8', python_requires='>=3.10',
# Python 3.13 grammars are added to parso in 0.8.4 # Python 3.13 grammars are added to parso in 0.8.4
install_requires=['parso>=0.8.5,<0.9.0'], install_requires=['parso>=0.8.6,<0.9.0'],
extras_require={ extras_require={
'testing': [ 'dev': [
'pytest<9.0.0', 'pytest<9.0.0',
# docopt for sith doctests # docopt for sith doctests
'docopt', 'docopt',
@@ -48,42 +48,41 @@ setup(name='jedi',
'Django', 'Django',
'attrs', 'attrs',
'typing_extensions', 'typing_extensions',
],
'qa': [
# latest version on 2025-06-16 # latest version on 2025-06-16
'flake8==7.2.0', 'flake8==7.1.2',
# latest version supporting Python 3.6 'zuban==0.7.0',
'mypy==1.16',
# Arbitrary pins, latest at the time of pinning # Arbitrary pins, latest at the time of pinning
'types-setuptools==80.9.0.20250529', 'types-setuptools==80.9.0.20250529',
], ],
'docs': [ 'docs': [
# Just pin all of these. # Just pin all of these.
'Jinja2==2.11.3', 'alabaster==1.0.0',
'MarkupSafe==1.1.1', 'babel==2.18.0',
'Pygments==2.8.1', 'certifi==2026.4.22',
'alabaster==0.7.12', 'charset-normalizer==3.4.7',
'babel==2.9.1', 'docutils==0.22.4',
'chardet==4.0.0', 'idna==3.13',
'commonmark==0.8.1', 'imagesize==2.0.0',
'docutils==0.17.1', 'iniconfig==2.3.0',
'future==0.18.2', 'Jinja2==3.1.6',
'idna==2.10', 'MarkupSafe==3.0.3',
'imagesize==1.2.0', 'packaging==26.2',
'mock==1.0.1', 'pluggy==1.6.0',
'packaging==20.9', 'Pygments==2.20.0',
'pyparsing==2.4.7', 'pytest==9.0.3',
'pytz==2021.1', 'requests==2.33.1',
'readthedocs-sphinx-ext==2.1.4', 'roman-numerals==4.1.0',
'recommonmark==0.5.0', 'snowballstemmer==3.0.1',
'requests==2.25.1', 'Sphinx==9.1.0',
'six==1.15.0', 'sphinx_rtd_theme==3.1.0',
'snowballstemmer==2.1.0', 'sphinxcontrib-applehelp==2.0.0',
'sphinx==1.8.5', 'sphinxcontrib-devhelp==2.0.0',
'sphinx-rtd-theme==0.4.3', 'sphinxcontrib-htmlhelp==2.1.0',
'sphinxcontrib-serializinghtml==1.1.4', 'sphinxcontrib-jquery==4.1',
'sphinxcontrib-websupport==1.2.4', 'sphinxcontrib-jsmath==1.0.1',
'urllib3==1.26.4', 'sphinxcontrib-qthelp==2.0.0',
'sphinxcontrib-serializinghtml==2.0.0',
'urllib3==2.6.3',
], ],
}, },
package_data={'jedi': ['*.pyi', 'third_party/typeshed/LICENSE', package_data={'jedi': ['*.pyi', 'third_party/typeshed/LICENSE',
@@ -96,8 +95,6 @@ setup(name='jedi',
'License :: OSI Approved :: MIT License', 'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent', 'Operating System :: OS Independent',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.12',
-2
View File
@@ -35,7 +35,6 @@ Usage:
Options: Options:
-h --help Show this screen. -h --help Show this screen.
--record=<file> Exceptions are recorded in here [default: record.json]. --record=<file> Exceptions are recorded in here [default: record.json].
-f, --fs-cache By default, file system cache is off for reproducibility.
-n, --maxtries=<nr> Maximum of random tries [default: 100] -n, --maxtries=<nr> Maximum of random tries [default: 100]
-d, --debug Jedi print debugging when an error is raised. -d, --debug Jedi print debugging when an error is raised.
-s Shows the path/line numbers of every completion before it starts. -s Shows the path/line numbers of every completion before it starts.
@@ -187,7 +186,6 @@ def main(arguments):
'pudb' if arguments['--pudb'] else None 'pudb' if arguments['--pudb'] else None
record = arguments['--record'] record = arguments['--record']
jedi.settings.use_filesystem_cache = arguments['--fs-cache']
if arguments['--debug']: if arguments['--debug']:
jedi.set_debug_function() jedi.set_debug_function()
-1
View File
@@ -1,7 +1,6 @@
# For assignment expressions / named expressions / walrus operators / whatever # For assignment expressions / named expressions / walrus operators / whatever
# they are called. # they are called.
# python >= 3.8
b = (a:=1, a) b = (a:=1, a)
#? int() #? int()
-2
View File
@@ -476,8 +476,6 @@ dynamic_annotation('')
# TypeDict # TypeDict
# ------------------------- # -------------------------
# python >= 3.8
class Foo(typing.TypedDict): class Foo(typing.TypedDict):
foo: str foo: str
bar: typing.List[float] bar: typing.List[float]
-2
View File
@@ -1,5 +1,3 @@
# python >= 3.9
from typing import Annotated from typing import Annotated
# This is just a dummy and very meaningless thing to use with to the Annotated # This is just a dummy and very meaningless thing to use with to the Annotated
@@ -1,5 +1,3 @@
# python >= 3.8
def positional_only_call(a, /, b): def positional_only_call(a, /, b):
#? str() #? str()
a a
-2
View File
@@ -459,8 +459,6 @@ X().just_partial('')[0]
#? str() #? str()
X().just_partial('')[1] X().just_partial('')[1]
# python >= 3.8
@functools.lru_cache @functools.lru_cache
def x() -> int: ... def x() -> int: ...
@functools.lru_cache() @functools.lru_cache()
+1 -1
View File
@@ -14,7 +14,7 @@ from jedi.api.interpreter import MixedModuleContext
# For interpreter tests sometimes the path of this directory is in the sys # For interpreter tests sometimes the path of this directory is in the sys
# path, which we definitely don't want. So just remove it globally. # path, which we definitely don't want. So just remove it globally.
try: try:
sys.path.remove(helpers.test_dir) sys.path.remove(str(helpers.test_dir))
except ValueError: except ValueError:
pass pass
+1 -1
View File
@@ -2,7 +2,7 @@
import sys import sys
sys.path[0:0] = [ sys.path[0:0] = [
'/usr/lib/python3.8/site-packages', '/usr/lib/python3.14/site-packages',
'/tmp/.buildout/eggs/important_package.egg' '/tmp/.buildout/eggs/important_package.egg'
] ]
+3 -1
View File
@@ -168,6 +168,8 @@ class BaseTestCase(object):
class IntegrationTestCase(BaseTestCase): class IntegrationTestCase(BaseTestCase):
source: str # Defined as a side effect
def __init__(self, test_type, correct, line_nr, column, start, line, def __init__(self, test_type, correct, line_nr, column, start, line,
path=None, skip_version_info=None): path=None, skip_version_info=None):
super().__init__(skip_version_info) super().__init__(skip_version_info)
@@ -446,7 +448,7 @@ Options:
--pdb Enable pdb debugging on fail. --pdb Enable pdb debugging on fail.
-d, --debug Enable text output debugging (please install ``colorama``). -d, --debug Enable text output debugging (please install ``colorama``).
--thirdparty Also run thirdparty tests (in ``completion/thirdparty``). --thirdparty Also run thirdparty tests (in ``completion/thirdparty``).
--env <dotted> A Python version, like 3.9, 3.8, etc. --env <dotted> A Python version, like 3.14, 3.13, etc.
""" """
if __name__ == '__main__': if __name__ == '__main__':
import docopt import docopt
+1 -1
View File
@@ -523,7 +523,7 @@ def test_added_equals_to_params(Script):
def test_builtin_module_with_path(Script): def test_builtin_module_with_path(Script):
""" """
This test simply tests if a module from /usr/lib/python3.8/lib-dynload/ has This test simply tests if a module from /usr/lib/python3.14/lib-dynload/ has
a path or not. It shouldn't have a module_path, because that is just a path or not. It shouldn't have a module_path, because that is just
confusing. confusing.
""" """
+2 -1
View File
@@ -3,6 +3,7 @@ import os
from textwrap import dedent from textwrap import dedent
from itertools import count from itertools import count
from pathlib import Path from pathlib import Path
from typing import Any
import pytest import pytest
@@ -301,7 +302,7 @@ def test_file_path_should_have_completions(Script):
assert Script('r"').complete() # See GH #1503 assert Script('r"').complete() # See GH #1503
_dict_keys_completion_tests = [ _dict_keys_completion_tests: "list[tuple[str, int | None, list[str | Any]]]" = [
('ints[', 5, ['1', '50', Ellipsis]), ('ints[', 5, ['1', '50', Ellipsis]),
('ints[]', 5, ['1', '50', Ellipsis]), ('ints[]', 5, ['1', '50', Ellipsis]),
('ints[1]', 5, ['1', '50', Ellipsis]), ('ints[1]', 5, ['1', '50', Ellipsis]),
+2 -2
View File
@@ -26,13 +26,13 @@ def test_find_system_environments():
@pytest.mark.parametrize( @pytest.mark.parametrize(
'version', 'version',
['3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] ['3.10', '3.11', '3.12', '3.13']
) )
def test_versions(version): def test_versions(version):
try: try:
env = get_system_environment(version) env = get_system_environment(version)
except InvalidPythonEnvironment: except InvalidPythonEnvironment:
if int(version.replace('.', '')) == str(sys.version_info[0]) + str(sys.version_info[1]): if version.replace('.', '') == str(sys.version_info[0]) + str(sys.version_info[1]):
# At least the current version has to work # At least the current version has to work
raise raise
pytest.skip() pytest.skip()
+3
View File
@@ -15,6 +15,7 @@ There are three kinds of test:
import textwrap import textwrap
from unittest import TestCase from unittest import TestCase
from typing import Any
import pytest import pytest
@@ -22,6 +23,8 @@ import jedi
class MixinTestFullName(object): class MixinTestFullName(object):
assertEqual: Any
operation = None operation = None
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
+13 -13
View File
@@ -78,7 +78,7 @@ def test_numpy_like_non_zero():
def test_nested_resolve(): def test_nested_resolve():
class XX: class XX:
def x(): def x(): # type: ignore[misc]
pass pass
cls = get_completion('XX', locals()) cls = get_completion('XX', locals())
@@ -92,7 +92,7 @@ def test_side_effect_completion():
Python code, however we want references to Python code as well. Therefore Python code, however we want references to Python code as well. Therefore
we need some mixed kind of magic for tests. we need some mixed kind of magic for tests.
""" """
_GlobalNameSpace.SideEffectContainer.foo = 1 _GlobalNameSpace.SideEffectContainer.foo = 1 # type: ignore[attr-defined]
side_effect = get_completion('SideEffectContainer', _GlobalNameSpace.__dict__) side_effect = get_completion('SideEffectContainer', _GlobalNameSpace.__dict__)
# It's a class that contains MixedObject. # It's a class that contains MixedObject.
@@ -166,7 +166,7 @@ def test_getitem_side_effects():
# Possible side effects here, should therefore not call this. # Possible side effects here, should therefore not call this.
if True: if True:
raise NotImplementedError() raise NotImplementedError()
return index return index # type: ignore[unreachable]
foo = Foo2() foo = Foo2()
_assert_interpreter_complete('foo["asdf"].upper', locals(), ['upper']) _assert_interpreter_complete('foo["asdf"].upper', locals(), ['upper'])
@@ -198,7 +198,7 @@ def test__getattr__completions(allow_unsafe_getattr, class_is_findable):
raise AttributeError(name) raise AttributeError(name)
def __dir__(self): def __dir__(self):
return ['foo', 'fbar'] + object.__dir__(self) return ['foo', 'fbar'] + object.__dir__(self) # type: ignore[operator]
if not class_is_findable: if not class_is_findable:
CompleteGetattr.__name__ = "something_somewhere" CompleteGetattr.__name__ = "something_somewhere"
@@ -388,7 +388,7 @@ def test_dir_magic_method(allow_unsafe_getattr):
raise AttributeError(name) raise AttributeError(name)
def __dir__(self): def __dir__(self):
return ['foo', 'bar'] + object.__dir__(self) return ['foo', 'bar'] + object.__dir__(self) # type: ignore[operator]
itp = jedi.Interpreter("ca.", [{'ca': CompleteAttrs()}]) itp = jedi.Interpreter("ca.", [{'ca': CompleteAttrs()}])
completions = itp.complete() completions = itp.complete()
@@ -410,7 +410,7 @@ def test_dir_magic_method(allow_unsafe_getattr):
def test_name_not_findable(): def test_name_not_findable():
class X(): class X():
if 0: if 0:
NOT_FINDABLE # noqa: F821 NOT_FINDABLE # type: ignore[unreachable] # noqa: F821
def hidden(self): def hidden(self):
return return
@@ -493,7 +493,7 @@ def test__wrapped__():
def test_illegal_class_instance(): def test_illegal_class_instance():
class X: class X:
__class__ = 1 __class__ = 1 # type: ignore[assignment]
X.__name__ = 'asdf' X.__name__ = 'asdf'
d, = jedi.Interpreter('foo', [{'foo': X()}]).infer() d, = jedi.Interpreter('foo', [{'foo': X()}]).infer()
v, = d._name.infer() v, = d._name.infer()
@@ -537,7 +537,7 @@ def test_partial_signatures(code, expected, index):
def test_type_var(): def test_type_var():
"""This was an issue before, see Github #1369""" """This was an issue before, see Github #1369"""
x = typing.TypeVar('myvar') x = typing.TypeVar('myvar') # type: ignore[misc]
def_, = jedi.Interpreter('x', [locals()]).infer() def_, = jedi.Interpreter('x', [locals()]).infer()
assert def_.name == 'TypeVar' assert def_.name == 'TypeVar'
@@ -576,7 +576,7 @@ def test_dict_completion(code, column, expected):
strs = {'asdf': 1, """foo""": 2, r'fbar': 3} strs = {'asdf': 1, """foo""": 2, r'fbar': 3}
mixed = {1: 2, 1.10: 4, None: 6, r'a\sdf': 8, b'foo': 9} mixed = {1: 2, 1.10: 4, None: 6, r'a\sdf': 8, b'foo': 9}
class Inherited(dict): class Inherited(dict): # type: ignore[type-arg]
pass pass
inherited = Inherited(blablu=3) inherited = Inherited(blablu=3)
@@ -624,10 +624,10 @@ def test_dunders(class_is_findable, code, expected, allow_unsafe_getattr):
def __getitem__(self, key) -> int: def __getitem__(self, key) -> int:
return 1 return 1
def __iter__(self, key) -> Iterator[str]: def __iter__(self, key) -> Iterator[str]: # type: ignore[empty-body]
pass pass
def __next__(self, key) -> float: def __next__(self, key) -> float: # type: ignore[empty-body]
pass pass
if not class_is_findable: if not class_is_findable:
@@ -810,11 +810,11 @@ def test_try_to_use_return_annotation_for_property(class_is_findable):
raise BaseException raise BaseException
@property @property
def with_annotation_garbage1(self) -> 'asldjflksjdfljdslkjfsl': # noqa def with_annotation_garbage1(self) -> 'asldjflksjdfljdslkjfsl': # type: ignore[name-defined] # noqa
return Hello() return Hello()
@property @property
def with_annotation_garbage2(self) -> 'sdf$@@$5*+8': # noqa def with_annotation_garbage2(self) -> 'sdf & 8': # type: ignore[valid-type] # noqa
return Hello() return Hello()
@property @property
-1
View File
@@ -54,7 +54,6 @@ def test_rename_mod(Script, dir_with_content):
''').format(dir=dir_with_content) ''').format(dir=dir_with_content)
@pytest.mark.skipif('sys.version_info[:2] < (3, 8)', message="Python 3.8 introduces dirs_exist_ok")
def test_namespace_package(Script, tmpdir): def test_namespace_package(Script, tmpdir):
origin = get_example_dir('implicit_namespace_package') origin = get_example_dir('implicit_namespace_package')
shutil.copytree(origin, tmpdir.strpath, dirs_exist_ok=True) shutil.copytree(origin, tmpdir.strpath, dirs_exist_ok=True)
+1 -1
View File
@@ -13,7 +13,7 @@ class SomeClass:
def twice(self, b): def twice(self, b):
pass pass
def some_function(): def some_function(self):
pass pass
@@ -67,7 +67,7 @@ def test_path_from_sys_path_assignment(Script):
import sys import sys
sys.path[0:0] = [ sys.path[0:0] = [
{os.path.abspath('/usr/lib/python3.8/site-packages')!r}, {os.path.abspath('/usr/lib/python3.14/site-packages')!r},
{os.path.abspath('/home/test/.buildout/eggs/important_package.egg')!r}, {os.path.abspath('/home/test/.buildout/eggs/important_package.egg')!r},
] ]
+2 -2
View File
@@ -11,14 +11,14 @@ import jedi
from ..helpers import test_dir from ..helpers import test_dir
try: try:
import numpydoc # NOQA import numpydoc # type: ignore[import-not-found] # NOQA
except ImportError: except ImportError:
numpydoc_unavailable = True numpydoc_unavailable = True
else: else:
numpydoc_unavailable = False numpydoc_unavailable = False
try: try:
import numpy # NOQA import numpy # type: ignore[import-not-found] # NOQA
except ImportError: except ImportError:
numpy_unavailable = True numpy_unavailable = True
else: else:
+5 -2
View File
@@ -31,7 +31,9 @@ def test_get_signatures_stdlib(Script):
assert len(sigs[0].params) == 1 assert len(sigs[0].params) == 1
# Check only on linux 64 bit platform and Python3.8. # TODO This is currently only checked on linux 64 bit platform and Python3.8,
# which we don't support anymore, this test should be rewritten (or the
# extension recreated).
@pytest.mark.parametrize('load_unsafe_extensions', [False, True]) @pytest.mark.parametrize('load_unsafe_extensions', [False, True])
@pytest.mark.skipif( @pytest.mark.skipif(
'sys.platform != "linux" or sys.maxsize <= 2**32 or sys.version_info[:2] != (3, 8)', 'sys.platform != "linux" or sys.maxsize <= 2**32 or sys.version_info[:2] != (3, 8)',
@@ -48,7 +50,8 @@ def test_init_extension_module(Script, load_unsafe_extensions):
`__init__.cpython-38m.so` by compiling it (create a virtualenv and run `__init__.cpython-38m.so` by compiling it (create a virtualenv and run
`setup.py install`. `setup.py install`.
This is also why this test only runs on certain systems and Python 3.8. This is also why this test only runs on certain systems and a specific
Python version.
""" """
project = jedi.Project(get_example_dir(), load_unsafe_extensions=load_unsafe_extensions) project = jedi.Project(get_example_dir(), load_unsafe_extensions=load_unsafe_extensions)
@@ -222,7 +222,7 @@ def test_goto_stubs_on_itself(Script, code, type_):
def test_module_exists_only_as_stub(Script): def test_module_exists_only_as_stub(Script):
try: try:
import redis # noqa: F401 import redis # type: ignore[import-untyped] # noqa: F401
except ImportError: except ImportError:
pass pass
else: else:
+2 -2
View File
@@ -30,13 +30,13 @@ def test_find_module_basic():
def test_find_module_package(): def test_find_module_package():
file_io, is_package = _find_module('json') file_io, is_package = _find_module('json')
assert file_io.path.parts[-2:] == ('json', '__init__.py') assert file_io.path.parts[-2:] == ('json', '__init__.py') # type: ignore[union-attr]
assert is_package is True assert is_package is True
def test_find_module_not_package(): def test_find_module_not_package():
file_io, is_package = _find_module('io') file_io, is_package = _find_module('io')
assert file_io.path.name == 'io.py' assert file_io.path.name == 'io.py' # type: ignore[union-attr]
assert is_package is False assert is_package is False
+4 -4
View File
@@ -56,10 +56,10 @@ def test_generics_methods(code, expected, class_findable):
class Reader(Generic[T]): class Reader(Generic[T]):
@classmethod @classmethod
def read(cls) -> T: def read(cls) -> T:
return cls() return cls() # type: ignore[return-value]
def method(self) -> T: def method(self) -> T:
return 1 return 1 # type: ignore[return-value]
class Foo(Reader[str]): class Foo(Reader[str]):
def transform(self) -> int: def transform(self) -> int:
@@ -94,7 +94,7 @@ def test_signature():
pass pass
from inspect import Signature, Parameter from inspect import Signature, Parameter
some_signature.__signature__ = Signature([ some_signature.__signature__ = Signature([ # type: ignore[attr-defined]
Parameter('bar', kind=Parameter.KEYWORD_ONLY, default=1) Parameter('bar', kind=Parameter.KEYWORD_ONLY, default=1)
]) ])
@@ -105,7 +105,7 @@ def test_signature():
def test_compiled_signature_annotation_string(): def test_compiled_signature_annotation_string():
import typing import typing
def func(x: typing.Type, y: typing.Union[typing.Type, int]): def func(x: typing.Type, y: typing.Union[typing.Type, int]): # type: ignore[type-arg]
pass pass
func.__name__ = 'not_func' func.__name__ = 'not_func'
+5 -4
View File
@@ -2,6 +2,7 @@ from textwrap import dedent
from operator import eq, ge, lt from operator import eq, ge, lt
import re import re
import os import os
from typing import Any
import pytest import pytest
@@ -405,7 +406,7 @@ def test_wraps_signature(Script, code, signature):
], ],
) )
def test_dataclass_signature( def test_dataclass_signature(
Script, skip_pre_python37, start, start_params, include_params, environment Script, start, start_params, include_params, environment
): ):
if environment.version_info < (3, 8): if environment.version_info < (3, 8):
# Final is not yet supported # Final is not yet supported
@@ -448,7 +449,7 @@ def test_dataclass_signature(
assert price.name == price_type_infer assert price.name == price_type_infer
dataclass_transform_cases = [ dataclass_transform_cases: list[Any] = [
# Attributes on the decorated class and its base classes # Attributes on the decorated class and its base classes
# are not considered to be fields. # are not considered to be fields.
# 1/ Declare dataclass transformer # 1/ Declare dataclass transformer
@@ -724,7 +725,7 @@ ids = [
'start, start_params, include_params', dataclass_transform_cases, ids=ids 'start, start_params, include_params', dataclass_transform_cases, ids=ids
) )
def test_extensions_dataclass_transform_signature( def test_extensions_dataclass_transform_signature(
Script, skip_pre_python37, start, start_params, include_params, environment Script, start, start_params, include_params, environment
): ):
has_typing_ext = bool(Script('import typing_extensions').infer()) has_typing_ext = bool(Script('import typing_extensions').infer())
if not has_typing_ext: if not has_typing_ext:
@@ -845,7 +846,7 @@ def test_dataclass_transform_signature(
], ],
ids=["define", "frozen", "define_customized", "define_subclass", "define_both"] ids=["define", "frozen", "define_customized", "define_subclass", "define_both"]
) )
def test_attrs_signature(Script, skip_pre_python37, start, start_params): def test_attrs_signature(Script, start, start_params):
has_attrs = bool(Script('import attrs').infer()) has_attrs = bool(Script('import attrs').infer())
if not has_attrs: if not has_attrs:
raise pytest.skip("attrs needed in target environment to run this test") raise pytest.skip("attrs needed in target environment to run this test")
@@ -67,7 +67,7 @@ def test_hex_values_in_docstring():
('lambda x, y, z: x + y * z\n', '<lambda>(x, y, z)') ('lambda x, y, z: x + y * z\n', '<lambda>(x, y, z)')
]) ])
def test_get_signature(code, signature): def test_get_signature(code, signature):
node = parse(code, version='3.8').children[0] node = parse(code, version='3.14').children[0]
if node.type == 'simple_stmt': if node.type == 'simple_stmt':
node = node.children[0] node = node.children[0]
assert parser_utils.get_signature(node) == signature assert parser_utils.get_signature(node) == signature
+8 -6
View File
@@ -1,7 +1,9 @@
from typing import Any
try: try:
import readline import readline
except ImportError: except ImportError:
readline = False readline = False # type: ignore[assignment]
import unittest import unittest
from jedi import utils from jedi import utils
@@ -15,7 +17,7 @@ class TestSetupReadline(unittest.TestCase):
def setUp(self, *args, **kwargs): def setUp(self, *args, **kwargs):
super().setUp(*args, **kwargs) super().setUp(*args, **kwargs)
self.namespace = self.NameSpace() self.namespace: Any = self.NameSpace()
utils.setup_readline(self.namespace) utils.setup_readline(self.namespace)
def complete(self, text): def complete(self, text):
@@ -47,8 +49,8 @@ class TestSetupReadline(unittest.TestCase):
def test_modules(self): def test_modules(self):
import sys import sys
import os import os
self.namespace.sys = sys self.namespace.sys = sys # type: ignore[attr-defined]
self.namespace.os = os self.namespace.os = os # type: ignore[attr-defined]
try: try:
assert self.complete('os.path.join') == ['os.path.join'] assert self.complete('os.path.join') == ['os.path.join']
@@ -58,8 +60,8 @@ class TestSetupReadline(unittest.TestCase):
c = {'os.' + d for d in dir(os) if d.startswith('ch')} c = {'os.' + d for d in dir(os) if d.startswith('ch')}
assert set(self.complete('os.ch')) == set(c) assert set(self.complete('os.ch')) == set(c)
finally: finally:
del self.namespace.sys del self.namespace.sys # type: ignore[attr-defined]
del self.namespace.os del self.namespace.os # type: ignore[attr-defined]
def test_calls(self): def test_calls(self):
s = 'str(bytes' s = 'str(bytes'