Skip to content
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

Add to_python(..., serialize_generators=False) #1401

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
8 changes: 8 additions & 0 deletions python/pydantic_core/_pydantic_core.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ class SchemaSerializer:
fallback: Callable[[Any], Any] | None = None,
serialize_as_any: bool = False,
context: Any | None = None,
serialize_generators: bool = True,
) -> Any:
"""
Serialize/marshal a Python object to a Python object including transforming and filtering data.
Expand All @@ -289,6 +290,7 @@ class SchemaSerializer:
serialize_as_any: Whether to serialize fields with duck-typing serialization behavior.
context: The context to use for serialization, this is passed to functional serializers as
[`info.context`][pydantic_core.core_schema.SerializationInfo.context].
serialize_generators: Whether to consume and wrap generators. If this is False, generators are treated as an unknown type.

Raises:
PydanticSerializationError: If serialization fails and no `fallback` function is provided.
Expand All @@ -312,6 +314,7 @@ class SchemaSerializer:
fallback: Callable[[Any], Any] | None = None,
serialize_as_any: bool = False,
context: Any | None = None,
serialize_generators: bool = True,
) -> bytes:
"""
Serialize a Python object to JSON including transforming and filtering data.
Expand All @@ -334,6 +337,7 @@ class SchemaSerializer:
serialize_as_any: Whether to serialize fields with duck-typing serialization behavior.
context: The context to use for serialization, this is passed to functional serializers as
[`info.context`][pydantic_core.core_schema.SerializationInfo.context].
serialize_generators: Whether to consume and wrap generators. If this is False, generators are treated as an unknown type.

