Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fetch_link to make it much easier to follow linked URLs within an API #357

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions bravado/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ class SwaggerClient(object):
def __init__(self, swagger_spec, also_return_response=False):
self.__also_return_response = also_return_response
self.swagger_spec = swagger_spec
# provide a backref to the client from the spec
self.swagger_spec._client = self

@classmethod
def from_url(cls, spec_url, http_client=None, request_headers=None, config=None):
Expand Down Expand Up @@ -148,6 +150,16 @@ def _get_resource(self, item):
# execute a service call via the http_client.
return ResourceDecorator(resource, self.__also_return_response)

def _get_operation(self, operation_id):
""" Find an operation by operationId """

# Find it in the resources, we could add a single dict to lookup
# operationId since it is required to be unique across the spec
for resource in self.swagger_spec.resources.values():
op = resource.operations.get(operation_id)
if op:
return op

def __repr__(self):
return u"%s(%s)" % (self.__class__.__name__, self.swagger_spec.api_url)

Expand Down
61 changes: 60 additions & 1 deletion bravado/http_future.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,16 +199,75 @@ def unmarshal_response_inner(response, op):
if op.swagger_spec.config.get('validate_responses', False):
validate_schema_object(op.swagger_spec, content_spec, content_value)

return unmarshal_schema_object(
result = unmarshal_schema_object(
swagger_spec=op.swagger_spec,
schema_object_spec=content_spec,
value=content_value,
)

if op.swagger_spec.config.get('add_fetch_link', False):
wrap_linked_objects_with_fetch(op, result, content_spec)

return result
# TODO: Non-json response contents
return response.text


def wrap_linked_objects_with_fetch(op, result, content_spec):
"""
Wraps any returned results that have a x-operationId property
with a wrapper that allows the object to be callable to retrieve
the linked resource.
You can use both obj() and obj._fetch_linked() to access
the http_request for the linked resource.
"""

for key in result:
properties = content_spec['properties'].get(key)
linked_op_id = properties.get('x-operationId')
if (linked_op_id and result[key]
and properties.get('type') == 'string'
and properties.get('format') == 'url'):

linked_op = op.swagger_spec._client._get_operation(linked_op_id)

if linked_op:
result[key] = FetchLink(result[key], op=linked_op)
else:
# XXX find more appropriate exception
raise NotImplementedError("x-operationId: {} NOT FOUND".format(linked_op_id))


class FetchLink(object):
"""
This class makes an url object callable.

You can use both obj() and obj._fetch_linked() to access
the http_request for the linked resource.
"""

def __init__(self, url, op):
self._url = str(url)
self._linked_operation = op

def __str__(self):
return self._url

def __repr__(self):
return "{}({},{})".format(self.__class__.__name__, self._url, str(self._linked_operation))

def __call__(self):
return self._fetch_linked()

def _fetch_linked(self):
op = self._linked_operation

http_client = op.swagger_spec.http_client
return http_client.request({'method': 'GET', 'url': str(self)},
operation=op,
also_return_response=op.swagger_spec.config.get('also_return_response'))


def raise_on_unexpected(http_response):
"""Raise an HTTPError if the response is 5XX.

Expand Down