{"html_url": "https://github.com/simonw/datasette/issues/1959#issuecomment-1356478792", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1959", "id": 1356478792, "node_id": "IC_kwDOBm6k_c5Q2jlI", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-17T21:49:36Z", "updated_at": "2022-12-17T21:49:36Z", "author_association": "OWNER", "body": "Made a really good start on this in the just-merged PR:\r\n- #1960\r\n\r\nThe follow-up work will happen in:\r\n- #1962", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1499081664, "label": "Refactor test suite to use mostly `async def` tests"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1959#issuecomment-1355317782", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1959", "id": 1355317782, "node_id": "IC_kwDOBm6k_c5QyIIW", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-16T17:57:25Z", "updated_at": "2022-12-16T17:57:25Z", "author_association": "OWNER", "body": "Opened a follow-up issue here:\r\n- #1962", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1499081664, "label": "Refactor test suite to use mostly `async def` tests"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1959#issuecomment-1353747370", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1959", "id": 1353747370, "node_id": "IC_kwDOBm6k_c5QsIuq", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-15T21:45:14Z", "updated_at": "2022-12-15T21:45:14Z", "author_association": "OWNER", "body": "I'm going to do this in a PR.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1499081664, "label": "Refactor test suite to use mostly `async def` tests"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1959#issuecomment-1353738075", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1959", "id": 1353738075, "node_id": "IC_kwDOBm6k_c5QsGdb", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-15T21:35:56Z", "updated_at": "2022-12-15T21:35:56Z", "author_association": "OWNER", "body": "I built that `OldResponse` class:\r\n```diff\r\ndiff --git a/tests/utils.py b/tests/utils.py\r\nindex 191ead9b..f39ac434 100644\r\n--- a/tests/utils.py\r\n+++ b/tests/utils.py\r\n@@ -30,3 +30,25 @@ def inner_html(soup):\r\n def has_load_extension():\r\n conn = sqlite3.connect(\":memory:\")\r\n return hasattr(conn, \"enable_load_extension\")\r\n+\r\n+\r\n+class OldResponse:\r\n+ \"Transform an HTTPX response to simulate the older TestClient responses\"\r\n+ # https://github.com/simonw/datasette/issues/1959#issuecomment-1353721091\r\n+ def __init__(self, response):\r\n+ self.response = response\r\n+ self._json = None\r\n+\r\n+ @property\r\n+ def headers(self):\r\n+ return self.response.headers\r\n+\r\n+ @property\r\n+ def status(self):\r\n+ return self.response.status_code\r\n+\r\n+ @property\r\n+ def json(self):\r\n+ if self._json is None:\r\n+ self._json = self.response.json()\r\n+ return self._json\r\n```\r\nI can use it in tests like this:\r\n```python\r\n@pytest.mark.asyncio\r\nasync def test_homepage(ds_client):\r\n response = OldResponse(await ds_client.get(\"/.json\"))\r\n assert response.status == 200\r\n assert \"application/json; charset=utf-8\" == response.headers[\"content-type\"]\r\n assert response.json.keys() == {\"fixtures\": 0}.keys()\r\n d = response.json[\"fixtures\"]\r\n assert d[\"name\"] == \"fixtures\"\r\n assert d[\"tables_count\"] == 24\r\n assert len(d[\"tables_and_views_truncated\"]) == 5\r\n assert d[\"tables_and_views_more\"] is True\r\n # 4 hidden FTS tables + no_primary_key (hidden in metadata)\r\n assert d[\"hidden_tables_count\"] == 6\r\n # 201 in no_primary_key, plus 6 in other hidden tables:\r\n assert d[\"hidden_table_rows_sum\"] == 207, response.json\r\n assert d[\"views_count\"] == 4\r\n```\r\nBut as I work through the tests I'm finding it's actually not too hard to port them over, so I likely won't use it after all.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1499081664, "label": "Refactor test suite to use mostly `async def` tests"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1959#issuecomment-1353728682", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1959", "id": 1353728682, "node_id": "IC_kwDOBm6k_c5QsEKq", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-15T21:28:35Z", "updated_at": "2022-12-15T21:28:35Z", "author_association": "OWNER", "body": "Got this error trying to have two tests use the same `ds_client` async fixture when I added `scope=\"session\"` to that fixture:\r\n\r\n- https://github.com/tortoise/tortoise-orm/issues/638\r\n\r\nAdding this to `conftest.py` (as suggested in that issue thread) seemed to fix it:\r\n\r\n```python\r\n@pytest.fixture(scope=\"session\")\r\ndef event_loop():\r\n return asyncio.get_event_loop()\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1499081664, "label": "Refactor test suite to use mostly `async def` tests"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1959#issuecomment-1353721091", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1959", "id": 1353721091, "node_id": "IC_kwDOBm6k_c5QsCUD", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-15T21:20:32Z", "updated_at": "2022-12-15T21:20:32Z", "author_association": "OWNER", "body": "Rather than tediously rewriting every single test to the new shape I'm going to try a wrapper for that HTTPX response that transforms it into an imitation of the one returned by the existing `TestClient` class.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1499081664, "label": "Refactor test suite to use mostly `async def` tests"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1959#issuecomment-1353720559", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1959", "id": 1353720559, "node_id": "IC_kwDOBm6k_c5QsCLv", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-15T21:19:56Z", "updated_at": "2022-12-15T21:19:56Z", "author_association": "OWNER", "body": "Here's a port of the first `def ...(app_client)` test. Note that the TestClient object works slightly differently from the HTTPX response returned by `await datasette.client.get(...)`:\r\n\r\n```diff\r\ndiff --git a/datasette/app.py b/datasette/app.py\r\nindex f3cb8876..b770b469 100644\r\n--- a/datasette/app.py\r\n+++ b/datasette/app.py\r\n@@ -281,7 +281,7 @@ class Datasette:\r\n raise\r\n self.crossdb = crossdb\r\n self.nolock = nolock\r\n- if memory or crossdb or not self.files:\r\n+ if memory or crossdb or (not self.files and memory is not False):\r\n self.add_database(\r\n Database(self, is_mutable=False, is_memory=True), name=\"_memory\"\r\n )\r\ndiff --git a/pytest.ini b/pytest.ini\r\nindex 559e518c..0bcb0d1e 100644\r\n--- a/pytest.ini\r\n+++ b/pytest.ini\r\n@@ -8,4 +8,5 @@ filterwarnings=\r\n ignore:.*current_task.*:PendingDeprecationWarning\r\n markers =\r\n serial: tests to avoid using with pytest-xdist\r\n+ ds_client: tests using the ds_client fixture\r\n asyncio_mode = strict\r\ndiff --git a/tests/conftest.py b/tests/conftest.py\r\nindex cd735e12..648423ba 100644\r\n--- a/tests/conftest.py\r\n+++ b/tests/conftest.py\r\n@@ -2,6 +2,7 @@ import httpx\r\n import os\r\n import pathlib\r\n import pytest\r\n+import pytest_asyncio\r\n import re\r\n import subprocess\r\n import tempfile\r\n@@ -23,6 +24,22 @@ UNDOCUMENTED_PERMISSIONS = {\r\n }\r\n \r\n \r\n+@pytest_asyncio.fixture\r\n+async def ds_client():\r\n+ from datasette.app import Datasette\r\n+ from .fixtures import METADATA, PLUGINS_DIR\r\n+ ds = Datasette(memory=False, metadata=METADATA, plugins_dir=PLUGINS_DIR)\r\n+ from .fixtures import TABLES, TABLE_PARAMETERIZED_SQL\r\n+ db = ds.add_memory_database(\"fixtures\")\r\n+ def prepare(conn):\r\n+ conn.executescript(TABLES)\r\n+ for sql, params in TABLE_PARAMETERIZED_SQL:\r\n+ with conn:\r\n+ conn.execute(sql, params)\r\n+ await db.execute_write_fn(prepare)\r\n+ return ds.client\r\n+\r\n+\r\n def pytest_report_header(config):\r\n return \"SQLite: {}\".format(\r\n sqlite3.connect(\":memory:\").execute(\"select sqlite_version()\").fetchone()[0]\r\ndiff --git a/tests/test_api.py b/tests/test_api.py\r\nindex 5f2a6ea6..ddf4219c 100644\r\n--- a/tests/test_api.py\r\n+++ b/tests/test_api.py\r\n@@ -23,12 +23,15 @@ import sys\r\n import urllib\r\n \r\n \r\n-def test_homepage(app_client):\r\n- response = app_client.get(\"/.json\")\r\n- assert response.status == 200\r\n+@pytest.mark.ds_client\r\n+@pytest.mark.asyncio\r\n+async def test_homepage(ds_client):\r\n+ response = await ds_client.get(\"/.json\")\r\n+ assert response.status_code == 200\r\n assert \"application/json; charset=utf-8\" == response.headers[\"content-type\"]\r\n- assert response.json.keys() == {\"fixtures\": 0}.keys()\r\n- d = response.json[\"fixtures\"]\r\n+ data = response.json()\r\n+ assert data.keys() == {\"fixtures\": 0}.keys()\r\n+ d = data[\"fixtures\"]\r\n assert d[\"name\"] == \"fixtures\"\r\n assert d[\"tables_count\"] == 24\r\n assert len(d[\"tables_and_views_truncated\"]) == 5\r\n@@ -36,7 +39,7 @@ def test_homepage(app_client):\r\n # 4 hidden FTS tables + no_primary_key (hidden in metadata)\r\n assert d[\"hidden_tables_count\"] == 6\r\n # 201 in no_primary_key, plus 6 in other hidden tables:\r\n- assert d[\"hidden_table_rows_sum\"] == 207, response.json\r\n+ assert d[\"hidden_table_rows_sum\"] == 207, data\r\n assert d[\"views_count\"] == 4\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1499081664, "label": "Refactor test suite to use mostly `async def` tests"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1959#issuecomment-1353707828", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1959", "id": 1353707828, "node_id": "IC_kwDOBm6k_c5Qr_E0", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-15T21:06:29Z", "updated_at": "2022-12-15T21:06:29Z", "author_association": "OWNER", "body": "Previous, abandoned attempt at this work (for #1843):\r\n```diff\r\ndiff --git a/datasette/app.py b/datasette/app.py\r\nindex 7e682498..cf35c3a2 100644\r\n--- a/datasette/app.py\r\n+++ b/datasette/app.py\r\n@@ -228,7 +228,7 @@ class Datasette:\r\n template_dir=None,\r\n plugins_dir=None,\r\n static_mounts=None,\r\n- memory=False,\r\n+ memory=None,\r\n settings=None,\r\n secret=None,\r\n version_note=None,\r\n@@ -238,6 +238,7 @@ class Datasette:\r\n nolock=False,\r\n ):\r\n self._startup_invoked = False\r\n+ self._extra_on_startup = []\r\n assert config_dir is None or isinstance(\r\n config_dir, Path\r\n ), \"config_dir= should be a pathlib.Path\"\r\n@@ -278,7 +279,7 @@ class Datasette:\r\n raise\r\n self.crossdb = crossdb\r\n self.nolock = nolock\r\n- if memory or crossdb or not self.files:\r\n+ if memory or crossdb or (not self.files and memory is not False):\r\n self.add_database(\r\n Database(self, is_mutable=False, is_memory=True), name=\"_memory\"\r\n )\r\n@@ -391,6 +392,9 @@ class Datasette:\r\n self._root_token = secrets.token_hex(32)\r\n self.client = DatasetteClient(self)\r\n \r\n+ def _add_on_startup(self, fn):\r\n+ self._extra_on_startup.append(fn)\r\n+\r\n async def refresh_schemas(self):\r\n if self._refresh_schemas_lock.locked():\r\n return\r\n@@ -431,6 +435,8 @@ class Datasette:\r\n # This must be called for Datasette to be in a usable state\r\n if self._startup_invoked:\r\n return\r\n+ for fn in self._extra_on_startup:\r\n+ await fn()\r\n # Register permissions, but watch out for duplicate name/abbr\r\n names = {}\r\n abbrs = {}\r\n@@ -1431,9 +1437,9 @@ class Datasette:\r\n )\r\n if self.setting(\"trace_debug\"):\r\n asgi = AsgiTracer(asgi)\r\n- asgi = AsgiRunOnFirstRequest(asgi, on_startup=[setup_db, self.invoke_startup])\r\n for wrapper in pm.hook.asgi_wrapper(datasette=self):\r\n asgi = wrapper(asgi)\r\n+ asgi = AsgiRunOnFirstRequest(asgi, on_startup=[setup_db, self.invoke_startup])\r\n return asgi\r\n \r\n \r\ndiff --git a/datasette/utils/asgi.py b/datasette/utils/asgi.py\r\nindex 56690251..986755cb 100644\r\n--- a/datasette/utils/asgi.py\r\n+++ b/datasette/utils/asgi.py\r\n@@ -423,9 +423,9 @@ class AsgiFileDownload:\r\n \r\n \r\n class AsgiRunOnFirstRequest:\r\n- def __init__(self, asgi, on_startup):\r\n+ def __init__(self, app, on_startup):\r\n assert isinstance(on_startup, list)\r\n- self.asgi = asgi\r\n+ self.app = app\r\n self.on_startup = on_startup\r\n self._started = False\r\n \r\n@@ -434,4 +434,4 @@ class AsgiRunOnFirstRequest:\r\n self._started = True\r\n for hook in self.on_startup:\r\n await hook()\r\n- return await self.asgi(scope, receive, send)\r\n+ return await self.app(scope, receive, send)\r\ndiff --git a/tests/conftest.py b/tests/conftest.py\r\nindex cd735e12..d1301943 100644\r\n--- a/tests/conftest.py\r\n+++ b/tests/conftest.py\r\n@@ -23,6 +23,15 @@ UNDOCUMENTED_PERMISSIONS = {\r\n }\r\n \r\n \r\n+# @pytest.fixture(autouse=True)\r\n+# def log_name_of_test_before_test(request):\r\n+# # To help identify tests that are hanging\r\n+# name = str(request.node)\r\n+# with open(\"/tmp/test.log\", \"a\") as f:\r\n+# f.write(name + \"\\n\")\r\n+# yield\r\n+\r\n+\r\n def pytest_report_header(config):\r\n return \"SQLite: {}\".format(\r\n sqlite3.connect(\":memory:\").execute(\"select sqlite_version()\").fetchone()[0]\r\ndiff --git a/tests/fixtures.py b/tests/fixtures.py\r\nindex a6700239..18d3f1b7 100644\r\n--- a/tests/fixtures.py\r\n+++ b/tests/fixtures.py\r\n@@ -101,6 +101,19 @@ EXPECTED_PLUGINS = [\r\n ]\r\n \r\n \r\n+def _populate_connection(conn):\r\n+ # Drop any tables and views that exist\r\n+ to_drop = conn.execute(\r\n+ \"SELECT name, type FROM sqlite_master where type in ('table', 'view')\"\r\n+ ).fetchall()\r\n+ for name, type in to_drop:\r\n+ conn.execute(f\"DROP {type} IF EXISTS [{name}]\")\r\n+ conn.executescript(TABLES)\r\n+ for sql, params in TABLE_PARAMETERIZED_SQL:\r\n+ with conn:\r\n+ conn.execute(sql, params)\r\n+\r\n+\r\n @contextlib.contextmanager\r\n def make_app_client(\r\n sql_time_limit_ms=None,\r\n@@ -117,45 +130,22 @@ def make_app_client(\r\n metadata=None,\r\n crossdb=False,\r\n ):\r\n- with tempfile.TemporaryDirectory() as tmpdir:\r\n- filepath = os.path.join(tmpdir, filename)\r\n- if is_immutable:\r\n- files = []\r\n- immutables = [filepath]\r\n- else:\r\n- files = [filepath]\r\n- immutables = []\r\n- conn = sqlite3.connect(filepath)\r\n- conn.executescript(TABLES)\r\n- for sql, params in TABLE_PARAMETERIZED_SQL:\r\n- with conn:\r\n- conn.execute(sql, params)\r\n- # Close the connection to avoid \"too many open files\" errors\r\n- conn.close()\r\n- if extra_databases is not None:\r\n- for extra_filename, extra_sql in extra_databases.items():\r\n- extra_filepath = os.path.join(tmpdir, extra_filename)\r\n- c2 = sqlite3.connect(extra_filepath)\r\n- c2.executescript(extra_sql)\r\n- c2.close()\r\n- # Insert at start to help test /-/databases ordering:\r\n- files.insert(0, extra_filepath)\r\n- os.chdir(os.path.dirname(filepath))\r\n- settings = settings or {}\r\n- for key, value in {\r\n- \"default_page_size\": 50,\r\n- \"max_returned_rows\": max_returned_rows or 100,\r\n- \"sql_time_limit_ms\": sql_time_limit_ms or 200,\r\n- # Default is 3 but this results in \"too many open files\"\r\n- # errors when running the full test suite:\r\n- \"num_sql_threads\": 1,\r\n- }.items():\r\n- if key not in settings:\r\n- settings[key] = value\r\n+ settings = settings or {}\r\n+ for key, value in {\r\n+ \"default_page_size\": 50,\r\n+ \"max_returned_rows\": max_returned_rows or 100,\r\n+ \"sql_time_limit_ms\": sql_time_limit_ms or 200,\r\n+ # Default is 3 but this results in \"too many open files\"\r\n+ # errors when running the full test suite:\r\n+ \"num_sql_threads\": 1,\r\n+ }.items():\r\n+ if key not in settings:\r\n+ settings[key] = value\r\n+ # We can use an in-memory database, but only if we're not doing anything\r\n+ # with is_immutable or extra_databases and filename is the default\r\n+ if not is_immutable and not extra_databases and filename == \"fixtures.db\":\r\n ds = Datasette(\r\n- files,\r\n- immutables=immutables,\r\n- memory=memory,\r\n+ memory=memory or False,\r\n cors=cors,\r\n metadata=metadata or METADATA,\r\n plugins_dir=PLUGINS_DIR,\r\n@@ -165,12 +155,57 @@ def make_app_client(\r\n template_dir=template_dir,\r\n crossdb=crossdb,\r\n )\r\n+ db = ds.add_memory_database(\"fixtures\")\r\n+\r\n+ async def populate_fixtures():\r\n+ print(\"Here we go... populating fixtures\")\r\n+ await db.execute_write_fn(_populate_connection)\r\n+\r\n+ ds._add_on_startup(populate_fixtures)\r\n yield TestClient(ds)\r\n- # Close as many database connections as possible\r\n- # to try and avoid too many open files error\r\n- for db in ds.databases.values():\r\n- if not db.is_memory:\r\n- db.close()\r\n+ else:\r\n+ with tempfile.TemporaryDirectory() as tmpdir:\r\n+ filepath = os.path.join(tmpdir, filename)\r\n+ if is_immutable:\r\n+ files = []\r\n+ immutables = [filepath]\r\n+ else:\r\n+ files = [filepath]\r\n+ immutables = []\r\n+\r\n+ conn = sqlite3.connect(filepath)\r\n+ _populate_connection(conn)\r\n+ # Close the connection to reduce \"too many open files\" errors\r\n+ conn.close()\r\n+\r\n+ if extra_databases is not None:\r\n+ for extra_filename, extra_sql in extra_databases.items():\r\n+ extra_filepath = os.path.join(tmpdir, extra_filename)\r\n+ c2 = sqlite3.connect(extra_filepath)\r\n+ c2.executescript(extra_sql)\r\n+ c2.close()\r\n+ # Insert at start to help test /-/databases ordering:\r\n+ files.insert(0, extra_filepath)\r\n+ os.chdir(os.path.dirname(filepath))\r\n+ ds = Datasette(\r\n+ files,\r\n+ immutables=immutables,\r\n+ memory=memory,\r\n+ cors=cors,\r\n+ metadata=metadata or METADATA,\r\n+ plugins_dir=PLUGINS_DIR,\r\n+ settings=settings,\r\n+ inspect_data=inspect_data,\r\n+ static_mounts=static_mounts,\r\n+ template_dir=template_dir,\r\n+ crossdb=crossdb,\r\n+ )\r\n+ yield TestClient(ds)\r\n+ # Close as many database connections as possible\r\n+ # to try and avoid too many open files error\r\n+ for db in ds.databases.values():\r\n+ if not db.is_memory:\r\n+ db.close()\r\n \r\n \r\n @pytest.fixture(scope=\"session\")\r\ndiff --git a/tests/test_cli.py b/tests/test_cli.py\r\nindex d3e015fa..d9e4e457 100644\r\n--- a/tests/test_cli.py\r\n+++ b/tests/test_cli.py\r\n@@ -1,5 +1,6 @@\r\n from .fixtures import (\r\n app_client,\r\n+ app_client_with_cors,\r\n make_app_client,\r\n TestClient as _TestClient,\r\n EXPECTED_PLUGINS,\r\n@@ -38,7 +39,7 @@ def test_inspect_cli(app_client):\r\n assert expected_count == database[\"tables\"][table_name][\"count\"]\r\n \r\n \r\n-def test_inspect_cli_writes_to_file(app_client):\r\n+def test_inspect_cli_writes_to_file(app_client_with_cors):\r\n runner = CliRunner()\r\n result = runner.invoke(\r\n cli, [\"inspect\", \"fixtures.db\", \"--inspect-file\", \"foo.json\"]\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1499081664, "label": "Refactor test suite to use mostly `async def` tests"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1959#issuecomment-1353705072", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1959", "id": 1353705072, "node_id": "IC_kwDOBm6k_c5Qr-Zw", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-15T21:04:07Z", "updated_at": "2022-12-15T21:04:07Z", "author_association": "OWNER", "body": "I'm going to start by getting every test that uses the raw `(app_client)` fixture and nothing else (194 at the moment) to switch to `async def` using a shared Datasette instance and `datasette.client.get()`.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1499081664, "label": "Refactor test suite to use mostly `async def` tests"}, "performed_via_github_app": null}