From: Mike Bayer Date: Sat, 9 Dec 2006 04:00:35 +0000 (+0000) Subject: - added onupdate and ondelete keyword arguments to ForeignKey; propigate X-Git-Tag: rel_0_3_2~8 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f279e2b1f24bd6efc7542707707ea9724f679e65;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - added onupdate and ondelete keyword arguments to ForeignKey; propigate to underlying ForeignKeyConstraint if present. (dont propigate in the other direction, however) - patched attribute speed enhancement [ticket:389] courtesy Sébastien Lelong --- diff --git a/CHANGES b/CHANGES index 21a2307cd1..12e101670b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,12 +1,14 @@ 0.3.2 - major connection pool bug fixed. fixes MySQL out of sync errors, will also prevent transactions getting rolled back -accidentally [ticket:387] -- major speed enhancements vs. 0.3.1 +accidentally in all DBs [ticket:387] +- major speed enhancements vs. 0.3.1, to bring speed +back to 0.2.8 levels - made conditional dozens of debug log calls that were time-intensive to generate log messages - fixed bug in cascade rules whereby the entire object graph could be unnecessarily cascaded on the save/update cascade + - various speedups in attributes module - identity map in Session is by default *no longer weak referencing*. to have it be weak referencing, use create_session(weak_identity_map=True) - MySQL detects errors 2006 (server has gone away) and 2014 @@ -18,6 +20,9 @@ to have it be weak referencing, use create_session(weak_identity_map=True) [changeset:2110] - added label() function to Select class, when scalar=True is used to create a scalar subquery. +- added onupdate and ondelete keyword arguments to ForeignKey; propigate +to underlying ForeignKeyConstraint if present. (dont propigate in the +other direction, however) - fix to session.update() to preserve "dirty" status of incoming object - sending a selectable to an IN no longer creates a "union" out of multiple selects; only one selectable to an IN is allowed now (make a union yourself @@ -25,10 +30,9 @@ if union is needed; explicit better than implicit, dont guess, etc.) - improved support for disabling save-update cascade via cascade="none" etc. - added "remote_side" argument to relation(), used only with self-referential mappers to force the direction of the parent/child relationship. replaces -the usage of the "foreignkey" parameter for "switching" the direction; -while "foreignkey" can still be used to "switch" the direction of a parent/ -child relationship, this usage is deprecated; "foreignkey" should always -indicate the actual foreign key columns from now on. +the usage of the "foreignkey" parameter for "switching" the direction. +"foreignkey" argument is deprecated for all uses and will eventually +be replaced by an argument dedicated to ForeignKey specification; 0.3.1 - Engine/Pool: diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 915ccd209f..17f9096130 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -609,8 +609,19 @@ class AttributeHistory(object): class AttributeManager(object): """allows the instrumentation of object attributes. AttributeManager is stateless, but can be - overridden by subclasses to redefine some of its factory operations.""" - + overridden by subclasses to redefine some of its factory operations. Also be aware AttributeManager + will cache attributes for a given class, allowing not to determine those for each objects (used + in managed_attributes() and noninherited_managed_attributes()). This cache is cleared for a given class + while calling register_attribute(), and can be cleared using clear_attribute_cache()""" + + def __init__(self): + # will cache attributes, indexed by class objects + self._inherited_attribute_cache = weakref.WeakKeyDictionary() + self._noninherited_attribute_cache = weakref.WeakKeyDictionary() + + def clear_attribute_cache(self): + self._attribute_cache.clear() + def rollback(self, *obj): """retrieves the committed history for each object in the given list, and rolls back the attributes each instance to their original value.""" @@ -636,24 +647,28 @@ class AttributeManager(object): for o in obj: o._state['original'] = CommittedState(self, o) o._state['modified'] = False - + def managed_attributes(self, class_): """returns an iterator of all InstrumentedAttribute objects associated with the given class.""" - if not isinstance(class_, type): - raise repr(class_) + " is not a type" - for key in dir(class_): - value = getattr(class_, key, None) - if isinstance(value, InstrumentedAttribute): - yield value + try: + return self._inherited_attribute_cache[class_] + except KeyError: + if not isinstance(class_, type): + raise TypeError(repr(class_) + " is not a type") + inherited = [v for v in [getattr(class_, key, None) for key in dir(class_)] if isinstance(v, InstrumentedAttribute)] + self._inherited_attribute_cache[class_] = inherited + return inherited def noninherited_managed_attributes(self, class_): - if not isinstance(class_, type): - raise repr(class_) + " is not a type" - for key in list(class_.__dict__): - value = getattr(class_, key, None) - if isinstance(value, InstrumentedAttribute): - yield value - + try: + return self._noninherited_attribute_cache[class_] + except KeyError: + if not isinstance(class_, type): + raise TypeError(repr(class_) + " is not a type") + noninherited = [v for v in [getattr(class_, key, None) for key in list(class_.__dict__)] if isinstance(v, InstrumentedAttribute)] + self._noninherited_attribute_cache[class_] = noninherited + return noninherited + def is_modified(self, object): for attr in self.managed_attributes(object.__class__): if attr.check_mutable_modified(object): @@ -714,7 +729,9 @@ class AttributeManager(object): """removes all InstrumentedAttribute property objects from the given class.""" for attr in self.noninherited_managed_attributes(class_): delattr(class_, attr.key) - + self._inherited_attribute_cache.pop(class_,None) + self._noninherited_attribute_cache.pop(class_,None) + def is_class_managed(self, class_, key): """returns True if the given key correponds to an instrumented property on the given class.""" return hasattr(class_, key) and isinstance(getattr(class_, key), InstrumentedAttribute) @@ -733,6 +750,11 @@ class AttributeManager(object): def register_attribute(self, class_, key, uselist, callable_=None, **kwargs): """registers an attribute at the class level to be instrumented for all instances of the class.""" + # firt invalidate the cache for the given class + # (will be reconstituted as needed, while getting managed attributes) + self._inherited_attribute_cache.pop(class_,None) + self._noninherited_attribute_cache.pop(class_,None) + #print self, "register attribute", key, "for class", class_ if not hasattr(class_, '_state'): def _get_state(self): diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py index 8ebeaea27d..93f4574bc1 100644 --- a/lib/sqlalchemy/schema.py +++ b/lib/sqlalchemy/schema.py @@ -501,7 +501,7 @@ class ForeignKey(SchemaItem): One or more ForeignKey objects are used within a ForeignKeyConstraint object which represents the table-level constraint definition.""" - def __init__(self, column, constraint=None, use_alter=False, name=None): + def __init__(self, column, constraint=None, use_alter=False, name=None, onupdate=None, ondelete=None): """Construct a new ForeignKey object. "column" can be a schema.Column object representing the relationship, @@ -518,6 +518,8 @@ class ForeignKey(SchemaItem): self.constraint = constraint self.use_alter = use_alter self.name = name + self.onupdate = onupdate + self.ondelete = ondelete def __repr__(self): return "ForeignKey(%s)" % repr(self._get_colspec()) @@ -588,7 +590,7 @@ class ForeignKey(SchemaItem): self.parent = column if self.constraint is None and isinstance(self.parent.table, Table): - self.constraint = ForeignKeyConstraint([],[], use_alter=self.use_alter, name=self.name) + self.constraint = ForeignKeyConstraint([],[], use_alter=self.use_alter, name=self.name, onupdate=self.onupdate, ondelete=self.ondelete) self.parent.table.append_constraint(self.constraint) self.constraint._append_fk(self)