• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 socket
24import urllib2
25from autotest_lib.client.common_lib import error as exceptions
26
27from json import decoder
28
29from json import encoder as json_encoder
30json_encoder_class = json_encoder.JSONEncoder
31
32
33# Try to upgrade to the Django JSON encoder. It uses the standard json encoder
34# but can handle DateTime
35try:
36    # See http://crbug.com/418022 too see why the try except is needed here.
37    from django import conf as django_conf
38    # The serializers can't be imported if django isn't configured.
39    # Using try except here doesn't work, as test_that initializes it's own
40    # django environment (setup_django_lite_environment) which raises import
41    # errors if the django dbutils have been previously imported, as importing
42    # them leaves some state behind.
43    # This the variable name must not be undefined or empty string.
44    if os.environ.get(django_conf.ENVIRONMENT_VARIABLE, None):
45        from django.core.serializers import json as django_encoder
46        json_encoder_class = django_encoder.DjangoJSONEncoder
47except ImportError:
48    pass
49
50
51class JSONRPCException(Exception):
52    pass
53
54class ValidationError(JSONRPCException):
55    """Raised when the RPC is malformed."""
56    def __init__(self, error, formatted_message):
57        """Constructor.
58
59        @param error: a dict of error info like so:
60                      {error['name']: 'ErrorKind',
61                       error['message']: 'Pithy error description.',
62                       error['traceback']: 'Multi-line stack trace'}
63        @formatted_message: string representation of this exception.
64        """
65        self.problem_keys = eval(error['message'])
66        self.traceback = error['traceback']
67        super(ValidationError, self).__init__(formatted_message)
68
69def BuildException(error):
70    """Exception factory.
71
72    Given a dict of error info, determine which subclass of
73    JSONRPCException to build and return.  If can't determine the right one,
74    just return a JSONRPCException with a pretty-printed error string.
75
76    @param error: a dict of error info like so:
77                  {error['name']: 'ErrorKind',
78                   error['message']: 'Pithy error description.',
79                   error['traceback']: 'Multi-line stack trace'}
80    """
81    error_message = '%(name)s: %(message)s\n%(traceback)s' % error
82    for cls in JSONRPCException.__subclasses__():
83        if error['name'] == cls.__name__:
84            return cls(error, error_message)
85    for cls in (exceptions.CrosDynamicSuiteException.__subclasses__() +
86                exceptions.RPCException.__subclasses__()):
87        if error['name'] == cls.__name__:
88            return cls(error_message)
89    return JSONRPCException(error_message)
90
91class ServiceProxy(object):
92    def __init__(self, serviceURL, serviceName=None, headers=None):
93        self.__serviceURL = serviceURL
94        self.__serviceName = serviceName
95        self.__headers = headers or {}
96
97    def __getattr__(self, name):
98        if self.__serviceName is not None:
99            name = "%s.%s" % (self.__serviceName, name)
100        return ServiceProxy(self.__serviceURL, name, self.__headers)
101
102    def __call__(self, *args, **kwargs):
103        # Caller can pass in a minimum value of timeout to be used for urlopen
104        # call. Otherwise, the default socket timeout will be used.
105        min_rpc_timeout = kwargs.pop('min_rpc_timeout', None)
106        postdata = json_encoder_class().encode({'method': self.__serviceName,
107                                                'params': args + (kwargs,),
108                                                'id': 'jsonrpc'})
109        request = urllib2.Request(self.__serviceURL, data=postdata,
110                                  headers=self.__headers)
111        default_timeout = socket.getdefaulttimeout()
112        if not default_timeout:
113            # If default timeout is None, socket will never time out.
114            respdata = urllib2.urlopen(request).read()
115        else:
116            timeout = max(min_rpc_timeout, default_timeout)
117            respdata = urllib2.urlopen(request, timeout=timeout).read()
118        try:
119            resp = decoder.JSONDecoder().decode(respdata)
120        except ValueError:
121            raise JSONRPCException('Error decoding JSON reponse:\n' + respdata)
122        if resp['error'] is not None:
123            raise BuildException(resp['error'])
124        else:
125            return resp['result']
126