Skip to content

Commit 460564d

Browse files
committed
Add exclude_computed_fields serialization option
1 parent 17cbe98 commit 460564d

File tree

10 files changed

+44
-16
lines changed

10 files changed

+44
-16
lines changed

python/pydantic_core/_pydantic_core.pyi

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ class SchemaSerializer:
304304
exclude_unset: bool = False,
305305
exclude_defaults: bool = False,
306306
exclude_none: bool = False,
307+
exclude_computed_fields: bool = False,
307308
round_trip: bool = False,
308309
warnings: bool | Literal['none', 'warn', 'error'] = True,
309310
fallback: Callable[[Any], Any] | None = None,
@@ -324,6 +325,7 @@ class SchemaSerializer:
324325
e.g. are not included in `__pydantic_fields_set__`.
325326
exclude_defaults: Whether to exclude fields that are equal to their default value.
326327
exclude_none: Whether to exclude fields that have a value of `None`.
328+
exclude_computed_fields: Whether to exclude computed fields.
327329
round_trip: Whether to enable serialization and validation round-trip support.
328330
warnings: How to handle invalid fields. False/"none" ignores them, True/"warn" logs errors,
329331
"error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError].
@@ -351,6 +353,7 @@ class SchemaSerializer:
351353
exclude_unset: bool = False,
352354
exclude_defaults: bool = False,
353355
exclude_none: bool = False,
356+
exclude_computed_fields: bool = False,
354357
round_trip: bool = False,
355358
warnings: bool | Literal['none', 'warn', 'error'] = True,
356359
fallback: Callable[[Any], Any] | None = None,
@@ -372,6 +375,7 @@ class SchemaSerializer:
372375
e.g. are not included in `__pydantic_fields_set__`.
373376
exclude_defaults: Whether to exclude fields that are equal to their default value.
374377
exclude_none: Whether to exclude fields that have a value of `None`.
378+
exclude_computed_fields: Whether to exclude computed fields.
375379
round_trip: Whether to enable serialization and validation round-trip support.
376380
warnings: How to handle invalid fields. False/"none" ignores them, True/"warn" logs errors,
377381
"error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError].

python/pydantic_core/core_schema.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,11 @@ def exclude_none(self) -> bool:
169169
"""The `exclude_none` argument set during serialization."""
170170
...
171171

172+
@property
173+
def exclude_computed_fields(self) -> bool:
174+
"""The `exclude_computed_fields` argument set during serialization."""
175+
...
176+
172177
@property
173178
def serialize_as_any(self) -> bool:
174179
"""The `serialize_as_any` argument set during serialization."""

