Source code for gavo.formal.converters

"""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()]