[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