Skip to content

Commit 6c76160

Browse files
hugovkwoodruffw
andauthored
Add support for Python 3.12 and drop EOL 3.6 and 3.7 (#323)
* Add support for Python 3.12 * Drop support for EOL Python 3.6 * Upgrade Python syntax with pyupgrade --py37-plus * examples, tests: reformat * cachecontrol, tox: run mypy on 3.12 and tweak ignores. --------- Co-authored-by: William Woodruff <[email protected]>
1 parent 783a338 commit 6c76160

19 files changed

+27
-38
lines changed

.github/workflows/release.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ jobs:
1919
contents: write
2020

2121
steps:
22-
- uses: actions/checkout@v3
22+
- uses: actions/checkout@v4
2323

2424
- uses: actions/setup-python@v4
2525
with:
26-
python-version: ">= 3.6"
26+
python-version: "3.x"
2727

2828
- name: deps
2929
run: python -m pip install -U build
@@ -35,7 +35,7 @@ jobs:
3535
uses: pypa/gh-action-pypi-publish@release/v1
3636

3737
- name: sign
38-
uses: sigstore/gh-action-sigstore-python@v1.2.3
38+
uses: sigstore/gh-action-sigstore-python@v2.0.1
3939
with:
4040
inputs: ./dist/*.tar.gz ./dist/*.whl
4141
release-signing-artifacts: true

.github/workflows/tests.yml

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@ jobs:
1616
strategy:
1717
fail-fast: false
1818
matrix:
19-
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
19+
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
2020
os: ["macos-latest", "windows-latest", "ubuntu-latest"]
2121

2222
steps:
23-
- uses: "actions/checkout@v3"
23+
- uses: "actions/checkout@v4"
2424
- uses: "actions/setup-python@v4"
2525
with:
2626
python-version: "${{ matrix.python-version }}"
27+
allow-prereleases: true
2728
- name: "Install dependencies"
2829
run: |
2930
python -VV

cachecontrol/adapter.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -125,21 +125,21 @@ def build_response(
125125
else:
126126
# Wrap the response file with a wrapper that will cache the
127127
# response when the stream has been consumed.
128-
response._fp = CallbackFileWrapper( # type: ignore[attr-defined]
129-
response._fp, # type: ignore[attr-defined]
128+
response._fp = CallbackFileWrapper( # type: ignore[assignment]
129+
response._fp, # type: ignore[arg-type]
130130
functools.partial(
131131
self.controller.cache_response, request, response
132132
),
133133
)
134134
if response.chunked:
135-
super_update_chunk_length = response._update_chunk_length # type: ignore[attr-defined]
135+
super_update_chunk_length = response._update_chunk_length
136136

137137
def _update_chunk_length(self: HTTPResponse) -> None:
138138
super_update_chunk_length()
139139
if self.chunk_left == 0:
140-
self._fp._close() # type: ignore[attr-defined]
140+
self._fp._close() # type: ignore[union-attr]
141141

142-
response._update_chunk_length = types.MethodType( # type: ignore[attr-defined]
142+
response._update_chunk_length = types.MethodType( # type: ignore[method-assign]
143143
_update_chunk_length, response
144144
)
145145

cachecontrol/caches/file_cache.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ class _FileCacheMixin:
6464

6565
def __init__(
6666
self,
67-
directory: Union[str, Path],
67+
directory: str | Path,
6868
forever: bool = False,
6969
filemode: int = 0o0600,
7070
dirmode: int = 0o0700,

cachecontrol/controller.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ def update_cached_response(
480480
cached_response.headers.update(
481481
{
482482
k: v
483-
for k, v in response.headers.items() # type: ignore[no-untyped-call]
483+
for k, v in response.headers.items()
484484
if k.lower() not in excluded_headers
485485
}
486486
)

cachecontrol/heuristics.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def update_headers(self, response: HTTPResponse) -> dict[str, str]:
6868

6969
if "expires" not in response.headers:
7070
date = parsedate(response.headers["date"])
71-
expires = expire_after(timedelta(days=1), date=datetime(*date[:6], tzinfo=timezone.utc)) # type: ignore[misc]
71+
expires = expire_after(timedelta(days=1), date=datetime(*date[:6], tzinfo=timezone.utc)) # type: ignore[index,misc]
7272
headers["expires"] = datetime_to_header(expires)
7373
headers["cache-control"] = "public"
7474
return headers

cachecontrol/serialize.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@ def dumps(
3232
# also update the response with a new file handler to be
3333
# sure it acts as though it was never read.
3434
body = response.read(decode_content=False)
35-
response._fp = io.BytesIO(body) # type: ignore[attr-defined]
35+
response._fp = io.BytesIO(body) # type: ignore[assignment]
3636
response.length_remaining = len(body)
3737

3838
data = {
3939
"response": {
4040
"body": body, # Empty bytestring if body is stored separately
41-
"headers": {str(k): str(v) for k, v in response.headers.items()}, # type: ignore[no-untyped-call]
41+
"headers": {str(k): str(v) for k, v in response.headers.items()},
4242
"status": response.status,
4343
"version": response.version,
4444
"reason": str(response.reason),

docs/conf.py

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
# -*- coding: utf-8 -*-
2-
31
# SPDX-FileCopyrightText: 2015 Eric Larson
42
#
53
# SPDX-License-Identifier: Apache-2.0

examples/benchmark.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@
1313

1414
HOST = "localhost"
1515
PORT = 8050
16-
URL = "http://{}:{}/".format(HOST, PORT)
16+
URL = f"http://{HOST}:{PORT}/"
1717

1818

19-
class Server(object):
20-
19+
class Server:
2120
def __call__(self, env, sr):
2221
body = "Hello World!"
2322
status = "200 OK"
2423
headers = [
25-
("Cache-Control", "max-age=%i" % (60 * 10)), ("Content-Type", "text/plain")
24+
("Cache-Control", "max-age=%i" % (60 * 10)),
25+
("Content-Type", "text/plain"),
2626
]
2727
sr(status, headers)
2828
return body

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ classifiers = [
2424
"Environment :: Web Environment",
2525
"License :: OSI Approved :: Apache Software License",
2626
"Operating System :: OS Independent",
27-
"Programming Language :: Python :: 3.6",
2827
"Programming Language :: Python :: 3.7",
2928
"Programming Language :: Python :: 3.8",
3029
"Programming Language :: Python :: 3.9",
3130
"Programming Language :: Python :: 3.10",
3231
"Programming Language :: Python :: 3.11",
32+
"Programming Language :: Python :: 3.12",
3333
"Topic :: Internet :: WWW/HTTP",
3434
]
3535
keywords = ["requests", "http", "caching", "web"]

tests/conftest.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414

1515
class SimpleApp:
16-
1716
def __init__(self):
1817
self.etag_count = 0
1918
self.update_etag_string()
@@ -109,7 +108,7 @@ def fixed_length(self, env, start_response):
109108
headers = [
110109
("Content-Type", "text/plain"),
111110
("Cache-Control", "max-age=5000"),
112-
("Content-Length", str(len(body)))
111+
("Content-Length", str(len(body))),
113112
]
114113
start_response("200 OK", headers)
115114
return [body]

tests/test_adapter.py

-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ def sess(url, request):
3535

3636

3737
class TestSessionActions:
38-
3938
def test_get_caches(self, url, sess):
4039
r2 = sess.get(url)
4140
assert r2.from_cache is True

tests/test_chunked_response.py

-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ def sess():
2121

2222

2323
class TestChunkedResponses:
24-
2524
def test_cache_chunked_response(self, url, sess):
2625
"""
2726
Verify that an otherwise cacheable response is cached when the

tests/test_max_age.py

-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212

1313
class TestMaxAge:
14-
1514
@pytest.fixture()
1615
def sess(self, url):
1716
self.url = url

tests/test_regressions.py

-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
#
33
# SPDX-License-Identifier: Apache-2.0
44

5-
import sys
65
import pytest
76

87

@@ -13,10 +12,6 @@
1312

1413

1514
class Test39:
16-
17-
@pytest.mark.skipif(
18-
sys.version.startswith("2"), reason="Only run this for python 3.x"
19-
)
2015
def test_file_cache_recognizes_consumed_file_handle(self, url):
2116
s = CacheControl(Session(), FileCache("web_cache"))
2217
the_url = url + "cache_60"

tests/test_storage_filecache.py

-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ def randomdata():
2525

2626

2727
class FileCacheTestsMixin:
28-
2928
FileCacheClass = None # Either FileCache or SeparateBodyFileCache
3029

3130
@pytest.fixture()

tests/test_storage_redis.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ def test_set_expiration_datetime(self):
1818
assert self.conn.setex.called
1919

2020
def test_set_expiration_datetime_aware(self):
21-
self.cache.set("foo", "bar",
22-
expires=datetime(2014, 2, 2, tzinfo=timezone.utc))
21+
self.cache.set("foo", "bar", expires=datetime(2014, 2, 2, tzinfo=timezone.utc))
2322
assert self.conn.setex.called
2423

2524
def test_set_expiration_int(self):

tests/utils.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99

1010
class NullSerializer(Serializer):
11-
1211
def dumps(self, request, response, body=None):
1312
return response
1413

@@ -20,6 +19,7 @@ def loads(self, request, data, body_file=None):
2019

2120
class DummyResponse:
2221
"""Match a ``urllib3.response.HTTPResponse``."""
22+
2323
version = "1.1"
2424
reason = b"Because"
2525
strict = 0

tox.ini

+3-2
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@
44

55
[tox]
66
isolated_build = True
7-
envlist = py{36,37,38,39,310,311}, mypy
7+
envlist = py{37,38,39,310,311,312}, mypy
88

99
[gh-actions]
1010
python =
1111
3.7: py37
1212
3.8: py38
1313
3.9: py39
14-
3.10: py310, mypy
14+
3.10: py310
1515
3.11: py311
16+
3.12: py312, mypy
1617

1718
[testenv]
1819
deps = pytest

0 commit comments

Comments
 (0)