summaryrefslogtreecommitdiff
path: root/openstackclient/common/restapi.py
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient/common/restapi.py')
-rw-r--r--openstackclient/common/restapi.py297
1 files changed, 219 insertions, 78 deletions
diff --git a/openstackclient/common/restapi.py b/openstackclient/common/restapi.py
index a45c8426..1bb64fae 100644
--- a/openstackclient/common/restapi.py
+++ b/openstackclient/common/restapi.py
@@ -25,14 +25,15 @@ except ImportError:
from urllib import urlencode
+USER_AGENT = 'RAPI'
+
_logger = logging.getLogger(__name__)
class RESTApi(object):
- """A REST api client that handles the interface from us to the server
+ """A REST API client that handles the interface from us to the server
- RESTApi is an extension of a requests.Session that knows
- how to do:
+ RESTApi is requests.Session wrapper that knows how to do:
* JSON serialization/deserialization
* log requests in 'curl' format
* basic API boilerplate for create/delete/list/set/show verbs
@@ -46,26 +47,49 @@ class RESTApi(object):
it communicates with, such as the available endpoints, API versions, etc.
"""
- USER_AGENT = 'RAPI'
-
def __init__(
self,
- os_auth=None,
+ session=None,
+ auth_header=None,
user_agent=USER_AGENT,
- debug=None,
verify=True,
- **kwargs
+ logger=None,
+ debug=None,
):
- self.set_auth(os_auth)
+ """Construct a new REST client
+
+ :param object session: A Session object to be used for
+ communicating with the identity service.
+ :param string auth_header: A token from an initialized auth_reference
+ to be used in the X-Auth-Token header
+ :param string user_agent: Set the User-Agent header in the requests
+ :param boolean/string verify: If ``True``, the SSL cert will be
+ verified. A CA_BUNDLE path can also be
+ provided.
+ :param logging.Logger logger: A logger to output to. (optional)
+ :param boolean debug: Enables debug logging of all request and
+ responses to identity service.
+ default False (optional)
+ """
+
+ self.set_auth(auth_header)
self.debug = debug
- self.session = requests.Session(**kwargs)
- self.set_header('User-Agent', user_agent)
- self.set_header('Content-Type', 'application/json')
+ if not session:
+ # We create a default session object
+ session = requests.Session()
+ self.session = session
+ self.session.verify = verify
+ self.session.user_agent = user_agent
+
+ if logger:
+ self.logger = logger
+ else:
+ self.logger = _logger
- def set_auth(self, os_auth):
+ def set_auth(self, auth_header):
"""Sets the current auth blob"""
- self.os_auth = os_auth
+ self.auth_header = auth_header
def set_header(self, header, content):
"""Sets passed in headers into the session headers
@@ -78,37 +102,154 @@ class RESTApi(object):
self.session.headers[header] = content
def request(self, method, url, **kwargs):
- if self.os_auth:
- self.session.headers.setdefault('X-Auth-Token', self.os_auth)
- if 'data' in kwargs and isinstance(kwargs['data'], type({})):
- kwargs['data'] = json.dumps(kwargs['data'])
- log_request(method, url, headers=self.session.headers, **kwargs)
+ """Make an authenticated (if token available) request
+
+ :param method: Request HTTP method
+ :param url: Request URL
+ :param data: Request body
+ :param json: Request body to be encoded as JSON
+ Overwrites ``data`` argument if present
+ """
+
+ kwargs.setdefault('headers', {})
+ if self.auth_header:
+ kwargs['headers']['X-Auth-Token'] = self.auth_header
+
+ if 'json' in kwargs and isinstance(kwargs['json'], type({})):
+ kwargs['data'] = json.dumps(kwargs.pop('json'))
+ kwargs['headers']['Content-Type'] = 'application/json'
+
+ kwargs.setdefault('allow_redirects', True)
+
+ if self.debug:
+ self._log_request(method, url, **kwargs)
+
response = self.session.request(method, url, **kwargs)
- log_response(response)
+
+ if self.debug:
+ self._log_response(response)
+
return self._error_handler(response)
+ def _error_handler(self, response):
+ if response.status_code < 200 or response.status_code >= 300:
+ self.logger.debug(
+ "ERROR: %s",
+ response.text,
+ )
+ response.raise_for_status()
+ return response
+
+ # Convenience methods to mimic the ones provided by requests.Session
+
+ def delete(self, url, **kwargs):
+ """Send a DELETE request. Returns :class:`requests.Response` object.
+
+ :param url: Request URL
+ :param \*\*kwargs: Optional arguments passed to ``request``
+ """
+
+ return self.request('DELETE', url, **kwargs)
+
+ def get(self, url, **kwargs):
+ """Send a GET request. Returns :class:`requests.Response` object.
+
+ :param url: Request URL
+ :param \*\*kwargs: Optional arguments passed to ``request``
+ """
+
+ return self.request('GET', url, **kwargs)
+
+ def head(self, url, **kwargs):
+ """Send a HEAD request. Returns :class:`requests.Response` object.
+
+ :param url: Request URL
+ :param \*\*kwargs: Optional arguments passed to ``request``
+ """
+
+ kwargs.setdefault('allow_redirects', False)
+ return self.request('HEAD', url, **kwargs)
+
+ def options(self, url, **kwargs):
+ """Send an OPTIONS request. Returns :class:`requests.Response` object.
+
+ :param url: Request URL
+ :param \*\*kwargs: Optional arguments passed to ``request``
+ """
+
+ return self.request('OPTIONS', url, **kwargs)
+
+ def patch(self, url, data=None, json=None, **kwargs):
+ """Send a PUT request. Returns :class:`requests.Response` object.
+
+ :param url: Request URL
+ :param data: Request body
+ :param json: Request body to be encoded as JSON
+ Overwrites ``data`` argument if present
+ :param \*\*kwargs: Optional arguments passed to ``request``
+ """
+
+ return self.request('PATCH', url, data=data, json=json, **kwargs)
+
+ def post(self, url, data=None, json=None, **kwargs):
+ """Send a POST request. Returns :class:`requests.Response` object.
+
+ :param url: Request URL
+ :param data: Request body
+ :param json: Request body to be encoded as JSON
+ Overwrites ``data`` argument if present
+ :param \*\*kwargs: Optional arguments passed to ``request``
+ """
+
+ return self.request('POST', url, data=data, json=json, **kwargs)
+
+ def put(self, url, data=None, json=None, **kwargs):
+ """Send a PUT request. Returns :class:`requests.Response` object.
+
+ :param url: Request URL
+ :param data: Request body
+ :param json: Request body to be encoded as JSON
+ Overwrites ``data`` argument if present
+ :param \*\*kwargs: Optional arguments passed to ``request``
+ """
+
+ return self.request('PUT', url, data=data, json=json, **kwargs)
+
+ # Command verb methods
+
def create(self, url, data=None, response_key=None, **kwargs):
- response = self.request('POST', url, data=data, **kwargs)
+ """Create a new object via a POST request
+
+ :param url: Request URL
+ :param data: Request body, wil be JSON encoded
+ :param response_key: Dict key in response body to extract
+ :param \*\*kwargs: Optional arguments passed to ``request``
+ """
+
+ response = self.request('POST', url, json=data, **kwargs)
if response_key:
return response.json()[response_key]
else:
return response.json()
- #with self.completion_cache('human_id', self.resource_class, mode="a"):
- # with self.completion_cache('uuid', self.resource_class, mode="a"):
- # return self.resource_class(self, body[response_key])
+ def list(self, url, data=None, response_key=None, **kwargs):
+ """Retrieve a list of objects via a GET or POST request
- def delete(self, url):
- self.request('DELETE', url)
+ :param url: Request URL
+ :param data: Request body, will be JSON encoded
+ :param response_key: Dict key in response body to extract
+ :param \*\*kwargs: Optional arguments passed to ``request``
+ """
- def list(self, url, data=None, response_key=None, **kwargs):
if data:
- response = self.request('POST', url, data=data, **kwargs)
+ response = self.request('POST', url, json=data, **kwargs)
else:
- kwargs.setdefault('allow_redirects', True)
response = self.request('GET', url, **kwargs)
- return response.json()[response_key]
+ if response_key:
+ return response.json()[response_key]
+ else:
+ return response.json()
###hack this for keystone!!!
#data = body[response_key]
@@ -120,70 +261,70 @@ class RESTApi(object):
# except KeyError:
# pass
- #with self.completion_cache('human_id', obj_class, mode="w"):
- # with self.completion_cache('uuid', obj_class, mode="w"):
- # return [obj_class(self, res, loaded=True)
- # for res in data if res]
-
def set(self, url, data=None, response_key=None, **kwargs):
- response = self.request('PUT', url, data=data)
+ """Update an object via a PUT request
+
+ :param url: Request URL
+ :param data: Request body
+ :param json: Request body to be encoded as JSON
+ Overwrites ``data`` argument if present
+ :param \*\*kwargs: Optional arguments passed to ``request``
+ """
+
+ response = self.request('PUT', url, json=data)
if data:
if response_key:
return response.json()[response_key]
else:
return response.json()
else:
+ # Nothing to do here
return None
def show(self, url, response_key=None, **kwargs):
+ """Retrieve a single object via a GET request
+
+ :param url: Request URL
+ :param response_key: Dict key in response body to extract
+ :param \*\*kwargs: Optional arguments passed to ``request``
+ """
+
response = self.request('GET', url, **kwargs)
if response_key:
return response.json()[response_key]
else:
return response.json()
- def _error_handler(self, response):
- if response.status_code < 200 or response.status_code >= 300:
- _logger.debug(
- "ERROR: %s",
+ def _log_request(self, method, url, **kwargs):
+ if 'params' in kwargs and kwargs['params'] != {}:
+ url += '?' + urlencode(kwargs['params'])
+
+ string_parts = [
+ "curl -i",
+ "-X '%s'" % method,
+ "'%s'" % url,
+ ]
+
+ for element in kwargs['headers']:
+ header = " -H '%s: %s'" % (element, kwargs['headers'][element])
+ string_parts.append(header)
+
+ self.logger.debug("REQ: %s" % " ".join(string_parts))
+ if 'data' in kwargs:
+ self.logger.debug(" REQ BODY: %r\n" % (kwargs['data']))
+
+ def _log_response(self, response):
+ self.logger.debug(
+ "RESP: [%s] %r\n",
+ response.status_code,
+ response.headers,
+ )
+ if response._content_consumed:
+ self.logger.debug(
+ " RESP BODY: %s\n",
response.text,
)
- response.raise_for_status()
- return response
-
-
-def log_request(method, url, **kwargs):
- # put in an early exit if debugging is not enabled?
- if 'params' in kwargs and kwargs['params'] != {}:
- url += '?' + urlencode(kwargs['params'])
-
- string_parts = [
- "curl -i",
- "-X '%s'" % method,
- "'%s'" % url,
- ]
-
- for element in kwargs['headers']:
- header = " -H '%s: %s'" % (element, kwargs['headers'][element])
- string_parts.append(header)
-
- _logger.debug("REQ: %s" % " ".join(string_parts))
- if 'data' in kwargs:
- _logger.debug("REQ BODY: %s\n" % (kwargs['data']))
-
-
-def log_response(response):
- _logger.debug(
- "RESP: [%s] %s\n",
- response.status_code,
- response.headers,
- )
- if response._content_consumed:
- _logger.debug(
- "RESP BODY: %s\n",
- response.text,
+ self.logger.debug(
+ " encoding: %s",
+ response.encoding,
)
- _logger.debug(
- "encoding: %s",
- response.encoding,
- )