Skip to content

use timedelta repr as constraint in error messages #747

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 1 commit into from
Jul 6, 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
31 changes: 16 additions & 15 deletions src/input/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,24 +120,25 @@ impl<'a> EitherTimedelta<'a> {
}
}

pub fn try_into_py(self, py: Python<'_>) -> PyResult<PyObject> {
let timedelta = match self {
Self::Py(timedelta) => Ok(timedelta),
Self::Raw(duration) => {
let sign = if duration.positive { 1 } else { -1 };
PyDelta::new(
py,
sign * duration.day as i32,
sign * duration.second as i32,
sign * duration.microsecond as i32,
true,
)
}
}?;
Ok(timedelta.into_py(py))
pub fn try_into_py(&self, py: Python<'a>) -> PyResult<&'a PyDelta> {
match self {
Self::Py(timedelta) => Ok(*timedelta),
Self::Raw(duration) => duration_as_pytimedelta(py, duration),
}
}
}

pub fn duration_as_pytimedelta<'py>(py: Python<'py>, duration: &Duration) -> PyResult<&'py PyDelta> {
let sign = if duration.positive { 1 } else { -1 };
PyDelta::new(
py,
sign * duration.day as i32,
sign * duration.second as i32,
sign * duration.microsecond as i32,
true,
)
}

