Skip to content

Commit 9ba8acd

Browse files
authored
Fix to_json function (#577)
1 parent 119b265 commit 9ba8acd

File tree

4 files changed

+74
-27
lines changed

4 files changed

+74
-27
lines changed

src/serializers/extra.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -268,10 +268,6 @@ pub(crate) struct CollectWarnings {
268268
}
269269

270270
impl CollectWarnings {
271-
pub(crate) fn is_active(&self) -> bool {
272-
self.active
273-
}
274-
275271
pub(crate) fn new(active: bool) -> Self {
276272
Self {
277273
active,

src/serializers/infer.rs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use serde::ser::{Error, Serialize, SerializeMap, SerializeSeq, Serializer};
1313
use crate::build_tools::{py_err, safe_repr};
1414
use crate::serializers::errors::SERIALIZATION_ERR_MARKER;
1515
use crate::serializers::filter::SchemaFilter;
16-
use crate::serializers::shared::PydanticSerializer;
16+
use crate::serializers::shared::{PydanticSerializer, TypeSerializer};
1717
use crate::serializers::SchemaSerializer;
1818
use crate::url::{PyMultiHostUrl, PyUrl};
1919

@@ -100,20 +100,20 @@ pub(crate) fn infer_to_python_known(
100100
let serialize_with_serializer = |value: &PyAny, is_model: bool| {
101101
if let Ok(py_serializer) = value.getattr(intern!(py, "__pydantic_serializer__")) {
102102
if let Ok(serializer) = py_serializer.extract::<SchemaSerializer>() {
103-
return serializer.to_python(
103+
let extra = serializer.build_extra(
104104
py,
105-
value,
106-
extra.mode.to_object(py).extract(py)?,
107-
include,
108-
exclude,
105+
extra.mode,
109106
extra.by_alias,
107+
extra.warnings,
110108
extra.exclude_unset,
111109
extra.exclude_defaults,
112110
extra.exclude_none,
113111
extra.round_trip,
114-
extra.warnings.is_active(),
112+
extra.rec_guard,
113+
extra.serialize_unknown,
115114
extra.fallback,
116115
);
116+
return serializer.serializer.to_python(value, include, exclude, &extra);
117117
}
118118
}
119119
// Fallback to dict serialization if `__pydantic_serializer__` is not set.
@@ -408,10 +408,24 @@ pub(crate) fn infer_serialize_known<S: Serializer>(
408408

409409
macro_rules! serialize_with_serializer {
410410
($py_serializable:expr, $is_model:expr) => {{
411-
if let Ok(py_serializer) = value.getattr(intern!($py_serializable.py(), "__pydantic_serializer__")) {
411+
let py = $py_serializable.py();
412+
if let Ok(py_serializer) = value.getattr(intern!(py, "__pydantic_serializer__")) {
412413
if let Ok(extracted_serializer) = py_serializer.extract::<SchemaSerializer>() {
414+
let extra = extracted_serializer.build_extra(
415+
py,
416+
extra.mode,
417+
extra.by_alias,
418+
extra.warnings,
419+
extra.exclude_unset,
420+
extra.exclude_defaults,
421+
extra.exclude_none,
422+
extra.round_trip,
423+
extra.rec_guard,
424+
extra.serialize_unknown,
425+
extra.fallback,
426+
);
413427
let pydantic_serializer =
414-
PydanticSerializer::new(value, &extracted_serializer.serializer, include, exclude, extra);
428+
PydanticSerializer::new(value, &extracted_serializer.serializer, include, exclude, &extra);
415429
return pydantic_serializer.serialize(serializer);
416430
}
417431
}

src/serializers/mod.rs

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,40 @@ pub struct SchemaSerializer {
3333
config: SerializationConfig,
3434
}
3535

36+
impl SchemaSerializer {
37+
#[allow(clippy::too_many_arguments)]
38+
pub(crate) fn build_extra<'b, 'a: 'b>(
39+
&'b self,
40+
py: Python<'a>,
41+
mode: &'a SerMode,
42+
by_alias: bool,
43+
warnings: &'a CollectWarnings,
44+
exclude_unset: bool,
45+
exclude_defaults: bool,
46+
exclude_none: bool,
47+
round_trip: bool,
48+
rec_guard: &'a SerRecursionGuard,
49+
serialize_unknown: bool,
50+
fallback: Option<&'a PyAny>,
51+
) -> Extra<'b> {
52+
Extra::new(
53+
py,
54+
mode,
55+
&self.definitions,
56+
by_alias,
57+
warnings,
58+
exclude_unset,
59+
exclude_defaults,
60+
exclude_none,
61+
round_trip,
62+
&self.config,
63+
rec_guard,
64+
serialize_unknown,
65+
fallback,
66+
)
67+
}
68+
}
69+
3670
#[pymethods]
3771
impl SchemaSerializer {
3872
#[new]
@@ -72,17 +106,15 @@ impl SchemaSerializer {
72106
let mode: SerMode = mode.into();
73107
let warnings = CollectWarnings::new(warnings);
74108
let rec_guard = SerRecursionGuard::default();
75-
let extra = Extra::new(
109+
let extra = self.build_extra(
76110
py,
77111
&mode,
78-
&self.definitions,
79112
by_alias,
80113
&warnings,
81114
exclude_unset,
82115
exclude_defaults,
83116
exclude_none,
84117
round_trip,
85-
&self.config,
86118
&rec_guard,
87119
false,
88120
fallback,
@@ -113,17 +145,15 @@ impl SchemaSerializer {
113145
) -> PyResult<PyObject> {
114146
let warnings = CollectWarnings::new(warnings);
115147
let rec_guard = SerRecursionGuard::default();
116-
let extra = Extra::new(
148+
let extra = self.build_extra(
117149
py,
118150
&SerMode::Json,
119-
&self.definitions,
120151
by_alias,
121152
&warnings,
122153
exclude_unset,
123154
exclude_defaults,
124155
exclude_none,
125156
round_trip,
126-
&self.config,
127157
&rec_guard,
128158
false,
129159
fallback,

tests/test_json.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
import re
3+
from typing import List
34

45
import pytest
56
from dirty_equals import IsList
@@ -239,30 +240,36 @@ def test_to_jsonable_python_fallback():
239240

240241
def test_to_jsonable_python_schema_serializer():
241242
class Foobar:
242-
def __init__(self, my_foo: int, my_bar: str):
243+
def __init__(self, my_foo: int, my_inners: List['Foobar']):
243244
self.my_foo = my_foo
244-
self.my_bar = my_bar
245+
self.my_inners = my_inners
245246

247+
# force a recursive model to ensure we exercise the transfer of definitions from the loaded
248+
# serializer
246249
c = core_schema.model_schema(
247250
Foobar,
248251
core_schema.typed_dict_schema(
249252
{
250253
'my_foo': core_schema.typed_dict_field(core_schema.int_schema(), serialization_alias='myFoo'),
251-
'my_bar': core_schema.typed_dict_field(core_schema.str_schema(), serialization_alias='myBar'),
254+
'my_inners': core_schema.typed_dict_field(
255+
core_schema.list_schema(core_schema.definition_reference_schema('foobar')),
256+
serialization_alias='myInners',
257+
),
252258
}
253259
),
260+
ref='foobar',
254261
)
255262
v = SchemaValidator(c)
256263
s = SchemaSerializer(c)
257264

258265
Foobar.__pydantic_validator__ = v
259266
Foobar.__pydantic_serializer__ = s
260267

261-
instance = Foobar(my_foo=1, my_bar='a')
262-
assert to_jsonable_python(instance) == {'myFoo': 1, 'myBar': 'a'}
263-
assert to_jsonable_python(instance, by_alias=False) == {'my_foo': 1, 'my_bar': 'a'}
264-
assert to_json(instance) == b'{"myFoo":1,"myBar":"a"}'
265-
assert to_json(instance, by_alias=False) == b'{"my_foo":1,"my_bar":"a"}'
268+
instance = Foobar(my_foo=1, my_inners=[Foobar(my_foo=2, my_inners=[])])
269+
assert to_jsonable_python(instance) == {'myFoo': 1, 'myInners': [{'myFoo': 2, 'myInners': []}]}
270+
assert to_jsonable_python(instance, by_alias=False) == {'my_foo': 1, 'my_inners': [{'my_foo': 2, 'my_inners': []}]}
271+
assert to_json(instance) == b'{"myFoo":1,"myInners":[{"myFoo":2,"myInners":[]}]}'
272+
assert to_json(instance, by_alias=False) == b'{"my_foo":1,"my_inners":[{"my_foo":2,"my_inners":[]}]}'
266273

267274

268275
def test_cycle_same():

0 commit comments

Comments
 (0)