forked from pool/python-falcon
- Add patch support-new-uvicorn.patch: * Support new uvicorn, which now propagates its exit code. OBS-URL: https://build.opensuse.org/package/show/devel:languages:python/python-falcon?expand=0&rev=55
193 lines
6.8 KiB
Diff
193 lines
6.8 KiB
Diff
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
|
|
|