Skip to content
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
94 changes: 21 additions & 73 deletions src/serializers/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::str::{from_utf8, FromStr, Utf8Error};

use base64::Engine;
use pyo3::prelude::*;
use pyo3::types::{PyDate, PyDateTime, PyDelta, PyDict, PyString, PyTime};
use pyo3::types::{PyDate, PyDateTime, PyDict, PyString, PyTime};
use pyo3::{intern, IntoPyObjectExt};

use serde::ser::Error;
Expand All @@ -14,61 +14,54 @@ use crate::serializers::type_serializers::datetime_etc::{
date_to_milliseconds, date_to_seconds, date_to_string, datetime_to_milliseconds, datetime_to_seconds,
datetime_to_string, time_to_milliseconds, time_to_seconds, time_to_string,
};
use crate::serializers::type_serializers::timedelta::EffectiveDeltaMode;
use crate::tools::SchemaDict;

use super::errors::py_err_se_err;

#[derive(Debug, Clone)]
#[allow(clippy::struct_field_names)]
pub(crate) struct SerializationConfig {
pub timedelta_mode: TimedeltaMode,
pub temporal_mode: TemporalMode,
prefer_timedelta_mode: bool,
pub bytes_mode: BytesMode,
pub inf_nan_mode: InfNanMode,
}

impl SerializationConfig {
pub fn from_config(config: Option<&Bound<'_, PyDict>>) -> PyResult<Self> {
let timedelta_mode = TimedeltaMode::from_config(config)?;
let temporal_mode = TemporalMode::from_config(config)?;
let prefer_timedelta_mode = config
.and_then(|cfg| cfg.contains(intern!(cfg.py(), "ser_json_timedelta")).ok())
let temporal_set = config
.and_then(|cfg| cfg.contains(intern!(cfg.py(), "ser_json_temporal")).ok())
.unwrap_or(false);
let temporal_mode = if temporal_set {
TemporalMode::from_config(config)?
} else {
TimedeltaMode::from_config(config)?.into()
};
let bytes_mode = BytesMode::from_config(config)?;
let inf_nan_mode = InfNanMode::from_config(config)?;
Ok(Self {
timedelta_mode,
temporal_mode,
prefer_timedelta_mode,
bytes_mode,
inf_nan_mode,
})
}

pub fn from_args(
timedelta_mode: &str,
datetime_mode: &str,
temporal_mode: &str,
bytes_mode: &str,
inf_nan_mode: &str,
) -> PyResult<Self> {
let resolved_temporal_mode = if temporal_mode != "iso8601" {
TemporalMode::from_str(temporal_mode)?
} else {
TimedeltaMode::from_str(timedelta_mode)?.into()
};
Ok(Self {
timedelta_mode: TimedeltaMode::from_str(timedelta_mode)?,
temporal_mode: TemporalMode::from_str(datetime_mode)?,
prefer_timedelta_mode: true, // This is not settable via args
temporal_mode: resolved_temporal_mode,
bytes_mode: BytesMode::from_str(bytes_mode)?,
inf_nan_mode: InfNanMode::from_str(inf_nan_mode)?,
})
}

pub fn effective_delta_mode(&self) -> EffectiveDeltaMode {
if self.prefer_timedelta_mode {
EffectiveDeltaMode::Timedelta(self.timedelta_mode)
} else {
EffectiveDeltaMode::Temporal(self.temporal_mode)
}
}
}

pub trait FromConfig {
Expand Down Expand Up @@ -143,58 +136,13 @@ serialization_mode! {
Strings => "strings",
}

impl TimedeltaMode {
fn total_seconds<'py>(py_timedelta: &Bound<'py, PyDelta>) -> PyResult<Bound<'py, PyAny>> {
py_timedelta.call_method0(intern!(py_timedelta.py(), "total_seconds"))
}

