Added WeekDays Type

This commit is contained in:
Konsta Vesterinen
2013-09-15 19:52:05 +03:00
parent e88364aba7
commit e1f7e494cf
4 changed files with 320 additions and 10 deletions

View File

@@ -17,6 +17,7 @@ from .scalar_list import ScalarListException, ScalarListType
from .timezone import TimezoneType
from .ts_vector import TSVectorType
from .uuid import UUIDType
from .weekdays import WeekDay, WeekDays, WeekDaysType
__all__ = (
@@ -39,17 +40,12 @@ __all__ = (
TimezoneType,
TSVectorType,
UUIDType,
WeekDay,
WeekDays,
WeekDaysType
)
class ScalarCoercedType(object):
def _coerce(self, value):
raise NotImplemented
def coercion_listener(self, target, value, oldvalue, initiator):
return self._coerce(value)
class InstrumentedList(_InstrumentedList):
"""Enhanced version of SQLAlchemy InstrumentedList. Provides some
additional functionality."""

View File

@@ -1,7 +1,7 @@
from sqlalchemy import types
from sqlalchemy_utils import ImproperlyConfigured
import six
from .scalar_coercible import ScalarCoercible
from ..exceptions import ImproperlyConfigured
class Country(object):
@@ -14,7 +14,7 @@ class Country(object):
if self.get_locale is None:
raise ImproperlyConfigured(
"Country class needs define get_locale."
"Country class needs to define get_locale."
)
@property

View File

@@ -0,0 +1,146 @@
# -*- coding: utf-8 -*-
from functools import total_ordering
from sqlalchemy import types
from sqlalchemy.dialects.postgresql import BIT
get_day_names = None
try:
from babel.dates import get_day_names
except ImportError:
pass
from ..exceptions import ImproperlyConfigured
@total_ordering
class WeekDay(object):
NUM_WEEK_DAYS = 7
get_locale = None
def __init__(self, index, get_locale=None):
if not (0 <= index < self.NUM_WEEK_DAYS):
raise ValueError(
"index must be between 0 and %d" % self.NUM_WEEK_DAYS
)
self.index = index
if get_locale is not None:
self.get_locale = get_locale
if self.get_locale is None:
raise ImproperlyConfigured(
"Weekday class needs to define get_locale."
)
def __eq__(self, other):
if isinstance(other, WeekDay):
return self.index == other.index
else:
return NotImplemented
def __hash__(self):
return hash(self.index)
def __lt__(self, other):
return self.position < other.position
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, self.index)
def __str__(self):
return unicode(self).encode('utf-8')
def __unicode__(self):
return self.name
def get_name(self, width='wide', context='format'):
names = get_day_names(
width,
context,
self.get_locale()
)
return names[self.index]
@property
def name(self):
return self.get_name()
@property
def position(self):
return (
self.index -
self.get_locale().first_week_day
) % self.NUM_WEEK_DAYS
class WeekDays(object):
def __init__(self, bit_string_or_week_days):
if isinstance(bit_string_or_week_days, basestring):
self._days = set()
if len(bit_string_or_week_days) != WeekDay.NUM_WEEK_DAYS:
raise ValueError(
'Bit string must be {0} characters long.'.format(
WeekDay.NUM_WEEK_DAYS
)
)
for index, bit in enumerate(bit_string_or_week_days):
if bit not in '01':
raise ValueError(
'Bit string may only contain zeroes and ones.'
)
if bit == '1':
self._days.add(WeekDay(index))
elif isinstance(bit_string_or_week_days, WeekDays):
self._days = bit_string_or_week_days._days
else:
self._days = set(bit_string_or_week_days)
def __eq__(self, other):
if isinstance(other, WeekDays):
return self._days == other._days
elif isinstance(other, basestring):
return self.as_bit_string() == other
else:
return NotImplemented
def __iter__(self):
for day in sorted(self._days):
yield day
def __contains__(self, value):
return value in self._days
def __repr__(self):
return '%s(%r)' % (
self.__class__.__name__,
self.as_bit_string()
)
def __str__(self):
return unicode(self).encode('utf-8')
def __unicode__(self):
return u', '.join(unicode(day) for day in self)
def as_bit_string(self):
return ''.join(
'1' if WeekDay(index) in self._days else '0'
for index in xrange(WeekDay.NUM_WEEK_DAYS)
)
class WeekDaysType(types.TypeDecorator):
impl = BIT(WeekDay.NUM_WEEK_DAYS)
def process_bind_param(self, value, dialect):
if isinstance(value, WeekDays):
return value.as_bit_string()
if isinstance(value, basestring):
return value
def process_result_value(self, value, dialect):
if value is not None:
return WeekDays(value)

View File

