Source code for zope.catalog.attribute

#############################################################################
#
# 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)