[Zope-CVS] SVN: zope2/Products/ZemanticIndex/ True first pass (testing framework only).

Tres Seaver tseaver at zope.com
Thu Mar 24 18:29:14 EST 2005


Log message for revision 381:
  True first pass (testing framework only).

Changed:
  A   zope2/Products/ZemanticIndex/README.txt
  A   zope2/Products/ZemanticIndex/__init__.py
  A   zope2/Products/ZemanticIndex/index.py
  A   zope2/Products/ZemanticIndex/interfaces.py
  A   zope2/Products/ZemanticIndex/tests/
  A   zope2/Products/ZemanticIndex/tests/__init__.py
  A   zope2/Products/ZemanticIndex/tests/test_cataloguing.py

-=-
Added: zope2/Products/ZemanticIndex/README.txt
===================================================================
--- zope2/Products/ZemanticIndex/README.txt	2005-03-24 23:28:04 UTC (rev 380)
+++ zope2/Products/ZemanticIndex/README.txt	2005-03-24 23:29:14 UTC (rev 381)
@@ -0,0 +1,26 @@
+ZemanticIndex Product README
+
+  Overview
+
+    This product allows a Zemantic triple store to index content within
+    a Zope2 catalog.
+
+  Indexing Content
+
+    The index adapts the object being indexed (the "subject", in RDF terms)
+    to IZemanticInfo, which returns a sequence of two-tuple statement
+    fragments about the subject.  Thses statements are in the form,
+    (<predicate>, <object>).  The index then constructs three-tuples from
+    these values, in the form (<subject>, <predicate>, <object>), using
+    the catalog's UID as the first first term
+
+  Searching Content
+
+    The index expects its 'query' object to be adaptable to IZemanticInfo.
+    It uses the two-tuples supplied by that adapter to synthesize a query
+    for matching documents, duing an "and" search by default.  The 'operator'
+    passed to the index can be either 'and' (perform an intersection), or
+    an 'or' (perform a union).
+
+    As a convenience for HTML-generated queries, the indes will also
+    accept its 'query' term as a sequence of (<predicate>, <object>) tuples.

Added: zope2/Products/ZemanticIndex/__init__.py
===================================================================
--- zope2/Products/ZemanticIndex/__init__.py	2005-03-24 23:28:04 UTC (rev 380)
+++ zope2/Products/ZemanticIndex/__init__.py	2005-03-24 23:29:14 UTC (rev 381)
@@ -0,0 +1,7 @@
+""" Zope2 product wrapping a Zemantic triple-store as a PluginIndex.
+
+$Id$
+"""
+
+def initialize(context):
+    pass

Added: zope2/Products/ZemanticIndex/index.py
===================================================================
--- zope2/Products/ZemanticIndex/index.py	2005-03-24 23:28:04 UTC (rev 380)
+++ zope2/Products/ZemanticIndex/index.py	2005-03-24 23:29:14 UTC (rev 381)
@@ -0,0 +1,129 @@
+""" Classes:  ZemanticIndex
+
+$Id$
+"""
+from zope.interface import implements
+
+from Products.ZemanticIndex.interfaces import IZemanticIndex
+from Products.ZemanticIndex.interfaces import IZemanticInfo
+
+class ZemanticIndex:
+    """ Adapt the zemantic triple store to a Zope2 plugin index.
+    """
+    implements(IZemanticIndex)
+
+    def __init__(self, id):
+        self._id = id
+        self._stuff = {} # XXX: Stubbed out to get tests going
+
+    #
+    #   IPluginIndex implementation
+    #
+    def getId(self):
+        """ See interfaces.IPluginIndex.
+        """
+        return self._id
+
+    def getEntryForObject(self, documentId, default=None):
+        """ See interfaces.IPluginIndex.
+
+        o Returns a set of triples.
+        """
+        return self._stuff.get(documentId, default)
+
+    def getIndexSourceNames(self):
+        """ See interfaces.IPluginIndex.
+
+        o Note that since we index triples via adaptation to IZemanticInfo,
+          this is not meaningful
+        """
+        return ()
+
+    def index_object(self, documentId, obj, threshold=None):
+        """ See interfaces.IPluginIndex.
+        """
+        triples = []
+        info = IZemanticInfo(obj)
+        subject = info.getSubject()
+
+        for predicate, object in info.listPredicatesAndObjects():
+            triples.append((subject, predicate, object))
+
+        self._stuff[documentId] = tuple(triples)
+
+    def unindex_object(self, documentId):
+        """ See interfaces.IPluginIndex.
+        """
+
+    def _apply_index(self, request, cid=''):
+        """ See interfaces.IPluginIndex.
+        """
+    
+    def numObjects(self):
+        """ See interfaces.IPluginIndex.
+
+        o This will be the size of the unique set of subjects.
+        """
+        return len(self._stuff.keys())
+
+    def indexSize(self):
+        """ See interfaces.IPluginIndex.
+
+        o This will be the total number of triples indexed.
+        """
+        count = 0
+        for k, v in self._stuff.items():
+            count += len(v)
+        return count
+    
+    def clear(self):
+        """ See interfaces.IPluginIndex.
+        """
+        self._stuff.clear()
+
+    #
+    #   IZemanticIndex implementation
+    #
+    def listUniqueSubjects(self):
+        """ See IZemanticIndex.
+        """
+        return self._listUnique(0)
+
+    def listUniquePredicates(self):
+        """ See IZemanticIndex.
+        """
+        return self._listUnique(1)
+
+    def listUniqueObjects(self):
+        """ See IZemanticIndex.
+        """
+        return self._listUnique(2)
+
+    def search(self, subject, predicate, object):
+        """ See IZemanticIndex.
+        """
+        def _passes(triple):
+            if subject is not None and triple[0] != subject:
+                return 0
+            if predicate is not None and triple[1] != predicate:
+                return 0
+            if object is not None and triple[2] != object:
+                return 0
+            return 1
+
+        for triples in self._stuff.values():
+            for triple in triples:
+                if _passes(triple):
+                    yield triple
+
+    #
+    #   Helper methods
+    #
+    def _listUnique(self, which):
+        seen = {}
+        for triples in self._stuff.values():
+            for triple in triples:
+                seen[triple[which]] = 1
+
+        return seen.keys()
+

