summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2010-11-10 19:00:28 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2010-11-10 19:00:28 -0500
commit3564ea86e7cd982a353b42be4105a40bdf9415a3 (patch)
treeb1c9369a285e6e290e70c7817869c9326db9a810 /lib
parenta9b270a3ed4faf85f772897a867caf6762ff9160 (diff)
downloadsqlalchemy-3564ea86e7cd982a353b42be4105a40bdf9415a3.tar.gz
- move deprecated interfaces down to bottom of TOC, update verbiage
- more docs for engine, pool, DDL events - update DDL sequences documentation to use events - update DDL() docstring to refer to execute_if() - document parameters for DDLElement.execute_if() - add retval=True flag to Engine.on_before_execute(), on_before_cursor_execute(). wrap the function if retval=False, check for appropriate usage of the flag, add tests. - remove ScopedSession.mapper and tests entirely - remove ExtensionCarrier and tests - change remaining tests that use MapperExtension to use MapperEvents
Diffstat (limited to 'lib')
-rw-r--r--lib/sqlalchemy/engine/base.py49
-rw-r--r--lib/sqlalchemy/events.py67
-rw-r--r--lib/sqlalchemy/interfaces.py6
-rw-r--r--lib/sqlalchemy/orm/events.py2
-rw-r--r--lib/sqlalchemy/orm/scoping.py86
-rw-r--r--lib/sqlalchemy/orm/util.py72
-rw-r--r--lib/sqlalchemy/schema.py58
7 files changed, 136 insertions, 204 deletions
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py
index ba4ef6037..9d80af7a7 100644
--- a/lib/sqlalchemy/engine/base.py
+++ b/lib/sqlalchemy/engine/base.py
@@ -1887,49 +1887,50 @@ def _listener_connection_cls(cls, dispatch):
"""
class EventListenerConnection(cls):
def execute(self, clauseelement, *multiparams, **params):
- if dispatch.on_before_execute:
- for fn in dispatch.on_before_execute:
- result = fn(self, clauseelement, multiparams, params)
- if result:
- clauseelement, multiparams, params = result
+ for fn in dispatch.on_before_execute:
+ clauseelement, multiparams, params = \
+ fn(self, clauseelement, multiparams, params)
- ret = super(EventListenerConnection, self).execute(clauseelement, *multiparams, **params)
+ ret = super(EventListenerConnection, self).\
+ execute(clauseelement, *multiparams, **params)
- if dispatch.on_after_execute:
- for fn in dispatch.on_after_execute:
- fn(self, clauseelement, multiparams, params, ret)
+ for fn in dispatch.on_after_execute:
+ fn(self, clauseelement, multiparams, params, ret)
return ret
- def _execute_clauseelement(self, clauseelement, multiparams=None, params=None):
- return self.execute(clauseelement, *(multiparams or []), **(params or {}))
+ def _execute_clauseelement(self, clauseelement,
+ multiparams=None, params=None):
+ return self.execute(clauseelement,
+ *(multiparams or []),
+ **(params or {}))
def _cursor_execute(self, cursor, statement,
parameters, context=None):
- if dispatch.on_before_cursor_execute:
- for fn in dispatch.on_before_cursor_execute:
- result = fn(self, cursor, statement, parameters, context, False)
- if result:
- statement, parameters = result
+ for fn in dispatch.on_before_cursor_execute:
+ statement, parameters = \
+ fn(self, cursor, statement, parameters,
+ context, False)
ret = super(EventListenerConnection, self).\
- _cursor_execute(cursor, statement, parameters, context)
+ _cursor_execute(cursor, statement, parameters,
+ context)
- if dispatch.on_after_cursor_execute:
- for fn in dispatch.on_after_cursor_execute:
- fn(self, cursor, statement, parameters, context, False)
+ for fn in dispatch.on_after_cursor_execute:
+ fn(self, cursor, statement, parameters, context, False)
return ret
def _cursor_executemany(self, cursor, statement,
parameters, context=None):
for fn in dispatch.on_before_cursor_execute:
- result = fn(self, cursor, statement, parameters, context, True)
- if result:
- statement, parameters = result
+ statement, parameters = \
+ fn(self, cursor, statement, parameters,
+ context, True)
ret = super(EventListenerConnection, self).\
- _cursor_executemany(cursor, statement, parameters, context)
+ _cursor_executemany(cursor, statement,
+ parameters, context)
for fn in dispatch.on_after_cursor_execute:
fn(self, cursor, statement, parameters, context, True)
diff --git a/lib/sqlalchemy/events.py b/lib/sqlalchemy/events.py
index 355cf2235..e9763e075 100644
--- a/lib/sqlalchemy/events.py
+++ b/lib/sqlalchemy/events.py
@@ -1,6 +1,6 @@
"""Core event interfaces."""
-from sqlalchemy import event
+from sqlalchemy import event, exc
class DDLEvents(event.Events):
"""
@@ -8,7 +8,9 @@ class DDLEvents(event.Events):
See also:
- :mod:`sqlalchemy.event`
+ :ref:`event_toplevel`
+
+ :ref:`schema_ddl_sequences`
"""
@@ -34,18 +36,23 @@ class PoolEvents(event.Events):
e.g.::
- from sqlalchemy import events
+ from sqlalchemy import event
def my_on_checkout(dbapi_conn, connection_rec, connection_proxy):
"handle an on checkout event"
events.listen(my_on_checkout, 'on_checkout', Pool)
- In addition to the :class:`.Pool` class and :class:`.Pool` instances,
+ 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.
+ class::
+
+ engine = create_engine("postgresql://scott:tiger@localhost/test")
+
+ # will associate with engine.pool
+ events.listen(my_on_checkout, 'on_checkout', engine)
"""
@@ -123,16 +130,62 @@ class PoolEvents(event.Events):
"""
class EngineEvents(event.Events):
- """Available events for :class:`.Engine`."""
+ """Available events for :class:`.Engine`.
+
+ The methods here define the name of an event as well as the names of members that are passed to listener functions.
+
+ e.g.::
+
+ from sqlalchemy import event, create_engine
+
+ def on_before_execute(conn, clauseelement, multiparams, params):
+ log.info("Received statement: %s" % clauseelement)
+
+ engine = create_engine('postgresql://scott:tiger@localhost/test')
+ event.listen(on_before_execute, "on_before_execute", engine)
+
+ Some events allow modifiers to the listen() function.
+
+ :param retval=False: Applies to the :meth:`.on_before_execute` and
+ :meth:`.on_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
+ and parameters. See those methods for a description of
+ specific return arguments.
+
+ """
@classmethod
- def listen(cls, fn, identifier, target):
+ def listen(cls, fn, identifier, target, retval=False):
from sqlalchemy.engine.base import Connection, \
_listener_connection_cls
if target.Connection is Connection:
target.Connection = _listener_connection_cls(
Connection,
target.dispatch)
+
+ if not retval:
+ if identifier == 'on_before_execute':
+ orig_fn = fn
+ def wrap(conn, clauseelement, multiparams, params):
+ orig_fn(conn, clauseelement, multiparams, params)
+ return clauseelement, multiparams, params
+ fn = wrap
+ elif identifier == 'on_before_cursor_execute':
+ orig_fn = fn
+ def wrap(conn, cursor, statement,
+ parameters, context, executemany):
+ orig_fn(conn, cursor, statement,
+ parameters, context, executemany)
+ return statement, parameters
+ fn = wrap
+
+ elif retval and identifier not in ('on_before_execute', 'on_before_cursor_execute'):
+ raise exc.ArgumentError(
+ "Only the 'on_before_execute' and "
+ "'on_before_cursor_execute' engine "
+ "event listeners accept the 'retval=True' "
+ "argument.")
event.Events.listen(fn, identifier, target)
def on_before_execute(self, conn, clauseelement, multiparams, params):
diff --git a/lib/sqlalchemy/interfaces.py b/lib/sqlalchemy/interfaces.py
index 6cabcce29..a4398ecf5 100644
--- a/lib/sqlalchemy/interfaces.py
+++ b/lib/sqlalchemy/interfaces.py
@@ -17,8 +17,7 @@ class PoolListener(object):
"""Hooks into the lifecycle of connections in a :class:`Pool`.
.. note:: :class:`PoolListener` is deprecated. Please
- refer to :func:`event.listen` as well as
- :class:`.PoolEvents`.
+ refer to :class:`.PoolEvents`.
Usage::
@@ -150,8 +149,7 @@ class ConnectionProxy(object):
"""Allows interception of statement execution by Connections.
.. note:: :class:`ConnectionProxy` is deprecated. Please
- refer to :func:`event.listen` as well as
- :attr:`.Engine.events`.
+ refer to :class:`.EngineEvents`.
Either or both of the ``execute()`` and ``cursor_execute()``
may be implemented to intercept compiled statement and
diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py
index 77af97b5f..2ad7b806c 100644
--- a/lib/sqlalchemy/orm/events.py
+++ b/lib/sqlalchemy/orm/events.py
@@ -160,7 +160,7 @@ class MapperEvents(event.Events):
:param raw=False: When True, the "target" argument to the
event, if applicable will be the :class:`.InstanceState` management
object, rather than the mapped instance itself.
- :param retval=False: when True, the user-defined event listening
+ :param retval=False: when True, the user-defined event function
must have a return value, the purpose of which is either to
control subsequent event propagation, or to otherwise alter
the operation in progress by the mapper. Possible values
diff --git a/lib/sqlalchemy/orm/scoping.py b/lib/sqlalchemy/orm/scoping.py
index 140328e24..53336e5f4 100644
--- a/lib/sqlalchemy/orm/scoping.py
+++ b/lib/sqlalchemy/orm/scoping.py
@@ -5,12 +5,8 @@
# the MIT License: http://www.opensource.org/licenses/mit-license.php
import sqlalchemy.exceptions as sa_exc
-from sqlalchemy.util import ScopedRegistry, ThreadLocalRegistry, \
- to_list, get_cls_kwargs, deprecated,\
- warn
-from sqlalchemy.orm import (
- EXT_CONTINUE, MapperExtension, class_mapper, object_session
- )
+from sqlalchemy.util import ScopedRegistry, ThreadLocalRegistry, warn
+from sqlalchemy.orm import class_mapper
from sqlalchemy.orm import exc as orm_exc
from sqlalchemy.orm.session import Session
@@ -39,7 +35,6 @@ class ScopedSession(object):
self.registry = ScopedRegistry(session_factory, scopefunc)
else:
self.registry = ThreadLocalRegistry(session_factory)
- self.extension = _ScopedExt(self)
def __call__(self, **kwargs):
if kwargs:
@@ -64,27 +59,6 @@ class ScopedSession(object):
self.registry().close()
self.registry.clear()
- @deprecated("0.5", ":meth:`.ScopedSession.mapper` is deprecated. "
- "Please see http://www.sqlalchemy.org/trac/wiki/UsageRecipes/SessionAwareMapper "
- "for information on how to replicate its behavior.")
- def mapper(self, *args, **kwargs):
- """return a :func:`.mapper` function which associates this ScopedSession with the Mapper.
-
- """
-
- from sqlalchemy.orm import mapper
-
- extension_args = dict((arg, kwargs.pop(arg))
- for arg in get_cls_kwargs(_ScopedExt)
- if arg in kwargs)
-
- kwargs['extension'] = extension = to_list(kwargs.get('extension', []))
- if extension_args:
- extension.append(self.extension.configure(**extension_args))
- else:
- extension.append(self.extension)
- return mapper(*args, **kwargs)
-
def configure(self, **kwargs):
"""reconfigure the sessionmaker used by this ScopedSession."""
@@ -157,59 +131,3 @@ def clslevel(name):
for prop in ('close_all', 'object_session', 'identity_key'):
setattr(ScopedSession, prop, clslevel(prop))
-class _ScopedExt(MapperExtension):
- def __init__(self, context, validate=False, save_on_init=True):
- self.context = context
- self.validate = validate
- self.save_on_init = save_on_init
- self.set_kwargs_on_init = True
-
- def validating(self):
- return _ScopedExt(self.context, validate=True)
-
- def configure(self, **kwargs):
- return _ScopedExt(self.context, **kwargs)
-
- def instrument_class(self, mapper, class_):
- class query(object):
- def __getattr__(s, key):
- return getattr(self.context.registry().query(class_), key)
- def __call__(s):
- return self.context.registry().query(class_)
- def __get__(self, instance, cls):
- return self
-
- if not 'query' in class_.__dict__:
- class_.query = query()
-
- if self.set_kwargs_on_init and class_.__init__ is object.__init__:
- class_.__init__ = self._default__init__(mapper)
-
- def _default__init__(ext, mapper):
- def __init__(self, **kwargs):
- for key, value in kwargs.iteritems():
- if ext.validate:
- if not mapper.get_property(key, resolve_synonyms=False,
- raiseerr=False):
- raise sa_exc.ArgumentError(
- "Invalid __init__ argument: '%s'" % key)
- setattr(self, key, value)
- return __init__
-
- def init_instance(self, mapper, class_, oldinit, instance, args, kwargs):
- if self.save_on_init:
- session = kwargs.pop('_sa_session', None)
- if session is None:
- session = self.context.registry()
- session._save_without_cascade(instance)
- return EXT_CONTINUE
-
- def init_failed(self, mapper, class_, oldinit, instance, args, kwargs):
- sess = object_session(instance)
- if sess:
- sess.expunge(instance)
- return EXT_CONTINUE
-
- def dispose_class(self, mapper, class_):
- if hasattr(class_, 'query'):
- delattr(class_, 'query')
diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py
index 8ec161e99..c2b79666c 100644
--- a/lib/sqlalchemy/orm/util.py
+++ b/lib/sqlalchemy/orm/util.py
@@ -185,78 +185,6 @@ def identity_key(*args, **kwargs):
mapper = object_mapper(instance)
return mapper.identity_key_from_instance(instance)
-class ExtensionCarrier(dict):
- """Fronts an ordered collection of MapperExtension objects.
-
- Bundles multiple MapperExtensions into a unified callable unit,
- encapsulating ordering, looping and EXT_CONTINUE logic. The
- ExtensionCarrier implements the MapperExtension interface, e.g.::
-
- carrier.after_insert(...args...)
-
- The dictionary interface provides containment for implemented
- method names mapped to a callable which executes that method
- for participating extensions.
-
- """
-
- interface = set(method for method in dir(MapperExtension)
- if not method.startswith('_'))
-
- def __init__(self, extensions=None):
- self._extensions = []
- for ext in extensions or ():
- self.append(ext)
-
- def copy(self):
- return ExtensionCarrier(self._extensions)
-
- def push(self, extension):
- """Insert a MapperExtension at the beginning of the collection."""
- self._register(extension)
- self._extensions.insert(0, extension)
-
- def append(self, extension):
- """Append a MapperExtension at the end of the collection."""
- self._register(extension)
- self._extensions.append(extension)
-
- def __iter__(self):
- """Iterate over MapperExtensions in the collection."""
- return iter(self._extensions)
-
- def _register(self, extension):
- """Register callable fronts for overridden interface methods."""
-
- for method in self.interface.difference(self):
- impl = getattr(extension, method, None)
- if impl and impl is not getattr(MapperExtension, method):
- self[method] = self._create_do(method)
-
- def _create_do(self, method):
- """Return a closure that loops over impls of the named method."""
-
- def _do(*args, **kwargs):
- for ext in self._extensions:
- ret = getattr(ext, method)(*args, **kwargs)
- if ret is not EXT_CONTINUE:
- return ret
- else:
- return EXT_CONTINUE
- _do.__name__ = method
- return _do
-
- @staticmethod
- def _pass(*args, **kwargs):
- return EXT_CONTINUE
-
- def __getattr__(self, key):
- """Delegate MapperExtension methods to bundled fronts."""
-
- if key not in self.interface:
- raise AttributeError(key)
- return self.get(key, self._pass)
-
class ORMAdapter(sql_util.ColumnAdapter):
"""Extends ColumnAdapter to accept ORM entities.
diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py
index 6966fb90b..fcd9ae3f4 100644
--- a/lib/sqlalchemy/schema.py
+++ b/lib/sqlalchemy/schema.py
@@ -2238,7 +2238,7 @@ class DDLElement(expression.Executable, expression.ClauseElement):
"DDL execution skipped, criteria not met.")
@util.deprecated("0.7", "See :class:`.DDLEvents`, as well as "
- ":meth:`.DDLEvent.execute_if`.")
+ ":meth:`.DDLElement.execute_if`.")
def execute_at(self, event_name, target):
"""Link execution of this DDL to the DDL lifecycle of a SchemaItem.
@@ -2292,6 +2292,38 @@ class DDLElement(expression.Executable, expression.ClauseElement):
'on_before_create',
metadata
)
+
+ :param dialect: May be a string, tuple or a callable
+ predicate. If a string, it will be compared to the name of the
+ executing database dialect::
+
+ DDL('something').execute_if(dialect='postgresql')
+
+ If a tuple, specifies multiple dialect names::
+
+ DDL('something').execute_if(dialect=('postgresql', 'mysql'))
+
+ :param callable_: A callable, which will be invoked with
+ four positional arguments as well as optional keyword
+ arguments:
+
+ :ddl:
+ This DDL element.
+
+ :target:
+ The :class:`.Table` or :class:`.MetaData` object which is the target of
+ this event. May be None if the DDL is executed explicitly.
+
+ :bind:
+ The :class:`.Connection` being used for DDL execution
+
+ :tables:
+ Optional keyword argument - a list of Table objects which are to
+ be created/ dropped within a MetaData.create_all() or drop_all()
+ method call.
+
+ If the callable returns a true value, the DDL statement will be
+ executed.
See also:
@@ -2365,19 +2397,21 @@ class DDLElement(expression.Executable, expression.ClauseElement):
class DDL(DDLElement):
"""A literal DDL statement.
- Specifies literal SQL DDL to be executed by the database. DDL objects can
- be attached to ``Tables`` or ``MetaData`` instances, conditionally
- executing SQL as part of the DDL lifecycle of those schema items. Basic
- templating support allows a single DDL instance to handle repetitive tasks
- for multiple tables.
+ Specifies literal SQL DDL to be executed by the database. DDL objects
+ function as DDL event listeners, and can be subscribed to those events
+ listed in :ref:`.DDLEvents`, using either :class:`.Table` or :class:`.MetaData`
+ objects as targets. Basic templating support allows a single DDL instance
+ to handle repetitive tasks for multiple tables.
Examples::
+
+ from sqlalchemy import event, DDL
+
+ tbl = Table('users', metadata, Column('uid', Integer))
+ event.listen(DDL('DROP TRIGGER users_trigger'), 'on_before_create', tbl)
- tbl = Table('users', metadata, Column('uid', Integer)) # ...
- DDL('DROP TRIGGER users_trigger').execute_at('before-create', tbl)
-
- spow = DDL('ALTER TABLE %(table)s SET secretpowers TRUE', on='somedb')
- spow.execute_at('after-create', tbl)
+ spow = DDL('ALTER TABLE %(table)s SET secretpowers TRUE')
+ event.listen(spow.execute_if(dialect='somedb'), 'on_after_create', tbl)
drop_spow = DDL('ALTER TABLE users SET secretpowers FALSE')
connection.execute(drop_spow)
@@ -2389,7 +2423,7 @@ class DDL(DDLElement):
%(schema)s - the schema name, with any required quoting applied
%(fullname)s - the Table name including schema, quoted if needed
- The DDL's ``context``, if any, will be combined with the standard
+ The DDL's "context", if any, will be combined with the standard
substutions noted above. Keys present in the context will override
the standard substitutions.