Skip to content

Commit cdb8abc

Browse files
committed
fix(141): handle after_flush_postexec when creating version objects
when creating version objects if a user has created after_flush_postexec hook, which keeps calling `after_flush` untill it exhausts 100 attempts or session is no longer dirty, this is picked up by mapper as after_update which within same transaction adds a update operation type, so we have a check if target is already in UoW we continue with operation type that it is in untill transaction is completed. We also updated a existing testcase named `test_replace_deleted_object_with_update` as it was updating the pk of article object, but the pk being identity of object is used by operations to track target, so changing a non identity column to validate partial flush does not impact other objects
1 parent 5f1e098 commit cdb8abc

File tree

3 files changed

+50
-7
lines changed

3 files changed

+50
-7
lines changed

sqlalchemy_history/operation.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,11 @@ def add_update(self, target):
9090
del state_copy[rel_key]
9191

9292
if state_copy:
93-
self.add(Operation(target, Operation.UPDATE))
94-
93+
if target in self:
94+
# If already in current transaction and some event hook did a update
95+
# prior to commit hook, continue with operation type as it is
96+
self.add(Operation(target, self[self.format_key(target)].type))
97+
else:
98+
self.add(Operation(target, Operation.UPDATE))
9599
def add_delete(self, target):
96100
self.add(Operation(target, Operation.DELETE))
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import sqlalchemy as sa
2+
from copy import copy
3+
4+
from tests import TestCase
5+
from sqlalchemy_history import version_class
6+
7+
8+
class TestBug141(TestCase):
9+
# ref: https://github.com/corridor/sqlalchemy-history/issues/141
10+
def create_models(self):
11+
class Author(self.Model):
12+
__tablename__ = "author"
13+
__versioned__ = copy(self.options)
14+
15+
id = sa.Column(
16+
sa.Integer, sa.Sequence(f"{__tablename__}_seq", start=1), autoincrement=True, primary_key=True
17+
)
18+
name = sa.Column(sa.Unicode(255))
19+
20+
self.Author = Author
21+
22+
def test_add_record(self):
23+
author = self.Author(name="Author 1")
24+
@sa.event.listens_for(self.session, 'after_flush_postexec')
25+
def after_flush_postexec(session, flush_context):
26+
if author.name != "yoyoyoyoyo":
27+
author.name = "yoyoyoyoyo"
28+
self.session.add(author)
29+
self.session.commit()
30+
31+
versioned_objs = self.session.query(version_class(self.Author)).all()
32+
assert len(versioned_objs) == 1
33+
assert versioned_objs[0].operation_type == 0
34+
assert versioned_objs[0].name == "yoyoyoyoyo"
35+
author.name = "sdfeoinfe"
36+
self.session.add(author)
37+
self.session.commit()
38+
versioned_objs = self.session.query(version_class(self.Author)).all()
39+
assert len(versioned_objs) == 2
40+
assert versioned_objs[0].operation_type == 0
41+
assert versioned_objs[1].operation_type == 1
42+
assert versioned_objs[0].name == versioned_objs[1].name == "yoyoyoyoyo"
43+
sa.event.remove(self.session, "after_flush_postexec", after_flush_postexec)

tests/test_exotic_operation_combos.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,6 @@ def test_insert_deleted_and_flushed_object(self):
4040
assert article2.versions[0].operation_type == Operation.INSERT
4141
assert article2.versions[1].operation_type == Operation.UPDATE
4242

43-
# Ref for mssql: https://github.com/sqlalchemy/sqlalchemy/discussions/8829
44-
@mark.skipif(
45-
os.environ.get("DB") == "mssql", reason="mssql does not support changing the IDENTITY column"
46-
)
4743
def test_replace_deleted_object_with_update(self):
4844
article = self.Article()
4945
article.name = "Some article"
@@ -58,7 +54,7 @@ def test_replace_deleted_object_with_update(self):
5854
self.session.delete(article)
5955
self.session.flush()
6056

61-
article2.id = article.id
57+
article2.name = article.name
6258
self.session.commit()
6359
assert article2.versions.count() == 2
6460
assert article2.versions[0].operation_type == Operation.INSERT

0 commit comments

Comments
 (0)