1
2"""
3  Copyright (c) 2007 Jan-Klaas Kollhof
4
5  This file is part of jsonrpc.
6
7  jsonrpc is free software; you can redistribute it and/or modify
8  it under the terms of the GNU Lesser General Public License as published by
9  the Free Software Foundation; either version 2.1 of the License, or
10  (at your option) any later version.
11
12  This software is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  GNU Lesser General Public License for more details.
16
17  You should have received a copy of the GNU Lesser General Public License
18  along with this software; if not, write to the Free Software
19  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20"""
21
22import os
23import urllib2
24from autotest_lib.client.common_lib import error as exceptions
25
26from json import decoder
27
28from json import encoder as json_encoder
29json_encoder_class = json_encoder.JSONEncoder
30
31
32# Try to upgrade to the Django JSON encoder. It uses the standard json encoder
33# but can handle DateTime
34try:
35    # See http://crbug.com/418022 too see why the try except is needed here.
36    from django import conf as django_conf
37    # The serializers can't be imported if django isn't configured.
38    # Using try except here doesn't work, as test_that initializes it's own
39    # django environment (setup_django_lite_environment) which raises import
40    # errors if the django dbutils have been previously imported, as importing
41    # them leaves some state behind.
42    # This the variable name must not be undefined or empty string.
43    if os.environ.get(django_conf.ENVIRONMENT_VARIABLE, None):
44        from django.core.serializers import json as django_encoder
45        json_encoder_class = django_encoder.DjangoJSONEncoder
46except ImportError:
47    pass
48
49
50class JSONRPCException(Exception):
51    pass
52
53class ValidationError(JSONRPCException):
54    """Raised when the RPC is malformed."""
55    def __init__(self, error, formatted_message):
56        """Constructor.
57
58        @param error: a dict of error info like so:
59                      {error['name']: 'ErrorKind',
60                       error['message']: 'Pithy error description.',
61                       error['traceback']: 'Multi-line stack trace'}
62        @formatted_message: string representation of this exception.
63        """
64        self.problem_keys = eval(error['message'])
65        self.traceback = error['traceback']
66        super(ValidationError, self).__init__(formatted_message)
67
68def BuildException(error):
69    """Exception factory.
70
71    Given a dict of error info, determine which subclass of
72    JSONRPCException to build and return.  If can't determine the right one,
73    just return a JSONRPCException with a pretty-printed error string.
74
75    @param error: a dict of error info like so:
76                  {error['name']: 'ErrorKind',
77                   error['message']: 'Pithy error description.',
78                   error['traceback']: 'Multi-line stack trace'}
79    """
80    error_message = '%(name)s: %(message)s\n%(traceback)s' % error
81    for cls in JSONRPCException.__subclasses__():
82        if error['name'] == cls.__name__:
83            return cls(error, error_message)
84    for cls in (exceptions.CrosDynamicSuiteException.__subclasses__() +
85                exceptions.RPCException.__subclasses__()):
86        if error['name'] == cls.__name__:
87            return cls(error_message)
88    return JSONRPCException(error_message)
89
90class ServiceProxy(object):
91    def __init__(self, serviceURL, serviceName=None, headers=None):
92        self.__serviceURL = serviceURL
93        self.__serviceName = serviceName
94        self.__headers = headers or {}
95
96    def __getattr__(self, name):
97        if self.__serviceName is not None:
98            name = "%s.%s" % (self.__serviceName, name)
99        return ServiceProxy(self.__serviceURL, name, self.__headers)
100
101    def __call__(self, *args, **kwargs):
102        postdata = json_encoder_class().encode({'method': self.__serviceName,
103                                                'params': args + (kwargs,),
104                                                'id': 'jsonrpc'})
105        request = urllib2.Request(self.__serviceURL, data=postdata,
106                                  headers=self.__headers)
107        respdata = urllib2.urlopen(request).read()
108        try:
109            resp = decoder.JSONDecoder().decode(respdata)
110        except ValueError:
111            raise JSONRPCException('Error decoding JSON reponse:\n' + respdata)
112        if resp['error'] is not None:
113            raise BuildException(resp['error'])
114        else:
115            return resp['result']
116