Skip to content

Commit fe52df1

Browse files
authored
feat: Add type annotations (#279)
* Add type annotations * add typechecking to tox / github workflow * code review
1 parent 81ef6ba commit fe52df1

18 files changed

Lines changed: 389 additions & 176 deletions

.github/workflows/tests.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ jobs:
3434
python -VV
3535
python -m site
3636
python -m pip install --upgrade pip setuptools wheel
37-
python -m pip install --upgrade virtualenv tox tox-gh-actions
37+
python -m pip install --upgrade virtualenv tox tox-gh-actions
3838
3939
- name: "Run tox targets for ${{ matrix.python-version }}"
4040
run: "python -m tox"
41+
42+
- name: "Run mypy for ${{ matrix.python-version }}"
43+
run: "python -m tox -e mypy"

cachecontrol/__init__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,19 @@
1010
__email__ = "eric@ionrock.org"
1111
__version__ = "0.12.11"
1212

13-
from .wrapper import CacheControl
1413
from .adapter import CacheControlAdapter
1514
from .controller import CacheController
15+
from .wrapper import CacheControl
16+
17+
__all__ = [
18+
"__author__",
19+
"__email__",
20+
"__version__",
21+
"CacheControlAdapter",
22+
"CacheController",
23+
"CacheControl",
24+
]
1625

1726
import logging
27+
1828
logging.getLogger(__name__).addHandler(logging.NullHandler())

cachecontrol/_cmd.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,41 +3,46 @@
33
# SPDX-License-Identifier: Apache-2.0
44

55
import logging
6+
from argparse import ArgumentParser
7+
from typing import TYPE_CHECKING
68

79
import requests
810

911
from cachecontrol.adapter import CacheControlAdapter
1012
from cachecontrol.cache import DictCache
1113
from cachecontrol.controller import logger
1214

13-
from argparse import ArgumentParser
15+
if TYPE_CHECKING:
16+
from argparse import Namespace
1417

18+
from .controller import CacheController
1519

16-
def setup_logging():
20+
21+
def setup_logging() -> None:
1722
logger.setLevel(logging.DEBUG)
1823
handler = logging.StreamHandler()
1924
logger.addHandler(handler)
2025

2126

22-
def get_session():
27+
def get_session() -> requests.Session:
2328
adapter = CacheControlAdapter(
2429
DictCache(), cache_etags=True, serializer=None, heuristic=None
2530
)
2631
sess = requests.Session()
2732
sess.mount("http://", adapter)
2833
sess.mount("https://", adapter)
2934

30-
sess.cache_controller = adapter.controller
35+
sess.cache_controller = adapter.controller # type: ignore[attr-defined]
3136
return sess
3237

3338

34-
def get_args():
39+
def get_args() -> "Namespace":
3540
parser = ArgumentParser()
3641
parser.add_argument("url", help="The URL to try and cache")
3742
return parser.parse_args()
3843

3944

40-
def main(args=None):
45+
def main() -> None:
4146
args = get_args()
4247
sess = get_session()
4348

@@ -48,10 +53,13 @@ def main(args=None):
4853
setup_logging()
4954

5055
# try setting the cache
51-
sess.cache_controller.cache_response(resp.request, resp.raw)
56+
cache_controller: "CacheController" = (
57+
sess.cache_controller # type: ignore[attr-defined]
58+
)
59+
cache_controller.cache_response(resp.request, resp.raw)
5260

5361
# Now try to get it
54-
if sess.cache_controller.cached_request(resp.request):
62+
if cache_controller.cached_request(resp.request):
5563
print("Cached!")
5664
else:
5765
print("Not cached :(")

cachecontrol/adapter.py

Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,40 @@
22
#
33
# SPDX-License-Identifier: Apache-2.0
44

5-
import types
65
import functools
6+
import types
77
import zlib
8+
from typing import TYPE_CHECKING, Any, Collection, Mapping, Optional, Tuple, Type, Union
89

910
from requests.adapters import HTTPAdapter
1011

11-
from .controller import CacheController, PERMANENT_REDIRECT_STATUSES
1212
from .cache import DictCache
13+
from .controller import PERMANENT_REDIRECT_STATUSES, CacheController
1314
from .filewrapper import CallbackFileWrapper
1415

16+
if TYPE_CHECKING:
17+
from requests import PreparedRequest, Response
18+
19+
from .cache import BaseCache
20+
from .compat import HTTPResponse
21+
from .heuristics import BaseHeuristic
22+
from .serialize import Serializer
23+
1524

1625
class CacheControlAdapter(HTTPAdapter):
1726
invalidating_methods = {"PUT", "PATCH", "DELETE"}
1827

1928
def __init__(
2029
self,
21-
cache=None,
22-
cache_etags=True,
23-
controller_class=None,
24-
serializer=None,
25-
heuristic=None,
26-
cacheable_methods=None,
27-
*args,
28-
**kw
29-
):
30+
cache: Optional["BaseCache"] = None,
31+
cache_etags: bool = True,
32+
controller_class: Optional[Type[CacheController]] = None,
33+
serializer: Optional["Serializer"] = None,
34+
heuristic: Optional["BaseHeuristic"] = None,
35+
cacheable_methods: Optional[Collection[str]] = None,
36+
*args: Any,
37+
**kw: Any,
38+
) -> None:
3039
super(CacheControlAdapter, self).__init__(*args, **kw)
3140
self.cache = DictCache() if cache is None else cache
3241
self.heuristic = heuristic
@@ -37,7 +46,18 @@ def __init__(
3746
self.cache, cache_etags=cache_etags, serializer=serializer
3847
)
3948

