2012-03-01 70 views
7

我在SQLAlchemy中使用了聲明性擴展,並且當我試圖用不正確的數據保存映射類的實例時發現了一個奇怪的錯誤(特別是用nullable = False聲明的列無的價值)。SQLAlchemy引發無,導致TypeError

類(簡體):

class User(Base): 
    __tablename__ = 'users' 

    id = Column(Integer, primary_key=True, autoincrement=True) 
    userid = Column(String(50), unique=True, nullable=False) 

導致錯誤(會話是SQLAlchemy的會議):

>>> u = User() 
>>> session.add(u) 
>>> session.commit() 

... 

TypeError: exceptions must be old-style classes or derived from BaseException, not NoneType 

在導致該異常的代碼看,我發現(在SQLAlchemy的。 orm.session):

except: 
    transaction.rollback(_capture_exception=True) 
    raise 

在這種情況下被捕獲的異常是sqlalchemy.exc.Oper ationalError。如果我將這些行更改爲:

except Exception as e: 
    transaction.rollback(_capture_exception=True) 
    raise e 

然後問題消失,OperationalError被拋出而不是None。儘管原始代碼不應該在最新版本的Python中工作嗎? (我使用的是2.7.2)這個錯誤是否特定於我的應用程序?

的Python 2.7.2

SQLAlchemy的0.7.5

UPDATE:錯誤似乎是特定於我在某些方面的應用。我用一個SQLAlchemy引擎封裝了一個eventlet.db_pool,這似乎是問題的根源。使用內存中的SQLite或基本的MySQL引擎來運行我的簡單測試不存在這個問題,但是使用它的db_pool。

測試用例:https://gist.github.com/1980584

完整回溯是:

Traceback (most recent call last): 
    File "test_case_9525220.py", line 41, in <module> 
    session.commit() 
    File "/usr/local/Cellar/python/2.7.2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 645, in commit 
    self.transaction.commit() 
    File "/usr/local/Cellar/python/2.7.2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 313, in commit 
    self._prepare_impl() 
    File "/usr/local/Cellar/python/2.7.2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 297, in _prepare_impl 
    self.session.flush() 
    File "/usr/local/Cellar/python/2.7.2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 1547, in flush 
    self._flush(objects) 
    File "/usr/local/Cellar/python/2.7.2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 1635, in _flush 
    raise 
TypeError: exceptions must be old-style classes or derived from BaseException, not NoneType 
+0

你正在使用什麼版本的sqlalchemy? – Crast 2012-03-01 23:09:03

+0

SQLAlchemy 0.7.5 – robbles 2012-03-01 23:41:35

+2

DBAPI是什麼(包括版本)和錯誤的確切性質是什麼? OperationalError從DBAPI傳播。在這裏完整的再現測試將是最好的,並附上它作爲一張票到http://www.sqlalchemy.org/trac/newticket – zzzeek 2012-03-02 00:05:08

回答

5

以下是我發現:

  • 異常(一個OperationalError)是確定的,直到失敗的事務回滾(在Session._flush())。
  • 事務回滾由mysqldb通過eventlet.tpool處理。具體而言,調用eventlet.tpool.execute,其中涉及創建eventlet.Event並調用wait方法。
  • 在等待時,會發生一些複雜的與線程有關的事情,其中​​之一是檢查異常並將其傳遞給Event來處理。它拿起仍在sys.exc_type中的OperationalError,並最終在eventlet.event.hubs.hub.BaseHub.switch中清除它。
  • 控制返回到Session._flush,並重新提出例外(使用raise),但此時沒有例外,因此它會嘗試raise None

這種行爲可以用一個簡單的例子來重現:

from eventlet import tpool 

def m(): 
    pass 

try: 
    raise TypeError 
except: 
    tpool.execute(m) 
    raise 

它有點不清楚到底是什麼eventlet應該在這種情況下做的,所以我不知道是否錯誤,應當報sqlalchemy或eventlet,或兩者兼而有之。

整頓它,因爲你已經注意到,到sqlalchemy.orm.session.Session._flush最後幾行從

except Exception: 
     transaction.rollback(_capture_exception=True) 
     raise 

except Exception, e: 
     transaction.rollback(_capture_exception=True) 
     raise e 

編輯修改的最簡單方法:我提出關於eventlet的問題跟蹤器的issue。儘管如此,在sqlalchemy上也可能值得一提。

+1

不錯的工作 - 現在它是有道理的。我的修復工作是因爲它記錄了異常,在事件小組清除了回滾之前,然後明確提出了它。當我有機會時,我會嘗試在sqlalchemy上提出一個問題。 – robbles 2012-03-27 01:44:05

相關問題