Skip to content

Commit e1e6bc7

Browse files
author
ornariece
committed
ability to pass context to serialization
1 parent a98b8b0 commit e1e6bc7

File tree

10 files changed

+81
-14
lines changed

10 files changed

+81
-14
lines changed

python/pydantic_core/_pydantic_core.pyi

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ class SchemaSerializer:
278278
round_trip: bool = False,
279279
warnings: bool = True,
280280
fallback: Callable[[Any], Any] | None = None,
281+
context: 'dict[str, Any] | None' = None,
281282
) -> Any:
282283
"""
283284
Serialize/marshal a Python object to a Python object including transforming and filtering data.
@@ -297,6 +298,8 @@ class SchemaSerializer:
297298
warnings: Whether to log warnings when invalid fields are encountered.
298299
fallback: A function to call when an unknown value is encountered,
299300
if `None` a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised.
301+
context: The context to use for serialization, this is passed to functional serializers as
302+
[`info.context`][pydantic_core.core_schema.SerializationInfo.context].
300303
301304
Raises:
302305
PydanticSerializationError: If serialization fails and no `fallback` function is provided.
@@ -318,6 +321,7 @@ class SchemaSerializer:
318321
round_trip: bool = False,
319322
warnings: bool = True,
320323
fallback: Callable[[Any], Any] | None = None,
324+
context: 'dict[str, Any] | None' = None,
321325
) -> bytes:
322326
"""
323327
Serialize a Python object to JSON including transforming and filtering data.
@@ -336,6 +340,8 @@ class SchemaSerializer:
336340
warnings: Whether to log warnings when invalid fields are encountered.
337341
fallback: A function to call when an unknown value is encountered,
338342
if `None` a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised.
343+
context: The context to use for serialization, this is passed to functional serializers as
344+
[`info.context`][pydantic_core.core_schema.SerializationInfo.context].
339345
340346
Raises:
341347
PydanticSerializationError: If serialization fails and no `fallback` function is provided.
@@ -358,6 +364,7 @@ def to_json(
358364
inf_nan_mode: Literal['null', 'constants'] = 'constants',
359365
serialize_unknown: bool = False,
360366
fallback: Callable[[Any], Any] | None = None,
367+
context: 'dict[str, Any] | None' = None,
361368
) -> bytes:
362369
"""
363370
Serialize a Python object to JSON including transforming and filtering data.
@@ -379,6 +386,8 @@ def to_json(
379386
`"<Unserializable {value_type} object>"` will be used.
380387
fallback: A function to call when an unknown value is encountered,
381388
if `None` a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised.
389+
context: The context to use for serialization, this is passed to functional serializers as
390+
[`info.context`][pydantic_core.core_schema.SerializationInfo.context].
382391
383392
Raises:
384393
PydanticSerializationError: If serialization fails and no `fallback` function is provided.
@@ -419,6 +428,7 @@ def to_jsonable_python(
419428
inf_nan_mode: Literal['null', 'constants'] = 'constants',
420429
serialize_unknown: bool = False,
421430
fallback: Callable[[Any], Any] | None = None,
431+
context: 'dict[str, Any] | None' = None,
422432
) -> Any:
423433
"""
424434
Serialize/marshal a Python object to a JSON-serializable Python object including transforming and filtering data.
@@ -440,6 +450,8 @@ def to_jsonable_python(
440450
`"<Unserializable {value_type} object>"` will be used.
441451
fallback: A function to call when an unknown value is encountered,
442452
if `None` a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised.
453+
context: The context to use for serialization, this is passed to functional serializers as
454+
[`info.context`][pydantic_core.core_schema.SerializationInfo.context].
443455
444456
Raises:
445457
PydanticSerializationError: If serialization fails and no `fallback` function is provided.

python/pydantic_core/core_schema.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ def include(self) -> IncExCall: ...
122122
@property
123123
def exclude(self) -> IncExCall: ...
124124

125+
@property
126+
def context(self) -> Any | None:
127+
"""Current serialization context."""
128+
125129
@property
126130
def mode(self) -> str: ...
127131

src/errors/validation_exception.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ impl ValidationError {
320320
include_input: bool,
321321
) -> PyResult<&'py PyString> {
322322
let state = SerializationState::new("iso8601", "utf8", "constants")?;
323-
let extra = state.extra(py, &SerMode::Json, true, false, false, true, None);
323+
let extra = state.extra(py, &SerMode::Json, true, false, false, true, None, None);
324324
let serializer = ValidationErrorSerializer {
325325
py,
326326
line_errors: &self.line_errors,

src/serializers/extra.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ impl SerializationState {
4545
round_trip: bool,
4646
serialize_unknown: bool,
4747
fallback: Option<&'py PyAny>,
48+
context: Option<&'py PyAny>,
4849
) -> Extra<'py> {
4950
Extra::new(
5051
py,
@@ -59,6 +60,7 @@ impl SerializationState {
5960
&self.rec_guard,
6061
serialize_unknown,
6162
fallback,
63+
context,
6264
)
6365
}
6466

@@ -90,6 +92,7 @@ pub(crate) struct Extra<'a> {
9092
pub field_name: Option<&'a str>,
9193
pub serialize_unknown: bool,
9294
pub fallback: Option<&'a PyAny>,
95+
pub context: Option<&'a PyAny>,
9396
}
9497

9598
impl<'a> Extra<'a> {
@@ -107,6 +110,7 @@ impl<'a> Extra<'a> {
107110
rec_guard: &'a SerRecursionState,
108111
serialize_unknown: bool,
109112
fallback: Option<&'a PyAny>,
113+
context: Option<&'a PyAny>,
110114
) -> Self {
111115
Self {
112116
mode,
@@ -124,6 +128,7 @@ impl<'a> Extra<'a> {
124128
field_name: None,
125129
serialize_unknown,
126130
fallback,
131+
context,
127132
}
128133
}
129134

@@ -182,6 +187,7 @@ pub(crate) struct ExtraOwned {
182187
field_name: Option<String>,
183188
serialize_unknown: bool,
184189
fallback: Option<PyObject>,
190+
context: Option<PyObject>,
185191
}
186192

187193
impl ExtraOwned {
@@ -201,6 +207,7 @@ impl ExtraOwned {
201207
field_name: extra.field_name.map(ToString::to_string),
202208
serialize_unknown: extra.serialize_unknown,
203209
fallback: extra.fallback.map(Into::into),
210+
context: extra.context.map(Into::into),
204211
}
205212
}
206213

@@ -221,6 +228,7 @@ impl ExtraOwned {
221228
field_name: self.field_name.as_deref(),
222229
serialize_unknown: self.serialize_unknown,
223230
fallback: self.fallback.as_ref().map(|m| m.as_ref(py)),
231+
context: self.context.as_ref().map(|m| m.as_ref(py)),
224232
}
225233
}
226234
}

src/serializers/infer.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ pub(crate) fn infer_to_python_known(
9999
extra.rec_guard,
100100
extra.serialize_unknown,
101101
extra.fallback,
102+
extra.context,
102103
);
103104
serializer.serializer.to_python(value, include, exclude, &extra)
104105
};
@@ -468,6 +469,7 @@ pub(crate) fn infer_serialize_known<S: Serializer>(
468469
extra.rec_guard,
469470
extra.serialize_unknown,
470471
extra.fallback,
472+
extra.context,
471473
);
472474
let pydantic_serializer =
473475
PydanticSerializer::new(value, &extracted_serializer.serializer, include, exclude, &extra);

