Skip to content

Commit

Permalink
🐛 support/fixed signal when using @transaction because of session.beg…
Browse files Browse the repository at this point in the history
…in_nested
  • Loading branch information
legolas.zhan committed May 18, 2022
1 parent 65e60b5 commit ccfda8f
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 3 deletions.
5 changes: 5 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Change history
==============

2.2.3 (2022-05-18)
******************

* Support use nested=None(`@transaction(db.session, nested=None)`) to avoid bug from `flask_sqlalchemy.models_committed` signal.

2.2.2 (2022-02-17)
******************

Expand Down
8 changes: 7 additions & 1 deletion hobbit_core/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,9 @@ def transaction(session: Session, nested: bool = False):
or ``session.autocommit=True``.
See more: http://flask-sqlalchemy.pocoo.org/2.3/api/#sessions
2022-05-18 Updated: Use `nested=None` to prevent signal bug, See more:
https://github.com/pallets-eco/flask-sqlalchemy/issues/645
Tips:
* **Can't** do ``session.commit()`` **in func**, **otherwise raise**
``sqlalchemy.exc.ResourceClosedError``: `This transaction is closed`.
Expand Down Expand Up @@ -361,8 +364,11 @@ def inner(*args, **kwargs):
if session.autocommit is True and nested is False:
session.begin() # start a transaction
try:
with session.begin_nested():
if nested is None:
resp = func(*args, **kwargs)
else:
with session.begin_nested():
resp = func(*args, **kwargs)
if not nested:
# commit - begin(), transaction finished
session.commit()
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def gen_data(data_root='static'):

setup(
name='hobbit-core',
version='2.2.2',
version='2.2.3',
python_requires='>=3.6, <4',
description='Hobbit - A flask project generator.',
long_description=long_description,
Expand Down
17 changes: 17 additions & 0 deletions tests/test_app/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- encoding: utf-8 -*-
from sqlalchemy import UniqueConstraint, func, DateTime, BigInteger
from flask_sqlalchemy import models_committed

from hobbit_core.db import Column, BaseModel, EnumExt

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


@models_committed.connect
def signalling(app, changes, **kwargs):
for instance, operation in changes:
if instance.__tablename__ in [i.__tablename__ for i in [User]]:
models_committed.disconnect(signalling)
session = db.create_scoped_session()
user = session.query(User).first()
if user and user.username == 'signalling_test':
user.username = 'signalling_ok'
session.merge(user)
session.commit()
session.remove()
models_committed.connect(signalling)
break


class Role(BaseModel): # just for assert multi model worked
name = Column(db.String(50), nullable=False, unique=True)

Expand Down
2 changes: 1 addition & 1 deletion tests/test_app/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class ConfigClass:
'oracle': 'oracle://scott:tiger@localhost/test',
'mysql': 'mysql+pymysql://root:root@localhost/hobbit_core',
}
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_TRACK_MODIFICATIONS = True
# SQLALCHEMY_ECHO = True
TESTING = True

Expand Down
27 changes: 27 additions & 0 deletions tests/test_app/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
from webargs.flaskparser import use_kwargs as base_use_kwargs

from hobbit_core.utils import use_kwargs
from hobbit_core.db import transaction

from .schemas import UserSchema
from .exts import db
from .models import User

bp = Blueprint('test', __name__)

Expand Down Expand Up @@ -48,3 +51,27 @@ def use_kwargs_dictargmap_partial(**kwargs):
})
def base_use_kwargs_dictargmap_partial(**kwargs):
return jsonify(wrapper_kwargs(kwargs))


@bp.route('/create_user/success/', methods=['POST'])
@base_use_kwargs({'email': fields.Str()})
def create_user_success(email):
@transaction(db.session, nested=None)
def create_user():
user1 = User(username='signalling_test', email=email, password='1')
db.session.add(user1)

create_user()
return jsonify({})


@bp.route('/create_user/failed/', methods=['POST'])
@base_use_kwargs({'email': fields.Str()})
def create_user_failed(email):
@transaction(db.session)
def create_user():
user1 = User(username='signalling_test', email=email, password='1')
db.session.add(user1)

create_user()
return jsonify({})
19 changes: 19 additions & 0 deletions tests/test_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,3 +301,22 @@ def test_autocommittrue_not_excepted(self, auto_session, assert_session):

# assert not rollback. Be very careful when using commit. 😒😒😒😒
assert len(assert_session.query(User).all()) == 1


class TestNestedSessionSignal(BaseTest):

def test_transaction_signal_success(self, client, assert_session):
email = "[email protected]"
resp = client.post('/create_user/success/', json={"email": email})
assert resp.status_code == 200

user = assert_session.query(User).filter(User.email == email).first()
assert user and user.username == "signalling_ok"

def test_transaction_signal_dailed(self, client, assert_session):
email = "[email protected]"
resp = client.post('/create_user/failed/', json={"email": email})
assert resp.status_code == 200

user = assert_session.query(User).filter(User.email == email).first()
assert user and user.username != "signalling_ok"

0 comments on commit ccfda8f

Please sign in to comment.