{"html_url": "https://github.com/simonw/datasette/issues/1939#issuecomment-1343734812", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1939", "id": 1343734812, "node_id": "IC_kwDOBm6k_c5QF8Qc", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-09T01:57:07Z", "updated_at": "2022-12-09T01:57:07Z", "author_association": "OWNER", "body": "This search is better:\r\n\r\n datasette permission_allowed -user:simonw -path:datasette/** -path:docs/** -path:tests/** language:python\r\n\r\nThat returns 11 results: https://cs.github.com/?scopeName=All+repos&scope=&q=datasette+permission_allowed+-user%3Asimonw+-path%3Adatasette%2F**+-path%3Adocs%2F**+-path%3Atests%2F**+language%3Apython\r\n\r\n3 are forks of my repos. The rest are all by four users:\r\n\r\n- [20after4/ddd](https://github.com/20after4/ddd)\r\n- [emg110/datasette-graphql](https://github.com/emg110/datasette-graphql)\r\n- [next-LI/datasette-csv-importer](https://github.com/next-LI/datasette-csv-importer)\r\n- [next-LI/datasette-demo](https://github.com/next-LI/datasette-demo)\r\n- [next-LI/datasette-live-config](https://github.com/next-LI/datasette-live-config)\r\n- [next-LI/datasette-live-permissions](https://github.com/next-LI/datasette-live-permissions)\r\n- [next-LI/datasette-search-all](https://github.com/next-LI/datasette-search-all)\r\n- [next-LI/datasette-surveys](https://github.com/next-LI/datasette-surveys)\r\n- [next-LI/datasette-write](https://github.com/next-LI/datasette-write)\r\n- [rclement/datasette-dashboards](https://github.com/rclement/datasette-dashboards)\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": 1485757511, "label": "register_permissions(datasette) plugin hook"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1939#issuecomment-1343728929", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1939", "id": 1343728929, "node_id": "IC_kwDOBm6k_c5QF60h", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-09T01:48:11Z", "updated_at": "2022-12-09T01:52:33Z", "author_association": "OWNER", "body": "This code search shows a bunch of repos I don't know about that would be affected by this change: https://cs.github.com/?scopeName=All+repos&scope=&q=datasette+permission_allowed+-user%3Asimonw#\r\n\r\nThese (and likely more):\r\n\r\nRepositories\r\n\r\n- [20after4/ddd](https://github.com/20after4/ddd)\r\n- [next-LI/datasette-csv-importer](https://github.com/next-LI/datasette-csv-importer)\r\n- [digital-land/datasette](https://github.com/digital-land/datasette)\r\n- [mroswell/datasette](https://github.com/mroswell/datasette)\r\n- [next-LI/datasette-live-config](https://github.com/next-LI/datasette-live-config)\r\n- [keladhruv/datasette](https://github.com/keladhruv/datasette)\r\n- [RhetTbull/datasette](https://github.com/RhetTbull/datasette)\r\n- [chriswedgwood/datasette](https://github.com/chriswedgwood/datasette)\r\n- [boan-anbo/datasette](https://github.com/boan-anbo/datasette)\r\n- [MattTriano/datasette](https://github.com/MattTriano/datasette)\r\n- [incadenza/datasette](https://github.com/incadenza/datasette)\r\n- [robdyke/datasette](https://github.com/robdyke/datasette)\r\n- [ctb/datasette](https://github.com/ctb/datasette)\r\n- [eyeseast/datasette](https://github.com/eyeseast/datasette)\r\n- [symbol-management/api-match-audit](https://github.com/symbol-management/api-match-audit)\r\n\r\nActually a lot of those are forks of Datasette itself - so maybe this is manageable?\r\n\r\nWould be nice if I could come up with a GitHub search that excluded any repos with \"datasette\" as their exact name.\r\n\r\nhttps://docs.github.com/en/search-github/github-code-search/understanding-github-code-search-syntax#using-qualifiers says:\r\n\r\n> **Note:** The new code search beta does not currently support regular expressions or partial matching for repository names, so you will have to type the entire repository name (including the user prefix) for the `repo:` qualifier to work.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1485757511, "label": "register_permissions(datasette) plugin hook"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1939#issuecomment-1343727184", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1939", "id": 1343727184, "node_id": "IC_kwDOBm6k_c5QF6ZQ", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-09T01:45:15Z", "updated_at": "2022-12-09T01:45:15Z", "author_association": "OWNER", "body": "Moving the concept of the default for the permission into this registry warrants a redesign of this method anyway:\r\n\r\nhttps://github.com/simonw/datasette/blob/e539c1c024bc62d88df91d9107cbe37e7f0fe55f/datasette/app.py#L706", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1485757511, "label": "register_permissions(datasette) plugin hook"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1939#issuecomment-1343724732", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1939", "id": 1343724732, "node_id": "IC_kwDOBm6k_c5QF5y8", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-09T01:40:44Z", "updated_at": "2022-12-09T01:43:25Z", "author_association": "OWNER", "body": "```python\r\nPermission = collections.namedtuple( \r\n \"Permission\", (\"name\", \"abbr\", \"takes_database\", \"takes_table\", \"default\") \r\n) \r\n```\r\nI don`t think that design is quite right.\r\n\r\n- Elsewhere in the code the concept is called an \"action\" rather than a \"permission\" - I think I can stick with the `Permission` name here though, it's pretty clear\r\n- `takes_database` - is `takes_` the right verb here?\r\n- `takes_table` can also refer to a SQL view or a canned named query\r\n\r\nA question that was raised by the work in #1938 is whether you should be able to grant a permission like `insert-row` at the instance or database level - and if so, what does that look like? I think you should be able to do that, it doesn't make sense to have to grant it explicitly for every single table.\r\n\r\nSo maybe `takes_table` and `takes_database` are the right names here? But `table` is still bad because it doesn't reflect views and canned queries.\r\n\r\nOne thought is to use `resource` - but that will require a bunch of breaking changes to the existing APIs which treat resource as a tuple. Now's the best time to do that though before Datasette 1.0.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1485757511, "label": "register_permissions(datasette) plugin hook"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1881#issuecomment-1301645921", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1881", "id": 1301645921, "node_id": "IC_kwDOBm6k_c5NlYph", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-03T05:10:05Z", "updated_at": "2022-12-09T01:38:21Z", "author_association": "OWNER", "body": "I'd love to come up with a good short name for the second part of the resource two-tuple, the thing which is usually the name of a table but could also be the name of a SQL view or the name of a canned query.\r\n\r\nIdea 8th December: why not call it resource? A resource could be a thing that lives inside a database.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1434094365, "label": "Tool for simulating permission checks against actors"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1939#issuecomment-1343722020", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1939", "id": 1343722020, "node_id": "IC_kwDOBm6k_c5QF5Ik", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-09T01:36:05Z", "updated_at": "2022-12-09T01:36:16Z", "author_association": "OWNER", "body": "I originally added `permissions.py` for the permission debug tool in https://github.com/simonw/datasette/commit/c51d9246b996a2831c9bd6a1e205f6cb48b9a5f3 - I don't think anything else uses it yet.\r\n\r\n- #1881", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1485757511, "label": "register_permissions(datasette) plugin hook"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1939#issuecomment-1343721522", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1939", "id": 1343721522, "node_id": "IC_kwDOBm6k_c5QF5Ay", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-09T01:35:15Z", "updated_at": "2022-12-09T01:35:15Z", "author_association": "OWNER", "body": "One concern I have about this: there are a bunch of existing plugins that do stuff with permissions that won't currently be using this hook.\r\n\r\nDo I break those plugins, forcing new releases of them for compatibility with Datasette 1.0?\r\n\r\nOr maybe I keep them working, but until they've upgraded to register their permissions there are things about them that won't work - e.g. you won't be able to configure their permissions in `metadata.yml` until they release something that does this hook.\r\n\r\nBest thing is probably for me to get this working in core first and then evaluate the impact it would have on existing plugins once I have some running code.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1485757511, "label": "register_permissions(datasette) plugin hook"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1636#issuecomment-1343715746", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1636", "id": 1343715746, "node_id": "IC_kwDOBm6k_c5QF3mi", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-09T01:27:41Z", "updated_at": "2022-12-09T01:27:58Z", "author_association": "OWNER", "body": "I may need to consult this file to figure out if the permission that is being checked can act at the database/table/instance level:\r\n\r\nhttps://github.com/simonw/datasette/blob/e539c1c024bc62d88df91d9107cbe37e7f0fe55f/datasette/permissions.py#L1-L19", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1138008042, "label": "\"permissions\" propery in metadata for configuring arbitrary permissions"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/1938#issuecomment-1343449918", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1938", "id": 1343449918, "node_id": "IC_kwDOBm6k_c5QE2s-", "user": {"value": 22429695, "label": "codecov[bot]"}, "created_at": "2022-12-08T22:20:10Z", "updated_at": "2022-12-08T22:54:08Z", "author_association": "NONE", "body": "# [Codecov](https://codecov.io/gh/simonw/datasette/pull/1938?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) Report\nBase: **92.00**% // Head: **92.01**% // Increases project coverage by **`+0.01%`** :tada:\n> Coverage data is based on head [(`6e35a6b`)](https://codecov.io/gh/simonw/datasette/pull/1938?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) compared to base [(`e539c1c`)](https://codecov.io/gh/simonw/datasette/commit/e539c1c024bc62d88df91d9107cbe37e7f0fe55f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison).\n> Patch coverage: 100.00% of modified lines in pull request are covered.\n\n
Additional details and impacted files\n\n\n```diff\n@@ Coverage Diff @@\n## main #1938 +/- ##\n==========================================\n+ Coverage 92.00% 92.01% +0.01% \n==========================================\n Files 38 38 \n Lines 5378 5386 +8 \n==========================================\n+ Hits 4948 4956 +8 \n Misses 430 430 \n```\n\n\n| [Impacted Files](https://codecov.io/gh/simonw/datasette/pull/1938?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) | Coverage \u0394 | |\n|---|---|---|\n| [datasette/default\\_permissions.py](https://codecov.io/gh/simonw/datasette/pull/1938/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison#diff-ZGF0YXNldHRlL2RlZmF1bHRfcGVybWlzc2lvbnMucHk=) | `95.10% <100.00%> (+0.29%)` | :arrow_up: |\n\nHelp us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison)\n\n
\n\n[:umbrella: View full report at Codecov](https://codecov.io/gh/simonw/datasette/pull/1938?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison). \n:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison).\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1485488236, "label": "\"permissions\" blocks in metadata.json/yaml"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1636#issuecomment-1343446071", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1636", "id": 1343446071, "node_id": "IC_kwDOBm6k_c5QE1w3", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-08T22:16:17Z", "updated_at": "2022-12-08T22:16:17Z", "author_association": "OWNER", "body": "First draft of documentation: https://datasette--1938.org.readthedocs.build/en/1938/authentication.html#other-permissions-in-metadata", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1138008042, "label": "\"permissions\" propery in metadata for configuring arbitrary permissions"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/1938#issuecomment-1343445885", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1938", "id": 1343445885, "node_id": "IC_kwDOBm6k_c5QE1t9", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-08T22:16:03Z", "updated_at": "2022-12-08T22:16:03Z", "author_association": "OWNER", "body": "Docs: https://datasette--1938.org.readthedocs.build/en/1938/authentication.html#other-permissions-in-metadata", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1485488236, "label": "\"permissions\" blocks in metadata.json/yaml"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1636#issuecomment-1343440504", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1636", "id": 1343440504, "node_id": "IC_kwDOBm6k_c5QE0Z4", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-08T22:10:28Z", "updated_at": "2022-12-08T22:10:48Z", "author_association": "OWNER", "body": "What if you want to grant `insert-row` to a user for ALL tables in a database, or even for all tables in all databases?\r\n\r\nYou should be able to do that by putting that in the root `permissions:` block. Need to figure out how the implementation will handle that.\r\n\r\nAlso: there are some permissions like `view-instance` or `debug-menu` for which putting them at the `database` or `table` or `query` level doesn't actually make any sense.\r\n\r\nIdeally the implementation would spot those on startup and refuse to start the server, with a helpful error message.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1138008042, "label": "\"permissions\" propery in metadata for configuring arbitrary permissions"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/1930#issuecomment-1343360006", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1930", "id": 1343360006, "node_id": "IC_kwDOBm6k_c5QEgwG", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-08T21:12:28Z", "updated_at": "2022-12-08T21:12:28Z", "author_association": "OWNER", "body": "Thanks!", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473664029, "label": "Typo in JSON API `Updating a row` documentation"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1867#issuecomment-1341874378", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1867", "id": 1341874378, "node_id": "IC_kwDOBm6k_c5P-2DK", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-08T02:07:06Z", "updated_at": "2022-12-08T02:28:02Z", "author_association": "OWNER", "body": "Basic version of this allows you to rename a table:\r\n\r\n```\r\nPOST /db/table/-/rename\r\n{\r\n \"name\": \"new_table_name\"\r\n}\r\n```\r\nIf a table with that new name already exists you will get an error - unless you pass `\"replace\": true` in which case that table will be dropped and replaced by the new one.\r\n\r\n\r\n```\r\nPOST /db/table/-/rename\r\n{\r\n \"name\": \"new_table_name\",\r\n \"replace\": true\r\n}\r\n```\r\n\r\nThis is useful because it allows for that atomic replacement operation: upload brand new data into a `_tmp_name` table, then atomic rename and replace after the upload has completed.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1426080014, "label": "/db/table/-/rename API (also allows atomic replace)"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1636#issuecomment-1341854373", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1636", "id": 1341854373, "node_id": "IC_kwDOBm6k_c5P-xKl", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-08T01:43:35Z", "updated_at": "2022-12-08T01:43:35Z", "author_association": "OWNER", "body": "I'm going to write the documentation for this first.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1138008042, "label": "\"permissions\" propery in metadata for configuring arbitrary permissions"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1936#issuecomment-1341849735", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1936", "id": 1341849735, "node_id": "IC_kwDOBm6k_c5P-wCH", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-08T01:35:54Z", "updated_at": "2022-12-08T01:35:54Z", "author_association": "OWNER", "body": "Running that twice produced this:\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": 1483250004, "label": "Fix /db/table/-/upsert in the API explorer"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1936#issuecomment-1341849496", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1936", "id": 1341849496, "node_id": "IC_kwDOBm6k_c5P-v-Y", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-08T01:35:35Z", "updated_at": "2022-12-08T01:35:35Z", "author_association": "OWNER", "body": "Related bug: you can send `\"id\": null` and it works (it should throw an error):\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": 1483250004, "label": "Fix /db/table/-/upsert in the API explorer"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1937#issuecomment-1341848525", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1937", "id": 1341848525, "node_id": "IC_kwDOBm6k_c5P-vvN", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-08T01:34:03Z", "updated_at": "2022-12-08T01:34:03Z", "author_association": "OWNER", "body": "Check should go somewhere around here: https://github.com/simonw/datasette/blob/dee18ed8ce7af2ab8699bcb5a51a99f48301bc42/datasette/views/database.py#L625-L631", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1483320357, "label": "/db/-/create API should require insert-rows permission to use row: or rows: option"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/1931#issuecomment-1339906241", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1931", "id": 1339906241, "node_id": "IC_kwDOBm6k_c5P3VjB", "user": {"value": 22429695, "label": "codecov[bot]"}, "created_at": "2022-12-06T19:33:32Z", "updated_at": "2022-12-08T01:04:56Z", "author_association": "NONE", "body": "# [Codecov](https://codecov.io/gh/simonw/datasette/pull/1931?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) Report\nBase: **90.42**% // Head: **91.77**% // Increases project coverage by **`+1.34%`** :tada:\n> Coverage data is based on head [(`645be0f`)](https://codecov.io/gh/simonw/datasette/pull/1931?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) compared to base [(`cab5b60`)](https://codecov.io/gh/simonw/datasette/commit/cab5b60e09e94aca820dbec5308446a88c99ea3d?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison).\n> Patch coverage: 95.55% of modified lines in pull request are covered.\n\n> :exclamation: Current head 645be0f differs from pull request most recent head 7cd6fd9. Consider uploading reports for the commit 7cd6fd9 to get more accurate results\n\n
Additional details and impacted files\n\n\n```diff\n@@ Coverage Diff @@\n## main #1931 +/- ##\n==========================================\n+ Coverage 90.42% 91.77% +1.34% \n==========================================\n Files 36 36 \n Lines 5057 5019 -38 \n==========================================\n+ Hits 4573 4606 +33 \n+ Misses 484 413 -71 \n```\n\n\n| [Impacted Files](https://codecov.io/gh/simonw/datasette/pull/1931?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) | Coverage \u0394 | |\n|---|---|---|\n| [datasette/views/special.py](https://codecov.io/gh/simonw/datasette/pull/1931/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison#diff-ZGF0YXNldHRlL3ZpZXdzL3NwZWNpYWwucHk=) | `79.41% <0.00%> (\u00f8)` | |\n| [datasette/views/table.py](https://codecov.io/gh/simonw/datasette/pull/1931/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison#diff-ZGF0YXNldHRlL3ZpZXdzL3RhYmxlLnB5) | `92.44% <97.43%> (+0.20%)` | :arrow_up: |\n| [datasette/app.py](https://codecov.io/gh/simonw/datasette/pull/1931/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison#diff-ZGF0YXNldHRlL2FwcC5weQ==) | `94.42% <100.00%> (+<0.01%)` | :arrow_up: |\n| [datasette/default\\_permissions.py](https://codecov.io/gh/simonw/datasette/pull/1931/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison#diff-ZGF0YXNldHRlL2RlZmF1bHRfcGVybWlzc2lvbnMucHk=) | `94.81% <100.00%> (+0.07%)` | :arrow_up: |\n| [datasette/views/database.py](https://codecov.io/gh/simonw/datasette/pull/1931/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison#diff-ZGF0YXNldHRlL3ZpZXdzL2RhdGFiYXNlLnB5) | `95.83% <0.00%> (+17.01%)` | :arrow_up: |\n\nHelp us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison)\n\n
\n\n[:umbrella: View full report at Codecov](https://codecov.io/gh/simonw/datasette/pull/1931?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison). \n:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison).\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473814539, "label": "/db/table/-/upsert"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/1931#issuecomment-1341825314", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1931", "id": 1341825314, "node_id": "IC_kwDOBm6k_c5P-qEi", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-08T01:03:18Z", "updated_at": "2022-12-08T01:03:18Z", "author_association": "OWNER", "body": "I broke this test:\r\n```\r\nds_write = \r\n\r\n @pytest.mark.asyncio\r\n async def test_insert_row(ds_write):\r\n token = write_token(ds_write)\r\n response = await ds_write.client.post(\r\n \"/data/docs/-/insert\",\r\n json={\"row\": {\"title\": \"Test\", \"score\": 1.2, \"age\": 5}},\r\n headers={\r\n \"Authorization\": \"***\".format(token),\r\n \"Content-Type\": \"application/json\",\r\n },\r\n )\r\n expected_row = {\"id\": 1, \"title\": \"Test\", \"score\": 1.2, \"age\": 5}\r\n> assert response.status_code == 201\r\nE assert 500 == 201\r\nE + where 500 = .status_code\r\n\r\n/home/runner/work/datasette/datasette/tests/test_api_write.py:43: AssertionError\r\n----------------------------- Captured stderr call -----------------------------\r\nTraceback (most recent call last):\r\n File \"/home/runner/work/datasette/datasette/datasette/app.py\", line 1447, in route_path\r\n response = await view(request, send)\r\n File \"/home/runner/work/datasette/datasette/datasette/views/base.py\", line 151, in view\r\n return await self.dispatch_request(request)\r\n File \"/home/runner/work/datasette/datasette/datasette/views/base.py\", line 105, in dispatch_request\r\n response = await handler(request)\r\n File \"/home/runner/work/datasette/datasette/datasette/views/table.py\", line 1228, in post\r\n row_pk_values_for_later = [tuple(row[pk] for pk in pks) for row in rows]\r\n File \"/home/runner/work/datasette/datasette/datasette/views/table.py\", line 1228, in \r\n row_pk_values_for_later = [tuple(row[pk] for pk in pks) for row in rows]\r\n File \"/home/runner/work/datasette/datasette/datasette/views/table.py\", line 1228, in \r\n row_pk_values_for_later = [tuple(row[pk] for pk in pks) for row in rows]\r\nKeyError: 'id'\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473814539, "label": "/db/table/-/upsert"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/1931#issuecomment-1341821213", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1931", "id": 1341821213, "node_id": "IC_kwDOBm6k_c5P-pEd", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-08T00:58:21Z", "updated_at": "2022-12-08T00:58:21Z", "author_association": "OWNER", "body": "In the interests of shipping, I'm going to punt the API explorer to a later issue.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473814539, "label": "/db/table/-/upsert"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/1935#issuecomment-1340950566", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1935", "id": 1340950566, "node_id": "IC_kwDOBm6k_c5P7Ugm", "user": {"value": 22429695, "label": "codecov[bot]"}, "created_at": "2022-12-07T13:14:41Z", "updated_at": "2022-12-07T13:14:41Z", "author_association": "NONE", "body": "# [Codecov](https://codecov.io/gh/simonw/datasette/pull/1935?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) Report\nBase: **91.73**% // Head: **91.49**% // Decreases project coverage by **`-0.24%`** :warning:\n> Coverage data is based on head [(`e8ae41e`)](https://codecov.io/gh/simonw/datasette/pull/1935?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) compared to base [(`93ababe`)](https://codecov.io/gh/simonw/datasette/commit/93ababe6f7150454d2cf278dae08569e505d2a5b?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison).\n> Patch has no changes to coverable lines.\n\n
Additional details and impacted files\n\n\n```diff\n@@ Coverage Diff @@\n## main #1935 +/- ##\n==========================================\n- Coverage 91.73% 91.49% -0.25% \n==========================================\n Files 36 37 +1 \n Lines 4987 5031 +44 \n==========================================\n+ Hits 4575 4603 +28 \n- Misses 412 428 +16 \n```\n\n\n| [Impacted Files](https://codecov.io/gh/simonw/datasette/pull/1935?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) | Coverage \u0394 | |\n|---|---|---|\n| [datasette/utils/shutil\\_backport.py](https://codecov.io/gh/simonw/datasette/pull/1935/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison#diff-ZGF0YXNldHRlL3V0aWxzL3NodXRpbF9iYWNrcG9ydC5weQ==) | `9.09% <0.00%> (\u00f8)` | |\n| [datasette/app.py](https://codecov.io/gh/simonw/datasette/pull/1935/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison#diff-ZGF0YXNldHRlL2FwcC5weQ==) | `94.78% <0.00%> (+0.36%)` | :arrow_up: |\n| [datasette/plugins.py](https://codecov.io/gh/simonw/datasette/pull/1935/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison#diff-ZGF0YXNldHRlL3BsdWdpbnMucHk=) | `85.29% <0.00%> (+2.94%)` | :arrow_up: |\n| [datasette/utils/asgi.py](https://codecov.io/gh/simonw/datasette/pull/1935/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison#diff-ZGF0YXNldHRlL3V0aWxzL2FzZ2kucHk=) | `93.60% <0.00%> (+3.59%)` | :arrow_up: |\n| [datasette/cli.py](https://codecov.io/gh/simonw/datasette/pull/1935/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison#diff-ZGF0YXNldHRlL2NsaS5weQ==) | `82.18% <0.00%> (+4.00%)` | :arrow_up: |\n\nHelp us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison)\n\n
\n\n[:umbrella: View full report at Codecov](https://codecov.io/gh/simonw/datasette/pull/1935?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison). \n:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison).\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1481875485, "label": "Bump furo from 2022.9.29 to 2022.12.7"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/1931#issuecomment-1339968514", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1931", "id": 1339968514, "node_id": "IC_kwDOBm6k_c5P3kwC", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-06T20:28:47Z", "updated_at": "2022-12-06T20:28:47Z", "author_association": "OWNER", "body": "Should the `\"return\": true` mode reflect the order in which the rows were provided when the API was called?\r\n\r\nI think it should. Since this is small enough to happily fit in Python memory (thanks to the `max_insert_rows` setting) I can load the fresh data from the database and then sort it in Python space before returning it.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473814539, "label": "/db/table/-/upsert"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1855#issuecomment-1339952692", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1855", "id": 1339952692, "node_id": "IC_kwDOBm6k_c5P3g40", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-06T20:15:50Z", "updated_at": "2022-12-06T20:16:00Z", "author_association": "OWNER", "body": "That commit there https://github.com/simonw/datasette/commit/6da17d5529773dfe41b53ed4ce5a6ecb46ed2457 (which will be squash-merged in a PR later on) made it so that `_r` was correctly copied across from the token to the created actor, and fixed a bug in the code that checks permissions against it for resources.\r\n\r\nI needed that mechanism to write a test that exercised different API permissions.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1423336089, "label": "`datasette create-token` ability to create tokens with a reduced set of permissions"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/1931#issuecomment-1339916064", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1931", "id": 1339916064, "node_id": "IC_kwDOBm6k_c5P3X8g", "user": {"value": 3556, "label": "davidbgk"}, "created_at": "2022-12-06T19:42:45Z", "updated_at": "2022-12-06T19:42:45Z", "author_association": "CONTRIBUTOR", "body": "The `\"return\": true` option is really nice!", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473814539, "label": "/db/table/-/upsert"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/1931#issuecomment-1339911152", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1931", "id": 1339911152, "node_id": "IC_kwDOBm6k_c5P3Wvw", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-06T19:38:12Z", "updated_at": "2022-12-06T19:38:12Z", "author_association": "OWNER", "body": "Documentation: https://datasette--1931.org.readthedocs.build/en/1931/json_api.html#upserting-rows", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473814539, "label": "/db/table/-/upsert"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1927#issuecomment-1339910494", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1927", "id": 1339910494, "node_id": "IC_kwDOBm6k_c5P3Wle", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-06T19:37:39Z", "updated_at": "2022-12-06T19:37:39Z", "author_association": "OWNER", "body": "I'll finish this after I land:\r\n- #1931 ", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473411197, "label": "ignore:true/replace:true options for /db/-/create API"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1929#issuecomment-1339909159", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1929", "id": 1339909159, "node_id": "IC_kwDOBm6k_c5P3WQn", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-06T19:36:23Z", "updated_at": "2022-12-06T19:36:23Z", "author_association": "OWNER", "body": "https://docs.datasette.io/en/1.0a1/json_api.html \ud83d\udc4d ", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473659191, "label": "Incorrect link from the API explorer to the JSON API documentation"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1929#issuecomment-1339906969", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1929", "id": 1339906969, "node_id": "IC_kwDOBm6k_c5P3VuZ", "user": {"value": 3556, "label": "davidbgk"}, "created_at": "2022-12-06T19:34:20Z", "updated_at": "2022-12-06T19:34:20Z", "author_association": "CONTRIBUTOR", "body": "I confirm that it works \ud83d\udc4d ", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473659191, "label": "Incorrect link from the API explorer to the JSON API documentation"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1929#issuecomment-1339871933", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1929", "id": 1339871933, "node_id": "IC_kwDOBm6k_c5P3NK9", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-06T19:23:48Z", "updated_at": "2022-12-06T19:24:17Z", "author_association": "OWNER", "body": "I can do that on this page: https://readthedocs.org/projects/datasette/versions/?version_filter=1.0\r\n\r\n\"image\"\r\n\r\nI'm making them both active and hidden:\r\n\r\n\"CleanShot\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473659191, "label": "Incorrect link from the API explorer to the JSON API documentation"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1929#issuecomment-1339867570", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1929", "id": 1339867570, "node_id": "IC_kwDOBm6k_c5P3MGy", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-06T19:22:47Z", "updated_at": "2022-12-06T19:22:47Z", "author_association": "OWNER", "body": "Yeah I should deploy the docs for the alpha versions (the intention is that the docs exactly match the release you are using), thanks for spotting that.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473659191, "label": "Incorrect link from the API explorer to the JSON API documentation"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/516#issuecomment-1339844639", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/516", "id": 1339844639, "node_id": "IC_kwDOCGYnMM5P3Ggf", "user": {"value": 122043, "label": "briandorsey"}, "created_at": "2022-12-06T19:08:13Z", "updated_at": "2022-12-06T19:08:13Z", "author_association": "NONE", "body": "Reference: tqdm (https://tqdm.github.io/) shows a progress bar when total is known, and falls back to counting units of work done for streams. File input vs. stdin seems like a similar situation. ", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1479914599, "label": "Feature request: output number of ignored/replaced rows for insert command"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/516#issuecomment-1339839767", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/516", "id": 1339839767, "node_id": "IC_kwDOCGYnMM5P3FUX", "user": {"value": 122043, "label": "briandorsey"}, "created_at": "2022-12-06T19:04:17Z", "updated_at": "2022-12-06T19:04:17Z", "author_association": "NONE", "body": "Current behavior is different when importing via stdin vs. a file. Imports from a file give a progress bar. For this new request, I'd love to see total imported and total ignored/replaced in both cases. ", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1479914599, "label": "Feature request: output number of ignored/replaced rows for insert command"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/516#issuecomment-1339837520", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/516", "id": 1339837520, "node_id": "IC_kwDOCGYnMM5P3ExQ", "user": {"value": 122043, "label": "briandorsey"}, "created_at": "2022-12-06T19:02:30Z", "updated_at": "2022-12-06T19:02:30Z", "author_association": "NONE", "body": "`--verbose` or `--verbosity=ABC` were the flags I looked for. Expected to see them at a global level near `--version`. But only sharing because that's where I looked first, I don't have a strong opinion on the exact wording/location. ", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1479914599, "label": "Feature request: output number of ignored/replaced rows for insert command"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/516#issuecomment-1339834918", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/516", "id": 1339834918, "node_id": "IC_kwDOCGYnMM5P3EIm", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-06T19:00:18Z", "updated_at": "2022-12-06T19:00:35Z", "author_association": "OWNER", "body": "Right now the command produces no output at all.\r\n\r\nMaybe a `--verbose` mode that writes these numbers to standard error (or even standard output since it's an option)?\r\n\r\nIs there a better name than `--verbose` for this? `--summary` perhaps?", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1479914599, "label": "Feature request: output number of ignored/replaced rows for insert command"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/1931#issuecomment-1339784569", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1931", "id": 1339784569, "node_id": "IC_kwDOBm6k_c5P2315", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-06T18:16:15Z", "updated_at": "2022-12-06T18:17:56Z", "author_association": "OWNER", "body": "Just noticed the insert API returns `{}` when it should return `{\"ok\": true}` - will fix that here too.\r\n\r\nUPDATE: no it did that already, it was just the documentation that was wrong.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473814539, "label": "/db/table/-/upsert"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/1931#issuecomment-1339768422", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1931", "id": 1339768422, "node_id": "IC_kwDOBm6k_c5P2z5m", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-06T18:04:59Z", "updated_at": "2022-12-06T18:04:59Z", "author_association": "OWNER", "body": "I realized this API should require both the `insert-row` AND the `update-row` permissions, since calls to it could do either one.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473814539, "label": "/db/table/-/upsert"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1855#issuecomment-1302815929", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1855", "id": 1302815929, "node_id": "IC_kwDOBm6k_c5Np2S5", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-04T00:19:10Z", "updated_at": "2022-12-03T07:05:51Z", "author_association": "OWNER", "body": "Added the tests.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1423336089, "label": "`datasette create-token` ability to create tokens with a reduced set of permissions"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1878#issuecomment-1336100218", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1878", "id": 1336100218, "node_id": "IC_kwDOBm6k_c5Po0V6", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-03T07:02:15Z", "updated_at": "2022-12-03T07:02:15Z", "author_association": "OWNER", "body": "Moved this work to a PR:\r\n- #1931", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1432013704, "label": "/db/table/-/upsert API"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1927#issuecomment-1336099588", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1927", "id": 1336099588, "node_id": "IC_kwDOBm6k_c5Po0ME", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-03T06:58:14Z", "updated_at": "2022-12-03T06:58:14Z", "author_association": "OWNER", "body": "I have not yet documented the new `insert` and `replace` options.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473411197, "label": "ignore:true/replace:true options for /db/-/create API"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1927#issuecomment-1336099533", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1927", "id": 1336099533, "node_id": "IC_kwDOBm6k_c5Po0LN", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-03T06:57:52Z", "updated_at": "2022-12-03T06:57:52Z", "author_association": "OWNER", "body": "I'm going to push what I have anyway. I'll keep this issue open while I think through the above comment.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473411197, "label": "ignore:true/replace:true options for /db/-/create API"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1927#issuecomment-1336099368", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1927", "id": 1336099368, "node_id": "IC_kwDOBm6k_c5Po0Io", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-03T06:56:36Z", "updated_at": "2022-12-03T06:56:36Z", "author_association": "OWNER", "body": "Neither of these options make sense if you didn't pass a `\"pk\"`.\r\n\r\nMy initial implementation spotted if the `pk` was missing and looked it up from the table, but actually I don't think that makes sense - if you know the table exists and hence don't pass the `pk` you should be using `/-/insert` or `/-/upsert` instead.\r\n\r\nSo maybe this work should expanded to include validation that checks if the table exists already - and if it does, confirms that the primary key (and maybe even the columns) are the same as for that existing table.\r\n\r\nOf course if you only send `row` or `rows` then checking `columns` doesn't completely make sense - but we could check that the rows you have sent are equal to or a subset of the columns in the table. We could even check the column types as well, as seen in:\r\n- #1910 ", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473411197, "label": "ignore:true/replace:true options for /db/-/create API"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1878#issuecomment-1336094562", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1878", "id": 1336094562, "node_id": "IC_kwDOBm6k_c5Poy9i", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-03T06:27:50Z", "updated_at": "2022-12-03T06:29:06Z", "author_association": "OWNER", "body": "This adds it to the API explorer:\r\n\r\n```diff\r\ndiff --git a/datasette/views/special.py b/datasette/views/special.py\r\nindex 1f84b094..1b4a9d3c 100644\r\n--- a/datasette/views/special.py\r\n+++ b/datasette/views/special.py\r\n@@ -316,21 +316,37 @@ class ApiExplorerView(BaseView):\r\n request.actor, \"insert-row\", (name, table)\r\n ):\r\n pks = await db.primary_keys(table)\r\n- table_links.append(\r\n- {\r\n- \"path\": self.ds.urls.table(name, table) + \"/-/insert\",\r\n- \"method\": \"POST\",\r\n- \"label\": \"Insert rows into {}\".format(table),\r\n- \"json\": {\r\n- \"rows\": [\r\n- {\r\n- column: None\r\n- for column in await db.table_columns(table)\r\n- if column not in pks\r\n- }\r\n- ]\r\n+ table_links.extend(\r\n+ [\r\n+ {\r\n+ \"path\": self.ds.urls.table(name, table) + \"/-/insert\",\r\n+ \"method\": \"POST\",\r\n+ \"label\": \"Insert rows into {}\".format(table),\r\n+ \"json\": {\r\n+ \"rows\": [\r\n+ {\r\n+ column: None\r\n+ for column in await db.table_columns(table)\r\n+ if column not in pks\r\n+ }\r\n+ ]\r\n+ },\r\n },\r\n- }\r\n+ {\r\n+ \"path\": self.ds.urls.table(name, table) + \"/-/upsert\",\r\n+ \"method\": \"POST\",\r\n+ \"label\": \"Upsert rows into {}\".format(table),\r\n+ \"json\": {\r\n+ \"rows\": [\r\n+ {\r\n+ column: None\r\n+ for column in await db.table_columns(table)\r\n+ if column not in pks\r\n+ }\r\n+ ]\r\n+ },\r\n+ },\r\n+ ]\r\n )\r\n if await self.ds.permission_allowed(\r\n request.actor, \"drop-table\", (name, table)\r\n```\r\nExcept it doesn't quite, because the example JSON this generates is invalid as it does not include the primary key column.\r\n\r\n(Made me notice that the way example columns are created for `/-/insert` will fail for tables that don't have an auto-incrementing primary key)", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1432013704, "label": "/db/table/-/upsert API"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1878#issuecomment-1336094470", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1878", "id": 1336094470, "node_id": "IC_kwDOBm6k_c5Poy8G", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-03T06:27:13Z", "updated_at": "2022-12-03T06:27:13Z", "author_association": "OWNER", "body": "Tests are going to need to cover both rowid-only and compound primary key tables, including all of the error states.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1432013704, "label": "/db/table/-/upsert API"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1878#issuecomment-1336094381", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1878", "id": 1336094381, "node_id": "IC_kwDOBm6k_c5Poy6t", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-03T06:26:25Z", "updated_at": "2022-12-03T06:26:25Z", "author_association": "OWNER", "body": "Initial prototype:\r\n```diff\r\ndiff --git a/datasette/app.py b/datasette/app.py\r\nindex 125b4969..282c0984 100644\r\n--- a/datasette/app.py\r\n+++ b/datasette/app.py\r\n@@ -40,7 +40,7 @@ from .views.special import (\r\n PermissionsDebugView,\r\n MessagesDebugView,\r\n )\r\n-from .views.table import TableView, TableInsertView, TableDropView\r\n+from .views.table import TableView, TableInsertView, TableUpsertView, TableDropView\r\n from .views.row import RowView, RowDeleteView, RowUpdateView\r\n from .renderer import json_renderer\r\n from .url_builder import Urls\r\n@@ -1292,6 +1292,10 @@ class Datasette:\r\n TableInsertView.as_view(self),\r\n r\"/(?P[^\\/\\.]+)/(?P[^\\/\\.]+)/-/insert$\",\r\n )\r\n+ add_route(\r\n+ TableUpsertView.as_view(self),\r\n+ r\"/(?P[^\\/\\.]+)/(?P
[^\\/\\.]+)/-/upsert$\",\r\n+ )\r\n add_route(\r\n TableDropView.as_view(self),\r\n r\"/(?P[^\\/\\.]+)/(?P
[^\\/\\.]+)/-/drop$\",\r\ndiff --git a/datasette/views/table.py b/datasette/views/table.py\r\nindex 7ba78c11..ae0d6366 100644\r\n--- a/datasette/views/table.py\r\n+++ b/datasette/views/table.py\r\n@@ -1074,9 +1074,15 @@ class TableInsertView(BaseView):\r\n def __init__(self, datasette):\r\n self.ds = datasette\r\n \r\n- async def _validate_data(self, request, db, table_name):\r\n+ async def _validate_data(self, request, db, table_name, pks, upsert):\r\n errors = []\r\n \r\n+ pks_list = []\r\n+ if isinstance(pks, str):\r\n+ pks_list = [pks]\r\n+ else:\r\n+ pks_list = list(pks)\r\n+\r\n def _errors(errors):\r\n return None, errors, {}\r\n \r\n@@ -1135,6 +1141,15 @@ class TableInsertView(BaseView):\r\n # Validate columns of each row\r\n columns = set(await db.table_columns(table_name))\r\n for i, row in enumerate(rows):\r\n+ if upsert:\r\n+ # It MUST have the primary key\r\n+ missing_pks = [pk for pk in pks_list if pk not in row]\r\n+ if missing_pks:\r\n+ errors.append(\r\n+ 'Row {} is missing primary key column(s): \"{}\"'.format(\r\n+ i, '\", \"'.join(missing_pks)\r\n+ )\r\n+ )\r\n invalid_columns = set(row.keys()) - columns\r\n if invalid_columns:\r\n errors.append(\r\n@@ -1146,7 +1161,7 @@ class TableInsertView(BaseView):\r\n return _errors(errors)\r\n return rows, errors, extras\r\n \r\n- async def post(self, request):\r\n+ async def post(self, request, upsert=False):\r\n try:\r\n resolved = await self.ds.resolve_table(request)\r\n except NotFound as e:\r\n@@ -1164,7 +1179,12 @@ class TableInsertView(BaseView):\r\n request.actor, \"insert-row\", resource=(database_name, table_name)\r\n ):\r\n return _error([\"Permission denied\"], 403)\r\n- rows, errors, extras = await self._validate_data(request, db, table_name)\r\n+\r\n+ pks = await db.primary_keys(table_name)\r\n+\r\n+ rows, errors, extras = await self._validate_data(\r\n+ request, db, table_name, pks, upsert\r\n+ )\r\n if errors:\r\n return _error(errors, 400)\r\n \r\n@@ -1172,15 +1192,19 @@ class TableInsertView(BaseView):\r\n replace = extras.get(\"replace\")\r\n \r\n should_return = bool(extras.get(\"return\", False))\r\n- # Insert rows\r\n- def insert_rows(conn):\r\n+\r\n+ def insert_or_upsert_rows(conn):\r\n table = sqlite_utils.Database(conn)[table_name]\r\n+ kwargs = {}\r\n+ if upsert:\r\n+ kwargs[\"pk\"] = pks[0] if len(pks) == 1 else pks\r\n+ else:\r\n+ kwargs = {\"ignore\": ignore, \"replace\": replace}\r\n if should_return:\r\n rowids = []\r\n+ method = table.upsert if upsert else table.insert\r\n for row in rows:\r\n- rowids.append(\r\n- table.insert(row, ignore=ignore, replace=replace).last_rowid\r\n- )\r\n+ rowids.append(method(row, **kwargs).last_rowid)\r\n return list(\r\n table.rows_where(\r\n \"rowid in ({})\".format(\",\".join(\"?\" for _ in rowids)),\r\n@@ -1188,10 +1212,11 @@ class TableInsertView(BaseView):\r\n )\r\n )\r\n else:\r\n- table.insert_all(rows, ignore=ignore, replace=replace)\r\n+ method_all = table.upsert_all if upsert else table.insert_all\r\n+ method_all(rows, **kwargs)\r\n \r\n try:\r\n- rows = await db.execute_write_fn(insert_rows)\r\n+ rows = await db.execute_write_fn(insert_or_upsert_rows)\r\n except Exception as e:\r\n return _error([str(e)])\r\n result = {\"ok\": True}\r\n@@ -1200,6 +1225,13 @@ class TableInsertView(BaseView):\r\n return Response.json(result, status=201)\r\n \r\n \r\n+class TableUpsertView(TableInsertView):\r\n+ name = \"table-upsert\"\r\n+\r\n+ async def post(self, request):\r\n+ return await super().post(request, upsert=True)\r\n+\r\n+\r\n class TableDropView(BaseView):\r\n name = \"table-drop\"\r\n ```\r\nManual testing reveals that this mostly works... but it's not doing the right thing for `\"return\": true` - it always returns an empty list.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1432013704, "label": "/db/table/-/upsert API"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1878#issuecomment-1336073212", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1878", "id": 1336073212, "node_id": "IC_kwDOBm6k_c5Potv8", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-03T05:38:49Z", "updated_at": "2022-12-03T05:38:49Z", "author_association": "OWNER", "body": "And on Discord today: https://discord.com/channels/823971286308356157/823971286941302908/1048426072066236536", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1432013704, "label": "/db/table/-/upsert API"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1878#issuecomment-1336070843", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1878", "id": 1336070843, "node_id": "IC_kwDOBm6k_c5PotK7", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-03T05:37:53Z", "updated_at": "2022-12-03T05:37:53Z", "author_association": "OWNER", "body": "Also requested here: https://news.ycombinator.com/item?id=33839894", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1432013704, "label": "/db/table/-/upsert API"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/1930#issuecomment-1336017976", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1930", "id": 1336017976, "node_id": "IC_kwDOBm6k_c5PogQ4", "user": {"value": 22429695, "label": "codecov[bot]"}, "created_at": "2022-12-03T02:30:21Z", "updated_at": "2022-12-03T02:30:21Z", "author_association": "NONE", "body": "# [Codecov](https://codecov.io/gh/simonw/datasette/pull/1930?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) Report\nBase: **90.42**% // Head: **90.42**% // No change to project coverage :thumbsup:\n> Coverage data is based on head [(`9928ff1`)](https://codecov.io/gh/simonw/datasette/pull/1930?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) compared to base [(`cab5b60`)](https://codecov.io/gh/simonw/datasette/commit/cab5b60e09e94aca820dbec5308446a88c99ea3d?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison).\n> Patch has no changes to coverable lines.\n\n
Additional details and impacted files\n\n\n```diff\n@@ Coverage Diff @@\n## main #1930 +/- ##\n=======================================\n Coverage 90.42% 90.42% \n=======================================\n Files 36 36 \n Lines 5057 5057 \n=======================================\n Hits 4573 4573 \n Misses 484 484 \n```\n\n\n\nHelp us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison)\n\n
\n\n[:umbrella: View full report at Codecov](https://codecov.io/gh/simonw/datasette/pull/1930?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison). \n:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison).\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473664029, "label": "Typo in JSON API `Updating a row` documentation"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1927#issuecomment-1335984268", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1927", "id": 1335984268, "node_id": "IC_kwDOBm6k_c5PoYCM", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-03T00:26:26Z", "updated_at": "2022-12-03T00:26:26Z", "author_association": "OWNER", "body": "Also: the documentation should clarify that you can call this API multiple times when using the `rows` option.\r\n\r\n(It will probably grow `\"alter\": true` soon too).", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473411197, "label": "ignore:true/replace:true options for /db/-/create API"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1928#issuecomment-1335966329", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1928", "id": 1335966329, "node_id": "IC_kwDOBm6k_c5PoTp5", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-02T23:47:11Z", "updated_at": "2022-12-02T23:47:11Z", "author_association": "OWNER", "body": "Wrote about this extensively here: https://simonwillison.net/2022/Dec/2/datasette-write-api/", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473481262, "label": "Hacker News Datasette write demo"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1928#issuecomment-1335870889", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1928", "id": 1335870889, "node_id": "IC_kwDOBm6k_c5Pn8Wp", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-02T21:41:09Z", "updated_at": "2022-12-02T21:41:09Z", "author_association": "OWNER", "body": "Got it working!\r\n\r\nhttps://simon.datasette.cloud/data/hacker_news_posts\r\n\r\nhttps://github.com/simonw/scrape-hacker-news-by-domain/blob/main/submit-to-datasette-cloud.sh", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473481262, "label": "Hacker News Datasette write demo"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1928#issuecomment-1335870887", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1928", "id": 1335870887, "node_id": "IC_kwDOBm6k_c5Pn8Wn", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-02T21:25:16Z", "updated_at": "2022-12-02T21:25:16Z", "author_association": "OWNER", "body": "Here's the change that should submit data to Datasette Cloud: https://github.com/simonw/scrape-hacker-news-by-domain/commit/848bb7e835a9fb87cd656362591835179cd1dc1b\r\n\r\nI ran `delete from hacker_news_posts` against my instance so https://simon.datasette.cloud/data/hacker_news_posts is now empty.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473481262, "label": "Hacker News Datasette write demo"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1928#issuecomment-1335870884", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1928", "id": 1335870884, "node_id": "IC_kwDOBm6k_c5Pn8Wk", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-02T21:19:58Z", "updated_at": "2022-12-02T21:19:58Z", "author_association": "OWNER", "body": "But until I fix this issue:\r\n- https://github.com/simonw/datasette/issues/1927\r\n\r\nI need to insert freshly scraped data like this:\r\n\r\n```bash\r\nexport ROWS=$(\r\n jq -n --argjson rows \"$(cat simonwillison-net.json)\" \\\r\n '{ \"rows\": $rows, \"replace\": true }'\r\n)\r\n\r\ncurl -X POST \\\r\n https://simon.datasette.cloud/data/hacker_news_posts/-/insert \\\r\n -H \"Content-Type: application/json\" \\\r\n -H \"Authorization: Bearer $DS_TOKEN\" \\\r\n -d $ROWS\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473481262, "label": "Hacker News Datasette write demo"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1928#issuecomment-1335870883", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1928", "id": 1335870883, "node_id": "IC_kwDOBm6k_c5Pn8Wj", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-02T21:19:10Z", "updated_at": "2022-12-02T21:19:10Z", "author_association": "OWNER", "body": "I created the `hacker_news_posts` table like this:\r\n\r\n```bash\r\nexport ROWS=$(\r\n jq -n --argjson rows \"$(cat simonwillison-net.json)\" \\\r\n '{ \"table\": \"hacker_news_posts\", \"rows\": $rows, \"pk\": \"id\", \"replace\": true }'\r\n)\r\n# Use curl to POST some JSON to a URL\r\ncurl -X POST \\\r\n https://simon.datasette.cloud/data/-/create \\\r\n -H \"Content-Type: application/json\" \\\r\n -H \"Authorization: Bearer $DS_TOKEN\" \\\r\n -d $ROWS\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473481262, "label": "Hacker News Datasette write demo"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1928#issuecomment-1335870879", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1928", "id": 1335870879, "node_id": "IC_kwDOBm6k_c5Pn8Wf", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-02T21:18:25Z", "updated_at": "2022-12-02T21:18:25Z", "author_association": "OWNER", "body": "This is the SQL view for the atom feed:\r\n```sql\r\nCREATE VIEW hacker_news_posts_atom as select\r\n id as atom_id,\r\n title as atom_title,\r\n url,\r\n commentsUrl as atom_link,\r\n dt || 'Z' as atom_updated,\r\n 'Submitter: ' || submitter || ' - ' || points || ' points, ' || numComments || ' comments' as atom_content\r\nfrom\r\n hacker_news_posts\r\norder by\r\n dt desc\r\nlimit\r\n 100;\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473481262, "label": "Hacker News Datasette write demo"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1928#issuecomment-1335870877", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1928", "id": 1335870877, "node_id": "IC_kwDOBm6k_c5Pn8Wd", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-02T21:17:50Z", "updated_at": "2022-12-02T21:17:50Z", "author_association": "OWNER", "body": "https://simon.datasette.cloud/data/hacker_news_posts_atom.atom is working now.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1473481262, "label": "Hacker News Datasette write demo"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1636#issuecomment-1334759315", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1636", "id": 1334759315, "node_id": "IC_kwDOBm6k_c5Pjs-T", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-02T04:46:32Z", "updated_at": "2022-12-02T04:46:32Z", "author_association": "OWNER", "body": "Thankfully all of the logic for this already lives in just one place:\r\n\r\nhttps://github.com/simonw/datasette/blob/d7e5e3c9f98d194fdfb12f1ecc60ed5b3afbc464/datasette/default_permissions.py#L23-L59", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1138008042, "label": "\"permissions\" propery in metadata for configuring arbitrary permissions"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1636#issuecomment-1334758766", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1636", "id": 1334758766, "node_id": "IC_kwDOBm6k_c5Pjs1u", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-02T04:45:16Z", "updated_at": "2022-12-02T04:45:16Z", "author_association": "OWNER", "body": "Also, this is another thing which should live in `config.yml` rather than being crammed into `metadata.yml` - but I can fix that when I address:\r\n- #493", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1138008042, "label": "\"permissions\" propery in metadata for configuring arbitrary permissions"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1636#issuecomment-1334757597", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1636", "id": 1334757597, "node_id": "IC_kwDOBm6k_c5Pjsjd", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-02T04:42:35Z", "updated_at": "2022-12-02T04:42:35Z", "author_association": "OWNER", "body": "Should I call this key `permissions` or something else?\r\n\r\nSome options:\r\n\r\n- `permissions`\r\n- `perms` - shorter to type\r\n- `allow` - I like the word, but might be confusing to change its meaning since we use it already", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1138008042, "label": "\"permissions\" propery in metadata for configuring arbitrary permissions"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1636#issuecomment-1334673179", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1636", "id": 1334673179, "node_id": "IC_kwDOBm6k_c5PjX8b", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-02T02:07:20Z", "updated_at": "2022-12-02T04:27:07Z", "author_association": "OWNER", "body": "So the new mechanism needs to extend that to handle all of the other permissions as well.\r\n\r\nThe simplest design I can think of is this (here illustrated using YAML):\r\n\r\n```yaml\r\n# instance-level permissions - give every logged in user the debug menu:\r\npermissions:\r\n debug-menu:\r\n id: *\r\ndatabases:\r\n content:\r\n # Allow bob to create-table in the content database\r\n permissions:\r\n create-table:\r\n id: bob\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1138008042, "label": "\"permissions\" propery in metadata for configuring arbitrary permissions"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1636#issuecomment-1334666806", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1636", "id": 1334666806, "node_id": "IC_kwDOBm6k_c5PjWY2", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-02T01:58:40Z", "updated_at": "2022-12-02T02:00:53Z", "author_association": "OWNER", "body": "Current design:\r\n\r\n```json\r\n{\r\n \"databases\": {\r\n \"private\": {\r\n \"allow\": {\r\n \"id\": \"*\"\r\n }\r\n }\r\n }\r\n}\r\n```\r\nThis can be applied at the instance, database, table or query level within the nested JSON.\r\n\r\nhttps://docs.datasette.io/en/stable/authentication.html#controlling-access-to-specific-databases\r\n\r\nIt's actually controlling the following permissions:\r\n\r\n- `view-instance`\r\n- `view-database`\r\n- `view-table`\r\n- `view-query`\r\n\r\nThere's also a special case for allowing SQL queries,at the instance and database level:\r\n\r\n```json\r\n{\r\n \"databases\": {\r\n \"mydatabase\": {\r\n \"allow_sql\": {\r\n \"id\": \"root\"\r\n }\r\n }\r\n }\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": 1138008042, "label": "\"permissions\" propery in metadata for configuring arbitrary permissions"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1926#issuecomment-1334508062", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1926", "id": 1334508062, "node_id": "IC_kwDOBm6k_c5Pivoe", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-01T22:06:12Z", "updated_at": "2022-12-01T22:06:12Z", "author_association": "OWNER", "body": "Released: https://pypi.org/project/datasette/1.0a1/", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1471969984, "label": "Release notes for 1.0a1 (and release it)"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1924#issuecomment-1334165225", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1924", "id": 1334165225, "node_id": "IC_kwDOBm6k_c5Phb7p", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-01T18:15:15Z", "updated_at": "2022-12-01T18:15:15Z", "author_association": "OWNER", "body": "Updated docs at the bottom of this section: https://docs.datasette.io/en/latest/json_api.html#inserting-rows", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1470509936, "label": "Docs for replace:true and ignore:true options for insert API"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1924#issuecomment-1333042785", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1924", "id": 1333042785, "node_id": "IC_kwDOBm6k_c5PdJ5h", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-01T02:00:06Z", "updated_at": "2022-12-01T02:00:06Z", "author_association": "OWNER", "body": "Looks like I did implement these already:\r\n\r\nhttps://github.com/simonw/datasette/blob/9a1536b52a07e32da5900652da1bd7894c58fa9f/tests/test_api_write.py#L270-L273\r\n\r\nBut forgot to document them!", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1470509936, "label": "Docs for replace:true and ignore:true options for insert API"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1924#issuecomment-1333025076", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1924", "id": 1333025076, "node_id": "IC_kwDOBm6k_c5PdFk0", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-01T01:37:09Z", "updated_at": "2022-12-01T01:37:09Z", "author_association": "OWNER", "body": "Related question: what happens if you attempt to insert rows without either of these settings and 1:10 of them is an integrity error due to an existing primary key?\r\n\r\nDoes the entire batch fail to be inserted? Should it?\r\n\r\nThis may be the point that I need to think hard about how to use transactions here.\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": 1470509936, "label": "Docs for replace:true and ignore:true options for insert API"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1924#issuecomment-1333023273", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1924", "id": 1333023273, "node_id": "IC_kwDOBm6k_c5PdFIp", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-12-01T01:34:48Z", "updated_at": "2022-12-01T01:34:48Z", "author_association": "OWNER", "body": "This will enable some very cool Datasette write API demos - for example, Git scrapers that insert-replace the most recent copy of the scraped data to a table somewhere (which can then produce an atom feed with https://datasette.io/plugins/datasette-atom)", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1470509936, "label": "Docs for replace:true and ignore:true options for insert API"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1922#issuecomment-1332903011", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1922", "id": 1332903011, "node_id": "IC_kwDOBm6k_c5Pcnxj", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T23:45:29Z", "updated_at": "2022-11-30T23:45:29Z", "author_association": "OWNER", "body": "That worked for the preflight request - got this now:\r\n\r\n\"image\"\r\n\r\nSo it looks like error responses (in this case for permission denied) are missing CORS headers.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1469973742, "label": "Make sure CORS works for write APIs"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1922#issuecomment-1332855687", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1922", "id": 1332855687, "node_id": "IC_kwDOBm6k_c5PccOH", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T23:09:31Z", "updated_at": "2022-11-30T23:09:31Z", "author_association": "OWNER", "body": "Still getting a CORS header.\r\n\r\nI tried it in Chrome, which outputs helpful messages to the console:\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": 1469973742, "label": "Make sure CORS works for write APIs"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1923#issuecomment-1332851215", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1923", "id": 1332851215, "node_id": "IC_kwDOBm6k_c5PcbIP", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T23:04:56Z", "updated_at": "2022-11-30T23:04:56Z", "author_association": "OWNER", "body": "That fixed it.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1470320227, "label": "latest.datasette.io Cloud Run deploys failing"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1923#issuecomment-1332842435", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1923", "id": 1332842435, "node_id": "IC_kwDOBm6k_c5PcY_D", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T22:58:33Z", "updated_at": "2022-11-30T22:58:33Z", "author_association": "OWNER", "body": "https://stackoverflow.com/questions/74490465/github-actions-failing-for-setup-gcloud/74562740#74562740 suggests trying Python 3.9.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1470320227, "label": "latest.datasette.io Cloud Run deploys failing"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1923#issuecomment-1332835664", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1923", "id": 1332835664, "node_id": "IC_kwDOBm6k_c5PcXVQ", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T22:50:10Z", "updated_at": "2022-11-30T22:51:25Z", "author_association": "OWNER", "body": "https://stackoverflow.com/questions/74490465/github-actions-failing-for-setup-gcloud/74562526#74562526 suggests setting `version: '318.0.0'` of `google-github-actions/setup-gcloud`.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1470320227, "label": "latest.datasette.io Cloud Run deploys failing"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1922#issuecomment-1332698636", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1922", "id": 1332698636, "node_id": "IC_kwDOBm6k_c5Pb14M", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T20:25:50Z", "updated_at": "2022-11-30T20:25:50Z", "author_association": "OWNER", "body": "I just shipped this:\r\n\r\n Access-Control-Allow-Methods: GET, POST, HEAD, OPTIONS\r\n\r\nI'll try this out on `latest.datasette.io` - but I need to research more to check if this is a safe thing to do or not.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1469973742, "label": "Make sure CORS works for write APIs"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1922#issuecomment-1332689547", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1922", "id": 1332689547, "node_id": "IC_kwDOBm6k_c5PbzqL", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T20:16:21Z", "updated_at": "2022-11-30T20:16:46Z", "author_association": "OWNER", "body": "That notebook:\r\n```javascript\r\nviewof token = Inputs.text({\r\n label: \"Your API token\"\r\n})\r\n```\r\n```javascript\r\nviewof createResponse = Inputs.button(\"Create table\", {\r\n value: null,\r\n reduce: async () => {\r\n const response = await fetch(\r\n \"https://latest.datasette.io/ephemeral/-/create\",\r\n {\r\n method: \"POST\",\r\n mode: \"cors\",\r\n headers: {\r\n Authorization: `Bearer {$token}`,\r\n \"Content-Type\": \"application/json\"\r\n },\r\n body: JSON.stringify({\r\n table: \"my_new_table\",\r\n row: {\r\n task: \"Demonstrate a JSON creation from another domain\"\r\n }\r\n })\r\n }\r\n );\r\n return await response.json();\r\n }\r\n})\r\n```\r\nBased on this tip: https://talk.observablehq.com/t/best-pattern-for-click-here-to-submit-your-results-to-an-api-backend/7353/3", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1469973742, "label": "Make sure CORS works for write APIs"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1922#issuecomment-1332688245", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1922", "id": 1332688245, "node_id": "IC_kwDOBm6k_c5PbzV1", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T20:15:08Z", "updated_at": "2022-11-30T20:15:08Z", "author_association": "OWNER", "body": "Still getting a CORS error:\r\n\r\n\"image\"\r\n\r\nMy hunch is this is because I'm not sending `Access-Control-Allow-Methods: GET,HEAD,POST`.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1469973742, "label": "Make sure CORS works for write APIs"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1922#issuecomment-1332585861", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1922", "id": 1332585861, "node_id": "IC_kwDOBm6k_c5PbaWF", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T18:43:46Z", "updated_at": "2022-11-30T18:43:46Z", "author_association": "OWNER", "body": "Here's what Django Rest Framework does: https://github.com/encode/django-rest-framework/blob/1ae812ea209392ad76cc5d2f35f9f7fb337f63e4/rest_framework/views.py#L514-L521\r\n\r\n```python\r\n def options(self, request, *args, **kwargs):\r\n \"\"\"\r\n Handler method for HTTP 'OPTIONS' request.\r\n \"\"\"\r\n if self.metadata_class is None:\r\n return self.http_method_not_allowed(request, *args, **kwargs)\r\n data = self.metadata_class().determine_metadata(request, self)\r\n return Response(data, status=status.HTTP_200_OK)\r\n```\r\nThat default `determine_metadata` method looks like this: https://github.com/encode/django-rest-framework/blob/1ae812ea209392ad76cc5d2f35f9f7fb337f63e4/rest_framework/metadata.py#L61-L71\r\n\r\n```python\r\n def determine_metadata(self, request, view):\r\n metadata = OrderedDict()\r\n metadata['name'] = view.get_view_name()\r\n metadata['description'] = view.get_view_description()\r\n metadata['renders'] = [renderer.media_type for renderer in view.renderer_classes]\r\n metadata['parses'] = [parser.media_type for parser in view.parser_classes]\r\n if hasattr(view, 'get_serializer'):\r\n actions = self.determine_actions(request, view)\r\n if actions:\r\n metadata['actions'] = actions\r\n return metadata\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1469973742, "label": "Make sure CORS works for write APIs"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1922#issuecomment-1332580395", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1922", "id": 1332580395, "node_id": "IC_kwDOBm6k_c5PbZAr", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T18:38:22Z", "updated_at": "2022-11-30T18:38:22Z", "author_association": "OWNER", "body": "> [@simon](https://fedi.simonwillison.net/@simon) IMO, it should always be a 2XX series response, typically with no content & an extra `Allow` header with a list of HTTP verbs it responds to.\r\n\r\nhttps://mastodon.social/@daniellindsley/109434186252099323", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1469973742, "label": "Make sure CORS works for write APIs"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1922#issuecomment-1332572453", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1922", "id": 1332572453, "node_id": "IC_kwDOBm6k_c5PbXEl", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T18:30:38Z", "updated_at": "2022-11-30T18:30:54Z", "author_association": "OWNER", "body": "Started a conversation about how OPTIONS should work on Mastodon: https://fedi.simonwillison.net/@simon/109434148676475291", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1469973742, "label": "Make sure CORS works for write APIs"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1922#issuecomment-1332561813", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1922", "id": 1332561813, "node_id": "IC_kwDOBm6k_c5PbUeV", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T18:20:05Z", "updated_at": "2022-11-30T18:20:05Z", "author_association": "OWNER", "body": "Weird, GitHub reply with a 404!\r\n```\r\n~ % curl -X OPTIONS https://github.com/ -i\r\nHTTP/2 404 \r\nserver: GitHub.com\r\ndate: Wed, 30 Nov 2022 18:19:39 GMT\r\ncontent-type: text/html; charset=utf-8\r\ncontent-length: 0\r\nstrict-transport-security: max-age=31536000; includeSubdomains; preload\r\nx-frame-options: deny\r\nx-content-type-options: nosniff\r\nx-xss-protection: 0\r\nreferrer-policy: origin-when-cross-origin, strict-origin-when-cross-origin\r\ncontent-security-policy: default-src 'none'; base-uri 'self'; block-all-mixed-content; child-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/; connect-src 'self' uploads.github.com objects-origin.githubusercontent.com www.githubstatus.com collector.github.com raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com cdn.optimizely.com logx.optimizely.com/v1/events; font-src github.githubassets.com; form-action 'self' github.com gist.github.com objects-origin.githubusercontent.com; frame-ancestors 'none'; frame-src viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src 'self' data: github.githubassets.com media.githubusercontent.com camo.githubusercontent.com identicons.github.com avatars.githubusercontent.com github-cloud.s3.amazonaws.com objects.githubusercontent.com objects-origin.githubusercontent.com secured-user-images.githubusercontent.com/ opengraph.githubassets.com github-production-user-asset-6210df.s3.amazonaws.com customer-stories-feed.github.com spotlights-feed.github.com; manifest-src 'self'; media-src github.com user-images.githubusercontent.com/ secured-user-images.githubusercontent.com/; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com; worker-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/\r\nvary: Accept-Encoding, Accept, X-Requested-With\r\nx-github-request-id: DD6B:5ACA:102E8A6:1164A99:63879EBB\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1469973742, "label": "Make sure CORS works for write APIs"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1922#issuecomment-1332561059", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1922", "id": 1332561059, "node_id": "IC_kwDOBm6k_c5PbUSj", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T18:19:20Z", "updated_at": "2022-11-30T18:19:20Z", "author_association": "OWNER", "body": "Two test failures:\r\n```\r\n____________________________ test_homepage_options _____________________________\r\n[gw0] linux -- Python 3.11.0 /opt/hostedtoolcache/Python/3.11.0/x64/bin/python\r\n\r\napp_client = \r\n\r\n def test_homepage_options(app_client):\r\n response = app_client.get(\"/\", method=\"OPTIONS\")\r\n> assert response.status == 405\r\nE assert 200 == 405\r\nE + where 200 = .status\r\n\r\n/home/runner/work/datasette/datasette/tests/test_html.py:58: AssertionError\r\n______________________ test_client_methods[options-/-405] ______________________\r\n[gw1] linux -- Python 3.11.0 /opt/hostedtoolcache/Python/3.11.0/x64/bin/python\r\n\r\ndatasette = \r\nmethod = 'options', path = '/', expected_status = 405\r\n\r\n @pytest.mark.asyncio\r\n @pytest.mark.parametrize(\r\n \"method,path,expected_status\",\r\n [\r\n (\"get\", \"/\", 200),\r\n (\"options\", \"/\", 405),\r\n (\"head\", \"/\", 200),\r\n (\"put\", \"/\", 405),\r\n (\"patch\", \"/\", 405),\r\n (\"delete\", \"/\", 405),\r\n ],\r\n )\r\n async def test_client_methods(datasette, method, path, expected_status):\r\n client_method = getattr(datasette.client, method)\r\n response = await client_method(path)\r\n assert isinstance(response, httpx.Response)\r\n> assert response.status_code == expected_status\r\nE assert 200 == 405\r\nE + where 200 = .status_code\r\n\r\n/home/runner/work/datasette/datasette/tests/test_internals_datasette_client.py:29: AssertionError\r\n=============================== warnings summary ===============================\r\ntests/test_cli.py::test_inspect_cli_writes_to_file\r\ntests/test_cli.py::test_inspect_cli\r\n /home/runner/work/datasette/datasette/datasette/cli.py:163: DeprecationWarning: There is no current event loop\r\n loop = asyncio.get_event_loop()\r\n\r\ntests/test_cli_serve_get.py: 2 warnings\r\ntests/test_cli.py: 12 warnings\r\ntests/test_crossdb.py: 1 warning\r\n /home/runner/work/datasette/datasette/datasette/cli.py:591: DeprecationWarning: There is no current event loop\r\n asyncio.get_event_loop().run_until_complete(ds.invoke_startup())\r\n\r\ntests/test_cli_serve_get.py: 2 warnings\r\ntests/test_cli.py: 12 warnings\r\ntests/test_crossdb.py: 1 warning\r\n /home/runner/work/datasette/datasette/datasette/cli.py:594: DeprecationWarning: There is no current event loop\r\n asyncio.get_event_loop().run_until_complete(check_databases(ds))\r\n\r\n-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html\r\n=========================== short test summary info ============================\r\nFAILED tests/test_html.py::test_homepage_options - assert 200 == 405\r\n + where 200 = .status\r\nFAILED tests/test_internals_datasette_client.py::test_client_methods[options-/-405] - assert 200 == 405\r\n + where 200 = .status_code\r\n====== 2 failed, 1195 passed, 1 skipped, 32 warnings in 191.06s (0:03:11) ======\r\nError: Process completed with exit code 1.\r\n```\r\nOn reading https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS I feel like I should be a bit more thoughtful about how I treat OPTIONS - maybe it should work for every URL on the site, but return a `204 No Content` header?\r\n\r\nComparing a few different sites:\r\n\r\n```\r\n~ % curl -X OPTIONS https://www.google.com/ -i\r\nHTTP/2 405 \r\nallow: GET, HEAD\r\ndate: Wed, 30 Nov 2022 18:18:15 GMT\r\ncontent-type: text/html; charset=UTF-8\r\nserver: gws\r\ncontent-length: 1592\r\nx-xss-protection: 0\r\nx-frame-options: SAMEORIGIN\r\nalt-svc: h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000,h3-Q050=\":443\"; ma=2592000,h3-Q046=\":443\"; ma=2592000,h3-Q043=\":443\"; ma=2592000,quic=\":443\"; ma=2592000; v=\"46,43\"\r\n\r\n\r\n\r\n \r\n \r\n Error 405 (Method Not Allowed)!!1\r\n \r\n \r\n

405. That\u2019s an error.\r\n

The request method OPTIONS is inappropriate for the URL /. That\u2019s all we know.\r\n~ % curl -X OPTIONS https://www.mozilla.org/ -i\r\nHTTP/2 405 \r\ncontent-type: text/html; charset=utf-8\r\ncontent-length: 0\r\nserver: meinheld/1.0.2\r\ndate: Wed, 30 Nov 2022 18:18:38 GMT\r\nallow: GET, HEAD\r\nx-frame-options: DENY\r\ncontent-security-policy: child-src 'self' *.mozilla.net *.mozilla.org *.mozilla.com www.googletagmanager.com www.google-analytics.com www.youtube-nocookie.com trackertest.org www.surveygizmo.com accounts.firefox.com accounts.firefox.com.cn www.youtube.com; connect-src 'self' *.mozilla.net *.mozilla.org *.mozilla.com www.googletagmanager.com www.google-analytics.com region1.google-analytics.com logs.convertexperiments.com 1003350.metrics.convertexperiments.com 1003343.metrics.convertexperiments.com sentry.prod.mozaws.net o1069899.sentry.io o1069899.ingest.sentry.io https://accounts.firefox.com/ stage.cjms.nonprod.cloudops.mozgcp.net cjms.services.mozilla.com; frame-src 'self' *.mozilla.net *.mozilla.org *.mozilla.com www.googletagmanager.com www.google-analytics.com www.youtube-nocookie.com trackertest.org www.surveygizmo.com accounts.firefox.com accounts.firefox.com.cn www.youtube.com; script-src 'self' *.mozilla.net *.mozilla.org *.mozilla.com 'unsafe-inline' 'unsafe-eval' www.googletagmanager.com www.google-analytics.com tagmanager.google.com www.youtube.com s.ytimg.com cdn-3.convertexperiments.com app.convert.com data.track.convertexperiments.com 1003350.track.convertexperiments.com 1003343.track.convertexperiments.com; img-src 'self' *.mozilla.net *.mozilla.org *.mozilla.com data: mozilla.org www.googletagmanager.com www.google-analytics.com adservice.google.com adservice.google.de adservice.google.dk creativecommons.org cdn-3.convertexperiments.com logs.convertexperiments.com images.ctfassets.net ad.doubleclick.net; style-src 'self' *.mozilla.net *.mozilla.org *.mozilla.com 'unsafe-inline' app.convert.com; default-src 'self' *.mozilla.net *.mozilla.org *.mozilla.com; font-src 'self'\r\ncache-control: max-age=600\r\nexpires: Wed, 30 Nov 2022 18:28:38 GMT\r\nx-backend-server: bedrock-prod-web-b95bc569d-grd25.iowa-a\r\nstrict-transport-security: max-age=31536000\r\nx-content-type-options: nosniff\r\nx-xss-protection: 1; mode=block\r\nreferrer-policy: strict-origin-when-cross-origin\r\nvia: 1.1 google, 1.1 6c90b631453c435bd0022caa657b67e8.cloudfront.net (CloudFront)\r\nx-cache: Error from cloudfront\r\nx-amz-cf-pop: SFO5-P2\r\nx-amz-cf-id: A6-9mLztaE2tz840CbV9bXYiBMZRKEamDj6jGGEl1U7sg8egWfsDqg==\r\n\r\n~ % curl -X OPTIONS https://example.com -i \r\nHTTP/2 200 \r\nallow: OPTIONS, GET, HEAD, POST\r\ncache-control: max-age=604800\r\ncontent-type: text/html; charset=UTF-8\r\ndate: Wed, 30 Nov 2022 18:18:59 GMT\r\nexpires: Wed, 07 Dec 2022 18:18:59 GMT\r\nserver: EOS (vny/0451)\r\ncontent-length: 0\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1469973742, "label": "Make sure CORS works for write APIs"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1922#issuecomment-1332504654", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1922", "id": 1332504654, "node_id": "IC_kwDOBm6k_c5PbGhO", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T17:27:39Z", "updated_at": "2022-11-30T17:27:39Z", "author_association": "OWNER", "body": "I'll test this once it's deployed to https://latest.datasette.io/", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1469973742, "label": "Make sure CORS works for write APIs"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1922#issuecomment-1332493004", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1922", "id": 1332493004, "node_id": "IC_kwDOBm6k_c5PbDrM", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T17:18:10Z", "updated_at": "2022-11-30T17:18:10Z", "author_association": "OWNER", "body": "Here's why:\r\n\r\nhttps://github.com/simonw/datasette/blob/4ddd77e51254bda3bac990ea662bac2e6b29c5e0/datasette/views/base.py#L71-L79\r\n\r\nThat's code in `BaseView` - but it turns out the code that adds CORS headers is in the `DataView` subclass of that (which the various write API endpoints do not use).\r\n\r\nhttps://github.com/simonw/datasette/blob/4ddd77e51254bda3bac990ea662bac2e6b29c5e0/datasette/views/base.py#L158-L162", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1469973742, "label": "Make sure CORS works for write APIs"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1922#issuecomment-1332492092", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1922", "id": 1332492092, "node_id": "IC_kwDOBm6k_c5PbDc8", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T17:17:21Z", "updated_at": "2022-11-30T17:17:21Z", "author_association": "OWNER", "body": "I tried running `fetch()` with a POST from a separate domain and got a browser error because it did a GET against the `/db/-/create` endpoint and the 405 method not supported response did not include the CORS headers.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1469973742, "label": "Make sure CORS works for write APIs"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1605#issuecomment-1332310772", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1605", "id": 1332310772, "node_id": "IC_kwDOBm6k_c5PaXL0", "user": {"value": 25778, "label": "eyeseast"}, "created_at": "2022-11-30T15:06:37Z", "updated_at": "2022-11-30T15:06:37Z", "author_association": "CONTRIBUTOR", "body": "I'll add issues for both and do a documentation PR.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1108671952, "label": "Scripted exports"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1605#issuecomment-1331694246", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1605", "id": 1331694246, "node_id": "IC_kwDOBm6k_c5PYAqm", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T06:18:41Z", "updated_at": "2022-11-30T06:18:41Z", "author_association": "OWNER", "body": "Those sounds to me like they should be promoted to documented, supported internals.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1108671952, "label": "Scripted exports"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1918#issuecomment-1331658629", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1918", "id": 1331658629, "node_id": "IC_kwDOBm6k_c5PX3-F", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T05:21:51Z", "updated_at": "2022-11-30T05:21:51Z", "author_association": "OWNER", "body": "Much better:\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": 1469044738, "label": "API explorer should list mutable databases first"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1919#issuecomment-1331657404", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1919", "id": 1331657404, "node_id": "IC_kwDOBm6k_c5PX3q8", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T05:19:43Z", "updated_at": "2022-11-30T05:19:43Z", "author_association": "OWNER", "body": "This is the test: https://github.com/simonw/datasette/blob/8404b21556d133c89eda4bd1bf5335ed9a0785d6/tests/test_api_write.py#L342-L401\r\n\r\nI'm suspicious that there's a timing error of some sort but I can't think what it might be.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1469062686, "label": "Intermittent `test_delete_row` test failure "}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1916#issuecomment-1331651721", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1916", "id": 1331651721, "node_id": "IC_kwDOBm6k_c5PX2SJ", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T05:10:27Z", "updated_at": "2022-11-30T05:10:27Z", "author_association": "OWNER", "body": "They should return 405 method not allowed with an `{\"ok\":false, \"error\": \"Method not allowed\"}` body.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1469015001, "label": "GET requests against POST endpoints should not 500 error"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1917#issuecomment-1331644751", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1917", "id": 1331644751, "node_id": "IC_kwDOBm6k_c5PX0lP", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T04:59:22Z", "updated_at": "2022-11-30T04:59:22Z", "author_association": "OWNER", "body": "Yeah it looks like I introduced this bug here:\r\n\r\nhttps://github.com/simonw/datasette/commit/fb7e70d5e72a951efe4b29ad999d8915c032d021", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1469043836, "label": "Don't allow writable API to edit the `_memory` database"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1917#issuecomment-1331644078", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1917", "id": 1331644078, "node_id": "IC_kwDOBm6k_c5PX0au", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T04:58:06Z", "updated_at": "2022-11-30T04:58:06Z", "author_association": "OWNER", "body": "The problem might actually be here:\r\n\r\nhttps://github.com/simonw/datasette/blob/9f5321ff1eca58c469a45cc406d7eb5ad05accbd/datasette/app.py#L280-L281\r\n\r\n`is_mutable` defaults to `True`, so this line should probably be:\r\n\r\n```python\r\n self.add_database(Database(self, is_mutable=False, is_memory=True), name=\"_memory\")\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1469043836, "label": "Don't allow writable API to edit the `_memory` database"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1915#issuecomment-1331479606", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1915", "id": 1331479606, "node_id": "IC_kwDOBm6k_c5PXMQ2", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T00:09:06Z", "updated_at": "2022-11-30T00:09:06Z", "author_association": "OWNER", "body": "One last feature: I want to show an indication on the table page that the table has X seconds left to live.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1468709531, "label": "Interactive demo of Datasette 1.0 write APIs"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1915#issuecomment-1331479328", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1915", "id": 1331479328, "node_id": "IC_kwDOBm6k_c5PXMMg", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T00:08:41Z", "updated_at": "2022-11-30T00:08:41Z", "author_association": "OWNER", "body": "Five minute has now passed and https://latest.datasette.io/ephemeral/new_table is gone.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1468709531, "label": "Interactive demo of Datasette 1.0 write APIs"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1915#issuecomment-1331476246", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1915", "id": 1331476246, "node_id": "IC_kwDOBm6k_c5PXLcW", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T00:04:35Z", "updated_at": "2022-11-30T00:08:24Z", "author_association": "OWNER", "body": "The new https://github.com/simonw/datasette-ephemeral-tables plugin is live now: https://latest.datasette.io/ephemeral - you have to navigate through https://latest.datasette.io/login-as-root first\r\n\r\nIt work! I created a table using https://latest.datasette.io/-/api#path=%2Fephemeral%2F-%2Fcreate&json=%7B%0A++%22table%22%3A+%22new_table%22%2C%0A++%22columns%22%3A+%5B%0A++++%7B%0A++++++%22name%22%3A+%22id%22%2C%0A++++++%22type%22%3A+%22integer%22%0A++++%7D%2C%0A++++%7B%0A++++++%22name%22%3A+%22name%22%2C%0A++++++%22type%22%3A+%22text%22%0A++++%7D%0A++%5D%2C%0A++%22pk%22%3A+%22id%22%0A%7D&method=POST\r\n\r\nThe table should vanish in a few minutes.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1468709531, "label": "Interactive demo of Datasette 1.0 write APIs"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1915#issuecomment-1331478611", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1915", "id": 1331478611, "node_id": "IC_kwDOBm6k_c5PXMBT", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-30T00:07:37Z", "updated_at": "2022-11-30T00:07:37Z", "author_association": "OWNER", "body": "Then I created an API token at https://latest.datasette.io/-/create-token and ran this:\r\n\r\n```\r\ncurl -XPOST 'https://latest.datasette.io/ephemeral/new_table/-/insert' \\\r\n -H 'Authorization: Bearer xxx' \\\r\n -H 'Content-Type: application/json' \\\r\n -d '{\"row\": {\"name\": \"NAME\"}}'\r\n```\r\nAnd it inserted a row into https://latest.datasette.io/ephemeral/new_table", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1468709531, "label": "Interactive demo of Datasette 1.0 write APIs"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1915#issuecomment-1331432223", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1915", "id": 1331432223, "node_id": "IC_kwDOBm6k_c5PXAsf", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-29T23:06:17Z", "updated_at": "2022-11-29T23:06:17Z", "author_association": "OWNER", "body": "To (slightly) discourage abuse I'm going to make the demo database only visible to the root user - so people can't create tables with rude names and have them show to the public on https://latest.datasette.io/", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1468709531, "label": "Interactive demo of Datasette 1.0 write APIs"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1915#issuecomment-1331331082", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1915", "id": 1331331082, "node_id": "IC_kwDOBm6k_c5PWoAK", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-29T21:24:59Z", "updated_at": "2022-11-29T21:34:53Z", "author_association": "OWNER", "body": "Maybe a plugin called `datasette-temporary-tables` or `datasette-demo-tables` or `datasette-demo-database`.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1468709531, "label": "Interactive demo of Datasette 1.0 write APIs"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1850#issuecomment-1331238841", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1850", "id": 1331238841, "node_id": "IC_kwDOBm6k_c5PWRe5", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-29T20:11:20Z", "updated_at": "2022-11-29T20:11:20Z", "author_association": "OWNER", "body": "Released this in Datasette 1.0a0:\r\n- #1913", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1421529723, "label": "Write API in Datasette core"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1913#issuecomment-1331238029", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1913", "id": 1331238029, "node_id": "IC_kwDOBm6k_c5PWRSN", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-29T20:10:35Z", "updated_at": "2022-11-29T20:10:35Z", "author_association": "OWNER", "body": "Released:\r\n\r\n- https://pypi.org/project/datasette/1.0a0/\r\n- https://docs.datasette.io/en/latest/changelog.html#a0-2022-11-29", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1468603401, "label": "Release Datasette 1.0a0"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1913#issuecomment-1331226346", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1913", "id": 1331226346, "node_id": "IC_kwDOBm6k_c5PWObq", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-29T20:00:16Z", "updated_at": "2022-11-29T20:00:36Z", "author_association": "OWNER", "body": "Looks like a fix is coming: https://github.com/pypa/twine/issues/940#issuecomment-1331225509\r\n\r\n> > Note that `must_decode` was defined in `pkg_info/_compat.py`, and was thus never an API: before 1.9.0, it was only imported and used in `pkginfo/distribution.py'.\r\n> \r\n> Nevertheless, I will push out a 1.9.1 release of `pkginfo` which restores a deprecated compatibility alias.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1468603401, "label": "Release Datasette 1.0a0"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1913#issuecomment-1331225277", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1913", "id": 1331225277, "node_id": "IC_kwDOBm6k_c5PWOK9", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-29T19:59:14Z", "updated_at": "2022-11-29T19:59:34Z", "author_association": "OWNER", "body": "I deleted the tag and tried creating a new release. Now running here: https://github.com/simonw/datasette/actions/runs/3577554546", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1468603401, "label": "Release Datasette 1.0a0"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1913#issuecomment-1331216652", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1913", "id": 1331216652, "node_id": "IC_kwDOBm6k_c5PWMEM", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-11-29T19:54:22Z", "updated_at": "2022-11-29T19:54:22Z", "author_association": "OWNER", "body": "Filed a bug report here: https://bugs.launchpad.net/pkginfo/+bug/1998249", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1468603401, "label": "Release Datasette 1.0a0"}, "performed_via_github_app": null}