From 592bc26b99eb2fc9413f89fdcdcc73dd6d48f8ca Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 14 Oct 2012 11:53:34 +0200 Subject: [PATCH] Added remote function call abilities to AMP protocol, courtesy of patch by user Shell. This allows for Server to call functions on Portal and vice-versa. Some rewrites and cleanup done before applying /Griatch. --- src/server/amp.py | 62 +++++++++++++++++++++++++++++++++++++++++++++- src/utils/utils.py | 14 ++++++++--- 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/src/server/amp.py b/src/server/amp.py index 34b3261955..bcd994ea87 100644 --- a/src/server/amp.py +++ b/src/server/amp.py @@ -22,7 +22,8 @@ except ImportError: import pickle from twisted.protocols import amp from twisted.internet import protocol -from src.utils.utils import to_str +from twisted.internet.defer import Deferred +from src.utils.utils import to_str, variable_from_module # these are only needed on the server side, so we delay loading of them # so as to not have to load them on the portal too. Note: It's doubtful @@ -133,6 +134,8 @@ class AmpClientFactory(protocol.ReconnectingClientFactory): protocol.ReconnectingClientFactory.clientConnectionFailed(self, connector, reason) +# AMP Communication Command types + class MsgPortal2Server(amp.Command): """ Message portal -> server @@ -198,6 +201,23 @@ class PortalAdmin(amp.Command): errors = [(Exception, 'EXCEPTION')] response = [] +class FunctionCall(amp.Command): + """ + Bidirectional + + Sent when either process needs to call an + arbitrary function in the other. + """ + arguments = [('module', amp.String()), + ('function', amp.String()), + ('args', amp.String()), + ('kwargs', amp.String())] + errors = [(Exception, 'EXCEPTION')] + response = [('result', amp.String())] + + +# Helper functions + dumps = lambda data: to_str(pickle.dumps(data, pickle.HIGHEST_PROTOCOL)) loads = lambda data: pickle.loads(to_str(data)) @@ -469,3 +489,43 @@ class AMPProtocol(amp.AMP): sessid=sessid, operation=operation, data=data).addErrback(self.errback, "PortalAdmin") + + # Extra functions + + def amp_function_call(self, module, function, args, kwargs): + """ + This allows Portal- and Server-process to call an arbitrary function + in the other process. It is intended for use by plugin modules. + """ + args = loads(args) + kwargs = loads(kwargs) + + # call the function (don't catch tracebacks here) + result = variable_from_module(module, function)(*args, **kwargs) + + if isinstance(result, Deferred): + # if result is a deferred, attach handler to properly wrap the return value + result.addCallback(lambda r: {"result":dumps(r)}) + return result + else: + return {'result':dumps(result)} + FunctionCall.responder(amp_function_call) + + + def call_remote_FunctionCall(self, modulepath, functionname, *args, **kwargs): + """ + Access method called by either process. This will call an arbitrary function + on the other process (On Portal if calling from Server and vice versa). + + Inputs: + modulepath (str) - python path to module holding function to call + functionname (str) - name of function in given module + *args, **kwargs will be used as arguments/keyword args for the remote function call + Returns: + A deferred that fires with the return value of the remote function call + """ + return self.callRemote(FunctionCall, + module=modulepath, + function=functionname, + args=dumps(args), + kwargs=dumps(kwargs)).addCallback(lambda r: loads(r["result"])).addErrback(self.errback, "FunctionCall") diff --git a/src/utils/utils.py b/src/utils/utils.py index 8c53873a6a..4fff530a84 100644 --- a/src/utils/utils.py +++ b/src/utils/utils.py @@ -10,7 +10,7 @@ import os, sys, imp, types, math import textwrap, datetime, random from inspect import ismodule from collections import defaultdict -from twisted.internet import threads +from twisted.internet import threads, defer, reactor from django.contrib.contenttypes.models import ContentType from django.conf import settings @@ -39,6 +39,7 @@ def is_iter(iterable): except AttributeError: return False + def make_iter(obj): "Makes sure that the object is always iterable." return not hasattr(obj, '__iter__') and [obj] or obj @@ -87,9 +88,15 @@ def list_to_string(inlist, endsep="and", addquote=False): """ This pretty-formats a list as string output, adding an optional alternative separator to the second to last entry. - If addquote is True, the outgoing strints will be surrounded by quotes. + If addquote is True, the outgoing strings will be surrounded by quotes. - [1,2,3] -> '1, 2 and 3' + Examples: + no endsep: + [1,2,3] -> '1, 2, 3' + with endsep=='and': + [1,2,3] -> '1, 2 and 3' + with addquote and endsep + [1,2,3] -> '"1", "2" and "3"' """ if not inlist: return "" @@ -490,7 +497,6 @@ def uses_database(name="sqlite3"): return engine == "django.db.backends.%s" % name - _FROM_MODEL_MAP = None _TO_DBOBJ = lambda o: (hasattr(o, "dbobj") and o.dbobj) or o _TO_PACKED_DBOBJ = lambda natural_key, dbref: ('__packed_dbobj__', natural_key, dbref)