【问题标题】:Cherrypy sqlalchemy exception try catch blockCherrypy sqlalchemy 异常尝试捕获块
【发布时间】:2014-04-09 16:59:23
【问题描述】:

我到处搜索有关 CherryPy 如何处理异常的信息,但我似乎无法弄清楚为什么我的 try/catch 块没有被正确调用。我认为问题是异常在另一个线程上被抛出/处理,或者至少在我的 catch 块之前被拦截/处理。

在下面的代码中,最底部的 catch 块永远不会被调用。相反,它会在我能够处理异常之前向浏览器返回包含完整异常消息的响应。

如何在cherrypy处理响应之前捕获此异常?

import cherrypy
from cherrypy.process import wspbus, plugins
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, String
import os


# BELOW CLASSES COPIED FROM 
# https://bitbucket.org/Lawouach/cherrypy-recipes/src/d140e6da973aa271e6b68a8bc187e53615674c5e/web/database/?at=default
class SATool(cherrypy.Tool):
    def __init__(self):
        """
        The SA tool is responsible for associating a SA session
        to the SA engine and attaching it to the current request.
        Since we are running in a multithreaded application,
        we use the scoped_session that will create a session
        on a per thread basis so that you don't worry about
        concurrency on the session object itself.

        This tools binds a session to the engine each time
        a requests starts and commits/rollbacks whenever
        the request terminates.
        """
        cherrypy.Tool.__init__(self, 'on_start_resource',
                               self.bind_session,
                               priority=20)

    def _setup(self):
        cherrypy.Tool._setup(self)
        cherrypy.request.hooks.attach('on_end_resource',
                                      self.commit_transaction,
                                      priority=80)

    def bind_session(self):
        """
        Attaches a session to the request's scope by requesting
        the SA plugin to bind a session to the SA engine.
        """
        session = cherrypy.engine.publish('bind-session').pop()
        cherrypy.request.db = session

    def commit_transaction(self):
        """
        Commits the current transaction or rolls back
        if an error occurs. Removes the session handle
        from the request's scope.
        """
        if not hasattr(cherrypy.request, 'db'):
            return
        cherrypy.request.db = None
        cherrypy.engine.publish('commit-session')


class SAEnginePlugin(plugins.SimplePlugin):
    def __init__(self, bus):
        """
        The plugin is registered to the CherryPy engine and therefore
        is part of the bus (the engine *is* a bus) registery.

        We use this plugin to create the SA engine. At the same time,
        when the plugin starts we create the tables into the database
        using the mapped class of the global metadata.
        """
        plugins.SimplePlugin.__init__(self, bus)
        self.sa_engine = None
        self.session = scoped_session(sessionmaker(autoflush=True,
                                                   autocommit=False))

    def start(self):
        self.bus.log('Starting up DB access')

        self.sa_engine = create_engine('oracle+cx_oracle://%s:%s@%s' % 
            (os.getenv('DB_USERNAME'), os.getenv('DB_PASSWORD'), os.getenv('DB_SERVER')), 
            echo=True)

        self.bus.subscribe("bind-session", self.bind)
        self.bus.subscribe("commit-session", self.commit)

    def stop(self):
        self.bus.log('Stopping down DB access')
        self.bus.unsubscribe("bind-session", self.bind)
        self.bus.unsubscribe("commit-session", self.commit)
        if self.sa_engine:
            self.sa_engine.dispose()
            self.sa_engine = None

    def bind(self):
        """
        Whenever this plugin receives the 'bind-session' command, it applies
        this method and to bind the current session to the engine.

        It then returns the session to the caller.
        """
        self.session.configure(bind=self.sa_engine)
        return self.session

    def commit(self):
        """
        Commits the current transaction or rollbacks if an error occurs.

        In all cases, the current session is unbound and therefore
        not usable any longer.
        """
        try:
            self.session.commit()
        except:
            self.session.rollback()
            raise
        finally:
            self.session.remove()

# SQL Alchemy model
Base = declarative_base()
class Registration(Base):
    __tablename__ = 'beta_registration'
    email = Column('email', String, primary_key=True)


class Root():
    @cherrypy.expose
    def index(self):
        registration = Registration()
        registration.email = "test@test.com"

        db = cherrypy.request.db
        try:
            db.add(registration)
        except Exception as e:
            # **** never gets here *****
            # should be IntegrityError on second call from sqlalchemy.exc
            raise cherrypy.HTTPError("409 Conflict", "The email address has already been registered")

