blob: 31a8fe8b3ef83e0dbd22d29afdcedb20ed20b76c [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__ = [
12 'HttpRequest', 'RequestMockBuilder'
13 ]
14
Joe Gregorioc6722462010-12-20 14:29:28 -050015import httplib2
Joe Gregorio89174d22010-12-20 14:37:36 -050016from model import JsonModel
Joe Gregorioc5c5a372010-09-22 11:42:32 -040017
18
19class HttpRequest(object):
Joe Gregorioaf276d22010-12-09 14:26:58 -050020 """Encapsulates a single HTTP request.
Joe Gregorioc5c5a372010-09-22 11:42:32 -040021 """
22
Joe Gregorio00cf1d92010-09-27 09:22:03 -040023 def __init__(self, http, uri, method="GET", body=None, headers=None,
Joe Gregorioaf276d22010-12-09 14:26:58 -050024 postproc=None, methodId=None):
25 """Constructor for an HttpRequest.
26
27 Only http and uri are required.
28
29 Args:
30 http: httplib2.Http, the transport object to use to make a request
31 uri: string, the absolute URI to send the request to
32 method: string, the HTTP method to use
33 body: string, the request body of the HTTP request
34 headers: dict, the HTTP request headers
35 postproc: callable, called on the HTTP response and content to transform
36 it into a data object before returning, or raising an exception
37 on an error.
38 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
139 def __call__(self, http, uri, method="GET", body=None, headers=None,
140 postproc=None, methodId=None):
141 """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)