This commit is contained in:
Mike Bayer
2005-09-17 20:03:46 +00:00
parent 66444e0cfb
commit 5ca6c68b8d
4 changed files with 100 additions and 48 deletions
+52 -35
View File
@@ -1,7 +1,27 @@
# 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):
@@ -26,35 +46,8 @@ class SmartProperty(object):
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):
@@ -81,7 +74,7 @@ class PropHistory(object):
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 []
@@ -91,19 +84,43 @@ class PropHistory(object):
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_)
+22 -9
View File
@@ -30,7 +30,7 @@ def relation(*args, **params):
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:
@@ -434,9 +434,9 @@ class ColumnProperty(MapperProperty):
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
@@ -444,6 +444,7 @@ class PropertyLoader(MapperProperty):
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):
@@ -558,6 +559,8 @@ class PropertyLoader(MapperProperty):
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]))
@@ -568,23 +571,33 @@ class PropertyLoader(MapperProperty):
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 ?"
+16 -3
View File
@@ -166,7 +166,7 @@ class UnitOfWork(object):
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):
@@ -180,14 +180,19 @@ class UnitOfWork(object):
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:
@@ -227,6 +232,8 @@ class UOWTransaction(object):
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)
@@ -252,6 +259,12 @@ class UOWTransaction(object):
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:
+10 -1
View File
@@ -19,7 +19,9 @@ __ALL__ = ['OrderedProperties', 'OrderedDict']
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'] = []
@@ -87,6 +89,7 @@ class OrderedDict(dict):
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):
@@ -98,6 +101,7 @@ class ThreadLocal(object):
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:
@@ -128,6 +132,9 @@ class HashSet(object):
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
@@ -247,6 +254,8 @@ class HistoryArraySet(UserList.UserList):
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