Source code for lesana.types
"""
Type checkers for lesana fields.
Warning: this part of the code is still in flux and it may change
significantly in a future release.
"""
import datetime
import decimal
import logging
import dateutil.parser
[docs]class LesanaType:
"""
Base class for lesana field types.
"""
def __init__(self, field, types):
self.field = field
[docs] def load(self, data):
raise NotImplementedError
[docs] def empty(self):
raise NotImplementedError
[docs]class LesanaString(LesanaType):
"""
A string of unicode text
"""
name = 'string'
[docs] def load(self, data):
if not data:
return data
return str(data)
[docs] def empty(self):
return ""
[docs]class LesanaText(LesanaString):
"""
A longer block of unicode text
"""
name = 'text'
[docs]class LesanaInt(LesanaType):
"""
An integer number
"""
name = "integer"
[docs] def load(self, data):
if not data:
return data
try:
return int(data)
except ValueError:
raise LesanaValueError(
"Invalid value for integer field: {}".format(data)
)
[docs] def empty(self):
return 0
[docs]class LesanaFloat(LesanaType):
"""
A floating point number
"""
name = "float"
[docs] def load(self, data):
if not data:
return data
try:
return float(data)
except ValueError:
raise LesanaValueError(
"Invalid value for float field: {}".format(data)
)
[docs] def empty(self):
return 0.0
[docs]class LesanaDecimal(LesanaType):
"""
A floating point number
"""
name = "decimal"
[docs] def load(self, data):
if not data:
return data
try:
return decimal.Decimal(data)
except decimal.InvalidOperation:
raise LesanaValueError(
"Invalid value for decimal field: {}".format(data)
)
[docs] def empty(self):
return decimal.Decimal(0)
[docs]class LesanaTimestamp(LesanaType):
"""
A unix timestamp, assumed to be UTC
"""
name = "timestamp"
[docs] def load(self, data):
if not data:
return data
if isinstance(data, datetime.datetime):
return data
try:
return datetime.datetime.fromtimestamp(
int(data),
datetime.timezone.utc,
)
except (TypeError, ValueError):
raise LesanaValueError(
"Invalid value for timestamp field: {}".format(data)
)
[docs] def empty(self):
return None
[docs]class LesanaDatetime(LesanaType):
"""
A datetime
"""
name = "datetime"
[docs] def load(self, data):
if not data:
return data
if isinstance(data, datetime.datetime):
return data
if isinstance(data, datetime.date):
return datetime.datetime(data.year, data.month, data.day)
try:
return dateutil.parser.parse(data)
except dateutil.parser.ParserError:
raise LesanaValueError(
"Invalid value for datetime field: {}".format(data)
)
[docs] def empty(self):
return None
[docs]class LesanaDate(LesanaType):
"""
A date
"""
name = "date"
[docs] def load(self, data):
if not data:
return data
if isinstance(data, datetime.date):
return data
try:
return dateutil.parser.parse(data)
except dateutil.parser.ParserError:
raise LesanaValueError(
"Invalid value for date field: {}".format(data)
)
[docs] def empty(self):
return None
[docs]class LesanaBoolean(LesanaType):
"""
A boolean value
"""
name = 'boolean'
[docs] def load(self, data):
if not data:
return data
if isinstance(data, bool):
return data
else:
raise LesanaValueError(
"Invalid value for boolean field: {}".format(data)
)
[docs] def empty(self):
return None
[docs]class LesanaFile(LesanaString):
"""
A path to a local file.
Relative paths are assumed to be relative to the base lesana
directory (i.e. where .lesana lives)
"""
name = 'file'
[docs]class LesanaURL(LesanaString):
"""
An URL
"""
name = 'url'
[docs]class LesanaYAML(LesanaType):
"""
Free YAML contents (no structure is enforced)
"""
name = 'yaml'
[docs] def load(self, data):
return data
[docs] def empty(self):
return None
[docs]class LesanaList(LesanaType):
"""
A list of other values
"""
name = 'list'
def __init__(self, field, types):
super().__init__(field, types)
try:
self.sub_type = types[field['list']](field, types)
except KeyError:
logging.warning(
"Unknown field type %s in field %s",
field['type'],
field['name'],
)
self.sub_type = types['yaml'](field, types)
[docs] def load(self, data):
if data is None:
# empty for this type means an empty list
return []
try:
return [self.sub_type.load(x) for x in data]
except TypeError:
raise LesanaValueError(
"Invalid value for list field: {}".format(data)
)
[docs] def empty(self):
return []
[docs]class LesanaValueError(ValueError):
"""
Raised in case of validation errors.
"""