簡體   English   中英

如何在金字塔 web 應用程序中手動提交 sqlalchemy 數據庫事務?

[英]How do I manually commit a sqlalchemy database transaction inside a pyramid web app?

我有一個 Pyramid web 應用程序,在提交對 sqlalchemy 數據庫的更改后,它需要運行 Celery 任務。 我知道我可以使用 request.tm.get().addAfterCommitHook() 來做到這一點。 但是,這對我不起作用,因為我還需要在視圖中使用 celery 任務的 task_id。 因此,我需要在我的 Celery 任務上調用 task.delay() 之前提交對數據庫的更改。

zope.sqlalchemy 文檔說我可以使用 transaction.commit() 手動提交。 但是,這對我不起作用; celery 任務在更改提交到數據庫之前運行,即使我在調用 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 任務如下所示:

@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()

celery 任務失敗並出現 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()")

當我事后檢查數據庫時,我看到記錄實際上是在 celery 任務運行並失敗之后創建的。 所以這意味着 transaction.commit() 沒有按預期提交事務,而是在視圖返回后由 zope.sqlalchemy 機器自動提交更改。 如何在我的視圖代碼中手動提交事務?

request.tmpyramid_tm定義,可以是 threadlocal transaction.manager object 或每個請求的 object,具體取決於您如何配置pyramid_tm (查找在某處定義的pyramid_tm.manager_hook以確定正在使用哪個。

你的問題很棘手,因為無論你做什么都應該適合pyramid_tm以及它期望事情如何運作。 具體來說,它計划在請求的生命周期內控制事務——提早提交對於該事務不是一個好主意。 pyramid_tm試圖幫助提供故障保護功能,以在請求生命周期中的任何位置發生任何故障時回滾整個請求 - 而不僅僅是在您的視圖可調用中。

選項1:

無論如何,盡早提交。 如果您要執行此操作,則提交后的失敗無法回滾已提交的數據,因此您可能會部分提交請求。 好的,很好,這是你的問題,所以答案是使用request.tm.commit()可能后跟request.tm.begin()為任何后續更改啟動一個新的。 您還需要注意不要跨該邊界共享 sqlalchemy 托管對象,例如request.user等,因為它們需要刷新/合並到新事務中(默認情況下,SQLAlchemy 的身份緩存不能信任從不同事務加載的數據因為這就是隔離級別的工作方式)。

選項 2:

僅針對您要提前提交的數據啟動單獨的事務。 好的,所以假設您沒有使用任何 threadlocals,例如transaction.managerscoped_session ,那么您可能可以啟動自己的事務並提交它,而無需觸及由dbsession控制的pyramid_tm 一些適用於 pyramid-cookiecutter-starter 項目結構的通用代碼可能是:

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 {...}

這還不錯 - 就故障模式 go 而言,這可能是您可以做的最好的事情,而無需將 celery 連接到 pyramid_tm 控制的 dbsession 中。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM