1
0
forked from VimPlug/jedi

Documentation and better naming

This commit is contained in:
Eric Masseran
2025-05-04 23:34:58 +02:00
parent e49032ed6b
commit 5f4afa27e5
2 changed files with 92 additions and 27 deletions

View File

@@ -36,6 +36,8 @@ py__doc__() Returns the docstring for a value.
====================================== ========================================
"""
from typing import List
from jedi import debug
from jedi.parser_utils import get_cached_parent_scope, expr_is_dotted, \
function_is_property
@@ -133,9 +135,19 @@ class ClassFilter(ParserTreeFilter):
return [name for name in names if self._access_possible(name)]
def get_dataclass_param_names(cls):
def get_dataclass_param_names(cls) -> List["DataclassParamName"]:
"""
``cls`` is a :class:`ClassMixin`.
``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()
@@ -154,6 +166,7 @@ def get_dataclass_param_names(cls):
default = None
else:
default = annassign.children[3]
param_names.append(DataclassParamName(
parent_context=cls.parent_context,
tree_name=name.tree_name,
@@ -259,21 +272,21 @@ class ClassMixin:
for meta in self.get_metaclasses(): # type: ignore[attr-defined]
if (
# Not sure if necessary
(isinstance(meta, DataclassWrapper) and meta.dataclass_init)
(isinstance(meta, DataclassWrapper) and meta.should_generate_init)
or (
isinstance(meta, Decoratee)
# Internal leakage :|
and isinstance(meta._wrapped_value, DataclassWrapper)
and meta._wrapped_value.dataclass_init
and meta._wrapped_value.should_generate_init
)
):
return True
return False
def _get_dataclass_transform_signatures(self):
def _get_dataclass_transform_signatures(self) -> List["DataclassSignature"]:
"""
Returns: A non-empty list if the class is dataclass transformed else an
Returns: A non-empty list if the class has dataclass semantics else an
empty list.
"""
param_names = []
@@ -283,7 +296,7 @@ class ClassMixin:
# 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.
(isinstance(cls, DataclassWrapper) and cls.dataclass_init)
(isinstance(cls, DataclassWrapper) and cls.should_generate_init)
or (
# Some object like CompiledValues would not be compatible
isinstance(cls, ClassMixin)
@@ -295,9 +308,9 @@ class ClassMixin:
# considered to be fields.
continue
# All inherited behave like dataclass
# All inherited classes behave like dataclass semantics
if is_dataclass_transform_with_init and (
isinstance(cls, ClassValue) and not cls._has_init_false()
isinstance(cls, ClassValue) and not cls._has_init_param_set_false()
):
param_names.extend(
get_dataclass_param_names(cls)
@@ -306,7 +319,7 @@ class ClassMixin:
if is_dataclass_transform_with_init:
return [DataclassSignature(cls, param_names)]
else:
[]
return []
def get_signatures(self):
# Since calling staticmethod without a function is illegal, the Jedi
@@ -412,6 +425,17 @@ class ClassMixin:
class DataclassParamName(BaseTreeParamName):
"""
Represent a field declaration on a class with dataclass semantics.
.. code:: python
class A:
a: int
``a`` is a :class:`DataclassParamName`.
"""
def __init__(self, parent_context, tree_name, annotation_node, default_node):
super().__init__(parent_context, tree_name)
self.annotation_node = annotation_node
@@ -428,6 +452,12 @@ class DataclassParamName(BaseTreeParamName):
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
@@ -437,6 +467,21 @@ class DataclassSignature(AbstractSignature):
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):
"""
Args:
@@ -446,10 +491,10 @@ class DataclassDecorator(ValueWrapper, FunctionMixin):
self.arguments = arguments
@property
def has_dataclass_init_false(self) -> bool:
def has_init_param_set_false(self) -> bool:
"""
Returns:
bool: True if dataclass(init=False)
bool: ``True`` if ``@dataclass(init=False)``
"""
if not self.arguments.argument_node:
return False
@@ -471,12 +516,26 @@ class DataclassDecorator(ValueWrapper, FunctionMixin):
class DataclassWrapper(ValueWrapper, ClassMixin):
"""
A class with dataclass semantics.
.. code:: python
@dataclass
class A: ... # this
@dataclass_transform
def create_model(): pass
@create_model()
class B: ... # or this
"""
def __init__(
self, wrapped_value, dataclass_init: bool, is_dataclass_transform: bool = False
self, wrapped_value, should_generate_init: bool, is_dataclass_transform: bool = False
):
super().__init__(wrapped_value)
self.dataclass_init = dataclass_init
self.should_generate_init = should_generate_init
self.is_dataclass_transform = is_dataclass_transform
def get_signatures(self):
@@ -484,7 +543,7 @@ class DataclassWrapper(ValueWrapper, ClassMixin):
for cls in reversed(list(self.py__mro__())):
if (
isinstance(cls, DataclassWrapper)
and cls.dataclass_init
and cls.should_generate_init
# Attributes on the decorated class and its base classes are not
# considered to be fields.
and not cls.is_dataclass_transform
@@ -559,7 +618,7 @@ class ClassValue(ClassMixin, FunctionAndClassBase, metaclass=CachedMetaClass):
return values
return NO_VALUES
def _has_init_false(self) -> bool:
def _has_init_param_set_false(self) -> bool:
"""
It returns ``True`` if ``class X(init=False):`` else ``False``.
"""

View File

@@ -592,17 +592,22 @@ def _random_choice(sequences):
def _dataclass(value, arguments, callback):
"""
dataclass decorator can be called 2 times with different arguments. One to
customize it dataclass(eq=True) and another one with the class to transform.
It supports dataclass, dataclass_transform and attrs.
Entry points for the following cases:
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():
# Decorate a dataclass / base dataclass
dataclass_init = (
# dataclass(-like) semantics on a class from a
# dataclass(-like) decorator
should_generate_init = (
# Customized decorator, init may be disabled
not value.has_dataclass_init_false
not value.has_init_param_set_false
if isinstance(value, DataclassDecorator)
# Bare dataclass decorator, always with init
else True
@@ -622,21 +627,22 @@ def _dataclass(value, arguments, callback):
[
DataclassWrapper(
c,
dataclass_init,
should_generate_init,
is_dataclass_transform,
)
]
)
elif c.is_function():
# dataclass-like decorator instantiation:
# @dataclass_transform
# def create_model(): pass
# def create_model()
return ValueSet([value])
elif (
# @dataclass(...)
# @dataclass(smth=...)
value.name.string_name != "dataclass_transform"
# @dataclass_transform
# def create_model(): pass
# @create_model(...)
# @create_model(smth=...)
or isinstance(value, Decoratee)
):
# dataclass (or like) decorator customization
@@ -649,7 +655,7 @@ def _dataclass(value, arguments, callback):
]
)
else:
# dataclass_transform decorator customization; nothing impactful
# dataclass_transform decorator with parameters; nothing impactful
return ValueSet([value])
return NO_VALUES