blob: 2b35c85a0b465044180847439e0d0f80a7675a7f [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 Gregoriodeeb0202011-02-15 14:49:57 -050025 def __init__(self, http, postproc, uri,
Joe Gregorio7c22ab22011-02-16 15:32:39 -050026 method='GET',
Joe Gregoriodeeb0202011-02-15 14:49:57 -050027 body=None,
28 headers=None,
Joe Gregorioabda96f2011-02-11 20:19:33 -050029 methodId=None):
Joe Gregorioaf276d22010-12-09 14:26:58 -050030 """Constructor for an HttpRequest.
31
Joe Gregorioaf276d22010-12-09 14:26:58 -050032 Args:
33 http: httplib2.Http, the transport object to use to make a request
Joe Gregorioabda96f2011-02-11 20:19:33 -050034 postproc: callable, called on the HTTP response and content to transform
35 it into a data object before returning, or raising an exception
36 on an error.
Joe Gregorioaf276d22010-12-09 14:26:58 -050037 uri: string, the absolute URI to send the request to
38 method: string, the HTTP method to use
39 body: string, the request body of the HTTP request
40 headers: dict, the HTTP request headers
Joe Gregorioaf276d22010-12-09 14:26:58 -050041 methodId: string, a unique identifier for the API method being called.
42 """
Joe Gregorioc5c5a372010-09-22 11:42:32 -040043 self.uri = uri
44 self.method = method
45 self.body = body
46 self.headers = headers or {}
47 self.http = http
48 self.postproc = postproc
49
50 def execute(self, http=None):
51 """Execute the request.
52
Joe Gregorioaf276d22010-12-09 14:26:58 -050053 Args:
54 http: httplib2.Http, an http object to be used in place of the
55 one the HttpRequest request object was constructed with.
56
57 Returns:
58 A deserialized object model of the response body as determined
59 by the postproc.
60
61 Raises:
62 apiclient.errors.HttpError if the response was not a 2xx.
63 httplib2.Error if a transport error has occured.
Joe Gregorioc5c5a372010-09-22 11:42:32 -040064 """
65 if http is None:
66 http = self.http
67 resp, content = http.request(self.uri, self.method,
68 body=self.body,
69 headers=self.headers)
70 return self.postproc(resp, content)
Joe Gregorioaf276d22010-12-09 14:26:58 -050071
72
73class HttpRequestMock(object):
74 """Mock of HttpRequest.
75
76 Do not construct directly, instead use RequestMockBuilder.
77 """
78
79 def __init__(self, resp, content, postproc):
80 """Constructor for HttpRequestMock
81
82 Args:
83 resp: httplib2.Response, the response to emulate coming from the request
84 content: string, the response body
85 postproc: callable, the post processing function usually supplied by
86 the model class. See model.JsonModel.response() as an example.
87 """
88 self.resp = resp
89 self.content = content
90 self.postproc = postproc
91 if resp is None:
Joe Gregorioc6722462010-12-20 14:29:28 -050092 self.resp = httplib2.Response({'status': 200, 'reason': 'OK'})
Joe Gregorioaf276d22010-12-09 14:26:58 -050093 if 'reason' in self.resp:
94 self.resp.reason = self.resp['reason']
95
96 def execute(self, http=None):
97 """Execute the request.
98
99 Same behavior as HttpRequest.execute(), but the response is
100 mocked and not really from an HTTP request/response.
101 """
102 return self.postproc(self.resp, self.content)
103
104
105class RequestMockBuilder(object):
106 """A simple mock of HttpRequest
107
108 Pass in a dictionary to the constructor that maps request methodIds to
109 tuples of (httplib2.Response, content) that should be returned when that
110 method is called. None may also be passed in for the httplib2.Response, in
111 which case a 200 OK response will be generated.
112
113 Example:
114 response = '{"data": {"id": "tag:google.c...'
115 requestBuilder = RequestMockBuilder(
116 {
117 'chili.activities.get': (None, response),
118 }
119 )
120 apiclient.discovery.build("buzz", "v1", requestBuilder=requestBuilder)
121
122 Methods that you do not supply a response for will return a
123 200 OK with an empty string as the response content. The methodId
124 is taken from the rpcName in the discovery document.
125
126 For more details see the project wiki.
127 """
128
129 def __init__(self, responses):
130 """Constructor for RequestMockBuilder
131
132 The constructed object should be a callable object
133 that can replace the class HttpResponse.
134
135 responses - A dictionary that maps methodIds into tuples
136 of (httplib2.Response, content). The methodId
137 comes from the 'rpcName' field in the discovery
138 document.
139 """
140 self.responses = responses
141
Joe Gregorio7c22ab22011-02-16 15:32:39 -0500142 def __call__(self, http, postproc, uri, method='GET', body=None,
Joe Gregorioabda96f2011-02-11 20:19:33 -0500143 headers=None, methodId=None):
Joe Gregorioaf276d22010-12-09 14:26:58 -0500144 """Implements the callable interface that discovery.build() expects
145 of requestBuilder, which is to build an object compatible with
146 HttpRequest.execute(). See that method for the description of the
147 parameters and the expected response.
148 """
149 if methodId in self.responses:
150 resp, content = self.responses[methodId]
151 return HttpRequestMock(resp, content, postproc)
152 else:
153 model = JsonModel()
154 return HttpRequestMock(None, '{}', model.response)
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500155
Joe Gregoriodeeb0202011-02-15 14:49:57 -0500156
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500157class HttpMock(object):
158 """Mock of httplib2.Http"""
159
160 def __init__(self, filename, headers):
161 """
162 Args:
163 filename: string, absolute filename to read response from
164 headers: dict, header to return with response
165 """
166 f = file(filename, 'r')
167 self.data = f.read()
168 f.close()
169 self.headers = headers
170
Joe Gregoriodeeb0202011-02-15 14:49:57 -0500171 def request(self, uri,
Joe Gregorio7c22ab22011-02-16 15:32:39 -0500172 method='GET',
Joe Gregoriodeeb0202011-02-15 14:49:57 -0500173 body=None,
174 headers=None,
175 redirections=1,
176 connection_type=None):
Joe Gregoriocb8103d2011-02-11 23:20:52 -0500177 return httplib2.Response(self.headers), self.data