Skip to content

Commit

Permalink
-s/--setting x y gets merged into datasette.yml, refs #2143, #2156
Browse files Browse the repository at this point in the history
This change updates the `-s/--setting` option to `datasette serve` to allow it to be used to set arbitrarily complex nested settings in a way that is compatible with the new `-c datasette.yml` work happening in:
- #2143

It will enable things like this:
```
datasette data.db --setting plugins.datasette-ripgrep.path "/home/simon/code"
```
For the moment though it just affects [settings](https://docs.datasette.io/en/1.0a4/settings.html) - so you can do this:
```
datasette data.db --setting settings.sql_time_limit_ms 3500
```
I've also implemented a backwards compatibility mechanism, so if you use it this way (the old way):
```
datasette data.db --setting sql_time_limit_ms 3500
```
It will notice that the setting you passed is one of Datasette's core settings, and will treat that as if you said `settings.sql_time_limit_ms` instead.
  • Loading branch information
simonw committed Aug 28, 2023
1 parent 527cec6 commit d9aad1f
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 47 deletions.
62 changes: 31 additions & 31 deletions datasette/cli.py
Expand Up @@ -31,6 +31,7 @@
ConnectionProblem,
SpatialiteConnectionProblem,
initial_path_for_datasette,
pairs_to_nested_config,
temporary_docker_directory,
value_as_boolean,
SpatialiteNotFound,
Expand All @@ -56,35 +57,27 @@ class Setting(CompositeParamType):

def convert(self, config, param, ctx):
name, value = config
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")
if name in DEFAULT_SETTINGS:
# For backwards compatibility with how this worked prior to
# Datasette 1.0, we turn bare setting names into setting.name
# Type checking for those older settings
default = DEFAULT_SETTINGS[name]
name = "settings.{}".format(name)
if isinstance(default, bool):
try:
return name, "true" if value_as_boolean(value) else "false"
except ValueAsBooleanError:
self.fail(f'"{name}" should be on/off/true/false/1/0', param, ctx)
elif isinstance(default, int):
if not value.isdigit():
self.fail(f'"{name}" should be an integer', param, ctx)
return name, value
elif isinstance(default, str):
return name, value
else:
# Should never happen:
self.fail("Invalid option")
return name, value


def sqlite_extensions(fn):
Expand Down Expand Up @@ -425,7 +418,7 @@ def uninstall(packages, yes):
"--setting",
"settings",
type=Setting(),
help="Setting, see docs.datasette.io/en/stable/settings.html",
help="nested.key, value setting to use in Datasette configuration",
multiple=True,
)
@click.option(
Expand Down Expand Up @@ -547,6 +540,13 @@ def serve(
if config:
config_data = parse_metadata(config.read())

config_data = config_data or {}

# Merge in settings from -s/--setting
if settings:
settings_updates = pairs_to_nested_config(settings)
config_data.update(settings_updates)

kwargs = dict(
immutables=immutable,
cache_headers=not reload,
Expand All @@ -558,7 +558,7 @@ def serve(
template_dir=template_dir,
plugins_dir=plugins_dir,
static_mounts=static,
settings=dict(settings),
settings=None, # These are passed in config= now
memory=memory,
secret=secret,
version_note=version_note,
Expand Down
4 changes: 2 additions & 2 deletions docs/cli-reference.rst
Expand Up @@ -113,8 +113,8 @@ Once started you can access it at ``http://localhost:8001``
/MOUNT/...
--memory Make /_memory database available
-c, --config FILENAME Path to JSON/YAML Datasette configuration file
--setting SETTING... Setting, see
docs.datasette.io/en/stable/settings.html
-s, --setting SETTING... nested.key, value setting to use in Datasette
configuration
--secret TEXT Secret used for signing secure values, such as
signed cookies
--root Output URL that sets a cookie authenticating
Expand Down
27 changes: 13 additions & 14 deletions tests/test_cli.py
Expand Up @@ -220,20 +220,27 @@ def test_serve_invalid_ports(invalid_port):
assert "Invalid value for '-p'" in result.stderr


def test_setting():
@pytest.mark.parametrize(
"args",
(
["--setting", "default_page_size", "5"],
["--setting", "settings.default_page_size", "5"],
["-s", "settings.default_page_size", "5"],
),
)
def test_setting(args):
runner = CliRunner()
result = runner.invoke(
cli, ["--setting", "default_page_size", "5", "--get", "/-/settings.json"]
)
result = runner.invoke(cli, ["--get", "/-/settings.json"] + args)
assert result.exit_code == 0, result.output
assert json.loads(result.output)["default_page_size"] == 5
settings = json.loads(result.output)
assert settings["default_page_size"] == 5


def test_setting_type_validation():
runner = CliRunner(mix_stderr=False)
result = runner.invoke(cli, ["--setting", "default_page_size", "dog"])
assert result.exit_code == 2
assert '"default_page_size" should be an integer' in result.stderr
assert '"settings.default_page_size" should be an integer' in result.stderr


@pytest.mark.parametrize("default_allow_sql", (True, False))
Expand Down Expand Up @@ -360,11 +367,3 @@ def test_help_settings():
result = runner.invoke(cli, ["--help-settings"])
for setting in SETTINGS:
assert setting.name in result.output


@pytest.mark.parametrize("setting", ("hash_urls", "default_cache_ttl_hashed"))
def test_help_error_on_hash_urls_setting(setting):
runner = CliRunner()
result = runner.invoke(cli, ["--setting", setting, 1])
assert result.exit_code == 2
assert "The hash_urls setting has been removed" in result.output

0 comments on commit d9aad1f

Please sign in to comment.