1
# -*- test-case-name: twisted.test.test_persisted -*-
3
# Twisted, the Framework of Your Internet
4
# Copyright (C) 2001 Matthew W. Lefkowitz
6
# This library is free software; you can redistribute it and/or
7
# modify it under the terms of version 2.1 of the GNU Lesser General Public
8
# License as published by the Free Software Foundation.
10
# This library is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
# Lesser General Public License for more details.
15
# You should have received a copy of the GNU Lesser General Public
16
# License along with this library; if not, write to the Free Software
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
"""Marmalade: jelly, with just a hint of bitterness.
21
I can serialize a Python object to an XML DOM tree (twisted.web.microdom), and
22
therefore to XML data, similarly to twisted.spread.jelly. Because both Python
23
lists and DOM trees are tree data-structures, many of the idioms used here are
30
from twisted.python.reflect import namedModule, namedClass, namedObject, fullFuncName, qual
31
from twisted.persisted.crefutil import NotKnown, _Tuple, _InstanceMethod, _DictKeyAndValue, _Dereference, _Defer
34
from new import instancemethod
36
from org.python.core import PyMethod
37
instancemethod = PyMethod
42
#for some reason, __builtins__ == __builtin__.__dict__ in the context where this is used.
43
#Can someone tell me why?
47
def instance(klass, d):
48
if isinstance(klass, types.ClassType):
49
return new.instance(klass, d)
50
elif isinstance(klass, type):
51
o = object.__new__(klass)
55
raise TypeError, "%s is not a class" % klass
58
def getValueElement(node):
59
"""Get the one child element of a given element.
61
If there is more than one child element, raises ValueError. Otherwise,
62
returns the value element.
65
for subnode in node.childNodes:
66
if isinstance(subnode, Element):
70
raise ValueError("Only one value node allowed per instance!")
78
def jellyToDOM(self, jellier, element):
79
element.setAttribute("marmalade:version", str(self.jellyDOMVersion))
80
method = getattr(self, "jellyToDOM_%s" % self.jellyDOMVersion, None)
82
method(jellier, element)
84
element.appendChild(jellier.jellyToNode(self.__dict__))
86
def unjellyFromDOM(self, unjellier, element):
87
pDOMVersion = element.getAttribute("marmalade:version") or "0"
88
method = getattr(self, "unjellyFromDOM_%s" % pDOMVersion, None)
90
method(unjellier, element)
92
# XXX: DOMJellyable.unjellyNode does not exist
93
# XXX: 'node' is undefined - did you mean 'self', 'element', or 'Node'?
94
state = self.unjellyNode(getValueElement(node))
95
if hasattr(self.__class__, "__setstate__"):
96
self.__setstate__(state)
105
self._savedLater = []
107
def unjellyLater(self, node):
108
"""Unjelly a node, later.
111
self.unjellyInto(d, 0, node)
112
self._savedLater.append(d)
115
def unjellyInto(self, obj, loc, node):
116
"""Utility method for unjellying one object into another.
118
This automates the handling of backreferences.
120
o = self.unjellyNode(node)
122
if isinstance(o, NotKnown):
123
o.addDependant(obj, loc)
126
def unjellyAttribute(self, instance, attrName, valueNode):
127
"""Utility method for unjellying into instances of attributes.
129
Use this rather than unjellyNode unless you like surprising bugs!
130
Alternatively, you can use unjellyInto on your instance's __dict__.
132
self.unjellyInto(instance.__dict__, attrName, valueNode)
134
def unjellyNode(self, node):
135
if node.tagName.lower() == "none":
137
elif node.tagName == "string":
138
# XXX FIXME this is obviously insecure
140
# >>> unjellyFromXML('''<string value="h"+str(__import__("sys"))+"i" />''')
141
# "h<module 'sys' (built-in)>i"
142
retval = str(eval('"%s"' % node.getAttribute("value")))
143
elif node.tagName == "int":
144
retval = int(node.getAttribute("value"))
145
elif node.tagName == "float":
146
retval = float(node.getAttribute("value"))
147
elif node.tagName == "longint":
148
retval = long(node.getAttribute("value"))
149
elif node.tagName == "module":
150
retval = namedModule(str(node.getAttribute("name")))
151
elif node.tagName == "class":
152
retval = namedClass(str(node.getAttribute("name")))
153
elif node.tagName == "unicode":
154
retval = unicode(str(node.getAttribute("value")).replace("\\n", "\n").replace("\\t", "\t"), "raw_unicode_escape")
155
elif node.tagName == "function":
156
retval = namedObject(str(node.getAttribute("name")))
157
elif node.tagName == "method":
158
im_name = node.getAttribute("name")
159
im_class = namedClass(node.getAttribute("class"))
160
im_self = self.unjellyNode(getValueElement(node))
161
if im_class.__dict__.has_key(im_name):
163
retval = getattr(im_class, im_name)
164
elif isinstance(im_self, NotKnown):
165
retval = _InstanceMethod(im_name, im_self, im_class)
167
retval = instancemethod(im_class.__dict__[im_name],
171
raise "instance method changed"
172
elif node.tagName == "tuple":
175
for subnode in node.childNodes:
176
if isinstance(subnode, Element):
178
if isinstance(self.unjellyInto(l, len(l)-1, subnode), NotKnown):
181
elif node.tagName == "list":
184
for subnode in node.childNodes:
185
if isinstance(subnode, Element):
187
self.unjellyInto(l, len(l)-1, subnode)
189
elif node.tagName == "dictionary":
192
for subnode in node.childNodes:
193
if isinstance(subnode, Element):
195
kvd = _DictKeyAndValue(d)
196
if not subnode.getAttribute("role") == "key":
197
raise "Unjellying Error: key role not set"
198
self.unjellyInto(kvd, 0, subnode)
200
self.unjellyInto(kvd, 1, subnode)
201
keyMode = not keyMode
203
elif node.tagName == "instance":
204
className = node.getAttribute("class")
205
clasz = namedClass(className)
206
if issubclass(clasz, DOMJellyable):
207
retval = instance(clasz, {})
208
retval.unjellyFromDOM(self, node)
210
state = self.unjellyNode(getValueElement(node))
211
if hasattr(clasz, "__setstate__"):
212
inst = instance(clasz, {})
213
inst.__setstate__(state)
215
inst = instance(clasz, state)
217
elif node.tagName == "reference":
218
refkey = node.getAttribute("key")
219
retval = self.references.get(refkey)
221
der = _Dereference(refkey)
222
self.references[refkey] = der
224
elif node.tagName == "copyreg":
225
nodefunc = namedObject(node.getAttribute("loadfunc"))
226
loaddef = self.unjellyLater(getValueElement(node)).addCallback(
227
lambda result, _l: apply(_l, result), nodefunc)
230
raise "Unsupported Node Type: %s" % str(node.tagName)
231
if node.hasAttribute("reference"):
232
refkey = node.getAttribute("reference")
233
ref = self.references.get(refkey)
235
self.references[refkey] = retval
236
elif isinstance(ref, NotKnown):
237
ref.resolveDependants(retval)
238
self.references[refkey] = retval
240
assert 0, "Multiple references with the same ID!"
243
def unjelly(self, doc):
245
self.unjellyInto(l, 0, doc.childNodes[0])
246
for svd in self._savedLater:
253
# dict of {id(obj): (obj, node)}
255
self.document = Document()
258
def prepareElement(self, element, object):
259
self.prepared[id(object)] = (object, element)
261
def jellyToNode(self, obj):
262
"""Create a node representing the given object and return it.
265
#immutable (We don't care if these have multiple refs)
266
if objType is types.NoneType:
267
node = self.document.createElement("None")
268
elif objType is types.StringType:
269
node = self.document.createElement("string")
272
r = r.replace("'", "\\'")
274
r = r.replace('"', '\\"')
275
node.setAttribute("value", r[1:-1])
276
# node.appendChild(CDATASection(obj))
277
elif objType is types.IntType:
278
node = self.document.createElement("int")
279
node.setAttribute("value", str(obj))
280
elif objType is types.LongType:
281
node = self.document.createElement("longint")
285
node.setAttribute("value", s)
286
elif objType is types.FloatType:
287
node = self.document.createElement("float")
288
node.setAttribute("value", repr(obj))
289
elif objType is types.MethodType:
290
node = self.document.createElement("method")
291
node.setAttribute("name", obj.im_func.__name__)
292
node.setAttribute("class", qual(obj.im_class))
293
# TODO: make methods 'prefer' not to jelly the object internally,
294
# so that the object will show up where it's referenced first NOT
296
node.appendChild(self.jellyToNode(obj.im_self))
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 "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 Text, Element, Node, Document, parse, parseString, CDATASection, NodeList