[Zope3-checkins] CVS: Zope3/src/zope/app/container - contained.py:1.1.2.4 ordered.py:1.5.10.3 sample.py:1.7.24.4

Jim Fulton jim at zope.com
Mon Sep 15 14:13:01 EDT 2003


Update of /cvs-repository/Zope3/src/zope/app/container
In directory cvs.zope.org:/tmp/cvs-serv15511/src/zope/app/container

Modified Files:
      Tag: parentgeddon-branch
	contained.py ordered.py sample.py 
Log Message:
Got lots of tests to pass.

Added a setitem helper function to be used to help satisfy container
contracts.



=== Zope3/src/zope/app/container/contained.py 1.1.2.3 => 1.1.2.4 ===
--- Zope3/src/zope/app/container/contained.py:1.1.2.3	Fri Sep 12 16:25:20 2003
+++ Zope3/src/zope/app/container/contained.py	Mon Sep 15 14:12:30 2003
@@ -16,12 +16,12 @@
 $Id$
 """
 
+from zope.app import zapi
 from zope.app.decorator import DecoratedSecurityCheckerDescriptor
 from zope.app.decorator import DecoratorSpecificationDescriptor
-from zope.app.event import objectevent
+from zope.app.event.objectevent import ObjectEvent, modified
 from zope.app.event import publish
 from zope.app.i18n import ZopeMessageIDFactory as _
-from zope.app import zapi
 from zope.app.interfaces.container import IAddNotifiable, IMoveNotifiable
 from zope.app.interfaces.container import IContained
 from zope.app.interfaces.container import INameChooser
@@ -30,6 +30,7 @@
 from zope.app.interfaces.container import IObjectRemovedEvent
 from zope.app.interfaces.container import IRemoveNotifiable
 from zope.app.interfaces.location import ILocation
+from zope.exceptions import DuplicationError
 from zope.proxy import ProxyBase, getProxiedObject
 import zope.interface
 
@@ -41,13 +42,13 @@
 
     __parent__ = __name__ = None
 
-class ObjectMovedEvent(objectevent.ObjectEvent):
+class ObjectMovedEvent(ObjectEvent):
     """An object has been moved"""
 
     zope.interface.implements(IObjectMovedEvent)
 
     def __init__(self, object, oldParent, oldName, newParent, newName):
-        objectevent.ObjectEvent.__init__(self, object)
+        ObjectEvent.__init__(self, object)
         self.oldParent = oldParent
         self.oldName = oldName
         self.newParent = newParent
@@ -78,21 +79,69 @@
         ObjectMovedEvent.__init__(self, object, oldParent, oldName, None, None)
 
 
-def contained(object, container, name=None):
+def containedEvent(object, container, name=None):
     """Establish the containment of the object in the container
 
+    The object and necessary event are returned. The object may be a
+    ContainedProxy around the original object. The event is an added
+    event, a moved event, or None.
+
     If the object implements IContained, simply set it's __parent__
     and __name attributes:
 
     >>> container = {}
     >>> item = Contained()
-    >>> x = contained(item, container, 'foo')
+    >>> x, event = containedEvent(item, container, u'foo')
     >>> x is item
     1
     >>> item.__parent__ is container
     1
     >>> item.__name__
-    'foo'
+    u'foo'
+
+    We have an added event:
+
+    >>> event.__class__.__name__
+    'ObjectAddedEvent'
+    >>> event.object is item
+    1
+    >>> event.newParent is container
+    1
+    >>> event.newName
+    u'foo'
+    >>> event.oldParent
+    >>> event.oldName
+
+    Now if we call contained again:
+
+    >>> x2, event = containedEvent(item, container, u'foo')
+    >>> x2 is item
+    1
+    >>> item.__parent__ is container
+    1
+    >>> item.__name__
+    u'foo'
+
+    We don't get a new added event:
+
+    >>> event
+
+    If the object already had a parent but the parent or name was
+    different, we get a moved event:
+    
+    >>> x, event = containedEvent(item, container, u'foo2')
+    >>> event.__class__.__name__
+    'ObjectMovedEvent'
+    >>> event.object is item
+    1
+    >>> event.newParent is container
+    1
+    >>> event.newName
+    u'foo2'
+    >>> event.oldParent is container
+    1
+    >>> event.oldName
+    u'foo'
     
     If the object implements ILocation, but not IContained, set it's
     __parent__ and __name__ attributes *and* declare that it