src/serializers/computed_fields.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,8 @@ impl ComputedFields {
127127
convert_error: impl FnOnce(PyErr) -> E,
128128
mut serialize: impl FnMut(ComputedFieldToSerialize<'a, 'py>) -> Result<(), E>,
129129
) -> Result<(), E> {
130-
if extra.round_trip {
131-
// Do not serialize computed fields
130+
// In round trip mode, exclude computed fields:
131+
if extra.round_trip || extra.exclude_computed_fields {
132132
return Ok(());
133133
}
134134

src/serializers/extra.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ impl SerializationState {
6060
false,
6161
false,
6262
exclude_none,
63+
false,
6364
round_trip,
6465
&self.config,
6566
&self.rec_guard,
@@ -86,6 +87,7 @@ pub(crate) struct Extra<'a> {
8687
pub exclude_unset: bool,
8788
pub exclude_defaults: bool,
8889
pub exclude_none: bool,
90+
pub exclude_computed_fields: bool,
8991
pub round_trip: bool,
9092
pub config: &'a SerializationConfig,
9193
pub rec_guard: &'a SerRecursionState,
@@ -112,6 +114,7 @@ impl<'a> Extra<'a> {
112114
exclude_unset: bool,
113115
exclude_defaults: bool,
114116
exclude_none: bool,
117+
exclude_computed_fields: bool,
115118
round_trip: bool,
116119
config: &'a SerializationConfig,
117120
rec_guard: &'a SerRecursionState,
@@ -128,6 +131,7 @@ impl<'a> Extra<'a> {
128131
exclude_unset,
129132
exclude_defaults,
130133
exclude_none,
134+
exclude_computed_fields,
131135
round_trip,
132136
config,
133137
rec_guard,
@@ -196,6 +200,7 @@ pub(crate) struct ExtraOwned {
196200
exclude_unset: bool,
197201
exclude_defaults: bool,
198202
exclude_none: bool,
203+
exclude_computed_fields: bool,
199204
round_trip: bool,
200205
config: SerializationConfig,
201206
rec_guard: SerRecursionState,
@@ -217,6 +222,7 @@ impl ExtraOwned {
217222
exclude_unset: extra.exclude_unset,
218223
exclude_defaults: extra.exclude_defaults,
219224
exclude_none: extra.exclude_none,
225+
exclude_computed_fields: extra.exclude_computed_fields,
220226
round_trip: extra.round_trip,
221227
config: extra.config.clone(),
222228
rec_guard: extra.rec_guard.clone(),
@@ -239,6 +245,7 @@ impl ExtraOwned {
239245
exclude_unset: self.exclude_unset,
240246
exclude_defaults: self.exclude_defaults,
241247
exclude_none: self.exclude_none,
248+
exclude_computed_fields: self.exclude_computed_fields,
242249
round_trip: self.round_trip,
243250
config: &self.config,
244251
rec_guard: &self.rec_guard,

src/serializers/fields.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ impl GeneralFieldsSerializer {
226226

227227
if extra.check.enabled()
228228
// If any of these are true we can't count fields
229-
&& !(extra.exclude_defaults || extra.exclude_unset || extra.exclude_none || exclude.is_some())
229+
&& !(extra.exclude_defaults || extra.exclude_unset || extra.exclude_none || extra.exclude_computed_fields || exclude.is_some())
230230
// Check for missing fields, we can't have extra fields here
231231
&& self.required_fields > used_req_fields
232232
{

src/serializers/infer.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ pub(crate) fn infer_to_python_known(
102102
extra.exclude_unset,
103103
extra.exclude_defaults,
104104
extra.exclude_none,
105+
extra.exclude_computed_fields,
105106
extra.round_trip,
106107
extra.rec_guard,
107108
extra.serialize_unknown,
@@ -496,6 +497,7 @@ pub(crate) fn infer_serialize_known<S: Serializer>(
496497
extra.exclude_unset,
497498
extra.exclude_defaults,
498499
extra.exclude_none,
500+
extra.exclude_computed_fields,
499501
extra.round_trip,
500502
extra.rec_guard,
501503
extra.serialize_unknown,

src/serializers/mod.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ impl SchemaSerializer {
6060
exclude_unset: bool,
6161
exclude_defaults: bool,
6262
exclude_none: bool,
63+
exclude_computed_fields: bool,
6364
round_trip: bool,
6465
rec_guard: &'a SerRecursionState,
6566
serialize_unknown: bool,
@@ -75,6 +76,7 @@ impl SchemaSerializer {
7576
exclude_unset,
7677
exclude_defaults,
7778
exclude_none,
79+
exclude_computed_fields,
7880
round_trip,
7981
&self.config,
8082
rec_guard,
@@ -108,8 +110,8 @@ impl SchemaSerializer {
108110

109111
#[allow(clippy::too_many_arguments)]
110112
#[pyo3(signature = (value, *, mode = None, include = None, exclude = None, by_alias = None,
111-
exclude_unset = false, exclude_defaults = false, exclude_none = false, round_trip = false, warnings = WarningsArg::Bool(true),
112-
fallback = None, serialize_as_any = false, context = None))]
113+
exclude_unset = false, exclude_defaults = false, exclude_none = false, exclude_computed_fields = false,
114+
round_trip = false, warnings = WarningsArg::Bool(true), fallback = None, serialize_as_any = false, context = None))]
113115
pub fn to_python(
114116
&self,
115117
py: Python,
@@ -121,6 +123,7 @@ impl SchemaSerializer {
121123
exclude_unset: bool,
122124
exclude_defaults: bool,
123125
exclude_none: bool,
126+
exclude_computed_fields: bool,
124127
round_trip: bool,
125128
warnings: WarningsArg,
126129
fallback: Option<&Bound<'_, PyAny>>,
@@ -142,6 +145,7 @@ impl SchemaSerializer {
142145
exclude_unset,
143146
exclude_defaults,
144147
exclude_none,
148+
exclude_computed_fields,
145149
round_trip,
146150
&rec_guard,
147151
false,
@@ -156,8 +160,8 @@ impl SchemaSerializer {
156160

157161
#[allow(clippy::too_many_arguments)]
158162
#[pyo3(signature = (value, *, indent = None, ensure_ascii = false, include = None, exclude = None, by_alias = None,
159-
exclude_unset = false, exclude_defaults = false, exclude_none = false, round_trip = false, warnings = WarningsArg::Bool(true),
160-
fallback = None, serialize_as_any = false, context = None))]
163+
exclude_unset = false, exclude_defaults = false, exclude_none = false, exclude_computed_fields = false,
164+
round_trip = false, warnings = WarningsArg::Bool(true), fallback = None, serialize_as_any = false, context = None))]
161165
pub fn to_json(
162166
&self,
163167
py: Python,
@@ -170,6 +174,7 @@ impl SchemaSerializer {
170174
exclude_unset: bool,
171175
exclude_defaults: bool,
172176
exclude_none: bool,
177+
exclude_computed_fields: bool,
173178
round_trip: bool,
174179
warnings: WarningsArg,
175180
fallback: Option<&Bound<'_, PyAny>>,
@@ -190,6 +195,7 @@ impl SchemaSerializer {
190195
exclude_unset,
191196
exclude_defaults,
192197
exclude_none,
198+
exclude_computed_fields,
193199
round_trip,
194200
&rec_guard,
195201
false,

src/serializers/type_serializers/function.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,8 @@ struct SerializationInfo {
559559
#[pyo3(get)]
560560
exclude_none: bool,
561561
#[pyo3(get)]
562+
exclude_computed_fields: bool,
563+
#[pyo3(get)]
562564
round_trip: bool,
563565
field_name: Option<String>,
564566
#[pyo3(get)]
@@ -583,6 +585,7 @@ impl SerializationInfo {
583585
exclude_unset: extra.exclude_unset,
584586
exclude_defaults: extra.exclude_defaults,
585587
exclude_none: extra.exclude_none,
588+
exclude_computed_fields: extra.exclude_none,
586589
round_trip: extra.round_trip,
587590
field_name: Some(field_name.to_string()),
588591
serialize_as_any: extra.serialize_as_any,
@@ -601,6 +604,7 @@ impl SerializationInfo {
601604
exclude_unset: extra.exclude_unset,
602605
exclude_defaults: extra.exclude_defaults,
603606
exclude_none: extra.exclude_none,
607+
exclude_computed_fields: extra.exclude_computed_fields,
604608
round_trip: extra.round_trip,
605609
field_name: None,
606610
serialize_as_any: extra.serialize_as_any,
@@ -651,14 +655,15 @@ impl SerializationInfo {
651655
d.set_item("exclude_unset", self.exclude_unset)?;
652656
d.set_item("exclude_defaults", self.exclude_defaults)?;
653657
d.set_item("exclude_none", self.exclude_none)?;
658+
d.set_item("exclude_computed_fields", self.exclude_computed_fields)?;
654659
d.set_item("round_trip", self.round_trip)?;
655660
d.set_item("serialize_as_any", self.serialize_as_any)?;
656661
Ok(d)
657662
}
658663

659664
fn __repr__(&self, py: Python) -> PyResult<String> {
660665
Ok(format!(
661-
"SerializationInfo(include={}, exclude={}, context={}, mode='{}', by_alias={}, exclude_unset={}, exclude_defaults={}, exclude_none={}, round_trip={}, serialize_as_any={})",
666+
"SerializationInfo(include={}, exclude={}, context={}, mode='{}', by_alias={}, exclude_unset={}, exclude_defaults={}, exclude_none={}, exclude_computed_fields={}, round_trip={}, serialize_as_any={})",
662667
match self.include {
663668
Some(ref include) => include.bind(py).repr()?.to_str()?.to_owned(),
664669
None => "None".to_owned(),
@@ -676,6 +681,7 @@ impl SerializationInfo {
676681
py_bool(self.exclude_unset),
677682
py_bool(self.exclude_defaults),
678683
py_bool(self.exclude_none),
684+
py_bool(self.exclude_computed_fields),
679685
py_bool(self.round_trip),
680686
py_bool(self.serialize_as_any),
681687
))

tests/serializers/test_model.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,10 @@
22
import json
33
import platform
44
import warnings
5+
from functools import cached_property
56
from random import randint
67
from typing import Any, ClassVar
78

8-
try:
9-
from functools import cached_property
10-
except ImportError:
11-
cached_property = None
12-
139
import pytest
1410
from dirty_equals import IsJson
1511

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

506502

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

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

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

883881

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

998995

999-
@pytest.mark.skipif(cached_property is None, reason='cached_property is not available')
1000996
def test_property_setter():
1001997
class Square:
1002998
side: float

tests/test.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ dump_json_input_2 = {'a': 'something'}
151151
false,
152152
false,
153153
false,
154+
false,
154155
WarningsArg::Bool(false),
155156
None,
156157
false,
@@ -173,6 +174,7 @@ dump_json_input_2 = {'a': 'something'}
173174
false,
174175
false,
175176
false,
177+
false,
176178
WarningsArg::Bool(false),
177179
None,
178180
false,

0 commit comments

Comments
 (0)