diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-07-18 20:23:01 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-07-18 20:23:01 -0400 |
commit | 3e55ed778b38d1ee18e1317b431c53ac1f6b141f (patch) | |
tree | 5c2c113162d7823b1e0c61e83372245510244f82 | |
parent | e8ff3047c6596d39bb38956eb5aba5651c104e63 (diff) | |
download | sqlalchemy-3e55ed778b38d1ee18e1317b431c53ac1f6b141f.tar.gz |
- [feature] Connection event listeners can
now be associated with individual
Connection objects, not just Engine
objects. [ticket:2511]
-rw-r--r-- | CHANGES | 5 | ||||
-rw-r--r-- | doc/build/core/events.rst | 4 | ||||
-rw-r--r-- | lib/sqlalchemy/engine/base.py | 561 | ||||
-rw-r--r-- | lib/sqlalchemy/engine/default.py | 53 | ||||
-rw-r--r-- | lib/sqlalchemy/events.py | 148 | ||||
-rw-r--r-- | test/engine/test_execute.py | 183 | ||||
-rw-r--r-- | test/lib/engines.py | 20 | ||||
-rw-r--r-- | test/orm/test_unitofwork.py | 19 |
8 files changed, 562 insertions, 431 deletions
@@ -188,6 +188,11 @@ underneath "0.7.xx". (use sqlalchemy.ext.horizontal_shard) - engine + - [feature] Connection event listeners can + now be associated with individual + Connection objects, not just Engine + objects. [ticket:2511] + - [bug] Fixed bug whereby if a database restart affected multiple connections, each connection would individually invoke a new diff --git a/doc/build/core/events.rst b/doc/build/core/events.rst index fab7356c4..f43aa09f6 100644 --- a/doc/build/core/events.rst +++ b/doc/build/core/events.rst @@ -18,8 +18,8 @@ Connection Pool Events .. autoclass:: sqlalchemy.events.PoolEvents :members: -Connection Events ------------------------ +SQL Execution and Connection Events +------------------------------------ .. autoclass:: sqlalchemy.events.ConnectionEvents :members: diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py index 297275223..d0c4ae348 100644 --- a/lib/sqlalchemy/engine/base.py +++ b/lib/sqlalchemy/engine/base.py @@ -21,7 +21,7 @@ __all__ = [ 'connection_memoize'] import inspect, StringIO, sys, operator -from itertools import izip +from itertools import izip, chain from sqlalchemy import exc, schema, util, types, log, interfaces, \ event, events from sqlalchemy.sql import expression, util as sql_util @@ -118,10 +118,10 @@ class Dialect(object): Postgresql. implicit_returning - use RETURNING or equivalent during INSERT execution in order to load + use RETURNING or equivalent during INSERT execution in order to load newly generated primary keys and other column defaults in one execution, which are then available via inserted_primary_key. - If an insert statement has returning() specified explicitly, + If an insert statement has returning() specified explicitly, the "implicit" functionality is not used and inserted_primary_key will not be available. @@ -200,7 +200,7 @@ class Dialect(object): Allows dialects to configure options based on server version info or other properties. - The connection passed here is a SQLAlchemy Connection object, + The connection passed here is a SQLAlchemy Connection object, with full capabilities. The initalize() method of the base dialect should be called via @@ -219,8 +219,8 @@ class Dialect(object): set) is specified, limit the autoload to the given column names. - The default implementation uses the - :class:`~sqlalchemy.engine.reflection.Inspector` interface to + The default implementation uses the + :class:`~sqlalchemy.engine.reflection.Inspector` interface to provide the output, building upon the granular table/column/ constraint etc. methods of :class:`.Dialect`. @@ -261,12 +261,12 @@ class Dialect(object): def get_primary_keys(self, connection, table_name, schema=None, **kw): """Return information about primary keys in `table_name`. - - - Deprecated. This method is only called by the default + + + Deprecated. This method is only called by the default implementation of :meth:`get_pk_constraint()`. Dialects should instead implement this method directly. - + """ raise NotImplementedError() @@ -276,7 +276,7 @@ class Dialect(object): table_name`. Given a :class:`.Connection`, a string - `table_name`, and an optional string `schema`, return primary + `table_name`, and an optional string `schema`, return primary key information as a dictionary with these keys: constrained_columns @@ -358,7 +358,7 @@ class Dialect(object): raise NotImplementedError() def normalize_name(self, name): - """convert the given name to lowercase if it is detected as + """convert the given name to lowercase if it is detected as case insensitive. this method is only used if the dialect defines @@ -410,7 +410,7 @@ class Dialect(object): raise NotImplementedError() def _get_default_schema_name(self, connection): - """Return the string name of the currently selected schema from + """Return the string name of the currently selected schema from the given connection. This is used by the default implementation to populate the @@ -422,13 +422,13 @@ class Dialect(object): raise NotImplementedError() def do_begin(self, connection): - """Provide an implementation of *connection.begin()*, given a + """Provide an implementation of *connection.begin()*, given a DB-API connection.""" raise NotImplementedError() def do_rollback(self, connection): - """Provide an implementation of *connection.rollback()*, given + """Provide an implementation of *connection.rollback()*, given a DB-API connection.""" raise NotImplementedError() @@ -444,7 +444,7 @@ class Dialect(object): raise NotImplementedError() def do_commit(self, connection): - """Provide an implementation of *connection.commit()*, given a + """Provide an implementation of *connection.commit()*, given a DB-API connection.""" raise NotImplementedError() @@ -523,7 +523,7 @@ class Dialect(object): def connect(self): """return a callable which sets up a newly created DBAPI connection. - The callable accepts a single argument "conn" which is the + The callable accepts a single argument "conn" which is the DBAPI connection itself. It has no return value. This is used to set dialect-wide per-connection options such as @@ -602,7 +602,7 @@ class ExecutionContext(object): True if the statement is a "committable" statement. prefetch_cols - a list of Column objects for which a client-side default + a list of Column objects for which a client-side default was fired off. Applies to inserts and updates. postfetch_cols @@ -651,13 +651,13 @@ class ExecutionContext(object): raise NotImplementedError() def handle_dbapi_exception(self, e): - """Receive a DBAPI exception which occurred upon execute, result + """Receive a DBAPI exception which occurred upon execute, result fetch, etc.""" raise NotImplementedError() def should_autocommit_text(self, statement): - """Parse the given textual statement and return True if it refers to + """Parse the given textual statement and return True if it refers to a "committable" statement""" raise NotImplementedError() @@ -672,7 +672,7 @@ class ExecutionContext(object): def get_rowcount(self): """Return the DBAPI ``cursor.rowcount`` value, or in some cases an interpreted value. - + See :attr:`.ResultProxy.rowcount` for details on this. """ @@ -700,7 +700,7 @@ class Compiled(object): :param statement: ``ClauseElement`` to be compiled. - :param bind: Optional Engine or Connection to compile this + :param bind: Optional Engine or Connection to compile this statement against. """ @@ -761,7 +761,7 @@ class Compiled(object): return e._execute_compiled(self, multiparams, params) def scalar(self, *multiparams, **params): - """Execute this compiled object and return the result's + """Execute this compiled object and return the result's scalar value.""" return self.execute(*multiparams, **params).scalar() @@ -788,11 +788,14 @@ class Connectable(object): """ + dispatch = event.dispatcher(events.ConnectionEvents) + + def connect(self, **kwargs): """Return a :class:`.Connection` object. Depending on context, this may be ``self`` if this object - is already an instance of :class:`.Connection`, or a newly + is already an instance of :class:`.Connection`, or a newly procured :class:`.Connection` if this object is an instance of :class:`.Engine`. @@ -803,7 +806,7 @@ class Connectable(object): context. Depending on context, this may be ``self`` if this object - is already an instance of :class:`.Connection`, or a newly + is already an instance of :class:`.Connection`, or a newly procured :class:`.Connection` if this object is an instance of :class:`.Engine`. @@ -838,7 +841,7 @@ class Connectable(object): """ raise NotImplementedError() - def _run_visitor(self, visitorcallable, element, + def _run_visitor(self, visitorcallable, element, **kwargs): raise NotImplementedError() @@ -872,7 +875,9 @@ class Connection(Connectable): """ def __init__(self, engine, connection=None, close_with_result=False, - _branch=False, _execution_options=None): + _branch=False, _execution_options=None, + _dispatch=None, + _has_events=False): """Construct a new Connection. The constructor here is not public and is only called only by an @@ -888,7 +893,9 @@ class Connection(Connectable): self.__savepoint_seq = 0 self.__branch = _branch self.__invalid = False - self._has_events = engine._has_events + if _dispatch: + self.dispatch = _dispatch + self._has_events = _has_events or engine._has_events self._echo = self.engine._should_log_info() if _execution_options: self._execution_options =\ @@ -906,8 +913,11 @@ class Connection(Connectable): """ return self.engine._connection_cls( - self.engine, - self.__connection, _branch=True) + self.engine, + self.__connection, + _branch=True, + _has_events=self._has_events, + _dispatch=self.dispatch) def _clone(self): """Create a shallow copy of this Connection. @@ -924,7 +934,7 @@ class Connection(Connectable): self.close() def execution_options(self, **opt): - """ Set non-SQL options for the connection which take effect + """ Set non-SQL options for the connection which take effect during execution. The method returns a copy of this :class:`.Connection` which references @@ -939,11 +949,11 @@ class Connection(Connectable): :meth:`.Connection.execution_options` accepts all options as those accepted by :meth:`.Executable.execution_options`. Additionally, - it includes options that are applicable only to + it includes options that are applicable only to :class:`.Connection`. :param autocommit: Available on: Connection, statement. - When True, a COMMIT will be invoked after execution + When True, a COMMIT will be invoked after execution when executed in 'autocommit' mode, i.e. when an explicit transaction is not begun on the connection. Note that DBAPI connections by default are always in a transaction - SQLAlchemy uses @@ -958,17 +968,17 @@ class Connection(Connectable): :param compiled_cache: Available on: Connection. A dictionary where :class:`.Compiled` objects - will be cached when the :class:`.Connection` compiles a clause + will be cached when the :class:`.Connection` compiles a clause expression into a :class:`.Compiled` object. It is the user's responsibility to manage the size of this dictionary, which will have keys corresponding to the dialect, clause element, the column - names within the VALUES or SET clause of an INSERT or UPDATE, + names within the VALUES or SET clause of an INSERT or UPDATE, as well as the "batch" mode for an INSERT or UPDATE statement. The format of this dictionary is not guaranteed to stay the same in future releases. - Note that the ORM makes use of its own "compiled" caches for + Note that the ORM makes use of its own "compiled" caches for some operations, including flush operations. The caching used by the ORM internally supersedes a cache dictionary specified here. @@ -978,33 +988,33 @@ class Connection(Connectable): the lifespan of this connection. Valid values include those string values accepted by the ``isolation_level`` parameter passed to :func:`.create_engine`, and are - database specific, including those for :ref:`sqlite_toplevel`, + database specific, including those for :ref:`sqlite_toplevel`, :ref:`postgresql_toplevel` - see those dialect's documentation for further info. - Note that this option necessarily affects the underlying - DBAPI connection for the lifespan of the originating - :class:`.Connection`, and is not per-execution. This - setting is not removed until the underlying DBAPI connection + Note that this option necessarily affects the underlying + DBAPI connection for the lifespan of the originating + :class:`.Connection`, and is not per-execution. This + setting is not removed until the underlying DBAPI connection is returned to the connection pool, i.e. the :meth:`.Connection.close` method is called. - :param no_parameters: When ``True``, if the final parameter - list or dictionary is totally empty, will invoke the + :param no_parameters: When ``True``, if the final parameter + list or dictionary is totally empty, will invoke the statement on the cursor as ``cursor.execute(statement)``, not passing the parameter collection at all. Some DBAPIs such as psycopg2 and mysql-python consider - percent signs as significant only when parameters are + percent signs as significant only when parameters are present; this option allows code to generate SQL containing percent signs (and possibly other characters) that is neutral regarding whether it's executed by the DBAPI - or piped into a script that's later invoked by + or piped into a script that's later invoked by command line tools. .. versionadded:: 0.7.6 :param stream_results: Available on: Connection, statement. - Indicate to the dialect that results should be + Indicate to the dialect that results should be "streamed" and not pre-buffered, if possible. This is a limitation of many DBAPIs. The flag is currently understood only by the psycopg2 dialect. @@ -1017,7 +1027,7 @@ class Connection(Connectable): return c def _set_isolation_level(self): - self.dialect.set_isolation_level(self.connection, + self.dialect.set_isolation_level(self.connection, self._execution_options['isolation_level']) self.connection._connection_record.finalize_callback = \ self.dialect.reset_isolation_level @@ -1097,7 +1107,7 @@ class Connection(Connectable): return self def invalidate(self, exception=None): - """Invalidate the underlying DBAPI connection associated with + """Invalidate the underlying DBAPI connection associated with this Connection. The underlying DB-API connection is literally closed (if @@ -1154,26 +1164,26 @@ class Connection(Connectable): Nested calls to :meth:`.begin` on the same :class:`.Connection` will return new :class:`.Transaction` objects that represent - an emulated transaction within the scope of the enclosing + an emulated transaction within the scope of the enclosing transaction, that is:: - + trans = conn.begin() # outermost transaction - trans2 = conn.begin() # "nested" + trans2 = conn.begin() # "nested" trans2.commit() # does nothing trans.commit() # actually commits - - Calls to :meth:`.Transaction.commit` only have an effect + + Calls to :meth:`.Transaction.commit` only have an effect when invoked via the outermost :class:`.Transaction` object, though the :meth:`.Transaction.rollback` method of any of the :class:`.Transaction` objects will roll back the transaction. See also: - + :meth:`.Connection.begin_nested` - use a SAVEPOINT - + :meth:`.Connection.begin_twophase` - use a two phase /XID transaction - + :meth:`.Engine.begin` - context manager available from :class:`.Engine`. """ @@ -1195,7 +1205,7 @@ class Connection(Connectable): still controls the overall ``commit`` or ``rollback`` of the transaction of a whole. - See also :meth:`.Connection.begin`, + See also :meth:`.Connection.begin`, :meth:`.Connection.begin_twophase`. """ @@ -1214,10 +1224,10 @@ class Connection(Connectable): :class:`.Transaction`, also provides a :meth:`~.TwoPhaseTransaction.prepare` method. - :param xid: the two phase transaction id. If not supplied, a + :param xid: the two phase transaction id. If not supplied, a random id will be generated. - See also :meth:`.Connection.begin`, + See also :meth:`.Connection.begin`, :meth:`.Connection.begin_twophase`. """ @@ -1250,6 +1260,7 @@ class Connection(Connectable): self.engine.logger.info("BEGIN (implicit)") if self._has_events: + self.dispatch.begin(self) self.engine.dispatch.begin(self) try: @@ -1260,6 +1271,7 @@ class Connection(Connectable): def _rollback_impl(self): if self._has_events: + self.dispatch.rollback(self) self.engine.dispatch.rollback(self) if self._still_open_and_connection_is_valid: @@ -1276,6 +1288,7 @@ class Connection(Connectable): def _commit_impl(self): if self._has_events: + self.dispatch.commit(self) self.engine.dispatch.commit(self) if self._echo: @@ -1289,6 +1302,7 @@ class Connection(Connectable): def _savepoint_impl(self, name=None): if self._has_events: + self.dispatch.savepoint(self, name) self.engine.dispatch.savepoint(self, name) if name is None: @@ -1300,6 +1314,7 @@ class Connection(Connectable): def _rollback_to_savepoint_impl(self, name, context): if self._has_events: + self.dispatch.rollback_savepoint(self, name, context) self.engine.dispatch.rollback_savepoint(self, name, context) if self._still_open_and_connection_is_valid: @@ -1308,6 +1323,7 @@ class Connection(Connectable): def _release_savepoint_impl(self, name, context): if self._has_events: + self.dispatch.release_savepoint(self, name, context) self.engine.dispatch.release_savepoint(self, name, context) if self._still_open_and_connection_is_valid: @@ -1316,6 +1332,7 @@ class Connection(Connectable): def _begin_twophase_impl(self, xid): if self._has_events: + self.dispatch.begin_twophase(self, xid) self.engine.dispatch.begin_twophase(self, xid) if self._still_open_and_connection_is_valid: @@ -1323,6 +1340,7 @@ class Connection(Connectable): def _prepare_twophase_impl(self, xid): if self._has_events: + self.dispatch.prepare_twophase(self, xid) self.engine.dispatch.prepare_twophase(self, xid) if self._still_open_and_connection_is_valid: @@ -1331,6 +1349,7 @@ class Connection(Connectable): def _rollback_twophase_impl(self, xid, is_prepared): if self._has_events: + self.dispatch.rollback_twophase(self, xid, is_prepared) self.engine.dispatch.rollback_twophase(self, xid, is_prepared) if self._still_open_and_connection_is_valid: @@ -1340,6 +1359,7 @@ class Connection(Connectable): def _commit_twophase_impl(self, xid, is_prepared): if self._has_events: + self.dispatch.commit_twophase(self, xid, is_prepared) self.engine.dispatch.commit_twophase(self, xid, is_prepared) if self._still_open_and_connection_is_valid: @@ -1392,12 +1412,12 @@ class Connection(Connectable): def execute(self, object, *multiparams, **params): """Executes the a SQL statement construct and returns a :class:`.ResultProxy`. - :param object: The statement to be executed. May be + :param object: The statement to be executed. May be one of: * a plain string * any :class:`.ClauseElement` construct that is also - a subclass of :class:`.Executable`, such as a + a subclass of :class:`.Executable`, such as a :func:`~.expression.select` construct * a :class:`.FunctionElement`, such as that generated by :attr:`.func`, will be automatically wrapped in @@ -1412,7 +1432,7 @@ class Connection(Connectable): dictionaries passed to \*multiparams:: conn.execute( - table.insert(), + table.insert(), {"id":1, "value":"v1"}, {"id":2, "value":"v2"} ) @@ -1423,10 +1443,10 @@ class Connection(Connectable): table.insert(), id=1, value="v1" ) - In the case that a plain SQL string is passed, and the underlying + In the case that a plain SQL string is passed, and the underlying DBAPI accepts positional bind parameters, a collection of tuples or individual values in \*multiparams may be passed:: - + conn.execute( "INSERT INTO table (id, value) VALUES (?, ?)", (1, "v1"), (2, "v2") @@ -1438,9 +1458,9 @@ class Connection(Connectable): ) Note above, the usage of a question mark "?" or other - symbol is contingent upon the "paramstyle" accepted by the DBAPI + symbol is contingent upon the "paramstyle" accepted by the DBAPI in use, which may be any of "qmark", "named", "pyformat", "format", - "numeric". See `pep-249 <http://www.python.org/dev/peps/pep-0249/>`_ + "numeric". See `pep-249 <http://www.python.org/dev/peps/pep-0249/>`_ for details on paramstyle. To execute a textual SQL statement which uses bound parameters in a @@ -1450,9 +1470,9 @@ class Connection(Connectable): for c in type(object).__mro__: if c in Connection.executors: return Connection.executors[c]( - self, + self, object, - multiparams, + multiparams, params) else: raise exc.InvalidRequestError( @@ -1501,14 +1521,17 @@ class Connection(Connectable): def _execute_function(self, func, multiparams, params): """Execute a sql.FunctionElement object.""" - return self._execute_clauseelement(func.select(), + return self._execute_clauseelement(func.select(), multiparams, params) def _execute_default(self, default, multiparams, params): """Execute a schema.ColumnDefault object.""" if self._has_events: - for fn in self.engine.dispatch.before_execute: + for fn in chain( + self.dispatch.before_execute, + self.engine.dispatch.before_execute + ): default, multiparams, params = \ fn(self, default, multiparams, params) @@ -1530,7 +1553,9 @@ class Connection(Connectable): self.close() if self._has_events: - self.engine.dispatch.after_execute(self, + self.dispatch.after_execute(self, + default, multiparams, params, ret) + self.engine.dispatch.after_execute(self, default, multiparams, params, ret) return ret @@ -1539,7 +1564,10 @@ class Connection(Connectable): """Execute a schema.DDL object.""" if self._has_events: - for fn in self.engine.dispatch.before_execute: + for fn in chain( + self.dispatch.before_execute, + self.engine.dispatch.before_execute + ): ddl, multiparams, params = \ fn(self, ddl, multiparams, params) @@ -1549,12 +1577,12 @@ class Connection(Connectable): ret = self._execute_context( dialect, dialect.execution_ctx_cls._init_ddl, - compiled, + compiled, None, compiled ) if self._has_events: - self.engine.dispatch.after_execute(self, + self.engine.dispatch.after_execute(self, ddl, multiparams, params, ret) return ret @@ -1562,7 +1590,10 @@ class Connection(Connectable): """Execute a sql.ClauseElement object.""" if self._has_events: - for fn in self.engine.dispatch.before_execute: + for fn in chain( + self.dispatch.before_execute, + self.engine.dispatch.before_execute + ): elem, multiparams, params = \ fn(self, elem, multiparams, params) @@ -1579,24 +1610,26 @@ class Connection(Connectable): compiled_sql = self._execution_options['compiled_cache'][key] else: compiled_sql = elem.compile( - dialect=dialect, column_keys=keys, + dialect=dialect, column_keys=keys, inline=len(distilled_params) > 1) self._execution_options['compiled_cache'][key] = compiled_sql else: compiled_sql = elem.compile( - dialect=dialect, column_keys=keys, + dialect=dialect, column_keys=keys, inline=len(distilled_params) > 1) ret = self._execute_context( dialect, dialect.execution_ctx_cls._init_compiled, - compiled_sql, + compiled_sql, distilled_params, compiled_sql, distilled_params ) if self._has_events: - self.engine.dispatch.after_execute(self, + self.dispatch.after_execute(self, + elem, multiparams, params, ret) + self.engine.dispatch.after_execute(self, elem, multiparams, params, ret) return ret @@ -1604,7 +1637,10 @@ class Connection(Connectable): """Execute a sql.Compiled object.""" if self._has_events: - for fn in self.engine.dispatch.before_execute: + for fn in chain( + self.dispatch.before_execute, + self.engine.dispatch.before_execute + ): compiled, multiparams, params = \ fn(self, compiled, multiparams, params) @@ -1613,12 +1649,14 @@ class Connection(Connectable): ret = self._execute_context( dialect, dialect.execution_ctx_cls._init_compiled, - compiled, + compiled, parameters, compiled, parameters ) if self._has_events: - self.engine.dispatch.after_execute(self, + self.dispatch.after_execute(self, + compiled, multiparams, params, ret) + self.engine.dispatch.after_execute(self, compiled, multiparams, params, ret) return ret @@ -1626,7 +1664,10 @@ class Connection(Connectable): """Execute a string SQL statement.""" if self._has_events: - for fn in self.engine.dispatch.before_execute: + for fn in chain( + self.dispatch.before_execute, + self.engine.dispatch.before_execute + ): statement, multiparams, params = \ fn(self, statement, multiparams, params) @@ -1635,17 +1676,19 @@ class Connection(Connectable): ret = self._execute_context( dialect, dialect.execution_ctx_cls._init_statement, - statement, + statement, parameters, statement, parameters ) if self._has_events: - self.engine.dispatch.after_execute(self, + self.dispatch.after_execute(self, + statement, multiparams, params, ret) + self.engine.dispatch.after_execute(self, statement, multiparams, params, ret) return ret - def _execute_context(self, dialect, constructor, - statement, parameters, + def _execute_context(self, dialect, constructor, + statement, parameters, *args): """Create an :class:`.ExecutionContext` and execute, returning a :class:`.ResultProxy`.""" @@ -1658,8 +1701,8 @@ class Connection(Connectable): context = constructor(dialect, self, conn, *args) except Exception, e: - self._handle_dbapi_exception(e, - str(statement), parameters, + self._handle_dbapi_exception(e, + str(statement), parameters, None, None) raise @@ -1674,48 +1717,56 @@ class Connection(Connectable): parameters = parameters[0] if self._has_events: - for fn in self.engine.dispatch.before_cursor_execute: + for fn in chain( + self.dispatch.before_cursor_execute, + self.engine.dispatch.before_cursor_execute + ): statement, parameters = \ - fn(self, cursor, statement, parameters, + fn(self, cursor, statement, parameters, context, context.executemany) if self._echo: self.engine.logger.info(statement) - self.engine.logger.info("%r", + self.engine.logger.info("%r", sql_util._repr_params(parameters, batches=10)) try: if context.executemany: self.dialect.do_executemany( - cursor, - statement, - parameters, + cursor, + statement, + parameters, context) elif not parameters and context.no_parameters: self.dialect.do_execute_no_params( - cursor, - statement, + cursor, + statement, context) else: self.dialect.do_execute( - cursor, - statement, - parameters, + cursor, + statement, + parameters, context) except Exception, e: self._handle_dbapi_exception( - e, - statement, - parameters, - cursor, + e, + statement, + parameters, + cursor, context) raise if self._has_events: - self.engine.dispatch.after_cursor_execute(self, cursor, - statement, - parameters, - context, + self.dispatch.after_cursor_execute(self, cursor, + statement, + parameters, + context, + context.executemany) + self.engine.dispatch.after_cursor_execute(self, cursor, + statement, + parameters, + context, context.executemany) if context.compiled: @@ -1735,7 +1786,7 @@ class Connection(Connectable): elif not context._is_explicit_returning: result.close(_autoclose_connection=False) elif result._metadata is None: - # no results, get rowcount + # no results, get rowcount # (which requires open cursor on some drivers # such as kintersbasdb, mxodbc), result.rowcount @@ -1756,7 +1807,7 @@ class Connection(Connectable): This method is used by DefaultDialect for special-case executions, such as for sequences and column defaults. - The path of statement execution in the majority of cases + The path of statement execution in the majority of cases terminates at _execute_context(). """ @@ -1765,14 +1816,14 @@ class Connection(Connectable): self.engine.logger.info("%r", parameters) try: self.dialect.do_execute( - cursor, - statement, + cursor, + statement, parameters) except Exception, e: self._handle_dbapi_exception( - e, - statement, - parameters, + e, + statement, + parameters, cursor, None) raise @@ -1794,20 +1845,20 @@ class Connection(Connectable): if isinstance(e, (SystemExit, KeyboardInterrupt)): raise - def _handle_dbapi_exception(self, - e, - statement, - parameters, - cursor, + def _handle_dbapi_exception(self, + e, + statement, + parameters, + cursor, context): if getattr(self, '_reentrant_error', False): # Py3K - #raise exc.DBAPIError.instance(statement, parameters, e, + #raise exc.DBAPIError.instance(statement, parameters, e, # self.dialect.dbapi.Error) from e # Py2K - raise exc.DBAPIError.instance(statement, - parameters, - e, + raise exc.DBAPIError.instance(statement, + parameters, + e, self.dialect.dbapi.Error), \ None, sys.exc_info()[2] # end Py2K @@ -1820,11 +1871,17 @@ class Connection(Connectable): if should_wrap and context: if self._has_events: - self.engine.dispatch.dbapi_error(self, - cursor, - statement, - parameters, - context, + self.dispatch.dbapi_error(self, + cursor, + statement, + parameters, + context, + e) + self.engine.dispatch.dbapi_error(self, + cursor, + statement, + parameters, + context, e) context.handle_dbapi_exception(e) @@ -1850,17 +1907,17 @@ class Connection(Connectable): # Py3K #raise exc.DBAPIError.instance( - # statement, - # parameters, - # e, + # statement, + # parameters, + # e, # self.dialect.dbapi.Error, # connection_invalidated=is_disconnect) \ # from e # Py2K raise exc.DBAPIError.instance( - statement, - parameters, - e, + statement, + parameters, + e, self.dialect.dbapi.Error, connection_invalidated=is_disconnect), \ None, sys.exc_info()[2] @@ -1906,8 +1963,8 @@ class Connection(Connectable): set) is specified, limit the autoload to the given column names. - The default implementation uses the - :class:`.Inspector` interface to + The default implementation uses the + :class:`.Inspector` interface to provide the output, building upon the granular table/column/ constraint etc. methods of :class:`.Dialect`. @@ -1920,18 +1977,18 @@ class Connection(Connectable): def transaction(self, callable_, *args, **kwargs): """Execute the given function within a transaction boundary. - The function is passed this :class:`.Connection` + The function is passed this :class:`.Connection` as the first argument, followed by the given \*args and \**kwargs, e.g.:: - + def do_something(conn, x, y): conn.execute("some statement", {'x':x, 'y':y}) conn.transaction(do_something, 5, 10) The operations inside the function are all invoked within the - context of a single :class:`.Transaction`. - Upon success, the transaction is committed. If an + context of a single :class:`.Transaction`. + Upon success, the transaction is committed. If an exception is raised, the transaction is rolled back before propagating the exception. @@ -1940,20 +1997,20 @@ class Connection(Connectable): The :meth:`.transaction` method is superseded by the usage of the Python ``with:`` statement, which can be used with :meth:`.Connection.begin`:: - + with conn.begin(): conn.execute("some statement", {'x':5, 'y':10}) - + As well as with :meth:`.Engine.begin`:: - + with engine.begin() as conn: conn.execute("some statement", {'x':5, 'y':10}) - + See also: - - :meth:`.Engine.begin` - engine-level transactional + + :meth:`.Engine.begin` - engine-level transactional context - + :meth:`.Engine.transaction` - engine-level version of :meth:`.Connection.transaction` @@ -1975,7 +2032,7 @@ class Connection(Connectable): The given \*args and \**kwargs are passed subsequent to the :class:`.Connection` argument. - This function, along with :meth:`.Engine.run_callable`, + This function, along with :meth:`.Engine.run_callable`, allows a function to be run with a :class:`.Connection` or :class:`.Engine` object without the need to know which one is being dealt with. @@ -1991,7 +2048,7 @@ class Connection(Connectable): class Transaction(object): """Represent a database transaction in progress. - The :class:`.Transaction` object is procured by + The :class:`.Transaction` object is procured by calling the :meth:`~.Connection.begin` method of :class:`.Connection`:: @@ -2003,9 +2060,9 @@ class Transaction(object): trans.commit() The object provides :meth:`.rollback` and :meth:`.commit` - methods in order to control transaction boundaries. It - also implements a context manager interface so that - the Python ``with`` statement can be used with the + methods in order to control transaction boundaries. It + also implements a context manager interface so that + the Python ``with`` statement can be used with the :meth:`.Connection.begin` method:: with connection.begin(): @@ -2151,11 +2208,11 @@ class TwoPhaseTransaction(Transaction): class Engine(Connectable, log.Identified): """ - Connects a :class:`~sqlalchemy.pool.Pool` and - :class:`~sqlalchemy.engine.base.Dialect` together to provide a source + Connects a :class:`~sqlalchemy.pool.Pool` and + :class:`~sqlalchemy.engine.base.Dialect` together to provide a source of database connectivity and behavior. - An :class:`.Engine` object is instantiated publicly using the + An :class:`.Engine` object is instantiated publicly using the :func:`~sqlalchemy.create_engine` function. See also: @@ -2170,7 +2227,7 @@ class Engine(Connectable, log.Identified): _has_events = False _connection_cls = Connection - def __init__(self, pool, dialect, url, + def __init__(self, pool, dialect, url, logging_name=None, echo=None, proxy=None, execution_options=None ): @@ -2194,14 +2251,12 @@ class Engine(Connectable, log.Identified): ) self.update_execution_options(**execution_options) - dispatch = event.dispatcher(events.ConnectionEvents) - def update_execution_options(self, **opt): - """Update the default execution_options dictionary + """Update the default execution_options dictionary of this :class:`.Engine`. The given keys/values in \**opt are added to the - default execution options that will be used for + default execution options that will be used for all connections. The initial contents of this dictionary can be sent via the ``execution_options`` parameter to :func:`.create_engine`. @@ -2237,23 +2292,23 @@ class Engine(Connectable, log.Identified): A new connection pool is created immediately after the old one has been disposed. This new pool, like all SQLAlchemy connection pools, - does not make any actual connections to the database until one is + does not make any actual connections to the database until one is first requested. This method has two general use cases: * When a dropped connection is detected, it is assumed that all - connections held by the pool are potentially dropped, and + connections held by the pool are potentially dropped, and the entire pool is replaced. - * An application may want to use :meth:`dispose` within a test + * An application may want to use :meth:`dispose` within a test suite that is creating multiple engines. It is critical to note that :meth:`dispose` does **not** guarantee that the application will release all open database connections - only those connections that are checked into the pool are closed. Connections which remain checked out or have been detached from - the engine are not affected. + the engine are not affected. """ self.pool = self.pool._replace() @@ -2266,7 +2321,7 @@ class Engine(Connectable, log.Identified): from sqlalchemy.engine import ddl - self._run_visitor(ddl.SchemaGenerator, entity, + self._run_visitor(ddl.SchemaGenerator, entity, connection=connection, **kwargs) @util.deprecated("0.7", "Use the drop() method on the given schema " @@ -2277,7 +2332,7 @@ class Engine(Connectable, log.Identified): from sqlalchemy.engine import ddl - self._run_visitor(ddl.SchemaDropper, entity, + self._run_visitor(ddl.SchemaDropper, entity, connection=connection, **kwargs) def _execute_default(self, default): @@ -2288,15 +2343,15 @@ class Engine(Connectable, log.Identified): connection.close() @property - @util.deprecated("0.7", + @util.deprecated("0.7", "Use :attr:`~sqlalchemy.sql.expression.func` to create function constructs.") def func(self): return expression._FunctionGenerator(bind=self) - @util.deprecated("0.7", + @util.deprecated("0.7", "Use :func:`.expression.text` to create text constructs.") def text(self, text, *args, **kwargs): - """Return a :func:`~sqlalchemy.sql.expression.text` construct, + """Return a :func:`~sqlalchemy.sql.expression.text` construct, bound to this engine. This is equivalent to:: @@ -2307,7 +2362,7 @@ class Engine(Connectable, log.Identified): return expression.text(text, bind=self, *args, **kwargs) - def _run_visitor(self, visitorcallable, element, + def _run_visitor(self, visitorcallable, element, connection=None, **kwargs): if connection is None: conn = self.contextual_connect(close_with_result=False) @@ -2341,15 +2396,15 @@ class Engine(Connectable, log.Identified): with a :class:`.Transaction` established. E.g.:: - + with engine.begin() as conn: conn.execute("insert into table (x, y, z) values (1, 2, 3)") conn.execute("my_special_procedure(5)") - Upon successful operation, the :class:`.Transaction` + Upon successful operation, the :class:`.Transaction` is committed. If an error is raised, the :class:`.Transaction` - is rolled back. - + is rolled back. + The ``close_with_result`` flag is normally ``False``, and indicates that the :class:`.Connection` will be closed when the operation is complete. When set to ``True``, it indicates the :class:`.Connection` @@ -2359,9 +2414,9 @@ class Engine(Connectable, log.Identified): has exhausted all result rows. .. versionadded:: 0.7.6 - + See also: - + :meth:`.Engine.connect` - procure a :class:`.Connection` from an :class:`.Engine`. @@ -2381,19 +2436,19 @@ class Engine(Connectable, log.Identified): """Execute the given function within a transaction boundary. The function is passed a :class:`.Connection` newly procured - from :meth:`.Engine.contextual_connect` as the first argument, + from :meth:`.Engine.contextual_connect` as the first argument, followed by the given \*args and \**kwargs. - + e.g.:: - + def do_something(conn, x, y): conn.execute("some statement", {'x':x, 'y':y}) engine.transaction(do_something, 5, 10) - + The operations inside the function are all invoked within the - context of a single :class:`.Transaction`. - Upon success, the transaction is committed. If an + context of a single :class:`.Transaction`. + Upon success, the transaction is committed. If an exception is raised, the transaction is rolled back before propagating the exception. @@ -2402,15 +2457,15 @@ class Engine(Connectable, log.Identified): The :meth:`.transaction` method is superseded by the usage of the Python ``with:`` statement, which can be used with :meth:`.Engine.begin`:: - + with engine.begin() as conn: conn.execute("some statement", {'x':5, 'y':10}) - + See also: - - :meth:`.Engine.begin` - engine-level transactional + + :meth:`.Engine.begin` - engine-level transactional context - + :meth:`.Connection.transaction` - connection-level version of :meth:`.Engine.transaction` @@ -2429,7 +2484,7 @@ class Engine(Connectable, log.Identified): The given \*args and \**kwargs are passed subsequent to the :class:`.Connection` argument. - This function, along with :meth:`.Connection.run_callable`, + This function, along with :meth:`.Connection.run_callable`, allows a function to be run with a :class:`.Connection` or :class:`.Engine` object without the need to know which one is being dealt with. @@ -2500,9 +2555,9 @@ class Engine(Connectable, log.Identified): """ - return self._connection_cls(self, - self.pool.connect(), - close_with_result=close_with_result, + return self._connection_cls(self, + self.pool.connect(), + close_with_result=close_with_result, **kwargs) def table_names(self, schema=None, connection=None): @@ -2533,7 +2588,7 @@ class Engine(Connectable, log.Identified): Uses the given :class:`.Connection`, or if None produces its own :class:`.Connection`, and passes the ``table`` - and ``include_columns`` arguments onto that + and ``include_columns`` arguments onto that :class:`.Connection` object's :meth:`.Connection.reflecttable` method. The :class:`.Table` object is then populated with new attributes. @@ -2555,7 +2610,7 @@ class Engine(Connectable, log.Identified): def raw_connection(self): """Return a "raw" DBAPI connection from the connection pool. - The returned object is a proxied version of the DBAPI + The returned object is a proxied version of the DBAPI connection object used by the underlying driver in use. The object will have all the same behavior as the real DBAPI connection, except that its ``close()`` method will result in the @@ -2580,8 +2635,8 @@ try: # __setstate__. from sqlalchemy.cresultproxy import safe_rowproxy_reconstructor - # The extra function embedding is needed so that the - # reconstructor function has the same signature whether or not + # The extra function embedding is needed so that the + # reconstructor function has the same signature whether or not # the extension is present. def rowproxy_reconstructor(cls, state): return safe_rowproxy_reconstructor(cls, state) @@ -2715,7 +2770,7 @@ class RowProxy(BaseRowProxy): return iter(self) try: - # Register RowProxy with Sequence, + # Register RowProxy with Sequence, # so sequence protocol is implemented from collections import Sequence Sequence.register(RowProxy) @@ -2759,8 +2814,8 @@ class ResultMetaData(object): if context.result_map: try: - name, obj, type_ = context.result_map[colname - if self.case_sensitive + name, obj, type_ = context.result_map[colname + if self.case_sensitive else colname.lower()] except KeyError: name, obj, type_ = \ @@ -2780,17 +2835,17 @@ class ResultMetaData(object): # populate primary keymap, looking for conflicts. if primary_keymap.setdefault( - name if self.case_sensitive - else name.lower(), - rec) is not rec: + name if self.case_sensitive + else name.lower(), + rec) is not rec: # place a record that doesn't have the "index" - this # is interpreted later as an AmbiguousColumnError, - # but only when actually accessed. Columns + # but only when actually accessed. Columns # colliding by name is not a problem if those names # aren't used; integer and ColumnElement access is always # unambiguous. - primary_keymap[name - if self.case_sensitive + primary_keymap[name + if self.case_sensitive else name.lower()] = (processor, obj, None) @@ -2816,13 +2871,13 @@ class ResultMetaData(object): def _set_keymap_synonym(self, name, origname): """Set a synonym for the given name. - Some dialects (SQLite at the moment) may use this to + Some dialects (SQLite at the moment) may use this to adjust the column names that are significant within a row. """ - rec = (processor, obj, i) = self._keymap[origname if - self.case_sensitive + rec = (processor, obj, i) = self._keymap[origname if + self.case_sensitive else origname.lower()] if self._keymap.setdefault(name, rec) is not rec: self._keymap[name] = (processor, obj, None) @@ -2834,25 +2889,25 @@ class ResultMetaData(object): result = map.get(key if self.case_sensitive else key.lower()) # fallback for targeting a ColumnElement to a textual expression # this is a rare use case which only occurs when matching text() - # or colummn('name') constructs to ColumnElements, or after a + # or colummn('name') constructs to ColumnElements, or after a # pickle/unpickle roundtrip elif isinstance(key, expression.ColumnElement): if key._label and ( - key._label - if self.case_sensitive + key._label + if self.case_sensitive else key._label.lower()) in map: - result = map[key._label - if self.case_sensitive + result = map[key._label + if self.case_sensitive else key._label.lower()] elif hasattr(key, 'name') and ( - key.name - if self.case_sensitive + key.name + if self.case_sensitive else key.name.lower()) in map: # match is only on name. - result = map[key.name - if self.case_sensitive + result = map[key.name + if self.case_sensitive else key.name.lower()] - # search extra hard to make sure this + # search extra hard to make sure this # isn't a column/label name overlap. # this check isn't currently available if the row # was unpickled. @@ -2866,7 +2921,7 @@ class ResultMetaData(object): if result is None: if raiseerr: raise exc.NoSuchColumnError( - "Could not locate column in row for column '%s'" % + "Could not locate column in row for column '%s'" % expression._string_or_unprintable(key)) else: return None @@ -2921,7 +2976,7 @@ class ResultProxy(object): col3 = row[mytable.c.mycol] # access via Column object. ``ResultProxy`` also handles post-processing of result column - data using ``TypeEngine`` objects, which are referenced from + data using ``TypeEngine`` objects, which are referenced from the originating SQL statement that produced this result set. """ @@ -2959,38 +3014,38 @@ class ResultProxy(object): """Return the 'rowcount' for this result. The 'rowcount' reports the number of rows *matched* - by the WHERE criterion of an UPDATE or DELETE statement. - + by the WHERE criterion of an UPDATE or DELETE statement. + .. note:: - + Notes regarding :attr:`.ResultProxy.rowcount`: - - + + * This attribute returns the number of rows *matched*, which is not necessarily the same as the number of rows that were actually *modified* - an UPDATE statement, for example, may have no net change on a given row if the SET values given are the same as those present in the row already. Such a row would be matched but not modified. - On backends that feature both styles, such as MySQL, - rowcount is configured by default to return the match + On backends that feature both styles, such as MySQL, + rowcount is configured by default to return the match count in all cases. * :attr:`.ResultProxy.rowcount` is *only* useful in conjunction with an UPDATE or DELETE statement. Contrary to what the Python DBAPI says, it does *not* return the number of rows available from the results of a SELECT statement - as DBAPIs cannot support this functionality when rows are + as DBAPIs cannot support this functionality when rows are unbuffered. - + * :attr:`.ResultProxy.rowcount` may not be fully implemented by all dialects. In particular, most DBAPIs do not support an aggregate rowcount result from an executemany call. - The :meth:`.ResultProxy.supports_sane_rowcount` and + The :meth:`.ResultProxy.supports_sane_rowcount` and :meth:`.ResultProxy.supports_sane_multi_rowcount` methods will report from the dialect if each usage is known to be supported. - + * Statements that use RETURNING may not return a correct rowcount. @@ -3008,10 +3063,10 @@ class ResultProxy(object): This is a DBAPI specific method and is only functional for those backends which support it, for statements - where it is appropriate. It's behavior is not + where it is appropriate. It's behavior is not consistent across backends. - Usage of this method is normally unnecessary when + Usage of this method is normally unnecessary when using insert() expression constructs; the :attr:`~ResultProxy.inserted_primary_key` attribute provides a tuple of primary key values for a newly inserted row, @@ -3022,7 +3077,7 @@ class ResultProxy(object): return self._saved_cursor.lastrowid except Exception, e: self.connection._handle_dbapi_exception( - e, None, None, + e, None, None, self._saved_cursor, self.context) raise @@ -3030,8 +3085,8 @@ class ResultProxy(object): def returns_rows(self): """True if this :class:`.ResultProxy` returns rows. - I.e. if it is legal to call the methods - :meth:`~.ResultProxy.fetchone`, + I.e. if it is legal to call the methods + :meth:`~.ResultProxy.fetchone`, :meth:`~.ResultProxy.fetchmany` :meth:`~.ResultProxy.fetchall`. @@ -3041,10 +3096,10 @@ class ResultProxy(object): @property def is_insert(self): """True if this :class:`.ResultProxy` is the result - of a executing an expression language compiled + of a executing an expression language compiled :func:`.expression.insert` construct. - When True, this implies that the + When True, this implies that the :attr:`inserted_primary_key` attribute is accessible, assuming the statement did not include a user defined "returning" construct. @@ -3097,20 +3152,20 @@ class ResultProxy(object): def inserted_primary_key(self): """Return the primary key for the row just inserted. - The return value is a list of scalar values + The return value is a list of scalar values corresponding to the list of primary key columns in the target table. - This only applies to single row :func:`.insert` - constructs which did not explicitly specify + This only applies to single row :func:`.insert` + constructs which did not explicitly specify :meth:`.Insert.returning`. Note that primary key columns which specify a - server_default clause, + server_default clause, or otherwise do not qualify as "autoincrement" columns (see the notes at :class:`.Column`), and were generated using the database-side default, will - appear in this list as ``None`` unless the backend + appear in this list as ``None`` unless the backend supports "returning" and the insert statement executed with the "implicit returning" enabled. @@ -3185,7 +3240,7 @@ class ResultProxy(object): :class:`.ExecutionContext`. See :class:`.ExecutionContext` for details. - + """ return self.context.lastrow_has_defaults() @@ -3198,7 +3253,7 @@ class ResultProxy(object): Raises :class:`.InvalidRequestError` if the executed statement is not a compiled expression construct or is not an insert() or update() construct. - + """ if not self.context.compiled: @@ -3219,7 +3274,7 @@ class ResultProxy(object): Raises :class:`.InvalidRequestError` if the executed statement is not a compiled expression construct or is not an insert() or update() construct. - + """ if not self.context.compiled: @@ -3234,9 +3289,9 @@ class ResultProxy(object): def supports_sane_rowcount(self): """Return ``supports_sane_rowcount`` from the dialect. - + See :attr:`.ResultProxy.rowcount` for background. - + """ return self.dialect.supports_sane_rowcount @@ -3245,7 +3300,7 @@ class ResultProxy(object): """Return ``supports_sane_multi_rowcount`` from the dialect. See :attr:`.ResultProxy.rowcount` for background. - + """ return self.dialect.supports_sane_multi_rowcount @@ -3305,7 +3360,7 @@ class ResultProxy(object): return l except Exception, e: self.connection._handle_dbapi_exception( - e, None, None, + e, None, None, self.cursor, self.context) raise @@ -3325,7 +3380,7 @@ class ResultProxy(object): return l except Exception, e: self.connection._handle_dbapi_exception( - e, None, None, + e, None, None, self.cursor, self.context) raise @@ -3345,7 +3400,7 @@ class ResultProxy(object): return None except Exception, e: self.connection._handle_dbapi_exception( - e, None, None, + e, None, None, self.cursor, self.context) raise @@ -3362,7 +3417,7 @@ class ResultProxy(object): row = self._fetchone_impl() except Exception, e: self.connection._handle_dbapi_exception( - e, None, None, + e, None, None, self.cursor, self.context) raise diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py index 81ceec44e..38856b49e 100644 --- a/lib/sqlalchemy/engine/default.py +++ b/lib/sqlalchemy/engine/default.py @@ -146,11 +146,13 @@ class DefaultDialect(base.Dialect): self.label_length = label_length if self.description_encoding == 'use_encoding': - self._description_decoder = processors.to_unicode_processor_factory( + self._description_decoder = \ + processors.to_unicode_processor_factory( encoding ) elif self.description_encoding is not None: - self._description_decoder = processors.to_unicode_processor_factory( + self._description_decoder = \ + processors.to_unicode_processor_factory( self.description_encoding ) self._encoder = codecs.getencoder(self.encoding) @@ -213,6 +215,7 @@ class DefaultDialect(base.Dialect): # end Py2K # Py3K #cast_to = str + def check_unicode(formatstr, type_): cursor = connection.connection.cursor() try: @@ -222,7 +225,8 @@ class DefaultDialect(base.Dialect): expression.select( [expression.cast( expression.literal_column( - "'test %s returns'" % formatstr), type_) + "'test %s returns'" % formatstr), + type_) ]).compile(dialect=self) ) ) @@ -258,7 +262,8 @@ class DefaultDialect(base.Dialect): """ return sqltypes.adapt_type(typeobj, self.colspecs) - def reflecttable(self, connection, table, include_columns, exclude_columns=None): + def reflecttable(self, connection, table, include_columns, + exclude_columns=None): insp = reflection.Inspector.from_engine(connection) return insp.reflecttable(table, include_columns, exclude_columns) @@ -398,7 +403,8 @@ class DefaultExecutionContext(base.ExecutionContext): return self @classmethod - def _init_compiled(cls, dialect, connection, dbapi_connection, compiled, parameters): + def _init_compiled(cls, dialect, connection, dbapi_connection, + compiled, parameters): """Initialize execution context for a Compiled construct.""" self = cls.__new__(cls) @@ -424,7 +430,8 @@ class DefaultExecutionContext(base.ExecutionContext): self.unicode_statement = unicode(compiled) if not dialect.supports_unicode_statements: - self.statement = self.unicode_statement.encode(self.dialect.encoding) + self.statement = self.unicode_statement.encode( + self.dialect.encoding) else: self.statement = self.unicode_statement @@ -442,7 +449,7 @@ class DefaultExecutionContext(base.ExecutionContext): else: self.compiled_parameters = \ [compiled.construct_params(m, _group_number=grp) for - grp,m in enumerate(parameters)] + grp, m in enumerate(parameters)] self.executemany = len(parameters) > 1 @@ -477,7 +484,8 @@ class DefaultExecutionContext(base.ExecutionContext): param[dialect._encoder(key)[0]] = \ processors[key](compiled_params[key]) else: - param[dialect._encoder(key)[0]] = compiled_params[key] + param[dialect._encoder(key)[0]] = \ + compiled_params[key] else: for key in compiled_params: if key in processors: @@ -490,7 +498,8 @@ class DefaultExecutionContext(base.ExecutionContext): return self @classmethod - def _init_statement(cls, dialect, connection, dbapi_connection, statement, parameters): + def _init_statement(cls, dialect, connection, dbapi_connection, + statement, parameters): """Initialize execution context for a string SQL statement.""" self = cls.__new__(cls) @@ -513,7 +522,7 @@ class DefaultExecutionContext(base.ExecutionContext): if dialect.supports_unicode_statements: self.parameters = parameters else: - self.parameters= [ + self.parameters = [ dict((dialect._encoder(k)[0], d[k]) for k in d) for d in parameters ] or [{}] @@ -523,7 +532,8 @@ class DefaultExecutionContext(base.ExecutionContext): self.executemany = len(parameters) > 1 - if not dialect.supports_unicode_statements and isinstance(statement, unicode): + if not dialect.supports_unicode_statements and \ + isinstance(statement, unicode): self.unicode_statement = statement self.statement = dialect._encoder(statement)[0] else: @@ -670,7 +680,8 @@ class DefaultExecutionContext(base.ExecutionContext): autoinc_col = table._autoincrement_column if autoinc_col is not None: # apply type post processors to the lastrowid - proc = autoinc_col.type._cached_result_processor(self.dialect, None) + proc = autoinc_col.type._cached_result_processor( + self.dialect, None) if proc is not None: lastrowid = proc(lastrowid) @@ -719,27 +730,33 @@ class DefaultExecutionContext(base.ExecutionContext): inputsizes = [] for key in self.compiled.positiontup: typeengine = types[key] - dbtype = typeengine.dialect_impl(self.dialect).get_dbapi_type(self.dialect.dbapi) - if dbtype is not None and (not exclude_types or dbtype not in exclude_types): + dbtype = typeengine.dialect_impl(self.dialect).\ + get_dbapi_type(self.dialect.dbapi) + if dbtype is not None and \ + (not exclude_types or dbtype not in exclude_types): inputsizes.append(dbtype) try: self.cursor.setinputsizes(*inputsizes) except Exception, e: - self.root_connection._handle_dbapi_exception(e, None, None, None, self) + self.root_connection._handle_dbapi_exception( + e, None, None, None, self) raise else: inputsizes = {} for key in self.compiled.bind_names.values(): typeengine = types[key] - dbtype = typeengine.dialect_impl(self.dialect).get_dbapi_type(self.dialect.dbapi) - if dbtype is not None and (not exclude_types or dbtype not in exclude_types): + dbtype = typeengine.dialect_impl(self.dialect).\ + get_dbapi_type(self.dialect.dbapi) + if dbtype is not None and \ + (not exclude_types or dbtype not in exclude_types): if translate: key = translate.get(key, key) inputsizes[self.dialect._encoder(key)[0]] = dbtype try: self.cursor.setinputsizes(**inputsizes) except Exception, e: - self.root_connection._handle_dbapi_exception(e, None, None, None, self) + self.root_connection._handle_dbapi_exception( + e, None, None, None, self) raise def _exec_default(self, default, type_): diff --git a/lib/sqlalchemy/events.py b/lib/sqlalchemy/events.py index a5ebb031a..10717ee50 100644 --- a/lib/sqlalchemy/events.py +++ b/lib/sqlalchemy/events.py @@ -17,11 +17,11 @@ class DDLEvents(event.Events): that is, :class:`.SchemaItem` and :class:`.SchemaEvent` subclasses, including :class:`.MetaData`, :class:`.Table`, :class:`.Column`. - + :class:`.MetaData` and :class:`.Table` support events specifically regarding when CREATE and DROP - DDL is emitted to the database. - + DDL is emitted to the database. + Attachment events are also provided to customize behavior whenever a child schema element is associated with a parent, such as, when a :class:`.Column` is associated @@ -37,14 +37,14 @@ class DDLEvents(event.Events): some_table = Table('some_table', m, Column('data', Integer)) def after_create(target, connection, **kw): - connection.execute("ALTER TABLE %s SET name=foo_%s" % + connection.execute("ALTER TABLE %s SET name=foo_%s" % (target.name, target.name)) event.listen(some_table, "after_create", after_create) - DDL events integrate closely with the + DDL events integrate closely with the :class:`.DDL` class and the :class:`.DDLElement` hierarchy - of DDL clause constructs, which are themselves appropriate + of DDL clause constructs, which are themselves appropriate as listener callables:: from sqlalchemy import DDL @@ -81,7 +81,7 @@ class DDLEvents(event.Events): to the event. The contents of this dictionary may vary across releases, and include the list of tables being generated for a metadata-level - event, the checkfirst flag, and other + event, the checkfirst flag, and other elements used by internal events. """ @@ -97,7 +97,7 @@ class DDLEvents(event.Events): to the event. The contents of this dictionary may vary across releases, and include the list of tables being generated for a metadata-level - event, the checkfirst flag, and other + event, the checkfirst flag, and other elements used by internal events. """ @@ -113,7 +113,7 @@ class DDLEvents(event.Events): to the event. The contents of this dictionary may vary across releases, and include the list of tables being generated for a metadata-level - event, the checkfirst flag, and other + event, the checkfirst flag, and other elements used by internal events. """ @@ -129,52 +129,52 @@ class DDLEvents(event.Events): to the event. The contents of this dictionary may vary across releases, and include the list of tables being generated for a metadata-level - event, the checkfirst flag, and other + event, the checkfirst flag, and other elements used by internal events. """ def before_parent_attach(self, target, parent): - """Called before a :class:`.SchemaItem` is associated with + """Called before a :class:`.SchemaItem` is associated with a parent :class:`.SchemaItem`. - + :param target: the target object :param parent: the parent to which the target is being attached. - + :func:`.event.listen` also accepts a modifier for this event: - + :param propagate=False: When True, the listener function will be established for any copies made of the target object, i.e. those copies that are generated when :meth:`.Table.tometadata` is used. - + """ def after_parent_attach(self, target, parent): - """Called after a :class:`.SchemaItem` is associated with + """Called after a :class:`.SchemaItem` is associated with a parent :class:`.SchemaItem`. :param target: the target object :param parent: the parent to which the target is being attached. - + :func:`.event.listen` also accepts a modifier for this event: - + :param propagate=False: When True, the listener function will be established for any copies made of the target object, i.e. those copies that are generated when :meth:`.Table.tometadata` is used. - + """ def column_reflect(self, inspector, table, column_info): """Called for each unit of 'column info' retrieved when - a :class:`.Table` is being reflected. - + a :class:`.Table` is being reflected. + The dictionary of column information as returned by the dialect is passed, and can be modified. The dictionary - is that returned in each element of the list returned - by :meth:`.reflection.Inspector.get_columns`. - + is that returned in each element of the list returned + by :meth:`.reflection.Inspector.get_columns`. + The event is called before any action is taken against this dictionary, and the contents can be modified. The :class:`.Column` specific arguments ``info``, ``key``, @@ -182,45 +182,46 @@ class DDLEvents(event.Events): will be passed to the constructor of :class:`.Column`. Note that this event is only meaningful if either - associated with the :class:`.Table` class across the + associated with the :class:`.Table` class across the board, e.g.:: - + from sqlalchemy.schema import Table from sqlalchemy import event def listen_for_reflect(inspector, table, column_info): "receive a column_reflect event" # ... - + event.listen( - Table, - 'column_reflect', + Table, + 'column_reflect', listen_for_reflect) - + ...or with a specific :class:`.Table` instance using the ``listeners`` argument:: - + def listen_for_reflect(inspector, table, column_info): "receive a column_reflect event" # ... - + t = Table( - 'sometable', + 'sometable', autoload=True, listeners=[ ('column_reflect', listen_for_reflect) ]) - + This because the reflection process initiated by ``autoload=True`` completes within the scope of the constructor for :class:`.Table`. - + """ class SchemaEventTarget(object): - """Base class for elements that are the targets of :class:`.DDLEvents` events. - + """Base class for elements that are the targets of :class:`.DDLEvents` + events. + This includes :class:`.SchemaItem` as well as :class:`.SchemaType`. - + """ dispatch = event.dispatcher(DDLEvents) @@ -230,9 +231,9 @@ class SchemaEventTarget(object): raise NotImplementedError() def _set_parent_with_dispatch(self, parent): - self.dispatch.before_parent_attach(self, parent) - self._set_parent(parent) - self.dispatch.after_parent_attach(self, parent) + self.dispatch.before_parent_attach(self, parent) + self._set_parent(parent) + self.dispatch.after_parent_attach(self, parent) class PoolEvents(event.Events): """Available events for :class:`.Pool`. @@ -250,11 +251,11 @@ class PoolEvents(event.Events): event.listen(Pool, 'checkout', my_on_checkout) - In addition to accepting the :class:`.Pool` class and :class:`.Pool` instances, - :class:`.PoolEvents` also accepts :class:`.Engine` objects and - the :class:`.Engine` class as targets, which will be resolved - to the ``.pool`` attribute of the given engine or the :class:`.Pool` - class:: + In addition to accepting the :class:`.Pool` class and + :class:`.Pool` instances, :class:`.PoolEvents` also accepts + :class:`.Engine` objects and the :class:`.Engine` class as + targets, which will be resolved to the ``.pool`` attribute of the + given engine or the :class:`.Pool` class:: engine = create_engine("postgresql://scott:tiger@localhost/test") @@ -334,11 +335,14 @@ class PoolEvents(event.Events): """ class ConnectionEvents(event.Events): - """Available events for :class:`.Connection`. + """Available events for :class:`.Connectable`, which includes + :class:`.Connection` and :class:`.Engine`. - The methods here define the name of an event as well as the names of members that are passed to listener functions. + The methods here define the name of an event as well as the names of + members that are passed to listener functions. - e.g.:: + An event listener can be associated with any :class:`.Connectable`, + such as an :class:`.Engine`, e.g.:: from sqlalchemy import event, create_engine @@ -348,15 +352,19 @@ class ConnectionEvents(event.Events): engine = create_engine('postgresql://scott:tiger@localhost/test') event.listen(engine, "before_execute", before_execute) - Some events allow modifiers to the listen() function. + Some events allow modifiers to the :func:`.event.listen` function. - :param retval=False: Applies to the :meth:`.before_execute` and + :param retval=False: Applies to the :meth:`.before_execute` and :meth:`.before_cursor_execute` events only. When True, the user-defined event function must have a return value, which - is a tuple of parameters that replace the given statement + is a tuple of parameters that replace the given statement and parameters. See those methods for a description of specific return arguments. + .. versionchanged:: 0.8 :class:`.ConnectionEvents` can now be associated + with any :class:`.Connectable` including :class:`.Connection`, + in addition to the existing support for :class:`.Engine`. + """ @classmethod @@ -366,20 +374,24 @@ class ConnectionEvents(event.Events): if not retval: if identifier == 'before_execute': orig_fn = fn - def wrap(conn, clauseelement, multiparams, params): + + def wrap_before_execute(conn, clauseelement, + multiparams, params): orig_fn(conn, clauseelement, multiparams, params) return clauseelement, multiparams, params - fn = wrap + fn = wrap_before_execute elif identifier == 'before_cursor_execute': orig_fn = fn - def wrap(conn, cursor, statement, + + def wrap_before_cursor_execute(conn, cursor, statement, parameters, context, executemany): - orig_fn(conn, cursor, statement, + orig_fn(conn, cursor, statement, parameters, context, executemany) return statement, parameters - fn = wrap + fn = wrap_before_cursor_execute - elif retval and identifier not in ('before_execute', 'before_cursor_execute'): + elif retval and \ + identifier not in ('before_execute', 'before_cursor_execute'): raise exc.ArgumentError( "Only the 'before_execute' and " "'before_cursor_execute' engine " @@ -393,40 +405,40 @@ class ConnectionEvents(event.Events): def after_execute(self, conn, clauseelement, multiparams, params, result): """Intercept high level execute() events.""" - def before_cursor_execute(self, conn, cursor, statement, + def before_cursor_execute(self, conn, cursor, statement, parameters, context, executemany): """Intercept low-level cursor execute() events.""" - def after_cursor_execute(self, conn, cursor, statement, + def after_cursor_execute(self, conn, cursor, statement, parameters, context, executemany): """Intercept low-level cursor execute() events.""" - def dbapi_error(self, conn, cursor, statement, parameters, + def dbapi_error(self, conn, cursor, statement, parameters, context, exception): """Intercept a raw DBAPI error. - - This event is called with the DBAPI exception instance - received from the DBAPI itself, *before* SQLAlchemy wraps the + + This event is called with the DBAPI exception instance + received from the DBAPI itself, *before* SQLAlchemy wraps the exception with it's own exception wrappers, and before any other operations are performed on the DBAPI cursor; the existing transaction remains in effect as well as any state on the cursor. - + The use case here is to inject low-level exception handling into an :class:`.Engine`, typically for logging and debugging purposes. In general, user code should **not** modify any state or throw any exceptions here as this will interfere with SQLAlchemy's cleanup and error handling routines. - + Subsequent to this hook, SQLAlchemy may attempt any number of operations on the connection/cursor, including - closing the cursor, rolling back of the transaction in the + closing the cursor, rolling back of the transaction in the case of connectionless execution, and disposing of the entire connection pool if a "disconnect" was detected. The exception is then wrapped in a SQLAlchemy DBAPI exception wrapper and re-thrown. - + .. versionadded:: 0.7.7 """ diff --git a/test/engine/test_execute.py b/test/engine/test_execute.py index 2b6dd1c09..f2537e3fe 100644 --- a/test/engine/test_execute.py +++ b/test/engine/test_execute.py @@ -41,7 +41,7 @@ class ExecuteTest(fixtures.TestBase): def teardown_class(cls): metadata.drop_all() - @testing.fails_on("postgresql+pg8000", + @testing.fails_on("postgresql+pg8000", "pg8000 still doesn't allow single % without params") def test_no_params_option(self): stmt = "SELECT '%'" @@ -85,7 +85,7 @@ class ExecuteTest(fixtures.TestBase): ]: res = conn.execute( "select * from users where user_name=? or " - "user_name=? order by user_id", + "user_name=? order by user_id", *multiparam, **param) assert res.fetchall() == [ (1, 'jack'), @@ -126,7 +126,7 @@ class ExecuteTest(fixtures.TestBase): ]: res = conn.execute( "select * from users where user_name=%s or " - "user_name=%s order by user_id", + "user_name=%s order by user_id", *multiparam, **param) assert res.fetchall() == [ (1, 'jack'), @@ -152,7 +152,7 @@ class ExecuteTest(fixtures.TestBase): @testing.skip_if(lambda : testing.against('mysql+mysqldb'), 'db-api flaky') @testing.fails_on_everything_except('postgresql+psycopg2', - 'postgresql+pypostgresql', 'mysql+mysqlconnector', + 'postgresql+pypostgresql', 'mysql+mysqlconnector', 'mysql+pymysql') def test_raw_python(self): def go(conn): @@ -238,11 +238,11 @@ class ExecuteTest(fixtures.TestBase): def test_stmt_exception_pickleable_no_dbapi(self): self._test_stmt_exception_pickleable(Exception("hello world")) - @testing.fails_on("postgresql+psycopg2", + @testing.fails_on("postgresql+psycopg2", "Packages the cursor in the exception") - @testing.fails_on("mysql+oursql", + @testing.fails_on("mysql+oursql", "Exception doesn't come back exactly the same from pickle") - @testing.fails_on("oracle+cx_oracle", + @testing.fails_on("oracle+cx_oracle", "cx_oracle exception seems to be having " "some issue with pickling") def test_stmt_exception_pickleable_plus_dbapi(self): @@ -261,12 +261,12 @@ class ExecuteTest(fixtures.TestBase): def _test_stmt_exception_pickleable(self, orig): for sa_exc in ( - tsa.exc.StatementError("some error", - "select * from table", - {"foo":"bar"}, + tsa.exc.StatementError("some error", + "select * from table", + {"foo":"bar"}, orig), - tsa.exc.InterfaceError("select * from table", - {"foo":"bar"}, + tsa.exc.InterfaceError("select * from table", + {"foo":"bar"}, orig), tsa.exc.NoReferencedTableError("message", "tname"), tsa.exc.NoReferencedColumnError("message", "tname", "cname"), @@ -279,7 +279,7 @@ class ExecuteTest(fixtures.TestBase): eq_(repickled.params, {"foo":"bar"}) eq_(repickled.statement, sa_exc.statement) if hasattr(sa_exc, "connection_invalidated"): - eq_(repickled.connection_invalidated, + eq_(repickled.connection_invalidated, sa_exc.connection_invalidated) eq_(repickled.orig.args[0], orig.args[0]) @@ -403,7 +403,7 @@ class ConvenienceExecuteTest(fixtures.TablesTest): engine._connection_cls = MockConnection fn = self._trans_fn() assert_raises( - Exception, + Exception, engine.begin ) assert MockConnection.closed @@ -412,7 +412,7 @@ class ConvenienceExecuteTest(fixtures.TablesTest): fn = self._trans_rollback_fn() ctx = testing.db.begin() assert_raises_message( - Exception, + Exception, "breakage", testing.run_as_contextmanager, ctx, fn, 5, value=8 ) @@ -421,7 +421,7 @@ class ConvenienceExecuteTest(fixtures.TablesTest): def test_transaction_tlocal_engine_ctx_commit(self): fn = self._trans_fn() engine = engines.testing_engine(options=dict( - strategy='threadlocal', + strategy='threadlocal', pool=testing.db.pool)) ctx = engine.begin() testing.run_as_contextmanager(ctx, fn, 5, value=8) @@ -430,11 +430,11 @@ class ConvenienceExecuteTest(fixtures.TablesTest): def test_transaction_tlocal_engine_ctx_rollback(self): fn = self._trans_rollback_fn() engine = engines.testing_engine(options=dict( - strategy='threadlocal', + strategy='threadlocal', pool=testing.db.pool)) ctx = engine.begin() assert_raises_message( - Exception, + Exception, "breakage", testing.run_as_contextmanager, ctx, fn, 5, value=8 ) @@ -452,7 +452,7 @@ class ConvenienceExecuteTest(fixtures.TablesTest): conn = testing.db.connect() ctx = conn.begin() assert_raises_message( - Exception, + Exception, "breakage", testing.run_as_contextmanager, ctx, fn, 5, value=8 ) @@ -482,7 +482,7 @@ class ConvenienceExecuteTest(fixtures.TablesTest): def test_transaction_engine_fn_rollback(self): fn = self._trans_rollback_fn() assert_raises_message( - Exception, + Exception, "breakage", testing.db.transaction, fn, 5, value=8 ) @@ -498,7 +498,7 @@ class ConvenienceExecuteTest(fixtures.TablesTest): fn = self._trans_rollback_fn() conn = testing.db.connect() assert_raises( - Exception, + Exception, conn.transaction, fn, 5, value=8 ) self._assert_no_data() @@ -559,7 +559,7 @@ class LogParamsTest(fixtures.TestBase): def test_log_large_dict(self): self.eng.execute( - "INSERT INTO foo (data) values (:data)", + "INSERT INTO foo (data) values (:data)", [{"data":str(i)} for i in xrange(100)] ) eq_( @@ -572,7 +572,7 @@ class LogParamsTest(fixtures.TestBase): def test_log_large_list(self): self.eng.execute( - "INSERT INTO foo (data) values (?)", + "INSERT INTO foo (data) values (?)", [(str(i), ) for i in xrange(100)] ) eq_( @@ -591,7 +591,7 @@ class LogParamsTest(fixtures.TestBase): "{'data': '6'}, {'data': '7'} ... displaying 10 of " "100 total bound parameter sets ... {'data': '98'}, {'data': '99'}\]", lambda: self.eng.execute( - "INSERT INTO nonexistent (data) values (:data)", + "INSERT INTO nonexistent (data) values (:data)", [{"data":str(i)} for i in xrange(100)] ) ) @@ -605,7 +605,7 @@ class LogParamsTest(fixtures.TestBase): "10 of 100 total bound parameter sets ... " "\('98',\), \('99',\)\]", lambda: self.eng.execute( - "INSERT INTO nonexistent (data) values (?)", + "INSERT INTO nonexistent (data) values (?)", [(str(i), ) for i in xrange(100)] ) ) @@ -619,7 +619,7 @@ class LoggingNameTest(fixtures.TestBase): for name in [b.name for b in self.buf.buffer]: assert name in ( 'sqlalchemy.engine.base.Engine.%s' % eng_name, - 'sqlalchemy.pool.%s.%s' % + 'sqlalchemy.pool.%s.%s' % (eng.pool.__class__.__name__, pool_name) ) @@ -801,7 +801,7 @@ class MockStrategyTest(fixtures.TestBase): class ResultProxyTest(fixtures.TestBase): def test_nontuple_row(self): - """ensure the C version of BaseRowProxy handles + """ensure the C version of BaseRowProxy handles duck-type-dependent rows.""" from sqlalchemy.engine import RowProxy @@ -847,9 +847,9 @@ class ResultProxyTest(fixtures.TestBase): assert False execution_ctx_cls = engine.dialect.execution_ctx_cls - engine.dialect.execution_ctx_cls = type("FakeCtx", - (BreakRowcountMixin, - execution_ctx_cls), + engine.dialect.execution_ctx_cls = type("FakeCtx", + (BreakRowcountMixin, + execution_ctx_cls), {}) try: @@ -958,7 +958,7 @@ class AlternateResultProxyTest(fixtures.TestBase): from sqlalchemy.engine import base, default cls.engine = engine = testing_engine('sqlite://') m = MetaData() - cls.table = t = Table('test', m, + cls.table = t = Table('test', m, Column('x', Integer, primary_key=True), Column('y', String(50, convert_unicode='force')) ) @@ -1012,15 +1012,16 @@ class AlternateResultProxyTest(fixtures.TestBase): self._test_proxy(base.BufferedColumnResultProxy) class EngineEventsTest(fixtures.TestBase): - __requires__ = 'ad_hoc_engines', + __requires__ = 'ad_hoc_engines', def tearDown(self): Engine.dispatch._clear() def _assert_stmts(self, expected, received): + orig = list(received) for stmt, params, posn in expected: if not received: - assert False + assert False, "Nothing available for stmt: %s" % stmt while received: teststmt, testparams, testmultiparams = \ received.pop(0) @@ -1072,6 +1073,26 @@ class EngineEventsTest(fixtures.TestBase): eq_(canary, ['be1', 'be3', 'be2', 'be1', 'be3']) + def test_per_connection_plus_engine(self): + canary = [] + def be1(conn, stmt, *arg): + canary.append('be1') + def be2(conn, stmt, *arg): + canary.append('be2') + e1 = testing_engine(config.db_url) + + event.listen(e1, "before_execute", be1) + + conn = e1.connect() + event.listen(conn, "before_execute", be2) + canary[:] = [] + conn.execute(select([1])) + + eq_(canary, ['be2', 'be1']) + + conn._branch().execute(select([1])) + eq_(canary, ['be2', 'be1', 'be2', 'be1']) + def test_argument_format_execute(self): def before_execute(conn, clauseelement, multiparams, params): assert isinstance(multiparams, (list, tuple)) @@ -1115,22 +1136,23 @@ class EngineEventsTest(fixtures.TestBase): params ): stmts.append((str(clauseelement), params, multiparams)) - def cursor_execute(conn, cursor, statement, parameters, + def cursor_execute(conn, cursor, statement, parameters, context, executemany): cursor_stmts.append((str(statement), parameters, None)) for engine in [ - engines.testing_engine(options=dict(implicit_returning=False)), + engines.testing_engine(options=dict(implicit_returning=False)), engines.testing_engine(options=dict(implicit_returning=False, - strategy='threadlocal')) + strategy='threadlocal')), + engines.testing_engine(options=dict(implicit_returning=False)).\ + connect() ]: event.listen(engine, 'before_execute', execute) event.listen(engine, 'before_cursor_execute', cursor_execute) - m = MetaData(engine) - t1 = Table('t1', m, - Column('c1', Integer, primary_key=True), + t1 = Table('t1', m, + Column('c1', Integer, primary_key=True), Column('c2', String(50), default=func.lower('Foo'), primary_key=True) ) @@ -1142,21 +1164,25 @@ class EngineEventsTest(fixtures.TestBase): 'some data'), (6, 'foo')]) finally: m.drop_all() - engine.dispose() + compiled = [('CREATE TABLE t1', {}, None), - ('INSERT INTO t1 (c1, c2)', {'c2': 'some data', - 'c1': 5}, None), ('INSERT INTO t1 (c1, c2)', - {'c1': 6}, None), ('select * from t1', {}, - None), ('DROP TABLE t1', {}, None)] - if not testing.against('oracle+zxjdbc'): # or engine.dialect.pr - # eexecute_pk_sequence - # s: + ('INSERT INTO t1 (c1, c2)', + {'c2': 'some data', 'c1': 5}, None), + ('INSERT INTO t1 (c1, c2)', + {'c1': 6}, None), + ('select * from t1', {}, None), + ('DROP TABLE t1', {}, None)] + + # or engine.dialect.preexecute_pk_sequences: + if not testing.against('oracle+zxjdbc'): cursor = [ ('CREATE TABLE t1', {}, ()), - ('INSERT INTO t1 (c1, c2)', {'c2': 'some data', 'c1' - : 5}, (5, 'some data')), + ('INSERT INTO t1 (c1, c2)', { + 'c2': 'some data', 'c1': 5}, + (5, 'some data')), ('SELECT lower', {'lower_2': 'Foo'}, ('Foo', )), - ('INSERT INTO t1 (c1, c2)', {'c2': 'foo', 'c1': 6}, + ('INSERT INTO t1 (c1, c2)', + {'c2': 'foo', 'c1': 6}, (6, 'foo')), ('select * from t1', {}, ()), ('DROP TABLE t1', {}, ()), @@ -1166,18 +1192,20 @@ class EngineEventsTest(fixtures.TestBase): if testing.against('oracle+zxjdbc'): insert2_params += (ReturningParam(12), ) cursor = [('CREATE TABLE t1', {}, ()), - ('INSERT INTO t1 (c1, c2)', {'c2': 'some data' - , 'c1': 5}, (5, 'some data')), + ('INSERT INTO t1 (c1, c2)', + {'c2': 'some data', 'c1': 5}, (5, 'some data')), ('INSERT INTO t1 (c1, c2)', {'c1': 6, 'lower_2': 'Foo'}, insert2_params), - ('select * from t1', {}, ()), ('DROP TABLE t1' - , {}, ())] # bind param name 'lower_2' might - # be incorrect + ('select * from t1', {}, ()), + ('DROP TABLE t1', {}, ())] + # bind param name 'lower_2' might + # be incorrect self._assert_stmts(compiled, stmts) self._assert_stmts(cursor, cursor_stmts) def test_options(self): canary = [] + def execute(conn, *args, **kw): canary.append('execute') @@ -1206,7 +1234,7 @@ class EngineEventsTest(fixtures.TestBase): canary.append('execute') return clauseelement, multiparams, params - def cursor_execute(conn, cursor, statement, + def cursor_execute(conn, cursor, statement, parameters, context, executemany): canary.append('cursor_execute') return statement, parameters @@ -1255,20 +1283,30 @@ class EngineEventsTest(fixtures.TestBase): @testing.requires.savepoints @testing.requires.two_phase_transactions def test_transactional_advanced(self): - canary = [] - def tracker(name): + canary1 = [] + def tracker1(name): def go(*args, **kw): - canary.append(name) + canary1.append(name) + return go + canary2 = [] + def tracker2(name): + def go(*args, **kw): + canary2.append(name) return go engine = engines.testing_engine() - for name in ['begin', 'savepoint', + for name in ['begin', 'savepoint', 'rollback_savepoint', 'release_savepoint', - 'rollback', 'begin_twophase', + 'rollback', 'begin_twophase', 'prepare_twophase', 'commit_twophase']: - event.listen(engine, '%s' % name, tracker(name)) + event.listen(engine, '%s' % name, tracker1(name)) conn = engine.connect() + for name in ['begin', 'savepoint', + 'rollback_savepoint', 'release_savepoint', + 'rollback', 'begin_twophase', + 'prepare_twophase', 'commit_twophase']: + event.listen(conn, '%s' % name, tracker2(name)) trans = conn.begin() trans2 = conn.begin_nested() @@ -1284,9 +1322,14 @@ class EngineEventsTest(fixtures.TestBase): trans.prepare() trans.commit() - eq_(canary, ['begin', 'savepoint', + eq_(canary1, ['begin', 'savepoint', 'rollback_savepoint', 'savepoint', 'release_savepoint', - 'rollback', 'begin_twophase', + 'rollback', 'begin_twophase', + 'prepare_twophase', 'commit_twophase'] + ) + eq_(canary2, ['begin', 'savepoint', + 'rollback_savepoint', 'savepoint', 'release_savepoint', + 'rollback', 'begin_twophase', 'prepare_twophase', 'commit_twophase'] ) @@ -1296,7 +1339,7 @@ class ProxyConnectionTest(fixtures.TestBase): the deprecated ConnectionProxy interface. """ - __requires__ = 'ad_hoc_engines', + __requires__ = 'ad_hoc_engines', @testing.uses_deprecated(r'.*Use event.listen') @testing.fails_on('firebird', 'Data type unknown') @@ -1332,7 +1375,7 @@ class ProxyConnectionTest(fixtures.TestBase): def assert_stmts(expected, received): for stmt, params, posn in expected: if not received: - assert False + assert False, "Nothing available for stmt: %s" % stmt while received: teststmt, testparams, testmultiparams = \ received.pop(0) @@ -1349,8 +1392,8 @@ class ProxyConnectionTest(fixtures.TestBase): proxy=MyProxy(), strategy='threadlocal')): m = MetaData(engine) - t1 = Table('t1', m, - Column('c1', Integer, primary_key=True), + t1 = Table('t1', m, + Column('c1', Integer, primary_key=True), Column('c2', String(50), default=func.lower('Foo'), primary_key=True) ) @@ -1472,9 +1515,9 @@ class ProxyConnectionTest(fixtures.TestBase): trans.commit() canary = [t for t in canary if t not in ('cursor_execute', 'execute')] - eq_(canary, ['begin', 'savepoint', + eq_(canary, ['begin', 'savepoint', 'rollback_savepoint', 'savepoint', 'release_savepoint', - 'rollback', 'begin_twophase', + 'rollback', 'begin_twophase', 'prepare_twophase', 'commit_twophase'] ) diff --git a/test/lib/engines.py b/test/lib/engines.py index c8a44dc44..e226d11bc 100644 --- a/test/lib/engines.py +++ b/test/lib/engines.py @@ -214,7 +214,7 @@ def utf8_engine(url=None, options=None): if config.db.dialect.name == 'mysql' and \ config.db.driver in ['mysqldb', 'pymysql']: - # note 1.2.1.gamma.6 or greater of MySQLdb + # note 1.2.1.gamma.6 or greater of MySQLdb # needed here url = url or config.db_url url = engine_url.make_url(url) @@ -231,7 +231,7 @@ def mock_engine(dialect_name=None): by an Engine. It should not be used in other cases, as assert_compile() and - assert_sql_execution() are much better choices with fewer + assert_sql_execution() are much better choices with fewer moving parts. """ @@ -250,7 +250,7 @@ def mock_engine(dialect_name=None): def print_sql(): d = engine.dialect return "\n".join( - str(s.compile(dialect=d)) + str(s.compile(dialect=d)) for s in engine.mock ) engine = create_engine(dialect_name + '://', @@ -263,10 +263,10 @@ def mock_engine(dialect_name=None): class DBAPIProxyCursor(object): """Proxy a DBAPI cursor. - + Tests can provide subclasses of this to intercept DBAPI-level cursor operations. - + """ def __init__(self, engine, conn): self.engine = engine @@ -287,10 +287,10 @@ class DBAPIProxyCursor(object): class DBAPIProxyConnection(object): """Proxy a DBAPI connection. - + Tests can provide subclasses of this to intercept DBAPI-level connection operations. - + """ def __init__(self, engine, cursor_cls): self.conn = self._sqla_unwrap = engine.pool._creator() @@ -307,9 +307,9 @@ class DBAPIProxyConnection(object): return getattr(self.conn, key) def proxying_engine(conn_cls=DBAPIProxyConnection, cursor_cls=DBAPIProxyCursor): - """Produce an engine that provides proxy hooks for + """Produce an engine that provides proxy hooks for common methods. - + """ def mock_conn(): return conn_cls(config.db, cursor_cls) @@ -330,7 +330,7 @@ class ReplayableSession(object): # Py3K #Natives = set([getattr(types, t) # for t in dir(types) if not t.startswith('_')]). \ - # union([type(t) if not isinstance(t, type) + # union([type(t) if not isinstance(t, type) # else t for t in __builtins__.values()]).\ # difference([getattr(types, t) # for t in ('FunctionType', 'BuiltinFunctionType', diff --git a/test/orm/test_unitofwork.py b/test/orm/test_unitofwork.py index ddbc159d2..9691c38a6 100644 --- a/test/orm/test_unitofwork.py +++ b/test/orm/test_unitofwork.py @@ -944,13 +944,13 @@ class DefaultTest(fixtures.MappedTest): class ColumnPropertyTest(fixtures.MappedTest): @classmethod def define_tables(cls, metadata): - Table('data', metadata, + Table('data', metadata, Column('id', Integer, primary_key=True, test_needs_autoincrement=True), Column('a', String(50)), Column('b', String(50)) ) - Table('subdata', metadata, + Table('subdata', metadata, Column('id', Integer, ForeignKey('data.id'), primary_key=True), Column('c', String(50)), ) @@ -972,7 +972,7 @@ class ColumnPropertyTest(fixtures.MappedTest): Data, data = self.classes.Data, self.tables.data mapper(Data, data, properties={ - 'aplusb':column_property(data.c.a + literal_column("' '") + data.c.b, + 'aplusb':column_property(data.c.a + literal_column("' '") + data.c.b, expire_on_flush=False) }) self._test(False) @@ -1626,7 +1626,6 @@ class ManyToOneTest(_fixtures.FixtureTest): session.add(a) session.flush() - objects[2].email_address = 'imnew@foo.bar' objects[3].user = User() objects[3].user.name = 'imnewlyadded' @@ -1852,8 +1851,8 @@ class ManyToManyTest(_fixtures.FixtureTest): k.name = 'yellow' objects[5].keywords.append(k) self.assert_sql_execution( - testing.db, - session.flush, + testing.db, + session.flush, AllOf( CompiledSQL("UPDATE items SET description=:description " "WHERE items.id = :items_id", @@ -1875,8 +1874,8 @@ class ManyToManyTest(_fixtures.FixtureTest): dkid = objects[5].keywords[1].id del objects[5].keywords[1] self.assert_sql_execution( - testing.db, - session.flush, + testing.db, + session.flush, CompiledSQL("DELETE FROM item_keywords " "WHERE item_keywords.item_id = :item_id AND " "item_keywords.keyword_id = :keyword_id", @@ -2062,8 +2061,8 @@ class SaveTest2(_fixtures.FixtureTest): session.add_all(fixture()) self.assert_sql_execution( - testing.db, - session.flush, + testing.db, + session.flush, CompiledSQL("INSERT INTO users (name) VALUES (:name)", {'name': 'u1'}), CompiledSQL("INSERT INTO users (name) VALUES (:name)", |