requests-cache 1.3.1__tar.gz → 1.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. {requests_cache-1.3.1 → requests_cache-1.3.2}/.gitignore +1 -0
  2. {requests_cache-1.3.1 → requests_cache-1.3.2}/PKG-INFO +8 -8
  3. {requests_cache-1.3.1 → requests_cache-1.3.2}/README.md +6 -6
  4. {requests_cache-1.3.1 → requests_cache-1.3.2}/pyproject.toml +3 -2
  5. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/backends/base.py +12 -1
  6. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/cache_keys.py +11 -5
  7. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/policy/actions.py +11 -0
  8. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/policy/settings.py +11 -2
  9. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/serializers/cattrs.py +11 -0
  10. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/unit/test_session.py +23 -0
  11. {requests_cache-1.3.1 → requests_cache-1.3.2}/LICENSE +0 -0
  12. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/__init__.py +0 -0
  13. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/_utils.py +0 -0
  14. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/backends/__init__.py +0 -0
  15. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/backends/dynamodb.py +0 -0
  16. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/backends/filesystem.py +0 -0
  17. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/backends/gridfs.py +0 -0
  18. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/backends/mongodb.py +0 -0
  19. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/backends/redis.py +0 -0
  20. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/backends/sqlite.py +0 -0
  21. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/models/__init__.py +0 -0
  22. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/models/base.py +0 -0
  23. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/models/raw_response.py +0 -0
  24. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/models/request.py +0 -0
  25. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/models/response.py +0 -0
  26. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/patcher.py +0 -0
  27. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/policy/__init__.py +0 -0
  28. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/policy/directives.py +0 -0
  29. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/policy/expiration.py +0 -0
  30. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/py.typed +0 -0
  31. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/serializers/__init__.py +0 -0
  32. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/serializers/pipeline.py +0 -0
  33. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/serializers/preconf.py +0 -0
  34. {requests_cache-1.3.1 → requests_cache-1.3.2}/requests_cache/session.py +0 -0
  35. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/README.md +0 -0
  36. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/__init__.py +0 -0
  37. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/benchmark_serializers.py +0 -0
  38. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/compat/README.md +0 -0
  39. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/compat/httpbin_sample.test-db +0 -0
  40. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/compat/test_requests_mock_combine_cache.py +0 -0
  41. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/compat/test_requests_mock_disable_cache.py +0 -0
  42. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/compat/test_requests_mock_load_cache.py +0 -0
  43. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/compat/test_responses_load_cache.py +0 -0
  44. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/conftest.py +0 -0
  45. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/generate_test_db.py +0 -0
  46. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/integration/__init__.py +0 -0
  47. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/integration/base_cache_test.py +0 -0
  48. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/integration/base_storage_test.py +0 -0
  49. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/integration/test_dynamodb.py +0 -0
  50. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/integration/test_filesystem.py +0 -0
  51. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/integration/test_memory.py +0 -0
  52. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/integration/test_mongodb.py +0 -0
  53. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/integration/test_redis.py +0 -0
  54. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/integration/test_sqlite.py +0 -0
  55. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/unit/models/test_base.py +0 -0
  56. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/unit/models/test_raw_response.py +0 -0
  57. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/unit/models/test_request.py +0 -0
  58. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/unit/models/test_response.py +0 -0
  59. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/unit/policy/test_actions.py +0 -0
  60. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/unit/policy/test_expiration.py +0 -0
  61. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/unit/test_base_cache.py +0 -0
  62. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/unit/test_cache_keys.py +0 -0
  63. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/unit/test_normalize.py +0 -0
  64. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/unit/test_patcher.py +0 -0
  65. {requests_cache-1.3.1 → requests_cache-1.3.2}/tests/unit/test_serializers.py +0 -0
@@ -33,6 +33,7 @@ package.json
33
33
  .coverage.*
34
34
  .COVERAGE.*
35
35
  .mypy_cache/
36
+ .pytest_cache/
36
37
  .nox/
