PEP 681: Descriptor fields (#2369)

Co-authored-by: CAM Gerlach <CAM.Gerlach@Gerlach.CAM>
This commit is contained in:
Erik De Bonte 2022-03-14 18:07:18 -07:00 committed by GitHub
parent 15edc5ee28
commit fb1498c5a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 60 additions and 2 deletions

View File

@ -19,7 +19,7 @@ Abstract
libraries have behaviors that are similar to dataclasses, but these
behaviors cannot be described using standard type annotations. Such
projects include attrs, pydantic, and object relational mapper (ORM)
packages such as Django and EdgeDB.
packages such as SQLAlchemy and Django.
Most type checkers, linters and language servers have full support for
dataclasses. This proposal aims to generalize this functionality and
@ -214,6 +214,7 @@ customization of default behaviors:
eq_default: bool = True,
order_default: bool = False,
kw_only_default: bool = False,
transform_descriptor_types: bool = False,
field_descriptors: tuple[type | Callable[..., Any], ...] = (),
) -> Callable[[_T], _T]: ...
@ -229,6 +230,14 @@ customization of default behaviors:
assumed to be True or False if it is omitted by the caller. If not
specified, ``kw_only_default`` will default to False (the default
assumption for dataclass).
* ``transform_descriptor_types`` affects fields annotated with
descriptor types that define a ``__set__`` method. If True, the type
of each parameter on the synthesized ``__init__`` method
corresponding to such a field will be the type of the value
parameter to the descriptor's ``__set__`` method. If False, the
descriptor type will be used. If not specified,
``transform_descriptor_types`` will default to False (the default
behavior of dataclass).
* ``field_descriptors`` specifies a static list of supported classes
that describe fields. Some libraries also supply functions to
allocate instances of field descriptors, and those functions may
@ -334,6 +343,37 @@ Metaclass example
id: int
name: str
``transform_descriptor_types`` example
``````````````````````````````````````
Because ``transform_descriptor_types`` is set to ``True``, the
``target`` parameter on the synthesized ``__init__`` method will be of
type ``float`` (the type of ``__set__``\ 's ``value`` parameter)
instead of ``Descriptor``.
.. code-block:: python
@typing.dataclass_transform(transform_descriptor_types=True)
def create_model() -> Callable[[Type[_T]], Type[_T]]: ...
# We anticipate that most descriptor classes used with
# transform_descriptor_types will be generic with __set__ functions
# whose value parameters are based on the generic's type vars.
# However, this is not required.
class Descriptor:
def __get__(self, instance: object, owner: Any) -> int:
...
# The setter and getter can have different types (asymmetric).
# The setter's value type is used for the __init__ parameter.
# The getter's return type is ignored.
def __set__(self, instance: object, value: float):
...
@create_model
class CustomerModel:
target: Descriptor
Field descriptors
-----------------
@ -467,7 +507,8 @@ For example:
"eq_default": True,
"order_default": False,
"kw_only_default": False,
"field_descriptors": ()
"transform_descriptor_types": False,
"field_descriptors": (),
}
@ -557,6 +598,11 @@ If multiple ``dataclass_transform`` decorators are found, either on a
single function/class or within a class hierarchy, the resulting
behavior is undefined. Library authors should avoid these scenarios.
The ``__set__`` method on descriptors is not expected to be
overloaded. If such overloads are found when
``transform_descriptor_types`` is ``True``, the resulting behavior is
undefined.
Reference Implementation
========================
@ -657,6 +703,18 @@ users of Django would need to explicitly declare the ``id`` field.
This limitation may make it impractical to use the
``dataclass_transform`` mechanism with Django.
Class-wide default values
-------------------------
SQLAlchemy requested that we expose a way to specify that the default
value of all fields in the transformed class is None. It is typical
that all of their fields are optional, and None indicates that the
field is not set.
We chose not to support this feature, since it is specific to
SQLAlchemy. Users can manually set ``default=None`` on these fields
instead.
Open Issues
===========