Skip to content

Commit

Permalink
DOC-104: add note about preserving input types to migration guide (#6135
Browse files Browse the repository at this point in the history
)

Co-authored-by: Samuel Colvin <s@muelcolvin.com>
  • Loading branch information
adriangb and samuelcolvin committed Jun 15, 2023
1 parent 5349dde commit 9d93f28
Showing 1 changed file with 63 additions and 0 deletions.
63 changes: 63 additions & 0 deletions docs/migration.md
Expand Up @@ -180,6 +180,69 @@ Model(x=1)
To work around this, use an `IsInstance` validator (more details to come).
* Subclasses of built-ins won't validate into their subclass types; you'll need to use an `IsInstance` validator to validate these types.

### Input types are not preserved

In Pydantic V1 we made great efforts to preserve the types of all field inputs for generic collections when they were proper subtypes of the field annotations.
For example, given the annotation `Mapping[str, int]` if you passed in a `collection.Counter()` you'd get a `collection.Counter()` as the value.

Supporting this behavior in V2 would have negative performance implications for the general case (we'd have to check types every time) and would add a lot of complexity to validation. Further, even in V1 this behavior was inconsistent and partially broken: it did not work for most types (`str`, `UUID`, etc.) and even for generic collections it's impossible to re-build the original input correctly without a lot of special casing (consider `ChainMap`; rebuilding the input is necessary because we need to replace values after validation, e.g. if coercing strings to ints).

In Pydantic V2 we no longer attempt to preserve the input type.
Instead, we only promise that the output type will match the type annotations.
Going back to the `Mapping` example, we promise the output will be a valid `Mapping`, in practice it will be a plain `dict`.

```python
from typing import Mapping

from pydantic import TypeAdapter


class MyDict(dict):
pass


ta = TypeAdapter(Mapping[str, int])
v = ta.validate_python(MyDict())
print(type(v))
#> <class 'dict'>
```

If you want the output type to be a specific type, consider annotating it as such or implementing a custom validator:

```python
from typing import Any, Mapping, TypeVar

from typing_extensions import Annotated

from pydantic import (
TypeAdapter,
ValidationInfo,
ValidatorFunctionWrapHandler,
WrapValidator,
)


def restore_input_type(
value: Any, handler: ValidatorFunctionWrapHandler, _info: ValidationInfo
) -> Any:
return type(value)(handler(value))


T = TypeVar('T')
PreserveType = Annotated[T, WrapValidator(restore_input_type)]


ta = TypeAdapter(PreserveType[Mapping[str, int]])


class MyDict(dict):
pass


v = ta.validate_python(MyDict())
assert type(v) is MyDict
```

### Changes to Generic models

* While it does not raise an error at runtime yet, subclass checks for parametrized generics should no longer be used.
Expand Down

0 comments on commit 9d93f28

Please sign in to comment.