{"id": 870125126, "node_id": "MDU6SXNzdWU4NzAxMjUxMjY=", "number": 1310, "title": "I'm creating a plugin to export a spreadsheet file (.ods or .xlsx)", "user": {"value": 3747136, "label": "ColinMaudry"}, "state": "closed", "locked": 0, "assignee": null, "milestone": null, "comments": 2, "created_at": "2021-04-28T16:20:11Z", "updated_at": "2021-04-30T07:26:11Z", "closed_at": "2021-04-30T06:58:46Z", "author_association": "NONE", "pull_request": null, "body": "Hi,\r\n\r\nI have started developing a plugin to export records as a spreadsheet file. It could be ods or xlsx, whatever is easier.\r\n\r\nI have spotted the following packages:\r\n\r\n- ods files: https://pypi.org/project/odswriter/\r\n- xlsx files: https://openpyxl.readthedocs.io/en/stable/index.html (quite powerful) or https://xlsxwriter.readthedocs.io/ (faster)\r\n\r\nThis is the code I have so far, I test it with the `--plugins-dir` option:\r\n\r\n```python\r\nfrom datasette import hookimpl\r\nfrom datasette.utils.asgi import Response\r\nimport odswriter as ods\r\n\r\ndef render_spreadsheet(rows):\r\n with ods.writer(open(\"test.ods\",\"wb\")) as odsfile:\r\n for row in rows:\r\n odsfile.writerow([\"String\", \"ABCDEF123456\", \"123456\"])\r\n return Response(odsfile, content_type=\"application/vnd.oasis.opendocument.spreadsheet\", status=200)\r\n\r\n\r\n@hookimpl\r\ndef register_output_renderer():\r\n return {\"extension\": \"ods\", \"render\": render_spreadsheet}\r\n\r\n``` \r\n\r\nI get the following error:\r\n\r\n```\r\nTraceback (most recent call last):\r\n File \"/home/colin/.local/lib/python3.8/site-packages/datasette/app.py\", line 1128, in route_path\r\n await response.asgi_send(send)\r\n File \"/home/colin/.local/lib/python3.8/site-packages/datasette/utils/asgi.py\", line 339, in asgi_send\r\n body = body.encode(\"utf-8\")\r\nAttributeError: 'ODSWriter' object has no attribute 'encode'\r\nERROR: Exception in ASGI application\r\nTraceback (most recent call last):\r\n File \"/home/colin/.local/lib/python3.8/site-packages/datasette/app.py\", line 1128, in route_path\r\n await response.asgi_send(send)\r\n File \"/home/colin/.local/lib/python3.8/site-packages/datasette/utils/asgi.py\", line 339, in asgi_send\r\n body = body.encode(\"utf-8\")\r\nAttributeError: 'ODSWriter' object has no attribute 'encode'\r\n\r\nDuring handling of the above exception, another exception occurred:\r\n\r\nTraceback (most recent call last):\r\n File \"/home/colin/.local/lib/python3.8/site-packages/uvicorn/protocols/http/h11_impl.py\", line 396, in run_asgi\r\n result = await app(self.scope, self.receive, self.send)\r\n File \"/home/colin/.local/lib/python3.8/site-packages/uvicorn/middleware/proxy_headers.py\", line 45, in __call__\r\n return await self.app(scope, receive, send)\r\n File \"/home/colin/.local/lib/python3.8/site-packages/datasette/utils/asgi.py\", line 161, in __call__\r\n await self.app(scope, receive, send)\r\n File \"/home/colin/.local/lib/python3.8/site-packages/datasette/tracer.py\", line 75, in __call__\r\n await self.app(scope, receive, send)\r\n File \"/home/colin/.local/lib/python3.8/site-packages/asgi_csrf.py\", line 107, in app_wrapped_with_csrf\r\n await app(scope, receive, wrapped_send)\r\n File \"/home/colin/.local/lib/python3.8/site-packages/datasette/app.py\", line 1086, in __call__\r\n return await self.route_path(scope, receive, send, path)\r\n File \"/home/colin/.local/lib/python3.8/site-packages/datasette/app.py\", line 1133, in route_path\r\n return await self.handle_500(request, send, exception)\r\n File \"/home/colin/.local/lib/python3.8/site-packages/datasette/app.py\", line 1267, in handle_500\r\n await asgi_send_html(\r\n File \"/home/colin/.local/lib/python3.8/site-packages/datasette/utils/asgi.py\", line 217, in asgi_send_html\r\n await asgi_send(\r\n File \"/home/colin/.local/lib/python3.8/site-packages/datasette/utils/asgi.py\", line 237, in asgi_send\r\n await asgi_start(send, status, headers, content_type)\r\n File \"/home/colin/.local/lib/python3.8/site-packages/datasette/utils/asgi.py\", line 246, in asgi_start\r\n await send(\r\n File \"/home/colin/.local/lib/python3.8/site-packages/asgi_csrf.py\", line 103, in wrapped_send\r\n await send(event)\r\n File \"/home/colin/.local/lib/python3.8/site-packages/uvicorn/protocols/http/h11_impl.py\", line 482, in send\r\n raise RuntimeError(msg % message_type)\r\nRuntimeError: Expected ASGI message 'http.response.body', but got 'http.response.start'.\r\n```\r\n\r\nI tried with `AsgiFileDownload` like in [DatabaseDownload](https://github.com/simonw/datasette/blob/main/datasette/views/database.py#L150) to deal with the binary nature of the ods file, but the renderer expects a Response:\r\n\r\n> should be dict or Response\r\n\r\nHowever, the `Response` class only supports the following methods, not binary:\r\n\r\n- html\r\n- text\r\n- json\r\n- redirect\r\n\r\nHow would you suggest me to proceed to have my ods file downloaded?\r\n\r\n", "repo": {"value": 107914493, "label": "datasette"}, "type": "issue", "active_lock_reason": null, "performed_via_github_app": null, "reactions": "{\"url\": \"https://api.github.com/repos/simonw/datasette/issues/1310/reactions\", \"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "draft": null, "state_reason": "completed"}