@@ -0,0 +1,168 @@
Locale = None
try:
from babel import Locale
except ImportError:
pass
from flexmock import flexmock
import pytest
from sqlalchemy_utils.types import WeekDay, WeekDays
WeekDay.get_locale = lambda: Locale('fi')
@pytest.mark.skipif('Locale is None')
class TestWeekDay(object):
def test_constructor_with_valid_index(self):
day = WeekDay(1)
assert day.index == 1
@pytest.mark.parametrize('index', [-1, 7])
def test_constructor_with_invalid_index(self, index):
with pytest.raises(ValueError):
WeekDay(index)
def test_equality_with_equal_week_day(self):
day = WeekDay(1)
day2 = WeekDay(1)
assert day == day2
def test_equality_with_unequal_week_day(self):
day = WeekDay(1)
day2 = WeekDay(2)
assert day != day2
def test_equality_with_unsupported_comparison(self):
day = WeekDay(1)
assert day != 'foobar'
def test_hash_is_equal_to_index_hash(self):
day = WeekDay(1)
assert hash(day) == hash(day.index)
def test_representation(self):
day = WeekDay(1)
assert repr(day) == "WeekDay(1)"
@pytest.mark.parametrize(
('index', 'first_week_day', 'position'),
[
(0, 0, 0),
(1, 0, 1),
(6, 0, 6),
(0, 6, 1),
(1, 6, 2),
(6, 6, 0),
]
)
def test_position(self, index, first_week_day, position):
fake_locale = flexmock(first_week_day=first_week_day)
day = WeekDay(index, get_locale=lambda: fake_locale)
assert day.position == position
def test_get_name_returns_localized_week_day_name(self):
locale = Locale('fi')
day = WeekDay(0, get_locale=lambda: locale)
assert day.get_name() == u'maanantaina'
def test_name_delegates_to_get_name(self):
day = WeekDay(0)
flexmock(day).should_receive('get_name').and_return(u'maanantaina')
assert day.name == u'maanantaina'
def test_unicode(self):
day = WeekDay(0)
flexmock(day).should_receive('name').and_return(u'maanantaina')
assert unicode(day) == u'maanantaina'
def test_str(self):
day = WeekDay(0)
flexmock(day).should_receive('__unicode__').and_return(u'maanantaina')
assert str(day) == 'maanantaina'
@pytest.mark.skipif('Locale is None')
class TestWeekDays(object):
def test_constructor_with_valid_bit_string(self):
days = WeekDays('1000100')
assert days._days == set([WeekDay(0), WeekDay(4)])
@pytest.mark.parametrize(
'bit_string',
[
'000000', # too short
'00000000', # too long
]
)
def test_constructor_with_bit_string_of_invalid_length(self, bit_string):
with pytest.raises(ValueError):
WeekDays(bit_string)
def test_constructor_with_bit_string_containing_invalid_characters(self):
with pytest.raises(ValueError):
WeekDays('foobarz')
def test_constructor_with_another_week_days_object(self):
days = WeekDays('0000000')
another_days = WeekDays(days)
assert days._days == another_days._days
def test_representation(self):
days = WeekDays('0000000')
assert repr(days) == "WeekDays('0000000')"
@pytest.mark.parametrize(
'bit_string',
[
'0000000',
'1000000',
'0000001',
'0101000',
'1111111',
]
)
def test_as_bit_string(self, bit_string):
days = WeekDays(bit_string)
assert days.as_bit_string() == bit_string
def test_equality_with_equal_week_days_object(self):
days = WeekDays('0001000')
days2 = WeekDays('0001000')
assert days == days2
def test_equality_with_unequal_week_days_object(self):
days = WeekDays('0001000')
days2 = WeekDays('1000000')
assert days != days2
def test_equality_with_equal_bit_string(self):
days = WeekDays('0001000')
assert days == '0001000'
def test_equality_with_unequal_bit_string(self):
days = WeekDays('0001000')
assert days != '0101000'
def test_equality_with_unsupported_comparison(self):
days = WeekDays('0001000')
assert days != 0
def test_iterator_starts_from_locales_first_week_day(self):
fake_locale = flexmock(first_week_day=1)
flexmock(WeekDay).should_receive('get_locale').and_return(fake_locale)
days = WeekDays('1111111')
indices = list(day.index for day in days)
assert indices == [1, 2, 3, 4, 5, 6, 0]
def test_unicode(self):
locale = Locale('fi')
flexmock(WeekDay).should_receive('get_locale').and_return(locale)
days = WeekDays('1000100')
assert unicode(days) == u'maanantaina, perjantaina'
def test_str(self):
locale = Locale('fi')
flexmock(WeekDay).should_receive('get_locale').and_return(locale)
days = WeekDays('1000100')
assert str(days) == 'maanantaina, perjantaina'