Skip to content

Fix to_json function #577

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions src/serializers/extra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,10 +268,6 @@ pub(crate) struct CollectWarnings {
}

impl CollectWarnings {
pub(crate) fn is_active(&self) -> bool {
self.active
}

pub(crate) fn new(active: bool) -> Self {
Self {
active,
Expand Down
32 changes: 23 additions & 9 deletions src/serializers/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use serde::ser::{Error, Serialize, SerializeMap, SerializeSeq, Serializer};
use crate::build_tools::{py_err, safe_repr};
use crate::serializers::errors::SERIALIZATION_ERR_MARKER;
use crate::serializers::filter::SchemaFilter;
use crate::serializers::shared::PydanticSerializer;
use crate::serializers::shared::{PydanticSerializer, TypeSerializer};
use crate::serializers::SchemaSerializer;
use crate::url::{PyMultiHostUrl, PyUrl};

Expand Down Expand Up @@ -100,20 +100,20 @@ pub(crate) fn infer_to_python_known(
let serialize_with_serializer = |value: &PyAny, is_model: bool| {
if let Ok(py_serializer) = value.getattr(intern!(py, "__pydantic_serializer__")) {
if let Ok(serializer) = py_serializer.extract::<SchemaSerializer>() {
return serializer.to_python(
let extra = serializer.build_extra(
py,
value,
extra.mode.to_object(py).extract(py)?,
include,
exclude,
extra.mode,
extra.by_alias,
extra.warnings,
extra.exclude_unset,
extra.exclude_defaults,
extra.exclude_none,
extra.round_trip,
extra.warnings.is_active(),
extra.rec_guard,
extra.serialize_unknown,
extra.fallback,
);
return serializer.serializer.to_python(value, include, exclude, &extra);
}
}
// Fallback to dict serialization if `__pydantic_serializer__` is not set.
Expand Down Expand Up @@ -408,10 +408,24 @@ pub(crate) fn infer_serialize_known<S: Serializer>(

macro_rules! serialize_with_serializer {
($py_serializable:expr, $is_model:expr) => {{
if let Ok(py_serializer) = value.getattr(intern!($py_serializable.py(), "__pydantic_serializer__")) {
let py = $py_serializable.py();
if let Ok(py_serializer) = value.getattr(intern!(py, "__pydantic_serializer__")) {
if let Ok(extracted_serializer) = py_serializer.extract::<SchemaSerializer>() {
let extra = extracted_serializer.build_extra(
py,
extra.mode,
extra.by_alias,
extra.warnings,
extra.exclude_unset,
extra.exclude_defaults,
extra.exclude_none,
extra.round_trip,
extra.rec_guard,
extra.serialize_unknown,
extra.fallback,
);
let pydantic_serializer =
PydanticSerializer::new(value, &extracted_serializer.serializer, include, exclude, extra);
PydanticSerializer::new(value, &extracted_serializer.serializer, include, exclude, &extra);
return pydantic_serializer.serialize(serializer);
}
}
Expand Down
42 changes: 36 additions & 6 deletions src/serializers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,40 @@ pub struct SchemaSerializer {
config: SerializationConfig,
}

impl SchemaSerializer {
#[allow(clippy::too_many_arguments)]
pub(crate) fn build_extra<'b, 'a: 'b>(
&'b self,
py: Python<'a>,
mode: &'a SerMode,
by_alias: bool,
warnings: &'a CollectWarnings,
exclude_unset: bool,
exclude_defaults: bool,
exclude_none: bool,
round_trip: bool,
rec_guard: &'a SerRecursionGuard,
serialize_unknown: bool,
fallback: Option<&'a PyAny>,
) -> Extra<'b> {
Extra::new(
py,
mode,
&self.definitions,
by_alias,
warnings,
exclude_unset,
exclude_defaults,
exclude_none,
round_trip,
&self.config,
rec_guard,
serialize_unknown,
fallback,
)
}
}

#[pymethods]
impl SchemaSerializer {
#[new]
Expand Down Expand Up @@ -72,17 +106,15 @@ impl SchemaSerializer {
let mode: SerMode = mode.into();
let warnings = CollectWarnings::new(warnings);
let rec_guard = SerRecursionGuard::default();
let extra = Extra::new(
let extra = self.build_extra(
py,
&mode,
&self.definitions,
by_alias,
&warnings,
exclude_unset,
exclude_defaults,
exclude_none,
round_trip,
&self.config,
&rec_guard,
false,
fallback,
Expand Down Expand Up @@ -113,17 +145,15 @@ impl SchemaSerializer {
) -> PyResult<PyObject> {
let warnings = CollectWarnings::new(warnings);
let rec_guard = SerRecursionGuard::default();
let extra = Extra::new(
let extra = self.build_extra(
py,
&SerMode::Json,
&self.definitions,
by_alias,
&warnings,
exclude_unset,
exclude_defaults,
exclude_none,
round_trip,
&self.config,
&rec_guard,
false,
fallback,
Expand Down
23 changes: 15 additions & 8 deletions tests/test_json.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import re
from typing import List

import pytest
from dirty_equals import IsList
Expand Down Expand Up @@ -239,30 +240,36 @@ def test_to_jsonable_python_fallback():

def test_to_jsonable_python_schema_serializer():
class Foobar:
def __init__(self, my_foo: int, my_bar: str):
def __init__(self, my_foo: int, my_inners: List['Foobar']):
self.my_foo = my_foo
self.my_bar = my_bar
self.my_inners = my_inners

# force a recursive model to ensure we exercise the transfer of definitions from the loaded
# serializer
c = core_schema.model_schema(
Foobar,
core_schema.typed_dict_schema(
{
'my_foo': core_schema.typed_dict_field(core_schema.int_schema(), serialization_alias='myFoo'),
'my_bar': core_schema.typed_dict_field(core_schema.str_schema(), serialization_alias='myBar'),
'my_inners': core_schema.typed_dict_field(
core_schema.list_schema(core_schema.definition_reference_schema('foobar')),
serialization_alias='myInners',
),
}
),
ref='foobar',
)
v = SchemaValidator(c)
s = SchemaSerializer(c)

Foobar.__pydantic_validator__ = v
Foobar.__pydantic_serializer__ = s

instance = Foobar(my_foo=1, my_bar='a')
assert to_jsonable_python(instance) == {'myFoo': 1, 'myBar': 'a'}
assert to_jsonable_python(instance, by_alias=False) == {'my_foo': 1, 'my_bar': 'a'}
assert to_json(instance) == b'{"myFoo":1,"myBar":"a"}'
assert to_json(instance, by_alias=False) == b'{"my_foo":1,"my_bar":"a"}'
instance = Foobar(my_foo=1, my_inners=[Foobar(my_foo=2, my_inners=[])])
assert to_jsonable_python(instance) == {'myFoo': 1, 'myInners': [{'myFoo': 2, 'myInners': []}]}
assert to_jsonable_python(instance, by_alias=False) == {'my_foo': 1, 'my_inners': [{'my_foo': 2, 'my_inners': []}]}
assert to_json(instance) == b'{"myFoo":1,"myInners":[{"myFoo":2,"myInners":[]}]}'
assert to_json(instance, by_alias=False) == b'{"my_foo":1,"my_inners":[{"my_foo":2,"my_inners":[]}]}'


def test_cycle_same():
Expand Down