Skip to content
Open
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
29 changes: 29 additions & 0 deletions tests/test_traitlets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1961,6 +1961,35 @@ class TestInstanceFullyValidatedDict(TraitTestBase):
_bad_values = [{"foo": 0, "bar": 1}, {"foo": "0", "bar": "1"}, {"foo": 0, 0: "1"}]


def test_dict_per_key_traits_error_names_key():
"""A `per_key_traits` failure should say which key was rejected."""

class Foo(HasTraits):
bar = Dict(per_key_traits={"this": Unicode(), "that": Int()})

with pytest.raises(TraitError) as excinfo:
Foo().bar = {"this": "ok", "that": "not an int"}

message = str(excinfo.value)
assert "at key 'that'" in message
assert "'bar' trait" in message
assert "an int" in message


def test_dict_value_trait_error_does_not_name_a_key():
"""A uniform `value_trait` failure still reports against the trait as a whole."""

class Foo(HasTraits):
bar = Dict(value_trait=Int())

with pytest.raises(TraitError) as excinfo:
Foo().bar = {"this": "not an int"}

message = str(excinfo.value)
assert message.startswith("Values of the 'bar' trait")
assert "at key" not in message


def test_dict_default_value():
"""Check that the `{}` default value of the Dict traitlet constructor is
actually copied."""
Expand Down
12 changes: 12 additions & 0 deletions traitlets/traitlets.py
Original file line number Diff line number Diff line change
Expand Up @@ -3995,6 +3995,16 @@ def element_error(
)
raise TraitError(e)

def per_key_element_error(
self, obj: t.Any, key: t.Any, element: t.Any, validator: t.Any
) -> None:
"""Raise a TraitError naming the key whose `per_key_traits` entry rejected a value."""
e = (
f"Value at key {key!r} of the '{self.name}' trait of {class_of(obj)} instance"
f" must be {validator.info()}, but a value of {repr_type(element)} was specified."
)
raise TraitError(e)

def validate(self, obj: t.Any, value: t.Any) -> dict[K, V] | None:
value = super().validate(obj, value)
if value is None:
Expand All @@ -4020,6 +4030,8 @@ def validate_elements(self, obj: t.Any, value: dict[t.Any, t.Any]) -> dict[K, V]
try:
v = active_value_trait._validate(obj, v)
except TraitError:
if key in per_key_override:
self.per_key_element_error(obj, key, v, active_value_trait)
self.element_error(obj, v, active_value_trait, "Values")
validated[key] = v

Expand Down
Loading