From b794554a26ddc81bd772c4422d80d5ee863e92b0 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sun, 23 Jun 2019 20:03:33 -0700 Subject: [PATCH] Replaced sanic.response and finished removing Sanic entirely in favour of ASGI --- datasette/utils/asgi.py | 69 ++++++++++++++++++++++++++++--------- datasette/views/base.py | 36 +++++++++---------- datasette/views/database.py | 2 -- datasette/views/index.py | 5 ++- datasette/views/special.py | 4 +-- setup.py | 1 - 6 files changed, 74 insertions(+), 43 deletions(-) diff --git a/datasette/utils/asgi.py b/datasette/utils/asgi.py index 63034872..fdf330ae 100644 --- a/datasette/utils/asgi.py +++ b/datasette/utils/asgi.py @@ -168,23 +168,7 @@ class AsgiView: response = await self.dispatch_request( request, **scope["url_route"]["kwargs"] ) - if hasattr(response, "asgi_send"): - await response.asgi_send(send) - else: - headers = {} - headers.update(response.headers) - headers["content-type"] = response.content_type - await send( - { - "type": "http.response.start", - "status": response.status, - "headers": [ - [key.encode("utf-8"), value.encode("utf-8")] - for key, value in headers.items() - ], - } - ) - await send({"type": "http.response.body", "body": response.body}) + await response.asgi_send(send) view.view_class = cls view.__doc__ = cls.__doc__ @@ -330,6 +314,57 @@ def asgi_static(root_path, chunk_size=4096, headers=None, content_type=None): return inner_static +class Response: + def __init__(self, body=None, status=200, headers=None, content_type="text/plain"): + self.body = body + self.status = status + self.headers = headers or {} + self.content_type = content_type + + async def asgi_send(self, send): + headers = {} + headers.update(self.headers) + headers["content-type"] = self.content_type + await send( + { + "type": "http.response.start", + "status": self.status, + "headers": [ + [key.encode("utf-8"), value.encode("utf-8")] + for key, value in headers.items() + ], + } + ) + body = self.body + if not isinstance(body, bytes): + body = body.encode("utf-8") + await send({"type": "http.response.body", "body": body}) + + @classmethod + def html(cls, body, status=200, headers=None): + return cls( + body, + status=status, + headers=headers, + content_type="text/html; charset=utf-8", + ) + + @classmethod + def text(cls, body, status=200, headers=None): + return cls( + body, + status=status, + headers=headers, + content_type="text/plain; charset=utf-8", + ) + + @classmethod + def redirect(cls, path, status=302, headers=None): + headers = headers or {} + headers["Location"] = path + return cls("", status=status, headers=headers) + + class AsgiFileDownload: def __init__( self, filepath, filename=None, content_type="application/octet-stream" diff --git a/datasette/views/base.py b/datasette/views/base.py index 69a17315..7acb7304 100644 --- a/datasette/views/base.py +++ b/datasette/views/base.py @@ -7,7 +7,6 @@ import urllib import jinja2 import pint -from sanic import response from html import escape @@ -26,7 +25,14 @@ from datasette.utils import ( sqlite3, to_css_class, ) -from datasette.utils.asgi import AsgiStream, AsgiWriter, AsgiRouter, AsgiView, NotFound +from datasette.utils.asgi import ( + AsgiStream, + AsgiWriter, + AsgiRouter, + AsgiView, + NotFound, + Response, +) ureg = pint.UnitRegistry() @@ -112,7 +118,7 @@ class BaseView(AsgiView): datasette=self.ds, ): body_scripts.append(jinja2.Markup(script)) - return response.html( + return Response.html( template.render( { **context, @@ -144,7 +150,7 @@ class DataView(BaseView): self.ds = datasette def options(self, request, *args, **kwargs): - r = response.text("ok") + r = Response.text("ok") if self.ds.cors: r.headers["Access-Control-Allow-Origin"] = "*" return r @@ -154,7 +160,7 @@ class DataView(BaseView): path = "{}?{}".format(path, request.query_string) if remove_args: path = path_with_removed_args(request, remove_args, path=path) - r = response.redirect(path) + r = Response.redirect(path) r.headers["Link"] = "<{}>; rel=preload".format(path) if self.ds.cors: r.headers["Access-Control-Allow-Origin"] = "*" @@ -254,7 +260,7 @@ class DataView(BaseView): response_or_template_contexts = await self.data( request, database, hash, **kwargs ) - if isinstance(response_or_template_contexts, response.HTTPResponse): + if isinstance(response_or_template_contexts, Response): return response_or_template_contexts else: data, _, _ = response_or_template_contexts @@ -371,7 +377,7 @@ class DataView(BaseView): response_or_template_contexts = await self.data( request, database, hash, **kwargs ) - if isinstance(response_or_template_contexts, response.HTTPResponse): + if isinstance(response_or_template_contexts, Response): return response_or_template_contexts else: @@ -422,17 +428,11 @@ class DataView(BaseView): if result is None: raise NotFound("No data") - response_args = { - "content_type": result.get("content_type", "text/plain"), - "status": result.get("status_code", 200), - } - - if type(result.get("body")) == bytes: - response_args["body_bytes"] = result.get("body") - else: - response_args["body"] = result.get("body") - - r = response.HTTPResponse(**response_args) + r = Response( + body=result.get("body"), + status=result.get("status_code", 200), + content_type=result.get("content_type", "text/plain"), + ) else: extras = {} if callable(extra_template_data): diff --git a/datasette/views/database.py b/datasette/views/database.py index 4809fef0..78af19c5 100644 --- a/datasette/views/database.py +++ b/datasette/views/database.py @@ -1,7 +1,5 @@ import os -from sanic import response - from datasette.utils import to_css_class, validate_sql_select from datasette.utils.asgi import AsgiFileDownload diff --git a/datasette/views/index.py b/datasette/views/index.py index c01ae811..2c1c017a 100644 --- a/datasette/views/index.py +++ b/datasette/views/index.py @@ -1,9 +1,8 @@ import hashlib import json -from sanic import response - from datasette.utils import CustomJSONEncoder +from datasette.utils.asgi import Response from datasette.version import __version__ from .base import BaseView @@ -104,7 +103,7 @@ class IndexView(BaseView): headers = {} if self.ds.cors: headers["Access-Control-Allow-Origin"] = "*" - return response.HTTPResponse( + return Response( json.dumps({db["name"]: db for db in databases}, cls=CustomJSONEncoder), content_type="application/json; charset=utf-8", headers=headers, diff --git a/datasette/views/special.py b/datasette/views/special.py index 1e0c2032..c4976bb2 100644 --- a/datasette/views/special.py +++ b/datasette/views/special.py @@ -1,5 +1,5 @@ import json -from sanic import response +from datasette.utils.asgi import Response from .base import BaseView @@ -17,7 +17,7 @@ class JsonDataView(BaseView): headers = {} if self.ds.cors: headers["Access-Control-Allow-Origin"] = "*" - return response.HTTPResponse( + return Response( json.dumps(data), content_type="application/json; charset=utf-8", headers=headers, diff --git a/setup.py b/setup.py index 3a8201cb..f66d03da 100644 --- a/setup.py +++ b/setup.py @@ -43,7 +43,6 @@ setup( install_requires=[ "click>=6.7", "click-default-group==1.2", - "Sanic==0.7.0", "Jinja2==2.10.1", "hupper==1.0", "pint==0.8.1",