Repository: django/channels
Pull request #1541 (2020-10-26 19:42:22)

3.0 deprecations
Refs #1527


Patch:
diff --git a/channels/http.py b/channels/http.py
index ebfae13..b668973 100644
--- a/channels/http.py
+++ b/channels/http.py
@@ -4,6 +4,7 @@ import logging
 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
index fbdcc47..fa9a9aa 100644
--- 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
index ba1bdc5..7eb8494 100644
--- 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):
diff --git a/docs/releases/3.0.0.rst b/docs/releases/3.0.0.rst
index 10a8a4c..c13be81 100644
--- a/docs/releases/3.0.0.rst
+++ b/docs/releases/3.0.0.rst
@@ -1,12 +1,41 @@
 3.0.0 Release Notes
 ===================
 
+The Channels 3 update brings Channels into line with Django's own async ASGI
+support, introduced with Django 3.0.
+
+Channels now integrated with Django's async HTTP handling, whilst continuing to
+give support for WebSockets, and other exciting consumer types.
+
+Channels 3 supports Django 3.x, and beyond, as well continuing to the Django
+2.2 LTS. We will support Django 2.2 at least until the Django 3.2 LTS is
+released, yet may drop support after that time but before Django 2.2 is
+officially end-of- life.
+
+Likewise, we support Python 3.6+ but we **strongly advise** you to update to
+the latest Python versions, so 3.9 at the time of release.
+
+In both our Django and Python support, we reflect the reality that async Python
+and async Django are still both rapidly evolving. Many issue simply disappear
+if you update. If you want to be doing async you should make sure you're on the
+latest versions.
+
+The highlight of this release is the upgrade to ASGI v3, and the integration
+with Django's ASGI support that it entails. There are also two additional
+deprecations that you will need to deal with if you are updating an existing
+application.
+
+
 Update to ASGI 3
 ----------------
 
-* Consumers are *single-callables*, taking the scope at the beginning of the
-  request. You'll need to update init and call **if you implemented** these yourself.
-  (Otherwise you should be good.)
+* Consumers are now ASGI 3 *single-callables*::
+
+    application(scope, receive, send)
+
+  For generic consumers this change should be largely transparent, but you will
+  need to update ``__init__()`` (no longer taking the scope) and ``__call__()``
+  (now taking the scope) **if you implemented** these yourself.
 
 * Consumers now have an ``as_asgi()`` class method you need to call when
   setting up your routing::
@@ -15,24 +44,35 @@ Update to ASGI 3
       re_path(r'ws/chat/(?P<room_name>\w+)/$', consumers.ChatConsumer.as_asgi()),
     ]
 
-  This returns an ASGI application that will instantiate the consumer per-request.
-
-  It's similar to Django's ``as_view()``, which serves the same purpose. You
+  This returns an ASGI application that will instantiate the consumer
+  per-request. It's similar to Django's ``as_view()``, which serves the same purpose. You
   can pass ``initkwargs`` if your consumer requires them.
 
 * Middleware will also need to be updated to ASGI v3 signature.
 
-Other Bits
-----------
+Deprecations
+------------
 
-* Built-in `AsgiHandler` is deprecated. Update to Django 3.0+ and use its version.
+* Using ``ProtocolTypeRouter`` without an explicit ``"http"`` key is now
+  deprecated.
 
-* Explicitly specify `"http"` in ProtocolTypeRouter. (Deprecate auto usage.)
+  Following Django conventions, your entry point script should be named
+  ``asgi.py``, and you should use Django's ``get_asgi_application()``, that is
+  used by Django's default ``asgi.py`` template to route the ``"http"``
+  handler::
 
-* Update your `routing.py` to an `asgi.py`
+      from django.core.asgi import get_asgi_application
 
-* Dropped Python 3.5
+      application = ProtocolTypeRouter(
+          "http": get_asgi_application()
+          # Other protocols here.
+      )
 
-* Added support for Python 3.9
+  Once the deprecation is removed, when we drop support for Django 2.2, not
+  specifying an ``"http"`` key will mean that your application will not handle
+  HTTP requests.
 
-* Will require Daphne v???
+* The Channels built-in HTTP protocol ``AsgiHandler`` is also deprecated. You
+  should update to Django 3.0 or higher and use Django's
+  ``get_asgi_application()``. Channel's ``AsgiHandler`` will be removed when we
+  drop support for Django 2.2.


Test patch:
diff --git a/tests/test_deprecations.py b/tests/test_deprecations.py
new file mode 100644
index 0000000..39a33f0
--- /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
index a0beecc..99131e0 100644
--- 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
index 005be67..5c1c028 100644
--- a/tests/test_http.py
+++ b/tests/test_http.py
@@ -258,6 +258,7 @@ class MockHandler(AsgiHandler):
 
 @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 test_sessions():
 
 
 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
index 5f74239..af1b5e0 100644
--- a/tests/test_routing.py
+++ b/tests/test_routing.py
@@ -20,6 +20,7 @@ class MockApplication:
 
 
 @pytest.mark.asyncio
+@pytest.mark.filterwarnings("ignore::DeprecationWarning")
 async def test_protocol_type_router():
     """
     Tests the ProtocolTypeRouter