[Checkins] SVN: zc.intid/trunk/ initial implementation

Fred Drake fdrake at gmail.com
Thu Nov 19 17:54:33 EST 2009


Log message for revision 105893:
  initial implementation

Changed:
  _U  zc.intid/trunk/
  A   zc.intid/trunk/README.txt
  A   zc.intid/trunk/buildout.cfg
  A   zc.intid/trunk/setup.py
  A   zc.intid/trunk/src/
  A   zc.intid/trunk/src/zc/
  A   zc.intid/trunk/src/zc/__init__.py
  A   zc.intid/trunk/src/zc/intid/
  A   zc.intid/trunk/src/zc/intid/__init__.py
  A   zc.intid/trunk/src/zc/intid/configure.zcml
  A   zc.intid/trunk/src/zc/intid/tests.py
  A   zc.intid/trunk/src/zc/intid/utility.py

-=-

Property changes on: zc.intid/trunk
___________________________________________________________________
Added: svn:ignore
   + .installed.cfg
bin
build
develop-eggs
dist
eggs
parts


Added: zc.intid/trunk/README.txt
===================================================================
--- zc.intid/trunk/README.txt	                        (rev 0)
+++ zc.intid/trunk/README.txt	2009-11-19 22:54:33 UTC (rev 105893)
@@ -0,0 +1,24 @@
+==============================================
+zc.intid - Reduced-conflict integer id utility
+==============================================
+
+This package provides an API to create integer ids for any object.
+Objects can later be looked up by their id as well.  This functionality
+is commonly used in situations where dealing with objects is
+undesirable, such as in search indices or any code that needs an easy
+hash of an object.
+
+This is similar to the ``zope.intid`` package, but has fewer
+dependencies and induces fewer conflict errors, since object ids are not
+used as part of the stored data.  The id for an object is stored in an
+attribute of the object itself, with the attribute name being configured
+by the construction of the id utility.
+
+
+Changes
+=======
+
+1.0.0 (unreleased)
+------------------
+
+- Initial release.


Property changes on: zc.intid/trunk/README.txt
___________________________________________________________________
Added: svn:mime-type
   + text/plain
Added: svn:eol-style
   + native

Added: zc.intid/trunk/buildout.cfg
===================================================================
--- zc.intid/trunk/buildout.cfg	                        (rev 0)
+++ zc.intid/trunk/buildout.cfg	2009-11-19 22:54:33 UTC (rev 105893)
@@ -0,0 +1,7 @@
+[buildout]
+develop = .
+parts = test
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = zc.intid


Property changes on: zc.intid/trunk/buildout.cfg
___________________________________________________________________
Added: svn:mime-type
   + text/plain
Added: svn:eol-style
   + native

Added: zc.intid/trunk/setup.py
===================================================================
--- zc.intid/trunk/setup.py	                        (rev 0)
+++ zc.intid/trunk/setup.py	2009-11-19 22:54:33 UTC (rev 105893)
@@ -0,0 +1,55 @@
+##############################################################################
+#
+# Copyright (c) 2006, 2009 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.
+#
+##############################################################################
+
+import os
+import setuptools
+
+
+def read(*rnames):
+    return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
+
+
+setuptools.setup(
+    name="zc.intid",
+    version="1.0.0dev",
+    author="Zope Corporation and Contributors",
+    author_email="zope-dev at zope.org",
+    description="Conflict-Free Integer Id Utility",
+    long_description=read("README.txt"),
+    keywords="zope3 integer id utility",
+    classifiers=[
+        #"Development Status :: 5 - Production/Stable",
+        "Environment :: Web Environment",
+        "Intended Audience :: Developers",
+        "License :: OSI Approved :: Zope Public License",
+        "Programming Language :: Python",
+        "Natural Language :: English",
+        "Operating System :: OS Independent",
+        "Topic :: Internet :: WWW/HTTP",
+        "Framework :: Zope3"],
+    url="http://pypi.python.org/pypi/zc.intid",
+    license="ZPL 2.1",
+    packages=setuptools.find_packages("src"),
+    package_dir={"": "src"},
+    namespace_packages=["zc"],
+    install_requires=["setuptools",
+                      "ZODB3",
+                      "zope.component",
+                      "zope.event",
+                      "zope.interface",
+                      "zope.security",
+                      ],
+    include_package_data=True,
+    zip_safe=False,
+    )


