Python 如何在金字塔web应用程序中手动提交sqlalchemy数据库事务?

Python 如何在金字塔web应用程序中手动提交sqlalchemy数据库事务?,python,sqlalchemy,celery,pyramid,Python,Sqlalchemy,Celery,Pyramid,我有一个金字塔网络应用程序,在将更改提交到sqlalchemy数据库后需要运行芹菜任务。我知道我可以使用request.tm.get().addAfterCommitHook()来完成这项工作。但是,这对我不起作用,因为我还需要在视图中使用芹菜任务的task_id。因此,在调用芹菜任务的task.delay()之前,我需要提交对数据库的更改 sqlalchemy文档说我可以使用transaction.commit()手动提交。但是,这对我来说不起作用;芹菜任务在将更改提交到数据库之前运行,即使我

我有一个金字塔网络应用程序,在将更改提交到sqlalchemy数据库后需要运行芹菜任务。我知道我可以使用request.tm.get().addAfterCommitHook()来完成这项工作。但是,这对我不起作用,因为我还需要在视图中使用芹菜任务的task_id。因此,在调用芹菜任务的task.delay()之前,我需要提交对数据库的更改

sqlalchemy文档说我可以使用transaction.commit()手动提交。但是,这对我来说不起作用;芹菜任务在将更改提交到数据库之前运行,即使我在调用task.delay()之前调用了transaction.commit()

我的金字塔视图代码如下所示:

ride=appstruct_to_ride(dbsession,appstruct)
dbsession.add(ride)

# Flush dbsession so ride gets an id assignment
dbsession.flush()

# Store ride id
ride_id=ride.id
log.info('Created ride {}'.format(ride_id))

# Commit ride to database
import transaction
transaction.commit()

# Queue a task to update ride's weather data
from ..processing.weather import update_ride_weather
update_weather_task=update_ride_weather.delay(ride_id)

url = self.request.route_url('rides')
return HTTPFound(
    url,
    content_type='application/json',
    charset='',
    text=json.dumps(
        {'ride_id':ride_id,
         'update_weather_task_id':update_weather_task.task_id}))

@celery.task(bind=True,ignore_result=False)
def update_ride_weather(self,ride_id, train_model=True):

    from ..celery import session_factory
    
    logger.debug('Received update weather task for ride {}'.format(ride_id))

    dbsession=session_factory()
    dbsession.expire_on_commit=False

    with transaction.manager:
        ride=dbsession.query(Ride).filter(Ride.id==ride_id).one()

我的芹菜任务如下所示:

ride=appstruct_to_ride(dbsession,appstruct)
dbsession.add(ride)

# Flush dbsession so ride gets an id assignment
dbsession.flush()

# Store ride id
ride_id=ride.id
log.info('Created ride {}'.format(ride_id))

# Commit ride to database
import transaction
transaction.commit()

# Queue a task to update ride's weather data
from ..processing.weather import update_ride_weather
update_weather_task=update_ride_weather.delay(ride_id)

url = self.request.route_url('rides')
return HTTPFound(
    url,
    content_type='application/json',
    charset='',
    text=json.dumps(
        {'ride_id':ride_id,
         'update_weather_task_id':update_weather_task.task_id}))

@celery.task(bind=True,ignore_result=False)
def update_ride_weather(self,ride_id, train_model=True):

    from ..celery import session_factory
    
    logger.debug('Received update weather task for ride {}'.format(ride_id))

    dbsession=session_factory()
    dbsession.expire_on_commit=False

    with transaction.manager:
        ride=dbsession.query(Ride).filter(Ride.id==ride_id).one()

芹菜任务失败,NoResultFound:

  File "/app/cycling_data/processing/weather.py", line 478, in update_ride_weather
    ride=dbsession.query(Ride).filter(Ride.id==ride_id).one()
  File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3282, in one
    raise orm_exc.NoResultFound("No row was found for one()")

当我事后检查数据库时,我看到记录实际上是在芹菜任务运行并失败后创建的。这意味着transaction.commit()没有按照预期提交事务,而是在视图返回后由zope.sqlalchemy机制自动提交更改。如何在查看代码中手动提交事务?

