Skip to content

Commit facdc87

Browse files
Jenkinsopenstack-gerrit
authored andcommitted
Merge "Bring RESTApi closer to ithe imminent keystoneclient.Session"
2 parents cd87088 + f2dbe2e commit facdc87

File tree

7 files changed

+427
-230
lines changed

7 files changed

+427
-230
lines changed

openstackclient/common/restapi.py

Lines changed: 219 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,15 @@
2525
from urllib import urlencode
2626

2727

28+
USER_AGENT = 'RAPI'
29+
2830
_logger = logging.getLogger(__name__)
2931

3032

3133
class 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

Comments
 (0)