-
Notifications
You must be signed in to change notification settings - Fork 89
ENG-3298: Fix DetachedInstanceError on Comment.delete() replies access #7906
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| from enum import Enum as EnumType | ||
| from typing import TYPE_CHECKING, Any | ||
|
|
||
| from loguru import logger | ||
| from sqlalchemy import Column, DateTime, ForeignKey, Index, String, func, orm | ||
| from sqlalchemy import Enum as EnumColumn | ||
| from sqlalchemy.ext.declarative import declared_attr | ||
|
|
@@ -168,9 +169,17 @@ def delete(self, db: Session) -> None: | |
| # are added, prevent deletion of comments with those types to preserve | ||
| # correspondence threads. | ||
|
|
||
| # Re-fetch from the DB to guarantee a session-bound instance. | ||
| # Callers (e.g. test fixture teardown) may hold a detached reference, | ||
| # which would raise DetachedInstanceError on any lazy relationship access. | ||
| comment = db.query(Comment).filter(Comment.id == self.id).first() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The re-fetch unconditionally issues a A lighter alternative that avoids the extra query for the happy path: from sqlalchemy import inspect as sa_inspect
if sa_inspect(self).detached:
comment = db.query(Comment).filter(Comment.id == self.id).first()
if comment is None:
logger.debug("Comment {} already deleted, skipping", self.id)
return
else:
comment = selfThis reserves the DB round-trip for the detached case (fixture teardown, etc.) while the normal flow incurs no overhead. Not a blocker if the call volume is low, but worth considering. |
||
| if comment is None: | ||
| logger.debug("Comment {} already deleted, skipping", self.id) | ||
| return | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The
The two are complementary, not redundant. A brief note linking them would help future readers understand why both checks are needed. |
||
|
|
||
| # Collect all descendants iteratively (breadth-first) | ||
| to_delete = [] | ||
| stack = list(self.replies) | ||
| stack = list(comment.replies) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| while stack: | ||
| node = stack.pop() | ||
| stack.extend(node.replies) | ||
|
|
@@ -187,11 +196,11 @@ def delete(self, db: Session) -> None: | |
|
|
||
| # Delete self | ||
| AttachmentService(db).delete_for_reference( | ||
| self.id, AttachmentReferenceType.comment | ||
| comment.id, AttachmentReferenceType.comment | ||
| ) | ||
| for reference in self.references: | ||
| for reference in comment.references: | ||
| reference.delete(db) | ||
| db.delete(self) | ||
| db.delete(comment) | ||
|
|
||
| @staticmethod | ||
| def delete_comments_for_reference_and_type( | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.