diff --git a/dclient/cli.py b/dclient/cli.py index 3adfebc..48f249b 100644 --- a/dclient/cli.py +++ b/dclient/cli.py @@ -60,7 +60,10 @@ def _resolve_instance(instance, config_file): if env_url: return env_url.rstrip("/") raise click.ClickException( - "No instance specified. Use -i, set a default instance, or set DATASETTE_URL." + "No instance specified. Use -i , or configure a default:\n\n" + " dclient alias add \n" + " dclient alias default \n\n" + "Or set the DATASETTE_URL environment variable." ) @@ -81,7 +84,9 @@ def _resolve_database(database, instance_alias, config_file): if env_db: return env_db raise click.ClickException( - "No database specified. Use -d, set a default database, or set DATASETTE_DATABASE." + "No database specified. Use -d , or configure a default:\n\n" + " dclient alias default-db \n\n" + "Or set the DATASETTE_DATABASE environment variable." ) @@ -642,6 +647,31 @@ def default_query(sql, instance, database, token, verbose): click.echo(json.dumps(response.json()["rows"], indent=2)) +@cli.command() +@click.option("--json", "_json", is_flag=True, help="Output raw JSON") +def instances(_json): + """ + List known instances from the config + + Example usage: + + \b + dclient instances + dclient instances --json + """ + config_file = get_config_dir() / "config.json" + config = _load_config(config_file) + inst_map = config.get("instances", {}) + default = config.get("default_instance") + if _json: + click.echo(json.dumps(config, indent=2)) + else: + for name, inst in inst_map.items(): + marker = "* " if name == default else " " + db_info = f" (db: {inst['default_database']})" if inst.get("default_database") else "" + click.echo(f"{marker}{name} = {inst['url']}{db_info}") + + # -- alias command group -- diff --git a/tests/test_commands_v2.py b/tests/test_commands_v2.py index 2077a45..afc4e7c 100644 --- a/tests/test_commands_v2.py +++ b/tests/test_commands_v2.py @@ -536,3 +536,57 @@ def test_get_command(httpx_mock, mocker, tmpdir): assert data == {"hello": "world"} request = httpx_mock.get_request() assert request.url.path == "/-/plugins.json" + + +# -- instances command -- + + +def test_instances_plain(mocker, tmpdir): + config_dir = pathlib.Path(tmpdir) + mocker.patch("dclient.cli.get_config_dir", return_value=config_dir) + (config_dir / "config.json").write_text( + json.dumps( + { + "default_instance": "prod", + "instances": { + "prod": {"url": "https://prod.example.com", "default_database": "main"}, + "staging": {"url": "https://staging.example.com", "default_database": None}, + }, + } + ) + ) + runner = CliRunner() + result = runner.invoke(cli, ["instances"]) + assert result.exit_code == 0 + assert "* prod = https://prod.example.com (db: main)" in result.output + assert " staging = https://staging.example.com" in result.output + + +def test_instances_json(mocker, tmpdir): + config_dir = pathlib.Path(tmpdir) + mocker.patch("dclient.cli.get_config_dir", return_value=config_dir) + (config_dir / "config.json").write_text( + json.dumps( + { + "default_instance": "prod", + "instances": { + "prod": {"url": "https://prod.example.com", "default_database": "main"}, + }, + } + ) + ) + runner = CliRunner() + result = runner.invoke(cli, ["instances", "--json"]) + assert result.exit_code == 0 + data = json.loads(result.output) + assert "prod" in data["instances"] + assert data["default_instance"] == "prod" + + +def test_instances_empty(mocker, tmpdir): + config_dir = pathlib.Path(tmpdir) + mocker.patch("dclient.cli.get_config_dir", return_value=config_dir) + runner = CliRunner() + result = runner.invoke(cli, ["instances"]) + assert result.exit_code == 0 + assert result.output.strip() == ""