pub fn pytime_as_time(py_time: &PyAny, py_dt: Option<&PyAny>) -> PyResult<Time> {
let py = py_time.py();

Expand Down
4 changes: 2 additions & 2 deletions src/input/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ mod return_enums;
mod shared;

pub(crate) use datetime::{
pydate_as_date, pydatetime_as_datetime, pytime_as_time, pytimedelta_as_duration, EitherDate, EitherDateTime,
EitherTime, EitherTimedelta,
duration_as_pytimedelta, pydate_as_date, pydatetime_as_datetime, pytime_as_time, pytimedelta_as_duration,
EitherDate, EitherDateTime, EitherTime, EitherTimedelta,
};
pub(crate) use input_abstract::{Input, InputType};
pub(crate) use parse_json::{JsonInput, JsonObject};
Expand Down
56 changes: 30 additions & 26 deletions src/validators/timedelta.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use pyo3::intern;
use pyo3::prelude::*;
use pyo3::types::{PyDelta, PyDict, PyString};
use pyo3::types::{PyDelta, PyDict};
use speedate::Duration;

use crate::build_tools::is_strict;
use crate::errors::{ErrorType, ValError, ValResult};
use crate::input::{EitherTimedelta, Input};
use crate::input::{duration_as_pytimedelta, pytimedelta_as_duration, Input};
use crate::recursion_guard::RecursionGuard;
use crate::tools::SchemaDict;

Expand All @@ -24,6 +24,7 @@ struct TimedeltaConstraints {
ge: Option<Duration>,
gt: Option<Duration>,
}

impl BuildValidator for TimeDeltaValidator {
const EXPECTED_TYPE: &'static str = "timedelta";

Expand All @@ -32,23 +33,29 @@ impl BuildValidator for TimeDeltaValidator {
config: Option<&PyDict>,
_definitions: &mut DefinitionsBuilder<CombinedValidator>,
) -> PyResult<CombinedValidator> {
let py = schema.py();
let has_constraints = schema.get_item(intern!(py, "le")).is_some()
|| schema.get_item(intern!(py, "lt")).is_some()
|| schema.get_item(intern!(py, "ge")).is_some()
|| schema.get_item(intern!(py, "gt")).is_some();
let py: Python<'_> = schema.py();
let constraints = TimedeltaConstraints {
le: schema
.get_as::<&PyDelta>(intern!(py, "le"))?
.map(pytimedelta_as_duration),
lt: schema
.get_as::<&PyDelta>(intern!(py, "lt"))?
.map(pytimedelta_as_duration),
ge: schema
.get_as::<&PyDelta>(intern!(py, "ge"))?
.map(pytimedelta_as_duration),
gt: schema
.get_as::<&PyDelta>(intern!(py, "gt"))?
.map(pytimedelta_as_duration),
};

Ok(Self {
strict: is_strict(schema, config)?,
constraints: match has_constraints {
true => Some(TimedeltaConstraints {
le: py_timedelta_as_timedelta(schema, intern!(py, "le"))?,
lt: py_timedelta_as_timedelta(schema, intern!(py, "lt"))?,
ge: py_timedelta_as_timedelta(schema, intern!(py, "ge"))?,
gt: py_timedelta_as_timedelta(schema, intern!(py, "gt"))?,
}),
false => None,
},
constraints: (constraints.le.is_some()
|| constraints.lt.is_some()
|| constraints.ge.is_some()
|| constraints.gt.is_some())
.then_some(constraints),
}
.into())
}
Expand All @@ -64,6 +71,7 @@ impl Validator for TimeDeltaValidator {
_recursion_guard: &'s mut RecursionGuard,
) -> ValResult<'data, PyObject> {
let timedelta = input.validate_timedelta(extra.strict.unwrap_or(self.strict))?;
let py_timedelta = timedelta.try_into_py(py)?;
if let Some(constraints) = &self.constraints {
let raw_timedelta = timedelta.as_raw();

Expand All @@ -73,9 +81,12 @@ impl Validator for TimeDeltaValidator {
if !raw_timedelta.$constraint(constraint) {
return Err(ValError::new(
ErrorType::$error {
$constraint: constraint.to_string().into(),
$constraint: duration_as_pytimedelta(py, constraint)?
.repr()?
.to_string()
.into(),
},
input,
py_timedelta.as_ref(),
));
}
}
Expand All @@ -87,7 +98,7 @@ impl Validator for TimeDeltaValidator {
check_constraint!(ge, GreaterThanEqual);
check_constraint!(gt, GreaterThan);
}
Ok(timedelta.try_into_py(py)?)
Ok(py_timedelta.into())
}

fn different_strict_behavior(
Expand All @@ -106,10 +117,3 @@ impl Validator for TimeDeltaValidator {
Ok(())
}
}

fn py_timedelta_as_timedelta(schema: &PyDict, field: &PyString) -> PyResult<Option<Duration>> {
match schema.get_as::<&PyDelta>(field)? {
Some(timedelta) => Ok(Some(EitherTimedelta::Py(timedelta).as_raw())),
None => Ok(None),
}
}
20 changes: 14 additions & 6 deletions tests/validators/test_timedelta.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,27 +131,35 @@ def test_timedelta_strict_json(input_value, expected):
({}, 'P0Y0M3D2WT1H2M3S', timedelta(days=3, weeks=2, hours=1, minutes=2, seconds=3)),
({'le': timedelta(days=3)}, 'P2DT1H', timedelta(days=2, hours=1)),
({'le': timedelta(days=3)}, 'P3DT0H', timedelta(days=3)),
({'le': timedelta(days=3)}, 'P3DT1H', Err('Input should be less than or equal to P3D')),
({'le': timedelta(days=3)}, 'P3DT1H', Err('Input should be less than or equal to datetime.timedelta(days=3)')),
({'lt': timedelta(days=3)}, 'P2DT1H', timedelta(days=2, hours=1)),
({'lt': timedelta(days=3)}, 'P3DT1H', Err('Input should be less than P3D')),
({'lt': timedelta(days=3)}, 'P3DT1H', Err('Input should be less than datetime.timedelta(days=3)')),
({'ge': timedelta(days=3)}, 'P3DT1H', timedelta(days=3, hours=1)),
({'ge': timedelta(days=3)}, 'P3D', timedelta(days=3)),
({'ge': timedelta(days=3)}, 'P2DT1H', Err('Input should be greater than or equal to P3D')),
(
{'ge': timedelta(days=3)},
'P2DT1H',
Err('Input should be greater than or equal to datetime.timedelta(days=3)'),
),
({'gt': timedelta(days=3)}, 'P3DT1H', timedelta(days=3, hours=1)),
({'gt': 'P3D'}, 'P2DT1H', Err('Input should be greater than P3D')),
({'gt': 'P3D'}, 'P2DT1H', Err('Input should be greater than datetime.timedelta(days=3)')),
({'le': timedelta(seconds=-86400.123)}, '-PT86400.123S', timedelta(seconds=-86400.123)),
({'le': timedelta(seconds=-86400.123)}, '-PT86400.124S', timedelta(seconds=-86400.124)),
(
{'le': timedelta(seconds=-86400.123)},
'-PT86400.122S',
Err('Input should be less than or equal to -P1DT0.123S [type=less_than_equal'),
Err(
'Input should be less than or equal to datetime.timedelta(days=-2, seconds=86399, microseconds=877000) [type=less_than_equal' # noqa: E501
),
),
({'gt': timedelta(seconds=-86400.123)}, timedelta(seconds=-86400.122), timedelta(seconds=-86400.122)),
({'gt': timedelta(seconds=-86400.123)}, '-PT86400.122S', timedelta(seconds=-86400.122)),
(
{'gt': timedelta(seconds=-86400.123)},
'-PT86400.124S',
Err('Input should be greater than -P1DT0.123S [type=greater_than'),
Err(
'Input should be greater than datetime.timedelta(days=-2, seconds=86399, microseconds=877000) [type=greater_than' # noqa: E501
),
),
],
ids=repr,
Expand Down