[Zope3-Users] Re: Renaming and OrderedContainer

jürgen Kartnaller j.kartnaller at cable.vol.at
Mon Jan 31 14:21:52 EST 2005


Hi Garret,
here are the files from zope.app.container

ordered.py
configure.zcml
test.test_ordered.py

And thanks for forcing me to write the test ;)

Jürgen

Garrett Smith wrote:
> Jürgen, 
> 
> I think adapting the container to IObjectRenamer is the right thing.
> 
> Just reply to this with your new/modified files.
> 
> The tests should end up looking like those for ObjectMover and
> ObjectCopier. I'd encourage you to give it a try -- there's a learning
> curve, but you may find it as rewarding as writing adapters :) In any
> event, we'll have to have them before committing to the trunk. (I'll
> help if you need it.)
> 
>  -- Garrett
> 
> jürgen Kartnaller wrote:
> 
>>Garrett Smith wrote:
>>
>>>jürgen Kartnaller wrote:
>>>
>>>
>>>>Hello all,
>>>>I found the following problem when renaming objects :
>>>>
>>>>zope.app.copypastemove.rename uses the ObjectMover to rename an
>>>>object within a container. There is no problem as long as you are
>>>>using BTree containers. 
>>>>
>>>>If rename is used on an OrderedContainer then the object is moved to
>>>>the end of the list because ObjectMover first adds the object with
>>>>the new name to the container (OrderedContainer puts the new object
>>>>to the end of the list) and then deletes the old name.
>>>>
>>>>To solve this problem a container should have a rename function !
>>>
>>>
>>>The problem is that copypastemove.rename doesn't use something like
>>>IObjectRenamer. The default implementation of rename works fine for
>>>normal containers, but not for ordered containers.
>>>
>>>This would be straight forward to implement, if you're iterested :)
>>
>>Of course it is, even for a zope3 newbie like me
>>I added an IObjectRenamer (see below)
>>
>>
>>>A 'rename' method isn't z3onic (pronounced zee-thronic? ;) We like to
>>>tack on new functionality using the component patterns, which have
>>>some nice advantages over polymorphic methods.
>>
>>Right, with this implementation I got the understanding of adapters.
>>
>>
>>I added IObjectRenamer, implemented an ObjectRenamer (which is doing
>>the same than the ObjectMover but only within one container).
>>
>>What I found is :
>>I need to implement an adapter for OrderedContainer.
>>The problem now is, that I need to adapt to the container and not to
>>the object I want to rename.
>>IObjectMover and IObjectCopier are adapting to the object not to the
>>container, which is ok for this case.
>>For renaming I need to differentiate between different behavior of the
>>container and not the object.
>>So for IObjectRenamer I need to adapt to the container.
>>
>>Is this right ?
>>
>>I implemented OrderedContainerObjectRenamer as adapter for
>>OrderedContainer. 
>>
>>I love this adapter thing !!!
>>
>>My question is now : (for my understanding of adapters)
>>
>>Is an adapter allowed to do everything on the class it adapts ?
>>I mean, should it only use the interface of that class or is it
>>allowed to use internals of the class ?
>>I did it in my adapter to directly access _order in OrderedContainer.
>>
>>I now have a working implementation which passes the tests and, of
>>cource, is doing what my application expects.
>>I have no test for the OrderedContainer adapter because I don'nt know
>>how to implement it.
>>
>>I did my changes on the release version of zope3.
>>Garret can you check it and if it seems good to you also commit it
>>into the trunk ?
>>I send my changes to you, just let me know your email.
>>
>>
>>Jürgen
>>
>>
>>_______________________________________________
>>Zope3-users mailing list
>>Zope3-users at zope.org
>>http://mail.zope.org/mailman/listinfo/zope3-users

-------------- next part --------------
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Ordered container implementation.

