Skip to content

Commit ffd2fb8

Browse files
committed
fix(141): handle after_flush_postexec when creating version objects
when creating version objects if a person 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.
1 parent 5f1e098 commit ffd2fb8

2 files changed

Lines changed: 82 additions & 2 deletions

File tree

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: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import datetime
2+
import sqlalchemy as sa
3+
from copy import copy
4+
5+
from tests import TestCase
6+
from sqlalchemy_history import version_class
7+
8+
9+
class TestBug141(TestCase):
10+
# ref: https://github.com/corridor/sqlalchemy-history/issues/141
11+
def create_models(self):
12+
article_author_table = sa.Table(
13+
"article_author",
14+
self.Model.metadata,
15+
sa.Column(
16+
"article_id", sa.Integer, sa.ForeignKey("article.id"), primary_key=True, nullable=False
17+
),
18+
sa.Column("author_id", sa.Integer, sa.ForeignKey("author.id"), primary_key=True, nullable=False),
19+
sa.Column(
20+
"created_date",
21+
sa.DateTime,
22+
nullable=False,
23+
server_default=sa.func.current_timestamp(),
24+
default=lambda: datetime.datetime.now(datetime.timezone.utc)
25+
),
26+
)
27+
28+
class Article(self.Model):
29+
__tablename__ = "article"
30+
__versioned__ = copy(self.options)
31+
32+
id = sa.Column(
33+
sa.Integer, sa.Sequence(f"{__tablename__}_seq", start=1), autoincrement=True, primary_key=True
34+
)
35+
name = sa.Column(sa.Unicode(255), nullable=False)
36+
content = sa.Column(sa.UnicodeText)
37+
38+
class Author(self.Model):
39+
__tablename__ = "author"
40+
__versioned__ = copy(self.options)
41+
42+
id = sa.Column(
43+
sa.Integer, sa.Sequence(f"{__tablename__}_seq", start=1), autoincrement=True, primary_key=True
44+
)
45+
name = sa.Column(sa.Unicode(255))
46+
article_id = sa.Column(sa.Integer, sa.ForeignKey(Article.id))
47+
articles = sa.orm.relationship(Article, secondary=article_author_table, backref="authors")
48+
49+
self.Article = Article
50+
self.Author = Author
51+
self.article_author_table = article_author_table
52+
53+
def test_add_record(self):
54+
article = self.Article(name="Article 1")
55+
author = self.Author(name="Author 1", articles=[article])
56+
@sa.event.listens_for(self.session, 'after_flush_postexec')
57+
def after_flush_postexec(session, flush_context):
58+
if author.name != "yoyoyoyoyo":
59+
author.name = "yoyoyoyoyo"
60+
self.session.add(article)
61+
self.session.add(author)
62+
self.session.commit()
63+
64+
versioned_objs = self.session.query(version_class(self.Author)).all()
65+
assert len(versioned_objs) == 1
66+
assert versioned_objs[0].operation_type == 0
67+
assert versioned_objs[0].name == "yoyoyoyoyo"
68+
author.name = "sdfeoinfe"
69+
self.session.add(author)
70+
self.session.commit()
71+
versioned_objs = self.session.query(version_class(self.Author)).all()
72+
assert len(versioned_objs) == 2
73+
assert versioned_objs[0].operation_type == 0
74+
assert versioned_objs[1].operation_type == 1
75+
assert versioned_objs[0].name == versioned_objs[1].name == "yoyoyoyoyo"
76+
sa.event.remove(self.session, "after_flush_postexec", after_flush_postexec)

0 commit comments

Comments
 (0)