Renamed /:memory: to /_memory, with redirects - closes #1205

pull/1211/head
Simon Willison 2021-01-28 14:48:56 -08:00
rodzic 382e9ecd1d
commit 1600d2a3ec
10 zmienionych plików z 65 dodań i 34 usunięć

Wyświetl plik

@ -78,7 +78,7 @@ Now visiting http://localhost:8001/History/downloads will show you a web interfa
--plugins-dir DIRECTORY Path to directory containing custom plugins --plugins-dir DIRECTORY Path to directory containing custom plugins
--static STATIC MOUNT mountpoint:path-to-directory for serving static --static STATIC MOUNT mountpoint:path-to-directory for serving static
files files
--memory Make :memory: database available --memory Make /_memory database available
--config CONFIG Set config option using configname:value --config CONFIG Set config option using configname:value
docs.datasette.io/en/stable/config.html docs.datasette.io/en/stable/config.html
--version-note TEXT Additional note to show on /-/versions --version-note TEXT Additional note to show on /-/versions

Wyświetl plik

@ -218,7 +218,7 @@ class Datasette:
self.immutables = set(immutables or []) self.immutables = set(immutables or [])
self.databases = collections.OrderedDict() self.databases = collections.OrderedDict()
if memory or not self.files: if memory or not self.files:
self.add_database(Database(self, is_memory=True), name=":memory:") self.add_database(Database(self, is_memory=True), name="_memory")
# memory_name is a random string so that each Datasette instance gets its own # memory_name is a random string so that each Datasette instance gets its own
# unique in-memory named database - otherwise unit tests can fail with weird # unique in-memory named database - otherwise unit tests can fail with weird
# errors when different instances accidentally share an in-memory database # errors when different instances accidentally share an in-memory database
@ -633,7 +633,7 @@ class Datasette:
def _versions(self): def _versions(self):
conn = sqlite3.connect(":memory:") conn = sqlite3.connect(":memory:")
self._prepare_connection(conn, ":memory:") self._prepare_connection(conn, "_memory")
sqlite_version = conn.execute("select sqlite_version()").fetchone()[0] sqlite_version = conn.execute("select sqlite_version()").fetchone()[0]
sqlite_extensions = {} sqlite_extensions = {}
for extension, testsql, hasversion in ( for extension, testsql, hasversion in (
@ -927,6 +927,12 @@ class Datasette:
plugin["name"].replace("-", "_") plugin["name"].replace("-", "_")
), ),
) )
add_route(
permanent_redirect(
"/_memory", forward_query_string=True, forward_rest=True
),
r"/:memory:(?P<rest>.*)$",
)
add_route( add_route(
JsonDataView.as_view(self, "metadata.json", lambda: self._metadata), JsonDataView.as_view(self, "metadata.json", lambda: self._metadata),
r"/-/metadata(?P<as_format>(\.json)?)$", r"/-/metadata(?P<as_format>(\.json)?)$",
@ -1290,9 +1296,18 @@ def wrap_view(view_fn, datasette):
return async_view_fn return async_view_fn
def permanent_redirect(path): def permanent_redirect(path, forward_query_string=False, forward_rest=False):
return wrap_view( return wrap_view(
lambda request, send: Response.redirect(path, status=301), lambda request, send: Response.redirect(
path
+ (request.url_vars["rest"] if forward_rest else "")
+ (
("?" + request.query_string)
if forward_query_string and request.query_string
else ""
),
status=301,
),
datasette=None, datasette=None,
) )

Wyświetl plik

@ -365,7 +365,7 @@ def uninstall(packages, yes):
help="Serve static files from this directory at /MOUNT/...", help="Serve static files from this directory at /MOUNT/...",
multiple=True, multiple=True,
) )
@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=Config(),

Wyświetl plik

@ -138,7 +138,7 @@ class DatabaseView(DataView):
"metadata": metadata, "metadata": metadata,
"allow_download": self.ds.setting("allow_download") "allow_download": self.ds.setting("allow_download")
and not db.is_mutable and not db.is_mutable
and database != ":memory:", and not db.is_memory,
}, },
(f"database-{to_css_class(database)}.html", "database.html"), (f"database-{to_css_class(database)}.html", "database.html"),
) )
@ -160,7 +160,7 @@ class DatabaseDownload(DataView):
raise DatasetteError("Invalid database", status=404) raise DatasetteError("Invalid database", status=404)
db = self.ds.databases[database] db = self.ds.databases[database]
if db.is_memory: if db.is_memory:
raise DatasetteError("Cannot download :memory: database", status=404) raise DatasetteError("Cannot download in-memory databases", status=404)
if not self.ds.setting("allow_download") or db.is_mutable: if not self.ds.setting("allow_download") or db.is_mutable:
raise Forbidden("Database download is forbidden") raise Forbidden("Database download is forbidden")
if not db.path: if not db.path:

