#############################################################################
#
# Copyright (c) 2004 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.
#
##############################################################################
"""Index interface-defined attributes
"""
__docformat__ = 'restructuredtext'
import zope.interface
from zope.catalog.interfaces import IAttributeIndex
[docs]@zope.interface.implementer(IAttributeIndex)
class AttributeIndex(object):
"""Index interface-defined attributes
Mixin for indexing a particular attribute of an object after
first adapting the object to be indexed to an interface.
The class is meant to be mixed with a base class that defines an
``index_doc`` method and an ``unindex_doc`` method:
>>> class BaseIndex(object):
... def __init__(self):
... self.data = []
... def index_doc(self, id, value):
... self.data.append((id, value))
... def unindex_doc(self, id):
... for n, (iid, _) in enumerate(self.data):
... if id == iid:
... del self.data[n]
... break
The class does two things. The first is to get a named field
from an object:
>>> class Data(object):
... def __init__(self, v):
... self.x = v
>>> from zope.catalog.attribute import AttributeIndex
>>> class Index(AttributeIndex, BaseIndex):
... pass
>>> index = Index('x')
>>> index.index_doc(11, Data(1))
>>> index.index_doc(22, Data(2))
>>> index.data
[(11, 1), (22, 2)]
If the field value is ``None``, indexing it removes it from the
index:
>>> index.index_doc(11, Data(None))
>>> index.data
[(22, 2)]
If the field names a method (any callable object), the results
of calling that field can be indexed:
>>> def z(self): return self.x + 20
>>> Data.z = z
>>> index = Index('z', field_callable=True)
>>> index.index_doc(11, Data(1))
>>> index.index_doc(22, Data(2))
>>> index.data
[(11, 21), (22, 22)]
Of course, if you neglect to set ``field_callable`` when you
index a method, it's likely that most concrete index implementations will
raise an exception, but this class will happily pass that callable
on:
>>> index = Index('z')
>>> data = Data(1)
>>> index.index_doc(11, data)
>>> index.data
[(11, <bound method ...>>)]
The class can also adapt an object to an interface before getting the
field:
>>> from zope.interface import Interface
>>> class I(Interface):
... pass
>>> class Data(object):
... def __init__(self, v):
... self.x = v
... def __conform__(self, iface):
... if iface is I:
... return Data2(self.x)
>>> class Data2(object):
... def __init__(self, v):
... self.y = v * v
>>> index = Index('y', I)
>>> index.index_doc(11, Data(3))
>>> index.index_doc(22, Data(2))
>>> index.data
[(11, 9), (22, 4)]
If adapting to the interface fails, the object is not indexed:
>>> class I2(Interface): pass
>>> I2(Data(3), None) is None
True
>>> index = Index('y', I2)
>>> index.index_doc(11, Data(3))
>>> index.data
[]
When you define an index class, you can define a default
interface and/or a default interface:
>>> class Index(AttributeIndex, BaseIndex):
... default_interface = I
... default_field_name = 'y'
>>> index = Index()
>>> index.index_doc(11, Data(3))
>>> index.index_doc(22, Data(2))
>>> index.data
[(11, 9), (22, 4)]
"""
#: Subclasses can set this to a string if they want to allow
#: construction without passing the ``field_name``.
default_field_name = None
#: Subclasses can set this to an interface (a callable taking
#: the object do index and the default value to return)
#: if they want to allow construction that doesn't provide an
#: ``interface``.
default_interface = None
def __init__(self, field_name=None, interface=None, field_callable=False,
*args, **kwargs):
super(AttributeIndex, self).__init__(*args, **kwargs)
if field_name is None and self.default_field_name is None:
raise ValueError("Must pass a field_name")
if field_name is None:
self.field_name = self.default_field_name
else:
self.field_name = field_name
if interface is None:
self.interface = self.default_interface
else:
self.interface = interface
self.field_callable = field_callable
[docs] def index_doc(self, docid, object):
"""
Derives the value to index for *object*.
Uses the interface passed to the constructor to adapt the
object, and then gets the field (calling it if
``field_callable`` was set). If the value thus found is ``None``,
calls ``unindex_doc``. Otherwise, passes the *docid* and the value to
the superclass's implementation of ``index_doc``.
"""
if self.interface is not None:
object = self.interface(object, None)
if object is None:
return None
value = getattr(object, self.field_name, None)
if value is not None and self.field_callable:
# do not eat the exception raised below
value = value()
if value is None:
# unindex the previous value!
super(AttributeIndex, self).unindex_doc(docid)
return None
return super(AttributeIndex, self).index_doc(docid, value)