Show more useful error message for SQL interrupted, closes #142

This commit is contained in:
Simon Willison 2018-05-28 14:24:19 -07:00
commit b0a95da963
No known key found for this signature in database
GPG key ID: 17E2DEA2588B7F52
3 changed files with 25 additions and 3 deletions

View file

@ -12,6 +12,7 @@ import urllib.parse
from concurrent import futures from concurrent import futures
from pathlib import Path from pathlib import Path
from markupsafe import Markup
import pluggy import pluggy
from jinja2 import ChoiceLoader, Environment, FileSystemLoader, PrefixLoader from jinja2 import ChoiceLoader, Environment, FileSystemLoader, PrefixLoader
from sanic import Sanic, response from sanic import Sanic, response
@ -461,6 +462,7 @@ class Datasette:
@app.exception(Exception) @app.exception(Exception)
def on_exception(request, exception): def on_exception(request, exception):
title = None title = None
help = None
if isinstance(exception, NotFound): if isinstance(exception, NotFound):
status = 404 status = 404
info = {} info = {}
@ -473,6 +475,8 @@ class Datasette:
status = exception.status status = exception.status
info = exception.error_dict info = exception.error_dict
message = exception.message message = exception.message
if exception.messagge_is_html:
message = Markup(message)
title = exception.title title = exception.title
else: else:
status = 500 status = 500

View file

@ -27,11 +27,12 @@ HASH_LENGTH = 7
class DatasetteError(Exception): 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.message = message
self.title = title self.title = title
self.error_dict = error_dict or {} self.error_dict = error_dict or {}
self.status = status self.status = status
self.messagge_is_html = messagge_is_html
class RenderMixin(HTTPMethodView): class RenderMixin(HTTPMethodView):
@ -154,7 +155,11 @@ class BaseView(RenderMixin):
else: else:
data, extra_template_data, templates = response_or_template_contexts data, extra_template_data, templates = response_or_template_contexts
except InterruptedError as e: 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
<a href="https://datasette.readthedocs.io/en/stable/config.html#sql-time-limit-ms">sql_time_limit_ms</a>
configuration option.
""", title="SQL Interrupted", status=400, messagge_is_html=True)
except (sqlite3.OperationalError, InvalidSql) as e: except (sqlite3.OperationalError, InvalidSql) as e:
raise DatasetteError(str(e), title="Invalid SQL", status=400) raise DatasetteError(str(e), title="Invalid SQL", status=400)

View file

@ -1,10 +1,11 @@
from bs4 import BeautifulSoup as Soup from bs4 import BeautifulSoup as Soup
from .fixtures import app_client from .fixtures import app_client, app_client_shorter_time_limit
import pytest import pytest
import re import re
import urllib.parse import urllib.parse
pytest.fixture(scope='session')(app_client) pytest.fixture(scope='session')(app_client)
pytest.fixture(scope='session')(app_client_shorter_time_limit)
def test_homepage(app_client): 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 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 = """
<a href="https://datasette.readthedocs.io/en/stable/config.html#sql-time-limit-ms">sql_time_limit_ms</a>
""".strip()
assert expected_html_fragment in response.text
def test_view(app_client): def test_view(app_client):
response = app_client.get('/test_tables/simple_view', gather_request=False) response = app_client.get('/test_tables/simple_view', gather_request=False)
assert response.status == 200 assert response.status == 200