]> git.ipfire.org Git - thirdparty/starlette.git/commitdiff
Request presents 'scope' interface 8/head
authorTom Christie <tom@tomchristie.com>
Tue, 26 Jun 2018 09:51:51 +0000 (10:51 +0100)
committerTom Christie <tom@tomchristie.com>
Tue, 26 Jun 2018 09:51:51 +0000 (10:51 +0100)
README.md
starlette/decorators.py
starlette/request.py
tests/test_request.py

index f80bdb0e3717971a41cf8d9f2130b10ad3df0f8b..a8fb92f5c2bbe093232dfe8cc75dd1c55ffcc08a 100644 (file)
--- 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`.
index 8c575315716ad59b1153d93d55b39ca1880a25af..6c7969b073c430d3acd8435fff7ac5f5a49086fa 100644 (file)
@@ -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)
 
index 25918e04a7750ca50a4401973a064dbb18b3d007..b677beeaaff006ea745fc26748c5f890d7d5f9ad 100644 (file)
@@ -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()
index 8dd2d417ecd35e64d13366aeac2f41983f1157e2..fbbc4cd9e7c16d45c4b12430bdba294c5739f6dd 100644 (file)
@@ -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"}