Skip to content

Commit b384a37

Browse files
authored
adding to_jsonable_python method (#500)
1 parent eab7e5d commit b384a37

File tree

6 files changed

+77
-4
lines changed

6 files changed

+77
-4
lines changed

pydantic_core/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
ValidationError,
1717
__version__,
1818
to_json,
19+
to_jsonable_python,
1920
)
2021
from .core_schema import CoreConfig, CoreSchema, CoreSchemaType
2122

@@ -48,6 +49,7 @@
4849
'PydanticSerializationError',
4950
'PydanticSerializationUnexpectedValue',
5051
'to_json',
52+
'to_jsonable_python',
5153
)
5254

5355

pydantic_core/_pydantic_core.pyi

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,17 @@ def to_json(
108108
bytes_mode: Literal['utf8', 'base64'] = 'utf8',
109109
serialize_unknown: bool = False,
110110
) -> bytes: ...
111+
def to_jsonable_python(
112+
value: Any,
113+
*,
114+
include: IncEx = None,
115+
exclude: IncEx = None,
116+
exclude_none: bool = False,
117+
round_trip: bool = False,
118+
timedelta_mode: Literal['iso8601', 'float'] = 'iso8601',
119+
bytes_mode: Literal['utf8', 'base64'] = 'utf8',
120+
serialize_unknown: bool = False,
121+
) -> Any: ...
111122

112123
class Url:
113124
@property

src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ pub use self::url::{PyMultiHostUrl, PyUrl};
2626
pub use args_kwargs::ArgsKwargs;
2727
pub use build_tools::SchemaError;
2828
pub use errors::{list_all_errors, PydanticCustomError, PydanticKnownError, PydanticOmit, ValidationError};
29-
pub use serializers::{to_json, PydanticSerializationError, PydanticSerializationUnexpectedValue, SchemaSerializer};
29+
pub use serializers::{
30+
to_json, to_jsonable_python, PydanticSerializationError, PydanticSerializationUnexpectedValue, SchemaSerializer,
31+
};
3032
pub use validators::SchemaValidator;
3133

3234
pub fn get_version() -> String {
@@ -56,6 +58,7 @@ fn _pydantic_core(_py: Python, m: &PyModule) -> PyResult<()> {
5658
m.add_class::<ArgsKwargs>()?;
5759
m.add_class::<SchemaSerializer>()?;
5860
m.add_function(wrap_pyfunction!(to_json, m)?)?;
61+
m.add_function(wrap_pyfunction!(to_jsonable_python, m)?)?;
5962
m.add_function(wrap_pyfunction!(list_all_errors, m)?)?;
6063
Ok(())
6164
}

src/serializers/infer.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,13 @@ pub(crate) fn infer_to_python_known(
179179
}
180180
PyList::new(py, items).into_py(py)
181181
}
182-
ObType::Unknown => return Err(unknown_type_error(value)),
182+
ObType::Unknown => {
183+
return if extra.serialize_unknown {
184+
Ok(serialize_unknown(value).into_py(py))
185+
} else {
186+
Err(unknown_type_error(value))
187+
};
188+
}
183189
},
184190
_ => match ob_type {
185191
ObType::Tuple => {

src/serializers/mod.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,43 @@ pub fn to_json(
197197
Ok(py_bytes.into())
198198
}
199199

200+
#[allow(clippy::too_many_arguments)]
201+
#[pyfunction]
202+
#[pyo3(signature = (value, *, include = None, exclude = None, exclude_none = false, round_trip = false,
203+
timedelta_mode = None, bytes_mode = None, serialize_unknown = false))]
204+
pub fn to_jsonable_python(
205+
py: Python,
206+
value: &PyAny,
207+
include: Option<&PyAny>,
208+
exclude: Option<&PyAny>,
209+
exclude_none: Option<bool>,
210+
round_trip: Option<bool>,
211+
timedelta_mode: Option<&str>,
212+
bytes_mode: Option<&str>,
213+
serialize_unknown: Option<bool>,
214+
) -> PyResult<PyObject> {
215+
let warnings = CollectWarnings::new(None);
216+
let rec_guard = SerRecursionGuard::default();
217+
let config = SerializationConfig::from_args(timedelta_mode, bytes_mode)?;
218+
let extra = Extra::new(
219+
py,
220+
&SerMode::Json,
221+
&[],
222+
None,
223+
&warnings,
224+
None,
225+
None,
226+
exclude_none,
227+
round_trip,
228+
&config,
229+
&rec_guard,
230+
serialize_unknown,
231+
);
232+
let v = infer::infer_to_python(value, include, exclude, &extra)?;
233+
warnings.final_check(py)?;
234+
Ok(v)
235+
}
236+
200237
/// this is ugly, but would be much better if extra could be stored in `GeneralSerializeContext`
201238
/// then `GeneralSerializeContext` got a `serialize_infer` method, but I couldn't get it to work
202239
pub(crate) struct GeneralSerializeContext {

tests/test_json.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import re
22

33
import pytest
4+
from dirty_equals import IsList
45

5-
from pydantic_core import SchemaValidator, ValidationError, to_json
6+
from pydantic_core import PydanticSerializationError, SchemaValidator, ValidationError, to_json, to_jsonable_python
67

78
from .conftest import Err
89

@@ -184,12 +185,25 @@ def __str__(self):
184185
def test_to_json():
185186
assert to_json([1, 2]) == b'[1,2]'
186187
assert to_json([1, 2], indent=2) == b'[\n 1,\n 2\n]'
188+
assert to_json([1, b'x']) == b'[1,"x"]'
187189

188-
with pytest.raises(ValueError, match='Unable to serialize unknown type:'):
190+
with pytest.raises(PydanticSerializationError, match=r'Unable to serialize unknown type: <.+\.Foobar'):
189191
to_json(Foobar())
190192

191193
assert to_json(Foobar(), serialize_unknown=True) == b'"Foobar.__str__"'
192194

193195
# kwargs required
194196
with pytest.raises(TypeError, match=r'to_json\(\) takes 1 positional arguments but 2 were given'):
195197
to_json([1, 2], 2)
198+
199+
200+
def test_to_jsonable_python():
201+
assert to_jsonable_python([1, 2]) == [1, 2]
202+
assert to_jsonable_python({1, 2}) == IsList(1, 2, check_order=False)
203+
assert to_jsonable_python([1, b'x']) == [1, 'x']
204+
assert to_jsonable_python([0, 1, 2, 3, 4], exclude={1, 3}) == [0, 2, 4]
205+
206+
with pytest.raises(PydanticSerializationError, match=r'Unable to serialize unknown type: <.+\.Foobar'):
207+
to_jsonable_python(Foobar())
208+
209+
assert to_jsonable_python(Foobar(), serialize_unknown=True) == 'Foobar.__str__'

0 commit comments

Comments
 (0)