Skip to content

Commit 0b5891d

Browse files
committed
use timedelta repr as constraint in error messages
1 parent ce68729 commit 0b5891d

File tree

4 files changed

+62
-49
lines changed

4 files changed

+62
-49
lines changed

src/input/datetime.rs

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -120,24 +120,25 @@ impl<'a> EitherTimedelta<'a> {
120120
}
121121
}
122122

123-
pub fn try_into_py(self, py: Python<'_>) -> PyResult<PyObject> {
124-
let timedelta = match self {
125-
Self::Py(timedelta) => Ok(timedelta),
126-
Self::Raw(duration) => {
127-
let sign = if duration.positive { 1 } else { -1 };
128-
PyDelta::new(
129-
py,
130-
sign * duration.day as i32,
131-
sign * duration.second as i32,
132-
sign * duration.microsecond as i32,
133-
true,
134-
)
135-
}
136-
}?;
137-
Ok(timedelta.into_py(py))
123+
pub fn try_into_py(&self, py: Python<'a>) -> PyResult<&'a PyDelta> {
124+
match self {
125+
Self::Py(timedelta) => Ok(*timedelta),
126+
Self::Raw(duration) => duration_as_pytimedelta(py, duration),
127+
}
138128
}
139129
}
140130

131+
pub fn duration_as_pytimedelta<'py>(py: Python<'py>, duration: &Duration) -> PyResult<&'py PyDelta> {
132+
let sign = if duration.positive { 1 } else { -1 };
133+
PyDelta::new(
134+
py,
135+
sign * duration.day as i32,
136+
sign * duration.second as i32,
137+
sign * duration.microsecond as i32,
138+
true,
139+
)
140+
}
141+
141142
pub fn pytime_as_time(py_time: &PyAny, py_dt: Option<&PyAny>) -> PyResult<Time> {
142143
let py = py_time.py();
143144

src/input/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ mod return_enums;
1111
mod shared;
1212

1313
pub(crate) use datetime::{
14-
pydate_as_date, pydatetime_as_datetime, pytime_as_time, pytimedelta_as_duration, EitherDate, EitherDateTime,
15-
EitherTime, EitherTimedelta,
14+
duration_as_pytimedelta, pydate_as_date, pydatetime_as_datetime, pytime_as_time, pytimedelta_as_duration,
15+
EitherDate, EitherDateTime, EitherTime, EitherTimedelta,
1616
};
1717
pub(crate) use input_abstract::{Input, InputType};
1818
pub(crate) use parse_json::{JsonInput, JsonObject};

src/validators/timedelta.rs

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
use pyo3::intern;
22
use pyo3::prelude::*;
3-
use pyo3::types::{PyDelta, PyDict, PyString};
3+
use pyo3::types::{PyDelta, PyDict};
44
use speedate::Duration;
55

66
use crate::build_tools::is_strict;
77
use crate::errors::{ErrorType, ValError, ValResult};
8-
use crate::input::{EitherTimedelta, Input};
8+
use crate::input::{duration_as_pytimedelta, pytimedelta_as_duration, Input};
99
use crate::recursion_guard::RecursionGuard;
1010
use crate::tools::SchemaDict;
1111

@@ -24,6 +24,7 @@ struct TimedeltaConstraints {
2424
ge: Option<Duration>,
2525
gt: Option<Duration>,
2626
}
27+
2728
impl BuildValidator for TimeDeltaValidator {
2829
const EXPECTED_TYPE: &'static str = "timedelta";
2930

@@ -32,23 +33,29 @@ impl BuildValidator for TimeDeltaValidator {
3233
config: Option<&PyDict>,
3334
_definitions: &mut DefinitionsBuilder<CombinedValidator>,
3435
) -> PyResult<CombinedValidator> {
35-
let py = schema.py();
36-
let has_constraints = schema.get_item(intern!(py, "le")).is_some()
37-
|| schema.get_item(intern!(py, "lt")).is_some()
38-
|| schema.get_item(intern!(py, "ge")).is_some()
39-
|| schema.get_item(intern!(py, "gt")).is_some();
36+
let py: Python<'_> = schema.py();
37+
let constraints = TimedeltaConstraints {
38+
le: schema
39+
.get_as::<&PyDelta>(intern!(py, "le"))?
40+
.map(pytimedelta_as_duration),
41+
lt: schema
42+
.get_as::<&PyDelta>(intern!(py, "lt"))?
43+
.map(pytimedelta_as_duration),
44+
ge: schema
45+
.get_as::<&PyDelta>(intern!(py, "ge"))?
46+
.map(pytimedelta_as_duration),
47+
gt: schema
48+
.get_as::<&PyDelta>(intern!(py, "gt"))?
49+
.map(pytimedelta_as_duration),
50+
};
4051

4152
Ok(Self {
4253
strict: is_strict(schema, config)?,
43-
constraints: match has_constraints {
44-
true => Some(TimedeltaConstraints {
45-
le: py_timedelta_as_timedelta(schema, intern!(py, "le"))?,
46-
lt: py_timedelta_as_timedelta(schema, intern!(py, "lt"))?,
47-
ge: py_timedelta_as_timedelta(schema, intern!(py, "ge"))?,
48-
gt: py_timedelta_as_timedelta(schema, intern!(py, "gt"))?,
49-
}),
50-
false => None,
51-
},
54+
constraints: (constraints.le.is_some()
55+
|| constraints.lt.is_some()
56+
|| constraints.ge.is_some()
57+
|| constraints.gt.is_some())
58+
.then_some(constraints),
5259
}
5360
.into())
5461
}
@@ -64,6 +71,7 @@ impl Validator for TimeDeltaValidator {
6471
_recursion_guard: &'s mut RecursionGuard,
6572
) -> ValResult<'data, PyObject> {
6673
let timedelta = input.validate_timedelta(extra.strict.unwrap_or(self.strict))?;
74+
let py_timedelta = timedelta.try_into_py(py)?;
6775
if let Some(constraints) = &self.constraints {
6876
let raw_timedelta = timedelta.as_raw();
6977

@@ -73,9 +81,12 @@ impl Validator for TimeDeltaValidator {
7381
if !raw_timedelta.$constraint(constraint) {
7482
return Err(ValError::new(
7583
ErrorType::$error {
76-
$constraint: constraint.to_string().into(),
84+
$constraint: duration_as_pytimedelta(py, constraint)?
85+
.repr()?
86+
.to_string()
87+
.into(),
7788
},
78-
input,
89+
py_timedelta.as_ref(),
7990
));
8091
}
8192
}
@@ -87,7 +98,7 @@ impl Validator for TimeDeltaValidator {
8798
check_constraint!(ge, GreaterThanEqual);
8899
check_constraint!(gt, GreaterThan);
89100
}
90-
Ok(timedelta.try_into_py(py)?)
101+
Ok(py_timedelta.into())
91102
}
92103

93104
fn different_strict_behavior(
@@ -106,10 +117,3 @@ impl Validator for TimeDeltaValidator {
106117
Ok(())
107118
}
108119
}
109-
110-
fn py_timedelta_as_timedelta(schema: &PyDict, field: &PyString) -> PyResult<Option<Duration>> {
111-
match schema.get_as::<&PyDelta>(field)? {
112-
Some(timedelta) => Ok(Some(EitherTimedelta::Py(timedelta).as_raw())),
113-
None => Ok(None),
114-
}
115-
}

tests/validators/test_timedelta.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,27 +131,35 @@ def test_timedelta_strict_json(input_value, expected):
131131
({}, 'P0Y0M3D2WT1H2M3S', timedelta(days=3, weeks=2, hours=1, minutes=2, seconds=3)),
132132
({'le': timedelta(days=3)}, 'P2DT1H', timedelta(days=2, hours=1)),
133133
({'le': timedelta(days=3)}, 'P3DT0H', timedelta(days=3)),
134-
({'le': timedelta(days=3)}, 'P3DT1H', Err('Input should be less than or equal to P3D')),
134+
({'le': timedelta(days=3)}, 'P3DT1H', Err('Input should be less than or equal to datetime.timedelta(days=3)')),
135135
({'lt': timedelta(days=3)}, 'P2DT1H', timedelta(days=2, hours=1)),
136-
({'lt': timedelta(days=3)}, 'P3DT1H', Err('Input should be less than P3D')),
136+
({'lt': timedelta(days=3)}, 'P3DT1H', Err('Input should be less than datetime.timedelta(days=3)')),
137137
({'ge': timedelta(days=3)}, 'P3DT1H', timedelta(days=3, hours=1)),
138138
({'ge': timedelta(days=3)}, 'P3D', timedelta(days=3)),
139-
({'ge': timedelta(days=3)}, 'P2DT1H', Err('Input should be greater than or equal to P3D')),
139+
(
140+
{'ge': timedelta(days=3)},
141+
'P2DT1H',
142+
Err('Input should be greater than or equal to datetime.timedelta(days=3)'),
143+
),
140144
({'gt': timedelta(days=3)}, 'P3DT1H', timedelta(days=3, hours=1)),
141-
({'gt': 'P3D'}, 'P2DT1H', Err('Input should be greater than P3D')),
145+
({'gt': 'P3D'}, 'P2DT1H', Err('Input should be greater than datetime.timedelta(days=3)')),
142146
({'le': timedelta(seconds=-86400.123)}, '-PT86400.123S', timedelta(seconds=-86400.123)),
143147
({'le': timedelta(seconds=-86400.123)}, '-PT86400.124S', timedelta(seconds=-86400.124)),
144148
(
145149
{'le': timedelta(seconds=-86400.123)},
146150
'-PT86400.122S',
147-
Err('Input should be less than or equal to -P1DT0.123S [type=less_than_equal'),
151+
Err(
152+
'Input should be less than or equal to datetime.timedelta(days=-2, seconds=86399, microseconds=877000) [type=less_than_equal' # noqa: E501
153+
),
148154
),
149155
({'gt': timedelta(seconds=-86400.123)}, timedelta(seconds=-86400.122), timedelta(seconds=-86400.122)),
150156
({'gt': timedelta(seconds=-86400.123)}, '-PT86400.122S', timedelta(seconds=-86400.122)),
151157
(
152158
{'gt': timedelta(seconds=-86400.123)},
153159
'-PT86400.124S',
154-
Err('Input should be greater than -P1DT0.123S [type=greater_than'),
160+
Err(
161+
'Input should be greater than datetime.timedelta(days=-2, seconds=86399, microseconds=877000) [type=greater_than' # noqa: E501
162+
),
155163
),
156164
],
157165
ids=repr,

0 commit comments

Comments
 (0)