$Id: ordered.py 27409 2004-09-02 07:05:38Z pruggera $
"""
__docformat__ = 'restructuredtext'

from zope.app.container.interfaces import IOrderedContainer
from zope.interface import implements
from persistent import Persistent
from persistent.dict import PersistentDict
from persistent.list import PersistentList
from types import StringTypes, TupleType, ListType
from zope.app.container.contained import Contained, setitem, uncontained
from zope.app.copypastemove.interfaces import IObjectRenamer
from zope.app.copypastemove import ObjectRenamer

class OrderedContainer(Persistent, Contained):
    """ `OrderedContainer` maintains entries' order as added and moved.

    >>> oc = OrderedContainer()
    >>> int(IOrderedContainer.providedBy(oc))
    1
    >>> len(oc)
    0
    """

    implements(IOrderedContainer)

    def __init__(self):

        self._data = PersistentDict()
        self._order = PersistentList()

    def keys(self):
        """ See `IOrderedContainer`.

        >>> oc = OrderedContainer()
        >>> oc.keys()
        []
        >>> oc['foo'] = 'bar'
        >>> oc.keys()
        ['foo']
        >>> oc['baz'] = 'quux'
        >>> oc.keys()
        ['foo', 'baz']
        >>> int(len(oc._order) == len(oc._data))
        1
        """

        return self._order[:]

    def __iter__(self):
        """ See `IOrderedContainer`.

        >>> oc = OrderedContainer()
        >>> oc.keys()
        []
        >>> oc['foo'] = 'bar'
        >>> oc['baz'] = 'quux'
        >>> [i for i in oc]
        ['foo', 'baz']
        >>> int(len(oc._order) == len(oc._data))
        1
        """

        return iter(self.keys())

    def __getitem__(self, key):
        """ See `IOrderedContainer`.

        >>> oc = OrderedContainer()
        >>> oc['foo'] = 'bar'
        >>> oc['foo']
        'bar'
        """

        return self._data[key]

    def get(self, key, default=None):
        """ See `IOrderedContainer`.

        >>> oc = OrderedContainer()
        >>> oc['foo'] = 'bar'
        >>> oc.get('foo')
        'bar'
        >>> oc.get('funky', 'No chance, dude.')
        'No chance, dude.'
        """

        return self._data.get(key, default)

    def values(self):
        """ See `IOrderedContainer`.

        >>> oc = OrderedContainer()
        >>> oc.keys()
        []
        >>> oc['foo'] = 'bar'
        >>> oc.values()
        ['bar']
        >>> oc['baz'] = 'quux'
        >>> oc.values()
        ['bar', 'quux']
        >>> int(len(oc._order) == len(oc._data))
        1
        """

        return [self._data[i] for i in self._order]

    def __len__(self):
        """ See `IOrderedContainer`.

        >>> oc = OrderedContainer()
        >>> int(len(oc) == 0)
        1
        >>> oc['foo'] = 'bar'
        >>> int(len(oc) == 1)
        1
        """

        return len(self._data)

    def items(self):
        """ See `IOrderedContainer`.

        >>> oc = OrderedContainer()
        >>> oc.keys()
        []
        >>> oc['foo'] = 'bar'
        >>> oc.items()
        [('foo', 'bar')]
        >>> oc['baz'] = 'quux'
        >>> oc.items()
        [('foo', 'bar'), ('baz', 'quux')]
        >>> int(len(oc._order) == len(oc._data))
        1
        """

        return [(i, self._data[i]) for i in self._order]

    def __contains__(self, key):
        """ See `IOrderedContainer`.

        >>> oc = OrderedContainer()
        >>> oc['foo'] = 'bar'
        >>> int('foo' in oc)
        1
        >>> int('quux' in oc)
        0
        """

        return self._data.has_key(key)

    has_key = __contains__

    def __setitem__(self, key, object):
        """ See `IOrderedContainer`.

        >>> oc = OrderedContainer()
        >>> oc.keys()
        []
        >>> oc['foo'] = 'bar'
        >>> oc._order
        ['foo']
        >>> oc['baz'] = 'quux'
        >>> oc._order
        ['foo', 'baz']
        >>> int(len(oc._order) == len(oc._data))
        1
        """

        existed = self._data.has_key(key)

        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")

        setitem(self, self._data.__setitem__, key, object)

        if not existed:
            self._order.append(key)

        return key

    def __delitem__(self, key):
        """ See `IOrderedContainer`.

        >>> oc = OrderedContainer()
        >>> oc.keys()
        []
        >>> oc['foo'] = 'bar'
        >>> oc['baz'] = 'quux'
        >>> oc['zork'] = 'grue'
        >>> oc.items()
        [('foo', 'bar'), ('baz', 'quux'), ('zork', 'grue')]
        >>> int(len(oc._order) == len(oc._data))
        1
        >>> del oc['baz']
        >>> oc.items()
        [('foo', 'bar'), ('zork', 'grue')]
        >>> int(len(oc._order) == len(oc._data))
        1
        """

        uncontained(self._data[key], self, key)
        del self._data[key]
        self._order.remove(key)

    def updateOrder(self, order):
        """ See `IOrderedContainer`.

        >>> oc = OrderedContainer()
        >>> oc['foo'] = 'bar'
        >>> oc['baz'] = 'quux'
        >>> oc['zork'] = 'grue'
        >>> oc.keys()
        ['foo', 'baz', 'zork']
        >>> oc.updateOrder(['baz', 'foo', 'zork'])
        >>> oc.keys()
        ['baz', 'foo', 'zork']
        >>> oc.updateOrder(['baz', 'zork', 'foo'])
        >>> oc.keys()
        ['baz', 'zork', 'foo']
        >>> oc.updateOrder(['baz', 'zork', 'foo'])
        >>> oc.keys()
        ['baz', 'zork', 'foo']
        >>> oc.updateOrder(('zork', 'foo', 'baz'))
        >>> oc.keys()
        ['zork', 'foo', 'baz']
        >>> oc.updateOrder(['baz', 'zork'])
        Traceback (most recent call last):
        ...
        ValueError: Incompatible key set.
        >>> oc.updateOrder(['foo', 'bar', 'baz', 'quux'])
        Traceback (most recent call last):
        ...
        ValueError: Incompatible key set.
        >>> oc.updateOrder(1)
        Traceback (most recent call last):
        ...
        TypeError: order must be a tuple or a list.
        >>> oc.updateOrder('bar')
        Traceback (most recent call last):
        ...
        TypeError: order must be a tuple or a list.
        >>> oc.updateOrder(['baz', 'zork', 'quux'])
        Traceback (most recent call last):
        ...
        ValueError: Incompatible key set.
        >>> del oc['baz']
        >>> del oc['zork']
        >>> del oc['foo']
        >>> len(oc)
        0
        """

        if not isinstance(order, ListType) and \
            not isinstance(order, TupleType):
            raise TypeError('order must be a tuple or a list.')

        if len(order) != len(self._order):
            raise ValueError("Incompatible key set.")

        was_dict = {}
        will_be_dict = {}
        new_order = PersistentList()

        for i in range(len(order)):
            was_dict[self._order[i]] = 1
            will_be_dict[order[i]] = 1
            new_order.append(order[i])

        if will_be_dict != was_dict:
            raise ValueError("Incompatible key set.")

        self._order = new_order


class OrderedContainerObjectRenamer(ObjectRenamer):
    """Adapter for renaming objects within an ordered container
    """

    implements(IObjectRenamer)

    def renameTo(self, oldName, newName):
        '''Rename oldName to newName.'''
        if newName == oldName:
            # Nothing to do
            return
        container = self.context
        oldPos=container._order.index(oldName)
        super(OrderedContainerObjectRenamer,self).renameTo(oldName,newName)
        if oldPos < (len(container._order)-1):
            container._order.remove(newName)
            container._order.insert(oldPos,newName)
        
-------------- next part --------------
<configure
   xmlns="http://namespaces.zope.org/zope"
   xmlns:browser="http://namespaces.zope.org/browser"
   xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc"
   i18n_domain="zope"
   >

  <adapter
     provides=".interfaces.IFind"
     for=".interfaces.IReadContainer"
     permission="zope.ManageContent"
     factory="zope.app.container.find.FindAdapter"
     />

  <adapter
      for=".interfaces.IReadContainer"
      provides="zope.app.filerepresentation.interfaces.IReadDirectory"
      factory=".directory.noop"
      />

  <adapter
      for=".interfaces.IWriteContainer"
      provides="zope.app.filerepresentation.interfaces.IWriteDirectory"
      factory=".directory.noop"
      />

  <adapter
      factory="zope.app.container.traversal.ContainerTraversable"
      provides="zope.app.traversing.interfaces.ITraversable"
      for="zope.app.container.interfaces.IReadContainer"
      />


  <adapter
      factory="zope.app.container.size.ContainerSized"
      provides="zope.app.size.interfaces.ISized"
      for="zope.app.container.interfaces.IReadContainer"
      />

  <adapter
      provides=".interfaces.INameChooser"
      for="zope.app.container.interfaces.IWriteContainer"
      factory=".contained.NameChooser"
      />

  <adapter
      factory="zope.app.container.ordered.OrderedContainerObjectRenamer"
      provides="zope.app.copypastemove.interfaces.IObjectRenamer"
      permission="zope.ManageContent"
      for="zope.app.container.interfaces.IOrderedContainer"
      trusted="y"
      />

  <subscriber
      factory=".dependency.CheckDependency"
      for="zope.app.container.interfaces.IObjectRemovedEvent"
      trusted="y"
      />

  <subscriber
      for="zope.app.location.interfaces.ILocation
           zope.app.container.interfaces.IObjectMovedEvent"
      factory=".contained.dispatchToSublocations"
      >
      Handler dispatches moved events to sublocations of the original object.
  </subscriber>

  <adapter
      provides="zope.app.location.interfaces.ISublocations"
      for="zope.app.container.interfaces.IReadContainer"
      factory=".contained.ContainerSublocations"
      />

  <content class=".constraints.ItemTypePrecondition">
    <allow interface=".constraints.IItemTypePrecondition" />
  </content>

</configure>
-------------- next part --------------
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test the OrderedContainer.


$Id: test_ordered.py 25177 2004-06-02 13:17:31Z jim $
"""

