source: main/waeup.kofa/trunk/src/waeup/kofa/catalog.txt @ 12927

Last change on this file since 12927 was 12920, checked in by Henrik Bettermann, 10 years ago

Untegrate doctests into sphinx docu.

File size: 7.0 KB
Line 
1Cataloging Support
2******************
3
4.. module:: waeup.kofa.catalog
5
6Components that support cataloging and searching objects inside a
7Kofa site.
8
9.. :doctest:
10.. :layer: waeup.kofa.testing.KofaUnitTestLayer
11
12The KofaQuery Class
13===================
14
15.. class:: KofaQuery()
16
17   .. attribute:: grok.implements(hurry.query.interfaces.IQuery)
18
19   A `hurry.query.query.Query` compatible query that also supports
20   retrival of plain ``Bree`` result sets as used inside a catalog.
21
22   Like `hurry.query.query.Query` objects, `KofaQuery` is some kind
23   of a meta query or 'compound query' that can give the cataloged
24   objects (or their int ids) matching one or more 'subqueries'.
25
26   This way you can search for objects (or their int ids) that match
27   several criteria at the same time. See ``examples`` section below.
28
29   A singleton instance of this class is also available as global
30   utility.
31
32   .. method:: searchResults(query)
33
34     Get the cataloged objects determined by ``query``.
35
36   .. method:: apply(query)
37
38     Get the list of int ids (a `BTree` result set) for objects
39     determined by ``query``.
40
41     The list of int ids is less expensive to compute than the
42     complete search results and sufficient, for instance, when you
43     only need the number of objects that match a query and not the
44     objects themselves.
45
46Examples
47========
48
49Getting a general query object
50------------------------------
51
52We can get a KofaQuery object by asking for an unnamed global utility
53implementing `hurry.query.interfaces.IQuery`:
54
55    >>> from hurry.query.interfaces import IQuery
56    >>> from zope.component import getUtility
57    >>> q = getUtility(IQuery)
58    >>> q
59    <waeup.kofa.catalog.KofaQuery object at 0x...>
60
61This query can get 'subqueries' and delivers the objects found or
62their ids. To show this we have to setup a catalog with some entries.
63
64
65Setting up a catalog and feeding it
66-----------------------------------
67
68    >>> from zope.catalog.interfaces import ICatalog
69    >>> from zope.catalog.catalog import Catalog
70    >>> mycat = Catalog()
71
72We register this catalog with the component architechture as a utility
73named 'mycatalog':
74
75    >>> from zope.component import provideUtility
76    >>> provideUtility(mycat, ICatalog, 'mycatalog')
77
78We setup a special content type whose instances we will catalog later:
79
80    >>> from zope.interface import Interface, Attribute, implements
81    >>> from zope.container.contained import Contained
82    >>> class IMammoth(Interface):
83    ...   name = Attribute('name')
84    ...   age = Attribute('age')
85
86    >>> class Mammoth(Contained):
87    ...   implements(IMammoth)
88    ...   def __init__(self, name, age):
89    ...     self.name = name
90    ...     self.age = age
91    ...   def __cmp__(self, other):
92    ...     return cmp(self.name, other.name)
93
94By including the __cmp__ method we make sure search results can be
95stably sorted.
96
97We also setup a `zope.intid.interfaces.IIntIds` utility. This is not
98necessary for plain catalogs, but when we want to use KofaQuery (or
99`hurry.query.query.Query` objects), as to get a unique mapping from
100objects (stored in ZODB) to integer numbers (stored in catalogs),
101these query objects lookup a global IIntIds utiliy:
102
103    >>> from zope import interface
104    >>> import zope.intid.interfaces
105    >>> class DummyIntId(object):
106    ...     interface.implements(zope.intid.interfaces.IIntIds)
107    ...     MARKER = '__dummy_int_id__'
108    ...     def __init__(self):
109    ...         self.counter = 0
110    ...         self.data = {}
111    ...     def register(self, obj):
112    ...         intid = getattr(obj, self.MARKER, None)
113    ...         if intid is None:
114    ...             setattr(obj, self.MARKER, self.counter)
115    ...             self.data[self.counter] = obj
116    ...             intid = self.counter
117    ...             self.counter += 1
118    ...         return intid
119    ...     def getObject(self, intid):
120    ...         return self.data[intid]
121    ...     def __iter__(self):
122    ...         return iter(self.data)
123    >>> intid = DummyIntId()
124    >>> from zope.component import provideUtility
125    >>> provideUtility(intid, zope.intid.interfaces.IIntIds)
126
127Now we can catalog some mammoths. Here we create a herd and catalog
128each item of it:
129
130    >>> from zope.catalog.field import FieldIndex
131    >>> mycat['mammoth_name'] = FieldIndex('name', IMammoth)
132    >>> mycat['mammoth_age'] = FieldIndex('age', IMammoth)
133
134    >>> herd = [
135    ...   Mammoth(name='Fred', age=33),
136    ...   Mammoth(name='Hank', age=30),
137    ...   Mammoth(name='Wilma', age=28),
138    ... ]
139
140    >>> for mammoth in herd:
141    ...   mycat.index_doc(intid.register(mammoth), mammoth)
142
143
144Searching for result sets
145-------------------------
146
147Finally we can perform queries:
148
149    >>> from hurry.query import Eq
150    >>> from zope.component import getUtility
151    >>> subquery1 = Eq(('mycatalog', 'mammoth_name'), 'Fred')
152
153The latter means: search for objects whose name is ``'Fred'`` in the
154``mammoth_name`` index of a catalog registered as a utility named
155``mycatalog``.
156
157    >>> from hurry.query import Between
158    >>> subquery2 = Between(('mycatalog', 'mammoth_age'), 30, 33)
159
160This means: ask for objects cataloged in an index named 'mammoth_age',
161whose cataloged value is between 30 and 33 (including this values).
162
163    >>> r1 = q.apply(subquery2)
164    >>> r1
165    IFSet([0, 1])
166
167Using ``apply()`` above, we get a set of values stored in an
168``IFBTree``:
169
170    >>> type(r1)
171    <type 'BTrees.IFBTree.IFSet'>
172
173``IFBTree`` objects implement a rather efficient integer to float
174mapping where also integers are allowed as values. For each object
175found (i.e. mammoths whose age is between 30 and 33), we get the
176number of its entry.
177
178To get the real object, we can use intids here, because we setup an
179appropriate IIntIds utility before:
180
181    >>> [intid.getObject(x).name for x in r1]
182    ['Fred', 'Hank']
183
184We can (and should) also use the `searchResults()` method explained
185below to do that.
186
187Retrieving BTree sets can, however, make sense, if you want to know
188only the number of results for a particular query or whether there are
189results at all in a more efficient way:
190
191    >>> len(r1)
192    2
193
194Searching for objects
195---------------------
196
197Very often we don't want to know the catalog-internal 'ids' of
198searched objects but the objects themselves.
199
200This can be done by using the ``searchResults`` method of
201``KofaQuery``:
202
203    >>> r2 = q.searchResults(subquery1)
204    >>> r2
205    <zope.catalog.catalog.ResultSet instance at 0x...>
206
207    >>> list(r2)
208    [<Mammoth object at 0x...>]
209
210We got one result item, we can immediately ask for further infos. To
211access a result item by its index number, we have to turn the
212ResultSet into an ordinary list before:
213
214    >>> entry = list(r2)[0]
215    >>> entry.name, entry.age
216    ('Fred', 33)
217
218We can also use ``subquery2`` as above:
219
220    >>> r3 = q.searchResults(subquery2)
221    >>> [(x.name, x.age) for x in r3]
222    [('Fred', 33), ('Hank', 30)]
223
224or use both queries at once:
225
226    >>> r4 = q.searchResults(subquery1 & subquery2)
227    >>> [(x.name, x.age) for x in r4]
228    [('Fred', 33)]
229
230which will give us, of course, the same result set as with subquery1.
Note: See TracBrowser for help on using the repository browser.