[Zope-Checkins] CVS: Zope/lib/python/Products/PluginIndexes/TopicIndex - FilteredSet.py:1.2 README.txt:1.2 TopicIndex.py:1.2 __init__.py:1.2

Andreas Jung andreas@digicool.com
Thu, 28 Feb 2002 10:32:12 -0500


Update of /cvs-repository/Zope/lib/python/Products/PluginIndexes/TopicIndex
In directory cvs.zope.org:/tmp/cvs-serv29886

Added Files:
	FilteredSet.py README.txt TopicIndex.py __init__.py 
Log Message:
added TopicIndexes to trunk (merge from ajung-topicindex branch)


=== Zope/lib/python/Products/PluginIndexes/TopicIndex/FilteredSet.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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
+# 
+##############################################################################
+
+__version__ = '$Id$'
+
+from BTrees.IIBTree import IISet
+from Persistence import Persistent
+from Globals import DTMLFile
+from zLOG import WARNING,LOG
+import sys
+
+
+class FilteredSetBase(Persistent):
+
+    def __init__(self, id, expr):
+        self.id   = id
+        self.expr = expr
+        self.clear()
+
+    
+    def clear(self):
+        self.ids  = IISet()
+
+
+    def index_object(self, documentId, obj):
+        raise RuntimeError,'index_object not defined'
+
+
+    def unindex_object(self,documentId):
+        try: self.ids.remove(Id)
+        except: pass
+
+
+    def getId(self):            return self.id
+    def getExpression(self):    return self.expr
+    def getIds(self):           return self.ids
+    def getType(self):          return self.meta_type
+
+    def setExpression(self, expr): self.expr = expr
+    
+    def __repr__(self):
+        return '%s: (%s) %s' % (self.id,self.expr,map(None,self.ids))
+        
+    __str__ = __repr__
+
+
+
+class AttributeFilteredSet(FilteredSetBase):
+    """ The implementation of this FS is currently nonsense """
+
+    meta_type = 'AttributeFilteredSet'
+
+    def index_object(self, documentId, o):
+
+        if hasattr(o,self.id):
+            attr = getattr(o,self.id)
+            if callable(attr):
+                attr = attr()
+
+            try:
+                if attr in eval(self.expr):
+                    self.ids.insert(documentId)
+            except: 
+                pass
+    
+
+class PythonFilteredSet(FilteredSetBase):
+
+    meta_type = 'PythonFilteredSet'
+
+    def index_object(self, documentId, o):
+
+        try:
+            if eval(self.expr): self.ids.insert(documentId)
+        except: 
+            LOG('FilteredSet',WARNING,'eval() failed',\
+                'Object: %s, expr: %s' % (o.getId(),self.expr),\
+                sys.exc_info())
+
+
+class CatalogFilteredSet(FilteredSetBase):
+
+    meta_type = 'CatalogFilteredSet'
+
+    def index_object(self, documentId, obj):
+        raise RuntimeError, 'not implemented yet' 
+
+
+def factory(f_id, f_type, expr):
+    """ factory function for FilteredSets """
+
+    if f_type=='PythonFilteredSet':
+        return PythonFilteredSet(f_id, expr)
+
+    elif f_type=='AttributeFilteredSet':
+        return AttributeFilteredSet(f_id, expr)
+
+    elif f_type=='CatalogFilteredSet':
+        return CatalogFilteredSet(f_id, expr)
+
+    else:
+        raise TypeError,'unknown type for FilteredSets: %s' % f_type


