hurry.query-1.1.1/0000775000076400007640000000000011771070325015126 5ustar faassenfaassen00000000000000hurry.query-1.1.1/INSTALL.txt0000664000076400007640000000041011771070320016763 0ustar faassenfaassen00000000000000Installation ------------ 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:: Or you can use z3c.autoinclude. hurry.query-1.1.1/setup.cfg0000664000076400007640000000007311771070325016747 0ustar faassenfaassen00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 hurry.query-1.1.1/buildout.cfg0000664000076400007640000000037411771070320017435 0ustar faassenfaassen00000000000000[buildout] develop = . parts = releaser test newest = false [releaser] recipe = z3c.recipe.scripts eggs = zest.releaser [test] recipe = zc.recipe.testrunner eggs = hurry.query hurry.query [test] defaults = ['--tests-pattern', '^f?tests$', '-v'] hurry.query-1.1.1/COPYRIGHT.txt0000664000076400007640000000004011771070320017224 0ustar faassenfaassen00000000000000Zope Foundation and Contributorshurry.query-1.1.1/LICENSE.txt0000664000076400007640000000402611771070320016746 0ustar faassenfaassen00000000000000Zope 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. hurry.query-1.1.1/src/0000775000076400007640000000000011771070325015715 5ustar faassenfaassen00000000000000hurry.query-1.1.1/src/hurry.query.egg-info/0000775000076400007640000000000011771070325021724 5ustar faassenfaassen00000000000000hurry.query-1.1.1/src/hurry.query.egg-info/not-zip-safe0000664000076400007640000000000111771070320024145 0ustar faassenfaassen00000000000000 hurry.query-1.1.1/src/hurry.query.egg-info/namespace_packages.txt0000664000076400007640000000000611771070322026250 0ustar faassenfaassen00000000000000hurry hurry.query-1.1.1/src/hurry.query.egg-info/requires.txt0000664000076400007640000000014611771070322024322 0ustar faassenfaassen00000000000000setuptools zc.catalog ZODB3 zope.catalog zope.component zope.interface zope.intid [test] zope.testinghurry.query-1.1.1/src/hurry.query.egg-info/dependency_links.txt0000664000076400007640000000000111771070322025767 0ustar faassenfaassen00000000000000 hurry.query-1.1.1/src/hurry.query.egg-info/PKG-INFO0000664000076400007640000005310011771070322023015 0ustar faassenfaassen00000000000000Metadata-Version: 1.1 Name: hurry.query Version: 1.1.1 Summary: Higher level query system for the zope.catalog Home-page: http://pypi.python.org/pypi/hurry.query Author: Infrae Author-email: faassen@startifact.com License: ZPL 2.1 Description: Hurry Query =========== The hurry query system for the zope.catalog builds on its catalog indexes, 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. .. contents:: 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.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.intid.interfaces >>> class DummyIntId(object): ... interface.implements(zope.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() >>> from zope.component import provideUtility >>> provideUtility(intid, zope.intid.interfaces.IIntIds) Now let's register a catalog:: >>> from zope.catalog.interfaces import ICatalog >>> from zope.catalog.catalog import Catalog >>> catalog = Catalog() >>> provideUtility(catalog, ICatalog, 'catalog1') And set it up with various indexes:: >>> from zope.catalog.field import FieldIndex >>> from zope.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 >>> provideUtility(Query(), IQuery) Set up some code to make querying and display the result easy:: >>> from zope.component import getUtility >>> from hurry.query.interfaces import IQuery >>> def displayQuery(q, context=None): ... query = getUtility(IQuery) ... r = query.searchResults(q, context) ... 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() >>> provideUtility(intid, zope.intid.interfaces.IIntIds) >>> from zope.catalog.interfaces import ICatalog >>> from zope.catalog.catalog import Catalog >>> catalog = Catalog() >>> provideUtility(catalog, ICatalog, '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() >>> provideUtility(intid, zope.intid.interfaces.IIntIds) >>> from zope.catalog.interfaces import ICatalog >>> from zope.catalog.catalog import Catalog >>> catalog = Catalog() >>> provideUtility(catalog, ICatalog, '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] If all the items in the catalog satisfy the NotEq condition, the query does not crash. >>> displayQuery(value.NotEq(f1, 'z')) [1, 2, 3, 4, 5, 6] 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] Querying different indexes -------------------------- It's possible to specify the context when creating a query. This context determines which index will be searched. First setup a second registry and second catalog and populate it. >>> catalog2 = Catalog() >>> from zope.component.registry import Components >>> import zope.component.interfaces >>> import zope.interface >>> intid1 = DummyIntId() >>> class MockSite(object): ... zope.interface.implements(zope.component.interfaces.IComponentLookup) ... def __init__(self): ... self.registry = Components('components') ... def queryUtility(self, interface, name='', default=None): ... if name == '': return intid1 ... else: return catalog2 ... def getSiteManager(self): ... return self.registry >>> from zope.component.hooks import setSite >>> site1 = MockSite() >>> setSite(site1) >>> catalog2['f1'] = FieldIndex('f1', IContent) >>> content = [ ... Content(1,'A'), ... Content(2,'B'),] >>> for entry in content: ... catalog2.index_doc(intid1.register(entry), entry) Now we can query this catalog by specifying the context: >>> query = getUtility(IQuery) >>> displayQuery(Eq(f1, 'A'), context=site1) [1] >>> displayQuery(In(f1, ['A', 'B']), context=site1) [1, 2] Sorting and limiting the resultset ---------------------------------- It's possible to have the resultset sorted on one of the fields in the query. >>> catalog = Catalog() >>> provideUtility(catalog, ICatalog, 'catalog1') >>> catalog['f1'] = FieldIndex('f1', IContent) >>> catalog['f2'] = FieldIndex('f2', IContent) >>> catalog['t'] = TextIndex('t1', IContent) First let's set up some new data:: >>> content = [ ... Content(1, 'a', 2, t1='Beautiful is better than ugly.'), ... Content(2, 'a', 3, t1='Explicit is better than implicit'), ... Content(3, 'b', 9, t1='Simple is better than complex'), ... Content(4, 'c', 8, t1='Complex is better than complicated'), ... Content(5, 'c', 7, t1='Readability counts'), ... Content(6, 'a', 1, t1='Although practicality beats purity')] And catalog them now:: >>> for entry in content: ... catalog.index_doc(intid.register(entry), entry) Define a convenience function for quickly displaying a result set without performing any sorting here ourselves. >>> def displayResult(q, context=None, **kw): ... query = getUtility(IQuery) ... r = query.searchResults(q, context, **kw) ... return [e.id for e in r] Without using sorting in the query itself, the resultset has an undefined order. We "manually" sort the results here to have something testable. >>> f1 = ('catalog1', 'f1') >>> [r for r in sorted(displayResult(Eq(f1, 'a')))] [1, 2, 6] Now we sort on the f2 index. >>> f1 = ('catalog1', 'f1') >>> displayResult(Eq(f1, 'a'), sort_field=('catalog1', 'f2')) [6, 1, 2] Reverse the order. >>> f1 = ('catalog1', 'f1') >>> displayResult(Eq(f1, 'a'), sort_field=('catalog1', 'f2'), reverse=True) [2, 1, 6] We can limit the amount of found items. >>> f1 = ('catalog1', 'f1') >>> displayResult(Eq(f1, 'a'), sort_field=('catalog1', 'f2'), limit=2) [6, 1] We can limit the reversed resultset too. >>> f1 = ('catalog1', 'f1') >>> displayResult( ... Eq(f1, 'a'), sort_field=('catalog1', 'f2'), limit=2, reverse=True) [2, 1] Whenever a field is used for sorting that does not support is, an error is raised. >>> f1 = ('catalog1', 'f1') >>> displayResult(Eq(f1, 'a'), sort_field=('catalog1', 't')) Traceback (most recent call last): ... ValueError: Index t in catalog catalog1 does not support sorting. The resultset can still be reversed and limited even if there's no sort_field given (Note that the actual order of the result set when not using explicit sorting is not defined. In this test it is assumed that the natural order of the tested index is deterministic enough to be used as a proper test). >>> f1 = ('catalog1', 'f1') >>> displayResult(Eq(f1, 'a'), limit=2) [1, 2] >>> f1 = ('catalog1', 'f1') >>> displayResult(Eq(f1, 'a'), limit=2, reverse=True) [6, 2] CHANGES ======= 1.1.1 (2012-06-22) ------------------ * ExtentNone in set.py missed a parameter ``index_id``. Thanks to Danilo Botelho for the bug report. 1.1.0 (2010-07-12) ------------------ * Allow the searchResults method of a Query to take an additional keyword argument `sort_field` that defines that defines (catalog_name, index_name) to sort on. That index in that catalog should implement IIndexSort. In addition to this keyword argument, `limit` and `reverse` keyword arguments can be passed too, that will limit the sorted resultset and/or reverse its order. * Allow the searchResults method of a Query object to take an additional optional context argument. This context will determine which catalog the search is performed on. 1.0.0 (2009-11-30) ------------------ * Refresh dependencies. Use zope.catalog and zope.intid instead of zope.app.catalog and zope.app.intid respectively. Don't zope.app.zapi. * Make package description more modern. * Clean up the code style. 0.9.3 (2008-09-29) ------------------ * BUG: NotEq query no longer fails when all values in the index satisfy the NotEq condition. 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. Keywords: zope zope3 catalog index query Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 hurry.query-1.1.1/src/hurry.query.egg-info/SOURCES.txt0000664000076400007640000000120411771070322023602 0ustar faassenfaassen00000000000000CHANGES.txt COPYRIGHT.txt CREDITS.txt INSTALL.txt LICENSE.txt README.txt ZopePublicLicense.txt bootstrap.py buildout.cfg setup.py src/hurry/__init__.py src/hurry.query.egg-info/PKG-INFO src/hurry.query.egg-info/SOURCES.txt src/hurry.query.egg-info/dependency_links.txt src/hurry.query.egg-info/namespace_packages.txt src/hurry.query.egg-info/not-zip-safe src/hurry.query.egg-info/requires.txt src/hurry.query.egg-info/top_level.txt src/hurry/query/__init__.py src/hurry/query/configure.zcml src/hurry/query/interfaces.py src/hurry/query/query.py src/hurry/query/query.txt src/hurry/query/set.py src/hurry/query/tests.py src/hurry/query/value.pyhurry.query-1.1.1/src/hurry.query.egg-info/top_level.txt0000664000076400007640000000000611771070322024447 0ustar faassenfaassen00000000000000hurry hurry.query-1.1.1/src/hurry/0000775000076400007640000000000011771070325017066 5ustar faassenfaassen00000000000000hurry.query-1.1.1/src/hurry/query/0000775000076400007640000000000011771070325020233 5ustar faassenfaassen00000000000000hurry.query-1.1.1/src/hurry/query/interfaces.py0000664000076400007640000000263111771070320022725 0ustar faassenfaassen00000000000000############################################################################## # # Copyright (c) 2005-2009 Zope Foundation 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. # ############################################################################## """Query interfaces $Id: interfaces.py 114321 2010-07-08 08:33:43Z janwijbrand $ """ from zope.interface import Interface class IQuery(Interface): def searchResults( query, context=None, sort_field=None, limit=None, reverse=False): """Query indexes. The query argument is a query composed of terms. Optionally provide the `context` parameter for the component lookups. Optionally provide a `sort_field` tuple that determines the index used to sort the result set with. This index is required to provide IIndexSort. Optionally provide a `limit` parameter to limit the result set to the given size. Optionally provide a `reverse` parameter to reverse the order of the result set. """ hurry.query-1.1.1/src/hurry/query/query.py0000664000076400007640000001664311771070320021757 0ustar faassenfaassen00000000000000############################################################################## # # Copyright (c) 2005-2009 Zope Foundation 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. # ############################################################################## """Basic query implementation This module contains an IQuery utility implementation, basic query term implementations and concrete term implementations for zope.catalog indexes. $Id: query.py 114320 2010-07-08 08:26:20Z janwijbrand $ """ from BTrees.IFBTree import weightedIntersection, union, difference, IFBTree from zope.catalog.catalog import ResultSet from zope.catalog.field import IFieldIndex from zope.catalog.interfaces import ICatalog from zope.catalog.text import ITextIndex from zope.component import getUtility from zope.interface import implements from zope.intid.interfaces import IIntIds from zope.index.interfaces import IIndexSort from hurry.query import interfaces # XXX look into using multiunion for performance? class Query(object): implements(interfaces.IQuery) def searchResults( self, query, context=None, sort_field=None, limit=None, reverse=False): results = query.apply(context) if results is None: return if sort_field is not None: # Like in zope.catalog's searchResults we require the given # index to sort on to provide IIndexSort. We bail out if # the index does not. catalog_name, index_name = sort_field catalog = getUtility(ICatalog, catalog_name, context) index = catalog[index_name] if not IIndexSort.providedBy(index): raise ValueError( 'Index %s in catalog %s does not support ' 'sorting.' % (index_name, catalog_name)) results = list(index.sort(results, limit=limit, reverse=reverse)) else: # There's no sort_field given. We still allow to reverse # and/or limit the resultset. This mimics zope.catalog's # searchResults semantics. if reverse or limit: results = list(results) if reverse: results.reverse() if limit: del results[limit:] uidutil = getUtility(IIntIds, '', context) return ResultSet(results, uidutil) 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, context=None): results = [] for term in self.terms: r = term.apply(context) 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, context=None): results = [] for term in self.terms: r = term.apply(context) # 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, context=None): return difference(self._all(), self.term.apply(context)) 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 = 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, context): catalog = getUtility(ICatalog, self.catalog_name, context) 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, context): index = super(Text, self).getIndex(context) assert ITextIndex.providedBy(index) return index def apply(self, context=None): index = self.getIndex(context) return index.apply(self.text) class FieldTerm(IndexTerm): def getIndex(self, context): index = super(FieldTerm, self).getIndex(context) 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, context=None): return self.getIndex(context).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, context=None): index = self.getIndex(context) 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, context=None): return self.getIndex(context).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, context=None): results = [] index = self.getIndex(context) 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 hurry.query-1.1.1/src/hurry/query/tests.py0000664000076400007640000000153311771070320021744 0ustar faassenfaassen00000000000000############################################################################## # # Copyright (c) 2005-2008 Zope Foundation 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. # ############################################################################## """ $Id: tests.py 112992 2010-06-03 08:22:48Z janwijbrand $ """ import unittest from zope.testing import doctest def test_suite(): return unittest.TestSuite(( doctest.DocFileSuite('query.txt'), )) hurry.query-1.1.1/src/hurry/query/query.txt0000664000076400007640000003541111771070320022140 0ustar faassenfaassen00000000000000Hurry Query =========== The hurry query system for the zope.catalog builds on its catalog indexes, 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. .. contents:: 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.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.intid.interfaces >>> class DummyIntId(object): ... interface.implements(zope.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() >>> from zope.component import provideUtility >>> provideUtility(intid, zope.intid.interfaces.IIntIds) Now let's register a catalog:: >>> from zope.catalog.interfaces import ICatalog >>> from zope.catalog.catalog import Catalog >>> catalog = Catalog() >>> provideUtility(catalog, ICatalog, 'catalog1') And set it up with various indexes:: >>> from zope.catalog.field import FieldIndex >>> from zope.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 >>> provideUtility(Query(), IQuery) Set up some code to make querying and display the result easy:: >>> from zope.component import getUtility >>> from hurry.query.interfaces import IQuery >>> def displayQuery(q, context=None): ... query = getUtility(IQuery) ... r = query.searchResults(q, context) ... 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() >>> provideUtility(intid, zope.intid.interfaces.IIntIds) >>> from zope.catalog.interfaces import ICatalog >>> from zope.catalog.catalog import Catalog >>> catalog = Catalog() >>> provideUtility(catalog, ICatalog, '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() >>> provideUtility(intid, zope.intid.interfaces.IIntIds) >>> from zope.catalog.interfaces import ICatalog >>> from zope.catalog.catalog import Catalog >>> catalog = Catalog() >>> provideUtility(catalog, ICatalog, '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] If all the items in the catalog satisfy the NotEq condition, the query does not crash. >>> displayQuery(value.NotEq(f1, 'z')) [1, 2, 3, 4, 5, 6] 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] Querying different indexes -------------------------- It's possible to specify the context when creating a query. This context determines which index will be searched. First setup a second registry and second catalog and populate it. >>> catalog2 = Catalog() >>> from zope.component.registry import Components >>> import zope.component.interfaces >>> import zope.interface >>> intid1 = DummyIntId() >>> class MockSite(object): ... zope.interface.implements(zope.component.interfaces.IComponentLookup) ... def __init__(self): ... self.registry = Components('components') ... def queryUtility(self, interface, name='', default=None): ... if name == '': return intid1 ... else: return catalog2 ... def getSiteManager(self): ... return self.registry >>> from zope.component.hooks import setSite >>> site1 = MockSite() >>> setSite(site1) >>> catalog2['f1'] = FieldIndex('f1', IContent) >>> content = [ ... Content(1,'A'), ... Content(2,'B'),] >>> for entry in content: ... catalog2.index_doc(intid1.register(entry), entry) Now we can query this catalog by specifying the context: >>> query = getUtility(IQuery) >>> displayQuery(Eq(f1, 'A'), context=site1) [1] >>> displayQuery(In(f1, ['A', 'B']), context=site1) [1, 2] Sorting and limiting the resultset ---------------------------------- It's possible to have the resultset sorted on one of the fields in the query. >>> catalog = Catalog() >>> provideUtility(catalog, ICatalog, 'catalog1') >>> catalog['f1'] = FieldIndex('f1', IContent) >>> catalog['f2'] = FieldIndex('f2', IContent) >>> catalog['t'] = TextIndex('t1', IContent) First let's set up some new data:: >>> content = [ ... Content(1, 'a', 2, t1='Beautiful is better than ugly.'), ... Content(2, 'a', 3, t1='Explicit is better than implicit'), ... Content(3, 'b', 9, t1='Simple is better than complex'), ... Content(4, 'c', 8, t1='Complex is better than complicated'), ... Content(5, 'c', 7, t1='Readability counts'), ... Content(6, 'a', 1, t1='Although practicality beats purity')] And catalog them now:: >>> for entry in content: ... catalog.index_doc(intid.register(entry), entry) Define a convenience function for quickly displaying a result set without performing any sorting here ourselves. >>> def displayResult(q, context=None, **kw): ... query = getUtility(IQuery) ... r = query.searchResults(q, context, **kw) ... return [e.id for e in r] Without using sorting in the query itself, the resultset has an undefined order. We "manually" sort the results here to have something testable. >>> f1 = ('catalog1', 'f1') >>> [r for r in sorted(displayResult(Eq(f1, 'a')))] [1, 2, 6] Now we sort on the f2 index. >>> f1 = ('catalog1', 'f1') >>> displayResult(Eq(f1, 'a'), sort_field=('catalog1', 'f2')) [6, 1, 2] Reverse the order. >>> f1 = ('catalog1', 'f1') >>> displayResult(Eq(f1, 'a'), sort_field=('catalog1', 'f2'), reverse=True) [2, 1, 6] We can limit the amount of found items. >>> f1 = ('catalog1', 'f1') >>> displayResult(Eq(f1, 'a'), sort_field=('catalog1', 'f2'), limit=2) [6, 1] We can limit the reversed resultset too. >>> f1 = ('catalog1', 'f1') >>> displayResult( ... Eq(f1, 'a'), sort_field=('catalog1', 'f2'), limit=2, reverse=True) [2, 1] Whenever a field is used for sorting that does not support is, an error is raised. >>> f1 = ('catalog1', 'f1') >>> displayResult(Eq(f1, 'a'), sort_field=('catalog1', 't')) Traceback (most recent call last): ... ValueError: Index t in catalog catalog1 does not support sorting. The resultset can still be reversed and limited even if there's no sort_field given (Note that the actual order of the result set when not using explicit sorting is not defined. In this test it is assumed that the natural order of the tested index is deterministic enough to be used as a proper test). >>> f1 = ('catalog1', 'f1') >>> displayResult(Eq(f1, 'a'), limit=2) [1, 2] >>> f1 = ('catalog1', 'f1') >>> displayResult(Eq(f1, 'a'), limit=2, reverse=True) [6, 2] hurry.query-1.1.1/src/hurry/query/value.py0000664000076400007640000000714011771070320021716 0ustar faassenfaassen00000000000000############################################################################## # # Copyright (c) 2005-2008 Zope Foundation 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. # ############################################################################## """Query terms for zc.catalog's ValueIndex $Id: value.py 112992 2010-06-03 08:22:48Z janwijbrand $ """ from zc.catalog.interfaces import IValueIndex from hurry.query import query class ValueTerm(query.IndexTerm): def getIndex(self, context): index = super(ValueTerm, self).getIndex(context) 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, context=None): return self.getIndex(context).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, context=None): index = self.getIndex(context) values = list(index.values()) # the remove method produces a value error when the value to # be removed is not in the list in the first place. Having a # try/except clause is more efficent than first searching the # list for the value to remove. try: values.remove(self.not_value) except ValueError: pass 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, context=None): return self.getIndex(context).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, context=None): return self.getIndex(context).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, context=None): return self.getIndex(context).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, context=None): return self.getIndex(context).apply({'none': self.extent}) hurry.query-1.1.1/src/hurry/query/set.py0000664000076400007640000000473211771070320021401 0ustar faassenfaassen00000000000000############################################################################## # # Copyright (c) 2005-2009 Zope Foundation 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. # ############################################################################## """Query terms for zc.catalog's SetIndex $Id: set.py 127010 2012-06-22 13:17:34Z faassen $ """ from zc.catalog.interfaces import ISetIndex from hurry.query import query class SetTerm(query.IndexTerm): def getIndex(self, context): index = super(SetTerm, self).getIndex(context) 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, context=None): return self.getIndex(context).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, context=None): return self.getIndex(context).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, context=None): return self.getIndex(context).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, context=None): return self.getIndex(context).apply({'any': self.extent}) class ExtentNone(SetTerm): """Any ids in the extent that are not indexed by this index.""" def __init__(self, index_id, extent): super(None, self).__init__(index_id) self.extent = extent def apply(self, context=None): return self.getIndex(context).apply({'none': self.extent}) hurry.query-1.1.1/src/hurry/query/__init__.py0000664000076400007640000000142011771070320022334 0ustar faassenfaassen00000000000000############################################################################## # # Copyright (c) 2005-2009 Zope Foundation 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. # ############################################################################## """ $Id: __init__.py 112992 2010-06-03 08:22:48Z janwijbrand $ """ from hurry.query.query import And, Or, Eq, NotEq, Between, In, Ge, Le, Text hurry.query-1.1.1/src/hurry/query/configure.zcml0000664000076400007640000000023011771070320023071 0ustar faassenfaassen00000000000000 hurry.query-1.1.1/src/hurry/__init__.py0000664000076400007640000000007011771070320021167 0ustar faassenfaassen00000000000000__import__('pkg_resources').declare_namespace(__name__) hurry.query-1.1.1/CHANGES.txt0000664000076400007640000000276011771070320016737 0ustar faassenfaassen00000000000000CHANGES ======= 1.1.1 (2012-06-22) ------------------ * ExtentNone in set.py missed a parameter ``index_id``. Thanks to Danilo Botelho for the bug report. 1.1.0 (2010-07-12) ------------------ * Allow the searchResults method of a Query to take an additional keyword argument `sort_field` that defines that defines (catalog_name, index_name) to sort on. That index in that catalog should implement IIndexSort. In addition to this keyword argument, `limit` and `reverse` keyword arguments can be passed too, that will limit the sorted resultset and/or reverse its order. * Allow the searchResults method of a Query object to take an additional optional context argument. This context will determine which catalog the search is performed on. 1.0.0 (2009-11-30) ------------------ * Refresh dependencies. Use zope.catalog and zope.intid instead of zope.app.catalog and zope.app.intid respectively. Don't zope.app.zapi. * Make package description more modern. * Clean up the code style. 0.9.3 (2008-09-29) ------------------ * BUG: NotEq query no longer fails when all values in the index satisfy the NotEq condition. 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. hurry.query-1.1.1/CREDITS.txt0000664000076400007640000000037111771070320016760 0ustar faassenfaassen00000000000000Credits ------- 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). hurry.query-1.1.1/bootstrap.py0000664000076400007640000002352211771070320017514 0ustar faassenfaassen00000000000000############################################################################## # # Copyright (c) 2006 Zope Foundation 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. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os, shutil, sys, tempfile, textwrap, urllib, urllib2, subprocess from optparse import OptionParser if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: quote = str # See zc.buildout.easy_install._has_broken_dash_S for motivation and comments. stdout, stderr = subprocess.Popen( [sys.executable, '-Sc', 'try:\n' ' import ConfigParser\n' 'except ImportError:\n' ' print 1\n' 'else:\n' ' print 0\n'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() has_broken_dash_S = bool(int(stdout.strip())) # In order to be more robust in the face of system Pythons, we want to # run without site-packages loaded. This is somewhat tricky, in # particular because Python 2.6's distutils imports site, so starting # with the -S flag is not sufficient. However, we'll start with that: if not has_broken_dash_S and 'site' in sys.modules: # We will restart with python -S. args = sys.argv[:] args[0:0] = [sys.executable, '-S'] args = map(quote, args) os.execv(sys.executable, args) # Now we are running with -S. We'll get the clean sys.path, import site # because distutils will do it later, and then reset the path and clean # out any namespace packages from site-packages that might have been # loaded by .pth files. clean_path = sys.path[:] import site sys.path[:] = clean_path for k, v in sys.modules.items(): if k in ('setuptools', 'pkg_resources') or ( hasattr(v, '__path__') and len(v.__path__)==1 and not os.path.exists(os.path.join(v.__path__[0],'__init__.py'))): # This is a namespace package. Remove it. sys.modules.pop(k) is_jython = sys.platform.startswith('java') setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py' distribute_source = 'http://python-distribute.org/distribute_setup.py' # parsing arguments def normalize_to_url(option, opt_str, value, parser): if value: if '://' not in value: # It doesn't smell like a URL. value = 'file://%s' % ( urllib.pathname2url( os.path.abspath(os.path.expanduser(value))),) if opt_str == '--download-base' and not value.endswith('/'): # Download base needs a trailing slash to make the world happy. value += '/' else: value = None name = opt_str[2:].replace('-', '_') setattr(parser.values, name, value) usage = '''\ [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] Bootstraps a buildout-based project. Simply run this script in a directory containing a buildout.cfg, using the Python that you want bin/buildout to use. Note that by using --setup-source and --download-base to point to local resources, you can keep this script from going over the network. ''' parser = OptionParser(usage=usage) parser.add_option("-v", "--version", dest="version", help="use a specific zc.buildout version") parser.add_option("-d", "--distribute", action="store_true", dest="use_distribute", default=False, help="Use Distribute rather than Setuptools.") parser.add_option("--setup-source", action="callback", dest="setup_source", callback=normalize_to_url, nargs=1, type="string", help=("Specify a URL or file location for the setup file. " "If you use Setuptools, this will default to " + setuptools_source + "; if you use Distribute, this " "will default to " + distribute_source +".")) parser.add_option("--download-base", action="callback", dest="download_base", callback=normalize_to_url, nargs=1, type="string", help=("Specify a URL or directory for downloading " "zc.buildout and either Setuptools or Distribute. " "Defaults to PyPI.")) parser.add_option("--eggs", help=("Specify a directory for storing eggs. Defaults to " "a temporary directory that is deleted when the " "bootstrap script completes.")) parser.add_option("-t", "--accept-buildout-test-releases", dest='accept_buildout_test_releases', action="store_true", default=False, help=("Normally, if you do not specify a --version, the " "bootstrap script and buildout gets the newest " "*final* versions of zc.buildout and its recipes and " "extensions for you. If you use this flag, " "bootstrap and buildout will get the newest releases " "even if they are alphas or betas.")) parser.add_option("-c", None, action="store", dest="config_file", help=("Specify the path to the buildout configuration " "file to be used.")) options, args = parser.parse_args() # if -c was provided, we push it back into args for buildout's main function if options.config_file is not None: args += ['-c', options.config_file] if options.eggs: eggs_dir = os.path.abspath(os.path.expanduser(options.eggs)) else: eggs_dir = tempfile.mkdtemp() if options.setup_source is None: if options.use_distribute: options.setup_source = distribute_source else: options.setup_source = setuptools_source if options.accept_buildout_test_releases: args.append('buildout:accept-buildout-test-releases=true') args.append('bootstrap') try: import pkg_resources import setuptools # A flag. Sometimes pkg_resources is installed alone. if not hasattr(pkg_resources, '_distribute'): raise ImportError except ImportError: ez_code = urllib2.urlopen( options.setup_source).read().replace('\r\n', '\n') ez = {} exec ez_code in ez setup_args = dict(to_dir=eggs_dir, download_delay=0) if options.download_base: setup_args['download_base'] = options.download_base if options.use_distribute: setup_args['no_fake'] = True ez['use_setuptools'](**setup_args) if 'pkg_resources' in sys.modules: reload(sys.modules['pkg_resources']) import pkg_resources # This does not (always?) update the default working set. We will # do it. for path in sys.path: if path not in pkg_resources.working_set.entries: pkg_resources.working_set.add_entry(path) cmd = [quote(sys.executable), '-c', quote('from setuptools.command.easy_install import main; main()'), '-mqNxd', quote(eggs_dir)] if not has_broken_dash_S: cmd.insert(1, '-S') find_links = options.download_base if not find_links: find_links = os.environ.get('bootstrap-testing-find-links') if find_links: cmd.extend(['-f', quote(find_links)]) if options.use_distribute: setup_requirement = 'distribute' else: setup_requirement = 'setuptools' ws = pkg_resources.working_set setup_requirement_path = ws.find( pkg_resources.Requirement.parse(setup_requirement)).location env = dict( os.environ, PYTHONPATH=setup_requirement_path) requirement = 'zc.buildout' version = options.version if version is None and not options.accept_buildout_test_releases: # Figure out the most recent final version of zc.buildout. import setuptools.package_index _final_parts = '*final-', '*final' def _final_version(parsed_version): for part in parsed_version: if (part[:1] == '*') and (part not in _final_parts): return False return True index = setuptools.package_index.PackageIndex( search_path=[setup_requirement_path]) if find_links: index.add_find_links((find_links,)) req = pkg_resources.Requirement.parse(requirement) if index.obtain(req) is not None: best = [] bestv = None for dist in index[req.project_name]: distv = dist.parsed_version if _final_version(distv): if bestv is None or distv > bestv: best = [dist] bestv = distv elif distv == bestv: best.append(dist) if best: best.sort() version = best[-1].version if version: requirement = '=='.join((requirement, version)) cmd.append(requirement) if is_jython: import subprocess exitcode = subprocess.Popen(cmd, env=env).wait() else: # Windows prefers this, apparently; otherwise we would prefer subprocess exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env])) if exitcode != 0: sys.stdout.flush() sys.stderr.flush() print ("An error occurred when trying to install zc.buildout. " "Look above this message for any errors that " "were output by easy_install.") sys.exit(exitcode) ws.add_entry(eggs_dir) ws.require(requirement) import zc.buildout.buildout zc.buildout.buildout.main(args) if not options.eggs: # clean up temporary egg directory shutil.rmtree(eggs_dir) hurry.query-1.1.1/ZopePublicLicense.txt0000664000076400007640000000420311771070320021240 0ustar faassenfaassen00000000000000Zope 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. hurry.query-1.1.1/PKG-INFO0000664000076400007640000005310011771070325016222 0ustar faassenfaassen00000000000000Metadata-Version: 1.1 Name: hurry.query Version: 1.1.1 Summary: Higher level query system for the zope.catalog Home-page: http://pypi.python.org/pypi/hurry.query Author: Infrae Author-email: faassen@startifact.com License: ZPL 2.1 Description: Hurry Query =========== The hurry query system for the zope.catalog builds on its catalog indexes, 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. .. contents:: 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.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.intid.interfaces >>> class DummyIntId(object): ... interface.implements(zope.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() >>> from zope.component import provideUtility >>> provideUtility(intid, zope.intid.interfaces.IIntIds) Now let's register a catalog:: >>> from zope.catalog.interfaces import ICatalog >>> from zope.catalog.catalog import Catalog >>> catalog = Catalog() >>> provideUtility(catalog, ICatalog, 'catalog1') And set it up with various indexes:: >>> from zope.catalog.field import FieldIndex >>> from zope.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 >>> provideUtility(Query(), IQuery) Set up some code to make querying and display the result easy:: >>> from zope.component import getUtility >>> from hurry.query.interfaces import IQuery >>> def displayQuery(q, context=None): ... query = getUtility(IQuery) ... r = query.searchResults(q, context) ... 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() >>> provideUtility(intid, zope.intid.interfaces.IIntIds) >>> from zope.catalog.interfaces import ICatalog >>> from zope.catalog.catalog import Catalog >>> catalog = Catalog() >>> provideUtility(catalog, ICatalog, '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() >>> provideUtility(intid, zope.intid.interfaces.IIntIds) >>> from zope.catalog.interfaces import ICatalog >>> from zope.catalog.catalog import Catalog >>> catalog = Catalog() >>> provideUtility(catalog, ICatalog, '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] If all the items in the catalog satisfy the NotEq condition, the query does not crash. >>> displayQuery(value.NotEq(f1, 'z')) [1, 2, 3, 4, 5, 6] 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] Querying different indexes -------------------------- It's possible to specify the context when creating a query. This context determines which index will be searched. First setup a second registry and second catalog and populate it. >>> catalog2 = Catalog() >>> from zope.component.registry import Components >>> import zope.component.interfaces >>> import zope.interface >>> intid1 = DummyIntId() >>> class MockSite(object): ... zope.interface.implements(zope.component.interfaces.IComponentLookup) ... def __init__(self): ... self.registry = Components('components') ... def queryUtility(self, interface, name='', default=None): ... if name == '': return intid1 ... else: return catalog2 ... def getSiteManager(self): ... return self.registry >>> from zope.component.hooks import setSite >>> site1 = MockSite() >>> setSite(site1) >>> catalog2['f1'] = FieldIndex('f1', IContent) >>> content = [ ... Content(1,'A'), ... Content(2,'B'),] >>> for entry in content: ... catalog2.index_doc(intid1.register(entry), entry) Now we can query this catalog by specifying the context: >>> query = getUtility(IQuery) >>> displayQuery(Eq(f1, 'A'), context=site1) [1] >>> displayQuery(In(f1, ['A', 'B']), context=site1) [1, 2] Sorting and limiting the resultset ---------------------------------- It's possible to have the resultset sorted on one of the fields in the query. >>> catalog = Catalog() >>> provideUtility(catalog, ICatalog, 'catalog1') >>> catalog['f1'] = FieldIndex('f1', IContent) >>> catalog['f2'] = FieldIndex('f2', IContent) >>> catalog['t'] = TextIndex('t1', IContent) First let's set up some new data:: >>> content = [ ... Content(1, 'a', 2, t1='Beautiful is better than ugly.'), ... Content(2, 'a', 3, t1='Explicit is better than implicit'), ... Content(3, 'b', 9, t1='Simple is better than complex'), ... Content(4, 'c', 8, t1='Complex is better than complicated'), ... Content(5, 'c', 7, t1='Readability counts'), ... Content(6, 'a', 1, t1='Although practicality beats purity')] And catalog them now:: >>> for entry in content: ... catalog.index_doc(intid.register(entry), entry) Define a convenience function for quickly displaying a result set without performing any sorting here ourselves. >>> def displayResult(q, context=None, **kw): ... query = getUtility(IQuery) ... r = query.searchResults(q, context, **kw) ... return [e.id for e in r] Without using sorting in the query itself, the resultset has an undefined order. We "manually" sort the results here to have something testable. >>> f1 = ('catalog1', 'f1') >>> [r for r in sorted(displayResult(Eq(f1, 'a')))] [1, 2, 6] Now we sort on the f2 index. >>> f1 = ('catalog1', 'f1') >>> displayResult(Eq(f1, 'a'), sort_field=('catalog1', 'f2')) [6, 1, 2] Reverse the order. >>> f1 = ('catalog1', 'f1') >>> displayResult(Eq(f1, 'a'), sort_field=('catalog1', 'f2'), reverse=True) [2, 1, 6] We can limit the amount of found items. >>> f1 = ('catalog1', 'f1') >>> displayResult(Eq(f1, 'a'), sort_field=('catalog1', 'f2'), limit=2) [6, 1] We can limit the reversed resultset too. >>> f1 = ('catalog1', 'f1') >>> displayResult( ... Eq(f1, 'a'), sort_field=('catalog1', 'f2'), limit=2, reverse=True) [2, 1] Whenever a field is used for sorting that does not support is, an error is raised. >>> f1 = ('catalog1', 'f1') >>> displayResult(Eq(f1, 'a'), sort_field=('catalog1', 't')) Traceback (most recent call last): ... ValueError: Index t in catalog catalog1 does not support sorting. The resultset can still be reversed and limited even if there's no sort_field given (Note that the actual order of the result set when not using explicit sorting is not defined. In this test it is assumed that the natural order of the tested index is deterministic enough to be used as a proper test). >>> f1 = ('catalog1', 'f1') >>> displayResult(Eq(f1, 'a'), limit=2) [1, 2] >>> f1 = ('catalog1', 'f1') >>> displayResult(Eq(f1, 'a'), limit=2, reverse=True) [6, 2] CHANGES ======= 1.1.1 (2012-06-22) ------------------ * ExtentNone in set.py missed a parameter ``index_id``. Thanks to Danilo Botelho for the bug report. 1.1.0 (2010-07-12) ------------------ * Allow the searchResults method of a Query to take an additional keyword argument `sort_field` that defines that defines (catalog_name, index_name) to sort on. That index in that catalog should implement IIndexSort. In addition to this keyword argument, `limit` and `reverse` keyword arguments can be passed too, that will limit the sorted resultset and/or reverse its order. * Allow the searchResults method of a Query object to take an additional optional context argument. This context will determine which catalog the search is performed on. 1.0.0 (2009-11-30) ------------------ * Refresh dependencies. Use zope.catalog and zope.intid instead of zope.app.catalog and zope.app.intid respectively. Don't zope.app.zapi. * Make package description more modern. * Clean up the code style. 0.9.3 (2008-09-29) ------------------ * BUG: NotEq query no longer fails when all values in the index satisfy the NotEq condition. 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. Keywords: zope zope3 catalog index query Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 hurry.query-1.1.1/README.txt0000664000076400007640000000032111771070320016613 0ustar faassenfaassen00000000000000hurry.query - higher level query system built on top of the zope.catalog. Some inspiration came from Dieter Maurer's AdvancedQuery. See src/hurry/query/query.txt for documentation. hurry.query-1.1.1/setup.py0000664000076400007640000000437011771070320016637 0ustar faassenfaassen00000000000000############################################################################## # # Copyright (c) 2007 Zope Foundation 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. # ############################################################################## """Setup $Id: setup.py 127012 2012-06-22 13:18:26Z faassen $ """ import os from setuptools import setup, find_packages def read(*rnames): text = open(os.path.join(os.path.dirname(__file__), *rnames)).read() text = unicode(text, 'utf-8').encode('ascii', 'xmlcharrefreplace') return text tests_require = [ 'zope.testing', ] setup( name="hurry.query", version='1.1.1', author='Infrae', author_email='faassen@startifact.com', description="Higher level query system for the zope.catalog", long_description=read('src','hurry','query','query.txt') + '\n\n' + read('CHANGES.txt'), license='ZPL 2.1', keywords="zope zope3 catalog index query", 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/hurry.query', packages=find_packages('src'), package_dir= {'':'src'}, namespace_packages=['hurry'], package_data = { '': ['*.txt', '*.zcml'], }, zip_safe=False, install_requires=[ 'setuptools', 'zc.catalog', 'ZODB3', 'zope.catalog', 'zope.component', 'zope.interface', 'zope.intid', ], tests_require = tests_require, extras_require = {'test': tests_require}, )