Skip to content

Commit 59e088a

Browse files
committed
typeddicts: improve error message on invalid input
1 parent 5c5876e commit 59e088a

File tree

4 files changed

+23
-15
lines changed

4 files changed

+23
-15
lines changed

src/cattrs/gen/typeddicts.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import re
44
import sys
5+
from collections.abc import Mapping
56
from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar
67

78
from attrs import NOTHING, Attribute
@@ -308,15 +309,18 @@ def make_dict_structure_fn(
308309
globs["__c_feke"] = ForbiddenExtraKeysError
309310

310311
if _cattrs_detailed_validation:
311-
# When running under detailed validation, be extra careful about copying
312-
# so that the correct error is raised if the input isn't a dict.
313-
lines.append(" try:")
314-
lines.append(" res = o.copy()")
315-
lines.append(" except Exception as exc:")
312+
# When running under detailed validation, be extra careful about the
313+
# input type so that the correct error is raised if the input isn't a dict.
314+
internal_arg_parts["__c_mapping"] = Mapping
315+
lines.append(" if not isinstance(o, __c_mapping):")
316+
te = "TypeError(f'expected a mapping, not {o.__class__.__name__}')"
316317
lines.append(
317-
f" raise __c_cve('While structuring ' + {cl.__name__!r}, [exc], __cl)"
318+
f" raise __c_cve('While structuring ' + {cl.__name__!r}, [{te}], __cl)"
318319
)
319320

321+
lines.append(" res = o.copy()")
322+
323+
if _cattrs_detailed_validation:
320324
lines.append(" errors = []")
321325
internal_arg_parts["__c_cve"] = ClassValidationError
322326
internal_arg_parts["__c_avn"] = AttributeValidationNote
@@ -390,7 +394,6 @@ def make_dict_structure_fn(
390394
f" if errors: raise __c_cve('While structuring ' + {cl.__name__!r}, errors, __cl)"
391395
)
392396
else:
393-
lines.append(" res = o.copy()")
394397
non_required = []
395398

396399
# The first loop deals with required args.

src/cattrs/v.py

-6
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,6 @@ def format_exception(exc: BaseException, type: Union[type, None]) -> str:
4747
):
4848
# This was supposed to be a mapping (and have .items()) but it something else.
4949
res = "expected a mapping"
50-
elif isinstance(exc, AttributeError) and exc.args[0].endswith(
51-
"object has no attribute 'copy'"
52-
):
53-
# This was supposed to be a mapping (and have .copy()) but it something else.
54-
# Used for TypedDicts.
55-
res = "expected a mapping"
5650
else:
5751
res = f"unknown error ({exc})"
5852

tests/test_typeddicts.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -517,4 +517,13 @@ def test_nondict_input():
517517
with raises(ClassValidationError) as exc:
518518
converter.structure(1, TypedDictA)
519519

520-
assert transform_error(exc.value) == ["expected a mapping @ $"]
520+
assert transform_error(exc.value) == [
521+
"invalid type (expected a mapping, not int) @ $"
522+
]
523+
524+
with raises(ClassValidationError) as exc:
525+
converter.structure([1], TypedDictA)
526+
527+
assert transform_error(exc.value) == [
528+
"invalid type (expected a mapping, not list) @ $"
529+
]

tests/test_v.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,9 @@ class D(TypedDict):
322322
try:
323323
c.structure({"c": 1}, D)
324324
except Exception as exc:
325-
assert transform_error(exc) == ["expected a mapping @ $.c"]
325+
assert transform_error(exc) == [
326+
"invalid type (expected a mapping, not int) @ $.c"
327+
]
326328

327329
try:
328330
c.structure({"c": {"a": "str"}}, D)

0 commit comments

Comments
 (0)