diff options
author | shmel1k <shmel1k@ydb.tech> | 2023-11-26 18:16:14 +0300 |
---|---|---|
committer | shmel1k <shmel1k@ydb.tech> | 2023-11-26 18:43:30 +0300 |
commit | b8cf9e88f4c5c64d9406af533d8948deb050d695 (patch) | |
tree | 218eb61fb3c3b96ec08b4d8cdfef383104a87d63 /contrib/python/Twisted/py3/twisted/persisted/aot.py | |
parent | 523f645a83a0ec97a0332dbc3863bb354c92a328 (diff) | |
download | ydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz |
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py3/twisted/persisted/aot.py')
-rw-r--r-- | contrib/python/Twisted/py3/twisted/persisted/aot.py | 631 |
1 files changed, 631 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py3/twisted/persisted/aot.py b/contrib/python/Twisted/py3/twisted/persisted/aot.py new file mode 100644 index 0000000000..f46866bc02 --- /dev/null +++ b/contrib/python/Twisted/py3/twisted/persisted/aot.py @@ -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 |