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 resolution of forward refs in dataclass base classes that are not present in the subclass module namespace #8751

Merged
Show file tree
Hide file tree
Changes from 2 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
12 changes: 10 additions & 2 deletions pydantic/_internal/_generate_schema.py
Expand Up @@ -8,7 +8,7 @@
import sys
import typing
import warnings
from contextlib import contextmanager
from contextlib import ExitStack, contextmanager
from copy import copy, deepcopy
from enum import Enum
from functools import partial
Expand Down Expand Up @@ -1471,7 +1471,11 @@ def _dataclass_schema(
dataclass = origin

config = getattr(dataclass, '__pydantic_config__', None)
with self._config_wrapper_stack.push(config), self._types_namespace_stack.push(dataclass):
with self._config_wrapper_stack.push(config), ExitStack() as dataclass_bases_stack:
for dataclass_base in dataclass.__mro__:
if dataclasses.is_dataclass(dataclass_base):
dataclass_bases_stack.enter_context(self._types_namespace_stack.push(dataclass_base))
matsjoyce-refeyn marked this conversation as resolved.
Show resolved Hide resolved

core_config = self._config_wrapper.core_config(dataclass)

self = self._current_generate_schema
Expand Down Expand Up @@ -1540,6 +1544,10 @@ def _dataclass_schema(
self.defs.definitions[dataclass_ref] = self._post_process_generated_schema(schema)
return core_schema.definition_reference_schema(dataclass_ref)

# Type checkers seem to assume ExitStack may suppress exceptions and therefore
# control flow can exit the `with` block without returning.
assert False, 'Unreachable'
matsjoyce-refeyn marked this conversation as resolved.
Show resolved Hide resolved

def _callable_schema(self, function: Callable[..., Any]) -> core_schema.CallSchema:
"""Generate schema for a Callable.

Expand Down
51 changes: 51 additions & 0 deletions tests/test_dataclasses.py
Expand Up @@ -1622,6 +1622,57 @@ class D2:
]


@pytest.mark.parametrize('dataclass_decorator', [pydantic.dataclasses.dataclass, dataclasses.dataclass])
def test_base_dataclasses_annotations_resolving(create_module, dataclass_decorator: Callable):
@create_module
def module():
import dataclasses
from typing import NewType

OddInt = NewType('OddInt', int)

@dataclasses.dataclass
class D1:
d1: 'OddInt'

@dataclass_decorator
class D2(module.D1):
d2: int

assert TypeAdapter(D2).validate_python({'d1': 1, 'd2': 2}) == D2(d1=1, d2=2)


@pytest.mark.parametrize('dataclass_decorator', [pydantic.dataclasses.dataclass, dataclasses.dataclass])
def test_base_dataclasses_annotations_resolving_with_override(create_module, dataclass_decorator: Callable):
@create_module
def module1():
import dataclasses
from typing import NewType

IDType = NewType('IDType', int)

@dataclasses.dataclass
class D1:
db_id: 'IDType'

@create_module
def module2():
import dataclasses
from typing import NewType

IDType = NewType('IDType', str)

@dataclasses.dataclass
class D2:
db_id: 'IDType'

@dataclass_decorator
class D3(module1.D1, module2.D2):
...

assert TypeAdapter(D3).validate_python({'db_id': 42}) == D3(db_id=42)


@pytest.mark.skipif(sys.version_info < (3, 10), reason='kw_only is not available in python < 3.10')
def test_kw_only():
@pydantic.dataclasses.dataclass(kw_only=True)
Expand Down