pub fn either_delta_to_json(self, py: Python, either_delta: EitherTimedelta) -> PyResult<PyObject> {
match self {
Self::Iso8601 => {
let d = either_delta.to_duration()?;
d.to_string().into_py_any(py)
}
Self::Float => {
// convert to int via a py timedelta not duration since we know this this case the input would have
// been a py timedelta
let py_timedelta = either_delta.into_pyobject(py)?;
let seconds = Self::total_seconds(&py_timedelta)?;
Ok(seconds.unbind())
}
}
}

pub fn json_key<'py>(self, py: Python, either_delta: EitherTimedelta) -> PyResult<Cow<'py, str>> {
match self {
Self::Iso8601 => {
let d = either_delta.to_duration()?;
Ok(d.to_string().into())
}
Self::Float => {
let py_timedelta = either_delta.into_pyobject(py)?;
let seconds: f64 = Self::total_seconds(&py_timedelta)?.extract()?;
Ok(seconds.to_string().into())
}
}
}
impl TimedeltaMode {}

pub fn timedelta_serialize<S: serde::ser::Serializer>(
self,
py: Python,
either_delta: EitherTimedelta,
serializer: S,
) -> Result<S::Ok, S::Error> {
match self {
Self::Iso8601 => {
let d = either_delta.to_duration().map_err(py_err_se_err)?;
serializer.serialize_str(&d.to_string())
}
Self::Float => {
let py_timedelta = either_delta.into_pyobject(py).map_err(py_err_se_err)?;
let seconds = Self::total_seconds(&py_timedelta).map_err(py_err_se_err)?;
let seconds: f64 = seconds.extract().map_err(py_err_se_err)?;
serializer.serialize_f64(seconds)
}
impl From<TimedeltaMode> for TemporalMode {
fn from(value: TimedeltaMode) -> Self {
match value {
TimedeltaMode::Iso8601 => TemporalMode::Iso8601,
TimedeltaMode::Float => TemporalMode::Seconds,
}
}
}
Expand Down
25 changes: 3 additions & 22 deletions src/serializers/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ use serde::ser::{Error, Serialize, SerializeMap, SerializeSeq, Serializer};

use crate::input::{EitherTimedelta, Int};
use crate::serializers::type_serializers;
use crate::serializers::type_serializers::timedelta::EffectiveDeltaMode;
use crate::tools::{extract_int, py_err, safe_repr};
use crate::url::{PyMultiHostUrl, PyUrl};

Expand Down Expand Up @@ -193,15 +192,7 @@ pub(crate) fn infer_to_python_known(
}
ObType::Timedelta => {
let either_delta = EitherTimedelta::try_from(value)?;
let x = match extra.config.effective_delta_mode() {
EffectiveDeltaMode::Temporal(temporal_mode) => {
temporal_mode.timedelta_to_json(value.py(), either_delta)
}
EffectiveDeltaMode::Timedelta(timedelta_mode) => {
timedelta_mode.either_delta_to_json(value.py(), either_delta)
}
}?;
x
extra.config.temporal_mode.timedelta_to_json(value.py(), either_delta)?
}
ObType::Url => {
let py_url: PyUrl = value.extract()?;
Expand Down Expand Up @@ -480,14 +471,7 @@ pub(crate) fn infer_serialize_known<S: Serializer>(
}
ObType::Timedelta => {
let either_delta = EitherTimedelta::try_from(value).map_err(py_err_se_err)?;
match extra.config.effective_delta_mode() {
EffectiveDeltaMode::Temporal(temporal_mode) => {
temporal_mode.timedelta_serialize(either_delta, serializer)
}
EffectiveDeltaMode::Timedelta(timedelta_mode) => {
timedelta_mode.timedelta_serialize(value.py(), either_delta, serializer)
}
}
extra.config.temporal_mode.timedelta_serialize(either_delta, serializer)
}
ObType::Url => {
let py_url: PyUrl = value.extract().map_err(py_err_se_err)?;
Expand Down Expand Up @@ -654,10 +638,7 @@ pub(crate) fn infer_json_key_known<'a>(
}
ObType::Timedelta => {
let either_delta = EitherTimedelta::try_from(key)?;
match extra.config.effective_delta_mode() {
EffectiveDeltaMode::Temporal(temporal_mode) => temporal_mode.timedelta_json_key(&either_delta),
EffectiveDeltaMode::Timedelta(timedelta_mode) => timedelta_mode.json_key(key.py(), either_delta),
}
extra.config.temporal_mode.timedelta_json_key(&either_delta)
}
ObType::Url => {
let py_url: PyUrl = key.extract()?;
Expand Down
37 changes: 20 additions & 17 deletions src/serializers/type_serializers/datetime_etc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,23 @@ pub(crate) fn datetime_to_string(py_dt: &Bound<'_, PyDateTime>) -> PyResult<Stri
}

pub(crate) fn datetime_to_seconds(py_dt: &Bound<'_, PyDateTime>) -> PyResult<f64> {
pydatetime_as_datetime(py_dt).map(|dt|
pydatetime_as_datetime(py_dt).map(|dt| {
dt.date.timestamp() as f64
+ dt.time.hour as f64 * 3600.0
+ dt.time.minute as f64 * 60.0
+ dt.time.second as f64
+ dt.time.microsecond as f64 / 1_000_000.0
)
+ f64::from(dt.time.hour) * 3600.0
+ f64::from(dt.time.minute) * 60.0
+ f64::from(dt.time.second)
+ f64::from(dt.time.microsecond) / 1_000_000.0
})
}

pub(crate) fn datetime_to_milliseconds(py_dt: &Bound<'_, PyDateTime>) -> PyResult<f64> {
pydatetime_as_datetime(py_dt).map(|dt|
pydatetime_as_datetime(py_dt).map(|dt| {
dt.date.timestamp_ms() as f64
+ dt.time.hour as f64 * 3_600_000.0
+ dt.time.minute as f64 * 60_000.0
+ dt.time.second as f64 * 1_000.0
+ dt.time.microsecond as f64 / 1_000.0
)
+ f64::from(dt.time.hour) * 3_600_000.0
+ f64::from(dt.time.minute) * 60_000.0
+ f64::from(dt.time.second) * 1_000.0
+ f64::from(dt.time.microsecond) / 1_000.0
})
}

pub(crate) fn date_to_seconds(py_date: &Bound<'_, PyDate>) -> PyResult<f64> {
Expand All @@ -53,16 +53,19 @@ pub(crate) fn time_to_string(py_time: &Bound<'_, PyTime>) -> PyResult<String> {

pub(crate) fn time_to_seconds(py_time: &Bound<'_, PyTime>) -> PyResult<f64> {
pytime_as_time(py_time, None).map(|t| {
t.hour as f64 * 3600.0 + t.minute as f64 * 60.0 + t.second as f64 + t.microsecond as f64 / 1_000_000.0
f64::from(t.hour) * 3600.0
+ f64::from(t.minute) * 60.0
+ f64::from(t.second)
+ f64::from(t.microsecond) / 1_000_000.0
})
}

pub(crate) fn time_to_milliseconds(py_time: &Bound<'_, PyTime>) -> PyResult<f64> {
pytime_as_time(py_time, None).map(|t| {
t.hour as f64 * 3_600_000.0
+ t.minute as f64 * 60_000.0
+ t.second as f64 * 1_000.0
+ t.microsecond as f64 / 1_000.0
f64::from(t.hour) * 3_600_000.0
+ f64::from(t.minute) * 60_000.0
+ f64::from(t.second) * 1_000.0
+ f64::from(t.microsecond) / 1_000.0
})
}

Expand Down
60 changes: 12 additions & 48 deletions src/serializers/type_serializers/timedelta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,7 @@ use super::{

#[derive(Debug)]
pub struct TimeDeltaSerializer {
timedelta_mode: TimedeltaMode,
temporal_mode: TemporalMode,
prefer_timedelta: bool,
}

pub enum EffectiveDeltaMode {
Timedelta(TimedeltaMode),
Temporal(TemporalMode),
}

impl BuildSerializer for TimeDeltaSerializer {
Expand All @@ -32,29 +25,17 @@ impl BuildSerializer for TimeDeltaSerializer {
config: Option<&Bound<'_, PyDict>>,
_definitions: &mut DefinitionsBuilder<CombinedSerializer>,
) -> PyResult<CombinedSerializer> {
let timedelta_mode = TimedeltaMode::from_config(config)?;
let temporal_mode = TemporalMode::from_config(config)?;

let prefer_timedelta_mode = config
.and_then(|cfg| cfg.contains(intern!(cfg.py(), "ser_json_timedelta")).ok())
let temporal_set = config
.and_then(|cfg| cfg.contains(intern!(cfg.py(), "ser_json_temporal")).ok())
.unwrap_or(false);

Ok(Self {
timedelta_mode,
temporal_mode,
prefer_timedelta: prefer_timedelta_mode,
}
.into())
}
}

impl TimeDeltaSerializer {
pub fn effective_delta_mode(&self) -> EffectiveDeltaMode {
if self.prefer_timedelta {
EffectiveDeltaMode::Timedelta(self.timedelta_mode)
let temporal_mode = if temporal_set {
TemporalMode::from_config(config)?
} else {
EffectiveDeltaMode::Temporal(self.temporal_mode)
}
let td_mode = TimedeltaMode::from_config(config)?;
td_mode.into()
};

Ok(Self { temporal_mode }.into())
}
}

Expand All @@ -70,14 +51,7 @@ impl TypeSerializer for TimeDeltaSerializer {
) -> PyResult<PyObject> {
match extra.mode {
SerMode::Json => match EitherTimedelta::try_from(value) {
Ok(either_timedelta) => match self.effective_delta_mode() {
EffectiveDeltaMode::Timedelta(timedelta_mode) => {
Ok(timedelta_mode.either_delta_to_json(value.py(), either_timedelta)?)
}
EffectiveDeltaMode::Temporal(temporal_mode) => {
Ok(temporal_mode.timedelta_to_json(value.py(), either_timedelta)?)
}
},
Ok(either_timedelta) => Ok(self.temporal_mode.timedelta_to_json(value.py(), either_timedelta)?),
Err(_) => {
extra.warnings.on_fallback_py(self.get_name(), value, extra)?;
infer_to_python(value, include, exclude, extra)
Expand All @@ -89,10 +63,7 @@ impl TypeSerializer for TimeDeltaSerializer {

fn json_key<'a>(&self, key: &'a Bound<'_, PyAny>, extra: &Extra) -> PyResult<Cow<'a, str>> {
match EitherTimedelta::try_from(key) {
Ok(either_timedelta) => match self.effective_delta_mode() {
EffectiveDeltaMode::Timedelta(timedelta_mode) => timedelta_mode.json_key(key.py(), either_timedelta),
EffectiveDeltaMode::Temporal(temporal_mode) => temporal_mode.timedelta_json_key(&either_timedelta),
},
Ok(either_timedelta) => self.temporal_mode.timedelta_json_key(&either_timedelta),
Err(_) => {
extra.warnings.on_fallback_py(self.get_name(), key, extra)?;
infer_json_key(key, extra)
Expand All @@ -109,14 +80,7 @@ impl TypeSerializer for TimeDeltaSerializer {
extra: &Extra,
) -> Result<S::Ok, S::Error> {
match EitherTimedelta::try_from(value) {
Ok(either_timedelta) => match self.effective_delta_mode() {
EffectiveDeltaMode::Timedelta(timedelta_mode) => {
timedelta_mode.timedelta_serialize(value.py(), either_timedelta, serializer)
}
EffectiveDeltaMode::Temporal(temporal_mode) => {
temporal_mode.timedelta_serialize(either_timedelta, serializer)
}
},
Ok(either_timedelta) => self.temporal_mode.timedelta_serialize(either_timedelta, serializer),
Err(_) => {
extra.warnings.on_fallback_ser::<S>(self.get_name(), value, extra)?;
infer_serialize(value, serializer, include, exclude, extra)
Expand Down