Skip to content

Commit 5f660f0

Browse files
authored
fix borrow error when serializing recursive models (#764)
1 parent 3e7cc4f commit 5f660f0

File tree

5 files changed

+28
-13
lines changed

5 files changed

+28
-13
lines changed

src/serializers/infer.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ pub(crate) fn infer_to_python_known(
101101

102102
let serialize_with_serializer = || {
103103
let py_serializer = value.getattr(intern!(py, "__pydantic_serializer__"))?;
104-
let serializer: SchemaSerializer = py_serializer.extract()?;
104+
let serializer: PyRef<SchemaSerializer> = py_serializer.extract()?;
105105
let extra = serializer.build_extra(
106106
py,
107107
extra.mode,
@@ -464,7 +464,7 @@ pub(crate) fn infer_serialize_known<S: Serializer>(
464464
let py_serializer = value
465465
.getattr(intern!(py, "__pydantic_serializer__"))
466466
.map_err(py_err_se_err)?;
467-
let extracted_serializer: SchemaSerializer = py_serializer.extract().map_err(py_err_se_err)?;
467+
let extracted_serializer: PyRef<SchemaSerializer> = py_serializer.extract().map_err(py_err_se_err)?;
468468
let extra = extracted_serializer.build_extra(
469469
py,
470470
extra.mode,

src/serializers/mod.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::fmt::Debug;
2+
use std::sync::atomic::{AtomicUsize, Ordering};
23

34
use pyo3::prelude::*;
45
use pyo3::types::{PyBytes, PyDict};
@@ -26,11 +27,11 @@ mod shared;
2627
mod type_serializers;
2728

2829
#[pyclass(module = "pydantic_core._pydantic_core")]
29-
#[derive(Debug, Clone)]
30+
#[derive(Debug)]
3031
pub struct SchemaSerializer {
3132
serializer: CombinedSerializer,
3233
definitions: Vec<CombinedSerializer>,
33-
json_size: usize,
34+
expected_json_size: AtomicUsize,
3435
config: SerializationConfig,
3536
}
3637

@@ -80,7 +81,7 @@ impl SchemaSerializer {
8081
Ok(Self {
8182
serializer,
8283
definitions: definitions_builder.finish()?,
83-
json_size: 1024,
84+
expected_json_size: AtomicUsize::new(1024),
8485
config: SerializationConfig::from_config(config)?,
8586
})
8687
}
@@ -130,7 +131,7 @@ impl SchemaSerializer {
130131
exclude_unset = false, exclude_defaults = false, exclude_none = false, round_trip = false, warnings = true,
131132
fallback = None))]
132133
pub fn to_json(
133-
&mut self,
134+
&self,
134135
py: Python,
135136
value: &PyAny,
136137
indent: Option<usize>,
@@ -166,12 +167,12 @@ impl SchemaSerializer {
166167
exclude,
167168
&extra,
168169
indent,
169-
self.json_size,
170+
self.expected_json_size.load(Ordering::Relaxed),
170171
)?;
171172

172173
warnings.final_check(py)?;
173174

174-
self.json_size = bytes.len();
175+
self.expected_json_size.store(bytes.len(), Ordering::Relaxed);
175176
let py_bytes = PyBytes::new(py, &bytes);
176177
Ok(py_bytes.into())
177178
}

src/serializers/shared.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,11 +308,11 @@ pub(crate) fn to_json_bytes(
308308
exclude: Option<&PyAny>,
309309
extra: &Extra,
310310
indent: Option<usize>,
311-
json_size: usize,
311+
expected_json_size: usize,
312312
) -> PyResult<Vec<u8>> {
313313
let serializer = PydanticSerializer::new(value, serializer, include, exclude, extra);
314314

315-
let writer: Vec<u8> = Vec::with_capacity(json_size);
315+
let writer: Vec<u8> = Vec::with_capacity(expected_json_size);
316316
let bytes = match indent {
317317
Some(indent) => {
318318
let indent = vec![b' '; indent];

tests/serializers/test_functions.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -627,9 +627,7 @@ def bad_recursive(value):
627627
s.to_json(42)
628628
# insert_assert(str(exc_info.value))
629629
assert str(exc_info.value) == (
630-
'Error serializing to JSON: '
631-
'PydanticSerializationError: Error calling function `bad_recursive`: '
632-
'RuntimeError: Already mutably borrowed'
630+
'Error serializing to JSON: PydanticSerializationError: Error calling function `bad_recursive`: RecursionError'
633631
)
634632

635633

tests/serializers/test_model.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,22 @@ def test_model_allow_extra():
141141
assert j == b'{"bar":"more","foo":1,"c":3}'
142142

143143

144+
def test_model_recursive_in_extra():
145+
# See https://github.com/pydantic/pydantic/issues/6571
146+
147+
class Model(BasicModel):
148+
__slots__ = '__pydantic_extra__'
149+
150+
s = SchemaSerializer(
151+
core_schema.model_schema(
152+
Model, core_schema.model_fields_schema({}, extra_behavior='allow'), extra_behavior='allow'
153+
)
154+
)
155+
Model.__pydantic_serializer__ = s
156+
157+
assert s.to_json(Model(__pydantic_extra__=dict(other=Model(__pydantic_extra__={})))) == b'{"other":{}}'
158+
159+
144160
@pytest.mark.parametrize(
145161
'params',
146162
[

0 commit comments

Comments
 (0)