[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
+#
+##############################################################################
+