Skip to content

Commit

Permalink
fix: Support pydantic.Field(repr=False) in dataclasses
Browse files Browse the repository at this point in the history
Why

What
- Modified `make_pydantic_fields_compatible` to address `repr`
- Added test for `repr` support

Notes
  • Loading branch information
tigeryy2 committed Jan 7, 2024
1 parent e4fa099 commit 179c6f4
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 27 deletions.
49 changes: 30 additions & 19 deletions pydantic/dataclasses.py
Expand Up @@ -143,28 +143,39 @@ def dataclass(

if sys.version_info >= (3, 10):
kwargs = dict(kw_only=kw_only, slots=slots)

def make_pydantic_fields_compatible(cls: type[Any]) -> None:
"""Make sure that stdlib `dataclasses` understands `Field` kwargs like `kw_only`
To do that, we simply change
`x: int = pydantic.Field(..., kw_only=True)`
into
`x: int = dataclasses.field(default=pydantic.Field(..., kw_only=True), kw_only=True)`
"""
for field_name in cls.__annotations__:
try:
field_value = getattr(cls, field_name)
except AttributeError:
# no default value has been set for this field
continue
if isinstance(field_value, FieldInfo) and field_value.kw_only:
setattr(cls, field_name, dataclasses.field(default=field_value, kw_only=True))

else:
kwargs = {}

def make_pydantic_fields_compatible(_) -> None:
return None
def make_pydantic_fields_compatible(cls: type[Any]) -> None:
"""Make sure that stdlib `dataclasses` understands `Field` kwargs like `kw_only`
To do that, we simply change
`x: int = pydantic.Field(..., kw_only=True)`
into
`x: int = dataclasses.field(default=pydantic.Field(..., kw_only=True), kw_only=True)`
"""
for field_name in cls.__annotations__:
try:
field_value = getattr(cls, field_name)
except AttributeError:
# no default value has been set for this field
continue

# Process only if this is an instance of `FieldInfo`.
if not isinstance(field_value, FieldInfo):
continue

# Initialize arguments for the standard `dataclasses.field`.
field_args: dict = {'default': field_value}

# Handle `kw_only` for Python 3.10+
if sys.version_info >= (3, 10) and field_value.kw_only:
field_args['kw_only'] = True

# Set `repr` attribute if it's explicitly specified to be not `True`.
if field_value.repr is not True:
field_args['repr'] = field_value.repr

setattr(cls, field_name, dataclasses.field(**field_args))

def create_dataclass(cls: type[Any]) -> type[PydanticDataclass]:
"""Create a Pydantic dataclass from a regular dataclass.
Expand Down
45 changes: 37 additions & 8 deletions tests/test_dataclasses.py
Expand Up @@ -522,16 +522,27 @@ class User:
id: int
other: Dict[str, str] = dataclasses.field(default_factory=lambda: {'John': 'Joey'})

user = User(id=123)
assert user.id == 123
# assert user.other == {'John': 'Joey'}
fields = user.__pydantic_fields__
@pydantic.dataclasses.dataclass
class User2:
id: int
other: Dict[str, str] = pydantic.dataclasses.Field(default_factory=lambda: {'John': 'Joey'})

assert fields['id'].is_required() is True
assert repr(fields['id'].default) == 'PydanticUndefined'
user1 = User(id=123)
user2 = User2(id=123)

assert fields['other'].is_required() is False
assert fields['other'].default_factory() == {'John': 'Joey'}
def validate_user(user):
assert user.id == 123
assert user.other == {'John': 'Joey'}
fields = user.__pydantic_fields__

assert fields['id'].is_required() is True
assert repr(fields['id'].default) == 'PydanticUndefined'

assert fields['other'].is_required() is False
assert fields['other'].default_factory() == {'John': 'Joey'}

validate_user(user1)
validate_user(user2)


def test_default_factory_singleton_field():
Expand Down Expand Up @@ -1647,6 +1658,24 @@ class B(A):
assert B(1, y=2, z=3) == B(x=1, y=2, z=3)


def test_repr_false():
@pydantic.dataclasses.dataclass
class A:
visible_field: str
hidden_field: str = pydantic.dataclasses.Field(repr=False)

@pydantic.dataclasses.dataclass
class B:
visible_field: str
hidden_field: str = dataclasses.field(repr=False)

assert "hidden_field='excluded'" not in repr(B(visible_field='included', hidden_field='excluded'))

instance = A(visible_field='this_should_be_included', hidden_field='this_should_not_be_included')
assert "visible_field='this_should_be_included'" in repr(instance)
assert "hidden_field='this_should_not_be_included'" not in repr(instance)


def dataclass_decorators(include_identity: bool = False, exclude_combined: bool = False):
decorators = [pydantic.dataclasses.dataclass, dataclasses.dataclass]
ids = ['pydantic', 'stdlib']
Expand Down

0 comments on commit 179c6f4

Please sign in to comment.