Treat plugins in metadata as if they were in config, closes #2248

pull/2257/head
Simon Willison 2024-02-01 15:33:33 -08:00
rodzic d4bc2b2dfc
commit be4f02335f
3 zmienionych plików z 97 dodań i 0 usunięć

Wyświetl plik

@ -74,6 +74,7 @@ from .utils import (
find_spatialite,
format_bytes,
module_from_path,
move_plugins,
parse_metadata,
resolve_env_secrets,
resolve_routes,
@ -341,6 +342,11 @@ class Datasette:
with config_files[0].open() as fp:
config = parse_metadata(fp.read())
# Move any "plugins" settings from metadata to config - updates them in place
metadata = metadata or {}
config = config or {}
move_plugins(metadata, config)
self._metadata_local = metadata or {}
self.sqlite_extensions = []
for extension in sqlite_extensions or []:

Wyświetl plik

@ -1287,3 +1287,43 @@ def make_slot_function(name, datasette, request, **kwargs):
return markupsafe.Markup("".join(html_bits))
return inner
def move_plugins(source, destination):
"""
Move 'plugins' keys from source to destination dictionary. Creates hierarchy in destination if needed.
After moving, recursively remove any keys in the source that are left empty.
"""
def recursive_move(src, dest, path=None):
if path is None:
path = []
for key, value in list(src.items()):
new_path = path + [key]
if key == "plugins":
# Navigate and create the hierarchy in destination if needed
d = dest
for step in path:
d = d.setdefault(step, {})
# Move the plugins
d[key] = value
# Remove the plugins from source
src.pop(key, None)
elif isinstance(value, dict):
recursive_move(value, dest, new_path)
# After moving, check if the current dictionary is empty and remove it if so
if not value:
src.pop(key, None)
def prune_empty_dicts(d):
"""
Recursively prune all empty dictionaries from a given dictionary.
"""
for key, value in list(d.items()):
if isinstance(value, dict):
prune_empty_dicts(value)
if value == {}:
d.pop(key, None)
recursive_move(source, destination)
prune_empty_dicts(source)

Wyświetl plik

@ -1458,3 +1458,54 @@ async def test_hook_register_events():
datasette = Datasette(memory=True)
await datasette.invoke_startup()
assert any(k.__name__ == "OneEvent" for k in datasette.event_classes)
@pytest.mark.parametrize(
"metadata,config,expected_metadata,expected_config",
(
(
# Instance level
{"plugins": {"datasette-foo": "bar"}},
{},
{},
{"plugins": {"datasette-foo": "bar"}},
),
(
# Database level
{"databases": {"foo": {"plugins": {"datasette-foo": "bar"}}}},
{},
{},
{"databases": {"foo": {"plugins": {"datasette-foo": "bar"}}}},
),
(
# Table level
{
"databases": {
"foo": {"tables": {"bar": {"plugins": {"datasette-foo": "bar"}}}}
}
},
{},
{},
{
"databases": {
"foo": {"tables": {"bar": {"plugins": {"datasette-foo": "bar"}}}}
}
},
),
(
# Keep other keys
{"plugins": {"datasette-foo": "bar"}, "other": "key"},
{"original_config": "original"},
{"other": "key"},
{"original_config": "original", "plugins": {"datasette-foo": "bar"}},
),
),
)
def test_metadata_plugin_config_treated_as_config(
metadata, config, expected_metadata, expected_config
):
ds = Datasette(metadata=metadata, config=config)
actual_metadata = ds.metadata()
assert "plugins" not in actual_metadata
assert actual_metadata == expected_metadata
assert ds.config == expected_config