From e7776eb9e87114944ded880bf0385d0dfafc5ea8 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sun, 22 Mar 2026 05:23:18 -0400 Subject: [PATCH] v3.0.0: Modernize for latest Starlette, drop EOL Pythons - Bump version to 3.0.0 - Replace deprecated starlette.middleware.wsgi with a2wsgi - Pin starlette[full]>=0.40, add a2wsgi dependency - Bump minimum Python to 3.9, drop 3.7/3.8 support - Remove whitenoise conditional (Python <3.8 no longer supported) - Clean up CI matrix: drop old Python versions and OS exclusions - Fix deprecated httpx TestClient usage in tests (data= -> content=, per-request cookies) Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/test.yaml | 39 +------------------------------------ responder/__version__.py | 2 +- responder/routes.py | 2 +- setup.py | 16 ++++++--------- tests/test_encodings.py | 4 ++-- tests/test_responder.py | 7 ++++--- 6 files changed, 15 insertions(+), 55 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index da91c89..3e1bbdc 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -20,15 +20,11 @@ jobs: fail-fast: false matrix: os: [ - "ubuntu-20.04", - "macos-13", + "ubuntu-latest", "macos-latest", "windows-latest", ] python-version: [ - "3.6", - "3.7", - "3.8", "3.9", "3.10", "3.11", @@ -36,33 +32,6 @@ jobs: "3.13", "pypy3.10", ] - - exclude: - - # Exclude test matrix slots that are no longer supported by GHA runners. - - os: 'ubuntu-20.04' - python-version: '3.6' - - os: 'macos-latest' - python-version: '3.6' - - os: 'macos-latest' - python-version: '3.7' - - os: 'macos-latest' - python-version: '3.8' - - os: 'macos-latest' - python-version: '3.9' - - os: 'macos-latest' - python-version: '3.10' - - # Exclude Python 3.7 on Windows, because GHA fails on it. - # - # SyntaxError: Non-UTF-8 code starting with '\x83' in file - # C:\hostedtoolcache\windows\Python\3.7.9\x64\Scripts\poe.exe - # on line 2, but no encoding declared; - # see http://python.org/dev/peps/pep-0263/ for details - # - # https://github.com/kennethreitz/responder/actions/runs/11526258626/job/32090071392?pr=546#step:6:73 - - os: 'windows-latest' - python-version: '3.7' env: UV_SYSTEM_PYTHON: true @@ -77,9 +46,6 @@ jobs: with: python-version: ${{ matrix.python-version }} architecture: x64 - cache: 'pip' - cache-dependency-path: | - pyproject.toml - name: Set up uv uses: astral-sh/setup-uv@v5 @@ -115,9 +81,6 @@ jobs: with: python-version: ${{ matrix.python-version }} architecture: x64 - cache: 'pip' - cache-dependency-path: | - pyproject.toml - name: Set up uv uses: astral-sh/setup-uv@v5 diff --git a/responder/__version__.py b/responder/__version__.py index 962c851..528787c 100644 --- a/responder/__version__.py +++ b/responder/__version__.py @@ -1 +1 @@ -__version__ = "2.0.7" +__version__ = "3.0.0" diff --git a/responder/routes.py b/responder/routes.py index b78aeae..08b1402 100644 --- a/responder/routes.py +++ b/responder/routes.py @@ -7,7 +7,7 @@ from collections import defaultdict from starlette.concurrency import run_in_threadpool from starlette.exceptions import HTTPException -from starlette.middleware.wsgi import WSGIMiddleware +from a2wsgi import WSGIMiddleware from starlette.types import ASGIApp from starlette.websockets import WebSocket, WebSocketClose diff --git a/setup.py b/setup.py index 451dc02..6de1703 100644 --- a/setup.py +++ b/setup.py @@ -22,18 +22,16 @@ if sys.argv[-1] == "publish": sys.exit() required = [ + "a2wsgi", "apispec>=1.0.0b1", "chardet", "marshmallow", "requests", "requests-toolbelt", "rfc3986", - # ServeStatic is the successor to WhiteNoise. - # WhiteNoise is used for backward compatibility with Python <3.8. - "servestatic; python_version>='3.8'", - "starlette[full]", + "servestatic", + "starlette[full]>=0.40", "uvicorn[standard]", - "whitenoise; python_version<'3.8'", ] @@ -115,7 +113,7 @@ setup( packages=find_packages(exclude=["tests"]), package_data={}, entry_points={"console_scripts": ["responder=responder.ext.cli:cli"]}, - python_requires=">=3.7", + python_requires=">=3.9", setup_requires=[], install_requires=required, extras_require={ @@ -129,8 +127,8 @@ setup( ], "develop": [ "poethepoet", - "pyproject-fmt; python_version>='3.7'", - "ruff; python_version>='3.7'", + "pyproject-fmt", + "ruff", "validate-pyproject", ], "docs": [ @@ -165,8 +163,6 @@ setup( "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", diff --git a/tests/test_encodings.py b/tests/test_encodings.py index 976de02..614c68e 100644 --- a/tests/test_encodings.py +++ b/tests/test_encodings.py @@ -6,7 +6,7 @@ def test_custom_encoding(api, session): req.encoding = "ascii" resp.text = await req.text - r = session.post(api.url_for(route), data=data) + r = session.post(api.url_for(route), content=data) assert r.text == data @@ -17,5 +17,5 @@ def test_bytes_encoding(api, session): async def route(req, resp): resp.text = (await req.content).decode("utf-8") - r = session.post(api.url_for(route), data=data) + r = session.post(api.url_for(route), content=data) assert r.content == data diff --git a/tests/test_responder.py b/tests/test_responder.py index 0ce9931..9856782 100644 --- a/tests/test_responder.py +++ b/tests/test_responder.py @@ -277,7 +277,7 @@ def test_yaml_uploads(api): dump = {"complicated": "times"} r = api.requests.post( api.url_for(route), - data=yaml.dump(dump), + content=yaml.dump(dump), headers={"Content-Type": "application/x-yaml"}, ) assert r.json() == dump @@ -512,7 +512,7 @@ def test_async_class_based_views(api): resp.text = await req.text data = "frame" - r = api.requests.post(api.url_for(Resource), data=data) + r = api.requests.post(api.url_for(Resource), content=data) assert r.text == data @@ -531,7 +531,8 @@ def test_cookies(api): httponly=True, ) - r = api.requests.get(api.url_for(cookies), cookies={"hello": "universe"}) + api.requests.cookies.set("hello", "universe") + r = api.requests.get(api.url_for(cookies)) assert r.json() == {"cookies": {"hello": "universe"}} assert "sent" in r.cookies assert "hello" in r.cookies