register_routes() plugin hook (#819)

Fixes #215
This commit is contained in:
Simon Willison 2020-06-08 20:12:06 -07:00 committed by GitHub
commit f5e79adf26
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 129 additions and 4 deletions

View file

@ -39,6 +39,7 @@ from .renderer import json_renderer
from .database import Database, QueryInterrupted
from .utils import (
async_call_with_supported_arguments,
escape_css_string,
escape_sqlite,
format_bytes,
@ -783,6 +784,10 @@ class Datasette:
"Returns an ASGI app function that serves the whole of Datasette"
routes = []
for routes_to_add in pm.hook.register_routes():
for regex, view_fn in routes_to_add:
routes.append((regex, wrap_view(view_fn, self)))
def add_route(view, regex):
routes.append((regex, view))
@ -1048,3 +1053,19 @@ def _cleaner_task_str(task):
# running at /Users/simonw/Dropbox/Development/datasette/venv-3.7.5/lib/python3.7/site-packages/uvicorn/main.py:361>
# Clean up everything up to and including site-packages
return _cleaner_task_str_re.sub("", s)
def wrap_view(view_fn, datasette):
async def asgi_view_fn(scope, receive, send):
response = await async_call_with_supported_arguments(
view_fn,
scope=scope,
receive=receive,
send=send,
request=Request(scope, receive),
datasette=datasette,
)
if response is not None:
await response.asgi_send(send)
return asgi_view_fn

View file

@ -60,6 +60,11 @@ def register_facet_classes():
"Register Facet subclasses"
@hookspec
def register_routes():
"Register URL routes: return a list of (regex, view_function) pairs"
@hookspec
def actor_from_request(datasette, request):
"Return an actor dictionary based on the incoming request"

View file

@ -842,7 +842,7 @@ def parse_metadata(content):
raise BadMetadataError("Metadata is not valid JSON or YAML")
def call_with_supported_arguments(fn, **kwargs):
def _gather_arguments(fn, kwargs):
parameters = inspect.signature(fn).parameters.keys()
call_with = []
for parameter in parameters:
@ -853,9 +853,19 @@ def call_with_supported_arguments(fn, **kwargs):
)
)
call_with.append(kwargs[parameter])
return call_with
def call_with_supported_arguments(fn, **kwargs):
call_with = _gather_arguments(fn, kwargs)
return fn(*call_with)
async def async_call_with_supported_arguments(fn, **kwargs):
call_with = _gather_arguments(fn, kwargs)
return await fn(*call_with)
def actor_matches_allow(actor, allow):
actor = actor or {}
if allow is None:

View file

@ -399,7 +399,7 @@ class Response:
@classmethod
def text(cls, body, status=200, headers=None):
return cls(
body,
str(body),
status=status,
headers=headers,
content_type="text/plain; charset=utf-8",