Welcome, guest | Sign In | My Account | Store | Cart

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.

Python, 157 lines
  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.