37
38
  test-reports/
38
39
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: requests-cache
3
- Version: 1.3.1
3
+ Version: 1.3.2
4
4
  Summary: A persistent cache for python requests
5
5
  Project-URL: homepage, https://github.com/requests-cache/requests-cache
6
6
  Project-URL: repository, https://github.com/requests-cache/requests-cache
@@ -32,7 +32,7 @@ Provides-Extra: all
32
32
  Requires-Dist: boto3>=1.15; extra == 'all'
33
33
  Requires-Dist: botocore>=1.18; extra == 'all'
34
34
  Requires-Dist: itsdangerous>=2.0; extra == 'all'
35
- Requires-Dist: orjson>=3.0; extra == 'all'
35
+ Requires-Dist: orjson>=3.0; (python_version < '3.14') and extra == 'all'
36
36
  Requires-Dist: pymongo>=3; extra == 'all'
37
37
  Requires-Dist: pyyaml>=6.0.1; extra == 'all'
38
38
  Requires-Dist: redis>=3; extra == 'all'
@@ -72,25 +72,25 @@ Complete project documentation can be found at [requests-cache.readthedocs.io](h
72
72
  <!-- END-RTD-IGNORE -->
73
73
 
74
74
  ## Features
75
- * 🍰 **Ease of use:** Keep using the `requests` library you're already familiar with. Add caching
75
+ * **Ease of use:** Keep using the `requests` library you're already familiar with. Add caching
76
76
  with a [drop-in replacement](https://requests-cache.readthedocs.io/en/stable/user_guide/general.html#sessions)
77
77
  for `requests.Session`, or
78
78
  [install globally](https://requests-cache.readthedocs.io/en/stable/user_guide/general.html#patching)
79
79
  to add transparent caching to all `requests` functions.
80
- * 🚀 **Performance:** Get sub-millisecond response times for cached responses. When they expire, you
80
+ * **Performance:** Get sub-millisecond response times for cached responses. When they expire, you
81
81
  still save time with
82
82
  [conditional requests](https://requests-cache.readthedocs.io/en/stable/user_guide/headers.html#conditional-requests).
83
- * 💾 **Persistence:** Works with several
83
+ * **Persistence:** Works with several
84
84
  [storage backends](https://requests-cache.readthedocs.io/en/stable/user_guide/backends.html)
85
85
  including SQLite, Redis, MongoDB, and DynamoDB; or save responses as plain JSON files, YAML,
86
86
  and more
87
- * 🕗 **Expiration:** Use
87
+ * **Expiration:** Use
88
88
  [Cache-Control](https://requests-cache.readthedocs.io/en/stable/user_guide/headers.html#cache-control)
89
89
  and other standard HTTP headers, define your own expiration schedule, keep your cache clutter-free
90
90
  with backends that natively support TTL, or any combination of strategies
91
- * ⚙️ **Customization:** Works out of the box with zero config, but with a robust set of features for
91
+ * **Customization:** Works out of the box with zero config, but with a robust set of features for
92
92
  configuring and extending the library to suit your needs
93
- * 🧩 **Compatibility:** Can be combined with other
93
+ * **Compatibility:** Can be combined with other
94
94
  [popular libraries based on requests](https://requests-cache.readthedocs.io/en/stable/user_guide/compatibility.html)
95
95
 
96
96
  ## Quickstart
@@ -20,25 +20,25 @@ Complete project documentation can be found at [requests-cache.readthedocs.io](h
20
20
  <!-- END-RTD-IGNORE -->
21
21
 
22
22
  ## Features
23
- * 🍰 **Ease of use:** Keep using the `requests` library you're already familiar with. Add caching
23
+ * **Ease of use:** Keep using the `requests` library you're already familiar with. Add caching
24
24
  with a [drop-in replacement](https://requests-cache.readthedocs.io/en/stable/user_guide/general.html#sessions)
25
25
  for `requests.Session`, or
26
26
  [install globally](https://requests-cache.readthedocs.io/en/stable/user_guide/general.html#patching)
27
27
  to add transparent caching to all `requests` functions.
28
- * 🚀 **Performance:** Get sub-millisecond response times for cached responses. When they expire, you
28
+ * **Performance:** Get sub-millisecond response times for cached responses. When they expire, you
29
29
  still save time with
30
30
  [conditional requests](https://requests-cache.readthedocs.io/en/stable/user_guide/headers.html#conditional-requests).
31
- * 💾 **Persistence:** Works with several
31
+ * **Persistence:** Works with several
32
32
  [storage backends](https://requests-cache.readthedocs.io/en/stable/user_guide/backends.html)
33
33
  including SQLite, Redis, MongoDB, and DynamoDB; or save responses as plain JSON files, YAML,
34
34
  and more
35
- * 🕗 **Expiration:** Use
35
+ * **Expiration:** Use
36
36
  [Cache-Control](https://requests-cache.readthedocs.io/en/stable/user_guide/headers.html#cache-control)
37
37
  and other standard HTTP headers, define your own expiration schedule, keep your cache clutter-free
38
38
  with backends that natively support TTL, or any combination of strategies
39
- * ⚙️ **Customization:** Works out of the box with zero config, but with a robust set of features for
39
+ * **Customization:** Works out of the box with zero config, but with a robust set of features for
40
40
  configuring and extending the library to suit your needs
41
- * 🧩 **Compatibility:** Can be combined with other
41
+ * **Compatibility:** Can be combined with other
42
42
  [popular libraries based on requests](https://requests-cache.readthedocs.io/en/stable/user_guide/compatibility.html)
43
43
 
44
44
  ## Quickstart
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = 'requests-cache'
3
- version = '1.3.1'
3
+ version = '1.3.2'
4
4
  description = 'A persistent cache for python requests'
5
5
  authors = [{name='Jordan Cook'}, {name='Roman Haritonov'}]
6
6
  license = 'BSD-2-Clause'
@@ -61,7 +61,7 @@ all = [
61
61
  'boto3 >=1.15',
62
62
  'botocore >=1.18',
63
63
  'itsdangerous >=2.0',
64
- 'orjson >=3.0',
64
+ 'orjson >=3.0; python_version < "3.14"',
65
65
  'pymongo >=3',
66
66
  'pyyaml >=6.0.1',
67
67
  'redis >=3',
@@ -79,6 +79,7 @@ dev = [
79
79
  'pytest-pretty >=1.2',
80
80
  'pytest-rerunfailures >=10.1',
81
81
  'pytest-xdist >=2.2',
82
+ 'requests >=2.22.0.dev0', # Use a prerelease if it's the latest version
82
83
  'requests-mock ~=1.12',
83
84
  'responses >=0.19',
84
85
  'tenacity ~=8.0',
@@ -12,9 +12,11 @@ from collections import UserDict
12
12
  from datetime import datetime
13
13
  from logging import getLogger
14
14
  from pickle import PickleError
15
- from typing import TYPE_CHECKING, Iterable, Iterator, List, MutableMapping, Optional, TypeVar
15
+ from typing import TYPE_CHECKING, Iterable, Iterator, List, MutableMapping, Optional, TypeVar, Union
16
16
  from warnings import warn
17
17
 
18
+ from pathlib import Path
19
+
18
20
  from requests import Request, Response
19
21
 
20
22
  from ..cache_keys import create_key, redact_response
@@ -55,6 +57,15 @@ class BaseCache:
55
57
  self.redirects: BaseStorage[str, str] = DictStorage()
56
58
  self._settings = CacheSettings() # Init and public access is done in CachedSession
57
59
 
60
+ @property
61
+ def db_path(self) -> Union[str, Path]:
62
+ """Path to the cache database file.
63
+
64
+ Raises:
65
+ NotImplementedError: For backends that do not use a file-based database.
66
+ """
67
+ raise NotImplementedError
68
+
58
69
  # Main cache operations
59
70
  # ---------------------
60
71
 
@@ -66,8 +66,9 @@ def create_key(
66
66
  request: Request object to generate a cache key from
67
67
  ignored_parameters: Request parameters, headers, and/or JSON body params to exclude
68
68
  match_headers: Match only the specified headers, or ``True`` to match all headers
69
- request_kwargs: Additional keyword arguments for :py:func:`~requests.request`
69
+ serializer: Serializer name or instance
70
70
  content_root_key: root element in the request body to apply ignored_parameters to
71
+ request_kwargs: Additional keyword arguments for :py:func:`~requests.request`
71
72
  """
72
73
  # Normalize and gather all relevant request info to match against
73
74
  request = normalize_request(request, ignored_parameters, content_root_key)
@@ -142,7 +143,8 @@ def normalize_request(
142
143
 
143
144
 
144
145
  def normalize_headers(
145
- headers: MutableMapping[str, str], ignored_parameters: ParamList = None
146
+ headers: MutableMapping[str, str],
147
+ ignored_parameters: ParamList = None,
146
148
  ) -> CaseInsensitiveDict:
147
149
  """Sort and filter request headers, and normalize minor variations in multi-value headers"""
148
150
  headers = {k: decode(v) for (k, v) in headers.items()}
@@ -160,7 +162,7 @@ def normalize_url(url: str, ignored_parameters: ParamList) -> str:
160
162
  port, etc.
161
163
  """
162
164
  url = filter_url(url, ignored_parameters)
163
- return url_normalize(url)
165
+ return url_normalize(url) or ''
164
166
 
165
167
 
166
168
  def normalize_body(
@@ -257,7 +259,8 @@ def filter_sort_json(data: Union[List, Mapping], ignored_parameters: ParamList):
257
259
 
258
260
 
259
261
  def filter_sort_dict(
260
- data: Mapping[str, str], ignored_parameters: ParamList = None
262
+ data: Mapping[str, str],
263
+ ignored_parameters: ParamList = None,
261
264
  ) -> Dict[str, str]:
262
265
  # Note: Any ignored_parameters present will have their values replaced instead of removing the
263
266
  # parameter, so the cache key will still match whether the parameter was present or not.
@@ -265,7 +268,10 @@ def filter_sort_dict(
265
268
  return {k: ('REDACTED' if k in ignored_parameters else v) for k, v in sorted(data.items())}
266
269
 
267
270
 
268
- def filter_sort_multidict(data: KVList, ignored_parameters: ParamList = None) -> KVList:
271
+ def filter_sort_multidict(
272
+ data: KVList,
273
+ ignored_parameters: ParamList = None,
274
+ ) -> KVList:
269
275
  ignored_parameters = set(ignored_parameters or [])
270
276
  return [(k, 'REDACTED' if k in ignored_parameters else v) for k, v in sorted(data)]
271
277
 
@@ -350,9 +350,20 @@ class CacheActions(RichMixin):
350
350
  logger.debug('Failed Vary check: cookies do not match')
351
351
  return False
352
352
  match_headers.remove('cookie')
353
+
353
354
  if not match_headers:
354
355
  return True
355
356
 
357
+ # If a Vary header is in ignored_parameters and on the request, treat it as a cache miss
358
+ # rather than potentially returning an invalid response
359
+ ignored = {h.lower() for h in (self._settings.ignored_parameters or [])}
360
+ ignore_overlap = ignored & set(match_headers)
361
+ if ignore_overlap and any(h in self._request.headers for h in ignore_overlap):
362
+ logger.debug(
363
+ f'Failed Vary check: Vary header(s) are in ignored_parameters: {ignore_overlap}'
364
+ )
365
+ return False
366
+
356
367
  key_kwargs['match_headers'] = match_headers
357
368
  vary_key_cached = create_key(vary_request, **key_kwargs)
358
369
  vary_key_current = create_key(self._request, **key_kwargs)
@@ -11,8 +11,17 @@ DEFAULT_CACHE_NAME = 'http_cache'
11
11
  DEFAULT_METHODS = ('GET', 'HEAD')
12
12
  DEFAULT_STATUS_CODES = (200,)
13
13
 
14
- # Default params and/or headers that are excluded from cache keys and redacted from cached responses
15
- DEFAULT_IGNORED_PARAMS = ('Authorization', 'X-API-KEY', 'access_token', 'api_key')
14
+ # Common auth headers and params that are excluded from cache keys and redacted from cached responses
15
+ AUTH_HEADERS = (
16
+ 'Authorization',
17
+ 'Proxy-Authorization',
18
+ 'X-API-Key',
19
+ 'X-Auth-Token',
20
+ 'X-API-Token',
21
+ 'X-Access-Token',
22
+ )
23
+ AUTH_PARAMS = ('access_token', 'api_key', 'apikey')
24
+ DEFAULT_IGNORED_PARAMS = AUTH_HEADERS + AUTH_PARAMS
16
25
 
17
26
 
18
27
  @define(repr=False)
@@ -21,7 +21,9 @@ from functools import singledispatchmethod
21
21
  from json import JSONDecodeError
22
22
  from typing import Callable, Dict, ForwardRef, List, Optional, Union
23
23
 
24
+ import attrs
24
25
  from cattrs import Converter
26
+ from requests.adapters import HTTPAdapter
25
27
  from requests.cookies import RequestsCookieJar, cookiejar_from_dict
26
28
  from requests.exceptions import RequestException
27
29
  from requests.structures import CaseInsensitiveDict
@@ -30,6 +32,15 @@ from .._utils import is_json_content_type
30
32
  from ..models import CachedResponse, DecodedContent
31
33
  from .pipeline import Stage
32
34
 
35
+ # requests/models.py uses `from __future__ import annotations` and imports RequestsCookieJar
36
+ # and HTTPAdapter under TYPE_CHECKING only, so get_type_hints() can't find them at runtime
37
+ # when traversing Response's MRO. Pre-resolve CachedResponse field types now so cattrs
38
+ # won't call resolve_types() without the necessary localns.
39
+ attrs.resolve_types(
40
+ CachedResponse,
41
+ localns={'RequestsCookieJar': RequestsCookieJar, 'HTTPAdapter': HTTPAdapter},
42
+ )
43
+
33
44
  try:
34
45
  import ujson as json
35
46
  except ImportError:
@@ -406,6 +406,29 @@ def test_match_headers__vary_alternating(mock_session):
406
406
  assert mock_session.get(MOCKED_URL_VARY, headers=headers_html).from_cache is True
407
407
 
408
408
 
409
+ def test_match_headers__vary_authorization(mock_session):
410
+ """When Vary headers overlaps with ignored_parameters and the header is present on the
411
+ request, it's always a cache miss (prevents cross-user cache leakage).
412
+ """
413
+ url = f'{MOCKED_URL}/vary-auth'
414
+ mock_session.mock_adapter.register_uri(
415
+ 'GET', url, headers={'Vary': 'Authorization'}, text='mock response', status_code=200
416
+ )
417
+ mock_session.settings.ignored_parameters = ['Authorization']
418
+
419
+ # Header present: always a miss
420
+ r1 = mock_session.get(url, headers={'Authorization': 'Bearer user-1-token'})
421
+ r2 = mock_session.get(url, headers={'Authorization': 'Bearer user-1-token'})
422
+ assert r1.from_cache is False
423
+ assert r2.from_cache is False
424
+
425
+ # Header missing: no collision risk, proceed as normal
426
+ r3 = mock_session.get(url)
427
+ r4 = mock_session.get(url)
428
+ assert r3.from_cache is False
429
+ assert r4.from_cache is True
430
+
431
+
409
432
  @pytest.mark.parametrize(
410
433
  'headers, expected_from_cache',
411
434
  [
File without changes