From 44d626e40ed08ee4acff581b153b68ff20b2da66 Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Mon, 4 Nov 2019 12:39:22 +1100 Subject: [PATCH 1/4] Assign correct endpoint to each resource instance --- flask_rest_jsonapi/api.py | 7 ++++++- flask_rest_jsonapi/resource.py | 12 +++++++++++- tests/test_sqlalchemy_data_layer.py | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/flask_rest_jsonapi/api.py b/flask_rest_jsonapi/api.py index a385256..65cecb0 100644 --- a/flask_rest_jsonapi/api.py +++ b/flask_rest_jsonapi/api.py @@ -66,10 +66,15 @@ def route(self, resource, view, *urls, **kwargs): :param list urls: the urls of the view :param dict kwargs: additional options of the route """ + # Give the resource class a default endpoint. This will be overwritten if route() + # is called again for this resource resource.view = view url_rule_options = kwargs.get('url_rule_options') or dict() - view_func = resource.as_view(view) + view_func = resource.as_view( + view, + endpoint=view, + ) if 'blueprint' in kwargs: resource.view = '.'.join([kwargs['blueprint'].name, resource.view]) diff --git a/flask_rest_jsonapi/resource.py b/flask_rest_jsonapi/resource.py index 34ceccf..d940306 100644 --- a/flask_rest_jsonapi/resource.py +++ b/flask_rest_jsonapi/resource.py @@ -52,13 +52,23 @@ def __new__(cls, name, bases, d): class Resource(MethodView): """Base resource class""" - def __new__(cls): + def __new__(cls, **kwargs): """Constructor of a resource instance""" if hasattr(cls, '_data_layer'): cls._data_layer.resource = cls return super(Resource, cls).__new__(cls) + def __init__(self, endpoint=None): + # By default we assign each Resource class with a view/endpoint. However if the + # same resource is used for multiple routes, the endpoint will be overwritten. + # This ensures the Resource instances have the correct view + if endpoint is not None: + self.view = endpoint + + def parse_request(self): + return self.request_parsers[request.content_type](request) + @jsonapi_exception_formatter def dispatch_request(self, *args, **kwargs): """Logic of how to handle a request""" diff --git a/tests/test_sqlalchemy_data_layer.py b/tests/test_sqlalchemy_data_layer.py index ea40923..08e5cf0 100644 --- a/tests/test_sqlalchemy_data_layer.py +++ b/tests/test_sqlalchemy_data_layer.py @@ -1800,6 +1800,24 @@ def test_api_resources(app, person_list): api.route(person_list, 'person_list2', '/persons', '/person_list') api.init_app(app) +def test_api_resources_multiple_route(app, person_list): + """ + If we use the same resource twice, each instance of that resource should have the + correct endpoint + """ + api = Api() + + class DummyResource(ResourceDetail): + def get(self): + return self.view + + api.route(DummyResource, 'endpoint1', '/url1') + api.route(DummyResource, 'endpoint2', '/url2') + api.init_app(app) + + with app.test_client() as client: + assert client.get('/url1', content_type='application/vnd.api+json').json == 'endpoint1' + assert client.get('/url2', content_type='application/vnd.api+json').json == 'endpoint2' def test_relationship_containing_hyphens(client, register_routes, person_computers, computer_schema, person): response = client.get('/persons/{}/relationships/computers-owned'.format(person.person_id), content_type='application/vnd.api+json') From 01d28471e779b0dc4a3e9e4bfa04c83a614695f0 Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Mon, 4 Nov 2019 13:19:22 +1100 Subject: [PATCH 2/4] Add the blueprint prefix to all endpoint names --- flask_rest_jsonapi/api.py | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/flask_rest_jsonapi/api.py b/flask_rest_jsonapi/api.py index 65cecb0..49bffbd 100644 --- a/flask_rest_jsonapi/api.py +++ b/flask_rest_jsonapi/api.py @@ -71,27 +71,35 @@ def route(self, resource, view, *urls, **kwargs): resource.view = view url_rule_options = kwargs.get('url_rule_options') or dict() + # Find the parent object for this route, and also the correct endpoint name + if 'blueprint' in kwargs: + view_name = '.'.join([kwargs['blueprint'].name, resource.view]) + blueprint = kwargs['blueprint'] + elif self.blueprint is not None: + view_name = '.'.join([self.blueprint.name, resource.view]) + blueprint = self.blueprint + elif self.app is not None: + view_name = resource.view + blueprint = self.app + else: + view_name = resource.view + blueprint = None + view_func = resource.as_view( view, - endpoint=view, + endpoint=view_name, ) - if 'blueprint' in kwargs: - resource.view = '.'.join([kwargs['blueprint'].name, resource.view]) - for url in urls: - kwargs['blueprint'].add_url_rule(url, view_func=view_func, **url_rule_options) - elif self.blueprint is not None: - resource.view = '.'.join([self.blueprint.name, resource.view]) - for url in urls: - self.blueprint.add_url_rule(url, view_func=view_func, **url_rule_options) - elif self.app is not None: + if blueprint is not None: for url in urls: - self.app.add_url_rule(url, view_func=view_func, **url_rule_options) + blueprint.add_url_rule(url, view_func=view_func, **url_rule_options) else: - self.resources.append({'resource': resource, - 'view': view, - 'urls': urls, - 'url_rule_options': url_rule_options}) + self.resources.append({ + 'resource': resource, + 'view': view, + 'urls': urls, + 'url_rule_options': url_rule_options + }) self.resource_registry.append(resource) From c91d5a8968d7d7f803ebfa5fac49b2d210e8c789 Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Mon, 4 Nov 2019 16:35:27 +1100 Subject: [PATCH 3/4] Use the same endpoint logic for setting view on the class --- flask_rest_jsonapi/api.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/flask_rest_jsonapi/api.py b/flask_rest_jsonapi/api.py index 49bffbd..66354c0 100644 --- a/flask_rest_jsonapi/api.py +++ b/flask_rest_jsonapi/api.py @@ -66,9 +66,6 @@ def route(self, resource, view, *urls, **kwargs): :param list urls: the urls of the view :param dict kwargs: additional options of the route """ - # Give the resource class a default endpoint. This will be overwritten if route() - # is called again for this resource - resource.view = view url_rule_options = kwargs.get('url_rule_options') or dict() # Find the parent object for this route, and also the correct endpoint name @@ -85,6 +82,10 @@ def route(self, resource, view, *urls, **kwargs): view_name = resource.view blueprint = None + # Give the resource class a default endpoint. This will be overwritten if route() + # is called again for this resource + resource.view = view_name + view_func = resource.as_view( view, endpoint=view_name, From fbf28cdeb2460959c9ea9349efa04cf476aaf97d Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Mon, 4 Nov 2019 16:42:22 +1100 Subject: [PATCH 4/4] Fix previous commit --- flask_rest_jsonapi/api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flask_rest_jsonapi/api.py b/flask_rest_jsonapi/api.py index 66354c0..5b6e459 100644 --- a/flask_rest_jsonapi/api.py +++ b/flask_rest_jsonapi/api.py @@ -70,16 +70,16 @@ def route(self, resource, view, *urls, **kwargs): # Find the parent object for this route, and also the correct endpoint name if 'blueprint' in kwargs: - view_name = '.'.join([kwargs['blueprint'].name, resource.view]) + view_name = '.'.join([kwargs['blueprint'].name, view]) blueprint = kwargs['blueprint'] elif self.blueprint is not None: - view_name = '.'.join([self.blueprint.name, resource.view]) + view_name = '.'.join([self.blueprint.name, view]) blueprint = self.blueprint elif self.app is not None: - view_name = resource.view + view_name = view blueprint = self.app else: - view_name = resource.view + view_name = view blueprint = None # Give the resource class a default endpoint. This will be overwritten if route()