Skip to content

Commit d5434f7

Browse files
committed
Coerce 'timedelta' schema constraints
1 parent 6c936de commit d5434f7

File tree

10 files changed

+64
-25
lines changed

10 files changed

+64
-25
lines changed

src/validators/date.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use crate::build_tools::{is_strict, py_schema_error_type};
99
use crate::errors::{ErrorType, ErrorTypeDefaults, ValError, ValResult};
1010
use crate::input::{EitherDate, Input};
1111

12-
use crate::tools::SchemaDict;
1312
use crate::validators::datetime::{NowConstraint, NowOp};
1413

1514
use super::Exactness;
@@ -177,7 +176,7 @@ impl DateConstraints {
177176
}
178177

179178
fn convert_pydate(schema: &Bound<'_, PyDict>, key: &Bound<'_, PyString>) -> PyResult<Option<Date>> {
180-
match schema.get_as::<Bound<'_, PyAny>>(key)? {
179+
match schema.get_item(key)? {
181180
Some(value) => match value.validate_date(false) {
182181
Ok(v) => Ok(Some(v.into_inner().as_raw()?)),
183182
Err(_) => Err(PyValueError::new_err(format!(

src/validators/datetime.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ impl DateTimeConstraints {
210210
}
211211

212212
fn py_datetime_as_datetime(schema: &Bound<'_, PyDict>, key: &Bound<'_, PyString>) -> PyResult<Option<DateTime>> {
213-
match schema.get_as::<Bound<'_, PyAny>>(key)? {
213+
match schema.get_item(key)? {
214214
Some(value) => match value.validate_datetime(false, MicrosecondsPrecisionOverflowBehavior::Truncate) {
215215
Ok(v) => Ok(Some(v.into_inner().as_raw()?)),
216216
Err(_) => Err(PyValueError::new_err(format!(

src/validators/decimal.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use pyo3::exceptions::{PyTypeError, PyValueError};
22
use pyo3::intern;
33
use pyo3::sync::GILOnceCell;
4-
use pyo3::types::{IntoPyDict, PyDict, PyString, PyTuple, PyType};
4+
use pyo3::types::{IntoPyDict, PyDict, PyTuple, PyType};
55
use pyo3::{prelude::*, PyTypeInfo};
66

77
use crate::build_tools::{is_strict, schema_or_config_same};
@@ -29,7 +29,7 @@ pub fn get_decimal_type(py: Python) -> &Bound<'_, PyType> {
2929
}
3030

3131
fn validate_as_decimal(py: Python, schema: &Bound<'_, PyDict>, key: &str) -> PyResult<Option<Py<PyAny>>> {
32-
match schema.get_as::<Bound<'_, PyAny>>(&PyString::new(py, key))? {
32+
match schema.get_item(key)? {
3333
Some(value) => match value.validate_decimal(false, py) {
3434
Ok(v) => Ok(Some(v.into_inner().unbind())),
3535
Err(_) => Err(PyValueError::new_err(format!(

src/validators/int.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,17 @@ use num_bigint::BigInt;
22
use pyo3::exceptions::PyValueError;
33
use pyo3::intern;
44
use pyo3::prelude::*;
5-
use pyo3::types::{PyDict, PyString};
5+
use pyo3::types::PyDict;
66
use pyo3::IntoPyObjectExt;
77

88
use crate::build_tools::is_strict;
99
use crate::errors::{ErrorType, ValError, ValResult};
1010
use crate::input::{Input, Int};
11-
use crate::tools::SchemaDict;
1211

1312
use super::{BuildValidator, CombinedValidator, DefinitionsBuilder, ValidationState, Validator};
1413

15-
fn validate_as_int(py: Python, schema: &Bound<'_, PyDict>, key: &str) -> PyResult<Option<Int>> {
16-
match schema.get_as::<Bound<'_, PyAny>>(&PyString::new(py, key))? {
14+
fn validate_as_int(schema: &Bound<'_, PyDict>, key: &str) -> PyResult<Option<Int>> {
15+
match schema.get_item(key)? {
1716
Some(value) => match value.validate_int(false) {
1817
Ok(v) => match v.into_inner().as_int() {
1918
Ok(v) => Ok(Some(v)),
@@ -90,14 +89,13 @@ pub struct ConstrainedIntValidator {
9089

9190
impl ConstrainedIntValidator {
9291
fn build(schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>) -> PyResult<CombinedValidator> {
93-
let py = schema.py();
9492
Ok(Self {
9593
strict: is_strict(schema, config)?,
96-
multiple_of: validate_as_int(py, schema, "multiple_of")?,
97-
le: validate_as_int(py, schema, "le")?,
98-
lt: validate_as_int(py, schema, "lt")?,
99-
ge: validate_as_int(py, schema, "ge")?,
100-
gt: validate_as_int(py, schema, "gt")?,
94+
multiple_of: validate_as_int(schema, "multiple_of")?,
95+
le: validate_as_int(schema, "le")?,
96+
lt: validate_as_int(schema, "lt")?,
97+
ge: validate_as_int(schema, "ge")?,
98+
gt: validate_as_int(schema, "gt")?,
10199
}
102100
.into())
103101
}

src/validators/timedelta.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
use pyo3::exceptions::PyValueError;
12
use pyo3::prelude::*;
23
use pyo3::types::{PyDelta, PyDeltaAccess, PyDict};
3-
use speedate::Duration;
4+
use speedate::{Duration, MicrosecondsPrecisionOverflowBehavior};
45

56
use crate::build_tools::is_strict;
67
use crate::errors::{ErrorType, ValError, ValResult};
7-
use crate::input::{duration_as_pytimedelta, EitherTimedelta, Input};
8+
use crate::input::{duration_as_pytimedelta, Input};
89

910
use super::datetime::extract_microseconds_precision;
1011
use super::{BuildValidator, CombinedValidator, DefinitionsBuilder, ValidationState, Validator};
@@ -26,10 +27,12 @@ struct TimedeltaConstraints {
2627

2728
fn get_constraint(schema: &Bound<'_, PyDict>, key: &str) -> PyResult<Option<Duration>> {
2829
match schema.get_item(key)? {
29-
Some(value) => {
30-
let either_timedelta = EitherTimedelta::try_from(&value)?;
31-
Ok(Some(either_timedelta.to_duration()?))
32-
}
30+
Some(value) => match value.validate_timedelta(false, MicrosecondsPrecisionOverflowBehavior::default()) {
31+
Ok(v) => Ok(Some(v.into_inner().to_duration()?)),
32+
Err(_) => Err(PyValueError::new_err(format!(
33+
"'{key}' must be coercible to a timedelta instance"
34+
))),
35+
},
3336
None => Ok(None),
3437
}
3538
}

tests/validators/test_date.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,17 @@
1717
'constraint',
1818
['le', 'lt', 'ge', 'gt'],
1919
)
20-
def test_constraints_schema_validation(constraint: str) -> None:
20+
def test_constraints_schema_validation_error(constraint: str) -> None:
2121
with pytest.raises(SchemaError, match=f"'{constraint}' must be coercible to a date instance"):
2222
SchemaValidator(cs.date_schema(**{constraint: 'bad_value'}))
2323

2424

25+
def test_constraints_schema_validation() -> None:
26+
val = SchemaValidator(cs.date_schema(gt='2020-01-01'))
27+
with pytest.raises(ValidationError):
28+
val.validate_python('2019-01-01')
29+
30+
2531
@pytest.mark.parametrize(
2632
'input_value,expected',
2733
[

tests/validators/test_datetime.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,17 @@
1818
'constraint',
1919
['le', 'lt', 'ge', 'gt'],
2020
)
21-
def test_constraints_schema_validation(constraint: str) -> None:
21+
def test_constraints_schema_validation_error(constraint: str) -> None:
2222
with pytest.raises(SchemaError, match=f"'{constraint}' must be coercible to a datetime instance"):
2323
SchemaValidator(cs.datetime_schema(**{constraint: 'bad_value'}))
2424

2525

26+
def test_constraints_schema_validation() -> None:
27+
val = SchemaValidator(cs.datetime_schema(gt='2020-01-01T00:00:00'))
28+
with pytest.raises(ValidationError):
29+
val.validate_python('2019-01-01T00:00:00')
30+
31+
2632
@pytest.mark.parametrize(
2733
'input_value,expected',
2834
[

tests/validators/test_decimal.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,17 @@ class DecimalSubclass(Decimal):
2525
'constraint',
2626
['multiple_of', 'le', 'lt', 'ge', 'gt'],
2727
)
28-
def test_constraints_schema_validation(constraint: str) -> None:
28+
def test_constraints_schema_validation_error(constraint: str) -> None:
2929
with pytest.raises(SchemaError, match=f"'{constraint}' must be coercible to a Decimal instance"):
3030
SchemaValidator(cs.decimal_schema(**{constraint: 'bad_value'}))
3131

3232

33+
def test_constraints_schema_validation() -> None:
34+
val = SchemaValidator(cs.decimal_schema(gt='1'))
35+
with pytest.raises(ValidationError):
36+
val.validate_python('0')
37+
38+
3339
@pytest.mark.parametrize(
3440
'input_value,expected',
3541
[

tests/validators/test_int.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,17 @@
1818
'constraint',
1919
['multiple_of', 'le', 'lt', 'ge', 'gt'],
2020
)
21-
def test_constraints_schema_validation(constraint: str) -> None:
21+
def test_constraints_schema_validation_error(constraint: str) -> None:
2222
with pytest.raises(SchemaError, match=f"'{constraint}' must be coercible to an integer"):
2323
SchemaValidator(cs.int_schema(**{constraint: 'bad_value'}))
2424

2525

26+
def test_constraints_schema_validation() -> None:
27+
val = SchemaValidator(cs.int_schema(gt='1'))
28+
with pytest.raises(ValidationError):
29+
val.validate_python('0')
30+
31+
2632
@pytest.mark.parametrize(
2733
'input_value,expected',
2834
[

tests/validators/test_timedelta.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,21 @@
1515
pandas = None
1616

1717

18+
@pytest.mark.parametrize(
19+
'constraint',
20+
['le', 'lt', 'ge', 'gt'],
21+
)
22+
def test_constraints_schema_validation_error(constraint: str) -> None:
23+
with pytest.raises(SchemaError, match=f"'{constraint}' must be coercible to a timedelta instance"):
24+
SchemaValidator(core_schema.timedelta_schema(**{constraint: 'bad_value'}))
25+
26+
27+
def test_constraints_schema_validation() -> None:
28+
val = SchemaValidator(core_schema.timedelta_schema(gt=3))
29+
with pytest.raises(ValidationError):
30+
val.validate_python(1)
31+
32+
1833
@pytest.mark.parametrize(
1934
'input_value,expected',
2035
[

0 commit comments

Comments
 (0)