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
2 changes: 2 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Our backwards-compatibility policy can be found [here](https://github.com/python

## NEXT (UNRELEASED)

- Fix `ForbiddenExtraKeysError` and {func}`transform_error <cattrs.transform_error>` crashing with `TypeError` when an extra key isn't a string, like an int key from YAML.
([#784](https://github.com/python-attrs/cattrs/pull/784))
- Fix heterogeneous tuples and `NamedTuple`s with a member type containing a quote in its `repr`, like `tuple[Literal["a"], int]`, crashing structuring code generation with `SyntaxError`; the index note is now embedded with `repr`.
([#777](https://github.com/python-attrs/cattrs/pull/777))
- Fix {func}`transform_error <cattrs.transform_error>` listing the extra keys of a `ForbiddenExtraKeysError` in set iteration order, which made the message differ between runs; the keys are now sorted, like the error's own `__str__` already sorts them.
Expand Down
2 changes: 1 addition & 1 deletion src/cattrs/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,5 +138,5 @@ def __str__(self) -> str:
return (
self.message
or f"Extra fields in constructor for {self.cl.__name__}: "
f"{', '.join(sorted(self.extra_fields))}"
f"{', '.join(sorted(map(str, self.extra_fields)))}"
)
2 changes: 1 addition & 1 deletion src/cattrs/v.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def format_exception(exc: BaseException, type: Union[type, None]) -> str:
tn = type.__name__ if hasattr(type, "__name__") else repr(type)
res = f"invalid value for type, expected {tn}"
elif isinstance(exc, ForbiddenExtraKeysError):
res = f"extra fields found ({', '.join(sorted(exc.extra_fields))})"
res = f"extra fields found ({', '.join(sorted(map(str, exc.extra_fields)))})"
elif isinstance(exc, AttributeError) and exc.args[0].endswith(
"object has no attribute 'items'"
):
Expand Down
20 changes: 20 additions & 0 deletions tests/test_v.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,26 @@ class C:
assert transform_error(exc_info.value) == ["extra fields found (b, c, d, e) @ $"]


def test_extra_keys_not_strings(c: Converter) -> None:
"""Extra keys that aren't strings, like ints from YAML, are formatted."""

@define
class C:
a: int

c.register_structure_hook(
C, make_dict_structure_fn(C, c, _cattrs_forbid_extra_keys=True)
)

with raises(Exception) as exc_info:
c.structure({"a": 1, 2: 2, "b": 3}, C)

assert transform_error(exc_info.value) == ["extra fields found (2, b) @ $"]
assert (
str(exc_info.value.exceptions[0]) == "Extra fields in constructor for C: 2, b"
)


def test_untyped_class_errors(c: Converter) -> None:
"""Errors on untyped attrs classes transform correctly."""

Expand Down