Skip to content

Commit

Permalink
Merge branch 'DEV_0.7.0' of github.com:OnroerendErfgoed/pyramid_urire…
Browse files Browse the repository at this point in the history
…ferencer into issues/18-improve-accept-header-check
  • Loading branch information
Wim-De-Clercq committed Jan 29, 2019
2 parents 4d8a3ad + e770fe4 commit 510ff03
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 61 deletions.
2 changes: 2 additions & 0 deletions pyramid_urireferencer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
)
from zope.interface import Interface

from pyramid_urireferencer import protected_resources
from .referencer import Referencer
from .renderers import json_renderer

Expand All @@ -17,6 +18,7 @@ def includeme(config):
"""this function adds some configuration for the application"""
config.add_route('references', '/references')
_add_referencer(config.registry)
config.add_view_deriver(protected_resources.protected_view)
config.add_renderer('json_item', json_renderer)
config.scan()

Expand Down
169 changes: 108 additions & 61 deletions pyramid_urireferencer/protected_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
that might be used in external applications.
.. versionadded:: 0.4.0
"""
import functools
import logging

from pyramid.httpexceptions import (
Expand All @@ -16,6 +17,69 @@
log = logging.getLogger(__name__)


def _advice(request):
referencer = pyramid_urireferencer.get_referencer(request.registry)
uri = referencer.get_uri(request)
registery_response = referencer.is_referenced(uri)
if registery_response.has_references:
if 'application/json' in request.accept:
response = Response()
response.status_code = 409
response_json = {
"message": "The uri {0} is still in use by other applications. "
"A total of {1} references have been found.".format(uri, registery_response.count),
"errors": [],
"registry_response": registery_response.to_json()
}
for app_response in registery_response.applications:
if app_response.has_references:
error_string = "{0}: {1} references found, such as {2}" \
.format(app_response.uri,
app_response.count,
', '.join([i.uri for i in app_response.items]))
response_json["errors"].append(error_string)
response.json_body = response_json
response.content_type = 'application/json'
return response
else:
raise HTTPConflict(
detail="Urireferencer: The uri {0} is still in use by other applications. "
"A total of {1} references have been found "
"in the following applications: {2}".format(uri, registery_response.count,
', '.join([app_response.title for app_response in
registery_response.applications
if app_response.has_references])))
elif not registery_response.success:
if 'application/json' in request.accept:
response = Response()
response.status_code = 500
response_json = {
"message": "Unable to verify the uri {0} is no longer being used.".format(uri),
"errors": [],
"registry_response": registery_response.to_json()
}
for app_response in registery_response.applications:
if not app_response.success:
response_json["errors"].append(
"{}: Could not verify the uri is no longer being used.".format(app_response.uri))
response.json_body = response_json
response.content_type = 'application/json'
return response
else:
log.error("Urireferencer: Unable to verify the uri {0} is no longer being used. "
"Could not verify with {1}".format(uri, ', '
.join(["{0} ({1})".format(app_response.uri,
app_response.service_url)
for app_response
in registery_response.applications if
not app_response.success])))
raise HTTPInternalServerError(
detail="Urireferencer: Unable to verify the uri {0} is no longer being used. "
"Could not verify with {1}".format(uri, ', '.join([app_response.uri for app_response
in registery_response.applications if
not app_response.success])))


def protected_operation(fn):
"""
Use this decorator to prevent an operation from being executed
Expand All @@ -28,68 +92,51 @@ def protected_operation(fn):
:raises pyramid.httpexceptions.HTTPInternalServerError: Raised when we were
unable to check that the URI is no longer being used.
"""

@functools.wraps(fn)
def advice(parent_object, *args, **kw):
referencer = pyramid_urireferencer.get_referencer(parent_object.request.registry)
uri = referencer.get_uri(parent_object.request)
registery_response = referencer.is_referenced(uri)
if registery_response.has_references:
if 'application/json' in parent_object.request.accept:
response = Response()
response.status_code = 409
response_json = {
"message": "The uri {0} is still in use by other applications. "
"A total of {1} references have been found.".format(uri, registery_response.count),
"errors": [],
"registry_response": registery_response.to_json()
}
for app_response in registery_response.applications:
if app_response.has_references:
error_string = "{0}: {1} references found, such as {2}" \
.format(app_response.uri,
app_response.count,
', '.join([i.uri for i in app_response.items]))
response_json["errors"].append(error_string)
response.json_body = response_json
response.content_type = 'application/json'
return response
else:
raise HTTPConflict(
detail="Urireferencer: The uri {0} is still in use by other applications. "
"A total of {1} references have been found "
"in the following applications: {2}".format(uri, registery_response.count,
', '.join([app_response.title for app_response in
registery_response.applications
if app_response.has_references])))
elif not registery_response.success:
if 'application/json' in parent_object.request.accept:
response = Response()
response.status_code = 500
response_json = {
"message": "Unable to verify the uri {0} is no longer being used.".format(uri),
"errors": [],
"registry_response": registery_response.to_json()
}
for app_response in registery_response.applications:
if not app_response.success:
response_json["errors"].append(
"{}: Could not verify the uri is no longer being used.".format(app_response.uri))
response.json_body = response_json
response.content_type = 'application/json'
response = _advice(parent_object.request)
if response is not None:
return response
else:
return fn(parent_object, *args, **kw)

return advice


def protected_operation_with_request(fn):
"""
Use this decorator to prevent an operation from being executed
when the related uri resource is still in use.
The request must contain a registry.queryUtility(IReferencer)
:raises pyramid.httpexceptions.HTTPConflict: Signals that we don't want to
delete a certain URI because it's still in use somewhere else.
:raises pyramid.httpexceptions.HTTPInternalServerError: Raised when we were
unable to check that the URI is no longer being used.
"""

@functools.wraps(fn)
def wrapped(request, *args, **kwargs):
response = _advice(request)
if response is not None:
return response
else:
return fn(request, *args, **kwargs)

return wrapped


def protected_view(view, info):
"""allows adding `protected=True` to a view_config`"""

if info.options.get('protected'):
def wrapper_view(context, request):
response = _advice(request)
if response is not None:
return response
else:
log.error("Urireferencer: Unable to verify the uri {0} is no longer being used. "
"Could not verify with {1}".format(uri, ', '
.join(["{0} ({1})".format(app_response.uri,
app_response.service_url)
for app_response
in registery_response.applications if
not app_response.success])))
raise HTTPInternalServerError(
detail="Urireferencer: Unable to verify the uri {0} is no longer being used. "
"Could not verify with {1}".format(uri, ', '.join([app_response.uri for app_response
in registery_response.applications if
not app_response.success])))
return fn(parent_object, *args, **kw)
return view(context, request)
return wrapper_view
return view

return advice

protected_view.options = ('protected',)
94 changes: 94 additions & 0 deletions tests/test_protected_resources.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# -*- coding: utf-8 -*-
import logging
import unittest

import pytest
from pyramid import testing
from webob.acceptparse import AcceptValidHeader

from pyramid_urireferencer.protected_resources import protected_operation
from pyramid_urireferencer.protected_resources \
import protected_operation_with_request
from pyramid_urireferencer.models import RegistryResponse, Item, ApplicationResponse
from pyramid.httpexceptions import HTTPConflict, HTTPInternalServerError

Expand Down Expand Up @@ -143,3 +147,93 @@ def test_protected_operation_500_json(self, is_referenced_mock):

is_referenced_call = is_referenced_mock.mock_calls[0]
self.assertEqual('https://id.erfgoed.net/resources/1', is_referenced_call[1][0])


@protected_operation_with_request
def protected_dummy(request):
return 'dummy ok'


@patch('pyramid_urireferencer.protected_resources.pyramid_urireferencer'
'.Referencer.is_referenced')
class TestProtectedWithRequest(object):
def setUp(self):
logging.basicConfig(level=logging.WARN)

@pytest.fixture()
def request(self):
request = testing.DummyRequest()
request.accept = AcceptValidHeader('application/html')
config = testing.setUp(request=request)
config.registry.settings = {
'urireferencer.referencer': 'test_views.TestReferencer',
'urireferencer.registry_url': 'http://my.registry.org'
}
config.include('pyramid_urireferencer')
return request

def test_protected_operation(self, is_referenced_mock, request):
is_referenced_mock.return_value = RegistryResponse(
'https://id.erfgoed.net/resources/1', True, False, 0, [])
protected_dummy(request)
is_referenced_call = is_referenced_mock.mock_calls[0]
assert 'https://id.erfgoed.net/resources/1' == is_referenced_call[1][0]

def test_protected_operation_409(self, is_referenced_mock, request):
is_referenced_mock.return_value = RegistryResponse(
'https://id.erfgoed.net/resources/1', True, True, 10,
[get_app(1), get_app(2)])
with pytest.raises(HTTPConflict):
protected_dummy(request)
is_referenced_call = is_referenced_mock.mock_calls[0]
assert 'https://id.erfgoed.net/resources/1' == is_referenced_call[1][0]

def test_protected_operation_409_2(self, is_referenced_mock, request):
is_referenced_mock.return_value = RegistryResponse(
'https://id.erfgoed.net/resources/1', False, True, 10,
[get_app(1), get_app(2)])
with pytest.raises(HTTPConflict):
protected_dummy(request)
is_referenced_call = is_referenced_mock.mock_calls[0]
assert 'https://id.erfgoed.net/resources/1' == is_referenced_call[1][0]

def test_protected_operation_409_json(self, is_referenced_mock, request):
request.accept = AcceptValidHeader('application/json')
is_referenced_mock.return_value = RegistryResponse(
'https://id.erfgoed.net/resources/1', False, True, 2,
[get_app(1), get_app(2)])
res = protected_dummy(request)
assert 409 == res.status_code
msg = ("The uri https://id.erfgoed.net/resources/1 is still in use by "
"other applications. A total of 2 references have been found.")
assert res.json_body["message"] == msg
assert "application/json" == res.content_type

is_referenced_call = is_referenced_mock.mock_calls[0]
assert 'https://id.erfgoed.net/resources/1' == is_referenced_call[1][0]

def test_protected_operation_500(self, is_referenced_mock, request):
is_referenced_mock.return_value = RegistryResponse(
'https://id.erfgoed.net/resources/1', False, None, None,
[get_app_500()])
with pytest.raises(HTTPInternalServerError):
protected_dummy(request)
is_referenced_call = is_referenced_mock.mock_calls[0]
assert 'https://id.erfgoed.net/resources/1' == is_referenced_call[1][0]

def test_protected_operation_500_json(self, is_referenced_mock, request):
request.accept = AcceptValidHeader('application/json')
is_referenced_mock.return_value = RegistryResponse(
'https://id.erfgoed.net/resources/1', False, None, None,
[get_app_500()])
res = protected_dummy(request)
assert 500 == res.status_code
msg = ("Unable to verify the uri https://id.erfgoed.net/resources/1 "
"is no longer being used.")
assert res.json_body["message"] == msg
errors = ["https://dev-app.onroerenderfgoed.be/: Could not verify the "
"uri is no longer being used."]
assert res.json_body["errors"] == errors
assert "application/json" == res.content_type
is_referenced_call = is_referenced_mock.mock_calls[0]
assert 'https://id.erfgoed.net/resources/1' == is_referenced_call[1][0]

0 comments on commit 510ff03

Please sign in to comment.