~certify-web-dev/twisted/certify-trunk

« back to all changes in this revision

Viewing changes to twisted/persisted/marmalade.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2004-06-21 22:01:11 UTC
  • mto: (2.2.3 sid)
  • mto: This revision was merged to the branch mainline in revision 3.
  • Revision ID: james.westby@ubuntu.com-20040621220111-vkf909euqnyrp3nr
Tags: upstream-1.3.0
ImportĀ upstreamĀ versionĀ 1.3.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.test.test_persisted -*-
 
2
 
 
3
# Twisted, the Framework of Your Internet
 
4
# Copyright (C) 2001 Matthew W. Lefkowitz
 
5
#
 
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.
 
9
#
 
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.
 
14
#
 
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
 
18
 
 
19
"""Marmalade: jelly, with just a hint of bitterness.
 
20
 
 
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
 
24
identical.
 
25
 
 
26
"""
 
27
 
 
28
import new
 
29
 
 
30
from twisted.python.reflect import namedModule, namedClass, namedObject, fullFuncName, qual
 
31
from twisted.persisted.crefutil import NotKnown, _Tuple, _InstanceMethod, _DictKeyAndValue, _Dereference, _Defer
 
32
 
 
33
try:
 
34
    from new import instancemethod
 
35
except:
 
36
    from org.python.core import PyMethod
 
37
    instancemethod = PyMethod
 
38
 
 
39
import types
 
40
import copy_reg
 
41
 
 
42
#for some reason, __builtins__ == __builtin__.__dict__ in the context where this is used.
 
43
#Can someone tell me why?
 
44
import __builtin__ 
 
45
 
 
46
 
 
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)
 
52
        o.__dict__ = d
 
53
        return o
 
54
    else:
 
55
        raise TypeError, "%s is not a class" % klass
 
56
 
 
57
 
 
58
def getValueElement(node):
 
59
    """Get the one child element of a given element.
 
60
 
 
61
    If there is more than one child element, raises ValueError.  Otherwise,
 
62
    returns the value element.
 
63
    """
 
64
    valueNode = None
 
65
    for subnode in node.childNodes:
 
66
        if isinstance(subnode, Element):
 
67
            if valueNode is None:
 
68
                valueNode = subnode
 
69
            else:
 
70
                raise ValueError("Only one value node allowed per instance!")
 
71
    return valueNode
 
72
 
 
73
 
 
74
class DOMJellyable:
 
75
 
 
76
    jellyDOMVersion = 1
 
77
 
 
78
    def jellyToDOM(self, jellier, element):
 
79
        element.setAttribute("marmalade:version", str(self.jellyDOMVersion))
 
80
        method = getattr(self, "jellyToDOM_%s" % self.jellyDOMVersion, None)
 
81
        if method:
 
82
            method(jellier, element)
 
83
        else:
 
84
            element.appendChild(jellier.jellyToNode(self.__dict__))
 
85
 
 
86
    def unjellyFromDOM(self, unjellier, element):
 
87
        pDOMVersion = element.getAttribute("marmalade:version") or "0"
 
88
        method = getattr(self, "unjellyFromDOM_%s" % pDOMVersion, None)
 
89
        if method:
 
90
            method(unjellier, element)
 
91
        else:
 
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)
 
97
            else:
 
98
                self.__dict__ = state
 
99
 
 
100
 
 
101
 
 
102
class DOMUnjellier:
 
103
    def __init__(self):
 
104
        self.references = {}
 
105
        self._savedLater = []
 
106
 
 
107
    def unjellyLater(self, node):
 
108
        """Unjelly a node, later.
 
109
        """
 
110
        d = _Defer()
 
111
        self.unjellyInto(d, 0, node)
 
112
        self._savedLater.append(d)
 
113
        return d
 
114
 
 
115
    def unjellyInto(self, obj, loc, node):
 
116
        """Utility method for unjellying one object into another.
 
117
 
 
118
        This automates the handling of backreferences.
 
119
        """
 
120
        o = self.unjellyNode(node)
 
121
        obj[loc] = o
 
122
        if isinstance(o, NotKnown):
 
123
            o.addDependant(obj, loc)
 
124
        return o
 
125
 
 
126
    def unjellyAttribute(self, instance, attrName, valueNode):
 
127
        """Utility method for unjellying into instances of attributes.
 
128
 
 
129
        Use this rather than unjellyNode unless you like surprising bugs!
 
130
        Alternatively, you can use unjellyInto on your instance's __dict__.
 
131
        """
 
132
        self.unjellyInto(instance.__dict__, attrName, valueNode)
 
133
 
 
134
    def unjellyNode(self, node):
 
135
        if node.tagName.lower() == "none":
 
136
            retval = None
 
137
        elif node.tagName == "string":
 
138
            # XXX FIXME this is obviously insecure
 
139
            # if you doubt:
 
140
            # >>> unjellyFromXML('''<string value="h&quot;+str(__import__(&quot;sys&quot;))+&quot;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):
 
162
                if im_self is None:
 
163
                    retval = getattr(im_class, im_name)
 
164
                elif isinstance(im_self, NotKnown):
 
165
                    retval = _InstanceMethod(im_name, im_self, im_class)
 
166
                else:
 
167
                    retval = instancemethod(im_class.__dict__[im_name],
 
168
                                            im_self,
 
169
                                            im_class)
 
170
            else:
 
171
                raise "instance method changed"
 
172
        elif node.tagName == "tuple":
 
173
            l = []
 
174
            tupFunc = tuple
 
175
            for subnode in node.childNodes:
 
176
                if isinstance(subnode, Element):
 
