mirror of
https://github.com/simonw/dclient.git
synced 2026-07-29 12:24:33 +02:00
dclient aliases command, closes #2
This commit is contained in:
parent
1ecb6a02e8
commit
6bc35f46c2
4 changed files with 156 additions and 11 deletions
73
README.md
73
README.md
|
|
@ -13,18 +13,33 @@ Install this tool using `pip`:
|
|||
|
||||
pip install dclient
|
||||
|
||||
## Usage
|
||||
## Running queries
|
||||
|
||||
For help, run:
|
||||
You can run SQL queries against a Datasette instance like so:
|
||||
|
||||
dclient --help
|
||||
|
||||
You can also use:
|
||||
|
||||
python -m dclient --help
|
||||
|
||||
### dclient query
|
||||
```
|
||||
$ dclient query https://latest.datasette.io/fixtures "select * from facetable limit 1"
|
||||
```
|
||||
Output:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"pk": 1,
|
||||
"created": "2019-01-14 08:00:00",
|
||||
"planet_int": 1,
|
||||
"on_earth": 1,
|
||||
"state": "CA",
|
||||
"_city_id": 1,
|
||||
"_neighborhood": "Mission",
|
||||
"tags": "[\"tag1\", \"tag2\"]",
|
||||
"complex_array": "[{\"foo\": \"bar\"}]",
|
||||
"distinct_some_null": "one",
|
||||
"n": "n1"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### dclient query --help
|
||||
<!-- [[[cog
|
||||
import cog
|
||||
from dclient import cli
|
||||
|
|
@ -49,6 +64,46 @@ Options:
|
|||
```
|
||||
<!-- [[[end]]] -->
|
||||
|
||||
## Aliases
|
||||
|
||||
You can assign an alias to a Datasette database using the `dclient alias` command:
|
||||
|
||||
dclient alias add content https://datasette.io/content
|
||||
|
||||
You can list aliases with `dclient alias list`:
|
||||
|
||||
$ dclient alias list
|
||||
content = https://datasette.io/content
|
||||
|
||||
Once registered, you can pass an alias to commands such as `dclient query`:
|
||||
|
||||
dclient query content "select * from news limit 1"
|
||||
|
||||
### dclient alias --help
|
||||
|
||||
<!-- [[[cog
|
||||
import cog
|
||||
result = runner.invoke(cli.cli, ["alias", "--help"])
|
||||
help = result.output.replace("Usage: cli", "Usage: dclient")
|
||||
cog.out(
|
||||
"```\n{}\n```".format(help)
|
||||
)
|
||||
]]] -->
|
||||
```
|
||||
Usage: dclient alias [OPTIONS] COMMAND [ARGS]...
|
||||
|
||||
Manage aliases for different instances
|
||||
|
||||
Options:
|
||||
--help Show this message and exit.
|
||||
|
||||
Commands:
|
||||
add Add an alias
|
||||
list List aliases
|
||||
|
||||
```
|
||||
<!-- [[[end]]] -->
|
||||
|
||||
## Development
|
||||
|
||||
To contribute to this tool, first checkout the code. Then create a new virtual environment:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
import click
|
||||
import httpx
|
||||
import json
|
||||
import appdirs
|
||||
import pathlib
|
||||
|
||||
|
||||
def get_config_dir():
|
||||
return pathlib.Path(appdirs.user_config_dir("dclient"))
|
||||
|
||||
|
||||
@click.group()
|
||||
|
|
@ -18,6 +24,10 @@ def query(url, sql):
|
|||
|
||||
Returns a JSON array of objects
|
||||
"""
|
||||
aliases_file = get_config_dir() / "aliases.json"
|
||||
aliases = _load_aliases(aliases_file)
|
||||
if url in aliases:
|
||||
url = aliases[url]
|
||||
if not url.endswith(".json"):
|
||||
url += ".json"
|
||||
response = httpx.get(url, params={"sql": sql, "_shape": "objects"})
|
||||
|
|
@ -31,3 +41,38 @@ def query(url, sql):
|
|||
raise click.ClickException(": ".join(bits))
|
||||
else:
|
||||
click.echo(json.dumps(response.json()["rows"], indent=2))
|
||||
|
||||
|
||||
@cli.group()
|
||||
def alias():
|
||||
"Manage aliases for different instances"
|
||||
|
||||
|
||||
@alias.command(name="list")
|
||||
def list_():
|
||||
"List aliases"
|
||||
aliases_file = get_config_dir() / "aliases.json"
|
||||
aliases = _load_aliases(aliases_file)
|
||||
for alias, url in aliases.items():
|
||||
click.echo(f"{alias} = {url}")
|
||||
|
||||
|
||||
@alias.command()
|
||||
@click.argument("name")
|
||||
@click.argument("url")
|
||||
def add(name, url):
|
||||
"Add an alias"
|
||||
config_dir = get_config_dir()
|
||||
config_dir.mkdir(parents=True, exist_ok=True)
|
||||
aliases_file = config_dir / "aliases.json"
|
||||
aliases = _load_aliases(aliases_file)
|
||||
aliases[name] = url
|
||||
aliases_file.write_text(json.dumps(aliases, indent=4))
|
||||
|
||||
|
||||
def _load_aliases(aliases_file):
|
||||
if aliases_file.exists():
|
||||
aliases = json.loads(aliases_file.read_text())
|
||||
else:
|
||||
aliases = {}
|
||||
return aliases
|
||||
|
|
|
|||
4
setup.py
4
setup.py
|
|
@ -31,7 +31,7 @@ setup(
|
|||
[console_scripts]
|
||||
dclient=dclient.cli:cli
|
||||
""",
|
||||
install_requires=["click", "httpx"],
|
||||
extras_require={"test": ["pytest", "pytest-httpx", "cogapp"]},
|
||||
install_requires=["click", "httpx", "appdirs"],
|
||||
extras_require={"test": ["pytest", "pytest-httpx", "cogapp", "pytest-mock"]},
|
||||
python_requires=">=3.7",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from click.testing import CliRunner
|
||||
from dclient.cli import cli
|
||||
import json
|
||||
import pathlib
|
||||
|
||||
|
||||
def test_query_error(httpx_mock):
|
||||
|
|
@ -39,3 +40,47 @@ def test_query(httpx_mock):
|
|||
result = runner.invoke(cli, ["query", "https://example.com", "hello"])
|
||||
assert result.exit_code == 0
|
||||
assert json.loads(result.output) == [{"5 * 2": 10}]
|
||||
|
||||
|
||||
def test_aliases(mocker, tmpdir, httpx_mock):
|
||||
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ["alias", "list"])
|
||||
assert result.exit_code == 0
|
||||
assert result.output == ""
|
||||
|
||||
result = runner.invoke(cli, ["alias", "add", "foo", "https://example.com/foo"])
|
||||
assert result.exit_code == 0
|
||||
assert result.output == ""
|
||||
|
||||
result = runner.invoke(cli, ["alias", "list"])
|
||||
assert result.exit_code == 0
|
||||
assert result.output == "foo = https://example.com/foo\n"
|
||||
|
||||
# Check the aliases file
|
||||
aliases_file = pathlib.Path(tmpdir) / "aliases.json"
|
||||
assert json.loads(aliases_file.read_text()) == {"foo": "https://example.com/foo"}
|
||||
|
||||
# Try a query against that alias
|
||||
httpx_mock.add_response(
|
||||
json={
|
||||
"ok": True,
|
||||
"database": "foo",
|
||||
"query_name": None,
|
||||
"rows": [{"11 * 3": 33}],
|
||||
"truncated": False,
|
||||
"columns": ["11 * 3"],
|
||||
"query": {"sql": "select 11 * 3", "params": {}},
|
||||
"error": None,
|
||||
"private": False,
|
||||
"allow_execute_sql": True,
|
||||
},
|
||||
status_code=200,
|
||||
)
|
||||
result = runner.invoke(cli, ["query", "foo", "select 11 * 3"])
|
||||
assert result.exit_code == 0
|
||||
assert json.loads(result.output) == [{"11 * 3": 33}]
|
||||
|
||||
# Should have hit https://example.com/foo.json
|
||||
url = httpx_mock.get_request().url
|
||||
assert url == "https://example.com/foo.json?sql=select+11+%2A+3&_shape=objects"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue