From b0a95da96386ddf99816911e08df86178ffa9a89 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 28 May 2018 14:24:19 -0700 Subject: [PATCH] Show more useful error message for SQL interrupted, closes #142 --- datasette/app.py | 4 ++++ datasette/views/base.py | 9 +++++++-- tests/test_html.py | 15 ++++++++++++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/datasette/app.py b/datasette/app.py index 8a3442b5..9f2b54c2 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -12,6 +12,7 @@ import urllib.parse from concurrent import futures from pathlib import Path +from markupsafe import Markup import pluggy from jinja2 import ChoiceLoader, Environment, FileSystemLoader, PrefixLoader from sanic import Sanic, response @@ -461,6 +462,7 @@ class Datasette: @app.exception(Exception) def on_exception(request, exception): title = None + help = None if isinstance(exception, NotFound): status = 404 info = {} @@ -473,6 +475,8 @@ class Datasette: status = exception.status info = exception.error_dict message = exception.message + if exception.messagge_is_html: + message = Markup(message) title = exception.title else: status = 500 diff --git a/datasette/views/base.py b/datasette/views/base.py index 0aff72bc..d8aa8a14 100644 --- a/datasette/views/base.py +++ b/datasette/views/base.py @@ -27,11 +27,12 @@ HASH_LENGTH = 7 class DatasetteError(Exception): - def __init__(self, message, title=None, error_dict=None, status=500, template=None): + def __init__(self, message, title=None, error_dict=None, status=500, template=None, messagge_is_html=False): self.message = message self.title = title self.error_dict = error_dict or {} self.status = status + self.messagge_is_html = messagge_is_html class RenderMixin(HTTPMethodView): @@ -154,7 +155,11 @@ class BaseView(RenderMixin): else: data, extra_template_data, templates = response_or_template_contexts except InterruptedError as e: - raise DatasetteError(str(e), title="SQL Interrupted", status=400) + raise DatasetteError(""" + SQL query took too long. The time limit is controlled by the + sql_time_limit_ms + configuration option. + """, title="SQL Interrupted", status=400, messagge_is_html=True) except (sqlite3.OperationalError, InvalidSql) as e: raise DatasetteError(str(e), title="Invalid SQL", status=400) diff --git a/tests/test_html.py b/tests/test_html.py index b333362f..bec186c3 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -1,10 +1,11 @@ from bs4 import BeautifulSoup as Soup -from .fixtures import app_client +from .fixtures import app_client, app_client_shorter_time_limit import pytest import re import urllib.parse pytest.fixture(scope='session')(app_client) +pytest.fixture(scope='session')(app_client_shorter_time_limit) def test_homepage(app_client): @@ -29,6 +30,18 @@ def test_invalid_custom_sql(app_client): assert 'Statement must be a SELECT' in response.text +def test_sql_time_limit(app_client_shorter_time_limit): + response = app_client_shorter_time_limit.get( + '/test_tables?sql=select+sleep(0.5)', + gather_request=False + ) + assert 400 == response.status + expected_html_fragment = """ + sql_time_limit_ms + """.strip() + assert expected_html_fragment in response.text + + def test_view(app_client): response = app_client.get('/test_tables/simple_view', gather_request=False) assert response.status == 200