request.tm
pyramid\u tm
定义,可以是threadlocal
transaction.manager
对象或每个请求对象,具体取决于您如何配置
pyramid\u tm
(查找正在某处定义的
pyramid\u tm.manager\u hook
,以确定正在使用哪一个

您的问题很棘手,因为无论您做什么,都应该符合
pyramid_tm
的要求,以及它预期的操作方式。具体来说,它计划在请求的生命周期内控制事务-尽早提交不是一个好主意。
pyramid_tm
试图帮助提供故障保护功能如果请求生命周期中的任何地方(而不仅仅是视图中)发生任何故障,则回滚整个请求是可调用的

备选案文1: 无论如何,请尽早提交。如果要这样做,那么提交后的失败将无法回滚提交的数据,因此您可以部分提交请求。好的,好的,这是您的问题,所以答案是使用
request.tm.Commit()
可能后跟
request.tm.begin()
为任何后续更改启动一个新对象。您还需要注意不要跨越该边界共享sqlalchemy管理的对象,如
请求.user
等,因为它们需要刷新/合并到新事务中(默认情况下,SQLAlchemy的标识缓存不能信任从不同事务加载的数据,因为隔离级别就是这样工作的)

备选案文2: 只为要提前提交的数据启动一个单独的事务。好吧,假设您没有使用任何线程局部变量,如
transaction.manager
,或
scoped_session
,那么您可能可以启动自己的事务并提交它,而无需触摸由
pyramid_tm控制的
dbsession
。与金字塔cookiecutter初学者项目结构一起使用的一些通用代码可能是:

from myapp.models import get_tm_session

tmp_tm = transaction.TransactionManager(explicit=True)
with tmp_tm:
    dbsession_factory = request.registry['dbsession_factory']
    tmp_dbsession = get_tm_session(dbsession_factory, tmp_tm)
    # ... do stuff with tmp_dbsession that is committed in this with-statement
    ride = appstruct_to_ride(tmp_dbsession, appstruct)
    # do not use this ride object outside of the with-statement
    tmp_dbsession.add(ride)
    tmp_dbsession.flush()
    ride_id = ride.id

# we are now committed so go ahead and start your background worker
update_weather_task = update_ride_weather.delay(ride_id)

# maybe you want the ride object outside of the tmp_dbsession
ride = dbsession.query(Ride).filter(Ride.id==ride_id).one()

return {...}

这还不错-就故障模式而言,如果不将芹菜连接到金字塔控制的数据会话中,这可能是你能做的最好的了。

谢谢@michael。我发现你的答案在帮助我更好地理解交易机制方面很有帮助。但是,我的芹菜任务仍然会提出一个Noresultfind,这两种方法都有。如果我插入t
dbsession.query(Ride).filter(Ride.id==Ride_id).one()
在事务提交后,我在视图中也会找到一个noresultfind。上述注释的例外是,在我的单元测试(使用sqlite数据库)中运行查询
dbsession.query(Ride).filter(Ride.id==Ride_id).one()
工作正常,但在模拟的生产环境(使用mariadb数据库)中,相同的查询失败。与以前一样,在视图返回后提交事务,但手动提交似乎与mariadb无关。顺便说一句,我查看了我的app/models/_init_uuuuuuuuuuuuuy,它具有
设置['tm.manager\u hook']='pyramid_tm.explicit_manager'
,这是一个非线程本地管理器。我粘贴的代码段,如果放在可调用的视图中,应该可以工作。如果在提交后调用芹菜任务(如在粘贴中),并且它返回一个noresultfind,我怀疑我粘贴到您的位置的代码段中有什么地方工作不正常“我们可能正在使用
dbsession
而不是
tmp_dbsession
。看起来我的问题可能是,在粘贴代码之前,我正在使用request.dbsession运行查询。似乎如果在创建tmp_dbsession之前,我在request.dbsession上运行任何查询(即使它只是一个SELECT),那么我就会得到NoResultFound错误。”。