From 48c4fd790fa0c29588cd7e6743b3ecbf8fef4ecb Mon Sep 17 00:00:00 2001 From: "Brian J. Dowling" Date: Sat, 7 Apr 2018 23:46:00 -0400 Subject: [PATCH 1/2] Added add_linked_fetch config option to promote API links --- bravado/client.py | 10 ++++++++++ bravado/http_future.py | 43 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/bravado/client.py b/bravado/client.py index ed838a9d..9340c02c 100644 --- a/bravado/client.py +++ b/bravado/client.py @@ -148,6 +148,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.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) diff --git a/bravado/http_future.py b/bravado/http_future.py index fd7be4f6..f37ec209 100644 --- a/bravado/http_future.py +++ b/bravado/http_future.py @@ -199,16 +199,57 @@ 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_linked_fetch', 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): + for key in result: + properties = content_spec['properties'][key] + linked_op_id = properties.get('x-operationId') + if (linked_op_id + and properties.get('type') == 'string' + and properties.get('format') == 'url'): + + linked_op = op.swagger_spec._get_operation(linked_op_id) + if linked_op: + result[key] = AddLinkedFetchWrapper(result[key], linked_op) + else: + # XXX find more appropriate exception + raise NotImplementedError("x-operationId: {} NOT FOUND".format(linked_op_id)) + + +class AddLinkedFetchWrapper(object): + def __init__(self, baseObject, op): + self.__class__ = type(baseObject.__class__.__name__, + (self.__class__, baseObject.__class__), + {}) + self.__dict__ = baseObject.__dict__ + self._linked_operation = op + + def _linked_operation(self): + return self._linked_operation + + def _linked_fetch(self): + op = self._linked_operation + + http_client = op.swagger_spec.http_client + return http_client.request({'method': 'GET', 'url': self.__str__}, + operation=op, + also_return_response=True) + # op.config.get('also_return_response')) + + def raise_on_unexpected(http_response): """Raise an HTTPError if the response is 5XX. From b577eb0df8738350423a6b36f06784d35f2ef66c Mon Sep 17 00:00:00 2001 From: "Brian J. Dowling" Date: Sun, 22 Apr 2018 22:04:47 -0400 Subject: [PATCH 2/2] FetchLink rename option to add_fetch_link fixed fetching also_return_response from config added docstrings misc cleanups --- bravado/client.py | 4 +++- bravado/http_future.py | 52 ++++++++++++++++++++++++++++-------------- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/bravado/client.py b/bravado/client.py index 9340c02c..02e0d1b8 100644 --- a/bravado/client.py +++ b/bravado/client.py @@ -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): @@ -153,7 +155,7 @@ def _get_operation(self, operation_id): # 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.resources.values(): + for resource in self.swagger_spec.resources.values(): op = resource.operations.get(operation_id) if op: return op diff --git a/bravado/http_future.py b/bravado/http_future.py index f37ec209..964cd730 100644 --- a/bravado/http_future.py +++ b/bravado/http_future.py @@ -205,7 +205,7 @@ def unmarshal_response_inner(response, op): value=content_value, ) - if op.swagger_spec.config.get('add_linked_fetch', False): + if op.swagger_spec.config.get('add_fetch_link', False): wrap_linked_objects_with_fetch(op, result, content_spec) return result @@ -214,40 +214,58 @@ def unmarshal_response_inner(response, op): 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'][key] + properties = content_spec['properties'].get(key) linked_op_id = properties.get('x-operationId') - if (linked_op_id + if (linked_op_id and result[key] and properties.get('type') == 'string' and properties.get('format') == 'url'): - linked_op = op.swagger_spec._get_operation(linked_op_id) + linked_op = op.swagger_spec._client._get_operation(linked_op_id) + if linked_op: - result[key] = AddLinkedFetchWrapper(result[key], 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 AddLinkedFetchWrapper(object): - def __init__(self, baseObject, op): - self.__class__ = type(baseObject.__class__.__name__, - (self.__class__, baseObject.__class__), - {}) - self.__dict__ = baseObject.__dict__ +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 _linked_operation(self): - return self._linked_operation + 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 _linked_fetch(self): + def _fetch_linked(self): op = self._linked_operation http_client = op.swagger_spec.http_client - return http_client.request({'method': 'GET', 'url': self.__str__}, + return http_client.request({'method': 'GET', 'url': str(self)}, operation=op, - also_return_response=True) - # op.config.get('also_return_response')) + also_return_response=op.swagger_spec.config.get('also_return_response')) def raise_on_unexpected(http_response):