From a000c80d5036822c8324c1cd037fe7d20e5246b5 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 16 Mar 2020 19:47:37 -0700 Subject: [PATCH] await Request(scope, receive).post_vars() method, closes #700 Needed for #698 --- datasette/utils/asgi.py | 21 +++++++++++++++++---- tests/test_utils.py | 20 ++++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/datasette/utils/asgi.py b/datasette/utils/asgi.py index 37196b36..a6c77225 100644 --- a/datasette/utils/asgi.py +++ b/datasette/utils/asgi.py @@ -1,7 +1,7 @@ import json from datasette.utils import RequestParameters from mimetypes import guess_type -from urllib.parse import parse_qs, urlunparse +from urllib.parse import parse_qs, urlunparse, parse_qsl from pathlib import Path from html import escape import re @@ -13,8 +13,9 @@ class NotFound(Exception): class Request: - def __init__(self, scope): + def __init__(self, scope, receive): self.scope = scope + self.receive = receive @property def method(self): @@ -66,6 +67,18 @@ class Request: def raw_args(self): return {key: value[0] for key, value in self.args.items()} + async def post_vars(self): + body = [] + body = b"" + more_body = True + while more_body: + message = await self.receive() + assert message["type"] == "http.request", message + body += message.get("body", b"") + more_body = message.get("more_body", False) + + return dict(parse_qsl(body.decode("utf-8"))) + @classmethod def fake(cls, path_with_query_string, method="GET", scheme="http"): "Useful for constructing Request objects for tests" @@ -79,7 +92,7 @@ class Request: "scheme": scheme, "type": "http", } - return cls(scope) + return cls(scope, None) class AsgiRouter: @@ -171,7 +184,7 @@ class AsgiView: # that were already tucked into scope["url_route"]["kwargs"] by # the router, similar to how Django Channels works: # https://channels.readthedocs.io/en/latest/topics/routing.html#urlrouter - request = Request(scope) + request = Request(scope, receive) self = view.view_class(*class_args, **class_kwargs) response = await self.dispatch_request( request, **scope["url_route"]["kwargs"] diff --git a/tests/test_utils.py b/tests/test_utils.py index df42ea5a..fe5d9a26 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -423,3 +423,23 @@ def test_check_connection_spatialite_raises(): def test_check_connection_passes(): conn = sqlite3.connect(":memory:") utils.check_connection(conn) + + +@pytest.mark.asyncio +async def test_request_post_vars(): + scope = { + "http_version": "1.1", + "method": "POST", + "path": "/", + "raw_path": b"/", + "query_string": b"", + "scheme": "http", + "type": "http", + "headers": [[b"content-type", b"application/x-www-form-urlencoded"]], + } + + async def receive(): + return {"type": "http.request", "body": b"foo=bar&baz=1", "more_body": False} + + request = Request(scope, receive) + assert {"foo": "bar", "baz": "1"} == await request.post_vars()