Repository: encode/uvicorn

Issue:
HTTP Scope missing fields
https://asgi.readthedocs.io/en/latest/specs/www.html#connection-scope

>The connection scope contains:
    ...
    asgi["version"] (Unicode string) – Version of the ASGI spec.
    asgi["spec_version"] (Unicode string) – Version of the ASGI HTTP spec this server understands; one of "2.0" or "2.1". Optional; if missing assume 2.0.

I sent a request through uvicorn and noticed these fields are missing. Additionally searching the code for "spec_version" does not return any hits so I think these fields are not being handled. Potentially these were added in a later ASGI version that is not supported or I may be misunderstanding what the ASGI docs mean.

Discussion:
euri10: @florimondmanca should I write a test for it @florimondmanca ? seems a little bit useless to me but if you want
florimondmanca: Yes ideally let's add a test to prevent regressions. Surely there's somewhere we test the content of `scope`?
euri10: I put the latest master changes, which caught some small stuff with flake8 !

I think it should be ok now, would be great if you have time to take a look at it @florimondmanca 

Patch:
diff --git a/uvicorn/config.py b/uvicorn/config.py
index 331f738..c35b567 100644
--- a/uvicorn/config.py
+++ b/uvicorn/config.py
@@ -200,6 +200,10 @@ class Config:
         else:
             self.forwarded_allow_ips = forwarded_allow_ips
 
+    @property
+    def asgi_version(self) -> str:
+        return {"asgi2": "2.0", "asgi3": "3.0"}[self.interface]
+
     @property
     def is_ssl(self) -> bool:
         return bool(self.ssl_keyfile or self.ssl_certfile)
diff --git a/uvicorn/protocols/http/h11_impl.py b/uvicorn/protocols/http/h11_impl.py
index a1d87eb..5e15d05 100644
--- a/uvicorn/protocols/http/h11_impl.py
+++ b/uvicorn/protocols/http/h11_impl.py
@@ -188,6 +188,10 @@ class H11Protocol(asyncio.Protocol):
                 raw_path, _, query_string = event.target.partition(b"?")
                 self.scope = {
                     "type": "http",
+                    "asgi": {
+                        "version": self.config.asgi_version,
+                        "spec_version": "2.1",
+                    },
                     "http_version": event.http_version.decode("ascii"),
                     "server": self.server,
                     "client": self.client,
diff --git a/uvicorn/protocols/http/httptools_impl.py b/uvicorn/protocols/http/httptools_impl.py
index 6e53131..7a60733 100644
--- a/uvicorn/protocols/http/httptools_impl.py
+++ b/uvicorn/protocols/http/httptools_impl.py
@@ -214,6 +214,7 @@ class HttpToolsProtocol(asyncio.Protocol):
         self.headers = []
         self.scope = {
             "type": "http",
+            "asgi": {"version": self.config.asgi_version, "spec_version": "2.1"},
             "http_version": "1.1",
             "server": self.server,
             "client": self.client,
diff --git a/uvicorn/protocols/websockets/websockets_impl.py b/uvicorn/protocols/websockets/websockets_impl.py
index 53f2d42..37e2cb7 100644
--- a/uvicorn/protocols/websockets/websockets_impl.py
+++ b/uvicorn/protocols/websockets/websockets_impl.py
@@ -100,6 +100,7 @@ class WebSocketProtocol(websockets.WebSocketServerProtocol):
 
         self.scope = {
             "type": "websocket",
+            "asgi": {"version": self.config.asgi_version, "spec_version": "2.1"},
             "scheme": self.scheme,
             "server": self.server,
             "client": self.client,
diff --git a/uvicorn/protocols/websockets/wsproto_impl.py b/uvicorn/protocols/websockets/wsproto_impl.py
index 5081c87..307ab9f 100644
--- a/uvicorn/protocols/websockets/wsproto_impl.py
+++ b/uvicorn/protocols/websockets/wsproto_impl.py
@@ -128,6 +128,7 @@ class WSProtocol(asyncio.Protocol):
         raw_path, _, query_string = event.target.partition("?")
         self.scope = {
             "type": "websocket",
+            "asgi": {"version": self.config.asgi_version, "spec_version": "2.1"},
             "http_version": "1.1",
             "scheme": self.scheme,
             "server": self.server,


Test patch:
diff --git a/tests/protocols/test_http.py b/tests/protocols/test_http.py
index 3900e3b..891a259 100644
--- a/tests/protocols/test_http.py
+++ b/tests/protocols/test_http.py
@@ -674,3 +674,29 @@ def test_supported_upgrade_request(protocol_cls):
     protocol.data_received(UPGRADE_REQUEST)
 
     assert b"HTTP/1.1 426 " in protocol.transport.buffer
+
+
+async def asgi3app(scope, receive, send):
+    pass
+
+
+def asgi2app(scope):
+    async def asgi(receive, send):
+        pass
+
+    return asgi
+
+
+asgi_scope_data = [
+    (asgi3app, {"version": "3.0", "spec_version": "2.1"}),
+    (asgi2app, {"version": "2.0", "spec_version": "2.1"}),
+]
+
+
+@pytest.mark.parametrize("asgi2or3_app, expected_scopes", asgi_scope_data)
+@pytest.mark.parametrize("protocol_cls", HTTP_PROTOCOLS)
+def test_scopes(asgi2or3_app, expected_scopes, protocol_cls):
+    protocol = get_connected_protocol(asgi2or3_app, protocol_cls,)
+    protocol.data_received(SIMPLE_GET_REQUEST)
+    protocol.loop.run_one()
+    assert expected_scopes == protocol.scope.get("asgi")
diff --git a/tests/test_config.py b/tests/test_config.py
index 1f3d34e..c1f763b 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -66,3 +66,19 @@ def test_ssl_config(certfile_and_keyfile):
     config.load()
 
     assert config.is_ssl is True
+
+
+def asgi2_app(scope):
+    async def asgi(receive, send):
+        pass
+
+    return asgi
+
+
+@pytest.mark.parametrize(
+    "app, expected_interface", [(asgi_app, "3.0",), (asgi2_app, "2.0",)]
+)
+def test_asgi_version(app, expected_interface):
+    config = Config(app=app)
+    config.load()
+    assert config.asgi_version == expected_interface