diff --git a/datasette/views/base.py b/datasette/views/base.py index 7262edb4..2c48567b 100644 --- a/datasette/views/base.py +++ b/datasette/views/base.py @@ -1,6 +1,9 @@ import csv import hashlib import sys +import textwrap + +import markupsafe from datasette.utils import ( EscapeHtmlWriter, @@ -39,6 +42,29 @@ class DatasetteError(Exception): self.plain_message = plain_message +def query_interrupted_error(ex): + """The SQL time limit error page, shared by every view that runs user SQL.""" + return DatasetteError( + textwrap.dedent(f""" +
SQL query took too long. The time limit is controlled by the + sql_time_limit_ms + configuration option.
+ + + """).strip(), + title="SQL Interrupted", + status=400, + message_is_html=True, + plain_message=( + "SQL query took too long. The time limit is" + " controlled by the sql_time_limit_ms setting." + ), + ) + + class View: async def head(self, request, datasette): if not hasattr(self, "get"): diff --git a/datasette/views/database.py b/datasette/views/database.py index da207601..5ac850b8 100644 --- a/datasette/views/database.py +++ b/datasette/views/database.py @@ -3,7 +3,6 @@ import hashlib import itertools import json import os -import textwrap from dataclasses import asdict, dataclass, field from urllib.parse import parse_qsl, urlencode @@ -39,7 +38,7 @@ from datasette.utils.asgi import AsgiFileDownload, Forbidden, NotFound, Response from datasette.write_sql import QueryWriteRejected from . import Context -from .base import DatasetteError, View, stream_csv +from .base import DatasetteError, View, query_interrupted_error, stream_csv from .query_helpers import ( _block_framing, _ensure_stored_query_execution_permissions, @@ -819,25 +818,7 @@ class QueryView(View): columns = results.columns rows = results.rows except QueryInterrupted as ex: - raise DatasetteError( - textwrap.dedent(f""" -SQL query took too long. The time limit is controlled by the - sql_time_limit_ms - configuration option.
- - - """).strip(), - title="SQL Interrupted", - status=400, - message_is_html=True, - plain_message=( - "SQL query took too long. The time limit is" - " controlled by the sql_time_limit_ms setting." - ), - ) + raise query_interrupted_error(ex) except sqlite3.DatabaseError as ex: query_error = str(ex) results = None diff --git a/datasette/views/row.py b/datasette/views/row.py index 92e75199..6220cf2a 100644 --- a/datasette/views/row.py +++ b/datasette/views/row.py @@ -1,6 +1,5 @@ import asyncio import json -import textwrap import time import urllib.parse from dataclasses import dataclass, field @@ -35,7 +34,7 @@ from datasette.utils.asgi import Forbidden, NotFound, PayloadTooLarge, Response from datasette.utils.sqlite import check_structured_write_table from . import Context, from_extra -from .base import BaseView, DatasetteError, stream_csv +from .base import BaseView, DatasetteError, query_interrupted_error, stream_csv from .table import ( _table_page_data, display_columns_and_rows, @@ -195,25 +194,7 @@ class RowView(BaseView): else: data, extra_template_data, templates = response_or_template_contexts except QueryInterrupted as ex: - raise DatasetteError( - textwrap.dedent(f""" -SQL query took too long. The time limit is controlled by the - sql_time_limit_ms - configuration option.
- - - """).strip(), - title="SQL Interrupted", - status=400, - message_is_html=True, - plain_message=( - "SQL query took too long. The time limit is" - " controlled by the sql_time_limit_ms setting." - ), - ) + raise query_interrupted_error(ex) except (sqlite3.OperationalError, InvalidSql) as e: raise DatasetteError(str(e), title="Invalid SQL", status=400) except DatasetteError: