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 )