+# attributes.py - manages object attributes
+# Copyright (C) 2005 Michael Bayer mike_mp@zzzcomputing.com
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+
import sqlalchemy.util as util
import weakref
class SmartProperty(object):
+ """attaches AttributeManager functionality to the property accessors of a class. all instances
+ of the class will retrieve and modify their properties via an AttributeManager."""
def __init__(self, manager):
self.manager = manager
def attribute_registry(self):
return property(get_prop, set_prop, del_prop)
-class ListElement(util.HistoryArraySet):
- """overrides HistoryArraySet to mark the parent object as dirty when changes occur"""
-
- def __init__(self, obj, key, items = None):
- self.obj = obj
- self.key = key
- util.HistoryArraySet.__init__(self, items)
- obj.__dict__[key] = self.data
-
- def list_value_changed(self, obj, key, listval):
- pass
-
- def setattr(self, value):
- self.obj.__dict__[self.key] = value
- self.set_data(value)
- def delattr(self, value):
- pass
- def _setrecord(self, item):
- res = util.HistoryArraySet._setrecord(self, item)
- if res:
- self.list_value_changed(self.obj, self.key, self)
- return res
- def _delrecord(self, item):
- res = util.HistoryArraySet._delrecord(self, item)
- if res:
- self.list_value_changed(self.obj, self.key, self)
- return res
-
class PropHistory(object):
+ """manages the value of a particular scalar attribute on a particular object instance."""
# make our own NONE to distinguish from "None"
NONE = object()
def __init__(self, obj, key):
else:
return []
def deleted_items(self):
- if self.orig is not PropHistory.NONE:
+ if self.orig is not PropHistory.NONE and self.orig is not None:
return [self.orig]
else:
return []
else:
return []
+class ListElement(util.HistoryArraySet):
+ """manages the value of a particular list-based attribute on a particular object instance."""
+ def __init__(self, obj, key, items = None):
+ self.obj = obj
+ self.key = key
+ util.HistoryArraySet.__init__(self, items)
+ obj.__dict__[key] = self.data
+
+ def list_value_changed(self, obj, key, listval):
+ pass
+
+ def setattr(self, value):
+ self.obj.__dict__[self.key] = value
+ self.set_data(value)
+ def delattr(self, value):
+ pass
+ def _setrecord(self, item):
+ res = util.HistoryArraySet._setrecord(self, item)
+ if res:
+ self.list_value_changed(self.obj, self.key, self)
+ return res
+ def _delrecord(self, item):
+ res = util.HistoryArraySet._delrecord(self, item)
+ if res:
+ self.list_value_changed(self.obj, self.key, self)
+ return res
+
+
class AttributeManager(object):
+ """maintains a set of per-attribute history objects for a set of objects."""
def __init__(self):
self.attribute_history = {}
+
def value_changed(self, obj, key, value):
pass
-# if hasattr(obj, '_instance_key'):
-# self.register_dirty(obj)
-# else:
-# self.register_new(obj)
-
def create_prop(self, key, uselist):
return SmartProperty(self).property(key, uselist)
-
def create_list(self, obj, key, list_):
return ListElement(obj, key, list_)
else:
return relation_mapper(*args, **params)
-def relation_loader(mapper, secondary = None, primaryjoin = None, secondaryjoin = None, lazy = True, private = False, **options):
+def relation_loader(mapper, secondary = None, primaryjoin = None, secondaryjoin = None, lazy = True, **options):
if lazy:
return LazyLoader(mapper, secondary, primaryjoin, secondaryjoin, **options)
else:
class PropertyLoader(MapperProperty):
- """describes an object property that holds a list of items that correspond to a related
+ """describes an object property that holds a single item or list of items that correspond to a related
database table."""
- def __init__(self, mapper, secondary, primaryjoin, secondaryjoin, uselist = True, foreignkey = None):
+ def __init__(self, mapper, secondary, primaryjoin, secondaryjoin, uselist = True, foreignkey = None, private = False):
self.uselist = uselist
self.mapper = mapper
self.target = self.mapper.selectable
self.primaryjoin = primaryjoin
self.secondaryjoin = secondaryjoin
self.foreignkey = foreignkey
+ self.private = private
self._hash_key = "%s(%s, %s, %s, %s, %s, uselist=%s)" % (self.__class__.__name__, hash_key(mapper), hash_key(secondary), hash_key(primaryjoin), hash_key(secondaryjoin), hash_key(foreignkey), repr(self.uselist))
def hash_key(self):
self.primaryjoin.accept_visitor(setter)
self.secondaryjoin.accept_visitor(setter)
secondary_delete.append(associationrow)
+ if self.private:
+ uowcommit.add_item_to_delete(obj)
uowcommit.register_saved_list(childlist)
if len(secondary_delete):
statement = self.secondary.delete(sql.and_(*[c == sql.bindparam(c.key) for c in self.secondary.c]))
elif self.foreignkey.table == self.target:
for obj in deplist:
childlist = getlist(obj)
+ clearkeys = False
for child in childlist.added_items():
associationrow = {}
self.primaryjoin.accept_visitor(setter)
uowcommit.register_saved_list(childlist)
- # TODO: deleted items
+ clearkeys = True
+ for child in childlist.deleted_items():
+ associationrow = {}
+ self.primaryjoin.accept_visitor(setter)
+ uowcommit.register_saved_list(childlist)
+ if self.private:
+ uowcommit.add_item_to_delete(child)
elif self.foreignkey.table == self.parent.table:
for child in deplist:
childlist = getlist(child)
- try:
- print "got a list and its " + repr(childlist)
- except:
- pass
+ clearkeys = False
for obj in childlist.added_items():
associationrow = {}
self.primaryjoin.accept_visitor(setter)
uowcommit.register_saved_list(childlist)
- # TODO: deleted items
+ clearkeys = True
+ for obj in childlist.deleted_items():
+ if self.private:
+ uowcommit.add_item_to_delete(obj)
+ associationrow = {}
+ self.primaryjoin.accept_visitor(setter)
+ uowcommit.register_saved_list(childlist)
else:
raise " no foreign key ?"
return True
def register_deleted(self, obj):
- pass
+ self.deleted.append(obj)
# TODO: tie in register_new/register_dirty with table transaction begins ?
def begin(self):
if len(objects):
for obj in objects:
- commit_context.append_task(obj)
+ if self.deleted.contains(obj):
+ commit_context.add_item_to_delete(obj)
+ elif self.new.contains(obj) or self.dirty.contains(obj):
+ commit_context.append_task(obj)
else:
for obj in [n for n in self.new] + [d for d in self.dirty]:
commit_context.append_task(obj)
for item in self.modified_lists:
obj = item.obj
commit_context.append_task(obj)
-
+ for obj in self.deleted:
+ commit_context.add_item_to_delete(obj)
+
engines = util.HashSet()
for mapper in commit_context.mappers:
for e in mapper.engines:
self.tasks = {}
self.saved_objects = util.HashSet()
self.saved_lists = util.HashSet()
+ self.deleted_objects = util.HashSet()
+ self.todelete = util.HashSet()
def append_task(self, obj):
mapper = self.object_mapper(obj)
def register_saved_list(self, listobj):
self.saved_lists.append(listobj)
+ def register_deleted(self, obj):
+ self.deleted_objects.append(obj)
+
+ def add_item_to_delete(self, obj):
+ self.todelete.append(obj)
+
def object_mapper(self, obj):
import sqlalchemy.mapper
try:
import thread, weakref, UserList
class OrderedProperties(object):
-
+ """an object that maintains the order in which attributes are set upon it.
+ also provides an iterator and a very basic dictionary interface to those attributes.
+ """
def __init__(self):
self.__dict__['_list'] = []
return dict.__getitem__(self, key)
class ThreadLocal(object):
+ """an object in which attribute access occurs only within the context of the current thread"""
def __init__(self):
object.__setattr__(self, 'tdict', {})
def __getattribute__(self, key):
object.__getattribute__(self, 'tdict')["%d_%s" % (thread.get_ident(), key)] = value
class HashSet(object):
+ """implements a Set."""
def __init__(self, iter = None):
self.map = {}
if iter is not None:
return self.map[key]
class HistoryArraySet(UserList.UserList):
+ """extends a UserList to provide unique-set functionality as well as history-aware
+ functionality, including information about what list elements were modified,
+ as well as rollback capability."""
def __init__(self, data = None):
# stores the array's items as keys, and a value of True, False or None indicating
# added, deleted, or unchanged for that item
class ScopedRegistry(object):
+ """a Registry that can store one or multiple instances of a single class
+ on a per-application or per-thread scoped basis"""
def __init__(self, createfunc, defaultscope):
self.createfunc = createfunc
self.defaultscope = defaultscope