=== Zope/lib/python/Products/PluginIndexes/TopicIndex/README.txt 1.1 => 1.2 ===
+
+  Reference: http://dev.zope.org/Wikis/DevSite/Proposals/TopicIndexes
+
+  A TopicIndex is a container for so-called FilteredSet. A FilteredSet
+  consists of an expression and a set of internal ZCatalog document 
+  identifiers that represent a pre-calculated result list for performance
+  reasons. Instead of executing the same query on a ZCatalog multiple times
+  it is much faster to use a TopicIndex instead.
+ 
+  Building up FilteredSets happens on the fly when objects are cataloged
+  and uncatalogued. Every indexed object is evaluated against the expressions
+  of every FilteredSet. An object is added to a FilteredSet if the expression
+  with the object evaluates to 1. Uncatalogued objects are removed from the
+  FilteredSet.
+
+
+  Types of FilteredSet
+
+    PythonFilteredSet
+     
+      A PythonFilteredSet evaluates using the eval() function inside the
+      context of the FilteredSet class. The object to be indexes must
+      be referenced inside the expression using "o.".
+
+      Examples::
+
+             "o.meta_type=='DTML Method'"
+    
+    
+
+  Queries on TopicIndexes
+ 
+    A TopicIndex is queried in the same way as other ZCatalog Indexes and supports
+    usage of the 'operator' parameter to specify how to combine search results.
+
+
+
+  API
+
+    The TopicIndex implements the API for pluggable Indexes.
+    Additionall it provides the following functions to manage FilteredSets
+
+      -- addFilteredSet(Id, filterType, expression): 
+
+        -- Id:  unique Id for the FilteredSet
+ 
+        -- filterType:     'PythonFilteredSet'
+    
+        -- expression:  Python expression
+
+
+      -- delFilteredSet(Id):
+
+      -- clearFilteredSet(Id):
+     
+  


