hurry.query-1.1.1/ 0000775 0000764 0000764 00000000000 11771070325 015126 5 ustar faassen faassen 0000000 0000000 hurry.query-1.1.1/INSTALL.txt 0000664 0000764 0000764 00000000410 11771070320 016763 0 ustar faassen faassen 0000000 0000000 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::
Or you can use z3c.autoinclude.
hurry.query-1.1.1/setup.cfg 0000664 0000764 0000764 00000000073 11771070325 016747 0 ustar faassen faassen 0000000 0000000 [egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
hurry.query-1.1.1/buildout.cfg 0000664 0000764 0000764 00000000374 11771070320 017435 0 ustar faassen faassen 0000000 0000000 [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.txt 0000664 0000764 0000764 00000000040 11771070320 017224 0 ustar faassen faassen 0000000 0000000 Zope Foundation and Contributors hurry.query-1.1.1/LICENSE.txt 0000664 0000764 0000764 00000004026 11771070320 016746 0 ustar faassen faassen 0000000 0000000 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.
hurry.query-1.1.1/src/ 0000775 0000764 0000764 00000000000 11771070325 015715 5 ustar faassen faassen 0000000 0000000 hurry.query-1.1.1/src/hurry.query.egg-info/ 0000775 0000764 0000764 00000000000 11771070325 021724 5 ustar faassen faassen 0000000 0000000 hurry.query-1.1.1/src/hurry.query.egg-info/not-zip-safe 0000664 0000764 0000764 00000000001 11771070320 024145 0 ustar faassen faassen 0000000 0000000
hurry.query-1.1.1/src/hurry.query.egg-info/namespace_packages.txt 0000664 0000764 0000764 00000000006 11771070322 026250 0 ustar faassen faassen 0000000 0000000 hurry
hurry.query-1.1.1/src/hurry.query.egg-info/requires.txt 0000664 0000764 0000764 00000000146 11771070322 024322 0 ustar faassen faassen 0000000 0000000 setuptools
zc.catalog
ZODB3
zope.catalog
zope.component
zope.interface
zope.intid
[test]
zope.testing hurry.query-1.1.1/src/hurry.query.egg-info/dependency_links.txt 0000664 0000764 0000764 00000000001 11771070322 025767 0 ustar faassen faassen 0000000 0000000
hurry.query-1.1.1/src/hurry.query.egg-info/PKG-INFO 0000664 0000764 0000764 00000053100 11771070322 023015 0 ustar faassen faassen 0000000 0000000 Metadata-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.txt 0000664 0000764 0000764 00000001204 11771070322 023602 0 ustar faassen faassen 0000000 0000000 CHANGES.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.py hurry.query-1.1.1/src/hurry.query.egg-info/top_level.txt 0000664 0000764 0000764 00000000006 11771070322 024447 0 ustar faassen faassen 0000000 0000000 hurry
hurry.query-1.1.1/src/hurry/ 0000775 0000764 0000764 00000000000 11771070325 017066 5 ustar faassen faassen 0000000 0000000 hurry.query-1.1.1/src/hurry/query/ 0000775 0000764 0000764 00000000000 11771070325 020233 5 ustar faassen faassen 0000000 0000000 hurry.query-1.1.1/src/hurry/query/interfaces.py 0000664 0000764 0000764 00000002631 11771070320 022725 0 ustar faassen faassen 0000000 0000000 ##############################################################################
#
# 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.py 0000664 0000764 0000764 00000016643 11771070320 021757 0 ustar faassen faassen 0000000 0000000 ##############################################################################
#
# 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.py 0000664 0000764 0000764 00000001533 11771070320 021744 0 ustar faassen faassen 0000000 0000000 ##############################################################################
#
# 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.txt 0000664 0000764 0000764 00000035411 11771070320 022140 0 ustar faassen faassen 0000000 0000000 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]
hurry.query-1.1.1/src/hurry/query/value.py 0000664 0000764 0000764 00000007140 11771070320 021716 0 ustar faassen faassen 0000000 0000000 ##############################################################################
#
# 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.py 0000664 0000764 0000764 00000004732 11771070320 021401 0 ustar faassen faassen 0000000 0000000 ##############################################################################
#
# 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__.py 0000664 0000764 0000764 00000001420 11771070320 022334 0 ustar faassen faassen 0000000 0000000 ##############################################################################
#
# 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.zcml 0000664 0000764 0000764 00000000230 11771070320 023071 0 ustar faassen faassen 0000000 0000000
hurry.query-1.1.1/src/hurry/__init__.py 0000664 0000764 0000764 00000000070 11771070320 021167 0 ustar faassen faassen 0000000 0000000 __import__('pkg_resources').declare_namespace(__name__)
hurry.query-1.1.1/CHANGES.txt 0000664 0000764 0000764 00000002760 11771070320 016737 0 ustar faassen faassen 0000000 0000000 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.
hurry.query-1.1.1/CREDITS.txt 0000664 0000764 0000764 00000000371 11771070320 016760 0 ustar faassen faassen 0000000 0000000 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).
hurry.query-1.1.1/bootstrap.py 0000664 0000764 0000764 00000023522 11771070320 017514 0 ustar faassen faassen 0000000 0000000 ##############################################################################
#
# 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.txt 0000664 0000764 0000764 00000004203 11771070320 021240 0 ustar faassen faassen 0000000 0000000 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.
hurry.query-1.1.1/PKG-INFO 0000664 0000764 0000764 00000053100 11771070325 016222 0 ustar faassen faassen 0000000 0000000 Metadata-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.txt 0000664 0000764 0000764 00000000321 11771070320 016613 0 ustar faassen faassen 0000000 0000000 hurry.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.py 0000664 0000764 0000764 00000004370 11771070320 016637 0 ustar faassen faassen 0000000 0000000 ##############################################################################
#
# 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},
)