[Sqlalchemy-commits] [1250] sqlalchemy/trunk/test: attributes overhaul #2 - attribute manager now tr
Brought to you by:
zzzeek
From: <co...@sq...> - 2006-04-03 21:04:34
|
<!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>[1250] sqlalchemy/trunk/test: attributes overhaul #2 - attribute manager now tracks class-level initializers strictly through the SmartPropery instances attached to the class, so that attributes retain their natural polymorphic behavior.</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1250</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-04-03 16:04:16 -0500 (Mon, 03 Apr 2006)</dd> </dl> <h3>Log Message</h3> <pre>attributes overhaul #2 - attribute manager now tracks class-level initializers strictly through the SmartPropery instances attached to the class, so that attributes retain their natural polymorphic behavior. naming conventions migrating to "managed_attribute", simplifying codepaths.</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemytrunklibsqlalchemyattributespy">sqlalchemy/trunk/lib/sqlalchemy/attributes.py</a></li> <li><a href="#sqlalchemytrunklibsqlalchemymappingunitofworkpy">sqlalchemy/trunk/lib/sqlalchemy/mapping/unitofwork.py</a></li> <li><a href="#sqlalchemytrunktestattributespy">sqlalchemy/trunk/test/attributes.py</a></li> <li><a href="#sqlalchemytrunktestmapperpy">sqlalchemy/trunk/test/mapper.py</a></li> <li><a href="#sqlalchemytrunktestmasscreatepy">sqlalchemy/trunk/test/masscreate.py</a></li> <li><a href="#sqlalchemytrunktestobjectstorepy">sqlalchemy/trunk/test/objectstore.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 (1249 => 1250)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/attributes.py 2006-04-03 17:49:41 UTC (rev 1249) +++ sqlalchemy/trunk/lib/sqlalchemy/attributes.py 2006-04-03 21:04:16 UTC (rev 1250) </span><span class="lines">@@ -37,10 +37,20 @@ </span><span class="cx"> create_prop method on AttributeManger, which can be overridden to provide </span><span class="cx"> subclasses of SmartProperty. </span><span class="cx"> """ </span><del>- def __init__(self, manager, key, uselist): </del><ins>+ def __init__(self, manager, key, uselist, callable_, **kwargs): </ins><span class="cx"> self.manager = manager </span><span class="cx"> self.key = key </span><span class="cx"> self.uselist = uselist </span><ins>+ self.callable_ = callable_ + self.kwargs = kwargs + def init(self, obj, attrhist=None): + """creates an appropriate ManagedAttribute for the given object and establishes + it with the object's list of managed attributes.""" + if self.callable_ is not None: + func = self.callable_(obj) + else: + func = None + return self.manager.create_managed_attribute(obj, self.key, self.uselist, callable_=func, attrdict=attrhist, **self.kwargs) </ins><span class="cx"> def __set__(self, obj, value): </span><span class="cx"> self.manager.set_attribute(obj, self.key, value) </span><span class="cx"> def __delete__(self, obj): </span><span class="lines">@@ -300,10 +310,11 @@ </span><span class="cx"> upon an attribute change of value.""" </span><span class="cx"> pass </span><span class="cx"> </span><del>- def create_prop(self, class_, key, uselist, **kwargs): </del><ins>+ def create_prop(self, class_, key, uselist, callable_, **kwargs): </ins><span class="cx"> """creates a scalar property object, defaulting to SmartProperty, which </span><span class="cx"> will communicate change events back to this AttributeManager.""" </span><del>- return SmartProperty(self, key, uselist) </del><ins>+ return SmartProperty(self, key, uselist, callable_, **kwargs) + </ins><span class="cx"> def create_list(self, obj, key, list_, **kwargs): </span><span class="cx"> """creates a history-aware list property, defaulting to a ListAttribute which </span><span class="cx"> is a subclass of HistoryArrayList.""" </span><span class="lines">@@ -365,22 +376,19 @@ </span><span class="cx"> # currently a no-op since the state of the object is attached to the object itself </span><span class="cx"> pass </span><span class="cx"> </span><del>- def create_history(self, obj, key, uselist, callable_=None, **kwargs): - """creates a new "history" container for a specific attribute on the given object. - this can be used to override a class-level attribute with something different, - such as a callable. """ - p = self.create_history_container(obj, key, uselist, callable_=callable_, **kwargs) - self.attribute_history(obj)[key] = p - return p </del><span class="cx"> </span><span class="cx"> def init_attr(self, obj): </span><del>- """sets up the _managed_attributes dictionary on an object. this happens anyway regardless - of this method being called, but saves on KeyErrors being thrown in get_history().""" </del><ins>+ """sets up the _managed_attributes dictionary on an object. this happens anyway + when a particular attribute is first accessed on the object regardless + of this method being called, however calling this first will result in an elimination of + AttributeError/KeyErrors that are thrown when get_unexec_history is called for the first + time for a particular key.""" </ins><span class="cx"> d = {} </span><span class="cx"> obj._managed_attributes = d </span><del>- cls_managed = self.class_managed(obj.__class__) - for value in cls_managed.values(): - value(obj, d).plain_init(d) </del><ins>+ for value in obj.__class__.__dict__.values(): + if not isinstance(value, SmartProperty): + continue + value.init(obj, attrhist=d).plain_init(d) </ins><span class="cx"> </span><span class="cx"> def get_unexec_history(self, obj, key): </span><span class="cx"> """returns the "history" container for the given attribute on the given object. </span><span class="lines">@@ -389,31 +397,35 @@ </span><span class="cx"> try: </span><span class="cx"> return obj._managed_attributes[key] </span><span class="cx"> except AttributeError, ae: </span><del>- return self.class_managed(obj.__class__)[key](obj) </del><ins>+ return getattr(obj.__class__, key).init(obj) </ins><span class="cx"> except KeyError, e: </span><del>- return self.class_managed(obj.__class__)[key](obj) </del><ins>+ return getattr(obj.__class__, key).init(obj) </ins><span class="cx"> </span><span class="cx"> def get_history(self, obj, key, **kwargs): </span><del>- """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.""" </del><ins>+ """accesses the appropriate ManagedAttribute container and calls its history() method. + For a TriggeredAttribute this will execute the underlying callable and return the + resulting ScalarAttribute or ListAttribute object. For an existing ScalarAttribute + or ListAttribute, just returns the container.""" </ins><span class="cx"> return self.get_unexec_history(obj, key).history(**kwargs) </span><span class="cx"> </span><span class="cx"> def attribute_history(self, obj): </span><del>- """returns a dictionary of "history" containers corresponding to the given object. </del><ins>+ """returns a dictionary of ManagedAttribute containers corresponding to the given object. </ins><span class="cx"> this dictionary is attached to the object via the attribute '_managed_attributes'. </span><del>- If the dictionary does not exist, it will be created.""" </del><ins>+ If the dictionary does not exist, it will be created. If a 'trigger' has been placed on + this object via the trigger_history() method, it will first be executed.""" </ins><span class="cx"> try: </span><span class="cx"> return obj._managed_attributes </span><span class="cx"> except AttributeError: </span><ins>+ obj._managed_attributes = {} </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 = {} - obj._managed_attributes = attr - return attr </del><ins>+ return obj._managed_attributes </ins><span class="cx"> </span><span class="cx"> def trigger_history(self, obj, callable): </span><ins>+ """removes all ManagedAttribute instances from the given object and places the given callable + as an attribute-wide "trigger", which will execute upon the next attribute access, after + which the trigger is removed and the object re-initialized to receive new ManagedAttributes. """ </ins><span class="cx"> try: </span><span class="cx"> del obj._managed_attributes </span><span class="cx"> except KeyError: </span><span class="lines">@@ -440,62 +452,42 @@ </span><span class="cx"> except KeyError: </span><span class="cx"> pass </span><span class="cx"> </span><del>- def class_managed(self, class_): - """returns a dictionary of "history container definitions", which is attached to a - class. creates the dictionary if it doesnt exist.""" - try: - attr = class_._class_managed_attributes - except AttributeError: - attr = {} - class_._class_managed_attributes = attr - class_._attribute_manager = self - return attr - </del><span class="cx"> def reset_class_managed(self, class_): </span><del>- try: - attr = class_._class_managed_attributes - for key in attr.keys(): - delattr(class_, key) - del class_._class_managed_attributes - except AttributeError: - pass </del><ins>+ for value in class_.__dict__.values(): + if not isinstance(value, SmartProperty): + continue + delattr(class_, value.key) </ins><span class="cx"> </span><span class="cx"> def is_class_managed(self, class_, key): </span><del>- try: - return class_._class_managed_attributes.has_key(key) - except AttributeError: - return False - - def create_history_container(self, obj, key, uselist, callable_ = None, **kwargs): - """creates a new history container for the given attribute on the given object.""" </del><ins>+ return hasattr(class_, key) and isinstance(getattr(class_, key), SmartProperty) + + def create_managed_attribute(self, obj, key, uselist, callable_=None, attrdict=None, **kwargs): + """creates a new ManagedAttribute corresponding to the given attribute key on the + given object instance, and installs it in the attribute dictionary attached to the object.""" </ins><span class="cx"> if callable_ is not None: </span><del>- return self.create_callable(obj, key, callable_, uselist=uselist, **kwargs) </del><ins>+ prop = self.create_callable(obj, key, callable_, uselist=uselist, **kwargs) </ins><span class="cx"> elif not uselist: </span><del>- return ScalarAttribute(obj, key, **kwargs) </del><ins>+ prop = ScalarAttribute(obj, key, **kwargs) </ins><span class="cx"> else: </span><del>- return self.create_list(obj, key, None, **kwargs) - </del><ins>+ prop = self.create_list(obj, key, None, **kwargs) + if attrdict is None: + attrdict = self.attribute_history(obj) + attrdict[key] = prop + return prop + + # deprecated + create_history=create_managed_attribute + </ins><span class="cx"> def register_attribute(self, class_, key, uselist, callable_=None, **kwargs): </span><span class="cx"> """registers an attribute's behavior at the class level. This attribute </span><span class="cx"> can be scalar or list based, and also may have a callable unit that will be </span><del>- used to create the initial value. The definition for this attribute is - wrapped up into a callable which is then stored in the classes' - dictionary of "class managed" attributes. When instances of the class </del><ins>+ used to create the initial value (i.e. a lazy loader). The definition for this attribute is + wrapped up into a callable which is then stored in the corresponding + SmartProperty object attached to the class. When instances of the class </ins><span class="cx"> are created and the attribute first referenced, the callable is invoked with </span><del>- the new object instance as an argument to create the new history container. </del><ins>+ the new object instance as an argument to create the new ManagedAttribute. </ins><span class="cx"> Extra keyword arguments can be sent which </span><del>- will be passed along to newly created history containers.""" - def createprop(obj, attrhist=None): - if callable_ is not None: - func = callable_(obj) - else: - func = None - p = self.create_history_container(obj, key, uselist, callable_=func, **kwargs) - if attrhist is None: - attrhist = self.attribute_history(obj) - attrhist[key] = p - return p - - self.class_managed(class_)[key] = createprop - setattr(class_, key, self.create_prop(class_, key, uselist)) </del><ins>+ will be passed along to newly created ManagedAttribute.""" + class_._attribute_manager = self + setattr(class_, key, self.create_prop(class_, key, uselist, callable_, **kwargs)) </ins><span class="cx"> </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemymappingunitofworkpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/mapping/unitofwork.py (1249 => 1250)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/mapping/unitofwork.py 2006-04-03 17:49:41 UTC (rev 1249) +++ sqlalchemy/trunk/lib/sqlalchemy/mapping/unitofwork.py 2006-04-03 21:04:16 UTC (rev 1250) </span><span class="lines">@@ -66,8 +66,8 @@ </span><span class="cx"> else: </span><span class="cx"> get_session(obj).register_new(obj) </span><span class="cx"> </span><del>- def create_prop(self, class_, key, uselist, **kwargs): - return UOWProperty(class_, self, key, uselist) </del><ins>+ def create_prop(self, class_, key, uselist, callable_, **kwargs): + return UOWProperty(class_, self, key, uselist, callable_, **kwargs) </ins><span class="cx"> </span><span class="cx"> def create_list(self, obj, key, list_, **kwargs): </span><span class="cx"> return UOWListElement(obj, key, list_, **kwargs) </span></span></pre></div> <a id="sqlalchemytrunktestattributespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/test/attributes.py (1249 => 1250)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/attributes.py 2006-04-03 17:49:41 UTC (rev 1249) +++ sqlalchemy/trunk/test/attributes.py 2006-04-03 21:04:16 UTC (rev 1250) </span><span class="lines">@@ -131,6 +131,30 @@ </span><span class="cx"> </span><span class="cx"> j.port = None </span><span class="cx"> self.assert_(p.jack is None) </span><ins>+ + def testinheritance(self): + """tests that attributes are polymorphic""" + class Foo(object):pass + class Bar(Foo):pass </ins><span class="cx"> </span><ins>+ manager = attributes.AttributeManager() + + def func1(): + return "this is the foo attr" + def func2(): + return "this is the bar attr" + def func3(): + return "this is the shared attr" + manager.register_attribute(Foo, 'element', uselist=False, callable_=lambda o:func1) + manager.register_attribute(Foo, 'element2', uselist=False, callable_=lambda o:func3) + manager.register_attribute(Bar, 'element', uselist=False, callable_=lambda o:func2) + + x = Foo() + y = Bar() + assert x.element == 'this is the foo attr' + assert y.element == 'this is the bar attr' + assert x.element2 == 'this is the shared attr' + assert y.element2 == 'this is the shared attr' + </ins><span class="cx"> if __name__ == "__main__": </span><span class="cx"> unittest.main() </span></span></pre></div> <a id="sqlalchemytrunktestmapperpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/test/mapper.py (1249 => 1250)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/mapper.py 2006-04-03 17:49:41 UTC (rev 1249) +++ sqlalchemy/trunk/test/mapper.py 2006-04-03 21:04:16 UTC (rev 1250) </span><span class="lines">@@ -125,12 +125,15 @@ </span><span class="cx"> self.assert_sql_count(db, go, 1) </span><span class="cx"> </span><span class="cx"> def testexpire(self): </span><del>- m = mapper(User, users, properties={'addresses':relation(mapper(Address, addresses))}) </del><ins>+ m = mapper(User, users, properties={'addresses':relation(mapper(Address, addresses), lazy=False)}) </ins><span class="cx"> u = m.get(7) </span><ins>+ assert(len(u.addresses) == 1) </ins><span class="cx"> u.user_name = 'foo' </span><ins>+ del u.addresses[0] </ins><span class="cx"> objectstore.expire(u) </span><span class="cx"> # test plain expire </span><span class="cx"> self.assert_(u.user_name =='jack') </span><ins>+ self.assert_(len(u.addresses) == 1) </ins><span class="cx"> </span><span class="cx"> # we're changing the database here, so if this test fails in the middle, </span><span class="cx"> # it'll screw up the other tests which are hardcoded to 7/'jack' </span></span></pre></div> <a id="sqlalchemytrunktestmasscreatepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/test/masscreate.py (1249 => 1250)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/masscreate.py 2006-04-03 17:49:41 UTC (rev 1249) +++ sqlalchemy/trunk/test/masscreate.py 2006-04-03 21:04:16 UTC (rev 1250) </span><span class="lines">@@ -35,4 +35,4 @@ </span><span class="cx"> u.addresses.append(u) </span><span class="cx"> </span><span class="cx"> total = time.time() - now </span><del>-print "Total time", total </del><span class="cx">\ No newline at end of file </span><ins>+print "Total time", total </ins></span></pre></div> <a id="sqlalchemytrunktestobjectstorepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/test/objectstore.py (1249 => 1250)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/objectstore.py 2006-04-03 17:49:41 UTC (rev 1249) +++ sqlalchemy/trunk/test/objectstore.py 2006-04-03 21:04:16 UTC (rev 1250) </span><span class="lines">@@ -1078,7 +1078,6 @@ </span><span class="cx"> a.user = u </span><span class="cx"> objectstore.commit() </span><span class="cx"> print repr(u.addresses) </span><del>- print repr(u.addresses) </del><span class="cx"> x = False </span><span class="cx"> try: </span><span class="cx"> u.addresses.append('hi') </span><span class="lines">@@ -1087,7 +1086,7 @@ </span><span class="cx"> pass </span><span class="cx"> </span><span class="cx"> if x: </span><del>- self.assert_(False, "User addresses element should be read-only") </del><ins>+ self.assert_(False, "User addresses element should be scalar based") </ins><span class="cx"> </span><span class="cx"> objectstore.delete(u) </span><span class="cx"> objectstore.commit() </span></span></pre> </div> </div> </body> </html> |