diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index fac946039..14567a26e 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -14,7 +14,7 @@ jobs: strategy: 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"] steps: - uses: "actions/checkout@v4" diff --git a/pyproject.toml b/pyproject.toml index 0712fe5da..51acf7355 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ dynamic = ["version"] description = "The little ASGI library that shines." readme = "README.md" license = "BSD-3-Clause" -requires-python = ">=3.8" +requires-python = ">=3.9" authors = [{ name = "Tom Christie", email = "tom@tomchristie.com" }] classifiers = [ "Development Status :: 3 - Alpha", @@ -18,7 +18,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", @@ -70,7 +69,7 @@ combine-as-imports = true [tool.mypy] strict = true ignore_missing_imports = true -python_version = "3.8" +python_version = "3.9" [[tool.mypy.overrides]] module = "starlette.testclient.*" diff --git a/requirements.txt b/requirements.txt index fc147263d..83c34fc9c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,11 +14,10 @@ pytest==8.3.4 trio==0.27.0 # Documentation -black==24.10.0; python_version >= "3.9" +black==24.10.0 mkdocs==1.6.1 mkdocs-material==9.5.47 -mkdocstrings-python<1.12.0; python_version < "3.9" -mkdocstrings-python==1.12.2; python_version >= "3.9" +mkdocstrings-python==1.12.2 # Packaging build==1.2.2.post1 diff --git a/starlette/_compat.py b/starlette/_compat.py deleted file mode 100644 index 718bc9020..000000000 --- a/starlette/_compat.py +++ /dev/null @@ -1,26 +0,0 @@ -import hashlib - -# Compat wrapper to always include the `usedforsecurity=...` parameter, -# which is only added from Python 3.9 onwards. -# We use this flag to indicate that we use `md5` hashes only for non-security -# cases (our ETag checksums). -# If we don't indicate that we're using MD5 for non-security related reasons, -# then attempting to use this function will raise an error when used -# environments which enable a strict "FIPs mode". -# -# See issue: https://github.com/encode/starlette/issues/1365 -try: - # check if the Python version supports the parameter - # using usedforsecurity=False to avoid an exception on FIPS systems - # that reject usedforsecurity=True - hashlib.md5(b"data", usedforsecurity=False) # type: ignore[call-arg] - - def md5_hexdigest(data: bytes, *, usedforsecurity: bool = True) -> str: # pragma: no cover - return hashlib.md5( # type: ignore[call-arg] - data, usedforsecurity=usedforsecurity - ).hexdigest() - -except TypeError: # pragma: no cover - - def md5_hexdigest(data: bytes, *, usedforsecurity: bool = True) -> str: - return hashlib.md5(data).hexdigest() diff --git a/starlette/_exception_handler.py b/starlette/_exception_handler.py index baf6e2f50..72bc89d91 100644 --- a/starlette/_exception_handler.py +++ b/starlette/_exception_handler.py @@ -9,8 +9,8 @@ from starlette.types import ASGIApp, ExceptionHandler, Message, Receive, Scope, Send from starlette.websockets import WebSocket -ExceptionHandlers = typing.Dict[typing.Any, ExceptionHandler] -StatusHandlers = typing.Dict[int, ExceptionHandler] +ExceptionHandlers = dict[typing.Any, ExceptionHandler] +StatusHandlers = dict[int, ExceptionHandler] def _lookup_exception_handler(exc_handlers: ExceptionHandlers, exc: Exception) -> ExceptionHandler | None: diff --git a/starlette/middleware/__init__.py b/starlette/middleware/__init__.py index 5164e49e0..b99538a27 100644 --- a/starlette/middleware/__init__.py +++ b/starlette/middleware/__init__.py @@ -1,7 +1,8 @@ from __future__ import annotations import sys -from typing import Any, Iterator, Protocol +from collections.abc import Iterator +from typing import Any, Protocol if sys.version_info >= (3, 10): # pragma: no cover from typing import ParamSpec diff --git a/starlette/responses.py b/starlette/responses.py index c711dfa39..31874f655 100644 --- a/starlette/responses.py +++ b/starlette/responses.py @@ -1,5 +1,6 @@ from __future__ import annotations +import hashlib import http.cookies import json import os @@ -17,7 +18,6 @@ import anyio import anyio.to_thread -from starlette._compat import md5_hexdigest from starlette.background import BackgroundTask from starlette.concurrency import iterate_in_threadpool from starlette.datastructures import URL, Headers, MutableHeaders @@ -328,7 +328,7 @@ def set_stat_headers(self, stat_result: os.stat_result) -> None: content_length = str(stat_result.st_size) last_modified = formatdate(stat_result.st_mtime, usegmt=True) etag_base = str(stat_result.st_mtime) + "-" + str(stat_result.st_size) - etag = f'"{md5_hexdigest(etag_base.encode(), usedforsecurity=False)}"' + etag = f'"{hashlib.md5(etag_base.encode(), usedforsecurity=False).hexdigest()}"' self.headers.setdefault("content-length", content_length) self.headers.setdefault("last-modified", last_modified) diff --git a/tests/middleware/test_base.py b/tests/middleware/test_base.py index 449624e2f..7232cfd18 100644 --- a/tests/middleware/test_base.py +++ b/tests/middleware/test_base.py @@ -1,8 +1,9 @@ from __future__ import annotations import contextvars +from collections.abc import AsyncGenerator, AsyncIterator, Generator from contextlib import AsyncExitStack -from typing import Any, AsyncGenerator, AsyncIterator, Generator +from typing import Any import anyio import pytest diff --git a/tests/middleware/test_wsgi.py b/tests/middleware/test_wsgi.py index e4ac66ab4..3511c89c9 100644 --- a/tests/middleware/test_wsgi.py +++ b/tests/middleware/test_wsgi.py @@ -1,5 +1,6 @@ import sys -from typing import Any, Callable, Dict, Iterable +from collections.abc import Iterable +from typing import Any, Callable import pytest @@ -9,7 +10,7 @@ WSGIResponse = Iterable[bytes] StartResponse = Callable[..., Any] -Environment = Dict[str, Any] +Environment = dict[str, Any] def hello_world( diff --git a/tests/test_applications.py b/tests/test_applications.py index db2a6050b..310eef6b4 100644 --- a/tests/test_applications.py +++ b/tests/test_applications.py @@ -1,9 +1,10 @@ from __future__ import annotations import os +from collections.abc import AsyncGenerator, AsyncIterator, Generator from contextlib import asynccontextmanager from pathlib import Path -from typing import AsyncGenerator, AsyncIterator, Callable, Generator +from typing import Callable import anyio.from_thread import pytest diff --git a/tests/test_authentication.py b/tests/test_authentication.py index 40886cd23..ddd2ad805 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -2,19 +2,14 @@ import base64 import binascii -from typing import Any, Awaitable, Callable +from collections.abc import Awaitable +from typing import Any, Callable from urllib.parse import urlencode import pytest from starlette.applications import Starlette -from starlette.authentication import ( - AuthCredentials, - AuthenticationBackend, - AuthenticationError, - SimpleUser, - requires, -) +from starlette.authentication import AuthCredentials, AuthenticationBackend, AuthenticationError, SimpleUser, requires from starlette.endpoints import HTTPEndpoint from starlette.middleware import Middleware from starlette.middleware.authentication import AuthenticationMiddleware diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py index 380eb9ef2..d620984c1 100644 --- a/tests/test_concurrency.py +++ b/tests/test_concurrency.py @@ -1,5 +1,5 @@ +from collections.abc import Iterator from contextvars import ContextVar -from typing import Iterator import anyio import pytest diff --git a/tests/test_convertors.py b/tests/test_convertors.py index 0fabe101b..d8430c672 100644 --- a/tests/test_convertors.py +++ b/tests/test_convertors.py @@ -1,5 +1,5 @@ +from collections.abc import Iterator from datetime import datetime -from typing import Iterator from uuid import UUID import pytest diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py index 42776a5b3..76163873c 100644 --- a/tests/test_endpoints.py +++ b/tests/test_endpoints.py @@ -1,4 +1,4 @@ -from typing import Iterator +from collections.abc import Iterator import pytest diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index ffc8883b3..ff2913254 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -1,5 +1,5 @@ import warnings -from typing import Generator +from collections.abc import Generator import pytest diff --git a/tests/test_formparsers.py b/tests/test_formparsers.py index f781a9ecb..70beebc3f 100644 --- a/tests/test_formparsers.py +++ b/tests/test_formparsers.py @@ -17,7 +17,7 @@ from tests.types import TestClientFactory -class ForceMultipartDict(typing.Dict[typing.Any, typing.Any]): +class ForceMultipartDict(dict[typing.Any, typing.Any]): def __bool__(self) -> bool: return True diff --git a/tests/test_requests.py b/tests/test_requests.py index 665dceb87..7e2c608dc 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -1,7 +1,8 @@ from __future__ import annotations import sys -from typing import Any, Iterator +from collections.abc import Iterator +from typing import Any import anyio import pytest diff --git a/tests/test_responses.py b/tests/test_responses.py index 28f2feb2d..d5ed83499 100644 --- a/tests/test_responses.py +++ b/tests/test_responses.py @@ -2,9 +2,10 @@ import datetime as dt import time +from collections.abc import AsyncGenerator, AsyncIterator, Iterator from http.cookies import SimpleCookie from pathlib import Path -from typing import Any, AsyncGenerator, AsyncIterator, Iterator +from typing import Any import anyio import pytest diff --git a/tests/test_testclient.py b/tests/test_testclient.py index 279b81d91..58ab6f6f2 100644 --- a/tests/test_testclient.py +++ b/tests/test_testclient.py @@ -3,8 +3,9 @@ import itertools import sys from asyncio import Task, current_task as asyncio_current_task +from collections.abc import AsyncGenerator from contextlib import asynccontextmanager -from typing import Any, AsyncGenerator +from typing import Any import anyio import anyio.lowlevel diff --git a/tests/test_websockets.py b/tests/test_websockets.py index 8ecf6304c..e76d8f29b 100644 --- a/tests/test_websockets.py +++ b/tests/test_websockets.py @@ -1,5 +1,6 @@ import sys -from typing import Any, MutableMapping +from collections.abc import MutableMapping +from typing import Any import anyio import pytest