Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PEP 681: Move descriptor-field support to Rejected Ideas #2477

Merged
merged 3 commits into from
Mar 31, 2022
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 21 additions & 46 deletions pep-0681.rst
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,6 @@ 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]: ...

Expand All @@ -229,14 +228,6 @@ 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
Expand Down Expand Up @@ -342,37 +333,6 @@ 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
-----------------
Expand Down Expand Up @@ -506,7 +466,6 @@ For example:
"eq_default": True,
"order_default": False,
"kw_only_default": False,
"transform_descriptor_types": False,
"field_descriptors": (),
}

Expand Down Expand Up @@ -597,11 +556,6 @@ 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
========================
Expand Down Expand Up @@ -714,6 +668,26 @@ We chose not to support this feature, since it is specific to
SQLAlchemy. Users can manually set ``default=None`` on these fields
instead.

Descriptor-typed field support
------------------------------

We considered adding a boolean parameter on ``dataclass_transform``
to enable better support for fields with descriptor types, which is
common in SQLAlchemy. When enabled, the type of each parameter on the
synthesized ``__init__`` method corresponding to a descriptor-typed
field would be the type of the value parameter to the descriptor's
``__set__`` method rather than the descriptor type itself. Similarly,
when setting the field, the ``__set__`` value type would be expected.
And when getting the value of the field, its type would be expected to
match the return type of ``__get__``.

This idea was based on the belief that ``dataclass`` did not properly
support descriptor-typed fields. In fact it does, but type checkers
(at least mypy and pyright) did not reflect the runtime behavior which
led to our misunderstanding. For more details, see the
`Pyright bug <#pyright-descriptor-bug_>`__.


Open Issues
===========

Expand Down Expand Up @@ -754,6 +728,7 @@ References
.. _#kw-only-docs: https://docs.python.org/3/library/dataclasses.html#dataclasses.KW_ONLY
.. _#kw-only-issue: https://bugs.python.org/issue43532
.. _#class-var: https://docs.python.org/3/library/dataclasses.html#class-variables
.. _#pyright-descriptor-bug: https://github.com/microsoft/pyright/issues/3245

Copyright
=========
Expand Down