fix(versioning): skip non-clean parents in force-parent-dirty hook

The force-parent-dirty listener was calling attributes.flag_modified
on every parent reachable from a dirty child — including parents
themselves in session.new (e.g. brand-new SqlaTable + brand-new
TableColumns from POST /api/v1/dataset/). flag_modified rejects
unloaded attributes, and a session.new SqlaTable's uuid (default=uuid4
fires at flush time) is unloaded until then. CI caught this with
InvalidRequestError cascading into 422s across dataset creation /
upload / Playwright dataset specs.

The hook is only needed for the persistent-and-clean case (child
edited, parent's own scalars untouched, dropdown otherwise empty).
Anything in session.new will flush anyway; anything in session.dirty
is already flagged; session.deleted shouldn't be touched. Short-
circuit before the flag_modified call.

Unblocks test-sqlite, test-mysql, test-postgres (previous), and
playwright dataset specs.
This commit is contained in:
Mike Bridge
2026-05-20 16:13:03 -06:00
parent 77236afa14
commit d0520f6766

View File

@@ -131,6 +131,15 @@ def _force_parent_dirty_on_child_change(session: Session) -> None:
parent = getattr(obj, parent_attr, None)
if parent is None or type(parent) is not parent_cls: # noqa: E721
continue
# Only flag *persistent + clean* parents. Anything else is
# either already going to surface in the flush (``session.new``
# → INSERT, ``session.dirty`` → already flagged) or shouldn't be
# flagged (``session.deleted`` → being removed). Also avoids
# InvalidRequestError from ``attributes.flag_modified`` when a
# brand-new parent's ``uuid`` default (``default=uuid4``) hasn't
# fired yet so the attribute is unloaded in instance state.
if parent in session.new or parent in session.dirty or parent in session.deleted:
continue
col_keys = [prop.key for prop in versioned_column_properties(parent)]
if not col_keys:
continue