Skip to content

Commit 4d3e9c8

Browse files
committed
typeddicts: improve error message on invalid input
1 parent 527291f commit 4d3e9c8

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, TypedDict, TypeVar
67

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

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

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

395398
# 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
@@ -323,7 +323,9 @@ class D(TypedDict):
323323
try:
324324
c.structure({"c": 1}, D)
325325
except Exception as exc:
326-
assert transform_error(exc) == ["expected a mapping @ $.c"]
326+
assert transform_error(exc) == [
327+
"invalid type (expected a mapping, not int) @ $.c"
328+
]
327329

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

0 commit comments

Comments
 (0)