Skip to content

Commit efbd0ae

Browse files
committed
fix: tidy up, docs, etc. Might be almost finished
1 parent 52b5799 commit efbd0ae

File tree

6 files changed

+136
-216
lines changed

6 files changed

+136
-216
lines changed

python/pydantic_core/_pydantic_core.pyi

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -426,8 +426,9 @@ def to_json(
426426
exclude_none: Whether to exclude fields that have a value of `None`.
427427
round_trip: Whether to enable serialization and validation round-trip support.
428428
timedelta_mode: How to serialize `timedelta` objects, either `'iso8601'` or `'float'`.
429-
temporal_mode: How to serialize datetime like objects, either
430-
`'iso8601'` or `'seconds'` or `'milliseconds'`.
429+
temporal_mode: How to serialize datetime-like objects (`datetime`, `date`, `time`), either `'iso8601'`, `'seconds'`, or `'milliseconds'`.
430+
`iso8601` returns an ISO 8601 string; `seconds` returns the Unix timestamp in seconds as a float; `milliseconds` returns the Unix timestamp in milliseconds as a float.
431+
431432
bytes_mode: How to serialize `bytes` objects, either `'utf8'`, `'base64'`, or `'hex'`.
432433
inf_nan_mode: How to serialize `Infinity`, `-Infinity` and `NaN` values, either `'null'`, `'constants'`, or `'strings'`.
433434
serialize_unknown: Attempt to serialize unknown types, `str(value)` will be used, if that fails
@@ -508,8 +509,9 @@ def to_jsonable_python(
508509
exclude_none: Whether to exclude fields that have a value of `None`.
509510
round_trip: Whether to enable serialization and validation round-trip support.
510511
timedelta_mode: How to serialize `timedelta` objects, either `'iso8601'` or `'float'`.
511-
temporal_mode: How to serialize datetime like objects, either
512-
`'iso8601'` or `'seconds'` or `'milliseconds'`.
512+
temporal_mode: How to serialize datetime-like objects (`datetime`, `date`, `time`), either `'iso8601'`, `'seconds'`, or `'milliseconds'`.
513+
`iso8601` returns an ISO 8601 string; `seconds` returns the Unix timestamp in seconds as a float; `milliseconds` returns the Unix timestamp in milliseconds as a float.
514+
513515
bytes_mode: How to serialize `bytes` objects, either `'utf8'`, `'base64'`, or `'hex'`.
514516
inf_nan_mode: How to serialize `Infinity`, `-Infinity` and `NaN` values, either `'null'`, `'constants'`, or `'strings'`.
515517
serialize_unknown: Attempt to serialize unknown types, `str(value)` will be used, if that fails

python/pydantic_core/core_schema.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ class CoreConfig(TypedDict, total=False):
6161
str_to_upper: Whether to convert string fields to uppercase.
6262
allow_inf_nan: Whether to allow infinity and NaN values for float fields. Default is `True`.
6363
ser_json_timedelta: The serialization option for `timedelta` values. Default is 'iso8601'.
64+
Note that if ser_json_temporal is set, then this param will be ignored.
65+
ser_json_temporal: The serialization option for datetime like values. Default is 'iso8601'.
66+
The types this covers are datetime, date, time and timedelta.
67+
If this is set, it will take precedence over ser_json_timedelta
6468
ser_json_bytes: The serialization option for `bytes` values. Default is 'utf8'.
6569
ser_json_inf_nan: The serialization option for infinity and NaN values
6670
in float fields. Default is 'null'.

src/serializers/config.rs

Lines changed: 20 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::str::{from_utf8, FromStr, Utf8Error};
33

44
use base64::Engine;
55
use pyo3::prelude::*;
6-
use pyo3::types::{PyDate, PyDateTime, PyDelta, PyDict, PyString, PyTime};
6+
use pyo3::types::{PyDate, PyDateTime, PyDict, PyString, PyTime};
77
use pyo3::{intern, IntoPyObjectExt};
88

99
use serde::ser::Error;
@@ -14,34 +14,32 @@ use crate::serializers::type_serializers::datetime_etc::{
1414
date_to_milliseconds, date_to_seconds, date_to_string, datetime_to_milliseconds, datetime_to_seconds,
1515
datetime_to_string, time_to_milliseconds, time_to_seconds, time_to_string,
1616
};
17-
use crate::serializers::type_serializers::timedelta::EffectiveDeltaMode;
1817
use crate::tools::SchemaDict;
1918

2019
use super::errors::py_err_se_err;
2120

2221
#[derive(Debug, Clone)]
2322
#[allow(clippy::struct_field_names)]
2423
pub(crate) struct SerializationConfig {
25-
pub timedelta_mode: TimedeltaMode,
2624
pub temporal_mode: TemporalMode,
27-
prefer_timedelta_mode: bool,
2825
pub bytes_mode: BytesMode,
2926
pub inf_nan_mode: InfNanMode,
3027
}
3128

3229
impl SerializationConfig {
3330
pub fn from_config(config: Option<&Bound<'_, PyDict>>) -> PyResult<Self> {
34-
let timedelta_mode = TimedeltaMode::from_config(config)?;
35-
let temporal_mode = TemporalMode::from_config(config)?;
36-
let prefer_timedelta_mode = config
37-
.and_then(|cfg| cfg.contains(intern!(cfg.py(), "ser_json_timedelta")).ok())
31+
let temporal_set = config
32+
.and_then(|cfg| cfg.contains(intern!(cfg.py(), "ser_json_temporal")).ok())
3833
.unwrap_or(false);
34+
let temporal_mode = if temporal_set {
35+
TemporalMode::from_config(config)?
36+
} else {
37+
TimedeltaMode::from_config(config)?.into()
38+
};
3939
let bytes_mode = BytesMode::from_config(config)?;
4040
let inf_nan_mode = InfNanMode::from_config(config)?;
4141
Ok(Self {
42-
timedelta_mode,
4342
temporal_mode,
44-
prefer_timedelta_mode,
4543
bytes_mode,
4644
inf_nan_mode,
4745
})
@@ -53,22 +51,17 @@ impl SerializationConfig {
5351
bytes_mode: &str,
5452
inf_nan_mode: &str,
5553
) -> PyResult<Self> {
54+
let resolved_temporal_mode = if temporal_mode != "iso8601" {
55+
TemporalMode::from_str(temporal_mode)?
56+
} else {
57+
TimedeltaMode::from_str(timedelta_mode)?.into()
58+
};
5659
Ok(Self {
57-
timedelta_mode: TimedeltaMode::from_str(timedelta_mode)?,
58-
temporal_mode: TemporalMode::from_str(temporal_mode)?,
59-
prefer_timedelta_mode: true, // This is not settable via args
60+
temporal_mode: resolved_temporal_mode,
6061
bytes_mode: BytesMode::from_str(bytes_mode)?,
6162
inf_nan_mode: InfNanMode::from_str(inf_nan_mode)?,
6263
})
6364
}
64-
65-
pub fn effective_delta_mode(&self) -> EffectiveDeltaMode {
66-
if self.prefer_timedelta_mode {
67-
EffectiveDeltaMode::Timedelta(self.timedelta_mode)
68-
} else {
69-
EffectiveDeltaMode::Temporal(self.temporal_mode)
70-
}
71-
}
7265
}
7366

7467
pub trait FromConfig {
@@ -143,58 +136,13 @@ serialization_mode! {
143136
Strings => "strings",
144137
}
145138

146-
impl TimedeltaMode {
147-
fn total_seconds<'py>(py_timedelta: &Bound<'py, PyDelta>) -> PyResult<Bound<'py, PyAny>> {
148-
py_timedelta.call_method0(intern!(py_timedelta.py(), "total_seconds"))
149-
}
150-
151-
pub fn either_delta_to_json(self, py: Python, either_delta: EitherTimedelta) -> PyResult<PyObject> {
152-
match self {
153-
Self::Iso8601 => {
154-
let d = either_delta.to_duration()?;
155-
d.to_string().into_py_any(py)
156-
}
157-
Self::Float => {
158-
// convert to int via a py timedelta not duration since we know this this case the input would have
159-
// been a py timedelta
160-
let py_timedelta = either_delta.into_pyobject(py)?;
161-
let seconds = Self::total_seconds(&py_timedelta)?;
162-
Ok(seconds.unbind())
163-
}
164-
}
165-
}
166-
167-
pub fn json_key<'py>(self, py: Python, either_delta: EitherTimedelta) -> PyResult<Cow<'py, str>> {
168-
match self {
169-
Self::Iso8601 => {
170-
let d = either_delta.to_duration()?;
171-
Ok(d.to_string().into())
172-
}
173-
Self::Float => {
174-
let py_timedelta = either_delta.into_pyobject(py)?;
175-
let seconds: f64 = Self::total_seconds(&py_timedelta)?.extract()?;
176-
Ok(seconds.to_string().into())
177-
}
178-
}
179-
}
139+
impl TimedeltaMode {}
180140

181-
pub fn timedelta_serialize<S: serde::ser::Serializer>(
182-
self,
183-
py: Python,
184-
either_delta: EitherTimedelta,
185-
serializer: S,
186-
) -> Result<S::Ok, S::Error> {
187-
match self {
188-
Self::Iso8601 => {
189-
let d = either_delta.to_duration().map_err(py_err_se_err)?;
190-
serializer.serialize_str(&d.to_string())
191-
}
192-
Self::Float => {
193-
let py_timedelta = either_delta.into_pyobject(py).map_err(py_err_se_err)?;
194-
let seconds = Self::total_seconds(&py_timedelta).map_err(py_err_se_err)?;
195-
let seconds: f64 = seconds.extract().map_err(py_err_se_err)?;
196-
serializer.serialize_f64(seconds)
197-
}
141+
impl From<TimedeltaMode> for TemporalMode {
142+
fn from(value: TimedeltaMode) -> Self {
143+
match value {
144+
TimedeltaMode::Iso8601 => TemporalMode::Iso8601,
145+
TimedeltaMode::Float => TemporalMode::Seconds,
198146
}
199147
}
200148
}

src/serializers/infer.rs

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ use serde::ser::{Error, Serialize, SerializeMap, SerializeSeq, Serializer};
1212

1313
use crate::input::{EitherTimedelta, Int};
1414
use crate::serializers::type_serializers;
15-
use crate::serializers::type_serializers::timedelta::EffectiveDeltaMode;
1615
use crate::tools::{extract_int, py_err, safe_repr};
1716
use crate::url::{PyMultiHostUrl, PyUrl};
1817

@@ -193,15 +192,7 @@ pub(crate) fn infer_to_python_known(
193192
}
194193
ObType::Timedelta => {
195194
let either_delta = EitherTimedelta::try_from(value)?;
196-
let x = match extra.config.effective_delta_mode() {
197-
EffectiveDeltaMode::Temporal(temporal_mode) => {
198-
temporal_mode.timedelta_to_json(value.py(), either_delta)
199-
}
200-
EffectiveDeltaMode::Timedelta(timedelta_mode) => {
201-
timedelta_mode.either_delta_to_json(value.py(), either_delta)
202-
}
203-
}?;
204-
x
195+
extra.config.temporal_mode.timedelta_to_json(value.py(), either_delta)?
205196
}
206197
ObType::Url => {
207198
let py_url: PyUrl = value.extract()?;
@@ -480,14 +471,7 @@ pub(crate) fn infer_serialize_known<S: Serializer>(
480471
}
481472
ObType::Timedelta => {
482473
let either_delta = EitherTimedelta::try_from(value).map_err(py_err_se_err)?;
483-
match extra.config.effective_delta_mode() {
484-
EffectiveDeltaMode::Temporal(temporal_mode) => {
485-
temporal_mode.timedelta_serialize(either_delta, serializer)
486-
}
487-
EffectiveDeltaMode::Timedelta(timedelta_mode) => {
488-
timedelta_mode.timedelta_serialize(value.py(), either_delta, serializer)
489-
}
490-
}
474+
extra.config.temporal_mode.timedelta_serialize(either_delta, serializer)
491475
}
492476
ObType::Url => {
493477
let py_url: PyUrl = value.extract().map_err(py_err_se_err)?;
@@ -654,10 +638,7 @@ pub(crate) fn infer_json_key_known<'a>(
654638
}
655639
ObType::Timedelta => {
656640
let either_delta = EitherTimedelta::try_from(key)?;
657-
match extra.config.effective_delta_mode() {
658-
EffectiveDeltaMode::Temporal(temporal_mode) => temporal_mode.timedelta_json_key(&either_delta),
659-
EffectiveDeltaMode::Timedelta(timedelta_mode) => timedelta_mode.json_key(key.py(), either_delta),
660-
}
641+
extra.config.temporal_mode.timedelta_json_key(&either_delta)
661642
}
662643
ObType::Url => {
663644
let py_url: PyUrl = key.extract()?;

src/serializers/type_serializers/timedelta.rs

Lines changed: 12 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,7 @@ use super::{
1414

1515
#[derive(Debug)]
1616
pub struct TimeDeltaSerializer {
17-
timedelta_mode: TimedeltaMode,
1817
temporal_mode: TemporalMode,
19-
prefer_timedelta: bool,
20-
}
21-
22-
pub enum EffectiveDeltaMode {
23-
Timedelta(TimedeltaMode),
24-
Temporal(TemporalMode),
2518
}
2619

2720
impl BuildSerializer for TimeDeltaSerializer {
@@ -32,29 +25,17 @@ impl BuildSerializer for TimeDeltaSerializer {
3225
config: Option<&Bound<'_, PyDict>>,
3326
_definitions: &mut DefinitionsBuilder<CombinedSerializer>,
3427
) -> PyResult<CombinedSerializer> {
35-
let timedelta_mode = TimedeltaMode::from_config(config)?;
36-
let temporal_mode = TemporalMode::from_config(config)?;
37-
38-
let prefer_timedelta_mode = config
39-
.and_then(|cfg| cfg.contains(intern!(cfg.py(), "ser_json_timedelta")).ok())
28+
let temporal_set = config
29+
.and_then(|cfg| cfg.contains(intern!(cfg.py(), "ser_json_temporal")).ok())
4030
.unwrap_or(false);
41-
42-
Ok(Self {
43-
timedelta_mode,
44-
temporal_mode,
45-
prefer_timedelta: prefer_timedelta_mode,
46-
}
47-
.into())
48-
}
49-
}
50-
51-
impl TimeDeltaSerializer {
52-
pub fn effective_delta_mode(&self) -> EffectiveDeltaMode {
53-
if self.prefer_timedelta {
54-
EffectiveDeltaMode::Timedelta(self.timedelta_mode)
31+
let temporal_mode = if temporal_set {
32+
TemporalMode::from_config(config)?
5533
} else {
56-
EffectiveDeltaMode::Temporal(self.temporal_mode)
57-
}
34+
let td_mode = TimedeltaMode::from_config(config)?;
35+
td_mode.into()
36+
};
37+
38+
Ok(Self { temporal_mode }.into())
5839
}
5940
}
6041

@@ -70,14 +51,7 @@ impl TypeSerializer for TimeDeltaSerializer {
7051
) -> PyResult<PyObject> {
7152
match extra.mode {
7253
SerMode::Json => match EitherTimedelta::try_from(value) {
73-
Ok(either_timedelta) => match self.effective_delta_mode() {
74-
EffectiveDeltaMode::Timedelta(timedelta_mode) => {
75-
Ok(timedelta_mode.either_delta_to_json(value.py(), either_timedelta)?)
76-
}
77-
EffectiveDeltaMode::Temporal(temporal_mode) => {
78-
Ok(temporal_mode.timedelta_to_json(value.py(), either_timedelta)?)
79-
}
80-
},
54+
Ok(either_timedelta) => Ok(self.temporal_mode.timedelta_to_json(value.py(), either_timedelta)?),
8155
Err(_) => {
8256
extra.warnings.on_fallback_py(self.get_name(), value, extra)?;
8357
infer_to_python(value, include, exclude, extra)
@@ -89,10 +63,7 @@ impl TypeSerializer for TimeDeltaSerializer {
8963

9064
fn json_key<'a>(&self, key: &'a Bound<'_, PyAny>, extra: &Extra) -> PyResult<Cow<'a, str>> {
9165
match EitherTimedelta::try_from(key) {
92-
Ok(either_timedelta) => match self.effective_delta_mode() {
93-
EffectiveDeltaMode::Timedelta(timedelta_mode) => timedelta_mode.json_key(key.py(), either_timedelta),
94-
EffectiveDeltaMode::Temporal(temporal_mode) => temporal_mode.timedelta_json_key(&either_timedelta),
95-
},
66+
Ok(either_timedelta) => self.temporal_mode.timedelta_json_key(&either_timedelta),
9667
Err(_) => {
9768
extra.warnings.on_fallback_py(self.get_name(), key, extra)?;
9869
infer_json_key(key, extra)
@@ -109,14 +80,7 @@ impl TypeSerializer for TimeDeltaSerializer {
10980
extra: &Extra,
11081
) -> Result<S::Ok, S::Error> {
11182
match EitherTimedelta::try_from(value) {
112-
Ok(either_timedelta) => match self.effective_delta_mode() {
113-
EffectiveDeltaMode::Timedelta(timedelta_mode) => {
114-
timedelta_mode.timedelta_serialize(value.py(), either_timedelta, serializer)
115-
}
116-
EffectiveDeltaMode::Temporal(temporal_mode) => {
117-
temporal_mode.timedelta_serialize(either_timedelta, serializer)
118-
}
119-
},
83+
Ok(either_timedelta) => self.temporal_mode.timedelta_serialize(either_timedelta, serializer),
12084
Err(_) => {
12185
extra.warnings.on_fallback_ser::<S>(self.get_name(), value, extra)?;
12286
infer_serialize(value, serializer, include, exclude, extra)

0 commit comments

Comments
 (0)