mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Plugin configuration now lives in datasette.yaml/json
* Checkpoint, moving top-level plugin config to datasette.json * Support database-level and table-level plugin configuration in datasette.yaml Refs #2093
This commit is contained in:
parent
a4c96d01b2
commit
b2ec8717c3
10 changed files with 217 additions and 54 deletions
|
|
@ -41,7 +41,7 @@ def wait_until_responds(url, timeout=5.0, client=httpx, **kwargs):
|
|||
@pytest_asyncio.fixture
|
||||
async def ds_client():
|
||||
from datasette.app import Datasette
|
||||
from .fixtures import METADATA, PLUGINS_DIR
|
||||
from .fixtures import CONFIG, METADATA, PLUGINS_DIR
|
||||
|
||||
global _ds_client
|
||||
if _ds_client is not None:
|
||||
|
|
@ -49,6 +49,7 @@ async def ds_client():
|
|||
|
||||
ds = Datasette(
|
||||
metadata=METADATA,
|
||||
config=CONFIG,
|
||||
plugins_dir=PLUGINS_DIR,
|
||||
settings={
|
||||
"default_page_size": 50,
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ def make_app_client(
|
|||
inspect_data=None,
|
||||
static_mounts=None,
|
||||
template_dir=None,
|
||||
config=None,
|
||||
metadata=None,
|
||||
crossdb=False,
|
||||
):
|
||||
|
|
@ -158,6 +159,7 @@ def make_app_client(
|
|||
memory=memory,
|
||||
cors=cors,
|
||||
metadata=metadata or METADATA,
|
||||
config=config or CONFIG,
|
||||
plugins_dir=PLUGINS_DIR,
|
||||
settings=settings,
|
||||
inspect_data=inspect_data,
|
||||
|
|
@ -296,6 +298,33 @@ def generate_sortable_rows(num):
|
|||
}
|
||||
|
||||
|
||||
CONFIG = {
|
||||
"plugins": {
|
||||
"name-of-plugin": {"depth": "root"},
|
||||
"env-plugin": {"foo": {"$env": "FOO_ENV"}},
|
||||
"env-plugin-list": [{"in_a_list": {"$env": "FOO_ENV"}}],
|
||||
"file-plugin": {"foo": {"$file": TEMP_PLUGIN_SECRET_FILE}},
|
||||
},
|
||||
"databases": {
|
||||
"fixtures": {
|
||||
"plugins": {"name-of-plugin": {"depth": "database"}},
|
||||
"tables": {
|
||||
"simple_primary_key": {
|
||||
"plugins": {
|
||||
"name-of-plugin": {
|
||||
"depth": "table",
|
||||
"special": "this-is-simple_primary_key",
|
||||
}
|
||||
},
|
||||
},
|
||||
"sortable": {
|
||||
"plugins": {"name-of-plugin": {"depth": "table"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
METADATA = {
|
||||
"title": "Datasette Fixtures",
|
||||
"description_html": 'An example SQLite database demonstrating Datasette. <a href="/login-as-root">Sign in as root user</a>',
|
||||
|
|
@ -306,26 +335,13 @@ METADATA = {
|
|||
"about": "About Datasette",
|
||||
"about_url": "https://github.com/simonw/datasette",
|
||||
"extra_css_urls": ["/static/extra-css-urls.css"],
|
||||
"plugins": {
|
||||
"name-of-plugin": {"depth": "root"},
|
||||
"env-plugin": {"foo": {"$env": "FOO_ENV"}},
|
||||
"env-plugin-list": [{"in_a_list": {"$env": "FOO_ENV"}}],
|
||||
"file-plugin": {"foo": {"$file": TEMP_PLUGIN_SECRET_FILE}},
|
||||
},
|
||||
"databases": {
|
||||
"fixtures": {
|
||||
"description": "Test tables description",
|
||||
"plugins": {"name-of-plugin": {"depth": "database"}},
|
||||
"tables": {
|
||||
"simple_primary_key": {
|
||||
"description_html": "Simple <em>primary</em> key",
|
||||
"title": "This <em>HTML</em> is escaped",
|
||||
"plugins": {
|
||||
"name-of-plugin": {
|
||||
"depth": "table",
|
||||
"special": "this-is-simple_primary_key",
|
||||
}
|
||||
},
|
||||
},
|
||||
"sortable": {
|
||||
"sortable_columns": [
|
||||
|
|
@ -334,7 +350,6 @@ METADATA = {
|
|||
"sortable_with_nulls_2",
|
||||
"text",
|
||||
],
|
||||
"plugins": {"name-of-plugin": {"depth": "table"}},
|
||||
},
|
||||
"no_primary_key": {"sortable_columns": [], "hidden": True},
|
||||
"units": {"units": {"distance": "m", "frequency": "Hz"}},
|
||||
|
|
@ -768,6 +783,7 @@ def assert_permissions_checked(datasette, actions):
|
|||
type=click.Path(file_okay=True, dir_okay=False),
|
||||
)
|
||||
@click.argument("metadata", required=False)
|
||||
@click.argument("config", required=False)
|
||||
@click.argument(
|
||||
"plugins_path", type=click.Path(file_okay=False, dir_okay=True), required=False
|
||||
)
|
||||
|
|
@ -782,7 +798,7 @@ def assert_permissions_checked(datasette, actions):
|
|||
type=click.Path(file_okay=True, dir_okay=False),
|
||||
help="Write out second test DB to this file",
|
||||
)
|
||||
def cli(db_filename, metadata, plugins_path, recreate, extra_db_filename):
|
||||
def cli(db_filename, config, metadata, plugins_path, recreate, extra_db_filename):
|
||||
"""Write out the fixtures database used by Datasette's test suite"""
|
||||
if metadata and not metadata.endswith(".json"):
|
||||
raise click.ClickException("Metadata should end with .json")
|
||||
|
|
@ -805,6 +821,10 @@ def cli(db_filename, metadata, plugins_path, recreate, extra_db_filename):
|
|||
with open(metadata, "w") as fp:
|
||||
fp.write(json.dumps(METADATA, indent=4))
|
||||
print(f"- metadata written to {metadata}")
|
||||
if config:
|
||||
with open(config, "w") as fp:
|
||||
fp.write(json.dumps(CONFIG, indent=4))
|
||||
print(f"- config written to {config}")
|
||||
if plugins_path:
|
||||
path = pathlib.Path(plugins_path)
|
||||
if not path.exists():
|
||||
|
|
|
|||
|
|
@ -238,6 +238,44 @@ def test_setting(args):
|
|||
assert settings["default_page_size"] == 5
|
||||
|
||||
|
||||
def test_plugin_s_overwrite():
|
||||
runner = CliRunner()
|
||||
plugins_dir = str(pathlib.Path(__file__).parent / "plugins")
|
||||
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
[
|
||||
"--plugins-dir",
|
||||
plugins_dir,
|
||||
"--get",
|
||||
"/_memory.json?sql=select+prepare_connection_args()",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0, result.output
|
||||
assert (
|
||||
json.loads(result.output).get("rows")[0].get("prepare_connection_args()")
|
||||
== 'database=_memory, datasette.plugin_config("name-of-plugin")=None'
|
||||
)
|
||||
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
[
|
||||
"--plugins-dir",
|
||||
plugins_dir,
|
||||
"--get",
|
||||
"/_memory.json?sql=select+prepare_connection_args()",
|
||||
"-s",
|
||||
"plugins.name-of-plugin",
|
||||
"OVERRIDE",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0, result.output
|
||||
assert (
|
||||
json.loads(result.output).get("rows")[0].get("prepare_connection_args()")
|
||||
== 'database=_memory, datasette.plugin_config("name-of-plugin")=OVERRIDE'
|
||||
)
|
||||
|
||||
|
||||
def test_setting_type_validation():
|
||||
runner = CliRunner(mix_stderr=False)
|
||||
result = runner.invoke(cli, ["--setting", "default_page_size", "dog"])
|
||||
|
|
|
|||
|
|
@ -234,9 +234,6 @@ async def test_plugin_config(ds_client):
|
|||
async def test_plugin_config_env(ds_client):
|
||||
os.environ["FOO_ENV"] = "FROM_ENVIRONMENT"
|
||||
assert {"foo": "FROM_ENVIRONMENT"} == ds_client.ds.plugin_config("env-plugin")
|
||||
# Ensure secrets aren't visible in /-/metadata.json
|
||||
metadata = await ds_client.get("/-/metadata.json")
|
||||
assert {"foo": {"$env": "FOO_ENV"}} == metadata.json()["plugins"]["env-plugin"]
|
||||
del os.environ["FOO_ENV"]
|
||||
|
||||
|
||||
|
|
@ -246,11 +243,6 @@ async def test_plugin_config_env_from_list(ds_client):
|
|||
assert [{"in_a_list": "FROM_ENVIRONMENT"}] == ds_client.ds.plugin_config(
|
||||
"env-plugin-list"
|
||||
)
|
||||
# Ensure secrets aren't visible in /-/metadata.json
|
||||
metadata = await ds_client.get("/-/metadata.json")
|
||||
assert [{"in_a_list": {"$env": "FOO_ENV"}}] == metadata.json()["plugins"][
|
||||
"env-plugin-list"
|
||||
]
|
||||
del os.environ["FOO_ENV"]
|
||||
|
||||
|
||||
|
|
@ -259,11 +251,6 @@ async def test_plugin_config_file(ds_client):
|
|||
with open(TEMP_PLUGIN_SECRET_FILE, "w") as fp:
|
||||
fp.write("FROM_FILE")
|
||||
assert {"foo": "FROM_FILE"} == ds_client.ds.plugin_config("file-plugin")
|
||||
# Ensure secrets aren't visible in /-/metadata.json
|
||||
metadata = await ds_client.get("/-/metadata.json")
|
||||
assert {"foo": {"$file": TEMP_PLUGIN_SECRET_FILE}} == metadata.json()["plugins"][
|
||||
"file-plugin"
|
||||
]
|
||||
os.remove(TEMP_PLUGIN_SECRET_FILE)
|
||||
|
||||
|
||||
|
|
@ -722,7 +709,7 @@ async def test_hook_register_routes(ds_client, path, body):
|
|||
@pytest.mark.parametrize("configured_path", ("path1", "path2"))
|
||||
def test_hook_register_routes_with_datasette(configured_path):
|
||||
with make_app_client(
|
||||
metadata={
|
||||
config={
|
||||
"plugins": {
|
||||
"register-route-demo": {
|
||||
"path": configured_path,
|
||||
|
|
@ -741,7 +728,7 @@ def test_hook_register_routes_with_datasette(configured_path):
|
|||
def test_hook_register_routes_override():
|
||||
"Plugins can over-ride default paths such as /db/table"
|
||||
with make_app_client(
|
||||
metadata={
|
||||
config={
|
||||
"plugins": {
|
||||
"register-route-demo": {
|
||||
"path": "blah",
|
||||
|
|
@ -1099,7 +1086,7 @@ async def test_hook_filters_from_request(ds_client):
|
|||
@pytest.mark.parametrize("extra_metadata", (False, True))
|
||||
async def test_hook_register_permissions(extra_metadata):
|
||||
ds = Datasette(
|
||||
metadata={
|
||||
config={
|
||||
"plugins": {
|
||||
"datasette-register-permissions": {
|
||||
"permissions": [
|
||||
|
|
@ -1151,7 +1138,7 @@ async def test_hook_register_permissions_no_duplicates(duplicate):
|
|||
if duplicate == "abbr":
|
||||
abbr2 = "abbr1"
|
||||
ds = Datasette(
|
||||
metadata={
|
||||
config={
|
||||
"plugins": {
|
||||
"datasette-register-permissions": {
|
||||
"permissions": [
|
||||
|
|
@ -1186,7 +1173,7 @@ async def test_hook_register_permissions_no_duplicates(duplicate):
|
|||
@pytest.mark.asyncio
|
||||
async def test_hook_register_permissions_allows_identical_duplicates():
|
||||
ds = Datasette(
|
||||
metadata={
|
||||
config={
|
||||
"plugins": {
|
||||
"datasette-register-permissions": {
|
||||
"permissions": [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue