Source code for gocd.api.response
import json
import re
# TODO: Test this class explicitly, the implicit testing we got now
# works but it doesn't define the behavior.
[docs]class Response(object):
"""A nicely wrapped HTTP Response
This class will be instantiated from API calls and is not intended
to be used in any other way.
Special handling of the responses:
1. A response has a boolean value that corresponds with :meth:`is_ok`:
>>> bool(Response(404, 'Not found'))
False
>>> bool(Response(200, 'OK'))
True
2. If the payload is json you can access the json object through
the response as if it was a `dict`:
>>> response = Response(200, '{"hello": "there"}',
{'Content-Type': 'application/json'})
>>> response['hello']
"there"
3. If the payload is json you can look if a key is part of the
payload as you would with a `dict`:
>>> response = Response(200, '{"hello": "there"}',
{'Content-Type': 'application/json'})
>>> 'hello' in response
True
Args:
status_code (int): The HTTP status of this response
body (str, fp): The HTTP response body/payload of this response
headers (dict, optional): The headers supplied by this response
ok_status (int, optional): If the `status_code` is this then this
response is successful. Default: 200
"""
def __init__(self, status_code, body, headers=None, ok_status=200):
self.__body = None
self.status_code = status_code
self._body = body
self._body_parsed = None
self.content_type = headers.get('content-type', '').split(';')[0] or False
self.headers = headers or {}
self.ok_status = ok_status or 200
@property
def is_ok(self):
"""Whether this response is considered successful
Returns
bool: True if `status_code` is `ok_status`
"""
return self.status_code == self.ok_status
@property
def is_json(self):
"""
Returns:
bool: True if `content_type` is `application/json`
"""
return (self.content_type.startswith('application/json') or
re.match(r'application/vnd.go.cd.v(\d+)\+json', self.content_type))
def __bool__(self):
# XXX
# I'm not sure if this is a good idea or not,
# but I think a response should be true/false depending on whether it was ok or not.
# Let's try it out and see whether it's a crazy idea.
return self.is_ok
__nonzero__ = __bool__
def __getitem__(self, item):
if self.is_json:
return self.payload[item]
self._raise_non_json_response()
def __contains__(self, item):
if self.is_json:
return item in self.payload
self._raise_non_json_response()
@property
def payload(self):
"""
Returns:
`str` when not json.
`dict` when json.
"""
if self.is_json:
if not self._body_parsed:
if hasattr(self._body, 'decode'):
body = self._body.decode('utf-8')
else:
body = self._body
self._body_parsed = json.loads(body)
return self._body_parsed
else:
return self._body
#: Alias for :meth:`payload`
body = payload
@property
def _body(self):
if hasattr(self.__body, 'read'):
self.__body = self.__body.read()
return self.__body
@_body.setter
def _body(self, value):
self.__body = value
@property
def fp(self):
"""Returns a file-like object if the class was instantiated with one
Returns:
None, file-like object: If :attribute:`_body` responds to read else None
"""
if hasattr(self.__body, 'read'):
return self.__body
return None
@property
def etag(self):
return self.headers["ETag"]
@classmethod
def _from_request(cls, response, ok_status=None):
return Response(
response.code,
response,
response.headers,
ok_status=ok_status
)
@classmethod
def _from_http_error(cls, http_error):
return Response(
http_error.code,
http_error.fp.read(),
http_error.headers,
)
@classmethod
def _from_json(cls, body):
return Response(200, json.dumps(body), {'content-type': 'application/json'})
def _raise_non_json_response(self):
raise AttributeError(
"Can't lookup item in a non-JSON response.",
content_type=self.content_type
)