kopia lustrzana https://github.com/simonw/datasette
Start datasette.json, re-add --config, rm settings.json
The first step in defining the new `datasette.json/yaml` configuration mechanism. Refs #2093, #2143, #493isort-22-aug-2023
rodzic
01e0558825
commit
17ec309e14
|
@ -242,6 +242,7 @@ class Datasette:
|
||||||
cache_headers=True,
|
cache_headers=True,
|
||||||
cors=False,
|
cors=False,
|
||||||
inspect_data=None,
|
inspect_data=None,
|
||||||
|
config=None,
|
||||||
metadata=None,
|
metadata=None,
|
||||||
sqlite_extensions=None,
|
sqlite_extensions=None,
|
||||||
template_dir=None,
|
template_dir=None,
|
||||||
|
@ -316,6 +317,7 @@ class Datasette:
|
||||||
)
|
)
|
||||||
self.cache_headers = cache_headers
|
self.cache_headers = cache_headers
|
||||||
self.cors = cors
|
self.cors = cors
|
||||||
|
config_files = []
|
||||||
metadata_files = []
|
metadata_files = []
|
||||||
if config_dir:
|
if config_dir:
|
||||||
metadata_files = [
|
metadata_files = [
|
||||||
|
@ -323,9 +325,19 @@ class Datasette:
|
||||||
for filename in ("metadata.json", "metadata.yaml", "metadata.yml")
|
for filename in ("metadata.json", "metadata.yaml", "metadata.yml")
|
||||||
if (config_dir / filename).exists()
|
if (config_dir / filename).exists()
|
||||||
]
|
]
|
||||||
|
config_files = [
|
||||||
|
config_dir / filename
|
||||||
|
for filename in ("datasette.json", "datasette.yaml", "datasette.yml")
|
||||||
|
if (config_dir / filename).exists()
|
||||||
|
]
|
||||||
if config_dir and metadata_files and not metadata:
|
if config_dir and metadata_files and not metadata:
|
||||||
with metadata_files[0].open() as fp:
|
with metadata_files[0].open() as fp:
|
||||||
metadata = parse_metadata(fp.read())
|
metadata = parse_metadata(fp.read())
|
||||||
|
|
||||||
|
if config_dir and config_files and not config:
|
||||||
|
with config_files[0].open() as fp:
|
||||||
|
config = parse_metadata(fp.read())
|
||||||
|
|
||||||
self._metadata_local = metadata or {}
|
self._metadata_local = metadata or {}
|
||||||
self.sqlite_extensions = []
|
self.sqlite_extensions = []
|
||||||
for extension in sqlite_extensions or []:
|
for extension in sqlite_extensions or []:
|
||||||
|
@ -344,17 +356,19 @@ class Datasette:
|
||||||
if config_dir and (config_dir / "static").is_dir() and not static_mounts:
|
if config_dir and (config_dir / "static").is_dir() and not static_mounts:
|
||||||
static_mounts = [("static", str((config_dir / "static").resolve()))]
|
static_mounts = [("static", str((config_dir / "static").resolve()))]
|
||||||
self.static_mounts = static_mounts or []
|
self.static_mounts = static_mounts or []
|
||||||
if config_dir and (config_dir / "config.json").exists():
|
if config_dir and (config_dir / "datasette.json").exists() and not config:
|
||||||
raise StartupError("config.json should be renamed to settings.json")
|
config = json.loads((config_dir / "datasette.json").read_text())
|
||||||
if config_dir and (config_dir / "settings.json").exists() and not settings:
|
|
||||||
settings = json.loads((config_dir / "settings.json").read_text())
|
config = config or {}
|
||||||
# Validate those settings
|
config_settings = config.get("settings") or {}
|
||||||
for key in settings:
|
|
||||||
if key not in DEFAULT_SETTINGS:
|
# validate "settings" keys in datasette.json
|
||||||
raise StartupError(
|
for key in config_settings:
|
||||||
"Invalid setting '{}' in settings.json".format(key)
|
if key not in DEFAULT_SETTINGS:
|
||||||
)
|
raise StartupError("Invalid setting '{}' in datasette.json".format(key))
|
||||||
self._settings = dict(DEFAULT_SETTINGS, **(settings or {}))
|
|
||||||
|
# CLI settings should overwrite datasette.json settings
|
||||||
|
self._settings = dict(DEFAULT_SETTINGS, **(config_settings), **(settings or {}))
|
||||||
self.renderers = {} # File extension -> (renderer, can_render) functions
|
self.renderers = {} # File extension -> (renderer, can_render) functions
|
||||||
self.version_note = version_note
|
self.version_note = version_note
|
||||||
if self.setting("num_sql_threads") == 0:
|
if self.setting("num_sql_threads") == 0:
|
||||||
|
|
|
@ -50,46 +50,6 @@ except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Config(click.ParamType):
|
|
||||||
# This will be removed in Datasette 1.0 in favour of class Setting
|
|
||||||
name = "config"
|
|
||||||
|
|
||||||
def convert(self, config, param, ctx):
|
|
||||||
if ":" not in config:
|
|
||||||
self.fail(f'"{config}" should be name:value', param, ctx)
|
|
||||||
return
|
|
||||||
name, value = config.split(":", 1)
|
|
||||||
if name not in DEFAULT_SETTINGS:
|
|
||||||
msg = (
|
|
||||||
OBSOLETE_SETTINGS.get(name)
|
|
||||||
or f"{name} is not a valid option (--help-settings to see all)"
|
|
||||||
)
|
|
||||||
self.fail(
|
|
||||||
msg,
|
|
||||||
param,
|
|
||||||
ctx,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
# Type checking
|
|
||||||
default = DEFAULT_SETTINGS[name]
|
|
||||||
if isinstance(default, bool):
|
|
||||||
try:
|
|
||||||
return name, value_as_boolean(value)
|
|
||||||
except ValueAsBooleanError:
|
|
||||||
self.fail(f'"{name}" should be on/off/true/false/1/0', param, ctx)
|
|
||||||
return
|
|
||||||
elif isinstance(default, int):
|
|
||||||
if not value.isdigit():
|
|
||||||
self.fail(f'"{name}" should be an integer', param, ctx)
|
|
||||||
return
|
|
||||||
return name, int(value)
|
|
||||||
elif isinstance(default, str):
|
|
||||||
return name, value
|
|
||||||
else:
|
|
||||||
# Should never happen:
|
|
||||||
self.fail("Invalid option")
|
|
||||||
|
|
||||||
|
|
||||||
class Setting(CompositeParamType):
|
class Setting(CompositeParamType):
|
||||||
name = "setting"
|
name = "setting"
|
||||||
arity = 2
|
arity = 2
|
||||||
|
@ -456,9 +416,8 @@ def uninstall(packages, yes):
|
||||||
@click.option("--memory", is_flag=True, help="Make /_memory database available")
|
@click.option("--memory", is_flag=True, help="Make /_memory database available")
|
||||||
@click.option(
|
@click.option(
|
||||||
"--config",
|
"--config",
|
||||||
type=Config(),
|
type=click.File(mode="r"),
|
||||||
help="Deprecated: set config option using configname:value. Use --setting instead.",
|
help="Path to JSON/YAML Datasette configuration file",
|
||||||
multiple=True,
|
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--setting",
|
"--setting",
|
||||||
|
@ -568,6 +527,8 @@ def serve(
|
||||||
reloader = hupper.start_reloader("datasette.cli.serve")
|
reloader = hupper.start_reloader("datasette.cli.serve")
|
||||||
if immutable:
|
if immutable:
|
||||||
reloader.watch_files(immutable)
|
reloader.watch_files(immutable)
|
||||||
|
if config:
|
||||||
|
reloader.watch_files([config.name])
|
||||||
if metadata:
|
if metadata:
|
||||||
reloader.watch_files([metadata.name])
|
reloader.watch_files([metadata.name])
|
||||||
|
|
||||||
|
@ -580,26 +541,22 @@ def serve(
|
||||||
if metadata:
|
if metadata:
|
||||||
metadata_data = parse_metadata(metadata.read())
|
metadata_data = parse_metadata(metadata.read())
|
||||||
|
|
||||||
combined_settings = {}
|
config_data = None
|
||||||
if config:
|
if config:
|
||||||
click.echo(
|
config_data = parse_metadata(config.read())
|
||||||
"--config name:value will be deprecated in Datasette 1.0, use --setting name value instead",
|
|
||||||
err=True,
|
|
||||||
)
|
|
||||||
combined_settings.update(config)
|
|
||||||
combined_settings.update(settings)
|
|
||||||
|
|
||||||
kwargs = dict(
|
kwargs = dict(
|
||||||
immutables=immutable,
|
immutables=immutable,
|
||||||
cache_headers=not reload,
|
cache_headers=not reload,
|
||||||
cors=cors,
|
cors=cors,
|
||||||
inspect_data=inspect_data,
|
inspect_data=inspect_data,
|
||||||
|
config=config_data,
|
||||||
metadata=metadata_data,
|
metadata=metadata_data,
|
||||||
sqlite_extensions=sqlite_extensions,
|
sqlite_extensions=sqlite_extensions,
|
||||||
template_dir=template_dir,
|
template_dir=template_dir,
|
||||||
plugins_dir=plugins_dir,
|
plugins_dir=plugins_dir,
|
||||||
static_mounts=static,
|
static_mounts=static,
|
||||||
settings=combined_settings,
|
settings=dict(settings),
|
||||||
memory=memory,
|
memory=memory,
|
||||||
secret=secret,
|
secret=secret,
|
||||||
version_note=version_note,
|
version_note=version_note,
|
||||||
|
|
|
@ -112,8 +112,7 @@ Once started you can access it at ``http://localhost:8001``
|
||||||
--static MOUNT:DIRECTORY Serve static files from this directory at
|
--static MOUNT:DIRECTORY Serve static files from this directory at
|
||||||
/MOUNT/...
|
/MOUNT/...
|
||||||
--memory Make /_memory database available
|
--memory Make /_memory database available
|
||||||
--config CONFIG Deprecated: set config option using
|
--config FILENAME Path to JSON/YAML Datasette configuration file
|
||||||
configname:value. Use --setting instead.
|
|
||||||
--setting SETTING... Setting, see
|
--setting SETTING... Setting, see
|
||||||
docs.datasette.io/en/stable/settings.html
|
docs.datasette.io/en/stable/settings.html
|
||||||
--secret TEXT Secret used for signing secure values, such as
|
--secret TEXT Secret used for signing secure values, such as
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
.. _configuration:
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
========
|
||||||
|
|
||||||
|
Datasette offers many way to configure your Datasette instances: server settings, plugin configuration, authentication, and more.
|
||||||
|
|
||||||
|
To facilitate this, You can provide a `datasette.yaml` configuration file to datasette with the ``--config``/ ``-c`` flag:
|
||||||
|
|
||||||
|
datasette mydatabase.db --config datasette.yaml
|
|
@ -47,9 +47,9 @@ Datasette will detect the files in that directory and automatically configure it
|
||||||
The files that can be included in this directory are as follows. All are optional.
|
The files that can be included in this directory are as follows. All are optional.
|
||||||
|
|
||||||
* ``*.db`` (or ``*.sqlite3`` or ``*.sqlite``) - SQLite database files that will be served by Datasette
|
* ``*.db`` (or ``*.sqlite3`` or ``*.sqlite``) - SQLite database files that will be served by Datasette
|
||||||
|
* ``datasette.json`` - :ref:`configuration` for the Datasette instance
|
||||||
* ``metadata.json`` - :ref:`metadata` for those databases - ``metadata.yaml`` or ``metadata.yml`` can be used as well
|
* ``metadata.json`` - :ref:`metadata` for those databases - ``metadata.yaml`` or ``metadata.yml`` can be used as well
|
||||||
* ``inspect-data.json`` - the result of running ``datasette inspect *.db --inspect-file=inspect-data.json`` from the configuration directory - any database files listed here will be treated as immutable, so they should not be changed while Datasette is running
|
* ``inspect-data.json`` - the result of running ``datasette inspect *.db --inspect-file=inspect-data.json`` from the configuration directory - any database files listed here will be treated as immutable, so they should not be changed while Datasette is running
|
||||||
* ``settings.json`` - settings that would normally be passed using ``--setting`` - here they should be stored as a JSON object of key/value pairs
|
|
||||||
* ``templates/`` - a directory containing :ref:`customization_custom_templates`
|
* ``templates/`` - a directory containing :ref:`customization_custom_templates`
|
||||||
* ``plugins/`` - a directory containing plugins, see :ref:`writing_plugins_one_off`
|
* ``plugins/`` - a directory containing plugins, see :ref:`writing_plugins_one_off`
|
||||||
* ``static/`` - a directory containing static files - these will be served from ``/static/filename.txt``, see :ref:`customization_static_files`
|
* ``static/`` - a directory containing static files - these will be served from ``/static/filename.txt``, see :ref:`customization_static_files`
|
||||||
|
|
|
@ -258,17 +258,6 @@ def test_setting_default_allow_sql(default_allow_sql):
|
||||||
assert "Forbidden" in result.output
|
assert "Forbidden" in result.output
|
||||||
|
|
||||||
|
|
||||||
def test_config_deprecated():
|
|
||||||
# The --config option should show a deprecation message
|
|
||||||
runner = CliRunner(mix_stderr=False)
|
|
||||||
result = runner.invoke(
|
|
||||||
cli, ["--config", "allow_download:off", "--get", "/-/settings.json"]
|
|
||||||
)
|
|
||||||
assert result.exit_code == 0
|
|
||||||
assert not json.loads(result.output)["allow_download"]
|
|
||||||
assert "will be deprecated in" in result.stderr
|
|
||||||
|
|
||||||
|
|
||||||
def test_sql_errors_logged_to_stderr():
|
def test_sql_errors_logged_to_stderr():
|
||||||
runner = CliRunner(mix_stderr=False)
|
runner = CliRunner(mix_stderr=False)
|
||||||
result = runner.invoke(cli, ["--get", "/_memory.json?sql=select+blah"])
|
result = runner.invoke(cli, ["--get", "/_memory.json?sql=select+blah"])
|
||||||
|
|
|
@ -19,8 +19,10 @@ def extra_template_vars():
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
METADATA = {"title": "This is from metadata"}
|
METADATA = {"title": "This is from metadata"}
|
||||||
SETTINGS = {
|
CONFIG = {
|
||||||
"default_cache_ttl": 60,
|
"settings": {
|
||||||
|
"default_cache_ttl": 60,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
CSS = """
|
CSS = """
|
||||||
body { margin-top: 3em}
|
body { margin-top: 3em}
|
||||||
|
@ -47,7 +49,7 @@ def config_dir(tmp_path_factory):
|
||||||
(static_dir / "hello.css").write_text(CSS, "utf-8")
|
(static_dir / "hello.css").write_text(CSS, "utf-8")
|
||||||
|
|
||||||
(config_dir / "metadata.json").write_text(json.dumps(METADATA), "utf-8")
|
(config_dir / "metadata.json").write_text(json.dumps(METADATA), "utf-8")
|
||||||
(config_dir / "settings.json").write_text(json.dumps(SETTINGS), "utf-8")
|
(config_dir / "datasette.json").write_text(json.dumps(CONFIG), "utf-8")
|
||||||
|
|
||||||
for dbname in ("demo.db", "immutable.db", "j.sqlite3", "k.sqlite"):
|
for dbname in ("demo.db", "immutable.db", "j.sqlite3", "k.sqlite"):
|
||||||
db = sqlite3.connect(str(config_dir / dbname))
|
db = sqlite3.connect(str(config_dir / dbname))
|
||||||
|
@ -81,16 +83,16 @@ def config_dir(tmp_path_factory):
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_settings(config_dir):
|
def test_invalid_settings(config_dir):
|
||||||
previous = (config_dir / "settings.json").read_text("utf-8")
|
previous = (config_dir / "datasette.json").read_text("utf-8")
|
||||||
(config_dir / "settings.json").write_text(
|
(config_dir / "datasette.json").write_text(
|
||||||
json.dumps({"invalid": "invalid-setting"}), "utf-8"
|
json.dumps({"settings": {"invalid": "invalid-setting"}}), "utf-8"
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
with pytest.raises(StartupError) as ex:
|
with pytest.raises(StartupError) as ex:
|
||||||
ds = Datasette([], config_dir=config_dir)
|
ds = Datasette([], config_dir=config_dir)
|
||||||
assert ex.value.args[0] == "Invalid setting 'invalid' in settings.json"
|
assert ex.value.args[0] == "Invalid setting 'invalid' in datasette.json"
|
||||||
finally:
|
finally:
|
||||||
(config_dir / "settings.json").write_text(previous, "utf-8")
|
(config_dir / "datasette.json").write_text(previous, "utf-8")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
|
@ -111,15 +113,6 @@ def test_settings(config_dir_client):
|
||||||
assert 60 == response.json["default_cache_ttl"]
|
assert 60 == response.json["default_cache_ttl"]
|
||||||
|
|
||||||
|
|
||||||
def test_error_on_config_json(tmp_path_factory):
|
|
||||||
config_dir = tmp_path_factory.mktemp("config-dir")
|
|
||||||
(config_dir / "config.json").write_text(json.dumps(SETTINGS), "utf-8")
|
|
||||||
runner = CliRunner(mix_stderr=False)
|
|
||||||
result = runner.invoke(cli, [str(config_dir), "--get", "/-/settings.json"])
|
|
||||||
assert result.exit_code == 1
|
|
||||||
assert "config.json should be renamed to settings.json" in result.stderr
|
|
||||||
|
|
||||||
|
|
||||||
def test_plugins(config_dir_client):
|
def test_plugins(config_dir_client):
|
||||||
response = config_dir_client.get("/-/plugins.json")
|
response = config_dir_client.get("/-/plugins.json")
|
||||||
assert 200 == response.status
|
assert 200 == response.status
|
||||||
|
|
Ładowanie…
Reference in New Issue