New JSON design for query views (#2118)

* Refs #2111, closes #2110
* New Context dataclass/subclass mechanism, refs #2127
* Define QueryContext and extract get_tables() method, refs #2127
* Fix OPTIONS bug by porting DaatbaseView to be a View subclass
* Expose async_view_for_class.view_class for test_routes test
* Error/truncated aruments for renderers, closes #2130
This commit is contained in:
Simon Willison 2023-08-07 18:47:39 -07:00 committed by GitHub
commit 1377a290cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 579 additions and 112 deletions

View file

@ -638,22 +638,21 @@ def test_database_page_for_database_with_dot_in_name(app_client_with_dot):
@pytest.mark.asyncio
async def test_custom_sql(ds_client):
response = await ds_client.get(
"/fixtures.json?sql=select+content+from+simple_primary_key&_shape=objects"
"/fixtures.json?sql=select+content+from+simple_primary_key"
)
data = response.json()
assert {"sql": "select content from simple_primary_key", "params": {}} == data[
"query"
]
assert [
{"content": "hello"},
{"content": "world"},
{"content": ""},
{"content": "RENDER_CELL_DEMO"},
{"content": "RENDER_CELL_ASYNC"},
] == data["rows"]
assert ["content"] == data["columns"]
assert "fixtures" == data["database"]
assert not data["truncated"]
assert data == {
"rows": [
{"content": "hello"},
{"content": "world"},
{"content": ""},
{"content": "RENDER_CELL_DEMO"},
{"content": "RENDER_CELL_ASYNC"},
],
"columns": ["content"],
"ok": True,
"truncated": False,
}
def test_sql_time_limit(app_client_shorter_time_limit):

View file

@ -36,7 +36,6 @@ def test_serve_with_get(tmp_path_factory):
)
assert 0 == result.exit_code, result.output
assert {
"database": "_memory",
"truncated": False,
"columns": ["sqlite_version()"],
}.items() <= json.loads(result.output).items()

View file

@ -248,6 +248,9 @@ async def test_css_classes_on_body(ds_client, path, expected_classes):
assert classes == expected_classes
templates_considered_re = re.compile(r"<!-- Templates considered: (.*?) -->")
@pytest.mark.asyncio
@pytest.mark.parametrize(
"path,expected_considered",
@ -271,7 +274,10 @@ async def test_css_classes_on_body(ds_client, path, expected_classes):
async def test_templates_considered(ds_client, path, expected_considered):
response = await ds_client.get(path)
assert response.status_code == 200
assert f"<!-- Templates considered: {expected_considered} -->" in response.text
match = templates_considered_re.search(response.text)
assert match, "No templates considered comment found"
actual_considered = match.group(1)
assert actual_considered == expected_considered
@pytest.mark.asyncio

View file

@ -1,10 +1,12 @@
"""
Tests for the datasette.app.Datasette class
"""
from datasette import Forbidden
import dataclasses
from datasette import Forbidden, Context
from datasette.app import Datasette, Database
from itsdangerous import BadSignature
import pytest
from typing import Optional
@pytest.fixture
@ -136,6 +138,22 @@ async def test_datasette_render_template_no_request():
assert "Error " in rendered
@pytest.mark.asyncio
async def test_datasette_render_template_with_dataclass():
@dataclasses.dataclass
class ExampleContext(Context):
title: str
status: int
error: str
context = ExampleContext(title="Hello", status=200, error="Error message")
ds = Datasette(memory=True)
await ds.invoke_startup()
rendered = await ds.render_template("error.html", context)
assert "<h1>Hello</h1>" in rendered
assert "Error message" in rendered
def test_datasette_error_if_string_not_list(tmpdir):
# https://github.com/simonw/datasette/issues/1985
db_path = str(tmpdir / "data.db")

View file

@ -12,7 +12,7 @@ import pytest
],
)
async def test_add_message_sets_cookie(ds_client, qs, expected):
response = await ds_client.get(f"/fixtures.message?{qs}")
response = await ds_client.get(f"/fixtures.message?sql=select+1&{qs}")
signed = response.cookies["ds_messages"]
decoded = ds_client.ds.unsign(signed, "messages")
assert expected == decoded
@ -21,7 +21,9 @@ async def test_add_message_sets_cookie(ds_client, qs, expected):
@pytest.mark.asyncio
async def test_messages_are_displayed_and_cleared(ds_client):
# First set the message cookie
set_msg_response = await ds_client.get("/fixtures.message?add_msg=xmessagex")
set_msg_response = await ds_client.get(
"/fixtures.message?sql=select+1&add_msg=xmessagex"
)
# Now access a page that displays messages
response = await ds_client.get("/", cookies=set_msg_response.cookies)
# Messages should be in that HTML

View file

@ -121,9 +121,8 @@ async def test_hook_extra_css_urls(ds_client, path, expected_decoded_object):
][0]["href"]
# This link has a base64-encoded JSON blob in it
encoded = special_href.split("/")[3]
assert expected_decoded_object == json.loads(
base64.b64decode(encoded).decode("utf8")
)
actual_decoded_object = json.loads(base64.b64decode(encoded).decode("utf8"))
assert expected_decoded_object == actual_decoded_object
@pytest.mark.asyncio

View file

@ -700,7 +700,6 @@ async def test_max_returned_rows(ds_client):
"/fixtures.json?sql=select+content+from+no_primary_key"
)
data = response.json()
assert {"sql": "select content from no_primary_key", "params": {}} == data["query"]
assert data["truncated"]
assert 100 == len(data["rows"])