[Sqlalchemy-commits] [1235] sqlalchemy/trunk/test: cleanup of attributes, better naming, added weak
Brought to you by:
zzzeek
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head><style type="text/css"><!-- #msg dl { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; } #msg dt { float: left; width: 6em; font-weight: bold; } #msg dt:after { content:':';} #msg dl, #msg dt, #msg ul, #msg li { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; } #msg dl a { font-weight: bold} #msg dl a:link { color:#fc3; } #msg dl a:active { color:#ff0; } #msg dl a:visited { color:#cc6; } h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; } #msg pre { overflow: auto; background: #ffc; border: 1px #fc0 solid; padding: 6px; } #msg ul, pre { overflow: auto; } #patch { width: 100%; } #patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;} #patch .propset h4, #patch .binary h4 {margin:0;} #patch pre {padding:0;line-height:1.2em;margin:0;} #patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;} #patch .propset .diff, #patch .binary .diff {padding:10px 0;} #patch span {display:block;padding:0 10px;} #patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;} #patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;} #patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;} #patch .lines, .info {color:#888;background:#fff;} --></style> <title>[1235] sqlalchemy/trunk/test: cleanup of attributes, better naming, added weak reference to base managed attribute to break circular refs, slightly shorter codepaths in some cases.</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1235</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-04-01 15:00:41 -0600 (Sat, 01 Apr 2006)</dd> </dl> <h3>Log Message</h3> <pre>cleanup of attributes, better naming, added weak reference to base managed attribute to break circular refs, slightly shorter codepaths in some cases. added performance tester</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemytrunklibsqlalchemyattributespy">sqlalchemy/trunk/lib/sqlalchemy/attributes.py</a></li> </ul> <h3>Added Paths</h3> <ul> <li><a href="#sqlalchemytrunktestmasscreatepy">sqlalchemy/trunk/test/masscreate.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemytrunklibsqlalchemyattributespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/attributes.py (1234 => 1235)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/attributes.py 2006-03-31 23:46:02 UTC (rev 1234) +++ sqlalchemy/trunk/lib/sqlalchemy/attributes.py 2006-04-01 21:00:41 UTC (rev 1235) </span><span class="lines">@@ -60,8 +60,18 @@ </span><span class="cx"> """appends a value to a list-based attribute without triggering a history event.""" </span><span class="cx"> h = self.manager.get_history(obj, self.key) </span><span class="cx"> h.append_nohistory(value) </span><ins>+ +class ManagedAttribute(object): + def __init__(self, obj, key): + self.__obj = weakref.ref(obj) + self.key = key + obj = property(lambda s:s.__obj()) + def history(self, **kwargs): + return self + def plain_init(self, *args, **kwargs): + pass </ins><span class="cx"> </span><del>-class PropHistory(object): </del><ins>+class ScalarAttribute(ManagedAttribute): </ins><span class="cx"> """Used by AttributeManager to track the history of a scalar attribute </span><span class="cx"> on an object instance. This is the "scalar history container" object. </span><span class="cx"> Has an interface similar to util.HistoryList </span><span class="lines">@@ -69,14 +79,9 @@ </span><span class="cx"> # make our own NONE to distinguish from "None" </span><span class="cx"> NONE = object() </span><span class="cx"> def __init__(self, obj, key, extension=None, **kwargs): </span><del>- self.obj = obj - self.key = key - self.orig = PropHistory.NONE </del><ins>+ ManagedAttribute.__init__(self, obj, key) + self.orig = ScalarAttribute.NONE </ins><span class="cx"> self.extension = extension </span><del>- def plain_init(self, *args): - pass - def gethistory(self, *args, **kwargs): - return self </del><span class="cx"> def clear(self): </span><span class="cx"> del self.obj.__dict__[self.key] </span><span class="cx"> def history_contains(self, obj): </span><span class="lines">@@ -85,22 +90,22 @@ </span><span class="cx"> self.obj.__dict__[self.key] = value </span><span class="cx"> def delattr_clean(self): </span><span class="cx"> del self.obj.__dict__[self.key] </span><del>- def getattr(self): </del><ins>+ def getattr(self, **kwargs): </ins><span class="cx"> return self.obj.__dict__[self.key] </span><del>- def setattr(self, value): </del><ins>+ def setattr(self, value, **kwargs): </ins><span class="cx"> if isinstance(value, list): </span><span class="cx"> raise InvalidRequestError("assigning a list to scalar property '%s' on '%s' instance %d" % (self.key, self.obj.__class__.__name__, id(self.obj))) </span><span class="cx"> orig = self.obj.__dict__.get(self.key, None) </span><span class="cx"> if orig is value: </span><span class="cx"> return </span><del>- if self.orig is PropHistory.NONE: </del><ins>+ if self.orig is ScalarAttribute.NONE: </ins><span class="cx"> self.orig = orig </span><span class="cx"> self.obj.__dict__[self.key] = value </span><span class="cx"> if self.extension is not None: </span><span class="cx"> self.extension.set(self.obj, value, orig) </span><del>- def delattr(self): </del><ins>+ def delattr(self, **kwargs): </ins><span class="cx"> orig = self.obj.__dict__.get(self.key, None) </span><del>- if self.orig is PropHistory.NONE: </del><ins>+ if self.orig is ScalarAttribute.NONE: </ins><span class="cx"> self.orig = orig </span><span class="cx"> self.obj.__dict__[self.key] = None </span><span class="cx"> if self.extension is not None: </span><span class="lines">@@ -110,35 +115,34 @@ </span><span class="cx"> def remove(self, obj): </span><span class="cx"> self.delattr() </span><span class="cx"> def rollback(self): </span><del>- if self.orig is not PropHistory.NONE: </del><ins>+ if self.orig is not ScalarAttribute.NONE: </ins><span class="cx"> self.obj.__dict__[self.key] = self.orig </span><del>- self.orig = PropHistory.NONE </del><ins>+ self.orig = ScalarAttribute.NONE </ins><span class="cx"> def commit(self): </span><del>- self.orig = PropHistory.NONE </del><ins>+ self.orig = ScalarAttribute.NONE </ins><span class="cx"> def added_items(self): </span><del>- if self.orig is not PropHistory.NONE: </del><ins>+ if self.orig is not ScalarAttribute.NONE: </ins><span class="cx"> return [self.obj.__dict__[self.key]] </span><span class="cx"> else: </span><span class="cx"> return [] </span><span class="cx"> def deleted_items(self): </span><del>- if self.orig is not PropHistory.NONE and self.orig is not None: </del><ins>+ if self.orig is not ScalarAttribute.NONE and self.orig is not None: </ins><span class="cx"> return [self.orig] </span><span class="cx"> else: </span><span class="cx"> return [] </span><span class="cx"> def unchanged_items(self): </span><del>- if self.orig is PropHistory.NONE: </del><ins>+ if self.orig is ScalarAttribute.NONE: </ins><span class="cx"> return [self.obj.__dict__[self.key]] </span><span class="cx"> else: </span><span class="cx"> return [] </span><span class="cx"> </span><del>-class ListElement(util.HistoryArraySet): </del><ins>+class ListAttribute(util.HistoryArraySet, ManagedAttribute): </ins><span class="cx"> """Used by AttributeManager to track the history of a list-based object attribute. </span><span class="cx"> This is the "list history container" object. </span><span class="cx"> Subclasses util.HistoryArraySet to provide "onchange" event handling as well </span><span class="cx"> as a plugin point for BackrefExtension objects.""" </span><span class="cx"> def __init__(self, obj, key, data=None, extension=None, **kwargs): </span><del>- self.obj = obj - self.key = key </del><ins>+ ManagedAttribute.__init__(self, obj, key) </ins><span class="cx"> self.extension = extension </span><span class="cx"> # if we are given a list, try to behave nicely with an existing </span><span class="cx"> # list that might be set on the object already </span><span class="lines">@@ -157,16 +161,12 @@ </span><span class="cx"> obj.__dict__[key] = [] </span><span class="cx"> </span><span class="cx"> util.HistoryArraySet.__init__(self, list_, readonly=kwargs.get('readonly', False)) </span><del>- def plain_init(self, *args): - pass - def gethistory(self, *args, **kwargs): - return self </del><span class="cx"> def list_value_changed(self, obj, key, item, listval, isdelete): </span><span class="cx"> pass </span><del>- def setattr(self, value): </del><ins>+ def setattr(self, value, **kwargs): </ins><span class="cx"> self.obj.__dict__[self.key] = value </span><span class="cx"> self.set_data(value) </span><del>- def delattr(self, value): </del><ins>+ def delattr(self, value, **kwargs): </ins><span class="cx"> pass </span><span class="cx"> def _setrecord(self, item): </span><span class="cx"> res = util.HistoryArraySet._setrecord(self, item) </span><span class="lines">@@ -182,33 +182,39 @@ </span><span class="cx"> if self.extension is not None: </span><span class="cx"> self.extension.delete(self.obj, item) </span><span class="cx"> return res </span><del>- -class CallableProp(object): </del><ins>+ +# deprecated +class ListElement(ListAttribute):pass + +class TriggeredAttribute(ManagedAttribute): </ins><span class="cx"> """Used by AttributeManager to allow the attaching of a callable item, representing the future value </span><del>- of a particular attribute on a particular object instance, to an attribute on an object. - This is the "callable history container" object. - When the attributemanager first accesses the object attribute, either to get its history or - its real value, the __call__ method - is invoked which runs the underlying callable_ and sets the new value to the object attribute, - at which point the CallableProp itself is dereferenced.""" </del><ins>+ of a particular attribute on a particular object instance, as the current attribute on an object. + When accessed normally, its history() method is invoked to run the underlying callable, which + is then used to create a new ScalarAttribute or ListAttribute. This new attribute object + is then registered with the attribute manager to replace this TriggeredAttribute as the + current ManagedAttribute.""" </ins><span class="cx"> def __init__(self, manager, callable_, obj, key, uselist = False, live = False, **kwargs): </span><ins>+ ManagedAttribute.__init__(self, obj, key) </ins><span class="cx"> self.manager = manager </span><span class="cx"> self.callable_ = callable_ </span><del>- self.obj = obj - self.key = key </del><span class="cx"> self.uselist = uselist </span><del>- self.live = live </del><span class="cx"> self.kwargs = kwargs </span><span class="cx"> </span><span class="cx"> def plain_init(self, attrhist): </span><span class="cx"> if not self.uselist: </span><del>- p = PropHistory(self.obj, self.key, **self.kwargs) </del><ins>+ p = ScalarAttribute(self.obj, self.key, **self.kwargs) </ins><span class="cx"> self.obj.__dict__[self.key] = None </span><span class="cx"> else: </span><del>- p = self.manager.create_list(self.obj, self.key, None, readonly=self.live, **self.kwargs) </del><ins>+ p = self.manager.create_list(self.obj, self.key, None, **self.kwargs) </ins><span class="cx"> attrhist[self.key] = p </span><del>- - def gethistory(self, passive=False, *args, **kwargs): </del><ins>+ + def __getattr__(self, key): + def callit(*args, **kwargs): + passive = kwargs.pop('passive', False) + return getattr(self.history(passive=passive), key)(*args, **kwargs) + return callit + + def history(self, passive=False): </ins><span class="cx"> if not self.uselist: </span><span class="cx"> if self.obj.__dict__.get(self.key, None) is None: </span><span class="cx"> if passive: </span><span class="lines">@@ -222,9 +228,9 @@ </span><span class="cx"> raise AssertionError("AttributeError caught in callable prop:" + str(e.args)) </span><span class="cx"> self.obj.__dict__[self.key] = value </span><span class="cx"> </span><del>- p = PropHistory(self.obj, self.key, **self.kwargs) </del><ins>+ p = ScalarAttribute(self.obj, self.key, **self.kwargs) </ins><span class="cx"> else: </span><del>- if self.live or not self.obj.__dict__.has_key(self.key) or len(self.obj.__dict__[self.key]) == 0: </del><ins>+ if not self.obj.__dict__.has_key(self.key) or len(self.obj.__dict__[self.key]) == 0: </ins><span class="cx"> if passive: </span><span class="cx"> value = None </span><span class="cx"> else: </span><span class="lines">@@ -236,12 +242,11 @@ </span><span class="cx"> raise AssertionError("AttributeError caught in callable prop:" + str(e.args)) </span><span class="cx"> else: </span><span class="cx"> value = None </span><del>- p = self.manager.create_list(self.obj, self.key, value, readonly=self.live, **self.kwargs) - if not self.live and not passive: </del><ins>+ p = self.manager.create_list(self.obj, self.key, value, **self.kwargs) + if not passive: </ins><span class="cx"> # set the new history list as the new attribute, discards ourself </span><span class="cx"> self.manager.attribute_history(self.obj)[self.key] = p </span><span class="cx"> self.manager = None </span><del>- # unless we are "live", in which case we stay around to execute again </del><span class="cx"> return p </span><span class="cx"> </span><span class="cx"> def commit(self): </span><span class="lines">@@ -260,6 +265,11 @@ </span><span class="cx"> pass </span><span class="cx"> </span><span class="cx"> class GenericBackrefExtension(AttributeExtension): </span><ins>+ """an attachment to a ScalarAttribute or ListAttribute which receives change events, + and upon such an event synchronizes a two-way relationship. A typical two-way + relationship is a parent object containing a list of child objects, where each + child object references the parent. The other are two objects which contain + scalar references to each other.""" </ins><span class="cx"> def __init__(self, key): </span><span class="cx"> self.key = key </span><span class="cx"> def set(self, obj, child, oldchild): </span><span class="lines">@@ -292,22 +302,22 @@ </span><span class="cx"> will communicate change events back to this AttributeManager.""" </span><span class="cx"> return SmartProperty(self, key, uselist) </span><span class="cx"> def create_list(self, obj, key, list_, **kwargs): </span><del>- """creates a history-aware list property, defaulting to a ListElement which </del><ins>+ """creates a history-aware list property, defaulting to a ListAttribute which </ins><span class="cx"> is a subclass of HistoryArrayList.""" </span><del>- return ListElement(obj, key, list_, **kwargs) </del><ins>+ return ListAttribute(obj, key, list_, **kwargs) </ins><span class="cx"> def create_callable(self, obj, key, func, uselist, **kwargs): </span><span class="cx"> """creates a callable container that will invoke a function the first </span><span class="cx"> time an object property is accessed. The return value of the function </span><span class="cx"> will become the object property's new value.""" </span><del>- return CallableProp(self, func, obj, key, uselist, **kwargs) </del><ins>+ return TriggeredAttribute(self, func, obj, key, uselist, **kwargs) </ins><span class="cx"> </span><span class="cx"> def get_attribute(self, obj, key, **kwargs): </span><span class="cx"> """returns the value of an object's scalar attribute, or None if </span><span class="cx"> its not defined on the object (since we are a property accessor, this </span><span class="cx"> is considered more appropriate than raising AttributeError).""" </span><del>- h = self.get_history(obj, key, **kwargs) </del><ins>+ h = self.get_unexec_history(obj, key) </ins><span class="cx"> try: </span><del>- return h.getattr() </del><ins>+ return h.getattr(**kwargs) </ins><span class="cx"> except KeyError: </span><span class="cx"> return None </span><span class="cx"> </span><span class="lines">@@ -317,12 +327,12 @@ </span><span class="cx"> </span><span class="cx"> def set_attribute(self, obj, key, value, **kwargs): </span><span class="cx"> """sets the value of an object's attribute.""" </span><del>- self.get_history(obj, key, **kwargs).setattr(value) </del><ins>+ self.get_unexec_history(obj, key).setattr(value, **kwargs) </ins><span class="cx"> self.value_changed(obj, key, value) </span><span class="cx"> </span><span class="cx"> def delete_attribute(self, obj, key, **kwargs): </span><span class="cx"> """deletes the value from an object's attribute.""" </span><del>- self.get_history(obj, key, **kwargs).delattr() </del><ins>+ self.get_unexec_history(obj, key).delattr(**kwargs) </ins><span class="cx"> self.value_changed(obj, key, None) </span><span class="cx"> </span><span class="cx"> def rollback(self, *obj): </span><span class="lines">@@ -363,47 +373,55 @@ </span><span class="cx"> def init_attr(self, obj): </span><span class="cx"> """sets up the _managed_attributes dictionary on an object. this happens anyway regardless </span><span class="cx"> of this method being called, but saves on KeyErrors being thrown in get_history().""" </span><del>- d = managed_attribute_dict() - obj.__dict__['_managed_attributes'] = d </del><ins>+ d = {} + obj._managed_attributes = d </ins><span class="cx"> cls_managed = self.class_managed(obj.__class__) </span><span class="cx"> for value in cls_managed.values(): </span><span class="cx"> value(obj, d).plain_init(d) </span><span class="cx"> </span><del>- def get_history(self, obj, key, passive=False, **kwargs): </del><ins>+ def get_unexec_history(self, obj, key): </ins><span class="cx"> """returns the "history" container for the given attribute on the given object. </span><span class="cx"> If the container does not exist, it will be created based on the class-level </span><span class="cx"> history container definition.""" </span><span class="cx"> try: </span><del>- return obj.__dict__['_managed_attributes'][key].gethistory(passive=passive, **kwargs) </del><ins>+ return obj._managed_attributes[key] + except AttributeError, ae: + return self.class_managed(obj.__class__)[key](obj) </ins><span class="cx"> except KeyError, e: </span><del>- return self.class_managed(obj.__class__)[key](obj, **kwargs).gethistory(passive=passive, **kwargs) </del><ins>+ return self.class_managed(obj.__class__)[key](obj) </ins><span class="cx"> </span><ins>+ def get_history(self, obj, key, **kwargs): + """returns the "history" container, and calls its history() method, + which for a TriggeredAttribute will execute the underlying callable and return the + resulting ScalarAttribute or ListHistory object.""" + return self.get_unexec_history(obj, key).history(**kwargs) + </ins><span class="cx"> def attribute_history(self, obj): </span><span class="cx"> """returns a dictionary of "history" containers corresponding to the given object. </span><span class="cx"> this dictionary is attached to the object via the attribute '_managed_attributes'. </span><span class="cx"> If the dictionary does not exist, it will be created.""" </span><span class="cx"> try: </span><del>- return obj.__dict__['_managed_attributes'] - except KeyError: </del><ins>+ return obj._managed_attributes + except AttributeError: </ins><span class="cx"> trigger = obj.__dict__.pop('_managed_trigger', None) </span><span class="cx"> if trigger: </span><span class="cx"> trigger() </span><del>- attr = managed_attribute_dict() - obj.__dict__['_managed_attributes'] = attr </del><ins>+ attr = {} + obj._managed_attributes = attr </ins><span class="cx"> return attr </span><span class="cx"> </span><span class="cx"> def trigger_history(self, obj, callable): </span><span class="cx"> try: </span><del>- del obj.__dict__['_managed_attributes'] </del><ins>+ del obj._managed_attributes </ins><span class="cx"> except KeyError: </span><span class="cx"> pass </span><del>- obj.__dict__['_managed_trigger'] = callable </del><ins>+ obj._managed_trigger = callable </ins><span class="cx"> </span><span class="cx"> def untrigger_history(self, obj): </span><del>- del obj.__dict__['_managed_trigger'] </del><ins>+ del obj._managed_trigger </ins><span class="cx"> </span><span class="cx"> def has_trigger(self, obj): </span><del>- return obj.__dict__.has_key('_managed_trigger') </del><ins>+ return hasattr(obj, '_managed_trigger') </ins><span class="cx"> </span><span class="cx"> def reset_history(self, obj, key): </span><span class="cx"> """removes the history object for the given attribute on the given object. </span><span class="lines">@@ -423,7 +441,7 @@ </span><span class="cx"> """returns a dictionary of "history container definitions", which is attached to a </span><span class="cx"> class. creates the dictionary if it doesnt exist.""" </span><span class="cx"> try: </span><del>- attr = getattr(class_, '_class_managed_attributes') </del><ins>+ attr = class_._class_managed_attributes </ins><span class="cx"> except AttributeError: </span><span class="cx"> attr = {} </span><span class="cx"> class_._class_managed_attributes = attr </span><span class="lines">@@ -432,10 +450,10 @@ </span><span class="cx"> </span><span class="cx"> def reset_class_managed(self, class_): </span><span class="cx"> try: </span><del>- attr = getattr(class_, '_class_managed_attributes') </del><ins>+ attr = class_._class_managed_attributes </ins><span class="cx"> for key in attr.keys(): </span><span class="cx"> delattr(class_, key) </span><del>- delattr(class_, '_class_managed_attributes') </del><ins>+ del class_._class_managed_attributes </ins><span class="cx"> except AttributeError: </span><span class="cx"> pass </span><span class="cx"> </span><span class="lines">@@ -450,7 +468,7 @@ </span><span class="cx"> if callable_ is not None: </span><span class="cx"> return self.create_callable(obj, key, callable_, uselist=uselist, **kwargs) </span><span class="cx"> elif not uselist: </span><del>- return PropHistory(obj, key, **kwargs) </del><ins>+ return ScalarAttribute(obj, key, **kwargs) </ins><span class="cx"> else: </span><span class="cx"> return self.create_list(obj, key, None, **kwargs) </span><span class="cx"> </span><span class="lines">@@ -478,9 +496,3 @@ </span><span class="cx"> self.class_managed(class_)[key] = createprop </span><span class="cx"> setattr(class_, key, self.create_prop(class_, key, uselist)) </span><span class="cx"> </span><del>-# make this function return a weakref.WeakValueDictionary to avoid -# creating circular references in objects -def managed_attribute_dict(): - return {} -# return weakref.WeakValueDictionary() - </del></span></pre></div> <a id="sqlalchemytrunktestmasscreatepy"></a> <div class="addfile"><h4>Added: sqlalchemy/trunk/test/masscreate.py (1234 => 1235)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/masscreate.py 2006-03-31 23:46:02 UTC (rev 1234) +++ sqlalchemy/trunk/test/masscreate.py 2006-04-01 21:00:41 UTC (rev 1235) </span><span class="lines">@@ -0,0 +1,38 @@ </span><ins>+# times how long it takes to create 26000 objects + +from sqlalchemy.attributes import * +import time + +manage_attributes = True +init_attributes = manage_attributes and True + +class User(object): + pass +class Address(object): + pass + +attr_manager = AttributeManager() +if manage_attributes: + attr_manager.register_attribute(User, 'id', uselist=False) + attr_manager.register_attribute(User, 'name', uselist=False) + attr_manager.register_attribute(User, 'addresses', uselist=True) + attr_manager.register_attribute(Address, 'email', uselist=False) + +now = time.time() +for i in range(0,130): + u = User() + if init_attributes: + attr_manager.init_attr(u) + u.id = i + u.name = "user " + str(i) + if not manage_attributes: + u.addresses = [] + for j in range(0,200): + a = Address() + if init_attributes: + attr_manager.init_attr(a) + a.email = 'fo...@ba...' + u.addresses.append(u) + +total = time.time() - now +print "Total time", total </ins><span class="cx">\ No newline at end of file </span></span></pre> </div> </div> </body> </html> |