Raises:
PydanticSerializationError: If serialization fails and no `fallback` function is provided.
Expand All @@ -358,6 +362,7 @@ def to_json(
fallback: Callable[[Any], Any] | None = None,
serialize_as_any: bool = False,
context: Any | None = None,
serialize_generators: bool = True,
) -> bytes:
"""
Serialize a Python object to JSON including transforming and filtering data.
Expand All @@ -382,6 +387,7 @@ def to_json(
serialize_as_any: Whether to serialize fields with duck-typing serialization behavior.
context: The context to use for serialization, this is passed to functional serializers as
[`info.context`][pydantic_core.core_schema.SerializationInfo.context].
serialize_generators: Whether to consume and wrap generators. If this is False, generators are treated as an unknown type.

Raises:
PydanticSerializationError: If serialization fails and no `fallback` function is provided.
Expand Down Expand Up @@ -433,6 +439,7 @@ def to_jsonable_python(
fallback: Callable[[Any], Any] | None = None,
serialize_as_any: bool = False,
context: Any | None = None,
serialize_generators: bool = True,
) -> Any:
"""
Serialize/marshal a Python object to a JSON-serializable Python object including transforming and filtering data.
Expand All @@ -457,6 +464,7 @@ def to_jsonable_python(
serialize_as_any: Whether to serialize fields with duck-typing serialization behavior.
context: The context to use for serialization, this is passed to functional serializers as
[`info.context`][pydantic_core.core_schema.SerializationInfo.context].
serialize_generators: Whether to consume and wrap generators. If this is False, generators are treated as an unknown type.

Raises:
PydanticSerializationError: If serialization fails and no `fallback` function is provided.
Expand Down
1 change: 1 addition & 0 deletions src/errors/validation_exception.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ impl ValidationError {
None,
DuckTypingSerMode::SchemaBased,
None,
true,
);
let serializer = ValidationErrorSerializer {
py,
Expand Down
8 changes: 8 additions & 0 deletions src/serializers/extra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ impl SerializationState {
fallback: Option<&'py Bound<'_, PyAny>>,
duck_typing_ser_mode: DuckTypingSerMode,
context: Option<&'py Bound<'_, PyAny>>,
serialize_generators: bool,
) -> Extra<'py> {
Extra::new(
py,
Expand All @@ -105,6 +106,7 @@ impl SerializationState {
fallback,
duck_typing_ser_mode,
context,
serialize_generators,
)
}

Expand Down Expand Up @@ -138,6 +140,7 @@ pub(crate) struct Extra<'a> {
pub fallback: Option<&'a Bound<'a, PyAny>>,
pub duck_typing_ser_mode: DuckTypingSerMode,
pub context: Option<&'a Bound<'a, PyAny>>,
pub serialize_generators: bool,
}

impl<'a> Extra<'a> {
Expand All @@ -157,6 +160,7 @@ impl<'a> Extra<'a> {
fallback: Option<&'a Bound<'a, PyAny>>,
duck_typing_ser_mode: DuckTypingSerMode,
context: Option<&'a Bound<'a, PyAny>>,
serialize_generators: bool,
) -> Self {
Self {
mode,
Expand All @@ -176,6 +180,7 @@ impl<'a> Extra<'a> {
fallback,
duck_typing_ser_mode,
context,
serialize_generators,
}
}

Expand Down Expand Up @@ -236,6 +241,7 @@ pub(crate) struct ExtraOwned {
pub fallback: Option<PyObject>,
duck_typing_ser_mode: DuckTypingSerMode,
pub context: Option<PyObject>,
serialize_generators: bool,
}

impl ExtraOwned {
Expand All @@ -257,6 +263,7 @@ impl ExtraOwned {
fallback: extra.fallback.map(|model| model.clone().into()),
duck_typing_ser_mode: extra.duck_typing_ser_mode,
context: extra.context.map(|model| model.clone().into()),
serialize_generators: extra.serialize_generators,
}
}

Expand All @@ -279,6 +286,7 @@ impl ExtraOwned {
fallback: self.fallback.as_ref().map(|m| m.bind(py)),
duck_typing_ser_mode: self.duck_typing_ser_mode,
context: self.context.as_ref().map(|m| m.bind(py)),
serialize_generators: self.serialize_generators,
}
}
}
Expand Down
19 changes: 11 additions & 8 deletions src/serializers/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ pub(crate) fn infer_to_python_known(
extra.fallback,
extra.duck_typing_ser_mode,
extra.context,
extra.serialize_generators,
);
serializer.serializer.to_python(value, include, exclude, &extra)
};
Expand Down Expand Up @@ -207,7 +208,7 @@ pub(crate) fn infer_to_python_known(
let v = value.getattr(intern!(py, "value"))?;
infer_to_python(&v, include, exclude, extra)?.into_py(py)
}
ObType::Generator => {
ObType::Generator if extra.serialize_generators => {
let py_seq = value.downcast::<PyIterator>()?;
let mut items = Vec::new();
let filter = AnyFilter::new();
Expand All @@ -228,7 +229,7 @@ pub(crate) fn infer_to_python_known(
}
ObType::Path => value.str()?.into_py(py),
ObType::Pattern => value.getattr(intern!(py, "pattern"))?.into_py(py),
ObType::Unknown => {
_ => {
if let Some(fallback) = extra.fallback {
let next_value = fallback.call1((value,))?;
let next_result = infer_to_python(&next_value, include, exclude, extra);
Expand Down Expand Up @@ -263,7 +264,7 @@ pub(crate) fn infer_to_python_known(
}
ObType::PydanticSerializable => serialize_with_serializer()?,
ObType::Dataclass => serialize_pairs_python(py, any_dataclass_iter(value)?.0, include, exclude, extra, Ok)?,
ObType::Generator => {
ObType::Generator if extra.serialize_generators => {
let iter = super::type_serializers::generator::SerializationIterator::new(
value.downcast()?,
super::type_serializers::any::AnySerializer.into(),
Expand All @@ -274,7 +275,8 @@ pub(crate) fn infer_to_python_known(
);
iter.into_py(py)
}
ObType::Unknown => {
ObType::Unknown | ObType::Generator => {
// if !extra.serialize_generators
if let Some(fallback) = extra.fallback {
let next_value = fallback.call1((value,))?;
let next_result = infer_to_python(&next_value, include, exclude, extra);
Expand Down Expand Up @@ -482,6 +484,7 @@ pub(crate) fn infer_serialize_known<S: Serializer>(
extra.fallback,
extra.duck_typing_ser_mode,
extra.context,
extra.serialize_generators,
);
let pydantic_serializer =
PydanticSerializer::new(value, &extracted_serializer.serializer, include, exclude, &extra);
Expand All @@ -499,7 +502,7 @@ pub(crate) fn infer_serialize_known<S: Serializer>(
let v = value.getattr(intern!(value.py(), "value")).map_err(py_err_se_err)?;
infer_serialize(&v, serializer, include, exclude, extra)
}
ObType::Generator => {
ObType::Generator if extra.serialize_generators => {
let py_seq = value.downcast::<PyIterator>().map_err(py_err_se_err)?;
let mut seq = serializer.serialize_seq(None)?;
let filter = AnyFilter::new();
Expand Down Expand Up @@ -530,7 +533,7 @@ pub(crate) fn infer_serialize_known<S: Serializer>(
.map_err(py_err_se_err)?;
serializer.serialize_str(&s)
}
ObType::Unknown => {
ObType::Unknown | ObType::Generator => {
if let Some(fallback) = extra.fallback {
let next_value = fallback.call1((value,)).map_err(py_err_se_err)?;
let next_result = infer_serialize(&next_value, serializer, include, exclude, extra);
Expand All @@ -550,14 +553,14 @@ pub(crate) fn infer_serialize_known<S: Serializer>(
ser_result
}

fn unknown_type_error(value: &Bound<'_, PyAny>) -> PyErr {
pub(crate) fn unknown_type_error(value: &Bound<'_, PyAny>) -> PyErr {
PydanticSerializationError::new_err(format!(
"Unable to serialize unknown type: {}",
safe_repr(&value.get_type())
))
}

fn serialize_unknown<'py>(value: &Bound<'py, PyAny>) -> Cow<'py, str> {
pub(crate) fn serialize_unknown<'py>(value: &Bound<'py, PyAny>) -> Cow<'py, str> {
if let Ok(s) = value.str() {
s.to_string_lossy().into_owned().into()
} else if let Ok(name) = value.get_type().qualname() {
Expand Down
18 changes: 14 additions & 4 deletions src/serializers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ impl SchemaSerializer {
fallback: Option<&'a Bound<'a, PyAny>>,
duck_typing_ser_mode: DuckTypingSerMode,
context: Option<&'a Bound<'a, PyAny>>,
serialize_generators: bool,
) -> Extra<'b> {
Extra::new(
py,
Expand All @@ -80,6 +81,7 @@ impl SchemaSerializer {
fallback,
duck_typing_ser_mode,
context,
serialize_generators,
)
}
}
Expand Down Expand Up @@ -107,7 +109,7 @@ impl SchemaSerializer {
#[allow(clippy::too_many_arguments)]
#[pyo3(signature = (value, *, mode = None, include = None, exclude = None, by_alias = true,
exclude_unset = false, exclude_defaults = false, exclude_none = false, round_trip = false, warnings = WarningsArg::Bool(true),
fallback = None, serialize_as_any = false, context = None))]
fallback = None, serialize_as_any = false, context = None, serialize_generators = true))]
pub fn to_python(
&self,
py: Python,
Expand All @@ -124,6 +126,7 @@ impl SchemaSerializer {
fallback: Option<&Bound<'_, PyAny>>,
serialize_as_any: bool,
context: Option<&Bound<'_, PyAny>>,
serialize_generators: bool,
) -> PyResult<PyObject> {
let mode: SerMode = mode.into();
let warnings_mode = match warnings {
Expand All @@ -147,6 +150,7 @@ impl SchemaSerializer {
fallback,
duck_typing_ser_mode,
context,
serialize_generators,
);
let v = self.serializer.to_python(value, include, exclude, &extra)?;
warnings.final_check(py)?;
Expand All @@ -156,7 +160,7 @@ impl SchemaSerializer {
#[allow(clippy::too_many_arguments)]
#[pyo3(signature = (value, *, indent = None, include = None, exclude = None, by_alias = true,
exclude_unset = false, exclude_defaults = false, exclude_none = false, round_trip = false, warnings = WarningsArg::Bool(true),
fallback = None, serialize_as_any = false, context = None))]
fallback = None, serialize_as_any = false, context = None, serialize_generators = true))]
pub fn to_json(
&self,
py: Python,
Expand All @@ -173,6 +177,7 @@ impl SchemaSerializer {
fallback: Option<&Bound<'_, PyAny>>,
serialize_as_any: bool,
context: Option<&Bound<'_, PyAny>>,
serialize_generators: bool,
) -> PyResult<PyObject> {
let warnings_mode = match warnings {
WarningsArg::Bool(b) => b.into(),
Expand All @@ -195,6 +200,7 @@ impl SchemaSerializer {
fallback,
duck_typing_ser_mode,
context,
serialize_generators,
);
let bytes = to_json_bytes(
value,
Expand Down Expand Up @@ -244,7 +250,7 @@ impl SchemaSerializer {
#[pyo3(signature = (value, *, indent = None, include = None, exclude = None, by_alias = true,
exclude_none = false, round_trip = false, timedelta_mode = "iso8601", bytes_mode = "utf8",
inf_nan_mode = "constants", serialize_unknown = false, fallback = None, serialize_as_any = false,
context = None))]
context = None, serialize_generators = true))]
pub fn to_json(
py: Python,
value: &Bound<'_, PyAny>,
Expand All @@ -261,6 +267,7 @@ pub fn to_json(
fallback: Option<&Bound<'_, PyAny>>,
serialize_as_any: bool,
context: Option<&Bound<'_, PyAny>>,
serialize_generators: bool,
) -> PyResult<PyObject> {
let state = SerializationState::new(timedelta_mode, bytes_mode, inf_nan_mode)?;
let duck_typing_ser_mode = DuckTypingSerMode::from_bool(serialize_as_any);
Expand All @@ -274,6 +281,7 @@ pub fn to_json(
fallback,
duck_typing_ser_mode,
context,
serialize_generators,
);
let serializer = type_serializers::any::AnySerializer.into();
let bytes = to_json_bytes(value, &serializer, include, exclude, &extra, indent, 1024)?;
Expand All @@ -286,7 +294,7 @@ pub fn to_json(
#[pyfunction]
#[pyo3(signature = (value, *, include = None, exclude = None, by_alias = true, exclude_none = false, round_trip = false,
timedelta_mode = "iso8601", bytes_mode = "utf8", inf_nan_mode = "constants", serialize_unknown = false, fallback = None,
serialize_as_any = false, context = None))]
serialize_as_any = false, context = None, serialize_generators = true))]
pub fn to_jsonable_python(
py: Python,
value: &Bound<'_, PyAny>,
Expand All @@ -302,6 +310,7 @@ pub fn to_jsonable_python(
fallback: Option<&Bound<'_, PyAny>>,
serialize_as_any: bool,
context: Option<&Bound<'_, PyAny>>,
serialize_generators: bool,
) -> PyResult<PyObject> {
let state = SerializationState::new(timedelta_mode, bytes_mode, inf_nan_mode)?;
let duck_typing_ser_mode = DuckTypingSerMode::from_bool(serialize_as_any);
Expand All @@ -315,6 +324,7 @@ pub fn to_jsonable_python(
fallback,
duck_typing_ser_mode,
context,
serialize_generators,
);
let v = infer::infer_to_python(value, include, exclude, &extra)?;
state.final_check(py)?;
Expand Down
Loading