Wyświetl plik

@ -24,7 +24,7 @@ Options:
--template-dir DIRECTORY Path to directory containing custom templates --template-dir DIRECTORY Path to directory containing custom templates
--plugins-dir DIRECTORY Path to directory containing custom plugins --plugins-dir DIRECTORY Path to directory containing custom plugins
--static MOUNT:DIRECTORY Serve static files from this directory at /MOUNT/... --static MOUNT:DIRECTORY Serve static files from this directory at /MOUNT/...
--memory Make :memory: database available --memory Make /_memory database available
--config CONFIG Deprecated: set config option using configname:value. Use --config CONFIG Deprecated: set config option using configname:value. Use
--setting instead. --setting instead.

Wyświetl plik

@ -608,11 +608,11 @@ def test_no_files_uses_memory_database(app_client_no_files):
response = app_client_no_files.get("/.json") response = app_client_no_files.get("/.json")
assert response.status == 200 assert response.status == 200
assert { assert {
":memory:": { "_memory": {
"name": ":memory:", "name": "_memory",
"hash": None, "hash": None,
"color": "f7935d", "color": "a6c7b9",
"path": "/%3Amemory%3A", "path": "/_memory",
"tables_and_views_truncated": [], "tables_and_views_truncated": [],
"tables_and_views_more": False, "tables_and_views_more": False,
"tables_count": 0, "tables_count": 0,
@ -626,12 +626,28 @@ def test_no_files_uses_memory_database(app_client_no_files):
} == response.json } == response.json
# Try that SQL query # Try that SQL query
response = app_client_no_files.get( response = app_client_no_files.get(
"/:memory:.json?sql=select+sqlite_version()&_shape=array" "/_memory.json?sql=select+sqlite_version()&_shape=array"
) )
assert 1 == len(response.json) assert 1 == len(response.json)
assert ["sqlite_version()"] == list(response.json[0].keys()) assert ["sqlite_version()"] == list(response.json[0].keys())
@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):
response = app_client_no_files.get(path, allow_redirects=False)
assert response.status == 301
assert response.headers["location"] == expected_redirect
def test_database_page_for_database_with_dot_in_name(app_client_with_dot): def test_database_page_for_database_with_dot_in_name(app_client_with_dot):
response = app_client_with_dot.get("/fixtures.dot.json") response = app_client_with_dot.get("/fixtures.dot.json")
assert 200 == response.status assert 200 == response.status

Wyświetl plik

@ -221,7 +221,7 @@ def test_config_deprecated(ensure_eventloop):
def test_sql_errors_logged_to_stderr(ensure_eventloop): def test_sql_errors_logged_to_stderr(ensure_eventloop):
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"])
assert result.exit_code == 1 assert result.exit_code == 1
assert "sql = 'select blah', params = {}: no such column: blah\n" in result.stderr assert "sql = 'select blah', params = {}: no such column: blah\n" in result.stderr

Wyświetl plik

@ -30,12 +30,12 @@ def test_serve_with_get(tmp_path_factory):
"--plugins-dir", "--plugins-dir",
str(plugins_dir), str(plugins_dir),
"--get", "--get",
"/:memory:.json?sql=select+sqlite_version()", "/_memory.json?sql=select+sqlite_version()",
], ],
) )
assert 0 == result.exit_code, result.output assert 0 == result.exit_code, result.output
assert { assert {
"database": ":memory:", "database": "_memory",
"truncated": False, "truncated": False,
"columns": ["sqlite_version()"], "columns": ["sqlite_version()"],
}.items() <= json.loads(result.output).items() }.items() <= json.loads(result.output).items()

Wyświetl plik

