diff --git a/swh/web/ui/main.py b/swh/web/ui/main.py index 287774242..4837a8e76 100644 --- a/swh/web/ui/main.py +++ b/swh/web/ui/main.py @@ -1,141 +1,139 @@ # Copyright (C) 2015 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information import logging import os import json from flask import Flask from swh.core import config -from swh.web.ui.renderers import RENDERERS, urlize_api_links +from swh.web.ui.renderers import urlize_api_links from swh.web.ui.renderers import safe_docstring_display from swh.web.ui.renderers import revision_id_from_url from swh.web.ui.renderers import SWHMultiResponse from swh.storage import get_storage DEFAULT_CONFIG = { 'storage_args': ('list[str]', ['http://localhost:5000/']), 'storage_class': ('str', 'remote_storage'), 'log_dir': ('string', '/tmp/swh/log'), 'debug': ('bool', None), 'host': ('string', '127.0.0.1'), 'port': ('int', 6543), 'secret_key': ('string', 'development key'), 'max_log_revs': ('int', 25), } # api's definition app = Flask(__name__) app.response_class = SWHMultiResponse app.jinja_env.filters['urlize_api_links'] = urlize_api_links app.jinja_env.filters['safe_docstring_display'] = safe_docstring_display app.jinja_env.filters['revision_id_from_url'] = revision_id_from_url def read_config(config_file): """Read the configuration file `config_file`, update the app with parameters (secret_key, conf) and return the parsed configuration as a dict""" conf = config.read(config_file, DEFAULT_CONFIG) config.prepare_folders(conf, 'log_dir') conf['storage'] = get_storage(conf['storage_class'], conf['storage_args']) return conf def load_controllers(): """Load the controllers for the application. """ from swh.web.ui import views, apidoc # flake8: noqa def rules(): """Returns rules from the application in dictionary form. Beware, must be called after swh.web.ui.main.load_controllers funcall. Returns: Generator of application's rules. """ for rule in app.url_map._rules: yield {'rule': rule.rule, 'methods': rule.methods, 'endpoint': rule.endpoint} def storage(): """Return the current application's storage. """ return app.config['conf']['storage'] def run_from_webserver(environ, start_response): """Run the WSGI app from the webserver, loading the configuration. Note: This function is called on a per-request basis so beware the side effects here! """ load_controllers() config_path = '/etc/softwareheritage/webapp/webapp.ini' conf = read_config(config_path) app.secret_key = conf['secret_key'] app.config['conf'] = conf - app.config['DEFAULT_RENDERERS'] = RENDERERS logging.basicConfig(filename=os.path.join(conf['log_dir'], 'web-ui.log'), level=logging.INFO) return app(environ, start_response) def run_debug_from(config_path, verbose=False): """Run the api's server in dev mode. Note: This is called only once (contrast with the production mode in run_from_webserver function) Args: conf is a dictionary of keywords: - 'db_url' the db url's access (through psycopg2 format) - 'content_storage_dir' revisions/directories/contents storage on disk - 'host' to override the default 127.0.0.1 to open or not the server to the world - 'port' to override the default of 5000 (from the underlying layer: flask) - 'debug' activate the verbose logs - 'secret_key' the flask secret key Returns: Never """ load_controllers() conf = read_config(config_path) app.secret_key = conf['secret_key'] app.config['conf'] = conf - app.config['DEFAULT_RENDERERS'] = RENDERERS host = conf.get('host', '127.0.0.1') port = conf.get('port') debug = conf.get('debug') log_file = os.path.join(conf['log_dir'], 'web-ui.log') logging.basicConfig(level=logging.DEBUG if verbose else logging.INFO, handlers=[logging.FileHandler(log_file), logging.StreamHandler()]) app.run(host=host, port=port, debug=debug) diff --git a/swh/web/ui/renderers.py b/swh/web/ui/renderers.py index d61e1544b..421c2ffce 100644 --- a/swh/web/ui/renderers.py +++ b/swh/web/ui/renderers.py @@ -1,239 +1,150 @@ # Copyright (C) 2015 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information import re import yaml import json from docutils.core import publish_parts from docutils.writers.html4css1 import Writer, HTMLTranslator -from flask import make_response, request, Response, render_template +from flask import request, Response, render_template from flask import g -from flask.ext.api import renderers, parsers -from flask_api.mediatypes import MediaType from swh.web.ui import utils class SWHFilterEnricher(): """Global filter on fields. """ def filter_by_fields(self, data): """Extract a request parameter 'fields' if it exists to permit the filtering on the data dict's keys. If such field is not provided, returns the data as is. """ fields = request.args.get('fields') if fields: fields = set(fields.split(',')) data = utils.filter_field_keys(data, fields) return data -class YAMLRenderer(renderers.BaseRenderer, SWHFilterEnricher): - """Renderer for application/yaml. - Orchestrate from python data structure to yaml. - - """ - media_type = 'application/yaml' - - def render(self, data, media_type, **options): - data = self.filter_by_fields(data) - return yaml.dump(data, encoding=self.charset) - - -class JSONPEnricher(): - """JSONP rendering. - - """ - def enrich_with_jsonp(self, data): - """Defines a jsonp function that extracts a potential 'callback' - request parameter holding the function name and wraps the data - inside a call to such function - - e.g: - GET /blah/foo/bar renders: {'output': 'wrapped'} - GET /blah/foo/bar?callback=fn renders: fn({'output': 'wrapped'}) - """ - jsonp = request.args.get('callback') - if jsonp: - return '%s(%s)' % (jsonp, data) - - return data - - -class SWHJSONRenderer(renderers.JSONRenderer, - SWHFilterEnricher, - JSONPEnricher): - """Renderer for application/json. - Serializes in json the data and returns it. - - Also deals with jsonp. If callback is found in request parameter, - wrap the result as a function with name the value of the parameter - query 'callback'. - - """ - def render(self, data, media_type, **options): - data = self.filter_by_fields(data) - res = super().render(data, media_type, **options) - return self.enrich_with_jsonp(res) - - class SWHMultiResponse(Response, SWHFilterEnricher): """ A Flask Response subclass. Override force_type to transform dict responses into callable Flask response objects whose mimetype matches the request's Accept header: HTML template render, YAML dump or default to a JSON dump. """ @classmethod - def make_response_from_mimetype(cls, rv): + def make_response_from_mimetype(cls, rv, options={}): + if not (isinstance(rv, list) or isinstance(rv, dict)): + return rv def wants_html(best_match): return best_match == 'text/html' and \ request.accept_mimetypes[best_match] > \ request.accept_mimetypes['application/json'] def wants_yaml(best_match): return best_match == 'application/yaml' and \ request.accept_mimetypes[best_match] > \ request.accept_mimetypes['application/json'] - if isinstance(rv, dict) or isinstance(rv, list): - rv = cls.filter_by_fields(cls, rv) - acc_mime = ['application/json', 'application/yaml', 'text/html'] - best_match = request.accept_mimetypes.best_match(acc_mime) - # return a template render - if wants_html(best_match): - data = json.dumps(rv, sort_keys=True, - indent=4, separators=(',', ': ')) - env = g.get('doc_env', {}) - env['response_data'] = data - env['request'] = request - rv = Response(render_template('apidoc.html', **env), - content_type='text/html') - # return formatted yaml - elif wants_yaml(best_match): - rv = Response( - yaml.dump(rv), - content_type='application/yaml') - # return formatted json - else: - # jsonify is unhappy with lists in Flask 0.10.1, use json.dumps - rv = Response( - json.dumps(rv), - content_type='application/json') + rv = cls.filter_by_fields(cls, rv) + acc_mime = ['application/json', 'application/yaml', 'text/html'] + best_match = request.accept_mimetypes.best_match(acc_mime) + # return a template render + if wants_html(best_match): + data = json.dumps(rv, sort_keys=True, + indent=4, separators=(',', ': ')) + env = g.get('doc_env', {}) + env['response_data'] = data + env['request'] = request + rv = Response(render_template('apidoc.html', **env), + content_type='text/html', + **options) + # return formatted yaml + elif wants_yaml(best_match): + rv = Response( + yaml.dump(rv), + content_type='application/yaml', + **options) + # return formatted json + else: + # jsonify is unhappy with lists in Flask 0.10.1, use json.dumps + rv = Response( + json.dumps(rv), + content_type='application/json', + **options) return rv @classmethod def force_type(cls, rv, environ=None): - # Data from apidoc if isinstance(rv, dict) or isinstance(rv, list): rv = cls.make_response_from_mimetype(rv) return super().force_type(rv, environ) +def error_response(error_code, error): + """Private function to create a custom error response. + + """ + error_opts = {'status': error_code} + error_data = {'error': str(error)} + + return SWHMultiResponse.make_response_from_mimetype(error_data, + options=error_opts) + + def urlize_api_links(content): """Utility function for decorating api links in browsable api.""" return re.sub(r'"(/api/.*|/browse/.*)"', r'"\1"', content) class NoHeaderHTMLTranslator(HTMLTranslator): """ Docutils translator subclass to customize the generation of HTML from reST-formatted docstrings """ def __init__(self, document): super().__init__(document) self.body_prefix = [] self.body_suffix = [] # disable blockquotes to ignore indentation issue with docstrings def visit_block_quote(self, node): pass def depart_block_quote(self, node): pass def visit_bullet_list(self, node): self.context.append((self.compact_simple, self.compact_p)) self.compact_p = None self.compact_simple = self.is_compactable(node) self.body.append(self.starttag(node, 'ul', CLASS='docstring')) DOCSTRING_WRITER = Writer() DOCSTRING_WRITER.translator_class = NoHeaderHTMLTranslator def safe_docstring_display(docstring): """ Utility function to htmlize reST-formatted documentation in browsable api. """ return publish_parts(docstring, writer=DOCSTRING_WRITER)['html_body'] def revision_id_from_url(url): """Utility function to obtain a revision's ID from its browsing URL.""" return re.sub(r'/browse/revision/([0-9a-f]{40}|[0-9a-f]{64})/.*', r'\1', url) - - -class SWHBrowsableAPIRenderer(renderers.BrowsableAPIRenderer): - """SWH's browsable api renderer. - - """ - template = "api.html" - - -RENDERERS = [ - 'swh.web.ui.renderers.SWHJSONRenderer', - 'swh.web.ui.renderers.SWHBrowsableAPIRenderer', - 'flask.ext.api.parsers.URLEncodedParser', - 'swh.web.ui.renderers.YAMLRenderer', -] - - -RENDERERS_INSTANCE = [ - SWHJSONRenderer(), - SWHBrowsableAPIRenderer(), - parsers.URLEncodedParser(), - YAMLRenderer(), -] - - -RENDERERS_BY_TYPE = { - r.media_type: r - for r in RENDERERS_INSTANCE -} - - -def error_response(default_error_msg, error_code, error): - """Private function to create a custom error response. - - """ - # if nothing is requested by client, use json - default_application_type = 'application/json' - accept_type = request.headers.get('Accept', default_application_type) - renderer = RENDERERS_BY_TYPE.get( - accept_type, - RENDERERS_BY_TYPE[default_application_type]) - - # for edge cases, use the elected renderer's media type - accept_type = renderer.media_type - response = make_response(default_error_msg, error_code) - response.headers['Content-Type'] = accept_type - response.data = renderer.render({"error": str(error)}, - media_type=MediaType(accept_type), - status=error_code, - headers={'Content-Type': accept_type}) - - return response diff --git a/swh/web/ui/tests/test_app.py b/swh/web/ui/tests/test_app.py index 2c96e82c7..4a218b791 100644 --- a/swh/web/ui/tests/test_app.py +++ b/swh/web/ui/tests/test_app.py @@ -1,96 +1,95 @@ # Copyright (C) 2015 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information # Functions defined here are NOT DESIGNED FOR PRODUCTION import unittest from swh.storage.api.client import RemoteStorage as Storage -from swh.web.ui import renderers, main +from swh.web.ui import main from flask.ext.testing import TestCase # Because the Storage's __init__ function does side effect at startup... class RemoteStorageAdapter(Storage): def __init__(self, base_url): self.base_url = base_url def _init_mock_storage(base_url='https://somewhere.org:4321'): """Instanciate a remote storage whose goal is to be mocked in a test context. NOT FOR PRODUCTION Returns: An instance of swh.storage.api.client.RemoteStorage destined to be mocked (it does not do any rest call) """ return RemoteStorageAdapter(base_url) # destined to be used as mock def create_app(base_url='https://somewhere.org:4321'): """Function to initiate a flask app with storage designed to be mocked. Returns: Tuple: - app test client (for testing api, client decorator from flask) - application's full configuration - the storage instance to stub and mock - the main app without any decoration NOT FOR PRODUCTION """ storage = _init_mock_storage(base_url) # inject the mock data conf = {'storage': storage, 'max_log_revs': 25} main.app.config.update({'conf': conf}) - main.app.config['DEFAULT_RENDERERS'] = renderers.RENDERERS if not main.app.config['TESTING']: # HACK: install controllers only once! main.app.config['TESTING'] = True main.load_controllers() return main.app.test_client(), main.app.config, storage, main.app class SWHApidocTestCase(unittest.TestCase): """Testing APIDoc class. """ @classmethod def setUpClass(cls): cls.app, cls.app_config, cls.storage, _ = create_app() cls.maxDiff = None class SWHApiTestCase(unittest.TestCase): """Testing API class. """ @classmethod def setUpClass(cls): cls.app, cls.app_config, cls.storage, _ = create_app() cls.maxDiff = None class SWHViewTestCase(TestCase): """Testing view class. cf. http://pythonhosted.org/Flask-Testing/ """ # This inhibits template rendering # render_templates = False def create_app(self): """Initialize a Flask-Testing application instance to test view without template rendering """ _, _, _, appToDecorate = create_app() return appToDecorate diff --git a/swh/web/ui/tests/test_renderers.py b/swh/web/ui/tests/test_renderers.py index e78f6681f..3e9e47189 100644 --- a/swh/web/ui/tests/test_renderers.py +++ b/swh/web/ui/tests/test_renderers.py @@ -1,356 +1,233 @@ # Copyright (C) 2015 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information import json import unittest import yaml from flask import Response -from flask_api.mediatypes import MediaType from nose.tools import istest from unittest.mock import patch, MagicMock from swh.web.ui import renderers class RendererTestCase(unittest.TestCase): @patch('swh.web.ui.renderers.g') @patch('swh.web.ui.renderers.json') @patch('swh.web.ui.renderers.request') @patch('swh.web.ui.renderers.render_template') @patch('swh.web.ui.renderers.SWHMultiResponse.filter_by_fields') @istest def swh_multi_response_mimetype_html(self, mock_filter, mock_render, mock_request, mock_json, mock_g): # given data = {'data': [12, 34], 'id': 'adc83b19e793491b1c6ea0fd8b46cd9f32e592fc'} mock_g.get.return_value = {'my_key': 'my_display_value'} mock_filter.return_value = data expected_env = { 'my_key': 'my_display_value', 'response_data': json.dumps(data), 'request': mock_request } def mock_mimetypes(key): mimetypes = { 'text/html': 10, 'application/json': 0.1, 'application/yaml': 0.1 } return mimetypes[key] accept_mimetypes = MagicMock() accept_mimetypes.__getitem__.side_effect = mock_mimetypes accept_mimetypes.best_match = MagicMock(return_value='text/html') mock_request.accept_mimetypes = accept_mimetypes mock_json.dumps.return_value = json.dumps(data) # when rv = renderers.SWHMultiResponse.make_response_from_mimetype(data) # then mock_filter.assert_called_once_with(renderers.SWHMultiResponse, data) mock_render.assert_called_with('apidoc.html', **expected_env) self.assertEqual(rv.status_code, 200) self.assertEqual(rv.mimetype, 'text/html') @patch('swh.web.ui.renderers.g') @patch('swh.web.ui.renderers.yaml') @patch('swh.web.ui.renderers.request') @patch('swh.web.ui.renderers.SWHMultiResponse.filter_by_fields') @istest def swh_multi_response_mimetype_yaml(self, mock_filter, mock_request, mock_yaml, mock_g): # given data = {'data': [12, 34], 'id': 'adc83b19e793491b1c6ea0fd8b46cd9f32e592fc'} def mock_mimetypes(key): mimetypes = { 'application/yaml': 10, 'application/json': 0.1, 'text/html': 0.1 } return mimetypes[key] accept_mimetypes = MagicMock() accept_mimetypes.__getitem__.side_effect = mock_mimetypes accept_mimetypes.best_match = MagicMock( return_value='application/yaml') mock_request.accept_mimetypes = accept_mimetypes mock_yaml.dump.return_value = yaml.dump(data) mock_filter.return_value = data # when rv = renderers.SWHMultiResponse.make_response_from_mimetype(data) # then mock_filter.assert_called_once_with(renderers.SWHMultiResponse, data) mock_yaml.dump.assert_called_once_with(data) self.assertEqual(rv.status_code, 200) self.assertEqual(rv.mimetype, 'application/yaml') self.assertEqual(data, yaml.load(rv.data.decode('utf-8'))) @patch('swh.web.ui.renderers.g') @patch('swh.web.ui.renderers.json') @patch('swh.web.ui.renderers.request') @patch('swh.web.ui.renderers.SWHMultiResponse.filter_by_fields') @istest def swh_multi_response_mimetype_json(self, mock_filter, mock_request, mock_json, mock_g): # given data = {'data': [12, 34], 'id': 'adc83b19e793491b1c6ea0fd8b46cd9f32e592fc'} def mock_mimetypes(key): mimetypes = { 'application/json': 10, 'text/html': 0.1, 'application/yaml': 0.1 } return mimetypes[key] accept_mimetypes = MagicMock() accept_mimetypes.__getitem__.side_effect = mock_mimetypes accept_mimetypes.best_match = MagicMock( return_value='application/json') mock_request.accept_mimetypes = accept_mimetypes mock_json.dumps.return_value = json.dumps(data) mock_filter.return_value = data # when rv = renderers.SWHMultiResponse.make_response_from_mimetype(data) # then mock_filter.assert_called_once_with(renderers.SWHMultiResponse, data) mock_json.dumps.assert_called_once_with(data) self.assertEqual(rv.status_code, 200) self.assertEqual(rv.mimetype, 'application/json') self.assertEqual(data, json.loads(rv.data.decode('utf-8'))) + @patch('swh.web.ui.renderers.request') @istest - def apidoc_make_response_not_list_dict(self): + def swh_multi_response_make_response_not_list_dict(self, mock_request): # given incoming = Response() # when rv = renderers.SWHMultiResponse.make_response_from_mimetype(incoming) # then self.assertEqual(rv, incoming) @patch('swh.web.ui.renderers.request') @istest def swh_filter_renderer_do_nothing(self, mock_request): # given mock_request.args = {} swh_filter_renderer = renderers.SWHFilterEnricher() input_data = {'a': 'some-data'} # when actual_data = swh_filter_renderer.filter_by_fields(input_data) # then self.assertEquals(actual_data, input_data) @patch('swh.web.ui.renderers.utils') @patch('swh.web.ui.renderers.request') @istest def swh_filter_renderer_do_filter(self, mock_request, mock_utils): # given mock_request.args = {'fields': 'a,c'} mock_utils.filter_field_keys.return_value = {'a': 'some-data'} - swh_filter_renderer = renderers.SWHFilterEnricher() + swh_filter_user = renderers.SWHMultiResponse() input_data = {'a': 'some-data', 'b': 'some-other-data'} # when - actual_data = swh_filter_renderer.filter_by_fields(input_data) + actual_data = swh_filter_user.filter_by_fields(input_data) # then self.assertEquals(actual_data, {'a': 'some-data'}) mock_utils.filter_field_keys.assert_called_once_with(input_data, {'a', 'c'}) - @patch('swh.web.ui.renderers.request') - @istest - def yaml_renderer_without_filter(self, mock_request): - # given - mock_request.args = {} - yaml_renderer = renderers.YAMLRenderer() - - input_data = {'target': 'sha1-dir', - 'type': 'dir', - 'dir-id': 'dir-id-sha1-git'} - - expected_data = input_data - - # when - actual_data = yaml_renderer.render(input_data, 'application/yaml') - - # then - self.assertEqual(yaml.load(actual_data), expected_data) - - @patch('swh.web.ui.renderers.request') - @istest - def yaml_renderer(self, mock_request): - # given - mock_request.args = {'fields': 'type,target'} - yaml_renderer = renderers.YAMLRenderer() - - input_data = {'target': 'sha1-dir', - 'type': 'dir', - 'dir-id': 'dir-id-sha1-git'} - - expected_data = {'target': 'sha1-dir', 'type': 'dir'} - - # when - actual_data = yaml_renderer.render(input_data, 'application/yaml') - - # then - self.assertEqual(yaml.load(actual_data), expected_data) - - @patch('swh.web.ui.renderers.request') - @istest - def json_renderer_basic(self, mock_request): - # given - mock_request.args = {} - json_renderer = renderers.SWHJSONRenderer() - - input_data = {'target': 'sha1-dir', - 'type': 'dir', - 'dir-id': 'dir-id-sha1-git'} - - expected_data = input_data - - # when - actual_data = json_renderer.render(input_data, MediaType( - 'application/json')) - - # then - self.assertEqual(json.loads(actual_data), expected_data) - - @patch('swh.web.ui.renderers.request') - @istest - def json_renderer_basic_with_filter(self, mock_request): - # given - mock_request.args = {'fields': 'target'} - json_renderer = renderers.SWHJSONRenderer() - - input_data = {'target': 'sha1-dir', - 'type': 'dir', - 'dir-id': 'dir-id-sha1-git'} - - expected_data = {'target': 'sha1-dir'} - - # when - actual_data = json_renderer.render(input_data, MediaType( - 'application/json')) - - # then - self.assertEqual(json.loads(actual_data), expected_data) - - @patch('swh.web.ui.renderers.request') - @istest - def json_renderer_basic_with_filter_and_jsonp(self, mock_request): - # given - mock_request.args = {'fields': 'target', - 'callback': 'jsonpfn'} - json_renderer = renderers.SWHJSONRenderer() - - input_data = {'target': 'sha1-dir', - 'type': 'dir', - 'dir-id': 'dir-id-sha1-git'} - - # when - actual_data = json_renderer.render(input_data, MediaType( - 'application/json')) - - # then - self.assertEqual(actual_data, 'jsonpfn({"target": "sha1-dir"})') - - @patch('swh.web.ui.renderers.request') - @istest - def jsonp_enricher_basic_with_filter_and_jsonp(self, mock_request): - # given - mock_request.args = {'callback': 'jsonpfn'} - jsonp_enricher = renderers.JSONPEnricher() - - # when - actual_output = jsonp_enricher.enrich_with_jsonp({'output': 'test'}) - - # then - self.assertEqual(actual_output, "jsonpfn({'output': 'test'})") - - @patch('swh.web.ui.renderers.request') - @istest - def jsonp_enricher_do_nothing(self, mock_request): - # given - mock_request.args = {} - jsonp_enricher = renderers.JSONPEnricher() - - # when - actual_output = jsonp_enricher.enrich_with_jsonp({'output': 'test'}) - - # then - self.assertEqual(actual_output, {'output': 'test'}) - @istest def urlize_api_links(self): # update api link with html links content with links content = '{"url": "/api/1/abc/"}' expected_content = '{"url": "/api/1/abc/"}' self.assertEquals(renderers.urlize_api_links(content), expected_content) # update /browse link with html links content with links content = '{"url": "/browse/def/"}' expected_content = '{"url": "' \ '/browse/def/"}' self.assertEquals(renderers.urlize_api_links(content), expected_content) # will do nothing since it's not an api url other_content = '{"url": "/something/api/1/other"}' self.assertEquals(renderers.urlize_api_links(other_content), other_content) @istest def revision_id_from_url(self): url = ('/browse/revision/9ba4bcb645898d562498ea66a0df958ef0e7a68c/' 'prev/9ba4bcb645898d562498ea66a0df958ef0e7aaaa/') expected_id = '9ba4bcb645898d562498ea66a0df958ef0e7a68c' self.assertEqual(renderers.revision_id_from_url(url), expected_id) @istest def safe_docstring_display(self): # update api link with html links content with links docstring = """This is my list header: - Here is item 1, with a continuation line right here - Here is item 2 Here is something that is not part of the list""" expected_docstring = """

This is my list header:

Here is something that is not part of the list

""" self.assertEquals(renderers.safe_docstring_display(docstring), expected_docstring) diff --git a/swh/web/ui/views/errorhandler.py b/swh/web/ui/views/errorhandler.py index 6d11d6de2..da279f16a 100644 --- a/swh/web/ui/views/errorhandler.py +++ b/swh/web/ui/views/errorhandler.py @@ -1,37 +1,35 @@ # Copyright (C) 2015 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information from swh.storage.exc import StorageDBError, StorageAPIError from .. import renderers from ..exc import NotFoundExc from ..main import app @app.errorhandler(ValueError) def value_error_as_bad_request(error): """Compute a bad request and add body as payload. """ - return renderers.error_response('Bad request', 400, error) + return renderers.error_response(400, error) @app.errorhandler(NotFoundExc) def value_not_found(error): """Compute a not found and add body as payload. """ - return renderers.error_response('Not found', 404, error) + return renderers.error_response(404, error) @app.errorhandler(StorageDBError) @app.errorhandler(StorageAPIError) def backend_problem(error): """Compute a not found and add body as payload. """ - return renderers.error_response('Unexpected problem in SWH Storage.', - 503, - error) + return renderers.error_response(503, error)