SAEnginePlugin(cherrypy.engine).subscribe()
cherrypy.tools.db = SATool()

cherrypy.config.update({
'tools.sessions.on' : True,
'tools.sessions.storage_type' : 'File',
'tools.sessions.storage_path' : 'adf'
})


cherrypy.quickstart(Root(), '/')

日志:

[09/Apr/2014:16:43:54] ENGINE Error in 'commit-session' listener <bound method SAEnginePlugin.commit of <plugins.saplugin.SAEnginePlugin object at 0x105a0cd10>>
Traceback (most recent call last):
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/cherrypy/process/wspbus.py", line 197, in publish
    output.append(listener(*args, **kwargs))
  File "/Users/xatter/Sites/connectfor/plugins/saplugin.py", line 57, in commit
    self.session.commit()
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/sqlalchemy/orm/scoping.py", line 149, in do
    return getattr(self.registry(), name)(*args, **kwargs)
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 765, in commit
    self.transaction.commit()
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 370, in commit
    self._prepare_impl()
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 350, in _prepare_impl
    self.session.flush()
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 1879, in flush
    self._flush(objects)
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 1997, in _flush
    transaction.rollback(_capture_exception=True)
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/sqlalchemy/util/langhelpers.py", line 57, in __exit__
    compat.reraise(exc_type, exc_value, exc_tb)
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 1961, in _flush
    flush_context.execute()
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/sqlalchemy/orm/unitofwork.py", line 370, in execute
    rec.execute(self)
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/sqlalchemy/orm/unitofwork.py", line 523, in execute
    uow
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/sqlalchemy/orm/persistence.py", line 64, in save_obj
    mapper, table, insert)
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/sqlalchemy/orm/persistence.py", line 562, in _emit_insert_statements
    execute(statement, multiparams)
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 717, in execute
    return meth(self, multiparams, params)
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/sqlalchemy/sql/elements.py", line 317, in _execute_on_connection
    return connection._execute_clauseelement(self, multiparams, params)
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 814, in _execute_clauseelement
    compiled_sql, distilled_params
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 927, in _execute_context
    context)
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1076, in _handle_dbapi_exception
    exc_info
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/sqlalchemy/util/compat.py", line 185, in raise_from_cause
    reraise(type(exception), exception, tb=exc_tb)
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 920, in _execute_context
    context)
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/sqlalchemy/engine/default.py", line 425, in do_execute
    cursor.execute(statement, parameters)
IntegrityError: (IntegrityError) duplicate key value violates unique constraint "beta_registration_pk"
DETAIL:  Key (email)=(test@test.com) already exists.
 'INSERT INTO beta_registration (email) VALUES (%(email)s)' {'email': u'test@test.com'}

[09/Apr/2014:16:43:54]  Traceback (most recent call last):
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/cherrypy/_cprequest.py", line 102, in run
    hook()
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/cherrypy/_cprequest.py", line 62, in __call__
    return self.callback(**self.kwargs)
  File "/Users/xatter/Sites/connectfor/plugins/satool.py", line 47, in commit_transaction
    cherrypy.engine.publish('commit-session')
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/cherrypy/process/wspbus.py", line 215, in publish
    raise exc
ChannelFailures: IntegrityError('(IntegrityError) duplicate key value violates unique constraint "beta_registration_pk"\nDETAIL:  Key (email)=(test@test.com) already exists.\n',)

