### Request
-Signature: `Request(scope, receive)`
+Signature: `Request(scope, receive=None)`
```python
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`.
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)
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"]
return self._url
@property
- def headers(self):
+ def headers(self) -> Headers:
if not hasattr(self, "_headers"):
self._headers = Headers(
[
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)
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()
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"}