{"html_url": "https://github.com/simonw/datasette/issues/14#issuecomment-381446392", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/14", "id": 381446392, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTQ0NjM5Mg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-15T23:22:40Z", "updated_at": "2018-04-16T05:25:57Z", "author_association": "OWNER", "body": "OK, from that prototype in f2720b0c6b7172ebe8820 it looks like pluggy provides a solid path forward.\r\n\r\nNext steps:\r\n\r\n- [x] Build a demo plugin that uses setuptools entrypoints to register with the `datasette` plugin manager via pluggy\r\n- [x] Figure out a mechanism for registering plugins without first needing to publish them to PyPI. Can I load plugins from a special `plugins/` directory similar to the `--template-dir=templates/` option already supported by Datasette? #211", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 267707940, "label": "Datasette Plugins"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/14#issuecomment-381450394", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/14", "id": 381450394, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTQ1MDM5NA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T00:27:23Z", "updated_at": "2018-04-16T00:27:23Z", "author_association": "OWNER", "body": "I created https://github.com/simonw/datasette-plugin-demos which is now published to PyPI and can be installed with `pip install datasette-plugin-demos` - I've confirmed that if you DO install it my Datasette `plugins` branch picks up the plugins, and `select random_integer(1, 4)` works as it should.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 267707940, "label": "Datasette Plugins"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/14#issuecomment-381450591", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/14", "id": 381450591, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTQ1MDU5MQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T00:30:22Z", "updated_at": "2018-04-16T00:34:42Z", "author_association": "OWNER", "body": "Slight code design problem... when I tried installing my branch in a fresh virtual environment I got this error, because `setup.py` now depends on `pluggy` (from importing `__version__`):\r\n\r\n```\r\n File \"/private/var/folders/jj/fngnv0810tn2lt_kd3911pdc0000gp/T/pip-req-build-dftqdezt/setup.py\", line 2, in \r\n from datasette import __version__\r\n File \"/private/var/folders/jj/fngnv0810tn2lt_kd3911pdc0000gp/T/pip-req-build-dftqdezt/datasette/__init__.py\", line 2, in \r\n from .hookspecs import hookimpl # noqa\r\n File \"/private/var/folders/jj/fngnv0810tn2lt_kd3911pdc0000gp/T/pip-req-build-dftqdezt/datasette/hookspecs.py\", line 1, in \r\n from pluggy import HookimplMarker\r\n ModuleNotFoundError: No module named 'pluggy'\r\n```\r\n\r\nLooks like I've run into point 6 on https://packaging.python.org/guides/single-sourcing-package-version/ :\r\n\r\n![2018-04-15 at 5 34 pm](https://user-images.githubusercontent.com/9599/38785314-403ce86a-40d3-11e8-8542-ba426eddf4ac.png)\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 267707940, "label": "Datasette Plugins"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/139#issuecomment-381455054", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/139", "id": 381455054, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTQ1NTA1NA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T01:24:13Z", "updated_at": "2018-04-16T01:24:13Z", "author_association": "OWNER", "body": "I think Vega-Lite is the way to go here: https://vega.github.io/vega-lite/\r\n\r\nI've been playing around with it and Datasette with some really positive initial results: \r\n\r\nhttps://vega.github.io/editor/#/gist/vega-lite/simonw/89100ce80573d062d70f780d10e5e609/decada131575825875c0a076e418c661c2adb014/vice-shootings-gender-race-by-department.vl.json\r\n\r\nhttps://vega.github.io/editor/#/gist/vega-lite/simonw/5f69fbe29380b0d5d95f31a385f49ee4/7087b64df03cf9dba44a5258a606f29182cb8619/trees-san-francisco.vl.json", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 275493851, "label": "Build a visualization plugin for Vega"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/211#issuecomment-381456434", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/211", "id": 381456434, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTQ1NjQzNA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T01:36:16Z", "updated_at": "2018-04-16T01:37:44Z", "author_association": "OWNER", "body": "The easiest way to implement this in Python 2 would be `execfile(...)` - but that was removed in Python 3. According to https://stackoverflow.com/a/437857/6083 `2to3` replaces that with this, which ensures the filename is associated with the code for debugging purposes:\r\n\r\n```\r\nwith open(\"somefile.py\") as f:\r\n code = compile(f.read(), \"somefile.py\", 'exec')\r\n exec(code, global_vars, local_vars)\r\n```\r\n\r\nImplementing it this way would force this kind of plugin to be self-contained in a single file. I think that's OK: if you want a more complex plugin you can use the standard pluggy-powered setuptools mechanism to build it.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 314471743, "label": "Load plugins from a `--plugins-dir=plugins/` directory"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/211#issuecomment-381462005", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/211", "id": 381462005, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTQ2MjAwNQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T02:23:07Z", "updated_at": "2018-04-16T02:23:07Z", "author_association": "OWNER", "body": "This needs unit tests. I also need to manually test the `datasette package` and `datesette publish` commands.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 314471743, "label": "Load plugins from a `--plugins-dir=plugins/` directory"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/211#issuecomment-381478217", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/211", "id": 381478217, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTQ3ODIxNw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T04:41:38Z", "updated_at": "2018-04-16T04:41:38Z", "author_association": "OWNER", "body": "Here's the result of running:\r\n\r\n datasette publish now fivethirtyeight.db \\\r\n --plugins-dir=plugins/ --title=\"FiveThirtyEight\" --branch=plugins-dir\r\n\r\nhttps://datasette-phjtvzwwzl.now.sh/fivethirtyeight-2628db9?sql=select+convert_units%28100%2C+%27m%27%2C+%27ft%27%29\r\n\r\nWhere `plugins/pint_plugin.py` contains the following:\r\n```\r\nfrom datasette import hookimpl\r\nimport pint\r\n\r\nureg = pint.UnitRegistry()\r\n\r\n@hookimpl\r\ndef prepare_connection(conn):\r\n def convert_units(amount, from_, to_):\r\n \"select convert_units(100, 'm', 'ft');\"\r\n return (amount * ureg(from_)).to(to_).to_tuple()[0]\r\n conn.create_function('convert_units', 3, convert_units)\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 314471743, "label": "Load plugins from a `--plugins-dir=plugins/` directory"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/211#issuecomment-381478253", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/211", "id": 381478253, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTQ3ODI1Mw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T04:42:02Z", "updated_at": "2018-04-16T04:42:02Z", "author_association": "OWNER", "body": "This worked as well:\r\n\r\n datasette package fivethirtyeight.db \\\r\n --plugins-dir=plugins/ --title=\"FiveThirtyEight\" --branch=plugins-dir\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 314471743, "label": "Load plugins from a `--plugins-dir=plugins/` directory"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/211#issuecomment-381481990", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/211", "id": 381481990, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTQ4MTk5MA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T05:14:57Z", "updated_at": "2018-04-16T05:14:57Z", "author_association": "OWNER", "body": "Added unit tests in 33c6bcadb962457be6b0c7f369826b404e2bcef5", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 314471743, "label": "Load plugins from a `--plugins-dir=plugins/` directory"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/211#issuecomment-381482407", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/211", "id": 381482407, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTQ4MjQwNw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T05:18:29Z", "updated_at": "2018-04-16T05:18:29Z", "author_association": "OWNER", "body": "Here's the result of running this:\r\n\r\n datasette publish heroku fivethirtyeight.db \\\r\n --plugins-dir=plugins/ --title=\"FiveThirtyEight\" --branch=plugins-dir\r\n\r\nhttps://intense-river-24599.herokuapp.com/fivethirtyeight-2628db9?sql=select+convert_units%28100%2C+%27m%27%2C+%27ft%27%29", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 314471743, "label": "Load plugins from a `--plugins-dir=plugins/` directory"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/209#issuecomment-381483301", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/209", "id": 381483301, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTQ4MzMwMQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T05:25:08Z", "updated_at": "2018-04-16T05:25:08Z", "author_association": "OWNER", "body": "I think this is a good improvement. If you fix the tests I'll merge it.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 314455877, "label": " Don't duplicate simple primary keys in the link column"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/191#issuecomment-381488049", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/191", "id": 381488049, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTQ4ODA0OQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T05:58:15Z", "updated_at": "2018-04-16T05:58:15Z", "author_association": "OWNER", "body": "I think this is pretty hard. @coleifer has done some work in this direction, including https://github.com/coleifer/pysqlite3 which ports the standalone pysqlite module to Python 3. ", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 310533258, "label": "Figure out how to bundle a more up-to-date SQLite"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/214#issuecomment-381490361", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/214", "id": 381490361, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTQ5MDM2MQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T06:13:02Z", "updated_at": "2018-04-16T06:13:02Z", "author_association": "OWNER", "body": "Packaging JS and CSS in a pip installable wheel is fiddly but possible. http://peak.telecommunity.com/DevCenter/PythonEggs#accessing-package-resources\r\n\r\n from pkg_resources import resource_string\r\n foo_config = resource_string(__name__, 'foo.conf')", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 314506446, "label": "Ability for plugins to define extra JavaScript and CSS"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/214#issuecomment-381491707", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/214", "id": 381491707, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTQ5MTcwNw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T06:21:23Z", "updated_at": "2018-04-16T06:21:23Z", "author_association": "OWNER", "body": "This looks like a good example: https://github.com/funkey/nyroglancer/commit/d4438ab42171360b2b8e9020f672846dd70c8d80", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 314506446, "label": "Ability for plugins to define extra JavaScript and CSS"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/191#issuecomment-381602005", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/191", "id": 381602005, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTYwMjAwNQ==", "user": {"value": 119974, "label": "coleifer"}, "created_at": "2018-04-16T13:37:32Z", "updated_at": "2018-04-16T13:37:32Z", "author_association": "NONE", "body": "I don't think it should be too difficult... you can look at what @ghaering did with pysqlite (and similarly what I copied for pysqlite3). You would theoretically take an amalgamation build of Sqlite (all code in a single .c and .h file). The `AmalgamationLibSqliteBuilder` class detects the presence of this amalgamated source file and builds a statically-linked pysqlite.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 310533258, "label": "Figure out how to bundle a more up-to-date SQLite"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/14#issuecomment-381611738", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/14", "id": 381611738, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTYxMTczOA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T14:07:30Z", "updated_at": "2018-04-16T14:07:30Z", "author_association": "OWNER", "body": "I should check if it's possible to have two template registration function plugins in a single plugin module. If it isn't maybe I should use class plugins instead of module plugins.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 267707940, "label": "Datasette Plugins"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/214#issuecomment-381612585", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/214", "id": 381612585, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTYxMjU4NQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T14:10:16Z", "updated_at": "2018-04-16T14:10:16Z", "author_association": "OWNER", "body": "`resource_stream` returns a file-like object which may be better for serving from Sanic.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 314506446, "label": "Ability for plugins to define extra JavaScript and CSS"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/14#issuecomment-381621338", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/14", "id": 381621338, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTYyMTMzOA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T14:36:27Z", "updated_at": "2018-04-16T14:36:27Z", "author_association": "OWNER", "body": "Annoyingly, the following only results in the last of the two `prepare_connection` hooks being registered:\r\n\r\n```\r\nfrom datasette import hookimpl\r\nimport pint\r\nimport random\r\n\r\nureg = pint.UnitRegistry()\r\n\r\n\r\n@hookimpl\r\ndef prepare_connection(conn):\r\n def convert_units(amount, from_, to_):\r\n \"select convert_units(100, 'm', 'ft');\"\r\n return (amount * ureg(from_)).to(to_).to_tuple()[0]\r\n conn.create_function('convert_units', 3, convert_units)\r\n\r\n\r\n@hookimpl\r\ndef prepare_connection(conn):\r\n conn.create_function('random_integer', 2, random.randint)\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 267707940, "label": "Datasette Plugins"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/216#issuecomment-381643173", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/216", "id": 381643173, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTY0MzE3Mw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T15:21:17Z", "updated_at": "2018-04-16T15:21:17Z", "author_association": "OWNER", "body": "Yikes, definitely a bug.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 314665147, "label": "Bug: Sort by column with NULL in next_page URL"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/216#issuecomment-381644355", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/216", "id": 381644355, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTY0NDM1NQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T15:24:38Z", "updated_at": "2018-04-16T15:24:38Z", "author_association": "OWNER", "body": "So there are two tricky problems to solve here:\r\n\r\n* I need a way of encoding `null` into that `_next=` that is unambiguous from the string `None` or `null`. This means introducing some kind of escaping mechanism in those strings. I already use URL encoding as part of the construction of those components here, maybe that can help here?\r\n* I need to figure out what the SQL should be for the \"next\" set of results if the previous value was null. Thankfully we use the primary key as a tie-breaker so this shouldn't be impossible.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 314665147, "label": "Bug: Sort by column with NULL in next_page URL"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/216#issuecomment-381645274", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/216", "id": 381645274, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTY0NTI3NA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T15:27:16Z", "updated_at": "2018-04-16T15:27:16Z", "author_association": "OWNER", "body": "Relevant code:\r\n\r\nhttps://github.com/simonw/datasette/blob/904f1c75a3c17671d25c53b91e177c249d14ab3b/datasette/app.py#L828-L832", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 314665147, "label": "Bug: Sort by column with NULL in next_page URL"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/216#issuecomment-381645973", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/216", "id": 381645973, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTY0NTk3Mw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T15:29:11Z", "updated_at": "2018-04-16T15:29:11Z", "author_association": "OWNER", "body": "I could use `$null` as a magic value that means None. Since I'm applying `quote_plus()` to actual values, any legit strings that look like this will be encoded as `%24null`:\r\n\r\n```\r\n>>> urllib.parse.quote_plus('$null')\r\n'%24null'\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 314665147, "label": "Bug: Sort by column with NULL in next_page URL"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/216#issuecomment-381648053", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/216", "id": 381648053, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTY0ODA1Mw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T15:35:17Z", "updated_at": "2018-04-16T15:35:17Z", "author_association": "OWNER", "body": "I think the correct SQL is this: https://datasette-issue-189-demo-3.now.sh/salaries-7859114-7859114?sql=select+rowid%2C+*+from+%5B2017+Maryland+state+salaries%5D%0D%0Awhere+%28middle_initial+is+not+null+or+%28middle_initial+is+null+and+rowid+%3E+%3Ap0%29%29%0D%0Aorder+by+middle_initial+limit+101&p0=391\r\n\r\n```\r\nselect rowid, * from [2017 Maryland state salaries]\r\nwhere (middle_initial is not null or (middle_initial is null and rowid > :p0))\r\norder by middle_initial limit 101\r\n```\r\n\r\nThough this will also need to be taken into account for #198 ", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 314665147, "label": "Bug: Sort by column with NULL in next_page URL"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/216#issuecomment-381649140", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/216", "id": 381649140, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTY0OTE0MA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T15:38:29Z", "updated_at": "2018-04-16T15:38:29Z", "author_association": "OWNER", "body": "But what would that SQL look like for `_sort_desc`?", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 314665147, "label": "Bug: Sort by column with NULL in next_page URL"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/216#issuecomment-381649437", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/216", "id": 381649437, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTY0OTQzNw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T15:39:21Z", "updated_at": "2018-04-16T15:39:21Z", "author_association": "OWNER", "body": "Here's where that SQL gets constructed at the moment:\r\n\r\nhttps://github.com/simonw/datasette/blob/10a34f995c70daa37a8a2aa02c3135a4b023a24c/datasette/app.py#L761-L771", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 314665147, "label": "Bug: Sort by column with NULL in next_page URL"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/209#issuecomment-381738137", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/209", "id": 381738137, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTczODEzNw==", "user": {"value": 45057, "label": "russss"}, "created_at": "2018-04-16T20:27:43Z", "updated_at": "2018-04-16T20:27:43Z", "author_association": "CONTRIBUTOR", "body": "Tests now fixed, honest. The failing test on Travis looks like an intermittent sqlite failure which should resolve itself on a retry...", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 314455877, "label": " Don't duplicate simple primary keys in the link column"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/203#issuecomment-381763651", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/203", "id": 381763651, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTc2MzY1MQ==", "user": {"value": 45057, "label": "russss"}, "created_at": "2018-04-16T21:59:17Z", "updated_at": "2018-04-16T21:59:17Z", "author_association": "CONTRIBUTOR", "body": "Ah, I had no idea you could bind python functions into sqlite!\r\n\r\nI think the primary purpose of this issue has been served now - I'm going to close this and create a new issue for the only bit of this that hasn't been touched yet, which is (optionally) exposing units in the JSON API.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 313837303, "label": "Support for units"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/220#issuecomment-381777108", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/220", "id": 381777108, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTc3NzEwOA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T23:04:04Z", "updated_at": "2018-04-16T23:04:04Z", "author_association": "OWNER", "body": "This could also help workaround the current predicament that a single plugin can only define one prepare_connection hook.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 314847571, "label": "Investigate syntactic sugar for plugins"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/216#issuecomment-381786522", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/216", "id": 381786522, "node_id": "MDEyOklzc3VlQ29tbWVudDM4MTc4NjUyMg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-04-16T23:58:45Z", "updated_at": "2018-04-16T23:59:13Z", "author_association": "OWNER", "body": "Weird... tests are failing in Travis, despite passing on my local machine. https://travis-ci.org/simonw/datasette/builds/367423706", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 314665147, "label": "Bug: Sort by column with NULL in next_page URL"}, "performed_via_github_app": null}