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"
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",
"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",
[tool.mypy]
strict = true
ignore_missing_imports = true
-python_version = "3.8"
+python_version = "3.9"
[[tool.mypy.overrides]]
module = "starlette.testclient.*"
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
+++ /dev/null
-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()
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:
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
from __future__ import annotations
+import hashlib
import http.cookies
import json
import os
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
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)
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
import sys
-from typing import Any, Callable, Dict, Iterable
+from collections.abc import Iterable
+from typing import Any, Callable
import pytest
WSGIResponse = Iterable[bytes]
StartResponse = Callable[..., Any]
-Environment = Dict[str, Any]
+Environment = dict[str, Any]
def hello_world(
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
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
+from collections.abc import Iterator
from contextvars import ContextVar
-from typing import Iterator
import anyio
import pytest
+from collections.abc import Iterator
from datetime import datetime
-from typing import Iterator
from uuid import UUID
import pytest
-from typing import Iterator
+from collections.abc import Iterator
import pytest
import warnings
-from typing import Generator
+from collections.abc import Generator
import pytest
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
from __future__ import annotations
import sys
-from typing import Any, Iterator
+from collections.abc import Iterator
+from typing import Any
import anyio
import pytest
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
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
import sys
-from typing import Any, MutableMapping
+from collections.abc import MutableMapping
+from typing import Any
import anyio
import pytest