@@ -102,7 +151,7 @@
     >>> item = Location()
     >>> IContained.isImplementedBy(item)
     0
-    >>> x = contained(item, container, 'foo')
+    >>> x, event = containedEvent(item, container, 'foo')
     >>> x is item
     1
     >>> item.__parent__ is container
@@ -116,14 +165,14 @@
     ContainedProxy around it:
 
     >>> item = []
-    >>> x = contained(item, container, 'foo')
+    >>> x, event = containedEvent(item, container, 'foo')
     >>> x is item
     0
     >>> x.__parent__ is container
     1
     >>> x.__name__
     'foo'
-    
+
     """
 
     if not IContained.isImplementedBy(object):
@@ -134,66 +183,369 @@
 
     oldparent = object.__parent__
     oldname = object.__name__
+
+    if oldparent is container and oldname == name:
+        # No events
+        return object, None
     
-    if oldparent is not container or oldname != name:
-        object.__parent__ = container
-        object.__name__ = name
+    object.__parent__ = container
+    object.__name__ = name
 
-        if oldparent is None:
-            event = ObjectAddedEvent(object, container, name)
-            a = zapi.queryAdapter(object, IAddNotifiable)
-            if a is not None:
-                a.addNotify(event)
-        else:
-            event = ObjectMovedEvent(
-                object, oldparent, oldname, container, name)
+    if oldparent is None:
+        event = ObjectAddedEvent(object, container, name)
+    else:
+        event = ObjectMovedEvent(object, oldparent, oldname, container, name)
 
-        a = zapi.queryAdapter(object, IMoveNotifiable)
-        if a is not None:
-            a.moveNotify(event)
-        publish(container, event)
+    return object, event
 
-    event = objectevent.ObjectModifiedEvent(container)
-    publish(container, event)
+def contained(object, container, name=None):
+    """Establish the containment of the object in the container
 
-    return object
+    Just return the contained object without an event. This is a convenience
+    "macro" for:
 
