aot.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. # -*- test-case-name: twisted.test.test_persisted -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. AOT: Abstract Object Trees
  6. The source-code-marshallin'est abstract-object-serializin'est persister
  7. this side of Marmalade!
  8. """
  9. from __future__ import division, absolute_import
  10. import types, re
  11. try:
  12. from tokenize import generate_tokens as tokenize
  13. except ImportError:
  14. from tokenize import tokenize
  15. try:
  16. import copy_reg
  17. except:
  18. import copyreg as copy_reg
  19. from twisted.python import reflect, log
  20. from twisted.persisted import crefutil
  21. from twisted.python.compat import unicode, _PY3, _constructMethod
  22. ###########################
  23. # Abstract Object Classes #
  24. ###########################
  25. #"\0" in a getSource means "insert variable-width indention here".
  26. #see `indentify'.
  27. class Named:
  28. def __init__(self, name):
  29. self.name = name
  30. class Class(Named):
  31. def getSource(self):
  32. return "Class(%r)" % self.name
  33. class Function(Named):
  34. def getSource(self):
  35. return "Function(%r)" % self.name
  36. class Module(Named):
  37. def getSource(self):
  38. return "Module(%r)" % self.name
  39. class InstanceMethod:
  40. def __init__(self, name, klass, inst):
  41. if not (isinstance(inst, Ref) or isinstance(inst, Instance) or isinstance(inst, Deref)):
  42. raise TypeError("%s isn't an Instance, Ref, or Deref!" % inst)
  43. self.name = name
  44. self.klass = klass
  45. self.instance = inst
  46. def getSource(self):
  47. return "InstanceMethod(%r, %r, \n\0%s)" % (self.name, self.klass, prettify(self.instance))
  48. class _NoStateObj:
  49. pass
  50. NoStateObj = _NoStateObj()
  51. _SIMPLE_BUILTINS = [
  52. bool, bytes, unicode, int, float, complex, type(None),
  53. slice, type(Ellipsis)
  54. ]
  55. try:
  56. _SIMPLE_BUILTINS.append(long)
  57. except NameError:
  58. pass
  59. class Instance:
  60. def __init__(self, className, __stateObj__=NoStateObj, **state):
  61. if not isinstance(className, str):
  62. raise TypeError("%s isn't a string!" % className)
  63. self.klass = className
  64. if __stateObj__ is not NoStateObj:
  65. self.state = __stateObj__
  66. self.stateIsDict = 0
  67. else:
  68. self.state = state
  69. self.stateIsDict = 1
  70. def getSource(self):
  71. #XXX make state be foo=bar instead of a dict.
  72. if self.stateIsDict:
  73. stateDict = self.state
  74. elif isinstance(self.state, Ref) and isinstance(self.state.obj, dict):
  75. stateDict = self.state.obj
  76. else:
  77. stateDict = None
  78. if stateDict is not None:
  79. try:
  80. return "Instance(%r, %s)" % (self.klass, dictToKW(stateDict))
  81. except NonFormattableDict:
  82. return "Instance(%r, %s)" % (self.klass, prettify(stateDict))
  83. return "Instance(%r, %s)" % (self.klass, prettify(self.state))
  84. class Ref:
  85. def __init__(self, *args):
  86. #blargh, lame.
  87. if len(args) == 2:
  88. self.refnum = args[0]
  89. self.obj = args[1]
  90. elif not args:
  91. self.refnum = None
  92. self.obj = None
  93. def setRef(self, num):
  94. if self.refnum:
  95. raise ValueError("Error setting id %s, I already have %s" % (num, self.refnum))
  96. self.refnum = num
  97. def setObj(self, obj):
  98. if self.obj:
  99. raise ValueError("Error setting obj %s, I already have %s" % (obj, self.obj))
  100. self.obj = obj
  101. def getSource(self):
  102. if self.obj is None:
  103. raise RuntimeError("Don't try to display me before setting an object on me!")
  104. if self.refnum:
  105. return "Ref(%d, \n\0%s)" % (self.refnum, prettify(self.obj))
  106. return prettify(self.obj)
  107. class Deref:
  108. def __init__(self, num):
  109. self.refnum = num
  110. def getSource(self):
  111. return "Deref(%d)" % self.refnum
  112. __repr__ = getSource
  113. class Copyreg:
  114. def __init__(self, loadfunc, state):
  115. self.loadfunc = loadfunc
  116. self.state = state
  117. def getSource(self):
  118. return "Copyreg(%r, %s)" % (self.loadfunc, prettify(self.state))
  119. ###############
  120. # Marshalling #
  121. ###############
  122. def getSource(ao):
  123. """Pass me an AO, I'll return a nicely-formatted source representation."""
  124. return indentify("app = " + prettify(ao))
  125. class NonFormattableDict(Exception):
  126. """A dictionary was not formattable.
  127. """
  128. r = re.compile('[a-zA-Z_][a-zA-Z0-9_]*$')
  129. def dictToKW(d):
  130. out = []
  131. items = list(d.items())
  132. items.sort()
  133. for k, v in items:
  134. if not isinstance(k, str):
  135. raise NonFormattableDict("%r ain't a string" % k)
  136. if not r.match(k):
  137. raise NonFormattableDict("%r ain't an identifier" % k)
  138. out.append(
  139. "\n\0%s=%s," % (k, prettify(v))
  140. )
  141. return ''.join(out)
  142. def prettify(obj):
  143. if hasattr(obj, 'getSource'):
  144. return obj.getSource()
  145. else:
  146. #basic type
  147. t = type(obj)
  148. if t in _SIMPLE_BUILTINS:
  149. return repr(obj)
  150. elif t is dict:
  151. out = ['{']
  152. for k,v in obj.items():
  153. out.append('\n\0%s: %s,' % (prettify(k), prettify(v)))
  154. out.append(len(obj) and '\n\0}' or '}')
  155. return ''.join(out)
  156. elif t is list:
  157. out = ["["]
  158. for x in obj:
  159. out.append('\n\0%s,' % prettify(x))
  160. out.append(len(obj) and '\n\0]' or ']')
  161. return ''.join(out)
  162. elif t is tuple:
  163. out = ["("]
  164. for x in obj:
  165. out.append('\n\0%s,' % prettify(x))
  166. out.append(len(obj) and '\n\0)' or ')')
  167. return ''.join(out)
  168. else:
  169. raise TypeError("Unsupported type %s when trying to prettify %s." % (t, obj))
  170. def indentify(s):
  171. out = []
  172. stack = []
  173. l = ['', s]
  174. for (tokenType, tokenString, (startRow, startColumn),
  175. (endRow, endColumn), logicalLine) in tokenize(l.pop):
  176. if tokenString in ['[', '(', '{']:
  177. stack.append(tokenString)
  178. elif tokenString in [']', ')', '}']:
  179. stack.pop()
  180. if tokenString == '\0':
  181. out.append(' '*len(stack))
  182. else:
  183. out.append(tokenString)
  184. return ''.join(out)
  185. ###########
  186. # Unjelly #
  187. ###########
  188. def unjellyFromAOT(aot):
  189. """
  190. Pass me an Abstract Object Tree, and I'll unjelly it for you.
  191. """
  192. return AOTUnjellier().unjelly(aot)
  193. def unjellyFromSource(stringOrFile):
  194. """
  195. Pass me a string of code or a filename that defines an 'app' variable (in
  196. terms of Abstract Objects!), and I'll execute it and unjelly the resulting
  197. AOT for you, returning a newly unpersisted Application object!
  198. """
  199. ns = {"Instance": Instance,
  200. "InstanceMethod": InstanceMethod,
  201. "Class": Class,
  202. "Function": Function,
  203. "Module": Module,
  204. "Ref": Ref,
  205. "Deref": Deref,
  206. "Copyreg": Copyreg,
  207. }
  208. if hasattr(stringOrFile, "read"):
  209. source = stringOrFile.read()
  210. else:
  211. source = stringOrFile
  212. code = compile(source, "<source>", "exec")
  213. eval(code, ns, ns)
  214. if 'app' in ns:
  215. return unjellyFromAOT(ns['app'])
  216. else:
  217. raise ValueError("%s needs to define an 'app', it didn't!" % stringOrFile)
  218. class AOTUnjellier:
  219. """I handle the unjellying of an Abstract Object Tree.
  220. See AOTUnjellier.unjellyAO
  221. """
  222. def __init__(self):
  223. self.references = {}
  224. self.stack = []
  225. self.afterUnjelly = []
  226. ##
  227. # unjelly helpers (copied pretty much directly from (now deleted) marmalade)
  228. ##
  229. def unjellyLater(self, node):
  230. """Unjelly a node, later.
  231. """
  232. d = crefutil._Defer()
  233. self.unjellyInto(d, 0, node)
  234. return d
  235. def unjellyInto(self, obj, loc, ao):
  236. """Utility method for unjellying one object into another.
  237. This automates the handling of backreferences.
  238. """
  239. o = self.unjellyAO(ao)
  240. obj[loc] = o
  241. if isinstance(o, crefutil.NotKnown):
  242. o.addDependant(obj, loc)
  243. return o
  244. def callAfter(self, callable, result):
  245. if isinstance(result, crefutil.NotKnown):
  246. l = [None]
  247. result.addDependant(l, 1)
  248. else:
  249. l = [result]
  250. self.afterUnjelly.append((callable, l))
  251. def unjellyAttribute(self, instance, attrName, ao):
  252. #XXX this is unused????
  253. """Utility method for unjellying into instances of attributes.
  254. Use this rather than unjellyAO unless you like surprising bugs!
  255. Alternatively, you can use unjellyInto on your instance's __dict__.
  256. """
  257. self.unjellyInto(instance.__dict__, attrName, ao)
  258. def unjellyAO(self, ao):
  259. """Unjelly an Abstract Object and everything it contains.
  260. I return the real object.
  261. """
  262. self.stack.append(ao)
  263. t = type(ao)
  264. if t in _SIMPLE_BUILTINS:
  265. return ao
  266. elif t is list:
  267. l = []
  268. for x in ao:
  269. l.append(None)
  270. self.unjellyInto(l, len(l)-1, x)
  271. return l
  272. elif t is tuple:
  273. l = []
  274. tuple_ = tuple
  275. for x in ao:
  276. l.append(None)
  277. if isinstance(self.unjellyInto(l, len(l)-1, x), crefutil.NotKnown):
  278. tuple_ = crefutil._Tuple
  279. return tuple_(l)
  280. elif t is dict:
  281. d = {}
  282. for k,v in ao.items():
  283. kvd = crefutil._DictKeyAndValue(d)
  284. self.unjellyInto(kvd, 0, k)
  285. self.unjellyInto(kvd, 1, v)
  286. return d
  287. else:
  288. #Abstract Objects
  289. c = ao.__class__
  290. if c is Module:
  291. return reflect.namedModule(ao.name)
  292. elif c in [Class, Function] or issubclass(c, type):
  293. return reflect.namedObject(ao.name)
  294. elif c is InstanceMethod:
  295. im_name = ao.name
  296. im_class = reflect.namedObject(ao.klass)
  297. im_self = self.unjellyAO(ao.instance)
  298. if im_name in im_class.__dict__:
  299. if im_self is None:
  300. return getattr(im_class, im_name)
  301. elif isinstance(im_self, crefutil.NotKnown):
  302. return crefutil._InstanceMethod(im_name, im_self, im_class)
  303. else:
  304. return _constructMethod(im_class, im_name, im_self)
  305. else:
  306. raise TypeError("instance method changed")
  307. elif c is Instance:
  308. klass = reflect.namedObject(ao.klass)
  309. state = self.unjellyAO(ao.state)
  310. if hasattr(klass, "__new__"):
  311. inst = klass.__new__(klass)
  312. else:
  313. inst = _OldStyleInstance(klass)
  314. if hasattr(klass, "__setstate__"):
  315. self.callAfter(inst.__setstate__, state)
  316. else:
  317. inst.__dict__ = state
  318. return inst
  319. elif c is Ref:
  320. o = self.unjellyAO(ao.obj) #THIS IS CHANGING THE REF OMG
  321. refkey = ao.refnum
  322. ref = self.references.get(refkey)
  323. if ref is None:
  324. self.references[refkey] = o
  325. elif isinstance(ref, crefutil.NotKnown):
  326. ref.resolveDependants(o)
  327. self.references[refkey] = o
  328. elif refkey is None:
  329. # This happens when you're unjellying from an AOT not read from source
  330. pass
  331. else:
  332. raise ValueError("Multiple references with the same ID: %s, %s, %s!" % (ref, refkey, ao))
  333. return o
  334. elif c is Deref:
  335. num = ao.refnum
  336. ref = self.references.get(num)
  337. if ref is None:
  338. der = crefutil._Dereference(num)
  339. self.references[num] = der
  340. return der
  341. return ref
  342. elif c is Copyreg:
  343. loadfunc = reflect.namedObject(ao.loadfunc)
  344. d = self.unjellyLater(ao.state).addCallback(
  345. lambda result, _l: _l(*result), loadfunc)
  346. return d
  347. else:
  348. raise TypeError("Unsupported AOT type: %s" % t)
  349. del self.stack[-1]
  350. def unjelly(self, ao):
  351. try:
  352. l = [None]
  353. self.unjellyInto(l, 0, ao)
  354. for func, v in self.afterUnjelly:
  355. func(v[0])
  356. return l[0]
  357. except:
  358. log.msg("Error jellying object! Stacktrace follows::")
  359. log.msg("\n".join(map(repr, self.stack)))
  360. raise
  361. #########
  362. # Jelly #
  363. #########
  364. def jellyToAOT(obj):
  365. """Convert an object to an Abstract Object Tree."""
  366. return AOTJellier().jelly(obj)
  367. def jellyToSource(obj, file=None):
  368. """
  369. Pass me an object and, optionally, a file object.
  370. I'll convert the object to an AOT either return it (if no file was
  371. specified) or write it to the file.
  372. """
  373. aot = jellyToAOT(obj)
  374. if file:
  375. file.write(getSource(aot).encode("utf-8"))
  376. else:
  377. return getSource(aot)
  378. try:
  379. from types import (ClassType as _OldStyleClass,
  380. InstanceType as _OldStyleInstance)
  381. except ImportError:
  382. _OldStyleClass = None
  383. _OldStyleInstance = None
  384. def _classOfMethod(methodObject):
  385. """
  386. Get the associated class of the given method object.
  387. @param methodObject: a bound method
  388. @type methodObject: L{types.MethodType}
  389. @return: a class
  390. @rtype: L{types.ClassType} or L{type}
  391. """
  392. if _PY3:
  393. return methodObject.__self__.__class__
  394. return methodObject.im_class
  395. def _funcOfMethod(methodObject):
  396. """
  397. Get the associated function of the given method object.
  398. @param methodObject: a bound method
  399. @type methodObject: L{types.MethodType}
  400. @return: the function implementing C{methodObject}
  401. @rtype: L{types.FunctionType}
  402. """
  403. if _PY3:
  404. return methodObject.__func__
  405. return methodObject.im_func
  406. def _selfOfMethod(methodObject):
  407. """
  408. Get the object that a bound method is bound to.
  409. @param methodObject: a bound method
  410. @type methodObject: L{types.MethodType}
  411. @return: the C{self} passed to C{methodObject}
  412. @rtype: L{object}
  413. """
  414. if _PY3:
  415. return methodObject.__self__
  416. return methodObject.im_self
  417. class AOTJellier:
  418. def __init__(self):
  419. # dict of {id(obj): (obj, node)}
  420. self.prepared = {}
  421. self._ref_id = 0
  422. self.stack = []
  423. def prepareForRef(self, aoref, object):
  424. """I prepare an object for later referencing, by storing its id()
  425. and its _AORef in a cache."""
  426. self.prepared[id(object)] = aoref
  427. def jellyToAO(self, obj):
  428. """I turn an object into an AOT and return it."""
  429. objType = type(obj)
  430. self.stack.append(repr(obj))
  431. #immutable: We don't care if these have multiple refs!
  432. if objType in _SIMPLE_BUILTINS:
  433. retval = obj
  434. elif objType is types.MethodType:
  435. # TODO: make methods 'prefer' not to jelly the object internally,
  436. # so that the object will show up where it's referenced first NOT
  437. # by a method.
  438. retval = InstanceMethod(_funcOfMethod(obj).__name__,
  439. reflect.qual(_classOfMethod(obj)),
  440. self.jellyToAO(_selfOfMethod(obj)))
  441. elif objType is types.ModuleType:
  442. retval = Module(obj.__name__)
  443. elif objType is _OldStyleClass:
  444. retval = Class(reflect.qual(obj))
  445. elif issubclass(objType, type):
  446. retval = Class(reflect.qual(obj))
  447. elif objType is types.FunctionType:
  448. retval = Function(reflect.fullFuncName(obj))
  449. else: #mutable! gotta watch for refs.
  450. #Marmalade had the nicety of being able to just stick a 'reference' attribute
  451. #on any Node object that was referenced, but in AOT, the referenced object
  452. #is *inside* of a Ref call (Ref(num, obj) instead of
  453. #<objtype ... reference="1">). The problem is, especially for built-in types,
  454. #I can't just assign some attribute to them to give them a refnum. So, I have
  455. #to "wrap" a Ref(..) around them later -- that's why I put *everything* that's
  456. #mutable inside one. The Ref() class will only print the "Ref(..)" around an
  457. #object if it has a Reference explicitly attached.
  458. if id(obj) in self.prepared:
  459. oldRef = self.prepared[id(obj)]
  460. if oldRef.refnum:
  461. # it's been referenced already
  462. key = oldRef.refnum
  463. else:
  464. # it hasn't been referenced yet
  465. self._ref_id = self._ref_id + 1
  466. key = self._ref_id
  467. oldRef.setRef(key)
  468. return Deref(key)
  469. retval = Ref()
  470. def _stateFrom(state):
  471. retval.setObj(Instance(reflect.qual(obj.__class__),
  472. self.jellyToAO(state)))
  473. self.prepareForRef(retval, obj)
  474. if objType is list:
  475. retval.setObj([self.jellyToAO(o) for o in obj]) #hah!
  476. elif objType is tuple:
  477. retval.setObj(tuple(map(self.jellyToAO, obj)))
  478. elif objType is dict:
  479. d = {}
  480. for k,v in obj.items():
  481. d[self.jellyToAO(k)] = self.jellyToAO(v)
  482. retval.setObj(d)
  483. elif objType in copy_reg.dispatch_table:
  484. unpickleFunc, state = copy_reg.dispatch_table[objType](obj)
  485. retval.setObj(Copyreg( reflect.fullFuncName(unpickleFunc),
  486. self.jellyToAO(state)))
  487. elif hasattr(obj, "__getstate__"):
  488. _stateFrom(obj.__getstate__())
  489. elif hasattr(obj, "__dict__"):
  490. _stateFrom(obj.__dict__)
  491. else:
  492. raise TypeError("Unsupported type: %s" % objType.__name__)
  493. del self.stack[-1]
  494. return retval
  495. def jelly(self, obj):
  496. try:
  497. ao = self.jellyToAO(obj)
  498. return ao
  499. except:
  500. log.msg("Error jellying object! Stacktrace follows::")
  501. log.msg('\n'.join(self.stack))
  502. raise