mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Refactored test plugins into tests/plugins, closes #775
This commit is contained in:
parent
4b96857f17
commit
446e5de65d
6 changed files with 197 additions and 194 deletions
|
|
@ -19,6 +19,8 @@ from urllib.parse import unquote, quote
|
||||||
# This temp file is used by one of the plugin config tests
|
# This temp file is used by one of the plugin config tests
|
||||||
TEMP_PLUGIN_SECRET_FILE = os.path.join(tempfile.gettempdir(), "plugin-secret")
|
TEMP_PLUGIN_SECRET_FILE = os.path.join(tempfile.gettempdir(), "plugin-secret")
|
||||||
|
|
||||||
|
PLUGINS_DIR = str(pathlib.Path(__file__).parent / "plugins")
|
||||||
|
|
||||||
|
|
||||||
class TestResponse:
|
class TestResponse:
|
||||||
def __init__(self, status, headers, body):
|
def __init__(self, status, headers, body):
|
||||||
|
|
@ -109,7 +111,6 @@ def make_app_client(
|
||||||
inspect_data=None,
|
inspect_data=None,
|
||||||
static_mounts=None,
|
static_mounts=None,
|
||||||
template_dir=None,
|
template_dir=None,
|
||||||
extra_plugins=None,
|
|
||||||
):
|
):
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
filepath = os.path.join(tmpdir, filename)
|
filepath = os.path.join(tmpdir, filename)
|
||||||
|
|
@ -130,12 +131,6 @@ def make_app_client(
|
||||||
sqlite3.connect(extra_filepath).executescript(extra_sql)
|
sqlite3.connect(extra_filepath).executescript(extra_sql)
|
||||||
files.append(extra_filepath)
|
files.append(extra_filepath)
|
||||||
os.chdir(os.path.dirname(filepath))
|
os.chdir(os.path.dirname(filepath))
|
||||||
plugins_dir = os.path.join(tmpdir, "plugins")
|
|
||||||
os.mkdir(plugins_dir)
|
|
||||||
open(os.path.join(plugins_dir, "my_plugin.py"), "w").write(PLUGIN1)
|
|
||||||
open(os.path.join(plugins_dir, "my_plugin_2.py"), "w").write(PLUGIN2)
|
|
||||||
for filename, content in (extra_plugins or {}).items():
|
|
||||||
open(os.path.join(plugins_dir, filename), "w").write(content)
|
|
||||||
config = config or {}
|
config = config or {}
|
||||||
config.update(
|
config.update(
|
||||||
{
|
{
|
||||||
|
|
@ -150,7 +145,7 @@ def make_app_client(
|
||||||
memory=memory,
|
memory=memory,
|
||||||
cors=cors,
|
cors=cors,
|
||||||
metadata=METADATA,
|
metadata=METADATA,
|
||||||
plugins_dir=plugins_dir,
|
plugins_dir=PLUGINS_DIR,
|
||||||
config=config,
|
config=config,
|
||||||
inspect_data=inspect_data,
|
inspect_data=inspect_data,
|
||||||
static_mounts=static_mounts,
|
static_mounts=static_mounts,
|
||||||
|
|
@ -334,177 +329,6 @@ METADATA = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
PLUGIN1 = """
|
|
||||||
from datasette import hookimpl
|
|
||||||
import base64
|
|
||||||
import pint
|
|
||||||
import json
|
|
||||||
|
|
||||||
ureg = pint.UnitRegistry()
|
|
||||||
|
|
||||||
|
|
||||||
@hookimpl
|
|
||||||
def prepare_connection(conn, database, datasette):
|
|
||||||
def convert_units(amount, from_, to_):
|
|
||||||
"select convert_units(100, 'm', 'ft');"
|
|
||||||
return (amount * ureg(from_)).to(to_).to_tuple()[0]
|
|
||||||
conn.create_function('convert_units', 3, convert_units)
|
|
||||||
def prepare_connection_args():
|
|
||||||
return 'database={}, datasette.plugin_config("name-of-plugin")={}'.format(
|
|
||||||
database, datasette.plugin_config("name-of-plugin")
|
|
||||||
)
|
|
||||||
conn.create_function('prepare_connection_args', 0, prepare_connection_args)
|
|
||||||
|
|
||||||
|
|
||||||
@hookimpl
|
|
||||||
def extra_css_urls(template, database, table, datasette):
|
|
||||||
return ['https://plugin-example.com/{}/extra-css-urls-demo.css'.format(
|
|
||||||
base64.b64encode(json.dumps({
|
|
||||||
"template": template,
|
|
||||||
"database": database,
|
|
||||||
"table": table,
|
|
||||||
}).encode("utf8")).decode("utf8")
|
|
||||||
)]
|
|
||||||
|
|
||||||
|
|
||||||
@hookimpl
|
|
||||||
def extra_js_urls():
|
|
||||||
return [{
|
|
||||||
'url': 'https://plugin-example.com/jquery.js',
|
|
||||||
'sri': 'SRIHASH',
|
|
||||||
}, 'https://plugin-example.com/plugin1.js']
|
|
||||||
|
|
||||||
|
|
||||||
@hookimpl
|
|
||||||
def extra_body_script(template, database, table, datasette):
|
|
||||||
return 'var extra_body_script = {};'.format(
|
|
||||||
json.dumps({
|
|
||||||
"template": template,
|
|
||||||
"database": database,
|
|
||||||
"table": table,
|
|
||||||
"config": datasette.plugin_config(
|
|
||||||
"name-of-plugin",
|
|
||||||
database=database,
|
|
||||||
table=table,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@hookimpl
|
|
||||||
def render_cell(value, column, table, database, datasette):
|
|
||||||
# Render some debug output in cell with value RENDER_CELL_DEMO
|
|
||||||
if value != "RENDER_CELL_DEMO":
|
|
||||||
return None
|
|
||||||
return json.dumps({
|
|
||||||
"column": column,
|
|
||||||
"table": table,
|
|
||||||
"database": database,
|
|
||||||
"config": datasette.plugin_config(
|
|
||||||
"name-of-plugin",
|
|
||||||
database=database,
|
|
||||||
table=table,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@hookimpl
|
|
||||||
def extra_template_vars(template, database, table, view_name, request, datasette):
|
|
||||||
return {
|
|
||||||
"extra_template_vars": json.dumps({
|
|
||||||
"template": template,
|
|
||||||
"scope_path": request.scope["path"] if request else None
|
|
||||||
}, default=lambda b: b.decode("utf8"))
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
PLUGIN2 = """
|
|
||||||
from datasette import hookimpl
|
|
||||||
from functools import wraps
|
|
||||||
import jinja2
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
@hookimpl
|
|
||||||
def extra_js_urls():
|
|
||||||
return [{
|
|
||||||
'url': 'https://plugin-example.com/jquery.js',
|
|
||||||
'sri': 'SRIHASH',
|
|
||||||
}, 'https://plugin-example.com/plugin2.js']
|
|
||||||
|
|
||||||
|
|
||||||
@hookimpl
|
|
||||||
def render_cell(value, database):
|
|
||||||
# Render {"href": "...", "label": "..."} as link
|
|
||||||
if not isinstance(value, str):
|
|
||||||
return None
|
|
||||||
stripped = value.strip()
|
|
||||||
if not stripped.startswith("{") and stripped.endswith("}"):
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
data = json.loads(value)
|
|
||||||
except ValueError:
|
|
||||||
return None
|
|
||||||
if not isinstance(data, dict):
|
|
||||||
return None
|
|
||||||
if set(data.keys()) != {"href", "label"}:
|
|
||||||
return None
|
|
||||||
href = data["href"]
|
|
||||||
if not (
|
|
||||||
href.startswith("/") or href.startswith("http://")
|
|
||||||
or href.startswith("https://")
|
|
||||||
):
|
|
||||||
return None
|
|
||||||
return jinja2.Markup(
|
|
||||||
'<a data-database="{database}" href="{href}">{label}</a>'.format(
|
|
||||||
database=database,
|
|
||||||
href=jinja2.escape(data["href"]),
|
|
||||||
label=jinja2.escape(data["label"] or "") or " "
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@hookimpl
|
|
||||||
def extra_template_vars(template, database, table, view_name, request, datasette):
|
|
||||||
async def query_database(sql):
|
|
||||||
first_db = list(datasette.databases.keys())[0]
|
|
||||||
return (
|
|
||||||
await datasette.execute(first_db, sql)
|
|
||||||
).rows[0][0]
|
|
||||||
async def inner():
|
|
||||||
return {
|
|
||||||
"extra_template_vars_from_awaitable": json.dumps({
|
|
||||||
"template": template,
|
|
||||||
"scope_path": request.scope["path"] if request else None,
|
|
||||||
"awaitable": True,
|
|
||||||
}, default=lambda b: b.decode("utf8")),
|
|
||||||
"query_database": query_database,
|
|
||||||
}
|
|
||||||
return inner
|
|
||||||
|
|
||||||
|
|
||||||
@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 = (
|
TABLES = (
|
||||||
"""
|
"""
|
||||||
CREATE TABLE simple_primary_key (
|
CREATE TABLE simple_primary_key (
|
||||||
|
|
|
||||||
89
tests/plugins/my_plugin.py
Normal file
89
tests/plugins/my_plugin.py
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
from datasette import hookimpl
|
||||||
|
import base64
|
||||||
|
import pint
|
||||||
|
import json
|
||||||
|
|
||||||
|
ureg = pint.UnitRegistry()
|
||||||
|
|
||||||
|
|
||||||
|
@hookimpl
|
||||||
|
def prepare_connection(conn, database, datasette):
|
||||||
|
def convert_units(amount, from_, to_):
|
||||||
|
"select convert_units(100, 'm', 'ft');"
|
||||||
|
return (amount * ureg(from_)).to(to_).to_tuple()[0]
|
||||||
|
|
||||||
|
conn.create_function("convert_units", 3, convert_units)
|
||||||
|
|
||||||
|
def prepare_connection_args():
|
||||||
|
return 'database={}, datasette.plugin_config("name-of-plugin")={}'.format(
|
||||||
|
database, datasette.plugin_config("name-of-plugin")
|
||||||
|
)
|
||||||
|
|
||||||
|
conn.create_function("prepare_connection_args", 0, prepare_connection_args)
|
||||||
|
|
||||||
|
|
||||||
|
@hookimpl
|
||||||
|
def extra_css_urls(template, database, table, datasette):
|
||||||
|
return [
|
||||||
|
"https://plugin-example.com/{}/extra-css-urls-demo.css".format(
|
||||||
|
base64.b64encode(
|
||||||
|
json.dumps(
|
||||||
|
{"template": template, "database": database, "table": table,}
|
||||||
|
).encode("utf8")
|
||||||
|
).decode("utf8")
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@hookimpl
|
||||||
|
def extra_js_urls():
|
||||||
|
return [
|
||||||
|
{"url": "https://plugin-example.com/jquery.js", "sri": "SRIHASH",},
|
||||||
|
"https://plugin-example.com/plugin1.js",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@hookimpl
|
||||||
|
def extra_body_script(template, database, table, datasette):
|
||||||
|
return "var extra_body_script = {};".format(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"template": template,
|
||||||
|
"database": database,
|
||||||
|
"table": table,
|
||||||
|
"config": datasette.plugin_config(
|
||||||
|
"name-of-plugin", database=database, table=table,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@hookimpl
|
||||||
|
def render_cell(value, column, table, database, datasette):
|
||||||
|
# Render some debug output in cell with value RENDER_CELL_DEMO
|
||||||
|
if value != "RENDER_CELL_DEMO":
|
||||||
|
return None
|
||||||
|
return json.dumps(
|
||||||
|
{
|
||||||
|
"column": column,
|
||||||
|
"table": table,
|
||||||
|
"database": database,
|
||||||
|
"config": datasette.plugin_config(
|
||||||
|
"name-of-plugin", database=database, table=table,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@hookimpl
|
||||||
|
def extra_template_vars(template, database, table, view_name, request, datasette):
|
||||||
|
return {
|
||||||
|
"extra_template_vars": json.dumps(
|
||||||
|
{
|
||||||
|
"template": template,
|
||||||
|
"scope_path": request.scope["path"] if request else None,
|
||||||
|
},
|
||||||
|
default=lambda b: b.decode("utf8"),
|
||||||
|
)
|
||||||
|
}
|
||||||
94
tests/plugins/my_plugin_2.py
Normal file
94
tests/plugins/my_plugin_2.py
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
from datasette import hookimpl
|
||||||
|
from functools import wraps
|
||||||
|
import jinja2
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
@hookimpl
|
||||||
|
def extra_js_urls():
|
||||||
|
return [
|
||||||
|
{"url": "https://plugin-example.com/jquery.js", "sri": "SRIHASH",},
|
||||||
|
"https://plugin-example.com/plugin2.js",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@hookimpl
|
||||||
|
def render_cell(value, database):
|
||||||
|
# Render {"href": "...", "label": "..."} as link
|
||||||
|
if not isinstance(value, str):
|
||||||
|
return None
|
||||||
|
stripped = value.strip()
|
||||||
|
if not stripped.startswith("{") and stripped.endswith("}"):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
data = json.loads(value)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
return None
|
||||||
|
if set(data.keys()) != {"href", "label"}:
|
||||||
|
return None
|
||||||
|
href = data["href"]
|
||||||
|
if not (
|
||||||
|
href.startswith("/")
|
||||||
|
or href.startswith("http://")
|
||||||
|
or href.startswith("https://")
|
||||||
|
):
|
||||||
|
return None
|
||||||
|
return jinja2.Markup(
|
||||||
|
'<a data-database="{database}" href="{href}">{label}</a>'.format(
|
||||||
|
database=database,
|
||||||
|
href=jinja2.escape(data["href"]),
|
||||||
|
label=jinja2.escape(data["label"] or "") or " ",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@hookimpl
|
||||||
|
def extra_template_vars(template, database, table, view_name, request, datasette):
|
||||||
|
async def query_database(sql):
|
||||||
|
first_db = list(datasette.databases.keys())[0]
|
||||||
|
return (await datasette.execute(first_db, sql)).rows[0][0]
|
||||||
|
|
||||||
|
async def inner():
|
||||||
|
return {
|
||||||
|
"extra_template_vars_from_awaitable": json.dumps(
|
||||||
|
{
|
||||||
|
"template": template,
|
||||||
|
"scope_path": request.scope["path"] if request else None,
|
||||||
|
"awaitable": True,
|
||||||
|
},
|
||||||
|
default=lambda b: b.decode("utf8"),
|
||||||
|
),
|
||||||
|
"query_database": query_database,
|
||||||
|
}
|
||||||
|
|
||||||
|
return inner
|
||||||
|
|
||||||
|
|
||||||
|
@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
|
||||||
9
tests/plugins/view_name.py
Normal file
9
tests/plugins/view_name.py
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
from datasette import hookimpl
|
||||||
|
|
||||||
|
|
||||||
|
@hookimpl
|
||||||
|
def extra_template_vars(view_name, request):
|
||||||
|
return {
|
||||||
|
"view_name": view_name,
|
||||||
|
"request": request,
|
||||||
|
}
|
||||||
|
|
@ -1267,6 +1267,7 @@ def test_plugins_json(app_client):
|
||||||
"templates": False,
|
"templates": False,
|
||||||
"version": None,
|
"version": None,
|
||||||
},
|
},
|
||||||
|
{"name": "view_name.py", "static": False, "templates": False, "version": None},
|
||||||
] == sorted(response.json, key=lambda p: p["name"])
|
] == sorted(response.json, key=lambda p: p["name"])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,10 @@
|
||||||
import pytest
|
import pytest
|
||||||
from .fixtures import make_app_client
|
from .fixtures import make_app_client
|
||||||
|
|
||||||
VIEW_NAME_PLUGIN = """
|
|
||||||
from datasette import hookimpl
|
|
||||||
|
|
||||||
@hookimpl
|
|
||||||
def extra_template_vars(view_name, request):
|
|
||||||
return {
|
|
||||||
"view_name": view_name,
|
|
||||||
"request": request,
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def custom_pages_client(tmp_path_factory):
|
def custom_pages_client(tmp_path_factory):
|
||||||
template_dir = tmp_path_factory.mktemp("page-templates")
|
template_dir = tmp_path_factory.mktemp("page-templates")
|
||||||
extra_plugins = {"view_name.py": VIEW_NAME_PLUGIN}
|
|
||||||
pages_dir = template_dir / "pages"
|
pages_dir = template_dir / "pages"
|
||||||
pages_dir.mkdir()
|
pages_dir.mkdir()
|
||||||
(pages_dir / "about.html").write_text("ABOUT! view_name:{{ view_name }}", "utf-8")
|
(pages_dir / "about.html").write_text("ABOUT! view_name:{{ view_name }}", "utf-8")
|
||||||
|
|
@ -39,9 +27,7 @@ def custom_pages_client(tmp_path_factory):
|
||||||
nested_dir = pages_dir / "nested"
|
nested_dir = pages_dir / "nested"
|
||||||
nested_dir.mkdir()
|
nested_dir.mkdir()
|
||||||
(nested_dir / "nest.html").write_text("Nest!", "utf-8")
|
(nested_dir / "nest.html").write_text("Nest!", "utf-8")
|
||||||
for client in make_app_client(
|
for client in make_app_client(template_dir=str(template_dir)):
|
||||||
template_dir=str(template_dir), extra_plugins=extra_plugins
|
|
||||||
):
|
|
||||||
yield client
|
yield client
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue