blob: d6aca5d1bea52be0af3ae080ac0dccd1ead98ab7 [file] [log] [blame]
showardf828c772010-01-25 21:49:42 +00001import logging, pprint, re, urllib
2import httplib2
3from django.utils import simplejson
4
5
6_RESOURCE_DIRECTORY_PATH = '/afe/server/resources/'
7
8
9_http = httplib2.Http()
10
11
12class RestClientError(Exception):
13 pass
14
15
16class ClientError(Exception):
17 pass
18
19
20class ServerError(Exception):
21 pass
22
23
24class Response(object):
25 def __init__(self, httplib_response, httplib_content):
26 self.status = int(httplib_response['status'])
27 self.headers = httplib_response
28 self.entity_body = httplib_content
29
30
31 def decoded_body(self):
32 return simplejson.loads(self.entity_body)
33
34
35 def __str__(self):
36 return '\n'.join([str(self.status), self.entity_body])
37
38
39class Resource(object):
40 def __init__(self, base_uri, representation_dict):
41 self._base_uri = base_uri
42
43 assert 'href' in representation_dict
44 for key, value in representation_dict.iteritems():
45 setattr(self, str(key), value)
46
47
48 def __repr__(self):
49 return 'Resource(%r)' % self._representation()
50
51
52 def pprint(self):
53 # pretty-print support for debugging/interactive use
54 pprint.pprint(self._representation())
55
56
57 @classmethod
58 def directory(cls, base_uri):
59 directory = cls(base_uri, {'href': _RESOURCE_DIRECTORY_PATH})
60 return directory.get()
61
62
63 def _read_representation(self, value):
64 # recursively convert representation dicts to Resource objects
65 if isinstance(value, list):
66 return [self._read_representation(element) for element in value]
67 if isinstance(value, dict):
68 converted_dict = dict((key, self._read_representation(sub_value))
69 for key, sub_value in value.iteritems())
70 if 'href' in converted_dict:
71 return type(self)(self._base_uri, converted_dict)
72 return converted_dict
73 return value
74
75
76 def _write_representation(self, value):
77 # recursively convert Resource objects to representation dicts
78 if isinstance(value, list):
79 return [self._write_representation(element) for element in value]
80 if isinstance(value, dict):
81 return dict((key, self._write_representation(sub_value))
82 for key, sub_value in value.iteritems())
83 if isinstance(value, Resource):
84 return value._representation()
85 return value
86
87
88 def _representation(self):
89 return dict((key, self._write_representation(value))
90 for key, value in self.__dict__.iteritems()
91 if not key.startswith('_')
92 and not callable(value))
93
94
95 def _request(self, method, query_parameters=None, encoded_body=None):
96 uri_parts = []
97 if not re.match(r'^https?://', self.href):
98 uri_parts.append(self._base_uri)
99 uri_parts.append(self.href)
100 if query_parameters:
101 query_string = urllib.urlencode(query_parameters)
102 uri_parts.extend(['?', query_string])
103
104 if encoded_body:
105 entity_body = simplejson.dumps(encoded_body)
106 else:
107 entity_body = None
108
109 full_uri = ''.join(uri_parts)
110 logging.debug('%s %s', method, full_uri)
111 if entity_body:
112 logging.debug(entity_body)
113 headers, response_body = _http.request(
114 ''.join(uri_parts), method, body=entity_body,
115 headers={'Content-Type': 'application/json'})
116 logging.debug('Response: %s', headers['status'])
117
118 response = Response(headers, response_body)
119 if 300 <= response.status < 400: # redirection
120 raise NotImplementedError(str(response)) # TODO
121 if 400 <= response.status < 500:
122 raise ClientError(str(response))
123 if 500 <= response.status < 600:
124 raise ServerError(str(response))
125 return response
126
127
128 def _stringify_query_parameter(self, value):
129 if isinstance(value, (list, tuple)):
130 return ','.join(value)
131 return str(value)
132
133
134 def get(self, **query_parameters):
135 string_parameters = dict((key, self._stringify_query_parameter(value))
136 for key, value in query_parameters.iteritems()
137 if value is not None)
138 response = self._request('GET', query_parameters=string_parameters)
139 assert response.status == 200
140 return self._read_representation(response.decoded_body())
141
142
143 def put(self):
144 response = self._request('PUT', encoded_body=self._representation())
145 assert response.status == 200
146 return self._read_representation(response.decoded_body())
147
148
149 def delete(self):
150 response = self._request('DELETE')
151 assert response.status == 204 # no content
152
153
154 def post(self, request_dict):
155 # request_dict may still have resources in it
156 request_dict = self._write_representation(request_dict)
157 response = self._request('POST', encoded_body=request_dict)
158 assert response.status == 201 # created
159 return self._read_representation({'href': response.headers['location']})