2525 from urllib import urlencode
2626
2727
28+ USER_AGENT = 'RAPI'
29+
2830_logger = logging .getLogger (__name__ )
2931
3032
3133class RESTApi (object ):
32- """A REST api client that handles the interface from us to the server
34+ """A REST API client that handles the interface from us to the server
3335
34- RESTApi is an extension of a requests.Session that knows
35- how to do:
36+ RESTApi is requests.Session wrapper that knows how to do:
3637 * JSON serialization/deserialization
3738 * log requests in 'curl' format
3839 * basic API boilerplate for create/delete/list/set/show verbs
@@ -46,26 +47,49 @@ class RESTApi(object):
4647 it communicates with, such as the available endpoints, API versions, etc.
4748 """
4849
49- USER_AGENT = 'RAPI'
50-
5150 def __init__ (
5251 self ,
53- os_auth = None ,
52+ session = None ,
53+ auth_header = None ,
5454 user_agent = USER_AGENT ,
55- debug = None ,
5655 verify = True ,
57- ** kwargs
56+ logger = None ,
57+ debug = None ,
5858 ):
59- self .set_auth (os_auth )
59+ """Construct a new REST client
60+
61+ :param object session: A Session object to be used for
62+ communicating with the identity service.
63+ :param string auth_header: A token from an initialized auth_reference
64+ to be used in the X-Auth-Token header
65+ :param string user_agent: Set the User-Agent header in the requests
66+ :param boolean/string verify: If ``True``, the SSL cert will be
67+ verified. A CA_BUNDLE path can also be
68+ provided.
69+ :param logging.Logger logger: A logger to output to. (optional)
70+ :param boolean debug: Enables debug logging of all request and
71+ responses to identity service.
72+ default False (optional)
73+ """
74+
75+ self .set_auth (auth_header )
6076 self .debug = debug
61- self .session = requests .Session (** kwargs )
6277
63- self .set_header ('User-Agent' , user_agent )
64- self .set_header ('Content-Type' , 'application/json' )
78+ if not session :
79+ # We create a default session object
80+ session = requests .Session ()
81+ self .session = session
82+ self .session .verify = verify
83+ self .session .user_agent = user_agent
84+
85+ if logger :
86+ self .logger = logger
87+ else :
88+ self .logger = _logger
6589
66- def set_auth (self , os_auth ):
90+ def set_auth (self , auth_header ):
6791 """Sets the current auth blob"""
68- self .os_auth = os_auth
92+ self .auth_header = auth_header
6993
7094 def set_header (self , header , content ):
7195 """Sets passed in headers into the session headers
@@ -78,37 +102,154 @@ def set_header(self, header, content):
78102 self .session .headers [header ] = content
79103
80104 def request (self , method , url , ** kwargs ):
81- if self .os_auth :
82- self .session .headers .setdefault ('X-Auth-Token' , self .os_auth )
83- if 'data' in kwargs and isinstance (kwargs ['data' ], type ({})):
84- kwargs ['data' ] = json .dumps (kwargs ['data' ])
85- log_request (method , url , headers = self .session .headers , ** kwargs )
105+ """Make an authenticated (if token available) request
106+
107+ :param method: Request HTTP method
108+ :param url: Request URL
109+ :param data: Request body
110+ :param json: Request body to be encoded as JSON
111+ Overwrites ``data`` argument if present
112+ """
113+
114+ kwargs .setdefault ('headers' , {})
115+ if self .auth_header :
116+ kwargs ['headers' ]['X-Auth-Token' ] = self .auth_header
117+
118+ if 'json' in kwargs and isinstance (kwargs ['json' ], type ({})):
119+ kwargs ['data' ] = json .dumps (kwargs .pop ('json' ))
120+ kwargs ['headers' ]['Content-Type' ] = 'application/json'
121+
122+ kwargs .setdefault ('allow_redirects' , True )
123+
124+ if self .debug :
125+ self ._log_request (method , url , ** kwargs )
126+
86127 response = self .session .request (method , url , ** kwargs )
87- log_response (response )
128+
129+ if self .debug :
130+ self ._log_response (response )
131+
88132 return self ._error_handler (response )
89133
134+ def _error_handler (self , response ):
135+ if response .status_code < 200 or response .status_code >= 300 :
136+ self .logger .debug (
137+ "ERROR: %s" ,
138+ response .text ,
139+ )
140+ response .raise_for_status ()
141+ return response
142+
143+ # Convenience methods to mimic the ones provided by requests.Session
144+
145+ def delete (self , url , ** kwargs ):
146+ """Send a DELETE request. Returns :class:`requests.Response` object.
147+
148+ :param url: Request URL
149+ :param \*\*kwargs: Optional arguments passed to ``request``
150+ """
151+
152+ return self .request ('DELETE' , url , ** kwargs )
153+
154+ def get (self , url , ** kwargs ):
155+ """Send a GET request. Returns :class:`requests.Response` object.
156+
157+ :param url: Request URL
158+ :param \*\*kwargs: Optional arguments passed to ``request``
159+ """
160+
161+ return self .request ('GET' , url , ** kwargs )
162+
163+ def head (self , url , ** kwargs ):
164+ """Send a HEAD request. Returns :class:`requests.Response` object.
165+
166+ :param url: Request URL
167+ :param \*\*kwargs: Optional arguments passed to ``request``
168+ """
169+
170+ kwargs .setdefault ('allow_redirects' , False )
171+ return self .request ('HEAD' , url , ** kwargs )
172+
173+ def options (self , url , ** kwargs ):
174+ """Send an OPTIONS request. Returns :class:`requests.Response` object.
175+
176+ :param url: Request URL
177+ :param \*\*kwargs: Optional arguments passed to ``request``
178+ """
179+
180+ return self .request ('OPTIONS' , url , ** kwargs )
181+
182+ def patch (self , url , data = None , json = None , ** kwargs ):
183+ """Send a PUT request. Returns :class:`requests.Response` object.
184+
185+ :param url: Request URL
186+ :param data: Request body
187+ :param json: Request body to be encoded as JSON
188+ Overwrites ``data`` argument if present
189+ :param \*\*kwargs: Optional arguments passed to ``request``
190+ """
191+
192+ return self .request ('PATCH' , url , data = data , json = json , ** kwargs )
193+
194+ def post (self , url , data = None , json = None , ** kwargs ):
195+ """Send a POST request. Returns :class:`requests.Response` object.
196+
197+ :param url: Request URL
198+ :param data: Request body
199+ :param json: Request body to be encoded as JSON
200+ Overwrites ``data`` argument if present
201+ :param \*\*kwargs: Optional arguments passed to ``request``
202+ """
203+
204+ return self .request ('POST' , url , data = data , json = json , ** kwargs )
205+
206+ def put (self , url , data = None , json = None , ** kwargs ):
207+ """Send a PUT request. Returns :class:`requests.Response` object.
208+
209+ :param url: Request URL
210+ :param data: Request body
211+ :param json: Request body to be encoded as JSON
212+ Overwrites ``data`` argument if present
213+ :param \*\*kwargs: Optional arguments passed to ``request``
214+ """
215+
216+ return self .request ('PUT' , url , data = data , json = json , ** kwargs )
217+
218+ # Command verb methods
219+
90220 def create (self , url , data = None , response_key = None , ** kwargs ):
91- response = self .request ('POST' , url , data = data , ** kwargs )
221+ """Create a new object via a POST request
222+
223+ :param url: Request URL
224+ :param data: Request body, wil be JSON encoded
225+ :param response_key: Dict key in response body to extract
226+ :param \*\*kwargs: Optional arguments passed to ``request``
227+ """
228+
229+ response = self .request ('POST' , url , json = data , ** kwargs )
92230 if response_key :
93231 return response .json ()[response_key ]
94232 else :
95233 return response .json ()
96234
97- #with self.completion_cache('human_id', self.resource_class, mode="a"):
98- # with self.completion_cache('uuid', self.resource_class, mode="a"):
99- # return self.resource_class(self, body[response_key])
235+ def list (self , url , data = None , response_key = None , ** kwargs ):
236+ """Retrieve a list of objects via a GET or POST request
100237
101- def delete (self , url ):
102- self .request ('DELETE' , url )
238+ :param url: Request URL
239+ :param data: Request body, will be JSON encoded
240+ :param response_key: Dict key in response body to extract
241+ :param \*\*kwargs: Optional arguments passed to ``request``
242+ """
103243
104- def list (self , url , data = None , response_key = None , ** kwargs ):
105244 if data :
106- response = self .request ('POST' , url , data = data , ** kwargs )
245+ response = self .request ('POST' , url , json = data , ** kwargs )
107246 else :
108- kwargs .setdefault ('allow_redirects' , True )
109247 response = self .request ('GET' , url , ** kwargs )
110248
111- return response .json ()[response_key ]
249+ if response_key :
250+ return response .json ()[response_key ]
251+ else :
252+ return response .json ()
112253
113254 ###hack this for keystone!!!
114255 #data = body[response_key]
@@ -120,70 +261,70 @@ def list(self, url, data=None, response_key=None, **kwargs):
120261 # except KeyError:
121262 # pass
122263
123- #with self.completion_cache('human_id', obj_class, mode="w"):
124- # with self.completion_cache('uuid', obj_class, mode="w"):
125- # return [obj_class(self, res, loaded=True)
126- # for res in data if res]
127-
128264 def set (self , url , data = None , response_key = None , ** kwargs ):
129- response = self .request ('PUT' , url , data = data )
265+ """Update an object via a PUT request
266+
267+ :param url: Request URL
268+ :param data: Request body
269+ :param json: Request body to be encoded as JSON
270+ Overwrites ``data`` argument if present
271+ :param \*\*kwargs: Optional arguments passed to ``request``
272+ """
273+
274+ response = self .request ('PUT' , url , json = data )
130275 if data :
131276 if response_key :
132277 return response .json ()[response_key ]
133278 else :
134279 return response .json ()
135280 else :
281+ # Nothing to do here
136282 return None
137283
138284 def show (self , url , response_key = None , ** kwargs ):
285+ """Retrieve a single object via a GET request
286+
287+ :param url: Request URL
288+ :param response_key: Dict key in response body to extract
289+ :param \*\*kwargs: Optional arguments passed to ``request``
290+ """
291+
139292 response = self .request ('GET' , url , ** kwargs )
140293 if response_key :
141294 return response .json ()[response_key ]
142295 else :
143296 return response .json ()
144297
145- def _error_handler (self , response ):
146- if response .status_code < 200 or response .status_code >= 300 :
147- _logger .debug (
148- "ERROR: %s" ,
298+ def _log_request (self , method , url , ** kwargs ):
299+ if 'params' in kwargs and kwargs ['params' ] != {}:
300+ url += '?' + urlencode (kwargs ['params' ])
301+
302+ string_parts = [
303+ "curl -i" ,
304+ "-X '%s'" % method ,
305+ "'%s'" % url ,
306+ ]
307+
308+ for element in kwargs ['headers' ]:
309+ header = " -H '%s: %s'" % (element , kwargs ['headers' ][element ])
310+ string_parts .append (header )
311+
312+ self .logger .debug ("REQ: %s" % " " .join (string_parts ))
313+ if 'data' in kwargs :
314+ self .logger .debug (" REQ BODY: %r\n " % (kwargs ['data' ]))
315+
316+ def _log_response (self , response ):
317+ self .logger .debug (
318+ "RESP: [%s] %r\n " ,
319+ response .status_code ,
320+ response .headers ,
321+ )
322+ if response ._content_consumed :
323+ self .logger .debug (
324+ " RESP BODY: %s\n " ,
149325 response .text ,
150326 )
151- response .raise_for_status ()
152- return response
153-
154-
155- def log_request (method , url , ** kwargs ):
156- # put in an early exit if debugging is not enabled?
157- if 'params' in kwargs and kwargs ['params' ] != {}:
158- url += '?' + urlencode (kwargs ['params' ])
159-
160- string_parts = [
161- "curl -i" ,
162- "-X '%s'" % method ,
163- "'%s'" % url ,
164- ]
165-
166- for element in kwargs ['headers' ]:
167- header = " -H '%s: %s'" % (element , kwargs ['headers' ][element ])
168- string_parts .append (header )
169-
170- _logger .debug ("REQ: %s" % " " .join (string_parts ))
171- if 'data' in kwargs :
172- _logger .debug ("REQ BODY: %s\n " % (kwargs ['data' ]))
173-
174-
175- def log_response (response ):
176- _logger .debug (
177- "RESP: [%s] %s\n " ,
178- response .status_code ,
179- response .headers ,
180- )
181- if response ._content_consumed :
182- _logger .debug (
183- "RESP BODY: %s\n " ,
184- response .text ,
327+ self .logger .debug (
328+ " encoding: %s" ,
329+ response .encoding ,
185330 )
186- _logger .debug (
187- "encoding: %s" ,
188- response .encoding ,
189- )
0 commit comments