diff --git a/py314.patch b/py314.patch new file mode 100644 index 0000000..e33f167 --- /dev/null +++ b/py314.patch @@ -0,0 +1,156 @@ +From 208a37a4de8f2fd6697dc3eaf076bac7442f5628 Mon Sep 17 00:00:00 2001 +From: Thomas Grainger +Date: Sat, 14 Oct 2023 11:48:19 +0100 +Subject: [PATCH 01/19] use asyncio.run(..., loop_factory) to avoid + asyncio.set_event_loop_policy + +--- + tests/test_auto_detection.py | 11 ++--- + uvicorn/_compat.py | 86 ++++++++++++++++++++++++++++++++++++ + uvicorn/config.py | 17 +++---- + uvicorn/loops/asyncio.py | 13 ++++-- + uvicorn/loops/auto.py | 18 +++++--- + uvicorn/loops/uvloop.py | 9 +++- + uvicorn/main.py | 4 +- + uvicorn/server.py | 4 +- + uvicorn/workers.py | 6 +-- + 9 files changed, 138 insertions(+), 30 deletions(-) + create mode 100644 uvicorn/_compat.py + +Index: uvicorn-0.36.0/tests/test_auto_detection.py +=================================================================== +--- uvicorn-0.36.0.orig/tests/test_auto_detection.py ++++ uvicorn-0.36.0/tests/test_auto_detection.py +@@ -1,6 +1,7 @@ + import asyncio + import contextlib + import importlib ++import sys + + import pytest + +@@ -12,9 +13,14 @@ from uvicorn.server import ServerState + + try: + importlib.import_module("uvloop") +- expected_loop = "uvloop" # pragma: py-win32 + except ImportError: # pragma: py-not-win32 + expected_loop = "asyncio" ++except AttributeError: # pragma: py-lt-314 # pragma: py-win32 ++ if sys.version_info < (3, 14): # pragma: no cover ++ raise ++ expected_loop = "asyncio" ++else: # pragma: py-win32 # pragma: py-gte-314 ++ expected_loop = "uvloop" + + try: + importlib.import_module("httptools") +Index: uvicorn-0.36.0/uvicorn/_compat.py +=================================================================== +--- uvicorn-0.36.0.orig/uvicorn/_compat.py ++++ uvicorn-0.36.0/uvicorn/_compat.py +@@ -5,6 +5,13 @@ import sys + from collections.abc import Callable, Coroutine + from typing import Any, TypeVar + ++__all__ = ["asyncio_run", "iscoroutinefunction"] ++ ++if sys.version_info >= (3, 14): ++ from inspect import iscoroutinefunction ++else: ++ from asyncio import iscoroutinefunction ++ + _T = TypeVar("_T") + + if sys.version_info >= (3, 12): +Index: uvicorn-0.36.0/uvicorn/config.py +=================================================================== +--- uvicorn-0.36.0.orig/uvicorn/config.py ++++ uvicorn-0.36.0/uvicorn/config.py +@@ -16,6 +16,7 @@ from typing import IO, Any, Callable, Li + + import click + ++from uvicorn._compat import iscoroutinefunction + from uvicorn._types import ASGIApplication + from uvicorn.importer import ImportFromStringError, import_from_string + from uvicorn.logging import TRACE_LOG_LEVEL +Index: uvicorn-0.36.0/uvicorn/loops/auto.py +=================================================================== +--- uvicorn-0.36.0.orig/uvicorn/loops/auto.py ++++ uvicorn-0.36.0/uvicorn/loops/auto.py +@@ -1,17 +1,23 @@ + from __future__ import annotations + + import asyncio ++import sys + from collections.abc import Callable + + +-def auto_loop_factory(use_subprocess: bool = False) -> Callable[[], asyncio.AbstractEventLoop]: ++def auto_loop_factory(use_subprocess: bool = False) -> Callable[[], asyncio.AbstractEventLoop]: # pragma: no cover + try: + import uvloop # noqa + except ImportError: # pragma: no cover +- from uvicorn.loops.asyncio import asyncio_loop_factory as loop_factory +- +- return loop_factory(use_subprocess=use_subprocess) ++ pass ++ except AttributeError: # pragma: no cover ++ if sys.version_info < (3, 14): ++ raise + else: # pragma: no cover + from uvicorn.loops.uvloop import uvloop_loop_factory + + return uvloop_loop_factory(use_subprocess=use_subprocess) ++ ++ from uvicorn.loops.asyncio import asyncio_loop_factory as loop_factory ++ ++ return loop_factory(use_subprocess=use_subprocess) +Index: uvicorn-0.36.0/pyproject.toml +=================================================================== +--- uvicorn-0.36.0.orig/pyproject.toml ++++ uvicorn-0.36.0/pyproject.toml +@@ -25,6 +25,7 @@ classifiers = [ + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ++ "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Internet :: WWW/HTTP", +@@ -160,6 +161,7 @@ exclude_lines = [ + "tests/supervisors/test_multiprocess.py", + ] + "sys_platform != 'win32'" = ["uvicorn/loops/asyncio.py"] ++"sys_version_info >= (3, 14)" = ["uvicorn/loops/uvloop.py"] + + [tool.coverage.coverage_conditional_plugin.rules] + py-win32 = "sys_platform == 'win32'" +@@ -173,3 +175,5 @@ py-gte-310 = "sys_version_info >= (3, 10 + py-lt-310 = "sys_version_info < (3, 10)" + py-gte-311 = "sys_version_info >= (3, 11)" + py-lt-311 = "sys_version_info < (3, 11)" ++py-gte-314 = "sys_version_info >= (3, 14)" ++py-lt-314 = "sys_version_info < (3, 14)" +Index: uvicorn-0.36.0/tests/test_compat.py +=================================================================== +--- uvicorn-0.36.0.orig/tests/test_compat.py ++++ uvicorn-0.36.0/tests/test_compat.py +@@ -1,6 +1,7 @@ + from __future__ import annotations + + import asyncio ++import sys + from asyncio import AbstractEventLoop + + import pytest +@@ -23,7 +24,7 @@ def test_asyncio_run__custom_loop_factor + + + def test_asyncio_run__passing_a_non_awaitable_callback_should_throw_error() -> None: +- with pytest.raises(ValueError): ++ with pytest.raises(TypeError if sys.version_info >= (3, 14) else ValueError): + asyncio_run( + lambda: None, # type: ignore + loop_factory=CustomLoop, diff --git a/python-uvicorn.changes b/python-uvicorn.changes index ad3aab7..3cf26e8 100644 --- a/python-uvicorn.changes +++ b/python-uvicorn.changes @@ -1,3 +1,16 @@ +------------------------------------------------------------------- +Mon Sep 22 13:01:16 UTC 2025 - Markéta Machová + +- Update to 0.36.0 + * Don't include cwd() when non-empty --reload-dirs is passed + * Apply get_client_addr formatting to WebSocket logging + * Add WebSocketsSansIOProtocol + * Refine help message for option --proxy-headers + * Support custom IOLOOPs + * Allow to provide importable string in --http, --ws and --loop +- support-websockets-14+.patch was almost fixed upstream, apply the rest +- Add upstream patch py314.patch to fix build with Python 3.14 + ------------------------------------------------------------------- Mon Aug 25 14:27:42 UTC 2025 - Markéta Machová diff --git a/python-uvicorn.spec b/python-uvicorn.spec index 4e2fb54..bca6784 100644 --- a/python-uvicorn.spec +++ b/python-uvicorn.spec @@ -23,7 +23,7 @@ %endif %{?sle15_python_module_pythons} Name: python-uvicorn -Version: 0.34.2 +Version: 0.36.0 Release: 0 Summary: An Asynchronous Server Gateway Interface server License: BSD-3-Clause @@ -31,6 +31,8 @@ URL: https://github.com/encode/uvicorn Source: https://github.com/encode/uvicorn/archive/%{version}.tar.gz#/uvicorn-%{version}.tar.gz # PATCH-FIX-OPENSUSE Ignore the large amount of DeprecationWarnings that websockets 14 gave us Patch0: support-websockets-14+.patch +# PATCH-FIX-UPSTREAM small part of https://github.com/Kludex/uvicorn/pull/2548 test on 3.14 +Patch1: py314.patch BuildRequires: %{python_module base >= 3.8} BuildRequires: %{python_module hatchling} BuildRequires: %{python_module pip} diff --git a/support-websockets-14+.patch b/support-websockets-14+.patch index 0581c0a..606f623 100644 --- a/support-websockets-14+.patch +++ b/support-websockets-14+.patch @@ -1,15 +1,11 @@ -Index: uvicorn-0.34.2/pyproject.toml +Index: uvicorn-0.36.0/pyproject.toml =================================================================== ---- uvicorn-0.34.2.orig/pyproject.toml -+++ uvicorn-0.34.2/pyproject.toml -@@ -92,6 +92,11 @@ filterwarnings = [ - "ignore:Uvicorn's native WSGI implementation is deprecated.*:DeprecationWarning", - "ignore: 'cgi' is deprecated and slated for removal in Python 3.13:DeprecationWarning", - "ignore: remove second argument of ws_handler:DeprecationWarning:websockets", -+ # Websockets 14+ -+ "ignore: websockets.server.WebSocketServerProtocol is deprecated:DeprecationWarning", -+ "ignore: websockets.legacy is deprecated.*:DeprecationWarning", -+ "ignore: websockets.client.connect is deprecated:DeprecationWarning", +--- uvicorn-0.36.0.orig/pyproject.toml ++++ uvicorn-0.36.0/pyproject.toml +@@ -130,6 +130,7 @@ filterwarnings = [ + "ignore: websockets.legacy is deprecated.*:DeprecationWarning", + "ignore: websockets.server.WebSocketServerProtocol is deprecated.*:DeprecationWarning", + "ignore: websockets.client.connect is deprecated.*:DeprecationWarning", + "ignore: websockets.exceptions.InvalidStatusCode is deprecated:DeprecationWarning", ] diff --git a/uvicorn-0.34.2.tar.gz b/uvicorn-0.34.2.tar.gz deleted file mode 100644 index 7bfd42f..0000000 --- a/uvicorn-0.34.2.tar.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ea0a4b037cbb5135344b52f21eb286630b8d5d4be7661981860f3dd11e6fdaa0 -size 709898 diff --git a/uvicorn-0.36.0.tar.gz b/uvicorn-0.36.0.tar.gz new file mode 100644 index 0000000..1cbff89 --- /dev/null +++ b/uvicorn-0.36.0.tar.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dcff1c0826862d125b5b5bf45fe1d03241346e631802d29454792c6c5107ca8d +size 815375