-def uncontained(object, container):
-    """Clear the containment relationship between the object amd the container
+       containedEvent(object, container, name)[0]
+
+    This function is only used for tests.
+    """
+    return containedEvent(object, container, name)[0]
+
+def setitem(container, setitemf, name, object):
+    """Helper function to set an item and generate needed events
+
+    This helper is needed, in part, because the events need to get
+    published after the object has been added to the container.
+
+    If the item implements IContained, simply set it's __parent__
+    and __name attributes:
+
+    >>> class Item(Contained):
+    ...     zope.interface.implements(IAddNotifiable, IMoveNotifiable)
+    ...     def addNotify(self, event):
+    ...         self.added = event
+    ...     def moveNotify(self, event):
+    ...         self.moved = event
+
+    >>> item = Item()
 
     >>> container = {}
-    >>> item = Contained()
-    >>> x = contained(item, container, 'foo')
-    >>> x is item
+    >>> setitem(container, container.__setitem__, u'c', item)
+    >>> container[u'c'] is item
     1
     >>> item.__parent__ is container
     1
     >>> item.__name__
-    'foo'
+    u'c'
+
+    If we run this using the testing framework, we'll use getEvents to
+    track the events generated:
+
+    >>> from zope.app.event.tests.placelesssetup import getEvents
+    >>> from zope.app.interfaces.container import IObjectAddedEvent
+    >>> from zope.app.interfaces.event import IObjectModifiedEvent
+
+    We have an added event:
+    
+    >>> len(getEvents(IObjectAddedEvent))
+    1
+    >>> event = getEvents(IObjectAddedEvent)[-1]
+    >>> event.object is item
+    1
+    >>> event.newParent is container
+    1
+    >>> event.newName
+    u'c'
+    >>> event.oldParent
+    >>> event.oldName
+
+    As well as a modification event for the container:
+    
+    >>> len(getEvents(IObjectModifiedEvent))
+    1
+    >>> getEvents(IObjectModifiedEvent)[-1].object is container
+    1
+
+    The item's hooks have been called:
+
+    >>> item.added is event
+    1
+    >>> item.moved is event
+    1
+
+    We can suppress events and hooks by setting the __parent__ and
+    __name__ first:
+
+    >>> item = Item()
+    >>> item.__parent__, item.__name__ = container, 'c2'
+    >>> setitem(container, container.__setitem__, u'c2', item)
+    >>> len(container)
+    2
+    >>> len(getEvents(IObjectAddedEvent))
+    1
+    >>> len(getEvents(IObjectModifiedEvent))
+    1
+
+    >>> getattr(item, 'added', None)
+    >>> getattr(item, 'moved', None)
+
+    If the item had a parent or name (as in a move or rename),
+    we generate a move event, rather than an add event:
+
+    >>> setitem(container, container.__setitem__, u'c3', item)
+    >>> len(container)
+    3
+    >>> len(getEvents(IObjectAddedEvent))
+    1
+    >>> len(getEvents(IObjectModifiedEvent))
+    2
+    >>> from zope.app.interfaces.container import IObjectMovedEvent
+    >>> len(getEvents(IObjectMovedEvent))
+    2
+
+    (Note that we have 2 move events because add are move events.)
+
+    We also get the move hook called, but not the add hook:
+    
+    >>> event = getEvents(IObjectMovedEvent)[-1]
+    >>> getattr(item, 'added', None)
+    >>> item.moved is event
+    1
+
+    If we try to replace an item without deleting it first, we'll get
+    an error:
+
+    >>> setitem(container, container.__setitem__, u'c', [])
+    Traceback (most recent call last):
+    ...
+    DuplicationError: c
 
-    >>> x = uncontained(item, container)
+
+    >>> del container[u'c']
+    >>> setitem(container, container.__setitem__, u'c', [])
+    >>> len(getEvents(IObjectAddedEvent))
+    2
+    >>> len(getEvents(IObjectModifiedEvent))
+    3
+    
+        
+    If the object implements ILocation, but not IContained, set it's
+    __parent__ and __name__ attributes *and* declare that it
+    implements IContained:
+
+    >>> from zope.app.location import Location
+    >>> item = Location()
+    >>> IContained.isImplementedBy(item)
+    0
+    >>> setitem(container, container.__setitem__, u'l', item)
+    >>> container[u'l'] is item
+    1
     >>> item.__parent__ is container
-    False
-    >>> item.__name__    
+    1
+    >>> item.__name__
+    u'l'
+    >>> IContained.isImplementedBy(item)
+    1
+
+    We get new added and modification events:
+
+    >>> len(getEvents(IObjectAddedEvent))
+    3
+    >>> len(getEvents(IObjectModifiedEvent))
+    4
+
+    If the object doesn't even implement ILocation, put a
+    ContainedProxy around it:
+
+    >>> item = []
+    >>> setitem(container, container.__setitem__, u'i', item)
+    >>> container[u'i']
+    []
+    >>> container[u'i'] is item
+    0
+    >>> item = container[u'i']
+    >>> item.__parent__ is container
+    1
+    >>> item.__name__
+    u'i'
+    >>> IContained.isImplementedBy(item)
+    1
+
+    >>> len(getEvents(IObjectAddedEvent))
+    4
+    >>> len(getEvents(IObjectModifiedEvent))
+    5
+
+    We'll get type errors if we give keys that aren't unicode or ascii keys:
+    
+    >>> setitem(container, container.__setitem__, 42, item)
+    Traceback (most recent call last):
+    ...
+    TypeError: name not unicode or ascii string
+
+    >>> setitem(container, container.__setitem__, None, item)
+    Traceback (most recent call last):
+    ...
+    TypeError: name not unicode or ascii string
+
+    >>> setitem(container, container.__setitem__, 'hello ' + chr(200), item)
+    Traceback (most recent call last):
+    ...
+    TypeError: name not unicode or ascii string
+
+    and we'll get a value error of we give an empty string or unicode:
+
+    >>> setitem(container, container.__setitem__, '', item)
+    Traceback (most recent call last):
+    ...
+    ValueError: empty names are not allowed
+
+    >>> setitem(container, container.__setitem__, u'', item)
+    Traceback (most recent call last):
+    ...
+    ValueError: empty names are not allowed
     
     """
