mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-06 14:04:26 +08:00
Compare commits
5 Commits
master
...
4ce52c8349
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ce52c8349 | ||
|
|
c078c42a94 | ||
|
|
1ce0d00fb6 | ||
|
|
61848afc7c | ||
|
|
a4ec9e0b2b |
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@@ -6,9 +6,9 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-24.04, windows-2022]
|
||||
python-version: ["3.13", "3.12", "3.11", "3.10", "3.9", "3.8"]
|
||||
environment: ['3.8', '3.13', '3.12', '3.11', '3.10', '3.9', 'interpreter']
|
||||
os: [ubuntu-20.04, windows-2019]
|
||||
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
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
JEDI_TEST_ENVIRONMENT: ${{ matrix.environment }}
|
||||
|
||||
code-quality:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
python -m mypy jedi sith.py setup.py
|
||||
|
||||
coverage:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
|
||||
@@ -10,9 +10,6 @@ python:
|
||||
submodules:
|
||||
include: all
|
||||
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
Jedi - an awesome autocompletion, static analysis and refactoring library for Python
|
||||
####################################################################################
|
||||
|
||||
**I released the successor to Jedi: A
|
||||
Mypy-Compatible Python Language Server Built in Rust** - `ZubanLS <https://zubanls.com>`_
|
||||
|
||||
.. image:: http://isitmaintained.com/badge/open/davidhalter/jedi.svg
|
||||
:target: https://github.com/davidhalter/jedi/issues
|
||||
:alt: The percentage of open issues and pull requests
|
||||
@@ -13,7 +10,7 @@ Mypy-Compatible Python Language Server Built in Rust** - `ZubanLS <https://zuban
|
||||
:target: https://github.com/davidhalter/jedi/issues
|
||||
:alt: The resolution time is the median time an issue or pull request stays open.
|
||||
|
||||
.. image:: https://github.com/davidhalter/jedi/actions/workflows/ci.yml/badge.svg?branch=master
|
||||
.. image:: https://github.com/davidhalter/jedi/workflows/ci/badge.svg?branch=master
|
||||
:target: https://github.com/davidhalter/jedi/actions
|
||||
:alt: Tests
|
||||
|
||||
@@ -102,7 +99,7 @@ Features and Limitations
|
||||
Jedi's features are listed here:
|
||||
`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.6+ but it should also
|
||||
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>`_
|
||||
very well.
|
||||
|
||||
@@ -156,14 +156,6 @@ def jedi_path():
|
||||
return os.path.dirname(__file__)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def skip_pre_python311(environment):
|
||||
if environment.version_info < (3, 11):
|
||||
# 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_python38(environment):
|
||||
if environment.version_info < (3, 8):
|
||||
|
||||
@@ -16,7 +16,7 @@ Jedi's main API calls and features are:
|
||||
Basic Features
|
||||
--------------
|
||||
|
||||
- Python 3.8+ support
|
||||
- Python 3.6+ support
|
||||
- Ignores syntax errors and wrong indentation
|
||||
- Can deal with complex module / function / class structures
|
||||
- Great ``virtualenv``/``venv`` support
|
||||
|
||||
@@ -38,7 +38,7 @@ using pip::
|
||||
|
||||
If you want to install the current development version (master branch)::
|
||||
|
||||
sudo pip install -e git+https://github.com/davidhalter/jedi.git#egg=jedi
|
||||
sudo pip install -e git://github.com/davidhalter/jedi.git#egg=jedi
|
||||
|
||||
|
||||
System-wide installation via a package manager
|
||||
|
||||
@@ -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', '3.9', '3.8']
|
||||
_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)
|
||||
|
||||
@@ -28,7 +28,7 @@ def clear_time_caches(delete_all: bool = False) -> None:
|
||||
:param delete_all: Deletes also the cache that is normally not deleted,
|
||||
like parser cache, which is important for faster parsing.
|
||||
"""
|
||||
global _time_caches # noqa: F824
|
||||
global _time_caches
|
||||
|
||||
if delete_all:
|
||||
for cache in _time_caches.values():
|
||||
|
||||
@@ -21,7 +21,7 @@ try:
|
||||
raise ImportError
|
||||
else:
|
||||
# Use colorama for nicer console output.
|
||||
from colorama import Fore, init # type: ignore[import, unused-ignore]
|
||||
from colorama import Fore, init # type: ignore[import]
|
||||
from colorama import initialise
|
||||
|
||||
def _lazy_colorama_init(): # noqa: F811
|
||||
|
||||
@@ -122,14 +122,14 @@ class InferenceState:
|
||||
return value_set
|
||||
|
||||
# mypy doesn't suppport decorated propeties (https://github.com/python/mypy/issues/1362)
|
||||
@property
|
||||
@property # type: ignore[misc]
|
||||
@inference_state_function_cache()
|
||||
def builtins_module(self):
|
||||
module_name = 'builtins'
|
||||
builtins_module, = self.import_module((module_name,), sys_path=[])
|
||||
return builtins_module
|
||||
|
||||
@property
|
||||
@property # type: ignore[misc]
|
||||
@inference_state_function_cache()
|
||||
def typing_module(self):
|
||||
typing_module, = self.import_module(('typing',))
|
||||
|
||||
@@ -3,6 +3,10 @@ import sys
|
||||
from importlib.abc import MetaPathFinder
|
||||
from importlib.machinery import PathFinder
|
||||
|
||||
# Remove the first entry, because it's simply a directory entry that equals
|
||||
# this directory.
|
||||
del sys.path[0]
|
||||
|
||||
|
||||
def _get_paths():
|
||||
# Get the path to jedi.
|
||||
|
||||
@@ -48,7 +48,7 @@ def _get_numpy_doc_string_cls():
|
||||
global _numpy_doc_string_cache
|
||||
if isinstance(_numpy_doc_string_cache, (ImportError, SyntaxError)):
|
||||
raise _numpy_doc_string_cache
|
||||
from numpydoc.docscrape import NumpyDocString # type: ignore[import, unused-ignore]
|
||||
from numpydoc.docscrape import NumpyDocString # type: ignore[import]
|
||||
_numpy_doc_string_cache = NumpyDocString
|
||||
return _numpy_doc_string_cache
|
||||
|
||||
@@ -109,7 +109,7 @@ def _expand_typestr(type_str):
|
||||
yield type_str.split('of')[0]
|
||||
# Check if type has is a set of valid literal values eg: {'C', 'F', 'A'}
|
||||
elif type_str.startswith('{'):
|
||||
node = parse(type_str, version='3.13').children[0]
|
||||
node = parse(type_str, version='3.7').children[0]
|
||||
if node.type == 'atom':
|
||||
for leaf in getattr(node.children[1], "children", []):
|
||||
if leaf.type == 'number':
|
||||
|
||||
@@ -480,7 +480,7 @@ def _load_builtin_module(inference_state, import_names=None, sys_path=None):
|
||||
if sys_path is None:
|
||||
sys_path = inference_state.get_sys_path()
|
||||
if not project._load_unsafe_extensions:
|
||||
safe_paths = set(project._get_base_sys_path(inference_state))
|
||||
safe_paths = project._get_base_sys_path(inference_state)
|
||||
sys_path = [p for p in sys_path if p in safe_paths]
|
||||
|
||||
dotted_name = '.'.join(import_names)
|
||||
|
||||
@@ -251,8 +251,6 @@ def _infer_node(context, element):
|
||||
return NO_VALUES
|
||||
elif typ == 'namedexpr_test':
|
||||
return context.infer_node(element.children[2])
|
||||
elif typ == 'star_expr':
|
||||
return NO_VALUES
|
||||
else:
|
||||
return infer_or_test(context, element)
|
||||
|
||||
|
||||
@@ -36,10 +36,6 @@ py__doc__() Returns the docstring for a value.
|
||||
====================================== ========================================
|
||||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from jedi import debug
|
||||
from jedi.parser_utils import get_cached_parent_scope, expr_is_dotted, \
|
||||
function_is_property
|
||||
@@ -51,15 +47,11 @@ from jedi.inference.filters import ParserTreeFilter
|
||||
from jedi.inference.names import TreeNameDefinition, ValueName
|
||||
from jedi.inference.arguments import unpack_arglist, ValuesArguments
|
||||
from jedi.inference.base_value import ValueSet, iterator_to_value_set, \
|
||||
NO_VALUES, ValueWrapper
|
||||
NO_VALUES
|
||||
from jedi.inference.context import ClassContext
|
||||
from jedi.inference.value.function import FunctionAndClassBase, FunctionMixin
|
||||
from jedi.inference.value.decorator import Decoratee
|
||||
from jedi.inference.value.function import FunctionAndClassBase
|
||||
from jedi.inference.gradual.generics import LazyGenericManager, TupleGenericManager
|
||||
from jedi.plugins import plugin_manager
|
||||
from inspect import Parameter
|
||||
from jedi.inference.names import BaseTreeParamName
|
||||
from jedi.inference.signature import AbstractSignature
|
||||
|
||||
|
||||
class ClassName(TreeNameDefinition):
|
||||
@@ -137,65 +129,6 @@ class ClassFilter(ParserTreeFilter):
|
||||
return [name for name in names if self._access_possible(name)]
|
||||
|
||||
|
||||
def init_param_value(arg_nodes) -> Optional[bool]:
|
||||
"""
|
||||
Returns:
|
||||
|
||||
- ``True`` if ``@dataclass(init=True)``
|
||||
- ``False`` if ``@dataclass(init=False)``
|
||||
- ``None`` if not specified ``@dataclass()``
|
||||
"""
|
||||
for arg_node in arg_nodes:
|
||||
if (
|
||||
arg_node.type == "argument"
|
||||
and arg_node.children[0].value == "init"
|
||||
):
|
||||
if arg_node.children[2].value == "False":
|
||||
return False
|
||||
elif arg_node.children[2].value == "True":
|
||||
return True
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_dataclass_param_names(cls) -> List[DataclassParamName]:
|
||||
"""
|
||||
``cls`` is a :class:`ClassMixin`. The type is only documented as mypy would
|
||||
complain that some fields are missing.
|
||||
|
||||
.. code:: python
|
||||
|
||||
@dataclass
|
||||
class A:
|
||||
a: int
|
||||
b: str = "toto"
|
||||
|
||||
For the previous example, the param names would be ``a`` and ``b``.
|
||||
"""
|
||||
param_names = []
|
||||
filter_ = cls.as_context().get_global_filter()
|
||||
for name in sorted(filter_.values(), key=lambda name: name.start_pos):
|
||||
d = name.tree_name.get_definition()
|
||||
annassign = d.children[1]
|
||||
if d.type == 'expr_stmt' and annassign.type == 'annassign':
|
||||
node = annassign.children[1]
|
||||
if node.type == "atom_expr" and node.children[0].value == "ClassVar":
|
||||
continue
|
||||
|
||||
if len(annassign.children) < 4:
|
||||
default = None
|
||||
else:
|
||||
default = annassign.children[3]
|
||||
|
||||
param_names.append(DataclassParamName(
|
||||
parent_context=cls.parent_context,
|
||||
tree_name=name.tree_name,
|
||||
annotation_node=annassign.children[1],
|
||||
default_node=default,
|
||||
))
|
||||
return param_names
|
||||
|
||||
|
||||
class ClassMixin:
|
||||
def is_class(self):
|
||||
return True
|
||||
@@ -288,73 +221,6 @@ class ClassMixin:
|
||||
assert x is not None
|
||||
yield x
|
||||
|
||||
def _has_dataclass_transform_metaclasses(self) -> Tuple[bool, Optional[bool]]:
|
||||
for meta in self.get_metaclasses(): # type: ignore[attr-defined]
|
||||
if (
|
||||
isinstance(meta, Decoratee)
|
||||
# Internal leakage :|
|
||||
and isinstance(meta._wrapped_value, DataclassTransformer)
|
||||
):
|
||||
return True, meta._wrapped_value.init_mode_from_new()
|
||||
|
||||
return False, None
|
||||
|
||||
def _get_dataclass_transform_signatures(self) -> List[DataclassSignature]:
|
||||
"""
|
||||
Returns: A non-empty list if the class has dataclass semantics else an
|
||||
empty list.
|
||||
|
||||
The dataclass-like semantics will be assumed for any class that directly
|
||||
or indirectly derives from the decorated class or uses the decorated
|
||||
class as a metaclass.
|
||||
"""
|
||||
param_names = []
|
||||
is_dataclass_transform = False
|
||||
default_init_mode: Optional[bool] = None
|
||||
for cls in reversed(list(self.py__mro__())):
|
||||
if not is_dataclass_transform:
|
||||
|
||||
# If dataclass_transform is applied to a class, dataclass-like semantics
|
||||
# will be assumed for any class that directly or indirectly derives from
|
||||
# the decorated class or uses the decorated class as a metaclass.
|
||||
if (
|
||||
isinstance(cls, DataclassTransformer)
|
||||
and cls.init_mode_from_init_subclass
|
||||
):
|
||||
is_dataclass_transform = True
|
||||
default_init_mode = cls.init_mode_from_init_subclass
|
||||
|
||||
elif (
|
||||
# Some object like CompiledValues would not be compatible
|
||||
isinstance(cls, ClassMixin)
|
||||
):
|
||||
is_dataclass_transform, default_init_mode = (
|
||||
cls._has_dataclass_transform_metaclasses()
|
||||
)
|
||||
|
||||
# Attributes on the decorated class and its base classes are not
|
||||
# considered to be fields.
|
||||
if is_dataclass_transform:
|
||||
continue
|
||||
|
||||
# All inherited classes behave like dataclass semantics
|
||||
if (
|
||||
is_dataclass_transform
|
||||
and isinstance(cls, ClassValue)
|
||||
and (
|
||||
cls.init_param_mode()
|
||||
or (cls.init_param_mode() is None and default_init_mode)
|
||||
)
|
||||
):
|
||||
param_names.extend(
|
||||
get_dataclass_param_names(cls)
|
||||
)
|
||||
|
||||
if is_dataclass_transform:
|
||||
return [DataclassSignature(cls, param_names)]
|
||||
else:
|
||||
return []
|
||||
|
||||
def get_signatures(self):
|
||||
# Since calling staticmethod without a function is illegal, the Jedi
|
||||
# plugin doesn't return anything. Therefore call directly and get what
|
||||
@@ -366,11 +232,6 @@ class ClassMixin:
|
||||
return sigs
|
||||
args = ValuesArguments([])
|
||||
init_funcs = self.py__call__(args).py__getattribute__('__init__')
|
||||
|
||||
dataclass_sigs = self._get_dataclass_transform_signatures()
|
||||
if dataclass_sigs:
|
||||
return dataclass_sigs
|
||||
else:
|
||||
return [sig.bind(self) for sig in init_funcs.get_signatures()]
|
||||
|
||||
def _as_context(self):
|
||||
@@ -458,158 +319,6 @@ class ClassMixin:
|
||||
return ValueSet({self})
|
||||
|
||||
|
||||
class DataclassParamName(BaseTreeParamName):
|
||||
"""
|
||||
Represent a field declaration on a class with dataclass semantics.
|
||||
"""
|
||||
|
||||
def __init__(self, parent_context, tree_name, annotation_node, default_node):
|
||||
super().__init__(parent_context, tree_name)
|
||||
self.annotation_node = annotation_node
|
||||
self.default_node = default_node
|
||||
|
||||
def get_kind(self):
|
||||
return Parameter.POSITIONAL_OR_KEYWORD
|
||||
|
||||
def infer(self):
|
||||
if self.annotation_node is None:
|
||||
return NO_VALUES
|
||||
else:
|
||||
return self.parent_context.infer_node(self.annotation_node)
|
||||
|
||||
|
||||
class DataclassSignature(AbstractSignature):
|
||||
"""
|
||||
It represents the ``__init__`` signature of a class with dataclass semantics.
|
||||
|
||||
.. code:: python
|
||||
|
||||
"""
|
||||
def __init__(self, value, param_names):
|
||||
super().__init__(value)
|
||||
self._param_names = param_names
|
||||
|
||||
def get_param_names(self, resolve_stars=False):
|
||||
return self._param_names
|
||||
|
||||
|
||||
class DataclassDecorator(ValueWrapper, FunctionMixin):
|
||||
"""
|
||||
A dataclass(-like) decorator with custom parameters.
|
||||
|
||||
.. code:: python
|
||||
|
||||
@dataclass(init=True) # this
|
||||
class A: ...
|
||||
|
||||
@dataclass_transform
|
||||
def create_model(*, init=False): pass
|
||||
|
||||
@create_model(init=False) # or this
|
||||
class B: ...
|
||||
"""
|
||||
|
||||
def __init__(self, function, arguments, default_init: bool = True):
|
||||
"""
|
||||
Args:
|
||||
function: Decoratee | function
|
||||
arguments: The parameters to the dataclass function decorator
|
||||
default_init: Boolean to indicate the default init value
|
||||
"""
|
||||
super().__init__(function)
|
||||
argument_init = self._init_param_value(arguments)
|
||||
self.init_param_mode = (
|
||||
argument_init if argument_init is not None else default_init
|
||||
)
|
||||
|
||||
def _init_param_value(self, arguments) -> Optional[bool]:
|
||||
if not arguments.argument_node:
|
||||
return None
|
||||
|
||||
arg_nodes = (
|
||||
arguments.argument_node.children
|
||||
if arguments.argument_node.type == "arglist"
|
||||
else [arguments.argument_node]
|
||||
)
|
||||
|
||||
return init_param_value(arg_nodes)
|
||||
|
||||
|
||||
class DataclassTransformer(ValueWrapper, ClassMixin):
|
||||
"""
|
||||
A class decorated with the ``dataclass_transform`` decorator. dataclass-like
|
||||
semantics will be assumed for any class that directly or indirectly derives
|
||||
from the decorated class or uses the decorated class as a metaclass.
|
||||
Attributes on the decorated class and its base classes are not considered to
|
||||
be fields.
|
||||
"""
|
||||
def __init__(self, wrapped_value):
|
||||
super().__init__(wrapped_value)
|
||||
|
||||
def init_mode_from_new(self) -> bool:
|
||||
"""Default value if missing is ``True``"""
|
||||
new_methods = self._wrapped_value.py__getattribute__("__new__")
|
||||
|
||||
if not new_methods:
|
||||
return True
|
||||
|
||||
new_method = list(new_methods)[0]
|
||||
|
||||
for param in new_method.get_param_names():
|
||||
if (
|
||||
param.string_name == "init"
|
||||
and param.default_node
|
||||
and param.default_node.type == "keyword"
|
||||
):
|
||||
if param.default_node.value == "False":
|
||||
return False
|
||||
elif param.default_node.value == "True":
|
||||
return True
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def init_mode_from_init_subclass(self) -> Optional[bool]:
|
||||
# def __init_subclass__(cls) -> None: ... is hardcoded in the typeshed
|
||||
# so the extra parameters can not be inferred.
|
||||
return True
|
||||
|
||||
|
||||
class DataclassWrapper(ValueWrapper, ClassMixin):
|
||||
"""
|
||||
A class with dataclass semantics from a decorator. The init parameters are
|
||||
only from the current class and parent classes decorated where the ``init``
|
||||
parameter was ``True``.
|
||||
|
||||
.. code:: python
|
||||
|
||||
@dataclass
|
||||
class A: ... # this
|
||||
|
||||
@dataclass_transform
|
||||
def create_model(): pass
|
||||
|
||||
@create_model()
|
||||
class B: ... # or this
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, wrapped_value, should_generate_init: bool
|
||||
):
|
||||
super().__init__(wrapped_value)
|
||||
self.should_generate_init = should_generate_init
|
||||
|
||||
def get_signatures(self):
|
||||
param_names = []
|
||||
for cls in reversed(list(self.py__mro__())):
|
||||
if (
|
||||
isinstance(cls, DataclassWrapper)
|
||||
and cls.should_generate_init
|
||||
):
|
||||
param_names.extend(get_dataclass_param_names(cls))
|
||||
return [DataclassSignature(cls, param_names)]
|
||||
|
||||
|
||||
class ClassValue(ClassMixin, FunctionAndClassBase, metaclass=CachedMetaClass):
|
||||
api_type = 'class'
|
||||
|
||||
@@ -676,19 +385,6 @@ class ClassValue(ClassMixin, FunctionAndClassBase, metaclass=CachedMetaClass):
|
||||
return values
|
||||
return NO_VALUES
|
||||
|
||||
def init_param_mode(self) -> Optional[bool]:
|
||||
"""
|
||||
It returns ``True`` if ``class X(init=False):`` else ``False``.
|
||||
"""
|
||||
bases_arguments = self._get_bases_arguments()
|
||||
|
||||
if bases_arguments.argument_node.type != "arglist":
|
||||
# If it is not inheriting from the base model and having
|
||||
# extra parameters, then init behavior is not changed.
|
||||
return None
|
||||
|
||||
return init_param_value(bases_arguments.argument_node.children)
|
||||
|
||||
@plugin_manager.decorate()
|
||||
def get_metaclass_signatures(self, metaclasses):
|
||||
return []
|
||||
|
||||
@@ -80,7 +80,7 @@ class ModuleMixin(SubModuleDictMixin):
|
||||
def is_stub(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
@property # type: ignore[misc]
|
||||
@inference_state_method_cache()
|
||||
def name(self):
|
||||
return self._module_name_class(self, self.string_names[-1])
|
||||
@@ -138,7 +138,7 @@ class ModuleValue(ModuleMixin, TreeValue):
|
||||
api_type = 'module'
|
||||
|
||||
def __init__(self, inference_state, module_node, code_lines, file_io=None,
|
||||
string_names=None, is_package=False) -> None:
|
||||
string_names=None, is_package=False):
|
||||
super().__init__(
|
||||
inference_state,
|
||||
parent_context=None,
|
||||
@@ -149,7 +149,7 @@ class ModuleValue(ModuleMixin, TreeValue):
|
||||
self._path: Optional[Path] = None
|
||||
else:
|
||||
self._path = file_io.path
|
||||
self.string_names: Optional[tuple[str, ...]] = string_names
|
||||
self.string_names = string_names # Optional[Tuple[str, ...]]
|
||||
self.code_lines = code_lines
|
||||
self._is_package = is_package
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class ImplicitNamespaceValue(Value, SubModuleDictMixin):
|
||||
def get_qualified_names(self):
|
||||
return ()
|
||||
|
||||
@property
|
||||
@property # type: ignore[misc]
|
||||
@inference_state_method_cache()
|
||||
def name(self):
|
||||
string_name = self.py__package__()[-1]
|
||||
|
||||
@@ -11,6 +11,7 @@ compiled module that returns the types for C-builtins.
|
||||
"""
|
||||
import parso
|
||||
import os
|
||||
from inspect import Parameter
|
||||
|
||||
from jedi import debug
|
||||
from jedi.inference.utils import safe_property
|
||||
@@ -24,20 +25,15 @@ from jedi.inference.value.instance import \
|
||||
from jedi.inference.base_value import ContextualizedNode, \
|
||||
NO_VALUES, ValueSet, ValueWrapper, LazyValueWrapper
|
||||
from jedi.inference.value import ClassValue, ModuleValue
|
||||
from jedi.inference.value.decorator import Decoratee
|
||||
from jedi.inference.value.klass import (
|
||||
DataclassWrapper,
|
||||
DataclassDecorator,
|
||||
DataclassTransformer,
|
||||
)
|
||||
from jedi.inference.value.klass import ClassMixin
|
||||
from jedi.inference.value.function import FunctionMixin
|
||||
from jedi.inference.value import iterable
|
||||
from jedi.inference.lazy_value import LazyTreeValue, LazyKnownValue, \
|
||||
LazyKnownValues
|
||||
from jedi.inference.names import ValueName
|
||||
from jedi.inference.names import ValueName, BaseTreeParamName
|
||||
from jedi.inference.filters import AttributeOverwrite, publish_method, \
|
||||
ParserTreeFilter, DictFilter
|
||||
from jedi.inference.signature import SignatureWrapper
|
||||
from jedi.inference.signature import AbstractSignature, SignatureWrapper
|
||||
|
||||
|
||||
# Copied from Python 3.6's stdlib.
|
||||
@@ -595,103 +591,65 @@ def _random_choice(sequences):
|
||||
|
||||
|
||||
def _dataclass(value, arguments, callback):
|
||||
"""
|
||||
Decorator entry points for dataclass.
|
||||
|
||||
1. dataclass decorator declaration with parameters
|
||||
2. dataclass semantics on a class from a dataclass(-like) decorator
|
||||
"""
|
||||
for c in _follow_param(value.inference_state, arguments, 0):
|
||||
if c.is_class():
|
||||
# Declare dataclass semantics on a class from a dataclass decorator
|
||||
should_generate_init = (
|
||||
# Customized decorator, init may be disabled
|
||||
value.init_param_mode
|
||||
if isinstance(value, DataclassDecorator)
|
||||
# Bare dataclass decorator, always with init mode
|
||||
else True
|
||||
)
|
||||
return ValueSet([DataclassWrapper(c, should_generate_init)])
|
||||
return ValueSet([DataclassWrapper(c)])
|
||||
else:
|
||||
# @dataclass(init=False)
|
||||
# dataclass decorator customization
|
||||
return ValueSet(
|
||||
[
|
||||
DataclassDecorator(
|
||||
value,
|
||||
arguments=arguments,
|
||||
default_init=True,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
return NO_VALUES
|
||||
|
||||
|
||||
def _dataclass_transform(value, arguments, callback):
|
||||
"""
|
||||
Decorator entry points for dataclass_transform.
|
||||
|
||||
1. dataclass-like decorator instantiation from a dataclass_transform decorator
|
||||
2. dataclass_transform decorator declaration with parameters
|
||||
3. dataclass-like decorator declaration with parameters
|
||||
4. dataclass-like semantics on a class from a dataclass-like decorator
|
||||
"""
|
||||
for c in _follow_param(value.inference_state, arguments, 0):
|
||||
if c.is_class():
|
||||
is_dataclass_transform = (
|
||||
value.name.string_name == "dataclass_transform"
|
||||
# The decorator function from dataclass_transform acting as the
|
||||
# dataclass decorator.
|
||||
and not isinstance(value, Decoratee)
|
||||
# The decorator function from dataclass_transform acting as the
|
||||
# dataclass decorator with customized parameters
|
||||
and not isinstance(value, DataclassDecorator)
|
||||
)
|
||||
|
||||
if is_dataclass_transform:
|
||||
# Declare base class
|
||||
return ValueSet([DataclassTransformer(c)])
|
||||
else:
|
||||
# Declare dataclass-like semantics on a class from a
|
||||
# dataclass-like decorator
|
||||
should_generate_init = value.init_param_mode
|
||||
return ValueSet([DataclassWrapper(c, should_generate_init)])
|
||||
elif c.is_function():
|
||||
# dataclass-like decorator instantiation:
|
||||
# @dataclass_transform
|
||||
# def create_model()
|
||||
return ValueSet(
|
||||
[
|
||||
DataclassDecorator(
|
||||
value,
|
||||
arguments=arguments,
|
||||
default_init=True,
|
||||
)
|
||||
]
|
||||
)
|
||||
elif (
|
||||
# @dataclass_transform
|
||||
# def create_model(): pass
|
||||
# @create_model(init=...)
|
||||
isinstance(value, Decoratee)
|
||||
):
|
||||
# dataclass (or like) decorator customization
|
||||
return ValueSet(
|
||||
[
|
||||
DataclassDecorator(
|
||||
value,
|
||||
arguments=arguments,
|
||||
default_init=value._wrapped_value.init_param_mode,
|
||||
)
|
||||
]
|
||||
)
|
||||
else:
|
||||
# dataclass_transform decorator with parameters; nothing impactful
|
||||
return ValueSet([value])
|
||||
return NO_VALUES
|
||||
|
||||
|
||||
class DataclassWrapper(ValueWrapper, ClassMixin):
|
||||
def get_signatures(self):
|
||||
param_names = []
|
||||
for cls in reversed(list(self.py__mro__())):
|
||||
if isinstance(cls, DataclassWrapper):
|
||||
filter_ = cls.as_context().get_global_filter()
|
||||
# .values ordering is not guaranteed, at least not in
|
||||
# Python < 3.6, when dicts where not ordered, which is an
|
||||
# implementation detail anyway.
|
||||
for name in sorted(filter_.values(), key=lambda name: name.start_pos):
|
||||
d = name.tree_name.get_definition()
|
||||
annassign = d.children[1]
|
||||
if d.type == 'expr_stmt' and annassign.type == 'annassign':
|
||||
if len(annassign.children) < 4:
|
||||
default = None
|
||||
else:
|
||||
default = annassign.children[3]
|
||||
param_names.append(DataclassParamName(
|
||||
parent_context=cls.parent_context,
|
||||
tree_name=name.tree_name,
|
||||
annotation_node=annassign.children[1],
|
||||
default_node=default,
|
||||
))
|
||||
return [DataclassSignature(cls, param_names)]
|
||||
|
||||
|
||||
class DataclassSignature(AbstractSignature):
|
||||
def __init__(self, value, param_names):
|
||||
super().__init__(value)
|
||||
self._param_names = param_names
|
||||
|
||||
def get_param_names(self, resolve_stars=False):
|
||||
return self._param_names
|
||||
|
||||
|
||||
class DataclassParamName(BaseTreeParamName):
|
||||
def __init__(self, parent_context, tree_name, annotation_node, default_node):
|
||||
super().__init__(parent_context, tree_name)
|
||||
self.annotation_node = annotation_node
|
||||
self.default_node = default_node
|
||||
|
||||
def get_kind(self):
|
||||
return Parameter.POSITIONAL_OR_KEYWORD
|
||||
|
||||
def infer(self):
|
||||
if self.annotation_node is None:
|
||||
return NO_VALUES
|
||||
else:
|
||||
return self.parent_context.infer_node(self.annotation_node)
|
||||
|
||||
|
||||
class ItemGetterCallable(ValueWrapper):
|
||||
def __init__(self, instance, args_value_set):
|
||||
super().__init__(instance)
|
||||
@@ -840,17 +798,22 @@ _implemented = {
|
||||
# runtime_checkable doesn't really change anything and is just
|
||||
# adding logs for infering stuff, so we can safely ignore it.
|
||||
'runtime_checkable': lambda value, arguments, callback: NO_VALUES,
|
||||
# Python 3.11+
|
||||
'dataclass_transform': _dataclass_transform,
|
||||
},
|
||||
'typing_extensions': {
|
||||
# Python <3.11
|
||||
'dataclass_transform': _dataclass_transform,
|
||||
},
|
||||
'dataclasses': {
|
||||
# For now this works at least better than Jedi trying to understand it.
|
||||
'dataclass': _dataclass
|
||||
},
|
||||
# attrs exposes declaration interface roughly compatible with dataclasses
|
||||
# via attrs.define, attrs.frozen and attrs.mutable
|
||||
# https://www.attrs.org/en/stable/names.html
|
||||
'attr': {
|
||||
'define': _dataclass,
|
||||
'frozen': _dataclass,
|
||||
},
|
||||
'attrs': {
|
||||
'define': _dataclass,
|
||||
'frozen': _dataclass,
|
||||
},
|
||||
'os.path': {
|
||||
'dirname': _create_string_input_function(os.path.dirname),
|
||||
'abspath': _create_string_input_function(os.path.abspath),
|
||||
|
||||
16
setup.py
16
setup.py
@@ -1,5 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
from typing import cast
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools.depends import get_module_constant
|
||||
@@ -10,7 +9,7 @@ __AUTHOR__ = 'David Halter'
|
||||
__AUTHOR_EMAIL__ = 'davidhalter88@gmail.com'
|
||||
|
||||
# Get the version from within jedi. It's defined in exactly one place now.
|
||||
version = cast(str, get_module_constant("jedi", "__version__"))
|
||||
version = get_module_constant("jedi", "__version__")
|
||||
|
||||
readme = open('README.rst').read() + '\n\n' + open('CHANGELOG.rst').read()
|
||||
|
||||
@@ -35,7 +34,7 @@ setup(name='jedi',
|
||||
keywords='python completion refactoring vim',
|
||||
long_description=readme,
|
||||
packages=find_packages(exclude=['test', 'test.*']),
|
||||
python_requires='>=3.8',
|
||||
python_requires='>=3.6',
|
||||
# Python 3.13 grammars are added to parso in 0.8.4
|
||||
install_requires=['parso>=0.8.4,<0.9.0'],
|
||||
extras_require={
|
||||
@@ -47,15 +46,14 @@ setup(name='jedi',
|
||||
'colorama',
|
||||
'Django',
|
||||
'attrs',
|
||||
'typing_extensions',
|
||||
],
|
||||
'qa': [
|
||||
# latest version on 2025-06-16
|
||||
'flake8==7.2.0',
|
||||
# latest version supporting Python 3.6
|
||||
'mypy==1.16',
|
||||
'flake8==5.0.4',
|
||||
# latest version supporting Python 3.6
|
||||
'mypy==0.971',
|
||||
# Arbitrary pins, latest at the time of pinning
|
||||
'types-setuptools==80.9.0.20250529',
|
||||
'types-setuptools==67.2.0.1',
|
||||
],
|
||||
'docs': [
|
||||
# Just pin all of these.
|
||||
@@ -96,6 +94,8 @@ setup(name='jedi',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
|
||||
2
sith.py
2
sith.py
@@ -44,7 +44,7 @@ Options:
|
||||
--pudb Launch pudb when error is raised.
|
||||
"""
|
||||
|
||||
from docopt import docopt # type: ignore[import, unused-ignore]
|
||||
from docopt import docopt # type: ignore[import]
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
@@ -527,11 +527,3 @@ lc = [x for a, *x in [(1, '', 1.0)]]
|
||||
lc[0][0]
|
||||
#?
|
||||
lc[0][1]
|
||||
|
||||
|
||||
xy = (1,)
|
||||
x, y = *xy, None
|
||||
|
||||
# whatever it is should not crash
|
||||
#?
|
||||
x
|
||||
|
||||
@@ -134,7 +134,7 @@ TEST_GOTO = 2
|
||||
TEST_REFERENCES = 3
|
||||
|
||||
|
||||
grammar313 = parso.load_grammar(version='3.13')
|
||||
grammar36 = parso.load_grammar(version='3.6')
|
||||
|
||||
|
||||
class BaseTestCase(object):
|
||||
@@ -238,7 +238,7 @@ class IntegrationTestCase(BaseTestCase):
|
||||
should_be = set()
|
||||
for match in re.finditer('(?:[^ ]+)', correct):
|
||||
string = match.group(0)
|
||||
parser = grammar313.parse(string, start_symbol='eval_input', error_recovery=False)
|
||||
parser = grammar36.parse(string, start_symbol='eval_input', error_recovery=False)
|
||||
parser_utils.move(parser.get_root_node(), self.line_nr)
|
||||
node = parser.get_root_node()
|
||||
module_context = script._get_module_context()
|
||||
@@ -504,7 +504,7 @@ if __name__ == '__main__':
|
||||
if arguments['--env']:
|
||||
environment = get_system_environment(arguments['--env'])
|
||||
else:
|
||||
# Will be 3.13.
|
||||
# Will be 3.6.
|
||||
environment = get_default_environment()
|
||||
|
||||
import traceback
|
||||
|
||||
@@ -26,7 +26,7 @@ def test_find_system_environments():
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'version',
|
||||
['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
|
||||
['3.6', '3.7', '3.8', '3.9']
|
||||
)
|
||||
def test_versions(version):
|
||||
try:
|
||||
|
||||
@@ -9,8 +9,10 @@ import pytest
|
||||
|
||||
import jedi
|
||||
import jedi.settings
|
||||
from jedi import Project
|
||||
from jedi.inference.compiled import mixed
|
||||
from importlib import import_module
|
||||
from test.helpers import get_example_dir
|
||||
|
||||
|
||||
class _GlobalNameSpace:
|
||||
@@ -591,6 +593,23 @@ def test_dict_completion(code, column, expected):
|
||||
assert [c.complete for c in comps] == expected
|
||||
|
||||
|
||||
def test_implicit_namespace_package_with_subpackages(monkeypatch):
|
||||
sys_path_dir1 = get_example_dir('implicit_namespace_package_with_subpackages', 'ns1')
|
||||
sys_path_dir2 = get_example_dir('implicit_namespace_package_with_subpackages', 'ns2')
|
||||
monkeypatch.syspath_prepend(sys_path_dir1)
|
||||
monkeypatch.syspath_prepend(sys_path_dir2)
|
||||
|
||||
import pkg_implicit_namespace_package_test
|
||||
interpreter = jedi.Interpreter(
|
||||
"pkg_implicit_namespace_package_test.",
|
||||
namespaces=[locals()],
|
||||
project=Project('.')
|
||||
)
|
||||
comps = interpreter.complete()
|
||||
expected = ["pkgA", "pkgB"]
|
||||
assert [c.complete for c in comps] == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'code, types', [
|
||||
('dct[1]', ['int']),
|
||||
|
||||
@@ -16,13 +16,13 @@ def test_on_code():
|
||||
assert i.infer()
|
||||
|
||||
|
||||
def test_generics_without_definition() -> None:
|
||||
def test_generics_without_definition():
|
||||
# Used to raise a recursion error
|
||||
T = TypeVar('T')
|
||||
|
||||
class Stack(Generic[T]):
|
||||
def __init__(self) -> None:
|
||||
self.items: List[T] = []
|
||||
def __init__(self):
|
||||
self.items = [] # type: List[T]
|
||||
|
||||
def push(self, item):
|
||||
self.items.append(item)
|
||||
|
||||
@@ -318,511 +318,40 @@ def test_wraps_signature(Script, code, signature):
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"start, start_params, include_params",
|
||||
[
|
||||
["@dataclass\nclass X:", [], True],
|
||||
["@dataclass(eq=True)\nclass X:", [], True],
|
||||
[
|
||||
dedent(
|
||||
"""
|
||||
'start, start_params', [
|
||||
['@dataclass\nclass X:', []],
|
||||
['@dataclass(eq=True)\nclass X:', []],
|
||||
[dedent('''
|
||||
class Y():
|
||||
y: int
|
||||
@dataclass
|
||||
class X(Y):"""
|
||||
),
|
||||
[],
|
||||
True,
|
||||
],
|
||||
[
|
||||
dedent(
|
||||
"""
|
||||
class X(Y):'''), []],
|
||||
[dedent('''
|
||||
@dataclass
|
||||
class Y():
|
||||
y: int
|
||||
z = 5
|
||||
@dataclass
|
||||
class X(Y):"""
|
||||
),
|
||||
["y"],
|
||||
True,
|
||||
],
|
||||
[
|
||||
dedent(
|
||||
"""
|
||||
@dataclass
|
||||
class Y():
|
||||
y: int
|
||||
class Z(Y): # Not included
|
||||
z = 5
|
||||
@dataclass
|
||||
class X(Z):"""
|
||||
),
|
||||
["y"],
|
||||
True,
|
||||
],
|
||||
# init=False
|
||||
[
|
||||
dedent(
|
||||
"""
|
||||
@dataclass(init=False)
|
||||
class X:"""
|
||||
),
|
||||
[],
|
||||
False,
|
||||
],
|
||||
[
|
||||
dedent(
|
||||
"""
|
||||
@dataclass(eq=True, init=False)
|
||||
class X:"""
|
||||
),
|
||||
[],
|
||||
False,
|
||||
],
|
||||
# custom init
|
||||
[
|
||||
dedent(
|
||||
"""
|
||||
@dataclass()
|
||||
class X:
|
||||
def __init__(self, toto: str):
|
||||
pass
|
||||
"""
|
||||
),
|
||||
["toto"],
|
||||
False,
|
||||
],
|
||||
],
|
||||
ids=[
|
||||
"direct_transformed",
|
||||
"transformed_with_params",
|
||||
"subclass_transformed",
|
||||
"both_transformed",
|
||||
"intermediate_not_transformed",
|
||||
"init_false",
|
||||
"init_false_multiple",
|
||||
"custom_init",
|
||||
],
|
||||
)
|
||||
def test_dataclass_signature(
|
||||
Script, skip_pre_python37, 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"
|
||||
|
||||
code = dedent(
|
||||
f"""
|
||||
name: str
|
||||
foo = 3
|
||||
blob: ClassVar[str]
|
||||
price: {price_type}
|
||||
quantity: int = 0.0
|
||||
|
||||
X("""
|
||||
)
|
||||
|
||||
code = (
|
||||
"from dataclasses import dataclass\n"
|
||||
+ "from typing import ClassVar, Final\n"
|
||||
+ start
|
||||
+ code
|
||||
)
|
||||
|
||||
sig, = Script(code).get_signatures()
|
||||
expected_params = (
|
||||
[*start_params, "name", "price", "quantity"]
|
||||
if include_params
|
||||
else [*start_params]
|
||||
)
|
||||
assert [p.name for p in sig.params] == expected_params
|
||||
|
||||
if include_params:
|
||||
quantity, = sig.params[-1].infer()
|
||||
assert quantity.name == 'int'
|
||||
price, = sig.params[-2].infer()
|
||||
assert price.name == price_type_infer
|
||||
|
||||
|
||||
dataclass_transform_cases = [
|
||||
# Attributes on the decorated class and its base classes
|
||||
# are not considered to be fields.
|
||||
# 1/ Declare dataclass transformer
|
||||
# Base Class
|
||||
['@dataclass_transform\nclass X:', [], False],
|
||||
# Base Class with params
|
||||
['@dataclass_transform(eq_default=True)\nclass X:', [], False],
|
||||
# Subclass
|
||||
[dedent('''
|
||||
class Y():
|
||||
y: int
|
||||
@dataclass_transform
|
||||
class X(Y):'''), [], False],
|
||||
# 2/ Declare dataclass transformed
|
||||
# Class based
|
||||
[dedent('''
|
||||
@dataclass_transform
|
||||
class Y():
|
||||
y: int
|
||||
z = 5
|
||||
class X(Y):'''), [], True],
|
||||
# Class based with params
|
||||
[dedent('''
|
||||
@dataclass_transform(eq_default=True)
|
||||
class Y():
|
||||
y: int
|
||||
z = 5
|
||||
class X(Y):'''), [], True],
|
||||
# Decorator based
|
||||
[dedent('''
|
||||
@dataclass_transform
|
||||
def create_model():
|
||||
pass
|
||||
@create_model
|
||||
class X:'''), [], True],
|
||||
[dedent('''
|
||||
@dataclass_transform
|
||||
def create_model():
|
||||
pass
|
||||
class Y:
|
||||
y: int
|
||||
@create_model
|
||||
class X(Y):'''), [], True],
|
||||
[dedent('''
|
||||
@dataclass_transform
|
||||
def create_model():
|
||||
pass
|
||||
@create_model
|
||||
class Y:
|
||||
y: int
|
||||
@create_model
|
||||
class X(Y):'''), ["y"], True],
|
||||
[dedent('''
|
||||
@dataclass_transform
|
||||
def create_model():
|
||||
pass
|
||||
@create_model
|
||||
class Y:
|
||||
y: int
|
||||
class Z(Y):
|
||||
z: int
|
||||
@create_model
|
||||
class X(Z):'''), ["y"], True],
|
||||
# Metaclass based
|
||||
[dedent('''
|
||||
@dataclass_transform
|
||||
class ModelMeta():
|
||||
y: int
|
||||
z = 5
|
||||
class ModelBase(metaclass=ModelMeta):
|
||||
t: int
|
||||
p = 5
|
||||
class X(ModelBase):'''), [], True],
|
||||
# 3/ Init custom init
|
||||
[dedent('''
|
||||
@dataclass_transform()
|
||||
class Y():
|
||||
y: int
|
||||
z = 5
|
||||
class X(Y):
|
||||
def __init__(self, toto: str):
|
||||
pass
|
||||
'''), ["toto"], False],
|
||||
# 4/ init=false
|
||||
# Class based
|
||||
# WARNING: Unsupported
|
||||
# [dedent('''
|
||||
# @dataclass_transform
|
||||
# class Y():
|
||||
# y: int
|
||||
# z = 5
|
||||
# def __init_subclass__(
|
||||
# cls,
|
||||
# *,
|
||||
# init: bool = False,
|
||||
# )
|
||||
# class X(Y):'''), [], False],
|
||||
[dedent('''
|
||||
@dataclass_transform
|
||||
class Y():
|
||||
y: int
|
||||
z = 5
|
||||
def __init_subclass__(
|
||||
cls,
|
||||
*,
|
||||
init: bool = False,
|
||||
)
|
||||
class X(Y, init=True):'''), [], True],
|
||||
[dedent('''
|
||||
@dataclass_transform
|
||||
class Y():
|
||||
y: int
|
||||
z = 5
|
||||
def __init_subclass__(
|
||||
cls,
|
||||
*,
|
||||
init: bool = False,
|
||||
)
|
||||
class X(Y, init=False):'''), [], False],
|
||||
[dedent('''
|
||||
@dataclass_transform
|
||||
class Y():
|
||||
y: int
|
||||
z = 5
|
||||
class X(Y, init=False):'''), [], False],
|
||||
# Decorator based
|
||||
[dedent('''
|
||||
@dataclass_transform
|
||||
def create_model(init=False):
|
||||
pass
|
||||
@create_model()
|
||||
class X:'''), [], False],
|
||||
[dedent('''
|
||||
@dataclass_transform
|
||||
def create_model(init=False):
|
||||
pass
|
||||
@create_model(init=True)
|
||||
class X:'''), [], True],
|
||||
[dedent('''
|
||||
@dataclass_transform
|
||||
def create_model(init=False):
|
||||
pass
|
||||
@create_model(init=False)
|
||||
class X:'''), [], False],
|
||||
[dedent('''
|
||||
@dataclass_transform
|
||||
def create_model():
|
||||
pass
|
||||
@create_model(init=False)
|
||||
class X:'''), [], False],
|
||||
# Metaclass based
|
||||
[dedent('''
|
||||
@dataclass_transform
|
||||
class ModelMeta():
|
||||
y: int
|
||||
z = 5
|
||||
def __new__(
|
||||
cls,
|
||||
name,
|
||||
bases,
|
||||
namespace,
|
||||
*,
|
||||
init: bool = False,
|
||||
):
|
||||
...
|
||||
class ModelBase(metaclass=ModelMeta):
|
||||
t: int
|
||||
p = 5
|
||||
class X(ModelBase):'''), [], False],
|
||||
[dedent('''
|
||||
@dataclass_transform
|
||||
class ModelMeta():
|
||||
y: int
|
||||
z = 5
|
||||
def __new__(
|
||||
cls,
|
||||
name,
|
||||
bases,
|
||||
namespace,
|
||||
*,
|
||||
init: bool = False,
|
||||
):
|
||||
...
|
||||
class ModelBase(metaclass=ModelMeta):
|
||||
t: int
|
||||
p = 5
|
||||
class X(ModelBase, init=True):'''), [], True],
|
||||
[dedent('''
|
||||
@dataclass_transform
|
||||
class ModelMeta():
|
||||
y: int
|
||||
z = 5
|
||||
def __new__(
|
||||
cls,
|
||||
name,
|
||||
bases,
|
||||
namespace,
|
||||
*,
|
||||
init: bool = False,
|
||||
):
|
||||
...
|
||||
class ModelBase(metaclass=ModelMeta):
|
||||
t: int
|
||||
p = 5
|
||||
class X(ModelBase, init=False):'''), [], False],
|
||||
[dedent('''
|
||||
@dataclass_transform
|
||||
class ModelMeta():
|
||||
y: int
|
||||
z = 5
|
||||
class ModelBase(metaclass=ModelMeta):
|
||||
t: int
|
||||
p = 5
|
||||
class X(ModelBase, init=False):'''), [], False],
|
||||
# 4/ Other parameters
|
||||
# Class based
|
||||
[dedent('''
|
||||
@dataclass_transform
|
||||
class Y():
|
||||
y: int
|
||||
z = 5
|
||||
class X(Y, eq=True):'''), [], True],
|
||||
# Decorator based
|
||||
[dedent('''
|
||||
@dataclass_transform
|
||||
def create_model():
|
||||
pass
|
||||
@create_model(eq=True)
|
||||
class X:'''), [], True],
|
||||
# Metaclass based
|
||||
[dedent('''
|
||||
@dataclass_transform
|
||||
class ModelMeta():
|
||||
y: int
|
||||
z = 5
|
||||
class ModelBase(metaclass=ModelMeta):
|
||||
t: int
|
||||
p = 5
|
||||
class X(ModelBase, eq=True):'''), [], True],
|
||||
class X(Y):'''), ['y']],
|
||||
]
|
||||
|
||||
ids = [
|
||||
"direct_transformer",
|
||||
"transformer_with_params",
|
||||
"subclass_transformer",
|
||||
"base_transformed",
|
||||
"base_transformed_with_params",
|
||||
"decorator_transformed_direct",
|
||||
"decorator_transformed_subclass",
|
||||
"decorator_transformed_both",
|
||||
"decorator_transformed_intermediate_not",
|
||||
"metaclass_transformed",
|
||||
"custom_init",
|
||||
# "base_transformed_init_false_dataclass_init_default",
|
||||
"base_transformed_init_false_dataclass_init_true",
|
||||
"base_transformed_init_false_dataclass_init_false",
|
||||
"base_transformed_init_default_dataclass_init_false",
|
||||
"decorator_transformed_init_false_dataclass_init_default",
|
||||
"decorator_transformed_init_false_dataclass_init_true",
|
||||
"decorator_transformed_init_false_dataclass_init_false",
|
||||
"decorator_transformed_init_default_dataclass_init_false",
|
||||
"metaclass_transformed_init_false_dataclass_init_default",
|
||||
"metaclass_transformed_init_false_dataclass_init_true",
|
||||
"metaclass_transformed_init_false_dataclass_init_false",
|
||||
"metaclass_transformed_init_default_dataclass_init_false",
|
||||
"base_transformed_other_parameters",
|
||||
"decorator_transformed_other_parameters",
|
||||
"metaclass_transformed_other_parameters",
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'start, start_params, include_params', dataclass_transform_cases, ids=ids
|
||||
)
|
||||
def test_extensions_dataclass_transform_signature(
|
||||
Script, skip_pre_python37, start, start_params, include_params, environment
|
||||
):
|
||||
has_typing_ext = bool(Script('import typing_extensions').infer())
|
||||
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"
|
||||
|
||||
code = dedent(
|
||||
f"""
|
||||
name: str
|
||||
foo = 3
|
||||
blob: ClassVar[str]
|
||||
price: {price_type}
|
||||
quantity: int = 0.0
|
||||
|
||||
X("""
|
||||
)
|
||||
|
||||
code = (
|
||||
"from typing_extensions import dataclass_transform\n"
|
||||
+ "from typing import ClassVar, Final\n"
|
||||
+ start
|
||||
+ code
|
||||
)
|
||||
|
||||
(sig,) = Script(code).get_signatures()
|
||||
expected_params = (
|
||||
[*start_params, "name", "price", "quantity"]
|
||||
if include_params
|
||||
else [*start_params]
|
||||
)
|
||||
assert [p.name for p in sig.params] == expected_params
|
||||
|
||||
if include_params:
|
||||
quantity, = sig.params[-1].infer()
|
||||
assert quantity.name == 'int'
|
||||
price, = sig.params[-2].infer()
|
||||
assert price.name == price_type_infer
|
||||
|
||||
|
||||
def test_dataclass_transform_complete(Script):
|
||||
script = Script('''\
|
||||
@dataclass_transform
|
||||
class Y():
|
||||
y: int
|
||||
z = 5
|
||||
|
||||
class X(Y):
|
||||
name: str
|
||||
foo = 3
|
||||
|
||||
def f(x: X):
|
||||
x.na''')
|
||||
completion, = script.complete()
|
||||
assert completion.description == 'name: str'
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"start, start_params, include_params", dataclass_transform_cases, ids=ids
|
||||
)
|
||||
def test_dataclass_transform_signature(
|
||||
Script, skip_pre_python311, start, start_params, include_params
|
||||
):
|
||||
def test_dataclass_signature(Script, skip_pre_python37, start, start_params):
|
||||
code = dedent('''
|
||||
name: str
|
||||
foo = 3
|
||||
blob: ClassVar[str]
|
||||
price: Final[float]
|
||||
price: float
|
||||
quantity: int = 0.0
|
||||
|
||||
X(''')
|
||||
|
||||
code = (
|
||||
"from typing import dataclass_transform\n"
|
||||
+ "from typing import ClassVar, Final\n"
|
||||
+ start
|
||||
+ code
|
||||
)
|
||||
code = 'from dataclasses import dataclass\n' + start + code
|
||||
|
||||
sig, = Script(code).get_signatures()
|
||||
expected_params = (
|
||||
[*start_params, "name", "price", "quantity"]
|
||||
if include_params
|
||||
else [*start_params]
|
||||
)
|
||||
assert [p.name for p in sig.params] == expected_params
|
||||
|
||||
if include_params:
|
||||
assert [p.name for p in sig.params] == start_params + ['name', 'price', 'quantity']
|
||||
quantity, = sig.params[-1].infer()
|
||||
assert quantity.name == 'int'
|
||||
price, = sig.params[-2].infer()
|
||||
assert price.name == 'object'
|
||||
assert price.name == 'float'
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -842,8 +371,7 @@ def test_dataclass_transform_signature(
|
||||
z = 5
|
||||
@define
|
||||
class X(Y):'''), ['y']],
|
||||
],
|
||||
ids=["define", "frozen", "define_customized", "define_subclass", "define_both"]
|
||||
]
|
||||
)
|
||||
def test_attrs_signature(Script, skip_pre_python37, start, start_params):
|
||||
has_attrs = bool(Script('import attrs').infer())
|
||||
|
||||
@@ -91,7 +91,7 @@ class TestSetupReadline(unittest.TestCase):
|
||||
}
|
||||
# There are quite a few differences, because both Windows and Linux
|
||||
# (posix and nt) libraries are included.
|
||||
assert len(difference) < 40
|
||||
assert len(difference) < 30
|
||||
|
||||
def test_local_import(self):
|
||||
s = 'import test.test_utils'
|
||||
|
||||
Reference in New Issue
Block a user