# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 John Paulett (john -at- paulett.org)
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution.
import operator
import types
#import tic.utils.jsonpickle.util as util
#import tic.utils.jsonpickle.tags as tags
from tic.utils.jsonpickle.handlers import registry
from tic.utils.jsonpickle.util import *
[docs]class Pickler(object):
"""Converts a Python object to a JSON representation.
Setting unpicklable to False removes the ability to regenerate
the objects into object types beyond what the standard
simplejson library supports.
Setting max_depth to a negative number means there is no
limit to the depth jsonpickle should recurse into an
object. Setting it to zero or higher places a hard limit
on how deep jsonpickle recurses into objects, dictionaries, etc.
>>> p = Pickler()
>>> p.flatten('hello world')
'hello world'
"""
def __init__(self, unpicklable=True, max_depth=None):
self.unpicklable = unpicklable
## The current recursion depth
self._depth = -1
## The maximal recursion depth
self._max_depth = max_depth
## Maps id(obj) to reference names
self._objs = {}
## The namestack grows whenever we recurse into a child object
self._namestack = []
def _reset(self):
self._objs = {}
self._namestack = []
def _push(self):
"""Steps down one level in the namespace.
"""
self._depth += 1
def _pop(self, value):
"""Step up one level in the namespace and return the value.
If we're at the root, reset the pickler's state.
"""
self._depth -= 1
if self._depth == -1:
self._reset()
return value
def _mkref(self, obj):
# Do not use references if not unpicklable.
if self.unpicklable is False:
return True
objid = id(obj)
if objid not in self._objs:
self._objs[objid] = '/' + '/'.join(self._namestack)
return True
return False
def _getref(self, obj):
return {tags.REF: self._objs.get(id(obj))}
[docs] def flatten(self, obj):
"""Takes an object and returns a JSON-safe representation of it.
Simply returns any of the basic builtin datatypes
>>> p = Pickler()
>>> p.flatten('hello world')
'hello world'
>>> p.flatten(u'hello world')
u'hello world'
>>> p.flatten(49)
49
>>> p.flatten(350.0)
350.0
>>> p.flatten(True)
True
>>> p.flatten(False)
False
>>> r = p.flatten(None)
>>> r is None
True
>>> p.flatten(False)
False
>>> p.flatten([1, 2, 3, 4])
[1, 2, 3, 4]
>>> p.flatten((1,2,))[tags.TUPLE]
[1, 2]
>>> p.flatten({'key': 'value'})
{'key': 'value'}
"""
self._push()
if self._depth == self._max_depth:
return self._pop(repr(obj))
if is_primitive(obj):
return self._pop(obj)
if is_list(obj):
return self._pop([ self.flatten(v) for v in obj ])
# We handle tuples and sets by encoding them in a "(tuple|set)dict"
if is_tuple(obj):
if self.unpicklable is True:
return self._pop({tags.TUPLE: [ self.flatten(v) for v in obj ]})
else:
return self._pop([ self.flatten(v) for v in obj ])
if is_set(obj):
if self.unpicklable is True:
return self._pop({tags.SET: [ self.flatten(v) for v in obj ]})
else:
return self._pop([ self.flatten(v) for v in obj ])
if is_dictionary(obj):
return self._pop(self._flatten_dict_obj(obj, obj.__class__()))
if is_type(obj):
return self._pop(_mktyperef(obj))
if is_object(obj):
if self._mkref(obj):
# We've never seen this object so return its
# json representation.
return self._pop(self._flatten_obj_instance(obj))
else:
# We've seen this object before so place an object
# reference tag in the data. This avoids infinite recursion
# when processing cyclical objects.
return self._pop(self._getref(obj))
return self._pop(data)
# else, what else? (methods, functions, old style classes...)
def _flatten_obj_instance(self, obj):
"""Recursively flatten an instance and return a json-friendly dict
"""
data = {}
has_class = hasattr(obj, '__class__')
has_dict = hasattr(obj, '__dict__')
has_slots = not has_dict and hasattr(obj, '__slots__')
has_getstate = has_dict and hasattr(obj, '__getstate__')
has_getstate_support = has_getstate and hasattr(obj, '__setstate__')
HandlerClass = registry.get(type(obj))
if (has_class and not is_repr(obj) and
not is_module(obj)):
module, name = _getclassdetail(obj)
if self.unpicklable is True:
data[tags.OBJECT] = '%s.%s' % (module, name)
# Check for a custom handler
if HandlerClass:
handler = HandlerClass(self)
return handler.flatten(obj, data)
if is_module(obj):
if self.unpicklable is True:
data[tags.REPR] = '%s/%s' % (obj.__name__,
obj.__name__)
else:
data = unicode(obj)
return data
if is_repr(obj):
if self.unpicklable is True:
"""
ok .. seems that dates are unpicklable, however they one of
the most important data types so ...
we need to deal with that
"""
from datetime import datetime
if isinstance(obj, datetime):
#wohoo we got a date :)
from time import mktime
data = "new Date(" \
+ unicode(int((mktime(obj.timetuple())+1e-6*obj.microsecond)*1000)) \
+ ")"
else:
data[tags.REPR] = '%s/%s' % (obj.__class__.__module__,
repr(obj))
else:
data = unicode(obj)
return data
if is_dictionary_subclass(obj):
return self._flatten_dict_obj(obj, data)
if is_noncomplex(obj):
return [self.flatten(v) for v in obj]
if has_dict:
# Support objects that subclasses list and set
if is_collection_subclass(obj):
return self._flatten_collection_obj(obj, data)
# Support objects with __getstate__(); this ensures that
# both __setstate__() and __getstate__() are implemented
if has_getstate_support:
state = self.flatten(obj.__getstate__())
if self.unpicklable:
data[tags.STATE] = state
else:
data = state
return data
# hack for zope persistent objects; this unghostifies the object
getattr(obj, '_', None)
return self._flatten_dict_obj(obj.__dict__, data)
if has_slots:
return self._flatten_newstyle_with_slots(obj, data)
def _flatten_dict_obj(self, obj, data):
"""Recursively call flatten() and return json-friendly dict
"""
for k, v in sorted(obj.iteritems(), key=operator.itemgetter(0)):
self._flatten_key_value_pair(k, v, data)
return data
def _flatten_newstyle_with_slots(self, obj, data):
"""Return a json-friendly dict for new-style objects with __slots__.
"""
for k in obj.__slots__:
self._flatten_key_value_pair(k, getattr(obj, k), data)
return data
def _flatten_key_value_pair(self, k, v, data):
"""Flatten a key/value pair into the passed-in dictionary."""
if not is_picklable(k, v):
return data
if type(k) not in types.StringTypes:
try:
k = repr(k)
except:
k = unicode(k)
self._namestack.append(k)
data[k] = self.flatten(v)
self._namestack.pop()
return data
def _flatten_collection_obj(self, obj, data):
"""Return a json-friendly dict for a collection subclass."""
self._flatten_dict_obj(obj.__dict__, data)
value = [ self.flatten(v) for v in obj ]
if self.unpicklable:
data[tags.SEQ] = value
else:
return value
return data
def _mktyperef(obj):
"""Return a typeref dictionary. Used for references.
>>> from tic.utils.jsonpickle import tags
>>> _mktyperef(AssertionError)[tags.TYPE].rsplit('.', 1)[0]
'exceptions'
>>> _mktyperef(AssertionError)[tags.TYPE].rsplit('.', 1)[-1]
'AssertionError'
"""
return {tags.TYPE: '%s.%s' % (obj.__module__, obj.__name__)}
def _getclassdetail(obj):
"""Helper class to return the class of an object.
"""
cls = obj.__class__
module = getattr(cls, '__module__')
name = getattr(cls, '__name__')
return module, name