[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