Skip to content

Commit 0107915

Browse files
committed
feat: add 'millisecond' option to ser_json_timedelta config parameter
1 parent d93e6b1 commit 0107915

File tree

3 files changed

+48
-2
lines changed

3 files changed

+48
-2
lines changed

python/pydantic_core/core_schema.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ class CoreConfig(TypedDict, total=False):
105105
# fields related to float fields only
106106
allow_inf_nan: bool # default: True
107107
# the config options are used to customise serialization to JSON
108-
ser_json_timedelta: Literal['iso8601', 'float'] # default: 'iso8601'
108+
ser_json_timedelta: Literal['iso8601', 'float', 'millisecond'] # default: 'iso8601'
109109
ser_json_bytes: Literal['utf8', 'base64', 'hex'] # default: 'utf8'
110110
ser_json_inf_nan: Literal['null', 'constants', 'strings'] # default: 'null'
111111
val_json_bytes: Literal['utf8', 'base64', 'hex'] # default: 'utf8'

src/serializers/config.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::str::{from_utf8, FromStr, Utf8Error};
44
use base64::Engine;
55
use pyo3::intern;
66
use pyo3::prelude::*;
7-
use pyo3::types::{PyDelta, PyDict, PyString};
7+
use pyo3::types::{PyDelta, PyDict, PyFloat, PyString};
88

99
use serde::ser::Error;
1010

@@ -89,6 +89,7 @@ serialization_mode! {
8989
"ser_json_timedelta",
9090
Iso8601 => "iso8601",
9191
Float => "float",
92+
Millisecond => "millisecond"
9293
}
9394

9495
serialization_mode! {
@@ -125,6 +126,14 @@ impl TimedeltaMode {
125126
let seconds = Self::total_seconds(&py_timedelta)?;
126127
Ok(seconds.into_py(py))
127128
}
129+
Self::Millisecond => {
130+
// convert to int via a py timedelta not duration since we know this this case the input would have
131+
// been a py timedelta
132+
let py_timedelta = either_delta.try_into_py(py)?;
133+
let seconds: f64 = Self::total_seconds(&py_timedelta)?.extract()?;
134+
let object: Bound<PyFloat> = PyFloat::new_bound(py, seconds * 1000.0);
135+
Ok(object.into_py(py))
136+
}
128137
}
129138
}
130139

@@ -139,6 +148,12 @@ impl TimedeltaMode {
139148
let seconds: f64 = Self::total_seconds(&py_timedelta)?.extract()?;
140149
Ok(seconds.to_string().into())
141150
}
151+
Self::Millisecond => {
152+
let py_timedelta = either_delta.try_into_py(py)?;
153+
let seconds: f64 = Self::total_seconds(&py_timedelta)?.extract()?;
154+
let milliseconds: f64 = seconds * 1000.0;
155+
Ok(milliseconds.to_string().into())
156+
}
142157
}
143158
}
144159

@@ -159,6 +174,13 @@ impl TimedeltaMode {
159174
let seconds: f64 = seconds.extract().map_err(py_err_se_err)?;
160175
serializer.serialize_f64(seconds)
161176
}
177+
Self::Millisecond => {
178+
let py_timedelta = either_delta.try_into_py(py).map_err(py_err_se_err)?;
179+
let seconds = Self::total_seconds(&py_timedelta).map_err(py_err_se_err)?;
180+
let seconds: f64 = seconds.extract().map_err(py_err_se_err)?;
181+
let milliseconds: f64 = seconds * 1000.0;
182+
serializer.serialize_f64(milliseconds)
183+
}
162184
}
163185
}
164186
}

tests/serializers/test_any.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,30 @@ def test_any_config_timedelta_float_faction():
201201
assert s.to_json({one_half_s: 'foo'}) == b'{"1.5":"foo"}'
202202

203203

204+
def test_any_config_timedelta_millisecond():
205+
s = SchemaSerializer(core_schema.any_schema(), config={'ser_json_timedelta': 'millisecond'})
206+
h2 = timedelta(hours=2)
207+
assert s.to_python(h2) == h2
208+
assert s.to_python(h2, mode='json') == 7200000.0
209+
assert s.to_json(h2) == b'7200000.0'
210+
211+
assert s.to_python({h2: 'foo'}) == {h2: 'foo'}
212+
assert s.to_python({h2: 'foo'}, mode='json') == {'7200000': 'foo'}
213+
assert s.to_json({h2: 'foo'}) == b'{"7200000":"foo"}'
214+
215+
216+
def test_any_config_timedelta_millisecond_fraction():
217+
s = SchemaSerializer(core_schema.any_schema(), config={'ser_json_timedelta': 'millisecond'})
218+
h2 = timedelta(seconds=1.5)
219+
assert s.to_python(h2) == h2
220+
assert s.to_python(h2, mode='json') == 1500.0
221+
assert s.to_json(h2) == b'1500.0'
222+
223+
assert s.to_python({h2: 'foo'}) == {h2: 'foo'}
224+
assert s.to_python({h2: 'foo'}, mode='json') == {'1500': 'foo'}
225+
assert s.to_json({h2: 'foo'}) == b'{"1500":"foo"}'
226+
227+
204228
def test_recursion(any_serializer):
205229
v = [1, 2]
206230
v.append(v)

0 commit comments

Comments
 (0)