{"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-504857097", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 504857097, "node_id": "MDEyOklzc3VlQ29tbWVudDUwNDg1NzA5Nw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-06-24T04:54:15Z", "updated_at": "2019-06-24T04:54:15Z", "author_association": "OWNER", "body": "I wrote about this on my blog: https://simonwillison.net/2019/Jun/23/datasette-asgi/", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-504844339", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 504844339, "node_id": "MDEyOklzc3VlQ29tbWVudDUwNDg0NDMzOQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-06-24T03:33:06Z", "updated_at": "2019-06-24T03:33:06Z", "author_association": "OWNER", "body": "It's alive! Here's the first deployed version: https://a559123.datasette.io/\r\n\r\nYou can confirm it's running under ASGI by viewing https://a559123.datasette.io/-/versions and looking for the `\"asgi\"` key.\r\n\r\nCompare to the last version of master running on Sanic here: http://aa91112.datasette.io/", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-504761039", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 504761039, "node_id": "MDEyOklzc3VlQ29tbWVudDUwNDc2MTAzOQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-06-23T15:15:41Z", "updated_at": "2019-06-23T15:18:36Z", "author_association": "OWNER", "body": "And now the tests are all passing!\r\n\r\nStill to do:\r\n\r\n* Use `raw_path` so table names containing `/` can work correctly\r\n* Get ?_trace=1 working again\r\n* Replacement for `@app.listener(\"before_server_start\")`\r\n* Replace Sanic request object with my own request class, so I can remove Sanic dependency", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-504761165", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 504761165, "node_id": "MDEyOklzc3VlQ29tbWVudDUwNDc2MTE2NQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-06-23T15:17:07Z", "updated_at": "2019-06-23T15:17:07Z", "author_association": "OWNER", "body": "I'm going to move the remaining work into a pull request.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-504716988", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 504716988, "node_id": "MDEyOklzc3VlQ29tbWVudDUwNDcxNjk4OA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-06-23T03:43:46Z", "updated_at": "2019-06-23T15:15:26Z", "author_association": "OWNER", "body": "OK, it's beginning to shape up now. Next steps:\r\n\r\n- [x] Static file support (including for plugins) - plus tests\r\n- [x] Streaming support so the CSV tests will pass\r\n- [x] Ability to download the database file\r\n- [x] Implement missing-slash redirects\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-504760061", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 504760061, "node_id": "MDEyOklzc3VlQ29tbWVudDUwNDc2MDA2MQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-06-23T15:02:52Z", "updated_at": "2019-06-23T15:02:52Z", "author_association": "OWNER", "body": "Tests are failing on Python 3.5: https://travis-ci.org/simonw/datasette/jobs/549380098 - error is `TypeError: the JSON object must be str, not 'bytes'`", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-504759842", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 504759842, "node_id": "MDEyOklzc3VlQ29tbWVudDUwNDc1OTg0Mg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-06-23T15:00:06Z", "updated_at": "2019-06-23T15:00:06Z", "author_association": "OWNER", "body": "I also need to actually take advantage of `raw_path` such that pages like https://fivethirtyeight.datasettes.com/fivethirtyeight/twitter-ratio%2Fsenators can be correctly served.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-504759683", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 504759683, "node_id": "MDEyOklzc3VlQ29tbWVudDUwNDc1OTY4Mw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-06-23T14:57:50Z", "updated_at": "2019-06-23T14:57:50Z", "author_association": "OWNER", "body": "All of the tests are now passing!\r\n\r\nI still need a solution for this:\r\n\r\nhttps://github.com/simonw/datasette/blob/5bd510b01adae3f719e4426b9bfbc346a946ba5c/datasette/app.py#L706-L714\r\n\r\nI think the answer is ASGI lifespan, which is supported by Uvicorn. https://asgi.readthedocs.io/en/latest/specs/lifespan.html#startup", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-504754552", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 504754552, "node_id": "MDEyOklzc3VlQ29tbWVudDUwNDc1NDU1Mg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-06-23T13:53:39Z", "updated_at": "2019-06-23T13:53:39Z", "author_association": "OWNER", "body": "Next test to fix (because by new test harness doesn't actually obey the `allow_redirects=` parameter):\r\n```\r\n_____________ test_database_page_redirects_with_url_hash _____________\r\n\r\napp_client_with_hash = \r\n\r\n def test_database_page_redirects_with_url_hash(app_client_with_hash):\r\n response = app_client_with_hash.get(\"/fixtures\", allow_redirects=False)\r\n assert response.status == 302\r\n response = app_client_with_hash.get(\"/fixtures\")\r\n> assert \"fixtures\" in response.text\r\nE AssertionError: assert 'fixtures' in ''\r\nE + where '' = .text\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-504754433", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 504754433, "node_id": "MDEyOklzc3VlQ29tbWVudDUwNDc1NDQzMw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-06-23T13:51:53Z", "updated_at": "2019-06-23T13:51:53Z", "author_association": "OWNER", "body": "CSV tests all pass as of https://github.com/simonw/datasette/commit/ff9efa668ebc33f17ef9b30139960e29906a18fb\r\n\r\nThis code could be a lot neater though. At the very least I'm going to refactor `datasette/utils.py` into a `datasette/utils` package and put all of my new ASGI utilities in `datasette/utils/asgi.py`\r\n\r\nThe way I implemented streaming on top of a writer object (inspired by Sanic) is a bit of a weird hack. I think I'd rather use an abstraction where my view functions can yield chunks of body data.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-504711468", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 504711468, "node_id": "MDEyOklzc3VlQ29tbWVudDUwNDcxMTQ2OA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-06-23T01:36:33Z", "updated_at": "2019-06-23T01:36:33Z", "author_association": "OWNER", "body": "Published an in-progress demo:\r\n\r\n datasette publish now fixtures.db -n datasette-asgi-early-demo --branch=asgi\r\n\r\nHere it is: https://datasette-asgi-early-demo-qahhxctqpw.now.sh/", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-504710331", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 504710331, "node_id": "MDEyOklzc3VlQ29tbWVudDUwNDcxMDMzMQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-06-23T01:08:45Z", "updated_at": "2019-06-23T01:08:45Z", "author_association": "OWNER", "body": "Lots still to do:\r\n\r\n* Static files are not being served\r\n* Streaming CSV files don't work\r\n* Tests all fail\r\n* Some URLs (e.g. the 'next' link on tables) are incorrect\r\n\r\nI'm going to work on getting the unit test framework to be ASGI-compatible next.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-504697742", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 504697742, "node_id": "MDEyOklzc3VlQ29tbWVudDUwNDY5Nzc0Mg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-06-22T20:55:59Z", "updated_at": "2019-06-22T20:56:22Z", "author_association": "OWNER", "body": "Getting this to work with both Sanic AND ASGI at the same time (via the classes described previously with an `--asgi` command-line option) is proving way trickier than I expected, mainly because of the complexity of [the current Datasette.app() method](https://github.com/simonw/datasette/blob/35429f90894321eda7f2db31b9ea7976f31f73ac/datasette/app.py#L545-L721).\r\n\r\nI'm going to drop the compatibility path for a bit and see if I can make progress on a pure-ASGI port.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-503369834", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 503369834, "node_id": "MDEyOklzc3VlQ29tbWVudDUwMzM2OTgzNA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-06-19T01:26:24Z", "updated_at": "2019-06-19T01:26:24Z", "author_association": "OWNER", "body": "I need to be able to define the URL routes once and have them work for both Sanic and ASGI.\r\n\r\nI'm going to extract the web application bits out of the `Datasette` class into a `DatasetteServer` class. Then I can have a `add_route()` method on that class, then have `DatasetteSanic` and `DatasetteAsgi` subclasses which redefine that method.\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-502393107", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 502393107, "node_id": "MDEyOklzc3VlQ29tbWVudDUwMjM5MzEwNw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-06-15T19:25:54Z", "updated_at": "2019-06-19T01:20:14Z", "author_association": "OWNER", "body": "OK, time for a solid implementation plan.\r\n\r\nAs soon as https://github.com/django/asgiref/pull/92 is merged (hopefully very soon) the ASGI spec will have support for an optional `raw_path` - which means we can continue to use `table%2Fnames` with embedded `/` without being unable to tell if a path has been decoded or not.\r\n\r\nSteps to implement:\r\n\r\n## Refactor classes, then add .asgi() method to BaseView\r\n\r\nAdd a `.asgi(self, scope, receive, send)` method to my base view class. This will expose an ASGI interface to the outside world: the method itself will construct a request object and call the existing `.get()` method.\r\n\r\nMy only true shared base class is actually `RenderMixin` because the `IndexView` doesn't extend `BaseView`. I'm going to refactor the class hierarchy a bit here - `AsgiView` will be my top level class with the `.asgi()` method on it. `RenderMixin` will be renamed `BaseView(AsgiView)`, while existing `BaseView` will be renamed `DataView(BaseView)` since it mainly exists to introduce the handy `.data()` abstraction.\r\n\r\nSo...\r\n\r\n* `AsgiView` - has `.asgi()` method, extends Sanic `HTTPMethodView` (for the moment)\r\n* `BaseView(AsgiView)` - defines utility methods currently on `RenderMixin`\r\n* `IndexView(BaseView)` - the current `IndexView`\r\n* `DataView(BaseView)` - defines the utilities currently on `BaseView`, including `data()`\r\n* Everything else subclasses `DataView`\r\n\r\n## Extract routing logic out into a new `DatasetteView`\r\n\r\nI considered calling this `RouteView`, but one of the goals of this project is to allow other ASGI apps to import Datasette itself and reuse it as its own ASGI function.\r\n\r\nSo `DatasetteView` will subclass `BaseView` and will do all of the routing logic. That logic currently lives here:\r\n\r\nhttps://github.com/simonw/datasette/blob/aa911122feab13f8e65875c98edb00fd3832b7b8/datasette/app.py#L594-L640\r\n\r\n## For tests: Implement a version of app_client.get() that calls ASGI instead\r\n\r\nAlmost all of the unit tests currently use `app_client.get(\"/path...\")`. I want to be able to run tests against both ASGI and existing-Sanic, so for the moment I'm going to teach `app_client.get()` to use ASGI instead but only in the presence of a new environment variable. I can then have Travis run the tests twice - once with that environement variable and once without.\r\n\r\n## Make datasette serve --asgi run ASGI and uvicorn\r\n\r\nUvicorn supports Python 3.5 again as of https://github.com/encode/uvicorn/issues/330 - so it's going to be the new dependency for Datasette. \r\n\r\n## Do some comparative testing of ASGI and non-ASGI\r\n\r\nJust some sanity checking to make sure there aren't any weird issues.\r\n\r\n## Final step: refactor out Sanic\r\n\r\nHopefully this will just involve changes being made to the AsgiView base class, since that subclasses Sanic `HTTPMethodView`.\r\n\r\nBonus: It looks like dropping Sanic as a dependency in favour of Uvicorn should give us Windows support! https://github.com/encode/uvicorn/issues/82\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-503351966", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 503351966, "node_id": "MDEyOklzc3VlQ29tbWVudDUwMzM1MTk2Ng==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-06-18T23:45:17Z", "updated_at": "2019-06-18T23:45:17Z", "author_association": "OWNER", "body": "Uvicorn 0.8.1 is our and supports `raw_path`!", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-503195217", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 503195217, "node_id": "MDEyOklzc3VlQ29tbWVudDUwMzE5NTIxNw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-06-18T15:46:31Z", "updated_at": "2019-06-18T15:54:18Z", "author_association": "OWNER", "body": "How should file serving work?\r\n\r\nStarlette and Sanic both use `aiofiles` - https://github.com/Tinche/aiofiles - which is a small wrapper around file operations which runs them all in an executor thread. It doesn't have any C dependencies so it looks like a good option. [Quart uses it too](https://gitlab.com/pgjones/quart/blob/317562ea660edb7159efc20fa57b95223d408ea0/quart/wrappers/response.py#L122-169).\r\n\r\n`aiohttp` does things differently: it has [an implementation based on sendfile](https://github.com/aio-libs/aiohttp/blob/7a324fd46ff7dc9bb0bb1bc5afb326e04cf7cef0/aiohttp/web_fileresponse.py#L46-L122) with [an alternative fallback](https://github.com/aio-libs/aiohttp/blob/7a324fd46ff7dc9bb0bb1bc5afb326e04cf7cef0/aiohttp/web_fileresponse.py#L175-L200) which reads chunks from a file object and yields them one chunk at a time, \r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-502466466", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 502466466, "node_id": "MDEyOklzc3VlQ29tbWVudDUwMjQ2NjQ2Ng==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-06-16T16:28:10Z", "updated_at": "2019-06-16T16:28:10Z", "author_association": "OWNER", "body": "I have an open pull request to Uvicorn with an implementation of `raw_path`: https://github.com/encode/uvicorn/pull/372", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-502401078", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 502401078, "node_id": "MDEyOklzc3VlQ29tbWVudDUwMjQwMTA3OA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-06-15T21:35:26Z", "updated_at": "2019-06-15T21:35:26Z", "author_association": "OWNER", "body": "Started sketching out the router in the `asgi` branch: https://github.com/simonw/datasette/commit/7cdc55c6836fe246b1ca8a13a965a39991c9ffec", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-502395689", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 502395689, "node_id": "MDEyOklzc3VlQ29tbWVudDUwMjM5NTY4OQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-06-15T20:05:26Z", "updated_at": "2019-06-15T20:05:26Z", "author_association": "OWNER", "body": "For the routing component: I'm going to base my implementation on the one from Django Channels.\r\n\r\nhttps://github.com/django/channels/blob/507cb54fcb36df63282dd19653ea743036e7d63c/channels/routing.py#L123-L149\r\n\r\nDocumented here: https://channels.readthedocs.io/en/latest/topics/routing.html#urlrouter\r\n\r\nParticularly relevant: my view classes need access to the components that were already parsed out of the URL by the router. I'm going to copy the Django Channels mechanism of stashing those in `scope[\"url_route\"][\"kwargs\"]`.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-502394420", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 502394420, "node_id": "MDEyOklzc3VlQ29tbWVudDUwMjM5NDQyMA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-06-15T19:45:46Z", "updated_at": "2019-06-15T19:45:46Z", "author_association": "OWNER", "body": "For reference, here's some WIP code I wrote last year against the old ASGI 2 spec: https://github.com/simonw/datasette/commit/4fd36ba2f3f91da7258859808616078e3464fb97", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-502393267", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 502393267, "node_id": "MDEyOklzc3VlQ29tbWVudDUwMjM5MzI2Nw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-06-15T19:28:27Z", "updated_at": "2019-06-15T19:28:27Z", "author_association": "OWNER", "body": "I'll probably revert 9fdb47ca952b93b7b60adddb965ea6642b1ff523 from https://github.com/simonw/datasette/issues/272#issuecomment-494192779 since I won't need it now that ASGI is getting `raw_path` support.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-494192779", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 494192779, "node_id": "MDEyOklzc3VlQ29tbWVudDQ5NDE5Mjc3OQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-05-21T00:10:47Z", "updated_at": "2019-05-21T00:10:47Z", "author_association": "OWNER", "body": "https://github.com/simonw/datasette/commit/9fdb47ca952b93b7b60adddb965ea6642b1ff523 added `decode_path_component()` and `encode_path_component()` functions because ASGI decodes %2F encoded slashes in URLs automatically. The new encoding scheme looks like this:\r\n\r\n \"table/and/slashes\" => \"tableU+002FandU+002Fslashes\"\r\n \"~table\" => \"U+007Etable\"\r\n \"+bobcats!\" => \"U+002Bbobcats!\"\r\n \"U+007Etable\" => \"UU+002B007Etable\"\r\n\r\nFor background see this comment: https://github.com/django/asgiref/issues/51#issuecomment-450603464", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-494192163", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 494192163, "node_id": "MDEyOklzc3VlQ29tbWVudDQ5NDE5MjE2Mw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-05-21T00:07:25Z", "updated_at": "2019-05-21T00:07:25Z", "author_association": "OWNER", "body": "Bah, I'd much rather depend on Starlette for things like form parsing - but it's 3.6+ only!\r\n\r\nhttps://github.com/encode/starlette/blob/ab86530eddfcf56e0f7e5ca56f6ab69c15594a7d/setup.py#L39\r\n\r\nMaybe I could require Python 3.6 or higher if you want to handle POST data? This would make my internals far too complicated though I think.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-494191738", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 494191738, "node_id": "MDEyOklzc3VlQ29tbWVudDQ5NDE5MTczOA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-05-21T00:05:02Z", "updated_at": "2019-05-21T00:05:02Z", "author_association": "OWNER", "body": "While I'm not depending on Starlette any more I will need to instead depend on https://github.com/andrew-d/python-multipart for POST form parsing - as used by Starlette here https://github.com/encode/starlette/blob/ab86530eddfcf56e0f7e5ca56f6ab69c15594a7d/starlette/requests.py#L178-L193", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-494191378", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 494191378, "node_id": "MDEyOklzc3VlQ29tbWVudDQ5NDE5MTM3OA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-05-21T00:02:48Z", "updated_at": "2019-05-21T00:02:48Z", "author_association": "OWNER", "body": "I said earlier that I only need to support GET - I actually need to be able to support POST too, mainly to support plugins (e.g. a plugin that allows authenticated login before you can view Datasette, but potentially also plugins that let you write data directly to SQLite as well).", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-494190922", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 494190922, "node_id": "MDEyOklzc3VlQ29tbWVudDQ5NDE5MDkyMg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2019-05-21T00:00:40Z", "updated_at": "2019-05-21T00:01:09Z", "author_association": "OWNER", "body": "Wow, this issue has been open for a full year now!\r\n\r\nI've been thinking about this a lot. I've decided I want Datasette to use ASGI 3.0 internally with no dependencies on anything else - then I want the option to run Datasette under both daphne and uvicorn - because uvicorn doesn't support Python 3.5 but Datasette still needs to (primarily for Glitch), and daphne works with 3.5.\r\n\r\nSo I'm going to try to go the following route:\r\n\r\n- Every Datasette view becomes an ASGI app\r\n- The Datasette application itself is an ASGI app that routes to those views\r\n- When you `pip install datasette` you get Daphne as a dependency (I'd like you to be able to opt-out of installing Daphne, I'm not yet sure how that would work)\r\n- A new `asgi_serve` plugin hook allows a plugin to serve Datasette using uvicorn (or hypercorn) instead", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-408478935", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 408478935, "node_id": "MDEyOklzc3VlQ29tbWVudDQwODQ3ODkzNQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-07-27T17:00:08Z", "updated_at": "2018-07-27T17:00:08Z", "author_association": "OWNER", "body": "Refs \r\nhttps://github.com/encode/uvicorn/issues/168", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-408105251", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 408105251, "node_id": "MDEyOklzc3VlQ29tbWVudDQwODEwNTI1MQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-07-26T13:54:06Z", "updated_at": "2018-07-26T13:54:06Z", "author_association": "OWNER", "body": "Tom shipped my fix for that bug already, so https://datasette-starlette-demo.now.sh/ is now serving CSS!", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-408093480", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 408093480, "node_id": "MDEyOklzc3VlQ29tbWVudDQwODA5MzQ4MA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-07-26T13:15:55Z", "updated_at": "2018-07-26T13:46:40Z", "author_association": "OWNER", "body": "I'm now hacking around with an initial version of this in the [starlette branch](https://github.com/simonw/datasette/tree/starlette).\r\n\r\nHere's my work in progress, deployed using `datasette publish now fixtures.db -n datasette-starlette-demo --branch=starlette --extra-options=\"--asgi\"`\r\n\r\nhttps://datasette-starlette-demo.now.sh/\r\n\r\nLots more work to do - the CSS isn't being served correctly for example, it's showing this error when I hit `/-/static/app.css`:\r\n```\r\nINFO: 127.0.0.1 - \"GET /-/static/app.css HTTP/1.1\" 200\r\nERROR: Exception in ASGI application\r\nTraceback (most recent call last):\r\n File \"/Users/simonw/Dropbox/Development/datasette/venv/lib/python3.6/site-packages/uvicorn/protocols/http/httptools_impl.py\", line 363, in run_asgi\r\n result = await asgi(self.receive, self.send)\r\n File \"/Users/simonw/Dropbox/Development/datasette/venv/lib/python3.6/site-packages/starlette/staticfiles.py\", line 91, in __call__\r\n await response(receive, send)\r\n File \"/Users/simonw/Dropbox/Development/datasette/venv/lib/python3.6/site-packages/starlette/response.py\", line 180, in __call__\r\n {\"type\": \"http.response.body\", \"body\": chunk, \"more_body\": False}\r\n File \"/Users/simonw/Dropbox/Development/datasette/venv/lib/python3.6/site-packages/uvicorn/protocols/http/httptools_impl.py\", line 483, in send\r\n raise RuntimeError(\"Response content shorter than Content-Length\")\r\nRuntimeError: Response content shorter than Content-Length\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-408097719", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 408097719, "node_id": "MDEyOklzc3VlQ29tbWVudDQwODA5NzcxOQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-07-26T13:29:38Z", "updated_at": "2018-07-26T13:29:38Z", "author_association": "OWNER", "body": "It looks like that's a bug in Starlette - filed here: https://github.com/encode/starlette/issues/32", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-403959704", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 403959704, "node_id": "MDEyOklzc3VlQ29tbWVudDQwMzk1OTcwNA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-07-10T20:44:47Z", "updated_at": "2018-07-10T20:44:47Z", "author_association": "OWNER", "body": "No cookies or sessions - no POST requests in fact, Datasette just cares about GET (path and querystring) and being able to return custom HTTP headers.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-400166540", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 400166540, "node_id": "MDEyOklzc3VlQ29tbWVudDQwMDE2NjU0MA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-06-26T03:29:43Z", "updated_at": "2018-06-26T03:29:43Z", "author_association": "OWNER", "body": "This looks VERY relevant: https://github.com/encode/starlette", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-392118755", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 392118755, "node_id": "MDEyOklzc3VlQ29tbWVudDM5MjExODc1NQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-05-25T16:56:40Z", "updated_at": "2018-06-05T16:01:13Z", "author_association": "OWNER", "body": "Thinking about this further, maybe I should embrace ASGI turtles-all-the-way-down and teach each datasette view class to take a scope to the constructor and act entirely as an ASGI component. Would be a nice way of diving deep into ASGI and I can add utility helpers for things like querystring evaluation as I need them.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-394764713", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 394764713, "node_id": "MDEyOklzc3VlQ29tbWVudDM5NDc2NDcxMw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-06-05T15:58:54Z", "updated_at": "2018-06-05T16:00:40Z", "author_association": "OWNER", "body": "https://github.com/encode/uvicorn/blob/572b5fe6c811b63298d5350a06b664839624c860/uvicorn/run.py#L63 is how you start a Uvicorn server from code as opposed to the `uvicorn` CLI\r\n\r\n from uvicorn.run import UvicornServer\r\n UvicornServer().run(app, host=host, port=port)\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-394503399", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 394503399, "node_id": "MDEyOklzc3VlQ29tbWVudDM5NDUwMzM5OQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-06-04T21:20:14Z", "updated_at": "2018-06-04T21:20:14Z", "author_association": "OWNER", "body": "Results of an extremely simple micro-benchmark comparing the two shows that uvicorn is at least as fast as Sanic (benchmarks a little faster with a very simple payload): https://gist.github.com/simonw/418950af178c01c416363cc057420851", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-394431323", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 394431323, "node_id": "MDEyOklzc3VlQ29tbWVudDM5NDQzMTMyMw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-06-04T17:17:37Z", "updated_at": "2018-06-04T17:17:37Z", "author_association": "OWNER", "body": "I built this ASGI debugging tool to help with this migration: https://asgi-scope.now.sh/fivethirtyeight-34d6604/most-common-name%2Fsurnames.json?foo=bar&bazoeuto=onetuh&a=.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/272#issuecomment-391011268", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/272", "id": 391011268, "node_id": "MDEyOklzc3VlQ29tbWVudDM5MTAxMTI2OA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2018-05-22T14:28:12Z", "updated_at": "2018-05-22T14:28:12Z", "author_association": "OWNER", "body": "I think I can do this almost entirely within my existing BaseView class structure.\r\n\r\nFirst, decouple the async data() methods by teaching them to take a querystring object as an argument instead of a Sanic request object. The get() method can then send that new object instead of a request.\r\n\r\nNext teach the base class how to obey the ASGI protocol.\r\n\r\nI should be able to get support for both Sanic and uvicorn/daphne working in the same codebase, which will make it easy to compare their performance. ", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 324188953, "label": "Port Datasette to ASGI"}, "performed_via_github_app": null}