blob: 825bf803900475e40f85ccfc4d05818acf3e9f74 [file] [log] [blame]
Joe Gregorio3ad5e9a2010-12-09 15:01:04 -05001#!/usr/bin/python2.4
2#
3# Copyright 2010 Google Inc. All Rights Reserved.
4
Joe Gregorioec343652011-02-16 16:52:51 -05005"""Model objects for requests and responses.
Joe Gregorio3ad5e9a2010-12-09 15:01:04 -05006
7Each API may support one or more serializations, such
8as JSON, Atom, etc. The model classes are responsible
9for converting between the wire format and the Python
10object representation.
11"""
12
13__author__ = 'jcgregorio@google.com (Joe Gregorio)'
14
15import logging
16import urllib
17
Joe Gregoriob843fa22010-12-13 16:26:07 -050018from anyjson import simplejson
19from errors import HttpError
Joe Gregorio3ad5e9a2010-12-09 15:01:04 -050020
Joe Gregoriodeeb0202011-02-15 14:49:57 -050021
Joe Gregorioabda96f2011-02-11 20:19:33 -050022def _abstract():
23 raise NotImplementedError('You need to override this function')
Joe Gregorio3ad5e9a2010-12-09 15:01:04 -050024
Joe Gregorioabda96f2011-02-11 20:19:33 -050025
26class Model(object):
27 """Model base class.
28
29 All Model classes should implement this interface.
30 The Model serializes and de-serializes between a wire
31 format such as JSON and a Python object representation.
32 """
33
34 def request(self, headers, path_params, query_params, body_value):
35 """Updates outgoing requests with a deserialized body.
36
37 Args:
38 headers: dict, request headers
39 path_params: dict, parameters that appear in the request path
40 query_params: dict, parameters that appear in the query
41 body_value: object, the request body as a Python object, which must be
42 serializable.
43 Returns:
44 A tuple of (headers, path_params, query, body)
45
46 headers: dict, request headers
47 path_params: dict, parameters that appear in the request path
48 query: string, query part of the request URI
49 body: string, the body serialized in the desired wire format.
50 """
51 _abstract()
52
53 def response(self, resp, content):
54 """Convert the response wire format into a Python object.
55
56 Args:
57 resp: httplib2.Response, the HTTP response headers and status
58 content: string, the body of the HTTP response
59
60 Returns:
61 The body de-serialized as a Python object.
62
63 Raises:
64 apiclient.errors.HttpError if a non 2xx response is received.
65 """
66 _abstract()
67
68
69class JsonModel(Model):
Joe Gregorio3ad5e9a2010-12-09 15:01:04 -050070 """Model class for JSON.
71
72 Serializes and de-serializes between JSON and the Python
73 object representation of HTTP request and response bodies.
74 """
75
Joe Gregorio10894442011-02-23 14:46:55 -050076 def __init__(self, data_wrapper=False):
Joe Gregoriod433b2a2011-02-22 10:51:51 -050077 """Construct a JsonModel
78
79 Args:
80 data_wrapper: boolean, wrap requests and responses in a data wrapper
81 """
82 self._data_wrapper = data_wrapper
83
Joe Gregorio3ad5e9a2010-12-09 15:01:04 -050084 def request(self, headers, path_params, query_params, body_value):
85 """Updates outgoing requests with JSON bodies.
86
87 Args:
88 headers: dict, request headers
89 path_params: dict, parameters that appear in the request path
90 query_params: dict, parameters that appear in the query
91 body_value: object, the request body as a Python object, which must be
92 serializable by simplejson.
93 Returns:
94 A tuple of (headers, path_params, query, body)
95
96 headers: dict, request headers
97 path_params: dict, parameters that appear in the request path
98 query: string, query part of the request URI
99 body: string, the body serialized as JSON
100 """
101 query = self._build_query(query_params)
102 headers['accept'] = 'application/json'
Joe Gregorio6429bf62011-03-01 22:53:21 -0800103 headers['accept-encoding'] = 'gzip, deflate'
Joe Gregorio3ad5e9a2010-12-09 15:01:04 -0500104 if 'user-agent' in headers:
105 headers['user-agent'] += ' '
106 else:
107 headers['user-agent'] = ''
108 headers['user-agent'] += 'google-api-python-client/1.0'
Joe Gregoriod433b2a2011-02-22 10:51:51 -0500109
110 if (isinstance(body_value, dict) and 'data' not in body_value and
111 self._data_wrapper):
112 body_value = {'data': body_value}
Joe Gregorio3ad5e9a2010-12-09 15:01:04 -0500113 if body_value is None:
114 return (headers, path_params, query, None)
115 else:
116 headers['content-type'] = 'application/json'
117 return (headers, path_params, query, simplejson.dumps(body_value))
118
119 def _build_query(self, params):
120 """Builds a query string.
121
122 Args:
123 params: dict, the query parameters
124
125 Returns:
126 The query parameters properly encoded into an HTTP URI query string.
127 """
128 params.update({'alt': 'json'})
129 astuples = []
130 for key, value in params.iteritems():
Joe Gregorio61d7e962011-02-22 22:52:07 -0500131 if type(value) == type([]):
132 for x in value:
133 x = x.encode('utf-8')
134 astuples.append((key, x))
135 else:
136 if getattr(value, 'encode', False) and callable(value.encode):
137 value = value.encode('utf-8')
138 astuples.append((key, value))
Joe Gregorio3ad5e9a2010-12-09 15:01:04 -0500139 return '?' + urllib.urlencode(astuples)
140
141 def response(self, resp, content):
142 """Convert the response wire format into a Python object.
143
144 Args:
145 resp: httplib2.Response, the HTTP response headers and status
146 content: string, the body of the HTTP response
147
148 Returns:
149 The body de-serialized as a Python object.
150
151 Raises:
152 apiclient.errors.HttpError if a non 2xx response is received.
153 """
154 # Error handling is TBD, for example, do we retry
155 # for some operation/error combinations?
156 if resp.status < 300:
157 if resp.status == 204:
158 # A 204: No Content response should be treated differently
159 # to all the other success states
160 return simplejson.loads('{}')
161 body = simplejson.loads(content)
162 if isinstance(body, dict) and 'data' in body:
163 body = body['data']
164 return body
165 else:
166 logging.debug('Content from bad request was: %s' % content)
Ali Afshar2dcc6522010-12-16 10:11:53 +0100167 raise HttpError(resp, content)