Skip to content

Commit

Permalink
utils: new get_default_endpoint_for()
Browse files Browse the repository at this point in the history
* NEW Introduces new get_default_endpoint_for() helper to obtain the
  default endpoint-prefix to be used to build endpoints for a given
  `pid_type`. (closes inveniosoftware#101)

* NEW Introduces new `default_endpoint_prefix` prefix to explicitly
  declare which `endpoint-prefix` to use in case of ambiguity.

Signed-off-by: Samuele Kaplun <[email protected]>
  • Loading branch information
kaplun committed Oct 5, 2016
1 parent 3fad849 commit a66a238
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 10 deletions.
13 changes: 9 additions & 4 deletions invenio_records_rest/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,9 @@ def can(self):
RECORDS_REST_ENDPOINTS = {
'record-pid-type': {
'endpoint-prefix': {
'create_permission_factory_imp': permission_check_factory(),
'default_endpoint_prefix': True,
'default_media_type': 'application/json',
'delete_permission_factory_imp': permission_check_factory(),
'item_route': ''/recods/<pid(record-pid-type):pid_value>'',
Expand Down Expand Up @@ -132,6 +133,10 @@ def can(self):
:param create_permission_factory_imp: Import path to factory that create
permission object for a given record.
:param default_endpoint_prefix: declare the current endpoint as the default
when building endpoints for the defined ``pid_type``. By default the
default prefix is defined to be the value of ``pid_type``.
:param default_media_type: Default media type for both records and search.
:param delete_permission_factory_imp: Import path to factory that creates a
Expand All @@ -146,10 +151,10 @@ def can(self):
:param max_result_window: Maximum total number of records retrieved from a
query.
:param pid_type: It specifies the record pid type. It's used also to build the
endpoint name. Required.
:param pid_type: It specifies the record pid type. Required.
You can generate an URL to list all records of the given ``pid_type`` by
calling ``url_for('invenio_records_rest.{0}_list'.format(pid_type))``.
calling ``url_for('invenio_records_rest.{0}_list'.format(
get_default_endpoint_for(pid_type)))``.
:param pid_fetcher: It identifies the registered fetcher name. Required.
Expand Down
4 changes: 3 additions & 1 deletion invenio_records_rest/links.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@

from flask import url_for

from .utils import get_default_endpoint_for


def default_links_factory(pid):
"""Factory for record links generation.
:param pid: A Persistent Identifier instance.
:returns: Dictionary containing a list of useful links for the record.
"""
endpoint = '.{0}_item'.format(pid.pid_type)
endpoint = '.{0}_item'.format(get_default_endpoint_for(pid.pid_type))
links = dict(self=url_for(endpoint, pid_value=pid.pid_value,
_external=True))
return links
32 changes: 30 additions & 2 deletions invenio_records_rest/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,33 @@
PIDUnregisteredRESTError


@cached_property
def get_default_endpoint_for(pid_type, _record_rest_endpoints=None):
"""Get default endpoint for the given pid_type."""
if _record_rest_endpoints is None:
_record_rest_endpoints = current_app.config['RECORD_REST_ENDPOINTS']

endpoint_prefix = None

for key, value in _record_rest_endpoints.items():
if (value['pid_type'] == pid_type and
value.get('default_endpoint_prefix')):
if endpoint_prefix is None:
endpoint_prefix = key
else:
raise ValueError('More than one endpoint-prefix has been '
'defined as default for '
'pid_type="{0}"'.format(pid_type))

if endpoint_prefix:
return endpoint_prefix
if pid_type in _record_rest_endpoints:
return pid_type

raise ValueError('No endpoint-prefix corresponds to pid_type="{0}"'.format(
pid_type))


def obj_or_import_string(value, default=None):
"""Import string or return object.
Expand Down Expand Up @@ -129,7 +156,8 @@ def data(self):
except PIDRedirectedError as e:
try:
location = url_for(
'.{0}_item'.format(e.destination_pid.pid_type),
'.{0}_item'.format(
get_default_endpoint_for(e.destination_pid.pid_type)),
pid_value=e.destination_pid.pid_value)
data = dict(
status=301,
Expand All @@ -139,7 +167,7 @@ def data(self):
response = make_response(jsonify(data), data['status'])
response.headers['Location'] = location
abort(response)
except BuildError:
except (BuildError, ValueError):
current_app.logger.exception(
'Invalid redirect - pid_type "{0}" '
'endpoint missing.'.format(
Expand Down
6 changes: 3 additions & 3 deletions invenio_records_rest/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
from .links import default_links_factory
from .proxies import current_records_rest
from .query import default_search_factory
from .utils import obj_or_import_string
from .utils import get_default_endpoint_for, obj_or_import_string


def elasticsearch_query_parsing_exception_handler(error):
Expand Down Expand Up @@ -452,7 +452,7 @@ def get(self, **kwargs):
size=size,
_external=True,
)
endpoint = '.{0}_list'.format(self.pid_type)
endpoint = '.{0}_list'.format(get_default_endpoint_for(self.pid_type))
links = dict(self=url_for(endpoint, page=page, **urlkwargs))
if page > 1:
links['prev'] = url_for(endpoint, page=page - 1, **urlkwargs)
Expand Down Expand Up @@ -515,7 +515,7 @@ def post(self, **kwargs):
pid, record, 201, links_factory=self.item_links_factory)

# Add location headers
endpoint = '.{0}_item'.format(pid.pid_type)
endpoint = '.{0}_item'.format(get_default_endpoint_for(pid.pid_type))
location = url_for(endpoint, pid_value=pid.pid_value, _external=True)
response.headers.extend(dict(location=location))
return response
Expand Down
104 changes: 104 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2016 CERN.
#
# Invenio is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# Invenio is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Invenio; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307, USA.
#
# In applying this license, CERN does not
# waive the privileges and immunities granted to it by virtue of its status
# as an Intergovernmental Organization or submit itself to any jurisdiction.


"""Utils tests."""

from __future__ import absolute_import, print_function

from pytest import raises

from invenio_records_rest.utils import get_default_endpoint_for


def test_get_default_endpoint_for():
"""Test get_default_endpoint_for()."""
assert get_default_endpoint_for('recid', {
'recid': {
'pid_type': 'recid',
}}) == 'recid'

assert get_default_endpoint_for('recid', {
'recid': {
'pid_type': 'recid',
'default_endpoint_prefix': True,
}}) == 'recid'

assert get_default_endpoint_for('recid', {
'recid': {
'pid_type': 'recid',
},
'recid2': {
'pid_type': 'recid',
}}) == 'recid'

assert get_default_endpoint_for('recid', {
'recid': {
'pid_type': 'recid',
'default_endpoint_prefix': True,
},
'recid2': {
'pid_type': 'recid',
}}) == 'recid'

assert get_default_endpoint_for('recid', {
'recid': {
'pid_type': 'recid',
},
'recid2': {
'pid_type': 'recid',
'default_endpoint_prefix': True,
}}) == 'recid2'

with raises(ValueError) as excinfo:
get_default_endpoint_for('recid', {
'recid1': {
'pid_type': 'recid',
'default_endpoint_prefix': True,
},
'recid2': {
'pid_type': 'recid',
'default_endpoint_prefix': True,
}})
assert 'More than one' in str(excinfo.value)

with raises(ValueError) as excinfo:
get_default_endpoint_for('recid', {
'recid1': {
'pid_type': 'recid',
},
'recid2': {
'pid_type': 'recid',
}})
assert 'No endpoint-prefix' in str(excinfo.value)

with raises(ValueError) as excinfo:
get_default_endpoint_for('foo', {
'recid1': {
'pid_type': 'recid',
},
'recid2': {
'pid_type': 'recid',
}})
assert 'No endpoint-prefix' in str(excinfo.value)

0 comments on commit a66a238

Please sign in to comment.