directsvc.py 4.06 KB
Newer Older
Renaud Le Gac's avatar
Renaud Le Gac committed
1
""" Author: R. Le Gac
2

Renaud Le Gac's avatar
Renaud Le Gac committed
3
"""
4

5 6
import datetime
import json
Renaud Le Gac's avatar
Renaud Le Gac committed
7
import pprint
8
import traceback
Renaud Le Gac's avatar
Renaud Le Gac committed
9 10
import sys

11
from basesvc import BaseSvc
12
from gluon.http import HTTP
13
from helper import as_list
14

Renaud Le Gac's avatar
Renaud Le Gac committed
15
DBUI = 'Dbui'
16
PROC_KEY = '%s.%s'
Renaud Le Gac's avatar
Renaud Le Gac committed
17 18


19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
class MyJsonEncoder(json.JSONEncoder):
    """Add date, datetime and time to the standard encoder.
    
    """
    def default(self, obj):
        
        if isinstance(obj, (datetime.date,
                            datetime.datetime,
                            datetime.time)):
            return obj.isoformat().replace('T', ' ')
        
        return json.JSONEncoder.default(self, obj)

        
    
34
class DirectSvc(BaseSvc):
Renaud Le Gac's avatar
Renaud Le Gac committed
35
    """Generic service to implement the Ext.Direct protocol on the server side
36 37 38 39
    Specification: http://www.sencha.com/products/extjs/extdirect/
    
    This implementation is based on the web2py service: 
    http://www.web2py.com/book/default/chapter/09
Renaud Le Gac's avatar
Renaud Le Gac committed
40
    It is a simplified version of the class Service in gluon.tools.
41
    
Renaud Le Gac's avatar
Renaud Le Gac committed
42
    To register functions and make it available remotely use decorator
43 44 45 46 47 48
    
        service = DirectSvc(globals())
        
        @service.register
        def myfunction(a, b):
    
Renaud Le Gac's avatar
Renaud Le Gac committed
49 50 51 52 53 54 55 56 57 58 59 60
    The same technique can be used with method of a class.
    
    On the client side the api definition can be retrieved using the
    url: /app/plugin_dbui/call/get_api

    On the client side the url to send requests is /app/plugin_dbui/call    
    Remote functions / methods can be call in the following way:
    Dbui.myfunction(a, b, callback), MyClass.mymethod(c, d, callback).
    
    In this implementation requests are JSON-Encoded raw HTTP
    and response are JSON encoded.
    
61
    """
Renaud Le Gac's avatar
Renaud Le Gac committed
62

63 64 65 66
    def __init__(self, environment):
        
        BaseSvc.__init__(self, environment)

67 68 69 70
        self.procedures = {}
        self.register(self.echo)


Renaud Le Gac's avatar
Renaud Le Gac committed
71 72
    def __call__(self):
        self.dbg('Start directSvc.call')
73
        request = self.environment['request']
Renaud Le Gac's avatar
Renaud Le Gac committed
74 75 76 77 78

        # no arguments in a Json-encoded raw HTPP request
        if len(request.args) == 0:
            return self.route()
        
79
        self.error(404, 'Object does not exist.')
Renaud Le Gac's avatar
Renaud Le Gac committed
80 81
        
    
82 83
    def echo(self, x):
        """Helper function returning the input arguments.
Renaud Le Gac's avatar
Renaud Le Gac committed
84
        Helpful to run echo tests.
85 86 87 88
        
        """
        return x
    
Renaud Le Gac's avatar
Renaud Le Gac committed
89

90 91
    def error(self, code, message):
        raise HTTP(code, message)
92
    
Renaud Le Gac's avatar
Renaud Le Gac committed
93
        
94
    def register(self, f):
Renaud Le Gac's avatar
Renaud Le Gac committed
95 96 97 98 99 100 101
        # a procedure is registered as 'action.method'
        # The action is equal to Dbui for a function
        # and to the name of the class for a method
        action = DBUI
        if 'im_class' in dir(f):
            action = f.im_class.__name__

102
        k = PROC_KEY % (action, f.__name__)
Renaud Le Gac's avatar
Renaud Le Gac committed
103 104 105
        self.procedures[k] = f
        
        self.dbg("Procedure %s registered" % k)
106 107 108 109 110 111 112 113
        return f

    
    def route(self):
        """Route the request to the appropriate methods, pass the 
        proper arguments and return the results.
        
        """
Renaud Le Gac's avatar
Renaud Le Gac committed
114 115 116
        self.dbg('Start directSvc.route')
        
        # decode transactions, a list of Json-encoded raw HTPP requests:
117
        # {action: "x", method: "y", data: [], type:"rpc", tid:z}
118
        request = self.environment['request']    
119
        transactions = as_list(json.loads(request.body.read()))
120 121 122 123 124 125
        
        # process each transactions and encode each result as a dict
        # {action: "x", method: "y", result: [], type:"rpc", tid:z}
        li = []
        for arg in transactions:
            di = dict(arg)
126
            self.dbg('transaction:', di)
Renaud Le Gac's avatar
Renaud Le Gac committed
127
            try:
128
                k = PROC_KEY % (arg['action'], arg['method'])
129 130 131 132
                if arg['data']:
                    di['result'] = self.procedures[k](*arg['data'])
                else:
                    di['result'] = self.procedures[k]()
Renaud Le Gac's avatar
Renaud Le Gac committed
133
                
134
            except Exception, e:
Renaud Le Gac's avatar
Renaud Le Gac committed
135
                traceback.print_exc(file=sys.stdout)
136
                self.error(500, 'Internal server error.')
Renaud Le Gac's avatar
Renaud Le Gac committed
137
            
138 139 140
            del di['data']
            li.append(di)
            
Renaud Le Gac's avatar
Renaud Le Gac committed
141
        self.dbg('End directSvc.call', li)
142
        return json.dumps(li, cls=MyJsonEncoder)
143 144