Skip to content

Commit 959099f

Browse files
authored
Merge pull request #135 from TTWShell/bugfix/nested-session-signals
🐛 support/fixed signal when using @transaction because of session.beg…
2 parents 65e60b5 + 80fc5cc commit 959099f

File tree

9 files changed

+82
-3
lines changed

9 files changed

+82
-3
lines changed

.circleci/config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ jobs:
4444
pip install flake8 pytest pytest-cov pytest-env
4545
pip install --editable ".[hobbit,hobbit_core]"
4646
pip install celery
47+
pip install "MarkupSafe==2.0.1" blinker[flask]
4748
- run:
4849
name: use flake8 check self
4950
command: flake8 .
@@ -76,6 +77,7 @@ jobs:
7677
pip install flake8 pytest pytest-cov pytest-env
7778
pip install --editable ".[hobbit,hobbit_core]"
7879
pip install celery
80+
pip install "MarkupSafe==2.0.1" blinker[flask]
7981
- run:
8082
name: use flake8 check self
8183
command: flake8 .

docs/changelog.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
Change history
22
==============
33

4+
2.2.3 (2022-05-18)
5+
******************
6+
7+
* Support use nested=None(`@transaction(db.session, nested=None)`) to avoid bug from `flask_sqlalchemy.models_committed` signal.
8+
49
2.2.2 (2022-02-17)
510
******************
611

hobbit_core/db.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,9 @@ def transaction(session: Session, nested: bool = False):
312312
or ``session.autocommit=True``.
313313
See more: http://flask-sqlalchemy.pocoo.org/2.3/api/#sessions
314314
315+
2022-05-18 Updated: Use `nested=None` to prevent signal bug, See more:
316+
https://github.com/pallets-eco/flask-sqlalchemy/issues/645
317+
315318
Tips:
316319
* **Can't** do ``session.commit()`` **in func**, **otherwise raise**
317320
``sqlalchemy.exc.ResourceClosedError``: `This transaction is closed`.
@@ -361,8 +364,11 @@ def inner(*args, **kwargs):
361364
if session.autocommit is True and nested is False:
362365
session.begin() # start a transaction
363366
try:
364-
with session.begin_nested():
367+
if nested is None:
365368
resp = func(*args, **kwargs)
369+
else:
370+
with session.begin_nested():
371+
resp = func(*args, **kwargs)
366372
if not nested:
367373
# commit - begin(), transaction finished
368374
session.commit()

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def gen_data(data_root='static'):
3232

3333
setup(
3434
name='hobbit-core',
35-
version='2.2.2',
35+
version='2.2.3',
3636
python_requires='>=3.6, <4',
3737
description='Hobbit - A flask project generator.',
3838
long_description=long_description,

tests/test_app/models.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# -*- encoding: utf-8 -*-
22
from sqlalchemy import UniqueConstraint, func, DateTime, BigInteger
3+
from flask_sqlalchemy import models_committed
34

45
from hobbit_core.db import Column, BaseModel, EnumExt
56

@@ -18,6 +19,22 @@ class User(BaseModel):
1819
role = Column(db.Enum(RoleEnum), doc='角色', default=RoleEnum.admin)
1920

2021

22+
@models_committed.connect
23+
def signalling(app, changes, **kwargs):
24+
for instance, operation in changes:
25+
if instance.__tablename__ in [i.__tablename__ for i in [User]]:
26+
models_committed.disconnect(signalling)
27+
session = db.create_scoped_session()
28+
user = session.query(User).first()
29+
if user and user.username == 'signalling_test':
30+
user.username = 'signalling_ok'
31+
session.merge(user)
32+
session.commit()
33+
session.remove()
34+
models_committed.connect(signalling)
35+
break
36+
37+
2138
class Role(BaseModel): # just for assert multi model worked
2239
name = Column(db.String(50), nullable=False, unique=True)
2340

tests/test_app/run.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class ConfigClass:
1515
'oracle': 'oracle://scott:tiger@localhost/test',
1616
'mysql': 'mysql+pymysql://root:root@localhost/hobbit_core',
1717
}
18-
SQLALCHEMY_TRACK_MODIFICATIONS = False
18+
SQLALCHEMY_TRACK_MODIFICATIONS = True
1919
# SQLALCHEMY_ECHO = True
2020
TESTING = True
2121

tests/test_app/views.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
from webargs.flaskparser import use_kwargs as base_use_kwargs
55

66
from hobbit_core.utils import use_kwargs
7+
from hobbit_core.db import transaction
78

89
from .schemas import UserSchema
10+
from .exts import db
11+
from .models import User
912

1013
bp = Blueprint('test', __name__)
1114

@@ -48,3 +51,27 @@ def use_kwargs_dictargmap_partial(**kwargs):
4851
})
4952
def base_use_kwargs_dictargmap_partial(**kwargs):
5053
return jsonify(wrapper_kwargs(kwargs))
54+
55+
56+
@bp.route('/create_user/success/', methods=['POST'])
57+
@base_use_kwargs({'email': fields.Str()})
58+
def create_user_success(email):
59+
@transaction(db.session, nested=None)
60+
def create_user():
61+
user1 = User(username='signalling_test', email=email, password='1')
62+
db.session.add(user1)
63+
64+
create_user()
65+
return jsonify({})
66+
67+
68+
@bp.route('/create_user/failed/', methods=['POST'])
69+
@base_use_kwargs({'email': fields.Str()})
70+
def create_user_failed(email):
71+
@transaction(db.session)
72+
def create_user():
73+
user1 = User(username='signalling_test', email=email, password='1')
74+
db.session.add(user1)
75+
76+
create_user()
77+
return jsonify({})

tests/test_db.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,3 +301,22 @@ def test_autocommittrue_not_excepted(self, auto_session, assert_session):
301301

302302
# assert not rollback. Be very careful when using commit. 😒😒😒😒
303303
assert len(assert_session.query(User).all()) == 1
304+
305+
306+
class TestNestedSessionSignal(BaseTest):
307+
308+
def test_transaction_signal_success(self, client, assert_session):
309+
310+
resp = client.post('/create_user/success/', json={"email": email})
311+
assert resp.status_code == 200
312+
313+
user = assert_session.query(User).filter(User.email == email).first()
314+
assert user and user.username == "signalling_ok"
315+
316+
def test_transaction_signal_dailed(self, client, assert_session):
317+
318+
resp = client.post('/create_user/failed/', json={"email": email})
319+
assert resp.status_code == 200
320+
321+
user = assert_session.query(User).filter(User.email == email).first()
322+
assert user and user.username != "signalling_ok"

tox.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ deps =
2020
sphinx
2121
sphinx-autobuild
2222
flask-sphinx-themes
23+
MarkupSafe==2.0.1
2324
whitelist_externals = make
2425
commands = make html
2526

@@ -36,6 +37,8 @@ deps =
3637
pytest-env
3738
flake8
3839
pipenv
40+
MarkupSafe==2.0.1
41+
blinker[flask]
3942
commands =
4043
flake8 .
4144
# mypy hobbit hobbit_core tests

0 commit comments

Comments
 (0)