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

Update Dataclasses docs #6470

Merged
merged 12 commits into from Jul 11, 2023
1 change: 1 addition & 0 deletions changes/6470-tpdorsey.md
@@ -0,0 +1 @@
Docs: document new dataclases features, add links to TypeAdapter documentation.
31 changes: 18 additions & 13 deletions docs/migration.md
Expand Up @@ -145,23 +145,27 @@ The following properties have been removed from or changed in `Field`:

### Changes to dataclasses

Pydantic [dataclasses](usage/dataclasses.md) continue to be useful for enabling the data validation on standard
dataclasses without having to subclass `BaseModel`. However, we have made some changes to the behavior of Pydantic
dataclasses in V2:
tpdorsey marked this conversation as resolved.
Show resolved Hide resolved

* When used as fields, dataclasses (Pydantic or vanilla) no longer accept tuples as validation inputs; dicts should be
used instead.
* The `__post_init__` in Pydantic dataclasses will now be called _after_ validation, rather than before.
* As a result, the `__post_init_post_parse__` method would have become redundant, so has been removed.
* We no longer support `extra='allow'` for Pydantic dataclasses, where extra fields passed to the initializer would be
tpdorsey marked this conversation as resolved.
Show resolved Hide resolved
tpdorsey marked this conversation as resolved.
Show resolved Hide resolved
stored as extra attributes on the dataclass. `extra='ignore'` is still supported for the purpose of ignoring
unexpected fields while parsing data, they just won't be stored on the instance.
stored as extra attributes on the dataclass. `extra='ignore'` is still supported for the purpose of ignoring
unexpected fields while parsing data, they just won't be stored on the instance.
* Pydantic dataclasses no longer have an attribute `__pydantic_model__`, and no longer use an underlying `BaseModel`
to perform validation or provide other functionality.
to perform validation or provide other functionality.
* To perform validation, generate a JSON schema, or make use of
any other functionality that may have required `__pydantic_model__` in V1, you should now wrap the dataclass
with a `TypeAdapter` (discussed more below) and make use of its methods.
* [TODO: Add link to TypeAdapter documentation. You can find example usage in `tests/test_type_adapter.py`.]
any other functionality that may have required `__pydantic_model__` in V1, you should now wrap the dataclass
with a [`TypeAdapter`](usage/models.md#typeadapter) ([discussed more below](#introduction-of-typeadapter)) and
make use of its methods.
* In Pydantic V1, if you used a vanilla (i.e., non-Pydantic) dataclass as a field, the config of the parent type would
be used as though it was the config for the dataclass itself as well. In Pydantic V2, this is no longer the case.
* [TODO: Need to specify how to override the config used for vanilla dataclass; possibly need to add functionality?]

be used as though it was the config for the dataclass itself as well. In Pydantic V2, this is no longer the case.
* In Pydantic V2, to override the config like you would with a `BaseModel`, you can use the `config` parameter
tpdorsey marked this conversation as resolved.
Show resolved Hide resolved
on the `dataclass` decorator. See [Dataclass Config](usage/dataclasses.md#dataclass-config) for examples.
tpdorsey marked this conversation as resolved.
Show resolved Hide resolved
tpdorsey marked this conversation as resolved.
Show resolved Hide resolved

### Changes to config

Expand Down Expand Up @@ -589,10 +593,11 @@ Pydantic V1 had weak support for validating or serializing non-`BaseModel` types
To work with them, you had to either create a "root" model or use the utility functions in `pydantic.tools`
(namely, `parse_obj_as` and `schema_of`).

In Pydantic V2 this is _a lot_ easier: the `TypeAdapter` class lets you create an object with methods for validating,
serializing, and producing JSON schemas for arbitrary types. This serves as a complete replacement for `parse_obj_as`
and `schema_of` (which are now deprecated), and also covers some of the use cases of "root" models
(`RootModel`, discussed above, covers the others).
In Pydantic V2 this is _a lot_ easier: the [`TypeAdapter`](usage/models.md#typeadapter) class lets you create an object
with methods for validating, serializing, and producing JSON schemas for arbitrary types.
This serves as a complete replacement for `parse_obj_as` and `schema_of` (which are now deprecated),
and also covers some of the use cases of "root" models. ([`RootModel`](usage/models.md#rootmodel-and-custom-root-types),
[discussed above](#changes-to-pydanticbasemodel), covers the others.)

```python
from typing import List
Expand Down
61 changes: 38 additions & 23 deletions docs/usage/dataclasses.md
Expand Up @@ -25,18 +25,26 @@ User(id=42, name='John Doe', signup_ts=datetime.datetime(2032, 6, 21, 12, 0))
```

!!! note
Keep in mind that `pydantic.dataclasses.dataclass` is **not** a replacement for `pydantic.BaseModel` (with a small
difference in how [initialization hooks](#initialize-hooks) work). `pydantic.dataclasses.dataclass` provides a
similar functionality to `dataclasses.dataclass` with the addition of Pydantic validation. There are cases where
subclassing `pydantic.BaseModel` is the better choice.
Keep in mind that `pydantic.dataclasses.dataclass` is **not** a replacement for `pydantic.BaseModel`.
`pydantic.dataclasses.dataclass` provides a similar functionality to `dataclasses.dataclass` with the addition of
Pydantic validation.
There are cases where subclassing `pydantic.BaseModel` is the better choice.

For more information and discussion see
[pydantic/pydantic#710](https://github.com/pydantic/pydantic/issues/710).

You can use all the standard _pydantic_ field types. Note, however, that arguments passed to constructor will be copied in order to perform validation and, where necessary coercion.
Some differences between Pydantic dataclasses and `BaseModel` include:

The schema can be accessed by [TypeAdapter](models.md#typeadapter).
Also, fields that require a `default_factory` can be specified by either a `pydantic.Field` or a `dataclasses.field`.
* How [initialization hooks](#initialization-hooks) work
* [JSON dumping](#json-dumping)

You can use all the standard Pydantic field types. Note, however, that arguments passed to constructor will be copied in
order to perform validation and, where necessary coercion.

To perform validation or generate a JSON schema on a Pydantic dataclass, you should now wrap the dataclass
with a [`TypeAdapter`](models.md#typeadapter) and make use of its methods.

Fields that require a `default_factory` can be specified by either a `pydantic.Field` or a `dataclasses.field`.
tpdorsey marked this conversation as resolved.
Show resolved Hide resolved

```py
import dataclasses
Expand Down Expand Up @@ -101,10 +109,13 @@ keyword argument `config` which has the same meaning as [model_config](model_con
For more information about combining validators with dataclasses, see
[dataclass validators](validators.md#dataclass-validators).

## Dataclass Config
## Dataclass config

If you want to modify the `config` like you would with a `BaseModel`, you have two options:

* Apply config to the dataclass decorator as a dict
* Use `ConfigDict` as the config

```py
from pydantic import ConfigDict
from pydantic.dataclasses import dataclass
Expand All @@ -126,11 +137,12 @@ class MyDataclass2:

1. You can read more about `validate_assignment` in [model_config](model_config.md#validate-assignment).

!!! warning
After v1.10, _pydantic_ dataclasses support `Config.extra` but some default behaviour of stdlib dataclasses
may prevail. For example, when `print`ing a _pydantic_ dataclass with allowed extra fields, it will still
use the `__str__` method of stdlib dataclass and show only the required fields.
This may be improved further in the future.
!!! note
Pydantic dataclasses do not support [`extra='allow'`](model_config/#extra-attributes), where extra fields passed
to the initializer would be stored as extra attributes on the dataclass.
Comment on lines +138 to +139
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @tpdorsey! Could you help me understand what you meant by this diff? It looks to me like the current support for extra="allow" is the same as it was after #2557 (the PR where the removed copy was introduced), but I'm wondering if I'm missing something.

With extra="allow", extra values passed to __init__ still end up in the instantiated object, but just aren't surfaced via __str__. Example script:

from pydantic.dataclasses import dataclass

@dataclass(config=dict(extra="allow"))
class Example:
    ...

obj = Example(a=1, b=2)
print(obj)
print(f"Extra: a={obj.a}, b={obj.b}")

This script yields the following output when running with the latest pydantic==2.3.0:

Example()
Extra: a=1, b=2

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, Could you please make an issue or ask this in a GitHub discussion?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick response, @hramezani — opened #7362 to discuss.


`extra='ignore'` is still supported for the purpose of ignoring
unexpected fields while parsing data; they just won't be stored on the instance.

## Nested dataclasses

Expand All @@ -156,13 +168,13 @@ print(navbar)
#> Navbar(button=NavbarButton(href=Url('https://example.com/')))
```

Dataclasses attributes can be populated by tuples, dictionaries or instances of the dataclass itself.
When used as fields, dataclasses (Pydantic or vanilla) should use dicts as validation inputs.

## Stdlib dataclasses and _pydantic_ dataclasses
## Stdlib dataclasses and Pydantic dataclasses

### Inherit from stdlib dataclasses

Stdlib dataclasses (nested or not) can also be inherited and _pydantic_ will automatically validate
Stdlib dataclasses (nested or not) can also be inherited and Pydantic will automatically validate
all the inherited fields.

```py
Expand Down Expand Up @@ -203,8 +215,8 @@ except pydantic.ValidationError as e:

### Use of stdlib dataclasses with `BaseModel`

Bear in mind that stdlib dataclasses (nested or not) are **automatically converted** into _pydantic_
dataclasses when mixed with `BaseModel`! Furthermore the generated _pydantic_ dataclass will have
Bear in mind that stdlib dataclasses (nested or not) are **automatically converted** into Pydantic
dataclasses when mixed with `BaseModel`! Furthermore the generated Pydantic dataclass will have
the **exact same configuration** (`order`, `frozen`, ...) as the original one.

```py
Expand Down Expand Up @@ -260,8 +272,8 @@ except dataclasses.FrozenInstanceError as e:

### Use custom types

Since stdlib dataclasses are automatically converted to add validation using
custom types may cause some unexpected behaviour.
Since stdlib dataclasses are automatically converted to add validation, using
custom types may cause some unexpected behavior.
tpdorsey marked this conversation as resolved.
Show resolved Hide resolved
In this case you can simply add `arbitrary_types_allowed` in the config!

```py
Expand Down Expand Up @@ -317,10 +329,10 @@ print(repr(m))
#> Model(dc=DC(a=ArbitraryType(value=3), b='qwe'), other='other')
```

## Initialize hooks
## Initialization hooks
tpdorsey marked this conversation as resolved.
Show resolved Hide resolved

When you initialize a dataclass, it is possible to execute code *before* or *after* validation
with the help of `model_validator` decorator.
with the help of the [`@model_validator`](validators.md#model-validators) decorator `mode` parameter.

```py
from typing import Any, Dict
Expand Down Expand Up @@ -360,6 +372,9 @@ class User:
user = User(**{'birth': {'year': 1995, 'month': 3, 'day': 2}})
```

The `__post_init__` in Pydantic dataclasses is called _after_ validation, rather than before.


```py requires="3.8"
from dataclasses import InitVar
from pathlib import Path
Expand Down Expand Up @@ -394,7 +409,7 @@ methods decorated with `model_validator`.

## JSON dumping

_Pydantic_ dataclasses do not feature a `.model_dump_json()` function. To dump them as JSON, you will need to
Pydantic dataclasses do not feature a `.model_dump_json()` function. To dump them as JSON, you will need to
make use of the [RootModel](models.md#rootmodel-and-custom-root-types) as follows:

```py output="json"
Expand Down
2 changes: 1 addition & 1 deletion pydantic/dataclasses.py
Expand Up @@ -107,7 +107,7 @@ def dataclass(
kw_only: bool = False,
slots: bool = False,
) -> Callable[[type[_T]], type[PydanticDataclass]] | type[PydanticDataclass]:
"""A decorator used to create a Pydantic-enhanced dataclass, similar to the standard Python `dataclasses`,
"""A decorator used to create a Pydantic-enhanced dataclass, similar to standard Python `dataclasses`,
tpdorsey marked this conversation as resolved.
Show resolved Hide resolved
tpdorsey marked this conversation as resolved.
Show resolved Hide resolved
but with added validation.

This function should be used similarly to `dataclasses.dataclass`.
Expand Down