From 2c82048f07b7030281c6a3fd28d072804ce004ca Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Fri, 16 Dec 2005 08:26:28 +0000 Subject: [PATCH] docstrings, who knew --- lib/sqlalchemy/attributes.py | 105 +++++++++++++++++++++++++++++------ lib/sqlalchemy/util.py | 19 ++++++- 2 files changed, 104 insertions(+), 20 deletions(-) diff --git a/lib/sqlalchemy/attributes.py b/lib/sqlalchemy/attributes.py index c42a54be59..c40dfe028c 100644 --- a/lib/sqlalchemy/attributes.py +++ b/lib/sqlalchemy/attributes.py @@ -15,14 +15,37 @@ # along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""provides a class called AttributeManager that can attach history-aware attributes to object +instances. AttributeManager-enabled object attributes can be scalar or lists. In both cases, the "change +history" of each attribute is available via the AttributeManager in a unit called a "history +container". Via the history container, which can be a scalar or list based container, +the attribute can be "committed", meaning whatever changes it has are registered as the current value, +or "rolled back", which means the original "committed" value is restored; in both cases +the accumulated history is removed. + +The change history is represented as three lists, the "added items", the "deleted items", +and the "unchanged items". In the case of a scalar attribute, these lists would be zero or +one element in length. for a list based attribute, these lists are of arbitrary length. +"unchanged items" represents the assigned value or appended values on the attribute either +with "history tracking" disabled, or have been "committed". "added items" represent new +values that have been assigned or appended to the attribute. "deleted items" represents the +the value that was previously "unchanged", but has been de-assigned or removed from the attribute. + +AttributeManager can also assign a "callable" history container to an object's attribute, +which is invoked when first accessed, to provide the object's "committed" value. + +The package includes functions for managing "bi-directional" object relationships as well +via the ListBackrefExtension, OTMBackrefExtension, and MTOBackrefExtension objects. +""" 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.""" + """Provides a property object that will communicate set/get/delete operations + to an AttributeManager. SmartProperty objects are constructed by the + create_prop method on AttributeManger, which can be overridden to provide + subclasses of SmartProperty. + """ def __init__(self, manager): self.manager = manager def attribute_registry(self): @@ -41,7 +64,10 @@ class SmartProperty(object): return property(get_prop, set_prop, del_prop) class PropHistory(object): - """manages the value of a particular scalar attribute on a particular object instance.""" + """Used by AttributeManager to track the history of a scalar attribute + on an object instance. This is the "scalar history container" object. + Has an interface similar to util.HistoryList + so that the two objects can be called upon largely interchangeably.""" # make our own NONE to distinguish from "None" NONE = object() def __init__(self, obj, key, extension=None, **kwargs): @@ -94,7 +120,10 @@ class PropHistory(object): return [] class ListElement(util.HistoryArraySet): - """manages the value of a particular list-based attribute on a particular object instance.""" + """Used by AttributeManager to track the history of a list-based object attribute. + This is the "list history container" object. + Subclasses util.HistoryArraySet to provide "onchange" event handling as well + as a plugin point for BackrefExtension objects.""" def __init__(self, obj, key, data=None, extension=None, **kwargs): self.obj = obj self.key = key @@ -140,12 +169,13 @@ class ListElement(util.HistoryArraySet): return res class CallableProp(object): - """allows the attaching of a callable item, representing the future value - of a particular attribute on a particular object instance, to - the AttributeManager. When the attributemanager - 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 - via the manager, at which point the CallableProp itself is dereferenced.""" + """Used by AttributeManager to allow the attaching of a callable item, representing the future value + 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.""" def __init__(self, manager, callable_, obj, key, uselist = False, live = False, **kwargs): self.manager = manager self.callable_ = callable_ @@ -188,6 +218,8 @@ class CallableProp(object): pass class AttributeExtension(object): + """an abstract class which specifies an "onadd" or "ondelete" operation + to be attached to an object property.""" def append(self, obj, child): pass def delete(self, obj, child): @@ -224,41 +256,53 @@ class MTOBackrefExtension(AttributeExtension): # getattr(child, self.key).append(obj) class AttributeManager(object): - """maintains a set of per-attribute callable/history manager objects for a set of objects.""" + """maintains a set of per-attribute history container objects for a set of objects.""" def __init__(self): pass def value_changed(self, obj, key, value): - """subclasses override this method to provide functionality upon an attribute change of value.""" + """subclasses override this method to provide functionality that is triggered + upon an attribute change of value.""" pass def create_prop(self, key, uselist, **kwargs): + """creates a scalar property object, defaulting to SmartProperty, which + will communicate change events back to this AttributeManager.""" return SmartProperty(self).property(key, uselist) def create_list(self, obj, key, list_, **kwargs): + """creates a history-aware list property, defaulting to a ListElement which + is a subclass of HistoryArrayList.""" return ListElement(obj, key, list_, **kwargs) def create_callable(self, obj, key, func, uselist, **kwargs): + """creates a callable container that will invoke a function the first + time an object property is accessed. The return value of the function + will become the object property's new value.""" return CallableProp(self, func, obj, key, uselist, **kwargs) def get_attribute(self, obj, key, **kwargs): + """returns the value of an object's scalar attribiute.""" try: return self.get_history(obj, key, **kwargs).getattr() except KeyError: raise AttributeError(key) def get_list_attribute(self, obj, key, **kwargs): + """returns the value of an object's list-based attribute.""" return self.get_history(obj, key, **kwargs) def set_attribute(self, obj, key, value, **kwargs): - if key == 'parent' and value is not None and value.__class__.__name__ != 'Comment': - raise "wha?" + """sets the value of an object's attribute.""" self.get_history(obj, key, **kwargs).setattr(value) self.value_changed(obj, key, value) def delete_attribute(self, obj, key, **kwargs): + """deletes the value from an object's attribute.""" self.get_history(obj, key, **kwargs).delattr() self.value_changed(obj, key, None) def rollback(self, *obj): + """rolls back all attribute changes on the given list of objects, + and removes all history.""" for o in obj: try: attributes = self.attribute_history(o) @@ -268,6 +312,8 @@ class AttributeManager(object): pass def commit(self, *obj): + """commits all attribute changes on the given list of objects, + and removes all history.""" for o in obj: try: attributes = self.attribute_history(o) @@ -276,21 +322,31 @@ class AttributeManager(object): except KeyError: pass - def remove(self, obj): + def remove(self, obj): + """not sure what this is.""" pass 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 def get_history(self, obj, key, passive=False, **kwargs): + """returns the "history" container for the given attribute on the given object. + If the container does not exist, it will be created based on the class-level + history container definition.""" try: return self.attribute_history(obj)[key].gethistory(passive=passive, **kwargs) except KeyError, e: return self.class_managed(obj.__class__)[key](obj, **kwargs).gethistory(passive=passive, **kwargs) def attribute_history(self, obj): + """returns a dictionary of "history" containers corresponding to the given object. + this dictionary is attached to the object via the attribute '_managed_attributes'. + If the dictionary does not exist, it will be created.""" try: attr = obj.__dict__['_managed_attributes'] except KeyError: @@ -299,12 +355,17 @@ class AttributeManager(object): return attr def reset_history(self, obj, key): + """removes the history object for the given attribute on the given object. + When the attribute is next accessed, a new container will be created via the + class-level history container definition.""" try: del self.attribute_history(obj)[key] except KeyError: pass 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 = getattr(class_, '_class_managed_attributes') except AttributeError: @@ -315,6 +376,7 @@ class AttributeManager(object): def create_history_container(self, obj, key, uselist, callable_ = None, **kwargs): + """creates a new history container for the given attribute on the given object.""" if callable_ is not None: return self.create_callable(obj, key, callable_, uselist=uselist, **kwargs) elif not uselist: @@ -324,7 +386,14 @@ class AttributeManager(object): return self.create_list(obj, key, list_, **kwargs) def register_attribute(self, class_, key, uselist, callable_=None, **kwargs): - # create a function, that will create this object attribute when its first called + """registers an attribute's behavior at the class level. This attribute + can be scalar or list based, and also may have a callable unit that will be + 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 + are created and the attribute first referenced, the callable is invoked to + create the new history container. Extra keyword arguments can be sent which + will be passed along to newly created history containers.""" def createprop(obj): if callable_ is not None: func = callable_(obj) diff --git a/lib/sqlalchemy/util.py b/lib/sqlalchemy/util.py index b8228986d9..35a618f6c4 100644 --- a/lib/sqlalchemy/util.py +++ b/lib/sqlalchemy/util.py @@ -157,8 +157,11 @@ class HashSet(object): 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.""" + functionality, including information about what list elements were modified + and commit/rollback capability. When a HistoryArraySet is created with or + without initial data, it is in a "committed" state. as soon as changes are made + to the list via the normal list-based access, it tracks "added" and "deleted" items, + which remain until the history is committed or rolled back.""" def __init__(self, data = None, readonly=False): # stores the array's items as keys, and a value of True, False or None indicating # added, deleted, or unchanged for that item @@ -177,6 +180,8 @@ class HistoryArraySet(UserList.UserList): data array. this allows custom list classes to be used.""" return getattr(self.data, attr) def set_data(self, data): + """sets the data for this HistoryArraySet to be that of the given data. + duplicates in the incoming list will be removed.""" # first mark everything current as "deleted" for i in self.data: self.records[i] = False @@ -190,6 +195,8 @@ class HistoryArraySet(UserList.UserList): del self.data[i] i -= 1 def history_contains(self, obj): + """returns true if the given object exists within the history + for this HistoryArrayList.""" return self.records.has_key(obj) def __hash__(self): return id(self) @@ -221,6 +228,8 @@ class HistoryArraySet(UserList.UserList): except KeyError: return False def commit(self): + """commits the added values in this list to be the new "unchanged" values. + values that have been marked as deleted are removed from the history.""" for key in self.records.keys(): value = self.records[key] if value is False: @@ -228,6 +237,7 @@ class HistoryArraySet(UserList.UserList): else: self.records[key] = None def rollback(self): + """rolls back changes to this list to the last "committed" state.""" # TODO: speed this up list = [] for key, status in self.records.iteritems(): @@ -238,16 +248,21 @@ class HistoryArraySet(UserList.UserList): for l in list: self.append_nohistory(l) def added_items(self): + """returns a list of items that have been added since the last "committed" state.""" return [key for key in self.data if self.records[key] is True] def deleted_items(self): + """returns a list of items that have been deleted since the last "committed" state.""" return [key for key, value in self.records.iteritems() if value is False] def unchanged_items(self): + """returns a list of items that have not been changed since the last "committed" state.""" return [key for key in self.data if self.records[key] is None] def append_nohistory(self, item): + """appends an item to the list without affecting the "history".""" if not self.records.has_key(item): self.records[item] = None self.data.append(item) def remove_nohistory(self, item): + """removes an item from the list without affecting the "history".""" if self.records.has_key(item): del self.records[item] self.data.remove(item) -- 2.47.2