[Checkins] SVN: hurry.query/ Moving over from codespeak.net for easier maintenance.
Martijn Faassen
faassen at infrae.com
Tue Sep 16 17:58:07 EDT 2008
Log message for revision 91197:
Moving over from codespeak.net for easier maintenance.
Changed:
A hurry.query/
A hurry.query/trunk/
A hurry.query/trunk/.installed.cfg
A hurry.query/trunk/CHANGES.txt
A hurry.query/trunk/CREDITS.txt
A hurry.query/trunk/INSTALL.txt
A hurry.query/trunk/README.txt
A hurry.query/trunk/ZopePublicLicense.txt
A hurry.query/trunk/bin/
A hurry.query/trunk/bin/test
A hurry.query/trunk/buildout.cfg
A hurry.query/trunk/develop-eggs/
A hurry.query/trunk/develop-eggs/hurry.query.egg-link
A hurry.query/trunk/parts/
A hurry.query/trunk/parts/test/
A hurry.query/trunk/setup.py
A hurry.query/trunk/src/
A hurry.query/trunk/src/hurry/
A hurry.query/trunk/src/hurry/__init__.py
A hurry.query/trunk/src/hurry/query/
A hurry.query/trunk/src/hurry/query/__init__.py
A hurry.query/trunk/src/hurry/query/configure.zcml
A hurry.query/trunk/src/hurry/query/interfaces.py
A hurry.query/trunk/src/hurry/query/query.py
A hurry.query/trunk/src/hurry/query/query.txt
A hurry.query/trunk/src/hurry/query/set.py
A hurry.query/trunk/src/hurry/query/tests.py
A hurry.query/trunk/src/hurry/query/value.py
-=-
Added: hurry.query/trunk/.installed.cfg
===================================================================
--- hurry.query/trunk/.installed.cfg (rev 0)
+++ hurry.query/trunk/.installed.cfg 2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,20 @@
+[buildout]
+installed_develop_eggs = /home/faassen/buildout/hurry.query/develop-eggs/hurry.query.egg-link
+parts = test
+
+[test]
+__buildout_installed__ = /home/faassen/buildout/hurry.query/parts/test
+ /home/faassen/buildout/hurry.query/bin/test
+__buildout_signature__ = zc.recipe.testrunner-1.1.0-py2.4.egg zc.recipe.egg-1.1.0-py2.4.egg setuptools-0.6c8-py2.4.egg zope.testing-3.6.0-py2.4.egg zc.buildout-1.0.6-py2.4.egg zc.buildout-1.0.6-py2.4.egg zope.interface-3.4.1-py2.4-linux-i686.egg
+_b = /home/faassen/buildout/hurry.query/bin
+_d = /home/faassen/buildout/hurry.query/develop-eggs
+_e = /home/faassen/.buildout/eggs
+bin-directory = /home/faassen/buildout/hurry.query/bin
+defaults = ['--tests-pattern', '^f?tests$', '-v']
+develop-eggs-directory = /home/faassen/buildout/hurry.query/develop-eggs
+eggs = hurry.query
+eggs-directory = /home/faassen/.buildout/eggs
+executable = /home/faassen/bin/python2.4
+location = /home/faassen/buildout/hurry.query/parts/test
+recipe = zc.recipe.testrunner
+script = /home/faassen/buildout/hurry.query/bin/test
Added: hurry.query/trunk/CHANGES.txt
===================================================================
--- hurry.query/trunk/CHANGES.txt (rev 0)
+++ hurry.query/trunk/CHANGES.txt 2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,24 @@
+hurry.query changes
+===================
+
+0.9.2 (2006-09-22)
+------------------
+
+* First release on the cheeseshop.
+
+0.9.1 (2006-06-16)
+------------------
+
+* Make zc.catalog a dependency of hurry.query.
+
+0.9 (2006-05-16)
+----------------
+
+* Separate hurry.query from the other hurry packages. Eggification work.
+
+* Support for ValueIndex from zc.catalog.
+
+0.8 (2006-05-01)
+----------------
+
+Initial public release.
Added: hurry.query/trunk/CREDITS.txt
===================================================================
--- hurry.query/trunk/CREDITS.txt (rev 0)
+++ hurry.query/trunk/CREDITS.txt 2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,9 @@
+Credits
+-------
+
+Martijn Faassen - initial and main developer
+Stephan Richter - additional hurry.query index support
+Jan-Wijbrand Kolman - suggestions and feedback
+
+The hurry.query library for Zope 3 was developed at Infrae
+(http://www.infrae.com).
Added: hurry.query/trunk/INSTALL.txt
===================================================================
--- hurry.query/trunk/INSTALL.txt (rev 0)
+++ hurry.query/trunk/INSTALL.txt 2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,13 @@
+Installation
+------------
+
+To install hurry.query, just add it to your project's setup.py and
+install it (by re-running buildout, for instance).
+
+You can incldue hurry.query's ZCML by doing::
+
+ <include package="hurry.query" />
+
+Or you can use z3c.autoinclude.
+
+
Added: hurry.query/trunk/README.txt
===================================================================
--- hurry.query/trunk/README.txt (rev 0)
+++ hurry.query/trunk/README.txt 2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,4 @@
+hurry.query - higher level query system built on top of the Zope 3
+ catalog. Some inspiration came from Dieter Maurer's
+ AdvancedQuery. See src/hurry/query/query.txt for
+ documentation.
Added: hurry.query/trunk/ZopePublicLicense.txt
===================================================================
--- hurry.query/trunk/ZopePublicLicense.txt (rev 0)
+++ hurry.query/trunk/ZopePublicLicense.txt 2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,54 @@
+Zope Public License (ZPL) Version 2.1
+-------------------------------------
+
+A copyright notice accompanies this license document that
+identifies the copyright holders.
+
+This license has been certified as open source. It has also
+been designated as GPL compatible by the Free Software
+Foundation (FSF).
+
+Redistribution and use in source and binary forms, with or
+without modification, are permitted provided that the
+following conditions are met:
+
+1. Redistributions in source code must retain the
+ accompanying copyright notice, this list of conditions,
+ and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the accompanying
+ copyright notice, this list of conditions, and the
+ following disclaimer in the documentation and/or other
+ materials provided with the distribution.
+
+3. Names of the copyright holders must not be used to
+ endorse or promote products derived from this software
+ without prior written permission from the copyright
+ holders.
+
+4. The right to distribute this software or to use it for
+ any purpose does not give you the right to use
+ Servicemarks (sm) or Trademarks (tm) of the copyright
+ holders. Use of them is covered by separate agreement
+ with the copyright holders.
+
+5. If any files are modified, you must cause the modified
+ files to carry prominent notices stating that you changed
+ the files and the date of any change.
+
+Disclaimer
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS''
+ AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
+ NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ NO EVENT SHALL THE COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ DAMAGE.
Added: hurry.query/trunk/bin/test
===================================================================
--- hurry.query/trunk/bin/test (rev 0)
+++ hurry.query/trunk/bin/test 2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,101 @@
+#!/home/faassen/bin/python2.4
+
+import sys
+sys.path[0:0] = [
+ '/home/faassen/buildout/hurry.query/src',
+ '/home/faassen/.buildout/eggs/zope.testing-3.6.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.interface-3.4.1-py2.4-linux-i686.egg',
+ '/home/faassen/.buildout/eggs/setuptools-0.6c8-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zc.catalog-1.2.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.security-3.4.0-py2.4-linux-i686.egg',
+ '/home/faassen/.buildout/eggs/zope.schema-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.publisher-3.4.2-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.i18nmessageid-3.4.3-py2.4-linux-i686.egg',
+ '/home/faassen/.buildout/eggs/zope.component-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.container-3.5.3-py2.4-linux-i686.egg',
+ '/home/faassen/.buildout/eggs/zope.app.catalog-3.5.1-py2.4.egg',
+ '/home/faassen/.buildout/eggs/pytz-2007k-py2.4.egg',
+ '/home/faassen/.buildout/eggs/ZODB3-3.8.0-py2.4-linux-i686.egg',
+ '/home/faassen/.buildout/eggs/zope.proxy-3.4.0-py2.4-linux-i686.egg',
+ '/home/faassen/.buildout/eggs/zope.location-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.exceptions-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.deferredimport-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.configuration-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.event-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.deprecation-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.testing-3.4.1-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.i18n-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.copypastemove-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.broken-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.dublincore-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.traversing-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.size-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.filerepresentation-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.lifecycleevent-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.dottedname-3.4.2-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.cachedescriptors-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.zapi-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.publisher-3.4.1-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.index-3.4.1-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.intid-3.4.1-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.component-3.4.1-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.annotation-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zdaemon-2.0.1-py2.4.egg',
+ '/home/faassen/.buildout/eggs/ZConfig-2.5.1-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.security-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.publication-3.4.3-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.folder-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.dependable-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.debug-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.authentication-3.4.1-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.principalannotation-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.appsetup-3.4.1-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.applicationcontrol-3.4.1-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.interface-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.pagetemplate-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.datetime-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.contenttype-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.pagetemplate-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.keyreference-3.4.1-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.thread-3.4-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.formlib-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.form-3.4.1-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.exception-3.4.1-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.error-3.5.1-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.http-3.4.1-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.error-3.5.1-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.session-3.5.1-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zodbcode-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.tal-3.4.1-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.tales-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.zcmlfiles-3.4.3-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.hookable-3.4.0-py2.4-linux-i686.egg',
+ '/home/faassen/.buildout/eggs/zope.app.basicskin-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.session-3.4.1-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.minmax-1.1.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/RestrictedPython-3.4.2-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.schema-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.wsgi-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.rotterdam-3.4.1-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.zopeappgenerations-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.locales-3.4.1-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.i18n-3.4.4-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.generations-3.4.1-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.content-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.modulealias-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.app.renderer-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/zope.structuredtext-3.4.0-py2.4.egg',
+ '/home/faassen/.buildout/eggs/docutils-0.4-py2.4.egg',
+ ]
+
+import os
+sys.argv[0] = os.path.abspath(sys.argv[0])
+os.chdir('/home/faassen/buildout/hurry.query/parts/test')
+
+
+import zope.testing.testrunner
+
+if __name__ == '__main__':
+ zope.testing.testrunner.run((['--tests-pattern', '^f?tests$', '-v']) + [
+ '--test-path', '/home/faassen/buildout/hurry.query/src',
+ ])
Property changes on: hurry.query/trunk/bin/test
___________________________________________________________________
Name: svn:executable
+
Added: hurry.query/trunk/buildout.cfg
===================================================================
--- hurry.query/trunk/buildout.cfg (rev 0)
+++ hurry.query/trunk/buildout.cfg 2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,9 @@
+[buildout]
+develop = .
+parts = test
+newest = false
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = hurry.query
+defaults = ['--tests-pattern', '^f?tests$', '-v']
Added: hurry.query/trunk/develop-eggs/hurry.query.egg-link
===================================================================
--- hurry.query/trunk/develop-eggs/hurry.query.egg-link (rev 0)
+++ hurry.query/trunk/develop-eggs/hurry.query.egg-link 2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,2 @@
+/home/faassen/buildout/hurry.query/src
+../
\ No newline at end of file
Added: hurry.query/trunk/setup.py
===================================================================
--- hurry.query/trunk/setup.py (rev 0)
+++ hurry.query/trunk/setup.py 2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,27 @@
+from setuptools import setup, find_packages
+
+setup(
+ name="hurry.query",
+ version="0.9.4dev",
+ packages=find_packages('src'),
+ package_dir= {'':'src'},
+
+ namespace_packages=['hurry'],
+ package_data = {
+ '': ['*.txt', '*.zcml'],
+ },
+
+ zip_safe=False,
+ author='Infrae',
+ author_email='faassen at startifact.com',
+ description="""\
+hurry.query is a higher level query system built on top of the Zope 3
+catalog. It makes it easy to perform catalog queries in Zope 3 code.
+""",
+ license='ZPL 2.1',
+ keywords="zope zope3",
+ classifiers = ['Framework :: Zope3'],
+ install_requires=[
+ 'zc.catalog >= 0.1.1',
+ 'setuptools'],
+ )
Added: hurry.query/trunk/src/hurry/__init__.py
===================================================================
--- hurry.query/trunk/src/hurry/__init__.py (rev 0)
+++ hurry.query/trunk/src/hurry/__init__.py 2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,7 @@
+# this is a namespace package
+try:
+ import pkg_resources
+ pkg_resources.declare_namespace(__name__)
+except ImportError:
+ import pkgutil
+ __path__ = pkgutil.extend_path(__path__, __name__)
Added: hurry.query/trunk/src/hurry/query/__init__.py
===================================================================
--- hurry.query/trunk/src/hurry/query/__init__.py (rev 0)
+++ hurry.query/trunk/src/hurry/query/__init__.py 2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,3 @@
+# Copyright (c) 2007 Infrae. All rights reserved.
+# See also LICENSE.txt
+from query import And, Or, Eq, NotEq, Between, In, Ge, Le, Text
Added: hurry.query/trunk/src/hurry/query/configure.zcml
===================================================================
--- hurry.query/trunk/src/hurry/query/configure.zcml (rev 0)
+++ hurry.query/trunk/src/hurry/query/configure.zcml 2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,9 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope">
+
+ <utility
+ provides=".interfaces.IQuery"
+ factory=".query.Query"
+ />
+
+</configure>
Added: hurry.query/trunk/src/hurry/query/interfaces.py
===================================================================
--- hurry.query/trunk/src/hurry/query/interfaces.py (rev 0)
+++ hurry.query/trunk/src/hurry/query/interfaces.py 2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,11 @@
+# Copyright (c) 2007 Infrae. All rights reserved.
+# See also LICENSE.txt
+from zope.interface import Interface
+
+class IQuery(Interface):
+ def searchResults(query):
+ """Query indexes.
+
+ Argument is a query composed of terms.
+ """
+
Added: hurry.query/trunk/src/hurry/query/query.py
===================================================================
--- hurry.query/trunk/src/hurry/query/query.py (rev 0)
+++ hurry.query/trunk/src/hurry/query/query.py 2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,200 @@
+# Copyright (c) 2007 Infrae. All rights reserved.
+# See also LICENSE.txt
+from zope.interface import implements
+
+from zope.app import zapi
+from zope.app.intid.interfaces import IIntIds
+from zope.app.catalog.catalog import ResultSet
+from zope.app.catalog.field import IFieldIndex
+from zope.app.catalog.text import ITextIndex
+from zope.app.catalog.interfaces import ICatalog
+
+from BTrees.IFBTree import weightedIntersection, union, difference, IFBTree
+
+from hurry.query import interfaces
+
+# XXX look into using multiunion for performance?
+
+class Query(object):
+ implements(interfaces.IQuery)
+
+ def searchResults(self, query):
+ results = query.apply()
+ if results is not None:
+ uidutil = zapi.getUtility(IIntIds)
+ results = ResultSet(results, uidutil)
+ return results
+
+class Term(object):
+ def __and__(self, other):
+ return And(self, other)
+
+ def __rand__(self, other):
+ return And(other, self)
+
+ def __or__(self, other):
+ return Or(self, other)
+
+ def __ror__(self, other):
+ return Or(other, self)
+
+ def __invert__(self):
+ return Not(self)
+
+class And(Term):
+ def __init__(self, *terms):
+ self.terms = terms
+
+ def apply(self):
+ results = []
+ for term in self.terms:
+ r = term.apply()
+ if not r:
+ # empty results
+ return r
+ results.append((len(r), r))
+
+ if not results:
+ # no applicable terms at all
+ # XXX should this be possible?
+ return IFBTree()
+
+ results.sort()
+
+ _, result = results.pop(0)
+ for _, r in results:
+ _, result = weightedIntersection(result, r)
+ return result
+
+class Or(Term):
+ def __init__(self, *terms):
+ self.terms = terms
+
+ def apply(self):
+ results = []
+ for term in self.terms:
+ r = term.apply()
+ # empty results
+ if not r:
+ continue
+ results.append(r)
+
+ if not results:
+ # no applicable terms at all
+ # XXX should this be possible?
+ return IFBTree()
+
+ result = results.pop(0)
+ for r in results:
+ result = union(result, r)
+ return result
+
+class Not(Term):
+ def __init__(self, term):
+ self.term = term
+
+ def apply(self):
+ return difference(self._all(), self.term.apply())
+
+ def _all(self):
+ # XXX may not work well/be efficient with extentcatalog
+ # XXX not very efficient in general, better to use internal
+ # IntIds datastructure but that would break abstraction..
+ intids = zapi.getUtility(IIntIds)
+ result = IFBTree()
+ for uid in intids:
+ result.insert(uid, 0)
+ return result
+
+class IndexTerm(Term):
+ def __init__(self, (catalog_name, index_name)):
+ self.catalog_name = catalog_name
+ self.index_name = index_name
+
+ def getIndex(self):
+ catalog = zapi.getUtility(ICatalog, self.catalog_name)
+ index = catalog[self.index_name]
+ return index
+
+class Text(IndexTerm):
+ def __init__(self, index_id, text):
+ super(Text, self).__init__(index_id)
+ self.text = text
+
+ def getIndex(self):
+ index = super(Text, self).getIndex()
+ assert ITextIndex.providedBy(index)
+ return index
+
+ def apply(self):
+ index = self.getIndex()
+ return index.apply(self.text)
+
+class FieldTerm(IndexTerm):
+ def getIndex(self):
+ index = super(FieldTerm, self).getIndex()
+ assert IFieldIndex.providedBy(index)
+ return index
+
+class Eq(FieldTerm):
+ def __init__(self, index_id, value):
+ assert value is not None
+ super(Eq, self).__init__(index_id)
+ self.value = value
+
+ def apply(self):
+ return self.getIndex().apply((self.value, self.value))
+
+class NotEq(FieldTerm):
+ def __init__(self, index_id, not_value):
+ super(NotEq, self).__init__(index_id)
+ self.not_value = not_value
+
+ def apply(self):
+ index = self.getIndex()
+ all = index.apply((None, None))
+ r = index.apply((self.not_value, self.not_value))
+ return difference(all, r)
+
+class Between(FieldTerm):
+ def __init__(self, index_id, min_value, max_value):
+ super(Between, self).__init__(index_id)
+ self.min_value = min_value
+ self.max_value = max_value
+
+ def apply(self):
+ return self.getIndex().apply((self.min_value, self.max_value))
+
+class Ge(Between):
+ def __init__(self, index_id, min_value):
+ super(Ge, self).__init__(index_id, min_value, None)
+
+class Le(Between):
+ def __init__(self, index_id, max_value):
+ super(Le, self).__init__(index_id, None, max_value)
+
+class In(FieldTerm):
+ def __init__(self, index_id, values):
+ assert None not in values
+ super(In, self).__init__(index_id)
+ self.values = values
+
+ def apply(self):
+ results = []
+ index = self.getIndex()
+ for value in self.values:
+ r = index.apply((value, value))
+ # empty results
+ if not r:
+ continue
+ results.append(r)
+
+ if not results:
+ # no applicable terms at all
+ return IFBTree()
+
+ result = results.pop(0)
+ for r in results:
+ result = union(result, r)
+ return result
+
Added: hurry.query/trunk/src/hurry/query/query.txt
===================================================================
--- hurry.query/trunk/src/hurry/query/query.txt (rev 0)
+++ hurry.query/trunk/src/hurry/query/query.txt 2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,393 @@
+Hurry Query
+===========
+
+The hurry query system for the Zope 3 catalog builds on catalog
+indexes as defined in Zope 3 core, as well as the indexes in
+zc.catalog. It is in part inspired by AdvancedQuery for Zope 2 by
+Dieter Maurer, though has an independent origin.
+
+Setup
+-----
+
+Let's define a simple content object. First its interface::
+
+ >>> from zope.interface import Interface, Attribute, implements
+ >>> class IContent(Interface):
+ ... f1 = Attribute('f1')
+ ... f2 = Attribute('f2')
+ ... f3 = Attribute('f3')
+ ... f4 = Attribute('f4')
+ ... t1 = Attribute('t1')
+ ... t2 = Attribute('t2')
+
+And its implementation::
+
+ >>> from zope.app.container.contained import Contained
+ >>> class Content(Contained):
+ ... implements(IContent)
+ ... def __init__(self, id, f1='', f2='', f3='', f4='', t1='', t2=''):
+ ... self.id = id
+ ... self.f1 = f1
+ ... self.f2 = f2
+ ... self.f3 = f3
+ ... self.f4 = f4
+ ... self.t1 = t1
+ ... self.t2 = t2
+ ... def __cmp__(self, other):
+ ... return cmp(self.id, other.id)
+
+The id attribute is just so we can identify objects we find again
+easily. By including the __cmp__ method we make sure search results
+can be stably sorted.
+
+We use a fake int id utility here so we can test independent of
+the full-blown zope environment::
+
+ >>> from zope import interface
+ >>> import zope.app.intid.interfaces
+ >>> from zope.app.testing import ztapi
+ >>> class DummyIntId(object):
+ ... interface.implements(zope.app.intid.interfaces.IIntIds)
+ ... MARKER = '__dummy_int_id__'
+ ... def __init__(self):
+ ... self.counter = 0
+ ... self.data = {}
+ ... def register(self, obj):
+ ... intid = getattr(obj, self.MARKER, None)
+ ... if intid is None:
+ ... setattr(obj, self.MARKER, self.counter)
+ ... self.data[self.counter] = obj
+ ... intid = self.counter
+ ... self.counter += 1
+ ... return intid
+ ... def getObject(self, intid):
+ ... return self.data[intid]
+ ... def __iter__(self):
+ ... return iter(self.data)
+ >>> intid = DummyIntId()
+ >>> ztapi.provideUtility(
+ ... zope.app.intid.interfaces.IIntIds, intid)
+
+Now let's register a catalog::
+
+ >>> from zope.app.catalog.interfaces import ICatalog
+ >>> from zope.app.catalog.catalog import Catalog
+ >>> catalog = Catalog()
+ >>> ztapi.provideUtility(ICatalog, catalog, 'catalog1')
+
+And set it up with various indexes::
+
+ >>> from zope.app.catalog.field import FieldIndex
+ >>> from zope.app.catalog.text import TextIndex
+ >>> catalog['f1'] = FieldIndex('f1', IContent)
+ >>> catalog['f2'] = FieldIndex('f2', IContent)
+ >>> catalog['f3'] = FieldIndex('f3', IContent)
+ >>> catalog['f4'] = FieldIndex('f4', IContent)
+ >>> catalog['t1'] = TextIndex('t1', IContent)
+ >>> catalog['t2'] = TextIndex('t2', IContent)
+
+Now let's create some objects so that they'll be cataloged::
+
+ >>> content = [
+ ... Content(1, 'a', 'b', 'd'),
+ ... Content(2, 'a', 'c'),
+ ... Content(3, 'X', 'c'),
+ ... Content(4, 'a', 'b', 'e'),
+ ... Content(5, 'X', 'b', 'e'),
+ ... Content(6, 'Y', 'Z')]
+
+And catalog them now::
+
+ >>> for entry in content:
+ ... catalog.index_doc(intid.register(entry), entry)
+
+Now let's register a query utility::
+
+ >>> from hurry.query.query import Query
+ >>> from hurry.query.interfaces import IQuery
+ >>> ztapi.provideUtility(IQuery, Query())
+
+Set up some code to make querying and display the result
+easy::
+
+ >>> from zope.app import zapi
+ >>> from hurry.query.interfaces import IQuery
+ >>> def displayQuery(q):
+ ... query = zapi.getUtility(IQuery)
+ ... r = query.searchResults(q)
+ ... return [e.id for e in sorted(list(r))]
+
+FieldIndex Queries
+------------------
+
+Now for a query where f1 equals a::
+
+ >>> from hurry.query import Eq
+ >>> f1 = ('catalog1', 'f1')
+ >>> displayQuery(Eq(f1, 'a'))
+ [1, 2, 4]
+
+Not equals (this is more efficient than the generic ~ operator)::
+
+ >>> from hurry.query import NotEq
+ >>> displayQuery(NotEq(f1, 'a'))
+ [3, 5, 6]
+
+Testing whether a field is in a set::
+
+ >>> from hurry.query import In
+ >>> displayQuery(In(f1, ['a', 'X']))
+ [1, 2, 3, 4, 5]
+
+Whether documents are in a specified range::
+
+ >>> from hurry.query import Between
+ >>> displayQuery(Between(f1, 'X', 'Y'))
+ [3, 5, 6]
+
+You can leave out one end of the range::
+
+ >>> displayQuery(Between(f1, 'X', None)) # 'X' < 'a'
+ [1, 2, 3, 4, 5, 6]
+ >>> displayQuery(Between(f1, None, 'X'))
+ [3, 5]
+
+You can also use greater-equals and lesser-equals for the same purpose::
+
+ >>> from hurry.query import Ge, Le
+ >>> displayQuery(Ge(f1, 'X'))
+ [1, 2, 3, 4, 5, 6]
+ >>> displayQuery(Le(f1, 'X'))
+ [3, 5]
+
+It's also possible to use not with the ~ operator::
+
+ >>> displayQuery(~Eq(f1, 'a'))
+ [3, 5, 6]
+
+Using and (&)::
+
+ >>> f2 = ('catalog1', 'f2')
+ >>> displayQuery(Eq(f1, 'a') & Eq(f2, 'b'))
+ [1, 4]
+
+Using or (|)::
+
+ >>> displayQuery(Eq(f1, 'a') | Eq(f2, 'b'))
+ [1, 2, 4, 5]
+
+These can be chained::
+
+ >>> displayQuery(Eq(f1, 'a') & Eq(f2, 'b') & Between(f1, 'a', 'b'))
+ [1, 4]
+ >>> displayQuery(Eq(f1, 'a') | Eq(f1, 'X') | Eq(f2, 'b'))
+ [1, 2, 3, 4, 5]
+
+And nested::
+
+ >>> displayQuery((Eq(f1, 'a') | Eq(f1, 'X')) & (Eq(f2, 'b') | Eq(f2, 'c')))
+ [1, 2, 3, 4, 5]
+
+"and" and "or" can also be spelled differently::
+
+ >>> from hurry.query import And, Or
+ >>> displayQuery(And(Eq(f1, 'a'), Eq(f2, 'b')))
+ [1, 4]
+ >>> displayQuery(Or(Eq(f1, 'a'), Eq(f2, 'b')))
+ [1, 2, 4, 5]
+
+Combination of In and &
+-----------------------
+
+A combination of 'In' and '&'::
+
+ >>> displayQuery(In(f1, ['a', 'X', 'Y', 'Z']))
+ [1, 2, 3, 4, 5, 6]
+ >>> displayQuery(In(f1, ['Z']))
+ []
+ >>> displayQuery(In(f1, ['a', 'X', 'Y', 'Z']) & In(f1, ['Z']))
+ []
+
+
+SetIndex queries
+----------------
+
+The SetIndex is defined in zc.catalog. Let's make a catalog which uses
+it::
+
+ >>> intid = DummyIntId()
+ >>> ztapi.provideUtility(
+ ... zope.app.intid.interfaces.IIntIds, intid)
+ >>> from zope.app.catalog.interfaces import ICatalog
+ >>> from zope.app.catalog.catalog import Catalog
+ >>> catalog = Catalog()
+ >>> ztapi.provideUtility(ICatalog, catalog, 'catalog1')
+ >>> from zc.catalog.catalogindex import SetIndex
+ >>> catalog['f1'] = SetIndex('f1', IContent)
+ >>> catalog['f2'] = FieldIndex('f2', IContent)
+
+First let's set up some new data::
+
+ >>> content = [
+ ... Content(1, ['a', 'b', 'c'], 1),
+ ... Content(2, ['a'], 1),
+ ... Content(3, ['b'], 1),
+ ... Content(4, ['c', 'd'], 2),
+ ... Content(5, ['b', 'c'], 2),
+ ... Content(6, ['a', 'c'], 2)]
+
+And catalog them now::
+
+ >>> for entry in content:
+ ... catalog.index_doc(intid.register(entry), entry)
+
+Now do a a 'any of' query, which returns all documents that
+contain any of the values listed::
+
+ >>> from hurry.query.set import AnyOf
+ >>> displayQuery(AnyOf(f1, ['a', 'c']))
+ [1, 2, 4, 5, 6]
+ >>> displayQuery(AnyOf(f1, ['c', 'b']))
+ [1, 3, 4, 5, 6]
+ >>> displayQuery(AnyOf(f1, ['a']))
+ [1, 2, 6]
+
+Do a 'all of' query, which returns all documents that
+contain all of the values listed::
+
+ >>> from hurry.query.set import AllOf
+ >>> displayQuery(AllOf(f1, ['a']))
+ [1, 2, 6]
+ >>> displayQuery(AllOf(f1, ['a', 'b']))
+ [1]
+ >>> displayQuery(AllOf(f1, ['a', 'c']))
+ [1, 6]
+
+We can combine this with other queries::
+
+ >>> displayQuery(AnyOf(f1, ['a']) & Eq(f2, 1))
+ [1, 2]
+
+
+ValueIndex queries
+------------------
+
+The ``ValueIndex`` is defined in ``zc.catalog`` and provides a generalization
+of the standard field index.
+
+ >>> from hurry.query import value
+
+Let's set up a catalog that uses this index. The ``ValueIndex`` is defined in
+``zc.catalog``. Let's make a catalog which uses it:
+
+ >>> intid = DummyIntId()
+ >>> ztapi.provideUtility(zope.app.intid.interfaces.IIntIds, intid)
+
+ >>> from zope.app.catalog.interfaces import ICatalog
+ >>> from zope.app.catalog.catalog import Catalog
+ >>> catalog = Catalog()
+ >>> ztapi.provideUtility(ICatalog, catalog, 'catalog1')
+
+ >>> from zc.catalog.catalogindex import ValueIndex
+ >>> catalog['f1'] = ValueIndex('f1', IContent)
+
+Next we set up some content data to fill the indices:
+
+ >>> content = [
+ ... Content(1, 'a'),
+ ... Content(2, 'b'),
+ ... Content(3, 'c'),
+ ... Content(4, 'd'),
+ ... Content(5, 'c'),
+ ... Content(6, 'a')]
+
+And catalog them now:
+
+ >>> for entry in content:
+ ... catalog.index_doc(intid.register(entry), entry)
+
+
+Let's now query for all objects where ``f1`` equals 'a':
+
+ >>> f1 = ('catalog1', 'f1')
+ >>> displayQuery(value.Eq(f1, 'a'))
+ [1, 6]
+
+Next, let's find all objects where ``f1`` does not equal 'a'; this is more
+efficient than the generic ``~`` operator:
+
+ >>> displayQuery(value.NotEq(f1, 'a'))
+ [2, 3, 4, 5]
+
+You can also query for all objects where the value of ``f1`` is in a set of
+values:
+
+ >>> displayQuery(value.In(f1, ['a', 'd']))
+ [1, 4, 6]
+
+The next interesting set of queries allows you to make evaluations of the
+values. For example, you can ask for all objects between a certain set of
+values:
+
+ >>> displayQuery(value.Between(f1, 'a', 'c'))
+ [1, 2, 3, 5, 6]
+
+ >>> displayQuery(value.Between(f1, 'a', 'c', exclude_min=True))
+ [2, 3, 5]
+
+ >>> displayQuery(value.Between(f1, 'a', 'c', exclude_max=True))
+ [1, 2, 6]
+
+ >>> displayQuery(value.Between(f1, 'a', 'c',
+ ... exclude_min=True, exclude_max=True))
+ [2]
+
+You can also leave out one end of the range:
+
+ >>> displayQuery(value.Between(f1, 'c', None))
+ [3, 4, 5]
+ >>> displayQuery(value.Between(f1, None, 'c'))
+ [1, 2, 3, 5, 6]
+
+You can also use greater-equals and lesser-equals for the same purpose:
+
+ >>> displayQuery(value.Ge(f1, 'c'))
+ [3, 4, 5]
+ >>> displayQuery(value.Le(f1, 'c'))
+ [1, 2, 3, 5, 6]
+
+Of course, you can chain those queries with the others as demonstrated before.
+
+The ``value`` module also supports ``zc.catalog`` extents. The first query is
+``ExtentAny``, which returns all douments matching the extent. If the the
+extent is ``None``, all document ids are returned:
+
+ >>> displayQuery(value.ExtentAny(f1, None))
+ [1, 2, 3, 4, 5, 6]
+
+If we now create an extent that is only in the scope of the first four
+documents,
+
+ >>> from zc.catalog.extentcatalog import FilterExtent
+ >>> extent = FilterExtent(lambda extent, uid, obj: True)
+ >>> for i in range(4):
+ ... extent.add(i, i)
+
+then only the first four are returned:
+
+ >>> displayQuery(value.ExtentAny(f1, extent))
+ [1, 2, 3, 4]
+
+The opposite query is the ``ExtentNone`` query, which returns all ids in the
+extent that are *not* in the index:
+
+ >>> id = intid.register(Content(7, 'b'))
+ >>> id = intid.register(Content(8, 'c'))
+ >>> id = intid.register(Content(9, 'a'))
+
+ >>> extent = FilterExtent(lambda extent, uid, obj: True)
+ >>> for i in range(9):
+ ... extent.add(i, i)
+
+ >>> displayQuery(value.ExtentNone(f1, extent))
+ [7, 8, 9]
Added: hurry.query/trunk/src/hurry/query/set.py
===================================================================
--- hurry.query/trunk/src/hurry/query/set.py (rev 0)
+++ hurry.query/trunk/src/hurry/query/set.py 2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,56 @@
+# Copyright (c) 2007 Infrae. All rights reserved.
+# See also LICENSE.txt
+from zc.catalog.interfaces import ISetIndex
+from hurry.query import query
+
+class SetTerm(query.IndexTerm):
+ def getIndex(self):
+ index = super(SetTerm, self).getIndex()
+ assert ISetIndex.providedBy(index)
+ return index
+
+class AnyOf(SetTerm):
+ def __init__(self, index_id, values):
+ super(AnyOf, self).__init__(index_id)
+ self.values = values
+
+ def apply(self):
+ return self.getIndex().apply({'any_of': self.values})
+
+class AllOf(SetTerm):
+ def __init__(self, index_id, values):
+ super(AllOf, self).__init__(index_id)
+ self.values = values
+
+ def apply(self):
+ return self.getIndex().apply({'all_of': self.values})
+
+class SetBetween(SetTerm):
+ def __init__(self, index_id,
+ minimum=None, maximum=None,
+ include_minimum=False, include_maximum=False):
+ super(SetBetween, self).__init__(index_id)
+ self.tuple = (minimum, maximum, include_minimum, include_maximum)
+
+ def apply(self):
+ return self.getIndex().apply({'between': self.tuple})
+
+class ExtentAny(SetTerm):
+ """Any ids in the extent that are indexed by this index.
+ """
+ def __init__(self, index_id, extent):
+ super(Any, self).__init__(index_id)
+ self.extent = extent
+
+ def apply(self):
+ return self.getIndex().apply({'any': self.extent})
+
+class ExtentNone(SetTerm):
+ """Any ids in the extent that are not indexed by this index.
+ """
+ def __init__(self, extent):
+ super(None, self).__init__(index_id)
+ self.extent = extent
+
+ def apply(self):
+ return self.getIndex().apply({'none': self.extent})
Added: hurry.query/trunk/src/hurry/query/tests.py
===================================================================
--- hurry.query/trunk/src/hurry/query/tests.py (rev 0)
+++ hurry.query/trunk/src/hurry/query/tests.py 2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,12 @@
+# Copyright (c) 2007 Infrae. All rights reserved.
+# See also LICENSE.txt
+import unittest
+from zope.testing import doctest
+
+def test_suite():
+ return unittest.TestSuite((
+ doctest.DocFileSuite('query.txt'),
+ ))
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
Added: hurry.query/trunk/src/hurry/query/value.py
===================================================================
--- hurry.query/trunk/src/hurry/query/value.py (rev 0)
+++ hurry.query/trunk/src/hurry/query/value.py 2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,81 @@
+# Copyright (c) 2007 Infrae. All rights reserved.
+# See also LICENSE.txt
+from zc.catalog.interfaces import IValueIndex
+from hurry.query import query
+
+class ValueTerm(query.IndexTerm):
+ def getIndex(self):
+ index = super(ValueTerm, self).getIndex()
+ assert IValueIndex.providedBy(index)
+ return index
+
+class Eq(ValueTerm):
+ def __init__(self, index_id, value):
+ assert value is not None
+ super(Eq, self).__init__(index_id)
+ self.value = value
+
+ def apply(self):
+ return self.getIndex().apply({'any_of': (self.value,)})
+
+class NotEq(ValueTerm):
+ def __init__(self, index_id, not_value):
+ super(NotEq, self).__init__(index_id)
+ self.not_value = not_value
+
+ def apply(self):
+ index = self.getIndex()
+ values = list(index.values())
+ values.remove(self.not_value)
+ return index.apply({'any_of': values})
+
+class Between(ValueTerm):
+ def __init__(self, index_id, min_value=None, max_value=None,
+ exclude_min=False, exclude_max=False):
+ super(Between, self).__init__(index_id)
+ self.min_value = min_value
+ self.max_value = max_value
+ self.exclude_min = exclude_min
+ self.exclude_max = exclude_max
+
+ def apply(self):
+ return self.getIndex().apply(
+ {'between': (self.min_value, self.max_value,
+ self.exclude_min, self.exclude_max)})
+
+class Ge(Between):
+ def __init__(self, index_id, min_value):
+ super(Ge, self).__init__(index_id, min_value=min_value)
+
+class Le(Between):
+ def __init__(self, index_id, max_value):
+ super(Le, self).__init__(index_id, max_value=max_value)
+
+class In(ValueTerm):
+ def __init__(self, index_id, values):
+ assert None not in values
+ super(In, self).__init__(index_id)
+ self.values = values
+
+ def apply(self):
+ return self.getIndex().apply({'any_of': self.values})
+
+class ExtentAny(ValueTerm):
+ """Any ids in the extent that are indexed by this index.
+ """
+ def __init__(self, index_id, extent):
+ super(ExtentAny, self).__init__(index_id)
+ self.extent = extent
+
+ def apply(self):
+ return self.getIndex().apply({'any': self.extent})
+
+class ExtentNone(ValueTerm):
+ """Any ids in the extent that are not indexed by this index.
+ """
+ def __init__(self, index_id, extent):
+ super(ExtentNone, self).__init__(index_id)
+ self.extent = extent
+
+ def apply(self):
+ return self.getIndex().apply({'none': self.extent})
More information about the Checkins
mailing list