8000 DOC-104: add note about preserving input types to migration guide (#6… · pydantic/pydantic@9d93f28 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9d93f28

Browse files
DOC-104: add note about preserving input types to migration guide (#6135)
Co-authored-by: Samuel Colvin <s@muelcolvin.com>
1 parent 5349dde commit 9d93f28

File tree

1 file changed

+63
-0
lines changed

1 file changed

+63
-0
lines changed

docs/migration.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,69 @@ Model(x=1)
180180
To work around this, use an `IsInstance` validator (more details to come).
181181
* Subclasses of built-ins won't validate into their subclass types; you'll need to use an `IsInstance` validator to validate these types.
182182

183+
### Input types are not preserved
184+
185+
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.
186+
For example, given the annotation `Mapping[str, int]` if you passed in a `collection.Counter()` you'd get a `collection.Counter()` as the value.
187+
188+
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).
189+
190+
In Pydantic V2 we no longer attempt to preserve the input type.
191+
Instead, we only promise that the output type will match the type annotations.
192+
Going back to the `Mapping` example, we promise the output will be a valid `Mapping`, in practice it will be a plain `dict`.
193+
194+
```python
195+
from typing import Mapping
196+
197+
from pydantic import TypeAdapter
198+
199+
200+
class MyDict(dict):
201+
pass
202+
203+
204+
ta = TypeAdapter(Mapping[str, int])
205+
v = ta.validate_python(MyDict())
206+
print(type(v))
207+
#> <class 'dict'>
208+
```
209+
210+
If you want the output type to be a specific type, consider annotating it as such or implementing a custom validator:
211+
212+
```python
213+
from typing import Any, Mapping, TypeVar
214+
215+
from typing_extensions import Annotated
216+
217+
from pydantic import (
218+
TypeAdapter,
219+
ValidationInfo,
220+
ValidatorFunctionWrapHandler,
221+
WrapValidator,
222+
)
223+
224+
225+
def restore_input_type(
226+
value: Any, handler: ValidatorFunctionWrapHandler, _info: ValidationInfo
227+
) -> Any:
228+
return type(value)(handler(value))
229+
230+
231+
T = TypeVar('T')
232+
PreserveType = Annotated[T, WrapValidator(restore_input_type)]
233+
234+
235+
ta = TypeAdapter(PreserveType[Mapping[str, int]])
236+
237+
238+
class MyDict(dict):
239+
pass
240+
241+
242+
v = ta.validate_python(MyDict())
243+
assert type(v) is MyDict
244+
```
245+
183246
### Changes to Generic models
184247

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

0 commit comments

Comments
 (0)
0