{"html_url": "https://github.com/simonw/sqlite-utils/issues/196#issuecomment-722070569", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/196", "id": 722070569, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMjA3MDU2OQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-05T01:38:40Z", "updated_at": "2020-11-05T01:38:40Z", "author_association": "OWNER", "body": "I'm going to try `re.VERBOSE` to see if I can make this readable with comments. https://docs.python.org/3/howto/regex.html\r\n```python\r\ncharref = re.compile(r\"\"\"\r\n &[#] # Start of a numeric entity reference\r\n (\r\n 0[0-7]+ # Octal form\r\n | [0-9]+ # Decimal form\r\n | x[0-9a-fA-F]+ # Hexadecimal form\r\n )\r\n ; # Trailing semicolon\r\n\"\"\", re.VERBOSE)\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 736520310, "label": "Introspect if table is FTS4 or FTS5"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/196#issuecomment-722064258", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/196", "id": 722064258, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMjA2NDI1OA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-05T01:18:07Z", "updated_at": "2020-11-05T01:21:31Z", "author_association": "OWNER", "body": "```\r\nIn [8]: r = re.compile(r\"\"\"'[^']*(?:''[^']*)*'\"\"\")\r\nIn [9]: r.match(\"'fo'o'\")\r\nOut[9]: \r\nIn [10]: r.match(\"'fo''o'\")\r\nOut[10]: \r\n```\r\n\r\n`'[^']*(?:''[^']*)*'`\r\n\r\nThis matches a single quote, then 0+ not-single-quotes, then 0+ (either 0+ not-single quotes or a double single quote), then a single quote.\r\n\r\nUnrolling the loop technique described here: http://www.softec.lu/site/RegularExpressions/UnrollingTheLoop", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 736520310, "label": "Introspect if table is FTS4 or FTS5"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/196#issuecomment-722062449", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/196", "id": 722062449, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMjA2MjQ0OQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-05T01:12:14Z", "updated_at": "2020-11-05T01:12:14Z", "author_association": "OWNER", "body": "Good news: I don't think I have to deal with `foo.tablename`, because that doesn't get reflected in the `sqlite_master` table:\r\n```\r\nsqlite> attach 'foo.db' as foo;\r\nsqlite> create table foo.`bant` (id int);\r\nsqlite> select * from foo.sqlite_master;\r\ntable|bant|bant|2|CREATE TABLE `bant` (id int)\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 736520310, "label": "Introspect if table is FTS4 or FTS5"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/196#issuecomment-722062082", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/196", "id": 722062082, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMjA2MjA4Mg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-05T01:10:51Z", "updated_at": "2020-11-05T01:10:51Z", "author_association": "OWNER", "body": "I confirmed all three of these are valid syntax for creating tables:\r\n```\r\n~ % sqlite3 tmp.db\r\nSQLite version 3.28.0 2019-04-15 14:49:49\r\nEnter \".help\" for usage hints.\r\nsqlite> create table 'foo''and' (id int);\r\nsqlite> create table \"bar\"\"and\" (id int);\r\nsqlite> create table [baz] (id int);\r\nsqlite> create table `bant` (id int);\r\nsqlite> .schema\r\nCREATE TABLE IF NOT EXISTS 'foo''and' (id int);\r\nCREATE TABLE IF NOT EXISTS \"bar\"\"and\" (id int);\r\nCREATE TABLE [baz] (id int);\r\nCREATE TABLE `bant` (id int);\r\nsqlite> select * from sqlite_master;\r\ntable|foo'and|foo'and|2|CREATE TABLE 'foo''and' (id int)\r\ntable|bar\"and|bar\"and|3|CREATE TABLE \"bar\"\"and\" (id int)\r\ntable|baz|baz|4|CREATE TABLE [baz] (id int)\r\ntable|bant|bant|5|CREATE TABLE `bant` (id int)\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 736520310, "label": "Introspect if table is FTS4 or FTS5"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/196#issuecomment-722058598", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/196", "id": 722058598, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMjA1ODU5OA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-05T00:59:58Z", "updated_at": "2020-11-05T00:59:58Z", "author_association": "OWNER", "body": "That two-in-a-row thing works for `\"` too:\r\n\r\nhttps://latest.datasette.io/fixtures?sql=select+%22foo%22%2C+%27bar%27%2C+%22foo%22%22and%22%2C+%27bar%27%27and%27\r\n\r\n\"fixtures__select__foo____bar____foo__and____bar__and_\"\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 736520310, "label": "Introspect if table is FTS4 or FTS5"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/196#issuecomment-722057923", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/196", "id": 722057923, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMjA1NzkyMw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-05T00:57:22Z", "updated_at": "2020-11-05T00:57:22Z", "author_association": "OWNER", "body": "Then https://sqlite.org/lang_expr.html#literal_values_constants_ says:\r\n\r\n> A string constant is formed by enclosing the string in single quotes ('). A single quote within the string can be encoded by putting two single quotes in a row - as in Pascal. C-style escapes using the backslash character are not supported because they are not standard SQL. ", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 736520310, "label": "Introspect if table is FTS4 or FTS5"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/196#issuecomment-722057392", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/196", "id": 722057392, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMjA1NzM5Mg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-05T00:55:31Z", "updated_at": "2020-11-05T00:55:51Z", "author_association": "OWNER", "body": "https://sqlite.org/lang_keywords.html says:\r\n\r\n> There are four ways of quoting keywords in SQLite:\r\n>\r\n> **'keyword'** A keyword in single quotes is a string literal.\r\n> **\"keyword\"** A keyword in double-quotes is an identifier.\r\n> **[keyword]** A keyword enclosed in square brackets is an identifier. This is not standard SQL. This quoting mechanism is used by MS Access and SQL Server and is included in SQLite for compatibility.\r\n> **\\`keyword\\`** A keyword enclosed in grave accents (ASCII code 96) is an identifier. This is not standard SQL. This quoting mechanism is used by MySQL and is included in SQLite for compatibility.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 736520310, "label": "Introspect if table is FTS4 or FTS5"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/196#issuecomment-722056576", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/196", "id": 722056576, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMjA1NjU3Ng==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-05T00:52:42Z", "updated_at": "2020-11-05T00:52:42Z", "author_association": "OWNER", "body": "I could use a parsing library like https://parsy.readthedocs.io/en/latest/tutorial.html for this - or `pyparsing` which has a SQLite example here: https://github.com/pyparsing/pyparsing/blob/master/examples/select_parser.py\r\n\r\nI'd rather not add a new dependency for this though so I'm going to see if I can get something that's good-enough just using a regular expression.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 736520310, "label": "Introspect if table is FTS4 or FTS5"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/196#issuecomment-722055291", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/196", "id": 722055291, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMjA1NTI5MQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-05T00:48:10Z", "updated_at": "2020-11-05T00:48:10Z", "author_association": "OWNER", "body": "This is blocking landing `.search()` in #195", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 736520310, "label": "Introspect if table is FTS4 or FTS5"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/196#issuecomment-722055104", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/196", "id": 722055104, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMjA1NTEwNA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-05T00:47:34Z", "updated_at": "2020-11-05T00:47:34Z", "author_association": "OWNER", "body": "This is surprisingly difficult. I need to parse the `CREATE VIRTUAL TABLE` statement, which will look something like this:\r\n\r\n```sql\r\nCREATE VIRTUAL TABLE \"global-power-plants_fts\" USING FTS5 (\"name\", content=\"global-power-plants\")\r\n```\r\n\r\nThe problem is I need to be able to handle various different quoting formats for the table name (`mytable` v.s. `\"mytable\"` v.s. `[mytable]`) plus I need to look out for `CREATE TABLE IF NOT EXISTS`.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 736520310, "label": "Introspect if table is FTS4 or FTS5"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/192#issuecomment-722054264", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/192", "id": 722054264, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMjA1NDI2NA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-05T00:44:39Z", "updated_at": "2020-11-05T00:44:39Z", "author_association": "OWNER", "body": "I want `.search()` to work against both FTS5 and FTS4 tables - but sort by rank should only work for FTS5.\r\n\r\nThis means I need to be able to introspect and tell if a table is FTS4 or FTS5.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 735532751, "label": "sqlite-utils search command"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1082#issuecomment-721931504", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1082", "id": 721931504, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMTkzMTUwNA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-04T19:32:47Z", "updated_at": "2020-11-04T19:35:44Z", "author_association": "OWNER", "body": "I wonder if setting a soft memory limit within Datasette would help here: https://www.sqlite.org/malloc.html#_setting_memory_usage_limits \r\n\r\n> If attempts are made to allocate more memory than specified by the soft heap limit, then SQLite will first attempt to free cache memory before continuing with the allocation request.\r\n\r\nhttps://www.sqlite.org/pragma.html#pragma_soft_heap_limit\r\n\r\n> **PRAGMA soft_heap_limit**\r\n> **PRAGMA soft_heap_limit=N**\r\n> \r\n> This pragma invokes the [sqlite3_soft_heap_limit64()](https://www.sqlite.org/c3ref/hard_heap_limit64.html) interface with the argument N, if N is specified and is a non-negative integer. The soft_heap_limit pragma always returns the same integer that would be returned by the [sqlite3_soft_heap_limit64](https://www.sqlite.org/c3ref/hard_heap_limit64.html)(-1) C-language function.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 735852274, "label": "DigitalOcean buildpack memory errors for large sqlite db?"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1083#issuecomment-721927254", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1083", "id": 721927254, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMTkyNzI1NA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-04T19:24:34Z", "updated_at": "2020-11-04T19:24:34Z", "author_association": "OWNER", "body": "Related: #856 - if it's possible to paginate correctly configured canned query then the CSV option to \"stream all rows\" could work for queries as well as tables.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 736365306, "label": "Advanced CSV export for arbitrary queries"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1083#issuecomment-721926827", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1083", "id": 721926827, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMTkyNjgyNw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-04T19:23:42Z", "updated_at": "2020-11-04T19:23:42Z", "author_association": "OWNER", "body": "https://latest.datasette.io/fixtures/sortable#export has advanced export options, but https://latest.datasette.io/fixtures?sql=select+pk1%2C+pk2%2C+content%2C+sortable%2C+sortable_with_nulls%2C+sortable_with_nulls_2%2C+text+from+sortable+order+by+pk1%2C+pk2+limit+101 does not.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 736365306, "label": "Advanced CSV export for arbitrary queries"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/268#issuecomment-721896822", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/268", "id": 721896822, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMTg5NjgyMg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-04T18:23:29Z", "updated_at": "2020-11-04T18:23:29Z", "author_association": "OWNER", "body": "Worth noting that joining to get the rank works for FTS5 but not for FTS4 - see comment here: https://github.com/simonw/sqlite-utils/issues/192#issuecomment-721420539\r\n\r\nEasiest solution would be to only support sort-by-rank for FTS5 tables. Alternative would be to depend on https://github.com/simonw/sqlite-fts4", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 323718842, "label": "Mechanism for ranking results from SQLite full-text search"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1082#issuecomment-721547177", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1082", "id": 721547177, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMTU0NzE3Nw==", "user": {"value": 39538958, "label": "justmars"}, "created_at": "2020-11-04T06:52:30Z", "updated_at": "2020-11-04T06:53:16Z", "author_association": "NONE", "body": "I think I tried the same db size on the following scenarios in Digital Ocean:\r\n1. Basic ($5/month) with 512MB RAM\r\n2. Basic ($10/month) with 1GB RAM\r\n3. Pro ($12/month) with 1GB RAM\r\n\r\nAll such attempts conked out with \"out of memory\" errors", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 735852274, "label": "DigitalOcean buildpack memory errors for large sqlite db?"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1082#issuecomment-721545090", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1082", "id": 721545090, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMTU0NTA5MA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-04T06:47:15Z", "updated_at": "2020-11-04T06:47:15Z", "author_association": "OWNER", "body": "I've run into a similar problem with Google Cloud Run: beyond a certain size of database file I find myself needing to run instances there with more RAM assigned to them.\r\n\r\nI haven't yet figured out a method to estimate the amount of RAM that will be needed to successfully serve a database file of a specific size- I've been using trial and error.\r\n\r\n5GB is quite a big database file, so it doesn't surprise me that it may need a bigger instance. I recommend trying it on a 1GB or 2GB of RAM Digital Ocean instance (their default is 512MB) and see if that works.\r\n\r\nLet me know what you find out!", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 735852274, "label": "DigitalOcean buildpack memory errors for large sqlite db?"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/192#issuecomment-721453779", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/192", "id": 721453779, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMTQ1Mzc3OQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-04T00:59:24Z", "updated_at": "2020-11-04T00:59:36Z", "author_association": "OWNER", "body": "FTS5 was added in SQLite 3.9.0 in 2015-10-14 - so about a year after CTEs, which means CTEs will always be safe to use with FTS5 queries.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 735532751, "label": "sqlite-utils search command"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/192#issuecomment-721420907", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/192", "id": 721420907, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMTQyMDkwNw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-03T23:07:01Z", "updated_at": "2020-11-03T23:07:01Z", "author_association": "OWNER", "body": "I could depend on my `sqlite-fts4` library to solve this: https://github.com/simonw/sqlite-fts4\r\n\r\nOr I could say that only `FTS5` is supported for ranked searches - but still support `.search()` against FTS4 just without the option to sort by relevance.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 735532751, "label": "sqlite-utils search command"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/192#issuecomment-721420539", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/192", "id": 721420539, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMTQyMDUzOQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-03T23:05:53Z", "updated_at": "2020-11-03T23:05:53Z", "author_association": "OWNER", "body": "Just realized there's a problem with the SQL I am using here: joining to get `rank` in this way only works against FTS5 tables, it doesn't work against FTS4.\r\n\r\n https://github.com/simonw/sqlite-utils/blob/c8a900df59efd34f394c863c0adff9912f1bf1d7/sqlite_utils/db.py#L1303-L1319", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 735532751, "label": "sqlite-utils search command"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/pull/195#issuecomment-721397665", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/195", "id": 721397665, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMTM5NzY2NQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-03T22:02:57Z", "updated_at": "2020-11-03T22:02:57Z", "author_association": "OWNER", "body": "Documentation so far: https://github.com/simonw/sqlite-utils/blob/6cadc6103ff1ba58c6409ce7fba74259e72965d9/docs/cli.rst#executing-searches", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 735663855, "label": "table.search() improvements plus sqlite-utils search command"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/sqlite-utils/issues/192#issuecomment-721319602", "issue_url": "https://api.github.com/repos/simonw/sqlite-utils/issues/192", "id": 721319602, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMTMxOTYwMg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-03T19:05:05Z", "updated_at": "2020-11-03T19:05:05Z", "author_association": "OWNER", "body": "Relevant example using a SQLite CTE: https://github.com/simonw/datasette/issues/268#issuecomment-675725464\r\n\r\nCTEs were added in SQLite 3.8.3 in 2014-02-03 so they should be safe to use. If someone tries to run `sqlite-utils search` no an older version of SQLite they'll get an error, which I think is OK.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 735532751, "label": "sqlite-utils search command"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/596#issuecomment-720741903", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/596", "id": 720741903, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMDc0MTkwMw==", "user": {"value": 132978, "label": "terrycojones"}, "created_at": "2020-11-02T21:44:45Z", "updated_at": "2020-11-02T21:44:45Z", "author_association": "NONE", "body": "Hi & thanks for the note @simonw! I wish I had more time to play with (and contribute to) datasette. I know you don't need me to tell you that it's super cool :-)", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 507454958, "label": "Handle really wide tables better"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/830#issuecomment-720700065", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/830", "id": 720700065, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMDcwMDA2NQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-02T20:15:36Z", "updated_at": "2020-11-02T20:15:36Z", "author_association": "OWNER", "body": "#427 had a bunch of ambitious plans for faceting that I haven't realized yet:\r\n\r\n> Think of all of the potential kinds of facets:\r\n> \r\n> * `?_facet_array=tags` where tags is a JSON array of values\r\n> * `_facet_date=datetimecol` - faceted by date part of a datetime\r\n> * `_facet_bins=numeric_column` - can I do some kind of fancy binning here? Might need to take an argument\r\n> * `?_facet_bins=numeric_column:5` - could be a way to take an argument. We\u2019ll ignore columns with a : in their name.\r\n> * `?_facet_json=jsoncol:jsonpath` - could use a JSON path to extract out something to facet on?\r\n> * `?_facet_percentile=numericcolumn` - could this work?\r\n> * `?_facet_function=column:sqlfunctionname` - maybe this could be interesting? Would allow for e.g. facet by soundex\r\n> * `?_facet_prefix=column:prefix` - facet by terms but only if they start with a specific prefix\r\n> * `?_facet_substring=column:3,6` - facet by a substr(column, 3, 6)", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 636511683, "label": "Redesign register_facet_classes plugin hook"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1080#issuecomment-720696827", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1080", "id": 720696827, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMDY5NjgyNw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-02T20:08:49Z", "updated_at": "2020-11-02T20:13:56Z", "author_association": "OWNER", "body": "Implementing pagination for facets will be interesting. Would be easier if I had a nicer reusable internal pagination mechanism, which is also needed for #856 (pagination of canned queries).", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 734777631, "label": "\"View all\" option for facets, to provide a (paginated) list of ALL of the facet counts plus a link to view them"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1080#issuecomment-720699160", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1080", "id": 720699160, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMDY5OTE2MA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-02T20:13:42Z", "updated_at": "2020-11-02T20:13:42Z", "author_association": "OWNER", "body": "Also relevant to this issue: #830 - redesigning the facet plugin hook in preparation for Datasette 1.0. And #972 supporting faceting against arbitrary queries.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 734777631, "label": "\"View all\" option for facets, to provide a (paginated) list of ALL of the facet counts plus a link to view them"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1080#issuecomment-720698577", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1080", "id": 720698577, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMDY5ODU3Nw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-02T20:12:26Z", "updated_at": "2020-11-02T20:12:26Z", "author_association": "OWNER", "body": "For regular column faceting, here's the query that is used:\r\n\r\nhttps://github.com/simonw/datasette/blob/13d1228d80c91d382a05b1a9549ed02c300ef851/datasette/facets.py#L196-L204\r\n\r\nSince it uses `order by count desc, value` maybe those values could be used to implement cursor-based pagination.\r\n\r\nThat wouldn't be robust in the face of changing data, but I'm not sure it's possible to implement paginated faceting in a way that survives ongoing changes to the underlying data.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 734777631, "label": "\"View all\" option for facets, to provide a (paginated) list of ALL of the facet counts plus a link to view them"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1080#issuecomment-720697226", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1080", "id": 720697226, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMDY5NzIyNg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-02T20:09:38Z", "updated_at": "2020-11-02T20:09:38Z", "author_association": "OWNER", "body": "Maybe this ends up being code that defers to a simulated canned query, rendered using the existing `query.html` template.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 734777631, "label": "\"View all\" option for facets, to provide a (paginated) list of ALL of the facet counts plus a link to view them"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1080#issuecomment-720695174", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1080", "id": 720695174, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMDY5NTE3NA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-02T20:05:26Z", "updated_at": "2020-11-02T20:05:26Z", "author_association": "OWNER", "body": "URL design:\r\n\r\n`/database/table/-/facet/colname`\r\n\r\nAnd for other types of facet (to be supported later):\r\n\r\n`/database/table/-/facet/colname?_type=m2m`", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 734777631, "label": "\"View all\" option for facets, to provide a (paginated) list of ALL of the facet counts plus a link to view them"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/596#issuecomment-720689653", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/596", "id": 720689653, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMDY4OTY1Mw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-02T19:53:36Z", "updated_at": "2020-11-02T19:53:47Z", "author_association": "OWNER", "body": "In #998 I implemented a horizontal scrollbar for these tables, which is a big improvement - demo here: https://global-power-plants.datasettes.com/global-power-plants/global-power-plants", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 507454958, "label": "Handle really wide tables better"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1077#issuecomment-720654925", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1077", "id": 720654925, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMDY1NDkyNQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-02T18:43:25Z", "updated_at": "2020-11-02T18:43:25Z", "author_association": "OWNER", "body": "Demo: https://latest.datasette.io/fixtures?_bot=1", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 733829385, "label": "database_actions plugin hook"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1077#issuecomment-720637322", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1077", "id": 720637322, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMDYzNzMyMg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-02T18:09:17Z", "updated_at": "2020-11-02T18:09:17Z", "author_association": "OWNER", "body": "Here's the `table_actions` implementation: 2f7731e9e5ff9b324beb5039fbe2be55d704a184", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 733829385, "label": "database_actions plugin hook"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/838#issuecomment-720354227", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/838", "id": 720354227, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMDM1NDIyNw==", "user": {"value": 82988, "label": "psychemedia"}, "created_at": "2020-11-02T09:33:58Z", "updated_at": "2020-11-02T09:33:58Z", "author_association": "CONTRIBUTOR", "body": "Thanks; just a note that the `datasette.urls.static(path)` and `datasette.urls.static_plugins(plugin_name, path)` items both seem to be repeated and appear in the docs twice?", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 637395097, "label": "Incorrect URLs when served behind a proxy with base_url set"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1079#issuecomment-720110298", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1079", "id": 720110298, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMDExMDI5OA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-01T15:58:22Z", "updated_at": "2020-11-01T15:58:22Z", "author_association": "OWNER", "body": "Might try a drop shadow on that menu too.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 733999615, "label": "Handle long breadcrumbs better with new menu"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/782#issuecomment-720028476", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/782", "id": 720028476, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMDAyODQ3Ng==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-01T05:00:05Z", "updated_at": "2020-11-01T05:00:05Z", "author_association": "OWNER", "body": "This should be the key focus for Datasette 0.52.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 627794879, "label": "Redesign default .json format"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/949#issuecomment-720021029", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/949", "id": 720021029, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMDAyMTAyOQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-11-01T03:29:48Z", "updated_at": "2020-11-01T03:29:48Z", "author_association": "OWNER", "body": "I'm not going to do any more work on this - SQL isn't an auto-complete friendly enough language.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 684961449, "label": "Try out CodeMirror SQL hints"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1077#issuecomment-720003026", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1077", "id": 720003026, "node_id": "MDEyOklzc3VlQ29tbWVudDcyMDAwMzAyNg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-31T23:48:21Z", "updated_at": "2020-10-31T23:50:07Z", "author_association": "OWNER", "body": "Needed by https://github.com/simonw/datasette-backup/issues/6", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 733829385, "label": "database_actions plugin hook"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1047#issuecomment-719994676", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1047", "id": 719994676, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTk5NDY3Ng==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-31T22:11:25Z", "updated_at": "2020-10-31T22:11:25Z", "author_association": "OWNER", "body": "https://docs.datasette.io/en/latest/binary_data.html", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 728895233, "label": "A new section in the docs about how Datasette handles BLOB columns"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1027#issuecomment-719988113", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1027", "id": 719988113, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTk4ODExMw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-31T21:03:37Z", "updated_at": "2020-10-31T21:03:37Z", "author_association": "OWNER", "body": "On my Mac, I run:\r\n\r\n datasette . -p 8009 --config base_url:/datasette-prefix/\r\n\r\nThen I edited `/usr/local/etc/httpd/httpd.conf` and add this section:\r\n```\r\nLoadModule proxy_module lib/httpd/modules/mod_proxy.so\r\nLoadModule proxy_http_module lib/httpd/modules/mod_proxy_http.so\r\nProxyPass /datasette-prefix/ http://localhost:8009/datasette-prefix/\r\n```\r\nI ran Apache in the foreground like so:\r\n\r\n apachectl -X\r\n\r\nNow hitting http://localhost:8081/datasette-prefix/fixtures/compound_three_primary_keys worked!", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 722758132, "label": "Add documentation on serving Datasette behind a proxy using base_url"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1023#issuecomment-719986922", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1023", "id": 719986922, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTk4NjkyMg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-31T20:51:01Z", "updated_at": "2020-10-31T20:51:01Z", "author_association": "OWNER", "body": "This should all be working correctly now.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 722673818, "label": "Fix issues relating to base_url"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/838#issuecomment-719986904", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/838", "id": 719986904, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTk4NjkwNA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-31T20:50:41Z", "updated_at": "2020-10-31T20:50:41Z", "author_association": "OWNER", "body": "OK, this should be working now. You can use the `datasette.urls.static_plugins()` method to generate the correct URLs in the `extra_css_urls` plugin hook: https://docs.datasette.io/en/latest/internals.html#datasette-urls", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 637395097, "label": "Incorrect URLs when served behind a proxy with base_url set"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1041#issuecomment-719986800", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1041", "id": 719986800, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTk4NjgwMA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-31T20:49:28Z", "updated_at": "2020-10-31T20:49:28Z", "author_association": "OWNER", "body": "Implemented in a4ca26a2659d21779adf625183061d8879954c15", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 727627923, "label": "extra_js_urls and extra_css_urls should respect base_url setting"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1072#issuecomment-719986698", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1072", "id": 719986698, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTk4NjY5OA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-31T20:48:17Z", "updated_at": "2020-10-31T20:48:17Z", "author_association": "OWNER", "body": "Here's the `datasette-edit-templates` plugin WIP I had before removing the hook: https://github.com/simonw/datasette-edit-templates/tree/82855c2612b84bc09c48fca885f831633a0d1552", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 733499930, "label": "load_template hook doesn't work for include/extends"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1075#issuecomment-719983750", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1075", "id": 719983750, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTk4Mzc1MA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-31T20:22:29Z", "updated_at": "2020-10-31T20:22:29Z", "author_association": "OWNER", "body": "I bet this is because I'm mucking around with one of those `__` methods. I'll try just doing the non-underscore methods instead.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 733796942, "label": "PrefixedUrlString mechanism broke everything"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1075#issuecomment-719983565", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1075", "id": 719983565, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTk4MzU2NQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-31T20:21:03Z", "updated_at": "2020-10-31T20:21:03Z", "author_association": "OWNER", "body": "Here's the output of `dir(str)`:\r\n\r\n`['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']`", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 733796942, "label": "PrefixedUrlString mechanism broke everything"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1075#issuecomment-719983484", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1075", "id": 719983484, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTk4MzQ4NA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-31T20:20:28Z", "updated_at": "2020-10-31T20:20:28Z", "author_association": "OWNER", "body": "It looks like this is specific to the way `PrefixedUrlString` is built.\r\n```\r\n(Pdb) class Weird(str): pass\r\n(Pdb) isinstance(Weird('bob'), collections.abc.Awaitable)\r\nFalse\r\n```\r\nSo subclassing strings doesn't trigger this bug, but something about `PrefixedUrlString` causes the problem.\r\n\r\nHere's the current `PrefixedUrlString` implementation:\r\n\r\nhttps://github.com/simonw/datasette/blob/bf18b9ba175a7b25fb8b765847397dd6efb8bb7b/datasette/utils/__init__.py#L1015-L1035", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 733796942, "label": "PrefixedUrlString mechanism broke everything"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1075#issuecomment-719983240", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1075", "id": 719983240, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTk4MzI0MA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-31T20:18:49Z", "updated_at": "2020-10-31T20:18:49Z", "author_association": "OWNER", "body": "Here's the core problem:\r\n```\r\n(Pdb) isinstance('bob', collections.abc.Awaitable)\r\nFalse\r\n(Pdb) isinstance(PrefixedUrlString('bob'), collections.abc.Awaitable)\r\n*** TypeError: issubclass() arg 1 must be a class\r\n```\r\nFor some reason `isinstance()` does not like being handed an instance of PrefixedUrlString.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 733796942, "label": "PrefixedUrlString mechanism broke everything"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1075#issuecomment-719981173", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1075", "id": 719981173, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTk4MTE3Mw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-31T20:02:30Z", "updated_at": "2020-10-31T20:03:45Z", "author_association": "OWNER", "body": "I wonder how Jinja's `Markup()` class works? It uses https://pypi.org/project/MarkupSafe/\r\n\r\nIt's a subclass of `str`, defined here: https://github.com/pallets/markupsafe/blob/master/src/markupsafe/__init__.py", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 733796942, "label": "PrefixedUrlString mechanism broke everything"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1075#issuecomment-719980742", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1075", "id": 719980742, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTk4MDc0Mg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-31T19:58:57Z", "updated_at": "2020-10-31T19:58:57Z", "author_association": "OWNER", "body": "Sample traceback:\r\n```\r\n \r\n/opt/hostedtoolcache/Python/3.7.9/x64/lib/python3.7/site-packages/jinja2/asyncsupport.py:173: in auto_await\r\n if inspect.isawaitable(value):\r\n/opt/hostedtoolcache/Python/3.7.9/x64/lib/python3.7/inspect.py:226: in isawaitable\r\n isinstance(object, collections.abc.Awaitable))\r\n/opt/hostedtoolcache/Python/3.7.9/x64/lib/python3.7/abc.py:139: in __instancecheck__\r\n return _abc_instancecheck(cls, instance)\r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n\r\ncls = \r\nsubclass = .method of '/-/static/app.css'>\r\n\r\n def __subclasscheck__(cls, subclass):\r\n \"\"\"Override for issubclass(subclass, cls).\"\"\"\r\n> return _abc_subclasscheck(cls, subclass)\r\nE TypeError: issubclass() arg 1 must be a class\r\n```\r\nThis is within Jinja. It looks like Jinja really doesn't like methods that return non-string objects like `PrefixedUrlString`.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 733796942, "label": "PrefixedUrlString mechanism broke everything"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1074#issuecomment-719977864", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1074", "id": 719977864, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTk3Nzg2NA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-31T19:35:01Z", "updated_at": "2020-10-31T19:35:01Z", "author_association": "OWNER", "body": "These plugins were not designed to be actually hosted online, so they do some nasty things like linking to the made-up `plugin-example.com` domain. I should fix that.\r\n\r\n\"Datasette_Fixtures__fixtures\"\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 733768037, "label": "latest.datasette.io should include plugins from fixtures"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1067#issuecomment-719966176", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1067", "id": 719966176, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTk2NjE3Ng==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-31T17:51:31Z", "updated_at": "2020-10-31T17:51:31Z", "author_association": "OWNER", "body": "Demo:\r\n\r\n- https://latest.datasette.io/fixtures/facetable?_bot=1\r\n- https://latest.datasette.io/fixtures/simple_view?_bot=1", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 732905360, "label": "Table actions menu on view pages, not on query pages"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1074#issuecomment-719965426", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1074", "id": 719965426, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTk2NTQyNg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-31T17:45:00Z", "updated_at": "2020-10-31T17:45:00Z", "author_association": "OWNER", "body": "This is working. Go to https://latest.datasette.io/login-as-root and click the button, then visit this page to see extra content added by plugins: https://latest.datasette.io/fixtures/compound_three_primary_keys\r\n\r\n![latest-plugins](https://user-images.githubusercontent.com/9599/97786052-19e53d00-1b66-11eb-8268-b452e08965e3.gif)\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 733768037, "label": "latest.datasette.io should include plugins from fixtures"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1074#issuecomment-719963074", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1074", "id": 719963074, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTk2MzA3NA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-31T17:23:48Z", "updated_at": "2020-10-31T17:23:48Z", "author_association": "OWNER", "body": "Needs a way to login as root, seeing as several plugins only show extra content if the user is logged in as root.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 733768037, "label": "latest.datasette.io should include plugins from fixtures"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1067#issuecomment-719961701", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1067", "id": 719961701, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTk2MTcwMQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-31T17:11:59Z", "updated_at": "2020-10-31T17:11:59Z", "author_association": "OWNER", "body": "It bothers me that these aren't visible in any public demos. Maybe `latest.datasette.io` should include the `my_plugins.py` and `my_plugins2.py` plugins?", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 732905360, "label": "Table actions menu on view pages, not on query pages"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1026#issuecomment-719959754", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1026", "id": 719959754, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTk1OTc1NA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-31T16:56:35Z", "updated_at": "2020-10-31T16:56:35Z", "author_association": "OWNER", "body": "#1041 can also benefit from the string subclass that shows that `base_url` has been added.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 722738988, "label": "How should datasette.client interact with base_url"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1067#issuecomment-719959419", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1067", "id": 719959419, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTk1OTQxOQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-31T16:53:42Z", "updated_at": "2020-10-31T16:53:42Z", "author_association": "OWNER", "body": "For the 0.51 release I'm going to add tests that show this works on view pages. I won't implement it for query pages.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 732905360, "label": "Table actions menu on view pages, not on query pages"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1067#issuecomment-719956184", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1067", "id": 719956184, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTk1NjE4NA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-31T16:26:09Z", "updated_at": "2020-10-31T16:26:09Z", "author_association": "OWNER", "body": "Should the hook provide an indication that it's running on a different type of page? I think yes for queries. Not sure about views - they behave very much like tables, and the plugin can always introspect to see if something is a view if it needs to.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 732905360, "label": "Table actions menu on view pages, not on query pages"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1070#issuecomment-719955724", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1070", "id": 719955724, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTk1NTcyNA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-31T16:22:45Z", "updated_at": "2020-10-31T16:22:45Z", "author_association": "OWNER", "body": "I've removed this plugin hook in #1073.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 733390884, "label": "load_template() example in documentation showing loading from a database"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1072#issuecomment-719955491", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1072", "id": 719955491, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTk1NTQ5MQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-31T16:20:58Z", "updated_at": "2020-10-31T16:20:58Z", "author_association": "OWNER", "body": "Here's the proof of concept `FunctionLoader` that showed me that this wasn't going to work:\r\n```diff\r\ndiff --git a/datasette/app.py b/datasette/app.py\r\nindex 4b28e71..b076be7 100644\r\n--- a/datasette/app.py\r\n+++ b/datasette/app.py\r\n@@ -21,7 +21,7 @@ from pathlib import Path\r\n from markupsafe import Markup\r\n from itsdangerous import URLSafeSerializer\r\n import jinja2\r\n-from jinja2 import ChoiceLoader, Environment, FileSystemLoader, PrefixLoader\r\n+from jinja2 import ChoiceLoader, Environment, FileSystemLoader, FunctionLoader, PrefixLoader\r\n from jinja2.environment import Template\r\n from jinja2.exceptions import TemplateNotFound\r\n import uvicorn\r\n@@ -300,6 +300,7 @@ class Datasette:\r\n template_paths.append(default_templates)\r\n template_loader = ChoiceLoader(\r\n [\r\n+ FunctionLoader(self._load_template_from_plugins),\r\n FileSystemLoader(template_paths),\r\n # Support {% extends \"default:table.html\" %}:\r\n PrefixLoader(\r\n@@ -322,6 +323,17 @@ class Datasette:\r\n self._root_token = secrets.token_hex(32)\r\n self.client = DatasetteClient(self)\r\n \r\n+ def _load_template_from_plugins(self, template):\r\n+ # \"If auto reloading is enabled it\u2019s called to check if the template changed\"\r\n+ uptodatefunc = lambda: True\r\n+ source = pm.hook.load_template(\r\n+ template=template,\r\n+ datasette=self,\r\n+ )\r\n+ if source is None:\r\n+ return None\r\n+ return source, template, uptodatefunc\r\n+\r\n @property\r\n def urls(self):\r\n return Urls(self)\r\n@@ -719,35 +731,7 @@ class Datasette:\r\n else:\r\n if isinstance(templates, str):\r\n templates = [templates]\r\n-\r\n- # Give plugins first chance at loading the template\r\n- break_outer = False\r\n- plugin_template_source = None\r\n- plugin_template_name = None\r\n- template_name = None\r\n- for template_name in templates:\r\n- if break_outer:\r\n- break\r\n- plugin_template_source = pm.hook.load_template(\r\n- template=template_name,\r\n- request=request,\r\n- datasette=self,\r\n- )\r\n- plugin_template_source = await await_me_maybe(plugin_template_source)\r\n- if plugin_template_source:\r\n- break_outer = True\r\n- plugin_template_name = template_name\r\n- break\r\n- if plugin_template_source is not None:\r\n- template = self.jinja_env.from_string(plugin_template_source)\r\n- else:\r\n- template = self.jinja_env.select_template(templates)\r\n- for template_name in templates:\r\n- from_plugin = template_name == plugin_template_name\r\n- used = from_plugin or template_name == template.name\r\n- templates_considered.append(\r\n- {\"name\": template_name, \"used\": used, \"from_plugin\": from_plugin}\r\n- )\r\n+ template = self.jinja_env.select_template(templates)\r\n body_scripts = []\r\n # pylint: disable=no-member\r\n for extra_script in pm.hook.extra_body_script(\r\ndiff --git a/datasette/hookspecs.py b/datasette/hookspecs.py\r\nindex ca84b35..7804def 100644\r\n--- a/datasette/hookspecs.py\r\n+++ b/datasette/hookspecs.py\r\n@@ -50,7 +50,7 @@ def extra_template_vars(\r\n \r\n \r\n @hookspec(firstresult=True)\r\n-def load_template(template, request, datasette):\r\n+def load_template(template, datasette):\r\n \"Load the specified template, returning the template code as a string\"\r\n \r\n \r\ndiff --git a/docs/plugin_hooks.rst b/docs/plugin_hooks.rst\r\nindex 3c57b6a..8f2704e 100644\r\n--- a/docs/plugin_hooks.rst\r\n+++ b/docs/plugin_hooks.rst\r\n@@ -273,15 +273,12 @@ Example: `datasette-cluster-map >> def load_template(name):\r\n... if name == 'index.html':\r\n... return '...'\r\n...\r\n>>> loader = FunctionLoader(load_template)\r\n```\r\n\r\nJust one catch: I need to be able to load templates asynchronously, because they live in the database. Let's hope Jinja has a mechanism for that!", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 733499930, "label": "load_template hook doesn't work for include/extends"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1072#issuecomment-719785005", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1072", "id": 719785005, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTc4NTAwNQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-30T20:36:22Z", "updated_at": "2020-10-30T20:36:22Z", "author_association": "OWNER", "body": "It should be easy enough to show a comment that says which original template names were considered, but I may not be able to show which one was actually used (or which ones came from plugins).", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 733499930, "label": "load_template hook doesn't work for include/extends"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1072#issuecomment-719784606", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1072", "id": 719784606, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTc4NDYwNg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-30T20:35:33Z", "updated_at": "2020-10-30T20:35:33Z", "author_association": "OWNER", "body": "To fix this I think I need to move the `load_template` implementation into a Jinja template loader.\r\n\r\nI'm not sure I'll be able to keep the `Templates considered` comment working though:\r\n\r\nhttps://github.com/simonw/datasette/blob/a2a709072059c6b3da365df9a332ca744c2079e9/datasette/app.py#L745-L750", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 733499930, "label": "load_template hook doesn't work for include/extends"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1071#issuecomment-719777499", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1071", "id": 719777499, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTc3NzQ5OQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-30T20:20:01Z", "updated_at": "2020-10-30T20:20:01Z", "author_association": "OWNER", "body": "Fixed: https://latest.datasette.io/-/messages\r\n\r\n![demo-message](https://user-images.githubusercontent.com/9599/97753199-9c142980-1ab2-11eb-9be9-c0be41acc68e.gif)\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": 733485423, "label": "Messages should be displayed full width"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/1069#issuecomment-719657478", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1069", "id": 719657478, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTY1NzQ3OA==", "user": {"value": 22429695, "label": "codecov[bot]"}, "created_at": "2020-10-30T16:31:21Z", "updated_at": "2020-10-30T17:46:36Z", "author_association": "NONE", "body": "# [Codecov](https://codecov.io/gh/simonw/datasette/pull/1069?src=pr&el=h1) Report\n> Merging [#1069](https://codecov.io/gh/simonw/datasette/pull/1069?src=pr&el=desc) into [main](https://codecov.io/gh/simonw/datasette/commit/222f79bb4c6e2aa5426cc5ff25f1b2461e18a300?el=desc) will **increase** coverage by `0.01%`.\n> The diff coverage is `95.83%`.\n\n[![Impacted file tree graph](https://codecov.io/gh/simonw/datasette/pull/1069/graphs/tree.svg?width=650&height=150&src=pr&token=eSahVY7kw1)](https://codecov.io/gh/simonw/datasette/pull/1069?src=pr&el=tree)\n\n```diff\n@@ Coverage Diff @@\n## main #1069 +/- ##\n==========================================\n+ Coverage 91.30% 91.32% +0.01% \n==========================================\n Files 29 29 \n Lines 3736 3756 +20 \n==========================================\n+ Hits 3411 3430 +19 \n- Misses 325 326 +1 \n```\n\n\n| [Impacted Files](https://codecov.io/gh/simonw/datasette/pull/1069?src=pr&el=tree) | Coverage \u0394 | |\n|---|---|---|\n| [datasette/views/base.py](https://codecov.io/gh/simonw/datasette/pull/1069/diff?src=pr&el=tree#diff-ZGF0YXNldHRlL3ZpZXdzL2Jhc2UucHk=) | `93.94% <\u00f8> (-0.04%)` | :arrow_down: |\n| [datasette/app.py](https://codecov.io/gh/simonw/datasette/pull/1069/diff?src=pr&el=tree#diff-ZGF0YXNldHRlL2FwcC5weQ==) | `96.38% <95.45%> (-0.05%)` | :arrow_down: |\n| [datasette/hookspecs.py](https://codecov.io/gh/simonw/datasette/pull/1069/diff?src=pr&el=tree#diff-ZGF0YXNldHRlL2hvb2tzcGVjcy5weQ==) | `100.00% <100.00%> (\u00f8)` | |\n\n------\n\n[Continue to review full report at Codecov](https://codecov.io/gh/simonw/datasette/pull/1069?src=pr&el=continue).\n> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)\n> `\u0394 = absolute (impact)`, `\u00f8 = not affected`, `? = missing data`\n> Powered by [Codecov](https://codecov.io/gh/simonw/datasette/pull/1069?src=pr&el=footer). Last update [222f79b...92f3840](https://codecov.io/gh/simonw/datasette/pull/1069?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 733303548, "label": "load_template() plugin hook"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/1069#issuecomment-719672967", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1069", "id": 719672967, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTY3Mjk2Nw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-30T16:58:01Z", "updated_at": "2020-10-30T16:58:01Z", "author_association": "OWNER", "body": "OK, new hook specification is:\r\n```python\r\n@hookspec(firstresult=True)\r\ndef load_template(template, request, datasette):\r\n \"Load the specified template, returning the template code as a string\"\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 733303548, "label": "load_template() plugin hook"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/1069#issuecomment-719670714", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1069", "id": 719670714, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTY3MDcxNA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-30T16:53:56Z", "updated_at": "2020-10-30T16:53:56Z", "author_association": "OWNER", "body": "I'm having second thoughts about the design of the plugin hook.\r\n\r\nConsider the following:\r\n```python\r\n plugin_template_source = pm.hook.load_template(\r\n template=template_name,\r\n database=context.get(\"database\"),\r\n table=context.get(\"table\"),\r\n columns=context.get(\"columns\"),\r\n view_name=self.name,\r\n request=request,\r\n datasette=self.ds,\r\n )\r\n```\r\nIt's a bit gross that `database`, `table` and `columns` are pulled out of the context like that. This doesn't make sense for pages that are rendered by plugins, for example.\r\n\r\nSo maybe for the first release of this plugin hook I should cut it down to just seeing `template`, `request` and `datasette`. I can add the table/view/etc stuff back in later if it turns out to be necessary.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 733303548, "label": "load_template() plugin hook"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/1069#issuecomment-719666912", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1069", "id": 719666912, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTY2NjkxMg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-30T16:47:44Z", "updated_at": "2020-10-30T16:47:44Z", "author_association": "OWNER", "body": "Bringing over a comment from #1042:\r\n\r\n> I'd like to do this all in the `datasette.render_template()` method to ensure it's available to plugins as well, not just core code that uses the `BaseView` class.\r\n> \r\n> This code is the problem:\r\n> \r\n> https://github.com/simonw/datasette/blob/d3e9b0aecb6f8e9b2befd9c654ccb7ce852db3e7/datasette/views/base.py#L114-L133\r\n> \r\n> I think I'll fix this by moving the `select_templates` mechanism into `datasette.render_templates()`.\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": 733303548, "label": "load_template() plugin hook"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/1069#issuecomment-719664530", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1069", "id": 719664530, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTY2NDUzMA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-30T16:43:40Z", "updated_at": "2020-10-30T16:43:40Z", "author_association": "OWNER", "body": "I should include an example in the documentation that shows loading templates from a database table.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 733303548, "label": "load_template() plugin hook"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/1069#issuecomment-719640430", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1069", "id": 719640430, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTY0MDQzMA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-30T16:01:13Z", "updated_at": "2020-10-30T16:01:13Z", "author_association": "OWNER", "body": "Next steps: build a demonstration plugin against this.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 733303548, "label": "load_template() plugin hook"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1068#issuecomment-719630745", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1068", "id": 719630745, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTYzMDc0NQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-30T15:44:13Z", "updated_at": "2020-10-30T15:44:13Z", "author_association": "OWNER", "body": "Documentation: https://docs.datasette.io/en/latest/authentication.html#debug-menu", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 732939921, "label": "Default menu links should check a real permission "}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1068#issuecomment-719332460", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1068", "id": 719332460, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTMzMjQ2MA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-30T07:13:10Z", "updated_at": "2020-10-30T07:13:10Z", "author_association": "OWNER", "body": "I mainly want this so I can add that debug menu to my Dogsheep.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 732939921, "label": "Default menu links should check a real permission "}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1068#issuecomment-719331236", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1068", "id": 719331236, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTMzMTIzNg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-30T07:11:58Z", "updated_at": "2020-10-30T07:11:58Z", "author_association": "OWNER", "body": "Document the new permission here: https://docs.datasette.io/en/stable/authentication.html#built-in-permissions", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 732939921, "label": "Default menu links should check a real permission "}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1068#issuecomment-719329219", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1068", "id": 719329219, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTMyOTIxOQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-30T07:09:59Z", "updated_at": "2020-10-30T07:09:59Z", "author_association": "OWNER", "body": "Permission idea: `debug-menu`", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 732939921, "label": "Default menu links should check a real permission "}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1068#issuecomment-719328661", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1068", "id": 719328661, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTMyODY2MQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-30T07:09:30Z", "updated_at": "2020-10-30T07:09:30Z", "author_association": "OWNER", "body": "Then this can make it available to root: https://github.com/simonw/datasette/blob/18a64fbb29271ce607937110bbdb55488c43f4e0/datasette/default_permissions.py", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 732939921, "label": "Default menu links should check a real permission "}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1067#issuecomment-719322666", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1067", "id": 719322666, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTMyMjY2Ng==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-30T07:04:02Z", "updated_at": "2020-10-30T07:04:02Z", "author_association": "OWNER", "body": "Maybe rename it to `actions_menu` and have it work for database, view, table and query pages using different arguments on each.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 732905360, "label": "Table actions menu on view pages, not on query pages"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1067#issuecomment-719320948", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1067", "id": 719320948, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTMyMDk0OA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-30T07:02:37Z", "updated_at": "2020-10-30T07:02:37Z", "author_association": "OWNER", "body": "Yes, this should be possible - no point restricting what plugin authors can do with the feature. Will need to add some extra arguments to the plugin hook for this.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 732905360, "label": "Table actions menu on view pages, not on query pages"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/690#issuecomment-719195346", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/690", "id": 719195346, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTE5NTM0Ng==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-30T05:20:42Z", "updated_at": "2020-10-30T05:20:42Z", "author_association": "OWNER", "body": "I've now added two new plugin hooks: [menu_links()](https://docs.datasette.io/en/latest/plugin_hooks.html#menu-links-datasette-actor) and [table_actions()](https://docs.datasette.io/en/latest/plugin_hooks.html#table-actions-datasette-actor-database-table).\r\n\r\nI'm going to close this issue. Further work (on column actions and and database actions) can happen in separate tickets, but I won't include them in Datasette 0.51 since they're much less interesting than table and instance actions.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 573755726, "label": "Mechanism for plugins to add action menu items for various things"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1066#issuecomment-719194756", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1066", "id": 719194756, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTE5NDc1Ng==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-30T05:18:35Z", "updated_at": "2020-10-30T05:18:35Z", "author_association": "OWNER", "body": "Documentation: https://docs.datasette.io/en/latest/plugin_hooks.html#table-actions-datasette-actor-database-table", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 732859030, "label": "Table actions menu plus plugin hook"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1066#issuecomment-719194619", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1066", "id": 719194619, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTE5NDYxOQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-30T05:18:04Z", "updated_at": "2020-10-30T05:18:04Z", "author_association": "OWNER", "body": "The cog only appears if at least one table action has been registered by a plugin. It looks like this:\r\n\r\n![table-actions](https://user-images.githubusercontent.com/9599/97662535-9bd54900-1a34-11eb-8e0f-56d159c8834e.gif)\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 732859030, "label": "Table actions menu plus plugin hook"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1066#issuecomment-719154646", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1066", "id": 719154646, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTE1NDY0Ng==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-30T03:48:15Z", "updated_at": "2020-10-30T03:48:15Z", "author_association": "OWNER", "body": "This will use a very similar implementation to the navigation menu in #1064 - similar plugin hook and I'll use a `
` to implement it.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 732859030, "label": "Table actions menu plus plugin hook"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/1065#issuecomment-719153773", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1065", "id": 719153773, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTE1Mzc3Mw==", "user": {"value": 22429695, "label": "codecov[bot]"}, "created_at": "2020-10-30T03:44:57Z", "updated_at": "2020-10-30T03:44:57Z", "author_association": "NONE", "body": "# [Codecov](https://codecov.io/gh/simonw/datasette/pull/1065?src=pr&el=h1) Report\n> Merging [#1065](https://codecov.io/gh/simonw/datasette/pull/1065?src=pr&el=desc) into [main](https://codecov.io/gh/simonw/datasette/commit/1a861be19e326e0c88230a711a1b6536366697d7?el=desc) will **increase** coverage by `0.03%`.\n> The diff coverage is `100.00%`.\n\n[![Impacted file tree graph](https://codecov.io/gh/simonw/datasette/pull/1065/graphs/tree.svg?width=650&height=150&src=pr&token=eSahVY7kw1)](https://codecov.io/gh/simonw/datasette/pull/1065?src=pr&el=tree)\n\n```diff\n@@ Coverage Diff @@\n## main #1065 +/- ##\n==========================================\n+ Coverage 91.23% 91.27% +0.03% \n==========================================\n Files 28 29 +1 \n Lines 3710 3724 +14 \n==========================================\n+ Hits 3385 3399 +14 \n Misses 325 325 \n```\n\n\n| [Impacted Files](https://codecov.io/gh/simonw/datasette/pull/1065?src=pr&el=tree) | Coverage \u0394 | |\n|---|---|---|\n| [datasette/plugins.py](https://codecov.io/gh/simonw/datasette/pull/1065/diff?src=pr&el=tree#diff-ZGF0YXNldHRlL3BsdWdpbnMucHk=) | `82.35% <\u00f8> (\u00f8)` | |\n| [datasette/app.py](https://codecov.io/gh/simonw/datasette/pull/1065/diff?src=pr&el=tree#diff-ZGF0YXNldHRlL2FwcC5weQ==) | `96.42% <100.00%> (+0.03%)` | :arrow_up: |\n| [datasette/default\\_menu\\_links.py](https://codecov.io/gh/simonw/datasette/pull/1065/diff?src=pr&el=tree#diff-ZGF0YXNldHRlL2RlZmF1bHRfbWVudV9saW5rcy5weQ==) | `100.00% <100.00%> (\u00f8)` | |\n| [datasette/hookspecs.py](https://codecov.io/gh/simonw/datasette/pull/1065/diff?src=pr&el=tree#diff-ZGF0YXNldHRlL2hvb2tzcGVjcy5weQ==) | `100.00% <100.00%> (\u00f8)` | |\n\n------\n\n[Continue to review full report at Codecov](https://codecov.io/gh/simonw/datasette/pull/1065?src=pr&el=continue).\n> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)\n> `\u0394 = absolute (impact)`, `\u00f8 = not affected`, `? = missing data`\n> Powered by [Codecov](https://codecov.io/gh/simonw/datasette/pull/1065?src=pr&el=footer). Last update [1a861be...5f118b5](https://codecov.io/gh/simonw/datasette/pull/1065?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 732856937, "label": "Nav menu plus menu_links() hook"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1064#issuecomment-719117185", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1064", "id": 719117185, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTExNzE4NQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-30T01:35:17Z", "updated_at": "2020-10-30T01:35:17Z", "author_association": "OWNER", "body": "I'm going to go with a list of `{\"label\": ..., \"href\": ...}` as the first iteration of this. The logout link will not be returned as part of the plugin output. A default plugin will provide the debug tools if the user is logged in as root.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 732798913, "label": "Navigation menu plus plugin hook"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1064#issuecomment-719111597", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1064", "id": 719111597, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTExMTU5Nw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-30T01:15:05Z", "updated_at": "2020-10-30T01:15:05Z", "author_association": "OWNER", "body": "I'm torn on this one. I think I have a very slight preference for plugins returning structured objects as opposed to HTML. Less likely to regret that choice in the future?", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 732798913, "label": "Navigation menu plus plugin hook"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1064#issuecomment-719111373", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1064", "id": 719111373, "node_id": "MDEyOklzc3VlQ29tbWVudDcxOTExMTM3Mw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-10-30T01:14:13Z", "updated_at": "2020-10-30T01:14:13Z", "author_association": "OWNER", "body": "Plugins returning HTML makes more sense for some of the other areas that plugins will be able to inject content - e.g. injecting content on the table or row page above the table.\r\n\r\nIf I'm going to have that as a pattern though it may make sense to use HTML here, since that will be consistent with other places that plugins can inject additional content.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 732798913, "label": "Navigation menu plus plugin hook"}, "performed_via_github_app": null}