Property changes on: zc.intid/trunk/setup.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native

Added: zc.intid/trunk/src/zc/__init__.py
===================================================================
--- zc.intid/trunk/src/zc/__init__.py	                        (rev 0)
+++ zc.intid/trunk/src/zc/__init__.py	2009-11-19 22:54:33 UTC (rev 105893)
@@ -0,0 +1,11 @@
+# This is a Python namespace package.
+
+try:
+    import pkg_resources
+except ImportError:
+    import pkgutil
+    __path__ = pkgutil.extend_path(__path__, __name__)
+    del pkgutil
+else:
+    pkg_resources.declare_namespace(__name__)
+    del pkg_resources


Property changes on: zc.intid/trunk/src/zc/__init__.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native

Added: zc.intid/trunk/src/zc/intid/__init__.py
===================================================================
--- zc.intid/trunk/src/zc/intid/__init__.py	                        (rev 0)
+++ zc.intid/trunk/src/zc/intid/__init__.py	2009-11-19 22:54:33 UTC (rev 105893)
@@ -0,0 +1,122 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002, 2009 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.
+#
+##############################################################################
+"""\
+Interfaces for the unique id utility.
+
+"""
+
+import zope.interface
+
+
+class IIntIdsQuery(zope.interface.Interface):
+
+    def getObject(uid):
+        """Return an object by its unique id"""
+
+    def getId(ob):
+        """Get a unique id of an object.
+        """
+
+    def queryObject(uid, default=None):
+        """Return an object by its unique id
+
+        Return the default if the uid isn't registered
+        """
+
+    def queryId(ob, default=None):
+        """Get a unique id of an object.
+
+        Return the default if the object isn't registered
+        """
+
+    def __iter__():
+        """Return an iteration on the ids"""
+
+
+class IIntIdsSet(zope.interface.Interface):
+
+    def register(ob):
+        """Register an object and returns a unique id generated for it.
+
+        The object *must* be adaptable to IKeyReference.
+
+        If the object is already registered, its id is returned anyway.
+        """
+
+    def unregister(ob):
+        """Remove the object from the indexes.
+
+        KeyError is raised if ob is not registered previously.
+        """
+
+class IIntIdsManage(zope.interface.Interface):
+    """Some methods used by the view."""
+
+    def __len__():
+        """Return the number of objects indexed."""
+
+    def items():
+        """Return a list of (id, object) pairs."""
+
+
+class IIntIds(IIntIdsSet, IIntIdsQuery, IIntIdsManage):
+    """A utility that assigns unique ids to objects.
+
+    Allows to query object by id and id by object.
+    """
+
+
+class IIntIdsSubclassOverride(zope.interface.Interface):
+    """Methods that subclasses can usefully override."""
+
+    def generateId(ob):
+        """Return a new iid that isn't already used.
+
+        ``ob`` is the object the id is being generated for.
+
+        The default behavior is to generate arbitrary integers without
+        reference to the objects they're generated for.
+
+        """
+
+
+class IIdEvent(zope.interface.Interface):
+    """Generic base interface for IntId-related events"""
+
+    object = zope.interface.Attribute(
+        "The object related to this event")
+
+    idmanager = zope.interface.Attribute(
+        "The int id utility generating the event.")
+
+    id = zope.interface.Attribute(
+        "The id that is being assigned or unassigned.")
+
+
+class IIdRemovedEvent(IIdEvent):
+    """A unique id will be removed
+
+    The event is published before the unique id is removed
+    from the utility so that the indexing objects can unindex the object.
+
+    """
+
+
+class IIdAddedEvent(IIdEvent):
+    """A unique id has been added
+
+    The event gets sent when an object is registered in a
+    unique id utility.
+
+    """


