Skip to content

Commit

Permalink
Merge pull request #3 from minddistrict/master
Browse files Browse the repository at this point in the history
introduce DatetimeIndex
  • Loading branch information
janwijbrand committed Nov 20, 2015
2 parents ccdb655 + 780d00a commit 496c5cc
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 4 deletions.
6 changes: 3 additions & 3 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ CHANGES
2.2 (unreleased)
================

- Nothing changed yet.

- Introduce Datetime index that's more optimized for index datetime objects.
Please note the index uses seconds-resolution (the integer timestamp
representing the datetime's value).

2.1 (2015-06-11)
================
Expand All @@ -14,7 +15,6 @@ CHANGES
than ``IObjectAddedEvent`` using the ``grokcore.site.install_on``
directive.


2.0 (2013-05-07)
================

Expand Down
2 changes: 1 addition & 1 deletion src/grokcore/catalog/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
from grokcore.catalog.interfaces import IAttributeIndexDefinition
from grokcore.catalog.components import IndexesClass, Indexes
from grokcore.catalog.index import IndexDefinition, AttributeIndexDefinition
from grokcore.catalog.index import Field, Text, Set, Value
from grokcore.catalog.index import Field, Text, Set, Value, Datetime
98 changes: 98 additions & 0 deletions src/grokcore/catalog/ftests/catalog/indexes_datetimeindex.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""
We now demonstrate the use of a ValueIndex with Grok::
Let's set up a site in which we manage a couple of objects::
>>> herd = Herd()
>>> getRootFolder()['herd'] = herd
>>> from zope.site.hooks import setSite
>>> setSite(herd)
Now we add some indexable objects to the site::
>>> herd['alpha'] = Opossum('Alpha', datetime.datetime(2001, 6, 7))
>>> herd['beta'] = Opossum('Beta', datetime.datetime(2001, 7, 8))
>>> herd['gamma'] = Opossum('Gamma', datetime.datetime(2001, 7, 9))
>>> herd['delta'] = Opossum('Delta', datetime.datetime(2001, 9, 24))
Let's query the datetime index::
>>> from zope.catalog.interfaces import ICatalog
>>> from zope.component import getUtility, queryUtility
>>> catalog = getUtility(ICatalog)
>>> def sortedResults(catalog, **kw):
... result = list(catalog.searchResults(**kw))
... result.sort(key=lambda x:x.name)
... return [item.name for item in result]
>>> sortedResults(catalog, birthday={
... 'any_of': [datetime.datetime(2001, 6, 7),
... datetime.datetime(2001, 9, 24)]})
['Alpha', 'Delta']
>>> sortedResults(catalog, birthday={
... 'between': [datetime.datetime(2001, 6, 7),
... datetime.datetime(2001, 7, 9)]})
['Alpha', 'Beta', 'Gamma']
>>> sortedResults(catalog, birthday={
... 'between': [datetime.datetime(2001, 6, 7),
... datetime.datetime(2001, 7, 9),
... True, # exclude boundary.
... True # exclude boundary.
... ]})
['Beta']
Note how the index uses a seconds-resolution and sub-second data is ignored::
>>> herd['omega'] = Opossum(
... 'Omega', datetime.datetime(2002, 5, 3, 14, 53, 14, 000001))
>>> herd['psi'] = Opossum(
... 'Psi', datetime.datetime(2002, 5, 3, 14, 53, 14, 999999))
>>> sortedResults(catalog, birthday={
... 'between': [datetime.datetime(2002, 5, 3l, 14, 53, 14),
... datetime.datetime(2002, 5, 3l, 14, 53, 14)]})
['Omega', 'Psi']
Nuke the catalog and intids in the end, so as not to confuse
other tests::
>>> sm = herd.getSiteManager()
>>> from zope.catalog.interfaces import ICatalog
>>> sm.unregisterUtility(catalog, provided=ICatalog)
True
>>> from zope.intid.interfaces import IIntIds
>>> from zope import component
>>> intids = component.getUtility(IIntIds)
>>> sm.unregisterUtility(intids, provided=IIntIds)
True
Unfortunately ftests don't have good isolation from each other yet.
"""

import datetime
import grokcore.site
import grokcore.catalog
from grokcore.content import Container, Model
from zope.interface import implements, Interface, Attribute


class Herd(Container, grokcore.site.Application):
pass


class IOpossum(Interface):
name = Attribute('Name')
birthday = Attribute('Birthday')


class OpossumIndexes(grokcore.catalog.Indexes):
grokcore.site.site(Herd)
grokcore.catalog.context(IOpossum)

birthday = grokcore.catalog.Datetime()


class Opossum(Model):
implements(IOpossum)

def __init__(self, name, birthday):
self.name = name
self.birthday = birthday
63 changes: 63 additions & 0 deletions src/grokcore/catalog/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@
"""grokcore.catalog index definitions
"""
import sys
import calendar
import BTrees.Length
import zope.container.contained
import zope.catalog.interfaces
import zope.catalog.attribute
import zc.catalog.index

from zope.interface import implements
from zope.interface.interfaces import IMethod, IInterface
from zope.catalog.interfaces import IAttributeIndex
Expand Down Expand Up @@ -156,3 +162,60 @@ class Value(AttributeIndexDefinition):
values.
"""
index_class = ValueIndex


def to_timestamp(dt):
if dt is None:
return None
return calendar.timegm(dt.timetuple())


class _DatetimeIndex(zc.catalog.index.ValueIndex):

def clear(self):
self.values_to_documents = BTrees.LOBTree.LOBTree()
self.documents_to_values = BTrees.LLBTree.LLBTree()
self.documentCount = BTrees.Length.Length(0)
self.wordCount = BTrees.Length.Length(0)

def index_doc(self, doc_id, value):
if value is None:
return
value = to_timestamp(value)
super(_DatetimeIndex, self).index_doc(doc_id, value)

def apply(self, query):
if 'any_of' in query:
# The "value" of the dict is a sequence of datetime objects.
# Convert it into a timestamps.
query['any_of'] = [to_timestamp(v) for v in query['any_of']]
elif 'between' in query:
# The "value" of the dict is a sequence of arguments to pass on to
# the underlying btree. Convert the first two parameters that are
# datetime objects (or None) into timestamps.
query['between'] = parameters = list(query['between'])
parameters[0] = to_timestamp(parameters[0])
parameters[1] = to_timestamp(parameters[1])
return super(_DatetimeIndex, self).apply(query)

def values(self, min=None, max=None, excludemin=False, excludemax=False,
doc_id=None):
min = to_timestamp(min) if min is not None else None
max = to_timestamp(max) if max is not None else None
return super(_DatetimeIndex, self).values(
min, max, excludemin, excludemax, doc_id)


class DatetimeIndex(
zope.catalog.attribute.AttributeIndex,
_DatetimeIndex,
zope.container.contained.Contained):
pass


class Datetime(AttributeIndexDefinition):
"""A :class:`grokcore.catalog.Indexes` index specifically meant for
datetime objects.
"""
index_class = DatetimeIndex

0 comments on commit 496c5cc

Please sign in to comment.