from unittest import TestCase, TestSuite, main, makeSuite

from zope.interface import *
from zope.testing.doctestunit import DocTestSuite
from zope.interface import Interface
from zope.app.tests import ztapi
from zope.app.tests.placelesssetup import setUp, tearDown

from zope.exceptions import NotFoundError, DuplicationError
from zope.app.traversing.api import traverse
from zope.app.site.tests.placefulsetup import PlacefulSetup
from zope.app.copypastemove.interfaces import IObjectRenamer
from zope.app.copypastemove import rename
from zope.app.folder import rootFolder

from zope.app.container.ordered import OrderedContainer
from zope.app.container.ordered import OrderedContainerObjectRenamer

class File(object):
    pass

class OrderedContainerTest(PlacefulSetup, TestCase):

    def setUp(self):
        PlacefulSetup.setUp(self)
        root = self.rootFolder = rootFolder()
        root['folder1'] = OrderedContainer()
        ztapi.provideAdapter(None,
                             IObjectRenamer,
                             OrderedContainerObjectRenamer)

    def test_simplerename(self):
        root = self.rootFolder
        folder1 = traverse(root, 'folder1')
        self.failIf('file1' in folder1)
        folder1['file1'] = File()
        rename(folder1, 'file1', 'my_file1')
        self.failIf('file1' in folder1)
        self.failUnless('my_file1' in folder1)

    def test_renamenonexisting(self):
        root = self.rootFolder
        folder1 = traverse(root, 'folder1')
        self.failIf('a_test_file' in folder1)
        self.assertRaises(NotFoundError, rename, folder1, 'file1', 'my_file1')

    def test_renamesamename(self):
        root = self.rootFolder
        folder1 = traverse(root, 'folder1')
        self.failIf('file1' in folder1)
        self.failIf('file2' in folder1)
        folder1['file1'] = File()
        folder1['file2'] = File()
        self.assertRaises(DuplicationError, rename, folder1, 'file1', 'file2')

    def test_renamer(self):
        root = self.rootFolder
        container = root['orderedfolder'] = OrderedContainer()
        container['file1'] = File()
        container['file2'] = File()
        container['file3'] = File()
        self.failUnlessEqual(container.keys(),['file1','file2','file3'])
        rename(container,'file1','file11')
        self.failUnlessEqual(container.keys(),['file11','file2','file3'])
        rename(container,'file2','file21')
        self.failUnlessEqual(container.keys(),['file11','file21','file3'])
        rename(container,'file3','file31')
        self.failUnlessEqual(container.keys(),['file11','file21','file31'])
        
def test_suite():
    suite = makeSuite(OrderedContainerTest)
    suite.addTest(DocTestSuite("zope.app.container.ordered",
                               setUp=setUp, tearDown=tearDown))

    return suite

if __name__ == '__main__':
    main(defaultTest='test_suite')


More information about the Zope3-users mailing list