Added asgi_wrapper plugin hook, closes #520

prepare_asgi
Simon Willison 2019-07-02 20:57:28 -07:00
rodzic b9ede4c189
commit 93bfa26bfd
5 zmienionych plików z 78 dodań i 1 usunięć

Wyświetl plik

@ -651,9 +651,12 @@ class Datasette:
if not database.is_mutable:
await database.table_counts(limit=60 * 60 * 1000)
return AsgiLifespan(
asgi = AsgiLifespan(
AsgiTracer(DatasetteRouter(self, routes)), on_startup=setup_db
)
for wrapper in pm.hook.asgi_wrapper(datasette=self):
asgi = wrapper(asgi)
return asgi
class DatasetteRouter(AsgiRouter):

Wyświetl plik

@ -5,6 +5,11 @@ hookspec = HookspecMarker("datasette")
hookimpl = HookimplMarker("datasette")
@hookspec
def asgi_wrapper(datasette):
"Returns an ASGI middleware callable to wrap our ASGI application with"
@hookspec
def prepare_connection(conn):
"Modify SQLite connection in some way e.g. register custom SQL functions"

Wyświetl plik

@ -666,3 +666,44 @@ The plugin hook can then be used to register the new facet class like this:
@hookimpl
def register_facet_classes():
return [SpecialFacet]
.. _plugin_asgi_wrapper:
asgi_wrapper(datasette)
~~~~~~~~~~~~~~~~~~~~~~~
Return an `ASGI <https://asgi.readthedocs.io/>`__ middleware wrapper function that will be applied to the Datasette ASGI application.
This is a very powerful hook. You can use it to manipulate the entire Datasette response, or even to configure new URL routes that will be handled by your own custom code.
You can write your ASGI code directly against the low-level specification, or you can use the middleware utilites provided by an ASGI framework such as `Starlette <https://www.starlette.io/middleware/>`__.
This example plugin adds a ``x-databases`` HTTP header listing the currently attached databases:
.. code-block:: python
from datasette import hookimpl
from functools import wraps
@hookimpl
def asgi_wrapper(datasette):
def wrap_with_databases_header(app):
@wraps(app)
async def add_x_databases_header(scope, recieve, send):
async def wrapped_send(event):
if event["type"] == "http.response.start":
original_headers = event.get("headers") or []
event = {
"type": event["type"],
"status": event["status"],
"headers": original_headers + [
[b"x-databases",
", ".join(datasette.databases.keys()).encode("utf-8")]
],
}
await send(event)
await app(scope, recieve, wrapped_send)
return add_x_databases_header
return wrap_with_databases_header

Wyświetl plik

@ -372,6 +372,7 @@ def render_cell(value, column, table, database, datasette):
PLUGIN2 = """
from datasette import hookimpl
from functools import wraps
import jinja2
import json
@ -413,6 +414,28 @@ def render_cell(value, database):
label=jinja2.escape(data["label"] or "") or "&nbsp;"
)
)
@hookimpl
def asgi_wrapper(datasette):
def wrap_with_databases_header(app):
@wraps(app)
async def add_x_databases_header(scope, recieve, send):
async def wrapped_send(event):
if event["type"] == "http.response.start":
original_headers = event.get("headers") or []
event = {
"type": event["type"],
"status": event["status"],
"headers": original_headers + [
[b"x-databases",
", ".join(datasette.databases.keys()).encode("utf-8")]
],
}
await send(event)
await app(scope, recieve, wrapped_send)
return add_x_databases_header
return wrap_with_databases_header
"""
TABLES = (

Wyświetl plik

@ -162,3 +162,8 @@ def test_plugins_extra_body_script(app_client, path, expected_extra_body_script)
json_data = r.search(app_client.get(path).body.decode("utf8")).group(1)
actual_data = json.loads(json_data)
assert expected_extra_body_script == actual_data
def test_plugins_asgi_wrapper(app_client):
response = app_client.get("/fixtures")
assert "fixtures" == response.headers["x-databases"]