summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2011-01-30 20:29:48 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2011-01-30 20:29:48 -0500
commit12073e281eebdece0fe4e24c6704d57eafdc9247 (patch)
tree1c831d73551c5a2e490bae41af390bd44d6391b5 /lib/sqlalchemy
parent41d222b5f85d81c3cb7c33be284b9b5507463cb2 (diff)
downloadsqlalchemy-12073e281eebdece0fe4e24c6704d57eafdc9247.tar.gz
- SchemaItem, SchemaType now descend from common type
SchemaEventTarget, which supplies dispatch - the dispatch now provides before_parent_attach(), after_parent_attach(), events which generally bound the _set_parent() event. [ticket:2037] - the _on_table_attach mechanism now usually uses the event dispatch - fixed class-level event dispatch to propagate to all subclasses, not just immediate subclasses - fixed class-level event unpickling to handle more involved inheritance hierarchies, needed by the new schema event dispatch. - ForeignKeyConstraint doesn't re-call the column attach event on ForeignKey objects that are already associated with the correct Column - we still need that ImportError on mysqldb CLIENT FLAGS to support mock DBAPIs
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/connectors/mysqldb.py11
-rw-r--r--lib/sqlalchemy/event.py16
-rw-r--r--lib/sqlalchemy/events.py51
-rw-r--r--lib/sqlalchemy/schema.py61
-rw-r--r--lib/sqlalchemy/types.py17
5 files changed, 99 insertions, 57 deletions
diff --git a/lib/sqlalchemy/connectors/mysqldb.py b/lib/sqlalchemy/connectors/mysqldb.py
index 27a56b749..189c412a0 100644
--- a/lib/sqlalchemy/connectors/mysqldb.py
+++ b/lib/sqlalchemy/connectors/mysqldb.py
@@ -87,10 +87,13 @@ class MySQLDBConnector(Connector):
# supports_sane_rowcount.
client_flag = opts.get('client_flag', 0)
if self.dbapi is not None:
- CLIENT_FLAGS = __import__(
- self.dbapi.__name__ + '.constants.CLIENT'
- ).constants.CLIENT
- client_flag |= CLIENT_FLAGS.FOUND_ROWS
+ try:
+ CLIENT_FLAGS = __import__(
+ self.dbapi.__name__ + '.constants.CLIENT'
+ ).constants.CLIENT
+ client_flag |= CLIENT_FLAGS.FOUND_ROWS
+ except (AttributeError, ImportError):
+ pass
opts['client_flag'] = client_flag
return [[], opts]
diff --git a/lib/sqlalchemy/event.py b/lib/sqlalchemy/event.py
index 66b7e2d1b..0fcc8ef49 100644
--- a/lib/sqlalchemy/event.py
+++ b/lib/sqlalchemy/event.py
@@ -48,7 +48,11 @@ class _UnpickleDispatch(object):
"""
def __call__(self, _parent_cls):
- return _parent_cls.__dict__['dispatch'].dispatch_cls(_parent_cls)
+ for cls in _parent_cls.__mro__:
+ if 'dispatch' in cls.__dict__:
+ return cls.__dict__['dispatch'].dispatch_cls(_parent_cls)
+ else:
+ raise AttributeError("No class with a 'dispatch' member present.")
class _Dispatch(object):
"""Mirror the event listening definitions of an Events class with
@@ -167,11 +171,17 @@ class _DispatchDescriptor(object):
assert isinstance(target, type), \
"Class-level Event targets must be classes."
- for cls in [target] + target.__subclasses__():
+ stack = [target]
+ while stack:
+ cls = stack.pop(0)
+ stack.extend(cls.__subclasses__())
self._clslevel[cls].append(obj)
def remove(self, obj, target):
- for cls in [target] + target.__subclasses__():
+ stack = [target]
+ while stack:
+ cls = stack.pop(0)
+ stack.extend(cls.__subclasses__())
self._clslevel[cls].remove(obj)
def clear(self):
diff --git a/lib/sqlalchemy/events.py b/lib/sqlalchemy/events.py
index c1f10977d..8a2776898 100644
--- a/lib/sqlalchemy/events.py
+++ b/lib/sqlalchemy/events.py
@@ -10,12 +10,22 @@ from sqlalchemy import event, exc
class DDLEvents(event.Events):
"""
- Define create/drop event listers for schema objects.
-
- These events currently apply to :class:`.Table`
- and :class:`.MetaData` objects as targets.
-
- e.g.::
+ Define event listeners for schema objects,
+ 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.
+
+ 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
+ with its :class:`.Table`, when a :class:`.ForeignKeyConstraint`
+ is associated with a :class:`.Table`, etc.
+
+ Example using the ``after_create`` event::
from sqlalchemy import event
from sqlalchemy import Table, Column, Metadata, Integer
@@ -117,6 +127,35 @@ class DDLEvents(event.Events):
"""
+ def before_parent_attach(self, target, parent):
+ """Called before a :class:`.SchemaItem` is associated with
+ a parent :class:`.SchemaItem`.
+
+ """
+
+ def after_parent_attach(self, target, parent):
+ """Called after a :class:`.SchemaItem` is associated with
+ a parent :class:`.SchemaItem`.
+
+ """
+
+class SchemaEventTarget(object):
+ """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)
+
+ def _set_parent(self, parent):
+ """Associate with this SchemaEvent's parent 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)
class PoolEvents(event.Events):
"""Available events for :class:`.Pool`.
diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py
index cfed00610..9cf5c7f14 100644
--- a/lib/sqlalchemy/schema.py
+++ b/lib/sqlalchemy/schema.py
@@ -48,7 +48,7 @@ __all__.sort()
RETAIN_SCHEMA = util.symbol('retain_schema')
-class SchemaItem(visitors.Visitable):
+class SchemaItem(events.SchemaEventTarget, visitors.Visitable):
"""Base class for items that define a database schema."""
__visit_name__ = 'schema_item'
@@ -59,12 +59,7 @@ class SchemaItem(visitors.Visitable):
for item in args:
if item is not None:
- item._set_parent(self)
-
- def _set_parent(self, parent):
- """Associate with this SchemaItem's parent object."""
-
- raise NotImplementedError()
+ item._set_parent_with_dispatch(self)
def get_children(self, **kwargs):
"""used to allow SchemaVisitor access"""
@@ -177,8 +172,6 @@ class Table(SchemaItem, expression.TableClause):
__visit_name__ = 'table'
- dispatch = event.dispatcher(events.DDLEvents)
-
def __new__(cls, *args, **kw):
if not args:
# python3k pickle seems to call this
@@ -207,9 +200,11 @@ class Table(SchemaItem, expression.TableClause):
raise exc.InvalidRequestError(
"Table '%s' not defined" % (key))
table = object.__new__(cls)
+ table.dispatch.before_parent_attach(table, metadata)
metadata._add_table(name, schema, table)
try:
table._init(name, metadata, *args, **kw)
+ table.dispatch.after_parent_attach(table, metadata)
return table
except:
metadata._remove_table(name, schema)
@@ -365,12 +360,12 @@ class Table(SchemaItem, expression.TableClause):
def append_column(self, column):
"""Append a ``Column`` to this ``Table``."""
- column._set_parent(self)
+ column._set_parent_with_dispatch(self)
def append_constraint(self, constraint):
"""Append a ``Constraint`` to this ``Table``."""
- constraint._set_parent(self)
+ constraint._set_parent_with_dispatch(self)
def append_ddl_listener(self, event_name, listener):
"""Append a DDL event listener to this ``Table``.
@@ -708,14 +703,13 @@ class Column(SchemaItem, expression.ColumnClause):
self.autoincrement = kwargs.pop('autoincrement', True)
self.constraints = set()
self.foreign_keys = set()
- self._table_events = set()
# check if this Column is proxying another column
if '_proxies' in kwargs:
self.proxies = kwargs.pop('_proxies')
# otherwise, add DDL-related events
elif isinstance(self.type, types.SchemaType):
- self.type._set_parent(self)
+ self.type._set_parent_with_dispatch(self)
if self.default is not None:
if isinstance(self.default, (ColumnDefault, Sequence)):
@@ -777,7 +771,7 @@ class Column(SchemaItem, expression.ColumnClause):
return False
def append_foreign_key(self, fk):
- fk._set_parent(self)
+ fk._set_parent_with_dispatch(self)
def __repr__(self):
kwarg = []
@@ -851,15 +845,10 @@ class Column(SchemaItem, expression.ColumnClause):
"Index object external to the Table.")
table.append_constraint(UniqueConstraint(self.key))
- for fn in self._table_events:
- fn(table, self)
- del self._table_events
-
def _on_table_attach(self, fn):
if self.table is not None:
- fn(self.table, self)
- else:
- self._table_events.add(fn)
+ fn(self, self.table)
+ event.listen(self, 'after_parent_attach', fn)
def copy(self, **kw):
"""Create a copy of this ``Column``, unitialized.
@@ -873,7 +862,7 @@ class Column(SchemaItem, expression.ColumnClause):
[c.copy(**kw) for c in self.constraints] + \
[c.copy(**kw) for c in self.foreign_keys if not c.constraint]
- c = Column(
+ return Column(
name=self.name,
type_=self.type,
key = self.key,
@@ -891,9 +880,6 @@ class Column(SchemaItem, expression.ColumnClause):
doc=self.doc,
*args
)
- if hasattr(self, '_table_events'):
- c._table_events = list(self._table_events)
- return c
def _make_proxy(self, selectable, name=None):
"""Create a *proxy* for this column.
@@ -920,9 +906,7 @@ class Column(SchemaItem, expression.ColumnClause):
selectable._columns.add(c)
if self.primary_key:
selectable.primary_key.add(c)
- for fn in c._table_events:
- fn(selectable, c)
- del c._table_events
+ c.dispatch.after_parent_attach(c, selectable)
return c
def get_children(self, schema_visitor=False, **kwargs):
@@ -1210,7 +1194,7 @@ class ForeignKey(SchemaItem):
self.parent.foreign_keys.add(self)
self.parent._on_table_attach(self._set_table)
- def _set_table(self, table, column):
+ def _set_table(self, column, table):
# standalone ForeignKey - create ForeignKeyConstraint
# on the hosting Table when attached to the Table.
if self.constraint is None and isinstance(table, Table):
@@ -1220,7 +1204,7 @@ class ForeignKey(SchemaItem):
deferrable=self.deferrable, initially=self.initially,
)
self.constraint._elements[self.parent] = self
- self.constraint._set_parent(table)
+ self.constraint._set_parent_with_dispatch(table)
table.foreign_keys.add(self)
class DefaultGenerator(SchemaItem):
@@ -1382,7 +1366,7 @@ class Sequence(DefaultGenerator):
super(Sequence, self)._set_parent(column)
column._on_table_attach(self._set_table)
- def _set_table(self, table, column):
+ def _set_table(self, column, table):
self.metadata = table.metadata
@property
@@ -1407,7 +1391,7 @@ class Sequence(DefaultGenerator):
bind.drop(self, checkfirst=checkfirst)
-class FetchedValue(object):
+class FetchedValue(events.SchemaEventTarget):
"""A marker for a transparent database-side default.
Use :class:`.FetchedValue` when the database is configured
@@ -1556,7 +1540,7 @@ class ColumnCollectionMixin(object):
if self._pending_colargs and \
isinstance(self._pending_colargs[0], Column) and \
self._pending_colargs[0].table is not None:
- self._set_parent(self._pending_colargs[0].table)
+ self._set_parent_with_dispatch(self._pending_colargs[0].table)
def _set_parent(self, table):
for col in self._pending_colargs:
@@ -1643,7 +1627,7 @@ class CheckConstraint(Constraint):
__init__(name, deferrable, initially, _create_rule)
self.sqltext = expression._literal_as_text(sqltext)
if table is not None:
- self._set_parent(table)
+ self._set_parent_with_dispatch(table)
def __visit_name__(self):
if isinstance(self.parent, Table):
@@ -1744,7 +1728,7 @@ class ForeignKeyConstraint(Constraint):
)
if table is not None:
- self._set_parent(table)
+ self._set_parent_with_dispatch(table)
@property
def columns(self):
@@ -1761,7 +1745,10 @@ class ForeignKeyConstraint(Constraint):
# resolved to Column objects
if isinstance(col, basestring):
col = table.c[col]
- fk._set_parent(col)
+
+ if not hasattr(fk, 'parent') or \
+ fk.parent is not col:
+ fk._set_parent_with_dispatch(col)
if self.use_alter:
def supports_alter(ddl, event, schema_item, bind, **kw):
@@ -1924,8 +1911,6 @@ class MetaData(SchemaItem):
__visit_name__ = 'metadata'
- dispatch = event.dispatcher(events.DDLEvents)
-
def __init__(self, bind=None, reflect=False):
"""Create a new MetaData object.
diff --git a/lib/sqlalchemy/types.py b/lib/sqlalchemy/types.py
index f9320cf3e..3a1415796 100644
--- a/lib/sqlalchemy/types.py
+++ b/lib/sqlalchemy/types.py
@@ -32,7 +32,7 @@ from sqlalchemy.util import pickle
from sqlalchemy.util.compat import decimal
from sqlalchemy.sql.visitors import Visitable
from sqlalchemy import util
-from sqlalchemy import processors
+from sqlalchemy import processors, events
import collections
default = util.importlater("sqlalchemy.engine", "default")
@@ -1391,12 +1391,17 @@ class Binary(LargeBinary):
'LargeBinary.')
LargeBinary.__init__(self, *arg, **kw)
-class SchemaType(object):
+class SchemaType(events.SchemaEventTarget):
"""Mark a type as possibly requiring schema-level DDL for usage.
Supports types that must be explicitly created/dropped (i.e. PG ENUM type)
as well as types that are complimented by table or schema level
constraints, triggers, and other rules.
+
+ :class:`.SchemaType` classes can also be targets for the
+ :meth:`.DDLEvents.before_parent_attach` and :meth:`.DDLEvents.after_parent_attach`
+ events, where the events fire off surrounding the association of
+ the type object with a parent :class:`.Column`.
"""
@@ -1414,7 +1419,7 @@ class SchemaType(object):
def _set_parent(self, column):
column._on_table_attach(util.portable_instancemethod(self._set_table))
- def _set_table(self, table, column):
+ def _set_table(self, column, table):
table.append_ddl_listener('before-create',
util.portable_instancemethod(
self._on_table_create))
@@ -1550,9 +1555,9 @@ class Enum(String, SchemaType):
return not self.native_enum or \
not compiler.dialect.supports_native_enum
- def _set_table(self, table, column):
+ def _set_table(self, column, table):
if self.native_enum:
- SchemaType._set_table(self, table, column)
+ SchemaType._set_table(self, column, table)
e = schema.CheckConstraint(
@@ -1713,7 +1718,7 @@ class Boolean(TypeEngine, SchemaType):
def _should_create_constraint(self, compiler):
return not compiler.dialect.supports_native_boolean
- def _set_table(self, table, column):
+ def _set_table(self, column, table):
if not self.create_constraint:
return