| 
1 | 1 | import threading  | 
2 | 2 | from functools import partial  | 
3 | 3 | 
 
  | 
 | 4 | +import django  | 
4 | 5 | from django.contrib.staticfiles.handlers import ASGIStaticFilesHandler  | 
5 | 6 | from django.db import connections  | 
6 | 7 | from django.db.backends.base.creation import TEST_DATABASE_PREFIX  | 
7 | 8 | from django.test.testcases import TransactionTestCase  | 
8 | 9 | from django.test.utils import modify_settings  | 
9 | 10 | from django.utils.functional import classproperty  | 
 | 11 | +from django.utils.version import PY311  | 
10 | 12 | 
 
  | 
11 | 13 | from channels.routing import get_default_application  | 
12 | 14 | 
 
  | 
 | 15 | +if not (PY311 or django.VERSION >= (5, 1)):  | 
 | 16 | +    # Backport of unittest.case._enter_context() from Python 3.11.  | 
 | 17 | +    def _enter_context(cm, addcleanup):  | 
 | 18 | +        # Look up the special methods on the type to match the with statement.  | 
 | 19 | +        cls = type(cm)  | 
 | 20 | +        try:  | 
 | 21 | +            enter = cls.__enter__  | 
 | 22 | +            exit = cls.__exit__  | 
 | 23 | +        except AttributeError:  | 
 | 24 | +            raise TypeError(  | 
 | 25 | +                f"'{cls.__module__}.{cls.__qualname__}' object does not support the "  | 
 | 26 | +                f"context manager protocol"  | 
 | 27 | +            ) from None  | 
 | 28 | +        result = enter(cm)  | 
 | 29 | +        addcleanup(exit, cm, None, None, None)  | 
 | 30 | +        return result  | 
 | 31 | + | 
13 | 32 | 
 
  | 
14 | 33 | def make_application(*, static_wrapper):  | 
15 | 34 |     # Module-level function for pickle-ability  | 
@@ -124,6 +143,12 @@ class ChannelsLiveServerTestCase(TransactionTestCase):  | 
124 | 143 |     static_handler = ASGIStaticFilesHandler  | 
125 | 144 |     serve_static = True  | 
126 | 145 | 
 
  | 
 | 146 | +    if not PY311:  | 
 | 147 | +        # Backport of unittest.TestCase.enterClassContext() from Python 3.11.  | 
 | 148 | +        @classmethod  | 
 | 149 | +        def enterClassContext(cls, cm):  | 
 | 150 | +            return _enter_context(cm, cls.addClassCleanup)  | 
 | 151 | + | 
127 | 152 |     @classproperty  | 
128 | 153 |     def live_server_url(cls):  | 
129 | 154 |         return "http://%s:%s" % (cls.host, cls.server_thread.port)  | 
@@ -193,11 +218,6 @@ def _terminate_thread(cls):  | 
193 | 218 |         for conn in cls.server_thread.connections_override.values():  | 
194 | 219 |             conn.dec_thread_sharing()  | 
195 | 220 | 
 
  | 
196 |  | -    @classmethod  | 
197 |  | -    def tearDownClass(cls):  | 
198 |  | -        # The cleanup is now handled by addClassCleanup in _start_server_thread  | 
199 |  | -        super().tearDownClass()  | 
200 |  | - | 
201 | 221 |     @classmethod  | 
202 | 222 |     def _is_in_memory_db(cls, connection):  | 
203 | 223 |         """  | 
 | 
0 commit comments