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:
parent
049cc84be7
commit
592bc26b99
2 changed files with 71 additions and 5 deletions
|
|
@ -22,7 +22,8 @@ except ImportError:
|
||||||
import pickle
|
import pickle
|
||||||
from twisted.protocols import amp
|
from twisted.protocols import amp
|
||||||
from twisted.internet import protocol
|
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
|
# 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
|
# 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)
|
protocol.ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
|
||||||
|
|
||||||
|
|
||||||
|
# AMP Communication Command types
|
||||||
|
|
||||||
class MsgPortal2Server(amp.Command):
|
class MsgPortal2Server(amp.Command):
|
||||||
"""
|
"""
|
||||||
Message portal -> server
|
Message portal -> server
|
||||||
|
|
@ -198,6 +201,23 @@ class PortalAdmin(amp.Command):
|
||||||
errors = [(Exception, 'EXCEPTION')]
|
errors = [(Exception, 'EXCEPTION')]
|
||||||
response = []
|
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))
|
dumps = lambda data: to_str(pickle.dumps(data, pickle.HIGHEST_PROTOCOL))
|
||||||
loads = lambda data: pickle.loads(to_str(data))
|
loads = lambda data: pickle.loads(to_str(data))
|
||||||
|
|
||||||
|
|
@ -469,3 +489,43 @@ class AMPProtocol(amp.AMP):
|
||||||
sessid=sessid,
|
sessid=sessid,
|
||||||
operation=operation,
|
operation=operation,
|
||||||
data=data).addErrback(self.errback, "PortalAdmin")
|
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")
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import os, sys, imp, types, math
|
||||||
import textwrap, datetime, random
|
import textwrap, datetime, random
|
||||||
from inspect import ismodule
|
from inspect import ismodule
|
||||||
from collections import defaultdict
|
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.contrib.contenttypes.models import ContentType
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
@ -39,6 +39,7 @@ def is_iter(iterable):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def make_iter(obj):
|
def make_iter(obj):
|
||||||
"Makes sure that the object is always iterable."
|
"Makes sure that the object is always iterable."
|
||||||
return not hasattr(obj, '__iter__') and [obj] or obj
|
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
|
This pretty-formats a list as string output, adding
|
||||||
an optional alternative separator to the second to last entry.
|
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:
|
if not inlist:
|
||||||
return ""
|
return ""
|
||||||
|
|
@ -490,7 +497,6 @@ def uses_database(name="sqlite3"):
|
||||||
return engine == "django.db.backends.%s" % name
|
return engine == "django.db.backends.%s" % name
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
_FROM_MODEL_MAP = None
|
_FROM_MODEL_MAP = None
|
||||||
_TO_DBOBJ = lambda o: (hasattr(o, "dbobj") and o.dbobj) or o
|
_TO_DBOBJ = lambda o: (hasattr(o, "dbobj") and o.dbobj) or o
|
||||||
_TO_PACKED_DBOBJ = lambda natural_key, dbref: ('__packed_dbobj__', natural_key, dbref)
|
_TO_PACKED_DBOBJ = lambda natural_key, dbref: ('__packed_dbobj__', natural_key, dbref)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue