File support-new-uvicorn.patch of Package python-falcon

From 13da50949751de6683b57221c2ab5f1fdce8eb50 Mon Sep 17 00:00:00 2001
From: Vytautas Liuolia <vytautas.liuolia@gmail.com>
Date: Wed, 13 Mar 2024 16:31:00 +0100
Subject: [PATCH 1/7] chore(asyncio): replace `get_event_loop()` ->
 `get_running_loop()` where applicable

---
 tests/asgi/test_scope.py | 6 +++---
 tests/dump_asgi.py       | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)

Index: falcon-3.1.3/tests/asgi/test_scope.py
===================================================================
--- falcon-3.1.3.orig/tests/asgi/test_scope.py
+++ falcon-3.1.3/tests/asgi/test_scope.py
@@ -70,7 +70,7 @@ def test_supported_asgi_version(version,
     resp_event_collector = testing.ASGIResponseEventCollector()
 
     async def task():
-        coro = asyncio.get_event_loop().create_task(
+        coro = asyncio.get_running_loop().create_task(
             app(scope, req_event_emitter, resp_event_collector)
         )
 
@@ -142,7 +142,7 @@ def test_lifespan_scope_default_version(
     scope = {'type': 'lifespan'}
 
     async def t():
-        t = asyncio.get_event_loop().create_task(
+        t = asyncio.get_running_loop().create_task(
             app(scope, req_event_emitter, resp_event_collector)
         )
 
@@ -196,7 +196,7 @@ def test_lifespan_scope_version(spec_ver
         return
 
     async def t():
-        t = asyncio.get_event_loop().create_task(
+        t = asyncio.get_running_loop().create_task(
             app(scope, req_event_emitter, resp_event_collector)
         )
 
Index: falcon-3.1.3/tests/dump_asgi.py
===================================================================
--- falcon-3.1.3.orig/tests/dump_asgi.py
+++ falcon-3.1.3/tests/dump_asgi.py
@@ -23,5 +23,5 @@ async def app(scope, receive, send):
         }
     )
 
-    loop = asyncio.get_event_loop()
+    loop = asyncio.get_running_loop()
     loop.create_task(_say_hi())
Index: falcon-3.1.3/falcon/util/sync.py
===================================================================
--- falcon-3.1.3.orig/falcon/util/sync.py
+++ falcon-3.1.3/falcon/util/sync.py
@@ -4,7 +4,9 @@ from functools import partial
 from functools import wraps
 import inspect
 import os
+from typing import Awaitable
 from typing import Callable
+from typing import TypeVar
 
 
 __all__ = [
@@ -18,6 +20,42 @@ __all__ = [
 ]
 
 
+Result = TypeVar('Result')
+
+
+class _DummyRunner:
+    def run(self, coro: Awaitable[Result]) -> Result:  # pragma: nocover
+        # NOTE(vytas): Work around get_event_loop deprecation in 3.10 by going
+        #   via get_event_loop_policy(). This should be equivalent for
+        #   async_to_sync's use case as it is currently impossible to invoke
+        #   run_until_complete() from a running loop anyway.
+        return self.get_loop().run_until_complete(coro)
+
+    def get_loop(self) -> asyncio.AbstractEventLoop:  # pragma: nocover
+        return asyncio.get_event_loop_policy().get_event_loop()
+
+    def close(self) -> None:  # pragma: nocover
+        pass
+
+
+class _ActiveRunner:
+    def __init__(self, runner_cls: type):
+        self._runner_cls = runner_cls
+        self._runner = runner_cls()
+
+    # TODO(vytas): This typing is wrong on py311+, but mypy accepts it.
+    #   It doesn't, OTOH, accept any of my ostensibly valid attempts to
+    #   describe it.
+    def __call__(self) -> _DummyRunner:
+        # NOTE(vytas): Sometimes our runner's loop can get picked and consumed
+        #   by other utilities and test methods. If that happens, recreate the runner.
+        if self._runner.get_loop().is_closed():
+            # NOTE(vytas): This condition is never hit on _DummyRunner.
+            self._runner = self._runner_cls()  # pragma: nocover
+        return self._runner
+
+
+_active_runner = _ActiveRunner(getattr(asyncio, 'Runner', _DummyRunner))
 _one_thread_to_rule_them_all = ThreadPoolExecutor(max_workers=1)
 
 
@@ -207,8 +245,13 @@ def async_to_sync(coroutine, *args, **kw
     one will be created.
 
     Warning:
-        This method is very inefficient and is intended primarily for testing
-        and prototyping.
+        Executing async code in this manner is inefficient since it involves
+        synchronization via threading primitives, and is intended primarily for
+        testing, prototyping or compatibility purposes.
+
+    Note:
+        On Python 3.11+, this function leverages a module-wide
+        ``asyncio.Runner``.
 
     Args:
         coroutine: A coroutine function to invoke.
@@ -217,17 +260,7 @@ def async_to_sync(coroutine, *args, **kw
     Keyword Args:
         **kwargs: Additional args are passed through to the coroutine function.
     """
-
-    # TODO(vytas): The canonical way of doing this for simple use cases is
-    #   asyncio.run(), but that would be a breaking change wrt the above
-    #   documented behaviour; breaking enough to break some of our own tests.
-
-    # NOTE(vytas): Work around get_event_loop deprecation in 3.10 by going via
-    #   get_event_loop_policy(). This should be equivalent for async_to_sync's
-    #   use case as it is currently impossible to invoke run_until_complete()
-    #   from a running loop anyway.
-    loop = asyncio.get_event_loop_policy().get_event_loop()
-    return loop.run_until_complete(coroutine(*args, **kwargs))
+    return _active_runner().run(coroutine(*args, **kwargs))
 
 
 def runs_sync(coroutine):
Index: falcon-3.1.3/pyproject.toml
===================================================================
--- falcon-3.1.3.orig/pyproject.toml
+++ falcon-3.1.3/pyproject.toml
@@ -63,7 +63,6 @@ filterwarnings = [
     "ignore:.cgi. is deprecated and slated for removal:DeprecationWarning",
     "ignore:path is deprecated\\. Use files\\(\\) instead:DeprecationWarning",
     "ignore:This process \\(.+\\) is multi-threaded",
-    "ignore:There is no current event loop",
 ]
 testpaths = [
     "tests"
Index: falcon-3.1.3/tests/asgi/test_asgi_servers.py
===================================================================
--- falcon-3.1.3.orig/tests/asgi/test_asgi_servers.py
+++ falcon-3.1.3/tests/asgi/test_asgi_servers.py
@@ -4,6 +4,7 @@ import hashlib
 import os
 import platform
 import random
+import signal
 import subprocess
 import sys
 import time
@@ -27,7 +28,9 @@ _WIN32 = sys.platform.startswith('win')
 _SERVER_HOST = '127.0.0.1'
 _SIZE_1_KB = 1024
 _SIZE_1_MB = _SIZE_1_KB**2
-
+# NOTE(vytas): Windows specific: {Application Exit by CTRL+C}.
+#   The application terminated as a result of a CTRL+C.
+_STATUS_CONTROL_C_EXIT = 0xC000013A
 
 _REQUEST_TIMEOUT = 10
 
@@ -610,7 +613,10 @@ def server_base_url(request):
 
             yield base_url
 
-        assert server.returncode == 0
+        # NOTE(vytas): Starting with 0.29.0, Uvicorn will propagate signal
+        #   values into the return code (which is a good practice in Unix);
+        #   see also https://github.com/encode/uvicorn/pull/1600
+        assert server.returncode in (0, -signal.SIGTERM, _STATUS_CONTROL_C_EXIT)
 
         break
 
openSUSE Build Service is sponsored by