8000 Fix dataclass module name (#5624) · pydantic/pydantic@3543649 · GitHub
[go: up one dir, main page]

Skip to content

Commit 3543649

Browse files
adriangbKludex
andauthored
Fix dataclass module name (#5624)
* Fix wrapped dataclass' __module__ * remove dead code Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
1 parent 2160096 commit 3543649

File tree

3 files changed

+60
-44
lines changed

3 files changed

+60
-44
lines changed

pydantic/_internal/_dataclasses.py

Lines changed: 7 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,11 @@
1212
from pydantic_core import ArgsKwargs, SchemaSerializer, SchemaValidator, core_schema
1313
from typing_extensions import TypeGuard
1414

15-
from ..errors import PydanticUndefinedAnnotation
1615
from ..fields import FieldInfo
1716
from . import _decorators, _typing_extra
1817
from ._fields import collect_dataclass_fields
1918
from ._generate_schema import GenerateSchema
2019
from ._generics import get_standard_typevars_map
21-
from ._model_construction import MockValidator
2220

2321
if typing.TYPE_CHECKING:
2422
from ..config import ConfigDict
@@ -55,57 +53,30 @@ def set_dataclass_fields(cls: type[StandardDataclass], types_namespace: dict[str
5553
def complete_dataclass(
5654
cls: type[Any],
5755
config_wrapper: ConfigWrapper,
58-
*,
59-
raise_errors: bool = True,
60-
types_namespace: dict[str, Any] | None = None,
61-
) -> bool:
56+
) -> None:
6257
"""
6358
Prepare a raw class to become a pydantic dataclass.
6459
65-
Returns `True` if the validation construction is successfully completed, else `False`.
66-
6760
This logic is called on a class which is yet to be wrapped in `dataclasses.dataclass()`.
6861
"""
69-
cls_name = cls.__name__
70-
7162
if hasattr(cls, '__post_init_post_parse__'):
7263
warnings.warn(
7364
'Support for `__post_init_post_parse__` has been dropped, the method will not be called', DeprecationWarning
7465
)
7566

76-
types_namespace = _typing_extra.get_cls_types_namespace(cls, types_namespace)
67+
types_namespace = _typing_extra.get_cls_types_namespace(cls)
7768
typevars_map = get_standard_typevars_map(cls)
7869
gen_schema = GenerateSchema(
7970
config_wrapper,
8071
types_namespace,
8172
typevars_map,
8273
)
8374

84-
try:
85-
get_core_schema = getattr(cls, '__get_pydantic_core_schema__', None)
86-
if get_core_schema:
87-
schema = get_core_schema(cls, partial(gen_schema.generate_schema, from_dunder_get_core_schema=False))
88-
else:
89-
schema = gen_schema.generate_schema(cls, False)
90-
except PydanticUndefinedAnnotation as e:
91-
if raise_errors:
92-
raise
93-
if config_wrapper.undefined_types_warning:
94-
config_warning_string = (
95-
f'`{cls_name}` has an undefined annotation: `{e.name}`. '
96-
f'It may be possible to resolve this by setting '
97-
f'undefined_types_warning=False in the config for `{cls_name}`.'
98-
)
99-
# FIXME UserWarning should not be raised here, but rather warned!
100-
raise UserWarning(config_warning_string)
101-
usage_warning_string = (
102-
f'`{cls_name}` is not fully defined; you should define `{e.name}`, then call'
103-
f' TODO! `methods.rebuild({cls_name})` before the first `{cls_name}` instance is created.' # <--- TODO
104-
)
105-
cls.__pydantic_validator__ = MockValidator( # type: ignore[assignment]
106-
usage_warning_string, code='dataclass-not-fully-defined'
107-
)
108-
return False
75+
get_core_schema = getattr(cls, '__get_pydantic_core_schema__', None)
76+
if get_core_schema:
77+
schema = get_core_schema(cls, partial(gen_schema.generate_schema, from_dunder_get_core_schema=False))
78+
else:
79+
schema = gen_schema.generate_schema(cls, from_dunder_get_core_schema=False)
10980

11081
core_config = config_wrapper.core_config(cls)
11182

@@ -137,8 +108,6 @@ def __init__(__dataclass_self__: PydanticDataclass, *args: Any, **kwargs: Any) -
137108
__init__.__qualname__ = f'{cls.__qualname__}.__init__'
138109
cls.__init__ = __init__ # type: ignore
139110

140-
return True
141-
142111

143112
def is_builtin_dataclass(_cls: type[Any]) -> TypeGuard[type[StandardDataclass]]:
144113
"""

pydantic/dataclasses.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ def create_dataclass(cls: type[Any]) -> type[PydanticDataclass]:
158158
TypeError: If a non-class value is provided.
159159
"""
160160

161+
original_cls = cls
162+
161163
config_wrapper = _config.ConfigWrapper(config)
162164
decorators = _decorators.DecoratorInfos.build(cls)
163165

@@ -192,9 +194,11 @@ def create_dataclass(cls: type[Any]) -> type[PydanticDataclass]:
192194
)
193195

194196
cls.__pydantic_decorators__ = decorators # type: ignore
197+
cls.__doc__ = original_doc
198+
cls.__module__ = original_cls.__module__
199+
cls.__qualname__ = original_cls.__qualname__
195200
_pydantic_dataclasses.set_dataclass_fields(cls)
196201
_pydantic_dataclasses.complete_dataclass(cls, config_wrapper)
197-
cls.__doc__ = original_doc
198202
return cls
199203

200204
if _cls is None:

tests/test_dataclasses.py

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1747,7 +1747,7 @@ class ValidatingModel(Model):
17471747
]
17481748

17491749

1750-
def dataclass_decorators():
1750+
def dataclass_decorators(identity: bool = False):
17511751
def combined(cls):
17521752
"""
17531753
Should be equivalent to:
@@ -1756,12 +1756,14 @@ def combined(cls):
17561756
"""
17571757
return pydantic.dataclasses.dataclass(dataclasses.dataclass(cls))
17581758

1759+
def identity_decorator(cls):
1760+
return cls
1761+
17591762
decorators = [pydantic.dataclasses.dataclass, dataclasses.dataclass, combined]
17601763
ids = ['pydantic', 'stdlib', 'combined']
1761-
# decorators = [pydantic.dataclasses.dataclass]
1762-
# ids = ['pydantic']
1763-
# decorators = [dataclasses.dataclass]
1764-
# ids = ['stdlib']
1764+
if identity:
1765+
decorators.append(identity_decorator)
1766+
ids.append('identity')
17651767
return {'argvalues': decorators, 'ids': ids}
17661768

17671769

@@ -1825,3 +1827,44 @@ class GenericDataclass(Generic[T]):
18251827
with pytest.raises(ValidationError) as exc_info:
18261828
validator.validate_python({'x': input_value})
18271829
assert exc_info.value.errors() == output_value
1830+
1831+
1832+
@pytest.mark.parametrize('dataclass_decorator', **dataclass_decorators(identity=True))
1833+
def test_pydantic_dataclass_preserves_metadata(dataclass_decorator: Callable[[Any], Any]) -> None:
1834+
@dataclass_decorator
1835+
class FooStd:
1836+
"""Docstring"""
1837+
1838+
FooPydantic = pydantic.dataclasses.dataclass(FooStd)
1839+
1840+
assert FooPydantic.__module__ == FooStd.__module__
1841+
assert FooPydantic.__name__ == FooStd.__name__
1842+
assert FooPydantic.__qualname__ == FooStd.__qualname__
1843+
1844+
1845+
def test_recursive_dataclasses_gh_4509(create_module) -> None:
1846+
@create_module
1847+
def module():
1848+
import dataclasses
1849+
1850+
import pydantic
1851+
1852+
@dataclasses.dataclass
1853+
class Recipe:
1854+
author: 'Cook'
1855+
1856+
@dataclasses.dataclass
1857+
class Cook:
1858+
recipes: list[Recipe]
1859+
1860+
@pydantic.dataclasses.dataclass
1861+
class Foo(Cook):
1862+
pass
1863+
1864+
gordon = module.Cook([])
1865+
1866+
burger = module.Recipe(author=gordon)
1867+
1868+
me = module.Foo([burger])
1869+
1870+
assert me.recipes == [burger]

0 commit comments

Comments
 (0)
0