Skip to content
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
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,8 @@ jobs:
# https://github.com/marketplace/actions/alls-green#why used for branch protection checks
check:
if: always()
needs: [coverage, test-python, test-os, test-debug, lint, bench, build-wasm-emscripten]
# TODO: add build-wasm-emscripten back when CI installation failures are solved:
needs: [coverage, test-python, test-os, test-debug, lint, bench]
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
Expand Down
3 changes: 1 addition & 2 deletions src/validators/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use crate::build_tools::{is_strict, py_schema_error_type};
use crate::errors::{ErrorType, ErrorTypeDefaults, ValError, ValResult};
use crate::input::{EitherDate, Input};

use crate::tools::SchemaDict;
use crate::validators::datetime::{NowConstraint, NowOp};

use super::Exactness;
Expand Down Expand Up @@ -177,7 +176,7 @@ impl DateConstraints {
}

fn convert_pydate(schema: &Bound<'_, PyDict>, key: &Bound<'_, PyString>) -> PyResult<Option<Date>> {
match schema.get_as::<Bound<'_, PyAny>>(key)? {
match schema.get_item(key)? {
Some(value) => match value.validate_date(false) {
Ok(v) => Ok(Some(v.into_inner().as_raw()?)),
Err(_) => Err(PyValueError::new_err(format!(
Expand Down
2 changes: 1 addition & 1 deletion src/validators/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ impl DateTimeConstraints {
}

fn py_datetime_as_datetime(schema: &Bound<'_, PyDict>, key: &Bound<'_, PyString>) -> PyResult<Option<DateTime>> {
match schema.get_as::<Bound<'_, PyAny>>(key)? {
match schema.get_item(key)? {
Some(value) => match value.validate_datetime(false, MicrosecondsPrecisionOverflowBehavior::Truncate) {
Ok(v) => Ok(Some(v.into_inner().as_raw()?)),
Err(_) => Err(PyValueError::new_err(format!(
Expand Down
18 changes: 11 additions & 7 deletions src/validators/decimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,12 @@ pub fn get_decimal_type(py: Python) -> &Bound<'_, PyType> {
.bind(py)
}

fn validate_as_decimal(py: Python, schema: &Bound<'_, PyDict>, key: &str) -> PyResult<Option<Py<PyAny>>> {
match schema.get_as::<Bound<'_, PyAny>>(&PyString::new(py, key))? {
fn validate_as_decimal(
py: Python,
schema: &Bound<'_, PyDict>,
key: &Bound<'_, PyString>,
) -> PyResult<Option<Py<PyAny>>> {
match schema.get_item(key)? {
Some(value) => match value.validate_decimal(false, py) {
Ok(v) => Ok(Some(v.into_inner().unbind())),
Err(_) => Err(PyValueError::new_err(format!(
Expand Down Expand Up @@ -77,11 +81,11 @@ impl BuildValidator for DecimalValidator {
allow_inf_nan,
check_digits: decimal_places.is_some() || max_digits.is_some(),
decimal_places,
multiple_of: validate_as_decimal(py, schema, "multiple_of")?,
le: validate_as_decimal(py, schema, "le")?,
lt: validate_as_decimal(py, schema, "lt")?,
ge: validate_as_decimal(py, schema, "ge")?,
gt: validate_as_decimal(py, schema, "gt")?,
multiple_of: validate_as_decimal(py, schema, intern!(py, "multiple_of"))?,
le: validate_as_decimal(py, schema, intern!(py, "le"))?,
lt: validate_as_decimal(py, schema, intern!(py, "lt"))?,
ge: validate_as_decimal(py, schema, intern!(py, "ge"))?,
gt: validate_as_decimal(py, schema, intern!(py, "gt"))?,
max_digits,
}
.into())
Expand Down
15 changes: 7 additions & 8 deletions src/validators/int.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ use pyo3::IntoPyObjectExt;
use crate::build_tools::is_strict;
use crate::errors::{ErrorType, ValError, ValResult};
use crate::input::{Input, Int};
use crate::tools::SchemaDict;

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

fn validate_as_int(py: Python, schema: &Bound<'_, PyDict>, key: &str) -> PyResult<Option<Int>> {
match schema.get_as::<Bound<'_, PyAny>>(&PyString::new(py, key))? {
fn validate_as_int(schema: &Bound<'_, PyDict>, key: &Bound<'_, PyString>) -> PyResult<Option<Int>> {
match schema.get_item(key)? {
Some(value) => match value.validate_int(false) {
Ok(v) => match v.into_inner().as_int() {
Ok(v) => Ok(Some(v)),
Expand Down Expand Up @@ -93,11 +92,11 @@ impl ConstrainedIntValidator {
let py = schema.py();
Ok(Self {
strict: is_strict(schema, config)?,
multiple_of: validate_as_int(py, schema, "multiple_of")?,
le: validate_as_int(py, schema, "le")?,
lt: validate_as_int(py, schema, "lt")?,
ge: validate_as_int(py, schema, "ge")?,
gt: validate_as_int(py, schema, "gt")?,
multiple_of: validate_as_int(schema, intern!(py, "multiple_of"))?,
le: validate_as_int(schema, intern!(py, "le"))?,
lt: validate_as_int(schema, intern!(py, "lt"))?,
ge: validate_as_int(schema, intern!(py, "ge"))?,
gt: validate_as_int(schema, intern!(py, "gt"))?,
}
.into())
}
Expand Down
29 changes: 17 additions & 12 deletions src/validators/timedelta.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use pyo3::exceptions::PyValueError;
use pyo3::intern;
use pyo3::prelude::*;
use pyo3::types::{PyDelta, PyDeltaAccess, PyDict};
use speedate::Duration;
use pyo3::types::{PyDelta, PyDeltaAccess, PyDict, PyString};
use speedate::{Duration, MicrosecondsPrecisionOverflowBehavior};

use crate::build_tools::is_strict;
use crate::errors::{ErrorType, ValError, ValResult};
use crate::input::{duration_as_pytimedelta, EitherTimedelta, Input};
use crate::input::{duration_as_pytimedelta, Input};

use super::datetime::extract_microseconds_precision;
use super::{BuildValidator, CombinedValidator, DefinitionsBuilder, ValidationState, Validator};
Expand All @@ -24,12 +26,14 @@ struct TimedeltaConstraints {
gt: Option<Duration>,
}

fn get_constraint(schema: &Bound<'_, PyDict>, key: &str) -> PyResult<Option<Duration>> {
fn get_constraint(schema: &Bound<'_, PyDict>, key: &Bound<'_, PyString>) -> PyResult<Option<Duration>> {
match schema.get_item(key)? {
Some(value) => {
let either_timedelta = EitherTimedelta::try_from(&value)?;
Ok(Some(either_timedelta.to_duration()?))
}
Some(value) => match value.validate_timedelta(false, MicrosecondsPrecisionOverflowBehavior::default()) {
Ok(v) => Ok(Some(v.into_inner().to_duration()?)),
Err(_) => Err(PyValueError::new_err(format!(
"'{key}' must be coercible to a timedelta instance"
))),
},
None => Ok(None),
}
}
Expand All @@ -42,11 +46,12 @@ impl BuildValidator for TimeDeltaValidator {
config: Option<&Bound<'_, PyDict>>,
_definitions: &mut DefinitionsBuilder<CombinedValidator>,
) -> PyResult<CombinedValidator> {
let py = schema.py();
let constraints = TimedeltaConstraints {
le: get_constraint(schema, "le")?,
lt: get_constraint(schema, "lt")?,
ge: get_constraint(schema, "ge")?,
gt: get_constraint(schema, "gt")?,
le: get_constraint(schema, intern!(py, "le"))?,
lt: get_constraint(schema, intern!(py, "lt"))?,
ge: get_constraint(schema, intern!(py, "ge"))?,
gt: get_constraint(schema, intern!(py, "gt"))?,
};

Ok(Self {
Expand Down
8 changes: 7 additions & 1 deletion tests/validators/test_date.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,17 @@
'constraint',
['le', 'lt', 'ge', 'gt'],
)
def test_constraints_schema_validation(constraint: str) -> None:
def test_constraints_schema_validation_error(constraint: str) -> None:
with pytest.raises(SchemaError, match=f"'{constraint}' must be coercible to a date instance"):
SchemaValidator(cs.date_schema(**{constraint: 'bad_value'}))


def test_constraints_schema_validation() -> None:
val = SchemaValidator(cs.date_schema(gt='2020-01-01'))
with pytest.raises(ValidationError):
val.validate_python('2019-01-01')


@pytest.mark.parametrize(
'input_value,expected',
[
Expand Down
8 changes: 7 additions & 1 deletion tests/validators/test_datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,17 @@
'constraint',
['le', 'lt', 'ge', 'gt'],
)
def test_constraints_schema_validation(constraint: str) -> None:
def test_constraints_schema_validation_error(constraint: str) -> None:
with pytest.raises(SchemaError, match=f"'{constraint}' must be coercible to a datetime instance"):
SchemaValidator(cs.datetime_schema(**{constraint: 'bad_value'}))


def test_constraints_schema_validation() -> None:
val = SchemaValidator(cs.datetime_schema(gt='2020-01-01T00:00:00'))
with pytest.raises(ValidationError):
val.validate_python('2019-01-01T00:00:00')


@pytest.mark.parametrize(
'input_value,expected',
[
Expand Down
8 changes: 7 additions & 1 deletion tests/validators/test_decimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,17 @@ class DecimalSubclass(Decimal):
'constraint',
['multiple_of', 'le', 'lt', 'ge', 'gt'],
)
def test_constraints_schema_validation(constraint: str) -> None:
def test_constraints_schema_validation_error(constraint: str) -> None:
with pytest.raises(SchemaError, match=f"'{constraint}' must be coercible to a Decimal instance"):
SchemaValidator(cs.decimal_schema(**{constraint: 'bad_value'}))


def test_constraints_schema_validation() -> None:
val = SchemaValidator(cs.decimal_schema(gt='1'))
with pytest.raises(ValidationError):
val.validate_python('0')


@pytest.mark.parametrize(
'input_value,expected',
[
Expand Down
8 changes: 7 additions & 1 deletion tests/validators/test_int.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,17 @@
'constraint',
['multiple_of', 'le', 'lt', 'ge', 'gt'],
)
def test_constraints_schema_validation(constraint: str) -> None:
def test_constraints_schema_validation_error(constraint: str) -> None:
with pytest.raises(SchemaError, match=f"'{constraint}' must be coercible to an integer"):
SchemaValidator(cs.int_schema(**{constraint: 'bad_value'}))


def test_constraints_schema_validation() -> None:
val = SchemaValidator(cs.int_schema(gt='1'))
with pytest.raises(ValidationError):
val.validate_python('0')


@pytest.mark.parametrize(
'input_value,expected',
[
Expand Down
15 changes: 15 additions & 0 deletions tests/validators/test_timedelta.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,21 @@
pandas = None


@pytest.mark.parametrize(
'constraint',
['le', 'lt', 'ge', 'gt'],
)
def test_constraints_schema_validation_error(constraint: str) -> None:
with pytest.raises(SchemaError, match=f"'{constraint}' must be coercible to a timedelta instance"):
SchemaValidator(core_schema.timedelta_schema(**{constraint: 'bad_value'}))


def test_constraints_schema_validation() -> None:
val = SchemaValidator(core_schema.timedelta_schema(gt=3))
with pytest.raises(ValidationError):
val.validate_python(1)


@pytest.mark.parametrize(
'input_value,expected',
[
Expand Down
Loading