Added: zope2/Products/ZemanticIndex/interfaces.py
===================================================================
--- zope2/Products/ZemanticIndex/interfaces.py	2005-03-24 23:28:04 UTC (rev 380)
+++ zope2/Products/ZemanticIndex/interfaces.py	2005-03-24 23:29:14 UTC (rev 381)
@@ -0,0 +1,107 @@
+""" ZemanticIndex interfaces
+
+$Id$
+"""
+
+from zope.interface import Interface
+
+class IZemanticInfo(Interface):
+    
+    def getSubject():
+        """ Return the subject ID for the object being indexed.
+
+        o Returned value must be one of the unicode-string-derived
+          statement element types from the zemantic package.
+        """
+
+    def listPredicatesAndObjects():
+        """ Return an iterable containing two-tuples describing the object.
+
+        o Tuples should be in the form (<predicate>, <object>)
+        
+        o Each member of the tuple must be one of the unicode-string-derived
+          statement element types from the zemantic package.
+        """
+
+class IPluginIndex(Interface):
+
+    #
+    #   Zope2's PluginIndexInterface (could be five:bridge'd).
+    #
+    def getId():
+        """ Return the ID of the index.
+        """
+
+    def getEntryForObject(documentId, default=None):
+        """ Return all information contained for 'documentId'.
+        """
+
+    def getIndexSourceNames():
+        """ Return a sequence of indexed attribute names.
+        """
+
+    def index_object(documentId, obj, threshold=None):
+        """ Index an object.
+
+        o 'documentId' is the integer ID of the document.
+
+        o 'obj' is the object to be indexed.
+
+        o 'threshold' is the number of values to process between committing
+           subtransactions.  If None, subtransactions are disabled.
+        """
+
+    def unindex_object(documentId):
+        """ Remove all information about 'documentId' from the index.
+        """
+
+    def _apply_index(request, cid=''):
+        """ Apply the index to query parameters given in 'request'.
+
+        o The 'request' argument should be a mapping object.
+
+        o If the request does not contain the needed parametrs, then
+          return None is returned.
+
+        o If the request contains a parameter with the name of the
+          column and this parameter is either a Record or a class
+          instance then it is assumed that the parameters of this index
+          are passed as attributes.
+
+        o Return a two-tuple, (<resultset>, <argnames>).
+          The first object is a ResultSet containing the 'documentIds'
+          of the matching records.  The second object is a tuple
+          containing the names of all data fields used from the request.
+        """
+    
+    def numObjects():
+        """ Return the number of indexed objects.
+        """
+
+    def indexSize():
+        """ Return the size of the index in terms of distinct values.
+        """
+    
+    def clear():
+        """ Empty the index.
+        """
+
+class IZemanticIndex(IPluginIndex):
+
+    def listUniqueSubjects():
+        """ Return an iterable of the unique subjects in our triple store.
+        """
+
+    def listUniquePredicates():
+        """ Return an iterable of the unique predicates in our triple store.
+        """
+
+    def listUniqueObjects():
+        """ Return an iterable of the unique objects in our triple store.
+        """
+
+    def search(subject, predicate, object):
+        """ Return a set of triples matching the supplied values.
+
+        o For any of the values supplied, None is a wildcard.
+        """

