{"html_url": "https://github.com/simonw/datasette/issues/2104#issuecomment-1638552567", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2104", "id": 1638552567, "node_id": "IC_kwDOBm6k_c5hqlP3", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-07-17T17:14:20Z", "updated_at": "2023-07-17T17:14:20Z", "author_association": "OWNER", "body": "Relevant code: https://github.com/simonw/datasette/blob/0f7192b6154edb576c41b55bd3f2a3f53e5f436a/datasette/database.py#L391-L451", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1808215339, "label": "Tables starting with an underscore should be treated as hidden"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1640064620", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1640064620, "node_id": "IC_kwDOBm6k_c5hwWZs", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-07-18T11:47:21Z", "updated_at": "2023-07-18T11:47:21Z", "author_association": "OWNER", "body": "I think I've figured out the problem here.\r\n\r\nThe question being asked is \"can this actor access this resource, which is within this database within this instance\".\r\n\r\nThe answer to this question needs to consider the full set of questions at once - yes they can access within this instance IF they have access to the specified table and that's the table being asked about.\r\n\r\nBut the questions are currently being asked independently, which means the plugin hook acting on `view-instance` can't see that the answer here should be yes because it's actually about a table that the actor has explicit permission to view.\r\n\r\nSo I think I may need to redesign the plugin hook to always see the full hierarchy of checks, not just a single check at a time.\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1638567228", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1638567228, "node_id": "IC_kwDOBm6k_c5hqo08", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-07-17T17:24:19Z", "updated_at": "2023-07-17T17:25:12Z", "author_association": "OWNER", "body": "Confirmed that this is an issue with regular Datasette signed tokens as well. I created one on https://latest.datasette.io/-/create-token with these details:\r\n```json\r\n{\r\n \"_r\": {\r\n \"r\": {\r\n \"fixtures\": {\r\n \"sortable\": [\r\n \"vt\"\r\n ]\r\n }\r\n }\r\n },\r\n \"a\": \"root\",\r\n \"d\": 3600,\r\n \"t\": 1689614483\r\n}\r\n```\r\nRun like this:\r\n```\r\ncurl -H 'Authorization: Bearer dstok_eyJhIjoicm9vdCIsInQiOjE2ODk2MTQ0ODMsImQiOjM2MDAsIl9yIjp7InIiOnsiZml4dHVyZXMiOnsic29ydGFibGUiOlsidnQiXX19fX0.n-VGxxawz1Q0WK7sqLfhXUgcvY0' \\\r\n https://latest.datasette.io/fixtures/sortable.json\r\n```\r\nReturned an HTML Forbidden page:\r\n```html\r\n\r\n\r\n\r\n Forbidden\r\n \r\n...\r\n```\r\nSame token againts `/-/actor.json` returns:\r\n```json\r\n{\r\n \"actor\": {\r\n \"id\": \"root\",\r\n \"token\": \"dstok\",\r\n \"_r\": {\r\n \"r\": {\r\n \"fixtures\": {\r\n \"sortable\": [\r\n \"vt\"\r\n ]\r\n }\r\n }\r\n },\r\n \"token_expires\": 1689618083\r\n }\r\n}\r\n```\r\nReminder - `\"_r\"` means restrict, `\"r\"` means resource.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1636093730", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1636093730, "node_id": "IC_kwDOBm6k_c5hhM8i", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-07-14T16:26:27Z", "updated_at": "2023-07-14T16:32:49Z", "author_association": "OWNER", "body": "Here's that crucial comment:\r\n\r\n> If _r is defined then we use those to further restrict the actor.\r\n>\r\n>Crucially, we only use this to say NO (return False) - we never use it to return YES (True) because that might over-ride other restrictions placed on this actor\r\n\r\nSo that's why I implemented it like this.\r\n\r\nThe goal here is to be able to issue a token which can't do anything _more_ than the actor it is associated with, but CAN be configured to do less.\r\n\r\nSo I think the solution here is for the `_r` checking code to perhaps implement its own view cascade logic - it notices if you have `view-table` and consequently fails to block `view-table` and `view-instance`.\r\n\r\nI'm not sure that's going to work though - would that mean that granting `view-table` grants `view-database` in a surprising and harmful way?\r\n\r\nMaybe that's OK: if you have `view-database` but permission checks fail for individual tables and queries you shouldn't be able to see a thing that you shouldn't. Need to verify that though.\r\n\r\nAlso, do `Permission` instances have enough information to implement this kind of cascade without hard-coding anything? \r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1636053060", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1636053060, "node_id": "IC_kwDOBm6k_c5hhDBE", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-07-14T15:51:36Z", "updated_at": "2023-07-14T16:14:05Z", "author_association": "OWNER", "body": "This might only be an issue with the code that checks `_r` on actors.\r\n\r\nhttps://github.com/simonw/datasette/blob/0f7192b6154edb576c41b55bd3f2a3f53e5f436a/datasette/default_permissions.py#L185-L222\r\n\r\nAdded in https://github.com/simonw/datasette/commit/bcc781f4c50a8870e3389c4e60acb625c34b0317 - refs:\r\n\r\n- #1855 ", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1636042066", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1636042066, "node_id": "IC_kwDOBm6k_c5hhAVS", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-07-14T15:41:54Z", "updated_at": "2023-07-14T15:42:32Z", "author_association": "OWNER", "body": "I tried some code spelunking and came across https://github.com/simonw/datasette/commit/d6e03b04302a0852e7133dc030eab50177c37be7 which says:\r\n\r\n> - If you have table permission but not database permission you can now view the table page\r\n\r\nRefs:\r\n- #832 \r\n\r\nWhich suggests that my initial design decision wasn't what appears to be implemented today.\r\n\r\nNeeds more investigation.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1636040164", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1636040164, "node_id": "IC_kwDOBm6k_c5hg_3k", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-07-14T15:40:21Z", "updated_at": "2023-07-14T15:40:21Z", "author_association": "OWNER", "body": "Relevant code: \r\nhttps://github.com/simonw/datasette/blob/0f7192b6154edb576c41b55bd3f2a3f53e5f436a/datasette/app.py#L822-L855", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1636036312", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1636036312, "node_id": "IC_kwDOBm6k_c5hg-7Y", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-07-14T15:37:14Z", "updated_at": "2023-07-14T15:37:14Z", "author_association": "OWNER", "body": "I think I made this decision because I was thinking about default deny: obviously if a user has been denied access to a database. It doesn't make sense that they could access tables within it.\r\n\r\nBut now that I am spending more time with authentication tokens, which default to denying everything, except for the things that you have explicitly listed, this policy, no longer makes as much sense.\r\n\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2101#issuecomment-1634443907", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2101", "id": 1634443907, "node_id": "IC_kwDOBm6k_c5ha6KD", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-07-13T15:24:17Z", "updated_at": "2023-07-13T15:24:17Z", "author_association": "OWNER", "body": "https://github.com/simonw/datasette/blob/0f7192b6154edb576c41b55bd3f2a3f53e5f436a/datasette/views/table.py#L486-L506", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1803264272, "label": "alter: true support for JSON write API"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/567#issuecomment-1638926655", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/567", "id": 1638926655, "node_id": "IC_kwDOCGYnMM5hsAk_", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-07-17T21:42:37Z", "updated_at": "2023-07-17T21:42:37Z", "author_association": "OWNER", "body": "I really like this. I'm also interested in:\r\n- Plugins that make new custom SQL functions available - similar to this Datasette hook: https://docs.datasette.io/en/stable/plugin_hooks.html#prepare-connection-conn-database-datasette\r\n- Plugins that register functions that can be used as recipes for `sqlite-utils convert` https://sqlite-utils.datasette.io/en/stable/cli.html#sqlite-utils-convert-recipes\r\n\r\nThe upload-data-to-Datasette problem is planned to be solved by a future version of https://github.com/simonw/dclient ", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1801394744, "label": "Plugin system"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/566#issuecomment-1627598570", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/566", "id": 1627598570, "node_id": "IC_kwDOCGYnMM5hAy7q", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-07-09T04:13:34Z", "updated_at": "2023-07-09T04:13:34Z", "author_association": "OWNER", "body": "On consulting https://pypi.org/project/tabulate/ it looks like most of those formats don't actually makes sense without headers - so the right thing here might be to raise an error if `--fmt` and `--no-headers` are used at the same time.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1795219865, "label": "`--no-headers` doesn't work on most formats"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/566#issuecomment-1627597872", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/566", "id": 1627597872, "node_id": "IC_kwDOCGYnMM5hAyww", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-07-09T04:09:56Z", "updated_at": "2023-07-09T04:09:56Z", "author_association": "OWNER", "body": "Thanks, looks like a bug.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1795219865, "label": "`--no-headers` doesn't work on most formats"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/565#issuecomment-1618380888", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/565", "id": 1618380888, "node_id": "IC_kwDOCGYnMM5gdohY", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-07-03T14:09:11Z", "updated_at": "2023-07-03T14:09:31Z", "author_association": "OWNER", "body": "For the CLI:\r\n\r\n```bash\r\nsqlite-utils rename-table data.db old_table_name new_table_name\r\n```\r\n\r\nFor the Python code, should it go on Table or on Database?\r\n```python\r\ndb[\"foo\"].rename_table(\"bar\")\r\n\r\ndb.rename_table(\"foo\", \"bar\")\r\n```\r\nI think I like the second better, it's slightly more clear.\r\n\r\nAlso need a design for an option for the `.transform()` method to indicate that the new table should be created with a new name without dropping the old one.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1786258502, "label": "Table renaming utilities"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/563#issuecomment-1617395444", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/563", "id": 1617395444, "node_id": "IC_kwDOCGYnMM5gZ370", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-07-03T05:44:43Z", "updated_at": "2023-07-03T05:44:43Z", "author_association": "OWNER", "body": "Documentation at the bottom of this section: https://sqlite-utils.datasette.io/en/latest/cli.html#inserting-csv-or-tsv-data", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1785360409, "label": "`--empty-null` option when importing CSV"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/562#issuecomment-1616782404", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/562", "id": 1616782404, "node_id": "IC_kwDOCGYnMM5gXiRE", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-07-02T19:24:14Z", "updated_at": "2023-07-02T19:26:39Z", "author_association": "OWNER", "body": "[Dataclasses](https://docs.python.org/3/library/dataclasses.html) were added in Python 3.7 and `sqlite-utils` was originally written for Python 3.6 - but both 3.6 and 3.7 are EOL now.\r\n\r\nThe thing that makes Dataclasses particularly interesting is the potential to use type annotations with them to help specify the types of the related SQLite columns.\r\n\r\nExample for https://datasette.io/content/users\r\n\r\n```sql\r\nCREATE TABLE [users] (\r\n [login] TEXT,\r\n [id] INTEGER PRIMARY KEY,\r\n [node_id] TEXT,\r\n [avatar_url] TEXT,\r\n [gravatar_id] TEXT,\r\n [html_url] TEXT,\r\n [type] TEXT,\r\n [site_admin] INTEGER,\r\n [name] TEXT\r\n);\r\n```\r\nAnd the dataclass:\r\n```python\r\nfrom dataclasses import dataclass\r\n\r\n@dataclass\r\nclass User:\r\n id: int\r\n login: str\r\n node_id: str\r\n avatar_url: str\r\n gravatar_id: str\r\n html_url: str\r\n type: str\r\n site_admin: int\r\n name: str\r\n```\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1784794489, "label": "Explore the intersection between sqlite-utils and dataclasses"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2093#issuecomment-1614652001", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2093", "id": 1614652001, "node_id": "IC_kwDOBm6k_c5gPaJh", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-30T13:27:13Z", "updated_at": "2023-06-30T13:27:13Z", "author_association": "OWNER", "body": "I agree, settings in the DB doesn't make sense but metadata does.\r\n\r\nOn the JSON v YAML v TOML issue I just spotted Caddy has a concept of config adapters which they use to resolve exactly that problem: https://caddyserver.com/docs/config-adapters", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1781530343, "label": "Proposal: Combine settings, metadata, static, etc. into a single `datasette.toml` File"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2093#issuecomment-1613889979", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2093", "id": 1613889979, "node_id": "IC_kwDOBm6k_c5gMgG7", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-29T22:44:08Z", "updated_at": "2023-06-30T13:25:39Z", "author_association": "OWNER", "body": "I do like also being able to set options using command line options though - for things like SQL time limits I'd much rather be able to throw on `--setting sql_time_limit_ms 10000` than have to save a config file to disk.\r\n\r\nSo I'd want to support both. Which maybe means also having a way to set plugin options with CLI options. `datasette publish` kind of has that ability already:\r\n\r\n```\r\ndatasette publish heroku my_database.db \\\r\n --name my-heroku-app-demo \\\r\n --install=datasette-auth-github \\\r\n --plugin-secret datasette-auth-github client_id your_client_id \\\r\n --plugin-secret datasette-auth-github client_secret your_client_secret\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1781530343, "label": "Proposal: Combine settings, metadata, static, etc. into a single `datasette.toml` File"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2093#issuecomment-1613887492", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2093", "id": 1613887492, "node_id": "IC_kwDOBm6k_c5gMfgE", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-29T22:40:25Z", "updated_at": "2023-06-29T22:40:25Z", "author_association": "OWNER", "body": "I'm strongly in favour of combining settings, configuration and plugin configuration.\r\n\r\nI'm not keen on mixing in metadata as well - that feels like a different concept to me, and I'm unhappy with how that's already had things like plugin settings leak into it.\r\n\r\nI'm not yet sold on TOML - I actually find it less intuitive than YAML, surprisingly. They all have their warts I guess.\r\n\r\nDatasette already has the ability to consume JSON or YAML for metadata - maybe it could grow TOML support too? That way users could have a `datasette.json` or `datasette.yaml` or `datasette.toml` file depending on their preference.\r\n\r\nIn terms of metadata: since that's means to be driven by a plugin hook anyway, maybe one of the potential sources of metadata is a `metadata` nested object in that `datasette.*` configuration file. Or you can have it in a separate `metadata.json` or bundled into the SQLite database or some other plugin-driven mechanism.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1781530343, "label": "Proposal: Combine settings, metadata, static, etc. into a single `datasette.toml` File"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2092#issuecomment-1613381990", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2092", "id": 1613381990, "node_id": "IC_kwDOBm6k_c5gKkFm", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-29T15:23:04Z", "updated_at": "2023-06-29T15:26:28Z", "author_association": "OWNER", "body": "Felt lazy:\r\n```bash\r\nsymbex test_homepage -f tests/test_api.py | \\\r\n llm -m 4 --system 'Change all of the == some integer tests in this code to isinstance(thing, int) instead'\r\n```\r\nOutput:\r\n```python\r\n# File: tests/test_api.py Line: 26\r\n@pytest.mark.asyncio\r\nasync def test_homepage(ds_client):\r\n response = await ds_client.get(\"/.json\")\r\n assert response.status_code == 200\r\n assert \"application/json; charset=utf-8\" == response.headers[\"content-type\"]\r\n data = response.json()\r\n assert isinstance(data.keys(), int)\r\n d = data[\"fixtures\"]\r\n assert d[\"name\"] == \"fixtures\"\r\n assert isinstance(d[\"tables_count\"], int)\r\n assert isinstance(len(d[\"tables_and_views_truncated\"]), int)\r\n assert d[\"tables_and_views_more\"] is True\r\n # 4 hidden FTS tables + no_primary_key (hidden in metadata)\r\n assert isinstance(d[\"hidden_tables_count\"], int)\r\n # 201 in no_primary_key, plus 6 in other hidden tables:\r\n assert isinstance(d[\"hidden_table_rows_sum\"], int), data\r\n assert isinstance(d[\"views_count\"], int)\r\n```\r\nI'll use most of that and delete the obsoleted comments.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1781047747, "label": "test_homepage intermittent failure"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2092#issuecomment-1613375407", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2092", "id": 1613375407, "node_id": "IC_kwDOBm6k_c5gKiev", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-29T15:20:52Z", "updated_at": "2023-06-29T15:21:05Z", "author_association": "OWNER", "body": "I'm going to remove this assertion entirely. The homepage JSON needs a refactor anyway.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1781047747, "label": "test_homepage intermittent failure"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2091#issuecomment-1613369355", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2091", "id": 1613369355, "node_id": "IC_kwDOBm6k_c5gKhAL", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-29T15:18:34Z", "updated_at": "2023-06-29T15:18:34Z", "author_association": "OWNER", "body": "Posted on the Glitch feedback forum about this here: https://support.glitch.com/t/upgrade-python-version-from-3-7-which-is-now-eol-to-something-more-recent/63011", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1781022369, "label": "Drop support for Python 3.7"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2091#issuecomment-1613360413", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2091", "id": 1613360413, "node_id": "IC_kwDOBm6k_c5gKe0d", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-29T15:13:04Z", "updated_at": "2023-06-29T15:13:04Z", "author_association": "OWNER", "body": "One problem: https://glitch.com/ still provides 3.7:\r\n```\r\n$ python3 --version\r\nPython 3.7.10\r\n```\r\n\"image\"\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1781022369, "label": "Drop support for Python 3.7"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2090#issuecomment-1613346412", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2090", "id": 1613346412, "node_id": "IC_kwDOBm6k_c5gKbZs", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-29T15:05:04Z", "updated_at": "2023-06-29T15:05:04Z", "author_association": "OWNER", "body": "Decided to fix just those \"Ambiguous variable name\" ones:\r\n\r\n```bash\r\nruff check . | grep E741\r\n```\r\nThen iterated through and fixed them all.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1781005740, "label": "Adopt ruff for linting"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2090#issuecomment-1613339404", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2090", "id": 1613339404, "node_id": "IC_kwDOBm6k_c5gKZsM", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-29T15:01:01Z", "updated_at": "2023-06-29T15:01:20Z", "author_association": "OWNER", "body": "I tried it just now and got some interesting results.\r\n\r\nI dropped in a `ruff.toml` file:\r\n```toml\r\nline-length = 160\r\n```\r\nBecause the default line length limit of 88 was causing a lot of noisy errors.\r\n\r\nThen run:\r\n\r\n```bash\r\npip install ruff\r\nruff check .\r\n```\r\nPlenty of warnings about unused imports - running `ruff check . --fix` fixed those automatically, but I think I still need to manually review them as some might be imports which are deliberate and should be in `__all__` to ensure they are visible from that module as well.\r\n\r\nSome lines in tests are longer than even 160 chars, e.g.:\r\n\r\nhttps://github.com/simonw/datasette/blob/99ba05118891db9dc30f1dca22ad6709775560de/tests/test_html.py#L673-L681\r\n\r\nThese can have ` # noqa: E501` added to the end of those lines to skip the check for them.\r\n\r\nThat got it down to:\r\n\r\n```\r\n% ruff check . \r\ndatasette/views/table.py:23:5: F811 Redefinition of unused `format_bytes` from line 19\r\nrun_tests.py:2:5: E401 Multiple imports on one line\r\ntests/test_api.py:591:40: F811 Redefinition of unused `app_client_no_files` from line 7\r\ntests/test_api.py:629:35: F811 Redefinition of unused `app_client_no_files` from line 7\r\ntests/test_api.py:635:54: F811 Redefinition of unused `app_client_with_dot` from line 8\r\ntests/test_api.py:661:25: F811 Redefinition of unused `app_client_shorter_time_limit` from line 9\r\ntests/test_api.py:759:25: F811 Redefinition of unused `app_client_two_attached_databases_one_immutable` from line 10\r\ntests/test_api.py:892:28: F811 Redefinition of unused `app_client_larger_cache_size` from line 11\r\ntests/test_api.py:928:5: F811 Redefinition of unused `app_client_with_cors` from line 12\r\ntests/test_api.py:929:5: F811 Redefinition of unused `app_client_two_attached_databases_one_immutable` from line 10\r\ntests/test_api.py:969:38: F811 Redefinition of unused `app_client_two_attached_databases` from line 13\r\ntests/test_api.py:976:39: F811 Redefinition of unused `app_client_conflicting_database_names` from line 14\r\ntests/test_api.py:987:38: F811 Redefinition of unused `app_client_immutable_and_inspect_file` from line 15\r\ntests/test_api.py:1002:24: F811 Redefinition of unused `app_client` from line 6\r\ntests/test_csv.py:67:33: F811 Redefinition of unused `app_client_with_cors` from line 6\r\ntests/test_csv.py:157:21: F811 Redefinition of unused `app_client_csv_max_mb_one` from line 5\r\ntests/test_csv.py:198:20: F811 Redefinition of unused `app_client_with_trace` from line 7\r\ntests/test_csv.py:209:53: F811 Redefinition of unused `app_client_with_trace` from line 7\r\ntests/test_csv.py:215:53: F811 Redefinition of unused `app_client_with_trace` from line 7\r\ntests/test_filters.py:102:11: F811 Redefinition of unused `test_through_filters_from_request` from line 81\r\ntests/test_html.py:19:19: F811 Redefinition of unused `app_client_two_attached_databases` from line 7\r\ntests/test_html.py:175:25: F811 Redefinition of unused `app_client_shorter_time_limit` from line 6\r\ntests/test_html.py:469:51: F811 Redefinition of unused `app_client` from line 4\r\ntests/test_html.py:797:26: F811 Redefinition of unused `app_client_base_url_prefix` from line 5\r\ntests/test_html.py:840:44: F811 Redefinition of unused `app_client_base_url_prefix` from line 5\r\ntests/test_html.py:850:51: F811 Redefinition of unused `app_client_base_url_prefix` from line 5\r\ntests/test_pagination.py:50:43: F821 Undefined name `parse_next`\r\ntests/test_pagination.py:82:7: F811 Redefinition of unused `KeysetPaginator` from line 36\r\ntests/test_plugins.py:115:15: E741 Ambiguous variable name: `l`\r\ntests/test_plugins.py:482:161: E501 Line too long (170 > 160 characters)\r\ntests/test_plugins.py:543:29: E741 Ambiguous variable name: `l`\r\ntests/test_plugins.py:563:161: E501 Line too long (170 > 160 characters)\r\ntests/test_plugins.py:940:62: E741 Ambiguous variable name: `l`\r\ntests/test_table_api.py:739:5: F811 Redefinition of unused `app_client_returned_rows_matches_page_size` from line 6\r\ntests/test_table_api.py:1066:45: F811 Redefinition of unused `app_client_with_trace` from line 5\r\ntests/test_table_html.py:484:29: E741 Ambiguous variable name: `l`\r\ntests/test_table_html.py:524:29: E741 Ambiguous variable name: `l`\r\ntests/test_table_html.py:675:161: E501 Line too long (165 > 160 characters)\r\ntests/test_table_html.py:897:161: E501 Line too long (164 > 160 characters)\r\ntests/test_table_html.py:902:161: E501 Line too long (164 > 160 characters)\r\ntests/test_utils.py:141:161: E501 Line too long (176 > 160 characters)\r\nFound 41 errors.\r\n```\r\nThose \"Redefinition of unused `app_client_two_attached_databases`\" lines are caused because of the fixtures pattern I'm using here:\r\n\r\nhttps://github.com/simonw/datasette/blob/99ba05118891db9dc30f1dca22ad6709775560de/tests/test_html.py#L3-L20\r\n\r\nI could fix that by getting rid of `fixtures.py` and moving those into `conftest.py`.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1781005740, "label": "Adopt ruff for linting"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2089#issuecomment-1613316722", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2089", "id": 1613316722, "node_id": "IC_kwDOBm6k_c5gKUJy", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-29T14:48:10Z", "updated_at": "2023-06-29T14:48:10Z", "author_association": "OWNER", "body": "Spell check is passing now.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1780973290, "label": "codespell test failure"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2089#issuecomment-1613315851", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2089", "id": 1613315851, "node_id": "IC_kwDOBm6k_c5gKT8L", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-29T14:47:38Z", "updated_at": "2023-06-29T14:47:38Z", "author_association": "OWNER", "body": "Confirmed, this was a 2.2.5 change: https://github.com/codespell-project/codespell/releases/tag/v2.2.5\r\n\r\n> - Add displaing->displaying by [@peternewman](https://github.com/peternewman) in [#2808](https://github.com/codespell-project/codespell/pull/2808)", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1780973290, "label": "codespell test failure"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2089#issuecomment-1613307716", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2089", "id": 1613307716, "node_id": "IC_kwDOBm6k_c5gKR9E", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-29T14:42:23Z", "updated_at": "2023-06-29T14:42:23Z", "author_association": "OWNER", "body": "Yes, upgrading locally got me the correct version and the test failure:\r\n```\r\n% pip install -U codespell\r\nRequirement already satisfied: codespell in /Users/simon/.local/share/virtualenvs/datasette-AWNrQs95/lib/python3.10/site-packages (2.2.2)\r\nCollecting codespell\r\n Downloading codespell-2.2.5-py3-none-any.whl (242 kB)\r\n \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 242.7/242.7 kB 4.9 MB/s eta 0:00:00\r\nInstalling collected packages: codespell\r\n Attempting uninstall: codespell\r\n Found existing installation: codespell 2.2.2\r\n Uninstalling codespell-2.2.2:\r\n Successfully uninstalled codespell-2.2.2\r\nSuccessfully installed codespell-2.2.5\r\n% codespell docs/metadata.rst\r\ndocs/metadata.rst:192: displaing ==> displaying\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1780973290, "label": "codespell test failure"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2089#issuecomment-1613306787", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2089", "id": 1613306787, "node_id": "IC_kwDOBm6k_c5gKRuj", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-29T14:41:47Z", "updated_at": "2023-06-29T14:41:47Z", "author_association": "OWNER", "body": "Looks like in CI it's running 2.2.5:\r\n```\r\nCollecting codespell (from datasette==1.0a2)\r\n Downloading codespell-2.2.5-py3-none-any.whl (242 kB)\r\n \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 242.7/242.7 kB 31.1 MB/s eta 0:00:00\r\n```\r\nBut on my laptop it's 2.2.2:\r\n```\r\n% codespell --version\r\n2.2.2\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1780973290, "label": "codespell test failure"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2089#issuecomment-1613305070", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2089", "id": 1613305070, "node_id": "IC_kwDOBm6k_c5gKRTu", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-29T14:40:44Z", "updated_at": "2023-06-29T14:40:44Z", "author_association": "OWNER", "body": "I'm not sure why I can't duplicate this failure in my local development environment:\r\n```\r\n% codespell docs/metadata.rst\r\n```\r\nIt finds no errors.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1780973290, "label": "codespell test failure"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/561#issuecomment-1610040517", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/561", "id": 1610040517, "node_id": "IC_kwDOCGYnMM5f90TF", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-27T18:44:31Z", "updated_at": "2023-06-27T18:44:38Z", "author_association": "OWNER", "body": "Got this working:\r\n```bash\r\nsqlite-utils insert /tmp/playground.db Playground_Submission_Data \\\r\n ~/Downloads/Playground_Submission_Data.csv --csv --stop-after 2000\r\n```\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1777548699, "label": "`--stop-after` option for `insert` and `upsert` commands"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/560#issuecomment-1606315321", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/560", "id": 1606315321, "node_id": "IC_kwDOCGYnMM5fvm05", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-25T23:18:33Z", "updated_at": "2023-06-25T23:18:33Z", "author_association": "OWNER", "body": "Documentation preview: https://sqlite-utils--560.org.readthedocs.build/en/560/installation.html#alternatives-to-sqlite3", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1773458985, "label": "Use sqlean if available in environment"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/560#issuecomment-1606310630", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/560", "id": 1606310630, "node_id": "IC_kwDOCGYnMM5fvlrm", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-25T23:06:07Z", "updated_at": "2023-06-25T23:06:07Z", "author_association": "OWNER", "body": "Filed an issue about the above with `pysqlite3` (which `sqlean.py` is based on) here:\r\n- https://github.com/coleifer/pysqlite3/issues/58", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1773458985, "label": "Use sqlean if available in environment"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/560#issuecomment-1606297356", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/560", "id": 1606297356, "node_id": "IC_kwDOCGYnMM5fvicM", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-25T22:42:41Z", "updated_at": "2023-06-25T22:42:41Z", "author_association": "OWNER", "body": "Yes that does seem to do the trick:\r\n```pycon\r\n>>> import sqlean\r\n>>> db = sqlean.connect(\"/tmp/4.db\")\r\n>>> db.execute('PRAGMA journal_mode;').fetchall()\r\n[('delete',)]\r\n>>> db.isolation_level\r\n''\r\n>>> db.execute('PRAGMA journal_mode=wal;')\r\nTraceback (most recent call last):\r\n File \"\", line 1, in \r\nsqlean.dbapi2.OperationalError: cannot change into wal mode from within a transaction\r\n>>> db.isolation_level = None\r\n>>> db.isolation_level\r\n>>> db.execute('PRAGMA journal_mode=wal;')\r\n\r\n```\r\nWeird how `isolation_level` of empty string causes the error, but setting that to `None` fixes the error.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1773458985, "label": "Use sqlean if available in environment"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/560#issuecomment-1606294627", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/560", "id": 1606294627, "node_id": "IC_kwDOCGYnMM5fvhxj", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-25T22:40:10Z", "updated_at": "2023-06-25T22:40:10Z", "author_association": "OWNER", "body": "I suspect this has something to do with `autocommit` mode in `sqlite3` - which I may be able to turn off by setting `con.isolation_level = None`.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1773458985, "label": "Use sqlean if available in environment"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/560#issuecomment-1606293382", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/560", "id": 1606293382, "node_id": "IC_kwDOCGYnMM5fvheG", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-25T22:34:47Z", "updated_at": "2023-06-25T22:34:47Z", "author_association": "OWNER", "body": "```pycon\r\n>>> import sqlite3\r\n>>> db = sqlite3.connect(\"/tmp/1.db\")\r\n>>> db.execute('PRAGMA journal_mode=wal;')\r\n\r\n>>> import sqlean\r\n>>> db2 = sqlean.connect(\"/tmp/2.db\")\r\n>>> db2.execute('PRAGMA journal_mode=wal;')\r\nTraceback (most recent call last):\r\n File \"\", line 1, in \r\nsqlean.dbapi2.OperationalError: cannot change into wal mode from within a transaction\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1773458985, "label": "Use sqlean if available in environment"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/560#issuecomment-1606290917", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/560", "id": 1606290917, "node_id": "IC_kwDOCGYnMM5fvg3l", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-25T22:32:28Z", "updated_at": "2023-06-25T22:32:28Z", "author_association": "OWNER", "body": "I've fixed most of the test failures, but I still need to fix this one:\r\n\r\n> cannot change into wal mode from within a transaction", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1773458985, "label": "Use sqlean if available in environment"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/560#issuecomment-1606273005", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/560", "id": 1606273005, "node_id": "IC_kwDOCGYnMM5fvcft", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-25T21:47:47Z", "updated_at": "2023-06-25T21:47:47Z", "author_association": "OWNER", "body": "I can use https://github.com/simonw/sqlite-dump as an optional dependency to handle the missing `.iterdump()` method.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1773458985, "label": "Use sqlean if available in environment"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/560#issuecomment-1606270887", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/560", "id": 1606270887, "node_id": "IC_kwDOCGYnMM5fvb-n", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-25T21:37:12Z", "updated_at": "2023-06-26T08:21:00Z", "author_association": "OWNER", "body": "On my own laptop I got a crash running the tests - details here:\r\n\r\n- https://github.com/nalgeon/sqlean.py/issues/3", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1773458985, "label": "Use sqlean if available in environment"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/560#issuecomment-1606270055", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/560", "id": 1606270055, "node_id": "IC_kwDOCGYnMM5fvbxn", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-25T21:31:56Z", "updated_at": "2023-06-25T21:31:56Z", "author_association": "OWNER", "body": "Lots of failures now that I'm trying to run the tests against `sqlean.py` on macOS and Python 3.10: https://github.com/simonw/sqlite-utils/actions/runs/5371800108/jobs/9744802953\r\n\r\nA bunch of these, because `pysqlite3` chooses not to implement `.iterdump()`:\r\n```\r\n @pytest.fixture\r\n def db_to_analyze_path(db_to_analyze, tmpdir):\r\n path = str(tmpdir / \"test.db\")\r\n db = sqlite3.connect(path)\r\n> db.executescript(\"\\n\".join(db_to_analyze.conn.iterdump()))\r\nE AttributeError: 'sqlean.dbapi2.Connection' object has no attribute 'iterdump'\r\n```\r\nAlso some of these:\r\n```\r\n def test_analyze_whole_database(db):\r\n assert set(db.table_names()) == {\"one_index\", \"two_indexes\"}\r\n db.analyze()\r\n> assert set(db.table_names()) == {\"one_index\", \"two_indexes\", \"sqlite_stat1\"}\r\nE AssertionError: assert {'one_index',...'two_indexes'} == {'one_index',...'two_indexes'}\r\nE Extra items in the left set:\r\nE 'sqlite_stat4'\r\nE Full diff:\r\nE - {'two_indexes', 'sqlite_stat1', 'one_index'}\r\nE + {'two_indexes', 'sqlite_stat1', 'sqlite_stat4', 'one_index'}\r\nE ? ++++++++++++++++\r\n```\r\nApparently `sqlean.py` adds a `sqlite_stat4` table that the tests are not expecting.\r\n\r\nPlus some errors that look like this:\r\n```\r\n def test_enable_wal():\r\n runner = CliRunner()\r\n dbs = [\"test.db\", \"test2.db\"]\r\n with runner.isolated_filesystem():\r\n for dbname in dbs:\r\n db = Database(dbname)\r\n db[\"t\"].create({\"pk\": int}, pk=\"pk\")\r\n assert db.journal_mode == \"delete\"\r\n result = runner.invoke(cli.cli, [\"enable-wal\"] + dbs)\r\n> assert 0 == result.exit_code\r\nE AssertionError: assert 0 == 1\r\nE + where 1 = .exit_code\r\n```\r\nTest summary:\r\n```\r\n============ 13 failed, 909 passed, 16 skipped, 2 errors in 19.29s =============\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1773458985, "label": "Use sqlean if available in environment"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/2080#issuecomment-1563650990", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2080", "id": 1563650990, "node_id": "IC_kwDOBm6k_c5dM2uu", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-26T00:08:59Z", "updated_at": "2023-05-26T00:08:59Z", "author_association": "OWNER", "body": "I'm not going to document this yet, I want to let it bake for a bit longer first.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726603778, "label": "New View base class"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/2080#issuecomment-1563626231", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2080", "id": 1563626231, "node_id": "IC_kwDOBm6k_c5dMwr3", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T23:25:17Z", "updated_at": "2023-05-25T23:25:17Z", "author_association": "OWNER", "body": "I'm going to try using this for the `/-/patterns` page.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726603778, "label": "New View base class"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2079#issuecomment-1563607291", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2079", "id": 1563607291, "node_id": "IC_kwDOBm6k_c5dMsD7", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T22:56:28Z", "updated_at": "2023-05-25T22:56:28Z", "author_association": "OWNER", "body": "Wrote this up as a TIL: https://til.simonwillison.net/http/testing-cors-max-age", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726531350, "label": "Datasette should serve Access-Control-Max-Age"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2079#issuecomment-1563597589", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2079", "id": 1563597589, "node_id": "IC_kwDOBm6k_c5dMpsV", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T22:42:07Z", "updated_at": "2023-05-25T22:42:07Z", "author_association": "OWNER", "body": "Mystery solved as to why I wasn't seeing this work:\r\n\r\n\"CleanShot\r\n\r\nI had \"Disable Cache\" checked!\r\n\r\nI ran this experiment after un-checking that box:\r\n\r\n```javascript\r\nfetch('https://latest.datasette.io/ephemeral/foo/1/-/update', {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json'\r\n },\r\n body: JSON.stringify({ test: 'test' })\r\n});\r\n// And run it again\r\nfetch('https://latest.datasette.io/ephemeral/foo/1/-/update', {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json'\r\n },\r\n body: JSON.stringify({ test: 'test' })\r\n});\r\n// Now try a thing that doesn't serve that max-age header yet:\r\nfetch('https://latest-with-plugins.datasette.io/ephemeral/foo/1/-/update', {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json'\r\n },\r\n body: JSON.stringify({ test: 'test' })\r\n});\r\n// And a second time but within 5s\r\nfetch('https://latest-with-plugins.datasette.io/ephemeral/foo/1/-/update', {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json'\r\n },\r\n body: JSON.stringify({ test: 'test' })\r\n});\r\n// Third time after waiting longer than 5s\r\nfetch('https://latest-with-plugins.datasette.io/ephemeral/foo/1/-/update', {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json'\r\n },\r\n body: JSON.stringify({ test: 'test' })\r\n});\r\n// Try that original one again - still within the 1hr cache time\r\nfetch('https://latest.datasette.io/ephemeral/foo/1/-/update', {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json'\r\n },\r\n body: JSON.stringify({ test: 'test' })\r\n});\r\n```\r\nThe results show that the cache of 1hr was being obeyed for `latest.datasette.io` while the `latest-with-plugins.datasette.io` default cache of 5s was being obeyed too.\r\n\r\n\"image\"\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726531350, "label": "Datasette should serve Access-Control-Max-Age"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2079#issuecomment-1563588199", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2079", "id": 1563588199, "node_id": "IC_kwDOBm6k_c5dMnZn", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T22:29:47Z", "updated_at": "2023-05-25T22:30:12Z", "author_association": "OWNER", "body": "https://fetch.spec.whatwg.org/#http-access-control-max-age says:\r\n\r\n> Indicates the number of seconds (5 by default) the information provided by the [Access-Control-Allow-Methods](https://fetch.spec.whatwg.org/#http-access-control-allow-methods) and [Access-Control-Allow-Headers](https://fetch.spec.whatwg.org/#http-access-control-allow-headers) [headers](https://fetch.spec.whatwg.org/#concept-header) can be cached.\r\n\r\nSo there was already a 5s cache anyway.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726531350, "label": "Datasette should serve Access-Control-Max-Age"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2079#issuecomment-1563587230", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2079", "id": 1563587230, "node_id": "IC_kwDOBm6k_c5dMnKe", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T22:28:20Z", "updated_at": "2023-05-25T22:28:20Z", "author_association": "OWNER", "body": "Weird... after the deploy went out:\r\n\r\n\"image\"\r\n\r\nBut the request did indeed get the new header:\r\n\r\n\"image\"\r\n\r\nSo I'm not sure why it's making multiple `POST` requests like that.\r\n\r\nMaybe it's because the attempted `POST` failed with a 404?", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726531350, "label": "Datasette should serve Access-Control-Max-Age"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2079#issuecomment-1563565407", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2079", "id": 1563565407, "node_id": "IC_kwDOBm6k_c5dMh1f", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T22:09:53Z", "updated_at": "2023-05-25T22:09:53Z", "author_association": "OWNER", "body": "Updated docs: https://docs.datasette.io/en/latest/json_api.html#enabling-cors", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726531350, "label": "Datasette should serve Access-Control-Max-Age"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2079#issuecomment-1563563438", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2079", "id": 1563563438, "node_id": "IC_kwDOBm6k_c5dMhWu", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T22:08:28Z", "updated_at": "2023-05-25T22:08:28Z", "author_association": "OWNER", "body": "I ran this on https://www.example.com/ twice using the console:\r\n```javascript\r\nfetch(\r\n `https://latest.datasette.io/ephemeral/foo/1/-/update`,\r\n {\r\n method: \"POST\",\r\n mode: \"cors\",\r\n headers: {\r\n Authorization: `Bearer tok`,\r\n \"Content-Type\": \"application/json\",\r\n },\r\n body: JSON.stringify({update: {blah: 1}}),\r\n }\r\n)\r\n .then((r) => r.json())\r\n .then((data) => {\r\n console.log(data);\r\n });\r\n```\r\nAnd got this in the network pane:\r\n\r\n\"image\"\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726531350, "label": "Datasette should serve Access-Control-Max-Age"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2079#issuecomment-1563558915", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2079", "id": 1563558915, "node_id": "IC_kwDOBm6k_c5dMgQD", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T22:04:41Z", "updated_at": "2023-05-25T22:04:41Z", "author_association": "OWNER", "body": "I'm going with 3600 for 1 hour instead of 600 for 10 minutes.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726531350, "label": "Datasette should serve Access-Control-Max-Age"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2079#issuecomment-1563547097", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2079", "id": 1563547097, "node_id": "IC_kwDOBm6k_c5dMdXZ", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T21:51:38Z", "updated_at": "2023-05-25T21:51:38Z", "author_association": "OWNER", "body": "Also need to update this documentation:\r\n\r\nhttps://github.com/simonw/datasette/blob/9584879534ff0556e04e4c420262972884cac87b/docs/json_api.rst?plain=1#L453-L465\r\n\r\nOr maybe make that automated via `cog`.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726531350, "label": "Datasette should serve Access-Control-Max-Age"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563625093", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563625093, "node_id": "IC_kwDOBm6k_c5dMwaF", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T23:23:15Z", "updated_at": "2023-05-25T23:23:15Z", "author_association": "OWNER", "body": "Rest of the work on this will happen in the PR:\r\n- #2080", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563522011", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563522011, "node_id": "IC_kwDOBm6k_c5dMXPb", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T21:22:30Z", "updated_at": "2023-05-25T21:22:30Z", "author_association": "OWNER", "body": "This is bad:\r\n```python\r\n async def __call__(self, request, datasette):\r\n try:\r\n handler = getattr(self, request.method.lower())\r\n return await handler(request, datasette)\r\n except AttributeError:\r\n return await self.method_not_allowed(request)\r\n```\r\nBecause it hides any `AttributeError` exceptions that might occur in the view code.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563511171", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563511171, "node_id": "IC_kwDOBm6k_c5dMUmD", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T21:11:20Z", "updated_at": "2023-05-25T21:13:05Z", "author_association": "OWNER", "body": "I'm going to call this `VerbView` for the moment. Might even rename it to `View` later.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563498048", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563498048, "node_id": "IC_kwDOBm6k_c5dMRZA", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T20:57:52Z", "updated_at": "2023-05-25T20:58:13Z", "author_association": "OWNER", "body": "Here's a new `BaseView` class that automatically populates `OPTIONS` based on available methods:\r\n```python\r\nclass BaseView:\r\n async def head(self, *args, **kwargs):\r\n try:\r\n response = await self.get(*args, **kwargs)\r\n response.body = b\"\"\r\n return response\r\n except AttributeError:\r\n raise\r\n\r\n async def method_not_allowed(self, request):\r\n if (\r\n request.path.endswith(\".json\")\r\n or request.headers.get(\"content-type\") == \"application/json\"\r\n ):\r\n response = Response.json(\r\n {\"ok\": False, \"error\": \"Method not allowed\"}, status=405\r\n )\r\n else:\r\n response = Response.text(\"Method not allowed\", status=405)\r\n return response\r\n\r\n async def options(self, request, *args, **kwargs):\r\n response = Response.text(\"ok\")\r\n response.headers[\"allow\"] = \", \".join(\r\n method.upper()\r\n for method in (\"head\", \"get\", \"post\", \"put\", \"patch\", \"delete\")\r\n if hasattr(self, method)\r\n )\r\n return response\r\n\r\n async def __call__(self, request, datasette):\r\n try:\r\n handler = getattr(self, request.method.lower())\r\n return await handler(request, datasette)\r\n except AttributeError:\r\n return await self.method_not_allowed(request)\r\n\r\n\r\nclass DemoView(BaseView):\r\n async def get(self, datasette, request):\r\n return Response.text(\"Hello there! {} - {}\".format(datasette, request))\r\n\r\n post = get\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563488929", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563488929, "node_id": "IC_kwDOBm6k_c5dMPKh", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T20:48:12Z", "updated_at": "2023-05-25T20:48:39Z", "author_association": "OWNER", "body": "Actually no need for that extra level of parameter detection: `BaseView.__call__` should _always_ take `datasette, request` - `scope` and `receive` are both available on `request`, and `send` is only needed if you're not planning on returning a `Response` object.\r\n\r\nSo the `get` and `post` and suchlike methods should take `datasette` and `request` too.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563444296", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563444296, "node_id": "IC_kwDOBm6k_c5dMERI", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T20:06:08Z", "updated_at": "2023-05-25T20:06:08Z", "author_association": "OWNER", "body": "This prototype seems to work well:\r\n\r\n```diff\r\ndiff --git a/datasette/app.py b/datasette/app.py\r\nindex d7dace67..ed0edf28 100644\r\n--- a/datasette/app.py\r\n+++ b/datasette/app.py\r\n@@ -17,6 +17,7 @@ import secrets\r\n import sys\r\n import threading\r\n import time\r\n+import types\r\n import urllib.parse\r\n from concurrent import futures\r\n from pathlib import Path\r\n@@ -1266,6 +1267,8 @@ class Datasette:\r\n # TODO: /favicon.ico and /-/static/ deserve far-future cache expires\r\n add_route(favicon, \"/favicon.ico\")\r\n \r\n+ add_route(wrap_view(DemoView, self), '/demo')\r\n+\r\n add_route(\r\n asgi_static(app_root / \"datasette\" / \"static\"), r\"/-/static/(?P.*)$\"\r\n )\r\n@@ -1673,8 +1676,46 @@ def _cleaner_task_str(task):\r\n return _cleaner_task_str_re.sub(\"\", s)\r\n \r\n \r\n-def wrap_view(view_fn, datasette):\r\n- @functools.wraps(view_fn)\r\n+class DemoView:\r\n+ async def __call__(self, datasette, request):\r\n+ return Response.text(\"Hello there! {} - {}\".format(datasette, request))\r\n+\r\n+def wrap_view(view_fn_or_class, datasette):\r\n+ is_function = isinstance(view_fn_or_class, types.FunctionType)\r\n+ if is_function:\r\n+ return wrap_view_function(view_fn_or_class, datasette)\r\n+ else:\r\n+ if not isinstance(view_fn_or_class, type):\r\n+ raise ValueError(\"view_fn_or_class must be a function or a class\")\r\n+ return wrap_view_class(view_fn_or_class, datasette)\r\n+\r\n+\r\n+def wrap_view_class(view_class, datasette):\r\n+ async def async_view_for_class(request, send):\r\n+ instance = view_class()\r\n+ if inspect.iscoroutinefunction(instance.__call__):\r\n+ return await async_call_with_supported_arguments(\r\n+ instance.__call__,\r\n+ scope=request.scope,\r\n+ receive=request.receive,\r\n+ send=send,\r\n+ request=request,\r\n+ datasette=datasette,\r\n+ )\r\n+ else:\r\n+ return call_with_supported_arguments(\r\n+ instance.__call__,\r\n+ scope=request.scope,\r\n+ receive=request.receive,\r\n+ send=send,\r\n+ request=request,\r\n+ datasette=datasette,\r\n+ )\r\n+\r\n+ return async_view_for_class\r\n+\r\n+\r\n+def wrap_view_function(view_fn, datasette):\r\n async def async_view_fn(request, send):\r\n if inspect.iscoroutinefunction(view_fn):\r\n response = await async_call_with_supported_arguments(\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563419066", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563419066, "node_id": "IC_kwDOBm6k_c5dL-G6", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T19:42:16Z", "updated_at": "2023-05-25T19:43:08Z", "author_association": "OWNER", "body": "Maybe what I want here is the ability to register classes with the router - and have the router know that if it's a class it should instantiate it via its constructor and then await `__call__` it.\r\n\r\nThe neat thing about it is that it can reduce the risk of having a class instance that accidentally shares state between requests.\r\n\r\nIt also encourages that each class only responds based on the `datasette, request, ...` objects that are passed to its methods.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563359114", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563359114, "node_id": "IC_kwDOBm6k_c5dLveK", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T18:47:57Z", "updated_at": "2023-05-25T18:47:57Z", "author_association": "OWNER", "body": "Oops, that broke everything:\r\n```\r\n @documented\r\n async def await_me_maybe(value: typing.Any) -> typing.Any:\r\n \"If value is callable, call it. If awaitable, await it. Otherwise return it.\"\r\n> if callable(value):\r\nE TypeError: 'module' object is not callable\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563329245", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563329245, "node_id": "IC_kwDOBm6k_c5dLoLd", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T18:26:47Z", "updated_at": "2023-05-25T18:28:08Z", "author_association": "OWNER", "body": "With type hints and a namedtuple:\r\n```python\r\nimport asyncio\r\nimport types\r\nfrom typing import NamedTuple, Any\r\n\r\n\r\nclass CallableStatus(NamedTuple):\r\n is_callable: bool\r\n is_async_callable: bool\r\n\r\n\r\ndef check_callable(obj: Any) -> CallableStatus:\r\n if not callable(obj):\r\n return CallableStatus(False, False)\r\n\r\n if isinstance(obj, type):\r\n # It's a class\r\n return CallableStatus(True, False)\r\n\r\n if isinstance(obj, types.FunctionType):\r\n return CallableStatus(True, asyncio.iscoroutinefunction(obj))\r\n\r\n if hasattr(obj, \"__call__\"):\r\n return CallableStatus(True, asyncio.iscoroutinefunction(obj.__call__))\r\n\r\n assert False, \"obj {} is somehow callable with no __call__ method\".format(repr(obj))\r\n```\r\n```python\r\nfor thing in (\r\n async_func,\r\n non_async_func,\r\n AsyncClass(),\r\n NotAsyncClass(),\r\n ClassNoCall(),\r\n AsyncClass,\r\n NotAsyncClass,\r\n ClassNoCall,\r\n):\r\n print(thing, check_callable(thing))\r\n```\r\n```\r\n CallableStatus(is_callable=True, is_async_callable=True)\r\n CallableStatus(is_callable=True, is_async_callable=False)\r\n<__main__.AsyncClass object at 0x106ba7490> CallableStatus(is_callable=True, is_async_callable=True)\r\n<__main__.NotAsyncClass object at 0x106740150> CallableStatus(is_callable=True, is_async_callable=False)\r\n<__main__.ClassNoCall object at 0x10676d910> CallableStatus(is_callable=False, is_async_callable=False)\r\n CallableStatus(is_callable=True, is_async_callable=False)\r\n CallableStatus(is_callable=True, is_async_callable=False)\r\n CallableStatus(is_callable=True, is_async_callable=False)\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563326000", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563326000, "node_id": "IC_kwDOBm6k_c5dLnYw", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T18:23:38Z", "updated_at": "2023-05-25T18:23:38Z", "author_association": "OWNER", "body": "I don't like that `is_callable()` implies a single boolean result but actually returns a pair. I'll call it `check_callable(obj)` instead.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563318598", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563318598, "node_id": "IC_kwDOBm6k_c5dLllG", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T18:17:03Z", "updated_at": "2023-05-25T18:21:25Z", "author_association": "OWNER", "body": "I think I want that to return `(is_callable, is_async)` - so I can both test if the thing can be called AND if it should be awaited in the same operation (without any exceptions).\r\n\r\nI tried this:\r\n```python\r\ndef is_callable(obj):\r\n \"Returns (is_callable, is_async_callable)\"\r\n if not callable(obj):\r\n return False, False\r\n\r\n if isinstance(obj, types.FunctionType):\r\n return True, asyncio.iscoroutinefunction(obj)\r\n \r\n if hasattr(obj, '__call__'):\r\n return True, asyncio.iscoroutinefunction(obj.__call__)\r\n\r\n return False, False\r\n```\r\n```python\r\nfor thing in (\r\n async_func, non_async_func, AsyncClass(), NotAsyncClass(), ClassNoCall(),\r\n AsyncClass, NotAsyncClass, ClassNoCall\r\n):\r\n print(thing, is_callable(thing))\r\n```\r\nAnd got:\r\n```\r\n (True, True)\r\n (True, False)\r\n<__main__.AsyncClass object at 0x106cce490> (True, True)\r\n<__main__.NotAsyncClass object at 0x106ccf710> (True, False)\r\n<__main__.ClassNoCall object at 0x106ccc810> (False, False)\r\n (True, True)\r\n (True, False)\r\n (True, False)\r\n```\r\nWhich is almost right, but I don't like that `AsyncClass` is shown as callable (which it is, since it's a class) and awaitable (which it is not - the `__call__` method may be async but calling the class constructor is not).\r\n\r\nSo I'm going to detect classes using `isinstance(obj, type)`.\r\n\r\n```python\r\ndef is_callable(obj):\r\n \"Returns (is_callable, is_async_callable)\"\r\n if not callable(obj):\r\n return False, False\r\n\r\n if isinstance(obj, type):\r\n # It's a class\r\n return True, False\r\n \r\n if isinstance(obj, types.FunctionType):\r\n return True, asyncio.iscoroutinefunction(obj)\r\n \r\n if hasattr(obj, '__call__'):\r\n return True, asyncio.iscoroutinefunction(obj.__call__)\r\n\r\n assert False, \"obj {} somehow is callable with no __call__ method\".format(obj)\r\n```\r\nI am reasonably confident the `AssertionError` can never be raised.\r\n\r\nAnd now:\r\n```\r\n (True, True)\r\n (True, False)\r\n<__main__.AsyncClass object at 0x106ccfa50> (True, True)\r\n<__main__.NotAsyncClass object at 0x106ccc8d0> (True, False)\r\n<__main__.ClassNoCall object at 0x106cd7690> (False, False)\r\n (True, False)\r\n (True, False)\r\n (True, False)\r\n```\r\nWhich is what I wanted.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563308919", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563308919, "node_id": "IC_kwDOBm6k_c5dLjN3", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T18:08:34Z", "updated_at": "2023-05-25T18:08:34Z", "author_association": "OWNER", "body": "After much fiddling this seems to work:\r\n```python\r\nimport asyncio, types\r\n\r\ndef is_async_callable(obj):\r\n if not callable(obj):\r\n raise ValueError(\"Object is not callable\")\r\n\r\n if isinstance(obj, types.FunctionType):\r\n return asyncio.iscoroutinefunction(obj)\r\n \r\n if hasattr(obj, '__call__'):\r\n return asyncio.iscoroutinefunction(obj.__call__)\r\n\r\n raise ValueError(\"Not a function and has no __call__ attribute\")\r\n```\r\nTested like so:\r\n```python\r\nclass AsyncClass:\r\n async def __call__(self):\r\n pass\r\n\r\nclass NotAsyncClass:\r\n def __call__(self):\r\n pass\r\n\r\nclass ClassNoCall:\r\n pass\r\n \r\nasync def async_func():\r\n pass\r\n\r\ndef non_async_func():\r\n pass\r\n\r\nfor thing in (AsyncClass(), NotAsyncClass(), ClassNoCall(), async_func, non_async_func):\r\n try:\r\n print(thing, is_async_callable(thing))\r\n except Exception as ex:\r\n print(thing, ex)\r\n```\r\n```\r\n<__main__.AsyncClass object at 0x106c32150> True\r\n<__main__.NotAsyncClass object at 0x106c32390> False\r\n<__main__.ClassNoCall object at 0x106c32750> Object is not callable\r\n True\r\n False\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563294669", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563294669, "node_id": "IC_kwDOBm6k_c5dLfvN", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T17:57:06Z", "updated_at": "2023-05-25T17:57:06Z", "author_association": "OWNER", "body": "I may need to be able to detect if a class instance has an `async def __call__` method - I think I can do that like so:\r\n\r\n```python\r\ndef iscoroutinefunction(obj):\r\n if inspect.iscoroutinefunction(obj):\r\n return True\r\n if hasattr(obj, '__call__') and inspect.iscoroutinefunction(obj.__call__):\r\n return True\r\n return False\r\n```\r\nFrom https://github.com/encode/starlette/issues/886#issuecomment-606585152", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563292373", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563292373, "node_id": "IC_kwDOBm6k_c5dLfLV", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T17:55:12Z", "updated_at": "2023-05-25T17:55:30Z", "author_association": "OWNER", "body": "So I think subclasses of `BaseView` need to offer a callable which accepts all five of the DI arguments - `datasette`, `request`, `scope`, `send`, `receive` - and then makes a decision based on the HTTP verb as to which method of the class to call. Those methods themselves can accept a subset of those parameters and will only be sent on to them.\r\n\r\nHaving two layers of parameter detection feels a little bit untidy, but I think it will work.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563283939", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563283939, "node_id": "IC_kwDOBm6k_c5dLdHj", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T17:47:38Z", "updated_at": "2023-05-25T17:47:38Z", "author_association": "OWNER", "body": "The idea behind `wrap_view()` is dependency injection - it's mainly used by plugins:\r\n\r\nhttps://docs.datasette.io/en/0.64.3/plugin_hooks.html#register-routes-datasette\r\n\r\nBut I like the pattern so I started using it for some of Datasette's own features.\r\n\r\nI should use it for _all_ of Datasette's own features.\r\n\r\nBut I still like the way `BaseView` helps with running different code for GET/POST/etc verbs.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2078#issuecomment-1563282327", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2078", "id": 1563282327, "node_id": "IC_kwDOBm6k_c5dLcuX", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-25T17:46:05Z", "updated_at": "2023-05-25T17:46:05Z", "author_association": "OWNER", "body": "Here's what `wrap_view()` does:\r\n\r\nhttps://github.com/simonw/datasette/blob/49184c569cd70efbda4f3f062afef3a34401d8d5/datasette/app.py#L1676-L1700\r\n\r\nIt's used e.g. here:\r\n\r\nhttps://github.com/simonw/datasette/blob/49184c569cd70efbda4f3f062afef3a34401d8d5/datasette/app.py#L1371-L1375\r\n\r\nThe `BaseView` thing meanwhile works like this:\r\n\r\nhttps://github.com/simonw/datasette/blob/d97e82df3c8a3f2e97038d7080167be9bb74a68d/datasette/views/base.py#L56-L157", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1726236847, "label": "Resolve the difference between `wrap_view()` and `BaseView`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/2077#issuecomment-1613290899", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2077", "id": 1613290899, "node_id": "IC_kwDOBm6k_c5gKN2T", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-06-29T14:32:16Z", "updated_at": "2023-06-29T14:32:16Z", "author_association": "OWNER", "body": "@dependabot recreate", "reactions": "{\"total_count\": 1, \"+1\": 1, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1719759468, "label": "Bump furo from 2023.3.27 to 2023.5.20"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/553#issuecomment-1556288300", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/553", "id": 1556288300, "node_id": "IC_kwDOCGYnMM5cwxMs", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T20:48:01Z", "updated_at": "2023-05-21T20:48:01Z", "author_association": "OWNER", "body": "If https://sqlite-utils--553.org.readthedocs.build/en/553/cli.html#running-sql-queries looks good I can merge this.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718635018, "label": "Reformatted CLI examples in docs"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/552#issuecomment-1556292204", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/552", "id": 1556292204, "node_id": "IC_kwDOCGYnMM5cwyJs", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T21:05:15Z", "updated_at": "2023-05-21T21:05:15Z", "author_association": "OWNER", "body": "Now live at https://sqlite-utils.datasette.io/en/latest/installation.html#setting-up-shell-completion", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718612569, "label": "Document how to setup shell auto-completion"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/551#issuecomment-1556291915", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/551", "id": 1556291915, "node_id": "IC_kwDOCGYnMM5cwyFL", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T21:04:03Z", "updated_at": "2023-05-21T21:04:03Z", "author_association": "OWNER", "body": "Now live at https://sqlite-utils.datasette.io/en/latest/cli.html", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718607907, "label": "Make as many examples in the CLI docs as possible copy-and-pastable"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/551#issuecomment-1556288270", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/551", "id": 1556288270, "node_id": "IC_kwDOCGYnMM5cwxMO", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T20:47:51Z", "updated_at": "2023-05-21T20:47:51Z", "author_association": "OWNER", "body": "This page has all of the changes: https://sqlite-utils--553.org.readthedocs.build/en/553/cli.html#running-sql-queries", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718607907, "label": "Make as many examples in the CLI docs as possible copy-and-pastable"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/551#issuecomment-1556287599", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/551", "id": 1556287599, "node_id": "IC_kwDOCGYnMM5cwxBv", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T20:44:55Z", "updated_at": "2023-05-21T20:44:55Z", "author_association": "OWNER", "body": "Put this in a PR so I can preview it: \r\n- #553\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718607907, "label": "Make as many examples in the CLI docs as possible copy-and-pastable"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/551#issuecomment-1556265772", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/551", "id": 1556265772, "node_id": "IC_kwDOCGYnMM5cwrss", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T19:16:15Z", "updated_at": "2023-05-21T19:16:15Z", "author_association": "OWNER", "body": "Another option:\r\n\r\n\"image\"\r\n\r\nThat's using this markup:\r\n\r\n```\r\nNewline-delimited JSON\r\n~~~~~~~~~~~~~~~~~~~~~~\r\n\r\nUse ``--nl`` to get back newline-delimited JSON objects:\r\n\r\n.. code-block:: bash\r\n\r\n sqlite-utils dogs.db \"select * from dogs\" --nl\r\n\r\n.. code-block:: output\r\n\r\n {\"id\": 1, \"age\": 4, \"name\": \"Cleo\"}\r\n {\"id\": 2, \"age\": 2, \"name\": \"Pancakes\"}\r\n```\r\nAnd this extra CSS:\r\n\r\n```css\r\n.highlight-output .highlight {\r\n border-left: 9px solid #30c94f;\r\n}\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718607907, "label": "Make as many examples in the CLI docs as possible copy-and-pastable"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/551#issuecomment-1556263182", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/551", "id": 1556263182, "node_id": "IC_kwDOCGYnMM5cwrEO", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T19:06:48Z", "updated_at": "2023-05-21T19:06:48Z", "author_association": "OWNER", "body": "I could split them up into two blocks like this:\r\n\r\n\"image\"\r\n\r\nI do miss the visual indication that one of these is the command and one is the output though.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718607907, "label": "Make as many examples in the CLI docs as possible copy-and-pastable"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/551#issuecomment-1556262574", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/551", "id": 1556262574, "node_id": "IC_kwDOCGYnMM5cwq6u", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T19:04:59Z", "updated_at": "2023-05-21T19:04:59Z", "author_association": "OWNER", "body": "I wrote the docs like this because early examples include both the command and its output:\r\n\r\nhttps://sqlite-utils.datasette.io/en/stable/cli.html#returning-json\r\n\r\n\"image\"", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718607907, "label": "Make as many examples in the CLI docs as possible copy-and-pastable"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/550#issuecomment-1556255309", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/550", "id": 1556255309, "node_id": "IC_kwDOCGYnMM5cwpJN", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T18:42:25Z", "updated_at": "2023-05-21T18:42:25Z", "author_association": "OWNER", "body": "Tests passed here: https://github.com/simonw/sqlite-utils/actions/runs/5039119716", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718595700, "label": "AttributeError: 'EntryPoints' object has no attribute 'get' for flake8 on Python 3.7"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/550#issuecomment-1556250236", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/550", "id": 1556250236, "node_id": "IC_kwDOCGYnMM5cwn58", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T18:25:26Z", "updated_at": "2023-05-21T18:25:26Z", "author_association": "OWNER", "body": "Relevant issues:\r\n- https://github.com/python/importlib_metadata/issues/406\r\n- https://github.com/PyCQA/flake8/issues/1701\r\n\r\nIt looks to me like this is only a problem for `flake8` on Python 3.7 - 3.8 and higher work OK.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718595700, "label": "AttributeError: 'EntryPoints' object has no attribute 'get' for flake8 on Python 3.7"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/550#issuecomment-1556249984", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/550", "id": 1556249984, "node_id": "IC_kwDOCGYnMM5cwn2A", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T18:24:48Z", "updated_at": "2023-05-21T18:24:48Z", "author_association": "OWNER", "body": "This is blocking:\r\n- #549", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718595700, "label": "AttributeError: 'EntryPoints' object has no attribute 'get' for flake8 on Python 3.7"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/549#issuecomment-1556242262", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/549", "id": 1556242262, "node_id": "IC_kwDOCGYnMM5cwl9W", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T18:00:05Z", "updated_at": "2023-05-21T18:00:05Z", "author_association": "OWNER", "body": "Failing `mypy` test: https://github.com/simonw/sqlite-utils/actions/runs/5038983349/jobs/9036828465\r\n```\r\nsqlite_utils/cli.py:37: error: Skipping analyzing \"trogon\": module is installed, but missing library stubs or py.typed marker [import]\r\nsqlite_utils/cli.py:37: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports\r\nFound 1 error in 1 file (checked 52 source files)\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718586377, "label": "TUI powered by Trogon"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/549#issuecomment-1556241812", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/549", "id": 1556241812, "node_id": "IC_kwDOCGYnMM5cwl2U", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T17:58:25Z", "updated_at": "2023-05-21T17:58:25Z", "author_association": "OWNER", "body": "Documentation: https://sqlite-utils--549.org.readthedocs.build/en/549/cli.html#cli-tui", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718586377, "label": "TUI powered by Trogon"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/548#issuecomment-1556231832", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/548", "id": 1556231832, "node_id": "IC_kwDOCGYnMM5cwjaY", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T17:24:13Z", "updated_at": "2023-05-21T17:24:13Z", "author_association": "OWNER", "body": "Oh, I see why that is now:\r\n\r\nhttps://github.com/simonw/sqlite-utils/blob/6027f3ea6939a399aeef2578fca17efec0e539df/sqlite_utils/cli.py#L2670-L2679\r\n\r\nThis is because of the following command:\r\n\r\n sqlite-utils analyze-tables table1 table2 --column x\r\n\r\nSince you can pass multiple tables AND multiple columns, the tool currently assumes that the column(s) you specify may be available on a subset of the provided tables.\r\n\r\nI'm going to change this so if the column is not on ANY of those tables you get an error.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718576761, "label": "analyze-tables should validate provide --column names"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/547#issuecomment-1556228395", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/547", "id": 1556228395, "node_id": "IC_kwDOCGYnMM5cwikr", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T17:11:15Z", "updated_at": "2023-05-21T17:11:15Z", "author_association": "OWNER", "body": "This will be a cosmetic change to the CLI output only - the options to save data to the database and the Python API function will continue to return `[(None, 158)]`.\r\n\r\nI can add an optimization though to avoid running the SQL count query if we know that it's all `null`.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718572201, "label": "No need to show common values if everything is null"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/546#issuecomment-1556213396", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/546", "id": 1556213396, "node_id": "IC_kwDOCGYnMM5cwe6U", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T15:58:12Z", "updated_at": "2023-05-21T16:18:46Z", "author_association": "OWNER", "body": "Documentation preview:\r\n- https://sqlite-utils--546.org.readthedocs.build/en/546/cli.html#cli-analyze-tables\r\n- https://sqlite-utils--546.org.readthedocs.build/en/546/cli-reference.html#analyze-tables\r\n- https://sqlite-utils--546.org.readthedocs.build/en/546/python-api.html#analyzing-a-column\r\n- https://sqlite-utils--546.org.readthedocs.build/en/546/reference.html#sqlite_utils.db.Table.analyze_column", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718550688, "label": "Analyze tables options: --common-limit, --no-most, --no-least"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/545#issuecomment-1556269616", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/545", "id": 1556269616, "node_id": "IC_kwDOCGYnMM5cwsow", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T19:33:13Z", "updated_at": "2023-05-21T19:33:13Z", "author_association": "OWNER", "body": "Now released: https://sqlite-utils.datasette.io/en/stable/changelog.html#v3-32", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718517882, "label": "Try out Trogon for a tui interface"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/545#issuecomment-1556247818", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/545", "id": 1556247818, "node_id": "IC_kwDOCGYnMM5cwnUK", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T18:17:46Z", "updated_at": "2023-05-21T18:17:46Z", "author_association": "OWNER", "body": "Draft documentation: https://sqlite-utils--549.org.readthedocs.build/en/549/cli.html#cli-tui", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718517882, "label": "Try out Trogon for a tui interface"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/545#issuecomment-1556210844", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/545", "id": 1556210844, "node_id": "IC_kwDOCGYnMM5cweSc", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T15:44:10Z", "updated_at": "2023-05-21T15:44:10Z", "author_association": "OWNER", "body": "It looks like `nargs=-1` on a positional argument isn't yet supported - opened an issue here:\r\n- https://github.com/Textualize/trogon/issues/4", "reactions": "{\"total_count\": 1, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 1, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718517882, "label": "Try out Trogon for a tui interface"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/545#issuecomment-1556191894", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/545", "id": 1556191894, "node_id": "IC_kwDOCGYnMM5cwZqW", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T14:20:14Z", "updated_at": "2023-05-21T14:20:14Z", "author_association": "OWNER", "body": "Opened a feature request for customizing the help and command name:\r\n\r\n- https://github.com/Textualize/trogon/issues/2", "reactions": "{\"total_count\": 2, \"+1\": 1, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 1, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718517882, "label": "Try out Trogon for a tui interface"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/545#issuecomment-1556190531", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/545", "id": 1556190531, "node_id": "IC_kwDOCGYnMM5cwZVD", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T14:13:43Z", "updated_at": "2023-05-21T14:13:43Z", "author_association": "OWNER", "body": "OK, this works!\r\n\r\n![trogon](https://github.com/simonw/sqlite-utils/assets/9599/2ae194c5-ec82-471a-9d1b-b01b3f2632f3)\r\n\r\nTo try it out, install that branch from GitHub:\r\n\r\n pip install https://github.com/simonw/sqlite-utils/archive/refs/heads/trogon.zip\r\n\r\nThen run this:\r\n\r\n sqlite-utils install trogon\r\n\r\nAnd this:\r\n\r\n sqlite-utils tui\r\n", "reactions": "{\"total_count\": 5, \"+1\": 2, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 3, \"eyes\": 0}", "issue": {"value": 1718517882, "label": "Try out Trogon for a tui interface"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/545#issuecomment-1556189823", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/545", "id": 1556189823, "node_id": "IC_kwDOCGYnMM5cwZJ_", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T14:09:59Z", "updated_at": "2023-05-21T14:09:59Z", "author_association": "OWNER", "body": "I don't want to add `trogon` as a default dependency because it's a little heavy - it pulls in all of Rich and Textual as well. People who use `sqlite-utils` just for its Python API won't benefit from this - it's a CLI feature only.\r\n\r\nBut I have a `sqlite-utils install ...` command for helping people to install packages into the same virtual environment as `sqlite-utils` no matter how they installed that tool: https://sqlite-utils.datasette.io/en/stable/cli.html#cli-install\r\n\r\nSo I can treat Trogon as an optional dependency and add the `sqlite-utils tui` command only if that package is also installed.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718517882, "label": "Try out Trogon for a tui interface"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/544#issuecomment-1556225788", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/544", "id": 1556225788, "node_id": "IC_kwDOCGYnMM5cwh78", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T17:02:05Z", "updated_at": "2023-05-21T17:02:05Z", "author_association": "OWNER", "body": "New docs:\r\n\r\n- https://sqlite-utils.datasette.io/en/latest/cli.html#cli-analyze-tables\r\n- https://sqlite-utils.datasette.io/en/latest/cli-reference.html#analyze-tables\r\n- https://sqlite-utils.datasette.io/en/latest/python-api.html#analyzing-a-column\r\n- https://sqlite-utils.datasette.io/en/latest/reference.html#sqlite_utils.db.Table.analyze_column\r\n\r\nNew help output:\r\n\r\n```\r\n % sqlite-utils analyze-tables --help\r\nUsage: sqlite-utils analyze-tables [OPTIONS] PATH [TABLES]...\r\n\r\n Analyze the columns in one or more tables\r\n\r\n Example:\r\n\r\n sqlite-utils analyze-tables data.db trees\r\n\r\nOptions:\r\n -c, --column TEXT Specific columns to analyze\r\n --save Save results to _analyze_tables table\r\n --common-limit INTEGER How many common values\r\n --no-most Skip most common values\r\n --no-least Skip least common values\r\n --load-extension TEXT Path to SQLite extension, with optional :entrypoint\r\n -h, --help Show this message and exit.\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718515590, "label": "New options for analyze-tables --common-limit --no-most and --no-least"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/544#issuecomment-1556211643", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/544", "id": 1556211643, "node_id": "IC_kwDOCGYnMM5cwee7", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-21T15:48:17Z", "updated_at": "2023-05-21T15:48:17Z", "author_association": "OWNER", "body": "I generated the commit message in https://github.com/simonw/sqlite-utils/commit/1c1991b447a1ddd3d61d9d4a8a1d6a9da47ced20 using `git diff | llm --system 'describe this change'`.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1718515590, "label": "New options for analyze-tables --common-limit --no-most and --no-least"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2073#issuecomment-1546119773", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2073", "id": 1546119773, "node_id": "IC_kwDOBm6k_c5cJ-pd", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-12T18:24:07Z", "updated_at": "2023-05-12T18:24:07Z", "author_association": "OWNER", "body": "Here's a demo of this breaking in Datasette Lite:\r\n\r\nhttps://lite.datasette.io/?sql=https://gist.github.com/simonw/261564c0ca01567df6eeb9b222b8be84&json=https%3A%2F%2Fcdn.jsdelivr.net%2Fnpm%2Fweb-features%2Findex.json#/data/baseline?_filter_column_1=is_baseline&_filter_op_1=exact&_filter_value_1=1&_filter_column_2=&_filter_op_2=notnull__1&_filter_value_2=1&_filter_column=&_filter_op=exact&_filter_value=&_sort=&_facet=is_baseline\r\n\r\n\"image\"\r\n\r\nHere's a SQL query that demonstrates the underlying issue:\r\n\r\n```sql\r\nselect 'working', count(*) from baseline where is_baseline = 1\r\nunion all\r\nselect 'broken', count(*) from baseline where is_baseline = '1'\r\n```\r\nhttps://lite.datasette.io/?sql=https://gist.github.com/simonw/261564c0ca01567df6eeb9b222b8be84&json=https%3A%2F%2Fcdn.jsdelivr.net%2Fnpm%2Fweb-features%2Findex.json#/data?sql=select+%27working%27%2C+count%28*%29+from+baseline+where+is_baseline+%3D+1%0Aunion+all%0Aselect+%27broken%27%2C+count%28*%29+from+baseline+where+is_baseline+%3D+%271%27\r\n\r\n\"image\"", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1708030220, "label": "Faceting doesn't work against integer columns in views"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2073#issuecomment-1546117538", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2073", "id": 1546117538, "node_id": "IC_kwDOBm6k_c5cJ-Gi", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-12T18:21:38Z", "updated_at": "2023-05-12T18:21:38Z", "author_association": "OWNER", "body": "https://latest.datasette.io/fixtures doesn't currently have a view with any integer columns in it, making this bug harder to demonstrate there.\r\n\r\nI can't replicate the bug using https://datasette.io/content/plugins?_facet=stargazers_count&stargazers_count=3 - I would expect that not to work correctly.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1708030220, "label": "Faceting doesn't work against integer columns in views"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2070#issuecomment-1540494121", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2070", "id": 1540494121, "node_id": "IC_kwDOBm6k_c5b0hMp", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-09T16:25:00Z", "updated_at": "2023-05-09T16:25:00Z", "author_association": "OWNER", "body": "Can now be used here: https://github.com/simonw/datasette/actions/workflows/deploy-branch-preview.yml", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1702354223, "label": "Mechanism for deploying a preview of a branch using Vercel"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2070#issuecomment-1540491751", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2070", "id": 1540491751, "node_id": "IC_kwDOBm6k_c5b0gnn", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-09T16:23:12Z", "updated_at": "2023-05-09T16:23:12Z", "author_association": "OWNER", "body": "Added a actions `BRANCH_PREVIEW_VERCEL_TOKEN` secret to this repository.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1702354223, "label": "Mechanism for deploying a preview of a branch using Vercel"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/542#issuecomment-1539052467", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/542", "id": 1539052467, "node_id": "IC_kwDOCGYnMM5bvBOz", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T21:07:41Z", "updated_at": "2023-05-08T21:07:41Z", "author_association": "OWNER", "body": "Relevant commits (will mostly revert these):\r\n- https://github.com/simonw/sqlite-utils/commit/455c35b512895c19bf922c2b804d750d27cb8dbd\r\n- https://github.com/simonw/sqlite-utils/commit/e0ec4c345129996011951e400388fd74865f65a2", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1700936245, "label": "Remove `skip_false=True` and `--no-skip-false` in `sqlite-utils` 4.0"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/541#issuecomment-1538963959", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/541", "id": 1538963959, "node_id": "IC_kwDOCGYnMM5burn3", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-08T19:59:34Z", "updated_at": "2023-05-08T19:59:34Z", "author_association": "OWNER", "body": "There are 8 failing tests left:\r\n\r\n```\r\n==== short test summary info ====\r\nFAILED tests/test_cli_memory.py::test_memory_csv[False-test] - pytest.PytestUnraisableExceptionWarning: Exception ignored in: <_io.FileIO [closed]>\r\nFAILED tests/test_cli_memory.py::test_memory_csv[False-t] - pytest.PytestUnraisableExceptionWarning: Exception ignored in: <_io.FileIO [closed]>\r\nFAILED tests/test_cli_memory.py::test_memory_csv[False-t1] - pytest.PytestUnraisableExceptionWarning: Exception ignored in: <_io.FileIO [closed]>\r\nFAILED tests/test_cli_memory.py::test_memory_tsv[False] - pytest.PytestUnraisableExceptionWarning: Exception ignored in: <_io.FileIO [closed]>\r\nFAILED tests/test_cli_memory.py::test_memory_dump[extra_args0] - pytest.PytestUnraisableExceptionWarning: Exception ignored in: <_io.FileIO [closed]>\r\nFAILED tests/test_cli_memory.py::test_memory_two_files_with_same_stem - pytest.PytestUnraisableExceptionWarning: Exception ignored in: <_io.FileIO [closed]>\r\nFAILED tests/test_recipes.py::test_dateparse_errors[None-parsedate] - pytest.PytestUnraisableExceptionWarning: Exception ignored in: .convert_value at 0x106bcaca0>\r\nFAILED tests/test_recipes.py::test_dateparse_errors[None-parsedatetime] - pytest.PytestUnraisableExceptionWarning: Exception ignored in: .convert_value at 0x106bc9620>\r\nERROR tests/test_cli.py::test_upsert_analyze - pytest.PytestUnraisableExceptionWarning: Exception ignored in: <_io.FileIO [closed]>\r\n==== 8 failed, 894 passed, 4 skipped, 1 error in 6.27s ====\r\n```\r\nFull traceback here: https://gist.github.com/simonw/b40b3e814729d6c02a0302a84ce54d9e", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1700840265, "label": "Get tests to pass with `pytest -Werror`"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/540#issuecomment-1537514069", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/540", "id": 1537514069, "node_id": "IC_kwDOCGYnMM5bpJpV", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-07T18:40:18Z", "updated_at": "2023-05-07T18:40:18Z", "author_association": "OWNER", "body": "https://docs.readthedocs.io/en/stable/config-file/v2.html suggests:\r\n\r\n```yaml\r\nbuild:\r\n os: ubuntu-22.04\r\n tools:\r\n python: \"3.11\"\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1699184583, "label": "sphinx.builders.linkcheck build error"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/540#issuecomment-1537513912", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/540", "id": 1537513912, "node_id": "IC_kwDOCGYnMM5bpJm4", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-07T18:39:29Z", "updated_at": "2023-05-07T18:39:29Z", "author_association": "OWNER", "body": "https://readthedocs.org/projects/sqlite-utils/builds/20513034/ said:\r\n\r\n> Problem in your project's configuration. Invalid \"python.version\": expected one of (2, 2.7, 3, 3.5, 3.6, 3.7, 3.8, pypy3.5), got 3.11", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1699184583, "label": "sphinx.builders.linkcheck build error"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/540#issuecomment-1537513653", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/540", "id": 1537513653, "node_id": "IC_kwDOCGYnMM5bpJi1", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-07T18:37:59Z", "updated_at": "2023-05-07T18:38:19Z", "author_association": "OWNER", "body": "Useful comment here:\r\n\r\n- https://github.com/urllib3/urllib3/issues/2168#issuecomment-1537360928\r\n\r\n> I faced the same issue. I switched to a different Python kernel (3.11.2) and it worked.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1699184583, "label": "sphinx.builders.linkcheck build error"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/539#issuecomment-1537514610", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/539", "id": 1537514610, "node_id": "IC_kwDOCGYnMM5bpJxy", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-05-07T18:43:24Z", "updated_at": "2023-05-07T18:43:24Z", "author_association": "OWNER", "body": "Documentation:\r\n- https://sqlite-utils.datasette.io/en/latest/cli.html#returning-raw-data-such-as-binary-content\r\n- https://sqlite-utils.datasette.io/en/latest/cli-reference.html#query\r\n- https://sqlite-utils.datasette.io/en/latest/cli-reference.html#memory", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1699174055, "label": "`--raw-lines` option, like `--raw` for multiple lines"}, "performed_via_github_app": null}