Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions python/pydantic_core/_pydantic_core.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ class SchemaSerializer:
exclude_unset: bool = False,
exclude_defaults: bool = False,
exclude_none: bool = False,
exclude_computed_fields: bool = False,
round_trip: bool = False,
warnings: bool | Literal['none', 'warn', 'error'] = True,
fallback: Callable[[Any], Any] | None = None,
Expand All @@ -324,6 +325,7 @@ class SchemaSerializer:
e.g. are not included in `__pydantic_fields_set__`.
exclude_defaults: Whether to exclude fields that are equal to their default value.
exclude_none: Whether to exclude fields that have a value of `None`.
exclude_computed_fields: Whether to exclude computed fields.
round_trip: Whether to enable serialization and validation round-trip support.
warnings: How to handle invalid fields. False/"none" ignores them, True/"warn" logs errors,
"error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError].
Expand Down Expand Up @@ -351,6 +353,7 @@ class SchemaSerializer:
exclude_unset: bool = False,
exclude_defaults: bool = False,
exclude_none: bool = False,
exclude_computed_fields: bool = False,
round_trip: bool = False,
warnings: bool | Literal['none', 'warn', 'error'] = True,
fallback: Callable[[Any], Any] | None = None,
Expand All @@ -372,6 +375,7 @@ class SchemaSerializer:
e.g. are not included in `__pydantic_fields_set__`.
exclude_defaults: Whether to exclude fields that are equal to their default value.
exclude_none: Whether to exclude fields that have a value of `None`.
exclude_computed_fields: Whether to exclude computed fields.
round_trip: Whether to enable serialization and validation round-trip support.
warnings: How to handle invalid fields. False/"none" ignores them, True/"warn" logs errors,
"error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError].
Expand Down
5 changes: 5 additions & 0 deletions python/pydantic_core/core_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,11 @@ def exclude_none(self) -> bool:
"""The `exclude_none` argument set during serialization."""
...

@property
def exclude_computed_fields(self) -> bool:
"""The `exclude_computed_fields` argument set during serialization."""
...

