- Add `colander.DateTime and colander.Date` data types.

- Depend on the ``iso8601`` package for date support.
This commit is contained in:
Chris McDonough
2010-03-30 04:38:22 +00:00
parent 84c918a6d5
commit 27b12a1060
5 changed files with 250 additions and 1 deletions

View File

@@ -1,6 +1,13 @@
Changes
=======
Next release
------------
- Add ``colander.DateTime`` and ``colander.Date`` data types.
- Depend on the ``iso8601`` package for date support.
0.3 (2010-03-29)
----------------

View File

@@ -1,4 +1,6 @@
import datetime
import itertools
import iso8601
import pprint
class _missing(object):
@@ -580,6 +582,102 @@ class GlobalObject(object):
except AttributeError:
raise Invalid(node, '%r has no __name__' % value)
class DateTime(object):
""" A type representing a Python ``datetime.datetime`` object.
This type serializes python ``datetime.datetime`` objects to a
`ISO8601 <http://en.wikipedia.org/wiki/ISO_8601>`_ string format.
The format includes the date, the time, and the timezone of the
datetime.
The constructor accepts a single argument named ``default_tzinfo``
which should be a Python ``tzinfo`` object; by default it is None,
meaning that the default tzinfo will be equivalent to UTC (Zulu
time). The ``default_tzinfo`` tzinfo object is used to convert
'naive' datetimes to a timezone-aware representation during
serialization.
For convenience, this type is also willing to coerce
``datetime.date`` objects to a DateTime ISO string representation
during serialization. It does so by using midnight of the day as
the time, and uses the ``default_tzinfo`` to give the
serialization a timezone.
This type can only convert ISO8601 values that include a date, a
time, and a timezone (or ``Z``) in their representations. It will
fail to parse date-only ISO8601 representations.
The subnodes of the :class:`colander.SchemaNode` that wraps
this type are ignored.
"""
def __init__(self, default_tzinfo=None):
if default_tzinfo is None:
default_tzinfo = iso8601.iso8601.Utc()
self.default_tzinfo = default_tzinfo
def serialize(self, node, value):
if type(value) is datetime.date: # cant use isinstance; dt subs date
value = datetime.datetime.combine(value, datetime.time())
if not isinstance(value, datetime.datetime):
raise Invalid(node, '%r is not a datetime object' % value)
if value.tzinfo is None:
value = value.replace(tzinfo=self.default_tzinfo)
return value.isoformat()
def deserialize(self, node, value):
try:
result = iso8601.parse_date(value)
except (iso8601.ParseError, TypeError), e:
raise Invalid(node,
'%s cannot be parsed as an iso8601 datetime: %s' %
(value, e))
return result
class Date(object):
""" A type representing a Python ``datetime.date`` object.
This type serializes python ``datetime.date`` objects to a
`ISO8601 <http://en.wikipedia.org/wiki/ISO_8601>`_ string format.
The format includes the date only.
The constructor accepts no arguments.
For convenience, this type is also willing to coerce
``datetime.datetime`` objects to a date-only ISO string
representation during serialization. It does so by stripping off
any time information, converting the ``datetime.datetime`` into a
date before serializing.
Likewise, for convenience, this type is also willing to coerce ISO
representations that contain time info into a ``datetime.date``
object during deserialization. It does so by throwing away any
time information related to the serialized value during
deserialization.
The subnodes of the :class:`colander.SchemaNode` that wraps
this type are ignored.
"""
def serialize(self, node, value):
if isinstance(value, datetime.datetime):
value = value.date()
if not isinstance(value, datetime.date):
raise Invalid(node, '%r is not a date object' % value)
return value.isoformat()
def deserialize(self, node, value):
try:
result = iso8601.parse_date(value)
result = result.date()
except (iso8601.ParseError, TypeError):
try:
year, month, day = map(int, value.split('-', 3))
result = datetime.date(year, month, day)
except Exception, e:
raise Invalid(node,
'%s cannot be parsed as an iso8601 date: %s' %
(value, e))
return result
class SchemaNode(object):
"""
Fundamental building block of schemas.

View File

@@ -899,6 +899,146 @@ class TestGlobalObject(unittest.TestCase):
e = invalid_exc(typ.serialize, None, None)
self.assertEqual(e.msg, 'None has no __name__')
class TestDateTime(unittest.TestCase):
def _makeOne(self, *arg, **kw):
from colander import DateTime
return DateTime(*arg, **kw)
def test_ctor_default_tzinfo_None(self):
import iso8601
typ = self._makeOne()
self.assertEqual(typ.default_tzinfo.__class__, iso8601.iso8601.Utc)
def test_ctor_default_tzinfo_non_None(self):
import iso8601
tzinfo = iso8601.iso8601.FixedOffset(1, 0, 'myname')
typ = self._makeOne(default_tzinfo=tzinfo)
self.assertEqual(typ.default_tzinfo, tzinfo)
def test_serialize_with_garbage(self):
typ = self._makeOne()
node = DummySchemaNode(None)
e = invalid_exc(typ.serialize, node, 'garbage')
self.assertEqual(e.msg, "'garbage' is not a datetime object")
def test_serialize_with_date(self):
import datetime
typ = self._makeOne()
date = datetime.date.today()
node = DummySchemaNode(None)
result = typ.serialize(node, date)
expected = datetime.datetime.combine(date, datetime.time())
expected = expected.replace(tzinfo=typ.default_tzinfo).isoformat()
self.assertEqual(result, expected)
def test_serialize_with_naive_datetime(self):
import datetime
typ = self._makeOne()
dt = datetime.datetime.now()
node = DummySchemaNode(None)
result = typ.serialize(node, dt)
expected = dt.replace(tzinfo=typ.default_tzinfo).isoformat()
self.assertEqual(result, expected)
def test_serialize_with_tzware_datetime(self):
import datetime
import iso8601
typ = self._makeOne()
dt = datetime.datetime.now()
tzinfo = iso8601.iso8601.FixedOffset(1, 0, 'myname')
dt = dt.replace(tzinfo=tzinfo)
node = DummySchemaNode(None)
result = typ.serialize(node, dt)
expected = dt.isoformat()
self.assertEqual(result, expected)
def test_deserialize_invalid_TypeError(self):
import datetime
# cant parse dates without times
date = datetime.date.today()
typ = self._makeOne()
formatted = date.isoformat()
node = DummySchemaNode(None)
e = invalid_exc(typ.deserialize, node, formatted)
self.failUnless('cannot be parsed' in e.msg)
def test_deserialize_invalid_ParseError(self):
node = DummySchemaNode(None)
typ = self._makeOne()
e = invalid_exc(typ.deserialize, node, 'garbage')
self.failUnless('Unable to parse' in e.msg)
def test_deserialize_success(self):
import datetime
import iso8601
typ = self._makeOne()
dt = datetime.datetime.now()
tzinfo = iso8601.iso8601.FixedOffset(1, 0, 'myname')
dt = dt.replace(tzinfo=tzinfo)
iso = dt.isoformat()
node = DummySchemaNode(None)
result = typ.deserialize(node, iso)
self.assertEqual(result.isoformat(), iso)
class TestDate(unittest.TestCase):
def _makeOne(self, *arg, **kw):
from colander import Date
return Date(*arg, **kw)
def test_serialize_with_garbage(self):
typ = self._makeOne()
node = DummySchemaNode(None)
e = invalid_exc(typ.serialize, node, 'garbage')
self.assertEqual(e.msg, "'garbage' is not a date object")
def test_serialize_with_date(self):
import datetime
typ = self._makeOne()
date = datetime.date.today()
node = DummySchemaNode(None)
result = typ.serialize(node, date)
expected = date.isoformat()
self.assertEqual(result, expected)
def test_serialize_with_datetime(self):
import datetime
typ = self._makeOne()
dt = datetime.datetime.now()
node = DummySchemaNode(None)
result = typ.serialize(node, dt)
expected = dt.date().isoformat()
self.assertEqual(result, expected)
def test_deserialize_invalid_ParseError(self):
node = DummySchemaNode(None)
typ = self._makeOne()
e = invalid_exc(typ.deserialize, node, 'garbage')
self.failUnless('cannot be parsed' in e.msg)
def test_deserialize_invalid_weird(self):
node = DummySchemaNode(None)
typ = self._makeOne()
e = invalid_exc(typ.deserialize, node, '10-10-10-10')
self.failUnless('cannot be parsed' in e.msg)
def test_deserialize_success_date(self):
import datetime
typ = self._makeOne()
date = datetime.date.today()
iso = date.isoformat()
node = DummySchemaNode(None)
result = typ.deserialize(node, iso)
self.assertEqual(result.isoformat(), iso)
def test_deserialize_success_datetime(self):
import datetime
dt = datetime.datetime.now()
typ = self._makeOne()
iso = dt.isoformat()
node = DummySchemaNode(None)
result = typ.deserialize(node, iso)
self.assertEqual(result.isoformat(), dt.date().isoformat())
class TestSchemaNode(unittest.TestCase):
def _makeOne(self, *arg, **kw):
from colander import SchemaNode

View File

@@ -47,6 +47,10 @@ Types
.. autoclass:: GlobalObject
.. autoclass:: DateTime
.. autoclass:: Date
Schema-Related
~~~~~~~~~~~~~~

View File

@@ -21,7 +21,7 @@ here = os.path.abspath(os.path.dirname(__file__))
README = open(os.path.join(here, 'README.txt')).read()
CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
requires = []
requires = ['iso8601']
setup(name='colander',
version='0.3',