SQLAlchemy引发None,导致TypeError

时间:2012-03-01 22:30:31

标签: python sqlalchemy

我在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

1 个答案:

答案 0 :(得分:5)

以下是我发现的内容:

  • 异常(OperationalError)一直正常,直到失败的事务回滚(在Session._flush()中)。
  • 事务回滚由mysqldb通过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上提高它。