From: Mike Bayer Date: Sat, 17 Sep 2005 20:03:46 +0000 (+0000) Subject: (no commit message) X-Git-Tag: rel_0_1_0~688 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5ca6c68b8dcecd8f0d52ff4ed93d78998ac41eda;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git --- diff --git a/lib/sqlalchemy/attributes.py b/lib/sqlalchemy/attributes.py index bf28111df7..06ed30605e 100644 --- a/lib/sqlalchemy/attributes.py +++ b/lib/sqlalchemy/attributes.py @@ -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_) diff --git a/lib/sqlalchemy/mapper.py b/lib/sqlalchemy/mapper.py index 98f225403c..9ededa8dfb 100644 --- a/lib/sqlalchemy/mapper.py +++ b/lib/sqlalchemy/mapper.py @@ -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 ?" diff --git a/lib/sqlalchemy/objectstore.py b/lib/sqlalchemy/objectstore.py index 8d46fd6b6b..7038c16576 100644 --- a/lib/sqlalchemy/objectstore.py +++ b/lib/sqlalchemy/objectstore.py @@ -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: diff --git a/lib/sqlalchemy/util.py b/lib/sqlalchemy/util.py index ae0442c16a..38b371a263 100644 --- a/lib/sqlalchemy/util.py +++ b/lib/sqlalchemy/util.py @@ -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