Skip to content

Commit

Permalink
Merge pull request #176 from analogue/make_result_configurable
Browse files Browse the repository at this point in the history
Make the return type of HttpFuture.result() configurable
  • Loading branch information
analogue committed Oct 19, 2015
2 parents 966659e + e3747f9 commit 4ac01e6
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 111 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Changelog
=========

6.1.0 (2015-XX-XX)
------------------
- Clients can now access the HTTP response from a service call to access things
like headers and status code. See `Advanced Usage <http://bravado.readthedocs.org/en/latest/advanced.html#getting-access-to-the-http-response>`_

6.0.0 (2015-10-12)
------------------
- User-defined formats are no longer global. The registration mechanism has
Expand Down
54 changes: 32 additions & 22 deletions bravado/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,21 +66,25 @@
log = logging.getLogger(__name__)


CONFIG_DEFAULTS = {
# See the constructor of :class:`bravado.http_future.HttpFuture` for an
# in depth explanation of what this means.
'also_return_response': False
}


class SwaggerClient(object):
"""A client for accessing a Swagger-documented RESTful service.
"""
:param swagger_spec: :class:`bravado_core.spec.Spec`
"""
def __init__(self, swagger_spec):
"""
:param swagger_spec: :class:`bravado_core.spec.Spec`
"""
self.swagger_spec = swagger_spec

@classmethod
def from_url(cls, spec_url, http_client=None, request_headers=None,
config=None):
"""
Build a :class:`SwaggerClient` from a url to the Swagger
"""Build a :class:`SwaggerClient` from a url to the Swagger
specification for a RESTful API.
:param spec_url: url pointing at the swagger API specification
Expand All @@ -89,11 +93,10 @@ def from_url(cls, spec_url, http_client=None, request_headers=None,
:type http_client: :class:`bravado.http_client.HttpClient`
:param request_headers: Headers to pass with http requests
:type request_headers: dict
:param config: bravado_core config dict. See
bravado_core.spec.CONFIG_DEFAULTS
:param config: Config dict for bravado and bravado_core.
See CONFIG_DEFAULTS in :module:`bravado_core.spec`.
See CONFIG_DEFAULTS in :module:`bravado.client`.
"""
# TODO: better way to customize the request for api calls, so we don't
# have to add new kwargs for everything
log.debug(u"Loading from %s" % spec_url)
http_client = http_client or RequestsClient()
loader = Loader(http_client, request_headers=request_headers)
Expand All @@ -104,14 +107,18 @@ def from_url(cls, spec_url, http_client=None, request_headers=None,
def from_spec(cls, spec_dict, origin_url=None, http_client=None,
config=None):
"""
Build a :class:`SwaggerClient` from swagger api docs
Build a :class:`SwaggerClient` from a Swagger spec in dict form.
:param spec_dict: a dict with a Swagger spec in json-like form
:param origin_url: the url used to retrieve the spec_dict
:type origin_url: str
:param config: Configuration dict - see spec.CONFIG_DEFAULTS
"""
http_client = http_client or RequestsClient()

# Apply bravado config defaults
config = dict(CONFIG_DEFAULTS, **(config or {}))

swagger_spec = Spec.from_dict(
spec_dict, origin_url, http_client, config)
return cls(swagger_spec)
Expand Down Expand Up @@ -248,19 +255,23 @@ def __call__(self, **op_kwargs):
warn_for_deprecated_op(self.operation)
request_params = self.construct_request(**op_kwargs)
callback = functools.partial(response_callback, operation=self)
return self.operation.swagger_spec.http_client.request(request_params,
callback)
also_return_response = \
self.operation.swagger_spec.config['also_return_response']
return self.operation.swagger_spec.http_client.request(
request_params,
callback,
also_return_response)


def response_callback(incoming_response, operation):
"""
So the http_client is finished with its part of processing the response.
This hands the response over to bravado_core for validation and
unmarshalling.
unmarshalling. On success, the swagger_result is available as
`incoming_response.swagger_result`.
:type incoming_response: :class:`bravado_core.response.IncomingResponse`
:type operation: :class:`bravado_core.operation.Operation`
:return: Response spec's return value.
:raises: HTTPError
- On 5XX status code, the HTTPError has minimal information.
- On non-2XX status code with no matching response, the HTTPError
Expand All @@ -271,15 +282,16 @@ def response_callback(incoming_response, operation):
raise_on_unexpected(incoming_response)

try:
swagger_return_value = unmarshal_response(incoming_response, operation)
incoming_response.swagger_result = unmarshal_response(
incoming_response,
operation)
except MatchingResponseNotFound as e:
six.reraise(
HTTPError,
HTTPError(response=incoming_response, message=str(e)),
sys.exc_info()[2])

raise_on_expected(incoming_response, swagger_return_value)
return swagger_return_value
raise_on_expected(incoming_response)


def raise_on_unexpected(http_response):
Expand All @@ -293,17 +305,15 @@ def raise_on_unexpected(http_response):
raise HTTPError(response=http_response)


def raise_on_expected(http_response, swagger_return_value):
def raise_on_expected(http_response):
"""
Raise an HTTPError if the response is non-2XX and matches a response in the
swagger spec.
:param http_response: :class:`bravado_core.response.IncomingResponse`
:param swagger_return_value: The return value of a swagger response if it
has one, None otherwise.
:raises: HTTPError
"""
if not 200 <= http_response.status_code < 300:
raise HTTPError(
response=http_response,
swagger_result=swagger_return_value)
swagger_result=http_response.swagger_result)
8 changes: 6 additions & 2 deletions bravado/fido_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ class FidoClient(HttpClient):
"""Fido (Asynchronous) HTTP client implementation.
"""

def request(self, request_params, response_callback=None):
def request(self, request_params, response_callback=None,
also_return_response=False):
"""Sets up the request params as per Twisted Agent needs.
Sets up crochet and triggers the API request in background
Expand All @@ -57,6 +58,8 @@ def request(self, request_params, response_callback=None):
:param response_callback: Function to be called after
receiving the response
:type response_callback: method
:param also_return_response: Consult the constructor documentation for
:class:`bravado.http_future.HttpFuture`.
:rtype: :class: `bravado_core.http_future.HttpFuture`
"""
Expand All @@ -77,7 +80,8 @@ def request(self, request_params, response_callback=None):

return HttpFuture(concurrent_future,
FidoResponseAdapter,
response_callback)
response_callback,
also_return_response)


def stringify_body(request_params):
Expand Down
6 changes: 4 additions & 2 deletions bravado/http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
class HttpClient(object):
"""Interface for a minimal HTTP client.
"""

def request(self, request_params, response_callback=None):
def request(self, request_params, response_callback=None,
also_return_response=False):
"""
:param request_params: complete request data. e.g. url, method,
headers, body, params, connect_timeout, timeout, etc.
:type request_params: dict
:param response_callback: Function to be called on response
:type response_callback: method
:param also_return_response: Consult the constructor documentation for
:class:`bravado.http_future.HttpFuture`.
:returns: HTTP Future object
:rtype: :class: `bravado_core.http_future.HttpFuture`
Expand Down
48 changes: 32 additions & 16 deletions bravado/http_future.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,52 @@


class HttpFuture(object):
"""A future which inputs HTTP params"""

def __init__(self, future, response_adapter, callback):
"""Kicks API call for Fido client
:param future: future object
:type future: :class: `concurrent.futures.Future`
:param response_adapter: Adapter which exposes json(), status_code()
:type response_adapter: :class: `bravado_core.response.IncomingResponse`
:param callback: Function to be called on the response
"""
"""Wrapper for a :class:`concurrent.futures.Future` that returns an HTTP
response.
:param future: The concurrent future to wrap.
:type future: :class: `concurrent.futures.Future`
:param response_adapter: Adapter type which exposes the innards of the HTTP
response in a non-http client specific way.
:type response_adapter: type that is a subclass of
:class:`bravado_core.response.IncomingResponse`.
:param callback: Function to be called on the response (usually for
bravado-core post-processing).
:param also_return_response: Determines if the incoming http response is
included as part of the return value from calling
`HttpFuture.result()`.
When False, only the swagger result is returned.
When True, the tuple(swagger result, http response) is returned.
This is useful if you want access to additional data that is not
accessible from the swagger result. e.g. http headers,
http response code, etc.
Defaults to False for backwards compatibility.
"""
def __init__(self, future, response_adapter, callback,
also_return_response=False):
self.future = future
self.response_adapter = response_adapter
self.response_callback = callback
self.also_return_response = also_return_response

def result(self, timeout=None):
"""Blocking call to wait for API response
"""Blocking call to wait for the HTTP response.
:param timeout: Number of seconds to wait for a response. Defaults to
None which means wait indefinitely.
:type timeout: float
:return: swagger response return value when given a callback or the
http_response otherwise.
:return: Depends on the value of also_return_response sent in
to the constructor.
"""
inner_response = self.future.result(timeout=timeout)
incoming_response = self.response_adapter(inner_response)

if self.response_callback:
swagger_return_value = self.response_callback(incoming_response)
return swagger_return_value
self.response_callback(incoming_response)
swagger_result = incoming_response.swagger_result
if self.also_return_response:
return swagger_result, incoming_response
return swagger_result

if 200 <= incoming_response.status_code < 300:
return incoming_response
Expand Down
7 changes: 6 additions & 1 deletion bravado/requests_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,15 @@ def separate_params(request_params):

return sanitized_params, misc_options

def request(self, request_params, response_callback=None):
def request(self, request_params, response_callback=None,
also_return_response=False):
"""
:param request_params: complete request data.
:type request_params: dict
:param response_callback: Function to be called on the response
:param also_return_response: Consult the constructor documentation for
:class:`bravado.http_future.HttpFuture`.
:returns: HTTP Future object
:rtype: :class: `bravado_core.http_future.HttpFuture`
"""
Expand All @@ -134,6 +138,7 @@ def request(self, request_params, response_callback=None):
requests_future,
RequestsResponseAdapter,
response_callback,
also_return_response
)

def set_basic_auth(self, host, username, password):
Expand Down
Loading

0 comments on commit 4ac01e6

Please sign in to comment.