blob: 55bcc9468350a56ad667c3f997589c98e51f29bd [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 Gregoriod433b2a2011-02-22 10:51:51 -050076 def __init__(self, data_wrapper):
77 """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'
103 if 'user-agent' in headers:
104 headers['user-agent'] += ' '
105 else:
106 headers['user-agent'] = ''
107 headers['user-agent'] += 'google-api-python-client/1.0'
Joe Gregoriod433b2a2011-02-22 10:51:51 -0500108
109 if (isinstance(body_value, dict) and 'data' not in body_value and
110 self._data_wrapper):
111 body_value = {'data': body_value}
Joe Gregorio3ad5e9a2010-12-09 15:01:04 -0500112 if body_value is None:
113 return (headers, path_params, query, None)
114 else:
115 headers['content-type'] = 'application/json'
116 return (headers, path_params, query, simplejson.dumps(body_value))
117
118 def _build_query(self, params):
119 """Builds a query string.
120
121 Args:
122 params: dict, the query parameters
123
124 Returns:
125 The query parameters properly encoded into an HTTP URI query string.
126 """
127 params.update({'alt': 'json'})
128 astuples = []
129 for key, value in params.iteritems():
130 if getattr(value, 'encode', False) and callable(value.encode):
131 value = value.encode('utf-8')
132 astuples.append((key, value))
133 return '?' + urllib.urlencode(astuples)
134
135 def response(self, resp, content):
136 """Convert the response wire format into a Python object.
137
138 Args:
139 resp: httplib2.Response, the HTTP response headers and status
140 content: string, the body of the HTTP response
141
142 Returns:
143 The body de-serialized as a Python object.
144
145 Raises:
146 apiclient.errors.HttpError if a non 2xx response is received.
147 """
148 # Error handling is TBD, for example, do we retry
149 # for some operation/error combinations?
150 if resp.status < 300:
151 if resp.status == 204:
152 # A 204: No Content response should be treated differently
153 # to all the other success states
154 return simplejson.loads('{}')
155 body = simplejson.loads(content)
156 if isinstance(body, dict) and 'data' in body:
157 body = body['data']
158 return body
159 else:
160 logging.debug('Content from bad request was: %s' % content)
Ali Afshar2dcc6522010-12-16 10:11:53 +0100161 raise HttpError(resp, content)