{"id": 1734786661, "node_id": "PR_kwDOBm6k_c5R0fcK", "number": 2082, "title": "Catch query interrupted on facet suggest row count", "user": {"value": 10843208, "label": "redraw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-05-31T18:42:46Z", "updated_at": "2023-05-31T18:45:26Z", "closed_at": null, "author_association": "FIRST_TIME_CONTRIBUTOR", "pull_request": "simonw/datasette/pulls/2082", "body": "Just like facet's `suggest()` is trapping `QueryInterrupted` for facet columns, we also need to trap `get_row_count()`, which can reach timeout if database tables are big enough. \r\n\r\nI've included `get_columns()` inside the block as that's just another query, despite it's a really cheap one and might never raise the exception.\r\n\r\n\r\n----\r\n:books: Documentation preview :books:: https://datasette--2082.org.readthedocs.build/en/2082/\r\n\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "pull", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2082/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": 0, "state_reason": null} {"id": 1727478903, "node_id": "I_kwDOBm6k_c5m9zx3", "number": 2081, "title": "Update Endpoints defined in metadata throws 403 Forbidden after a while", "user": {"value": 15085007, "label": "cutmasta-kun"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-05-26T11:52:30Z", "updated_at": "2023-05-26T11:52:30Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "Hello. I expose an endpoint to update `tasks`:\r\n```\r\n{\r\n \"title\": \"My Datasette Instance\",\r\n \"databases\": {\r\n \"tasks\": {\r\n \"queries\": {\r\n \"update_task\": {\r\n \"sql\": \"UPDATE tasks SET status = :status, result = :result, systemMessage = :systemMessage WHERE queueID = :queueID\",\r\n \"write\": true,\r\n \"on_success_message\": \"Task updated\",\r\n \"on_success_redirect\": \"/tasks/tasks.json\",\r\n \"on_error_message\": \"Task update failed\",\r\n \"on_error_redirect\": \"/tasks.json\",\r\n \"params\": [\"queueID\", \"taskData\", \"status\", \"result\", \"systemMessage\"]\r\n }\r\n }\r\n }\r\n }\r\n}\r\n```\r\n\r\nThis works really well! But after a while, the Datasette Instanz answers with **403 Forbidden**.\r\nI have to delete the database and recreate it in order to work again.\r\n\r\nAny help here? (\u00b4\u3002\uff3f\u3002\uff40)", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2081/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1720096994, "node_id": "I_kwDOCGYnMM5mhpji", "number": 554, "title": "`IndexError` when doing `.insert(..., pk='id')` after `insert_all`", "user": {"value": 1231935, "label": "xavdid"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2023-05-22T17:13:02Z", "updated_at": "2023-05-22T17:18:33Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "I believe this is related to https://github.com/simonw/sqlite-utils/issues/98.\r\n\r\nWhen `pk` is specified by table A's `insert` call, it throws an index error if a different table has written a row with a higher rowid than exists in the first table. Here's a basic example:\r\n\r\n```py\r\nfrom sqlite_utils import Database\r\n\r\n\r\ndef test_pk_for_insert(fresh_db):\r\n user = {\"id\": \"abc\", \"name\": \"david\"}\r\n\r\n fresh_db[\"users\"].insert(user, pk=\"id\")\r\n\r\n fresh_db[\"comments\"].insert_all(\r\n [\r\n {\"id\": \"def\", \"text\": \"ok\"},\r\n {\"id\": \"ghi\", \"text\": \"great\"},\r\n ],\r\n )\r\n\r\n fresh_db[\"users\"].insert(\r\n user,\r\n ignore=True,\r\n # BUG: when specifying pk on the second insert call \r\n # db.py goes into a block it doesn't expect and we get the error\r\n pk=\"id\",\r\n )\r\n\r\n\r\nif __name__ == \"__main__\":\r\n db = Database(\"bug.db\")\r\n if db[\"users\"].exists():\r\n raise ValueError(\r\n \"bug only shows on a new database - remove bug.db before running the script\"\r\n )\r\n test_pk_for_insert(db)\r\n```\r\n\r\nThe error is:\r\n\r\n```py\r\n File \"/Users/david/projects/reddit-to-sqlite/.venv/lib/python3.11/site-packages/sqlite_utils/db.py\", line 2960, in insert_chunk\r\n row = list(self.rows_where(\"rowid = ?\", [self.last_rowid]))[0]\r\n ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^\r\nIndexError: list index out of range\r\n```\r\n\r\nThe issue is in this block: \r\n\r\nhttps://github.com/simonw/sqlite-utils/blob/2747257a3334d55e890b40ec58fada57ae8cfbfd/sqlite_utils/db.py#L2954-L2958\r\n\r\nrelevant locals are:\r\n\r\n- `pk`: `'id'`\r\n- `result.lastrowid`: `2`\r\n\r\nWhat's most interesting is the comment `# self.last_rowid will be 0 if a \"INSERT OR IGNORE\" happened`, which doesn't seem to be the case here. ", "repo": {"value": 140912432, "label": "sqlite-utils"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/sqlite-utils/issues/554/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1715468032, "node_id": "PR_kwDOBm6k_c5QzEAM", "number": 2076, "title": "Datsette gpt plugin", "user": {"value": 130708713, "label": "StudioCordillera"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-05-18T11:22:30Z", "updated_at": "2023-05-18T11:22:45Z", "closed_at": null, "author_association": "FIRST_TIME_CONTRIBUTOR", "pull_request": "simonw/datasette/pulls/2076", "body": "\r\n\r\n\r\n----\n:books: Documentation preview :books:: https://datasette--2076.org.readthedocs.build/en/2076/\n\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "pull", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2076/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": 0, "state_reason": null} {"id": 1124731464, "node_id": "I_kwDOCGYnMM5DCgpI", "number": 399, "title": "Make it easier to insert geometries, with documentation and maybe code", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 25, "created_at": "2022-02-05T00:11:26Z", "updated_at": "2023-05-16T03:11:52Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "In playing with the new SpatiaLite helpers from #385 I noticed that actually populating geometry columns is still a little bit tricky. Here's what I ended up doing:\r\n\r\n```python\r\nimport httpx, sqlite_utils\r\ndb = sqlite_utils.Database(\"/tmp/spatial.db\")\r\nattractions = httpx.get(\"https://latest.datasette.io/fixtures/roadside_attractions.json?_shape=array\").json()\r\ndb[\"attractions\"].insert_all(attractions, pk=\"pk\")\r\n\r\n# Schema of that table is now:\r\n# CREATE TABLE [attractions] (\r\n# [pk] INTEGER PRIMARY KEY,\r\n# [name] TEXT,\r\n# [address] TEXT,\r\n# [latitude] FLOAT,\r\n# [longitude] FLOAT\r\n# )\r\n\r\ndb.init_spatialite()\r\ndb[\"attractions\"].add_geometry_column(\"point\", \"POINT\")\r\n\r\ndb.execute(\"\"\"\r\n update attractions set point = GeomFromText(\r\n 'POINT(' || longitude || ' ' || latitude || ')', 4326\r\n )\r\n\"\"\")\r\n```\r\nThat last line took some figuring out - especially the need for the SRID of `4326`, without which I got this error:\r\n\r\n> `IntegrityError: attractions.point violates Geometry constraint [geom-type or SRID not allowed]`\r\n\r\nIt would be good to both document this in more detail, but ideally also to come up with a more obvious pattern for inserting common types of spatial data.\r\n\r\nAlso related:\r\n- #398\r\n- #79", "repo": {"value": 140912432, "label": "sqlite-utils"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/sqlite-utils/issues/399/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1708981860, "node_id": "PR_kwDOBm6k_c5QdMea", "number": 2074, "title": "sort files by mtime", "user": {"value": 3919561, "label": "abbbi"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-05-14T15:25:15Z", "updated_at": "2023-05-14T15:25:29Z", "closed_at": null, "author_association": "FIRST_TIME_CONTRIBUTOR", "pull_request": "simonw/datasette/pulls/2074", "body": "serving multiple database files and getting tired by the default sort, changes so the sort order puts the latest changed databases to be on top of the list so don't have to scroll down, lazy as i am ;)\r\n\r\n\r\n----\n:books: Documentation preview :books:: https://datasette--2074.org.readthedocs.build/en/2074/\n\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "pull", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2074/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": 0, "state_reason": null} {"id": 1708030220, "node_id": "I_kwDOBm6k_c5lznkM", "number": 2073, "title": "Faceting doesn't work against integer columns in views", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 2, "created_at": "2023-05-12T18:20:10Z", "updated_at": "2023-05-12T18:24:07Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "Spotted this issue here: https://til.simonwillison.net/datasette/baseline\r\n\r\nI had to do this workaround:\r\n```sql\r\ncreate view baseline as select\r\n _key,\r\n spec,\r\n '' || json_extract(status, '$.is_baseline') as is_baseline,\r\n json_extract(status, '$.since') as baseline_since,\r\n json_extract(status, '$.support.chrome') as baseline_chrome,\r\n json_extract(status, '$.support.edge') as baseline_edge,\r\n json_extract(status, '$.support.firefox') as baseline_firefox,\r\n json_extract(status, '$.support.safari') as baseline_safari,\r\n compat_features,\r\n caniuse,\r\n usage_stats,\r\n status\r\nfrom\r\n [index]\r\n```\r\nI think the core issue here is that, against a table, `select * from x where integer_column = '1'` works correctly, due to some kind of column type conversion mechanism... but this mechanism doesn't work against views.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2073/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1700936245, "node_id": "I_kwDOCGYnMM5lYjo1", "number": 542, "title": "Remove `skip_false=True` and `--no-skip-false` in `sqlite-utils` 4.0", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": {"value": 9374594, "label": "4.0 backwards incomatible changes"}, "comments": 1, "created_at": "2023-05-08T21:04:28Z", "updated_at": "2023-05-08T21:07:41Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "Following:\r\n- #527\r\n\r\nThe only reason I didn't remove fix this mis-feature entirely is that it represents a backwards incompatible change. I'll make that change in 4.0.", "repo": {"value": 140912432, "label": "sqlite-utils"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/sqlite-utils/issues/542/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1595340692, "node_id": "I_kwDOCGYnMM5fFveU", "number": 530, "title": "add ability to configure \"on delete\" and \"on update\" attributes of foreign keys:", "user": {"value": 536941, "label": "fgregg"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 2, "created_at": "2023-02-22T15:44:14Z", "updated_at": "2023-05-08T20:39:01Z", "closed_at": null, "author_association": "CONTRIBUTOR", "pull_request": null, "body": "sqlite supports these, and it would be quite nice to be able to add them with sqlite-utils.\r\n\r\nhttps://www.sqlite.org/foreignkeys.html#fk_actions", "repo": {"value": 140912432, "label": "sqlite-utils"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/sqlite-utils/issues/530/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1700840265, "node_id": "I_kwDOCGYnMM5lYMNJ", "number": 541, "title": "Get tests to pass with `pytest -Werror`", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2023-05-08T19:57:23Z", "updated_at": "2023-05-08T19:59:35Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "Inspired by:\r\n- #534", "repo": {"value": 140912432, "label": "sqlite-utils"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/sqlite-utils/issues/541/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1698865182, "node_id": "I_kwDOBm6k_c5lQqAe", "number": 2069, "title": "[BUG] Cannot insert new data to deployed instance", "user": {"value": 31861128, "label": "yqlbu"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2023-05-07T02:59:42Z", "updated_at": "2023-05-07T03:17:35Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "## Summary\r\n\r\nRecently, I deployed an instance of datasette to Vercel with the following plugins:\r\n\r\n- datasette-auth-tokens\r\n- datasette-insert\r\n\r\nWith the above plugins, I was able to insert new data to local sqlite db. However, when it comes to the deployment on Vercel, things behave differently. I observed some errors from the logs console on Vercel:\r\n\r\n```console\r\nFile \"/var/task/datasette/database.py\", line 179, in _execute_writes\r\nconn = self.connect(write=True)\r\nFile \"/var/task/datasette/database.py\", line 93, in connect\r\nassert not (write and not self.is_mutable)\r\nAssertionError\r\n``` \r\n\r\n\"image\"\r\n\r\nI think it is a potential bug.\r\n\r\n## Reproduce\r\n\r\n
metadata.json\r\n
\r\n\r\n```json\r\n{\r\n \"plugins\": {\r\n \"datasette-insert\": {\r\n \"allow\": {\r\n \"id\": \"*\"\r\n }\r\n },\r\n \"datasette-auth-tokens\": {\r\n \"tokens\": [\r\n {\r\n \"token\": {\r\n \"$env\": \"INSERT_TOKEN\"\r\n },\r\n \"actor\": {\r\n \"id\": \"repeater\"\r\n }\r\n }\r\n ],\r\n \"param\": \"_auth_token\"\r\n }\r\n }\r\n}\r\n```\r\n\r\n
\r\n\r\n
commands\r\n
\r\n\r\n```bash\r\n# deploy\r\ndatasette publish vercel remote.db \\\r\n --project=repeater-bot-sqlite \\\r\n --metadata metadata.json \\\r\n --install datasette-auth-tokens \\\r\n --install datasette-insert \\\r\n --vercel-json=vercel.json\r\n\r\n# test insert\r\ncat fixtures/dogs.json | curl --request POST -d @- -H \"Authorization: Bearer \" \\\r\n 'https://repeater-bot-sqlite.vercel.app/-/insert/remote/dogs?pk=id'\r\n```\r\n\r\n
\r\n\r\n
logs\r\n
\r\n\r\n```console\r\nTraceback (most recent call last):\r\nFile \"/var/task/datasette/app.py\", line 1354, in route_path\r\nresponse = await view(request, send)\r\nFile \"/var/task/datasette/app.py\", line 1500, in async_view_fn\r\nresponse = await async_call_with_supported_arguments(\r\nFile \"/var/task/datasette/utils/__init__.py\", line 1005, in async_call_with_supported_arguments\r\nreturn await fn(*call_with)\r\nFile \"/var/task/datasette_insert/__init__.py\", line 14, in insert_or_upsert\r\nresponse = await insert_or_upsert_implementation(request, datasette)\r\nFile \"/var/task/datasette_insert/__init__.py\", line 91, in insert_or_upsert_implementation\r\ntable_count = await db.execute_write_fn(write_in_thread, block=True)\r\nFile \"/var/task/datasette/database.py\", line 167, in execute_write_fn\r\nraise result\r\nFile \"/var/task/datasette/database.py\", line 179, in _execute_writes\r\nconn = self.connect(write=True)\r\nFile \"/var/task/datasette/database.py\", line 93, in connect\r\nassert not (write and not self.is_mutable)\r\nAssertionError\r\n```\r\n\r\n
", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2069/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1393202060, "node_id": "I_kwDOCGYnMM5TCpOM", "number": 496, "title": "devrel/python api: Pylance type hinting", "user": {"value": 7908073, "label": "chapmanjacobd"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 4, "created_at": "2022-10-01T03:03:34Z", "updated_at": "2023-05-03T05:53:27Z", "closed_at": null, "author_association": "CONTRIBUTOR", "pull_request": null, "body": "Pylance is generally pretty good at figuring out stuff but `sqlite-utils` has some quirks which make type hinting kinda useless. Maybe you don't care but I thought I would bring it to your attention.\r\n\r\nFor example:\r\n\r\n```\r\ndb[\"subs\"].insert_all(subs, pk=\"index\")\r\n```\r\n\r\n```\r\nCannot access member \"insert_all\" for type \"View\"\r\n Member \"insert_all\" is unknown\r\n```\r\n\r\n`insert_all` and all the other methods show up as a type issues because the program can't know whether something is a View or a Table. Fair enough. But that basically throws all type checking out the window.\r\n\r\n`pk=\"index\"` also shows up as a type issue:\r\n\r\n```\r\nArgument of type \"Literal['index']\" cannot be assigned to parameter \"pk\" of type \"Default\" in function \"insert_all\"\r\n \"Literal['index']\" is incompatible with \"Default\"\r\n```\r\n\r\nI think this is because DEFAULT is an empty class? \r\n\r\nmaybe a few small changes could be made to make the library more type-friendly\r\n\r\nThe interim solution is of course to turn off type hints completely for the line\r\n```\r\ndb[\"subs\"].insert_all(subs, pk=\"index\") # type: ignore\r\n```\r\n", "repo": {"value": 140912432, "label": "sqlite-utils"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/sqlite-utils/issues/496/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1690765434, "node_id": "I_kwDOBm6k_c5kxwh6", "number": 2067, "title": "Litestream-restored db: errors on 3.11 and 3.10.8; but works on py3.10.7 and 3.10.6", "user": {"value": 39538958, "label": "justmars"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2023-05-01T12:42:28Z", "updated_at": "2023-05-03T00:16:03Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "Hi! Wondering if this issue is limited to my local system or if it affects others as well. \r\n\r\nIt seems like 3.11 errors out on a \"litestream-restored\" database. On further investigation, it also appears to conk out on 3.10.8 but works on 3.10.7 and 3.10.6.\r\n\r\nTo demo issue I created a test database, replicated it to an aws s3 bucket, then restored the same under various .pyenv-versioned shells where I test whether I can read the database via the sqlite3 cli.\r\n\r\n```sh\r\n# create new shell with 3.11.3\r\nlitestream restore -o data/db.sqlite s3://mytestbucketxx/db\r\nsqlite3 data/db.sqlite \r\n# SQLite version 3.41.2 2023-03-22 11:56:21\r\n# Enter \".help\" for usage hints.\r\n# sqlite> .tables\r\n# _litestream_lock _litestream_seq movie \r\n# sqlite> \r\n```\r\n\r\nHowever this get me an `OperationalError` when reading via datasette:\r\n\r\n
\r\nError on 3.11.3 and 3.10.8\r\n\r\n```sh\r\ndatasette data/db.sqlite\r\n```\r\n\r\n```console\r\n/tester/.venv/lib/python3.11/site-packages/pkg_resources/__init__.py:121: DeprecationWarning: pkg_resources is deprecated as an API\r\n warnings.warn(\"pkg_resources is deprecated as an API\", DeprecationWarning)\r\nTraceback (most recent call last):\r\n File \"/tester/.venv/bin/datasette\", line 8, in \r\n sys.exit(cli())\r\n ^^^^^\r\n File \"/tester/.venv/lib/python3.11/site-packages/click/core.py\", line 1130, in __call__\r\n return self.main(*args, **kwargs)\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/tester/.venv/lib/python3.11/site-packages/click/core.py\", line 1055, in main\r\n rv = self.invoke(ctx)\r\n ^^^^^^^^^^^^^^^^\r\n File \"/tester/.venv/lib/python3.11/site-packages/click/core.py\", line 1657, in invoke\r\n return _process_result(sub_ctx.command.invoke(sub_ctx))\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/tester/.venv/lib/python3.11/site-packages/click/core.py\", line 1404, in invoke\r\n return ctx.invoke(self.callback, **ctx.params)\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/tester/.venv/lib/python3.11/site-packages/click/core.py\", line 760, in invoke\r\n return __callback(*args, **kwargs)\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/tester/.venv/lib/python3.11/site-packages/datasette/cli.py\", line 143, in wrapped\r\n return fn(*args, **kwargs)\r\n ^^^^^^^^^^^^^^^^^^^\r\n File \"/tester/.venv/lib/python3.11/site-packages/datasette/cli.py\", line 615, in serve\r\n asyncio.get_event_loop().run_until_complete(check_databases(ds))\r\n File \"/Users/mv/.pyenv/versions/3.11.3/lib/python3.11/asyncio/base_events.py\", line 653, in run_until_complete\r\n return future.result()\r\n ^^^^^^^^^^^^^^^\r\n File \"/tester/.venv/lib/python3.11/site-packages/datasette/cli.py\", line 660, in check_databases\r\n await database.execute_fn(check_connection)\r\n File \"/tester/.venv/lib/python3.11/site-packages/datasette/database.py\", line 213, in execute_fn\r\n return await asyncio.get_event_loop().run_in_executor(\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/Users/mv/.pyenv/versions/3.11.3/lib/python3.11/concurrent/futures/thread.py\", line 58, in run\r\n result = self.fn(*self.args, **self.kwargs)\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/tester/.venv/lib/python3.11/site-packages/datasette/database.py\", line 211, in in_thread\r\n return fn(conn)\r\n ^^^^^^^^\r\n File \"/tester/.venv/lib/python3.11/site-packages/datasette/utils/__init__.py\", line 951, in check_connection\r\n for r in conn.execute(\r\n ^^^^^^^^^^^^^\r\nsqlite3.OperationalError: unable to open database file\r\n```\r\n\r\n
\r\n\r\n\r\n
\r\nWorks on 3.10.7, 3.10.6\r\n\r\n```sh\r\n# create new shell with 3.10.7 / 3.10.6\r\nlitestream restore -o data/db.sqlite s3://mytestbucketxx/db\r\ndatasette data/db.sqlite\r\n# ...\r\n# INFO: Uvicorn running on http://127.0.0.1:8001 (Press CTRL+C to quit)\r\n```\r\n\r\n
\r\n\r\nIn both scenarios, the only dependencies were the pinned python version and the latest Datasette version 0.64.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2067/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1674322631, "node_id": "PR_kwDOBm6k_c5OpEz_", "number": 2061, "title": "Add \"Packaging a plugin using Poetry\" section in docs", "user": {"value": 1238873, "label": "rclement"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-04-19T07:23:28Z", "updated_at": "2023-04-19T07:27:18Z", "closed_at": null, "author_association": "FIRST_TIME_CONTRIBUTOR", "pull_request": "simonw/datasette/pulls/2061", "body": "This PR adds a new section about packaging a plugin using `poetry` within the \"Writing plugins\" page of the documentation.\r\n\r\n\r\n----\n:books: Documentation preview :books:: https://datasette--2061.org.readthedocs.build/en/2061/\n\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "pull", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2061/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": 0, "state_reason": null} {"id": 1661860507, "node_id": "PR_kwDOBm6k_c5N_bMw", "number": 2056, "title": "GitHub Action to lint Python code with ruff", "user": {"value": 3709715, "label": "cclauss"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 6, "created_at": "2023-04-11T06:41:27Z", "updated_at": "2023-04-15T14:24:46Z", "closed_at": null, "author_association": "FIRST_TIME_CONTRIBUTOR", "pull_request": "simonw/datasette/pulls/2056", "body": "[Ruff](https://beta.ruff.rs/) supports [over 500 lint rules](https://beta.ruff.rs/docs/rules) and can be used to replace [Flake8](https://pypi.org/project/flake8/) (plus dozens of plugins), [isort](https://pypi.org/project/isort/), [pydocstyle](https://pypi.org/project/pydocstyle/), [yesqa](https://github.com/asottile/yesqa), [eradicate](https://pypi.org/project/eradicate/), [pyupgrade](https://pypi.org/project/pyupgrade/), and [autoflake](https://pypi.org/project/autoflake/), all while executing (in Rust) tens or hundreds of times faster than any individual tool.\r\n\r\nThe ruff Action uses minimal steps to run in ~5 seconds, rapidly providing intuitive GitHub Annotations to contributors.\r\n\r\n![image](https://user-images.githubusercontent.com/3709715/223758136-afc386d2-70aa-4eff-953a-2c2d82ceea23.png)\r\n\r\n\r\n----\n:books: Documentation preview :books:: https://datasette--2056.org.readthedocs.build/en/2056/\n\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "pull", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2056/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": 0, "state_reason": null} {"id": 1617602868, "node_id": "I_kwDOJHON9s5gaqk0", "number": 6, "title": "Character encoding problem", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 2, "created_at": "2023-03-09T16:44:34Z", "updated_at": "2023-04-14T15:22:09Z", "closed_at": null, "author_association": "MEMBER", "pull_request": null, "body": "I ran against a recent note with this in it:\r\n\r\n> Or just \"Actions \u2699\ufe0f \"\r\n\r\nAnd got back:\r\n\r\n\"image\"\r\n\r\n> `Actions \u201a\u00f6\u00f4\u00d4\u220f\u00e8`\r\n\r\nPasting that into https://ftfy.vercel.app/?s=Actions+%E2%80%9A%C3%B6%C3%B4%C3%94%E2%88%8F%C3%A8+ gives this:\r\n\r\n```python\r\ns = 'Actions \u00e2\\x80\\x9a\u00c3\u00b6\u00c3\u00b4\u00c3\\x94\u00e2\\x88\\x8f\u00c3\u00a8'\r\ns = s.encode('latin-1')\r\ns = s.decode('utf-8')\r\ns = s.encode('macroman')\r\ns = s.decode('utf-8')\r\nprint(s)\r\n```\r\n\"image\"\r\n", "repo": {"value": 611552758, "label": "apple-notes-to-sqlite"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/dogsheep/apple-notes-to-sqlite/issues/6/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1663399821, "node_id": "I_kwDOBm6k_c5jJXeN", "number": 2058, "title": "500 \"attempt to write a readonly database\" error caused by \"PRAGMA schema_version\"", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 9, "created_at": "2023-04-11T23:57:50Z", "updated_at": "2023-04-13T16:35:21Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "I've not been able to replicate this myself yet, but I've seen log files from a user affected by it.\r\n\r\n```\r\nFile \"/usr/local/lib/python3.11/site-packages/datasette/views/base.py\", line 89, in dispatch_request\r\nawait self.ds.refresh_schemas()\r\nFile \"/usr/local/lib/python3.11/site-packages/datasette/app.py\", line 371, in refresh_schemas\r\nawait self._refresh_schemas()\r\nFile \"/usr/local/lib/python3.11/site-packages/datasette/app.py\", line 386, in _refresh_schemas\r\nschema_version = (await db.execute(\"PRAGMA schema_version\")).first()[0]\r\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\nFile \"/usr/local/lib/python3.11/site-packages/datasette/database.py\", line 267, in execute\r\nresults = await self.execute_fn(sql_operation_in_thread)\r\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\nFile \"/usr/local/lib/python3.11/site-packages/datasette/database.py\", line 213, in execute_fn\r\nreturn await asyncio.get_event_loop().run_in_executor(\r\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\nFile \"/usr/local/lib/python3.11/concurrent/futures/thread.py\", line 58, in run\r\nresult = self.fn(*self.args, **self.kwargs)\r\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\nFile \"/usr/local/lib/python3.11/site-packages/datasette/database.py\", line 211, in in_thread\r\nreturn fn(conn)\r\n^^^^^^^^\r\nFile \"/usr/local/lib/python3.11/site-packages/datasette/database.py\", line 237, in sql_operation_in_thread\r\ncursor.execute(sql, params if params is not None else {})\r\nsqlite3.OperationalError: attempt to write a readonly database\r\n```\r\nThat's running the official Datasette Docker image on https://fly.io/ - it's causing 500 errors on every page of their site.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2058/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1665510265, "node_id": "I_kwDOBm6k_c5jRat5", "number": 2060, "title": "Clean up a bunch of warnings from ruff", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-04-13T01:23:02Z", "updated_at": "2023-04-13T01:23:02Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "See:\r\n- #2056\r\n\r\n`ruff` spots a bunch of warnings about things like unused variables - would be good to clean up as many of these as possible.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2060/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1665053646, "node_id": "I_kwDOBm6k_c5jPrPO", "number": 2059, "title": "\"Deceptive site ahead\" alert on Heroku deployment", "user": {"value": 1186275, "label": "mtdukes"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2023-04-12T18:34:51Z", "updated_at": "2023-04-13T01:13:01Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "I deployed a fairly basic instance of Datasette (`datasette-auth-passwords` is the only plugin) using Heroku. The deployed URL now gives a \"Deceptive site ahead\" warning to users.\r\n\r\nIs there way around this? Maybe a way to add ownership verification [through Google's search console](https://search.google.com/search-console/welcome)? ", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2059/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1373210675, "node_id": "I_kwDODD6af85R2Ygz", "number": 13, "title": "fails before generating views. ERR: table sqlite_master may not be modified", "user": {"value": 116795, "label": "pax"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 4, "created_at": "2022-09-14T15:41:50Z", "updated_at": "2023-04-11T03:46:17Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "generates checkins.db but seems to fail before generating views \r\n\r\nnote: it worked on an Ubuntu WSL but fails on macOS 12.5.1\r\n\r\nlater edit: I suspect this is a problem with my local set-up, `dogsheep-beta index` also throws the same error\r\n\r\nfull error:\r\n\r\n Importing 2591 checkins [###################################-] 98% 00:00:00\r\n Traceback (most recent call last):\r\n File \"/Users/pax/devbox/envAll/bin/swarm-to-sqlite\", line 8, in \r\n sys.exit(cli())\r\n File \"/Users/pax/devbox/envAll/lib/python3.8/site-packages/click/core.py\", line 829, in __call__\r\n return self.main(*args, **kwargs)\r\n File \"/Users/pax/devbox/envAll/lib/python3.8/site-packages/click/core.py\", line 782, in main\r\n rv = self.invoke(ctx)\r\n File \"/Users/pax/devbox/envAll/lib/python3.8/site-packages/click/core.py\", line 1066, in invoke\r\n return ctx.invoke(self.callback, **ctx.params)\r\n File \"/Users/pax/devbox/envAll/lib/python3.8/site-packages/click/core.py\", line 610, in invoke\r\n return callback(*args, **kwargs)\r\n File \"/Users/pax/devbox/envAll/lib/python3.8/site-packages/swarm_to_sqlite/cli.py\", line 77, in cli\r\n ensure_foreign_keys(db)\r\n File \"/Users/pax/devbox/envAll/lib/python3.8/site-packages/swarm_to_sqlite/utils.py\", line 145, in ensure_foreign_keys\r\n db[fk.table].add_foreign_key(fk.column, fk.other_table, fk.other_column)\r\n File \"/Users/pax/devbox/envAll/lib/python3.8/site-packages/sqlite_utils/db.py\", line 2123, in add_foreign_key\r\n self.db.add_foreign_keys([(self.name, column, other_table, other_column)])\r\n File \"/Users/pax/devbox/envAll/lib/python3.8/site-packages/sqlite_utils/db.py\", line 1086, in add_foreign_keys\r\n cursor.execute(\r\n sqlite3.OperationalError: table sqlite_master may not be modified", "repo": {"value": 205429375, "label": "swarm-to-sqlite"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/dogsheep/swarm-to-sqlite/issues/13/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1657861026, "node_id": "I_kwDOBm6k_c5i0POi", "number": 2054, "title": "Make detailed notes on how table, query and row views work right now", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 13, "created_at": "2023-04-06T18:21:09Z", "updated_at": "2023-04-07T20:14:38Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "Research to help influence the following:\r\n- #2049 \r\n- #2053 \r\n- #2050 \r\n- #262 ", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2054/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1646734246, "node_id": "I_kwDOBm6k_c5iJyum", "number": 2049, "title": "Custom SQL queries should use new JSON ?_extra= format", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": {"value": 8755003, "label": "Datasette 1.0a-next"}, "comments": 4, "created_at": "2023-03-30T00:42:53Z", "updated_at": "2023-04-05T23:29:27Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "Related:\r\n- #262\r\n\r\nI've made the change to the table view, now I need the new format to work for arbitrary SQL queries too.\r\n\r\nNote that this incorporates both arbitrary SQL queries and canned queries.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2049/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1650984552, "node_id": "PR_kwDOJHON9s5NbyYN", "number": 13, "title": "use universal command", "user": {"value": 14314871, "label": "amlestin"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-04-02T15:10:54Z", "updated_at": "2023-04-02T15:37:34Z", "closed_at": null, "author_association": "FIRST_TIME_CONTRIBUTOR", "pull_request": "dogsheep/apple-notes-to-sqlite/pulls/13", "body": null, "repo": {"value": 611552758, "label": "apple-notes-to-sqlite"}, "type": "pull", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/dogsheep/apple-notes-to-sqlite/issues/13/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": 0, "state_reason": null} {"id": 1650981564, "node_id": "I_kwDOJHON9s5iZ_q8", "number": 12, "title": "Error running pytest", "user": {"value": 14314871, "label": "amlestin"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-04-02T15:02:36Z", "updated_at": "2023-04-02T15:07:10Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "`______________________________________________________ ERROR collecting tests/test_apple_notes_to_sqlite.py _______________________________________________________\r\nImportError while importing test module '/Users/lol/development/apple-notes-to-sqlite/tests/test_apple_notes_to_sqlite.py'.\r\nHint: make sure your test modules/packages have valid Python names.\r\nTraceback:\r\n/opt/homebrew/Cellar/python@3.9/3.9.16/Frameworks/Python.framework/Versions/3.9/lib/python3.9/importlib/__init__.py:127: in import_module\r\n return _bootstrap._gcd_import(name[level:], package, level)\r\ntests/test_apple_notes_to_sqlite.py:2: in \r\n from apple_notes_to_sqlite.cli import cli, COUNT_SCRIPT, FOLDERS_SCRIPT\r\nE ModuleNotFoundError: No module named 'apple_notes_to_sqlite'`\r\n\r\nSolution:\r\nThis is likely a PYTHONPATH issue due to having pytest installed both globally and in the venv. We can guarantee the tests run by adding the current directory to sys.path automatically using\r\n\r\n`python -m pytest`\r\n\r\nThe alternative is to activate the venv, install pytest, deactivate, then activate the venv again (https://stackoverflow.com/questions/35045038/how-do-i-use-pytest-with-virtualenv)", "repo": {"value": 611552758, "label": "apple-notes-to-sqlite"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/dogsheep/apple-notes-to-sqlite/issues/12/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 907795562, "node_id": "MDU6SXNzdWU5MDc3OTU1NjI=", "number": 265, "title": "Using enable_fts before search term", "user": {"value": 36287, "label": "prabhur"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2021-06-01T01:43:34Z", "updated_at": "2023-04-01T17:27:18Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "Many thanks for the sqlite-utils suite of utilities. Has made my life much much easier. \r\nI used this to create a table and enable FTS. All works fine. The datasette utility detects FTS and shows a text box. Searching for a term using that interface works well.\r\n\r\nHowever, when I start to use features by following https://www.sqlite.org/fts5.html section **\"3. Full-text Query Syntax\"** I seem to run into issues that I suspect is due to `escape_fts` wrapper function.\r\n\r\nAs an example, if i search for the term `\"^\u0b95\u0bc1\u0b95\u0bc8\" `on the text box in datasette it produces 140 results. However, when i tweak the query produced by datasette to not use \"escape_fts\" it produces 5 results.\r\n\r\nSimilarly, when I try to restrict the search to a single column in FTS using a spec like `{title : ^\u0b95\u0bc1\u0b95\u0bc8}` it returns no rows. The same thing pulls results when used without `escape_fts`. The text in the table is in Tamil language and the search term is a Tamil word.\r\n\r\n```\r\n ...\r\n where\r\n posts_fts match escape_fts(:search)\r\n```\r\nvs\r\n\r\n```\r\n ...\r\n where\r\n posts_fts match (:search)\r\n```\r\n\r\nAny ideas why? How can I get the benefits of both escaping as well as utilizing different facets of providing / controlling search terms? Thanks.", "repo": {"value": 140912432, "label": "sqlite-utils"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/sqlite-utils/issues/265/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 702386948, "node_id": "MDU6SXNzdWU3MDIzODY5NDg=", "number": 159, "title": ".delete_where() does not auto-commit (unlike .insert() or .upsert())", "user": {"value": 11712349, "label": "spdkils"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 9, "created_at": "2020-09-16T01:55:52Z", "updated_at": "2023-04-01T17:21:05Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "When you use the delete_where() function on a table, it never commits....\r\n\r\nIs that intentional?", "repo": {"value": 140912432, "label": "sqlite-utils"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/sqlite-utils/issues/159/reactions\", \"total_count\": 1, \"+1\": 1, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1649791661, "node_id": "I_kwDOBm6k_c5iVdKt", "number": 2050, "title": "Row page JSON should use new ?_extra= format", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": {"value": 8755003, "label": "Datasette 1.0a-next"}, "comments": 1, "created_at": "2023-03-31T17:56:53Z", "updated_at": "2023-03-31T17:59:49Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "https://latest.datasette.io/fixtures/facetable/2.json\r\n\r\nRelated:\r\n- #2049\r\n- #1709 ", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2050/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1649793525, "node_id": "I_kwDOBm6k_c5iVdn1", "number": 2051, "title": "`?_extra=row_urls` for table pages", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-03-31T17:58:36Z", "updated_at": "2023-03-31T17:58:36Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "Provides URLs to the JSON version of those rows. Maybe it persists the `?_shape=` option too? Not sure about that.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2051/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1615692818, "node_id": "I_kwDOBm6k_c5gTYQS", "number": 2035, "title": "Potential feature: special support for `?a=1&a=2` on the query page", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": {"value": 3268330, "label": "Datasette 1.0"}, "comments": 14, "created_at": "2023-03-08T18:05:03Z", "updated_at": "2023-03-31T16:09:08Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "From a discussion on Discord: https://discord.com/channels/823971286308356157/996877076982415491/1082789517062320138\r\n\r\nThe key idea is to make it easier for people to implement `where id in (...)` that's populated from query string arguments.\r\n\r\nWhat if you could add `?id=11&id=32&id=62` to the URL and have that made available as a list that can be used in the query?", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2035/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1531991339, "node_id": "I_kwDOBm6k_c5bUFUr", "number": 1989, "title": "Suggestion: Hiding columns", "user": {"value": 116795, "label": "pax"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 3, "created_at": "2023-01-13T09:33:32Z", "updated_at": "2023-03-31T06:18:05Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "As there's the possibility of [hiding tables](https://docs.datasette.io/en/stable/metadata.html#hiding-tables) - I've run into the **need of hiding specific columns** - data that's either not relevant for public or can't be shown due to privacy reasons. ", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1989/reactions\", \"total_count\": 1, \"+1\": 1, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1515883470, "node_id": "I_kwDOC8tyDs5aWovO", "number": 24, "title": "DOC: xml.etree.ElementTree.ParseError due to healthkit version 12 ", "user": {"value": 6231413, "label": "mmngreco"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 2, "created_at": "2023-01-01T23:00:38Z", "updated_at": "2023-03-30T10:17:31Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "Hi @simonw \r\n\r\nI hope you find this issue ok, the idea is provide some documentation to other users like me about how to solve this problem and save some time.\r\n\r\nFollowing the instructions from the `README.md` I've faced this error:\r\n\r\n```bash\r\n(venv) mgreco@pop-os apple-health master* (23:44|0s)\r\n$ healthkit-to-sqlite apple_health_export/export.xml healthkit.db --xml\r\nImporting from HealthKit [------------------------------------] 0%\r\nTraceback (most recent call last):\r\n File \"/home/mgreco/github/mmngreco/apple-health/venv/bin/healthkit-to-sqlite\", line 33, in \r\n sys.exit(load_entry_point('healthkit-to-sqlite', 'console_scripts', 'healthkit-to-sqlite')())\r\n File \"/home/mgreco/github/mmngreco/apple-health/venv/lib/python3.10/site-packages/click/core.py\", line 1130, in __call__\r\n return self.main(*args, **kwargs)\r\n File \"/home/mgreco/github/mmngreco/apple-health/venv/lib/python3.10/site-packages/click/core.py\", line 1055, in main\r\n rv = self.invoke(ctx)\r\n File \"/home/mgreco/github/mmngreco/apple-health/venv/lib/python3.10/site-packages/click/core.py\", line 1404, in invoke\r\n return ctx.invoke(self.callback, **ctx.params)\r\n File \"/home/mgreco/github/mmngreco/apple-health/venv/lib/python3.10/site-packages/click/core.py\", line 760, in invoke\r\n return __callback(*args, **kwargs)\r\n File \"/home/mgreco/github/mmngreco/apple-health/.deps/healthkit-to-sqlite/healthkit_to_sqlite/cli.py\", line 57, in cli\r\n convert_xml_to_sqlite(fp, db, progress_callback=bar.update, zipfile=zf)\r\n File \"/home/mgreco/github/mmngreco/apple-health/.deps/healthkit-to-sqlite/healthkit_to_sqlite/utils.py\", line 25, in convert_xml_to_sqlite\r\n for tag, el in find_all_tags(\r\n File \"/home/mgreco/github/mmngreco/apple-health/.deps/healthkit-to-sqlite/healthkit_to_sqlite/utils.py\", line 12, in find_all_tags\r\n for event, el in parser.read_events():\r\n File \"/home/mgreco/github/mmngreco/apple-health/venv/lib/python3.10/xml/etree/ElementTree.py\", line 1324, in read_events\r\n raise event\r\n File \"/home/mgreco/github/mmngreco/apple-health/venv/lib/python3.10/xml/etree/ElementTree.py\", line 1296, in feed\r\n self._parser.feed(data)\r\nxml.etree.ElementTree.ParseError: syntax error: line 156, column 0\r\n```\r\n\r\nSo, after debugging and searching on internet I found this useful link: https://discussions.apple.com/thread/254202523 (etresoft, the real hero). Which basically says that the xml given by the health app (healthkit version 12) has some bugs but fortunately, they can be solved with a couple of commads:\r\n\r\n1. Uncompress the zip and move the new folder where `export.xml` is.\r\n1. Create a `patch.txt` with the following content\r\n\r\n ```diff\r\n --- export.xml\t2022-09-18 15:17:09.000000000 -0400\r\n +++ export-fixed.xml\t2022-09-18 16:37:08.000000000 -0400\r\n @@ -15,6 +15,7 @@\r\n HKCharacteristicTypeIdentifierBiologicalSex CDATA #REQUIRED\r\n HKCharacteristicTypeIdentifierBloodType CDATA #REQUIRED\r\n HKCharacteristicTypeIdentifierFitzpatrickSkinType CDATA #REQUIRED\r\n + HKCharacteristicTypeIdentifierCardioFitnessMedicationsUse CDATA #IMPLIED\r\n >\r\n \r\n \r\n -\r\n +\r\n \r\n -\r\n +\r\n \r\n \r\n \r\n \r\n \r\n - device CDATA #IMPLIED\r\n -\r\n -\r\n ->\r\n ]>\r\n \r\n \r\n ```\r\n1. Apply the path with the command: `patch < patch.txt`\r\n1. Fix endDates with the command `sed 's/startDate/endDate/2' export.xml > export-fixed.xml`\r\n1. Try again `healthkit-to-sqlite export-fixed.xml healthkit.db --xml`", "repo": {"value": 197882382, "label": "healthkit-to-sqlite"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/dogsheep/healthkit-to-sqlite/issues/24/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1613974869, "node_id": "PR_kwDOBm6k_c5LgPS-", "number": 2034, "title": "remove an unused `app` var in cli.py", "user": {"value": 4370201, "label": "wenhoujx"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 2, "created_at": "2023-03-07T18:19:05Z", "updated_at": "2023-03-29T20:56:20Z", "closed_at": null, "author_association": "FIRST_TIME_CONTRIBUTOR", "pull_request": "simonw/datasette/pulls/2034", "body": "this var `app` isn't actually used? unless init it does some side-effect outside of the event loop, idon't think it's necessary. \r\n\r\nFeel free to ignore this PR if the deleted line actually does something.\r\n\r\n\r\n----\n:books: Documentation preview :books:: https://datasette--2034.org.readthedocs.build/en/2034/\n\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "pull", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2034/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": 0, "state_reason": null} {"id": 1641117021, "node_id": "PR_kwDODtX3eM5M66op", "number": 6, "title": "Add permalink virtual field to items table", "user": {"value": 1231935, "label": "xavdid"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2023-03-26T22:22:38Z", "updated_at": "2023-03-29T18:38:52Z", "closed_at": null, "author_association": "FIRST_TIME_CONTRIBUTOR", "pull_request": "dogsheep/hacker-news-to-sqlite/pulls/6", "body": "I added a virtual column (no storage overhead) to the output that easily links back to the source. It works nicely out of the box with datasette:\r\n\r\n![](https://cdn.zappy.app/faf43661d539ee0fee02c0421de22d65.png)\r\n\r\nI got bit a bit by https://github.com/simonw/sqlite-utils/issues/411, so I went with a manual `table_xinfo` and creating the table via execute. Happy to adjust if that issue moves, but this seems like it works.\r\n\r\nI also added my best-guess instructions for local development on this package. I'm shooting in the dark, so feel free to replace with how you work on it locally.", "repo": {"value": 248903544, "label": "hacker-news-to-sqlite"}, "type": "pull", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/dogsheep/hacker-news-to-sqlite/issues/6/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": 0, "state_reason": null} {"id": 1646068413, "node_id": "I_kwDOBm6k_c5iHQK9", "number": 2048, "title": "Test failures encountered while packaging for GNU Guix", "user": {"value": 8332263, "label": "Apteryks"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-03-29T15:36:54Z", "updated_at": "2023-03-29T15:36:54Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "Hello,\r\n\r\nWhile reviewing a packaged submitted to Guix to add `datasette`, the test suite produces the following errors:\r\n```\r\n=================================== FAILURES ===================================\r\n_________________________ test_row_strange_table_name __________________________\r\n[gw21] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client = \r\n\r\n def test_row_strange_table_name(app_client):\r\n response = app_client.get(\r\n \"/fixtures/table~2Fwith~2Fslashes~2Ecsv/3.json?_shape=objects\"\r\n )\r\n> assert response.status == 200\r\nE assert 400 == 200\r\nE + where 400 = .status\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_api.py:701: AssertionError\r\n----------------------------- Captured stderr call -----------------------------\r\nERROR: conn=, sql = 'select rowid, * from [table%7E2Fwith%7E2Fslashes%7E2Ecsv] where \"rowid\"=:p0', params = {'p0': '3'}: no such table: table%7E2Fwith%7E2Fslashes%7E2Ecsv\r\n_______________ test_database_page_for_database_with_dot_in_name _______________\r\n[gw15] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client_with_dot = \r\n\r\n def test_database_page_for_database_with_dot_in_name(app_client_with_dot):\r\n response = app_client_with_dot.get(\"/fixtures~2Edot.json\")\r\n> assert response.status == 200\r\nE assert 302 == 200\r\nE + where 302 = .status\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_api.py:633: AssertionError\r\n___________________ test_tilde_encoded_database_names[fo%o] ____________________\r\n[gw6] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\ndb_name = 'fo%o'\r\n\r\n @pytest.mark.asyncio\r\n @pytest.mark.parametrize(\"db_name\", (\"foo\", r\"fo%o\", \"f~/c.d\"))\r\n async def test_tilde_encoded_database_names(db_name):\r\n ds = Datasette()\r\n ds.add_memory_database(db_name)\r\n response = await ds.client.get(\"/.json\")\r\n assert db_name in response.json().keys()\r\n path = response.json()[db_name][\"path\"]\r\n # And the JSON for that database\r\n response2 = await ds.client.get(path + \".json\")\r\n> assert response2.status_code == 200\r\nE assert 302 == 200\r\nE + where 302 = .status_code\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_api.py:983: AssertionError\r\n__________________ test_tilde_encoded_database_names[f~/c.d] ___________________\r\n[gw7] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\ndb_name = 'f~/c.d'\r\n\r\n @pytest.mark.asyncio\r\n @pytest.mark.parametrize(\"db_name\", (\"foo\", r\"fo%o\", \"f~/c.d\"))\r\n async def test_tilde_encoded_database_names(db_name):\r\n ds = Datasette()\r\n ds.add_memory_database(db_name)\r\n response = await ds.client.get(\"/.json\")\r\n assert db_name in response.json().keys()\r\n path = response.json()[db_name][\"path\"]\r\n # And the JSON for that database\r\n response2 = await ds.client.get(path + \".json\")\r\n> assert response2.status_code == 200\r\nE assert 302 == 200\r\nE + where 302 = .status_code\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_api.py:983: AssertionError\r\n______________ test_database_with_space_in_name[/searchable.json] ______________\r\n[gw21] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client_two_attached_databases = \r\npath = '/searchable.json'\r\n\r\n @pytest.mark.parametrize(\r\n \"path\",\r\n (\r\n \"/\",\r\n \".json\",\r\n \"/searchable\",\r\n \"/searchable.json\",\r\n \"/searchable_view\",\r\n \"/searchable_view.json\",\r\n ),\r\n )\r\n def test_database_with_space_in_name(app_client_two_attached_databases, path):\r\n> response = app_client_two_attached_databases.get(\r\n \"/extra~20database\" + path, follow_redirects=True\r\n )\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_api.py:920: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:223: in __call__\r\n return call_result.result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:438: in result\r\n return self.__get_result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:390: in __get_result\r\n raise self._exception\r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:292: in main_wrap\r\n result = await self.awaitable(*args, **kwargs)\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:66: in get\r\n return await self._request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:156: in _request\r\n httpx_response = await self.ds.client.request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/app.py:1602: in request\r\n return await client.request(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1527: in request\r\n return await self.send(request, auth=auth, follow_redirects=follow_redirects)\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1614: in send\r\n response = await self._send_handling_auth(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1642: in _send_handling_auth\r\n response = await self._send_handling_redirects(\r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n\r\nself = \r\nrequest = \r\nfollow_redirects = True\r\nhistory = [, , , , , , ...]\r\n\r\n async def _send_handling_redirects(\r\n self,\r\n request: Request,\r\n follow_redirects: bool,\r\n history: typing.List[Response],\r\n ) -> Response:\r\n while True:\r\n if len(history) > self.max_redirects:\r\n> raise TooManyRedirects(\r\n \"Exceeded maximum allowed redirects.\", request=request\r\n )\r\nE httpx.TooManyRedirects: Exceeded maximum allowed redirects.\r\n\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1672: TooManyRedirects\r\n___________________ test_database_with_space_in_name[.json] ____________________\r\n[gw19] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client_two_attached_databases = \r\npath = '.json'\r\n\r\n @pytest.mark.parametrize(\r\n \"path\",\r\n (\r\n \"/\",\r\n \".json\",\r\n \"/searchable\",\r\n \"/searchable.json\",\r\n \"/searchable_view\",\r\n \"/searchable_view.json\",\r\n ),\r\n )\r\n def test_database_with_space_in_name(app_client_two_attached_databases, path):\r\n> response = app_client_two_attached_databases.get(\r\n \"/extra~20database\" + path, follow_redirects=True\r\n )\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_api.py:920: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:223: in __call__\r\n return call_result.result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:438: in result\r\n return self.__get_result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:390: in __get_result\r\n raise self._exception\r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:292: in main_wrap\r\n result = await self.awaitable(*args, **kwargs)\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:66: in get\r\n return await self._request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:156: in _request\r\n httpx_response = await self.ds.client.request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/app.py:1602: in request\r\n return await client.request(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1527: in request\r\n return await self.send(request, auth=auth, follow_redirects=follow_redirects)\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1614: in send\r\n response = await self._send_handling_auth(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1642: in _send_handling_auth\r\n response = await self._send_handling_redirects(\r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n\r\nself = \r\nrequest = \r\nfollow_redirects = True\r\nhistory = [, , , , , , ...]\r\n\r\n async def _send_handling_redirects(\r\n self,\r\n request: Request,\r\n follow_redirects: bool,\r\n history: typing.List[Response],\r\n ) -> Response:\r\n while True:\r\n if len(history) > self.max_redirects:\r\n> raise TooManyRedirects(\r\n \"Exceeded maximum allowed redirects.\", request=request\r\n )\r\nE httpx.TooManyRedirects: Exceeded maximum allowed redirects.\r\n\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1672: TooManyRedirects\r\n______________ test_database_with_space_in_name[/searchable_view] ______________\r\n[gw22] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client_two_attached_databases = \r\npath = '/searchable_view'\r\n\r\n @pytest.mark.parametrize(\r\n \"path\",\r\n (\r\n \"/\",\r\n \".json\",\r\n \"/searchable\",\r\n \"/searchable.json\",\r\n \"/searchable_view\",\r\n \"/searchable_view.json\",\r\n ),\r\n )\r\n def test_database_with_space_in_name(app_client_two_attached_databases, path):\r\n> response = app_client_two_attached_databases.get(\r\n \"/extra~20database\" + path, follow_redirects=True\r\n )\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_api.py:920: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:223: in __call__\r\n return call_result.result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:438: in result\r\n return self.__get_result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:390: in __get_result\r\n raise self._exception\r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:292: in main_wrap\r\n result = await self.awaitable(*args, **kwargs)\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:66: in get\r\n return await self._request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:156: in _request\r\n httpx_response = await self.ds.client.request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/app.py:1602: in request\r\n return await client.request(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1527: in request\r\n return await self.send(request, auth=auth, follow_redirects=follow_redirects)\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1614: in send\r\n response = await self._send_handling_auth(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1642: in _send_handling_auth\r\n response = await self._send_handling_redirects(\r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n\r\nself = \r\nrequest = \r\nfollow_redirects = True\r\nhistory = [, , , , , , ...]\r\n\r\n async def _send_handling_redirects(\r\n self,\r\n request: Request,\r\n follow_redirects: bool,\r\n history: typing.List[Response],\r\n ) -> Response:\r\n while True:\r\n if len(history) > self.max_redirects:\r\n> raise TooManyRedirects(\r\n \"Exceeded maximum allowed redirects.\", request=request\r\n )\r\nE httpx.TooManyRedirects: Exceeded maximum allowed redirects.\r\n\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1672: TooManyRedirects\r\n_____________________ test_database_with_space_in_name[/] ______________________\r\n[gw18] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client_two_attached_databases = \r\npath = '/'\r\n\r\n @pytest.mark.parametrize(\r\n \"path\",\r\n (\r\n \"/\",\r\n \".json\",\r\n \"/searchable\",\r\n \"/searchable.json\",\r\n \"/searchable_view\",\r\n \"/searchable_view.json\",\r\n ),\r\n )\r\n def test_database_with_space_in_name(app_client_two_attached_databases, path):\r\n> response = app_client_two_attached_databases.get(\r\n \"/extra~20database\" + path, follow_redirects=True\r\n )\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_api.py:920: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:223: in __call__\r\n return call_result.result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:438: in result\r\n return self.__get_result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:390: in __get_result\r\n raise self._exception\r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:292: in main_wrap\r\n result = await self.awaitable(*args, **kwargs)\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:66: in get\r\n return await self._request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:156: in _request\r\n httpx_response = await self.ds.client.request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/app.py:1602: in request\r\n return await client.request(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1527: in request\r\n return await self.send(request, auth=auth, follow_redirects=follow_redirects)\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1614: in send\r\n response = await self._send_handling_auth(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1642: in _send_handling_auth\r\n response = await self._send_handling_redirects(\r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n\r\nself = \r\nrequest = \r\nfollow_redirects = True\r\nhistory = [, , , , , , ...]\r\n\r\n async def _send_handling_redirects(\r\n self,\r\n request: Request,\r\n follow_redirects: bool,\r\n history: typing.List[Response],\r\n ) -> Response:\r\n while True:\r\n if len(history) > self.max_redirects:\r\n> raise TooManyRedirects(\r\n \"Exceeded maximum allowed redirects.\", request=request\r\n )\r\nE httpx.TooManyRedirects: Exceeded maximum allowed redirects.\r\n\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1672: TooManyRedirects\r\n________________ test_database_with_space_in_name[/searchable] _________________\r\n[gw20] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client_two_attached_databases = \r\npath = '/searchable'\r\n\r\n @pytest.mark.parametrize(\r\n \"path\",\r\n (\r\n \"/\",\r\n \".json\",\r\n \"/searchable\",\r\n \"/searchable.json\",\r\n \"/searchable_view\",\r\n \"/searchable_view.json\",\r\n ),\r\n )\r\n def test_database_with_space_in_name(app_client_two_attached_databases, path):\r\n> response = app_client_two_attached_databases.get(\r\n \"/extra~20database\" + path, follow_redirects=True\r\n )\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_api.py:920: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:223: in __call__\r\n return call_result.result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:438: in result\r\n return self.__get_result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:390: in __get_result\r\n raise self._exception\r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:292: in main_wrap\r\n result = await self.awaitable(*args, **kwargs)\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:66: in get\r\n return await self._request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:156: in _request\r\n httpx_response = await self.ds.client.request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/app.py:1602: in request\r\n return await client.request(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1527: in request\r\n return await self.send(request, auth=auth, follow_redirects=follow_redirects)\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1614: in send\r\n response = await self._send_handling_auth(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1642: in _send_handling_auth\r\n response = await self._send_handling_redirects(\r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n\r\nself = \r\nrequest = \r\nfollow_redirects = True\r\nhistory = [, , , , , , ...]\r\n\r\n async def _send_handling_redirects(\r\n self,\r\n request: Request,\r\n follow_redirects: bool,\r\n history: typing.List[Response],\r\n ) -> Response:\r\n while True:\r\n if len(history) > self.max_redirects:\r\n> raise TooManyRedirects(\r\n \"Exceeded maximum allowed redirects.\", request=request\r\n )\r\nE httpx.TooManyRedirects: Exceeded maximum allowed redirects.\r\n\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1672: TooManyRedirects\r\n___________ test_database_with_space_in_name[/searchable_view.json] ____________\r\n[gw23] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client_two_attached_databases = \r\npath = '/searchable_view.json'\r\n\r\n @pytest.mark.parametrize(\r\n \"path\",\r\n (\r\n \"/\",\r\n \".json\",\r\n \"/searchable\",\r\n \"/searchable.json\",\r\n \"/searchable_view\",\r\n \"/searchable_view.json\",\r\n ),\r\n )\r\n def test_database_with_space_in_name(app_client_two_attached_databases, path):\r\n> response = app_client_two_attached_databases.get(\r\n \"/extra~20database\" + path, follow_redirects=True\r\n )\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_api.py:920: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:223: in __call__\r\n return call_result.result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:438: in result\r\n return self.__get_result()\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/concurrent/futures/_base.py:390: in __get_result\r\n raise self._exception\r\n/gnu/store/mcclmphjgbrgpa0v037a4nlq336482g8-python-asgiref-3.4.1/lib/python3.9/site-packages/asgiref/sync.py:292: in main_wrap\r\n result = await self.awaitable(*args, **kwargs)\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:66: in get\r\n return await self._request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:156: in _request\r\n httpx_response = await self.ds.client.request(\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/app.py:1602: in request\r\n return await client.request(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1527: in request\r\n return await self.send(request, auth=auth, follow_redirects=follow_redirects)\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1614: in send\r\n response = await self._send_handling_auth(\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1642: in _send_handling_auth\r\n response = await self._send_handling_redirects(\r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n\r\nself = \r\nrequest = \r\nfollow_redirects = True\r\nhistory = [, , , , , , ...]\r\n\r\n async def _send_handling_redirects(\r\n self,\r\n request: Request,\r\n follow_redirects: bool,\r\n history: typing.List[Response],\r\n ) -> Response:\r\n while True:\r\n if len(history) > self.max_redirects:\r\n> raise TooManyRedirects(\r\n \"Exceeded maximum allowed redirects.\", request=request\r\n )\r\nE httpx.TooManyRedirects: Exceeded maximum allowed redirects.\r\n\r\n/gnu/store/bj5lb299rfb4cbbq5kczq9imdk9a7y64-python-httpx-0.23.0/lib/python3.9/site-packages/httpx/_client.py:1672: TooManyRedirects\r\n________________ test_weird_database_names[database (1).sqlite] ________________\r\n[gw7] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\ntmpdir = local('/tmp/guix-build-datasette-0.64.2.drv-0/pytest-of-nixbld/pytest-0/popen-gw7/test_weird_database_names_data0')\r\nfilename = 'database (1).sqlite'\r\n\r\n @pytest.mark.parametrize(\r\n \"filename\", [\"test-database (1).sqlite\", \"database (1).sqlite\"]\r\n )\r\n def test_weird_database_names(tmpdir, filename):\r\n # https://github.com/simonw/datasette/issues/1181\r\n runner = CliRunner()\r\n db_path = str(tmpdir / filename)\r\n sqlite3.connect(db_path).execute(\"vacuum\")\r\n result1 = runner.invoke(cli, [db_path, \"--get\", \"/\"])\r\n assert result1.exit_code == 0, result1.output\r\n filename_no_stem = filename.rsplit(\".\", 1)[0]\r\n expected_link = '{}'.format(\r\n tilde_encode(filename_no_stem), filename_no_stem\r\n )\r\n assert expected_link in result1.output\r\n # Now try hitting that database page\r\n result2 = runner.invoke(\r\n cli, [db_path, \"--get\", \"/{}\".format(tilde_encode(filename_no_stem))]\r\n )\r\n> assert result2.exit_code == 0, result2.output\r\nE AssertionError: \r\nE \r\nE assert 1 == 0\r\nE + where 1 = .exit_code\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_cli.py:321: AssertionError\r\n_____________ test_weird_database_names[test-database (1).sqlite] ______________\r\n[gw6] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\ntmpdir = local('/tmp/guix-build-datasette-0.64.2.drv-0/pytest-of-nixbld/pytest-0/popen-gw6/test_weird_database_names_test0')\r\nfilename = 'test-database (1).sqlite'\r\n\r\n @pytest.mark.parametrize(\r\n \"filename\", [\"test-database (1).sqlite\", \"database (1).sqlite\"]\r\n )\r\n def test_weird_database_names(tmpdir, filename):\r\n # https://github.com/simonw/datasette/issues/1181\r\n runner = CliRunner()\r\n db_path = str(tmpdir / filename)\r\n sqlite3.connect(db_path).execute(\"vacuum\")\r\n result1 = runner.invoke(cli, [db_path, \"--get\", \"/\"])\r\n assert result1.exit_code == 0, result1.output\r\n filename_no_stem = filename.rsplit(\".\", 1)[0]\r\n expected_link = '{}'.format(\r\n tilde_encode(filename_no_stem), filename_no_stem\r\n )\r\n assert expected_link in result1.output\r\n # Now try hitting that database page\r\n result2 = runner.invoke(\r\n cli, [db_path, \"--get\", \"/{}\".format(tilde_encode(filename_no_stem))]\r\n )\r\n> assert result2.exit_code == 0, result2.output\r\nE AssertionError: \r\nE \r\nE assert 1 == 0\r\nE + where 1 = .exit_code\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_cli.py:321: AssertionError\r\n_ test_row_html_compound_primary_key[/fixtures/compound_primary_key/a~2Fb,~2Ec~2Dd-expected1] _\r\n[gw11] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client = \r\npath = '/fixtures/compound_primary_key/a~2Fb,~2Ec~2Dd'\r\nexpected = [['a/b', '.c-d', 'c']]\r\n\r\n @pytest.mark.parametrize(\r\n \"path,expected\",\r\n (\r\n (\r\n \"/fixtures/compound_primary_key/a,b\",\r\n [\r\n [\r\n 'a',\r\n 'b',\r\n 'c',\r\n ]\r\n ],\r\n ),\r\n (\r\n \"/fixtures/compound_primary_key/a~2Fb,~2Ec~2Dd\",\r\n [\r\n [\r\n 'a/b',\r\n '.c-d',\r\n 'c',\r\n ]\r\n ],\r\n ),\r\n ),\r\n )\r\n def test_row_html_compound_primary_key(app_client, path, expected):\r\n response = app_client.get(path)\r\n> assert response.status == 200\r\nE assert 302 == 200\r\nE + where 302 = .status\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_html.py:370: AssertionError\r\n_ test_css_classes_on_body[/fixtures/table~2Fwith~2Fslashes~2Ecsv-expected_classes5] _\r\n[gw3] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client = \r\npath = '/fixtures/table~2Fwith~2Fslashes~2Ecsv'\r\nexpected_classes = ['table', 'db-fixtures', 'table-tablewithslashescsv-fa7563']\r\n\r\n @pytest.mark.parametrize(\r\n \"path,expected_classes\",\r\n [\r\n (\"/\", [\"index\"]),\r\n (\"/fixtures\", [\"db\", \"db-fixtures\"]),\r\n (\"/fixtures?sql=select+1\", [\"query\", \"db-fixtures\"]),\r\n (\r\n \"/fixtures/simple_primary_key\",\r\n [\"table\", \"db-fixtures\", \"table-simple_primary_key\"],\r\n ),\r\n (\r\n \"/fixtures/neighborhood_search\",\r\n [\"query\", \"db-fixtures\", \"query-neighborhood_search\"],\r\n ),\r\n (\r\n \"/fixtures/table~2Fwith~2Fslashes~2Ecsv\",\r\n [\"table\", \"db-fixtures\", \"table-tablewithslashescsv-fa7563\"],\r\n ),\r\n (\r\n \"/fixtures/simple_primary_key/1\",\r\n [\"row\", \"db-fixtures\", \"table-simple_primary_key\"],\r\n ),\r\n ],\r\n )\r\n def test_css_classes_on_body(app_client, path, expected_classes):\r\n response = app_client.get(path)\r\n> assert response.status == 200\r\nE assert 302 == 200\r\nE + where 302 = .status\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_html.py:238: AssertionError\r\n_ test_templates_considered[/fixtures/table~2Fwith~2Fslashes~2Ecsv-table-fixtures-tablewithslashescsv-fa7563.html, *table.html] _\r\n[gw3] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client = \r\npath = '/fixtures/table~2Fwith~2Fslashes~2Ecsv'\r\nexpected_considered = 'table-fixtures-tablewithslashescsv-fa7563.html, *table.html'\r\n\r\n @pytest.mark.parametrize(\r\n \"path,expected_considered\",\r\n [\r\n (\"/\", \"*index.html\"),\r\n (\"/fixtures\", \"database-fixtures.html, *database.html\"),\r\n (\r\n \"/fixtures/simple_primary_key\",\r\n \"table-fixtures-simple_primary_key.html, *table.html\",\r\n ),\r\n (\r\n \"/fixtures/table~2Fwith~2Fslashes~2Ecsv\",\r\n \"table-fixtures-tablewithslashescsv-fa7563.html, *table.html\",\r\n ),\r\n (\r\n \"/fixtures/simple_primary_key/1\",\r\n \"row-fixtures-simple_primary_key.html, *row.html\",\r\n ),\r\n ],\r\n )\r\n def test_templates_considered(app_client, path, expected_considered):\r\n response = app_client.get(path)\r\n> assert response.status == 200\r\nE assert 302 == 200\r\nE + where 302 = .status\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_html.py:264: AssertionError\r\n_ test_alternate_url_json[/fixtures/table~2Fwith~2Fslashes~2Ecsv-http://localhost/fixtures/table~2Fwith~2Fslashes~2Ecsv.json] _\r\n[gw21] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client = \r\npath = '/fixtures/table~2Fwith~2Fslashes~2Ecsv'\r\nexpected = 'http://localhost/fixtures/table~2Fwith~2Fslashes~2Ecsv.json'\r\n\r\n @pytest.mark.parametrize(\r\n \"path,expected\",\r\n (\r\n # Instance index page\r\n (\"/\", \"http://localhost/.json\"),\r\n # Table page\r\n (\"/fixtures/facetable\", \"http://localhost/fixtures/facetable.json\"),\r\n (\r\n \"/fixtures/table~2Fwith~2Fslashes~2Ecsv\",\r\n \"http://localhost/fixtures/table~2Fwith~2Fslashes~2Ecsv.json\",\r\n ),\r\n # Row page\r\n (\r\n \"/fixtures/no_primary_key/1\",\r\n \"http://localhost/fixtures/no_primary_key/1.json\",\r\n ),\r\n # Database index page\r\n (\r\n \"/fixtures\",\r\n \"http://localhost/fixtures.json\",\r\n ),\r\n # Custom query page\r\n (\r\n \"/fixtures?sql=select+*+from+facetable\",\r\n \"http://localhost/fixtures.json?sql=select+*+from+facetable\",\r\n ),\r\n # Canned query page\r\n (\r\n \"/fixtures/neighborhood_search?text=town\",\r\n \"http://localhost/fixtures/neighborhood_search.json?text=town\",\r\n ),\r\n # /-/ pages\r\n (\r\n \"/-/plugins\",\r\n \"http://localhost/-/plugins.json\",\r\n ),\r\n ),\r\n )\r\n def test_alternate_url_json(app_client, path, expected):\r\n response = app_client.get(path)\r\n> assert response.status == 200\r\nE assert 302 == 200\r\nE + where 302 = .status\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_html.py:948: AssertionError\r\n_ test_edit_sql_link_on_canned_queries[/fixtures/~F0~9D~90~9C~F0~9D~90~A2~F0~9D~90~AD~F0~9D~90~A2~F0~9D~90~9E~F0~9D~90~AC-/fixtures?sql=select+id%2C+name+from+facet_cities+order+by+id+limit+1%3B] _\r\n[gw18] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client = \r\npath = '/fixtures/~F0~9D~90~9C~F0~9D~90~A2~F0~9D~90~AD~F0~9D~90~A2~F0~9D~90~9E~F0~9D~90~AC'\r\nexpected = '/fixtures?sql=select+id%2C+name+from+facet_cities+order+by+id+limit+1%3B'\r\n\r\n @pytest.mark.parametrize(\r\n \"path,expected\",\r\n [\r\n (\r\n \"/fixtures/neighborhood_search\",\r\n \"/fixtures?sql=%0Aselect+_neighborhood%2C+facet_cities.name%2C+state%0Afrom+facetable%0A++++join+facet_cities%0A++++++++on+facetable._city_id+%3D+facet_cities.id%0Awhere+_neighborhood+like+%27%25%27+%7C%7C+%3Atext+%7C%7C+%27%25%27%0Aorder+by+_neighborhood%3B%0A&text=\",\r\n ),\r\n (\r\n \"/fixtures/neighborhood_search?text=ber\",\r\n \"/fixtures?sql=%0Aselect+_neighborhood%2C+facet_cities.name%2C+state%0Afrom+facetable%0A++++join+facet_cities%0A++++++++on+facetable._city_id+%3D+facet_cities.id%0Awhere+_neighborhood+like+%27%25%27+%7C%7C+%3Atext+%7C%7C+%27%25%27%0Aorder+by+_neighborhood%3B%0A&text=ber\",\r\n ),\r\n (\"/fixtures/pragma_cache_size\", None),\r\n (\r\n # /fixtures/\ud835\udc1c\ud835\udc22\ud835\udc2d\ud835\udc22\ud835\udc1e\ud835\udc2c\r\n \"/fixtures/~F0~9D~90~9C~F0~9D~90~A2~F0~9D~90~AD~F0~9D~90~A2~F0~9D~90~9E~F0~9D~90~AC\",\r\n \"/fixtures?sql=select+id%2C+name+from+facet_cities+order+by+id+limit+1%3B\",\r\n ),\r\n (\"/fixtures/magic_parameters\", None),\r\n ],\r\n )\r\n def test_edit_sql_link_on_canned_queries(app_client, path, expected):\r\n response = app_client.get(path)\r\n> assert response.status == 200\r\nE assert 302 == 200\r\nE + where 302 = .status\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_html.py:841: AssertionError\r\n_______________________ test_table_with_slashes_in_name ________________________\r\n[gw9] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client = \r\n\r\n def test_table_with_slashes_in_name(app_client):\r\n response = app_client.get(\r\n \"/fixtures/table~2Fwith~2Fslashes~2Ecsv.json?_shape=objects\"\r\n )\r\n> assert response.status == 200\r\nE assert 302 == 200\r\nE + where 302 = .status\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_table_api.py:141: AssertionError\r\n__________________ test_custom_query_with_unicode_characters ___________________\r\n[gw8] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client = \r\n\r\n def test_custom_query_with_unicode_characters(app_client):\r\n # /fixtures/\ud835\udc1c\ud835\udc22\ud835\udc2d\ud835\udc22\ud835\udc1e\ud835\udc2c.json\r\n response = app_client.get(\r\n \"/fixtures/~F0~9D~90~9C~F0~9D~90~A2~F0~9D~90~AD~F0~9D~90~A2~F0~9D~90~9E~F0~9D~90~AC.json?_shape=array\"\r\n )\r\n> assert [{\"id\": 1, \"name\": \"San Francisco\"}] == response.json\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_table_api.py:1042: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/datasette/utils/testing.py:40: in json\r\n return json.loads(self.text)\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/json/__init__.py:346: in loads\r\n return _default_decoder.decode(s)\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/json/decoder.py:337: in decode\r\n obj, end = self.raw_decode(s, idx=_w(s, 0).end())\r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n\r\nself = , s = '', idx = 0\r\n\r\n def raw_decode(self, s, idx=0):\r\n \"\"\"Decode a JSON document from ``s`` (a ``str`` beginning with\r\n a JSON document) and return a 2-tuple of the Python\r\n representation and the index in ``s`` where the document ended.\r\n \r\n This can be used to decode a JSON document from a string that may\r\n have extraneous data at the end.\r\n \r\n \"\"\"\r\n try:\r\n obj, end = self.scan_once(s, idx)\r\n except StopIteration as err:\r\n> raise JSONDecodeError(\"Expecting value\", s, err.value) from None\r\nE json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)\r\n\r\n/gnu/store/65i3nhcwmz0p8rqbg48gaavyky4g4hwk-python-3.9.9/lib/python3.9/json/decoder.py:355: JSONDecodeError\r\n_ test_searchable[/fixtures/searchable.json?_search=te*+AND+do*&_searchmode=raw-expected_rows3] _\r\n[gw13] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\napp_client = \r\npath = '/fixtures/searchable.json?_search=te*+AND+do*&_searchmode=raw'\r\nexpected_rows = [[1, 'barry cat', 'terry dog', 'panther'], [2, 'terry dog', 'sara weasel', 'puma']]\r\n\r\n @pytest.mark.parametrize(\r\n \"path,expected_rows\",\r\n [\r\n (\r\n \"/fixtures/searchable.json?_search=dog\",\r\n [\r\n [1, \"barry cat\", \"terry dog\", \"panther\"],\r\n [2, \"terry dog\", \"sara weasel\", \"puma\"],\r\n ],\r\n ),\r\n (\r\n # Special keyword shouldn't break FTS query\r\n \"/fixtures/searchable.json?_search=AND\",\r\n [],\r\n ),\r\n (\r\n # Without _searchmode=raw this should return no results\r\n \"/fixtures/searchable.json?_search=te*+AND+do*\",\r\n [],\r\n ),\r\n (\r\n # _searchmode=raw\r\n \"/fixtures/searchable.json?_search=te*+AND+do*&_searchmode=raw\",\r\n [\r\n [1, \"barry cat\", \"terry dog\", \"panther\"],\r\n [2, \"terry dog\", \"sara weasel\", \"puma\"],\r\n ],\r\n ),\r\n (\r\n # _searchmode=raw combined with _search_COLUMN\r\n \"/fixtures/searchable.json?_search_text2=te*&_searchmode=raw\",\r\n [\r\n [1, \"barry cat\", \"terry dog\", \"panther\"],\r\n ],\r\n ),\r\n (\r\n \"/fixtures/searchable.json?_search=weasel\",\r\n [[2, \"terry dog\", \"sara weasel\", \"puma\"]],\r\n ),\r\n (\r\n \"/fixtures/searchable.json?_search_text2=dog\",\r\n [[1, \"barry cat\", \"terry dog\", \"panther\"]],\r\n ),\r\n (\r\n \"/fixtures/searchable.json?_search_name%20with%20.%20and%20spaces=panther\",\r\n [[1, \"barry cat\", \"terry dog\", \"panther\"]],\r\n ),\r\n ],\r\n )\r\n def test_searchable(app_client, path, expected_rows):\r\n response = app_client.get(path)\r\n> assert expected_rows == response.json[\"rows\"]\r\nE AssertionError: assert [[1, 'barry cat', 'terry dog', 'panther'],\\n [2, 'terry dog', 'sara weasel', 'puma']] == []\r\nE Left contains 2 more items, first extra item: [1, 'barry cat', 'terry dog', 'panther']\r\nE Full diff:\r\nE [\r\nE - ,\r\nE + [1,\r\nE + 'barry cat',\r\nE + 'terry dog',\r\nE + 'panther'],\r\nE + [2,\r\nE + 'terry dog',\r\nE + 'sara weasel',\r\nE + 'puma'],\r\nE ]\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_table_api.py:402: AssertionError\r\n_____ test_searchmode[table_metadata1-_search=te*+AND+do*-expected_rows1] ______\r\n[gw20] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\ntable_metadata = {'searchmode': 'raw'}, querystring = '_search=te*+AND+do*'\r\nexpected_rows = [[1, 'barry cat', 'terry dog', 'panther'], [2, 'terry dog', 'sara weasel', 'puma']]\r\n\r\n @pytest.mark.parametrize(\r\n \"table_metadata,querystring,expected_rows\",\r\n [\r\n (\r\n {},\r\n \"_search=te*+AND+do*\",\r\n [],\r\n ),\r\n (\r\n {\"searchmode\": \"raw\"},\r\n \"_search=te*+AND+do*\",\r\n _SEARCHMODE_RAW_RESULTS,\r\n ),\r\n (\r\n {},\r\n \"_search=te*+AND+do*&_searchmode=raw\",\r\n _SEARCHMODE_RAW_RESULTS,\r\n ),\r\n # Can be over-ridden with _searchmode=escaped\r\n (\r\n {\"searchmode\": \"raw\"},\r\n \"_search=te*+AND+do*&_searchmode=escaped\",\r\n [],\r\n ),\r\n ],\r\n )\r\n def test_searchmode(table_metadata, querystring, expected_rows):\r\n with make_app_client(\r\n metadata={\"databases\": {\"fixtures\": {\"tables\": {\"searchable\": table_metadata}}}}\r\n ) as client:\r\n response = client.get(\"/fixtures/searchable.json?\" + querystring)\r\n> assert expected_rows == response.json[\"rows\"]\r\nE AssertionError: assert [[1, 'barry cat', 'terry dog', 'panther'],\\n [2, 'terry dog', 'sara weasel', 'puma']] == []\r\nE Left contains 2 more items, first extra item: [1, 'barry cat', 'terry dog', 'panther']\r\nE Full diff:\r\nE [\r\nE - ,\r\nE + [1,\r\nE + 'barry cat',\r\nE + 'terry dog',\r\nE + 'panther'],\r\nE + [2,\r\nE + 'terry dog',\r\nE + 'sara weasel',\r\nE + 'puma'],\r\nE ]\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_table_api.py:442: AssertionError\r\n_ test_searchmode[table_metadata2-_search=te*+AND+do*&_searchmode=raw-expected_rows2] _\r\n[gw20] linux -- Python 3.9.9 /gnu/store/slsh0qjv5j68xda2bb6h8gsxwyi1j25a-python-wrapper-3.9.9/bin/python\r\n\r\ntable_metadata = {}, querystring = '_search=te*+AND+do*&_searchmode=raw'\r\nexpected_rows = [[1, 'barry cat', 'terry dog', 'panther'], [2, 'terry dog', 'sara weasel', 'puma']]\r\n\r\n @pytest.mark.parametrize(\r\n \"table_metadata,querystring,expected_rows\",\r\n [\r\n (\r\n {},\r\n \"_search=te*+AND+do*\",\r\n [],\r\n ),\r\n (\r\n {\"searchmode\": \"raw\"},\r\n \"_search=te*+AND+do*\",\r\n _SEARCHMODE_RAW_RESULTS,\r\n ),\r\n (\r\n {},\r\n \"_search=te*+AND+do*&_searchmode=raw\",\r\n _SEARCHMODE_RAW_RESULTS,\r\n ),\r\n # Can be over-ridden with _searchmode=escaped\r\n (\r\n {\"searchmode\": \"raw\"},\r\n \"_search=te*+AND+do*&_searchmode=escaped\",\r\n [],\r\n ),\r\n ],\r\n )\r\n def test_searchmode(table_metadata, querystring, expected_rows):\r\n with make_app_client(\r\n metadata={\"databases\": {\"fixtures\": {\"tables\": {\"searchable\": table_metadata}}}}\r\n ) as client:\r\n response = client.get(\"/fixtures/searchable.json?\" + querystring)\r\n> assert expected_rows == response.json[\"rows\"]\r\nE AssertionError: assert [[1, 'barry cat', 'terry dog', 'panther'],\\n [2, 'terry dog', 'sara weasel', 'puma']] == []\r\nE Left contains 2 more items, first extra item: [1, 'barry cat', 'terry dog', 'panther']\r\nE Full diff:\r\nE [\r\nE - ,\r\nE + [1,\r\nE + 'barry cat',\r\nE + 'terry dog',\r\nE + 'panther'],\r\nE + [2,\r\nE + 'terry dog',\r\nE + 'sara weasel',\r\nE + 'puma'],\r\nE ]\r\n\r\n/tmp/guix-build-datasette-0.64.2.drv-0/source/tests/test_table_api.py:442: AssertionError\r\n=========================== short test summary info ============================\r\nFAILED tests/test_api.py::test_row_strange_table_name - assert 400 == 200\r\nFAILED tests/test_api.py::test_database_page_for_database_with_dot_in_name - ...\r\nFAILED tests/test_api.py::test_tilde_encoded_database_names[fo%o] - assert 30...\r\nFAILED tests/test_api.py::test_tilde_encoded_database_names[f~/c.d] - assert ...\r\nFAILED tests/test_api.py::test_database_with_space_in_name[/searchable.json]\r\nFAILED tests/test_api.py::test_database_with_space_in_name[.json] - httpx.Too...\r\nFAILED tests/test_api.py::test_database_with_space_in_name[/searchable_view]\r\nFAILED tests/test_api.py::test_database_with_space_in_name[/] - httpx.TooMany...\r\nFAILED tests/test_api.py::test_database_with_space_in_name[/searchable] - htt...\r\nFAILED tests/test_api.py::test_database_with_space_in_name[/searchable_view.json]\r\nFAILED tests/test_cli.py::test_weird_database_names[database (1).sqlite] - As...\r\nFAILED tests/test_cli.py::test_weird_database_names[test-database (1).sqlite]\r\nFAILED tests/test_html.py::test_row_html_compound_primary_key[/fixtures/compound_primary_key/a~2Fb,~2Ec~2Dd-expected1]\r\nFAILED tests/test_html.py::test_css_classes_on_body[/fixtures/table~2Fwith~2Fslashes~2Ecsv-expected_classes5]\r\nFAILED tests/test_html.py::test_templates_considered[/fixtures/table~2Fwith~2Fslashes~2Ecsv-table-fixtures-tablewithslashescsv-fa7563.html, *table.html]\r\nFAILED tests/test_html.py::test_alternate_url_json[/fixtures/table~2Fwith~2Fslashes~2Ecsv-http://localhost/fixtures/table~2Fwith~2Fslashes~2Ecsv.json]\r\nFAILED tests/test_html.py::test_edit_sql_link_on_canned_queries[/fixtures/~F0~9D~90~9C~F0~9D~90~A2~F0~9D~90~AD~F0~9D~90~A2~F0~9D~90~9E~F0~9D~90~AC-/fixtures?sql=select+id%2C+name+from+facet_cities+order+by+id+limit+1%3B]\r\nFAILED tests/test_table_api.py::test_table_with_slashes_in_name - assert 302 ...\r\nFAILED tests/test_table_api.py::test_custom_query_with_unicode_characters - j...\r\nFAILED tests/test_table_api.py::test_searchable[/fixtures/searchable.json?_search=te*+AND+do*&_searchmode=raw-expected_rows3]\r\nFAILED tests/test_table_api.py::test_searchmode[table_metadata1-_search=te*+AND+do*-expected_rows1]\r\nFAILED tests/test_table_api.py::test_searchmode[table_metadata2-_search=te*+AND+do*&_searchmode=raw-expected_rows2]\r\n=========== 22 failed, 1049 passed, 3 skipped in 1522.28s (0:25:22) ============\r\nerror: in phase 'check': uncaught exception:\r\n%exception #<&invoke-error program: \"/gnu/store/ziqwkzz6znb5d3c245xn0cq5ra2ly0w3-python-pytest-7.1.3/bin/pytest\" arguments: (\"-vv\" \"-n\" \"24\" \"-m\" \"not serial\") exit-status: 1 term-signal: #f stop-signal: #f> \r\nphase `check' failed after 1523.3 seconds\r\n```\r\nThe tests run in a private namespace without internet connectivity, and the Python dependencies are at:\r\n```\r\npython-aiofiles@0.6.0 python-asgi-csrf@0.9 python-asgiref@3.4.1\r\n+ python-beautifulsoup4@4.11.1 python-black@22.3.0 python-click-default-group@1.2.2 python-click@8.1.3\r\n+ python-cogapp@3.3.0 python-httpx@0.23.0 python-hupper@1.10.3 python-itsdangerous@2.0.1\r\n+ python-janus@1.0.0 python-jinja2@3.1.1 python-mergedeep@1.3.4 python-pint@0.20.1 python-pluggy@1.0.0\r\n+ python-pytest-asyncio@0.17.2 python-pytest-runner@5.2 python-pytest-timeout@2.0.2\r\n+ python-pytest-xdist@2.5.0 python-pytest@7.1.3 python-pyyaml@6.0 python-setuptools@64.0.3\r\n+ python-trustme@0.9.0 python-uvicorn@0.17.6\r\n```\r\nWith Python 3.9.9.\r\n\r\nThank you!", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2048/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 323658641, "node_id": "MDU6SXNzdWUzMjM2NTg2NDE=", "number": 262, "title": "Add ?_extra= mechanism for requesting extra properties in JSON", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": {"value": 3268330, "label": "Datasette 1.0"}, "comments": 27, "created_at": "2018-05-16T14:55:42Z", "updated_at": "2023-03-29T06:22:22Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "Datasette views currently work by creating a set of data that should be returned as JSON, then defining an additional, optional `template_data()` function which is called if the view is being rendered as HTML.\r\n\r\nThis `template_data()` function calculates extra template context variables which are necessary for the HTML view but should not be included in the JSON.\r\n\r\nExample of how that is used today: https://github.com/simonw/datasette/blob/2b79f2bdeb1efa86e0756e741292d625f91cb93d/datasette/views/table.py#L672-L704\r\n\r\nWith features like Facets in #255 I'm beginning to want to move more items into the `template_data()` - in the case of facets it's the `suggested_facets` array. This saves that feature from being calculated (involving several SQL queries) for the JSON case where it is unlikely to be used.\r\n\r\nBut... as an API user, I want to still optionally be able to access that information.\r\n\r\nSolution: Add a `?_extra=suggested_facets&_extra=table_metadata` argument which can be used to optionally request additional blocks to be added to the JSON API.\r\n\r\nThen redefine as many of the current `template_data()` features as extra arguments instead, and teach Datasette to return certain extras by default when rendering templates.\r\n\r\nThis could allow the JSON representation to be slimmed down further (removing e.g. the `table_definition` and `view_definition` keys) while still making that information available to API users who need it.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/262/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1641013220, "node_id": "I_kwDOBm6k_c5hz9_k", "number": 2045, "title": "First column on a view page has no facet option in cog menu", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": {"value": 3268330, "label": "Datasette 1.0"}, "comments": 0, "created_at": "2023-03-26T18:02:47Z", "updated_at": "2023-03-26T18:02:48Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "e.g. first column on this page - cog menu has no option to facet.\r\n\r\nhttps://datasette.io/content/tools\r\n\r\n\"image\"\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2045/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1639873822, "node_id": "PR_kwDOBm6k_c5M29tt", "number": 2044, "title": "Expand labels in row view as well (patch for 0.64.x branch)", "user": {"value": 82332573, "label": "tmcl-it"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-03-24T18:44:44Z", "updated_at": "2023-03-24T18:44:57Z", "closed_at": null, "author_association": "FIRST_TIME_CONTRIBUTOR", "pull_request": "simonw/datasette/pulls/2044", "body": "This is a version of #2031 for the 0.64.x branch.\r\n\r\n\r\n----\n:books: Documentation preview :books:: https://datasette--2044.org.readthedocs.build/en/2044/\n\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "pull", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2044/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": 0, "state_reason": null} {"id": 1605481359, "node_id": "PR_kwDOBm6k_c5LDwrF", "number": 2031, "title": "Expand foreign key references in row view as well", "user": {"value": 82332573, "label": "tmcl-it"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 5, "created_at": "2023-03-01T18:43:09Z", "updated_at": "2023-03-24T18:35:25Z", "closed_at": null, "author_association": "FIRST_TIME_CONTRIBUTOR", "pull_request": "simonw/datasette/pulls/2031", "body": "Unlike the table view, the single row view does not resolve foreign key references into labels. This patch extracts the foreign key reference expansion code from TableView.data() into a standalone function that is then called by both TableView.data() and RowView.data().\r\n\r\n\r\n----\n:books: Documentation preview :books:: https://datasette--2031.org.readthedocs.build/en/2031/\n\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "pull", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2031/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": 0, "state_reason": null} {"id": 1121583414, "node_id": "I_kwDOBm6k_c5C2gE2", "number": 1619, "title": "JSON link on row page is 404 if base_url setting is used", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 5, "created_at": "2022-02-02T07:09:53Z", "updated_at": "2023-03-24T15:38:04Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "On my local environment:\r\n\r\n datasette fixtures.db -p 3344 --setting base_url /foo/bar/\r\n\r\nThen hit http://127.0.0.1:3344/foo/bar/fixtures/table%2Fwith%2Fslashes.csv/3\r\n\r\n\"image\"\r\n\r\nBut... that `json` link goes here, which is a 404:\r\n\r\nhttp://127.0.0.1:3344/foo/bar/foo/bar/fixtures/table%2Fwith%2Fslashes.csv/3?_format=json", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1619/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1636616315, "node_id": "I_kwDOBm6k_c5hjMh7", "number": 2042, "title": "Gather feedback on new ?_extra= design", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-03-22T23:07:43Z", "updated_at": "2023-03-22T23:08:19Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "Now that I've landed:\r\n- #1999\r\n\r\nSee also:\r\n- #262\r\n\r\nI want to get some feedback from people on the design of the new `?_extra=` feature, before freezing it into Datasette 1.0.\r\n\r\nThe big change is that the default JSON representation is now MUCH slimmer - it only gives you keys for `\"next\"` and `\"rows\"`, where rows is a list of JSON objects (not a list of arrays as was previously the default) - for example https://latest.datasette.io/fixtures/sortable.json\r\n\r\nIf you want extra stuff you can ask for it with the new `?_extra=` parameter - e.g. https://latest.datasette.io/fixtures/sortable.json?_extra=columns&_extra=suggested_facets\r\n\r\nYou can use `?_extra=extras` to see a list of available extras: https://latest.datasette.io/fixtures/sortable.json?_extra=extras\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2042/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1620515757, "node_id": "I_kwDOBm6k_c5glxut", "number": 2039, "title": "Subtle bug with `--load-extension` and `--static` flags with absolute Windows paths with`C:\\`", "user": {"value": 15178711, "label": "asg017"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-03-12T21:18:52Z", "updated_at": "2023-03-12T21:18:52Z", "closed_at": null, "author_association": "CONTRIBUTOR", "pull_request": null, "body": "From the Datasette discord: A user tried running the following command on windows:\r\n\r\n```\r\ndatasette --load-extension=\"C:\\spatialite\\mod_spatialite-5.0.1-win-x86\\mod_spatialite.dll\"\r\n```\r\nThis failed with `\"The specified module could not be found\"`, because the entrypoint option introduced in #1789 splits the input differently. Instead of loading the extension found at `\"C:\\spatialite\\mod_spatialite-5.0.1-win-x86\\mod_spatialite.dll\"`, it instead tried to load the extension at `\"C\"` with entrypoint `\"\\spatialite\\mod_spatialite-5.0.1-win-x86\\mod_spatialite.dll\". \r\n\r\nThis is hard because most absolute windows paths have a colon in them, like `C:\\foo.txt` or `D:\\bar.txt`. I'd image the `--static` flag is also vulnerable to this type of bug.\r\n\r\n\r\nThe \"solution\" is to use a relative path instead, but that doesn't feel that great. ", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2039/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 317001500, "node_id": "MDU6SXNzdWUzMTcwMDE1MDA=", "number": 236, "title": "datasette publish lambda plugin", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 11, "created_at": "2018-04-23T22:10:30Z", "updated_at": "2023-03-12T14:04:15Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "Refs #217 - create a publish plugin that can deploy to AWS Lambda.\r\n\r\nhttps://docs.aws.amazon.com/lambda/latest/dg/limits.html says lambda packages can be up to 50 MB, so this would only work with smaller databases (the command can check the filesize before attempting to package and deploy it).\r\n\r\nLambdas do get a 512 MB `/tmp` directory too, so for larger databases the function could start and then download up to 512MB from an S3 bucket - so the plugin could take an optional S3 bucket to write to and know how to upload the `.db` file there and then have the lambda download it on startup.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/236/reactions\", \"total_count\": 2, \"+1\": 2, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1618249044, "node_id": "I_kwDOBm6k_c5gdIVU", "number": 2038, "title": "Consider a `strict_templates` setting", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 2, "created_at": "2023-03-10T02:09:13Z", "updated_at": "2023-03-10T02:11:06Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "A setting which turns on Jinja strict mode, so any templates that access undefined variables raise a hard error.\r\n\r\nPrototype here:\r\n```diff\r\ndiff --git a/datasette/app.py b/datasette/app.py\r\nindex 40416713..1428a3f0 100644\r\n--- a/datasette/app.py\r\n+++ b/datasette/app.py\r\n@@ -200,6 +200,7 @@ SETTINGS = (\r\n \"Allow display of SQL trace debug information with ?_trace=1\",\r\n ),\r\n Setting(\"base_url\", \"/\", \"Datasette URLs should use this base path\"),\r\n+ Setting(\"strict_templates\", False, \"Raise errors for undefined template variables\"),\r\n )\r\n _HASH_URLS_REMOVED = \"The hash_urls setting has been removed, try the datasette-hashed-urls plugin instead\"\r\n OBSOLETE_SETTINGS = {\r\n@@ -399,11 +400,14 @@ class Datasette:\r\n ),\r\n ]\r\n )\r\n+ env_extras = {}\r\n+ if self.setting(\"strict_templates\"):\r\n+ env_extras[\"undefined\"] = StrictUndefined\r\n self.jinja_env = Environment(\r\n loader=template_loader,\r\n autoescape=True,\r\n enable_async=True,\r\n- undefined=StrictUndefined,\r\n+ **env_extras,\r\n )\r\n self.jinja_env.filters[\"escape_css_string\"] = escape_css_string\r\n self.jinja_env.filters[\"quote_plus\"] = urllib.parse.quote_plus\r\n```\r\nExplored this idea a bit in:\r\n- #1999", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2038/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1618130434, "node_id": "I_kwDOJHON9s5gcrYC", "number": 11, "title": "Implement a SQL view to make it easier to query files in a nested folder", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 3, "created_at": "2023-03-09T23:19:28Z", "updated_at": "2023-03-09T23:24:01Z", "closed_at": null, "author_association": "MEMBER", "pull_request": null, "body": "Working with nested data in SQL is tricky, can I make it easier with a view or canned query?", "repo": {"value": 611552758, "label": "apple-notes-to-sqlite"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/dogsheep/apple-notes-to-sqlite/issues/11/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1617938730, "node_id": "I_kwDOJHON9s5gb8kq", "number": 9, "title": "Default to just storing plaintext, store HTML if `--html` is passed", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-03-09T20:19:06Z", "updated_at": "2023-03-09T20:19:06Z", "closed_at": null, "author_association": "MEMBER", "pull_request": null, "body": "The full `body` version of the notes can get HUGE, due to embedded images. It turns out for my own purposes I'm usually happy with just the `plaintext` version.\r\n\r\nI'm tempted to say you don't get HTML unless you pass a `--html` option.", "repo": {"value": 611552758, "label": "apple-notes-to-sqlite"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/dogsheep/apple-notes-to-sqlite/issues/9/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1616429236, "node_id": "I_kwDOJHON9s5gWMC0", "number": 4, "title": "Support incremental updates", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 2, "created_at": "2023-03-09T05:14:00Z", "updated_at": "2023-03-09T18:20:56Z", "closed_at": null, "author_association": "MEMBER", "pull_request": null, "body": "Running this script can take several hours against a large notes database.\r\n\r\nWould be neat if it could run against just the notes that have been modified since it last ran. Could pull the max `updated` date and then keep on looping until it finds one modified before then.\r\n\r\nProblem is I don't actually know what order it iterates over the notes in.", "repo": {"value": 611552758, "label": "apple-notes-to-sqlite"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/dogsheep/apple-notes-to-sqlite/issues/4/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1616440856, "node_id": "I_kwDOJHON9s5gWO4Y", "number": 5, "title": "Configure full text search", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-03-09T05:20:46Z", "updated_at": "2023-03-09T05:20:46Z", "closed_at": null, "author_association": "MEMBER", "pull_request": null, "body": "FTS would be useful.\r\n\r\nMaybe even extract the plain text from the notes to make that index easier to create, rather than creating it against the HTML. Can use the `plaintext` property for that.", "repo": {"value": 611552758, "label": "apple-notes-to-sqlite"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/dogsheep/apple-notes-to-sqlite/issues/5/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1590183272, "node_id": "I_kwDOBm6k_c5eyEVo", "number": 2027, "title": "How to redirect from \"/\" to a specific db/table", "user": {"value": 1350673, "label": "dmick"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 4, "created_at": "2023-02-18T03:14:01Z", "updated_at": "2023-03-08T04:42:22Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "Using nginx to redirect public IP to the local uvicorn server as 'normal'. I can't figure out how to redirect such that '/' results in accessing the one db/table I want to serve; redirecting / to /db/table breaks some of the CSS; fooling with base_url doesn't seem to help. Can someone explain this, if it's possible?", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2027/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1605959201, "node_id": "I_kwDOBm6k_c5fuP4h", "number": 2032, "title": "datasette errors when foreign key integrity is enabled", "user": {"value": 193185, "label": "cldellow"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-03-02T01:27:51Z", "updated_at": "2023-03-02T01:31:58Z", "closed_at": null, "author_association": "CONTRIBUTOR", "pull_request": null, "body": "By default, [SQLite does not enforce foreign key constraints](https://www.sqlite.org/foreignkeys.html#fk_enable). I typically enable these checks by running:\r\n\r\n```sql\r\nPRAGMA foreign_keys = ON;\r\n```\r\n\r\ninside of a `prepare_connection` hook.\r\n\r\nIf a plugin causes the schema to change (eg datasette-scraper creating a new table, or datasette-edit-schema changing a column), then https://github.com/simonw/datasette/blob/0b4a28691468b5c758df74fa1d72a823813c96bf/datasette/utils/internal_db.py#L71-L77 will fail with:\r\n\r\n```\r\nFOREIGN KEY constraint failed\r\n```\r\n\r\nThis could be resolved by either:\r\n- deleting from the `tables` column last\r\n- changing the schema so that the foreign keys have [ON DELETE CASCADE](https://www.sqlite.org/foreignkeys.html#fk_actions)\r\n\r\nLet me know if you'd be open to a PR that addresses this -- since foreign key constraints aren't enabled by default, I guess it's questionable whether this is a bug. I think I can workaround this by inspecting the database parameter in `prepare_connection` and trying not to enable fkey checks on the `_internal` database.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2032/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 828858421, "node_id": "MDU6SXNzdWU4Mjg4NTg0MjE=", "number": 1258, "title": "Allow canned query params to specify default values", "user": {"value": 1385831, "label": "wdccdw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 5, "created_at": "2021-03-11T07:19:02Z", "updated_at": "2023-02-20T23:39:58Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "If I call a canned query that includes named parameters, without passing any parameters, datasette runs the query anyway, resulting in an HTTP status code 400, and a visible error in the browser, with only a link back to home. This means that one of the default links on https://site/database/ will lead to a broken page with no apparent way out.\r\n\r\n![image](https://user-images.githubusercontent.com/1385831/110748683-13e72300-820e-11eb-855c-32e03dfef5bf.png)\r\n\r\nIs there any way to skip performing the query when parameters aren't supplied, but otherwise render the usual canned query page? Alternatively, can I supply default values for my parameters, either when defining my canned queries or when linking to the canned query page from the default database template.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1258/reactions\", \"total_count\": 1, \"+1\": 1, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1592327343, "node_id": "I_kwDOBm6k_c5e6Pyv", "number": 2029, "title": "Sorry Simon, didn't know how else to contact you", "user": {"value": 5804626, "label": "llchristopherson"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-02-20T19:02:53Z", "updated_at": "2023-02-20T19:02:53Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "Hi Simon,\r\n\r\nWould you be willing to chat with me about Datasette? I have some questions. I am working on a project to evaluate data ingestion tools for a research organization and I ran across Datasette. I have looked through a lot of your documentation, but still have some questions, which are very specific. If you would be willing to write me back about this, my email is laura@renci.org.\r\n\r\nThanks,\r\nLaura", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2029/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1586980089, "node_id": "PR_kwDOBm6k_c5KF-by", "number": 2026, "title": "Avoid repeating primary key columns if included in _col args", "user": {"value": 8513, "label": "runderwood"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-02-16T04:16:25Z", "updated_at": "2023-02-16T04:16:41Z", "closed_at": null, "author_association": "FIRST_TIME_CONTRIBUTOR", "pull_request": "simonw/datasette/pulls/2026", "body": "...while maintaining given order.\r\n\r\nFixes #1975 (if I'm understanding correctly).\r\n\r\n\r\n----\n:books: Documentation preview :books:: https://datasette--2026.org.readthedocs.build/en/2026/\n\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "pull", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2026/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": 0, "state_reason": null} {"id": 1581218043, "node_id": "PR_kwDOBm6k_c5JyqPy", "number": 2025, "title": "Add database metadata to index.html template context", "user": {"value": 9993, "label": "palewire"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-02-12T11:16:58Z", "updated_at": "2023-02-12T11:17:14Z", "closed_at": null, "author_association": "FIRST_TIME_CONTRIBUTOR", "pull_request": "simonw/datasette/pulls/2025", "body": "Fixes #2016 \r\n\r\n\r\n----\n:books: Documentation preview :books:: https://datasette--2025.org.readthedocs.build/en/2025/\n\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "pull", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2025/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": 0, "state_reason": null} {"id": 1323346408, "node_id": "I_kwDOBm6k_c5O4Kno", "number": 1775, "title": "i18n support", "user": {"value": 428820, "label": "johnfelipe"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 9, "created_at": "2022-07-31T02:51:04Z", "updated_at": "2023-02-10T18:04:40Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "I want contribute for translate UI to es, de, de and it if you share strings", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1775/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1579973223, "node_id": "I_kwDOBm6k_c5eLHpn", "number": 2024, "title": "Mention WAL mode in documentation", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2023-02-10T16:11:10Z", "updated_at": "2023-02-10T16:11:53Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "It's not currently obvious from the docs how you can ensure that Datasette runs well in situations where other processes may update the underlying SQLite files.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2024/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1577548579, "node_id": "I_kwDOBm6k_c5eB3sj", "number": 2021, "title": "Docker images for 1.0 alphas?", "user": {"value": 1563881, "label": "meowcat"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-02-09T09:35:52Z", "updated_at": "2023-02-09T09:35:52Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "Hi,\r\nwould you consider putting 1.0alpha images on Dockerhub? \r\n\r\n(Also, how usable are the alphas?)", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2021/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1575880841, "node_id": "I_kwDOBm6k_c5d7giJ", "number": 2020, "title": "Documentation refers to \"off\" setting; doesn't seem to work, \"false\" does", "user": {"value": 1350673, "label": "dmick"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-02-08T10:38:10Z", "updated_at": "2023-02-08T10:38:10Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "https://docs.datasette.io/en/stable/settings.html#suggest-facets, among others, suggests using \"off\" to disable the setting; however, this doesn't appear to work in the JSON config files, where it apparently needs to be a \"JSON boolean\" and have the values \"true\" or \"false\". Perhaps the Python code is more flexible?...but either way, the documentation probably should mention it.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2020/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1573424830, "node_id": "I_kwDOBm6k_c5dyI6-", "number": 2019, "title": "Refactor out the keyset pagination code", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 14, "created_at": "2023-02-06T23:04:00Z", "updated_at": "2023-02-08T01:40:46Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "While working on:\r\n- #1999\r\n\r\nI noticed that some of the most complex code in the existing table view is the code that implements keyset pagination:\r\n\r\nhttps://github.com/simonw/datasette/blob/0b4a28691468b5c758df74fa1d72a823813c96bf/datasette/views/table.py#L417-L493\r\n\r\nExtracting that into a utility function would simplify that code a lot.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2019/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 473288428, "node_id": "MDExOlB1bGxSZXF1ZXN0MzAxNDgzNjEz", "number": 564, "title": "First proof-of-concept of Datasette Library", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2019-07-26T10:22:26Z", "updated_at": "2023-02-07T15:14:11Z", "closed_at": null, "author_association": "OWNER", "pull_request": "simonw/datasette/pulls/564", "body": "Refs #417. Run it like this:\r\n\r\n datasette -d ~/Library\r\n\r\nUses a new plugin hook - available_databases()\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "pull", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/564/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": 1, "state_reason": null} {"id": 1571711808, "node_id": "I_kwDOBm6k_c5drmtA", "number": 2018, "title": "`check_visibility` gives confusing (wrong?) results if permission is `None`", "user": {"value": 193185, "label": "cldellow"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-02-06T01:03:08Z", "updated_at": "2023-02-06T01:03:46Z", "closed_at": null, "author_association": "CONTRIBUTOR", "pull_request": null, "body": "I'm trying to gate access to an edit UI on the user having `update-row` on the underlying view or table.\r\n\r\nI expected [datasette.check_visibility](https://docs.datasette.io/en/latest/internals.html#await-check-visibility-actor-action-none-resource-none-permissions-none) to be a good way to do this:\r\n\r\n```python\r\n visible, private = await datasette.check_visibility(\r\n request.actor,\r\n permissions=[\r\n (\"update-row\", (database, table)),\r\n ],\r\n )\r\n\r\n if not visible:\r\n return None\r\n```\r\n\r\nBut `visible` is returning true, even when there is no explicit `update-row` permission. (In this case, `request.actor` is `None`.)\r\n\r\nBased on [the update-row permissions docs](https://docs.datasette.io/en/latest/authentication.html#update-row), I expected this to be default deny, and so no explicit permission would result in false.\r\n\r\nI think the root cause is that `check_visibility` calls `ensure_permissions` and expects it to throw if the permission is not available.\r\n\r\nBut `ensure_permissions` does not throw when `permission_allowed` returns None: https://github.com/simonw/datasette/blob/1.0a2/datasette/app.py#L825-L829", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2018/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1571207083, "node_id": "I_kwDOBm6k_c5dprer", "number": 2016, "title": "Database metadata fields like description are not available in the index page template's context", "user": {"value": 9993, "label": "palewire"}, "state": "open", "locked": 0, "assignee": null, "milestone": {"value": 3268330, "label": "Datasette 1.0"}, "comments": 1, "created_at": "2023-02-05T02:25:53Z", "updated_at": "2023-02-05T22:56:43Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "When looping through `databases` in the index.html template, I'd like to print the description of each database alongside its name. But it appears that isn't passed in from the view, unless I'm missing it. It would be great to have that.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2016/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1556065335, "node_id": "PR_kwDOBm6k_c5Ie5nA", "number": 2004, "title": "use single quotes for string literals, fixes #2001", "user": {"value": 193185, "label": "cldellow"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2023-01-25T05:08:46Z", "updated_at": "2023-02-01T06:37:18Z", "closed_at": null, "author_association": "CONTRIBUTOR", "pull_request": "simonw/datasette/pulls/2004", "body": "This modernizes some uses of double quotes for string literals to use only single quotes, fixes simonw/datasette#2001\r\n\r\nWhile developing it, I manually enabled the stricter mode by using the code snippet at https://gist.github.com/cldellow/85bba507c314b127f85563869cd94820\r\n\r\nI think that code snippet isn't generally safe/portable, so I haven't tried to automate it in the tests.\r\n\r\n\r\n----\r\n:books: Documentation preview :books:: https://datasette--2004.org.readthedocs.build/en/2004/\r\n\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "pull", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2004/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": 0, "state_reason": null} {"id": 1565179870, "node_id": "I_kwDOBm6k_c5dSr_e", "number": 2013, "title": "Datasette uses non-standard quoting for identifiers", "user": {"value": 193185, "label": "cldellow"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-02-01T00:05:39Z", "updated_at": "2023-02-01T00:06:30Z", "closed_at": null, "author_association": "CONTRIBUTOR", "pull_request": null, "body": "Related to #2001, but where #2001 was about literals, this is about identifiers\r\n\r\nFrom https://www.sqlite.org/lang_keywords.html:\r\n\r\n> \"keyword\" A keyword in double-quotes is an identifier.\r\n> [keyword] A keyword enclosed in square brackets is an identifier. This is not standard SQL. This quoting mechanism is used by MS Access and SQL Server and is included in SQLite for compatibility.\r\n\r\nDatasette uses this quoting here -- https://github.com/simonw/datasette/blob/0b4a28691468b5c758df74fa1d72a823813c96bf/datasette/utils/__init__.py#L345-L349, in some of the other DB access code, and in some of the test fixtures.\r\n\r\nMigrating to standard double quote identifiers would make it easier to get Datasette working with alternative backends", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2013/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1564774831, "node_id": "I_kwDOBm6k_c5dRJGv", "number": 2012, "title": "Missing space in database summary", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-01-31T18:01:13Z", "updated_at": "2023-01-31T18:01:13Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "Spotted this on an instance index page:\r\n\r\n\"image\"\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2012/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1564769997, "node_id": "I_kwDOBm6k_c5dRH7N", "number": 2011, "title": "Applied facet did not result in an \"x\" icon to dismiss it", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2023-01-31T17:57:44Z", "updated_at": "2023-01-31T17:58:54Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "![CleanShot 2023-01-31 at 09 55 56@2x](https://user-images.githubusercontent.com/9599/215843684-1761a230-d490-4f87-be6d-186319366794.png)\r\n\r\nThat's against this data https://data.sfgov.org/City-Management-and-Ethics/Supplier-Contracts/cqi5-hm2d imported using https://datasette.io/plugins/datasette-socrata\r\n\r\nIt's for `Contract Type` of `Non-Purchasing Contract (Rents, etc.)` - so possible that some of the spaces or punctuation in either the name of the value tripped up the code that decides if the X icon should be displayed.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2011/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1563264257, "node_id": "I_kwDOBm6k_c5dLYUB", "number": 2010, "title": "Row page should default to card view", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": {"value": 3268330, "label": "Datasette 1.0"}, "comments": 1, "created_at": "2023-01-30T21:49:37Z", "updated_at": "2023-01-30T21:52:06Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "Datasette currently uses the same table layout on the row pages as it does on the table pages:\r\n\r\nhttps://datasette.io/content/pypi_packages?_sort=name&name__exact=datasette-column-inspect\r\n\r\n\"image\"\r\n\r\nhttps://datasette.io/content/pypi_packages/datasette-column-inspect\r\n\r\n\"image\"\r\n\r\nIf you shrink down to mobile width you get this instead, on both of those pages:\r\n\r\n\"image\"\r\n\r\nI think that view, which I think of as the \"card view\", is plain better if you're looking at just a single row - and it (or a variant of it) should be the default presentation on the row page.\r\n\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2010/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1186696202, "node_id": "I_kwDOBm6k_c5Gu4wK", "number": 1696, "title": "Show foreign key label when filtering", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 2, "created_at": "2022-03-30T16:18:54Z", "updated_at": "2023-01-29T20:56:20Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "For example here:\r\n\r\n\"image\"\r\n\r\n3 corresponds to \"Human Related: Other\" - it would be neat to display this in this area of the page somehow.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1696/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1560982210, "node_id": "PR_kwDOBm6k_c5IvYKw", "number": 2008, "title": "array facet: don't materialize unnecessary columns", "user": {"value": 193185, "label": "cldellow"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 8, "created_at": "2023-01-28T19:33:40Z", "updated_at": "2023-01-29T18:17:40Z", "closed_at": null, "author_association": "CONTRIBUTOR", "pull_request": "simonw/datasette/pulls/2008", "body": "The presence of `inner.*` causes SQLite to materialize a row with all the columns. Those columns will be discarded later.\r\n\r\nInstead, we can select only the column we'll use. This lets SQLite's optimizer realize that the other columns in the CTE definition aren't needed.\r\n\r\nOn a test table with 278K rows, 98K of which had an array, this speeds up the facet calculation from 4 sec to 1 sec.\r\n\r\n\r\n----\n:books: Documentation preview :books:: https://datasette--2008.org.readthedocs.build/en/2008/\n\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "pull", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2008/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": 0, "state_reason": null} {"id": 1515815014, "node_id": "I_kwDOBm6k_c5aWYBm", "number": 1973, "title": "render_cell plugin hook's row object is not a sqlite.Row", "user": {"value": 193185, "label": "cldellow"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 4, "created_at": "2023-01-01T20:27:46Z", "updated_at": "2023-01-29T00:40:31Z", "closed_at": null, "author_association": "CONTRIBUTOR", "pull_request": null, "body": "From https://docs.datasette.io/en/stable/plugin_hooks.html#render-cell-row-value-column-table-database-datasette:\r\n\r\n> row - sqlite.Row\r\n> The SQLite row object that the value being rendered is part of\r\n\r\nThis appears to actually be a [CustomRow](https://github.com/simonw/datasette/blob/f0fadc28ddb9f82e5cc1ecaa51e8a342eb6dc528/datasette/utils/__init__.py#L773-L789), but I think that's unrelated to my issue.\r\n\r\nI have a table:\r\n\r\n```sql\r\nCREATE TABLE IF NOT EXISTS \"dss_job_stats\"(\r\n job_id integer not null references dss_job(id) on delete cascade,\r\n host text not null,\r\n // other columns elided as irrelevant\r\n primary key (job_id, host)\r\n);\r\n```\r\n\r\nOn datasette 0.63.2, the `render_cell` hook receives a `row` value that looks like:\r\n\r\n```\r\nCustomRow([('job_id', {'value': 2, 'label': '2'}), ('host', 'cldellow.com')])\r\n```\r\n\r\nI expected the `job_id` value to be `2`, but it's actually `{'value': 2, 'label': '2'}`.\r\n\r\nI can work around this, but was wondering if this was intended behaviour?", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1973/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1560651350, "node_id": "I_kwDOCGYnMM5dBaZW", "number": 523, "title": "Feature request: trim all leading and trailing white space for all columns for all tables in a database", "user": {"value": 536941, "label": "fgregg"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2023-01-28T02:40:10Z", "updated_at": "2023-01-28T02:41:14Z", "closed_at": null, "author_association": "CONTRIBUTOR", "pull_request": null, "body": "It's pretty common that i need to trim leading or trailing white space from lots of columns in a database a part of an initial ETL.\r\n\r\nI use the following recipe a lot, and it would be great to include this functionality into sqlite-utils\r\n\r\n`trimify.sql`\r\n```sql\r\nselect 'select group_concat(''update [' || name || '] set ['' || name || ''] = trim(['' || name || ''])'', '';\r\n'') || '';\r\n'' as sql_to_run from pragma_table_info('''||name||''');' from sqlite_schema;\r\n```\r\n\r\nthen something like:\r\n\r\n```bash\r\n\tsqlite3 example.db < scripts/trimify.sql > table_trim.sql && \\\r\n sqlite3 $example.db < table_trim.sql > trim.sql && \\\r\n sqlite3 $example.db < trim.sql\r\n```", "repo": {"value": 140912432, "label": "sqlite-utils"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/sqlite-utils/issues/523/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1558644003, "node_id": "I_kwDOBm6k_c5c5wUj", "number": 2006, "title": "Teach `datasette publish` to pin to `datasette<1.0` in a 0.x release", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": {"value": 3268330, "label": "Datasette 1.0"}, "comments": 2, "created_at": "2023-01-26T19:17:40Z", "updated_at": "2023-01-26T19:20:53Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "I just realized that when I ship Datasette 1.0 there may be automated deployments out there which could deploy the 1.0 version by accident, potentially breaking any customizations that aren't compatible with the 1.0 changes.\r\n\r\nI can hopefully help avoid that by shipping one last entry in the `0.x` series that ensures `datasette publish` pins to `<1.0` when it installs Datasette itself.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2006/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1557599877, "node_id": "I_kwDODFE5qs5c1xaF", "number": 12, "title": "location history changes", "user": {"value": 14809320, "label": "gerardrbentley"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-01-26T03:57:25Z", "updated_at": "2023-01-26T03:57:25Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "not sure if each download is unique, but I had to change some things to work with the takeout zip I made 2023-01-25\r\n\r\nfilename changed from \"Location History.json\" to \"Records.json\"\r\n\r\n`\"timestampMs\"` is not present, `\"timestamp\"` is roughly iso timestamp\r\n\r\n```py\r\ndef get_timestamp_ms(raw_timestamp):\r\n try:\r\n return datetime.datetime.strptime(raw_timestamp, \"%Y-%m-%dT%H:%M:%SZ\").timestamp()\r\n except ValueError:\r\n return datetime.datetime.strptime(raw_timestamp, \"%Y-%m-%dT%H:%M:%S.%fZ\").timestamp()\r\n\r\ndef save_location_history(db, zf):\r\n location_history = json.load(\r\n zf.open(\"Takeout/Location History/Records.json\")\r\n )\r\n db[\"location_history\"].upsert_all(\r\n (\r\n {\r\n \"id\": id_for_location_history(row),\r\n \"latitude\": row[\"latitudeE7\"] / 1e7,\r\n \"longitude\": row[\"longitudeE7\"] / 1e7,\r\n \"accuracy\": row[\"accuracy\"],\r\n \"timestampMs\": get_timestamp_ms(row[\"timestamp\"]),\r\n \"when\": row[\"timestamp\"],\r\n }\r\n for row in location_history[\"locations\"]\r\n ),\r\n pk=\"id\",\r\n )\r\n\r\n\r\ndef id_for_location_history(row):\r\n # We want an ID that is unique but can be sorted by in\r\n # date order - so we use the isoformat date + the first\r\n # 6 characters of a hash of the JSON\r\n first_six = hashlib.sha1(\r\n json.dumps(row, separators=(\",\", \":\"), sort_keys=True).encode(\"utf8\")\r\n ).hexdigest()[:6]\r\n return \"{}-{}\".format(\r\n row['timestamp'],\r\n first_six,\r\n )\r\n```\r\n\r\nexample locations from mine\r\n\r\n```json\r\n{\r\n \"latitudeE7\": 427220206,\r\n \"longitudeE7\": -923423972,\r\n \"accuracy\": 10,\r\n \"deviceTag\": -1312429967,\r\n \"deviceDesignation\": \"PRIMARY\",\r\n \"timestamp\": \"2019-01-08T23:31:50.867Z\"\r\n }\r\n```\r\n\r\n```json\r\n{\r\n \"latitudeE7\": 427011317,\r\n \"longitudeE7\": -923448300,\r\n \"accuracy\": 5,\r\n \"deviceTag\": -1312429967,\r\n \"deviceDesignation\": \"PRIMARY\",\r\n \"timestamp\": \"2019-01-08T23:33:53Z\"\r\n }, \r\n```", "repo": {"value": 206649770, "label": "google-takeout-to-sqlite"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/dogsheep/google-takeout-to-sqlite/issues/12/reactions\", \"total_count\": 2, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 2}", "draft": null, "state_reason": null} {"id": 1557507274, "node_id": "I_kwDOBm6k_c5c1azK", "number": 2005, "title": "`extra_template_vars` should be OK to return `None`", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2023-01-26T01:40:45Z", "updated_at": "2023-01-26T01:41:50Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "Got this exception and had to make sure it always returned `{}`:\r\n\r\n```\r\n File \".../python3.11/site-packages/datasette/app.py\", line 1049, in render_template\r\n assert isinstance(extra_vars, dict), \"extra_vars is of type {}\".format(\r\nAssertionError: extra_vars is of type \r\n```", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2005/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 743384829, "node_id": "MDExOlB1bGxSZXF1ZXN0NTIxMjg3OTk0", "number": 203, "title": "changes to allow for compound foreign keys", "user": {"value": 1049910, "label": "drkane"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 7, "created_at": "2020-11-16T00:30:10Z", "updated_at": "2023-01-25T18:47:18Z", "closed_at": null, "author_association": "FIRST_TIME_CONTRIBUTOR", "pull_request": "simonw/sqlite-utils/pulls/203", "body": "Add support for compound foreign keys, as per issue #117 \r\n\r\nNot sure if this is the right approach. In particular I'm unsure about:\r\n\r\n - the new `ForeignKey` class, which replaces the namedtuple in order to ensure that `column` and `other_column` are forced into tuples. The class does the job, but doesn't feel very elegant.\r\n - I haven't rewritten `guess_foreign_table` to take account of multiple columns, so it just checks for the first column in the foreign key definition. This isn't ideal.\r\n - I haven't added any ability to the CLI to add compound foreign keys, it's only in the python API at the moment.\r\n\r\nThe PR also contains a minor related change that columns and tables are always quoted in foreign key definitions.", "repo": {"value": 140912432, "label": "sqlite-utils"}, "type": "pull", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/sqlite-utils/issues/203/reactions\", \"total_count\": 1, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 1, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": 0, "state_reason": null} {"id": 1555701851, "node_id": "PR_kwDOBm6k_c5IdsD7", "number": 2003, "title": "Show referring tables and rows when the referring foreign key is compound", "user": {"value": 536941, "label": "fgregg"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 3, "created_at": "2023-01-24T21:31:31Z", "updated_at": "2023-01-25T18:44:42Z", "closed_at": null, "author_association": "CONTRIBUTOR", "pull_request": "simonw/datasette/pulls/2003", "body": "sqlite foreign keys can be compound, but that is not as well supported by datasette as single column foreign keys.\r\n\r\nin particular, \r\n\r\n1. in a table view, there is not a link from the row to the referenced row if the foreign key is compound\r\n2. in a row view, there is no listing of tables and rows that refer to the focal row if those referencing foreign keys are compound.\r\n\r\nBoth of these issues are discussed in #1099. \r\n\r\nThis PR only fixes the second one, because it's not clear what the right UX is for the first issue.\r\n\r\n![Screenshot 2023-01-24 at 19-47-40 nlrb bargaining_unit](https://user-images.githubusercontent.com/536941/214454749-d53deead-4151-4329-a5d4-8a7a454de7d3.png)\r\n\r\nSome things that might not be desirable about this approach.\r\n\r\n1. it changes the external API, by changing `column` => `columns` and `other_column` => `other_columns` (see inline comment for more discussion.\r\n2. There are various places where the plural foreign keys have to be checked for length and discarded or transformed to singular. \r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "pull", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2003/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": 0, "state_reason": null} {"id": 1553615704, "node_id": "I_kwDOBm6k_c5cmktY", "number": 2001, "title": "Datasette is not compatible with SQLite's strict quoting compilation option", "user": {"value": 406380, "label": "gwk"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 4, "created_at": "2023-01-23T19:10:07Z", "updated_at": "2023-01-25T04:59:58Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "I have linked Python3.11 on macOS against recent SQLite that was compiled using `-DSQLITE_DQS=0`. This option disables interpretation of double-quoted identifiers as string literals, described in the SQLite docs as a \"MySQL 3.x misfeature\". See https://www.sqlite.org/quirks.html#dblquote for background.\r\n\r\nDatasette uses the double-quote syntax in a number of key places, and is thus completely broken in this environment.\r\n\r\nMy experience was to `pip install datasette`, then run `datasette serve -I my-data.db`. When I visit `http://127.0.0.1:8001` I get a 500 response.\r\n\r\nThe error: `sqlite3.OperationalError: no such column: geometry_columns`\r\n\r\nThe responsible SQL: `'select 1 from sqlite_master where tbl_name = \"geometry_columns\"'`\r\n\r\nI then installed datasette from GitHub master in development mode and changed the offending SQL to use correct quotes: `\"select 1 from sqlite_master where tbl_name = 'geometry_columns'\"`.\r\n\r\nWith this change, I get a little further, but have the same problem with the first table name in my database (in my case, \"Meta\"):\r\n```\r\nOperationalError: no such column: Meta\r\nTraceback (most recent call last):\r\n File \"/Users/gwk/external/datasette/datasette/app.py\", line 1522, in route_path\r\n response = await view(request, send)\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/Users/gwk/external/datasette/datasette/views/base.py\", line 151, in view\r\n return await self.dispatch_request(request)\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/Users/gwk/external/datasette/datasette/views/base.py\", line 105, in dispatch_request\r\n response = await handler(request)\r\n ^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/Users/gwk/external/datasette/datasette/views/index.py\", line 70, in get\r\n \"fts_table\": await db.fts_table(table),\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/Users/gwk/external/datasette/datasette/database.py\", line 363, in fts_table\r\n return await self.execute_fn(lambda conn: detect_fts(conn, table))\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/Users/gwk/external/datasette/datasette/database.py\", line 213, in execute_fn\r\n return await asyncio.get_event_loop().run_in_executor(\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/usr/local/py/Python.framework/Versions/3.11/lib/python3.11/concurrent/futures/thread.py\", line 58, in run\r\n result = self.fn(*self.args, **self.kwargs)\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/Users/gwk/external/datasette/datasette/database.py\", line 211, in in_thread\r\n return fn(conn)\r\n ^^^^^^^^\r\n File \"/Users/gwk/external/datasette/datasette/database.py\", line 363, in \r\n return await self.execute_fn(lambda conn: detect_fts(conn, table))\r\n ^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/Users/gwk/external/datasette/datasette/utils/__init__.py\", line 588, in detect_fts\r\n rows = conn.execute(detect_fts_sql(table)).fetchall()\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\nsqlite3.OperationalError: no such column: Meta\r\nINFO: 127.0.0.1:50258 - \"GET / HTTP/1.1\" 500 Internal Server Error\r\n```\r\n\r\nI will try to continue playing with this, but I also hope that the datasette developers will enable this mode in a test environment as I am unlikely to be able to exercise all of the SQL in the codebase, or make a pull request very soon.\r\n\r\nNote that the DQS setting compile-time option can be overridden at runtime with calls to the C API:\r\n```\r\nsqlite3_db_config(db, SQLITE_DBCONFIG_DQS_DDL, 0, (void*)0);\r\nsqlite3_db_config(db, SQLITE_DBCONFIG_DQS_DML, 0, (void*)0);\r\n```\r\n\r\nAs far as I can tell, `sqlite3_db_config` is not exposed in Python, but perhaps we could figure out how to invoke it using `ctypes`.\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2001/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 743371103, "node_id": "MDU6SXNzdWU3NDMzNzExMDM=", "number": 1099, "title": "Support linking to compound foreign keys", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 6, "created_at": "2020-11-15T23:23:17Z", "updated_at": "2023-01-25T00:58:26Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "Reported as a bug in #1098 because they caused 500 errors - but it would be even better if Datasette could hyperlink to related rows via compound foreign keys.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1099/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1554032168, "node_id": "I_kwDOBm6k_c5coKYo", "number": 2002, "title": "Document how actors are displayed", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-01-24T00:08:49Z", "updated_at": "2023-01-24T00:08:49Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "https://github.com/simonw/datasette/blob/e4ebef082de90db4e1b8527abc0d582b7ae0bc9d/datasette/utils/__init__.py#L1052-L1056\r\n\r\nThis logic should be reflected in the documentation on https://docs.datasette.io/en/stable/authentication.html#actors", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2002/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1536851861, "node_id": "I_kwDOBm6k_c5bmn-V", "number": 1994, "title": "Stuck on loading screen", "user": {"value": 10913053, "label": "jackhagley"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2023-01-17T18:33:49Z", "updated_at": "2023-01-23T08:21:08Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "Can\u2019t actually open it!\r\n\r\nDownloaded today from the releases tab\r\n\r\nRunning macOS13.1 \r\n\r\n```\r\nbin/python3.9 --version\r\nPython 3.9.6\r\nTook 83ms\r\nbin/python3.9 --version\r\nPython 3.9.6\r\nTook 113ms\r\nbin/pip install datasette>=0.59 datasette-app-support>=0.11.6 datasette-vega>=0.6.2 datasette-cluster-map>=0.17.1 datasette-pretty-json>=0.2.1 datasette-edit-schema>=0.4 datasette-configure-fts>=1.1 datasette-leaflet>=0.2.2 --disable-pip-version-check\r\nRequirement already satisfied: datasette>=0.59 in lib/python3.9/site-packages (0.63)\r\nRequirement already satisfied: datasette-app-support>=0.11.6 in lib/python3.9/site-packages (0.11.6)\r\nRequirement already satisfied: datasette-vega>=0.6.2 in lib/python3.9/site-packages (0.6.2)\r\nRequirement already satisfied: datasette-cluster-map>=0.17.1 in lib/python3.9/site-packages (0.17.2)\r\nRequirement already satisfied: datasette-pretty-json>=0.2.1 in lib/python3.9/site-packages (0.2.2)\r\nRequirement already satisfied: datasette-edit-schema>=0.4 in lib/python3.9/site-packages (0.5.1)\r\nRequirement already satisfied: datasette-configure-fts>=1.1 in lib/python3.9/site-packages (1.1)\r\nRequirement already satisfied: datasette-leaflet>=0.2.2 in lib/python3.9/site-packages (0.2.2)\r\nRequirement already satisfied: click>=7.1.1 in lib/python3.9/site-packages (from datasette>=0.59) (8.1.3)\r\nRequirement already satisfied: hupper>=1.9 in lib/python3.9/site-packages (from datasette>=0.59) (1.10.3)\r\nRequirement already satisfied: pint>=0.9 in lib/python3.9/site-packages (from datasette>=0.59) (0.20.1)\r\nRequirement already satisfied: PyYAML>=5.3 in lib/python3.9/site-packages (from datasette>=0.59) (6.0)\r\nRequirement already satisfied: httpx>=0.20 in lib/python3.9/site-packages (from datasette>=0.59) (0.23.0)\r\nRequirement already satisfied: aiofiles>=0.4 in lib/python3.9/site-packages (from datasette>=0.59) (22.1.0)\r\nRequirement already satisfied: asgi-csrf>=0.9 in lib/python3.9/site-packages (from datasette>=0.59) (0.9)\r\nRequirement already satisfied: asgiref>=3.2.10 in lib/python3.9/site-packages (from datasette>=0.59) (3.5.2)\r\nRequirement already satisfied: uvicorn>=0.11 in lib/python3.9/site-packages (from datasette>=0.59) (0.19.0)\r\nRequirement already satisfied: itsdangerous>=1.1 in lib/python3.9/site-packages (from datasette>=0.59) (2.1.2)\r\nRequirement already satisfied: click-default-group-wheel>=1.2.2 in lib/python3.9/site-packages (from datasette>=0.59) (1.2.2)\r\nRequirement already satisfied: janus>=0.6.2 in lib/python3.9/site-packages (from datasette>=0.59) (1.0.0)\r\nRequirement already satisfied: pluggy>=1.0 in lib/python3.9/site-packages (from datasette>=0.59) (1.0.0)\r\nRequirement already satisfied: Jinja2>=2.10.3 in lib/python3.9/site-packages (from datasette>=0.59) (3.1.2)\r\nRequirement already satisfied: mergedeep>=1.1.1 in lib/python3.9/site-packages (from datasette>=0.59) (1.3.4)\r\nRequirement already satisfied: sqlite-utils in lib/python3.9/site-packages (from datasette-app-support>=0.11.6) (3.30)\r\nRequirement already satisfied: packaging in lib/python3.9/site-packages (from datasette-app-support>=0.11.6) (21.3)\r\nRequirement already satisfied: python-multipart in lib/python3.9/site-packages (from asgi-csrf>=0.9->datasette>=0.59) (0.0.5)\r\nRequirement already satisfied: httpcore<0.16.0,>=0.15.0 in lib/python3.9/site-packages (from httpx>=0.20->datasette>=0.59) (0.15.0)\r\nRequirement already satisfied: certifi in lib/python3.9/site-packages (from httpx>=0.20->datasette>=0.59) (2022.9.24)\r\nRequirement already satisfied: rfc3986[idna2008]<2,>=1.3 in lib/python3.9/site-packages (from httpx>=0.20->datasette>=0.59) (1.5.0)\r\nRequirement already satisfied: sniffio in lib/python3.9/site-packages (from httpx>=0.20->datasette>=0.59) (1.3.0)\r\nRequirement already satisfied: h11<0.13,>=0.11 in lib/python3.9/site-packages (from httpcore<0.16.0,>=0.15.0->httpx>=0.20->datasette>=0.59) (0.12.0)\r\nRequirement already satisfied: anyio==3.* in lib/python3.9/site-packages (from httpcore<0.16.0,>=0.15.0->httpx>=0.20->datasette>=0.59) (3.6.2)\r\nRequirement already satisfied: idna>=2.8 in lib/python3.9/site-packages (from anyio==3.*->httpcore<0.16.0,>=0.15.0->httpx>=0.20->datasette>=0.59) (3.4)\r\nRequirement already satisfied: typing-extensions>=3.7.4.3 in lib/python3.9/site-packages (from janus>=0.6.2->datasette>=0.59) (4.4.0)\r\nRequirement already satisfied: MarkupSafe>=2.0 in lib/python3.9/site-packages (from Jinja2>=2.10.3->datasette>=0.59) (2.1.1)\r\nRequirement already satisfied: tabulate in lib/python3.9/site-packages (from sqlite-utils->datasette-app-support>=0.11.6) (0.9.0)\r\nRequirement already satisfied: python-dateutil in lib/python3.9/site-packages (from sqlite-utils->datasette-app-support>=0.11.6) (2.8.2)\r\nRequirement already satisfied: sqlite-fts4 in lib/python3.9/site-packages (from sqlite-utils->datasette-app-support>=0.11.6) (1.0.3)\r\nRequirement already satisfied: pyparsing!=3.0.5,>=2.0.2 in lib/python3.9/site-packages (from packaging->datasette-app-support>=0.11.6) (3.0.9)\r\nRequirement already satisfied: six>=1.5 in lib/python3.9/site-packages (from python-dateutil->sqlite-utils->datasette-app-support>=0.11.6) (1.16.0)\r\nTook 784ms\r\n```\r\nSTUCK", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1994/reactions\", \"total_count\": 1, \"+1\": 1, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1552368054, "node_id": "I_kwDOBm6k_c5ch0G2", "number": 2000, "title": "rewrite_sql hook", "user": {"value": 193185, "label": "cldellow"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2023-01-23T01:02:52Z", "updated_at": "2023-01-23T06:08:01Z", "closed_at": null, "author_association": "CONTRIBUTOR", "pull_request": null, "body": "I'm not sold that this is a good idea, but thought it'd be worth writing up a ticket. Proposal: add a hook like\r\n\r\n```python\r\ndef rewrite_sql(datasette, database, request, fn, sql, params)\r\n```\r\n\r\nIt would be called from Database.execute, Database.execute_write, Database.execute_write_script, Database.execute_write_many before running the user's SQL. `fn` would indicate which method was being used, in case that's relevant for the SQL inspection -- for example `execute` only permits a single statement.\r\n\r\nThe hook could return a SQL statement to be executed instead, or an async function to be awaited on that returned the SQL to be executed.\r\n\r\nPlugins that could be written with this hook:\r\n\r\n- https://github.com/cldellow/datasette-ersatz-table-valued-functions would use this to avoid monkey-patching\r\n- a plugin to inspect and reject unsafe Spatialite function calls (reported by [Simon in Discord](https://discord.com/channels/823971286308356157/823971286941302908/1066438832293159004))\r\n- a plugin to do more general rewrites of queries to enforce table or row-level security, for example, based on the currently logged in actor's ID\r\n- a plugin to maintain audit tables when users write to a table\r\n- a plugin to cache expensive queries (eg the queries that drive facets) - these could allow stale reads if previously cached, then refresh them in an offline queue\r\n\r\nFlaws with this idea:\r\n\r\n`execute_fn` and `execute_write_fn` would not go through this hook, which limits the guarantees you can make about it for security purposes.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/2000/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 749283032, "node_id": "MDU6SXNzdWU3NDkyODMwMzI=", "number": 1101, "title": "register_output_renderer() should support streaming data", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": {"value": 3268330, "label": "Datasette 1.0"}, "comments": 13, "created_at": "2020-11-24T02:17:09Z", "updated_at": "2023-01-21T22:07:19Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "> I'd like to implement this by first extending the `register_output_renderer()` hook to support streaming huge responses, then switching CSV to use the plugin hook in addition to TSV using it.\r\n\r\n_Originally posted by @simonw in https://github.com/simonw/datasette/issues/1096#issuecomment-732542285_", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1101/reactions\", \"total_count\": 1, \"+1\": 1, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1551113681, "node_id": "I_kwDOBm6k_c5cdB3R", "number": 1998, "title": "`datasette --version` should also show the SQLite version", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 2, "created_at": "2023-01-20T16:11:30Z", "updated_at": "2023-01-20T18:19:06Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "Idea came up here: https://discord.com/channels/823971286308356157/823971286941302908/1066026473003159783", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1998/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1550536442, "node_id": "I_kwDOCGYnMM5ca076", "number": 521, "title": "Custom JSON encoder", "user": {"value": 31504, "label": "janrito"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-01-20T09:19:40Z", "updated_at": "2023-01-20T09:19:40Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "It would be nice if we could specify a custom encoder (and decoder) for types that will need extra deserialisation \u2013 e.g., sets, enums or sparse matrices \u2013 or even project-specific types", "repo": {"value": 140912432, "label": "sqlite-utils"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/sqlite-utils/issues/521/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1533673397, "node_id": "I_kwDOBm6k_c5baf-1", "number": 1991, "title": "fts5 tables are not auto-detected and hidden", "user": {"value": 83819, "label": "keturn"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-01-15T06:00:42Z", "updated_at": "2023-01-20T04:54:24Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "I set up a [Datasette instance](https://huggingface.co/spaces/Sygil/INE-dataset-explorer/tree/main) and was following the docs on full-text search.\r\n\r\nWhen I used fts4, datasette automatically hid the FTS tables and added the FTS search box where appropriate, but when I changed to fts5 it no longer does either.\r\n\r\nIf I [manually set](https://huggingface.co/spaces/keturn/INED-datasette/blob/main/metadata.json#L9) `fts_table` for a view, then search does work as expected.\r\n\r\nMy table and view creation code looks like this:\r\n```py\r\nconnection.execute(\"\"\"CREATE TABLE IF NOT EXISTS\r\n captions(image_key text PRIMARY KEY, caption text NOT NULL)\r\n\"\"\")\r\n\u00a0\r\nconnection.execute(\"\"\"CREATE VIRTUAL TABLE\r\n captions_fts USING\r\n fts5(caption, image_key UNINDEXED, content=captions)\r\n\"\"\")\r\n```", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1991/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1538342965, "node_id": "PR_kwDOBm6k_c5HpNYo", "number": 1996, "title": "Document custom json encoder", "user": {"value": 25778, "label": "eyeseast"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2023-01-18T16:54:14Z", "updated_at": "2023-01-19T12:55:57Z", "closed_at": null, "author_association": "CONTRIBUTOR", "pull_request": "simonw/datasette/pulls/1996", "body": "Closes #1983 \r\n\r\nAll documentation here. Edits welcome.\r\n\r\n\r\n\r\n----\n:books: Documentation preview :books:: https://datasette--1996.org.readthedocs.build/en/1996/\n\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "pull", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1996/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": 0, "state_reason": null} {"id": 1538197093, "node_id": "I_kwDOBm6k_c5brwZl", "number": 1995, "title": "foreign_keys error 500", "user": {"value": 137183, "label": "jonschoning"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-01-18T15:27:36Z", "updated_at": "2023-01-18T16:44:01Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "**Error 500 expected string or bytes-like object**\r\n\r\n[espial-new.sqlite3.zip](https://github.com/simonw/datasette/files/10447965/espial-new.sqlite3.zip)\r\n\r\nrun `datasette espial-new.sqlite3` & click on any table other than `User`\r\n\r\n```\r\n/home/jon/.local/lib/python3.10/site-packages/datasette/app.py:814 in \u2502\r\n\u2502 expand_foreign_keys \u2502\r\n\u2502 \u2502\r\n\u2502 811 \u2502 \u2502 \u2502 from {other_table} \u2502\r\n\u2502 812 \u2502 \u2502 \u2502 where {other_column} in ({placeholders}) \u2502\r\n\u2502 813 \u2502 \u2502 \"\"\".format( \u2502\r\n\u2502 \u2771 814 \u2502 \u2502 \u2502 other_column=escape_sqlite(fk[\"other_column\"]), \u2502\r\n\u2502 815 \u2502 \u2502 \u2502 label_column=escape_sqlite(label_column), \u2502\r\n\u2502 816 \u2502 \u2502 \u2502 other_table=escape_sqlite(fk[\"other_table\"]), \u2502\r\n\u2502 817 \u2502 \u2502 \u2502 placeholders=\", \".join([\"?\"] * len(set(values))), \u2502\r\n\u2502 \u2502\r\n\u2502 \u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 locals \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e \u2502\r\n\u2502 \u2502 column = 'user_id' \u2502 \u2502\r\n\u2502 \u2502 database = 'espial-new' \u2502 \u2502\r\n\u2502 \u2502 db = \u2502 \u2502\r\n\u2502 \u2502 fk = { \u2502 \u2502\r\n\u2502 \u2502 \u2502 'column': 'user_id', \u2502 \u2502\r\n\u2502 \u2502 \u2502 'other_table': 'user', \u2502 \u2502\r\n\u2502 \u2502 \u2502 'other_column': None \u2502 \u2502\r\n\u2502 \u2502 } \u2502 \u2502\r\n\u2502 \u2502 foreign_keys = [ \u2502 \u2502\r\n\u2502 \u2502 \u2502 { \u2502 \u2502\r\n\u2502 \u2502 \u2502 \u2502 'column': 'user_id', \u2502 \u2502\r\n\u2502 \u2502 \u2502 \u2502 'other_table': 'user', \u2502 \u2502\r\n\u2502 \u2502 \u2502 \u2502 'other_column': None \u2502 \u2502\r\n\u2502 \u2502 \u2502 } \u2502 \u2502\r\n\u2502 \u2502 ] \u2502 \u2502\r\n\u2502 \u2502 label_column = 'name' \u2502 \u2502\r\n\u2502 \u2502 labeled_fks = {} \u2502 \u2502\r\n\u2502 \u2502 self = \u2502 \u2502\r\n\u2502 \u2502 table = 'bookmark' \u2502 \u2502\r\n\u2502 \u2502 values = [] \u2502 \u2502\r\n\u2502 \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f \u2502\r\n\u2502 \u2502\r\n\u2502 /home/jon/.local/lib/python3.10/site-packages/datasette/utils/__init__.py:346 \u2502\r\n\u2502 in escape_sqlite \u2502\r\n\u2502 \u2502\r\n\u2502 343 \u2502\r\n\u2502 344 \u2502\r\n\u2502 345 def escape_sqlite(s): \u2502\r\n\u2502 \u2771 346 \u2502 if _boring_keyword_re.match(s) and (s.lower() not in reserved_words) \u2502\r\n\u2502 347 \u2502 \u2502 return s \u2502\r\n\u2502 348 \u2502 else: \u2502\r\n\u2502 349 \u2502 \u2502 return f\"[{s}]\" \u2502\r\n\u2502 \u2502\r\n\u2502 \u256d\u2500 locals \u2500\u256e \u2502\r\n\u2502 \u2502 s = None \u2502 \u2502\r\n\u2502 \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f \u2502\r\n\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f\r\nTypeError: expected string or bytes-like object\r\nTraceback (most recent call last):\r\n File \"/home/jon/.local/lib/python3.10/site-packages/datasette/app.py\", line 1354, in route_path\r\n response = await view(request, send)\r\n File \"/home/jon/.local/lib/python3.10/site-packages/datasette/views/base.py\", line 134, in view\r\n return await self.dispatch_request(request)\r\n File \"/home/jon/.local/lib/python3.10/site-packages/datasette/views/base.py\", line 91, in dispatch_request\r\n return await handler(request)\r\n File \"/home/jon/.local/lib/python3.10/site-packages/datasette/views/base.py\", line 361, in get\r\n response_or_template_contexts = await self.data(request, **data_kwargs)\r\n File \"/home/jon/.local/lib/python3.10/site-packages/datasette/views/table.py\", line 158, in data\r\n return await self._data_traced(request, default_labels, _next, _size)\r\n File \"/home/jon/.local/lib/python3.10/site-packages/datasette/views/table.py\", line 603, in _data_traced\r\n await self.ds.expand_foreign_keys(\r\n File \"/home/jon/.local/lib/python3.10/site-packages/datasette/app.py\", line 814, in expand_foreign_keys\r\n other_column=escape_sqlite(fk[\"other_column\"]),\r\n File \"/home/jon/.local/lib/python3.10/site-packages/datasette/utils/__init__.py\", line 346, in escape_sqlite\r\n if _boring_keyword_re.match(s) and (s.lower() not in reserved_words):\r\nTypeError: expected string or bytes-like object\r\nINFO: 127.0.0.1:38574 - \"GET /espial-new/bookmark HTTP/1.1\" 500 Internal Server Error\r\nINFO: 127.0.0.1:38574 - \"GET /-/static/app.css?d59929 HTTP/1.1\" 200 OK\r\n```\r\n\r\nSchema:\r\n```\r\nCREATE TABLE IF NOT EXISTS \"user\"\r\n (\r\n \"id\" INTEGER PRIMARY KEY,\r\n \"name\" VARCHAR NOT NULL,\r\n \"password_hash\" VARCHAR NOT NULL,\r\n \"api_token\" VARCHAR NULL,\r\n \"private_default\" BOOLEAN NOT NULL,\r\n \"archive_default\" BOOLEAN NOT NULL,\r\n \"privacy_lock\" BOOLEAN NOT NULL,\r\n CONSTRAINT \"unique_user_name\" UNIQUE (\"name\")\r\n );\r\n\r\nCREATE TABLE IF NOT EXISTS \"bookmark\"\r\n (\r\n \"id\" INTEGER PRIMARY KEY,\r\n \"user_id\" INTEGER NOT NULL REFERENCES \"user\" ON DELETE RESTRICT ON UPDATE RESTRICT,\r\n \"slug\" VARCHAR NOT NULL DEFAULT (Lower(Hex(Randomblob(6)))),\r\n \"href\" VARCHAR NOT NULL,\r\n \"description\" VARCHAR NOT NULL,\r\n \"extended\" VARCHAR NOT NULL,\r\n \"time\" TIMESTAMP NOT NULL,\r\n \"shared\" BOOLEAN NOT NULL,\r\n \"to_read\" BOOLEAN NOT NULL,\r\n \"selected\" BOOLEAN NOT NULL,\r\n \"archive_href\" VARCHAR NULL,\r\n CONSTRAINT \"unique_user_href\" UNIQUE (\"user_id\", \"href\"),\r\n CONSTRAINT \"unique_user_slug\" UNIQUE (\"user_id\", \"slug\")\r\n );\r\n\r\nCREATE TABLE IF NOT EXISTS \"bookmark_tag\"\r\n (\r\n \"id\" INTEGER PRIMARY KEY,\r\n \"user_id\" INTEGER NOT NULL REFERENCES \"user\" ON DELETE RESTRICT ON UPDATE RESTRICT,\r\n \"tag\" VARCHAR NOT NULL,\r\n \"bookmark_id\" INTEGER NOT NULL REFERENCES \"bookmark\" ON DELETE RESTRICT ON UPDATE RESTRICT,\r\n \"seq\" INTEGER NOT NULL,\r\n CONSTRAINT \"unique_user_tag_bookmark_id\" UNIQUE (\"user_id\", \"tag\", \"bookmark_id\"),\r\n CONSTRAINT \"unique_user_bookmark_id_tag_seq\" UNIQUE (\"user_id\", \"bookmark_id\", \"tag\", \"seq\")\r\n );\r\n\r\nCREATE TABLE IF NOT EXISTS \"note\"\r\n (\r\n \"id\" INTEGER PRIMARY KEY,\r\n \"user_id\" INTEGER NOT NULL REFERENCES \"user\" ON DELETE RESTRICT ON UPDATE RESTRICT,\r\n \"slug\" VARCHAR NOT NULL DEFAULT (Lower(Hex(Randomblob(10)))),\r\n \"length\" INTEGER NOT NULL,\r\n \"title\" VARCHAR NOT NULL,\r\n \"text\" VARCHAR NOT NULL,\r\n \"is_markdown\" BOOLEAN NOT NULL,\r\n \"shared\" BOOLEAN NOT NULL DEFAULT false,\r\n \"created\" TIMESTAMP NOT NULL,\r\n \"updated\" TIMESTAMP NOT NULL\r\n );\r\nCREATE INDEX idx_bookmark_time ON bookmark (user_id, time DESC);\r\nCREATE INDEX idx_bookmark_tag_bookmark_id ON bookmark_tag (bookmark_id, id, tag, seq);\r\nCREATE INDEX idx_note_user_created ON note (user_id, created DESC); \r\n```\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1995/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1532000914, "node_id": "I_kwDOBm6k_c5bUHqS", "number": 1990, "title": "Suggestion: Highlight error messages ('These facets timed out')", "user": {"value": 116795, "label": "pax"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-01-13T09:40:58Z", "updated_at": "2023-01-13T09:40:58Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "I had trouble figuring out why faceting didn't work in some instances, it took a while before I noticed the _These facets timed out_ notice. \r\n\r\nIt might help if that would be highlighted, or fading out highlight - if one might think it would be too visually disturbing.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1990/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1529707837, "node_id": "I_kwDOBm6k_c5bLX09", "number": 1988, "title": "Reconsider pattern where plugins could break existing template context", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": {"value": 3268330, "label": "Datasette 1.0"}, "comments": 4, "created_at": "2023-01-11T21:13:43Z", "updated_at": "2023-01-11T21:25:05Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "> I hadn't run into an issue with plugins like `datasette-template-sql` interfering with the existing context for other features before! Definitely not a good thing.\r\n\r\n_Originally posted by @simonw in https://github.com/simonw/datasette-write/issues/6#issuecomment-1379490596_\r\n ", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1988/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1509783085, "node_id": "I_kwDOBm6k_c5Z_XYt", "number": 1969, "title": "sql-formatter javascript is not now working with CloudFlare rocketloader", "user": {"value": 536941, "label": "fgregg"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2022-12-23T21:14:06Z", "updated_at": "2023-01-10T01:56:33Z", "closed_at": null, "author_association": "CONTRIBUTOR", "pull_request": null, "body": "This is probably not a bug with datasette, but I thought you might want to know, @simonw.\r\n\r\nI noticed today that my CloudFlare proxied datasette instance lost the \"Format SQL\" option. I'm pretty sure it was there last week.\r\n\r\nIn the CloudFlare settings, if I turn off [Rocket Loader](https://developers.cloudflare.com/fundamentals/speed/rocket-loader/), I get the \"Format SQL\" option back.\r\n\r\nRocket Loader works by asynchronously loading the javascript, so maybe there was a recent change that doesn't play well with the asynch loading?\r\n\r\nI'm up to date with https://github.com/simonw/datasette/commit/e03aed00026cc2e59c09ca41f69a247e1a85cc89", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1969/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1525815985, "node_id": "I_kwDOBm6k_c5a8hqx", "number": 1983, "title": "Make CustomJSONEncoder a documented public API", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 3, "created_at": "2023-01-09T15:27:05Z", "updated_at": "2023-01-09T15:35:58Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "It's used by `datasette-geojson` here: https://github.com/eyeseast/datasette-geojson/commit/902bf135a5a33a0dc8264673d00a59a67cb05152", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1983/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1426080014, "node_id": "I_kwDOBm6k_c5VAEEO", "number": 1867, "title": "/db/table/-/rename API (also allows atomic replace)", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": {"value": 8755003, "label": "Datasette 1.0a-next"}, "comments": 1, "created_at": "2022-10-27T18:13:23Z", "updated_at": "2023-01-09T15:34:12Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "> There's one catch with batched inserts: if your CLI tool fails half way through you could end up with a partially populated table - since a bunch of batches will have succeeded first.\r\n>\r\n> ...\r\n>\r\n> If people care about that kind of thing they could always push all of their inserts to a table called `_tablename` and then atomically rename that once they've uploaded all of the data (assuming I provide an atomic-rename-this-table mechanism).\r\n\r\n_Originally posted by @simonw in https://github.com/simonw/datasette/issues/1866#issuecomment-1293893789_\r\n ", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1867/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1082584499, "node_id": "I_kwDOBm6k_c5Ahu2z", "number": 1558, "title": "Redesign `facet_results` JSON structure prior to Datasette 1.0", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": {"value": 3268330, "label": "Datasette 1.0"}, "comments": 3, "created_at": "2021-12-16T19:45:10Z", "updated_at": "2023-01-09T15:31:17Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "> Decision: as an initial fix I'm going to de-duplicate those keys by using `tags__array` etc - with a `_2` on the end if that key is already used.\r\n>\r\n> I'll open a separate issue to redesign this better for Datasette 1.0.\r\n\r\n_Originally posted by @simonw in https://github.com/simonw/datasette/issues/625#issuecomment-996130862_", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1558/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1524983536, "node_id": "I_kwDOBm6k_c5a5Wbw", "number": 1981, "title": "Canned query field labels truncated", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 1, "created_at": "2023-01-09T06:04:24Z", "updated_at": "2023-01-09T06:05:44Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "Eg here on mobile: https://timezones.datasette.io/timezones/by_point?longitude=-0.1406632&latitude=50.8246776\r\n\r\n![107A1894-D1DA-4158-9EA3-40C840DD10E3](https://user-images.githubusercontent.com/9599/211248895-c922ce61-95d3-47ca-9314-dcff7c86afab.jpeg)\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1981/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1524867951, "node_id": "I_kwDOBm6k_c5a46Nv", "number": 1980, "title": "\"Cannot sort table by id\" when sortable_columns is used", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 2, "created_at": "2023-01-09T03:21:33Z", "updated_at": "2023-01-09T03:23:53Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "I had an instance with this in `metadata.yml`:\r\n\r\n```yaml\r\ndatabases:\r\n timezones:\r\n tables:\r\n timezones:\r\n sortable_columns:\r\n - tzid\r\n```\r\nWhen I clicked on the \"Apply\" button here:\r\n\r\n\"image\"\r\n\r\nIt sent me to `/timezones/timezones?_sort=id&id__exact=133` with the error message:\r\n\r\n> 500: Cannot sort table by id", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1980/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1524431805, "node_id": "I_kwDODEm0Qs5a3Pu9", "number": 72, "title": "Import thread, including self- and others' replies", "user": {"value": 601708, "label": "mcint"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-01-08T09:51:06Z", "updated_at": "2023-01-08T09:51:06Z", "closed_at": null, "author_association": "NONE", "pull_request": null, "body": "statuses-lookup, home-timeline, mentions (only for auth'ed user) don't cover this.\r\n\r\n`twitter-to-sqlite fetch-thread tw-group1.db 1234123412341234`\r\n\r\ntwitter-to-sqlite focuses on archiving users, but does not easily support archiving conversations or community activity.\r\n\r\nFor reference, this is [implemented in twarc](https://sourcegraph.com/github.com/DocNow/twarc/-/blob/twarc/client.py?L708-766&subtree=true), using a search, optionally recursively.\r\n\r\nOther research suggests that this formerly, or currently, requires a [search query](https://stackoverflow.com/a/30480103/1020467), use of [undocumented `related_results` api](https://stackoverflow.com/a/9419346/1020467), or with requested inclusion of [newer conversation_id](https://stackoverflow.com/a/68115718/1020467) with subsequent query.\r\n\r\n", "repo": {"value": 206156866, "label": "twitter-to-sqlite"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/dogsheep/twitter-to-sqlite/issues/72/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 779088071, "node_id": "MDU6SXNzdWU3NzkwODgwNzE=", "number": 54, "title": "Archive import appears to be broken on recent exports", "user": {"value": 21148, "label": "jacobian"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 5, "created_at": "2021-01-05T14:18:01Z", "updated_at": "2023-01-04T11:06:55Z", "closed_at": null, "author_association": "CONTRIBUTOR", "pull_request": null, "body": "I requested a Twitter export yesterday, and unfortunately they seem to have changed it such that `twitter-to-sqlite import` can't handle it anymore \ud83d\ude22 \r\n\r\nSo far I've ran into two issues. The first was easy to work around, but the second will take more investigation. If I can find the time I'll keep working on it and update this issue accordingly.\r\n\r\nThe issues (so far):\r\n\r\n### 1. Data seems to have moved to a `data/` subdirectory\r\n\r\nRunning `twitter-to-sqlite import` on the raw zip file reports a bunch of \"not yet implemented\" errors, and then exits without actually importing anything:\r\n\r\n```\r\n\u276f twitter-to-sqlite import tarchive.db twitter.zip\r\n...\r\ndata/manifest: not yet implemented\r\ndata/account-creation-ip: not yet implemented\r\ndata/account-suspension: not yet implemented\r\n... (dozens of more lines like this, including critical stuff like data/tweets) ...\r\n```\r\n\r\n(`tarchive.db` now exists, but is empty)\r\n\r\nWorkaround: unpack the zip file, and run `twitter-to-sqlite import tarchive.db path/to/archive/data`\r\n\r\nThat gets further, but:\r\n\r\n### 2. Some schema(s?) have changed\r\n\r\nAt least, the `blocks` schema seems different now:\r\n\r\n```\r\n\u276f twitter-to-sqlite import tarchive.db archive/data\r\ndirect-messages-group: not yet implemented\r\nbranch-links: not yet implemented\r\nperiscope-expired-broadcasts: not yet implemented\r\ndirect-messages: not yet implemented\r\nmute: not yet implemented\r\nTraceback (most recent call last):\r\n File \"/Users/jacob/Library/Caches/pypoetry/virtualenvs/jacobian-dogsheep-4AXaN4tu-py3.8/bin/twitter-to-sqlite\", line 8, in \r\n sys.exit(cli())\r\n File \"/Users/jacob/Library/Caches/pypoetry/virtualenvs/jacobian-dogsheep-4AXaN4tu-py3.8/lib/python3.8/site-packages/click/core.py\", line 829, in __call__\r\n return self.main(*args, **kwargs)\r\n File \"/Users/jacob/Library/Caches/pypoetry/virtualenvs/jacobian-dogsheep-4AXaN4tu-py3.8/lib/python3.8/site-packages/click/core.py\", line 782, in main\r\n rv = self.invoke(ctx)\r\n File \"/Users/jacob/Library/Caches/pypoetry/virtualenvs/jacobian-dogsheep-4AXaN4tu-py3.8/lib/python3.8/site-packages/click/core.py\", line 1259, in invoke\r\n return _process_result(sub_ctx.command.invoke(sub_ctx))\r\n File \"/Users/jacob/Library/Caches/pypoetry/virtualenvs/jacobian-dogsheep-4AXaN4tu-py3.8/lib/python3.8/site-packages/click/core.py\", line 1066, in invoke\r\n return ctx.invoke(self.callback, **ctx.params)\r\n File \"/Users/jacob/Library/Caches/pypoetry/virtualenvs/jacobian-dogsheep-4AXaN4tu-py3.8/lib/python3.8/site-packages/click/core.py\", line 610, in invoke\r\n return callback(*args, **kwargs)\r\n File \"/Users/jacob/Library/Caches/pypoetry/virtualenvs/jacobian-dogsheep-4AXaN4tu-py3.8/lib/python3.8/site-packages/twitter_to_sqlite/cli.py\", line 772, in import_\r\n archive.import_from_file(db, filepath.name, open(filepath, \"rb\").read())\r\n File \"/Users/jacob/Library/Caches/pypoetry/virtualenvs/jacobian-dogsheep-4AXaN4tu-py3.8/lib/python3.8/site-packages/twitter_to_sqlite/archive.py\", line 215, in import_from_file\r\n to_insert = transformer(data)\r\n File \"/Users/jacob/Library/Caches/pypoetry/virtualenvs/jacobian-dogsheep-4AXaN4tu-py3.8/lib/python3.8/site-packages/twitter_to_sqlite/archive.py\", line 115, in lists_member\r\n return {\"lists-member\": _list_from_common(data)}\r\n File \"/Users/jacob/Library/Caches/pypoetry/virtualenvs/jacobian-dogsheep-4AXaN4tu-py3.8/lib/python3.8/site-packages/twitter_to_sqlite/archive.py\", line 200, in _list_from_common\r\n for url in block[\"userListInfo\"][\"urls\"]:\r\nKeyError: 'urls'\r\n```\r\n\r\nThat's as far as I got before I needed to work on something else. I'll report back if I get further!", "repo": {"value": 206156866, "label": "twitter-to-sqlite"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/dogsheep/twitter-to-sqlite/issues/54/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1516815571, "node_id": "I_kwDOBm6k_c5aaMTT", "number": 1975, "title": "_col=id can cause id column to export twice in CSV export", "user": {"value": 9599, "label": "simonw"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-01-03T00:25:15Z", "updated_at": "2023-01-03T00:25:21Z", "closed_at": null, "author_association": "OWNER", "pull_request": null, "body": "https://datasette.simonwillison.net/simonwillisonblog/blog_entry.csv?_col=id&_col=title&_col=body&_labels=on&_size=1\r\n\r\n```csv\r\nid,id,title,body\r\n1,1,WaSP Phase II,\"

The Web Standards project has launched Phase II.

\"\r\n```\r\nThat should not have two `id` columns.", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1975/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": null} {"id": 1515717718, "node_id": "PR_kwDOC8tyDs5Gc-VH", "number": 23, "title": "Include workout statistics", "user": {"value": 2129, "label": "badboy"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2023-01-01T17:29:57Z", "updated_at": "2023-01-01T17:29:57Z", "closed_at": null, "author_association": "FIRST_TIME_CONTRIBUTOR", "pull_request": "dogsheep/healthkit-to-sqlite/pulls/23", "body": "Not sure when this changed (iOS 16 maybe?), but the `WorkoutStatistics` now has a whole bunch of information about workouts, e.g. for runs it contains the distance (as a `` element).\r\n\r\nAdding it as another column at leat allows me to pull these out (using SQLite's JSON support).\r\nI'm running with this patch on my own data now.", "repo": {"value": 197882382, "label": "healthkit-to-sqlite"}, "type": "pull", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/dogsheep/healthkit-to-sqlite/issues/23/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": 0, "state_reason": null} {"id": 1513238455, "node_id": "PR_kwDODEm0Qs5GUoPm", "number": 71, "title": "Archive: Fix \"ni devices\" typo in importer", "user": {"value": 26161409, "label": "sometimes-i-send-pull-requests"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2022-12-28T23:33:31Z", "updated_at": "2022-12-28T23:33:31Z", "closed_at": null, "author_association": "FIRST_TIME_CONTRIBUTOR", "pull_request": "dogsheep/twitter-to-sqlite/pulls/71", "body": null, "repo": {"value": 206156866, "label": "twitter-to-sqlite"}, "type": "pull", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/dogsheep/twitter-to-sqlite/issues/71/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": 0, "state_reason": null} {"id": 1513238314, "node_id": "PR_kwDODEm0Qs5GUoN6", "number": 70, "title": "Archive: Import Twitter Circle data", "user": {"value": 26161409, "label": "sometimes-i-send-pull-requests"}, "state": "open", "locked": 0, "assignee": null, "milestone": null, "comments": 0, "created_at": "2022-12-28T23:33:09Z", "updated_at": "2022-12-28T23:33:09Z", "closed_at": null, "author_association": "FIRST_TIME_CONTRIBUTOR", "pull_request": "dogsheep/twitter-to-sqlite/pulls/70", "body": null, "repo": {"value": 206156866, "label": "twitter-to-sqlite"}, "type": "pull", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/dogsheep/twitter-to-sqlite/issues/70/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": 0, "state_reason": null}