12
12
from urllib import urlencode
13
13
14
14
15
+ class Response (object ):
16
+ """Holds the response from an API call."""
17
+ def __init__ (self , response ):
18
+ """
19
+ :param response: The return value from a open call
20
+ on a urllib.build_opener()
21
+ :type response: urllib response object
22
+ """
23
+ self ._status_code = response .getcode ()
24
+ self ._response_body = response .read ()
25
+ self ._response_headers = response .info ()
26
+
27
+ @property
28
+ def status_code (self ):
29
+ """
30
+ :return: integer, status code of API call
31
+ """
32
+ return self ._status_code
33
+
34
+ @property
35
+ def response_body (self ):
36
+ """
37
+ :return: response from the API
38
+ """
39
+ return self ._response_body
40
+
41
+ @property
42
+ def response_headers (self ):
43
+ """
44
+ :return: dict of response headers
45
+ """
46
+ return self ._response_headers
47
+
48
+
15
49
class Client (object ):
16
50
"""Quickly and easily access any REST or REST-like API."""
17
51
def __init__ (self ,
18
52
host ,
19
53
request_headers = None ,
20
- version = None ):
54
+ version = None ,
55
+ url_path = None ):
21
56
"""
22
57
:param host: Base URL for the api. (e.g. https://api.sendgrid.com)
23
58
:type host: string
@@ -28,43 +63,25 @@ def __init__(self,
28
63
Subclass _build_versioned_url for custom behavior.
29
64
Or just pass the version as part of the URL
30
65
(e.g. client._("/v3"))
31
- :type integer:
66
+ :type version: integer
67
+ :param url_path: A list of the url path segments
68
+ :type url_path: list of strings
32
69
"""
33
70
self .host = host
34
- self .request_headers = request_headers
71
+ self .request_headers = request_headers or {}
72
+ self ._version = version
73
+ # _url_path keeps track of the dynamically built url
74
+ self ._url_path = url_path or []
35
75
# These are the supported HTTP verbs
36
76
self .methods = ['delete' , 'get' , 'patch' , 'post' , 'put' ]
37
- self ._version = version
38
- # _count and _url_path keep track of the dynamically built url
39
- self ._count = 0
40
- self ._url_path = {}
41
- self ._status_code = None
42
- self ._response_body = None
43
- self ._response_headers = None
44
- self ._response = None
45
-
46
- def _reset (self ):
47
- """Resets the URL builder, so you can make a fresh new dynamic call."""
48
- self ._count = 0
49
- self ._url_path = {}
50
- self ._response = None
51
-
52
- def _add_to_url_path (self , name ):
53
- """Takes the method chained call and adds to the url path.
54
-
55
- :param name: The name of the method call
56
- :type name: string
57
- """
58
- self ._url_path [self ._count ] = name
59
- self ._count += 1
60
77
61
78
def _build_versioned_url (self , url ):
62
79
"""Subclass this function for your own needs.
63
80
Or just pass the version as part of the URL
64
81
(e.g. client._('/v3'))
65
82
:param url: URI portion of the full URL being requested
66
83
:type url: string
67
- :return:
84
+ :return: string
68
85
"""
69
86
return '{0}/v{1}{2}' .format (self .host , str (self ._version ), url )
70
87
@@ -73,7 +90,7 @@ def _build_url(self, query_params):
73
90
74
91
:param query_params: A dictionary of all the query parameters
75
92
:type query_params: dictionary
76
- :return:
93
+ :return: string
77
94
"""
78
95
url = ''
79
96
count = 0
@@ -83,33 +100,30 @@ def _build_url(self, query_params):
83
100
if query_params :
84
101
url_values = urlencode (sorted (query_params .items ()))
85
102
url = '{0}?{1}' .format (url , url_values )
86
- if self ._version :
87
- url = self ._build_versioned_url (url )
88
- else :
89
- url = self .host + url
103
+ url = self ._build_versioned_url (url ) if self ._version else self .host + url
90
104
return url
91
105
92
- def _set_response (self , response ):
93
- """Build the API call's response
106
+ def _update_headers (self , request_headers ):
107
+ """Update the headers for the request
94
108
95
- :param response: The response object from the API call from urllib
96
- :type response: urllib.Request object
109
+ :param request_headers: headers to set for the API call
110
+ :type response: dictionary
111
+ :return: dictionary
97
112
"""
98
- self ._status_code = response .getcode ()
99
- self ._response_body = response .read ()
100
- self ._response_headers = response .info ()
113
+ self .request_headers .update (request_headers )
101
114
102
- def _set_headers (self , request_headers ):
103
- """Build the headers for the request
115
+ def _build_client (self , name = None ):
116
+ """Make a new Client object
104
117
105
- :param request_headers: headers to set for the API call
106
- :type response: dict
107
- :return:
118
+ :param name: Name of the url segment
119
+ :type name: string
120
+ :return: A Client object
108
121
"""
109
- if self .request_headers :
110
- self .request_headers .update (request_headers )
111
- else :
112
- self .request_headers = request_headers
122
+ url_path = self ._url_path + [name ] if name else self ._url_path
123
+ return Client (host = self .host ,
124
+ version = self ._version ,
125
+ request_headers = self .request_headers ,
126
+ url_path = url_path )
113
127
114
128
def _make_request (self , opener , request ):
115
129
"""Make the API call and return the response. This is separated into
@@ -119,7 +133,7 @@ def _make_request(self, opener, request):
119
133
:type opener:
120
134
:param request: url payload to request
121
135
:type request: urllib.Request object
122
- :return:
136
+ :return: urllib response
123
137
"""
124
138
return opener .open (request )
125
139
@@ -131,10 +145,9 @@ def _(self, name):
131
145
132
146
:param name: Name of the url segment
133
147
:type name: string
134
- :return:
148
+ :return: Client object
135
149
"""
136
- self ._add_to_url_path (name )
137
- return self
150
+ return self ._build_client (name )
138
151
139
152
def __getattr__ (self , name ):
140
153
"""Dynamically add method calls to the url, then call a method.
@@ -153,7 +166,7 @@ def get_version(*args, **kwargs):
153
166
:return: string, version
154
167
"""
155
168
self ._version = args [0 ]
156
- return self
169
+ return self . _build_client ()
157
170
return get_version
158
171
159
172
# We have reached the end of the method chain, make the API call
@@ -167,7 +180,7 @@ def http_request(*args, **kwargs):
167
180
:return: Client object
168
181
"""
169
182
if 'request_headers' in kwargs :
170
- self ._set_headers (kwargs ['request_headers' ])
183
+ self ._update_headers (kwargs ['request_headers' ])
171
184
data = json .dumps (kwargs ['request_body' ]).encode ('utf-8' )\
172
185
if 'request_body' in kwargs else None
173
186
params = kwargs ['query_params' ]\
@@ -178,32 +191,8 @@ def http_request(*args, **kwargs):
178
191
for key , value in self .request_headers .items ():
179
192
request .add_header (key , value )
180
193
request .get_method = lambda : method
181
- self ._response = self ._make_request (opener , request )
182
- self ._set_response (self ._response )
183
- self ._reset ()
184
- return self
194
+ return Response (self ._make_request (opener , request ))
185
195
return http_request
186
196
else :
187
- self ._add_to_url_path (name )
188
- return self
189
-
190
- @property
191
- def status_code (self ):
192
- """
193
- :return: integer, status code of API call
194
- """
195
- return self ._status_code
196
-
197
- @property
198
- def response_body (self ):
199
- """
200
- :return: response from the API
201
- """
202
- return self ._response_body
203
-
204
- @property
205
- def response_headers (self ):
206
- """
207
- :return: dict of response headers
208
- """
209
- return self ._response_headers
197
+ # Add a segment to the URL
198
+ return self ._ (name )
0 commit comments