This recipe automates the creation and lookup of objects passed from and to xmlrpc server methods so that incoming calls arguments are automatically replaced by their respective objects, and returned objects get mapped and their ids returned.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | import re, time, xmlrpclib
from SimpleXMLRPCServer import *
import threading
class SomeState(object):
def __init__(self):
self.value = 0
CLASSES = [SomeState]
TIMEOUT = 60.0 * 5.0
class Timeout:
pass
class InvalidId:
pass
class ObjectManager(object):
""" ObjectManager stores and manages id-to-object mappings. It utilizes the
Borg pattern to avoid global variables.
"""
shared_state = {"id2o" : {}}
def __init__(self):
self.__dict__ = self.shared_state
def map(self, val, classes):
""" Checks if a val is of a type found in classes and
then creates the mapping between the id of the value and
a tuple constisting of the value itself and the current time.
"""
if val.__class__ in classes:
vid = id(val)
self.id2o[vid] = (val, time.time())
val = vid
return val
def lookup(self, vid, timeout):
""" Lookups the passed vid in the o2id-mapping. If not found,
a InvalidId exception is raised. If found, but the timeout kicks in, a
Timeout exception is raised. Otherwise the associated value is returned.
"""
try:
o, ts = self.id2o[vid]
now = time.time()
if now - ts > timeout:
raise Timeout()
self.id2o[vid] = (o, now)
return o
except KeyError:
raise InvalidId()
def map(classes=CLASSES):
"""
Registers a result value in the ObjectManager. The optional classes argument specifies
the list of instances that are subject to mapping.
"""
def f(func):
def _map(self, *args, **kwargs):
om = ObjectManager()
res = func(self, *args, **kwargs)
return om.map(res, classes)
return _map
return f
def lookup(poslist=[0], timeout=TIMEOUT):
"""
Lookups the values at the indices listed in poslist in the ObjectManager.
"""
def f(func):
def _lookup(self, *args, **kwargs):
args = list(args)
om = ObjectManager()
for pos in poslist:
args[pos] = om.lookup(args[pos], timeout=timeout)
return func(self, *args, **kwargs)
return _lookup
return f
def wrap(poslist=[0], timeout=TIMEOUT, classes=CLASSES):
"""
Cobines lookup and map.
"""
def f(func):
def _wrap(self, *args, **kwargs):
args = list(args)
om = ObjectManager()
for pos in poslist:
args[pos] = om.lookup(args[pos], timeout=timeout)
res = func(self, *args, **kwargs)
return om.map(res, classes)
return _wrap
return f
class Test(object):
@map()
def init(self):
return SomeState()
@lookup()
def add(self, state, amount):
state.value += amount
return state.value
@wrap(timeout=1.0)
def clone(self, state):
ns = SomeState()
ns.value = state.value
return ns
@lookup(poslist=[0,1])
def equals(self, a, b):
print a.value , b.value
return a.value == b.value
def launch():
server = SimpleXMLRPCServer(("localhost", 8000))
server.register_instance(Test())
server.serve_forever()
def main():
server_thread = threading.Thread(target=launch)
server_thread.setDaemon(True)
server_thread.start()
# We need some time to start the server - this should be enough.
time.sleep(4)
t = xmlrpclib.ServerProxy("http://localhost:8000")
# test mapping
h = t.init()
# test lookup
h_value = t.add(h, 10)
# test wrap
cloned_h = t.clone(h)
assert cloned_h != h
cloned_h_value = t.add(cloned_h, 10)
assert cloned_h_value == 20 and h_value == 10
# test the timeout
time.sleep(2)
try:
cloned_h = t.clone(h)
assert False
except xmlrpclib.Fault:
pass
# test the poslist
assert not t.equals(h, cloned_h)
if __name__ == "__main__":
main()
|
Using xmlrpclib and SimpleXMLRPCServer makes usage of xmlrpc in python a piece of cake. But the limited C-style interfaces of xmlrpc are inherently stateless. To circumvene this, one usually expose some sort of session initialization that returns a unique id to associate subsequent calls to the interface with that same session.
This recipe automates the creation and lookup of those keys by decorating the xmlrpc servers methods so that incoming calls arguments are automatically replaced by their respective objects.
Additionally the management of session state usually requires timeouts. These too are part of the recipe.
An extension to this recipe would be to use the generic _dispatch method and directly call the passed function name on the passed object. Thus the xmlrpc Server is extremely primitive, while OO-style programming can be achieved at least at server-side without any effort.