Clean up compatibility with Pyodide (#1736)

* Optional uvicorn import for Pyodide, refs #1733
* --setting num_sql_threads 0 to disable threading, refs #1735
This commit is contained in:
Simon Willison 2022-05-02 13:15:27 -07:00 committed by GitHub
commit 3f00a29141
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 42 additions and 4 deletions

View file

@ -288,9 +288,12 @@ class Datasette:
self._settings = dict(DEFAULT_SETTINGS, **(settings or {})) self._settings = dict(DEFAULT_SETTINGS, **(settings or {}))
self.renderers = {} # File extension -> (renderer, can_render) functions self.renderers = {} # File extension -> (renderer, can_render) functions
self.version_note = version_note self.version_note = version_note
self.executor = futures.ThreadPoolExecutor( if self.setting("num_sql_threads") == 0:
max_workers=self.setting("num_sql_threads") self.executor = None
) else:
self.executor = futures.ThreadPoolExecutor(
max_workers=self.setting("num_sql_threads")
)
self.max_returned_rows = self.setting("max_returned_rows") self.max_returned_rows = self.setting("max_returned_rows")
self.sql_time_limit_ms = self.setting("sql_time_limit_ms") self.sql_time_limit_ms = self.setting("sql_time_limit_ms")
self.page_size = self.setting("default_page_size") self.page_size = self.setting("default_page_size")
@ -862,6 +865,8 @@ class Datasette:
] ]
def _threads(self): def _threads(self):
if self.setting("num_sql_threads") == 0:
return {"num_threads": 0, "threads": []}
threads = list(threading.enumerate()) threads = list(threading.enumerate())
d = { d = {
"num_threads": len(threads), "num_threads": len(threads),

View file

@ -45,6 +45,9 @@ class Database:
self._cached_table_counts = None self._cached_table_counts = None
self._write_thread = None self._write_thread = None
self._write_queue = None self._write_queue = None
# These are used when in non-threaded mode:
self._read_connection = None
self._write_connection = None
if not self.is_mutable and not self.is_memory: if not self.is_mutable and not self.is_memory:
p = Path(path) p = Path(path)
self.hash = inspect_hash(p) self.hash = inspect_hash(p)
@ -134,6 +137,14 @@ class Database:
return results return results
async def execute_write_fn(self, fn, block=True): async def execute_write_fn(self, fn, block=True):
if self.ds.executor is None:
# non-threaded mode
if self._write_connection is None:
self._write_connection = self.connect(write=True)
self.ds._prepare_connection(self._write_connection, self.name)
return fn(self._write_connection)
# threaded mode
task_id = uuid.uuid5(uuid.NAMESPACE_DNS, "datasette.io") task_id = uuid.uuid5(uuid.NAMESPACE_DNS, "datasette.io")
if self._write_queue is None: if self._write_queue is None:
self._write_queue = queue.Queue() self._write_queue = queue.Queue()
@ -177,6 +188,14 @@ class Database:
task.reply_queue.sync_q.put(result) task.reply_queue.sync_q.put(result)
async def execute_fn(self, fn): async def execute_fn(self, fn):
if self.ds.executor is None:
# non-threaded mode
if self._read_connection is None:
self._read_connection = self.connect()
self.ds._prepare_connection(self._read_connection, self.name)
return fn(self._read_connection)
# threaded mode
def in_thread(): def in_thread():
conn = getattr(connections, self.name, None) conn = getattr(connections, self.name, None)
if not conn: if not conn:

View file

@ -107,6 +107,8 @@ Maximum number of threads in the thread pool Datasette uses to execute SQLite qu
datasette mydatabase.db --setting num_sql_threads 10 datasette mydatabase.db --setting num_sql_threads 10
Setting this to 0 turns off threaded SQL queries entirely - useful for environments that do not support threading such as `Pyodide <https://pyodide.org/>`__.
.. _setting_allow_facet: .. _setting_allow_facet:
allow_facet allow_facet

View file

@ -1,7 +1,7 @@
""" """
Tests for the datasette.app.Datasette class Tests for the datasette.app.Datasette class
""" """
from datasette.app import Datasette from datasette.app import Datasette, Database
from itsdangerous import BadSignature from itsdangerous import BadSignature
from .fixtures import app_client from .fixtures import app_client
import pytest import pytest
@ -63,3 +63,15 @@ async def test_datasette_constructor():
"hash": None, "hash": None,
} }
] ]
@pytest.mark.asyncio
async def test_num_sql_threads_zero():
ds = Datasette([], memory=True, settings={"num_sql_threads": 0})
db = ds.add_database(Database(ds, memory_name="test_num_sql_threads_zero"))
await db.execute_write("create table t(id integer primary key)")
await db.execute_write("insert into t (id) values (1)")
response = await ds.client.get("/-/threads.json")
assert response.json() == {"num_threads": 0, "threads": []}
response2 = await ds.client.get("/test_num_sql_threads_zero/t.json?_shape=array")
assert response2.json() == [{"id": 1}]