40-
def send(self, request, cacheable_methods=None, **kw):
49+
def send(
50+
self,
51+
request: "PreparedRequest",
52+
stream: bool = False,
53+
timeout: Union[None, float, Tuple[float, float], Tuple[float, None]] = None,
54+
verify: Union[bool, str] = True,
55+
cert: Union[
56+
None, bytes, str, Tuple[Union[bytes, str], Union[bytes, str]]
57+
] = None,
58+
proxies: Optional[Mapping[str, str]] = None,
59+
cacheable_methods: Optional[Collection[str]] = None,
60+
) -> "Response":
4161
"""
4262
Send a request. Use the request information to see if it
4363
exists in the cache and cache the response if we need to and can.
@@ -54,13 +74,19 @@ def send(self, request, cacheable_methods=None, **kw):
5474
# check for etags and add headers if appropriate
5575
request.headers.update(self.controller.conditional_headers(request))
5676

57-
resp = super(CacheControlAdapter, self).send(request, **kw)
77+
resp = super(CacheControlAdapter, self).send(
78+
request, stream, timeout, verify, cert, proxies
79+
)
5880

5981
return resp
6082

6183
def build_response(
62-
self, request, response, from_cache=False, cacheable_methods=None
63-
):
84+
self,
85+
request: "PreparedRequest",
86+
response: "HTTPResponse",
87+
from_cache: bool = False,
88+
cacheable_methods: Optional[Collection[str]] = None,
89+
) -> "Response":
6490
"""
6591
Build a response by making a request or using the cache.
6692
@@ -111,7 +137,7 @@ def build_response(
111137
if response.chunked:
112138
super_update_chunk_length = response._update_chunk_length
113139

114-
def _update_chunk_length(self):
140+
def _update_chunk_length(self: "HTTPResponse") -> None:
115141
super_update_chunk_length()
116142
if self.chunk_left == 0:
117143
self._fp._close()
@@ -120,18 +146,21 @@ def _update_chunk_length(self):
120146
_update_chunk_length, response
121147
)
122148

123-
resp = super(CacheControlAdapter, self).build_response(request, response)
149+
resp: "Response" = super( # type: ignore[no-untyped-call]
150+
CacheControlAdapter, self
151+
).build_response(request, response)
124152

125153
# See if we should invalidate the cache.
126154
if request.method in self.invalidating_methods and resp.ok:
155+
assert request.url is not None
127156
cache_url = self.controller.cache_url(request.url)
128157
self.cache.delete(cache_url)
129158

130159
# Give the request a from_cache attr to let people use it
131-
resp.from_cache = from_cache
160+
resp.from_cache = from_cache # type: ignore[attr-defined]
132161

133162
return resp
134163

135-
def close(self):
164+
def close(self) -> None:
136165
self.cache.close()
137-
super(CacheControlAdapter, self).close()
166+
super(CacheControlAdapter, self).close() # type: ignore[no-untyped-call]

cachecontrol/cache.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,37 +7,43 @@
77
safe in-memory dictionary.
88
"""
99
from threading import Lock
10+
from typing import IO, TYPE_CHECKING, MutableMapping, Optional, Union
1011

12+
if TYPE_CHECKING:
13+
from datetime import datetime
1114

12-
class BaseCache(object):
1315

14-
def get(self, key):
16+
class BaseCache(object):
17+
def get(self, key: str) -> Optional[bytes]:
1518
raise NotImplementedError()
1619

17-
def set(self, key, value, expires=None):
20+
def set(
21+
self, key: str, value: bytes, expires: Optional[Union[int, "datetime"]] = None
22+
) -> None:
1823
raise NotImplementedError()
1924

20-
def delete(self, key):
25+
def delete(self, key: str) -> None:
2126
raise NotImplementedError()
2227

23-
def close(self):
28+
def close(self) -> None:
2429
pass
2530

2631

2732
class DictCache(BaseCache):
28-
29-
def __init__(self, init_dict=None):
33+
def __init__(self, init_dict: Optional[MutableMapping[str, bytes]] = None) -> None:
3034
self.lock = Lock()
3135
self.data = init_dict or {}
3236

33-
def get(self, key):
37+
def get(self, key: str) -> Optional[bytes]:
3438
return self.data.get(key, None)
3539

36-
def set(self, key, value, expires=None):
40+
def set(
41+
self, key: str, value: bytes, expires: Optional[Union[int, "datetime"]] = None
42+
) -> None:
3743
with self.lock:
3844
self.data.update({key: value})
3945

40-
def delete(self, key):
46+
def delete(self, key: str) -> None:
4147
with self.lock:
4248
if key in self.data:
4349
self.data.pop(key)
@@ -55,10 +61,11 @@ class SeparateBodyBaseCache(BaseCache):
5561
5662
Similarly, the body should be loaded separately via ``get_body()``.
5763
"""
58-
def set_body(self, key, body):
64+
65+
def set_body(self, key: str, body: bytes) -> None:
5966
raise NotImplementedError()
6067

61-
def get_body(self, key):
68+
def get_body(self, key: str) -> Optional["IO[bytes]"]:
6269
"""
6370
Return the body as file-like object.
6471
"""

0 commit comments

Comments
 (0)