Repository: django/channels

Issue:
Intermittent test failure on Python 3.8
e.g. https://github.com/django/channels/runs/1309406596?check_suite_focus=true#step:5:200

Added in: https://github.com/django/channels/commit/b285e1e29bba9c599cc607476af59cdf0114fddc
For the `as_asgi()` classmethod. 

Adjusted in: https://github.com/django/channels/commit/6362831502b0bd982a3d916ad60274988cbfb7e5
Was hoping `id()` would be more reliable than `repr()`. 

Need to dig in and see if there's a genuine issue here, or if it's just an artefact of the test, and if so why. 

@adamchainz — If you were to have a cycle, I'd be grateful for the eyes here. 🤹 

* Is the `as_asgi()` method sound? 
* Is the test flawed? 
   * How to best check two separate instances are used (if not this way)? 
   * It only comes up on Python 3.8. What's that about? 
        * Actions history: all the recent failures are this. https://github.com/django/channels/actions


Discussion:
Perahps store `time.time()` in `__init__` and use that as the unique identifier? Since `id()` is the memory location, it seems reasonable the same ID could be reused if an object is deallocated and another allocated immediately after.
> Since id() is the memory location... 

Yeah, that was the problem with `repr()`, but I've just done the same thing then. 🤔

> Perhaps store time.time()

OK. Thanks! I shall give that a run. 

👍 

Resolving patch:
diff --git a/channels/http.py b/channels/http.py
--- a/channels/http.py
+++ b/channels/http.py
@@ -4,6 +4,7 @@
 import sys
 import tempfile
 import traceback
+import warnings
 
 from asgiref.sync import async_to_sync, sync_to_async
 from django import http
@@ -169,6 +170,11 @@ class AsgiHandler(base.BaseHandler):
     chunk_size = 512 * 1024
 
     def __init__(self):
+        msg = (
+            "Channel's inbuilt http protocal AsgiHandler is deprecated. "
+            "Use Django's get_asgi_application() instead."
+        )
+        warnings.warn(msg, DeprecationWarning, stacklevel=2)
         super(AsgiHandler, self).__init__()
         self.load_middleware()
 
diff --git a/channels/layers.py b/channels/layers.py
--- a/channels/layers.py
+++ b/channels/layers.py
@@ -1,5 +1,3 @@
-from __future__ import unicode_literals
-
 import asyncio
 import fnmatch
 import random
diff --git a/channels/routing.py b/channels/routing.py
--- a/channels/routing.py
+++ b/channels/routing.py
@@ -1,6 +1,5 @@
-from __future__ import unicode_literals
-
 import importlib
+import warnings
 
 from asgiref.compatibility import guarantee_single_callable
 from django.conf import settings
@@ -38,6 +37,20 @@ def get_default_application():
     return value
 
 
+DEPRECATION_MSG = """
+Using ProtocolTypeRouter without an explicit "http" key is deprecated.
+Given that you have not passed the "http" you likely should use Django's
+get_asgi_application():
+
+    from django.core.asgi import get_asgi_application
+
+    application = ProtocolTypeRouter(
+        "http": get_asgi_application()
+        # Other protocols here.
+    )
+"""
+
+
 class ProtocolTypeRouter:
     """
     Takes a mapping of protocol type names to other Application instances,
@@ -47,6 +60,7 @@ class ProtocolTypeRouter:
     def __init__(self, application_mapping):
         self.application_mapping = application_mapping
         if "http" not in self.application_mapping:
+            warnings.warn(DEPRECATION_MSG, DeprecationWarning, stacklevel=2)
             self.application_mapping["http"] = AsgiHandler()
 
     async def __call__(self, scope, receive, send):


Test patch:
diff --git a/tests/test_deprecations.py b/tests/test_deprecations.py
new file mode 100644
--- /dev/null
+++ b/tests/test_deprecations.py
@@ -0,0 +1,14 @@
+import pytest
+
+from channels.http import AsgiHandler
+from channels.routing import ProtocolTypeRouter
+
+
+def test_automatical_http_protocol_registration_deprecation():
+    with pytest.warns(DeprecationWarning):
+        ProtocolTypeRouter({})
+
+
+def test_asgi_handler_deprecation():
+    with pytest.warns(DeprecationWarning, match="AsgiHandler is deprecated"):
+        AsgiHandler()
diff --git a/tests/test_generic_http.py b/tests/test_generic_http.py
--- a/tests/test_generic_http.py
+++ b/tests/test_generic_http.py
@@ -1,4 +1,5 @@
 import json
+import time
 
 import pytest
 
@@ -44,8 +45,12 @@ async def test_per_scope_consumers():
     """
 
     class TestConsumer(AsyncHttpConsumer):
+        def __init__(self):
+            super().__init__()
+            self.time = time.time()
+
         async def handle(self, body):
-            body = f"{self.__class__.__name__} {id(self)}"
+            body = f"{self.__class__.__name__} {id(self)} {self.time}"
 
             await self.send_response(
                 200,
diff --git a/tests/test_http.py b/tests/test_http.py
--- a/tests/test_http.py
+++ b/tests/test_http.py
@@ -258,6 +258,7 @@ def get_response(self, request):
 
 @pytest.mark.django_db
 @pytest.mark.asyncio
+@pytest.mark.filterwarnings("ignore::DeprecationWarning")
 async def test_handler_basic():
     """
     Tests very basic request handling, no body.
@@ -274,6 +275,7 @@ async def test_handler_basic():
 
 @pytest.mark.django_db
 @pytest.mark.asyncio
+@pytest.mark.filterwarnings("ignore::DeprecationWarning")
 async def test_handler_body_single():
     """
     Tests request handling with a single-part body
@@ -292,6 +294,7 @@ async def test_handler_body_single():
 
 @pytest.mark.django_db
 @pytest.mark.asyncio
+@pytest.mark.filterwarnings("ignore::DeprecationWarning")
 async def test_handler_body_multiple():
     """
     Tests request handling with a multi-part body
@@ -314,6 +317,7 @@ async def test_handler_body_multiple():
 
 @pytest.mark.django_db
 @pytest.mark.asyncio
+@pytest.mark.filterwarnings("ignore::DeprecationWarning")
 async def test_handler_body_ignore_extra():
     """
     Tests request handling ignores anything after more_body: False
@@ -372,6 +376,7 @@ async def http_request(self, event):
 
 
 class MiddlewareTests(unittest.TestCase):
+    @pytest.mark.filterwarnings("ignore::DeprecationWarning")
     def test_middleware_caching(self):
         """
         Tests that middleware is only loaded once
diff --git a/tests/test_routing.py b/tests/test_routing.py
--- a/tests/test_routing.py
+++ b/tests/test_routing.py
@@ -20,6 +20,7 @@ async def __call__(self, scope, receive, send):
 
 
 @pytest.mark.asyncio
+@pytest.mark.filterwarnings("ignore::DeprecationWarning")
 async def test_protocol_type_router():
     """
     Tests the ProtocolTypeRouter