-    oldparent = object.__parent__
-    if oldparent is container:
-        event = ObjectRemovedEvent(
-            object, oldparent, object.__name__)
-        a = zapi.queryAdapter(object, IRemoveNotifiable)
-        if a is not None:
-            a.removeNotify(event)
+
+    # Do basic name check:
+    if isinstance(name, str):
+        try:
+            name = unicode(name)
+        except UnicodeError:
+            raise TypeError("name not unicode or ascii string")
+    elif not isinstance(name, unicode):
+        raise TypeError("name not unicode or ascii string")
+
+    if not name:
+        raise ValueError("empty names are not allowed")
+
+    old = container.get(name)
+    if old is object:
+        return
+    if old is not None:
+        raise DuplicationError(name)
+
+    object, event = containedEvent(object, container, name)
+    setitemf(name, object)
+    if event:
+        if event.__class__ is ObjectAddedEvent:
+            a = zapi.queryAdapter(object, IAddNotifiable)
+            if a is not None:
+                a.addNotify(event)
         a = zapi.queryAdapter(object, IMoveNotifiable)
         if a is not None:
             a.moveNotify(event)
         publish(container, event)
+        modified(container)    
+
 
-        object.__parent__ = None
-        object.__name__ = None
+def uncontained(object, container, name=None):
+    """Clear the containment relationship between the object amd the container
+
+    If we run this using the testing framework, we'll use getEvents to
+    track the events generated:
+
+    >>> from zope.app.event.tests.placelesssetup import getEvents
+    >>> from zope.app.interfaces.container import IObjectRemovedEvent
+    >>> from zope.app.interfaces.event import IObjectModifiedEvent
+
+    We'll start by creating a container with an item:
+
+    >>> class Item(Contained):
+    ...     zope.interface.implements(IRemoveNotifiable)
+    ...     def removeNotify(self, event):
+    ...         self.removed = event
+
+    >>> item = Item()
+    >>> container = {u'foo': item}
+    >>> x, event = containedEvent(item, container, u'foo')
+    >>> item.__parent__ is container
+    1
+    >>> item.__name__
+    u'foo'
+
+    Now we'll remove the item. It's parent and name are cleared:
+
+    >>> uncontained(item, container, u'foo')
+    >>> item.__parent__
+    >>> item.__name__    
+
+    We now have a new removed event:
+    
+    >>> len(getEvents(IObjectRemovedEvent))
+    1
+    >>> event = getEvents(IObjectRemovedEvent)[-1]
+    >>> event.object is item
+    1
+    >>> event.oldParent is container
+    1
+    >>> event.oldName
+    u'foo'
+    >>> event.newParent
+    >>> event.newName
+
+    As well as a modification event for the container:
+    
+    >>> len(getEvents(IObjectModifiedEvent))
+    1
+    >>> getEvents(IObjectModifiedEvent)[-1].object is container
+    1
 
-    event = objectevent.ObjectModifiedEvent(container)
+    The reoved hook was also called:
+
+    >>> item.removed is event
+    1
+
+    Now if we call uncontained again:
+
+    >>> uncontained(item, container, u'foo')
+
+    We won't get any new events, because __parent__ and __name__ are None:
+
+    >>> len(getEvents(IObjectRemovedEvent))
+    1
+    >>> len(getEvents(IObjectModifiedEvent))
+    1
+
+    But, if either the name or parent are not None and they are not
+    the container and the old name, we'll get a modified event but not
+    a removed event.
+
+    >>> item.__parent__, item.__name__ = container, None
+    >>> uncontained(item, container, u'foo')
+    >>> len(getEvents(IObjectRemovedEvent))
+    1
+    >>> len(getEvents(IObjectModifiedEvent))
+    2
+
+    >>> item.__parent__, item.__name__ = None, u'bar'
+    >>> uncontained(item, container, u'foo')
+    >>> len(getEvents(IObjectRemovedEvent))
+    1
+    >>> len(getEvents(IObjectModifiedEvent))
+    3
+
+    """
+    oldparent = object.__parent__
+    oldname = object.__name__
+
+    if oldparent is not container or oldname != name:
+        if oldparent is not None or oldname is not None:
+            modified(container)
+        return
+    
+    event = ObjectRemovedEvent(object, oldparent, oldname)
+    a = zapi.queryAdapter(object, IRemoveNotifiable)
+    if a is not None:
+        a.removeNotify(event)
+    a = zapi.queryAdapter(object, IMoveNotifiable)
+    if a is not None:
+        a.moveNotify(event)
     publish(container, event)
