[Zope3-dev] proposed changes to contained helper functions
Garrett Smith
garrett at mojave-corp.com
Fri Sep 26 13:50:03 EDT 2003
This is a mini-proposal based on my experience in refactoring for
containergeddon.
My overall feedback is that the geddon is a *huge* improvement in the
maintainability of container/contained relationships. I'm delighted with
the prospect of never seeing the word 'wrapper' in our code again :)
However, the current set of helper functions (setitem, containedEvent,
uncontained) are very specific to containers. While this may make sense
for most applications, I have a couple of use cases that fall outside
the current design:
1. I need to setup parent/child (i.e. container/contained) relationships
for object attributes:
class Foo:
def __init__(self):
self.bar = Bar() # bar is a child of a Foo object
In this case, I'm interested in setting up parent/child relationships,
but not generating events. I would be inclined to use containedEvent for
this case, but it creates an event and its semantics are wrong.
2. I have a class that uses a container internally to store objects, but
is itself not a container:
class Foo:
def __init__(self):
self._data = {}
def addBar(self, name, bar):
self._data[name] = bar # bar is a child a Foo object
# other bar related methods, e.g. getBars, containsBar, etc.
In this case, I'm interested in setting up parent/child relationships
*and* generating events. I would be inclined to use setitem, but setitem
assumes that the parent implements IContainer, which in this case false.
Below are my proposed changes:
1. Add a new helper function 'containedObject'
def containedObject(object, parent, name=None):
if not IContained.isImplementedBy(object):
if ILocation.isImplementedBy(object):
zope.interface.directlyProvides(object, IContained)
else:
object = ContainedProxy(object)
object.__parent__ = parent
object.__name__ = name
return object
The class Foo in case 1 would be as follows:
class Foo:
def __init__(self):
self.bar = containedObject(Bar(), self, 'bar')
2. Add a new helper function 'setContained':
def setContained(parent, setf, getf, name, object):
# 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 = getf(name)
if old is object:
return
if old is not None:
raise DuplicationError(name)
object, event = containedEvent(object, parent, name)
setf(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)
This would allow Foo's addBar method to be as follows:
def addBar(self, name, bar):
setContained(
parent=self,
setf=self._data.__setitem__,
getf=self._data.get,
name=name,
object=bar)
This function is identical to setitem with the exception that it deals
with a generic parent rather than a container.
This function would allow setitem to be spelled as follows:
def setitem(container, setitemf, name, object):
setContained(container, setitemf, container.get, name, object)
3. Rename 'container' arg in containedEvent and uncontained to 'parent'
I agree with the decision to implement the container bookkeeping
directly in container classes rather than through adapters -- it's much
simpler and therefore less prone to error. However, I think the current
implementation is a bit too inflexible. These proposed changes are an
attempt to add enough flexibility without having to resort to adaptation.
-- Garrett
More information about the Zope3-dev
mailing list