From: Mike Bayer Date: Wed, 22 Jun 2011 15:45:28 +0000 (-0400) Subject: - Added mixin class sqlalchemy.ext.DontWrapMixin. X-Git-Tag: rel_0_7_2~59 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e821573a2470d42c3caa8988b93503353b1a5da2;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - Added mixin class sqlalchemy.ext.DontWrapMixin. User-defined exceptions of this type are never wrapped in StatementException when they occur in the context of a statement execution. - StatementException wrapping will display the original exception class in the message. --- diff --git a/CHANGES b/CHANGES index 49fa1e576f..e6ae6f1310 100644 --- a/CHANGES +++ b/CHANGES @@ -34,6 +34,15 @@ CHANGES no deprecation warning about cgi.parse_qsl() [ticket:1682] + - Added mixin class sqlalchemy.ext.DontWrapMixin. + User-defined exceptions of this type are never + wrapped in StatementException when they + occur in the context of a statement + execution. + + - StatementException wrapping will display the + original exception class in the message. + - mssql - Adjusted the pyodbc dialect such that bound values are passed as bytes and not unicode diff --git a/lib/sqlalchemy/exc.py b/lib/sqlalchemy/exc.py index 3e88ee3a6c..a7ea5b0e22 100644 --- a/lib/sqlalchemy/exc.py +++ b/lib/sqlalchemy/exc.py @@ -12,6 +12,7 @@ result of DBAPI exceptions are all subclasses of """ +import traceback class SQLAlchemyError(Exception): """Generic error class.""" @@ -98,6 +99,26 @@ class UnboundExecutionError(InvalidRequestError): """SQL was attempted without a database connection to execute it on.""" +class DontWrapMixin(object): + """A mixin class which, when applied to a user-defined Exception class, + will not be wrapped inside of :class:`.StatementError` if the error is + emitted within the process of executing a statement. + + E.g.:: + from sqlalchemy.exc import DontWrapMixin + + class MyCustomException(Exception, DontWrapMixin): + pass + + class MySpecialType(TypeDecorator): + impl = String + + def process_bind_param(self, value, dialect): + if value == 'invalid': + raise MyCustomException("invalid!") + + """ + # Moved to orm.exc; compatibility definition installed by orm import until 0.6 UnmappedColumnError = None @@ -160,14 +181,18 @@ class DBAPIError(StatementError): connection_invalidated=False): # Don't ever wrap these, just return them directly as if # DBAPIError didn't exist. - if isinstance(orig, (KeyboardInterrupt, SystemExit)): + if isinstance(orig, (KeyboardInterrupt, SystemExit, DontWrapMixin)): return orig if orig is not None: # not a DBAPI error, statement is present. # raise a StatementError if not isinstance(orig, dbapi_base_err) and statement: - return StatementError(str(orig), statement, params, orig) + return StatementError( + "%s (original cause: %s)" % ( + str(orig), + traceback.format_exception_only(orig.__class__, orig)[-1].strip() + ), statement, params, orig) name, glob = orig.__class__.__name__, globals() if name in glob and issubclass(glob[name], DBAPIError): diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py index ba612bc2c7..e9213845ba 100644 --- a/lib/sqlalchemy/util/langhelpers.py +++ b/lib/sqlalchemy/util/langhelpers.py @@ -53,7 +53,6 @@ def decorator(target): return update_wrapper(decorate, target) - def get_cls_kwargs(cls): """Return the full set of inherited kwargs for the given `cls`. diff --git a/test/engine/test_execute.py b/test/engine/test_execute.py index 33b34ca1b3..967fc92426 100644 --- a/test/engine/test_execute.py +++ b/test/engine/test_execute.py @@ -174,7 +174,34 @@ class ExecuteTest(fixtures.TestBase): def _go(conn): assert_raises_message( tsa.exc.StatementError, - "nope 'SELECT 1 ", + r"nope \(original cause: Exception: nope\) 'SELECT 1 ", + conn.execute, + select([1]).\ + where( + column('foo') == literal('bar', MyType()) + ) + ) + _go(testing.db) + conn = testing.db.connect() + try: + _go(conn) + finally: + conn.close() + + @testing.requires.python25 + def test_dont_wrap_mixin(self): + class MyException(Exception, tsa.exc.DontWrapMixin): + pass + + class MyType(TypeDecorator): + impl = Integer + def process_bind_param(self, value, dialect): + raise MyException("nope") + + def _go(conn): + assert_raises_message( + MyException, + "nope", conn.execute, select([1]).\ where(