1
# -*- test-case-name: twisted.test.test_persisted -*-
3
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
4
# See LICENSE for details.
9
AOT: Abstract Object Trees
10
The source-code-marshallin'est abstract-object-serializin'est persister
11
this side of Marmalade!
14
import types, new, string, copy_reg, tokenize, re
16
from twisted.python import reflect, log
17
from twisted.persisted import crefutil
19
###########################
20
# Abstract Object Classes #
21
###########################
23
#"\0" in a getSource means "insert variable-width indention here".
27
def __init__(self, name):
32
return "Class(%r)" % self.name
34
class Function(Named):
36
return "Function(%r)" % self.name
40
return "Module(%r)" % self.name
44
def __init__(self, name, klass, inst):
45
if not (isinstance(inst, Ref) or isinstance(inst, Instance) or isinstance(inst, Deref)):
46
raise TypeError("%s isn't an Instance, Ref, or Deref!" % inst)
52
return "InstanceMethod(%r, %r, \n\0%s)" % (self.name, self.klass, prettify(self.instance))
57
NoStateObj = _NoStateObj()
60
types.StringType, types.UnicodeType, types.IntType, types.FloatType,
61
types.ComplexType, types.LongType, types.NoneType, types.SliceType,
65
_SIMPLE_BUILTINS.append(types.BooleanType)
66
except AttributeError:
70
def __init__(self, className, __stateObj__=NoStateObj, **state):
71
if not isinstance(className, types.StringType):
72
raise TypeError("%s isn't a string!" % className)
73
self.klass = className
74
if __stateObj__ is not NoStateObj:
75
self.state = __stateObj__
82
#XXX make state be foo=bar instead of a dict.
84
stateDict = self.state
85
elif isinstance(self.state, Ref) and isinstance(self.state.obj, types.DictType):
86
stateDict = self.state.obj
89
if stateDict is not None:
91
return "Instance(%r, %s)" % (self.klass, dictToKW(stateDict))
92
except NonFormattableDict:
93
return "Instance(%r, %s)" % (self.klass, prettify(stateDict))
94
return "Instance(%r, %s)" % (self.klass, prettify(self.state))
98
def __init__(self, *args):
101
self.refnum = args[0]
107
def setRef(self, num):
109
raise ValueError("Error setting id %s, I already have %s" % (num, self.refnum))
112
def setObj(self, obj):
114
raise ValueError("Error setting obj %s, I already have %s" % (obj, self.obj))
119
raise RuntimeError("Don't try to display me before setting an object on me!")
121
return "Ref(%d, \n\0%s)" % (self.refnum, prettify(self.obj))
122
return prettify(self.obj)
126
def __init__(self, num):
130
return "Deref(%d)" % self.refnum
136
def __init__(self, loadfunc, state):
137
self.loadfunc = loadfunc
141
return "Copyreg(%r, %s)" % (self.loadfunc, prettify(self.state))
151
"""Pass me an AO, I'll return a nicely-formatted source representation."""
152
return indentify("app = " + prettify(ao))
155
class NonFormattableDict(Exception):
156
"""A dictionary was not formattable.
159
r = re.compile('[a-zA-Z_][a-zA-Z0-9_]*$')
166
if not isinstance(k, types.StringType):
167
raise NonFormattableDict("%r ain't a string" % k)
169
raise NonFormattableDict("%r ain't an identifier" % k)
171
"\n\0%s=%s," % (k, prettify(v))
173
return string.join(out, '')
177
if hasattr(obj, 'getSource'):
178
return obj.getSource()
183
if t in _SIMPLE_BUILTINS:
186
elif t is types.DictType:
188
for k,v in obj.items():
189
out.append('\n\0%s: %s,' % (prettify(k), prettify(v)))
190
out.append(len(obj) and '\n\0}' or '}')
191
return string.join(out, '')
193
elif t is types.ListType:
196
out.append('\n\0%s,' % prettify(x))
197
out.append(len(obj) and '\n\0]' or ']')
198
return string.join(out, '')
200
elif t is types.TupleType:
203
out.append('\n\0%s,' % prettify(x))
204
out.append(len(obj) and '\n\0)' or ')')
205
return string.join(out, '')
207
raise TypeError("Unsupported type %s when trying to prettify %s." % (t, obj))
212
def eater(type, val, r, c, l, out=out, stack=stack):
214
#sys.stdout.write(val)
215
if val in ['[', '(', '{']:
217
elif val in [']', ')', '}']:
220
out.append(' '*len(stack))
224
tokenize.tokenize(l.pop, eater)
225
return string.join(out, '')
235
def unjellyFromAOT(aot):
237
Pass me an Abstract Object Tree, and I'll unjelly it for you.
239
return AOTUnjellier().unjelly(aot)
241
def unjellyFromSource(stringOrFile):
243
Pass me a string of code or a filename that defines an 'app' variable (in
244
terms of Abstract Objects!), and I'll execute it and unjelly the resulting
245
AOT for you, returning a newly unpersisted Application object!
248
ns = {"Instance": Instance,
249
"InstanceMethod": InstanceMethod,
251
"Function": Function,
258
if hasattr(stringOrFile, "read"):
259
exec stringOrFile.read() in ns
261
exec stringOrFile in ns
263
if ns.has_key('app'):
264
return unjellyFromAOT(ns['app'])
266
raise ValueError("%s needs to define an 'app', it didn't!" % stringOrFile)
270
"""I handle the unjellying of an Abstract Object Tree.
271
See AOTUnjellier.unjellyAO
276
self.afterUnjelly = []
279
# unjelly helpers (copied pretty much directly from (now deleted) marmalade)
281
def unjellyLater(self, node):
282
"""Unjelly a node, later.
284
d = crefutil._Defer()
285
self.unjellyInto(d, 0, node)
288
def unjellyInto(self, obj, loc, ao):
289
"""Utility method for unjellying one object into another.
290
This automates the handling of backreferences.
292
o = self.unjellyAO(ao)
294
if isinstance(o, crefutil.NotKnown):
295
o.addDependant(obj, loc)
298
def callAfter(self, callable, result):
299
if isinstance(result, crefutil.NotKnown):
301
result.addDependant(l, 1)
304
self.afterUnjelly.append((callable, l))
306
def unjellyAttribute(self, instance, attrName, ao):
307
#XXX this is unused????
308
"""Utility method for unjellying into instances of attributes.
310
Use this rather than unjellyAO unless you like surprising bugs!
311
Alternatively, you can use unjellyInto on your instance's __dict__.
313
self.unjellyInto(instance.__dict__, attrName, ao)
315
def unjellyAO(self, ao):
316
"""Unjelly an Abstract Object and everything it contains.
317
I return the real object.
319
self.stack.append(ao)
321
if t is types.InstanceType:
325
return reflect.namedModule(ao.name)
327
elif c in [Class, Function] or issubclass(c, type):
328
return reflect.namedObject(ao.name)
330
elif c is InstanceMethod:
332
im_class = reflect.namedObject(ao.klass)
333
im_self = self.unjellyAO(ao.instance)
334
if im_name in im_class.__dict__:
336
return getattr(im_class, im_name)
337
elif isinstance(im_self, crefutil.NotKnown):
338
return crefutil._InstanceMethod(im_name, im_self, im_class)
340
return new.instancemethod(im_class.__dict__[im_name],
344
raise TypeError("instance method changed")
347
klass = reflect.namedObject(ao.klass)
348
state = self.unjellyAO(ao.state)
349
if hasattr(klass, "__setstate__"):
350
inst = new.instance(klass, {})
351
self.callAfter(inst.__setstate__, state)
353
inst = new.instance(klass, state)
357
o = self.unjellyAO(ao.obj) #THIS IS CHANGING THE REF OMG
359
ref = self.references.get(refkey)
361
self.references[refkey] = o
362
elif isinstance(ref, crefutil.NotKnown):
363
ref.resolveDependants(o)
364
self.references[refkey] = o
366
# This happens when you're unjellying from an AOT not read from source
369
raise ValueError("Multiple references with the same ID: %s, %s, %s!" % (ref, refkey, ao))
374
ref = self.references.get(num)
376
der = crefutil._Dereference(num)
377
self.references[num] = der
382
loadfunc = reflect.namedObject(ao.loadfunc)
383
d = self.unjellyLater(ao.state).addCallback(
384
lambda result, _l: apply(_l, result), loadfunc)
389
elif t in _SIMPLE_BUILTINS:
392
elif t is types.ListType:
396
self.unjellyInto(l, len(l)-1, x)
399
elif t is types.TupleType:
404
if isinstance(self.unjellyInto(l, len(l)-1, x), crefutil.NotKnown):
405
tuple_ = crefutil._Tuple
408
elif t is types.DictType:
410
for k,v in ao.items():
411
kvd = crefutil._DictKeyAndValue(d)
412
self.unjellyInto(kvd, 0, k)
413
self.unjellyInto(kvd, 1, v)
417
raise TypeError("Unsupported AOT type: %s" % t)
422
def unjelly(self, ao):
425
self.unjellyInto(l, 0, ao)
426
for callable, v in self.afterUnjelly:
430
log.msg("Error jellying object! Stacktrace follows::")
431
log.msg(string.join(map(repr, self.stack), "\n"))
439
"""Convert an object to an Abstract Object Tree."""
440
return AOTJellier().jelly(obj)
442
def jellyToSource(obj, file=None):
444
Pass me an object and, optionally, a file object.
445
I'll convert the object to an AOT either return it (if no file was
446
specified) or write it to the file.
449
aot = jellyToAOT(obj)
451
file.write(getSource(aot))
453
return getSource(aot)
458
# dict of {id(obj): (obj, node)}
463
def prepareForRef(self, aoref, object):
464
"""I prepare an object for later referencing, by storing its id()
465
and its _AORef in a cache."""
466
self.prepared[id(object)] = aoref
468
def jellyToAO(self, obj):
469
"""I turn an object into an AOT and return it."""
471
self.stack.append(repr(obj))
473
#immutable: We don't care if these have multiple refs!
474
if objType in _SIMPLE_BUILTINS:
477
elif objType is types.MethodType:
478
# TODO: make methods 'prefer' not to jelly the object internally,
479
# so that the object will show up where it's referenced first NOT
481
retval = InstanceMethod(obj.im_func.__name__, reflect.qual(obj.im_class),
482
self.jellyToAO(obj.im_self))
484
elif objType is types.ModuleType:
485
retval = Module(obj.__name__)
487
elif objType is types.ClassType:
488
retval = Class(reflect.qual(obj))
490
elif issubclass(objType, type):
491
retval = Class(reflect.qual(obj))
493
elif objType is types.FunctionType:
494
retval = Function(reflect.fullFuncName(obj))
496
else: #mutable! gotta watch for refs.
498
#Marmalade had the nicety of being able to just stick a 'reference' attribute
499
#on any Node object that was referenced, but in AOT, the referenced object
500
#is *inside* of a Ref call (Ref(num, obj) instead of
501
#<objtype ... reference="1">). The problem is, especially for built-in types,
502
#I can't just assign some attribute to them to give them a refnum. So, I have
503
#to "wrap" a Ref(..) around them later -- that's why I put *everything* that's
504
#mutable inside one. The Ref() class will only print the "Ref(..)" around an
505
#object if it has a Reference explicitly attached.
507
if self.prepared.has_key(id(obj)):
508
oldRef = self.prepared[id(obj)]
510
# it's been referenced already
513
# it hasn't been referenced yet
514
self._ref_id = self._ref_id + 1
520
self.prepareForRef(retval, obj)
522
if objType is types.ListType:
523
retval.setObj(map(self.jellyToAO, obj)) #hah!
525
elif objType is types.TupleType:
526
retval.setObj(tuple(map(self.jellyToAO, obj)))
528
elif objType is types.DictionaryType:
530
for k,v in obj.items():
531
d[self.jellyToAO(k)] = self.jellyToAO(v)
534
elif objType is types.InstanceType:
535
if hasattr(obj, "__getstate__"):
536
state = self.jellyToAO(obj.__getstate__())
538
state = self.jellyToAO(obj.__dict__)
539
retval.setObj(Instance(reflect.qual(obj.__class__), state))
541
elif copy_reg.dispatch_table.has_key(objType):
542
unpickleFunc, state = copy_reg.dispatch_table[objType](obj)
544
retval.setObj(Copyreg( reflect.fullFuncName(unpickleFunc),
545
self.jellyToAO(state)))
548
raise TypeError("Unsupported type: %s" % objType.__name__)
553
def jelly(self, obj):
555
ao = self.jellyToAO(obj)
558
log.msg("Error jellying object! Stacktrace follows::")
559
log.msg(string.join(self.stack, '\n'))