Skip to content

Commit 8a03493

Browse files
committed
Add input type validation to deep_mapping validator to prevent crashes on non-mapping inputs
1 parent 3823062 commit 8a03493

2 files changed

Lines changed: 24 additions & 0 deletions

File tree

src/attr/validators.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from contextlib import contextmanager
1111
from re import Pattern
1212

13+
from ._compat import Mapping
1314
from ._config import get_run_validators, set_run_validators
1415
from ._make import _AndValidator, and_, attrib, attrs
1516
from .converters import default_if_none
@@ -397,6 +398,15 @@ def __call__(self, inst, attr, value):
397398
if self.mapping_validator is not None:
398399
self.mapping_validator(inst, attr, value)
399400

401+
if not isinstance(value, Mapping):
402+
msg = f"'{attr.name}' must be a mapping (got {value!r} that is a {value.__class__!r})."
403+
raise TypeError(
404+
msg,
405+
attr,
406+
Mapping,
407+
value,
408+
)
409+
400410
for key in value:
401411
if self.key_validator is not None:
402412
self.key_validator(inst, attr, key)

tests/test_validators.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,20 @@ def test_validators_iterables(self, conv):
807807
assert and_(*value_validator) == v.value_validator
808808
assert and_(*mapping_validator) == v.mapping_validator
809809

810+
def test_fail_non_mapping(self):
811+
"""
812+
Raise TypeError if value is not a mapping.
813+
"""
814+
key_validator = instance_of(str)
815+
value_validator = instance_of(int)
816+
v = deep_mapping(key_validator, value_validator)
817+
a = simple_attr("test")
818+
with pytest.raises(TypeError) as e:
819+
v(None, a, [1, 2, 3])
820+
821+
msg = f"'{a.name}' must be a mapping (got [1, 2, 3] that is a {list!r})."
822+
assert msg in str(e.value)
823+
810824

811825
class TestIsCallable:
812826
"""

0 commit comments

Comments
 (0)