Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New JSON design for query views #2118

Merged
merged 23 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2e40a50
New database_view() works for HTML and JSON
simonw Jul 26, 2023
c3e3ecf
New query_view returning JSON
simonw Jul 26, 2023
ff728cc
WIP
simonw Jul 26, 2023
002289b
New Context dataclass/subclass mechanism, refs #2127
simonw Aug 7, 2023
2f9038a
Define QueryContext and extract get_tables() method, refs #2127
simonw Aug 7, 2023
743c13c
First version that uses the QueryContext context, refs #2127
simonw Aug 7, 2023
dff29b0
Handle DB errors
simonw Aug 7, 2023
2dd4e14
Down to 31 failing tests
simonw Aug 7, 2023
c138df6
Show error messages with a form to edit the SQL
simonw Aug 7, 2023
e2b60f5
Fix OPTIONS bug by porting DatbaseView to be a View subclass
simonw Aug 7, 2023
f2a110f
Expose async_view_for_class.view_class for test_routes test
simonw Aug 7, 2023
80953da
Fix for messages tests
simonw Aug 7, 2023
fcdb96c
Added table_columns schema back to query/database pages
simonw Aug 7, 2023
8ec6bc8
Clean up imports a bit
simonw Aug 7, 2023
cd1dd01
error/truncated aruments for renderers, refs #2130
simonw Aug 7, 2023
a791115
Implemented show/hide links
simonw Aug 7, 2023
ea24c9c
Fixed a test and implemented show_hide_hidden
simonw Aug 7, 2023
8fbcd7f
Implemented alternate_url_json
simonw Aug 8, 2023
7532feb
select_templates for templates considered comment
simonw Aug 8, 2023
b45965b
Set view_name correctly in a few more places
simonw Aug 8, 2023
0505032
More cors headers, plus deleted some dead code
simonw Aug 8, 2023
5382ec6
Fixed broken test
simonw Aug 8, 2023
8b1dea3
CSV links have _size=max on them
simonw Aug 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion datasette/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from datasette.permissions import Permission
from datasette.permissions import Permission # noqa
from datasette.version import __version_info__, __version__ # noqa
from datasette.utils.asgi import Forbidden, NotFound, Request, Response # noqa
from datasette.utils import actor_matches_allow # noqa
from datasette.views import Context # noqa
from .hookspecs import hookimpl # noqa
from .hookspecs import hookspec # noqa
16 changes: 13 additions & 3 deletions datasette/app.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import asyncio
from typing import Sequence, Union, Tuple, Optional, Dict, Iterable
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, Union
import asgi_csrf
import collections
import dataclasses
import datetime
import functools
import glob
Expand Down Expand Up @@ -33,6 +34,7 @@
from jinja2.environment import Template
from jinja2.exceptions import TemplateNotFound

from .views import Context
from .views.base import ureg
from .views.database import database_download, DatabaseView, TableCreateView
from .views.index import IndexView
Expand Down Expand Up @@ -1115,7 +1117,11 @@ def _register_renderers(self):
)

async def render_template(
self, templates, context=None, request=None, view_name=None
self,
templates: Union[List[str], str, Template],
context: Optional[Union[Dict[str, Any], Context]] = None,
request: Optional[Request] = None,
view_name: Optional[str] = None,
):
if not self._startup_invoked:
raise Exception("render_template() called before await ds.invoke_startup()")
Expand All @@ -1126,6 +1132,8 @@ async def render_template(
if isinstance(templates, str):
templates = [templates]
template = self.jinja_env.select_template(templates)
if dataclasses.is_dataclass(context):
context = dataclasses.asdict(context)
body_scripts = []
# pylint: disable=no-member
for extra_script in pm.hook.extra_body_script(
Expand Down Expand Up @@ -1368,7 +1376,8 @@ def add_route(view, regex):
r"/(?P<database>[^\/\.]+)\.db$",
)
add_route(
DatabaseView.as_view(self), r"/(?P<database>[^\/\.]+)(\.(?P<format>\w+))?$"
wrap_view(DatabaseView, self),
r"/(?P<database>[^\/\.]+)(\.(?P<format>\w+))?$",
)
add_route(TableCreateView.as_view(self), r"/(?P<database>[^\/\.]+)/-/create$")
add_route(
Expand Down Expand Up @@ -1707,6 +1716,7 @@ async def async_view_for_class(request, send):
datasette=datasette,
)

async_view_for_class.view_class = view_class
return async_view_for_class


Expand Down
21 changes: 14 additions & 7 deletions datasette/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def convert_specific_columns_to_json(rows, columns, json_cols):
return new_rows


def json_renderer(args, data, view_name):
def json_renderer(args, data, error, truncated=None):
"""Render a response as JSON"""
status_code = 200

Expand All @@ -47,8 +47,15 @@ def json_renderer(args, data, view_name):
# Deal with the _shape option
shape = args.get("_shape", "objects")
# if there's an error, ignore the shape entirely
if data.get("error"):
data["ok"] = True
if error:
shape = "objects"
status_code = 400
data["error"] = error
data["ok"] = False

if truncated is not None:
data["truncated"] = truncated

if shape == "arrayfirst":
if not data["rows"]:
Expand All @@ -64,13 +71,13 @@ def json_renderer(args, data, view_name):
if rows and columns:
data["rows"] = [dict(zip(columns, row)) for row in rows]
if shape == "object":
error = None
shape_error = None
if "primary_keys" not in data:
error = "_shape=object is only available on tables"
shape_error = "_shape=object is only available on tables"
else:
pks = data["primary_keys"]
if not pks:
error = (
shape_error = (
"_shape=object not available for tables with no primary keys"
)
else:
Expand All @@ -79,8 +86,8 @@ def json_renderer(args, data, view_name):
pk_string = path_from_row_pks(row, pks, not pks)
object_rows[pk_string] = row
data = object_rows
if error:
data = {"ok": False, "error": error}
if shape_error:
data = {"ok": False, "error": shape_error}
elif shape == "array":
data = data["rows"]

Expand Down
3 changes: 3 additions & 0 deletions datasette/views/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Context:
"Base class for all documented contexts"
pass
2 changes: 2 additions & 0 deletions datasette/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,8 @@ async def get(self, request):
table=data.get("table"),
request=request,
view_name=self.name,
truncated=False, # TODO: support this
error=data.get("error"),
# These will be deprecated in Datasette 1.0:
args=request.args,
data=data,
Expand Down
Loading
Loading