Skip to content

Commit

Permalink
?_extra= support and TableView refactor to table_view
Browse files Browse the repository at this point in the history
* Implemented ?_extra= option for JSON views, refs #262
* New dependency: asyncinject
* Remove now-obsolete TableView class
  • Loading branch information
simonw authored Mar 22, 2023
1 parent 56b0758 commit d97e82d
Show file tree
Hide file tree
Showing 20 changed files with 1,593 additions and 1,085 deletions.
22 changes: 17 additions & 5 deletions datasette/app.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import asyncio
from pydoc import plain
from typing import Sequence, Union, Tuple, Optional, Dict, Iterable
import asgi_csrf
import collections
Expand All @@ -24,7 +23,12 @@

from markupsafe import Markup, escape
from itsdangerous import URLSafeSerializer
from jinja2 import ChoiceLoader, Environment, FileSystemLoader, PrefixLoader
from jinja2 import (
ChoiceLoader,
Environment,
FileSystemLoader,
PrefixLoader,
)
from jinja2.environment import Template
from jinja2.exceptions import TemplateNotFound

Expand All @@ -42,7 +46,12 @@
PermissionsDebugView,
MessagesDebugView,
)
from .views.table import TableView, TableInsertView, TableUpsertView, TableDropView
from .views.table import (
TableInsertView,
TableUpsertView,
TableDropView,
table_view,
)
from .views.row import RowView, RowDeleteView, RowUpdateView
from .renderer import json_renderer
from .url_builder import Urls
Expand Down Expand Up @@ -389,7 +398,10 @@ def __init__(
]
)
self.jinja_env = Environment(
loader=template_loader, autoescape=True, enable_async=True
loader=template_loader,
autoescape=True,
enable_async=True,
# undefined=StrictUndefined,
)
self.jinja_env.filters["escape_css_string"] = escape_css_string
self.jinja_env.filters["quote_plus"] = urllib.parse.quote_plus
Expand Down Expand Up @@ -1358,7 +1370,7 @@ def add_route(view, regex):
)
add_route(TableCreateView.as_view(self), r"/(?P<database>[^\/\.]+)/-/create$")
add_route(
TableView.as_view(self),
wrap_view(table_view, self),
r"/(?P<database>[^\/\.]+)/(?P<table>[^\/\.]+)(\.(?P<format>\w+))?$",
)
add_route(
Expand Down
1 change: 1 addition & 0 deletions datasette/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ def sqlite_extensions(fn):
multiple=True,
help="Path to a SQLite extension to load, and optional entrypoint",
)(fn)

# Wrap it in a custom error handler
@functools.wraps(fn)
def wrapped(*args, **kwargs):
Expand Down
20 changes: 14 additions & 6 deletions datasette/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
remove_infinites,
CustomJSONEncoder,
path_from_row_pks,
sqlite3,
)
from datasette.utils.asgi import Response

Expand Down Expand Up @@ -49,10 +50,14 @@ def json_renderer(args, data, view_name):
if data.get("error"):
shape = "objects"

next_url = data.get("next_url")

if shape == "arrayfirst":
data = [row[0] for row in data["rows"]]
if not data["rows"]:
data = []
elif isinstance(data["rows"][0], sqlite3.Row):
data = [row[0] for row in data["rows"]]
else:
assert isinstance(data["rows"][0], dict)
data = [next(iter(row.values())) for row in data["rows"]]
elif shape in ("objects", "object", "array"):
columns = data.get("columns")
rows = data.get("rows")
Expand Down Expand Up @@ -80,7 +85,12 @@ def json_renderer(args, data, view_name):
data = data["rows"]