@property
def serialize_as_any(self) -> bool:
"""The `serialize_as_any` argument set during serialization."""
Expand Down
4 changes: 2 additions & 2 deletions src/serializers/computed_fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ impl ComputedFields {
convert_error: impl FnOnce(PyErr) -> E,
mut serialize: impl FnMut(ComputedFieldToSerialize<'a, 'py>) -> Result<(), E>,
) -> Result<(), E> {
if extra.round_trip {
// Do not serialize computed fields
// In round trip mode, exclude computed fields:
if extra.round_trip || extra.exclude_computed_fields {
return Ok(());
}

Expand Down
7 changes: 7 additions & 0 deletions src/serializers/extra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ impl SerializationState {
false,
false,
exclude_none,
false,
round_trip,
&self.config,
&self.rec_guard,
Expand All @@ -86,6 +87,7 @@ pub(crate) struct Extra<'a> {
pub exclude_unset: bool,
pub exclude_defaults: bool,
pub exclude_none: bool,
pub exclude_computed_fields: bool,
pub round_trip: bool,
pub config: &'a SerializationConfig,
pub rec_guard: &'a SerRecursionState,
Expand All @@ -112,6 +114,7 @@ impl<'a> Extra<'a> {
exclude_unset: bool,
exclude_defaults: bool,
exclude_none: bool,
exclude_computed_fields: bool,
round_trip: bool,
config: &'a SerializationConfig,
rec_guard: &'a SerRecursionState,
Expand All @@ -128,6 +131,7 @@ impl<'a> Extra<'a> {
exclude_unset,
exclude_defaults,
exclude_none,
exclude_computed_fields,
round_trip,
config,
rec_guard,
Expand Down Expand Up @@ -196,6 +200,7 @@ pub(crate) struct ExtraOwned {
exclude_unset: bool,
exclude_defaults: bool,
exclude_none: bool,
exclude_computed_fields: bool,
round_trip: bool,
config: SerializationConfig,
rec_guard: SerRecursionState,
Expand All @@ -217,6 +222,7 @@ impl ExtraOwned {
exclude_unset: extra.exclude_unset,
exclude_defaults: extra.exclude_defaults,
exclude_none: extra.exclude_none,
exclude_computed_fields: extra.exclude_computed_fields,
round_trip: extra.round_trip,
config: extra.config.clone(),
rec_guard: extra.rec_guard.clone(),
Expand All @@ -239,6 +245,7 @@ impl ExtraOwned {
exclude_unset: self.exclude_unset,
exclude_defaults: self.exclude_defaults,
exclude_none: self.exclude_none,
exclude_computed_fields: self.exclude_computed_fields,
round_trip: self.round_trip,
config: &self.config,
rec_guard: &self.rec_guard,
Expand Down
2 changes: 1 addition & 1 deletion src/serializers/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ impl GeneralFieldsSerializer {

if extra.check.enabled()
// If any of these are true we can't count fields
&& !(extra.exclude_defaults || extra.exclude_unset || extra.exclude_none || exclude.is_some())
&& !(extra.exclude_defaults || extra.exclude_unset || extra.exclude_none || extra.exclude_computed_fields || exclude.is_some())
// Check for missing fields, we can't have extra fields here
&& self.required_fields > used_req_fields
{
Expand Down
2 changes: 2 additions & 0 deletions src/serializers/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ pub(crate) fn infer_to_python_known(
extra.exclude_unset,
extra.exclude_defaults,
extra.exclude_none,
extra.exclude_computed_fields,
extra.round_trip,
extra.rec_guard,
extra.serialize_unknown,
Expand Down Expand Up @@ -496,6 +497,7 @@ pub(crate) fn infer_serialize_known<S: Serializer>(
extra.exclude_unset,
extra.exclude_defaults,
extra.exclude_none,
extra.exclude_computed_fields,
extra.round_trip,
extra.rec_guard,
extra.serialize_unknown,
Expand Down
14 changes: 10 additions & 4 deletions src/serializers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ impl SchemaSerializer {
exclude_unset: bool,
exclude_defaults: bool,
exclude_none: bool,
exclude_computed_fields: bool,
round_trip: bool,
rec_guard: &'a SerRecursionState,
serialize_unknown: bool,
Expand All @@ -75,6 +76,7 @@ impl SchemaSerializer {
exclude_unset,
exclude_defaults,
exclude_none,
exclude_computed_fields,
round_trip,
&self.config,
rec_guard,
Expand Down Expand Up @@ -108,8 +110,8 @@ impl SchemaSerializer {

#[allow(clippy::too_many_arguments)]
#[pyo3(signature = (value, *, mode = None, include = None, exclude = None, by_alias = None,
exclude_unset = false, exclude_defaults = false, exclude_none = false, round_trip = false, warnings = WarningsArg::Bool(true),
fallback = None, serialize_as_any = false, context = None))]
exclude_unset = false, exclude_defaults = false, exclude_none = false, exclude_computed_fields = false,
round_trip = false, warnings = WarningsArg::Bool(true), fallback = None, serialize_as_any = false, context = None))]
pub fn to_python(
&self,
py: Python,
Expand All @@ -121,6 +123,7 @@ impl SchemaSerializer {
exclude_unset: bool,
exclude_defaults: bool,
exclude_none: bool,
exclude_computed_fields: bool,
round_trip: bool,
warnings: WarningsArg,
fallback: Option<&Bound<'_, PyAny>>,
Expand All @@ -142,6 +145,7 @@ impl SchemaSerializer {
exclude_unset,
exclude_defaults,
exclude_none,
exclude_computed_fields,
round_trip,
&rec_guard,
false,
Expand All @@ -156,8 +160,8 @@ impl SchemaSerializer {

#[allow(clippy::too_many_arguments)]
#[pyo3(signature = (value, *, indent = None, ensure_ascii = false, include = None, exclude = None, by_alias = None,
exclude_unset = false, exclude_defaults = false, exclude_none = false, round_trip = false, warnings = WarningsArg::Bool(true),
fallback = None, serialize_as_any = false, context = None))]
exclude_unset = false, exclude_defaults = false, exclude_none = false, exclude_computed_fields = false,
round_trip = false, warnings = WarningsArg::Bool(true), fallback = None, serialize_as_any = false, context = None))]
pub fn to_json(
&self,
py: Python,
Expand All @@ -170,6 +174,7 @@ impl SchemaSerializer {
exclude_unset: bool,
exclude_defaults: bool,
exclude_none: bool,
exclude_computed_fields: bool,
round_trip: bool,
warnings: WarningsArg,
fallback: Option<&Bound<'_, PyAny>>,
Expand All @@ -190,6 +195,7 @@ impl SchemaSerializer {
exclude_unset,
exclude_defaults,
exclude_none,
exclude_computed_fields,
round_trip,
&rec_guard,
false,
Expand Down
8 changes: 7 additions & 1 deletion src/serializers/type_serializers/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,8 @@ struct SerializationInfo {
#[pyo3(get)]
exclude_none: bool,
#[pyo3(get)]
exclude_computed_fields: bool,
#[pyo3(get)]
round_trip: bool,
field_name: Option<String>,
#[pyo3(get)]
Expand All @@ -583,6 +585,7 @@ impl SerializationInfo {
exclude_unset: extra.exclude_unset,
exclude_defaults: extra.exclude_defaults,
exclude_none: extra.exclude_none,
exclude_computed_fields: extra.exclude_none,
round_trip: extra.round_trip,
field_name: Some(field_name.to_string()),
serialize_as_any: extra.serialize_as_any,
Expand All @@ -601,6 +604,7 @@ impl SerializationInfo {
exclude_unset: extra.exclude_unset,
exclude_defaults: extra.exclude_defaults,
exclude_none: extra.exclude_none,
exclude_computed_fields: extra.exclude_computed_fields,
round_trip: extra.round_trip,
field_name: None,
serialize_as_any: extra.serialize_as_any,
Expand Down Expand Up @@ -651,14 +655,15 @@ impl SerializationInfo {
d.set_item("exclude_unset", self.exclude_unset)?;
d.set_item("exclude_defaults", self.exclude_defaults)?;
d.set_item("exclude_none", self.exclude_none)?;
d.set_item("exclude_computed_fields", self.exclude_computed_fields)?;
d.set_item("round_trip", self.round_trip)?;
d.set_item("serialize_as_any", self.serialize_as_any)?;
Ok(d)
}

fn __repr__(&self, py: Python) -> PyResult<String> {
Ok(format!(
"SerializationInfo(include={}, exclude={}, context={}, mode='{}', by_alias={}, exclude_unset={}, exclude_defaults={}, exclude_none={}, round_trip={}, serialize_as_any={})",
"SerializationInfo(include={}, exclude={}, context={}, mode='{}', by_alias={}, exclude_unset={}, exclude_defaults={}, exclude_none={}, exclude_computed_fields={}, round_trip={}, serialize_as_any={})",
match self.include {
Some(ref include) => include.bind(py).repr()?.to_str()?.to_owned(),
None => "None".to_owned(),
Expand All @@ -676,6 +681,7 @@ impl SerializationInfo {
py_bool(self.exclude_unset),
py_bool(self.exclude_defaults),
py_bool(self.exclude_none),
py_bool(self.exclude_computed_fields),
py_bool(self.round_trip),
py_bool(self.serialize_as_any),
))
Expand Down
18 changes: 12 additions & 6 deletions tests/serializers/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def double(value, info):
'exclude_unset': False,
'exclude_defaults': False,
'exclude_none': False,
'exclude_computed_fields': False,
'round_trip': False,
'serialize_as_any': False,
}
Expand All @@ -85,6 +86,7 @@ def double(value, info):
'exclude_unset': False,
'exclude_defaults': False,
'exclude_none': False,
'exclude_computed_fields': False,
'round_trip': False,
'serialize_as_any': False,
}
Expand All @@ -97,6 +99,7 @@ def double(value, info):
'exclude_unset': False,
'exclude_defaults': False,
'exclude_none': False,
'exclude_computed_fields': False,
'round_trip': False,
'serialize_as_any': False,
}
Expand All @@ -109,6 +112,7 @@ def double(value, info):
'exclude_unset': True,
'exclude_defaults': False,
'exclude_none': False,
'exclude_computed_fields': False,
'round_trip': False,
'serialize_as_any': False,
}
Expand All @@ -123,6 +127,7 @@ def double(value, info):
'exclude_unset': False,
'exclude_defaults': False,
'exclude_none': False,
'exclude_computed_fields': False,
'round_trip': False,
'serialize_as_any': False,
}
Expand All @@ -136,6 +141,7 @@ def double(value, info):
'exclude_unset': False,
'exclude_defaults': False,
'exclude_none': False,
'exclude_computed_fields': False,
'round_trip': False,
'serialize_as_any': False,
}
Expand Down Expand Up @@ -231,27 +237,27 @@ def append_args(value, info):
)
assert s.to_python(123) == (
"123 info=SerializationInfo(include=None, exclude=None, context=None, mode='python', by_alias=False, exclude_unset=False, "
'exclude_defaults=False, exclude_none=False, round_trip=False, serialize_as_any=False)'
'exclude_defaults=False, exclude_none=False, exclude_computed_fields=False, round_trip=False, serialize_as_any=False)'
)
assert s.to_python(123, mode='other') == (
"123 info=SerializationInfo(include=None, exclude=None, context=None, mode='other', by_alias=False, exclude_unset=False, "
'exclude_defaults=False, exclude_none=False, round_trip=False, serialize_as_any=False)'
'exclude_defaults=False, exclude_none=False, exclude_computed_fields=False, round_trip=False, serialize_as_any=False)'
)
assert s.to_python(123, include={'x'}) == (
"123 info=SerializationInfo(include={'x'}, exclude=None, context=None, mode='python', by_alias=False, exclude_unset=False, "
'exclude_defaults=False, exclude_none=False, round_trip=False, serialize_as_any=False)'
'exclude_defaults=False, exclude_none=False, exclude_computed_fields=False, round_trip=False, serialize_as_any=False)'
)
assert s.to_python(123, context='context') == (
"123 info=SerializationInfo(include=None, exclude=None, context='context', mode='python', by_alias=False, exclude_unset=False, "
'exclude_defaults=False, exclude_none=False, round_trip=False, serialize_as_any=False)'
'exclude_defaults=False, exclude_none=False, exclude_computed_fields=False, round_trip=False, serialize_as_any=False)'
)
assert s.to_python(123, mode='json', exclude={1: {2}}) == (
"123 info=SerializationInfo(include=None, exclude={1: {2}}, context=None, mode='json', by_alias=False, exclude_unset=False, "
'exclude_defaults=False, exclude_none=False, round_trip=False, serialize_as_any=False)'
'exclude_defaults=False, exclude_none=False, exclude_computed_fields=False, round_trip=False, serialize_as_any=False)'
)
assert s.to_json(123) == (
b"\"123 info=SerializationInfo(include=None, exclude=None, context=None, mode='json', by_alias=False, exclude_unset=False, "
b'exclude_defaults=False, exclude_none=False, round_trip=False, serialize_as_any=False)"'
b'exclude_defaults=False, exclude_none=False, exclude_computed_fields=False, round_trip=False, serialize_as_any=False)"'
)


Expand Down
12 changes: 4 additions & 8 deletions tests/serializers/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@
import json
import platform
import warnings
from functools import cached_property
from random import randint
from typing import Any, ClassVar

try:
from functools import cached_property
except ImportError:
cached_property = None

import pytest
from dirty_equals import IsJson

Expand Down Expand Up @@ -504,7 +500,6 @@ def ser_x(self, v: Any, _) -> str:
assert s.to_python(Model(x=1000)) == {'x': '1_000'}


@pytest.mark.skipif(cached_property is None, reason='cached_property is not available')
def test_field_serializer_cached_property():
@dataclasses.dataclass
class Model:
Expand Down Expand Up @@ -709,6 +704,9 @@ def area(self) -> bytes:
assert s.to_python(Model(width=3, height=4), round_trip=True) == {'width': 3, 'height': 4}
assert s.to_json(Model(width=3, height=4), round_trip=True) == b'{"width":3,"height":4}'

assert s.to_python(Model(width=3, height=4), exclude_computed_fields=True) == {'width': 3, 'height': 4}
assert s.to_json(Model(width=3, height=4), exclude_computed_fields=True) == b'{"width":3,"height":4}'


def test_property_alias():
@dataclasses.dataclass
Expand Down Expand Up @@ -881,7 +879,6 @@ def area(self) -> int:
assert s.to_json(Model(3, 4), exclude_none=True, by_alias=True) == b'{"width":3,"height":4,"Area":12}'


@pytest.mark.skipif(cached_property is None, reason='cached_property is not available')
def test_cached_property_alias():
@dataclasses.dataclass
class Model:
Expand Down Expand Up @@ -996,7 +993,6 @@ def b(self):
assert s.to_json(Model(1), exclude={'b': [0]}) == b'{"a":1,"b":[2,"3"]}'


@pytest.mark.skipif(cached_property is None, reason='cached_property is not available')
def test_property_setter():
class Square:
side: float
Expand Down
Loading
Loading