我在SQLAlchemy中使用声明性扩展,当我尝试使用不正确的数据保存映射类的实例时(特别是声明为nullable = False且值为None的列),我注意到一个奇怪的错误。 / p>
班级(简化):
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.OperationalError。如果我将这些行更改为:
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
更新:错误似乎以某种方式特定于我的应用程序。我用一个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 :(得分:5)
以下是我发现的内容:
OperationalError
)一直正常,直到失败的事务回滚(在Session._flush()
中)。 eventlet.tpool
处理。具体而言,调用eventlet.tpool.execute
,其中涉及创建eventlet.Event
并调用其wait
方法。 OperationalError
中的sys.exc_type
,并最终在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
有点不清楚在这种情况下应该做什么样的事件,所以我不知道该bug是应该报告给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上提高它。