Add Time type.
This commit is contained in:
@@ -4,6 +4,8 @@ Changes
|
|||||||
Next release
|
Next release
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
- Add ``Time`` type.
|
||||||
|
|
||||||
- Add Dutch translation.
|
- Add Dutch translation.
|
||||||
|
|
||||||
- Fix documentation: 0.9.2 requires ``deserialize`` of types to explicitly
|
- Fix documentation: 0.9.2 requires ``deserialize`` of types to explicitly
|
||||||
|
@@ -1204,6 +1204,86 @@ class Date(SchemaType):
|
|||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
class Time(SchemaType):
|
||||||
|
""" A type representing a Python ``datetime.time`` object.
|
||||||
|
|
||||||
|
This type serializes python ``datetime.time`` objects to a
|
||||||
|
`ISO8601 <http://en.wikipedia.org/wiki/ISO_8601>`_ string format.
|
||||||
|
The format includes the date only.
|
||||||
|
|
||||||
|
The constructor accepts no arguments.
|
||||||
|
|
||||||
|
You can adjust the error message reported by this class by
|
||||||
|
changing its ``err_template`` attribute in a subclass on an
|
||||||
|
instance of this class. By default, the ``err_template``
|
||||||
|
attribute is the string ``Invalid date``. This string is used as
|
||||||
|
the interpolation subject of a dictionary composed of ``val`` and
|
||||||
|
``err``. ``val`` and ``err`` are the unvalidatable value and the
|
||||||
|
exception caused trying to convert the value, respectively. These
|
||||||
|
may be used in an overridden err_template as ``${val}`` and
|
||||||
|
``${err}`` respectively as necessary, e.g. ``_('${val} cannot be
|
||||||
|
parsed as an iso8601 date: ${err}')``.
|
||||||
|
|
||||||
|
For convenience, this type is also willing to coerce
|
||||||
|
``datetime.datetime`` objects to a time-only ISO string
|
||||||
|
representation during serialization. It does so by stripping off
|
||||||
|
any date information, converting the ``datetime.datetime`` into a
|
||||||
|
time before serializing.
|
||||||
|
|
||||||
|
Likewise, for convenience, this type is also willing to coerce ISO
|
||||||
|
representations that contain time info into a ``datetime.time``
|
||||||
|
object during deserialization. It does so by throwing away any
|
||||||
|
date information related to the serialized value during
|
||||||
|
deserialization.
|
||||||
|
|
||||||
|
If the :attr:`colander.null` value is passed to the serialize
|
||||||
|
method of this class, the :attr:`colander.null` value will be
|
||||||
|
returned.
|
||||||
|
|
||||||
|
The subnodes of the :class:`colander.SchemaNode` that wraps
|
||||||
|
this type are ignored.
|
||||||
|
"""
|
||||||
|
|
||||||
|
err_template = _('Invalid time')
|
||||||
|
|
||||||
|
def serialize(self, node, appstruct):
|
||||||
|
if appstruct is null:
|
||||||
|
return null
|
||||||
|
|
||||||
|
if isinstance(appstruct, datetime.datetime):
|
||||||
|
appstruct = appstruct.time()
|
||||||
|
|
||||||
|
if not isinstance(appstruct, datetime.time):
|
||||||
|
raise Invalid(node,
|
||||||
|
_('"${val}" is not a time object',
|
||||||
|
mapping={'val':appstruct})
|
||||||
|
)
|
||||||
|
|
||||||
|
return appstruct.isoformat().split('.')[0]
|
||||||
|
|
||||||
|
def deserialize(self, node, cstruct):
|
||||||
|
if not cstruct:
|
||||||
|
return null
|
||||||
|
try:
|
||||||
|
result = iso8601.parse_date(cstruct)
|
||||||
|
result = result.time()
|
||||||
|
except (iso8601.ParseError, TypeError):
|
||||||
|
try:
|
||||||
|
result = iso8601.parse_date('1970-01-01 %s' % cstruct)
|
||||||
|
result = result.time()
|
||||||
|
except (iso8601.ParseError, TypeError):
|
||||||
|
try:
|
||||||
|
parts = map(int, cstruct.split(':'))
|
||||||
|
if len(parts) > 3:
|
||||||
|
raise ValueError('Too many digits')
|
||||||
|
result = datetime.date(*parts[:3])
|
||||||
|
except Exception, e:
|
||||||
|
raise Invalid(node,
|
||||||
|
_(self.err_template,
|
||||||
|
mapping={'val':cstruct, 'err':e})
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
class SchemaNode(object):
|
class SchemaNode(object):
|
||||||
"""
|
"""
|
||||||
Fundamental building block of schemas.
|
Fundamental building block of schemas.
|
||||||
|
@@ -1424,6 +1424,85 @@ class TestDate(unittest.TestCase):
|
|||||||
result = typ.deserialize(node, iso)
|
result = typ.deserialize(node, iso)
|
||||||
self.assertEqual(result.isoformat(), dt.date().isoformat())
|
self.assertEqual(result.isoformat(), dt.date().isoformat())
|
||||||
|
|
||||||
|
class TestTime(unittest.TestCase):
|
||||||
|
def _makeOne(self, *arg, **kw):
|
||||||
|
from colander import Time
|
||||||
|
return Time(*arg, **kw)
|
||||||
|
|
||||||
|
def _dt(self):
|
||||||
|
import datetime
|
||||||
|
return datetime.datetime(2010, 4, 26, 10, 48)
|
||||||
|
|
||||||
|
def _now(self):
|
||||||
|
import datetime
|
||||||
|
return datetime.datetime.now().time()
|
||||||
|
|
||||||
|
def test_serialize_null(self):
|
||||||
|
import colander
|
||||||
|
val = colander.null
|
||||||
|
node = DummySchemaNode(None)
|
||||||
|
typ = self._makeOne()
|
||||||
|
result = typ.serialize(node, val)
|
||||||
|
self.assertEqual(result, colander.null)
|
||||||
|
|
||||||
|
def test_serialize_with_garbage(self):
|
||||||
|
typ = self._makeOne()
|
||||||
|
node = DummySchemaNode(None)
|
||||||
|
e = invalid_exc(typ.serialize, node, 'garbage')
|
||||||
|
self.assertEqual(e.msg.interpolate(), '"garbage" is not a time object')
|
||||||
|
|
||||||
|
def test_serialize_with_time(self):
|
||||||
|
typ = self._makeOne()
|
||||||
|
time = self._now()
|
||||||
|
node = DummySchemaNode(None)
|
||||||
|
result = typ.serialize(node, time)
|
||||||
|
expected = time.isoformat().split('.')[0]
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
def test_serialize_with_datetime(self):
|
||||||
|
typ = self._makeOne()
|
||||||
|
dt = self._dt()
|
||||||
|
node = DummySchemaNode(None)
|
||||||
|
result = typ.serialize(node, dt)
|
||||||
|
expected = dt.time().isoformat().split('.')[0]
|
||||||
|
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('Invalid' in e.msg)
|
||||||
|
|
||||||
|
def test_deserialize_null(self):
|
||||||
|
import colander
|
||||||
|
node = DummySchemaNode(None)
|
||||||
|
typ = self._makeOne()
|
||||||
|
result = typ.deserialize(node, colander.null)
|
||||||
|
self.assertEqual(result, colander.null)
|
||||||
|
|
||||||
|
def test_deserialize_empty(self):
|
||||||
|
import colander
|
||||||
|
node = DummySchemaNode(None)
|
||||||
|
typ = self._makeOne()
|
||||||
|
result = typ.deserialize(node, '')
|
||||||
|
self.assertEqual(result, colander.null)
|
||||||
|
|
||||||
|
def test_deserialize_success_time(self):
|
||||||
|
import datetime
|
||||||
|
typ = self._makeOne()
|
||||||
|
node = DummySchemaNode(None)
|
||||||
|
result = typ.deserialize(node, '10:12:13')
|
||||||
|
self.assertEqual(result, datetime.time(10, 12, 13))
|
||||||
|
|
||||||
|
def test_deserialize_success_datetime(self):
|
||||||
|
dt = self._dt()
|
||||||
|
typ = self._makeOne()
|
||||||
|
iso = dt.isoformat()
|
||||||
|
node = DummySchemaNode(None)
|
||||||
|
result = typ.deserialize(node, iso)
|
||||||
|
self.assertEqual(result.isoformat(),
|
||||||
|
dt.time().isoformat().split('.')[0])
|
||||||
|
|
||||||
class TestSchemaNode(unittest.TestCase):
|
class TestSchemaNode(unittest.TestCase):
|
||||||
def _makeOne(self, *arg, **kw):
|
def _makeOne(self, *arg, **kw):
|
||||||
from colander import SchemaNode
|
from colander import SchemaNode
|
||||||
|
@@ -98,6 +98,8 @@ Types
|
|||||||
|
|
||||||
.. autoclass:: Date
|
.. autoclass:: Date
|
||||||
|
|
||||||
|
.. autoclass:: Time
|
||||||
|
|
||||||
Schema-Related
|
Schema-Related
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user