Skip to content

Document dataclass caveats for Declarative Dataclass Mapping - #13470

Open
ArockiaRajamanickam wants to merge 1 commit into
sqlalchemy:mainfrom
ArockiaRajamanickam:dataclass-caveats-docs
Open

ArockiaRajamanickam wants to merge 1 commit into
sqlalchemy:mainfrom
ArockiaRajamanickam:dataclass-caveats-docs

Conversation

@ArockiaRajamanickam

Copy link
Copy Markdown

Adds a Dataclass Caveats section to doc/build/orm/dataclasses.rst, addressing #9410. It sits at the end of the Declarative Dataclass Mapping material, just before the Pydantic section.

Rather than transcribing the 2023 checklist in the issue, I ran each item against 2.0.51 and 2.1.0b3 and documented what those versions actually do. That changed the content in several places, so the notes below explain what is in the PR and what deliberately is not.

Documented (four items, each reproduced on both versions):

  • Field ordering — a field without a default declared after one with a default. Raises InvalidRequestError wrapping the stdlib TypeError("non-default argument 'email' follows default argument 'name'"). Both fixes are shown and both were tested: reorder the fields, or use kw_only=True.
  • kw_only and type checkers — reframed from the issue's wording. kw_only=True on a superclass is applied to subclasses at runtime (id lands in kwonlyargs on both 2.0 and 2.1), so this is not a runtime bug. The mismatch is with PEP 681 checkers, which scope the argument to the declaring class. Links to MappedAsDataclass inherits dataclasses attributes, while the spec says they don't #9493.
  • asdict() recursion — a back_populates pair makes the object graph cyclic and asdict() has no cycle detection. Confirmed RecursionError on both versions and in all three states (unloaded, eagerly loaded, transient); astuple() behaves the same. Links to dataclasses astuple and asdict crash on recursive dataclass structures / dont support deepcopy memo python/cpython#94345, still open.
  • Dataclass defaults and ORM operations — the Session.merge case. On 2.0 a collection with default_factory is treated as loaded-and-empty, and merging an object built by __init__() disassociates rows the database already holds; in my test both child rows had their foreign key set to NULL. On 2.1 the same test leaves them intact, so this carries a .. versionchanged:: 2.1 pointing at :ref:change_12168`` rather than re-explaining the mechanism.

Deliberately not included:

  • declared_attr on a mixin (item 2) — already covered by :ref:orm_declarative_dc_mixins, which has a tip on this and links to :ref:error_dcmx. On 2.1 the error message itself names the fix. Linked instead of duplicated.
  • Base-class ordering — a 2023 comment on the issue reports that DeclarativeBase, MappedAsDataclass fails where the reverse order works. It does not reproduce: both orders produce a working dataclass on 2.0.51 and 2.1.0b3. Left out rather than documenting a limitation that no longer exists — worth a look if it was fixed silently.
  • need docs /warnings that __init__ is not called on load #9930 / non-mapped field defaults on load — tracked separately, and it seemed out of scope for a caveats section on the mapping feature itself.

Open questions for you:

  • Placement: I put it before the Pydantic section, on the assumption caveats close out the native-dataclass material. Happy to move it.
  • Should any of this also land on the rel_2_0 doc branch? The field-ordering and asdict() items apply there unchanged, and the merge item is arguably most useful to 2.0 readers.

Verification: full sphinx -b html build adds no new warnings (the section previously tripped four "title underline too short" warnings, now fixed; the four remaining warnings in the build are pre-existing in changelog_07.rst, migration_21.rst and ambiguous Python xrefs). Every cross-reference resolves, checked in the built HTML — including :ref:change_12168``, :paramref: targets and the two stdlib links. Note that `conf.py` has no `intersphinx_mapping`, so I used the file's existing `dataclasses_` named target and explicit URLs instead of `:mod:`/`:func:` roles, which render unlinked here. `tools/format_docs_code.py --check` passes.

I used an AI assistant while working on this. The prose is my own and every behaviour claim above was verified by running it against both versions rather than taken from the issue text.

Adds a Dataclass Caveats section covering field ordering, kw_only
inheritance vs type checkers, asdict() recursion across
back_populates, and how dataclass defaults interact with ORM
operations including the 2.1 change.

References sqlalchemy#9410
@zzzeek

zzzeek commented Jul 30, 2026

Copy link
Copy Markdown
Member

I like that idea of doing docs and having the LLM independently confirm each bullet. I'm away this week so I can't look closely yet but I would imagine we'd backport to 2.0 as well , we usually do that from our gerrit review server.

@ArockiaRajamanickam

Copy link
Copy Markdown
Author

Thanks, and no rush at all. Understood on the backport, I'll leave that to your gerrit process and won't open anything against the 2.0 docs branch.

One thing that might be worth a look when you're back. The base class ordering report from 2023 in the issue list doesn't reproduce for me on either 2.0.51 or 2.1.0b3. Both MappedAsDataclass, DeclarativeBase and DeclarativeBase, MappedAsDataclass with kw_only=True give a working dataclass with identical __init__ signatures, so I left it out of the PR rather than document a limitation that looks like it's already gone. Might be one you can strike off the list.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants