diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index 6f7e2db8f..7646f10c9 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] os: [windows-latest, ubuntu-latest, macos-latest] steps: - uses: "actions/checkout@v4" diff --git a/pyproject.toml b/pyproject.toml index 6f809030e..0a8996666 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ dynamic = ["version"] description = "The lightning-fast ASGI server." readme = "README.md" license = "BSD-3-Clause" -requires-python = ">=3.8" +requires-python = ">=3.9" authors = [ { name = "Tom Christie", email = "tom@tomchristie.com" }, { name = "Marcelo Trylesinski", email = "marcelotryle@gmail.com" }, @@ -20,7 +20,6 @@ classifiers = [ "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -38,7 +37,7 @@ dependencies = [ [project.optional-dependencies] standard = [ - "colorama>=0.4;sys_platform == 'win32'", + "colorama>=0.4; sys_platform == 'win32'", "httptools>=0.6.3", "python-dotenv>=0.13", "PyYAML>=5.1", @@ -127,8 +126,6 @@ py-win32 = "sys_platform == 'win32'" py-not-win32 = "sys_platform != 'win32'" py-linux = "sys_platform == 'linux'" py-darwin = "sys_platform == 'darwin'" -py-gte-38 = "sys_version_info >= (3, 8)" -py-lt-38 = "sys_version_info < (3, 8)" py-gte-39 = "sys_version_info >= (3, 9)" py-lt-39 = "sys_version_info < (3, 9)" py-gte-310 = "sys_version_info >= (3, 10)" diff --git a/requirements.txt b/requirements.txt index b3a464c0b..e26e6b3c5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,11 +20,9 @@ pytest-mock==3.14.0 mypy==1.13.0 types-click==7.1.8 types-pyyaml==6.0.12.20240917 -trustme==1.1.0; python_version < '3.9' -trustme==1.2.0; python_version >= '3.9' +trustme==1.2.0 cryptography==44.0.0 -coverage==7.6.1; python_version < '3.9' -coverage==7.6.9; python_version >= '3.9' +coverage==7.6.9 coverage-conditional-plugin==0.9.0 httpx==0.28.1 diff --git a/tests/middleware/test_wsgi.py b/tests/middleware/test_wsgi.py index 69a40db8c..6003f27f9 100644 --- a/tests/middleware/test_wsgi.py +++ b/tests/middleware/test_wsgi.py @@ -2,7 +2,8 @@ import io import sys -from typing import AsyncGenerator, Callable +from collections.abc import AsyncGenerator +from typing import Callable import a2wsgi import httpx diff --git a/tests/supervisors/test_reload.py b/tests/supervisors/test_reload.py index c4ad76acb..44eb9970b 100644 --- a/tests/supervisors/test_reload.py +++ b/tests/supervisors/test_reload.py @@ -4,10 +4,11 @@ import signal import socket import sys +from collections.abc import Generator from pathlib import Path from threading import Thread from time import sleep -from typing import Callable, Generator +from typing import Callable import pytest from pytest_mock import MockerFixture diff --git a/tests/test_cli.py b/tests/test_cli.py index 8c54e6d19..303ae6feb 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -3,9 +3,9 @@ import os import platform import sys +from collections.abc import Iterator from pathlib import Path from textwrap import dedent -from typing import Iterator from unittest import mock import pytest diff --git a/tests/test_server.py b/tests/test_server.py index c650be290..d14206eb5 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -5,7 +5,9 @@ import logging import signal import sys -from typing import Callable, ContextManager, Generator +from collections.abc import Generator +from contextlib import AbstractContextManager +from typing import Callable import httpx import pytest @@ -62,7 +64,7 @@ async def app(scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable @pytest.mark.parametrize("exception_signal", signals) @pytest.mark.parametrize("capture_signal", signal_captures) async def test_server_interrupt( - exception_signal: signal.Signals, capture_signal: Callable[[signal.Signals], ContextManager[None]] + exception_signal: signal.Signals, capture_signal: Callable[[signal.Signals], AbstractContextManager[None]] ): # pragma: py-win32 """Test interrupting a Server that is run explicitly inside asyncio""" diff --git a/uvicorn/_types.py b/uvicorn/_types.py index 8c8065ae3..c927cc11d 100644 --- a/uvicorn/_types.py +++ b/uvicorn/_types.py @@ -32,20 +32,8 @@ import sys import types -from typing import ( - Any, - Awaitable, - Callable, - Iterable, - Literal, - MutableMapping, - Optional, - Protocol, - Tuple, - Type, - TypedDict, - Union, -) +from collections.abc import Awaitable, Iterable, MutableMapping +from typing import Any, Callable, Literal, Optional, Protocol, TypedDict, Union if sys.version_info >= (3, 11): # pragma: py-lt-311 from typing import NotRequired @@ -54,8 +42,8 @@ # WSGI Environ = MutableMapping[str, Any] -ExcInfo = Tuple[Type[BaseException], BaseException, Optional[types.TracebackType]] -StartResponse = Callable[[str, Iterable[Tuple[str, str]], Optional[ExcInfo]], None] +ExcInfo = tuple[type[BaseException], BaseException, Optional[types.TracebackType]] +StartResponse = Callable[[str, Iterable[tuple[str, str]], Optional[ExcInfo]], None] WSGIApp = Callable[[Environ, StartResponse], Union[Iterable[bytes], BaseException]] @@ -281,7 +269,7 @@ def __init__(self, scope: Scope) -> None: ... # pragma: no cover async def __call__(self, receive: ASGIReceiveCallable, send: ASGISendCallable) -> None: ... # pragma: no cover -ASGI2Application = Type[ASGI2Protocol] +ASGI2Application = type[ASGI2Protocol] ASGI3Application = Callable[ [ Scope, diff --git a/uvicorn/config.py b/uvicorn/config.py index b08a8426b..664d1918f 100644 --- a/uvicorn/config.py +++ b/uvicorn/config.py @@ -9,9 +9,10 @@ import socket import ssl import sys +from collections.abc import Awaitable from configparser import RawConfigParser from pathlib import Path -from typing import IO, Any, Awaitable, Callable, Literal +from typing import IO, Any, Callable, Literal import click diff --git a/uvicorn/middleware/wsgi.py b/uvicorn/middleware/wsgi.py index 078de1af0..2193e8194 100644 --- a/uvicorn/middleware/wsgi.py +++ b/uvicorn/middleware/wsgi.py @@ -6,7 +6,7 @@ import sys import warnings from collections import deque -from typing import Iterable +from collections.abc import Iterable from uvicorn._types import ( ASGIReceiveCallable, diff --git a/uvicorn/protocols/websockets/websockets_impl.py b/uvicorn/protocols/websockets/websockets_impl.py index af66c29b3..cd6c54f35 100644 --- a/uvicorn/protocols/websockets/websockets_impl.py +++ b/uvicorn/protocols/websockets/websockets_impl.py @@ -3,7 +3,8 @@ import asyncio import http import logging -from typing import Any, Literal, Optional, Sequence, cast +from collections.abc import Sequence +from typing import Any, Literal, Optional, cast from urllib.parse import unquote import websockets diff --git a/uvicorn/server.py b/uvicorn/server.py index f14026f16..cca2e850c 100644 --- a/uvicorn/server.py +++ b/uvicorn/server.py @@ -10,9 +10,10 @@ import sys import threading import time +from collections.abc import Generator, Sequence from email.utils import formatdate from types import FrameType -from typing import TYPE_CHECKING, Generator, Sequence, Union +from typing import TYPE_CHECKING, Union import click @@ -284,10 +285,7 @@ async def shutdown(self, sockets: list[socket.socket] | None = None) -> None: len(self.server_state.tasks), ) for t in self.server_state.tasks: - if sys.version_info < (3, 9): # pragma: py-gte-39 - t.cancel() - else: # pragma: py-lt-39 - t.cancel(msg="Task cancelled, timeout graceful shutdown exceeded") + t.cancel(msg="Task cancelled, timeout graceful shutdown exceeded") # Send the lifespan shutdown event, and wait for application shutdown. if not self.force_exit: diff --git a/uvicorn/supervisors/basereload.py b/uvicorn/supervisors/basereload.py index f07ca3912..4df50af33 100644 --- a/uvicorn/supervisors/basereload.py +++ b/uvicorn/supervisors/basereload.py @@ -5,10 +5,11 @@ import signal import sys import threading +from collections.abc import Iterator from pathlib import Path from socket import socket from types import FrameType -from typing import Callable, Iterator +from typing import Callable import click diff --git a/uvicorn/supervisors/statreload.py b/uvicorn/supervisors/statreload.py index 70d0a6d5c..bdcdaa0bf 100644 --- a/uvicorn/supervisors/statreload.py +++ b/uvicorn/supervisors/statreload.py @@ -1,9 +1,10 @@ from __future__ import annotations import logging +from collections.abc import Iterator from pathlib import Path from socket import socket -from typing import Callable, Iterator +from typing import Callable from uvicorn.config import Config from uvicorn.supervisors.basereload import BaseReload