from datetime import datetime
import sqlalchemy as sa
from .exceptions import UnknownJSONPropertyError
DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%f'
NONE = object()
class Hook:
def __init__(self, parent):
self.parent = parent
self.method = None
def __call__(self, method):
self.method = method
return self.parent
def call(self, instance, val):
if self.method is not None:
val = self.method(instance, val)
return val
[docs]class JSONProperty:
def __init__(self, default=None, prop_name='profile'):
self.name = None
self.default = default
self.prop_name = prop_name
self.expression = Hook(self)
self.after_get = Hook(self)
self.before_set = Hook(self)
def __get__(self, instance, owner):
if instance is None:
exp = self.make_expression(
getattr(owner, self.prop_name)[self.name])
return self.expression.call(owner, exp)
val = self.get_profile(instance).get(self.name, NONE)
if val is NONE:
if callable(self.default):
val = self.default(instance)
else:
val = self.default
return self.after_get.call(instance, val)
def __set__(self, instance, value):
self.get_profile(instance)[self.name] = self.before_set.call(
instance, value)
def __delete__(self, instance):
self.get_profile(instance).pop(self.name, None)
[docs] def get_profile(self, instance):
if instance.__profile__ is None:
props = type(instance).__dict__
instance.__profile__ = {}
for key, value in (
getattr(instance, self.prop_name, None) or {}
).items():
if key not in props:
raise UnknownJSONPropertyError(
'`{}` is found in `{}` of instance {}, '
'but it is not defined'.format(
key, self.prop_name, instance
))
instance.__profile__[key] = props[key].decode(value)
return instance.__profile__
[docs] def save(self, instance, value=NONE):
profile = getattr(instance, self.prop_name, None)
if profile is None:
profile = {}
setattr(instance, self.prop_name, profile)
if value is NONE:
value = instance.__profile__[self.name]
if not isinstance(value, sa.sql.ClauseElement):
value = self.encode(value)
rv = profile[self.name] = value
return rv
[docs] def reload(self, instance):
if instance.__profile__ is None:
return
profile = getattr(instance, self.prop_name, None) or {}
value = profile.get(self.name, NONE)
if value is NONE:
instance.__profile__.pop(self.name, None)
else:
instance.__profile__[self.name] = self.decode(value)
[docs] def make_expression(self, base_exp):
return base_exp
[docs] def decode(self, val):
return val
[docs] def encode(self, val):
return val
def __hash__(self):
return hash(self.name)
[docs]class StringProperty(JSONProperty):
[docs] def make_expression(self, base_exp):
return base_exp.astext
[docs]class DateTimeProperty(JSONProperty):
[docs] def make_expression(self, base_exp):
return base_exp.astext.cast(sa.DateTime)
[docs] def decode(self, val):
if val:
val = datetime.strptime(val, DATETIME_FORMAT)
return val
[docs] def encode(self, val):
if isinstance(val, datetime):
val = val.strftime(DATETIME_FORMAT)
return val
[docs]class IntegerProperty(JSONProperty):
[docs] def make_expression(self, base_exp):
return base_exp.astext.cast(sa.Integer)
[docs] def decode(self, val):
if val is not None:
val = int(val)
return val
[docs] def encode(self, val):
if val is not None:
val = int(val)
return val
[docs]class BooleanProperty(JSONProperty):
[docs] def make_expression(self, base_exp):
return base_exp.astext.cast(sa.Boolean)
[docs] def decode(self, val):
if val is not None:
val = bool(val)
return val
[docs] def encode(self, val):
if val is not None:
val = bool(val)
return val
[docs]class ObjectProperty(JSONProperty):
[docs] def decode(self, val):
if val is not None:
val = dict(val)
return val
[docs] def encode(self, val):
if val is not None:
val = dict(val)
return val
[docs]class ArrayProperty(JSONProperty):
[docs] def decode(self, val):
if val is not None:
val = list(val)
return val
[docs] def encode(self, val):
if val is not None:
val = list(val)
return val
__all__ = ['JSONProperty', 'StringProperty', 'DateTimeProperty',
'IntegerProperty', 'BooleanProperty', 'ObjectProperty',
'ArrayProperty']