Skip to content

Commit bd127eb

Browse files
committed
require validators to support partial
1 parent 470754d commit bd127eb

File tree

9 files changed

+49
-58
lines changed

9 files changed

+49
-58
lines changed

src/input/return_enums.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ pub(crate) fn validate_iter_to_vec<'py>(
130130
let mut errors: Vec<ValLineError> = Vec::new();
131131

132132
for (index, is_last_partial, item_result) in state.enumerate_last_partial(iter) {
133-
state.allow_partial = is_last_partial;
133+
state.allow_partial = is_last_partial && validator.supports_partial();
134134
let item = item_result.map_err(|e| any_next_error!(py, e, max_length_check.input, index))?;
135135
match validator.validate(py, item.borrow_input(), state) {
136136
Ok(item) => {
@@ -203,7 +203,7 @@ pub(crate) fn validate_iter_to_set<'py>(
203203
let mut errors: Vec<ValLineError> = Vec::new();
204204

205205
for (index, is_last_partial, item_result) in state.enumerate_last_partial(iter) {
206-
state.allow_partial = is_last_partial;
206+
state.allow_partial = is_last_partial && validator.supports_partial();
207207
let item = item_result.map_err(|e| any_next_error!(py, e, input, index))?;
208208
match validator.validate(py, item.borrow_input(), state) {
209209
Ok(item) => {

src/validators/dict.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ impl Validator for DictValidator {
8787
fn get_name(&self) -> &str {
8888
&self.name
8989
}
90+
91+
fn supports_partial(&self) -> bool {
92+
true
93+
}
9094
}
9195

9296
struct ValidateToDict<'a, 's, 'py, I: Input<'py> + ?Sized> {
@@ -125,7 +129,7 @@ where
125129
Err(ValError::Omit) => continue,
126130
Err(err) => return Err(err),
127131
};
128-
self.state.allow_partial = is_last_partial;
132+
self.state.allow_partial = is_last_partial && self.value_validator.supports_partial();
129133
let output_value = match self.value_validator.validate(self.py, value.borrow_input(), self.state) {
130134
Ok(value) => value,
131135
Err(ValError::LineErrors(line_errors)) => {

src/validators/frozenset.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ impl Validator for FrozenSetValidator {
5252
fn get_name(&self) -> &str {
5353
&self.name
5454
}
55+
56+
fn supports_partial(&self) -> bool {
57+
true
58+
}
5559
}
5660

5761
struct ValidateToFrozenSet<'a, 's, 'py, I: Input<'py> + ?Sized> {

src/validators/generator.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,6 @@ pub struct InternalValidator {
227227
hide_input_in_errors: bool,
228228
validation_error_cause: bool,
229229
cache_str: jiter::StringCacheMode,
230-
allow_partial: bool,
231230
}
232231

233232
impl fmt::Debug for InternalValidator {
@@ -260,7 +259,6 @@ impl InternalValidator {
260259
hide_input_in_errors,
261260
validation_error_cause,
262261
cache_str: extra.cache_str,
263-
allow_partial: state.allow_partial,
264262
}
265263
}
266264

@@ -281,7 +279,7 @@ impl InternalValidator {
281279
self_instance: self.self_instance.as_ref().map(|data| data.bind(py)),
282280
cache_str: self.cache_str,
283281
};
284-
let mut state = ValidationState::new(extra, &mut self.recursion_guard, self.allow_partial);
282+
let mut state = ValidationState::new(extra, &mut self.recursion_guard, false);
285283
state.exactness = self.exactness;
286284
let result = self
287285
.validator
@@ -316,7 +314,7 @@ impl InternalValidator {
316314
self_instance: self.self_instance.as_ref().map(|data| data.bind(py)),
317315
cache_str: self.cache_str,
318316
};
319-
let mut state = ValidationState::new(extra, &mut self.recursion_guard, self.allow_partial);
317+
let mut state = ValidationState::new(extra, &mut self.recursion_guard, false);
320318
state.exactness = self.exactness;
321319
let result = self.validator.validate(py, input, &mut state).map_err(|e| {
322320
ValidationError::from_val_error(

src/validators/list.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ impl Validator for ListValidator {
177177
}
178178
}
179179
}
180+
181+
fn supports_partial(&self) -> bool {
182+
true
183+
}
180184
}
181185

182186
struct ValidateToVec<'a, 's, 'py, I: Input<'py> + ?Sized> {

src/validators/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ impl SchemaValidator {
365365
self.cache_str,
366366
),
367367
&mut recursion_guard,
368-
allow_partial,
368+
allow_partial && self.validator.supports_partial(),
369369
);
370370
self.validator.validate(py, input, &mut state)
371371
}
@@ -801,4 +801,10 @@ pub trait Validator: Send + Sync + Debug {
801801
/// `get_name` generally returns `Self::EXPECTED_TYPE` or some other clear identifier of the validator
802802
/// this is used in the error location in unions, and in the top level message in `ValidationError`
803803
fn get_name(&self) -> &str;
804+
805+
/// Whether this validator supports partial validation, `state.allow_partial` should only be set to true
806+
/// if this is the case.
807+
fn supports_partial(&self) -> bool {
808+
false
809+
}
804810
}

src/validators/set.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ impl Validator for SetValidator {
8383
fn get_name(&self) -> &str {
8484
&self.name
8585
}
86+
87+
fn supports_partial(&self) -> bool {
88+
true
89+
}
8690
}
8791

8892
struct ValidateToSet<'a, 's, 'py, I: Input<'py> + ?Sized> {

src/validators/typed_dict.rs

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ impl Validator for TypedDictValidator {
156156
let output_dict = PyDict::new_bound(py);
157157
let mut errors: Vec<ValLineError> = Vec::with_capacity(self.fields.len());
158158

159-
let partial_last_key = if self.allow_partial && state.allow_partial {
159+
let partial_last_key = if state.allow_partial {
160160
dict.last_key().map(Into::into)
161161
} else {
162162
None
@@ -201,7 +201,7 @@ impl Validator for TypedDictValidator {
201201
} else {
202202
false
203203
};
204-
state.allow_partial = is_last_partial;
204+
state.allow_partial = is_last_partial && field.validator.supports_partial();
205205
match field.validator.validate(py, value.borrow_input(), state) {
206206
Ok(value) => {
207207
output_dict.set_item(&field.name_py, value)?;
@@ -316,7 +316,7 @@ impl Validator for TypedDictValidator {
316316
} else {
317317
false
318318
};
319-
self.state.allow_partial = last_partial;
319+
self.state.allow_partial = last_partial && validator.supports_partial();
320320
match validator.validate(self.py, value, self.state) {
321321
Ok(value) => {
322322
self.output_dict.set_item(py_key, value)?;
@@ -363,28 +363,8 @@ impl Validator for TypedDictValidator {
363363
fn get_name(&self) -> &str {
364364
Self::EXPECTED_TYPE
365365
}
366-
}
367366

368-
// impl TypedDictValidator {
369-
// /// If we're in `allow_partial` mode, whether all errors occurred in the last value of the dict.
370-
// fn valid_as_partial(
371-
// &self,
372-
// state: &ValidationState,
373-
// get_opt_last_key: impl FnOnce() -> Option<LocItem>,
374-
// errors: &[ValLineError],
375-
// ) -> bool {
376-
// if !state.allow_partial || !self.allow_partial {
377-
// false
378-
// } else if let Some(last_key) = get_opt_last_key() {
379-
// errors.iter().all(|error| {
380-
// if let Some(loc_item) = error.first_loc_item() {
381-
// loc_item == &last_key
382-
// } else {
383-
// false
384-
// }
385-
// })
386-
// } else {
387-
// false
388-
// }
389-
// }
390-
// }
367+
fn supports_partial(&self) -> bool {
368+
self.allow_partial
369+
}
370+
}

tests/validators/test_allow_partial.py

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -38,29 +38,6 @@ def test_list():
3838
assert v.validate_json(b'[[1, 2], [3,', allow_partial=True) == [(1, 2)]
3939

4040

41-
def test_list_partial_nested():
42-
v = SchemaValidator(
43-
core_schema.tuple_positional_schema(
44-
[core_schema.int_schema(), core_schema.list_schema(core_schema.int_schema())]
45-
),
46-
)
47-
assert v.validate_python([1, [2, 3]]) == (1, [2, 3])
48-
assert v.validate_python([1, [2, 3]], allow_partial=True) == (1, [2, 3])
49-
with pytest.raises(ValidationError) as exc_info:
50-
v.validate_python((1, [2, 3, 'x']))
51-
assert exc_info.value.errors(include_url=False) == snapshot(
52-
[
53-
{
54-
'type': 'int_parsing',
55-
'loc': (1, 2),
56-
'msg': 'Input should be a valid integer, unable to parse string as an integer',
57-
'input': 'x',
58-
}
59-
]
60-
)
61-
assert v.validate_python((1, [2, 3, 'x']), allow_partial=True) == (1, [2, 3])
62-
63-
6441
@pytest.mark.parametrize('collection_type', [core_schema.set_schema, core_schema.frozenset_schema])
6542
def test_set_frozenset(collection_type):
6643
v = SchemaValidator(
@@ -253,3 +230,17 @@ def test_double_nested():
253230
for i in range(1, len(json)):
254231
value = v.validate_json(json[:i], allow_partial=True)
255232
assert isinstance(value, dict)
233+
234+
235+
def test_tuple_list():
236+
"""Tuples don't support partial, so behaviour should be disabled."""
237+
v = SchemaValidator(
238+
core_schema.tuple_positional_schema(
239+
[core_schema.list_schema(core_schema.int_schema()), core_schema.int_schema()]
240+
),
241+
)
242+
assert v.validate_python([['1', '2'], '3'], allow_partial=True) == snapshot(([1, 2], 3))
243+
with pytest.raises(ValidationError, match=r'1\s+Input should be a valid integer'):
244+
v.validate_python([['1', '2'], 'x'], allow_partial=True)
245+
with pytest.raises(ValidationError, match=r'0\.1\s+Input should be a valid integer'):
246+
v.validate_python([['1', 'x'], '2'], allow_partial=True)

0 commit comments

Comments
 (0)