Property changes on: zc.intid/trunk/src/zc/intid/__init__.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native

Added: zc.intid/trunk/src/zc/intid/configure.zcml
===================================================================
--- zc.intid/trunk/src/zc/intid/configure.zcml	                        (rev 0)
+++ zc.intid/trunk/src/zc/intid/configure.zcml	2009-11-19 22:54:33 UTC (rev 105893)
@@ -0,0 +1,22 @@
+<configure xmlns="http://namespaces.zope.org/zope">
+
+  <class class=".utility.IntIds">
+
+    <require
+        permission="zope.Public"
+        interface=".IIntIdsQuery"
+        />
+
+    <require
+        permission="zope.ManageContent"
+        interface=".IIntIdsSet"
+        />
+
+    <require
+        permission="zope.Public"
+        interface=".IIntIdsManage"
+        />
+
+  </class>
+
+</configure>


Property changes on: zc.intid/trunk/src/zc/intid/configure.zcml
___________________________________________________________________
Added: svn:mime-type
   + text/xml
Added: svn:eol-style
   + native

Added: zc.intid/trunk/src/zc/intid/tests.py
===================================================================
--- zc.intid/trunk/src/zc/intid/tests.py	                        (rev 0)
+++ zc.intid/trunk/src/zc/intid/tests.py	2009-11-19 22:54:33 UTC (rev 105893)
@@ -0,0 +1,217 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002, 2009 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.
+#
+##############################################################################
+"""\
+Tests for the unique id utility.
+
+"""
+
+import BTrees
+import unittest
+import zc.intid
+import zc.intid.utility
+import zope.event
+import zope.interface.verify
+
+
+class P(object):
+    pass
+
+
+class TestIntIds(unittest.TestCase):
+
+    def createIntIds(self, attribute="iid"):
+        return zc.intid.utility.IntIds(attribute)
+
+    def setUp(self):
+        self.events = []
+        zope.event.subscribers.append(self.events.append)
+
+    def tearDown(self):
+        zope.event.subscribers.remove(self.events.append)
+
+    def test_interface(self):
+        zope.interface.verify.verifyObject(
+            zc.intid.IIntIds, self.createIntIds())
+
+    def test_non_keyreferences(self):
+        u = self.createIntIds()
+        obj = object()
+
+        self.assert_(u.queryId(obj) is None)
+        self.assert_(u.unregister(obj) is None)
+        self.assertRaises(KeyError, u.getId, obj)
+
+    def test(self):
+        u = self.createIntIds()
+        obj = P()
+
+        self.assertRaises(KeyError, u.getId, obj)
+        self.assertRaises(KeyError, u.getId, P())
+
+        self.assert_(u.queryId(obj) is None)
+        self.assert_(u.queryId(obj, 42) is 42)
+        self.assert_(u.queryId(P(), 42) is 42)
+        self.assert_(u.queryObject(42) is None)
+        self.assert_(u.queryObject(42, obj) is obj)
+
+        uid = u.register(obj)
+        self.assert_(u.getObject(uid) is obj)
+        self.assert_(u.queryObject(uid) is obj)
+        self.assertEquals(u.getId(obj), uid)
+        self.assertEquals(u.queryId(obj), uid)
+        self.assertEquals(obj.iid, uid)
+
+        # Check the id-added event:
+        self.assertEquals(len(self.events), 1)
+        event = self.events[-1]
+        self.assert_(zc.intid.IIdAddedEvent.providedBy(event))
+        self.assert_(event.object is obj)
+        self.assert_(event.idmanager is u)
+        self.assertEquals(event.id, uid)
+
+        uid2 = u.register(obj)
+        self.assertEquals(uid, uid2)
+
+        u.unregister(obj)
+        self.assert_(obj.iid is None)
+        self.assertRaises(KeyError, u.getObject, uid)
+        self.assertRaises(KeyError, u.getId, obj)
+
+        # Check the id-removed event:
+        self.assertEquals(len(self.events), 3)
+        event = self.events[-1]
+        self.assert_(zc.intid.IIdRemovedEvent.providedBy(event))
+        self.assert_(event.object is obj)
+        self.assert_(event.idmanager is u)
+        self.assertEquals(event.id, uid)
+
+    def test_btree_long(self):
+        # This is a somewhat arkward test, that *simulates* the border case
+        # behaviour of the generateId method
+        u = self.createIntIds()
+        u._randrange = lambda x,y:int(2**31-1)
+
+        # The chosen int is exactly the largest number possible that is
+        # delivered by the randint call in the code
+        obj = P()
+        uid = u.register(obj)
+        self.assertEquals(2**31-1, uid)
+        # Make an explicit tuple here to avoid implicit type casts on 2**31-1
+        # by the btree code
+        self.failUnless(2**31-1 in tuple(u.refs.keys()))
+
+    def test_len_items(self):
+        u = self.createIntIds()
+        obj = P()
+
+        self.assertEquals(len(u), 0)
+        self.assertEquals(u.items(), [])
+        self.assertEquals(list(u), [])
+
+        uid = u.register(obj)
+        self.assertEquals(len(u), 1)
+        self.assertEquals(u.items(), [(uid, obj)])
+        self.assertEquals(list(u), [uid])
+
+        obj2 = P()
+        obj2.__parent__ = obj
+
+        uid2 = u.register(obj2)
+        self.assertEquals(len(u), 2)
+        result = u.items()
+        expected = [(uid, obj), (uid2, obj2)]
+        result.sort()
+        expected.sort()
+        self.assertEquals(result, expected)
+        result = list(u)
+        expected = [uid, uid2]
+        result.sort()
+        expected.sort()
+        self.assertEquals(result, expected)
+
+        u.unregister(obj)
+        u.unregister(obj2)
+        self.assertEquals(len(u), 0)
+        self.assertEquals(u.items(), [])
+
+    def test_getenrateId(self):
+        u = self.createIntIds()
+        self.assertEquals(u._v_nextid, None)
+        id1 = u.generateId(None)
+        self.assert_(u._v_nextid is not None)
+        id2 = u.generateId(None)
+        self.assert_(id1 + 1, id2)
+        u.refs[id2 + 1] = "Taken"
+        id3 = u.generateId(None)
+        self.assertNotEqual(id3, id2 + 1)
+        self.assertNotEqual(id3, id2)
+        self.assertNotEqual(id3, id1)
+
+    def test_cohabitation(self):
+        # Show that two id utilities that use different attribute names
+        # can assign different ids for a single object, without stomping
+        # on each other.
+
+        def generator(utility, odd):
+            gen = utility.generateId
+            def generateId(ob):
+                iid = gen(ob)
+                while (iid % 2) != int(odd):
+                    iid = gen(ob)
+                return iid
+            return generateId
+
+        # u1 only generates odd ids
+        u1 = self.createIntIds("id1")
+        u1.generateId = generator(u1, True)
+
+        # u2 only generates even ids
+        u2 = self.createIntIds("id2")
+        u2.generateId = generator(u2, False)
+
+        obj = P()
+
+        uid1 = u1.register(obj)
+        uid2 = u2.register(obj)
+
+        self.assertNotEqual(uid1, uid2)
+        self.assertEquals(obj.id1, uid1)
+        self.assertEquals(obj.id2, uid2)
+
+        self.assert_(u1.queryObject(uid2) is None)
+        self.assert_(u2.queryObject(uid1) is None)
+
+        # Unregistering from on utility has no affect on the attribute
+        # assignment for the other.
+
+        u1.unregister(obj)
+        self.assert_(obj.id1 is None)
+        self.assertEquals(obj.id2, uid2)
+        self.assert_(u2.getObject(uid2) is obj)
+
+
+class TestIntIds64(TestIntIds):
+
+    def createIntIds(self, attribute="iid"):
+        return zc.intid.utility.IntIds(attribute, family=BTrees.family64)
+
+
+def test_suite():
+    return unittest.TestSuite([
+        unittest.makeSuite(TestIntIds),
+        unittest.makeSuite(TestIntIds64),
+        ])
+
+if __name__ == '__main__':
+    unittest.main()


