doc: Add git-cola-1.4.0.4 release notes
[git-cola.git] / jsonpickle / unpickler.py
blob25c929cd9aad4c750579beab556bc61e6fceaf9f
1 # -*- coding: utf-8 -*-
3 # Copyright (C) 2008 John Paulett (john -at- 7oars.com)
4 # All rights reserved.
6 # This software is licensed as described in the file COPYING, which
7 # you should have received as part of this distribution.
9 import sys
10 import jsonpickle.util as util
11 import jsonpickle.tags as tags
14 class Unpickler(object):
15 def __init__(self):
16 ## The current recursion depth
17 self._depth = 0
18 ## Maps reference names to object instances
19 self._namedict = {}
20 ## The namestack grows whenever we recurse into a child object
21 self._namestack = []
23 def _reset(self):
24 """Resets the object's internal state.
25 """
26 self._namedict = {}
27 self._namestack = []
29 def _push(self):
30 """Steps down one level in the namespace.
31 """
32 self._depth += 1
34 def _pop(self, value):
35 """Step up one level in the namespace and return the value.
36 If we're at the root, reset the unpickler's state.
37 """
38 self._depth -= 1
39 if self._depth == 0:
40 self._reset()
41 return value
43 def restore(self, obj):
44 """Restores a flattened object to its original python state.
46 Simply returns any of the basic builtin types
48 >>> u = Unpickler()
49 >>> u.restore('hello world')
50 'hello world'
51 >>> u.restore({'key': 'value'})
52 {'key': 'value'}
53 """
54 self._push()
56 if has_tag(obj, tags.REF):
57 return self._pop(self._namedict.get(obj[tags.REF]))
59 if has_tag(obj, tags.TYPE):
60 typeref = loadclass(obj[tags.TYPE])
61 if not typeref:
62 return self._pop(obj)
63 return self._pop(typeref)
65 if has_tag(obj, tags.REPR):
66 return self._pop(loadrepr(obj[tags.REPR]))
68 if has_tag(obj, tags.OBJECT):
70 cls = loadclass(obj[tags.OBJECT])
71 if not cls:
72 return self._pop(obj)
73 try:
74 instance = object.__new__(cls)
75 except TypeError:
76 # old-style classes
77 try:
78 instance = cls()
79 except TypeError:
80 # fail gracefully if the constructor requires arguments
81 self._mkref(obj)
82 return self._pop(obj)
84 # keep a obj->name mapping for use in the _isobjref() case
85 self._mkref(instance)
87 for k, v in obj.iteritems():
88 # ignore the reserved attribute
89 if k in tags.RESERVED:
90 continue
91 self._namestack.append(k)
92 # step into the namespace
93 value = self.restore(v)
94 if (util.is_noncomplex(instance) or
95 util.is_dictionary_subclass(instance)):
96 instance[k] = value
97 else:
98 instance.__dict__[k] = value
99 # step out
100 self._namestack.pop()
101 return self._pop(instance)
103 if util.is_list(obj):
104 return self._pop([self.restore(v) for v in obj])
106 if has_tag(obj, tags.TUPLE):
107 return self._pop(tuple([self.restore(v) for v in obj[tags.TUPLE]]))
109 if has_tag(obj, tags.SET):
110 return self._pop(set([self.restore(v) for v in obj[tags.SET]]))
112 if util.is_dictionary(obj):
113 data = {}
114 for k, v in obj.iteritems():
115 self._namestack.append(k)
116 data[k] = self.restore(v)
117 self._namestack.pop()
118 return self._pop(data)
120 return self._pop(obj)
122 def _refname(self):
123 """Calculates the name of the current location in the JSON stack.
125 This is called as jsonpickle traverses the object structure to
126 create references to previously-traversed objects. This allows
127 cyclical data structures such as doubly-linked lists.
128 jsonpickle ensures that duplicate python references to the same
129 object results in only a single JSON object definition and
130 special reference tags to represent each reference.
132 >>> u = Unpickler()
133 >>> u._namestack = []
134 >>> u._refname()
137 >>> u._namestack = ['a']
138 >>> u._refname()
139 '/a'
141 >>> u._namestack = ['a', 'b']
142 >>> u._refname()
143 '/a/b'
146 return '/' + '/'.join(self._namestack)
148 def _mkref(self, obj):
150 >>> from jsonpickle.tests.classes import Thing
151 >>> thing = Thing('referenced-thing')
152 >>> u = Unpickler()
153 >>> u._mkref(thing)
155 >>> u._namedict['/']
156 jsonpickle.tests.classes.Thing("referenced-thing")
159 name = self._refname()
160 if name not in self._namedict:
161 self._namedict[name] = obj
162 return name
164 def loadclass(module_and_name):
165 """Loads the module and returns the class.
167 >>> loadclass('jsonpickle.tests.classes.Thing')
168 <class 'jsonpickle.tests.classes.Thing'>
170 >>> loadclass('example.module.does.not.exist.Missing')
173 >>> loadclass('jsonpickle.tests.classes.MissingThing')
177 try:
178 module, name = module_and_name.rsplit('.', 1)
179 __import__(module)
180 return getattr(sys.modules[module], name)
181 except:
182 return None
184 def loadrepr(reprstr):
185 """Returns an instance of the object from the object's repr() string.
186 It involves the dynamic specification of code.
188 >>> from jsonpickle import tags
189 >>> loadrepr('jsonpickle.tests.classes/jsonpickle.tests.classes.Thing("json")')
190 jsonpickle.tests.classes.Thing("json")
193 module, evalstr = reprstr.split('/')
194 mylocals = locals()
195 localname = module
196 if '.' in localname:
197 localname = module.split('.', 1)[0]
198 mylocals[localname] = __import__(module)
199 return eval(evalstr)
201 def has_tag(obj, tag):
202 """Helper class that tests to see if the obj is a dictionary
203 and contains a particular key/tag.
205 >>> obj = {'test': 1}
206 >>> has_tag(obj, 'test')
207 True
208 >>> has_tag(obj, 'fail')
209 False
211 >>> has_tag(42, 'fail')
212 False
215 return type(obj) is dict and tag in obj