diff --git a/limpyd/contrib/indexes.py b/limpyd/contrib/indexes.py index 1b63d9e..040ed02 100644 --- a/limpyd/contrib/indexes.py +++ b/limpyd/contrib/indexes.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- from __future__ import unicode_literals -from limpyd.indexes import BaseIndex +from limpyd.indexes import BaseIndex, NumberRangeIndex, TextRangeIndex from limpyd.utils import cached_property @@ -208,3 +208,45 @@ def get_filtered_keys(self, suffix, *args, **kwargs): for index in self._indexes: if index.can_handle_suffix(suffix): return index.get_filtered_keys(suffix, *args, **kwargs) + + +# This is a multi-indexes managing the different parts of a date in the format YYYY-MM-SS +DateIndexParts = MultiIndexes.compose([ + NumberRangeIndex.configure(prefix='year', transform=lambda value: value[:4], handle_uniqueness=False, name='YearIndex'), + NumberRangeIndex.configure(prefix='month', transform=lambda value: value[5:7], handle_uniqueness=False, name='MonthIndex'), + NumberRangeIndex.configure(prefix='day', transform=lambda value: value[8:10], handle_uniqueness=False, name='DayIndex'), +], name='DateIndexParts') + +# A simple TextRangeIndex to filter on a date in the format YYYY-MM-SS +DateRangeIndex = TextRangeIndex.configure(key='date', transform=lambda value: value[:10], name='DateRangeIndex') + +# A full usable index for fields holding dates (without time) +DateIndex = MultiIndexes.compose([DateRangeIndex, DateIndexParts], name='DateIndex') + +# This is a multi-indexes managing the different parts of a tine in the format HH:MM:SS +TimeIndexParts = MultiIndexes.compose([ + NumberRangeIndex.configure(prefix='hour', transform=lambda value: value[0:2], handle_uniqueness=False, name='HourIndex'), + NumberRangeIndex.configure(prefix='minute', transform=lambda value: value[3:5], handle_uniqueness=False, name='MinuteIndex'), + NumberRangeIndex.configure(prefix='second', transform=lambda value: value[6:8], handle_uniqueness=False, name='SecondIndex'), +], name='TimeIndexParts') + +# A simple TextRangeIndex to filter on a date in the format HH:MM:SS +TimeRangeIndex = TextRangeIndex.configure(key='time', transform=lambda value: value[:8], name='TimeRangeIndex') + +# A full usable index for fields holding times (without date) +TimeIndex = MultiIndexes.compose([TimeRangeIndex, TimeIndexParts], name='TimeIndex') + +# A full usable index for fields holding dates+times, without filtering on hour/min/sec +# but only full field, full date and full time, and year, month, day +DateSimpleTimeIndex = MultiIndexes.compose([ + TextRangeIndex.configure(key='full', name='FullDateTimeRangeIndex'), + DateRangeIndex.configure(prefix='date'), + DateIndexParts, + TimeRangeIndex.configure(prefix='time', transform=lambda value: value[11:]) # pass only time +], name='DateSimpleTimeIndex', transform=lambda value: value[:19]) + +# A full usable index for fields holding dates+times, with full filtering capabilities +DateTimeIndex = MultiIndexes.compose([ + DateSimpleTimeIndex, + TimeIndexParts.configure(transform=lambda value: value[11:]), +], name='DateTimeIndex') diff --git a/tests/contrib/indexes.py b/tests/contrib/indexes.py index 4f63116..554c9b6 100644 --- a/tests/contrib/indexes.py +++ b/tests/contrib/indexes.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals from limpyd import fields -from limpyd.contrib.indexes import MultiIndexes +from limpyd.contrib.indexes import MultiIndexes, DateIndex, DateTimeIndex, TimeIndex from limpyd.exceptions import ImplementationError, UniquenessError from limpyd.indexes import BaseIndex, NumberRangeIndex, TextRangeIndex, EqualIndex @@ -166,3 +166,208 @@ class MultiIndexTestModel(TestRedisModel): set(MultiIndexTestModel.collection(name__first_letter='b', name='bar')), {pk2} ) + + +class DateTimeModelTest(TestRedisModel): + date = fields.InstanceHashField(indexable=True, indexes=[DateIndex]) + unique_date = fields.InstanceHashField(indexable=True, indexes=[DateIndex], unique=True) + time = fields.InstanceHashField(indexable=True, indexes=[TimeIndex]) + unique_time = fields.InstanceHashField(indexable=True, indexes=[TimeIndex], unique=True) + datetime = fields.InstanceHashField(indexable=True, indexes=[DateTimeIndex]) + unique_datetime = fields.InstanceHashField(indexable=True, indexes=[DateTimeIndex], unique=True) + + +class DateTimeIndexesTestCase(LimpydBaseTest): + + def test_date_index(self): + obj1 = DateTimeModelTest(date='2015-12-16') + pk1 = obj1.pk.get() + obj2 = DateTimeModelTest(date='2014-09-07') + pk2 = obj2.pk.get() + obj3 = DateTimeModelTest(date='2015-06-12') + pk3 = obj3.pk.get() + obj4 = DateTimeModelTest(date='2016-12-31') + pk4 = obj4.pk.get() + + # not unique so same date is ok + obj5 = DateTimeModelTest(date='2015-12-16') + pk5 = obj5.pk.get() + + # EqualIndex + self.assertSetEqual( + set(DateTimeModelTest.collection(date='2015-12-16')), + {pk1, pk5} + ) + self.assertSetEqual( + set(DateTimeModelTest.collection(date__gte='2015-06-12')), + {pk1, pk3, pk4, pk5} + ) + self.assertSetEqual( + set(DateTimeModelTest.collection(date__gt='2015')), + {pk1, pk3, pk4, pk5} + ) + self.assertSetEqual( + set(DateTimeModelTest.collection(date__lt='2015-07')), + {pk2, pk3} + ) + + # year index + self.assertSetEqual( + set(DateTimeModelTest.collection(date__year=2015)), + {pk1, pk3, pk5} + ) + self.assertSetEqual( + set(DateTimeModelTest.collection(date__year__lt=2015)), + {pk2} + ) + + # month index + self.assertSetEqual( + set(DateTimeModelTest.collection(date__month=12)), + {pk1, pk4, pk5} + ) + self.assertSetEqual( + set(DateTimeModelTest.collection(date__year=2015, date__month=12)), + {pk1, pk5} + ) + + def test_date_unique_index(self): + DateTimeModelTest(unique_date='2001-01-01') + # can add on same year (diff month/day) + DateTimeModelTest(unique_date='2001-02-02') + # can add on same month (diff year/day) + DateTimeModelTest(unique_date='2002-02-03') + # can add on same day (diff year/month) + DateTimeModelTest(unique_date='2003-03-03') + + # cannot add on same date + with self.assertRaises(UniquenessError): + DateTimeModelTest(unique_date='2003-03-03') + + def test_time_index(self): + # constructed the same as DateIndex so only test for transforms + + obj1 = DateTimeModelTest(time='15:16:17') + pk1 = obj1.pk.get() + obj2 = DateTimeModelTest(time='05:06:07') + pk2 = obj2.pk.get() + + self.assertSetEqual( + set(DateTimeModelTest.collection(time__hour='15')), + {pk1} + ) + self.assertSetEqual( + set(DateTimeModelTest.collection(time__minute='06')), + {pk2} + ) + self.assertSetEqual( + set(DateTimeModelTest.collection(time__second='17')), + {pk1} + ) + + def test_datetime_index(self): + obj1 = DateTimeModelTest(datetime='2015-12-16 15:16:17') + pk1 = obj1.pk.get() + obj2 = DateTimeModelTest(datetime='2014-09-07 05:06:07') + pk2 = obj2.pk.get() + obj3 = DateTimeModelTest(datetime='2015-06-12 15:16:17') + pk3 = obj3.pk.get() + obj4 = DateTimeModelTest(datetime='2016-12-31 05:06:07') + pk4 = obj4.pk.get() + + # not unique so same date is ok + obj5 = DateTimeModelTest(datetime='2015-12-16 15:16:17') + pk5 = obj5.pk.get() + + # check full date + self.assertSetEqual( + set(DateTimeModelTest.collection(datetime='2015-12-16 15:16:17')), + {pk1, pk5} + ) + self.assertSetEqual( + set(DateTimeModelTest.collection(datetime__gte='2015-06-12 1')), + {pk1, pk3, pk4, pk5} + ) + + # check date + self.assertSetEqual( + set(DateTimeModelTest.collection(datetime__date='2015-12-16')), + {pk1, pk5} + ) + + self.assertSetEqual( + set(DateTimeModelTest.collection(datetime__date__lt='2015-07')), + {pk2, pk3} + ) + + # check year + self.assertSetEqual( + set(DateTimeModelTest.collection(datetime__year=2015)), + {pk1, pk3, pk5} + ) + self.assertSetEqual( + set(DateTimeModelTest.collection(datetime__year__lt=2015)), + {pk2} + ) + + # check time + self.assertSetEqual( + set(DateTimeModelTest.collection(datetime__time='15:16:17')), + {pk1, pk3, pk5} + ) + self.assertSetEqual( + set(DateTimeModelTest.collection(datetime__time__lt='15')), + {pk2, pk4} + ) + + # check hour + self.assertSetEqual( + set(DateTimeModelTest.collection(datetime__hour=15)), + {pk1, pk3, pk5} + ) + self.assertSetEqual( + set(DateTimeModelTest.collection(datetime__hour__lt=15)), + {pk2, pk4} + ) + + # be crazy, check all for '2015-12-16 15:16:17' + # All are ended so it should work + self.assertSetEqual( + set(DateTimeModelTest.collection( + datetime='2015-12-16 15:16:17', + datetime__date='2015-12-16', + datetime__year=2015, + datetime__month=12, + datetime__day=16, + datetime__time='15:16:17', + datetime__hour=15, + datetime__minute=16, + datetime__second=17 + )), + {pk1, pk5} + ) + + def test_datetime_unique_index(self): + + DateTimeModelTest(unique_datetime='2001-01-01 01:01:01') + # can add on same year (diff month/day/hour/min/sec) + DateTimeModelTest(unique_datetime='2001-02-02 02:02:02') + # can add on same month (diff year/day/hour/min/sec) + DateTimeModelTest(unique_datetime='2002-02-03 03:03:03') + # can add on same day (diff year/month/hour/min/sec) + DateTimeModelTest(unique_datetime='2003-03-03 04:04:04') + # can add on same hour (diff year/month/day/min/sec) + DateTimeModelTest(unique_datetime='2004-04-04 04:05:05') + # can add on same minute (diff year/month/day/hour/sec) + DateTimeModelTest(unique_datetime='2005-05-05 05:05:06') + # can add on same second (diff year/month/day/hour/min) + DateTimeModelTest(unique_datetime='2006-06-06 06:06:06') + + # can add on same date (diff time) + DateTimeModelTest(unique_datetime='2006-06-06 07:07:07') + # can add on same time (diff date) + DateTimeModelTest(unique_datetime='2007-07-07 07:07:07') + + # but cannot add the same full datetime + with self.assertRaises(UniquenessError): + DateTimeModelTest(unique_datetime='2007-07-07 07:07:07')