blob: 58cd62483e4c3aa1876758c09f156bdecdf25ef8 [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
76 def request(self, headers, path_params, query_params, body_value):
77 """Updates outgoing requests with JSON bodies.
78
79 Args:
80 headers: dict, request headers
81 path_params: dict, parameters that appear in the request path
82 query_params: dict, parameters that appear in the query
83 body_value: object, the request body as a Python object, which must be
84 serializable by simplejson.
85 Returns:
86 A tuple of (headers, path_params, query, body)
87
88 headers: dict, request headers
89 path_params: dict, parameters that appear in the request path
90 query: string, query part of the request URI
91 body: string, the body serialized as JSON
92 """
93 query = self._build_query(query_params)
94 headers['accept'] = 'application/json'
95 if 'user-agent' in headers:
96 headers['user-agent'] += ' '
97 else:
98 headers['user-agent'] = ''
99 headers['user-agent'] += 'google-api-python-client/1.0'
100 if body_value is None:
101 return (headers, path_params, query, None)
102 else:
103 headers['content-type'] = 'application/json'
104 return (headers, path_params, query, simplejson.dumps(body_value))
105
106 def _build_query(self, params):
107 """Builds a query string.
108
109 Args:
110 params: dict, the query parameters
111
112 Returns:
113 The query parameters properly encoded into an HTTP URI query string.
114 """
115 params.update({'alt': 'json'})
116 astuples = []
117 for key, value in params.iteritems():
118 if getattr(value, 'encode', False) and callable(value.encode):
119 value = value.encode('utf-8')
120 astuples.append((key, value))
121 return '?' + urllib.urlencode(astuples)
122
123 def response(self, resp, content):
124 """Convert the response wire format into a Python object.
125
126 Args:
127 resp: httplib2.Response, the HTTP response headers and status
128 content: string, the body of the HTTP response
129
130 Returns:
131 The body de-serialized as a Python object.
132
133 Raises:
134 apiclient.errors.HttpError if a non 2xx response is received.
135 """
136 # Error handling is TBD, for example, do we retry
137 # for some operation/error combinations?
138 if resp.status < 300:
139 if resp.status == 204:
140 # A 204: No Content response should be treated differently
141 # to all the other success states
142 return simplejson.loads('{}')
143 body = simplejson.loads(content)
144 if isinstance(body, dict) and 'data' in body:
145 body = body['data']
146 return body
147 else:
148 logging.debug('Content from bad request was: %s' % content)
Ali Afshar2dcc6522010-12-16 10:11:53 +0100149 raise HttpError(resp, content)