@ -88,7 +88,7 @@ def test_static_mounts():
def test_memory_database_page(): def test_memory_database_page():
with make_app_client(memory=True) as client: with make_app_client(memory=True) as client:
response = client.get("/:memory:") response = client.get("/_memory")
assert response.status == 200 assert response.status == 200
@ -1056,10 +1056,10 @@ def test_database_download_disallowed_for_mutable(app_client):
def test_database_download_disallowed_for_memory(): def test_database_download_disallowed_for_memory():
with make_app_client(memory=True) as client: with make_app_client(memory=True) as client:
# Memory page should NOT have a download link # Memory page should NOT have a download link
response = client.get("/:memory:") response = client.get("/_memory")
soup = Soup(response.body, "html.parser") soup = Soup(response.body, "html.parser")
assert 0 == len(soup.findAll("a", {"href": re.compile(r"\.db$")})) assert 0 == len(soup.findAll("a", {"href": re.compile(r"\.db$")}))
assert 404 == client.get("/:memory:.db").status assert 404 == client.get("/_memory.db").status
def test_allow_download_off(): def test_allow_download_off():

Wyświetl plik

@ -103,14 +103,14 @@ def test_logout(ds, base_url, expected):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"base_url,format,expected", "base_url,format,expected",
[ [
("/", None, "/%3Amemory%3A"), ("/", None, "/_memory"),
("/prefix/", None, "/prefix/%3Amemory%3A"), ("/prefix/", None, "/prefix/_memory"),
("/", "json", "/%3Amemory%3A.json"), ("/", "json", "/_memory.json"),
], ],
) )
def test_database(ds, base_url, format, expected): def test_database(ds, base_url, format, expected):
ds._settings["base_url"] = base_url ds._settings["base_url"] = base_url
actual = ds.urls.database(":memory:", format=format) actual = ds.urls.database("_memory", format=format)
assert actual == expected assert actual == expected
assert isinstance(actual, PrefixedUrlString) assert isinstance(actual, PrefixedUrlString)
@ -118,18 +118,18 @@ def test_database(ds, base_url, format, expected):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"base_url,name,format,expected", "base_url,name,format,expected",
[ [
("/", "name", None, "/%3Amemory%3A/name"), ("/", "name", None, "/_memory/name"),
("/prefix/", "name", None, "/prefix/%3Amemory%3A/name"), ("/prefix/", "name", None, "/prefix/_memory/name"),
("/", "name", "json", "/%3Amemory%3A/name.json"), ("/", "name", "json", "/_memory/name.json"),
("/", "name.json", "json", "/%3Amemory%3A/name.json?_format=json"), ("/", "name.json", "json", "/_memory/name.json?_format=json"),
], ],
) )
def test_table_and_query(ds, base_url, name, format, expected): def test_table_and_query(ds, base_url, name, format, expected):
ds._settings["base_url"] = base_url ds._settings["base_url"] = base_url
actual1 = ds.urls.table(":memory:", name, format=format) actual1 = ds.urls.table("_memory", name, format=format)
assert actual1 == expected assert actual1 == expected
assert isinstance(actual1, PrefixedUrlString) assert isinstance(actual1, PrefixedUrlString)
actual2 = ds.urls.query(":memory:", name, format=format) actual2 = ds.urls.query("_memory", name, format=format)
assert actual2 == expected assert actual2 == expected
assert isinstance(actual2, PrefixedUrlString) assert isinstance(actual2, PrefixedUrlString)
@ -137,14 +137,14 @@ def test_table_and_query(ds, base_url, name, format, expected):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"base_url,format,expected", "base_url,format,expected",
[ [
("/", None, "/%3Amemory%3A/facetable/1"), ("/", None, "/_memory/facetable/1"),
("/prefix/", None, "/prefix/%3Amemory%3A/facetable/1"), ("/prefix/", None, "/prefix/_memory/facetable/1"),
("/", "json", "/%3Amemory%3A/facetable/1.json"), ("/", "json", "/_memory/facetable/1.json"),
], ],
) )
def test_row(ds, base_url, format, expected): def test_row(ds, base_url, format, expected):
ds._settings["base_url"] = base_url ds._settings["base_url"] = base_url
actual = ds.urls.row(":memory:", "facetable", "1", format=format) actual = ds.urls.row("_memory", "facetable", "1", format=format)
assert actual == expected assert actual == expected
assert isinstance(actual, PrefixedUrlString) assert isinstance(actual, PrefixedUrlString)