diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-11-10 19:00:28 -0500 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-11-10 19:00:28 -0500 |
commit | 3564ea86e7cd982a353b42be4105a40bdf9415a3 (patch) | |
tree | b1c9369a285e6e290e70c7817869c9326db9a810 /lib | |
parent | a9b270a3ed4faf85f772897a867caf6762ff9160 (diff) | |
download | sqlalchemy-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.py | 49 | ||||
-rw-r--r-- | lib/sqlalchemy/events.py | 67 | ||||
-rw-r--r-- | lib/sqlalchemy/interfaces.py | 6 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/events.py | 2 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/scoping.py | 86 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/util.py | 72 | ||||
-rw-r--r-- | lib/sqlalchemy/schema.py | 58 |
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. |