| OLD | NEW |
| (Empty) |
| 1 # -*- test-case-name: twisted.test.test_persisted -*- | |
| 2 | |
| 3 # Copyright (c) 2001-2004 Twisted Matrix Laboratories. | |
| 4 # See LICENSE for details. | |
| 5 | |
| 6 | |
| 7 """Marmalade: jelly, with just a hint of bitterness. | |
| 8 | |
| 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 | |
| 12 identical. | |
| 13 | |
| 14 """ | |
| 15 | |
| 16 import warnings | |
| 17 warnings.warn("twisted.persisted.marmalade is deprecated", DeprecationWarning, s
tacklevel=2) | |
| 18 | |
| 19 import new | |
| 20 | |
| 21 from twisted.python.reflect import namedModule, namedClass, namedObject, fullFun
cName, qual | |
| 22 from twisted.persisted.crefutil import NotKnown, _Tuple, _InstanceMethod, _DictK
eyAndValue, _Dereference, _Defer | |
| 23 | |
| 24 try: | |
| 25 from new import instancemethod | |
| 26 except: | |
| 27 from org.python.core import PyMethod | |
| 28 instancemethod = PyMethod | |
| 29 | |
| 30 import types | |
| 31 import copy_reg | |
| 32 | |
| 33 #for some reason, __builtins__ == __builtin__.__dict__ in the context where this
is used. | |
| 34 #Can someone tell me why? | |
| 35 import __builtin__ | |
| 36 | |
| 37 | |
| 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) | |
| 43 o.__dict__ = d | |
| 44 return o | |
| 45 else: | |
| 46 raise TypeError, "%s is not a class" % klass | |
| 47 | |
| 48 | |
| 49 def getValueElement(node): | |
| 50 """Get the one child element of a given element. | |
| 51 | |
| 52 If there is more than one child element, raises ValueError. Otherwise, | |
| 53 returns the value element. | |
| 54 """ | |
| 55 valueNode = None | |
| 56 for subnode in node.childNodes: | |
| 57 if isinstance(subnode, Element): | |
| 58 if valueNode is None: | |
| 59 valueNode = subnode | |
| 60 else: | |
| 61 raise ValueError("Only one value node allowed per instance!") | |
| 62 return valueNode | |
| 63 | |
| 64 | |
| 65 class DOMJellyable: | |
| 66 | |
| 67 jellyDOMVersion = 1 | |
| 68 | |
| 69 def jellyToDOM(self, jellier, element): | |
| 70 element.setAttribute("marmalade:version", str(self.jellyDOMVersion)) | |
| 71 method = getattr(self, "jellyToDOM_%s" % self.jellyDOMVersion, None) | |
| 72 if method: | |
| 73 method(jellier, element) | |
| 74 else: | |
| 75 element.appendChild(jellier.jellyToNode(self.__dict__)) | |
| 76 | |
| 77 def unjellyFromDOM(self, unjellier, element): | |
| 78 pDOMVersion = element.getAttribute("marmalade:version") or "0" | |
| 79 method = getattr(self, "unjellyFromDOM_%s" % pDOMVersion, None) | |
| 80 if method: | |
| 81 method(unjellier, element) | |
| 82 else: | |
| 83 # XXX: DOMJellyable.unjellyNode does not exist | |
| 84 # XXX: 'node' is undefined - did you mean 'self', 'element', or 'Nod
e'? | |
| 85 state = self.unjellyNode(getValueElement(node)) | |
| 86 if hasattr(self.__class__, "__setstate__"): | |
| 87 self.__setstate__(state) | |
| 88 else: | |
| 89 self.__dict__ = state | |
| 90 | |
| 91 | |
| 92 | |
| 93 class DOMUnjellier: | |
| 94 def __init__(self): | |
| 95 self.references = {} | |
| 96 self._savedLater = [] | |
| 97 | |
| 98 def unjellyLater(self, node): | |
| 99 """Unjelly a node, later. | |
| 100 """ | |
| 101 d = _Defer() | |
| 102 self.unjellyInto(d, 0, node) | |
| 103 self._savedLater.append(d) | |
| 104 return d | |
| 105 | |
| 106 def unjellyInto(self, obj, loc, node): | |
| 107 """Utility method for unjellying one object into another. | |
| 108 | |
| 109 This automates the handling of backreferences. | |
| 110 """ | |
| 111 o = self.unjellyNode(node) | |
| 112 obj[loc] = o | |
| 113 if isinstance(o, NotKnown): | |
| 114 o.addDependant(obj, loc) | |
| 115 return o | |
| 116 | |
| 117 def unjellyAttribute(self, instance, attrName, valueNode): | |
| 118 """Utility method for unjellying into instances of attributes. | |
| 119 | |
| 120 Use this rather than unjellyNode unless you like surprising bugs! | |
| 121 Alternatively, you can use unjellyInto on your instance's __dict__. | |
| 122 """ | |
| 123 self.unjellyInto(instance.__dict__, attrName, valueNode) | |
| 124 | |
| 125 def unjellyNode(self, node): | |
| 126 if node.tagName.lower() == "none": | |
| 127 retval = None | |
| 128 elif node.tagName == "string": | |
| 129 # XXX FIXME this is obviously insecure | |
| 130 # if you doubt: | |
| 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")) | |
| 142 if retval: | |
| 143 retval = True | |
| 144 else: | |
| 145 retval = False | |
| 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): | |
| 159 if im_self is None: | |
| 160 retval = getattr(im_class, im_name) | |
| 161 elif isinstance(im_self, NotKnown): | |
| 162 retval = _InstanceMethod(im_name, im_self, im_class) | |
| 163 else: | |
| 164 retval = instancemethod(im_class.__dict__[im_name], | |
| 165 im_self, | |
| 166 im_class) | |
| 167 else: | |
| 168 raise TypeError("instance method changed") | |
| 169 elif node.tagName == "tuple": | |
| 170 l = [] | |
| 171 tupFunc = tuple | |
| 172 for subnode in node.childNodes: | |
| 173 if isinstance(subnode, Element): | |
| 174 l.append(None) | |
| 175 if isinstance(self.unjellyInto(l, len(l)-1, subnode), NotKno
wn): | |
| 176 tupFunc = _Tuple | |
| 177 retval = tupFunc(l) | |
| 178 elif node.tagName == "list": | |
| 179 l = [] | |
| 180 finished = 1 | |
| 181 for subnode in node.childNodes: | |
| 182 if isinstance(subnode, Element): | |
| 183 l.append(None) | |
| 184 self.unjellyInto(l, len(l)-1, subnode) | |
| 185 retval = l | |
| 186 elif node.tagName == "dictionary": | |
| 187 d = {} | |
| 188 keyMode = 1 | |
| 189 for subnode in node.childNodes: | |
| 190 if isinstance(subnode, Element): | |
| 191 if keyMode: | |
| 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) | |
| 196 else: | |
| 197 self.unjellyInto(kvd, 1, subnode) | |
| 198 keyMode = not keyMode | |
| 199 retval = d | |
| 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) | |
| 206 else: | |
| 207 state = self.unjellyNode(getValueElement(node)) | |
| 208 if hasattr(clasz, "__setstate__"): | |
| 209 inst = instance(clasz, {}) | |
| 210 inst.__setstate__(state) | |
| 211 else: | |
| 212 inst = instance(clasz, state) | |
| 213 retval = inst | |
| 214 elif node.tagName == "reference": | |
| 215 refkey = node.getAttribute("key") | |
| 216 retval = self.references.get(refkey) | |
| 217 if retval is None: | |
| 218 der = _Dereference(refkey) | |
| 219 self.references[refkey] = der | |
| 220 retval = 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) | |
| 225 retval = loaddef | |
| 226 else: | |
| 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) | |
| 231 if ref is None: | |
| 232 self.references[refkey] = retval | |
| 233 elif isinstance(ref, NotKnown): | |
| 234 ref.resolveDependants(retval) | |
| 235 self.references[refkey] = retval | |
| 236 else: | |
| 237 assert 0, "Multiple references with the same ID!" | |
| 238 return retval | |
| 239 | |
| 240 def unjelly(self, doc): | |
| 241 l = [None] | |
| 242 self.unjellyInto(l, 0, doc.childNodes[0]) | |
| 243 for svd in self._savedLater: | |
| 244 svd.unpause() | |
| 245 return l[0] | |
| 246 | |
| 247 | |
| 248 class DOMJellier: | |
| 249 def __init__(self): | |
| 250 # dict of {id(obj): (obj, node)} | |
| 251 self.prepared = {} | |
| 252 self.document = Document() | |
| 253 self._ref_id = 0 | |
| 254 | |
| 255 def prepareElement(self, element, object): | |
| 256 self.prepared[id(object)] = (object, element) | |
| 257 | |
| 258 def jellyToNode(self, obj): | |
| 259 """Create a node representing the given object and return it. | |
| 260 """ | |
| 261 objType = type(obj) | |
| 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") | |
| 267 r = repr(obj) | |
| 268 if r[0] == '"': | |
| 269 r = r.replace("'", "\\'") | |
| 270 else: | |
| 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") | |
| 279 s = str(obj) | |
| 280 if s[-1] == 'L': | |
| 281 s = s[:-1] | |
| 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 | |
| 292 # by a method. | |
| 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)) | |
| 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 TypeError("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 Element, Document, parse, parseString, NodeList | |
| OLD | NEW |