src/serializers/mod.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ impl SchemaSerializer {
5555
rec_guard: &'a SerRecursionState,
5656
serialize_unknown: bool,
5757
fallback: Option<&'a PyAny>,
58+
context: Option<&'a PyAny>,
5859
) -> Extra<'b> {
5960
Extra::new(
6061
py,
@@ -69,6 +70,7 @@ impl SchemaSerializer {
6970
rec_guard,
7071
serialize_unknown,
7172
fallback,
73+
context,
7274
)
7375
}
7476
}
@@ -95,7 +97,7 @@ impl SchemaSerializer {
9597
#[allow(clippy::too_many_arguments)]
9698
#[pyo3(signature = (value, *, mode = None, include = None, exclude = None, by_alias = true,
9799
exclude_unset = false, exclude_defaults = false, exclude_none = false, round_trip = false, warnings = true,
98-
fallback = None))]
100+
fallback = None, context = None))]
99101
pub fn to_python(
100102
&self,
101103
py: Python,
@@ -110,6 +112,7 @@ impl SchemaSerializer {
110112
round_trip: bool,
111113
warnings: bool,
112114
fallback: Option<&PyAny>,
115+
context: Option<&PyAny>,
113116
) -> PyResult<PyObject> {
114117
let mode: SerMode = mode.into();
115118
let warnings = CollectWarnings::new(warnings);
@@ -126,6 +129,7 @@ impl SchemaSerializer {
126129
&rec_guard,
127130
false,
128131
fallback,
132+
context,
129133
);
130134
let v = self.serializer.to_python(value, include, exclude, &extra)?;
131135
warnings.final_check(py)?;
@@ -135,7 +139,7 @@ impl SchemaSerializer {
135139
#[allow(clippy::too_many_arguments)]
136140
#[pyo3(signature = (value, *, indent = None, include = None, exclude = None, by_alias = true,
137141
exclude_unset = false, exclude_defaults = false, exclude_none = false, round_trip = false, warnings = true,
138-
fallback = None))]
142+
fallback = None, context = None))]
139143
pub fn to_json(
140144
&self,
141145
py: Python,
@@ -150,6 +154,7 @@ impl SchemaSerializer {
150154
round_trip: bool,
151155
warnings: bool,
152156
fallback: Option<&PyAny>,
157+
context: Option<&PyAny>,
153158
) -> PyResult<PyObject> {
154159
let warnings = CollectWarnings::new(warnings);
155160
let rec_guard = SerRecursionState::default();
@@ -165,6 +170,7 @@ impl SchemaSerializer {
165170
&rec_guard,
166171
false,
167172
fallback,
173+
context,
168174
);
169175
let bytes = to_json_bytes(
170176
value,
@@ -213,7 +219,7 @@ impl SchemaSerializer {
213219
#[pyfunction]
214220
#[pyo3(signature = (value, *, indent = None, include = None, exclude = None, by_alias = true,
215221
exclude_none = false, round_trip = false, timedelta_mode = "iso8601", bytes_mode = "utf8",
216-
inf_nan_mode = "constants", serialize_unknown = false, fallback = None))]
222+
inf_nan_mode = "constants", serialize_unknown = false, fallback = None, context = None))]
217223
pub fn to_json(
218224
py: Python,
219225
value: &PyAny,
@@ -228,6 +234,7 @@ pub fn to_json(
228234
inf_nan_mode: &str,
229235
serialize_unknown: bool,
230236
fallback: Option<&PyAny>,
237+
context: Option<&PyAny>,
231238
) -> PyResult<PyObject> {
232239
let state = SerializationState::new(timedelta_mode, bytes_mode, inf_nan_mode)?;
233240
let extra = state.extra(
@@ -238,6 +245,7 @@ pub fn to_json(
238245
round_trip,
239246
serialize_unknown,
240247
fallback,
248+
context,
241249
);
242250
let serializer = type_serializers::any::AnySerializer.into();
243251
let bytes = to_json_bytes(value, &serializer, include, exclude, &extra, indent, 1024)?;
@@ -249,7 +257,7 @@ pub fn to_json(
249257
#[allow(clippy::too_many_arguments)]
250258
#[pyfunction]
251259
#[pyo3(signature = (value, *, include = None, exclude = None, by_alias = true, exclude_none = false, round_trip = false,
252-
timedelta_mode = "iso8601", bytes_mode = "utf8", inf_nan_mode = "constants", serialize_unknown = false, fallback = None))]
260+
timedelta_mode = "iso8601", bytes_mode = "utf8", inf_nan_mode = "constants", serialize_unknown = false, fallback = None, context = None))]
253261
pub fn to_jsonable_python(
254262
py: Python,
255263
value: &PyAny,
@@ -263,6 +271,7 @@ pub fn to_jsonable_python(
263271
inf_nan_mode: &str,
264272
serialize_unknown: bool,
265273
fallback: Option<&PyAny>,
274+
context: Option<&PyAny>,
266275
) -> PyResult<PyObject> {
267276
let state = SerializationState::new(timedelta_mode, bytes_mode, inf_nan_mode)?;
268277
let extra = state.extra(
@@ -273,6 +282,7 @@ pub fn to_jsonable_python(
273282
round_trip,
274283
serialize_unknown,
275284
fallback,
285+
context,
276286
);
277287
let v = infer::infer_to_python(value, include, exclude, &extra)?;
278288
state.final_check(py)?;

src/serializers/type_serializers/function.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,8 @@ struct SerializationInfo {
488488
include: Option<PyObject>,
489489
#[pyo3(get)]
490490
exclude: Option<PyObject>,
491+
#[pyo3(get)]
492+
context: Option<PyObject>,
491493
_mode: SerMode,
492494
#[pyo3(get)]
493495
by_alias: bool,
@@ -515,6 +517,7 @@ impl SerializationInfo {
515517
Some(field_name) => Ok(Self {
516518
include: include.map(|i| i.into_py(py)),
517519
exclude: exclude.map(|e| e.into_py(py)),
520+
context: extra.context.map(Into::into),
518521
_mode: extra.mode.clone(),
519522
by_alias: extra.by_alias,
520523
exclude_unset: extra.exclude_unset,
@@ -531,6 +534,7 @@ impl SerializationInfo {
531534
Ok(Self {
532535
include: include.map(|i| i.into_py(py)),
533536
exclude: exclude.map(|e| e.into_py(py)),
537+
context: extra.context.map(Into::into),
534538
_mode: extra.mode.clone(),
535539
by_alias: extra.by_alias,
536540
exclude_unset: extra.exclude_unset,
@@ -563,6 +567,9 @@ impl SerializationInfo {
563567
if let Some(ref exclude) = self.exclude {
564568
d.set_item("exclude", exclude)?;
565569
}
570+
if let Some(ref context) = self.context {
571+
d.set_item("context", context)?;
572+
}
566573
d.set_item("mode", self.mode(py))?;
567574
d.set_item("by_alias", self.by_alias)?;
568575
d.set_item("exclude_unset", self.exclude_unset)?;
@@ -574,7 +581,7 @@ impl SerializationInfo {
574581

575582
fn __repr__(&self, py: Python) -> PyResult<String> {
576583
Ok(format!(
577-
"SerializationInfo(include={}, exclude={}, mode='{}', by_alias={}, exclude_unset={}, exclude_defaults={}, exclude_none={}, round_trip={})",
584+
"SerializationInfo(include={}, exclude={}, context={}, mode='{}', by_alias={}, exclude_unset={}, exclude_defaults={}, exclude_none={}, round_trip={})",
578585
match self.include {
579586
Some(ref include) => include.as_ref(py).repr()?.to_str()?,
580587
None => "None",
@@ -583,6 +590,10 @@ impl SerializationInfo {
583590
Some(ref exclude) => exclude.as_ref(py).repr()?.to_str()?,
584591
None => "None",
585592
},
593+
match self.context {
594+
Some(ref context) => context.as_ref(py).repr()?.to_str()?,
595+
None => "None",
596+
},
586597
self._mode,
587598
py_bool(self.by_alias),
588599
py_bool(self.exclude_unset),

tests/serializers/test_functions.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,18 @@ def double(value, info):
122122
'round_trip': False,
123123
}
124124

125+
assert s.to_python(1, context='context') == 2
126+
# insert_assert(f_info)
127+
assert f_info == {
128+
'context': 'context',
129+
'mode': 'python',
130+
'by_alias': True,
131+
'exclude_unset': False,
132+
'exclude_defaults': False,
133+
'exclude_none': False,
134+
'round_trip': False,
135+
}
136+
125137

126138
def test_function_error():
127139
def raise_error(value, _info):
@@ -212,23 +224,27 @@ def append_args(value, info):
212224
)
213225
)
214226
assert s.to_python(123) == (
215-
"123 info=SerializationInfo(include=None, exclude=None, mode='python', by_alias=True, exclude_unset=False, "
227+
"123 info=SerializationInfo(include=None, exclude=None, context=None, mode='python', by_alias=True, exclude_unset=False, "
216228
'exclude_defaults=False, exclude_none=False, round_trip=False)'
217229
)
218230
assert s.to_python(123, mode='other') == (
219-
"123 info=SerializationInfo(include=None, exclude=None, mode='other', by_alias=True, exclude_unset=False, "
231+
"123 info=SerializationInfo(include=None, exclude=None, context=None, mode='other', by_alias=True, exclude_unset=False, "
220232
'exclude_defaults=False, exclude_none=False, round_trip=False)'
221233
)
222234
assert s.to_python(123, include={'x'}) == (
223-
"123 info=SerializationInfo(include={'x'}, exclude=None, mode='python', by_alias=True, exclude_unset=False, "
235+
"123 info=SerializationInfo(include={'x'}, exclude=None, context=None, mode='python', by_alias=True, exclude_unset=False, "
236+
'exclude_defaults=False, exclude_none=False, round_trip=False)'
237+
)
238+
assert s.to_python(123, context='context') == (
239+
"123 info=SerializationInfo(include=None, exclude=None, context='context', mode='python', by_alias=True, exclude_unset=False, "
224240
'exclude_defaults=False, exclude_none=False, round_trip=False)'
225241
)
226242
assert s.to_python(123, mode='json', exclude={1: {2}}) == (
227-
"123 info=SerializationInfo(include=None, exclude={1: {2}}, mode='json', by_alias=True, exclude_unset=False, "
243+
"123 info=SerializationInfo(include=None, exclude={1: {2}}, context=None, mode='json', by_alias=True, exclude_unset=False, "
228244
'exclude_defaults=False, exclude_none=False, round_trip=False)'
229245
)
230246
assert s.to_json(123) == (
231-
b"\"123 info=SerializationInfo(include=None, exclude=None, mode='json', by_alias=True, exclude_unset=False, "
247+
b"\"123 info=SerializationInfo(include=None, exclude=None, context=None, mode='json', by_alias=True, exclude_unset=False, "
232248
b'exclude_defaults=False, exclude_none=False, round_trip=False)"'
233249
)
234250

0 commit comments

Comments
 (0)