2020-11-30 13:29:57 -08:00
|
|
|
from datasette.app import Datasette
|
2020-06-05 16:55:08 -07:00
|
|
|
from datasette.plugins import DEFAULT_PLUGINS
|
2021-12-11 19:07:19 -08:00
|
|
|
from datasette.utils.sqlite import supports_table_xinfo
|
2020-10-28 20:38:15 -07:00
|
|
|
from datasette.version import __version__
|
2018-05-31 06:40:30 -07:00
|
|
|
from .fixtures import ( # noqa
|
2018-03-29 23:26:22 -07:00
|
|
|
app_client,
|
2019-03-14 16:42:38 -07:00
|
|
|
app_client_no_files,
|
2021-12-11 19:07:19 -08:00
|
|
|
app_client_with_dot,
|
2018-05-05 19:41:37 -03:00
|
|
|
app_client_shorter_time_limit,
|
2021-12-11 19:07:19 -08:00
|
|
|
app_client_two_attached_databases_one_immutable,
|
2018-06-04 09:02:07 -07:00
|
|
|
app_client_larger_cache_size,
|
2021-12-11 19:07:19 -08:00
|
|
|
app_client_with_cors,
|
2019-11-05 00:16:30 +01:00
|
|
|
app_client_two_attached_databases,
|
2019-10-18 15:51:07 -07:00
|
|
|
app_client_conflicting_database_names,
|
2020-02-25 15:19:29 -05:00
|
|
|
app_client_immutable_and_inspect_file,
|
2018-11-20 09:16:31 -08:00
|
|
|
make_app_client,
|
2020-06-05 16:46:37 -07:00
|
|
|
EXPECTED_PLUGINS,
|
2018-04-18 22:24:48 -07:00
|
|
|
METADATA,
|
2018-03-29 23:26:22 -07:00
|
|
|
)
|
2021-06-21 11:57:38 -04:00
|
|
|
import pathlib
|
2017-12-15 04:04:17 -08:00
|
|
|
import pytest
|
2019-12-04 22:46:39 -08:00
|
|
|
import sys
|
2018-05-23 06:41:14 -07:00
|
|
|
import urllib
|
2017-12-15 04:04:17 -08:00
|
|
|
|
|
|
|
|
|
2022-12-15 13:44:48 -08:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_homepage(ds_client):
|
|
|
|
|
response = await ds_client.get("/.json")
|
|
|
|
|
assert response.status_code == 200
|
2019-06-23 20:13:09 -07:00
|
|
|
assert "application/json; charset=utf-8" == response.headers["content-type"]
|
2022-12-15 13:44:48 -08:00
|
|
|
data = response.json()
|
|
|
|
|
assert data.keys() == {"fixtures": 0}.keys()
|
|
|
|
|
d = data["fixtures"]
|
2019-05-01 17:39:39 -07:00
|
|
|
assert d["name"] == "fixtures"
|
2021-07-10 12:03:19 -07:00
|
|
|
assert d["tables_count"] == 24
|
2019-05-15 17:28:07 -07:00
|
|
|
assert len(d["tables_and_views_truncated"]) == 5
|
|
|
|
|
assert d["tables_and_views_more"] is True
|
2019-05-01 17:54:48 -07:00
|
|
|
# 4 hidden FTS tables + no_primary_key (hidden in metadata)
|
2020-11-11 16:02:58 -08:00
|
|
|
assert d["hidden_tables_count"] == 6
|
|
|
|
|
# 201 in no_primary_key, plus 6 in other hidden tables:
|
2022-12-15 13:44:48 -08:00
|
|
|
assert d["hidden_table_rows_sum"] == 207, data
|
2019-05-01 17:39:39 -07:00
|
|
|
assert d["views_count"] == 4
|
2017-12-15 04:04:17 -08:00
|
|
|
|
|
|
|
|
|
2022-12-15 13:44:48 -08:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_homepage_sort_by_relationships(ds_client):
|
|
|
|
|
response = await ds_client.get("/.json?_sort=relationships")
|
|
|
|
|
assert response.status_code == 200
|
2019-05-15 20:23:33 -07:00
|
|
|
tables = [
|
2022-12-15 13:44:48 -08:00
|
|
|
t["name"] for t in response.json()["fixtures"]["tables_and_views_truncated"]
|
2019-05-15 20:23:33 -07:00
|
|
|
]
|
2020-11-11 15:37:37 -08:00
|
|
|
assert tables == [
|
2019-05-15 20:23:33 -07:00
|
|
|
"simple_primary_key",
|
2020-11-11 15:37:37 -08:00
|
|
|
"foreign_key_references",
|
2019-05-15 20:23:33 -07:00
|
|
|
"complex_foreign_keys",
|
2019-05-22 22:44:34 -07:00
|
|
|
"roadside_attraction_characteristics",
|
2019-05-15 20:23:33 -07:00
|
|
|
"searchable_tags",
|
2020-11-11 15:37:37 -08:00
|
|
|
]
|
2019-05-15 20:23:33 -07:00
|
|
|
|
|
|
|
|
|
2022-12-15 13:44:48 -08:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_database_page(ds_client):
|
|
|
|
|
response = await ds_client.get("/fixtures.json")
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.json()
|
2020-11-11 15:37:37 -08:00
|
|
|
assert data["database"] == "fixtures"
|
|
|
|
|
assert data["tables"] == [
|
2017-12-15 04:04:17 -08:00
|
|
|
{
|
|
|
|
|
"name": "123_starts_with_digits",
|
2019-05-22 22:44:34 -07:00
|
|
|
"columns": ["content"],
|
|
|
|
|
"primary_keys": [],
|
2019-05-03 12:43:59 -04:00
|
|
|
"count": 0,
|
|
|
|
|
"hidden": False,
|
2017-12-15 04:04:17 -08:00
|
|
|
"fts_table": None,
|
2019-05-22 22:44:34 -07:00
|
|
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2017-12-15 04:04:17 -08:00
|
|
|
},
|
|
|
|
|
{
|
2018-04-28 17:04:32 -07:00
|
|
|
"name": "Table With Space In Name",
|
2019-05-22 22:44:34 -07:00
|
|
|
"columns": ["pk", "content"],
|
|
|
|
|
"primary_keys": ["pk"],
|
2018-03-29 23:26:22 -07:00
|
|
|
"count": 0,
|
2018-03-29 22:10:09 -07:00
|
|
|
"hidden": False,
|
2018-04-28 17:04:32 -07:00
|
|
|
"fts_table": None,
|
2019-05-22 22:44:34 -07:00
|
|
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2019-05-22 22:44:34 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "attraction_characteristic",
|
|
|
|
|
"columns": ["pk", "name"],
|
2018-04-22 13:46:18 -07:00
|
|
|
"primary_keys": ["pk"],
|
2019-05-22 22:44:34 -07:00
|
|
|
"count": 2,
|
|
|
|
|
"hidden": False,
|
|
|
|
|
"fts_table": None,
|
|
|
|
|
"foreign_keys": {
|
|
|
|
|
"incoming": [
|
|
|
|
|
{
|
|
|
|
|
"other_table": "roadside_attraction_characteristics",
|
|
|
|
|
"column": "pk",
|
|
|
|
|
"other_column": "characteristic_id",
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
"outgoing": [],
|
|
|
|
|
},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2018-04-22 10:51:43 -07:00
|
|
|
},
|
2019-05-03 22:15:14 -04:00
|
|
|
{
|
2019-05-22 22:44:34 -07:00
|
|
|
"name": "binary_data",
|
2018-06-21 07:56:28 -07:00
|
|
|
"columns": ["data"],
|
2019-05-22 22:44:34 -07:00
|
|
|
"primary_keys": [],
|
2020-10-28 21:05:40 -07:00
|
|
|
"count": 3,
|
2018-05-15 10:52:02 -05:00
|
|
|
"hidden": False,
|
2019-05-22 22:44:34 -07:00
|
|
|
"fts_table": None,
|
|
|
|
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2018-05-15 10:52:02 -05:00
|
|
|
},
|
|
|
|
|
{
|
2019-04-10 08:17:19 -07:00
|
|
|
"name": "complex_foreign_keys",
|
2019-05-22 22:44:34 -07:00
|
|
|
"columns": ["pk", "f1", "f2", "f3"],
|
|
|
|
|
"primary_keys": ["pk"],
|
2018-05-14 18:33:24 -03:00
|
|
|
"count": 1,
|
2019-05-22 22:44:34 -07:00
|
|
|
"hidden": False,
|
|
|
|
|
"fts_table": None,
|
2018-05-15 10:52:02 -05:00
|
|
|
"foreign_keys": {
|
|
|
|
|
"incoming": [],
|
|
|
|
|
"outgoing": [
|
2019-05-03 22:15:14 -04:00
|
|
|
{
|
2019-05-22 22:44:34 -07:00
|
|
|
"other_table": "simple_primary_key",
|
2018-06-21 07:56:28 -07:00
|
|
|
"column": "f3",
|
|
|
|
|
"other_column": "id",
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
2018-05-15 10:52:02 -05:00
|
|
|
{
|
2019-05-22 22:44:34 -07:00
|
|
|
"other_table": "simple_primary_key",
|
2018-06-21 07:56:28 -07:00
|
|
|
"column": "f2",
|
2018-05-15 10:52:02 -05:00
|
|
|
"other_column": "id",
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
|
|
|
|
{
|
2019-05-22 22:44:34 -07:00
|
|
|
"other_table": "simple_primary_key",
|
2018-06-21 07:56:28 -07:00
|
|
|
"column": "f1",
|
2018-05-15 10:52:02 -05:00
|
|
|
"other_column": "id",
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2018-05-15 10:52:02 -05:00
|
|
|
},
|
2018-04-22 13:46:18 -07:00
|
|
|
{
|
2018-04-14 07:55:27 -07:00
|
|
|
"name": "compound_primary_key",
|
2019-05-22 22:44:34 -07:00
|
|
|
"columns": ["pk1", "pk2", "content"],
|
|
|
|
|
"primary_keys": ["pk1", "pk2"],
|
2022-03-07 07:38:29 -08:00
|
|
|
"count": 2,
|
2018-04-14 07:55:27 -07:00
|
|
|
"hidden": False,
|
|
|
|
|
"fts_table": None,
|
2019-05-22 22:44:34 -07:00
|
|
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2018-04-14 07:55:27 -07:00
|
|
|
},
|
2018-07-23 20:07:57 -07:00
|
|
|
{
|
|
|
|
|
"name": "compound_three_primary_keys",
|
2019-05-22 22:44:34 -07:00
|
|
|
"columns": ["pk1", "pk2", "pk3", "content"],
|
|
|
|
|
"primary_keys": ["pk1", "pk2", "pk3"],
|
2018-05-15 06:50:27 -03:00
|
|
|
"count": 1001,
|
2018-07-23 20:07:57 -07:00
|
|
|
"hidden": False,
|
2018-04-14 07:55:27 -07:00
|
|
|
"fts_table": None,
|
2019-05-22 22:44:34 -07:00
|
|
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
|
|
|
|
{
|
2018-04-14 07:55:27 -07:00
|
|
|
"name": "custom_foreign_key_label",
|
2019-05-22 22:44:34 -07:00
|
|
|
"columns": ["pk", "foreign_key_with_custom_label"],
|
|
|
|
|
"primary_keys": ["pk"],
|
2018-04-14 07:55:27 -07:00
|
|
|
"count": 1,
|
2018-06-21 07:56:28 -07:00
|
|
|
"hidden": False,
|
2019-05-22 22:44:34 -07:00
|
|
|
"fts_table": None,
|
2018-04-14 07:55:27 -07:00
|
|
|
"foreign_keys": {
|
|
|
|
|
"incoming": [],
|
2018-06-21 07:56:28 -07:00
|
|
|
"outgoing": [
|
2018-04-14 07:55:27 -07:00
|
|
|
{
|
2019-05-22 22:44:34 -07:00
|
|
|
"other_table": "primary_key_multiple_columns_explicit_label",
|
2018-04-14 07:55:27 -07:00
|
|
|
"column": "foreign_key_with_custom_label",
|
|
|
|
|
"other_column": "id",
|
2018-04-22 13:46:18 -07:00
|
|
|
}
|
|
|
|
|
],
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2018-04-22 13:46:18 -07:00
|
|
|
},
|
2019-05-03 22:15:14 -04:00
|
|
|
{
|
2018-04-22 13:46:18 -07:00
|
|
|
"name": "facet_cities",
|
2019-05-22 22:44:34 -07:00
|
|
|
"columns": ["id", "name"],
|
|
|
|
|
"primary_keys": ["id"],
|
2018-05-15 06:50:27 -03:00
|
|
|
"count": 4,
|
2019-05-22 22:44:34 -07:00
|
|
|
"hidden": False,
|
|
|
|
|
"fts_table": None,
|
2018-06-21 07:56:28 -07:00
|
|
|
"foreign_keys": {
|
|
|
|
|
"incoming": [
|
2019-05-03 22:15:14 -04:00
|
|
|
{
|
2019-05-22 22:44:34 -07:00
|
|
|
"other_table": "facetable",
|
2018-06-21 07:56:28 -07:00
|
|
|
"column": "id",
|
2021-11-29 22:17:27 -08:00
|
|
|
"other_column": "_city_id",
|
2019-05-03 22:15:14 -04:00
|
|
|
}
|
|
|
|
|
],
|
2018-06-21 07:56:28 -07:00
|
|
|
"outgoing": [],
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2018-04-14 07:55:27 -07:00
|
|
|
},
|
2018-05-05 19:01:14 -03:00
|
|
|
{
|
2019-05-22 22:44:34 -07:00
|
|
|
"name": "facetable",
|
2018-06-21 07:56:28 -07:00
|
|
|
"columns": [
|
|
|
|
|
"pk",
|
2019-05-20 23:09:22 -07:00
|
|
|
"created",
|
2018-06-21 07:56:28 -07:00
|
|
|
"planet_int",
|
|
|
|
|
"on_earth",
|
|
|
|
|
"state",
|
2021-11-29 22:17:27 -08:00
|
|
|
"_city_id",
|
2021-11-13 20:44:54 -08:00
|
|
|
"_neighborhood",
|
2018-06-21 07:56:28 -07:00
|
|
|
"tags",
|
2019-11-01 12:37:46 -07:00
|
|
|
"complex_array",
|
2019-11-21 16:56:55 -08:00
|
|
|
"distinct_some_null",
|
2022-03-18 18:37:54 -07:00
|
|
|
"n",
|
2018-06-21 07:56:28 -07:00
|
|
|
],
|
2019-05-22 22:44:34 -07:00
|
|
|
"primary_keys": ["pk"],
|
2018-05-15 06:50:27 -03:00
|
|
|
"count": 15,
|
2019-05-22 22:44:34 -07:00
|
|
|
"hidden": False,
|
|
|
|
|
"fts_table": None,
|
2018-06-21 07:56:28 -07:00
|
|
|
"foreign_keys": {
|
|
|
|
|
"incoming": [],
|
|
|
|
|
"outgoing": [
|
2019-05-03 22:15:14 -04:00
|
|
|
{
|
2019-05-22 22:44:34 -07:00
|
|
|
"other_table": "facet_cities",
|
2021-11-29 22:17:27 -08:00
|
|
|
"column": "_city_id",
|
2018-06-21 07:56:28 -07:00
|
|
|
"other_column": "id",
|
2019-05-03 22:15:14 -04:00
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
|
|
|
|
{
|
2018-04-14 07:55:27 -07:00
|
|
|
"name": "foreign_key_references",
|
2020-11-11 15:37:37 -08:00
|
|
|
"columns": [
|
|
|
|
|
"pk",
|
|
|
|
|
"foreign_key_with_label",
|
|
|
|
|
"foreign_key_with_blank_label",
|
|
|
|
|
"foreign_key_with_no_label",
|
2020-11-29 11:30:17 -08:00
|
|
|
"foreign_key_compound_pk1",
|
|
|
|
|
"foreign_key_compound_pk2",
|
2020-11-11 15:37:37 -08:00
|
|
|
],
|
2019-05-22 22:44:34 -07:00
|
|
|
"primary_keys": ["pk"],
|
2019-11-02 15:29:40 -07:00
|
|
|
"count": 2,
|
2018-06-21 07:56:28 -07:00
|
|
|
"hidden": False,
|
2019-05-22 22:44:34 -07:00
|
|
|
"fts_table": None,
|
2018-06-21 07:56:28 -07:00
|
|
|
"foreign_keys": {
|
|
|
|
|
"incoming": [],
|
|
|
|
|
"outgoing": [
|
2019-05-03 22:15:14 -04:00
|
|
|
{
|
2019-05-22 22:44:34 -07:00
|
|
|
"other_table": "primary_key_multiple_columns",
|
?_labels= and ?_label=COL to expand foreign keys in JSON/CSV
These new querystring arguments can be used to request expanded foreign keys
in both JSON and CSV formats.
?_labels=on turns on expansions for ALL foreign key columns
?_label=COLUMN1&_label=COLUMN2 can be used to pick specific columns to expand
e.g. `Street_Tree_List.json?_label=qSpecies&_label=qLegalStatus`
{
"rowid": 233,
"TreeID": 121240,
"qLegalStatus": {
"value" 2,
"label": "Private"
}
"qSpecies": {
"value": 16,
"label": "Sycamore"
}
"qAddress": "91 Commonwealth Ave",
...
}
The labels option also works for the HTML and CSV views.
HTML defaults to `?_labels=on`, so if you pass `?_labels=off` you can disable
foreign key expansion entirely - or you can use `?_label=COLUMN` to request
just specific columns.
If you expand labels on CSV you get additional columns in the output:
`/Street_Tree_List.csv?_label=qLegalStatus`
rowid,TreeID,qLegalStatus,qLegalStatus_label...
1,141565,1,Permitted Site...
2,232565,2,Undocumented...
I also refactored the existing foreign key expansion code.
Closes #233. Refs #266.
2018-06-16 15:18:57 -07:00
|
|
|
"column": "foreign_key_with_no_label",
|
2018-06-21 07:56:28 -07:00
|
|
|
"other_column": "id",
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
2020-11-11 15:37:37 -08:00
|
|
|
{
|
|
|
|
|
"other_table": "simple_primary_key",
|
|
|
|
|
"column": "foreign_key_with_blank_label",
|
|
|
|
|
"other_column": "id",
|
|
|
|
|
},
|
2019-05-03 22:15:14 -04:00
|
|
|
{
|
2019-05-22 22:44:34 -07:00
|
|
|
"other_table": "simple_primary_key",
|
2018-04-22 10:51:43 -07:00
|
|
|
"column": "foreign_key_with_label",
|
2018-06-21 07:56:28 -07:00
|
|
|
"other_column": "id",
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2018-06-21 07:56:28 -07:00
|
|
|
},
|
2021-07-10 12:03:19 -07:00
|
|
|
] + [
|
2018-04-03 06:39:50 -07:00
|
|
|
{
|
|
|
|
|
"name": "infinity",
|
2018-04-14 07:55:27 -07:00
|
|
|
"columns": ["value"],
|
2017-12-15 04:04:17 -08:00
|
|
|
"primary_keys": [],
|
2019-05-22 22:44:34 -07:00
|
|
|
"count": 3,
|
2017-12-15 04:04:17 -08:00
|
|
|
"hidden": False,
|
|
|
|
|
"fts_table": None,
|
|
|
|
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2017-12-15 04:04:17 -08:00
|
|
|
},
|
2019-05-03 22:15:14 -04:00
|
|
|
{
|
2018-04-14 07:55:27 -07:00
|
|
|
"name": "primary_key_multiple_columns",
|
2019-05-22 22:44:34 -07:00
|
|
|
"columns": ["id", "content", "content2"],
|
|
|
|
|
"primary_keys": ["id"],
|
2018-05-15 06:50:27 -03:00
|
|
|
"count": 1,
|
2019-05-22 22:44:34 -07:00
|
|
|
"hidden": False,
|
|
|
|
|
"fts_table": None,
|
?_labels= and ?_label=COL to expand foreign keys in JSON/CSV
These new querystring arguments can be used to request expanded foreign keys
in both JSON and CSV formats.
?_labels=on turns on expansions for ALL foreign key columns
?_label=COLUMN1&_label=COLUMN2 can be used to pick specific columns to expand
e.g. `Street_Tree_List.json?_label=qSpecies&_label=qLegalStatus`
{
"rowid": 233,
"TreeID": 121240,
"qLegalStatus": {
"value" 2,
"label": "Private"
}
"qSpecies": {
"value": 16,
"label": "Sycamore"
}
"qAddress": "91 Commonwealth Ave",
...
}
The labels option also works for the HTML and CSV views.
HTML defaults to `?_labels=on`, so if you pass `?_labels=off` you can disable
foreign key expansion entirely - or you can use `?_label=COLUMN` to request
just specific columns.
If you expand labels on CSV you get additional columns in the output:
`/Street_Tree_List.csv?_label=qLegalStatus`
rowid,TreeID,qLegalStatus,qLegalStatus_label...
1,141565,1,Permitted Site...
2,232565,2,Undocumented...
I also refactored the existing foreign key expansion code.
Closes #233. Refs #266.
2018-06-16 15:18:57 -07:00
|
|
|
"foreign_keys": {
|
2018-06-21 07:56:28 -07:00
|
|
|
"incoming": [
|
2019-05-03 22:15:14 -04:00
|
|
|
{
|
2019-05-22 22:44:34 -07:00
|
|
|
"other_table": "foreign_key_references",
|
2018-06-21 07:56:28 -07:00
|
|
|
"column": "id",
|
2018-04-22 10:51:43 -07:00
|
|
|
"other_column": "foreign_key_with_no_label",
|
2019-05-03 22:15:14 -04:00
|
|
|
}
|
|
|
|
|
],
|
2018-06-21 07:56:28 -07:00
|
|
|
"outgoing": [],
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2018-04-08 17:06:10 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "primary_key_multiple_columns_explicit_label",
|
2019-05-22 22:44:34 -07:00
|
|
|
"columns": ["id", "content", "content2"],
|
|
|
|
|
"primary_keys": ["id"],
|
2018-04-08 17:06:10 -07:00
|
|
|
"count": 1,
|
2019-05-22 22:44:34 -07:00
|
|
|
"hidden": False,
|
|
|
|
|
"fts_table": None,
|
2018-04-08 17:06:10 -07:00
|
|
|
"foreign_keys": {
|
2018-06-21 07:56:28 -07:00
|
|
|
"incoming": [
|
2019-05-03 22:15:14 -04:00
|
|
|
{
|
2019-05-22 22:44:34 -07:00
|
|
|
"other_table": "custom_foreign_key_label",
|
2018-06-21 07:56:28 -07:00
|
|
|
"column": "id",
|
2018-04-08 17:06:10 -07:00
|
|
|
"other_column": "foreign_key_with_custom_label",
|
2019-05-03 22:15:14 -04:00
|
|
|
}
|
|
|
|
|
],
|
2018-06-21 07:56:28 -07:00
|
|
|
"outgoing": [],
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2019-05-22 22:44:34 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "roadside_attraction_characteristics",
|
|
|
|
|
"columns": ["attraction_id", "characteristic_id"],
|
|
|
|
|
"primary_keys": [],
|
|
|
|
|
"count": 5,
|
2018-04-08 17:06:10 -07:00
|
|
|
"hidden": False,
|
|
|
|
|
"fts_table": None,
|
2019-05-22 22:44:34 -07:00
|
|
|
"foreign_keys": {
|
|
|
|
|
"incoming": [],
|
|
|
|
|
"outgoing": [
|
|
|
|
|
{
|
|
|
|
|
"other_table": "attraction_characteristic",
|
|
|
|
|
"column": "characteristic_id",
|
|
|
|
|
"other_column": "pk",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"other_table": "roadside_attractions",
|
|
|
|
|
"column": "attraction_id",
|
|
|
|
|
"other_column": "pk",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2019-05-22 22:44:34 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "roadside_attractions",
|
2022-09-06 16:50:43 -07:00
|
|
|
"columns": ["pk", "name", "address", "url", "latitude", "longitude"],
|
2019-05-22 22:44:34 -07:00
|
|
|
"primary_keys": ["pk"],
|
|
|
|
|
"count": 4,
|
|
|
|
|
"hidden": False,
|
|
|
|
|
"fts_table": None,
|
|
|
|
|
"foreign_keys": {
|
|
|
|
|
"incoming": [
|
|
|
|
|
{
|
|
|
|
|
"other_table": "roadside_attraction_characteristics",
|
|
|
|
|
"column": "pk",
|
|
|
|
|
"other_column": "attraction_id",
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
"outgoing": [],
|
|
|
|
|
},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
|
|
|
|
{
|
2018-06-21 07:56:28 -07:00
|
|
|
"name": "searchable",
|
2019-05-22 22:44:34 -07:00
|
|
|
"columns": ["pk", "text1", "text2", "name with . and spaces"],
|
|
|
|
|
"primary_keys": ["pk"],
|
2018-04-08 17:06:10 -07:00
|
|
|
"count": 2,
|
2019-05-22 22:44:34 -07:00
|
|
|
"hidden": False,
|
|
|
|
|
"fts_table": "searchable_fts",
|
?_labels= and ?_label=COL to expand foreign keys in JSON/CSV
These new querystring arguments can be used to request expanded foreign keys
in both JSON and CSV formats.
?_labels=on turns on expansions for ALL foreign key columns
?_label=COLUMN1&_label=COLUMN2 can be used to pick specific columns to expand
e.g. `Street_Tree_List.json?_label=qSpecies&_label=qLegalStatus`
{
"rowid": 233,
"TreeID": 121240,
"qLegalStatus": {
"value" 2,
"label": "Private"
}
"qSpecies": {
"value": 16,
"label": "Sycamore"
}
"qAddress": "91 Commonwealth Ave",
...
}
The labels option also works for the HTML and CSV views.
HTML defaults to `?_labels=on`, so if you pass `?_labels=off` you can disable
foreign key expansion entirely - or you can use `?_label=COLUMN` to request
just specific columns.
If you expand labels on CSV you get additional columns in the output:
`/Street_Tree_List.csv?_label=qLegalStatus`
rowid,TreeID,qLegalStatus,qLegalStatus_label...
1,141565,1,Permitted Site...
2,232565,2,Undocumented...
I also refactored the existing foreign key expansion code.
Closes #233. Refs #266.
2018-06-16 15:18:57 -07:00
|
|
|
"foreign_keys": {
|
2018-06-21 07:56:28 -07:00
|
|
|
"incoming": [
|
2019-05-03 22:15:14 -04:00
|
|
|
{
|
2018-04-08 17:06:10 -07:00
|
|
|
"other_table": "searchable_tags",
|
|
|
|
|
"column": "pk",
|
2018-04-16 18:41:17 -07:00
|
|
|
"other_column": "searchable_id",
|
2019-05-03 22:15:14 -04:00
|
|
|
}
|
|
|
|
|
],
|
2018-06-21 07:56:28 -07:00
|
|
|
"outgoing": [],
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
|
|
|
|
{
|
2018-06-21 07:56:28 -07:00
|
|
|
"name": "searchable_tags",
|
|
|
|
|
"columns": ["searchable_id", "tag"],
|
|
|
|
|
"primary_keys": ["searchable_id", "tag"],
|
|
|
|
|
"count": 2,
|
|
|
|
|
"hidden": False,
|
|
|
|
|
"fts_table": None,
|
?_labels= and ?_label=COL to expand foreign keys in JSON/CSV
These new querystring arguments can be used to request expanded foreign keys
in both JSON and CSV formats.
?_labels=on turns on expansions for ALL foreign key columns
?_label=COLUMN1&_label=COLUMN2 can be used to pick specific columns to expand
e.g. `Street_Tree_List.json?_label=qSpecies&_label=qLegalStatus`
{
"rowid": 233,
"TreeID": 121240,
"qLegalStatus": {
"value" 2,
"label": "Private"
}
"qSpecies": {
"value": 16,
"label": "Sycamore"
}
"qAddress": "91 Commonwealth Ave",
...
}
The labels option also works for the HTML and CSV views.
HTML defaults to `?_labels=on`, so if you pass `?_labels=off` you can disable
foreign key expansion entirely - or you can use `?_label=COLUMN` to request
just specific columns.
If you expand labels on CSV you get additional columns in the output:
`/Street_Tree_List.csv?_label=qLegalStatus`
rowid,TreeID,qLegalStatus,qLegalStatus_label...
1,141565,1,Permitted Site...
2,232565,2,Undocumented...
I also refactored the existing foreign key expansion code.
Closes #233. Refs #266.
2018-06-16 15:18:57 -07:00
|
|
|
"foreign_keys": {
|
2018-06-21 07:56:28 -07:00
|
|
|
"incoming": [],
|
|
|
|
|
"outgoing": [
|
2019-05-01 22:09:03 -07:00
|
|
|
{"other_table": "tags", "column": "tag", "other_column": "tag"},
|
2019-05-03 22:15:14 -04:00
|
|
|
{
|
2018-06-21 07:56:28 -07:00
|
|
|
"other_table": "searchable",
|
|
|
|
|
"column": "searchable_id",
|
|
|
|
|
"other_column": "pk",
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
|
|
|
|
{
|
2019-05-01 22:09:03 -07:00
|
|
|
"name": "select",
|
2019-05-22 22:44:34 -07:00
|
|
|
"columns": ["group", "having", "and", "json"],
|
|
|
|
|
"primary_keys": [],
|
2018-05-15 06:50:27 -03:00
|
|
|
"count": 1,
|
2018-06-21 07:56:28 -07:00
|
|
|
"hidden": False,
|
2018-04-28 17:04:32 -07:00
|
|
|
"fts_table": None,
|
2019-05-22 22:44:34 -07:00
|
|
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2017-12-15 04:04:17 -08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "simple_primary_key",
|
2019-05-22 22:44:34 -07:00
|
|
|
"columns": ["id", "content"],
|
|
|
|
|
"primary_keys": ["id"],
|
2021-08-08 16:04:42 -07:00
|
|
|
"count": 5,
|
2017-12-15 04:04:17 -08:00
|
|
|
"hidden": False,
|
2019-05-22 22:44:34 -07:00
|
|
|
"fts_table": None,
|
?_labels= and ?_label=COL to expand foreign keys in JSON/CSV
These new querystring arguments can be used to request expanded foreign keys
in both JSON and CSV formats.
?_labels=on turns on expansions for ALL foreign key columns
?_label=COLUMN1&_label=COLUMN2 can be used to pick specific columns to expand
e.g. `Street_Tree_List.json?_label=qSpecies&_label=qLegalStatus`
{
"rowid": 233,
"TreeID": 121240,
"qLegalStatus": {
"value" 2,
"label": "Private"
}
"qSpecies": {
"value": 16,
"label": "Sycamore"
}
"qAddress": "91 Commonwealth Ave",
...
}
The labels option also works for the HTML and CSV views.
HTML defaults to `?_labels=on`, so if you pass `?_labels=off` you can disable
foreign key expansion entirely - or you can use `?_label=COLUMN` to request
just specific columns.
If you expand labels on CSV you get additional columns in the output:
`/Street_Tree_List.csv?_label=qLegalStatus`
rowid,TreeID,qLegalStatus,qLegalStatus_label...
1,141565,1,Permitted Site...
2,232565,2,Undocumented...
I also refactored the existing foreign key expansion code.
Closes #233. Refs #266.
2018-06-16 15:18:57 -07:00
|
|
|
"foreign_keys": {
|
2018-06-21 07:56:28 -07:00
|
|
|
"incoming": [
|
2020-11-11 15:37:37 -08:00
|
|
|
{
|
|
|
|
|
"other_table": "foreign_key_references",
|
|
|
|
|
"column": "id",
|
|
|
|
|
"other_column": "foreign_key_with_blank_label",
|
|
|
|
|
},
|
2019-05-03 22:15:14 -04:00
|
|
|
{
|
2019-05-22 22:44:34 -07:00
|
|
|
"other_table": "foreign_key_references",
|
2018-06-21 07:56:28 -07:00
|
|
|
"column": "id",
|
2018-04-14 07:55:27 -07:00
|
|
|
"other_column": "foreign_key_with_label",
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
|
|
|
|
{
|
2019-05-22 22:44:34 -07:00
|
|
|
"other_table": "complex_foreign_keys",
|
2018-06-21 07:56:28 -07:00
|
|
|
"column": "id",
|
|
|
|
|
"other_column": "f3",
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
|
|
|
|
{
|
2019-05-22 22:44:34 -07:00
|
|
|
"other_table": "complex_foreign_keys",
|
2018-06-21 07:56:28 -07:00
|
|
|
"column": "id",
|
|
|
|
|
"other_column": "f2",
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
|
|
|
|
{
|
2019-05-22 22:44:34 -07:00
|
|
|
"other_table": "complex_foreign_keys",
|
2018-06-21 07:56:28 -07:00
|
|
|
"column": "id",
|
|
|
|
|
"other_column": "f1",
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
|
|
|
|
],
|
2018-06-21 07:56:28 -07:00
|
|
|
"outgoing": [],
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
|
|
|
|
{
|
2019-05-22 22:44:34 -07:00
|
|
|
"name": "sortable",
|
2018-06-21 07:56:28 -07:00
|
|
|
"columns": [
|
2019-05-03 22:15:14 -04:00
|
|
|
"pk1",
|
|
|
|
|
"pk2",
|
|
|
|
|
"content",
|
|
|
|
|
"sortable",
|
2018-04-16 18:41:17 -07:00
|
|
|
"sortable_with_nulls",
|
|
|
|
|
"sortable_with_nulls_2",
|
2019-05-03 22:15:14 -04:00
|
|
|
"text",
|
|
|
|
|
],
|
2019-05-22 22:44:34 -07:00
|
|
|
"primary_keys": ["pk1", "pk2"],
|
2018-06-21 07:56:28 -07:00
|
|
|
"count": 201,
|
|
|
|
|
"hidden": False,
|
2018-04-28 17:04:32 -07:00
|
|
|
"fts_table": None,
|
2019-05-22 22:44:34 -07:00
|
|
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2018-06-21 07:56:28 -07:00
|
|
|
},
|
|
|
|
|
{
|
2017-12-15 04:04:17 -08:00
|
|
|
"name": "table/with/slashes.csv",
|
2019-05-22 22:44:34 -07:00
|
|
|
"columns": ["pk", "content"],
|
|
|
|
|
"primary_keys": ["pk"],
|
2018-05-15 06:50:27 -03:00
|
|
|
"count": 1,
|
2018-06-21 07:56:28 -07:00
|
|
|
"hidden": False,
|
|
|
|
|
"fts_table": None,
|
2019-05-22 22:44:34 -07:00
|
|
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
|
|
|
|
{
|
2018-06-21 07:56:28 -07:00
|
|
|
"name": "tags",
|
|
|
|
|
"columns": ["tag"],
|
|
|
|
|
"primary_keys": ["tag"],
|
|
|
|
|
"count": 2,
|
|
|
|
|
"hidden": False,
|
|
|
|
|
"fts_table": None,
|
|
|
|
|
"foreign_keys": {
|
|
|
|
|
"incoming": [
|
|
|
|
|
{
|
|
|
|
|
"other_table": "searchable_tags",
|
|
|
|
|
"column": "tag",
|
|
|
|
|
"other_column": "tag",
|
|
|
|
|
}
|
2019-05-03 22:15:14 -04:00
|
|
|
],
|
2018-06-21 07:56:28 -07:00
|
|
|
"outgoing": [],
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
|
|
|
|
{
|
2018-07-23 20:07:57 -07:00
|
|
|
"name": "units",
|
2019-05-22 22:44:34 -07:00
|
|
|
"columns": ["pk", "distance", "frequency"],
|
|
|
|
|
"primary_keys": ["pk"],
|
2018-07-23 20:07:57 -07:00
|
|
|
"count": 3,
|
2018-06-21 07:56:28 -07:00
|
|
|
"hidden": False,
|
|
|
|
|
"fts_table": None,
|
2019-05-22 22:44:34 -07:00
|
|
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
|
|
|
|
{
|
2018-04-25 20:42:57 -07:00
|
|
|
"name": "no_primary_key",
|
2019-05-22 22:44:34 -07:00
|
|
|
"columns": ["content", "a", "b", "c"],
|
|
|
|
|
"primary_keys": [],
|
2018-06-21 07:56:28 -07:00
|
|
|
"count": 201,
|
Extract facet code out into a new plugin hook, closes #427 (#445)
Datasette previously only supported one type of faceting: exact column value counting.
With this change, faceting logic is extracted out into one or more separate classes which can implement other patterns of faceting - this is discussed in #427, but potential upcoming facet types include facet-by-date, facet-by-JSON-array, facet-by-many-2-many and more.
A new plugin hook, register_facet_classes, can be used by plugins to add in additional facet classes.
Each class must implement two methods: suggest(), which scans columns in the table to decide if they might be worth suggesting for faceting, and facet_results(), which executes the facet operation and returns results ready to be displayed in the UI.
2019-05-02 17:11:26 -07:00
|
|
|
"hidden": True,
|
2018-06-21 07:56:28 -07:00
|
|
|
"fts_table": None,
|
2019-05-22 22:44:34 -07:00
|
|
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
|
|
|
|
{
|
2019-05-22 22:44:34 -07:00
|
|
|
"name": "searchable_fts",
|
2020-11-30 13:29:57 -08:00
|
|
|
"columns": [
|
|
|
|
|
"text1",
|
|
|
|
|
"text2",
|
|
|
|
|
"name with . and spaces",
|
|
|
|
|
]
|
|
|
|
|
+ (
|
|
|
|
|
[
|
|
|
|
|
"searchable_fts",
|
|
|
|
|
"docid",
|
|
|
|
|
"__langid",
|
|
|
|
|
]
|
2020-11-30 16:28:02 -08:00
|
|
|
if supports_table_xinfo()
|
2020-11-30 13:29:57 -08:00
|
|
|
else []
|
|
|
|
|
),
|
2019-05-22 22:44:34 -07:00
|
|
|
"primary_keys": [],
|
2018-06-21 07:56:28 -07:00
|
|
|
"count": 2,
|
Extract facet code out into a new plugin hook, closes #427 (#445)
Datasette previously only supported one type of faceting: exact column value counting.
With this change, faceting logic is extracted out into one or more separate classes which can implement other patterns of faceting - this is discussed in #427, but potential upcoming facet types include facet-by-date, facet-by-JSON-array, facet-by-many-2-many and more.
A new plugin hook, register_facet_classes, can be used by plugins to add in additional facet classes.
Each class must implement two methods: suggest(), which scans columns in the table to decide if they might be worth suggesting for faceting, and facet_results(), which executes the facet operation and returns results ready to be displayed in the UI.
2019-05-02 17:11:26 -07:00
|
|
|
"hidden": True,
|
2019-05-22 22:44:34 -07:00
|
|
|
"fts_table": "searchable_fts",
|
|
|
|
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
|
|
|
|
{
|
2020-11-11 16:02:58 -08:00
|
|
|
"name": "searchable_fts_docsize",
|
|
|
|
|
"columns": ["docid", "size"],
|
2019-05-22 22:44:34 -07:00
|
|
|
"primary_keys": ["docid"],
|
2018-06-21 07:56:28 -07:00
|
|
|
"count": 2,
|
Extract facet code out into a new plugin hook, closes #427 (#445)
Datasette previously only supported one type of faceting: exact column value counting.
With this change, faceting logic is extracted out into one or more separate classes which can implement other patterns of faceting - this is discussed in #427, but potential upcoming facet types include facet-by-date, facet-by-JSON-array, facet-by-many-2-many and more.
A new plugin hook, register_facet_classes, can be used by plugins to add in additional facet classes.
Each class must implement two methods: suggest(), which scans columns in the table to decide if they might be worth suggesting for faceting, and facet_results(), which executes the facet operation and returns results ready to be displayed in the UI.
2019-05-02 17:11:26 -07:00
|
|
|
"hidden": True,
|
2019-05-22 22:44:34 -07:00
|
|
|
"fts_table": None,
|
|
|
|
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
|
|
|
|
{
|
2019-05-22 22:44:34 -07:00
|
|
|
"name": "searchable_fts_segdir",
|
2018-06-21 07:56:28 -07:00
|
|
|
"columns": [
|
2019-05-03 22:15:14 -04:00
|
|
|
"level",
|
|
|
|
|
"idx",
|
2018-05-05 19:01:14 -03:00
|
|
|
"start_block",
|
|
|
|
|
"leaves_end_block",
|
2019-05-03 22:15:14 -04:00
|
|
|
"end_block",
|
|
|
|
|
"root",
|
|
|
|
|
],
|
2019-05-22 22:44:34 -07:00
|
|
|
"primary_keys": ["level", "idx"],
|
2018-05-15 06:50:27 -03:00
|
|
|
"count": 1,
|
Extract facet code out into a new plugin hook, closes #427 (#445)
Datasette previously only supported one type of faceting: exact column value counting.
With this change, faceting logic is extracted out into one or more separate classes which can implement other patterns of faceting - this is discussed in #427, but potential upcoming facet types include facet-by-date, facet-by-JSON-array, facet-by-many-2-many and more.
A new plugin hook, register_facet_classes, can be used by plugins to add in additional facet classes.
Each class must implement two methods: suggest(), which scans columns in the table to decide if they might be worth suggesting for faceting, and facet_results(), which executes the facet operation and returns results ready to be displayed in the UI.
2019-05-02 17:11:26 -07:00
|
|
|
"hidden": True,
|
2019-05-22 22:44:34 -07:00
|
|
|
"fts_table": None,
|
|
|
|
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2019-05-03 22:15:14 -04:00
|
|
|
},
|
|
|
|
|
{
|
2019-05-22 22:44:34 -07:00
|
|
|
"name": "searchable_fts_segments",
|
2018-06-21 07:56:28 -07:00
|
|
|
"columns": ["blockid", "block"],
|
2019-05-22 22:44:34 -07:00
|
|
|
"primary_keys": ["blockid"],
|
2019-03-14 16:42:38 -07:00
|
|
|
"count": 0,
|
Extract facet code out into a new plugin hook, closes #427 (#445)
Datasette previously only supported one type of faceting: exact column value counting.
With this change, faceting logic is extracted out into one or more separate classes which can implement other patterns of faceting - this is discussed in #427, but potential upcoming facet types include facet-by-date, facet-by-JSON-array, facet-by-many-2-many and more.
A new plugin hook, register_facet_classes, can be used by plugins to add in additional facet classes.
Each class must implement two methods: suggest(), which scans columns in the table to decide if they might be worth suggesting for faceting, and facet_results(), which executes the facet operation and returns results ready to be displayed in the UI.
2019-05-02 17:11:26 -07:00
|
|
|
"hidden": True,
|
2019-05-22 22:44:34 -07:00
|
|
|
"fts_table": None,
|
|
|
|
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2018-06-21 07:56:28 -07:00
|
|
|
},
|
2020-11-11 16:02:58 -08:00
|
|
|
{
|
|
|
|
|
"name": "searchable_fts_stat",
|
|
|
|
|
"columns": ["id", "value"],
|
|
|
|
|
"primary_keys": ["id"],
|
|
|
|
|
"count": 1,
|
|
|
|
|
"hidden": True,
|
|
|
|
|
"fts_table": None,
|
|
|
|
|
"foreign_keys": {"incoming": [], "outgoing": []},
|
|
|
|
|
"private": False,
|
|
|
|
|
},
|
2020-11-11 15:37:37 -08:00
|
|
|
]
|
2017-12-15 04:04:17 -08:00
|
|
|
|
|
|
|
|
|
2019-03-14 16:42:38 -07:00
|
|
|
def test_no_files_uses_memory_database(app_client_no_files):
|
|
|
|
|
response = app_client_no_files.get("/.json")
|
|
|
|
|
assert response.status == 200
|
|
|
|
|
assert {
|
2021-01-28 14:48:56 -08:00
|
|
|
"_memory": {
|
|
|
|
|
"name": "_memory",
|
2019-05-01 17:39:39 -07:00
|
|
|
"hash": None,
|
2021-01-28 14:48:56 -08:00
|
|
|
"color": "a6c7b9",
|
|
|
|
|
"path": "/_memory",
|
2021-01-24 21:13:05 -08:00
|
|
|
"tables_and_views_truncated": [],
|
|
|
|
|
"tables_and_views_more": False,
|
|
|
|
|
"tables_count": 0,
|
|
|
|
|
"table_rows_sum": 0,
|
|
|
|
|
"show_table_row_counts": False,
|
2019-03-14 16:42:38 -07:00
|
|
|
"hidden_table_rows_sum": 0,
|
|
|
|
|
"hidden_tables_count": 0,
|
|
|
|
|
"views_count": 0,
|
2020-06-08 07:50:06 -07:00
|
|
|
"private": False,
|
2019-03-14 16:42:38 -07:00
|
|
|
}
|
|
|
|
|
} == response.json
|
|
|
|
|
# Try that SQL query
|
|
|
|
|
response = app_client_no_files.get(
|
2021-01-28 14:48:56 -08:00
|
|
|
"/_memory.json?sql=select+sqlite_version()&_shape=array"
|
2019-03-14 16:42:38 -07:00
|
|
|
)
|
|
|
|
|
assert 1 == len(response.json)
|
|
|
|
|
assert ["sqlite_version()"] == list(response.json[0].keys())
|
|
|
|
|
|
|
|
|
|
|
2021-01-28 14:48:56 -08:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"path,expected_redirect",
|
|
|
|
|
(
|
|
|
|
|
("/:memory:", "/_memory"),
|
|
|
|
|
("/:memory:.json", "/_memory.json"),
|
|
|
|
|
("/:memory:?sql=select+1", "/_memory?sql=select+1"),
|
|
|
|
|
("/:memory:.json?sql=select+1", "/_memory.json?sql=select+1"),
|
|
|
|
|
("/:memory:.csv?sql=select+1", "/_memory.csv?sql=select+1"),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
def test_old_memory_urls_redirect(app_client_no_files, path, expected_redirect):
|
2021-10-14 11:03:44 -07:00
|
|
|
response = app_client_no_files.get(path)
|
2021-01-28 14:48:56 -08:00
|
|
|
assert response.status == 301
|
|
|
|
|
assert response.headers["location"] == expected_redirect
|
|
|
|
|
|
|
|
|
|
|
2018-06-21 08:21:09 -07:00
|
|
|
def test_database_page_for_database_with_dot_in_name(app_client_with_dot):
|
2022-03-19 13:29:10 -07:00
|
|
|
response = app_client_with_dot.get("/fixtures~2Edot.json")
|
|
|
|
|
assert response.status == 200
|
2018-06-21 08:21:09 -07:00
|
|
|
|
|
|
|
|
|
2022-12-15 13:44:48 -08:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_custom_sql(ds_client):
|
|
|
|
|
response = await ds_client.get(
|
2018-06-17 11:34:16 -07:00
|
|
|
"/fixtures.json?sql=select+content+from+simple_primary_key&_shape=objects"
|
2017-12-15 04:04:17 -08:00
|
|
|
)
|
2022-12-15 13:44:48 -08:00
|
|
|
data = response.json()
|
2017-12-15 04:04:17 -08:00
|
|
|
assert {"sql": "select content from simple_primary_key", "params": {}} == data[
|
|
|
|
|
"query"
|
|
|
|
|
]
|
|
|
|
|
assert [
|
|
|
|
|
{"content": "hello"},
|
|
|
|
|
{"content": "world"},
|
2018-08-28 03:03:01 -07:00
|
|
|
{"content": ""},
|
|
|
|
|
{"content": "RENDER_CELL_DEMO"},
|
2021-08-08 16:04:42 -07:00
|
|
|
{"content": "RENDER_CELL_ASYNC"},
|
2017-12-15 04:04:17 -08:00
|
|
|
] == data["rows"]
|
|
|
|
|
assert ["content"] == data["columns"]
|
2018-06-17 11:34:16 -07:00
|
|
|
assert "fixtures" == data["database"]
|
2017-12-15 04:04:17 -08:00
|
|
|
assert not data["truncated"]
|
|
|
|
|
|
|
|
|
|
|
2018-05-05 19:41:37 -03:00
|
|
|
def test_sql_time_limit(app_client_shorter_time_limit):
|
|
|
|
|
response = app_client_shorter_time_limit.get("/fixtures.json?sql=select+sleep(0.5)")
|
2017-12-15 04:04:17 -08:00
|
|
|
assert 400 == response.status
|
2022-09-26 16:06:01 -07:00
|
|
|
assert response.json == {
|
|
|
|
|
"ok": False,
|
|
|
|
|
"error": (
|
|
|
|
|
"<p>SQL query took too long. The time limit is controlled by the\n"
|
|
|
|
|
'<a href="https://docs.datasette.io/en/stable/settings.html#sql-time-limit-ms">sql_time_limit_ms</a>\n'
|
|
|
|
|
"configuration option.</p>\n"
|
2022-11-01 10:22:26 -07:00
|
|
|
'<textarea style="width: 90%">select sleep(0.5)</textarea>\n'
|
|
|
|
|
"<script>\n"
|
|
|
|
|
'let ta = document.querySelector("textarea");\n'
|
|
|
|
|
'ta.style.height = ta.scrollHeight + "px";\n'
|
|
|
|
|
"</script>"
|
2022-09-26 16:06:01 -07:00
|
|
|
),
|
|
|
|
|
"status": 400,
|
|
|
|
|
"title": "SQL Interrupted",
|
|
|
|
|
}
|
2017-12-15 04:04:17 -08:00
|
|
|
|
|
|
|
|
|
2022-12-15 13:44:48 -08:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_custom_sql_time_limit(ds_client):
|
|
|
|
|
response = await ds_client.get("/fixtures.json?sql=select+sleep(0.01)")
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
response = await ds_client.get("/fixtures.json?sql=select+sleep(0.01)&_timelimit=5")
|
|
|
|
|
assert response.status_code == 400
|
|
|
|
|
assert response.json()["title"] == "SQL Interrupted"
|
2017-12-15 04:04:17 -08:00
|
|
|
|
|
|
|
|
|
2022-12-15 13:44:48 -08:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_invalid_custom_sql(ds_client):
|
|
|
|
|
response = await ds_client.get("/fixtures.json?sql=.schema")
|
|
|
|
|
assert response.status_code == 400
|
|
|
|
|
assert response.json()["ok"] is False
|
|
|
|
|
assert "Statement must be a SELECT" == response.json()["error"]
|
2017-12-15 04:04:17 -08:00
|
|
|
|
|
|
|
|
|
2022-12-15 13:44:48 -08:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_row(ds_client):
|
|
|
|
|
response = await ds_client.get("/fixtures/simple_primary_key/1.json?_shape=objects")
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
assert response.json()["rows"] == [{"id": "1", "content": "hello"}]
|
2018-07-07 22:21:51 -07:00
|
|
|
|
|
|
|
|
|
2022-12-15 13:44:48 -08:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_row_strange_table_name(ds_client):
|
|
|
|
|
response = await ds_client.get(
|
2022-03-15 11:01:57 -07:00
|
|
|
"/fixtures/table~2Fwith~2Fslashes~2Ecsv/3.json?_shape=objects"
|
2018-07-07 22:21:51 -07:00
|
|
|
)
|
2022-12-15 13:44:48 -08:00
|
|
|
assert response.status_code == 200
|
|
|
|
|
assert response.json()["rows"] == [{"pk": "3", "content": "hey"}]
|
2017-12-15 04:04:17 -08:00
|
|
|
|
|
|
|
|
|
2022-12-15 13:44:48 -08:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_row_foreign_key_tables(ds_client):
|
|
|
|
|
response = await ds_client.get(
|
2018-06-17 11:34:16 -07:00
|
|
|
"/fixtures/simple_primary_key/1.json?_extras=foreign_key_tables"
|
|
|
|
|
)
|
2022-12-15 13:44:48 -08:00
|
|
|
assert response.status_code == 200
|
|
|
|
|
assert response.json()["foreign_key_tables"] == [
|
2017-12-15 04:04:17 -08:00
|
|
|
{
|
2018-04-14 07:55:27 -07:00
|
|
|
"other_table": "foreign_key_references",
|
2020-11-11 15:37:37 -08:00
|
|
|
"column": "id",
|
|
|
|
|
"other_column": "foreign_key_with_blank_label",
|
|
|
|
|
"count": 0,
|
2021-11-29 22:45:04 -08:00
|
|
|
"link": "/fixtures/foreign_key_references?foreign_key_with_blank_label=1",
|
2018-04-14 07:55:27 -07:00
|
|
|
},
|
|
|
|
|
{
|
2020-11-11 15:37:37 -08:00
|
|
|
"other_table": "foreign_key_references",
|
2017-12-15 04:04:17 -08:00
|
|
|
"column": "id",
|
2020-11-11 15:37:37 -08:00
|
|
|
"other_column": "foreign_key_with_label",
|
2017-12-15 04:04:17 -08:00
|
|
|
"count": 1,
|
2021-11-29 22:45:04 -08:00
|
|
|
"link": "/fixtures/foreign_key_references?foreign_key_with_label=1",
|
2020-11-11 15:37:37 -08:00
|
|
|
},
|
|
|
|
|
{
|
2017-12-15 04:04:17 -08:00
|
|
|
"other_table": "complex_foreign_keys",
|
2020-11-11 15:37:37 -08:00
|
|
|
"column": "id",
|
|
|
|
|
"other_column": "f3",
|
|
|
|
|
"count": 1,
|
2021-11-29 22:45:04 -08:00
|
|
|
"link": "/fixtures/complex_foreign_keys?f3=1",
|
2017-12-15 04:04:17 -08:00
|
|
|
},
|
|
|
|
|
{
|
2020-11-11 15:37:37 -08:00
|
|
|
"other_table": "complex_foreign_keys",
|
2017-12-15 04:04:17 -08:00
|
|
|
"column": "id",
|
|
|
|
|
"other_column": "f2",
|
2020-11-11 15:37:37 -08:00
|
|
|
"count": 0,
|
2021-11-29 22:45:04 -08:00
|
|
|
"link": "/fixtures/complex_foreign_keys?f2=1",
|
2017-12-15 04:04:17 -08:00
|
|
|
},
|
|
|
|
|
{
|
2020-11-11 15:37:37 -08:00
|
|
|
"other_table": "complex_foreign_keys",
|
2017-12-15 04:04:17 -08:00
|
|
|
"column": "id",
|
|
|
|
|
"other_column": "f1",
|
2020-11-11 15:37:37 -08:00
|
|
|
"count": 1,
|
2021-11-29 22:45:04 -08:00
|
|
|
"link": "/fixtures/complex_foreign_keys?f1=1",
|
2017-12-15 04:04:17 -08:00
|
|
|
},
|
2020-11-11 15:37:37 -08:00
|
|
|
]
|
2018-04-14 15:06:52 +01:00
|
|
|
|
|
|
|
|
|
2019-05-16 07:49:34 -07:00
|
|
|
def test_databases_json(app_client_two_attached_databases_one_immutable):
|
|
|
|
|
response = app_client_two_attached_databases_one_immutable.get("/-/databases.json")
|
|
|
|
|
databases = response.json
|
|
|
|
|
assert 2 == len(databases)
|
|
|
|
|
extra_database, fixtures_database = databases
|
2019-11-05 00:16:30 +01:00
|
|
|
assert "extra database" == extra_database["name"]
|
2019-05-16 07:49:34 -07:00
|
|
|
assert None == extra_database["hash"]
|
|
|
|
|
assert True == extra_database["is_mutable"]
|
|
|
|
|
assert False == extra_database["is_memory"]
|
|
|
|
|
|
|
|
|
|
assert "fixtures" == fixtures_database["name"]
|
|
|
|
|
assert fixtures_database["hash"] is not None
|
|
|
|
|
assert False == fixtures_database["is_mutable"]
|
|
|
|
|
assert False == fixtures_database["is_memory"]
|
|
|
|
|
|
|
|
|
|
|
2022-12-15 13:44:48 -08:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_metadata_json(ds_client):
|
|
|
|
|
response = await ds_client.get("/-/metadata.json")
|
|
|
|
|
assert response.json() == METADATA
|
2018-04-18 22:24:48 -07:00
|
|
|
|
|
|
|
|
|
2022-12-15 13:44:48 -08:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_threads_json(ds_client):
|
|
|
|
|
response = await ds_client.get("/-/threads.json")
|
2019-12-04 22:46:39 -08:00
|
|
|
expected_keys = {"threads", "num_threads"}
|
|
|
|
|
if sys.version_info >= (3, 7, 0):
|
|
|
|
|
expected_keys.update({"tasks", "num_tasks"})
|
2022-12-15 13:44:48 -08:00
|
|
|
assert set(response.json().keys()) == expected_keys
|
2019-12-04 22:46:39 -08:00
|
|
|
|
|
|
|
|
|
2022-12-15 13:44:48 -08:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_plugins_json(ds_client):
|
|
|
|
|
response = await ds_client.get("/-/plugins.json")
|
|
|
|
|
assert EXPECTED_PLUGINS == sorted(response.json(), key=lambda p: p["name"])
|
2020-06-05 16:55:08 -07:00
|
|
|
# Try with ?all=1
|
2022-12-15 13:44:48 -08:00
|
|
|
response = await ds_client.get("/-/plugins.json?all=1")
|
|
|
|
|
names = {p["name"] for p in response.json()}
|
2020-06-05 16:55:08 -07:00
|
|
|
assert names.issuperset(p["name"] for p in EXPECTED_PLUGINS)
|
|
|
|
|
assert names.issuperset(DEFAULT_PLUGINS)
|
2018-04-25 21:04:12 -07:00
|
|
|
|
|
|
|
|
|
2022-12-15 13:44:48 -08:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_versions_json(ds_client):
|
|
|
|
|
response = await ds_client.get("/-/versions.json")
|
|
|
|
|
data = response.json()
|
|
|
|
|
assert "python" in data
|
|
|
|
|
assert "3.0" == data.get("asgi")
|
|
|
|
|
assert "version" in data["python"]
|
|
|
|
|
assert "full" in data["python"]
|
|
|
|
|
assert "datasette" in data
|
|
|
|
|
assert "version" in data["datasette"]
|
|
|
|
|
assert data["datasette"]["version"] == __version__
|
|
|
|
|
assert "sqlite" in data
|
|
|
|
|
assert "version" in data["sqlite"]
|
|
|
|
|
assert "fts_versions" in data["sqlite"]
|
|
|
|
|
assert "compile_options" in data["sqlite"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_settings_json(ds_client):
|
|
|
|
|
response = await ds_client.get("/-/settings.json")
|
|
|
|
|
assert response.json() == {
|
2018-05-20 10:01:49 -07:00
|
|
|
"default_page_size": 50,
|
2018-05-17 23:16:28 -07:00
|
|
|
"default_facet_size": 30,
|
|
|
|
|
"facet_suggest_time_limit_ms": 50,
|
|
|
|
|
"facet_time_limit_ms": 200,
|
|
|
|
|
"max_returned_rows": 100,
|
2022-10-29 23:03:45 -07:00
|
|
|
"max_insert_rows": 100,
|
2018-05-17 23:16:28 -07:00
|
|
|
"sql_time_limit_ms": 200,
|
2018-05-24 18:12:27 -07:00
|
|
|
"allow_download": True,
|
2022-10-25 21:26:12 -07:00
|
|
|
"allow_signed_tokens": True,
|
2022-10-26 14:13:31 -07:00
|
|
|
"max_signed_tokens_ttl": 0,
|
2018-05-24 18:12:27 -07:00
|
|
|
"allow_facet": True,
|
2018-05-24 22:50:50 -07:00
|
|
|
"suggest_facets": True,
|
2019-03-17 15:55:04 -07:00
|
|
|
"default_cache_ttl": 5,
|
2020-06-13 17:26:02 -07:00
|
|
|
"num_sql_threads": 1,
|
2018-06-04 09:02:07 -07:00
|
|
|
"cache_size_kb": 0,
|
2018-06-17 20:21:02 -07:00
|
|
|
"allow_csv_stream": True,
|
|
|
|
|
"max_csv_mb": 100,
|
2018-07-10 09:20:41 -07:00
|
|
|
"truncate_cells_html": 2048,
|
2018-07-23 08:58:29 -07:00
|
|
|
"force_https_urls": False,
|
2019-12-22 16:04:45 +00:00
|
|
|
"template_debug": False,
|
2021-06-05 13:15:58 -07:00
|
|
|
"trace_debug": False,
|
2020-03-24 17:18:43 -07:00
|
|
|
"base_url": "/",
|
2022-12-15 13:44:48 -08:00
|
|
|
}
|
2018-05-17 23:16:28 -07:00
|
|
|
|
|
|
|
|
|
2022-12-15 13:44:48 -08:00
|
|
|
@pytest.mark.asyncio
|
2020-11-24 12:19:14 -08:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"path,expected_redirect",
|
|
|
|
|
(
|
|
|
|
|
("/-/config.json", "/-/settings.json"),
|
|
|
|
|
("/-/config", "/-/settings"),
|
|
|
|
|
),
|
|
|
|
|
)
|
2022-12-15 13:44:48 -08:00
|
|
|
async def test_config_redirects_to_settings(ds_client, path, expected_redirect):
|
|
|
|
|
response = await ds_client.get(path)
|
|
|
|
|
assert response.status_code == 301
|
2020-11-24 12:19:14 -08:00
|
|
|
assert response.headers["Location"] == expected_redirect
|
|
|
|
|
|
|
|
|
|
|
2018-05-28 11:08:39 -07:00
|
|
|
test_json_columns_default_expected = [
|
|
|
|
|
{"intval": 1, "strval": "s", "floatval": 0.5, "jsonval": '{"foo": "bar"}'}
|
|
|
|
|
]
|
2019-05-03 22:15:14 -04:00
|
|
|
|
|
|
|
|
|
2022-12-15 13:44:48 -08:00
|
|
|
@pytest.mark.asyncio
|
2018-05-28 11:08:39 -07:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"extra_args,expected",
|
|
|
|
|
[
|
|
|
|
|
("", test_json_columns_default_expected),
|
|
|
|
|
("&_json=intval", test_json_columns_default_expected),
|
|
|
|
|
("&_json=strval", test_json_columns_default_expected),
|
|
|
|
|
("&_json=floatval", test_json_columns_default_expected),
|
|
|
|
|
(
|
|
|
|
|
"&_json=jsonval",
|
|
|
|
|
[{"intval": 1, "strval": "s", "floatval": 0.5, "jsonval": {"foo": "bar"}}],
|
2019-05-03 22:15:14 -04:00
|
|
|
),
|
2018-05-28 11:08:39 -07:00
|
|
|
],
|
|
|
|
|
)
|
2022-12-15 13:44:48 -08:00
|
|
|
async def test_json_columns(ds_client, extra_args, expected):
|
2018-05-28 11:08:39 -07:00
|
|
|
sql = """
|
|
|
|
|
select 1 as intval, "s" as strval, 0.5 as floatval,
|
|
|
|
|
'{"foo": "bar"}' as jsonval
|
2019-05-03 22:15:14 -04:00
|
|
|
"""
|
2018-06-17 11:34:16 -07:00
|
|
|
path = "/fixtures.json?" + urllib.parse.urlencode({"sql": sql, "_shape": "array"})
|
2018-05-28 11:08:39 -07:00
|
|
|
path += extra_args
|
2022-12-15 13:44:48 -08:00
|
|
|
response = await ds_client.get(path)
|
|
|
|
|
assert response.json() == expected
|
2018-06-04 09:02:07 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_config_cache_size(app_client_larger_cache_size):
|
|
|
|
|
response = app_client_larger_cache_size.get("/fixtures/pragma_cache_size.json")
|
2022-12-30 06:52:47 -08:00
|
|
|
assert response.json["rows"] == [{"cache_size": -2500}]
|
2018-07-23 08:58:29 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_config_force_https_urls():
|
2021-08-12 18:10:36 -07:00
|
|
|
with make_app_client(settings={"force_https_urls": True}) as client:
|
2018-07-23 08:58:29 -07:00
|
|
|
response = client.get("/fixtures/facetable.json?_size=3&_facet=state")
|
|
|
|
|
assert response.json["next_url"].startswith("https://")
|
|
|
|
|
assert response.json["facet_results"]["state"]["results"][0][
|
|
|
|
|
"toggle_url"
|
|
|
|
|
].startswith("https://")
|
|
|
|
|
assert response.json["suggested_facets"][0]["toggle_url"].startswith("https://")
|
2020-05-28 10:09:32 -07:00
|
|
|
# Also confirm that request.url and request.scheme are set correctly
|
|
|
|
|
response = client.get("/")
|
|
|
|
|
assert client.ds._last_request.url.startswith("https://")
|
|
|
|
|
assert client.ds._last_request.scheme == "https"
|
2018-07-23 20:07:57 -07:00
|
|
|
|
|
|
|
|
|
2019-05-05 07:59:45 -04:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"path,status_code",
|
|
|
|
|
[
|
2020-10-27 13:39:07 -07:00
|
|
|
("/fixtures.db", 200),
|
2019-05-05 07:59:45 -04:00
|
|
|
("/fixtures.json", 200),
|
|
|
|
|
("/fixtures/no_primary_key.json", 200),
|
|
|
|
|
# A 400 invalid SQL query should still have the header:
|
|
|
|
|
("/fixtures.json?sql=select+blah", 400),
|
2022-11-30 09:26:59 -08:00
|
|
|
# Write APIs
|
|
|
|
|
("/fixtures/-/create", 405),
|
|
|
|
|
("/fixtures/facetable/-/insert", 405),
|
|
|
|
|
("/fixtures/facetable/-/drop", 405),
|
2019-05-05 07:59:45 -04:00
|
|
|
],
|
|
|
|
|
)
|
2022-11-30 09:26:59 -08:00
|
|
|
def test_cors(
|
|
|
|
|
app_client_with_cors,
|
|
|
|
|
app_client_two_attached_databases_one_immutable,
|
|
|
|
|
path,
|
|
|
|
|
status_code,
|
|
|
|
|
):
|
2019-05-05 07:59:45 -04:00
|
|
|
response = app_client_with_cors.get(path)
|
|
|
|
|
assert response.status == status_code
|
2021-10-14 12:03:28 -07:00
|
|
|
assert response.headers["Access-Control-Allow-Origin"] == "*"
|
2022-11-30 15:17:39 -08:00
|
|
|
assert (
|
|
|
|
|
response.headers["Access-Control-Allow-Headers"]
|
|
|
|
|
== "Authorization, Content-Type"
|
|
|
|
|
)
|
2021-11-27 12:08:42 -08:00
|
|
|
assert response.headers["Access-Control-Expose-Headers"] == "Link"
|
2022-11-30 13:54:47 -08:00
|
|
|
assert (
|
|
|
|
|
response.headers["Access-Control-Allow-Methods"] == "GET, POST, HEAD, OPTIONS"
|
|
|
|
|
)
|
2022-11-30 09:26:59 -08:00
|
|
|
# Same request to app_client_two_attached_databases_one_immutable
|
|
|
|
|
# should not have those headers - I'm using that fixture because
|
|
|
|
|
# regular app_client doesn't have immutable fixtures.db which means
|
|
|
|
|
# the test for /fixtures.db returns a 403 error
|
|
|
|
|
response = app_client_two_attached_databases_one_immutable.get(path)
|
|
|
|
|
assert response.status == status_code
|
|
|
|
|
assert "Access-Control-Allow-Origin" not in response.headers
|
|
|
|
|
assert "Access-Control-Allow-Headers" not in response.headers
|
|
|
|
|
assert "Access-Control-Expose-Headers" not in response.headers
|
2022-11-30 12:25:12 -08:00
|
|
|
assert "Access-Control-Allow-Methods" not in response.headers
|
2019-10-18 15:51:07 -07:00
|
|
|
|
|
|
|
|
|
2019-11-05 00:16:30 +01:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"path",
|
|
|
|
|
(
|
|
|
|
|
"/",
|
|
|
|
|
".json",
|
|
|
|
|
"/searchable",
|
|
|
|
|
"/searchable.json",
|
|
|
|
|
"/searchable_view",
|
|
|
|
|
"/searchable_view.json",
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
def test_database_with_space_in_name(app_client_two_attached_databases, path):
|
2021-10-14 11:03:44 -07:00
|
|
|
response = app_client_two_attached_databases.get(
|
2022-03-15 11:01:57 -07:00
|
|
|
"/extra~20database" + path, follow_redirects=True
|
2021-10-14 11:03:44 -07:00
|
|
|
)
|
2019-11-05 00:16:30 +01:00
|
|
|
assert response.status == 200
|
|
|
|
|
|
|
|
|
|
|
2019-10-18 15:51:07 -07:00
|
|
|
def test_common_prefix_database_names(app_client_conflicting_database_names):
|
|
|
|
|
# https://github.com/simonw/datasette/issues/597
|
2021-06-01 20:03:07 -07:00
|
|
|
assert ["foo-bar", "foo", "fixtures"] == [
|
2019-10-18 15:51:07 -07:00
|
|
|
d["name"]
|
2020-06-02 14:29:12 -07:00
|
|
|
for d in app_client_conflicting_database_names.get("/-/databases.json").json
|
2019-10-18 15:51:07 -07:00
|
|
|
]
|
2022-03-15 11:01:57 -07:00
|
|
|
for db_name, path in (("foo", "/foo.json"), ("foo-bar", "/foo-bar.json")):
|
2020-06-02 14:29:12 -07:00
|
|
|
data = app_client_conflicting_database_names.get(path).json
|
2019-10-18 15:51:07 -07:00
|
|
|
assert db_name == data["database"]
|
2019-11-02 15:29:40 -07:00
|
|
|
|
|
|
|
|
|
2020-02-25 15:19:29 -05:00
|
|
|
def test_inspect_file_used_for_count(app_client_immutable_and_inspect_file):
|
|
|
|
|
response = app_client_immutable_and_inspect_file.get("/fixtures/sortable.json")
|
2022-12-31 12:52:57 -08:00
|
|
|
assert response.json["count"] == 100
|
2020-08-16 11:24:39 -07:00
|
|
|
|
|
|
|
|
|
2022-12-15 13:44:48 -08:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_http_options_request(ds_client):
|
|
|
|
|
response = await ds_client.options("/fixtures")
|
|
|
|
|
assert response.status_code == 200
|
2020-12-02 16:44:03 -08:00
|
|
|
assert response.text == "ok"
|
2021-05-26 21:17:43 -07:00
|
|
|
|
|
|
|
|
|
2021-06-21 11:57:38 -04:00
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_db_path(app_client):
|
2022-12-16 09:44:30 -08:00
|
|
|
# Needs app_client because needs file based database
|
2021-06-21 11:57:38 -04:00
|
|
|
db = app_client.ds.get_database()
|
|
|
|
|
path = pathlib.Path(db.path)
|
|
|
|
|
|
|
|
|
|
assert path.exists()
|
|
|
|
|
|
|
|
|
|
datasette = Datasette([path])
|
|
|
|
|
|
2021-12-11 19:07:19 -08:00
|
|
|
# Previously this broke if path was a pathlib.Path:
|
2021-06-21 11:57:38 -04:00
|
|
|
await datasette.refresh_schemas()
|
2022-01-19 20:12:46 -08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_hidden_sqlite_stat1_table():
|
|
|
|
|
ds = Datasette()
|
|
|
|
|
db = ds.add_memory_database("db")
|
|
|
|
|
await db.execute_write("create table normal (id integer primary key, name text)")
|
|
|
|
|
await db.execute_write("create index idx on normal (name)")
|
|
|
|
|
await db.execute_write("analyze")
|
|
|
|
|
data = (await ds.client.get("/db.json?_show_hidden=1")).json()
|
|
|
|
|
tables = [(t["name"], t["hidden"]) for t in data["tables"]]
|
|
|
|
|
assert tables == [("normal", False), ("sqlite_stat1", True)]
|
2022-03-07 07:38:29 -08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
@pytest.mark.parametrize("db_name", ("foo", r"fo%o", "f~/c.d"))
|
2022-03-15 11:01:57 -07:00
|
|
|
async def test_tilde_encoded_database_names(db_name):
|
2022-03-07 07:38:29 -08:00
|
|
|
ds = Datasette()
|
|
|
|
|
ds.add_memory_database(db_name)
|
|
|
|
|
response = await ds.client.get("/.json")
|
|
|
|
|
assert db_name in response.json().keys()
|
|
|
|
|
path = response.json()[db_name]["path"]
|
|
|
|
|
# And the JSON for that database
|
|
|
|
|
response2 = await ds.client.get(path + ".json")
|
|
|
|
|
assert response2.status_code == 200
|