177
                    l.append(None)
 
178
                    if isinstance(self.unjellyInto(l, len(l)-1, subnode), NotKnown):
 
179
                        tupFunc = _Tuple
 
180
            retval = tupFunc(l)
 
181
        elif node.tagName == "list":
 
182
            l = []
 
183
            finished = 1
 
184
            for subnode in node.childNodes:
 
185
                if isinstance(subnode, Element):
 
186
                    l.append(None)
 
187
                    self.unjellyInto(l, len(l)-1, subnode)
 
188
            retval = l
 
189
        elif node.tagName == "dictionary":
 
190
            d = {}
 
191
            keyMode = 1
 
192
            for subnode in node.childNodes:
 
193
                if isinstance(subnode, Element):
 
194
                    if keyMode:
 
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)
 
199
                    else:
 
200
                        self.unjellyInto(kvd, 1, subnode)
 
201
                    keyMode = not keyMode
 
202
            retval = d
 
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)
 
209
            else:
 
210
                state = self.unjellyNode(getValueElement(node))
 
211
                if hasattr(clasz, "__setstate__"):
 
212
                    inst = instance(clasz, {})
 
213
                    inst.__setstate__(state)
 
214
                else:
 
215
                    inst = instance(clasz, state)
 
216
                retval = inst
 
217
        elif node.tagName == "reference":
 
218
            refkey = node.getAttribute("key")
 
219
            retval = self.references.get(refkey)
 
220
            if retval is None:
 
221
                der = _Dereference(refkey)
 
222
                self.references[refkey] = der
 
223
                retval = 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)
 
228
            retval = loaddef
 
229
        else:
 
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)
 
234
            if ref is None:
 
235
                self.references[refkey] = retval
 
236
            elif isinstance(ref, NotKnown):
 
237
                ref.resolveDependants(retval)
 
238
                self.references[refkey] = retval
 
239
            else:
 
240
                assert 0, "Multiple references with the same ID!"
 
241
        return retval
 
242
 
 
243
    def unjelly(self, doc):
 
244
        l = [None]
 
245
        self.unjellyInto(l, 0, doc.childNodes[0])
 
246
        for svd in self._savedLater:
 
247
            svd.unpause()
 
248
        return l[0]
 
249
 
 
250
 
 
251
class DOMJellier:
 
252
    def __init__(self):
 
253
        # dict of {id(obj): (obj, node)}
 
254
        self.prepared = {}
 
255
        self.document = Document()
 
256
        self._ref_id = 0
 
257
 
 
258
    def prepareElement(self, element, object):
 
259
        self.prepared[id(object)] = (object, element)
 
260
 
 
261
    def jellyToNode(self, obj):
 
262
        """Create a node representing the given object and return it.
 
263
        """
 
264
        objType = type(obj)
 
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")
 
270
            r = repr(obj)
 
271
            if r[0] == '"':
 
272
                r = r.replace("'", "\\'")
 
273
            else:
 
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")
 
282
            s = str(obj)
 
283
            if s[-1] == 'L':
 
284
                s = s[:-1]
 
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
 
295
            # by a method.
 
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))
 
314
        else:
 
315
            #mutable!
 
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")
 
321
                else:
 
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)
 
328
                return node
 
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"
 
333
                for subobj in obj:
 
334
                    node.appendChild(self.jellyToNode(subobj))
 
335
            elif objType is types.TupleType:
 
336
                node.tagName = "tuple"
 
337
                for subobj in obj:
 
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)
 
345
                    node.appendChild(n)
 
346
                    node.appendChild(n2)
 
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)
 
359
                else:
 
360
                    if hasattr(obj, "__getstate__"):
 
361
                        state = obj.__getstate__()
 
362
                    else:
 
363
                        state = obj.__dict__
 
364
                    n = self.jellyToNode(state)
 
365
                    node.appendChild(n)
 
366
            else:
 
367
                raise "Unsupported type: %s" % objType.__name__
 
368
        return node
 
369
 
 
370
    def jelly(self, obj):
 
371
        """Create a document representing the current object, and return it.
 
372
        """
 
373
        node = self.jellyToNode(obj)
 
374
        self.document.appendChild(node)
 
375
        return self.document
 
376
 
 
377
 
 
378
def jellyToDOM(object):
 
379
    """Convert an Object into an twisted.web.microdom.Document.
 
380
    """
 
381
    dj = DOMJellier()
 
382
    document = dj.jelly(object)
 
383
    return document
 
384
 
 
385
 
 
386
def unjellyFromDOM(document):
 
387
    """Convert an twisted.web.microdom.Document into a Python object.
 
388
    """
 
389
    du = DOMUnjellier()
 
390
    return du.unjelly(document)
 
391
 
 
392
 
 
393
def jellyToXML(object, file=None):
 
394
    """jellyToXML(object, [file]) -> None | string
 
395
 
 
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
 
398
    returned.
 
399
    """
 
400
    document = jellyToDOM(object)
 
401
    if file:
 
402
        document.writexml(file, "", "  ", "\n")
 
403
    else:
 
404
        return document.toprettyxml("  ", "\n")
 
405
 
 
406
def unjellyFromXML(stringOrFile):
 
407
    """I convert a string or the contents of an XML file into a Python object.
 
408
    """
 
409
    if hasattr(stringOrFile, "read"):
 
410
        document = parse(stringOrFile)
 
411
    else:
 
412
        document = parseString(stringOrFile)
 
413
    return unjellyFromDOM(document)
 
414
 
 
415
 
 
416
from twisted.web.microdom import Text, Element, Node, Document, parse, parseString, CDATASection, NodeList