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.
This commit is contained in:
Griatch 2012-10-14 11:53:34 +02:00
parent 049cc84be7
commit 592bc26b99
2 changed files with 71 additions and 5 deletions

View file

@ -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")

View file

@ -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)