Skip to content

Fix ClassDecoratorSetting and get_class_settings to correctly handle __databind_settings__ on subclasses#77

Merged
NiklasRosenstein merged 8 commits intodevelopfrom
copilot/fix-extrakeeys-decorator-bug
Mar 25, 2026
Merged

Fix ClassDecoratorSetting and get_class_settings to correctly handle __databind_settings__ on subclasses#77
NiklasRosenstein merged 8 commits intodevelopfrom
copilot/fix-extrakeeys-decorator-bug

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 23, 2026

ClassDecoratorSetting.__call__ used getattr() to look up __databind_settings__, which resolves through MRO and returns the parent's list when the subclass has none. It then appends to that parent list in-place — but get_class_settings() originally used vars(), which only checks the class's own __dict__, so the setting was never found on the subclass. Net effect: @ExtraKeys() on a subclass whose parent was already decorated was silently a no-op.

Additionally, get_class_settings() did not traverse the MRO, meaning subclasses could not inherit settings (such as ExtraKeys()) from parent classes at all.

Changes

  • settings.pyClassDecoratorSetting.__call__: Replace getattr(type_, "__databind_settings__", None) with vars(type_).get("__databind_settings__", None), ensuring a new list is always created on the target class rather than reusing an inherited one.

  • settings.pyget_class_settings: Updated to traverse type_.__mro__ so that subclasses inherit ClassDecoratorSetting settings (e.g. ExtraKeys()) from parent classes. The child class's own settings are yielded first, so a child can override a parent's setting by decorating itself explicitly (e.g. @ExtraKeys(allow=False)).

  • converters_test.py: Add three regression tests covering the scenario:

    • Both parent and child decorated with @ExtraKeys() each get independent __databind_settings__ lists, and the parent's list is not polluted.
    • A decorated subclass correctly allows extra keys during deserialization.
    • An undecorated subclass inherits ExtraKeys() from its parent (extra keys allowed), while a child explicitly decorated with @ExtraKeys(allow=False) overrides that and rejects extra keys.
@ExtraKeys()
@dataclasses.dataclass
class Parent:
    a: int

@dataclasses.dataclass
class Child(Parent):  # Inherits ExtraKeys() from Parent via MRO
    b: str = ""

mapper.deserialize({"a": 1, "b": "hello", "extra": "ignored"}, Child)  # Now works correctly

@ExtraKeys(allow=False)
@dataclasses.dataclass
class ChildOverriding(Parent):  # Overrides parent's ExtraKeys()
    b: str = ""

mapper.deserialize({"a": 1, "b": "hello", "extra": "ignored"}, ChildOverriding)  # Raises ConversionError
Original prompt

This section details on the original issue you should resolve

<issue_title>ExtraKeys decorator does not create own databind_settings on subclasses</issue_title>
<issue_description>When applying ExtraKeys() to a subclass that has no own __databind_settings__, the decorator's getattr() call falls back to the parent's list via MRO traversal and appends to it in place, rather than creating a new list on the subclass. As a result, get_class_settings(), which uses vars(), finds no setting on the subclass, making ExtraKeys effectively a no-op for it. The bug is order-dependent: it only triggers when ExtraKeys() is applied to a subclass whose parent was already decorated.</issue_description>

<agent_instructions>Include at least one at max three test cases that cover the described sccenario.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


📍 Connect Copilot coding agent with Jira, Azure Boards or Linear to delegate work to Copilot in one click without leaving your project management tool.

Copilot AI changed the title [WIP] Fix ExtraKeys decorator to create own __databind_settings__ in subclasses Fix ClassDecoratorSetting to create own __databind_settings__ on subclasses Mar 23, 2026
Copilot AI requested a review from NiklasRosenstein March 23, 2026 16:06
Copilot AI changed the title Fix ClassDecoratorSetting to create own __databind_settings__ on subclasses Fix ClassDecoratorSetting and get_class_settings to correctly handle __databind_settings__ on subclasses Mar 25, 2026
Copilot AI requested a review from NiklasRosenstein March 25, 2026 11:14
NiklasRosenstein and others added 5 commits March 25, 2026 12:21
Ubuntu 20.04 runners have been removed, causing jobs to queue forever.
Update to ubuntu-latest and bump checkout/setup-python to v4/v5.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Alias annotation on enum members is intentional for databind but
violates mypy's enum typing spec. Add type: ignore to silence it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
pkg_resources has no stubs and is only imported on Python < 3.10 at
runtime, but mypy still checks the branch. Suppress import-not-found
and the resulting no-any-return error.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The pkg_resources type ignores are needed on 3.12+ but unused on 3.11
where stubs are available. Adding unused-ignore suppresses the warning
on versions where the ignore isn't needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@NiklasRosenstein NiklasRosenstein marked this pull request as ready for review March 25, 2026 11:56
@NiklasRosenstein NiklasRosenstein merged commit 879b858 into develop Mar 25, 2026
8 checks passed
@NiklasRosenstein NiklasRosenstein deleted the copilot/fix-extrakeeys-decorator-bug branch March 25, 2026 11:56
NiklasRosenstein added a commit that referenced this pull request Apr 2, 2026
## Summary

- Fixes #80: `RecursionError` when serializing/deserializing dataclasses
using the `Union` registry
- Adds `inheritable` attribute to `ClassDecoratorSetting` (default
`True`) and sets it to `False` on `Union`, preventing Union settings
from being inherited by subclasses through MRO traversal
- PR #77 introduced MRO traversal in `get_class_settings()` for
`ExtraKeys` inheritance, but this inadvertently caused `Union` to also
be inherited, leading to infinite recursion

## Test plan

- [x] Regression test: Union base class with registered subclasses can
serialize/deserialize without recursion
- [x] Regression test: Union is not inherited through multi-level MRO
chains (Base -> Middle -> Leaf)
- [x] Regression test: Subclass with its own `@Union` decorator still
works correctly
- [x] Existing `ExtraKeys` inheritance tests still pass (PR #77 behavior
preserved)
- [x] Full test suite passes (81 tests)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ExtraKeys decorator does not create own __databind_settings__ on subclasses

2 participants