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) <noreply@anthropic.com>
This commit is contained in:
2026-03-22 05:23:18 -04:00
parent 944d47da45
commit e7776eb9e8
6 changed files with 15 additions and 55 deletions
+1 -38
View File
@@ -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
+1 -1
View File
@@ -1 +1 @@
__version__ = "2.0.7"
__version__ = "3.0.0"
+1 -1
View File
@@ -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
+6 -10
View File
@@ -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",
+2 -2
View File
@@ -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
+4 -3
View File
@@ -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