fix: raise clear error when default_factory/default passed to WriteOnlyMapped/DynamicMapped relationship by algojogacor · Pull Request #13289 · sqlalchemy/sqlalchemy · GitHub
Skip to content

fix: raise clear error when default_factory/default passed to WriteOnlyMapped/DynamicMapped relationship#13289

Open
algojogacor wants to merge 3 commits into
sqlalchemy:mainfrom
algojogacor:fix/writeonly-default-factory
Open

fix: raise clear error when default_factory/default passed to WriteOnlyMapped/DynamicMapped relationship#13289
algojogacor wants to merge 3 commits into
sqlalchemy:mainfrom
algojogacor:fix/writeonly-default-factory

Conversation

@algojogacor

@algojogacor algojogacor commented May 12, 2026

Copy link
Copy Markdown

Description

Fixes: #13227

When default_factory=list or default is passed to a WriteOnlyMapped or DynamicMapped relationship, it previously caused cryptic crashes (e.g., TypeError: LoaderCallableStatus object is not iterable in _WriteOnlyAttributeImpl.set()). This happens because these mapped types have no in-memory collection, so default_factory and default are not meaningful.

This PR adds an explicit ArgumentError in RelationshipProperty.__init__() that fires when default_factory or default is set on a write-only or dynamic relationship, with a clear message:

WriteOnlyMapped / DynamicMapped relationship {name} does not support 'default' or 'default_factory' properties. Use 'init=False' to exclude this attribute from __init__.

Changes:

  • lib/sqlalchemy/orm/relationships.py: Added check for is_write_only or is_dynamic with dataclasses_default_factory/dataclasses_default raising ArgumentError. Also bypassed early default rejection in _RelationshipDeclared for write_only/dynamic lazy so the specific error fires.
  • test/orm/declarative/test_typed_mapping.py: Added tests for both WriteOnlyMapped and DynamicMapped with default_factory and default.

Checklist

  • Tests added for WriteOnlyMapped + DynamicMapped with both default_factory and default
  • Single consolidated error message per review feedback
  • Please include: Fixes: #13227 in the commit message

… default passed to WriteOnlyMapped relationship

WriteOnlyMapped relationships have no in-memory collection, so
default_factory and default are not meaningful. Previously, passing
default_factory=list would crash with a cryptic error (e.g.,
'LoaderCallableStatus' object is not iterable). Now, declarative_scan()
raises a clear ArgumentError suggesting init=False as the correct approach.

Added test verifying the expected error message.

@CaselIT CaselIT left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, left a few suggestions. let's also wait mike input on whatever we should include also dynamic here

Comment thread lib/sqlalchemy/orm/relationships.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zzzeek do default / default_factory make sense with dynamic? if I'm not mistaken it should be similar to write only

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dynamic is not exactly the same as write_only but yes, these are descriptors that probably shouldnt be considered within the dataclass regime, unless someone can show us a use case for that


if is_write_only:
if (
self._attribute_options.dataclasses_default_factory

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could use a single error, there isn't really a need to duplicate them here.

Something like

WriteOnlyMapped relationship {self._format_as_string(cls, key)} does not support 'default' or 'default_factory' properties. Use 'init=False' to exclude this attribute from init


self._assertions(A, B, "write_only")

def test_write_only_default_factory_raises(self, decl_base):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to also test the default

@CaselIT CaselIT requested a review from zzzeek May 12, 2026 08:07
@algojogacor

Copy link
Copy Markdown
Author

Thanks for the review! Addressed both suggestions:

  • Combined the default and default_factory error checks into a single ArgumentError with a unified message
  • Added test_write_only_default_raises covering the default=[] case

Waiting for Mike's input on the dynamic question.

Comment thread lib/sqlalchemy/orm/relationships.py Outdated
else:
is_write_only = is_dynamic = False

if is_write_only:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dynamic is not exactly the same as write_only but yes, these are descriptors that probably shouldnt be considered within the dataclass regime, unless someone can show us a use case for that

Comment thread lib/sqlalchemy/orm/relationships.py Outdated
f"For relationship "
f"{self._format_as_string(cls, key)}: "
"'default' and 'default_factory' are not supported for "
"WriteOnlyMapped relationships, which have "

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah would mention dynamic here also

Comment thread lib/sqlalchemy/orm/relationships.py Outdated
"no in-memory collection. Use 'init=False' "
"to exclude this attribute from __init__ "
"and add items after construction via "
"direct assignment or "

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this message is a bit wordy. if there's no way to make it more succinct consider linking to errors.rst (see the "code" parameter of the ArgumentError class hierarchy)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to move these tests. test_typed_mapping is not specific to dataclasses nor writeonly/dynamic.

these tests should be either in:

test_dc_transforms.py

or

test_dynamic.py

and there should be tests for both the writeonly and dynamic cases

…dback

- Single error message mentioning both WriteOnlyMapped and DynamicMapped
- Error message: 'WriteOnlyMapped / DynamicMapped relationship does
  not support default or default_factory properties. Use init=False'
- Add tests for DynamicMapped with default_factory and default
- Bypass early default rejection in _RelationshipDeclared for
  write_only/dynamic lazy so the specific error fires
@algojogacor algojogacor changed the title fix: raise clear error when default_factory/default passed to WriteOnlyMapped relationship fix: raise clear error when default_factory/default passed to WriteOnlyMapped/DynamicMapped relationship May 12, 2026
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.

WriteOnlyMapped: default_factory=list broken in 2.1 due to new declarative_scan validation

3 participants