"""Adapters for converting to and from a type's value according to an
IConvertible protocol.
"""
from datetime import date, time
import decimal
from zope.interface import implementer
from . import iformal, validation
class _Adapter(object):
def __init__(self, original):
self.original = original
[docs]@implementer( iformal.IStringConvertible )
class NullConverter(_Adapter):
[docs] def fromType(self, value):
if value is None:
return None
return value
[docs] def toType(self, value):
if value is None:
return None
return value
[docs]@implementer( iformal.IStringConvertible )
class ForceStringConverter(_Adapter):
cast = None
[docs] def fromType(self, value):
if value is None:
return None
return str(value)
[docs] def toType(self, value):
if not value:
return None
if isinstance(value, bytes):
return value.decode("utf-8", "replace")
return value
[docs]@implementer( iformal.IStringConvertible )
class NumberToStringConverter(_Adapter):
cast = None
[docs] def fromType(self, value):
if value is None:
return None
return str(value)
[docs] def toType(self, value):
if value is not None:
value = value.strip()
if not value:
return None
# "Cast" the value to the correct type. For some strange reason,
# Python's decimal.Decimal type raises an ArithmeticError when it's
# given a dodgy value.
try:
value = self.cast(value)
except (ValueError, ArithmeticError):
raise validation.FieldValidationError("Not a valid number")
return value
[docs]class IntegerToStringConverter(NumberToStringConverter):
cast = int
[docs]class FloatToStringConverter(NumberToStringConverter):
cast = float
[docs]class DecimalToStringConverter(NumberToStringConverter):
cast = decimal.Decimal
[docs]@implementer( iformal.IStringConvertible )
class BooleanToStringConverter(_Adapter):
[docs] def fromType(self, value):
if value is None:
return None
if value:
return 'True'
return 'False'
[docs] def toType(self, value):
if value is not None:
value = value.strip()
if not value:
return None
if value not in ('True', 'False'):
raise validation.FieldValidationError('%r should be either True or False'%value)
return value == 'True'
[docs]@implementer( iformal.IStringConvertible )
class DateToStringConverter(_Adapter):
[docs] def fromType(self, value):
if value is None:
return None
return value.isoformat()
[docs] def toType(self, value):
if value is not None:
value = value.strip()
if not value:
return None
return self.parseDate(value)
[docs] def parseDate(self, value):
try:
y, m, d = [int(p) for p in value.split('-')]
except ValueError:
raise validation.FieldValidationError('Invalid date')
try:
value = date(y, m, d)
except ValueError as e:
raise validation.FieldValidationError('Invalid date: '+str(e))
return value
[docs]@implementer( iformal.IStringConvertible )
class TimeToStringConverter(_Adapter):
[docs] def fromType(self, value):
if value is None:
return None
if isinstance(value, str):
return value
return value.isoformat()
[docs] def toType(self, value):
if value is not None:
value = value.strip()
if not value:
return None
return self.parseTime(value)
[docs] def parseTime(self, value):
if '.' in value:
value, ms = value.split('.')
else:
ms = 0
try:
parts = value.split(':')
if len(parts)<2 or len(parts)>3:
raise ValueError()
if len(parts) == 2:
h, m = parts
s = 0
else:
h, m, s = parts
h, m, s, ms = int(h), int(m), int(s), int(ms)
except:
raise validation.FieldValidationError('Invalid time')
try:
value = time(h, m, s, ms)
except ValueError as e:
raise validation.FieldValidationError('Invalid time: '+str(e))
return value
[docs]@implementer( iformal.IDateTupleConvertible )
class DateToDateTupleConverter(_Adapter):
[docs] def fromType(self, value):
if value is None:
return None, None, None
return value.year, value.month, value.day
[docs] def toType(self, value):
if value is None:
return None
try:
value = date(*value)
except (TypeError, ValueError) as e:
raise validation.FieldValidationError('Invalid date: '+str(e))
return value
[docs]@implementer(iformal.IStringConvertible)
class SequenceToStringConverter(_Adapter):
[docs] def fromType(self, value):
if value is None:
return None
cnv = iformal.IStringConvertible(self.original.instance).fromType
return " ".join(cnv(i) for i in value)
[docs] def toType(self, value):
if not value:
return None
cnv = iformal.IStringConvertible(self.original.instance).toType
return [cnv(i) for i in value.split()]