diff --git a/dav_events/converters.py b/dav_events/converters.py index 86f3b9f..26b015a 100644 --- a/dav_events/converters.py +++ b/dav_events/converters.py @@ -2,7 +2,6 @@ import datetime import logging import re import pytz -from six import string_types logger = logging.getLogger(__name__) @@ -24,15 +23,17 @@ class Iso8601Serializer: r')?' r'(?P(?P[\-+])(?P([01][0-9])|(2[0123]))(:(?P[0-5][0-9]))?)?$') - def __init__(self, instance=None, text=None): - if instance is not None: - self.instance = instance - elif text is not None: - self.instance = Iso8601Serializer.deserialize(text) + def __init__(self, value): + if isinstance(value, datetime.datetime) \ + or isinstance(value, datetime.date) \ + or isinstance(value, datetime.time): + dt_obj = value + else: + dt_obj = Iso8601Serializer.deserialize(value) + self._serialized = Iso8601Serializer.serialize(dt_obj) - @property - def str(self): - return Iso8601Serializer.serialize(self.instance) + def __str__(self): + return self._serialized @classmethod def serialize(cls, value, ignore_unsupported_input=False): @@ -55,32 +56,43 @@ class Iso8601Serializer: @classmethod def deserialize(cls, value, ignore_unsupported_input=False): prefix = '{marker}{sep}'.format(marker=cls.marker, sep=cls.separator) - if isinstance(value, string_types) and value.startswith(prefix): - haystack = value[len(prefix):] - m = cls._re.match(haystack) - if m is not None: - gd = m.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'])) + + if not isinstance(value, str): + if ignore_unsupported_input: + return value + raise TypeError('Expected string type, not {}'.format(value.__class__.__name__)) + + if not value.startswith(prefix): + if ignore_unsupported_input: + 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: - 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: - 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__)) + raise ValueError('Offset format not recognized \'{str}\''.format(str=gd['offset'])) + value = value.replace(tzinfo=pytz.UTC) + return value diff --git a/dav_events/tests/test_converters.py b/dav_events/tests/test_converters.py index dd4588d..23c4da4 100644 --- a/dav_events/tests/test_converters.py +++ b/dav_events/tests/test_converters.py @@ -27,7 +27,44 @@ class Iso8601SerializerTestCase(TestCase): obj = datetime.datetime.combine(date, time) 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 = [] # Check date objects @@ -43,10 +80,12 @@ class Iso8601SerializerTestCase(TestCase): serialized = Iso8601Serializer.serialize(obj) self.assertEqual(serialized, expected) - # Check invalid input + def test_serialize_invalid(self): invalid_values = ( - None, True, False, - '2019-03-01', + None, + True, + False, + 'ISO8601:1976-02-01', ) for value in invalid_values: emsg = ('Expected datetime.datetime, datetime.date or datetime.time,' @@ -56,7 +95,7 @@ class Iso8601SerializerTestCase(TestCase): serialized = Iso8601Serializer.serialize(value, ignore_unsupported_input=True) self.assertEqual(serialized, value) - def test_deserialize(self): + def test_deserialize_valid(self): test_data = [] # Check '--
' format @@ -71,3 +110,18 @@ class Iso8601SerializerTestCase(TestCase): deserialized = Iso8601Serializer.deserialize(text) self.assertIsInstance(deserialized, obj.__class__) 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) +