=== Zope/lib/python/Products/PluginIndexes/TopicIndex/TopicIndex.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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
+# 
+##############################################################################
+
+__version__ = '$Id$'
+
+from Products.PluginIndexes import PluggableIndex 
+from Products.PluginIndexes.common.util import parseIndexRequest
+
+from Globals import Persistent, DTMLFile
+from Acquisition import Implicit
+from OFS.SimpleItem import SimpleItem
+from BTrees.OOBTree import OOBTree,OOSet
+from BTrees.IIBTree import IISet,difference,intersection,union
+from types import StringType, ListType, TupleType
+import FilteredSet
+
+_marker = []
+
+class TopicIndex(PluggableIndex.PluggableIndex, Persistent,
+    Implicit, SimpleItem):
+
+    """ A TopicIndex maintains a set of FilteredSet objects.
+    Every FilteredSet object consists of an expression and 
+    and IISet with all Ids of indexed objects that eval with
+    this expression to 1.
+    """
+
+    __implements__ = (PluggableIndex.PluggableIndexInterface,)
+
+    meta_type="TopicIndex"
+    
+    manage_options= (
+        {'label': 'FilteredSets',     
+         'action': 'manage_workspace',
+         'help': ('TopicIndex','TopicIndex_searchResults.stx')},
+    )
+
+    manage_workspace = DTMLFile('dtml/manageTopicIndex',globals())
+
+    query_options = ('query','operator')
+
+    def __init__(self,id,caller=None):
+        self.id             = id
+        self.filteredSets   = OOBTree()
+        # experimental code for specifing the operator
+        self.operators       = ('or','and') 
+        self.defaultOperator = 'or'
+
+
+    def clear(self):
+        """ clear everything """
+        self.filteredSets = OOBTree()
+
+            
+    def index_object(self, documentId, obj ,threshold=100):
+        """ hook for (Z)Catalog """
+
+        for fid, filteredSet in self.filteredSets.items():
+            filteredSet.index_object(documentId,obj)
+
+        return 1
+
+
+    def unindex_object(self,documentId):
+        """ hook for (Z)Catalog """
+
+        for fs in self.filteredSets.values():
+            fs.unindex_object(documentId)
+
+        return 1
+
+
+    def __len__(self):
+        """ len """
+        n=0
+        for fs in self.filteredSets.values():
+            n = n + len(fs.getIds())
+        return n
+
+
+    numObjects = "does not apply"
+
+
+    def keys(self):   pass
+    def values(self): pass  
+    def items(self):  pass   
+
+
+    def search(self,filterId):
+
+        if self.filteredSets.has_key(filterId):
+            return self.filteredSets[filterId].getIds()
+
+
+    def _apply_index(self, request, cid=''): 
+        """ hook for (Z)Catalog
+        request   mapping type (usually {"topic": "..." }
+        cid      ???
+        """
+
+        record = parseIndexRequest(request,self.id,self.query_options)
+        if record.keys==None: return None
+
+        # experimental code for specifing the operator
+        operator = record.get('operator',self.defaultOperator).lower()
+
+        # depending on the operator we use intersection of union
+        if operator=="or":  set_func = union
+        else:               set_func = intersection
+
+        res = None
+
+        for filterId in record.keys:
+            rows = self.search(filterId)
+            res = set_func(res,rows)
+
+        if res:
+            return res, (self.id,)
+        else: 
+            return IISet(), (self.id,)
+
+    
+    def uniqueValues(self,name=None,withLength=0):
+        """ needed to be consistent with the interface """
+
+        return self.filteredSets.keys()
+
+
+    def getEntryForObject(self,documentId,default=_marker):
+        """ Takes a document ID and returns all the information we have
+        on that specific object. """
+
+        return self.filteredSets.keys()
+
+
+    def addFilteredSet(self, filterId, typeFilteredSet, expr):
+
+        if self.filteredSets.has_key(filterId):
+            raise KeyError,\
+                'A FilteredSet with this name already exists: %s' % filterId        
+
+        self.filteredSets[filterId] = \
+            FilteredSet.factory(filterId, typeFilteredSet, expr)
+
+
+    def delFilteredSet(self,filterId):
+        
+        if not self.filteredSets.has_key(filterId):
+            raise KeyError,\
+                'no such FilteredSet:  %s' % filterId        
+
+        del self.filteredSets[filterId]
+
+
+    def clearFilteredSet(self,filterId):
+        
+        if not self.filteredSets.has_key(filterId):
+            raise KeyError,\
+                'no such FilteredSet:  %s' % filterId        
+
+        self.filteredSets[filterId].clear()
+
+
+    def manage_addFilteredSet(self, filterId, typeFilteredSet, expr, URL1, \
+            REQUEST=None,RESPONSE=None):
+        """ add a new filtered set """
+
+        if len(filterId)==0: raise RuntimeError,'Length of ID too short'
+        if len(expr)==0: raise RuntimeError,'Length of expression too short'
+
+        self.addFilteredSet(filterId, typeFilteredSet, expr)
+
+        if RESPONSE:
+            RESPONSE.redirect(URL1+'/manage_workspace?manage_tabs_message=FilteredSet%20added')
+
+
+    def manage_delFilteredSet(self, filterIds=[], URL1=None, \
+            REQUEST=None,RESPONSE=None):
+        """ delete a list of FilteredSets"""
+
+        for filterId in filterIds:
+            self.delFilteredSet(filterId)
+
+        if RESPONSE:
+            RESPONSE.redirect(URL1+'/manage_workspace?manage_tabs_message=FilteredSet(s)%20deleted')
+
+
+    def manage_saveFilteredSet(self,filterId, expr, URL1=None,\
+            REQUEST=None,RESPONSE=None):
+        """ save expression for a FilteredSet """
+
+        self.filteredSets[filterId].setExpression(expr)
+
+        if RESPONSE:
+            RESPONSE.redirect(URL1+'/manage_workspace?manage_tabs_message=FilteredSet(s)%20updated')
+
+
+    def manage_clearFilteredSet(self, filterIds=[], URL1=None, \
+            REQUEST=None,RESPONSE=None):
+        """  clear a list of FilteredSets"""
+
+        for filterId in filterIds:
+            self.clearFilteredSet(filterId)
+
+        if RESPONSE:
+            RESPONSE.redirect(URL1+'/manage_workspace?manage_tabs_message=FilteredSet(s)%20cleared')
+
+
+    editFilteredSet = DTMLFile('dtml/editFilteredSet',globals())
+    index_html      = DTMLFile('dtml/index', globals())
+
+
+manage_addTopicIndexForm = DTMLFile('dtml/addTopicIndex', globals())
+
+def manage_addTopicIndex(self, id, REQUEST=None, RESPONSE=None, URL3=None):
+    """Add a TopicIndex"""
+    return self.manage_addIndex(id, 'TopicIndex', extra=None, \
+                REQUEST=REQUEST, RESPONSE=RESPONSE, URL1=URL3)


=== Zope/lib/python/Products/PluginIndexes/TopicIndex/__init__.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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
+# 
+##############################################################################
+