Added: zope2/Products/ZemanticIndex/tests/__init__.py
===================================================================
--- zope2/Products/ZemanticIndex/tests/__init__.py	2005-03-24 23:28:04 UTC (rev 380)
+++ zope2/Products/ZemanticIndex/tests/__init__.py	2005-03-24 23:29:14 UTC (rev 381)
@@ -0,0 +1,4 @@
+""" ZemanticIndex product unit tests
+
+$Id$
+"""

Added: zope2/Products/ZemanticIndex/tests/test_cataloguing.py
===================================================================
--- zope2/Products/ZemanticIndex/tests/test_cataloguing.py	2005-03-24 23:28:04 UTC (rev 380)
+++ zope2/Products/ZemanticIndex/tests/test_cataloguing.py	2005-03-24 23:29:14 UTC (rev 381)
@@ -0,0 +1,109 @@
+""" Test the cataloguing behavior of the index.
+
+$Id$
+"""
+import unittest
+
+class ZemanticIndexCatalogTests(unittest.TestCase):
+
+    def _getTargetClass(self):
+        from Products.ZemanticIndex.index import ZemanticIndex
+        return ZemanticIndex
+
+    def _makeOne(self, *args, **kw):
+        return self._getTargetClass()(*args, **kw)
+
+    def _makePerson(self, id, assertions):
+
+        from zope.interface import implements
+        from Products.ZemanticIndex.interfaces import IZemanticInfo
+
+        class _Person:
+
+            implements(IZemanticInfo)
+
+            def __init__(self, name, assertions):
+                self._name = name
+                self._assertions = tuple(assertions)
+
+            def getSubject(self):
+                return self._name
+
+            def listPredicatesAndObjects(self):
+                return self._assertions
+
+        return _Person(id, assertions)
+
+    def test_IZemanticIndex_conformance(self):
+        from zope.interface.verify import verifyClass
+        from Products.ZemanticIndex.interfaces import IZemanticIndex
+        verifyClass(IZemanticIndex, self._getTargetClass())
+
+    def test_empty(self):
+        index = self._makeOne(id='empty')
+        self.assertEqual(index.getId(), 'empty')
+
+        self.assertEqual(index.getEntryForObject('nonesuch'), None)
+        marker = []
+        self.failUnless(index.getEntryForObject('nonesuch', marker) is marker)
+
+        self.assertEqual(index.getIndexSourceNames(), ())
+        self.assertEqual(index.numObjects(), 0)
+        self.assertEqual(index.indexSize(), 0)
+        self.assertEqual(len(list(index.listUniqueSubjects())), 0)
+        self.assertEqual(len(list(index.listUniquePredicates())), 0)
+        self.assertEqual(len(list(index.listUniqueObjects())), 0)
+
+        self.assertEqual(len(list(index.search(None, None, None))), 0)
+
+    def test_index_object_simple(self):
+        index = self._makeOne(id='simple')
+
+        _PERSON_ID = 'TRES'
+        _NAME = 'Tres'
+        _ASSERTIONS = (('likes', 'cheese'),
+                       ('hates', 'liver'),
+                       ('loves', 'beer'),
+                       ('likes', 'baseball'),
+                      )
+
+        person = self._makePerson(_NAME, _ASSERTIONS)
+        index.index_object(_PERSON_ID, person)
+
+        info = index.getEntryForObject(_PERSON_ID)
+        self.assertEqual(type(info), type(()))
+        self.assertEqual(len(info), len(_ASSERTIONS))
+
+        for item in info:
+            self.assertEqual(item[0], _NAME)
+            self.failUnless((item[1], item[2]) in _ASSERTIONS)
+
+        self.assertEqual(index.numObjects(), 1)
+        self.assertEqual(index.indexSize(), len(_ASSERTIONS))
+
+        subjects = list(index.listUniqueSubjects())
+        self.assertEqual(len(subjects), 1)
+        self.assertEqual(subjects[0], _NAME)
+
+        predicates = list(index.listUniquePredicates())
+        self.assertEqual(len(predicates), len(_ASSERTIONS)-1)
+        for predicate in [x[0] for x in _ASSERTIONS]:
+            self.failUnless(predicate in predicates)
+
+        objects = list(index.listUniqueObjects())
+        self.assertEqual(len(objects), len(_ASSERTIONS))
+        for object in [x[1] for x in _ASSERTIONS]:
+            self.failUnless(object in objects)
+
+        results = list(index.search(None, None, None))
+        self.assertEqual(len(results), len(_ASSERTIONS))
+        for assertion in _ASSERTIONS:
+            self.failUnless((_NAME, assertion[0], assertion[1]) in results)
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(ZemanticIndexCatalogTests),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')



More information about the Zope-CVS mailing list