kopia lustrzana https://github.com/simonw/datasette
Support for :memory: databases
If you start Datasette with no files, it will connect to :memory: instead. When starting it with files you can add --memory to also get a :memory: database.optional-hash
rodzic
bf6b0f918d
commit
9743e1d91b
|
@ -42,7 +42,7 @@ from .version import __version__
|
|||
app_root = Path(__file__).parent.parent
|
||||
|
||||
connections = threading.local()
|
||||
|
||||
MEMORY = object()
|
||||
|
||||
ConfigOption = collections.namedtuple(
|
||||
"ConfigOption", ("name", "default", "help")
|
||||
|
@ -123,10 +123,15 @@ class Datasette:
|
|||
template_dir=None,
|
||||
plugins_dir=None,
|
||||
static_mounts=None,
|
||||
memory=False,
|
||||
config=None,
|
||||
version_note=None,
|
||||
):
|
||||
self.files = files
|
||||
if not self.files:
|
||||
self.files = [MEMORY]
|
||||
elif memory:
|
||||
self.files = (MEMORY,) + self.files
|
||||
self.cache_headers = cache_headers
|
||||
self.cors = cors
|
||||
self._inspect = inspect_data
|
||||
|
@ -296,31 +301,40 @@ class Datasette:
|
|||
|
||||
self._inspect = {}
|
||||
for filename in self.files:
|
||||
path = Path(filename)
|
||||
name = path.stem
|
||||
if name in self._inspect:
|
||||
raise Exception("Multiple files with same stem %s" % name)
|
||||
try:
|
||||
with sqlite3.connect(
|
||||
"file:{}?immutable=1".format(path), uri=True
|
||||
) as conn:
|
||||
self.prepare_connection(conn)
|
||||
self._inspect[name] = {
|
||||
"hash": inspect_hash(path),
|
||||
"file": str(path),
|
||||
"size": path.stat().st_size,
|
||||
"views": inspect_views(conn),
|
||||
"tables": inspect_tables(conn, (self.metadata("databases") or {}).get(name, {}))
|
||||
}
|
||||
except sqlite3.OperationalError as e:
|
||||
if (e.args[0] == 'no such module: VirtualSpatialIndex'):
|
||||
raise click.UsageError(
|
||||
"It looks like you're trying to load a SpatiaLite"
|
||||
" database without first loading the SpatiaLite module."
|
||||
"\n\nRead more: https://datasette.readthedocs.io/en/latest/spatialite.html"
|
||||
)
|
||||
else:
|
||||
raise
|
||||
if filename is MEMORY:
|
||||
self._inspect[":memory:"] = {
|
||||
"hash": "000",
|
||||
"file": ":memory:",
|
||||
"size": 0,
|
||||
"views": {},
|
||||
"tables": {},
|
||||
}
|
||||
else:
|
||||
path = Path(filename)
|
||||
name = path.stem
|
||||
if name in self._inspect:
|
||||
raise Exception("Multiple files with same stem %s" % name)
|
||||
try:
|
||||
with sqlite3.connect(
|
||||
"file:{}?immutable=1".format(path), uri=True
|
||||
) as conn:
|
||||
self.prepare_connection(conn)
|
||||
self._inspect[name] = {
|
||||
"hash": inspect_hash(path),
|
||||
"file": str(path),
|
||||
"size": path.stat().st_size,
|
||||
"views": inspect_views(conn),
|
||||
"tables": inspect_tables(conn, (self.metadata("databases") or {}).get(name, {}))
|
||||
}
|
||||
except sqlite3.OperationalError as e:
|
||||
if (e.args[0] == 'no such module: VirtualSpatialIndex'):
|
||||
raise click.UsageError(
|
||||
"It looks like you're trying to load a SpatiaLite"
|
||||
" database without first loading the SpatiaLite module."
|
||||
"\n\nRead more: https://datasette.readthedocs.io/en/latest/spatialite.html"
|
||||
)
|
||||
else:
|
||||
raise
|
||||
return self._inspect
|
||||
|
||||
def register_custom_units(self):
|
||||
|
@ -403,11 +417,14 @@ class Datasette:
|
|||
conn = getattr(connections, db_name, None)
|
||||
if not conn:
|
||||
info = self.inspect()[db_name]
|
||||
conn = sqlite3.connect(
|
||||
"file:{}?immutable=1".format(info["file"]),
|
||||
uri=True,
|
||||
check_same_thread=False,
|
||||
)
|
||||
if info["file"] == ":memory:":
|
||||
conn = sqlite3.connect(":memory:")
|
||||
else:
|
||||
conn = sqlite3.connect(
|
||||
"file:{}?immutable=1".format(info["file"]),
|
||||
uri=True,
|
||||
check_same_thread=False,
|
||||
)
|
||||
self.prepare_connection(conn)
|
||||
setattr(connections, db_name, conn)
|
||||
|
||||
|
|
|
@ -315,6 +315,9 @@ def package(
|
|||
help="mountpoint:path-to-directory for serving static files",
|
||||
multiple=True,
|
||||
)
|
||||
@click.option(
|
||||
"--memory", is_flag=True, help="Make :memory: database available"
|
||||
)
|
||||
@click.option(
|
||||
"--config",
|
||||
type=Config(),
|
||||
|
@ -340,6 +343,7 @@ def serve(
|
|||
template_dir,
|
||||
plugins_dir,
|
||||
static,
|
||||
memory,
|
||||
config,
|
||||
version_note,
|
||||
help_config,
|
||||
|
@ -384,6 +388,7 @@ def serve(
|
|||
plugins_dir=plugins_dir,
|
||||
static_mounts=static,
|
||||
config=dict(config),
|
||||
memory=memory,
|
||||
version_note=version_note,
|
||||
)
|
||||
# Force initial hashing/table counting
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
{% if config.allow_sql %}
|
||||
<form class="sql" action="/{{ database }}-{{ database_hash }}" method="get">
|
||||
<h3>Custom SQL query</h3>
|
||||
<p><textarea name="sql">select * from {{ tables[0].name|escape_sqlite }}</textarea></p>
|
||||
<p><textarea name="sql">{% if tables %}select * from {{ tables[0].name|escape_sqlite }}{% else %}select sqlite_version(){% endif %}</textarea></p>
|
||||
<p><input type="submit" value="Run SQL"></p>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
@ -56,7 +56,7 @@
|
|||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if config.allow_download %}
|
||||
{% if config.allow_download and database != ":memory:" %}
|
||||
<p class="download-sqlite">Download SQLite DB: <a href="/{{ database }}-{{ database_hash }}.db">{{ database }}.db</a> <em>{{ format_bytes(size) }}</em></p>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ Options:
|
|||
--template-dir DIRECTORY Path to directory containing custom templates
|
||||
--plugins-dir DIRECTORY Path to directory containing custom plugins
|
||||
--static STATIC MOUNT mountpoint:path-to-directory for serving static files
|
||||
--memory Make :memory: database available
|
||||
--config CONFIG Set config option using configname:value
|
||||
datasette.readthedocs.io/en/latest/config.html
|
||||
--version-note TEXT Additional note to show on /-/versions
|
||||
|
|
|
@ -65,6 +65,14 @@ def app_client():
|
|||
yield from make_app_client()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def app_client_no_files():
|
||||
ds = Datasette([])
|
||||
client = TestClient(ds.app().test_client)
|
||||
client.ds = ds
|
||||
yield client
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def app_client_shorter_time_limit():
|
||||
yield from make_app_client(20)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from .fixtures import ( # noqa
|
||||
app_client,
|
||||
app_client_no_files,
|
||||
app_client_shorter_time_limit,
|
||||
app_client_larger_cache_size,
|
||||
app_client_returned_rows_matches_page_size,
|
||||
|
@ -368,6 +369,31 @@ def test_database_page(app_client):
|
|||
}] == data['tables']
|
||||
|
||||
|
||||
def test_no_files_uses_memory_database(app_client_no_files):
|
||||
response = app_client_no_files.get("/.json")
|
||||
assert response.status == 200
|
||||
assert {
|
||||
":memory:": {
|
||||
"hash": "000",
|
||||
"hidden_table_rows_sum": 0,
|
||||
"hidden_tables_count": 0,
|
||||
"name": ":memory:",
|
||||
"path": ":memory:-000",
|
||||
"table_rows_sum": 0,
|
||||
"tables_count": 0,
|
||||
"tables_more": False,
|
||||
"tables_truncated": [],
|
||||
"views_count": 0,
|
||||
}
|
||||
} == response.json
|
||||
# Try that SQL query
|
||||
response = app_client_no_files.get(
|
||||
"/:memory:-0.json?sql=select+sqlite_version()&_shape=array"
|
||||
)
|
||||
assert 1 == len(response.json)
|
||||
assert ["sqlite_version()"] == list(response.json[0].keys())
|
||||
|
||||
|
||||
def test_database_page_for_database_with_dot_in_name(app_client_with_dot):
|
||||
response = app_client_with_dot.get("/fixtures.dot.json")
|
||||
assert 200 == response.status
|
||||
|
|
Ładowanie…
Reference in New Issue