Skip to content

Commit 5675952

Browse files
committed
Improve detailed validation for external validators
1 parent 59edafd commit 5675952

File tree

2 files changed

+32
-4
lines changed

2 files changed

+32
-4
lines changed

Diff for: src/cattrs/gen.py

+14-4
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ def make_dict_structure_fn(
215215
_cattrs_use_linecache: bool = True,
216216
_cattrs_prefer_attrib_converters: bool = False,
217217
_cattrs_detailed_validation: bool = True,
218-
**kwargs,
218+
**kwargs: AttributeOverride,
219219
) -> Callable[[Mapping[str, Any], Any], T]:
220220
"""Generate a specialized dict structuring function for an attrs class."""
221221

@@ -326,6 +326,15 @@ def make_dict_structure_fn(
326326
post_lines.append(
327327
f" if errors: raise __c_cve('While structuring {cl.__name__}', errors, __cl)"
328328
)
329+
instantiation_lines = (
330+
[" try:"]
331+
+ [" return __cl("]
332+
+ [f" {line}" for line in invocation_lines]
333+
+ [" )"]
334+
+ [
335+
f" except Exception as exc: raise __c_cve('While structuring {cl.__name__}', [exc], __cl)"
336+
]
337+
)
329338
else:
330339
non_required = []
331340
# The first loop deals with required args.
@@ -432,6 +441,9 @@ def make_dict_structure_fn(
432441
)
433442
else:
434443
post_lines.append(f" res['{ian}'] = o['{kn}']")
444+
instantiation_lines = (
445+
[" return __cl("] + [f" {line}" for line in invocation_lines] + [" )"]
446+
)
435447

436448
if _cattrs_forbid_extra_keys:
437449
globs["__c_a"] = allowed_fields
@@ -452,9 +464,7 @@ def make_dict_structure_fn(
452464
[f"def {fn_name}(o, _, *, {internal_arg_line}):"]
453465
+ lines
454466
+ post_lines
455-
+ [" return __cl("]
456-
+ [f" {line}" for line in invocation_lines]
457-
+ [" )"]
467+
+ instantiation_lines
458468
)
459469

460470
fname = _generate_unique_filename(cl, "structure", reserve=_cattrs_use_linecache)

Diff for: tests/test_validation.py

+18
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,24 @@ class Test:
3737
)
3838

3939

40+
def test_external_class_validation():
41+
"""Proper class validation errors are raised when a classes __init__ raises."""
42+
c = GenConverter(detailed_validation=True)
43+
44+
@define
45+
class Test:
46+
a: int
47+
b: str = field(validator=in_(["a", "b"]))
48+
c: str
49+
50+
with pytest.raises(ClassValidationError) as exc:
51+
c.structure({"a": 1, "b": "c", "c": "1"}, Test)
52+
53+
assert repr(exc.value.exceptions[0]) == repr(
54+
ValueError("'b' must be in ['a', 'b'] (got 'c')")
55+
)
56+
57+
4058
def test_list_validation():
4159
"""Proper validation errors are raised structuring lists."""
4260
c = GenConverter(detailed_validation=True)

0 commit comments

Comments
 (0)