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

Fix Generic Dataclass Fields Mutation Bug (when using TypeAdapter) #7435

Merged
merged 5 commits into from Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 11 additions & 2 deletions pydantic/_internal/_core_utils.py
@@ -1,7 +1,16 @@
from __future__ import annotations

from collections import defaultdict
from typing import Any, Callable, Hashable, Iterable, TypeVar, Union, cast
from typing import (
Any,
Callable,
Hashable,
Iterable,
TypeVar,
Union,
_GenericAlias, # type: ignore
cast,
)

from pydantic_core import CoreSchema, core_schema
from typing_extensions import TypeAliasType, TypeGuard, get_args
Expand Down Expand Up @@ -65,7 +74,7 @@ def get_type_ref(type_: type[Any], args_override: tuple[type[Any], ...] | None =
when creating generic models without needing to create a concrete class.
"""
origin = type_
args = args_override or ()
args = get_args(type_) if isinstance(type_, _GenericAlias) else (args_override or ())
generic_metadata = getattr(type_, '__pydantic_generic_metadata__', None)
if generic_metadata:
origin = generic_metadata['origin'] or origin
Expand Down
4 changes: 2 additions & 2 deletions pydantic/_internal/_generate_schema.py
Expand Up @@ -9,7 +9,7 @@
import typing
import warnings
from contextlib import contextmanager
from copy import copy
from copy import copy, deepcopy
from enum import Enum
from functools import partial
from inspect import Parameter, _ParameterKind, signature
Expand Down Expand Up @@ -1275,7 +1275,7 @@ def _dataclass_schema(
from ..dataclasses import is_pydantic_dataclass

if is_pydantic_dataclass(dataclass):
fields = dataclass.__pydantic_fields__
fields = deepcopy(dataclass.__pydantic_fields__)
if typevars_map:
for field in fields.values():
field.apply_typevars_map(typevars_map, self._types_namespace)
Expand Down
35 changes: 35 additions & 0 deletions tests/test_dataclasses.py
Expand Up @@ -2058,6 +2058,41 @@ class GenericDataclass(Generic[T]):
assert exc_info.value.errors(include_url=False) == output_value


def test_multiple_parametrized_generic_dataclasses():
T = TypeVar('T')

@pydantic.dataclasses.dataclass
class GenericDataclass(Generic[T]):
x: T

validator1 = pydantic.TypeAdapter(GenericDataclass[int])
validator2 = pydantic.TypeAdapter(GenericDataclass[str])

# verify that generic parameters are showing up in the type ref for generic dataclasses
# this can probably be removed if the schema changes in some way that makes this part of the test fail
assert '[int:' in validator1.core_schema['schema']['schema_ref']
assert '[str:' in validator2.core_schema['schema']['schema_ref']

assert validator1.validate_python({'x': 1}).x == 1
assert validator2.validate_python({'x': 'hello world'}).x == 'hello world'

with pytest.raises(ValidationError) as exc_info:
validator2.validate_python({'x': 1})
assert exc_info.value.errors(include_url=False) == [
{'input': 1, 'loc': ('x',), 'msg': 'Input should be a valid string', 'type': 'string_type'}
]
with pytest.raises(ValidationError) as exc_info:
validator1.validate_python({'x': 'hello world'})
assert exc_info.value.errors(include_url=False) == [
{
'input': 'hello world',
'loc': ('x',),
'msg': 'Input should be a valid integer, unable to parse string as an integer',
'type': 'int_parsing',
}
]


@pytest.mark.parametrize('dataclass_decorator', **dataclass_decorators(include_identity=True))
def test_pydantic_dataclass_preserves_metadata(dataclass_decorator: Callable[[Any], Any]) -> None:
@dataclass_decorator
Expand Down