Skip to content

Commit

Permalink
Add tests to cover fail-fast feature
Browse files Browse the repository at this point in the history
  • Loading branch information
uriyyo committed Jun 15, 2024
1 parent 0c85ada commit 27db075
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 2 deletions.
12 changes: 12 additions & 0 deletions python/pydantic_core/core_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -1551,6 +1551,7 @@ class TupleSchema(TypedDict, total=False):
variadic_item_index: int
min_length: int
max_length: int
fail_fast: bool
strict: bool
ref: str
metadata: Any
Expand All @@ -1563,6 +1564,7 @@ def tuple_schema(
variadic_item_index: int | None = None,
min_length: int | None = None,
max_length: int | None = None,
fail_fast: bool | None = None,
strict: bool | None = None,
ref: str | None = None,
metadata: Any = None,
Expand All @@ -1587,6 +1589,7 @@ def tuple_schema(
variadic_item_index: The index of the schema in `items_schema` to be treated as variadic (following PEP 646)
min_length: The value must be a tuple with at least this many items
max_length: The value must be a tuple with at most this many items
fail_fast: Stop validation on the first error
strict: The value must be a tuple with exactly this many items
ref: Optional unique identifier of the schema, used to reference the schema in other places
metadata: Any other information you want to include with the schema, not used by pydantic-core
Expand All @@ -1598,6 +1601,7 @@ def tuple_schema(
variadic_item_index=variadic_item_index,
min_length=min_length,
max_length=max_length,
fail_fast=fail_fast,
strict=strict,
ref=ref,
metadata=metadata,
Expand All @@ -1610,6 +1614,7 @@ class SetSchema(TypedDict, total=False):
items_schema: CoreSchema
min_length: int
max_length: int
fail_fast: bool
strict: bool
ref: str
metadata: Any
Expand All @@ -1621,6 +1626,7 @@ def set_schema(
*,
min_length: int | None = None,
max_length: int | None = None,
fail_fast: bool | None = None,
strict: bool | None = None,
ref: str | None = None,
metadata: Any = None,
Expand All @@ -1643,6 +1649,7 @@ def set_schema(
items_schema: The value must be a set with items that match this schema
min_length: The value must be a set with at least this many items
max_length: The value must be a set with at most this many items
fail_fast: Stop validation on the first error
strict: The value must be a set with exactly this many items
ref: optional unique identifier of the schema, used to reference the schema in other places
metadata: Any other information you want to include with the schema, not used by pydantic-core
Expand All @@ -1653,6 +1660,7 @@ def set_schema(
items_schema=items_schema,
min_length=min_length,
max_length=max_length,
fail_fast=fail_fast,
strict=strict,
ref=ref,
metadata=metadata,
Expand All @@ -1665,6 +1673,7 @@ class FrozenSetSchema(TypedDict, total=False):
items_schema: CoreSchema
min_length: int
max_length: int
fail_fast: bool
strict: bool
ref: str
metadata: Any
Expand All @@ -1676,6 +1685,7 @@ def frozenset_schema(
*,
min_length: int | None = None,
max_length: int | None = None,
fail_fast: bool | None = None,
strict: bool | None = None,
ref: str | None = None,
metadata: Any = None,
Expand All @@ -1698,6 +1708,7 @@ def frozenset_schema(
items_schema: The value must be a frozenset with items that match this schema
min_length: The value must be a frozenset with at least this many items
max_length: The value must be a frozenset with at most this many items
fail_fast: Stop validation on the first error
strict: The value must be a frozenset with exactly this many items
ref: optional unique identifier of the schema, used to reference the schema in other places
metadata: Any other information you want to include with the schema, not used by pydantic-core
Expand All @@ -1708,6 +1719,7 @@ def frozenset_schema(
items_schema=items_schema,
min_length=min_length,
max_length=max_length,
fail_fast=fail_fast,
strict=strict,
ref=ref,
metadata=metadata,
Expand Down
4 changes: 4 additions & 0 deletions src/validators/tuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@ impl TupleValidator {
self.fail_fast,
)?;

if self.fail_fast && !errors.is_empty() {
return Ok(output);
}

// Generate an error if there are any extra items:
if collection_iter.next().is_some() {
return Err(ValError::new(
Expand Down
44 changes: 44 additions & 0 deletions tests/validators/test_frozenset.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,3 +297,47 @@ def test_frozenset_from_dict_items(input_value, items_schema, expected):
output = v.validate_python(input_value)
assert isinstance(output, frozenset)
assert output == expected


@pytest.mark.parametrize(
'fail_fast,expected',
[
pytest.param(
True,
[
{
'type': 'int_parsing',
'loc': (1,),
'msg': 'Input should be a valid integer, unable to parse string as an integer',
'input': 'not-num',
},
],
id='fail_fast',
),
pytest.param(
False,
[
{
'type': 'int_parsing',
'loc': (1,),
'msg': 'Input should be a valid integer, unable to parse string as an integer',
'input': 'not-num',
},
{
'type': 'int_parsing',
'loc': (2,),
'msg': 'Input should be a valid integer, unable to parse string as an integer',
'input': 'again',
},
],
id='not_fail_fast',
),
],
)
def test_frozenset_fail_fast(fail_fast, expected):
v = SchemaValidator({'type': 'frozenset', 'items_schema': {'type': 'int'}, 'fail_fast': fail_fast})

with pytest.raises(ValidationError) as exc_info:
v.validate_python([1, 'not-num', 'again'])

assert exc_info.value.errors(include_url=False) == expected
44 changes: 44 additions & 0 deletions tests/validators/test_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,3 +277,47 @@ def test_set_any(input_value, expected):
output = v.validate_python(input_value)
assert output == expected
assert isinstance(output, set)


@pytest.mark.parametrize(
'fail_fast,expected',
[
pytest.param(
True,
[
{
'type': 'int_parsing',
'loc': (1,),
'msg': 'Input should be a valid integer, unable to parse string as an integer',
'input': 'not-num',
},
],
id='fail_fast',
),
pytest.param(
False,
[
{
'type': 'int_parsing',
'loc': (1,),
'msg': 'Input should be a valid integer, unable to parse string as an integer',
'input': 'not-num',
},
{
'type': 'int_parsing',
'loc': (2,),
'msg': 'Input should be a valid integer, unable to parse string as an integer',
'input': 'again',
},
],
id='not_fail_fast',
),
],
)
def test_set_fail_fast(fail_fast, expected):
v = SchemaValidator({'type': 'set', 'items_schema': {'type': 'int'}, 'fail_fast': fail_fast})

with pytest.raises(ValidationError) as exc_info:
v.validate_python([1, 'not-num', 'again'])

assert exc_info.value.errors(include_url=False) == expected
58 changes: 56 additions & 2 deletions tests/validators/test_tuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ def test_tuple_strict_passes_with_tuple(variadic_item_index, items, input_value,
assert v.validate_python(input_value) == expected


def test_empty_positional_tuple():
v = SchemaValidator({'type': 'tuple', 'items_schema': []})
@pytest.mark.parametrize('fail_fast', [True, False])
def test_empty_positional_tuple(fail_fast):
v = SchemaValidator({'type': 'tuple', 'items_schema': [], 'fail_fast': fail_fast})
assert v.validate_python(()) == ()
assert v.validate_python([]) == ()
with pytest.raises(ValidationError) as exc_info:
Expand Down Expand Up @@ -493,3 +494,56 @@ def test_length_constraints_omit(input_value, expected):
v.validate_python(input_value)
else:
assert v.validate_python(input_value) == expected


@pytest.mark.parametrize(
'fail_fast,expected',
[
pytest.param(
True,
[
{
'type': 'int_parsing',
'loc': (1,),
'msg': 'Input should be a valid integer, unable to parse string as an integer',
'input': 'not-num',
}
],
id='fail_fast',
),
pytest.param(
False,
[
{
'type': 'int_parsing',
'loc': (1,),
'msg': 'Input should be a valid integer, unable to parse string as an integer',
'input': 'not-num',
},
{
'type': 'float_parsing',
'loc': (2,),
'msg': 'Input should be a valid number, unable to parse string as a number',
'input': 'again',
},
],
id='not_fail_fast',
),
],
)
def test_tuple_fail_fast(fail_fast, expected):
s = core_schema.tuple_schema(
[
core_schema.str_schema(),
core_schema.int_schema(),
core_schema.float_schema(),
],
variadic_item_index=None,
fail_fast=fail_fast,
)
v = SchemaValidator(s)

with pytest.raises(ValidationError) as exc_info:
v.validate_python(['str', 'not-num', 'again'])

assert exc_info.value.errors(include_url=False) == expected

0 comments on commit 27db075

Please sign in to comment.