Property changes on: zc.intid/trunk/src/zc/intid/tests.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native

Added: zc.intid/trunk/src/zc/intid/utility.py
===================================================================
--- zc.intid/trunk/src/zc/intid/utility.py	                        (rev 0)
+++ zc.intid/trunk/src/zc/intid/utility.py	2009-11-19 22:54:33 UTC (rev 105893)
@@ -0,0 +1,146 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002, 2009 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.
+#
+##############################################################################
+"""Unique id utility.
+
+This utility assigns unique integer ids to objects and allows lookups
+by object and by id.
+
+This functionality can be used in cataloging.
+
+"""
+
+import BTrees
+import persistent
+import random
+import zc.intid
+import zope.event
+import zope.interface
+import zope.location
+import zope.security.proxy
+
+
+unwrap = zope.security.proxy.removeSecurityProxy
+
+
+class IntIds(persistent.Persistent):
+    """This utility provides a two way mapping between objects and
+    integer ids.
+
+    IKeyReferences to objects are stored in the indexes.
+    """
+
+    zope.interface.implements(
+        zc.intid.IIntIds,
+        zc.intid.IIntIdsSubclassOverride,
+        zope.location.ILocation)
+
+    __parent__ = None
+    __name__ = None
+
+    _v_nextid = None
+
+    _randrange = random.randrange
+
+    family = BTrees.family32
+
+    def __init__(self, attribute, family=None):
+        if family is not None:
+            self.family = family
+        self.attribute = attribute
+        self.refs = self.family.IO.BTree()
+
+    def __len__(self):
+        return len(self.refs)
+
+    def items(self):
+        return list(self.refs.items())
+
+    def __iter__(self):
+        return self.refs.iterkeys()
+
+    def getObject(self, id):
+        return self.refs[id]
+
+    def queryObject(self, id, default=None):
+        if id in self.refs:
+            return self.refs[id]
+        return default
+
+    def getId(self, ob):
+        uid = getattr(unwrap(ob), self.attribute, None)
+        if uid is None:
+            raise KeyError(ob)
+        if self.refs[uid] is not ob:
+            # not an id that matches
+            raise KeyError(ob)
+        return uid
+
+    def queryId(self, ob, default=None):
+        try:
+            return self.getId(ob)
+        except KeyError:
+            return default
+
+    def generateId(self, ob):
+        """Generate an id which is not yet taken.
+
+        This tries to allocate sequential ids so they fall into the same
+        BTree bucket, and randomizes if it stumbles upon a used one.
+
+        """
+        while True:
+            if self._v_nextid is None:
+                self._v_nextid = self._randrange(0, self.family.maxint)
+            uid = self._v_nextid
+            self._v_nextid += 1
+            if uid not in self.refs:
+                return uid
+            self._v_nextid = None
+
+    def register(self, ob):
+        ob = unwrap(ob)
+        uid = self.queryId(ob)
+        if uid is None:
+            uid = self.generateId(ob)
+            if uid in self.refs:
+                raise ValueError("id generator returned used id")
+        self.refs[uid] = ob
+        setattr(ob, self.attribute, uid)
+        zope.event.notify(AddedEvent(ob, self, uid))
+        return uid
+
+    def unregister(self, ob):
+        ob = unwrap(ob)
+        uid = self.queryId(ob)
+        if uid is None:
+            return
+        del self.refs[uid]
+        setattr(ob, self.attribute, None)
+        zope.event.notify(RemovedEvent(ob, self, uid))
+
+
+class Event(object):
+
+    def __init__(self, object, idmanager, id):
+        self.object = object
+        self.idmanager = idmanager
+        self.id = id
+
+
+class AddedEvent(Event):
+    zope.interface.implements(zc.intid.IIdAddedEvent)
+
+
+class RemovedEvent(Event):
+    zope.interface.implements(zc.intid.IIdRemovedEvent)


Property changes on: zc.intid/trunk/src/zc/intid/utility.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native



More information about the checkins mailing list