1
# -*- test-case-name: twisted.test.test_persisted -*-
3
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
4
# See LICENSE for details.
7
"""Marmalade: jelly, with just a hint of bitterness.
9
I can serialize a Python object to an XML DOM tree (twisted.web.microdom), and
10
therefore to XML data, similarly to twisted.spread.jelly. Because both Python
11
lists and DOM trees are tree data-structures, many of the idioms used here are
17
warnings.warn("twisted.persisted.marmalade is deprecated", DeprecationWarning, stacklevel=2)
21
from twisted.python.reflect import namedModule, namedClass, namedObject, fullFuncName, qual
22
from twisted.persisted.crefutil import NotKnown, _Tuple, _InstanceMethod, _DictKeyAndValue, _Dereference, _Defer
25
from new import instancemethod
27
from org.python.core import PyMethod
28
instancemethod = PyMethod
33
#for some reason, __builtins__ == __builtin__.__dict__ in the context where this is used.
34
#Can someone tell me why?
38
def instance(klass, d):
39
if isinstance(klass, types.ClassType):
40
return new.instance(klass, d)
41
elif isinstance(klass, type):
42
o = object.__new__(klass)
46
raise TypeError, "%s is not a class" % klass
49
def getValueElement(node):
50
"""Get the one child element of a given element.
52
If there is more than one child element, raises ValueError. Otherwise,
53
returns the value element.
56
for subnode in node.childNodes:
57
if isinstance(subnode, Element):
61
raise ValueError("Only one value node allowed per instance!")
69
def jellyToDOM(self, jellier, element):
70
element.setAttribute("marmalade:version", str(self.jellyDOMVersion))
71
method = getattr(self, "jellyToDOM_%s" % self.jellyDOMVersion, None)
73
method(jellier, element)
75
element.appendChild(jellier.jellyToNode(self.__dict__))
77
def unjellyFromDOM(self, unjellier, element):
78
pDOMVersion = element.getAttribute("marmalade:version") or "0"
79
method = getattr(self, "unjellyFromDOM_%s" % pDOMVersion, None)
81
method(unjellier, element)
83
# XXX: DOMJellyable.unjellyNode does not exist
84
# XXX: 'node' is undefined - did you mean 'self', 'element', or 'Node'?
85
state = self.unjellyNode(getValueElement(node))
86
if hasattr(self.__class__, "__setstate__"):
87
self.__setstate__(state)
98
def unjellyLater(self, node):
99
"""Unjelly a node, later.
102
self.unjellyInto(d, 0, node)
103
self._savedLater.append(d)
106
def unjellyInto(self, obj, loc, node):
107
"""Utility method for unjellying one object into another.
109
This automates the handling of backreferences.
111
o = self.unjellyNode(node)
113
if isinstance(o, NotKnown):
114
o.addDependant(obj, loc)
117
def unjellyAttribute(self, instance, attrName, valueNode):
118
"""Utility method for unjellying into instances of attributes.
120
Use this rather than unjellyNode unless you like surprising bugs!
121
Alternatively, you can use unjellyInto on your instance's __dict__.
123
self.unjellyInto(instance.__dict__, attrName, valueNode)
125
def unjellyNode(self, node):
126
if node.tagName.lower() == "none":
128
elif node.tagName == "string":
129
# XXX FIXME this is obviously insecure
131
# >>> unjellyFromXML('''<string value="h"+str(__import__("sys"))+"i" />''')
132
# "h<module 'sys' (built-in)>i"
133
retval = str(eval('"%s"' % node.getAttribute("value")))
134
elif node.tagName == "int":
135
retval = int(node.getAttribute("value"))
136
elif node.tagName == "float":
137
retval = float(node.getAttribute("value"))
138
elif node.tagName == "longint":
139
retval = long(node.getAttribute("value"))
140
elif node.tagName == "bool":
141
retval = int(node.getAttribute("value"))
146
elif node.tagName == "module":
147
retval = namedModule(str(node.getAttribute("name")))
148
elif node.tagName == "class":
149
retval = namedClass(str(node.getAttribute("name")))
150
elif node.tagName == "unicode":
151
retval = unicode(str(node.getAttribute("value")).replace("\\n", "\n").replace("\\t", "\t"), "raw_unicode_escape")
152
elif node.tagName == "function":
153
retval = namedObject(str(node.getAttribute("name")))
154
elif node.tagName == "method":
155
im_name = node.getAttribute("name")
156
im_class = namedClass(node.getAttribute("class"))
157
im_self = self.unjellyNode(getValueElement(node))
158
if im_class.__dict__.has_key(im_name):
160
retval = getattr(im_class, im_name)
161
elif isinstance(im_self, NotKnown):
162
retval = _InstanceMethod(im_name, im_self, im_class)
164
retval = instancemethod(im_class.__dict__[im_name],
168
raise TypeError("instance method changed")
169
elif node.tagName == "tuple":
172
for subnode in node.childNodes:
173
if isinstance(subnode, Element):
175
if isinstance(self.unjellyInto(l, len(l)-1, subnode), NotKnown):
178
elif node.tagName == "list":
181
for subnode in node.childNodes:
182
if isinstance(subnode, Element):
184
self.unjellyInto(l, len(l)-1, subnode)
186
elif node.tagName == "dictionary":
189
for subnode in node.childNodes:
190
if isinstance(subnode, Element):
192
kvd = _DictKeyAndValue(d)
193
if not subnode.getAttribute("role") == "key":
194
raise TypeError("Unjellying Error: key role not set")
195
self.unjellyInto(kvd, 0, subnode)
197
self.unjellyInto(kvd, 1, subnode)
198
keyMode = not keyMode
200
elif node.tagName == "instance":
201
className = node.getAttribute("class")
202
clasz = namedClass(className)
203
if issubclass(clasz, DOMJellyable):
204
retval = instance(clasz, {})
205
retval.unjellyFromDOM(self, node)
207
state = self.unjellyNode(getValueElement(node))
208
if hasattr(clasz, "__setstate__"):
209
inst = instance(clasz, {})
210
inst.__setstate__(state)
212
inst = instance(clasz, state)
214
elif node.tagName == "reference":
215
refkey = node.getAttribute("key")
216
retval = self.references.get(refkey)
218
der = _Dereference(refkey)
219
self.references[refkey] = der
221
elif node.tagName == "copyreg":
222
nodefunc = namedObject(node.getAttribute("loadfunc"))
223
loaddef = self.unjellyLater(getValueElement(node)).addCallback(
224
lambda result, _l: apply(_l, result), nodefunc)
227
raise TypeError("Unsupported Node Type: %s" % (node.tagName,))
228
if node.hasAttribute("reference"):
229
refkey = node.getAttribute("reference")
230
ref = self.references.get(refkey)
232
self.references[refkey] = retval
233
elif isinstance(ref, NotKnown):
234
ref.resolveDependants(retval)
235
self.references[refkey] = retval
237
assert 0, "Multiple references with the same ID!"
240
def unjelly(self, doc):
242
self.unjellyInto(l, 0, doc.childNodes[0])
243
for svd in self._savedLater:
250
# dict of {id(obj): (obj, node)}
252
self.document = Document()
255
def prepareElement(self, element, object):
256
self.prepared[id(object)] = (object, element)
258
def jellyToNode(self, obj):
259
"""Create a node representing the given object and return it.
262
#immutable (We don't care if these have multiple refs)
263
if objType is types.NoneType:
264
node = self.document.createElement("None")
265
elif objType is types.StringType:
266
node = self.document.createElement("string")
269
r = r.replace("'", "\\'")
271
r = r.replace('"', '\\"')
272
node.setAttribute("value", r[1:-1])
273
# node.appendChild(CDATASection(obj))
274
elif objType is types.IntType:
275
node = self.document.createElement("int")
276
node.setAttribute("value", str(obj))
277
elif objType is types.LongType:
278
node = self.document.createElement("longint")
282
node.setAttribute("value", s)
283
elif objType is types.FloatType:
284
node = self.document.createElement("float")
285
node.setAttribute("value", repr(obj))
286
elif objType is types.MethodType:
287
node = self.document.createElement("method")
288
node.setAttribute("name", obj.im_func.__name__)
289
node.setAttribute("class", qual(obj.im_class))
290
# TODO: make methods 'prefer' not to jelly the object internally,
291
# so that the object will show up where it's referenced first NOT
293
node.appendChild(self.jellyToNode(obj.im_self))
294
elif hasattr(types, 'BooleanType') and objType is types.BooleanType:
295
node = self.document.createElement("bool")
296
node.setAttribute("value", str(int(obj)))
297
elif objType is types.ModuleType:
298
node = self.document.createElement("module")
299
node.setAttribute("name", obj.__name__)
300
elif objType==types.ClassType or issubclass(objType, type):
301
node = self.document.createElement("class")
302
node.setAttribute("name", qual(obj))
303
elif objType is types.UnicodeType:
304
node = self.document.createElement("unicode")
305
obj = obj.encode('raw_unicode_escape')
306
s = obj.replace("\n", "\\n").replace("\t", "\\t")
307
node.setAttribute("value", s)
308
elif objType in (types.FunctionType, types.BuiltinFunctionType):
309
# TODO: beat pickle at its own game, and do BuiltinFunctionType
310
# separately, looking for __self__ attribute and unpickling methods
311
# of C objects when possible.
312
node = self.document.createElement("function")
313
node.setAttribute("name", fullFuncName(obj))
316
if self.prepared.has_key(id(obj)):
317
oldNode = self.prepared[id(obj)][1]
318
if oldNode.hasAttribute("reference"):
319
# it's been referenced already
320
key = oldNode.getAttribute("reference")
322
# it hasn't been referenced yet
323
self._ref_id = self._ref_id + 1
324
key = str(self._ref_id)
325
oldNode.setAttribute("reference", key)
326
node = self.document.createElement("reference")
327
node.setAttribute("key", key)
329
node = self.document.createElement("UNNAMED")
330
self.prepareElement(node, obj)
331
if objType is types.ListType or __builtin__.__dict__.has_key('object') and isinstance(obj, NodeList):
332
node.tagName = "list"
334
node.appendChild(self.jellyToNode(subobj))
335
elif objType is types.TupleType:
336
node.tagName = "tuple"
338
node.appendChild(self.jellyToNode(subobj))
339
elif objType is types.DictionaryType:
340
node.tagName = "dictionary"
341
for k, v in obj.items():
342
n = self.jellyToNode(k)
343
n.setAttribute("role", "key")
344
n2 = self.jellyToNode(v)
347
elif copy_reg.dispatch_table.has_key(objType):
348
unpickleFunc, state = copy_reg.dispatch_table[objType](obj)
349
node = self.document.createElement("copyreg")
350
# node.setAttribute("type", objType.__name__)
351
node.setAttribute("loadfunc", fullFuncName(unpickleFunc))
352
node.appendChild(self.jellyToNode(state))
353
elif objType is types.InstanceType or hasattr(objType, "__module__"):
354
className = qual(obj.__class__)
355
node.tagName = "instance"
356
node.setAttribute("class", className)
357
if isinstance(obj, DOMJellyable):
358
obj.jellyToDOM(self, node)
360
if hasattr(obj, "__getstate__"):
361
state = obj.__getstate__()
364
n = self.jellyToNode(state)
367
raise TypeError("Unsupported type: %s" % (objType.__name__,))
370
def jelly(self, obj):
371
"""Create a document representing the current object, and return it.
373
node = self.jellyToNode(obj)
374
self.document.appendChild(node)
378
def jellyToDOM(object):
379
"""Convert an Object into an twisted.web.microdom.Document.
382
document = dj.jelly(object)
386
def unjellyFromDOM(document):
387
"""Convert an twisted.web.microdom.Document into a Python object.
390
return du.unjelly(document)
393
def jellyToXML(object, file=None):
394
"""jellyToXML(object, [file]) -> None | string
396
Converts a Python object to an XML stream. If you pass a file, the XML
397
will be written to that file; otherwise, a string of the XML will be
400
document = jellyToDOM(object)
402
document.writexml(file, "", " ", "\n")
404
return document.toprettyxml(" ", "\n")
406
def unjellyFromXML(stringOrFile):
407
"""I convert a string or the contents of an XML file into a Python object.
409
if hasattr(stringOrFile, "read"):
410
document = parse(stringOrFile)
412
document = parseString(stringOrFile)
413
return unjellyFromDOM(document)
416
from twisted.web.microdom import Element, Document, parse, parseString, NodeList