blob: e9fd7e0d149db966ca2953e3553cf6d248d090dd [file] [log] [blame]
Joe Gregorioc5c5a372010-09-22 11:42:32 -04001# Copyright 2010 Google Inc. All Rights Reserved.
2
Joe Gregorioaf276d22010-12-09 14:26:58 -05003"""Classes to encapsulate a single HTTP request.
Joe Gregorioc5c5a372010-09-22 11:42:32 -04004
Joe Gregorioaf276d22010-12-09 14:26:58 -05005The classes implement a command pattern, with every
6object supporting an execute() method that does the
7actuall HTTP request.
Joe Gregorioc5c5a372010-09-22 11:42:32 -04008"""
9
10__author__ = 'jcgregorio@google.com (Joe Gregorio)'
Joe Gregorioaf276d22010-12-09 14:26:58 -050011__all__ = [
Joe Gregoriocb8103d2011-02-11 23:20:52 -050012 'HttpRequest', 'RequestMockBuilder', 'HttpMock'
Joe Gregorioaf276d22010-12-09 14:26:58 -050013 ]
14
Joe Gregorioc6722462010-12-20 14:29:28 -050015import httplib2
Joe Gregoriocb8103d2011-02-11 23:20:52 -050016import os
17
Joe Gregorio89174d22010-12-20 14:37:36 -050018from model import JsonModel
Joe Gregorioc5c5a372010-09-22 11:42:32 -040019
20
21class HttpRequest(object):
Joe Gregorioaf276d22010-12-09 14:26:58 -050022 """Encapsulates a single HTTP request.
Joe Gregorioc5c5a372010-09-22 11:42:32 -040023 """
24
Joe Gregorioabda96f2011-02-11 20:19:33 -050025 def __init__(self, http, postproc, uri, method="GET", body=None, headers=None,
26 methodId=None):
Joe Gregorioaf276d22010-12-09 14:26:58 -050027 """Constructor for an HttpRequest.
28
Joe Gregorioaf276d22010-12-09 14:26:58 -050029 Args:
30 http: httplib2.Http, the transport object to use to make a request
Joe Gregorioabda96f2011-02-11 20:19:33 -050031 postproc: callable, called on the HTTP response and content to transform
32 it into a data object before returning, or raising an exception
33 on an error.
Joe Gregorioaf276d22010-12-09 14:26:58 -050034 uri: string, the absolute URI to send the request to
35 method: string, the HTTP method to use
36 body: string, the request body of the HTTP request
37 headers: dict, the HTTP request headers
Joe Gregorioaf276d22010-12-09 14:26:58 -050038 methodId: string, a unique identifier for the API method being called.
39 """
Joe Gregorioc5c5a372010-09-22 11:42:32 -040040 self.uri = uri
41 self.method = method
42 self.body = body
43 self.headers = headers or {}
44 self.http = http
45 self.postproc = postproc
46
47 def execute(self, http=None):
48 """Execute the request.
49
Joe Gregorioaf276d22010-12-09 14:26:58 -050050 Args:
51 http: httplib2.Http, an http object to be used in place of the
52 one the HttpRequest request object was constructed with.
53
54 Returns:
55 A deserialized object model of the response body as determined
56 by the postproc.
57
58 Raises:
59 apiclient.errors.HttpError if the response was not a 2xx.
60 httplib2.Error if a transport error has occured.
Joe Gregorioc5c5a372010-09-22 11:42:32 -040061 """
62 if http is None:
63 http = self.http
64 resp, content = http.request(self.uri, self.method,
65 body=self.body,
66 headers=self.headers)
67 return self.postproc(resp, content)
Joe Gregorioaf276d22010-12-09 14:26:58 -050068
69
70class HttpRequestMock(object):
71 """Mock of HttpRequest.
72
73 Do not construct directly, instead use RequestMockBuilder.
74 """
75
76 def __init__(self, resp, content, postproc):
77 """Constructor for HttpRequestMock
78
79 Args:
80 resp: httplib2.Response, the response to emulate coming from the request
81 content: string, the response body
82 postproc: callable, the post processing function usually supplied by
83 the model class. See model.JsonModel.response() as an example.
84 """
85 self.resp = resp
86 self.content = content
87 self.postproc = postproc
88 if resp is None:
Joe Gregorioc6722462010-12-20 14:29:28 -050089 self.resp = httplib2.Response({'status': 200, 'reason': 'OK'})
Joe Gregorioaf276d22010-12-09 14:26:58 -050090 if 'reason' in self.resp:
91 self.resp.reason = self.resp['reason']
92
93 def execute(self, http=None):
94 """Execute the request.
95
96 Same behavior as HttpRequest.execute(), but the response is
97 mocked and not really from an HTTP request/response.
98 """
99 return self.postproc(self.resp, self.content)
100
101
102class RequestMockBuilder(object):
103 """A simple mock of HttpRequest
104
105 Pass in a dictionary to the constructor that maps request methodIds to
106 tuples of (httplib2.Response, content) that should be returned when that
107 method is called. None may also be passed in for the httplib2.Response, in
108 which case a 200 OK response will be generated.
109
110 Example:
111 response = '{"data": {"id": "tag:google.c...'
112 requestBuilder = RequestMockBuilder(
113 {
114 'chili.activities.get': (None, response),
115 }
116 )
117 apiclient.discovery.build("buzz", "v1", requestBuilder=requestBuilder)
118
119 Methods that you do not supply a response for will return a
120 200 OK with an empty string as the response content. The methodId
121 is taken from the rpcName in the discovery document.
122
123 For more details see the project wiki.
124 """
125
126 def __init__(self, responses):
127 """Constructor for RequestMockBuilder
128
129 The constructed object should be a callable object
130 that can replace the class HttpResponse.
131
132 responses - A dictionary that maps methodIds into tuples
133 of (httplib2.Response, content). The methodId
134 comes from the 'rpcName' field in the discovery
135 document.
136 """
137 self.responses = responses
138
Joe Gregorioabda96f2011-02-11 20:19:33 -0500139 def __call__(self, http, postproc, uri, method="GET", body=None,
140 headers=None, methodId=None):
Joe Gregorioaf276d22010-12-09 14:26:58 -0500141 """Implements the callable interface that discovery.build() expects
142 of requestBuilder, which is to build an object compatible with
143 HttpRequest.execute(). See that method for the description of the
144 parameters and the expected response.
145 """
146 if methodId in self.responses:
147 resp, content = self.responses[methodId]
148 return HttpRequestMock(resp, content, postproc)
149 else:
150 model = JsonModel()
151 return HttpRequestMock(None, '{}', model.response)
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500152
153class HttpMock(object):
154 """Mock of httplib2.Http"""
155
156 def __init__(self, filename, headers):
157 """
158 Args:
159 filename: string, absolute filename to read response from
160 headers: dict, header to return with response
161 """
162 f = file(filename, 'r')
163 self.data = f.read()
164 f.close()
165 self.headers = headers
166
167 def request(self, uri, method="GET", body=None, headers=None, redirections=1, connection_type=None):
168 return httplib2.Response(self.headers), self.data