[09/Apr/2014:16:43:54] HTTP 
Request Headers:
  Content-Length: 25
  COOKIE: remember_token=_tm4c-HNpJWTHB1PXj8Wbg; session_id=25757ddac3a84730ce3b87f32b80a4288f5421b4
  HOST: localhost:5000
  ORIGIN: http://localhost:5000
  CONNECTION: keep-alive
  Remote-Addr: 127.0.0.1
  ACCEPT: application/json, text/plain, */*
  USER-AGENT: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/537.75.14
  REFERER: http://localhost:5000/
  ACCEPT-LANGUAGE: en-us
  Content-Type: application/json;charset=UTF-8
  ACCEPT-ENCODING: gzip, deflate
[09/Apr/2014:16:43:54] HTTP Traceback (most recent call last):
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/cherrypy/_cprequest.py", line 670, in respond
    self.hooks.run('on_end_resource')
  File "/Users/xatter/Sites/connectfor/venv/lib/python2.7/site-packages/cherrypy/_cprequest.py", line 112, in run
    raise exc
ChannelFailures: IntegrityError('(IntegrityError) duplicate key value violates unique constraint "beta_registration_pk"\nDETAIL:  Key (email)=(test@test.com) already exists.\n',)

127.0.0.1 - - [09/Apr/2014:16:43:54] "POST /beta_registration HTTP/1.1" 500 1303 "http://localhost:5000/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/537.75.14"

【问题讨论】:

  • 你用的是哪个版本的python?
  • 对不起,CherryPy 3.2.4

标签: sqlalchemy cherrypy


【解决方案1】:

问题是 SQLAlchemy 在您尝试提交更改之前不会触及数据库,并且您正在使用的 SATool 在请求结束时提交。这意味着它在您的函数已经返回并且 try/except 已经执行时提交。

要捕获这些错误,您必须自己进行提交,这意味着删除 SATool 的此方法:

def _setup(self):
    cherrypy.Tool._setup(self)
    cherrypy.request.hooks.attach('on_end_resource',
                                  self.commit_transaction,
                                  priority=80)

然后发布到您要提交的引擎:

cherrypy.engine.publish('commit-session')

所以你的 Root 对象将是这样的:

class Root(object):

    @cherrypy.expose
    def index(self):
        registration = Registration()
        registration.email = "test@test.com"

        db = cherrypy.request.db
        db.add(registration)
        try:
            cherrypy.engine.publish('commit-session')               
        except Exception as e:
            raise cherrypy.HTTPError("409 Conflict", "The email address has already been   registered")

这意味着你需要这样做:

cherrypy.engine.publish('commit-session')

在所有修改数据库的请求上明确。

您还可以拆分工具以启用/禁用自动提交或仅对工具进行参数化。这可能需要更多细节。如果您想了解更多信息,请发表评论。

【讨论】:

    【解决方案2】:

    试试这个...除了 Exception as e

    import cherrypy
    
    cherrypy.config.update({
    'tools.sessions.on' : True,
    'tools.sessions.storage_type' : 'File',
    'tools.sessions.storage_path' : 'adf'
    })
    
    class Root(object):
        @cherrypy.expose
        def index(self):
            try:
                asdf = cherrypy.session['asdf']
            except Exception as e:
                raise cherrypy.HTTPError("409 Conflict", "The email address has already been registered")
                #return str(e)
            return 1
    
        @cherrypy.expose
        @cherrypy.tools.json_out()
        def main(self):
            return 'hi'
    
    cherrypy.quickstart(Root())
    

    使用此代码,我看到两个错误。 Cherrypy 的自动错误处理和我的客户错误。这是使用 python 3.3.3 和 cherrypy 3.2.4

    希望这会有所帮助!

    【讨论】:

    • 我确实将该行切换到 Exception as E,并添加了会话配置内容,但仍然不喜欢:(我包含了完整的日志输出,以提供一些线索。感谢您的帮助!
    • 啊 - 我看到你使用的是 python 2.7。我明天去看看那个版本。您似乎遇到了数据库错误 - “重复的键值违反了唯一约束 - 键(电子邮件)=(test@test.com)已经存在”。你能调试并进入 db.add( 函数吗?
    • 是的,这是预期的数据库错误,因为我通过用户输入插入了重复的主键。它引发了我想要捕获的异常,因此我可以发送除 500 之外的不同 HTTP 返回代码和消息。特别是对于此 IntegrityError,我想返回错误代码 409,但 CherryPy 拦截错误并在我有机会之前返回 500处理它。
    • 我在 python 2.7.5 上使用我的代码时也看到了“409 冲突”错误。另一个尝试捕获是捕获您的错误,但就像我说的那样,您需要调试才能快速找到它。
    猜你喜欢
    • 2020-07-31
    • 1970-01-01
    • 1970-01-01
    • 2021-12-13
    • 2011-12-05
    • 2014-05-11
    • 2013-03-14
    • 1970-01-01
    • 2020-10-11
    相关资源
    最近更新 更多