- Add `colander.DateTime
and
colander.Date
` data types.
- Depend on the ``iso8601`` package for date support.
This commit is contained in:
@@ -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)
|
||||
----------------
|
||||
|
||||
|
@@ -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.
|
||||
|
@@ -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
|
||||
|
@@ -47,6 +47,10 @@ Types
|
||||
|
||||
.. autoclass:: GlobalObject
|
||||
|
||||
.. autoclass:: DateTime
|
||||
|
||||
.. autoclass:: Date
|
||||
|
||||
Schema-Related
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
|
Reference in New Issue
Block a user