RAHHH
This commit is contained in:
631
backend/lib/python3.12/site-packages/twisted/persisted/aot.py
Normal file
631
backend/lib/python3.12/site-packages/twisted/persisted/aot.py
Normal file
@@ -0,0 +1,631 @@
|
||||
# -*- test-case-name: twisted.test.test_persisted -*-
|
||||
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
AOT: Abstract Object Trees
|
||||
The source-code-marshallin'est abstract-object-serializin'est persister
|
||||
this side of Marmalade!
|
||||
"""
|
||||
|
||||
|
||||
import copyreg as copy_reg
|
||||
import re
|
||||
import types
|
||||
|
||||
from twisted.persisted import crefutil
|
||||
from twisted.python import log, reflect
|
||||
from twisted.python.compat import _constructMethod
|
||||
|
||||
# tokenize from py3.11 is vendored to work around https://github.com/python/cpython/issues/105238
|
||||
# on 3.12
|
||||
from ._tokenize import generate_tokens as tokenize
|
||||
|
||||
###########################
|
||||
# Abstract Object Classes #
|
||||
###########################
|
||||
|
||||
# "\0" in a getSource means "insert variable-width indention here".
|
||||
# see `indentify'.
|
||||
|
||||
|
||||
class Named:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
|
||||
class Class(Named):
|
||||
def getSource(self):
|
||||
return "Class(%r)" % self.name
|
||||
|
||||
|
||||
class Function(Named):
|
||||
def getSource(self):
|
||||
return "Function(%r)" % self.name
|
||||
|
||||
|
||||
class Module(Named):
|
||||
def getSource(self):
|
||||
return "Module(%r)" % self.name
|
||||
|
||||
|
||||
class InstanceMethod:
|
||||
def __init__(self, name, klass, inst):
|
||||
if not (
|
||||
isinstance(inst, Ref)
|
||||
or isinstance(inst, Instance)
|
||||
or isinstance(inst, Deref)
|
||||
):
|
||||
raise TypeError("%s isn't an Instance, Ref, or Deref!" % inst)
|
||||
self.name = name
|
||||
self.klass = klass
|
||||
self.instance = inst
|
||||
|
||||
def getSource(self):
|
||||
return "InstanceMethod({!r}, {!r}, \n\0{})".format(
|
||||
self.name,
|
||||
self.klass,
|
||||
prettify(self.instance),
|
||||
)
|
||||
|
||||
|
||||
class _NoStateObj:
|
||||
pass
|
||||
|
||||
|
||||
NoStateObj = _NoStateObj()
|
||||
|
||||
_SIMPLE_BUILTINS = [
|
||||
bool,
|
||||
bytes,
|
||||
str,
|
||||
int,
|
||||
float,
|
||||
complex,
|
||||
type(None),
|
||||
slice,
|
||||
type(Ellipsis),
|
||||
]
|
||||
|
||||
|
||||
class Instance:
|
||||
def __init__(self, className, __stateObj__=NoStateObj, **state):
|
||||
if not isinstance(className, str):
|
||||
raise TypeError("%s isn't a string!" % className)
|
||||
self.klass = className
|
||||
if __stateObj__ is not NoStateObj:
|
||||
self.state = __stateObj__
|
||||
self.stateIsDict = 0
|
||||
else:
|
||||
self.state = state
|
||||
self.stateIsDict = 1
|
||||
|
||||
def getSource(self):
|
||||
# XXX make state be foo=bar instead of a dict.
|
||||
if self.stateIsDict:
|
||||
stateDict = self.state
|
||||
elif isinstance(self.state, Ref) and isinstance(self.state.obj, dict):
|
||||
stateDict = self.state.obj
|
||||
else:
|
||||
stateDict = None
|
||||
if stateDict is not None:
|
||||
try:
|
||||
return f"Instance({self.klass!r}, {dictToKW(stateDict)})"
|
||||
except NonFormattableDict:
|
||||
return f"Instance({self.klass!r}, {prettify(stateDict)})"
|
||||
return f"Instance({self.klass!r}, {prettify(self.state)})"
|
||||
|
||||
|
||||
class Ref:
|
||||
def __init__(self, *args):
|
||||
# blargh, lame.
|
||||
if len(args) == 2:
|
||||
self.refnum = args[0]
|
||||
self.obj = args[1]
|
||||
elif not args:
|
||||
self.refnum = None
|
||||
self.obj = None
|
||||
|
||||
def setRef(self, num):
|
||||
if self.refnum:
|
||||
raise ValueError(f"Error setting id {num}, I already have {self.refnum}")
|
||||
self.refnum = num
|
||||
|
||||
def setObj(self, obj):
|
||||
if self.obj:
|
||||
raise ValueError(f"Error setting obj {obj}, I already have {self.obj}")
|
||||
self.obj = obj
|
||||
|
||||
def getSource(self):
|
||||
if self.obj is None:
|
||||
raise RuntimeError(
|
||||
"Don't try to display me before setting an object on me!"
|
||||
)
|
||||
if self.refnum:
|
||||
return "Ref(%d, \n\0%s)" % (self.refnum, prettify(self.obj))
|
||||
return prettify(self.obj)
|
||||
|
||||
|
||||
class Deref:
|
||||
def __init__(self, num):
|
||||
self.refnum = num
|
||||
|
||||
def getSource(self):
|
||||
return "Deref(%d)" % self.refnum
|
||||
|
||||
__repr__ = getSource
|
||||
|
||||
|
||||
class Copyreg:
|
||||
def __init__(self, loadfunc, state):
|
||||
self.loadfunc = loadfunc
|
||||
self.state = state
|
||||
|
||||
def getSource(self):
|
||||
return f"Copyreg({self.loadfunc!r}, {prettify(self.state)})"
|
||||
|
||||
|
||||
###############
|
||||
# Marshalling #
|
||||
###############
|
||||
|
||||
|
||||
def getSource(ao):
|
||||
"""Pass me an AO, I'll return a nicely-formatted source representation."""
|
||||
return indentify("app = " + prettify(ao))
|
||||
|
||||
|
||||
class NonFormattableDict(Exception):
|
||||
"""A dictionary was not formattable."""
|
||||
|
||||
|
||||
r = re.compile("[a-zA-Z_][a-zA-Z0-9_]*$")
|
||||
|
||||
|
||||
def dictToKW(d):
|
||||
out = []
|
||||
items = list(d.items())
|
||||
items.sort()
|
||||
for k, v in items:
|
||||
if not isinstance(k, str):
|
||||
raise NonFormattableDict("%r ain't a string" % k)
|
||||
if not r.match(k):
|
||||
raise NonFormattableDict("%r ain't an identifier" % k)
|
||||
out.append(f"\n\0{k}={prettify(v)},")
|
||||
return "".join(out)
|
||||
|
||||
|
||||
def prettify(obj):
|
||||
if hasattr(obj, "getSource"):
|
||||
return obj.getSource()
|
||||
else:
|
||||
# basic type
|
||||
t = type(obj)
|
||||
|
||||
if t in _SIMPLE_BUILTINS:
|
||||
return repr(obj)
|
||||
|
||||
elif t is dict:
|
||||
out = ["{"]
|
||||
for k, v in obj.items():
|
||||
out.append(f"\n\0{prettify(k)}: {prettify(v)},")
|
||||
out.append(len(obj) and "\n\0}" or "}")
|
||||
return "".join(out)
|
||||
|
||||
elif t is list:
|
||||
out = ["["]
|
||||
for x in obj:
|
||||
out.append("\n\0%s," % prettify(x))
|
||||
out.append(len(obj) and "\n\0]" or "]")
|
||||
return "".join(out)
|
||||
|
||||
elif t is tuple:
|
||||
out = ["("]
|
||||
for x in obj:
|
||||
out.append("\n\0%s," % prettify(x))
|
||||
out.append(len(obj) and "\n\0)" or ")")
|
||||
return "".join(out)
|
||||
else:
|
||||
raise TypeError(f"Unsupported type {t} when trying to prettify {obj}.")
|
||||
|
||||
|
||||
def indentify(s):
|
||||
out = []
|
||||
stack = []
|
||||
l = ["", s]
|
||||
for (
|
||||
tokenType,
|
||||
tokenString,
|
||||
(startRow, startColumn),
|
||||
(endRow, endColumn),
|
||||
logicalLine,
|
||||
) in tokenize(l.pop):
|
||||
if tokenString in ["[", "(", "{"]:
|
||||
stack.append(tokenString)
|
||||
elif tokenString in ["]", ")", "}"]:
|
||||
stack.pop()
|
||||
if tokenString == "\0":
|
||||
out.append(" " * len(stack))
|
||||
else:
|
||||
out.append(tokenString)
|
||||
return "".join(out)
|
||||
|
||||
|
||||
###########
|
||||
# Unjelly #
|
||||
###########
|
||||
|
||||
|
||||
def unjellyFromAOT(aot):
|
||||
"""
|
||||
Pass me an Abstract Object Tree, and I'll unjelly it for you.
|
||||
"""
|
||||
return AOTUnjellier().unjelly(aot)
|
||||
|
||||
|
||||
def unjellyFromSource(stringOrFile):
|
||||
"""
|
||||
Pass me a string of code or a filename that defines an 'app' variable (in
|
||||
terms of Abstract Objects!), and I'll execute it and unjelly the resulting
|
||||
AOT for you, returning a newly unpersisted Application object!
|
||||
"""
|
||||
|
||||
ns = {
|
||||
"Instance": Instance,
|
||||
"InstanceMethod": InstanceMethod,
|
||||
"Class": Class,
|
||||
"Function": Function,
|
||||
"Module": Module,
|
||||
"Ref": Ref,
|
||||
"Deref": Deref,
|
||||
"Copyreg": Copyreg,
|
||||
}
|
||||
|
||||
if hasattr(stringOrFile, "read"):
|
||||
source = stringOrFile.read()
|
||||
else:
|
||||
source = stringOrFile
|
||||
code = compile(source, "<source>", "exec")
|
||||
eval(code, ns, ns)
|
||||
|
||||
if "app" in ns:
|
||||
return unjellyFromAOT(ns["app"])
|
||||
else:
|
||||
raise ValueError("%s needs to define an 'app', it didn't!" % stringOrFile)
|
||||
|
||||
|
||||
class AOTUnjellier:
|
||||
"""I handle the unjellying of an Abstract Object Tree.
|
||||
See AOTUnjellier.unjellyAO
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.references = {}
|
||||
self.stack = []
|
||||
self.afterUnjelly = []
|
||||
|
||||
##
|
||||
# unjelly helpers (copied pretty much directly from (now deleted) marmalade)
|
||||
##
|
||||
def unjellyLater(self, node):
|
||||
"""Unjelly a node, later."""
|
||||
d = crefutil._Defer()
|
||||
self.unjellyInto(d, 0, node)
|
||||
return d
|
||||
|
||||
def unjellyInto(self, obj, loc, ao):
|
||||
"""Utility method for unjellying one object into another.
|
||||
This automates the handling of backreferences.
|
||||
"""
|
||||
o = self.unjellyAO(ao)
|
||||
obj[loc] = o
|
||||
if isinstance(o, crefutil.NotKnown):
|
||||
o.addDependant(obj, loc)
|
||||
return o
|
||||
|
||||
def callAfter(self, callable, result):
|
||||
if isinstance(result, crefutil.NotKnown):
|
||||
listResult = [None]
|
||||
result.addDependant(listResult, 1)
|
||||
else:
|
||||
listResult = [result]
|
||||
self.afterUnjelly.append((callable, listResult))
|
||||
|
||||
def unjellyAttribute(self, instance, attrName, ao):
|
||||
# XXX this is unused????
|
||||
"""Utility method for unjellying into instances of attributes.
|
||||
|
||||
Use this rather than unjellyAO unless you like surprising bugs!
|
||||
Alternatively, you can use unjellyInto on your instance's __dict__.
|
||||
"""
|
||||
self.unjellyInto(instance.__dict__, attrName, ao)
|
||||
|
||||
def unjellyAO(self, ao):
|
||||
"""Unjelly an Abstract Object and everything it contains.
|
||||
I return the real object.
|
||||
"""
|
||||
self.stack.append(ao)
|
||||
t = type(ao)
|
||||
if t in _SIMPLE_BUILTINS:
|
||||
return ao
|
||||
|
||||
elif t is list:
|
||||
l = []
|
||||
for x in ao:
|
||||
l.append(None)
|
||||
self.unjellyInto(l, len(l) - 1, x)
|
||||
return l
|
||||
|
||||
elif t is tuple:
|
||||
l = []
|
||||
tuple_ = tuple
|
||||
for x in ao:
|
||||
l.append(None)
|
||||
if isinstance(self.unjellyInto(l, len(l) - 1, x), crefutil.NotKnown):
|
||||
tuple_ = crefutil._Tuple
|
||||
return tuple_(l)
|
||||
|
||||
elif t is dict:
|
||||
d = {}
|
||||
for k, v in ao.items():
|
||||
kvd = crefutil._DictKeyAndValue(d)
|
||||
self.unjellyInto(kvd, 0, k)
|
||||
self.unjellyInto(kvd, 1, v)
|
||||
return d
|
||||
else:
|
||||
# Abstract Objects
|
||||
c = ao.__class__
|
||||
if c is Module:
|
||||
return reflect.namedModule(ao.name)
|
||||
|
||||
elif c in [Class, Function] or issubclass(c, type):
|
||||
return reflect.namedObject(ao.name)
|
||||
|
||||
elif c is InstanceMethod:
|
||||
im_name = ao.name
|
||||
im_class = reflect.namedObject(ao.klass)
|
||||
im_self = self.unjellyAO(ao.instance)
|
||||
if im_name in im_class.__dict__:
|
||||
if im_self is None:
|
||||
return getattr(im_class, im_name)
|
||||
elif isinstance(im_self, crefutil.NotKnown):
|
||||
return crefutil._InstanceMethod(im_name, im_self, im_class)
|
||||
else:
|
||||
return _constructMethod(im_class, im_name, im_self)
|
||||
else:
|
||||
raise TypeError("instance method changed")
|
||||
|
||||
elif c is Instance:
|
||||
klass = reflect.namedObject(ao.klass)
|
||||
state = self.unjellyAO(ao.state)
|
||||
inst = klass.__new__(klass)
|
||||
if hasattr(klass, "__setstate__"):
|
||||
self.callAfter(inst.__setstate__, state)
|
||||
elif isinstance(state, dict):
|
||||
inst.__dict__ = state
|
||||
else:
|
||||
inst.__dict__ = state.__getstate__()
|
||||
return inst
|
||||
|
||||
elif c is Ref:
|
||||
o = self.unjellyAO(ao.obj) # THIS IS CHANGING THE REF OMG
|
||||
refkey = ao.refnum
|
||||
ref = self.references.get(refkey)
|
||||
if ref is None:
|
||||
self.references[refkey] = o
|
||||
elif isinstance(ref, crefutil.NotKnown):
|
||||
ref.resolveDependants(o)
|
||||
self.references[refkey] = o
|
||||
elif refkey is None:
|
||||
# This happens when you're unjellying from an AOT not read from source
|
||||
pass
|
||||
else:
|
||||
raise ValueError(
|
||||
"Multiple references with the same ID: %s, %s, %s!"
|
||||
% (ref, refkey, ao)
|
||||
)
|
||||
return o
|
||||
|
||||
elif c is Deref:
|
||||
num = ao.refnum
|
||||
ref = self.references.get(num)
|
||||
if ref is None:
|
||||
der = crefutil._Dereference(num)
|
||||
self.references[num] = der
|
||||
return der
|
||||
return ref
|
||||
|
||||
elif c is Copyreg:
|
||||
loadfunc = reflect.namedObject(ao.loadfunc)
|
||||
d = self.unjellyLater(ao.state).addCallback(
|
||||
lambda result, _l: _l(*result), loadfunc
|
||||
)
|
||||
return d
|
||||
else:
|
||||
raise TypeError("Unsupported AOT type: %s" % t)
|
||||
|
||||
def unjelly(self, ao):
|
||||
try:
|
||||
l = [None]
|
||||
self.unjellyInto(l, 0, ao)
|
||||
for func, v in self.afterUnjelly:
|
||||
func(v[0])
|
||||
return l[0]
|
||||
except BaseException:
|
||||
log.msg("Error jellying object! Stacktrace follows::")
|
||||
log.msg("\n".join(map(repr, self.stack)))
|
||||
raise
|
||||
|
||||
|
||||
#########
|
||||
# Jelly #
|
||||
#########
|
||||
|
||||
|
||||
def jellyToAOT(obj):
|
||||
"""Convert an object to an Abstract Object Tree."""
|
||||
return AOTJellier().jelly(obj)
|
||||
|
||||
|
||||
def jellyToSource(obj, file=None):
|
||||
"""
|
||||
Pass me an object and, optionally, a file object.
|
||||
I'll convert the object to an AOT either return it (if no file was
|
||||
specified) or write it to the file.
|
||||
"""
|
||||
|
||||
aot = jellyToAOT(obj)
|
||||
if file:
|
||||
file.write(getSource(aot).encode("utf-8"))
|
||||
else:
|
||||
return getSource(aot)
|
||||
|
||||
|
||||
def _classOfMethod(methodObject):
|
||||
"""
|
||||
Get the associated class of the given method object.
|
||||
|
||||
@param methodObject: a bound method
|
||||
@type methodObject: L{types.MethodType}
|
||||
|
||||
@return: a class
|
||||
@rtype: L{type}
|
||||
"""
|
||||
return methodObject.__self__.__class__
|
||||
|
||||
|
||||
def _funcOfMethod(methodObject):
|
||||
"""
|
||||
Get the associated function of the given method object.
|
||||
|
||||
@param methodObject: a bound method
|
||||
@type methodObject: L{types.MethodType}
|
||||
|
||||
@return: the function implementing C{methodObject}
|
||||
@rtype: L{types.FunctionType}
|
||||
"""
|
||||
return methodObject.__func__
|
||||
|
||||
|
||||
def _selfOfMethod(methodObject):
|
||||
"""
|
||||
Get the object that a bound method is bound to.
|
||||
|
||||
@param methodObject: a bound method
|
||||
@type methodObject: L{types.MethodType}
|
||||
|
||||
@return: the C{self} passed to C{methodObject}
|
||||
@rtype: L{object}
|
||||
"""
|
||||
return methodObject.__self__
|
||||
|
||||
|
||||
class AOTJellier:
|
||||
def __init__(self):
|
||||
# dict of {id(obj): (obj, node)}
|
||||
self.prepared = {}
|
||||
self._ref_id = 0
|
||||
self.stack = []
|
||||
|
||||
def prepareForRef(self, aoref, object):
|
||||
"""I prepare an object for later referencing, by storing its id()
|
||||
and its _AORef in a cache."""
|
||||
self.prepared[id(object)] = aoref
|
||||
|
||||
def jellyToAO(self, obj):
|
||||
"""I turn an object into an AOT and return it."""
|
||||
objType = type(obj)
|
||||
self.stack.append(repr(obj))
|
||||
|
||||
# immutable: We don't care if these have multiple refs!
|
||||
if objType in _SIMPLE_BUILTINS:
|
||||
retval = obj
|
||||
|
||||
elif issubclass(objType, types.MethodType):
|
||||
# TODO: make methods 'prefer' not to jelly the object internally,
|
||||
# so that the object will show up where it's referenced first NOT
|
||||
# by a method.
|
||||
retval = InstanceMethod(
|
||||
_funcOfMethod(obj).__name__,
|
||||
reflect.qual(_classOfMethod(obj)),
|
||||
self.jellyToAO(_selfOfMethod(obj)),
|
||||
)
|
||||
|
||||
elif issubclass(objType, types.ModuleType):
|
||||
retval = Module(obj.__name__)
|
||||
|
||||
elif issubclass(objType, type):
|
||||
retval = Class(reflect.qual(obj))
|
||||
|
||||
elif objType is types.FunctionType:
|
||||
retval = Function(reflect.fullFuncName(obj))
|
||||
|
||||
else: # mutable! gotta watch for refs.
|
||||
# Marmalade had the nicety of being able to just stick a 'reference' attribute
|
||||
# on any Node object that was referenced, but in AOT, the referenced object
|
||||
# is *inside* of a Ref call (Ref(num, obj) instead of
|
||||
# <objtype ... reference="1">). The problem is, especially for built-in types,
|
||||
# I can't just assign some attribute to them to give them a refnum. So, I have
|
||||
# to "wrap" a Ref(..) around them later -- that's why I put *everything* that's
|
||||
# mutable inside one. The Ref() class will only print the "Ref(..)" around an
|
||||
# object if it has a Reference explicitly attached.
|
||||
|
||||
if id(obj) in self.prepared:
|
||||
oldRef = self.prepared[id(obj)]
|
||||
if oldRef.refnum:
|
||||
# it's been referenced already
|
||||
key = oldRef.refnum
|
||||
else:
|
||||
# it hasn't been referenced yet
|
||||
self._ref_id = self._ref_id + 1
|
||||
key = self._ref_id
|
||||
oldRef.setRef(key)
|
||||
return Deref(key)
|
||||
|
||||
retval = Ref()
|
||||
|
||||
def _stateFrom(state):
|
||||
retval.setObj(
|
||||
Instance(reflect.qual(obj.__class__), self.jellyToAO(state))
|
||||
)
|
||||
|
||||
self.prepareForRef(retval, obj)
|
||||
|
||||
if objType is list:
|
||||
retval.setObj([self.jellyToAO(o) for o in obj]) # hah!
|
||||
|
||||
elif objType is tuple:
|
||||
retval.setObj(tuple(map(self.jellyToAO, obj)))
|
||||
|
||||
elif objType is dict:
|
||||
d = {}
|
||||
for k, v in obj.items():
|
||||
d[self.jellyToAO(k)] = self.jellyToAO(v)
|
||||
retval.setObj(d)
|
||||
|
||||
elif objType in copy_reg.dispatch_table:
|
||||
unpickleFunc, state = copy_reg.dispatch_table[objType](obj)
|
||||
|
||||
retval.setObj(
|
||||
Copyreg(reflect.fullFuncName(unpickleFunc), self.jellyToAO(state))
|
||||
)
|
||||
|
||||
elif hasattr(obj, "__getstate__"):
|
||||
_stateFrom(obj.__getstate__())
|
||||
elif hasattr(obj, "__dict__"):
|
||||
_stateFrom(obj.__dict__)
|
||||
else:
|
||||
raise TypeError("Unsupported type: %s" % objType.__name__)
|
||||
|
||||
del self.stack[-1]
|
||||
return retval
|
||||
|
||||
def jelly(self, obj):
|
||||
try:
|
||||
ao = self.jellyToAO(obj)
|
||||
return ao
|
||||
except BaseException:
|
||||
log.msg("Error jellying object! Stacktrace follows::")
|
||||
log.msg("\n".join(self.stack))
|
||||
raise
|
||||
Reference in New Issue
Block a user