from types import MethodType
from gluon.contrib import simplejson as sj
class Smart_Storage(dict): """
A Smart_Storage object is like a dictionary but:
- set/get value:
obj['foo'] == obj.foo
- set/get attribute:
# use `._.` or `._['attr_name']` to set attribute
obj._.attr = 'I`m attribute but not data!!!'
obj._['another_attr'] = 'I`m another one'
# set/update many attributes
obj._.update(dict(...))
# use dot only to get attribute
print obj.attr
# remember that
obj['any_attr'] == None
# but don't forget
obj.__dict__['any_attr'] == obj.any_attr
- bind/call method:
# use `._['@...']` to bind method
obj._['@my_meth'] = lambda self: 'I`m method of %s' % self
# nothing new
obj.my_meth()
- finally obj.__json__():
# works recursively
# for item that is Smart_Storage instance - item.__json__() will be applied
obj.__json__(only = None, exclude = None, values_only = False)
- and obj.__formatters__():
# use __formatters__ to control serialization (note double quote)
obj.__formatters__['key_name'] = lambda self, k, v: self.check_access(k) and v or '"-access denied-"'
# or just
obj.__formatters__['key_name'] = '"key = %(k)s, value = %(v)s"'
# serialize web2py helpers
obj.html = DIV()
obj.__formatters__['html'] = lambda self, k, v: '"%s"' % v.xml()
# serialize js-function/object for embedding as script
obj.surname='Smith'
obj.who_are_you = "function(){ console.log( 'I`m', this.name, this.surname );}" obj.__formatters__['who_are_you'] = '%(v)s' # not '"%(v)s"'
obj._['@xml'] = lambda self: \
SCRIPT( '\n'.join([
'var obj = %s;',
'obj.who_are_you();',
]) % self.__json__()
).xml()
...
# in the view just
{{ =obj }}
# that will be
<script><!--
var obj = {"who_are_you":function(){ console.log( 'I`m ', this.name, this.surname );},"surname":"Smith","name":"John"}; obj.who_are_you();
//--></script>
"""
__getattr__ = dict.get
__getitem__ = dict.get
__delattr__ = dict.__delitem__
__repr__ = lambda self: '<Smart_Storage %s>' % dict.__repr__(self)
class Add_Attr:
def __init__(self, mystor_obj, args=None):
self.__dict__['mystor_obj'] = mystor_obj
if args:
self.update(args)
def __setattr__(self, k, v):
if k in self.mystor_obj:
raise RuntimeError('"%s" is in keys' % k)
self.mystor_obj.__dict__[k] = v
def __setitem__(self, k ,v):
if k[0] in '0123456789&#.%$':
raise RuntimeError('"%s" is invalid name' % k)
if k[0]=='@':
v = MethodType(v, self.mystor_obj)
k=k[1:]
self.__setattr__(k ,v)
def update(self, d):
[self.__setattr__(k, d[k]) for k in d]
def __setattr__(self, k, v):
if k in self.__dict__:
raise RuntimeError('"%s" is in __dict__' % k)
dict.__setitem__(self, k, v)
def __init__(self, *a, **kwargs):
_ = kwargs.pop('_', None)
if 0:
self._ = None
self.__formatters__ = {}
dict.__init__(self, *a, **kwargs)
self.__dict__['_'] = Smart_Storage.Add_Attr(self, _)
self.__dict__['__formatters__'] = {}
def __json_pair__(self, k, v):
formatter = self.__formatters__.get(k, None)
if formatter is not None:
if callable( formatter):
ret = formatter(self, k, v)
elif isinstance(formatter, basestring):
ret = formatter % dict(k=k, v=v)
else:
raise RuntimeError('Unsupported formatter for %s : "%s"' % (k, formatter))
else:
if isinstance(v, Smart_Storage):
ret = v.__json__()
elif isinstance(v, (list, tuple)):
json_lst= [isinstance(it, Smart_Storage) and it.__json__() or sj.dumps(it) \
for it in v]
ret= '[%s]'% ','.join(json_lst)
else:
ret = sj.dumps(v)
return (k, ret)
def __json__(self, only = None, exclude = None, values_only = False):
"""
- only / exclude - filter out keys, 'exclude' applies after 'only', so:
(only = ['a', 'b', 'c'], exclude = ['b']) == (only = ['a', 'c'])
- values_only = True - serialize as json list of values
"""
f_dict = lambda pair: '"%s":%s' % pair
f_lst = lambda pair: pair[1]
ret_str, f_str= values_only and ('[%s]' , f_lst) or ('{%s}',f_dict)
buf = []
if only or exclude:
keys = only or self
if exclude:
keys = set(keys)-set(exclude)
for k in keys:
pair = self.__json_pair__(k, self[k])
if pair:
buf.append(f_str(pair))
else:
for k, v in self.iteritems():
pair = self.__json_pair__(k,v)
if pair:
buf.append(f_str(pair))
return ret_str % ','.join(buf)