Skip to content
Merged
Changes from 3 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
54 changes: 43 additions & 11 deletions pep-0712.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ Type: Standards Track
Content-Type: text/x-rst
Created: 01-Jan-2023
Python-Version: 3.13
Post-History: `27-Dec-2022 <https://mail.python.org/archives/list/[email protected]/thread/NWZQIINJQZDOCZGO6TGCUP2PNW4PEKNY/>`__, `19-Jan-2023 <https://discuss.python.org/t/add-converter-to-dataclass-field/22956>`__
Post-History: `27-Dec-2022 <https://mail.python.org/archives/list/[email protected]/thread/NWZQIINJQZDOCZGO6TGCUP2PNW4PEKNY/>`__,
`19-Jan-2023 <https://discuss.python.org/t/add-converter-to-dataclass-field/22956>`__,

Abstract
========

:pep:`557` added :mod:`dataclasses` to the Python stdlib. :pep:`681` added
:func:`~py3.11:~typing.dataclass_transform` to help type checkers understand
:func:`~py3.11:typing.dataclass_transform` to help type checkers understand
several common dataclass-like libraries, such as attrs, Pydantic, and object
relational mapper (ORM) packages such as SQLAlchemy and Django.

Expand All @@ -24,7 +25,7 @@ user-provided conversion function.

Therefore, this PEP adds a ``converter`` parameter to :func:`dataclasses.field`
(along with the requisite changes to :class:`dataclasses.Field` and
:func:`~py3.11:~typing.dataclass_transform` to specify the function to use to
:func:`~py3.11:typing.dataclass_transform`) to specify the function to use to
convert the input value for each field to the representation to be stored in
the dataclass.

Expand Down Expand Up @@ -57,7 +58,7 @@ dataclass-like libraries provide support. Adding this feature to the standard
library means more users are able to opt-in to these benefits without requiring
third-party libraries. Additionally third-party libraries are able to clue
type-checkers into their own conversion semantics through added support in
:func:`~py3.11:~typing.dataclass_transform`, meaning users of those libraries
:func:`~py3.11:typing.dataclass_transform`, meaning users of those libraries
benefit as well.

Specification
Expand All @@ -78,7 +79,7 @@ Adding this parameter also implies the following changes:

* A ``converter`` attribute will be added to :class:`dataclasses.Field`.
* ``converter`` will be added to the field specifier parameters of arguments
provided to :func:`~py3.11:~typing.dataclass_transform`'s ``field`` parameter.
provided to :func:`~py3.11:typing.dataclass_transform`'s ``field`` parameter.

Example
'''''''
Expand All @@ -95,7 +96,7 @@ Example
converter=lambda names: tuple(map(str.lower, names))
)

# The default value is also converted, therefore the following is not a
# The default value is also converted; therefore the following is not a
# type error.
stock_image_path: pathlib.PurePosixPath = dataclasses.field(
converter=pathlib.PurePosixPath, default="assets/unknown.png"
Expand Down Expand Up @@ -143,7 +144,7 @@ Type-checking the default value
Because the ``default`` value is unconditionally converted using ``converter``,
if arguments for both ``converter`` and ``default`` are provided to
:func:`dataclasses.field`, the ``default`` argument's type should be checked
using the ``converter``'s unary argument's type.
using the type of the unary argument to the ``converter`` callable.

Type-checking the return type
'''''''''''''''''''''''''''''
Expand Down Expand Up @@ -225,11 +226,23 @@ Example
def converter(x: int, y: str) -> list: ...
def converter(x=..., y = ...): ...

# Type checkers should not error on the following, since the default value
# is type-checked against the converter's unary argument type if a converter
# is provided.
@dataclasses.dataclass
class Example:
# Although the default value is of type `str` and the field is declared to
# be of type `pathlib.Path`, this is not a type error because the default
# value will be converted.
tmpdir: pathlib.Path = dataclasses.field(default="/tmp", converter=pathlib.Path)



Backward Compatibility
======================

These changes don't introduce any compatibility problems since they
only introduce new features.
only introduce opt-in new features.

Security Implications
======================
Expand All @@ -243,6 +256,10 @@ Documentation and examples explaining the new parameter and behavior will be
added to the relevant sections of the docs site (primarily on
:mod:`dataclasses`) and linked from the *What's New* document.

The added documentation/examples will also cover the "common pitfalls" that
users of converters are likely to encounter. Such pitfalls include:
* Needing to handle ``None``/sentinel values.
* Needing to handle values that are already of the correct type.

Reference Implementation
========================
Expand All @@ -255,8 +272,8 @@ CPython support is implemented on `a branch in the author's fork <cpython-branch
Rejected Ideas
==============

Just adding "converter" to :func:`~py3.11:~typing.dataclass_transform`'s ``field_specifiers``
---------------------------------------------------------------------------------------------
Just adding "converter" to ``typing.dataclass_transform``'s ``field_specifiers``
--------------------------------------------------------------------------------

The idea of isolating this addition to
:func:`~py3.11:~typing.dataclass_transform` was briefly
Expand All @@ -266,6 +283,21 @@ to broaden this to :mod:`dataclasses` more generally.
Additionally, adding this to :mod:`dataclasses` ensures anyone can reap the
benefits without requiring additional libraries.

Not converting default values
-----------------------------

There are pros and cons with both converting and not converting default values.
Leaving default values as-is allows type-checkers and dataclass authors to
expect that the type of the default matches the type of the field. However,
converting default values has two large advantages:

1. Compatibility with ``attrs``. Attrs unconditionally uses the converter to
convert the default value.

2. Simpler defaults. Allowing the default value to have the same type as
user-provided values means dataclass authors get the same conveniences as
their callers.

Automatic conversion using the field's type
-------------------------------------------

Expand All @@ -275,7 +307,7 @@ One idea could be to allow the type of the field specified (e.g. ``str`` or
appear to be similar to this approach.

This works well for fairly simple types, but leads to ambiguity in expected
behavior for complex types such as generics. E.g. For ``tuple[int]`` it is
behavior for complex types such as generics. E.g. For ``tuple[int, ...]`` it is
ambiguous if the converter is supposed to simply convert an iterable to a tuple,
or if it is additionally supposed to convert each element type to ``int``.

Expand Down