+
+    object.__parent__ = None
+    object.__name__ = None
+    modified(container)
+
 
 class ContainedProxy(ProxyBase):
     """Contained-object proxy


=== Zope3/src/zope/app/container/ordered.py 1.5.10.2 => 1.5.10.3 ===
--- Zope3/src/zope/app/container/ordered.py:1.5.10.2	Fri Sep 12 15:15:20 2003
+++ Zope3/src/zope/app/container/ordered.py	Mon Sep 15 14:12:30 2003
@@ -23,7 +23,7 @@
 from persistence.dict import PersistentDict
 from persistence.list import PersistentList
 from types import StringTypes, TupleType, ListType
-from zope.app.container.contained import Contained, contained, uncontained
+from zope.app.container.contained import Contained, setitem, uncontained
 
 class OrderedContainer(Persistent, Contained):
     """ OrderedContainer maintains entries' order as added and moved.
@@ -196,7 +196,7 @@
         if len(key) == 0:
             raise ValueError("The key cannot be an empty string")
 
-        self._data[key] = contained(object, self, key)
+        setitem(self, self._data.__setitem__, key, object)
 
         if not existed:
             self._order.append(key)
@@ -223,7 +223,7 @@
         1
         """
 
-        uncontained(self._data[key], self)
+        uncontained(self._data[key], self, key)
         del self._data[key]
         self._order.remove(key)
 


=== Zope3/src/zope/app/container/sample.py 1.7.24.3 => 1.7.24.4 ===
--- Zope3/src/zope/app/container/sample.py:1.7.24.3	Fri Sep 12 16:14:24 2003
+++ Zope3/src/zope/app/container/sample.py	Mon Sep 15 14:12:30 2003
@@ -26,7 +26,7 @@
 
 from zope.app.interfaces.container import IContainer
 from zope.interface import implements
-from zope.app.container.contained import Contained, contained, uncontained
+from zope.app.container.contained import Contained, setitem, uncontained
 
 class SampleContainer(Contained):
     """Sample container implementation suitable for testing.
@@ -55,7 +55,7 @@
         return self.__data.keys()
 
     def __iter__(self):
-        return iter(self.__data.keys())
+        return iter(self.__data)
 
     def __getitem__(self, key):
         '''See interface IReadContainer'''
@@ -85,24 +85,9 @@
 
     def __setitem__(self, key, object):
         '''See interface IWriteContainer'''
-        bad = False
-        if isinstance(key, StringTypes):
-            try:
-                unicode(key)
-            except UnicodeError:
-                bad = True
-        else:
-            bad = True
-        if bad:
-            raise TypeError("'%s' is invalid, the key must be an "
-                            "ascii or unicode string" % key)
-        if len(key) == 0:
-            raise ValueError("The key cannot be an empty string")
-
-        self.__data[key] = contained(object, self, key)
-        return key
+        setitem(self, self.__data.__setitem__, key, object)
 
     def __delitem__(self, key):
         '''See interface IWriteContainer'''
-        uncontained(self.__data[key], self)
+        uncontained(self.__data[key], self, key)
         del self.__data[key]




More information about the Zope3-Checkins mailing list