elif shape == "arrays":
pass
if not data["rows"]:
pass
elif isinstance(data["rows"][0], sqlite3.Row):
data["rows"] = [list(row) for row in data["rows"]]
else:
data["rows"] = [list(row.values()) for row in data["rows"]]
else:
status_code = 400
data = {
Expand All @@ -98,8 +108,6 @@ def json_renderer(args, data, view_name):
body = json.dumps(data, cls=CustomJSONEncoder)
content_type = "application/json; charset=utf-8"
headers = {}
if next_url:
headers["link"] = f'<{next_url}>; rel="next"'
return Response(
body, status=status_code, headers=headers, content_type=content_type
)
4 changes: 2 additions & 2 deletions datasette/templates/_description_source_license.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% if metadata.description_html or metadata.description %}
{% if metadata.get("description_html") or metadata.get("description") %}
<div class="metadata-description">
{% if metadata.description_html %}
{% if metadata.get("description_html") %}
{{ metadata.description_html|safe }}
{% else %}
{{ metadata.description }}
Expand Down
2 changes: 1 addition & 1 deletion datasette/templates/_suggested_facets.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<p class="suggested-facets">
Suggested facets: {% for facet in suggested_facets %}<a href="{{ facet.toggle_url }}#facet-{{ facet.name|to_css_class }}">{{ facet.name }}</a>{% if facet.type %} ({{ facet.type }}){% endif %}{% if not loop.last %}, {% endif %}{% endfor %}
Suggested facets: {% for facet in suggested_facets %}<a href="{{ facet.toggle_url }}#facet-{{ facet.name|to_css_class }}">{{ facet.name }}</a>{% if facet.get("type") %} ({{ facet.type }}){% endif %}{% if not loop.last %}, {% endif %}{% endfor %}
</p>
4 changes: 2 additions & 2 deletions datasette/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
<link rel="stylesheet" href="{{ urls.static('app.css') }}?{{ app_css_hash }}">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% for url in extra_css_urls %}
<link rel="stylesheet" href="{{ url.url }}"{% if url.sri %} integrity="{{ url.sri }}" crossorigin="anonymous"{% endif %}>
<link rel="stylesheet" href="{{ url.url }}"{% if url.get("sri") %} integrity="{{ url.sri }}" crossorigin="anonymous"{% endif %}>
{% endfor %}
{% for url in extra_js_urls %}
<script {% if url.module %}type="module" {% endif %}src="{{ url.url }}"{% if url.sri %} integrity="{{ url.sri }}" crossorigin="anonymous"{% endif %}></script>
<script {% if url.module %}type="module" {% endif %}src="{{ url.url }}"{% if url.get("sri") %} integrity="{{ url.sri }}" crossorigin="anonymous"{% endif %}></script>
{% endfor %}
{%- if alternate_url_json -%}
<link rel="alternate" type="application/json+datasette" href="{{ alternate_url_json }}">
Expand Down
6 changes: 3 additions & 3 deletions datasette/templates/table.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

{% block content %}
<div class="page-header" style="border-color: #{{ database_color(database) }}">
<h1>{{ metadata.title or table }}{% if is_view %} (view){% endif %}{% if private %} 🔒{% endif %}</h1>
<h1>{{ metadata.get("title") or table }}{% if is_view %} (view){% endif %}{% if private %} 🔒{% endif %}</h1>
{% set links = table_actions() %}{% if links %}
<details class="actions-menu-links details-menu">
<summary><svg aria-labelledby="actions-menu-links-title" role="img"
Expand All @@ -47,7 +47,7 @@ <h1>{{ metadata.title or table }}{% if is_view %} (view){% endif %}{% if private

{% block description_source_license %}{% include "_description_source_license.html" %}{% endblock %}

{% if metadata.columns %}
{% if metadata.get("columns") %}
<dl class="column-descriptions">
{% for column_name, column_description in metadata.columns.items() %}
<dt>{{ column_name }}</dt><dd>{{ column_description }}</dd>
Expand Down Expand Up @@ -94,7 +94,7 @@ <h3>{% if count or count == 0 %}{{ "{:,}".format(count) }} row{% if count == 1 %
</div><div class="select-wrapper filter-op">
<select name="_filter_op">
{% for key, display, no_argument in filters.lookups() %}
<option value="{{ key }}{% if no_argument %}__1{% endif %}"{% if key == lookup %} selected{% endif %}>{{ display }}</option>
<option value="{{ key }}{% if no_argument %}__1{% endif %}">{{ display }}</option>
{% endfor %}
</select>
</div><input type="text" name="_filter_value" class="filter-value">
Expand Down
13 changes: 11 additions & 2 deletions datasette/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -828,9 +828,18 @@ async def write(self, content):


def remove_infinites(row):
if any((c in _infinities) if isinstance(c, float) else 0 for c in row):
to_check = row
if isinstance(row, dict):
to_check = row.values()
if not any((c in _infinities) if isinstance(c, float) else 0 for c in to_check):
return row
if isinstance(row, dict):
return {
k: (None if (isinstance(v, float) and v in _infinities) else v)
for k, v in row.items()
}
else:
return [None if (isinstance(c, float) and c in _infinities) else c for c in row]
return row


class StaticMount(click.ParamType):
Expand Down
Loading

0 comments on commit d97e82d

Please sign in to comment.