Skip to content

Commit f7fb50b

Browse files
mikeedjonesJONEMI21
andauthored
Raise validation error when unhashable items added to a set (#1619)
Co-authored-by: JONEMI21 <michael.jones2@kingfisher.com>
1 parent 0862ea5 commit f7fb50b

File tree

5 files changed

+41
-3
lines changed

5 files changed

+41
-3
lines changed

python/pydantic_core/core_schema.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4037,6 +4037,7 @@ def definition_reference_schema(
40374037
'list_type',
40384038
'tuple_type',
40394039
'set_type',
4040+
'set_item_not_hashable',
40404041
'bool_type',
40414042
'bool_parsing',
40424043
'int_type',

src/errors/types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ error_types! {
268268
// ---------------------
269269
// set errors
270270
SetType {},
271+
SetItemNotHashable {},
271272
// ---------------------
272273
// bool errors
273274
BoolType {},
@@ -513,6 +514,7 @@ impl ErrorType {
513514
Self::ListType {..} => "Input should be a valid list",
514515
Self::TupleType {..} => "Input should be a valid tuple",
515516
Self::SetType {..} => "Input should be a valid set",
517+
Self::SetItemNotHashable {..} => "Set items should be hashable",
516518
Self::BoolType {..} => "Input should be a valid boolean",
517519
Self::BoolParsing {..} => "Input should be a valid boolean, unable to interpret input",
518520
Self::IntType {..} => "Input should be a valid integer",

src/input/return_enums.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,25 @@ impl BuildSet for Bound<'_, PyFrozenSet> {
194194
}
195195
}
196196

197+
fn validate_add<'py>(
198+
py: Python<'py>,
199+
set: &impl BuildSet,
200+
item: impl BorrowInput<'py>,
201+
state: &mut ValidationState<'_, 'py>,
202+
validator: &CombinedValidator,
203+
) -> ValResult<()> {
204+
let validated_item = validator.validate(py, item.borrow_input(), state)?;
205+
match set.build_add(validated_item) {
206+
Ok(()) => Ok(()),
207+
Err(err) => {
208+
if err.matches(py, py.get_type::<PyTypeError>())? {
209+
return Err(ValError::new(ErrorTypeDefaults::SetItemNotHashable, item));
210+
}
211+
Err(err)?
212+
}
213+
}
214+
}
215+
197216
#[allow(clippy::too_many_arguments)]
198217
pub(crate) fn validate_iter_to_set<'py>(
199218
py: Python<'py>,
@@ -216,9 +235,8 @@ pub(crate) fn validate_iter_to_set<'py>(
216235
false => PartialMode::Off,
217236
};
218237
let item = item_result.map_err(|e| any_next_error!(py, e, input, index))?;
219-
match validator.validate(py, item.borrow_input(), state) {
220-
Ok(item) => {
221-
set.build_add(item)?;
238+
match validate_add(py, set, item, state, validator) {
239+
Ok(()) => {
222240
if let Some(max_length) = max_length {
223241
if set.build_len() > max_length {
224242
return Err(ValError::new(

tests/test_errors.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ def f(input_value, info):
306306
('iteration_error', 'Error iterating over object, error: foobar', {'error': 'foobar'}),
307307
('list_type', 'Input should be a valid list', None),
308308
('tuple_type', 'Input should be a valid tuple', None),
309+
('set_item_not_hashable', 'Set items should be hashable', None),
309310
('set_type', 'Input should be a valid set', None),
310311
('bool_type', 'Input should be a valid boolean', None),
311312
('bool_parsing', 'Input should be a valid boolean, unable to interpret input', None),

tests/validators/test_set.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,22 @@ def test_set_multiple_errors():
104104
]
105105

106106

107+
def test_list_with_unhashable_items():
108+
v = SchemaValidator({'type': 'set'})
109+
110+
class Unhashable:
111+
__hash__ = None
112+
113+
unhashable = Unhashable()
114+
115+
with pytest.raises(ValidationError) as exc_info:
116+
v.validate_python([{'a': 'b'}, unhashable])
117+
assert exc_info.value.errors(include_url=False) == [
118+
{'type': 'set_item_not_hashable', 'loc': (0,), 'msg': 'Set items should be hashable', 'input': {'a': 'b'}},
119+
{'type': 'set_item_not_hashable', 'loc': (1,), 'msg': 'Set items should be hashable', 'input': unhashable},
120+
]
121+
122+
107123
def generate_repeats():
108124
for i in 1, 2, 3:
109125
yield i

0 commit comments

Comments
 (0)