From 15cf0fc0e8967ac580ecdae4b63776565aab9baf Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 26 Jun 2018 10:51:51 +0100 Subject: [PATCH] Request presents 'scope' interface --- README.md | 7 ++++++- starlette/decorators.py | 4 +++- starlette/request.py | 29 +++++++++++++++++++++++------ tests/test_request.py | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f80bdb0e..a8fb92f5 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ the incoming request, rather than accessing the ASGI scope and receive channel d ### Request -Signature: `Request(scope, receive)` +Signature: `Request(scope, receive=None)` ```python class App: @@ -166,6 +166,11 @@ class App: await response(receive, send) ``` +Requests present a mapping interface, so you can use them in the same +way as a `scope`. + +For instance: `request['path']` will return the ASGI path. + #### Method The request method is accessed as `request.method`. diff --git a/starlette/decorators.py b/starlette/decorators.py index 8c575315..6c7969b0 100644 --- a/starlette/decorators.py +++ b/starlette/decorators.py @@ -5,8 +5,10 @@ from starlette.types import ASGIInstance, Receive, Send, Scope def asgi_application(func): def app(scope: Scope) -> ASGIInstance: + request = Request(scope) + async def awaitable(receive: Receive, send: Send) -> None: - request = Request(scope, receive) + request.set_receive_channel(receive) response = func(request) await response(receive, send) diff --git a/starlette/request.py b/starlette/request.py index 25918e04..b677beea 100644 --- a/starlette/request.py +++ b/starlette/request.py @@ -1,19 +1,33 @@ from starlette.datastructures import URL, Headers, QueryParams +from collections.abc import Mapping import json +import typing -class Request: - def __init__(self, scope, receive): +class Request(Mapping): + def __init__(self, scope, receive=None): self._scope = scope self._receive = receive self._stream_consumed = False + def __getitem__(self, key): + return self._scope[key] + + def __iter__(self): + return iter(self._scope) + + def __len__(self): + return len(self._scope) + + def set_receive_channel(self, receive): + self._receive = receive + @property - def method(self): + def method(self) -> str: return self._scope["method"] @property - def url(self): + def url(self) -> URL: if not hasattr(self, "_url"): scheme = self._scope["scheme"] host, port = self._scope["server"] @@ -32,7 +46,7 @@ class Request: return self._url @property - def headers(self): + def headers(self) -> Headers: if not hasattr(self, "_headers"): self._headers = Headers( [ @@ -43,7 +57,7 @@ class Request: return self._headers @property - def query_params(self): + def query_params(self) -> QueryParams: if not hasattr(self, "_query_params"): query_string = self._scope["query_string"].decode() self._query_params = QueryParams(query_string) @@ -57,6 +71,9 @@ class Request: if self._stream_consumed: raise RuntimeError("Stream consumed") + if self._receive is None: + raise RuntimeError("Receive channel has not been made available") + self._stream_consumed = True while True: message = await self._receive() diff --git a/tests/test_request.py b/tests/test_request.py index 8dd2d417..fbbc4cd9 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -157,3 +157,37 @@ def test_request_json(): client = TestClient(app) response = client.post("/", json={"a": "123"}) assert response.json() == {"json": {"a": "123"}} + + +def test_request_scope_interface(): + """ + A Request can be isntantiated with a scope, and presents a `Mapping` + interface. + """ + request = Request({"method": "GET", "path": "/abc/"}) + assert request["method"] == "GET" + assert dict(request) == {"method": "GET", "path": "/abc/"} + assert len(request) == 2 + + +def test_request_without_setting_receive(): + """ + If Request is instantiated without the receive channel, then .body() + is not available. + """ + + def app(scope): + async def asgi(receive, send): + request = Request(scope) + try: + data = await request.json() + except RuntimeError: + data = "Receive channel not available" + response = JSONResponse({"json": data}) + await response(receive, send) + + return asgi + + client = TestClient(app) + response = client.post("/", json={"a": "123"}) + assert response.json() == {"json": "Receive channel not available"} -- 2.47.2