dav_events: improved Iso8601Serializer and its tests
Run tests / Execute tox to run the test suite (push) Successful in 2m51s
Run tests / Execute tox to run the test suite (push) Successful in 2m51s
This commit is contained in:
+48
-36
@@ -2,7 +2,6 @@ import datetime
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import pytz
|
import pytz
|
||||||
from six import string_types
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -24,15 +23,17 @@ class Iso8601Serializer:
|
|||||||
r')?'
|
r')?'
|
||||||
r'(?P<offset>(?P<offdir>[\-+])(?P<offhours>([01][0-9])|(2[0123]))(:(?P<offmins>[0-5][0-9]))?)?$')
|
r'(?P<offset>(?P<offdir>[\-+])(?P<offhours>([01][0-9])|(2[0123]))(:(?P<offmins>[0-5][0-9]))?)?$')
|
||||||
|
|
||||||
def __init__(self, instance=None, text=None):
|
def __init__(self, value):
|
||||||
if instance is not None:
|
if isinstance(value, datetime.datetime) \
|
||||||
self.instance = instance
|
or isinstance(value, datetime.date) \
|
||||||
elif text is not None:
|
or isinstance(value, datetime.time):
|
||||||
self.instance = Iso8601Serializer.deserialize(text)
|
dt_obj = value
|
||||||
|
else:
|
||||||
|
dt_obj = Iso8601Serializer.deserialize(value)
|
||||||
|
self._serialized = Iso8601Serializer.serialize(dt_obj)
|
||||||
|
|
||||||
@property
|
def __str__(self):
|
||||||
def str(self):
|
return self._serialized
|
||||||
return Iso8601Serializer.serialize(self.instance)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def serialize(cls, value, ignore_unsupported_input=False):
|
def serialize(cls, value, ignore_unsupported_input=False):
|
||||||
@@ -55,32 +56,43 @@ class Iso8601Serializer:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def deserialize(cls, value, ignore_unsupported_input=False):
|
def deserialize(cls, value, ignore_unsupported_input=False):
|
||||||
prefix = '{marker}{sep}'.format(marker=cls.marker, sep=cls.separator)
|
prefix = '{marker}{sep}'.format(marker=cls.marker, sep=cls.separator)
|
||||||
if isinstance(value, string_types) and value.startswith(prefix):
|
|
||||||
haystack = value[len(prefix):]
|
if not isinstance(value, str):
|
||||||
m = cls._re.match(haystack)
|
if ignore_unsupported_input:
|
||||||
if m is not None:
|
return value
|
||||||
gd = m.groupdict()
|
raise TypeError('Expected string type, not {}'.format(value.__class__.__name__))
|
||||||
if gd['hour'] is None:
|
|
||||||
value = datetime.date(int(gd['year']), int(gd['mon']), int(gd['day']))
|
if not value.startswith(prefix):
|
||||||
elif gd['year'] is None:
|
if ignore_unsupported_input:
|
||||||
value = datetime.time(hour=int(gd['hour']), minute=int(gd['min']), second=int(gd['sec']))
|
return value
|
||||||
|
raise ValueError('String must begin with \'{prefix}\''.format(prefix=prefix))
|
||||||
|
|
||||||
|
haystack = value[len(prefix):]
|
||||||
|
match = cls._re.match(haystack)
|
||||||
|
|
||||||
|
if match is None:
|
||||||
|
if ignore_unsupported_input:
|
||||||
|
return value
|
||||||
|
raise ValueError('Format not recognized \'{str}\''.format(str=haystack))
|
||||||
|
|
||||||
|
gd = match.groupdict()
|
||||||
|
if gd['hour'] is None:
|
||||||
|
value = datetime.date(int(gd['year']), int(gd['mon']), int(gd['day']))
|
||||||
|
elif gd['year'] is None:
|
||||||
|
value = datetime.time(hour=int(gd['hour']), minute=int(gd['min']), second=int(gd['sec']))
|
||||||
|
else:
|
||||||
|
value = datetime.datetime(int(gd['year']), int(gd['mon']), int(gd['day']),
|
||||||
|
hour=int(gd['hour']), minute=int(gd['min']), second=int(gd['sec']))
|
||||||
|
if gd['offset'] is not None:
|
||||||
|
offset = datetime.timedelta(hours=int(gd['offhours']))
|
||||||
|
if gd['offmins'] is not None:
|
||||||
|
offset += datetime.timedelta(minutes=int(gd['offmins']))
|
||||||
|
if gd['offdir'] == '+':
|
||||||
|
value -= offset
|
||||||
|
elif gd['offdir'] == '-':
|
||||||
|
value += offset
|
||||||
else:
|
else:
|
||||||
value = datetime.datetime(int(gd['year']), int(gd['mon']), int(gd['day']),
|
raise ValueError('Offset format not recognized \'{str}\''.format(str=gd['offset']))
|
||||||
hour=int(gd['hour']), minute=int(gd['min']), second=int(gd['sec']))
|
value = value.replace(tzinfo=pytz.UTC)
|
||||||
if gd['offset'] is not None:
|
|
||||||
offset = datetime.timedelta(hours=int(gd['offhours']))
|
|
||||||
if gd['offmins'] is not None:
|
|
||||||
offset += datetime.timedelta(minutes=int(gd['offmins']))
|
|
||||||
if gd['offdir'] == '+':
|
|
||||||
value -= offset
|
|
||||||
elif gd['offdir'] == '-':
|
|
||||||
value += offset
|
|
||||||
else:
|
|
||||||
raise ValueError('Offset format not recognized \'{str}\''.format(str=gd['offset']))
|
|
||||||
value = value.replace(tzinfo=pytz.UTC)
|
|
||||||
elif not ignore_unsupported_input:
|
|
||||||
raise ValueError('Format not recognized \'{str}\''.format(str=haystack))
|
|
||||||
elif not ignore_unsupported_input:
|
|
||||||
raise ValueError('Expected string type,'
|
|
||||||
' not {}'.format(value.__class__.__name__))
|
|
||||||
return value
|
return value
|
||||||
|
|||||||
@@ -27,7 +27,44 @@ class Iso8601SerializerTestCase(TestCase):
|
|||||||
obj = datetime.datetime.combine(date, time)
|
obj = datetime.datetime.combine(date, time)
|
||||||
yield (obj, text)
|
yield (obj, text)
|
||||||
|
|
||||||
def test_serialize(self):
|
def test_init_without_arg(self):
|
||||||
|
with self.assertRaises(TypeError) as cm:
|
||||||
|
_ = Iso8601Serializer()
|
||||||
|
self.assertEqual(str(cm.exception), 'Iso8601Serializer.__init__()'
|
||||||
|
' missing 1 required positional argument: \'value\'')
|
||||||
|
|
||||||
|
def test_init_with_date(self):
|
||||||
|
date_obj = datetime.date(1976, 2, 1)
|
||||||
|
serializer = Iso8601Serializer(date_obj)
|
||||||
|
self.assertEqual(str(serializer), 'ISO8601:1976-02-01')
|
||||||
|
|
||||||
|
def test_init_with_datetime(self):
|
||||||
|
datetime_obj = datetime.datetime(1976, 2, 1, 12, 34, 56)
|
||||||
|
serializer = Iso8601Serializer(datetime_obj)
|
||||||
|
self.assertEqual(str(serializer), 'ISO8601:1976-02-01T12:34:56')
|
||||||
|
|
||||||
|
def test_init_with_time(self):
|
||||||
|
time_obj = datetime.time(12, 34, 56)
|
||||||
|
serializer = Iso8601Serializer(time_obj)
|
||||||
|
self.assertEqual(str(serializer), 'ISO8601:12:34:56')
|
||||||
|
|
||||||
|
def test_init_with_valid_text(self):
|
||||||
|
text = 'ISO8601:1976-02-01'
|
||||||
|
serializer = Iso8601Serializer(text)
|
||||||
|
self.assertEqual(str(serializer), text)
|
||||||
|
|
||||||
|
def test_init_with_invalid_format(self):
|
||||||
|
text = 'ISO8601:02.01.1976'
|
||||||
|
with self.assertRaises(ValueError) as cm:
|
||||||
|
_ = Iso8601Serializer(text)
|
||||||
|
self.assertEqual(str(cm.exception), 'Format not recognized \'02.01.1976\'')
|
||||||
|
|
||||||
|
def test_init_with_invalid_string(self):
|
||||||
|
text = '1976-02-01'
|
||||||
|
with self.assertRaisesRegex(ValueError, 'String must begin with \'ISO8601:\''):
|
||||||
|
_ = Iso8601Serializer(text)
|
||||||
|
|
||||||
|
def test_serialize_valid(self):
|
||||||
test_data = []
|
test_data = []
|
||||||
|
|
||||||
# Check date objects
|
# Check date objects
|
||||||
@@ -43,10 +80,12 @@ class Iso8601SerializerTestCase(TestCase):
|
|||||||
serialized = Iso8601Serializer.serialize(obj)
|
serialized = Iso8601Serializer.serialize(obj)
|
||||||
self.assertEqual(serialized, expected)
|
self.assertEqual(serialized, expected)
|
||||||
|
|
||||||
# Check invalid input
|
def test_serialize_invalid(self):
|
||||||
invalid_values = (
|
invalid_values = (
|
||||||
None, True, False,
|
None,
|
||||||
'2019-03-01',
|
True,
|
||||||
|
False,
|
||||||
|
'ISO8601:1976-02-01',
|
||||||
)
|
)
|
||||||
for value in invalid_values:
|
for value in invalid_values:
|
||||||
emsg = ('Expected datetime.datetime, datetime.date or datetime.time,'
|
emsg = ('Expected datetime.datetime, datetime.date or datetime.time,'
|
||||||
@@ -56,7 +95,7 @@ class Iso8601SerializerTestCase(TestCase):
|
|||||||
serialized = Iso8601Serializer.serialize(value, ignore_unsupported_input=True)
|
serialized = Iso8601Serializer.serialize(value, ignore_unsupported_input=True)
|
||||||
self.assertEqual(serialized, value)
|
self.assertEqual(serialized, value)
|
||||||
|
|
||||||
def test_deserialize(self):
|
def test_deserialize_valid(self):
|
||||||
test_data = []
|
test_data = []
|
||||||
|
|
||||||
# Check '<YYYY>-<MM>-<DD>' format
|
# Check '<YYYY>-<MM>-<DD>' format
|
||||||
@@ -71,3 +110,18 @@ class Iso8601SerializerTestCase(TestCase):
|
|||||||
deserialized = Iso8601Serializer.deserialize(text)
|
deserialized = Iso8601Serializer.deserialize(text)
|
||||||
self.assertIsInstance(deserialized, obj.__class__)
|
self.assertIsInstance(deserialized, obj.__class__)
|
||||||
self.assertEqual(deserialized, obj)
|
self.assertEqual(deserialized, obj)
|
||||||
|
|
||||||
|
def test_deserialize_invalid(self):
|
||||||
|
invalid_data = (
|
||||||
|
(None, TypeError, 'Expected string type, not NoneType'),
|
||||||
|
(True, TypeError, 'Expected string type, not bool'),
|
||||||
|
(False, TypeError, 'Expected string type, not bool'),
|
||||||
|
('1976-02-01', ValueError, 'String must begin with \'ISO8601:\''),
|
||||||
|
('ISO8601:02.01.1976', ValueError, 'Format not recognized \'02.01.1976\''),
|
||||||
|
)
|
||||||
|
for value, expected_exception, expected_msg in invalid_data:
|
||||||
|
with self.assertRaisesRegex(expected_exception, expected_msg):
|
||||||
|
Iso8601Serializer.deserialize(value)
|
||||||
|
serialized = Iso8601Serializer.deserialize(value, ignore_unsupported_input=True)
|
||||||
|
self.assertEqual(serialized, value)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user