aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/Twisted/py3/twisted/spread/flavors.py
diff options
context:
space:
mode:
authorshmel1k <shmel1k@ydb.tech>2023-11-26 18:16:14 +0300
committershmel1k <shmel1k@ydb.tech>2023-11-26 18:43:30 +0300
commitb8cf9e88f4c5c64d9406af533d8948deb050d695 (patch)
tree218eb61fb3c3b96ec08b4d8cdfef383104a87d63 /contrib/python/Twisted/py3/twisted/spread/flavors.py
parent523f645a83a0ec97a0332dbc3863bb354c92a328 (diff)
downloadydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py3/twisted/spread/flavors.py')
-rw-r--r--contrib/python/Twisted/py3/twisted/spread/flavors.py651
1 files changed, 651 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py3/twisted/spread/flavors.py b/contrib/python/Twisted/py3/twisted/spread/flavors.py
new file mode 100644
index 0000000000..ef98fee272
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/spread/flavors.py
@@ -0,0 +1,651 @@
+# -*- test-case-name: twisted.spread.test.test_pb -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This module represents flavors of remotely accessible objects.
+
+Currently this is only objects accessible through Perspective Broker, but will
+hopefully encompass all forms of remote access which can emulate subsets of PB
+(such as XMLRPC or SOAP).
+
+Future Plans: Optimization. Exploitation of new-style object model.
+Optimizations to this module should not affect external-use semantics at all,
+but may have a small impact on users who subclass and override methods.
+
+@author: Glyph Lefkowitz
+"""
+
+
+# NOTE: this module should NOT import pb; it is supposed to be a module which
+# abstractly defines remotely accessible types. Many of these types expect to
+# be serialized by Jelly, but they ought to be accessible through other
+# mechanisms (like XMLRPC)
+
+import sys
+
+from zope.interface import Interface, implementer
+
+from twisted.python import log, reflect
+from twisted.python.compat import cmp, comparable
+from .jelly import (
+ Jellyable,
+ Unjellyable,
+ _createBlank,
+ getInstanceState,
+ setInstanceState,
+ setUnjellyableFactoryForClass,
+ setUnjellyableForClass,
+ setUnjellyableForClassTree,
+ unjellyableRegistry,
+)
+
+# compatibility
+setCopierForClass = setUnjellyableForClass
+setCopierForClassTree = setUnjellyableForClassTree
+setFactoryForClass = setUnjellyableFactoryForClass
+copyTags = unjellyableRegistry
+
+copy_atom = b"copy"
+cache_atom = b"cache"
+cached_atom = b"cached"
+remote_atom = b"remote"
+
+
+class NoSuchMethod(AttributeError):
+ """Raised if there is no such remote method"""
+
+
+class IPBRoot(Interface):
+ """Factory for root Referenceable objects for PB servers."""
+
+ def rootObject(broker):
+ """Return root Referenceable for broker."""
+
+
+class Serializable(Jellyable):
+ """An object that can be passed remotely.
+
+ I am a style of object which can be serialized by Perspective
+ Broker. Objects which wish to be referenceable or copied remotely
+ have to subclass Serializable. However, clients of Perspective
+ Broker will probably not want to directly subclass Serializable; the
+ Flavors of transferable objects are listed below.
+
+ What it means to be \"Serializable\" is that an object can be
+ passed to or returned from a remote method. Certain basic types
+ (dictionaries, lists, tuples, numbers, strings) are serializable by
+ default; however, classes need to choose a specific serialization
+ style: L{Referenceable}, L{Viewable}, L{Copyable} or L{Cacheable}.
+
+ You may also pass C{[lists, dictionaries, tuples]} of L{Serializable}
+ instances to or return them from remote methods, as many levels deep
+ as you like.
+ """
+
+ def processUniqueID(self):
+ """Return an ID which uniquely represents this object for this process.
+
+ By default, this uses the 'id' builtin, but can be overridden to
+ indicate that two values are identity-equivalent (such as proxies
+ for the same object).
+ """
+
+ return id(self)
+
+
+class Referenceable(Serializable):
+ perspective = None
+ """I am an object sent remotely as a direct reference.
+
+ When one of my subclasses is sent as an argument to or returned
+ from a remote method call, I will be serialized by default as a
+ direct reference.
+
+ This means that the peer will be able to call methods on me;
+ a method call xxx() from my peer will be resolved to methods
+ of the name remote_xxx.
+ """
+
+ def remoteMessageReceived(self, broker, message, args, kw):
+ """A remote message has been received. Dispatch it appropriately.
+
+ The default implementation is to dispatch to a method called
+ 'remote_messagename' and call it with the same arguments.
+ """
+ args = broker.unserialize(args)
+ kw = broker.unserialize(kw)
+ # Need this to interoperate with Python 2 clients
+ # which may try to send use keywords where keys are of type
+ # bytes.
+ if [key for key in kw.keys() if isinstance(key, bytes)]:
+ kw = {k.decode("utf8"): v for k, v in kw.items()}
+
+ if not isinstance(message, str):
+ message = message.decode("utf8")
+
+ method = getattr(self, "remote_%s" % message, None)
+ if method is None:
+ raise NoSuchMethod(f"No such method: remote_{message}")
+ try:
+ state = method(*args, **kw)
+ except TypeError:
+ log.msg(f"{method} didn't accept {args} and {kw}")
+ raise
+ return broker.serialize(state, self.perspective)
+
+ def jellyFor(self, jellier):
+ """(internal)
+
+ Return a tuple which will be used as the s-expression to
+ serialize this to a peer.
+ """
+
+ return [b"remote", jellier.invoker.registerReference(self)]
+
+
+@implementer(IPBRoot)
+class Root(Referenceable):
+ """I provide a root object to L{pb.Broker}s for a L{pb.PBClientFactory} or
+ L{pb.PBServerFactory}.
+
+ When a factory produces a L{pb.Broker}, it supplies that
+ L{pb.Broker} with an object named \"root\". That object is obtained
+ by calling my rootObject method.
+ """
+
+ def rootObject(self, broker):
+ """A factory is requesting to publish me as a root object.
+
+ When a factory is sending me as the root object, this
+ method will be invoked to allow per-broker versions of an
+ object. By default I return myself.
+ """
+ return self
+
+
+class ViewPoint(Referenceable):
+ """
+ I act as an indirect reference to an object accessed through a
+ L{pb.IPerspective}.
+
+ Simply put, I combine an object with a perspective so that when a
+ peer calls methods on the object I refer to, the method will be
+ invoked with that perspective as a first argument, so that it can
+ know who is calling it.
+
+ While L{Viewable} objects will be converted to ViewPoints by default
+ when they are returned from or sent as arguments to a remote
+ method, any object may be manually proxied as well. (XXX: Now that
+ this class is no longer named C{Proxy}, this is the only occurrence
+ of the term 'proxied' in this docstring, and may be unclear.)
+
+ This can be useful when dealing with L{pb.IPerspective}s, L{Copyable}s,
+ and L{Cacheable}s. It is legal to implement a method as such on
+ a perspective::
+
+ | def perspective_getViewPointForOther(self, name):
+ | defr = self.service.getPerspectiveRequest(name)
+ | defr.addCallbacks(lambda x, self=self: ViewPoint(self, x), log.msg)
+ | return defr
+
+ This will allow you to have references to Perspective objects in two
+ different ways. One is through the initial 'attach' call -- each
+ peer will have a L{pb.RemoteReference} to their perspective directly. The
+ other is through this method; each peer can get a L{pb.RemoteReference} to
+ all other perspectives in the service; but that L{pb.RemoteReference} will
+ be to a L{ViewPoint}, not directly to the object.
+
+ The practical offshoot of this is that you can implement 2 varieties
+ of remotely callable methods on this Perspective; view_xxx and
+ C{perspective_xxx}. C{view_xxx} methods will follow the rules for
+ ViewPoint methods (see ViewPoint.L{remoteMessageReceived}), and
+ C{perspective_xxx} methods will follow the rules for Perspective
+ methods.
+ """
+
+ def __init__(self, perspective, object):
+ """Initialize me with a Perspective and an Object."""
+ self.perspective = perspective
+ self.object = object
+
+ def processUniqueID(self):
+ """Return an ID unique to a proxy for this perspective+object combination."""
+ return (id(self.perspective), id(self.object))
+
+ def remoteMessageReceived(self, broker, message, args, kw):
+ """A remote message has been received. Dispatch it appropriately.
+
+ The default implementation is to dispatch to a method called
+ 'C{view_messagename}' to my Object and call it on my object with
+ the same arguments, modified by inserting my Perspective as
+ the first argument.
+ """
+ args = broker.unserialize(args, self.perspective)
+ kw = broker.unserialize(kw, self.perspective)
+
+ if not isinstance(message, str):
+ message = message.decode("utf8")
+
+ method = getattr(self.object, "view_%s" % message)
+ try:
+ state = method(*(self.perspective,) + args, **kw)
+ except TypeError:
+ log.msg(f"{method} didn't accept {args} and {kw}")
+ raise
+ rv = broker.serialize(state, self.perspective, method, args, kw)
+ return rv
+
+
+class Viewable(Serializable):
+ """I will be converted to a L{ViewPoint} when passed to or returned from a remote method.
+
+ The beginning of a peer's interaction with a PB Service is always
+ through a perspective. However, if a C{perspective_xxx} method returns
+ a Viewable, it will be serialized to the peer as a response to that
+ method.
+ """
+
+ def jellyFor(self, jellier):
+ """Serialize a L{ViewPoint} for me and the perspective of the given broker."""
+ return ViewPoint(jellier.invoker.serializingPerspective, self).jellyFor(jellier)
+
+
+class Copyable(Serializable):
+ """Subclass me to get copied each time you are returned from or passed to a remote method.
+
+ When I am returned from or passed to a remote method call, I will be
+ converted into data via a set of callbacks (see my methods for more
+ info). That data will then be serialized using Jelly, and sent to
+ the peer.
+
+ The peer will then look up the type to represent this with; see
+ L{RemoteCopy} for details.
+ """
+
+ def getStateToCopy(self):
+ """Gather state to send when I am serialized for a peer.
+
+ I will default to returning self.__dict__. Override this to
+ customize this behavior.
+ """
+
+ return self.__dict__
+
+ def getStateToCopyFor(self, perspective):
+ """
+ Gather state to send when I am serialized for a particular
+ perspective.
+
+ I will default to calling L{getStateToCopy}. Override this to
+ customize this behavior.
+ """
+
+ return self.getStateToCopy()
+
+ def getTypeToCopy(self):
+ """Determine what type tag to send for me.
+
+ By default, send the string representation of my class
+ (package.module.Class); normally this is adequate, but
+ you may override this to change it.
+ """
+
+ return reflect.qual(self.__class__).encode("utf-8")
+
+ def getTypeToCopyFor(self, perspective):
+ """Determine what type tag to send for me.
+
+ By default, defer to self.L{getTypeToCopy}() normally this is
+ adequate, but you may override this to change it.
+ """
+
+ return self.getTypeToCopy()
+
+ def jellyFor(self, jellier):
+ """Assemble type tag and state to copy for this broker.
+
+ This will call L{getTypeToCopyFor} and L{getStateToCopy}, and
+ return an appropriate s-expression to represent me.
+ """
+
+ if jellier.invoker is None:
+ return getInstanceState(self, jellier)
+ p = jellier.invoker.serializingPerspective
+ t = self.getTypeToCopyFor(p)
+ state = self.getStateToCopyFor(p)
+ sxp = jellier.prepare(self)
+ sxp.extend([t, jellier.jelly(state)])
+ return jellier.preserve(self, sxp)
+
+
+class Cacheable(Copyable):
+ """A cached instance.
+
+ This means that it's copied; but there is some logic to make sure
+ that it's only copied once. Additionally, when state is retrieved,
+ it is passed a "proto-reference" to the state as it will exist on
+ the client.
+
+ XXX: The documentation for this class needs work, but it's the most
+ complex part of PB and it is inherently difficult to explain.
+ """
+
+ def getStateToCacheAndObserveFor(self, perspective, observer):
+ """
+ Get state to cache on the client and client-cache reference
+ to observe locally.
+
+ This is similar to getStateToCopyFor, but it additionally
+ passes in a reference to the client-side RemoteCache instance
+ that will be created when it is unserialized. This allows
+ Cacheable instances to keep their RemoteCaches up to date when
+ they change, such that no changes can occur between the point
+ at which the state is initially copied and the client receives
+ it that are not propagated.
+ """
+
+ return self.getStateToCopyFor(perspective)
+
+ def jellyFor(self, jellier):
+ """Return an appropriate tuple to serialize me.
+
+ Depending on whether this broker has cached me or not, this may
+ return either a full state or a reference to an existing cache.
+ """
+ if jellier.invoker is None:
+ return getInstanceState(self, jellier)
+ luid = jellier.invoker.cachedRemotelyAs(self, 1)
+ if luid is None:
+ luid = jellier.invoker.cacheRemotely(self)
+ p = jellier.invoker.serializingPerspective
+ type_ = self.getTypeToCopyFor(p)
+ observer = RemoteCacheObserver(jellier.invoker, self, p)
+ state = self.getStateToCacheAndObserveFor(p, observer)
+ l = jellier.prepare(self)
+ jstate = jellier.jelly(state)
+ l.extend([type_, luid, jstate])
+ return jellier.preserve(self, l)
+ else:
+ return cached_atom, luid
+
+ def stoppedObserving(self, perspective, observer):
+ """This method is called when a client has stopped observing me.
+
+ The 'observer' argument is the same as that passed in to
+ getStateToCacheAndObserveFor.
+ """
+
+
+class RemoteCopy(Unjellyable):
+ """I am a remote copy of a Copyable object.
+
+ When the state from a L{Copyable} object is received, an instance will
+ be created based on the copy tags table (see setUnjellyableForClass) and
+ sent the L{setCopyableState} message. I provide a reasonable default
+ implementation of that message; subclass me if you wish to serve as
+ a copier for remote data.
+
+ NOTE: copiers are invoked with no arguments. Do not implement a
+ constructor which requires args in a subclass of L{RemoteCopy}!
+ """
+
+ def setCopyableState(self, state):
+ """I will be invoked with the state to copy locally.
+
+ 'state' is the data returned from the remote object's
+ 'getStateToCopyFor' method, which will often be the remote
+ object's dictionary (or a filtered approximation of it depending
+ on my peer's perspective).
+ """
+ if not state:
+ state = {}
+ state = {
+ x.decode("utf8") if isinstance(x, bytes) else x: y for x, y in state.items()
+ }
+ self.__dict__ = state
+
+ def unjellyFor(self, unjellier, jellyList):
+ if unjellier.invoker is None:
+ return setInstanceState(self, unjellier, jellyList)
+ self.setCopyableState(unjellier.unjelly(jellyList[1]))
+ return self
+
+
+class RemoteCache(RemoteCopy, Serializable):
+ """A cache is a local representation of a remote L{Cacheable} object.
+
+ This represents the last known state of this object. It may
+ also have methods invoked on it -- in order to update caches,
+ the cached class generates a L{pb.RemoteReference} to this object as
+ it is originally sent.
+
+ Much like copy, I will be invoked with no arguments. Do not
+ implement a constructor that requires arguments in one of my
+ subclasses.
+ """
+
+ def remoteMessageReceived(self, broker, message, args, kw):
+ """A remote message has been received. Dispatch it appropriately.
+
+ The default implementation is to dispatch to a method called
+ 'C{observe_messagename}' and call it on my with the same arguments.
+ """
+ if not isinstance(message, str):
+ message = message.decode("utf8")
+
+ args = broker.unserialize(args)
+ kw = broker.unserialize(kw)
+ method = getattr(self, "observe_%s" % message)
+ try:
+ state = method(*args, **kw)
+ except TypeError:
+ log.msg(f"{method} didn't accept {args} and {kw}")
+ raise
+ return broker.serialize(state, None, method, args, kw)
+
+ def jellyFor(self, jellier):
+ """serialize me (only for the broker I'm for) as the original cached reference"""
+ if jellier.invoker is None:
+ return getInstanceState(self, jellier)
+ assert (
+ jellier.invoker is self.broker
+ ), "You cannot exchange cached proxies between brokers."
+ return b"lcache", self.luid
+
+ def unjellyFor(self, unjellier, jellyList):
+ if unjellier.invoker is None:
+ return setInstanceState(self, unjellier, jellyList)
+ self.broker = unjellier.invoker
+ self.luid = jellyList[1]
+ borgCopy = self._borgify()
+ # XXX questionable whether this was a good design idea...
+ init = getattr(borgCopy, "__init__", None)
+ if init:
+ init()
+ unjellier.invoker.cacheLocally(jellyList[1], self)
+ borgCopy.setCopyableState(unjellier.unjelly(jellyList[2]))
+ # Might have changed due to setCopyableState method; we'll assume that
+ # it's bad form to do so afterwards.
+ self.__dict__ = borgCopy.__dict__
+ # chomp, chomp -- some existing code uses "self.__dict__ =", some uses
+ # "__dict__.update". This is here in order to handle both cases.
+ self.broker = unjellier.invoker
+ self.luid = jellyList[1]
+ return borgCopy
+
+ ## def __really_del__(self):
+ ## """Final finalization call, made after all remote references have been lost.
+ ## """
+
+ def __cmp__(self, other):
+ """Compare me [to another RemoteCache."""
+ if isinstance(other, self.__class__):
+ return cmp(id(self.__dict__), id(other.__dict__))
+ else:
+ return cmp(id(self.__dict__), other)
+
+ def __hash__(self):
+ """Hash me."""
+ return int(id(self.__dict__) % sys.maxsize)
+
+ broker = None
+ luid = None
+
+ def __del__(self):
+ """Do distributed reference counting on finalize."""
+ try:
+ # log.msg( ' --- decache: %s %s' % (self, self.luid) )
+ if self.broker:
+ self.broker.decCacheRef(self.luid)
+ except BaseException:
+ log.deferr()
+
+ def _borgify(self):
+ """
+ Create a new object that shares its state (i.e. its C{__dict__}) and
+ type with this object, but does not share its identity.
+
+ This is an instance of U{the Borg design pattern
+ <https://code.activestate.com/recipes/66531/>} originally described by
+ Alex Martelli, but unlike the example given there, this is not a
+ replacement for a Singleton. Instead, it is for lifecycle tracking
+ (and distributed garbage collection). The purpose of these separate
+ objects is to have a separate object tracking each application-level
+ reference to the root L{RemoteCache} object being tracked by the
+ broker, and to have their C{__del__} methods be invoked.
+
+ This may be achievable via a weak value dictionary to track the root
+ L{RemoteCache} instances instead, but this implementation strategy
+ predates the availability of weak references in Python.
+
+ @return: The new instance.
+ @rtype: C{self.__class__}
+ """
+ blank = _createBlank(self.__class__)
+ blank.__dict__ = self.__dict__
+ return blank
+
+
+def unjellyCached(unjellier, unjellyList):
+ luid = unjellyList[1]
+ return unjellier.invoker.cachedLocallyAs(luid)._borgify()
+
+
+setUnjellyableForClass("cached", unjellyCached)
+
+
+def unjellyLCache(unjellier, unjellyList):
+ luid = unjellyList[1]
+ obj = unjellier.invoker.remotelyCachedForLUID(luid)
+ return obj
+
+
+setUnjellyableForClass("lcache", unjellyLCache)
+
+
+def unjellyLocal(unjellier, unjellyList):
+ obj = unjellier.invoker.localObjectForID(unjellyList[1])
+ return obj
+
+
+setUnjellyableForClass("local", unjellyLocal)
+
+
+@comparable
+class RemoteCacheMethod:
+ """A method on a reference to a L{RemoteCache}."""
+
+ def __init__(self, name, broker, cached, perspective):
+ """(internal) initialize."""
+ self.name = name
+ self.broker = broker
+ self.perspective = perspective
+ self.cached = cached
+
+ def __cmp__(self, other):
+ return cmp((self.name, self.broker, self.perspective, self.cached), other)
+
+ def __hash__(self):
+ return hash((self.name, self.broker, self.perspective, self.cached))
+
+ def __call__(self, *args, **kw):
+ """(internal) action method."""
+ cacheID = self.broker.cachedRemotelyAs(self.cached)
+ if cacheID is None:
+ from pb import ProtocolError # type: ignore[import]
+
+ raise ProtocolError(
+ "You can't call a cached method when the object hasn't been given to the peer yet."
+ )
+ return self.broker._sendMessage(
+ b"cache", self.perspective, cacheID, self.name, args, kw
+ )
+
+
+@comparable
+class RemoteCacheObserver:
+ """I am a reverse-reference to the peer's L{RemoteCache}.
+
+ I am generated automatically when a cache is serialized. I
+ represent a reference to the client's L{RemoteCache} object that
+ will represent a particular L{Cacheable}; I am the additional
+ object passed to getStateToCacheAndObserveFor.
+ """
+
+ def __init__(self, broker, cached, perspective):
+ """(internal) Initialize me.
+
+ @param broker: a L{pb.Broker} instance.
+
+ @param cached: a L{Cacheable} instance that this L{RemoteCacheObserver}
+ corresponds to.
+
+ @param perspective: a reference to the perspective who is observing this.
+ """
+
+ self.broker = broker
+ self.cached = cached
+ self.perspective = perspective
+
+ def __repr__(self) -> str:
+ return "<RemoteCacheObserver({}, {}, {}) at {}>".format(
+ self.broker,
+ self.cached,
+ self.perspective,
+ id(self),
+ )
+
+ def __hash__(self):
+ """Generate a hash unique to all L{RemoteCacheObserver}s for this broker/perspective/cached triplet"""
+
+ return (
+ (hash(self.broker) % 2**10)
+ + (hash(self.perspective) % 2**10)
+ + (hash(self.cached) % 2**10)
+ )
+
+ def __cmp__(self, other):
+ """Compare me to another L{RemoteCacheObserver}."""
+
+ return cmp((self.broker, self.perspective, self.cached), other)
+
+ def callRemote(self, _name, *args, **kw):
+ """(internal) action method."""
+ cacheID = self.broker.cachedRemotelyAs(self.cached)
+ if isinstance(_name, str):
+ _name = _name.encode("utf-8")
+ if cacheID is None:
+ from pb import ProtocolError
+
+ raise ProtocolError(
+ "You can't call a cached method when the "
+ "object hasn't been given to the peer yet."
+ )
+ return self.broker._sendMessage(
+ b"cache", self.perspective, cacheID, _name, args, kw
+ )
+
+ def remoteMethod(self, key):
+ """Get a L{pb.RemoteMethod} for this key."""
+ return RemoteCacheMethod(key, self.broker, self.cached, self.perspective)