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

allow field_serializer('*') (fix #8990) #9001

Merged
merged 6 commits into from Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 4 additions & 0 deletions docs/concepts/serialization.md
Expand Up @@ -224,6 +224,10 @@ print(Model(x='test value').model_dump_json())
#> {"x":"serialized test value"}
```

!!! note
A single serializer can also be called on all fields by passing the special value '*' to the [`@field_serializer`][pydantic.functional_serializers.field_serializer] decorator.


In addition, [`PlainSerializer`][pydantic.functional_serializers.PlainSerializer] and
[`WrapSerializer`][pydantic.functional_serializers.WrapSerializer] enable you to use a function to modify the output of serialization.

Expand Down
7 changes: 3 additions & 4 deletions pydantic/_internal/_generate_schema.py
Expand Up @@ -125,9 +125,8 @@ def check_validator_fields_against_field_name(
Returns:
`True` if field name is in validator fields, `False` otherwise.
"""
if isinstance(info, (ValidatorDecoratorInfo, FieldValidatorDecoratorInfo)):
sydney-runkle marked this conversation as resolved.
Show resolved Hide resolved
if '*' in info.fields:
return True
if '*' in info.fields:
return True
for v_field_name in info.fields:
if v_field_name == field:
return True
Expand All @@ -148,7 +147,7 @@ def check_decorator_fields_exist(decorators: Iterable[AnyFieldDecorator], fields
"""
fields = set(fields)
for dec in decorators:
if isinstance(dec.info, (ValidatorDecoratorInfo, FieldValidatorDecoratorInfo)) and '*' in dec.info.fields:
if '*' in dec.info.fields:
continue
if dec.info.check_fields is False:
continue
Expand Down
12 changes: 12 additions & 0 deletions tests/test_serialize.py
Expand Up @@ -560,6 +560,18 @@ def serializer1(v) -> str:
assert MySubModel(x=1234).model_dump() == {'x': '1234'}


def test_serialize_all_fields():
class MyModel(BaseModel):
x: int

@field_serializer('*')
@classmethod
def serialize_all(cls, v: Any):
return v * 2

assert MyModel(x=10).model_dump() == {'x': 20}


def int_ser_func_without_info1(v: int, expected: int) -> str:
return f'{v:,}'

Expand Down
48 changes: 48 additions & 0 deletions tests/test_validators.py
Expand Up @@ -842,6 +842,18 @@ def check_a_two(cls, v: Any):
Child(a='snap')


def test_validate_all():
class MyModel(BaseModel):
x: int

@field_validator('*')
@classmethod
def validate_all(cls, v: Any):
return v * 2

assert MyModel(x=10).x == 20


def test_validate_child_all():
with pytest.warns(PydanticDeprecatedSince20, match=V1_VALIDATOR_DEPRECATION_MATCH):

Expand All @@ -861,6 +873,22 @@ def check_a(cls, v: Any):
with pytest.raises(ValidationError):
Child(a='snap')

class Parent(BaseModel):
a: str

class Child(Parent):
@field_validator('*')
@classmethod
def check_a(cls, v: Any):
if 'foobar' not in v:
raise ValueError('"foobar" not found in a')
return v

assert Parent(a='this is not a child').a == 'this is not a child'
assert Child(a='this is foobar good').a == 'this is foobar good'
with pytest.raises(ValidationError):
Child(a='snap')


def test_validate_parent():
class Parent(BaseModel):
Expand Down Expand Up @@ -907,6 +935,26 @@ class Child(Parent):
with pytest.raises(ValidationError):
Child(a='snap')

class Parent(BaseModel):
a: str

@field_validator('*')
@classmethod
def check_a(cls, v: Any):
if 'foobar' not in v:
raise ValueError('"foobar" not found in a')
return v

class Child(Parent):
pass

assert Parent(a='this is foobar good').a == 'this is foobar good'
assert Child(a='this is foobar good').a == 'this is foobar good'
with pytest.raises(ValidationError):
Parent(a='snap')
with pytest.raises(ValidationError):
Child(a='snap')


def test_inheritance_keep():
class Parent(BaseModel):
Expand Down