Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-CacheControl for openSUSE:Factory checked in at 2022-08-03 21:16:28 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-CacheControl (Old) and /work/SRC/openSUSE:Factory/.python-CacheControl.new.1533 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-CacheControl" Wed Aug 3 21:16:28 2022 rev:10 rq:992331 version:0.12.11 Changes: -------- --- /work/SRC/openSUSE:Factory/python-CacheControl/python-CacheControl.changes 2021-12-09 19:45:19.281124939 +0100 +++ /work/SRC/openSUSE:Factory/.python-CacheControl.new.1533/python-CacheControl.changes 2022-08-03 21:16:37.163429690 +0200 @@ -1,0 +2,8 @@ +Tue Aug 2 10:26:39 UTC 2022 - Otto Hollmann <otto.hollm...@suse.com> + +- Update to v0.12.11 + * Added new variant of FileCache, SeparateBodyFileCache, which uses + less memory by storing the body in a separate file than metadata, + and streaming data in and out directly to/from that file. + +------------------------------------------------------------------- Old: ---- CacheControl-0.12.10.tar.gz New: ---- CacheControl-0.12.11.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-CacheControl.spec ++++++ --- /var/tmp/diff_new_pack.5Vp38i/_old 2022-08-03 21:16:37.651430971 +0200 +++ /var/tmp/diff_new_pack.5Vp38i/_new 2022-08-03 21:16:37.655430981 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-CacheControl # -# Copyright (c) 2021 SUSE LLC +# Copyright (c) 2022 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -19,7 +19,7 @@ %{?!python_module:%define python_module() python3-%{**}} %define skip_python2 1 Name: python-CacheControl -Version: 0.12.10 +Version: 0.12.11 Release: 0 Summary: Caching library for Python requests License: Apache-2.0 ++++++ CacheControl-0.12.10.tar.gz -> CacheControl-0.12.11.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cachecontrol-0.12.10/.bumpversion.cfg new/cachecontrol-0.12.11/.bumpversion.cfg --- old/cachecontrol-0.12.10/.bumpversion.cfg 2021-11-05 18:07:09.000000000 +0100 +++ new/cachecontrol-0.12.11/.bumpversion.cfg 2022-04-19 19:20:49.000000000 +0200 @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.12.10 +current_version = 0.12.11 files = setup.py cachecontrol/__init__.py docs/conf.py commit = True tag = True diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cachecontrol-0.12.10/.gitignore new/cachecontrol-0.12.11/.gitignore --- old/cachecontrol-0.12.10/.gitignore 2021-11-05 18:07:09.000000000 +0100 +++ new/cachecontrol-0.12.11/.gitignore 2022-04-19 19:20:49.000000000 +0200 @@ -14,4 +14,6 @@ .Python docs/_build build/ -.tox \ No newline at end of file +.tox +.venv +web_cache diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cachecontrol-0.12.10/cachecontrol/__init__.py new/cachecontrol-0.12.11/cachecontrol/__init__.py --- old/cachecontrol-0.12.10/cachecontrol/__init__.py 2021-11-05 18:07:09.000000000 +0100 +++ new/cachecontrol-0.12.11/cachecontrol/__init__.py 2022-04-19 19:20:49.000000000 +0200 @@ -8,7 +8,7 @@ """ __author__ = "Eric Larson" __email__ = "e...@ionrock.org" -__version__ = "0.12.10" +__version__ = "0.12.11" from .wrapper import CacheControl from .adapter import CacheControlAdapter diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cachecontrol-0.12.10/cachecontrol/cache.py new/cachecontrol-0.12.11/cachecontrol/cache.py --- old/cachecontrol-0.12.10/cachecontrol/cache.py 2021-11-05 18:07:09.000000000 +0100 +++ new/cachecontrol-0.12.11/cachecontrol/cache.py 2022-04-19 19:20:49.000000000 +0200 @@ -41,3 +41,25 @@ with self.lock: if key in self.data: self.data.pop(key) + + +class SeparateBodyBaseCache(BaseCache): + """ + In this variant, the body is not stored mixed in with the metadata, but is + passed in (as a bytes-like object) in a separate call to ``set_body()``. + + That is, the expected interaction pattern is:: + + cache.set(key, serialized_metadata) + cache.set_body(key) + + Similarly, the body should be loaded separately via ``get_body()``. + """ + def set_body(self, key, body): + raise NotImplementedError() + + def get_body(self, key): + """ + Return the body as file-like object. + """ + raise NotImplementedError() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cachecontrol-0.12.10/cachecontrol/caches/__init__.py new/cachecontrol-0.12.11/cachecontrol/caches/__init__.py --- old/cachecontrol-0.12.10/cachecontrol/caches/__init__.py 2021-11-05 18:07:09.000000000 +0100 +++ new/cachecontrol-0.12.11/cachecontrol/caches/__init__.py 2022-04-19 19:20:49.000000000 +0200 @@ -2,5 +2,8 @@ # # SPDX-License-Identifier: Apache-2.0 -from .file_cache import FileCache # noqa -from .redis_cache import RedisCache # noqa +from .file_cache import FileCache, SeparateBodyFileCache +from .redis_cache import RedisCache + + +__all__ = ["FileCache", "SeparateBodyFileCache", "RedisCache"] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cachecontrol-0.12.10/cachecontrol/caches/file_cache.py new/cachecontrol-0.12.11/cachecontrol/caches/file_cache.py --- old/cachecontrol-0.12.10/cachecontrol/caches/file_cache.py 2021-11-05 18:07:09.000000000 +0100 +++ new/cachecontrol-0.12.11/cachecontrol/caches/file_cache.py 2022-04-19 19:20:49.000000000 +0200 @@ -6,7 +6,7 @@ import os from textwrap import dedent -from ..cache import BaseCache +from ..cache import BaseCache, SeparateBodyBaseCache from ..controller import CacheController try: @@ -57,7 +57,8 @@ raise -class FileCache(BaseCache): +class _FileCacheMixin: + """Shared implementation for both FileCache variants.""" def __init__( self, @@ -120,20 +121,25 @@ def set(self, key, value, expires=None): name = self._fn(key) + self._write(name, value) + def _write(self, path, data: bytes): + """ + Safely write the data to the given path. + """ # Make sure the directory exists try: - os.makedirs(os.path.dirname(name), self.dirmode) + os.makedirs(os.path.dirname(path), self.dirmode) except (IOError, OSError): pass - with self.lock_class(name) as lock: + with self.lock_class(path) as lock: # Write our actual file with _secure_open_write(lock.path, self.filemode) as fh: - fh.write(value) + fh.write(data) - def delete(self, key): - name = self._fn(key) + def _delete(self, key, suffix): + name = self._fn(key) + suffix if not self.forever: try: os.remove(name) @@ -141,6 +147,38 @@ pass +class FileCache(_FileCacheMixin, BaseCache): + """ + Traditional FileCache: body is stored in memory, so not suitable for large + downloads. + """ + + def delete(self, key): + self._delete(key, "") + + +class SeparateBodyFileCache(_FileCacheMixin, SeparateBodyBaseCache): + """ + Memory-efficient FileCache: body is stored in a separate file, reducing + peak memory usage. + """ + + def get_body(self, key): + name = self._fn(key) + ".body" + try: + return open(name, "rb") + except FileNotFoundError: + return None + + def set_body(self, key, body): + name = self._fn(key) + ".body" + self._write(name, body) + + def delete(self, key): + self._delete(key, "") + self._delete(key, ".body") + + def url_to_file_path(url, filecache): """Return the file cache path based on the URL. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cachecontrol-0.12.10/cachecontrol/caches/redis_cache.py new/cachecontrol-0.12.11/cachecontrol/caches/redis_cache.py --- old/cachecontrol-0.12.10/cachecontrol/caches/redis_cache.py 2021-11-05 18:07:09.000000000 +0100 +++ new/cachecontrol-0.12.11/cachecontrol/caches/redis_cache.py 2022-04-19 19:20:49.000000000 +0200 @@ -19,9 +19,11 @@ def set(self, key, value, expires=None): if not expires: self.conn.set(key, value) - else: + elif isinstance(expires, datetime): expires = expires - datetime.utcnow() self.conn.setex(key, int(expires.total_seconds()), value) + else: + self.conn.setex(key, expires, value) def delete(self, key): self.conn.delete(key) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cachecontrol-0.12.10/cachecontrol/controller.py new/cachecontrol-0.12.11/cachecontrol/controller.py --- old/cachecontrol-0.12.10/cachecontrol/controller.py 2021-11-05 18:07:09.000000000 +0100 +++ new/cachecontrol-0.12.11/cachecontrol/controller.py 2022-04-19 19:20:49.000000000 +0200 @@ -13,7 +13,7 @@ from requests.structures import CaseInsensitiveDict -from .cache import DictCache +from .cache import DictCache, SeparateBodyBaseCache from .serialize import Serializer @@ -27,15 +27,14 @@ def parse_uri(uri): """Parses a URI using the regex given in Appendix B of RFC 3986. - (scheme, authority, path, query, fragment) = parse_uri(uri) + (scheme, authority, path, query, fragment) = parse_uri(uri) """ groups = URI.match(uri).groups() return (groups[1], groups[3], groups[4], groups[6], groups[8]) class CacheController(object): - """An interface to see if request should cached or not. - """ + """An interface to see if request should cached or not.""" def __init__( self, cache=None, cache_etags=True, serializer=None, status_codes=None @@ -147,8 +146,13 @@ logger.debug("No cache entry available") return False + if isinstance(self.cache, SeparateBodyBaseCache): + body_file = self.cache.get_body(cache_url) + else: + body_file = None + # Check whether it can be deserialized - resp = self.serializer.loads(request, cache_data) + resp = self.serializer.loads(request, cache_data, body_file) if not resp: logger.warning("Cache entry deserialization failed, entry ignored") return False @@ -251,6 +255,26 @@ return new_headers + def _cache_set(self, cache_url, request, response, body=None, expires_time=None): + """ + Store the data in the cache. + """ + if isinstance(self.cache, SeparateBodyBaseCache): + # We pass in the body separately; just put a placeholder empty + # string in the metadata. + self.cache.set( + cache_url, + self.serializer.dumps(request, response, b""), + expires=expires_time, + ) + self.cache.set_body(cache_url, body) + else: + self.cache.set( + cache_url, + self.serializer.dumps(request, response, body), + expires=expires_time, + ) + def cache_response(self, request, response, body=None, status_codes=None): """ Algorithm for caching requests. @@ -326,17 +350,13 @@ logger.debug("etag object cached for {0} seconds".format(expires_time)) logger.debug("Caching due to etag") - self.cache.set( - cache_url, - self.serializer.dumps(request, response, body), - expires=expires_time, - ) + self._cache_set(cache_url, request, response, body, expires_time) # Add to the cache any permanent redirects. We do this before looking # that the Date headers. elif int(response.status) in PERMANENT_REDIRECT_STATUSES: logger.debug("Caching permanent redirect") - self.cache.set(cache_url, self.serializer.dumps(request, response, b"")) + self._cache_set(cache_url, request, response, b"") # Add to the cache if the response headers demand it. If there # is no date header then we can't do anything about expiring @@ -347,10 +367,12 @@ if "max-age" in cc and cc["max-age"] > 0: logger.debug("Caching b/c date exists and max-age > 0") expires_time = cc["max-age"] - self.cache.set( + self._cache_set( cache_url, - self.serializer.dumps(request, response, body), - expires=expires_time, + request, + response, + body, + expires_time, ) # If the request can expire, it means we should cache it @@ -368,10 +390,12 @@ expires_time ) ) - self.cache.set( + self._cache_set( cache_url, - self.serializer.dumps(request, response, body=body), - expires=expires_time, + request, + response, + body, + expires_time, ) def update_cached_response(self, request, response): @@ -410,6 +434,6 @@ cached_response.status = 200 # update our cache - self.cache.set(cache_url, self.serializer.dumps(request, cached_response)) + self._cache_set(cache_url, request, cached_response) return cached_response diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cachecontrol-0.12.10/cachecontrol/serialize.py new/cachecontrol-0.12.11/cachecontrol/serialize.py --- old/cachecontrol-0.12.10/cachecontrol/serialize.py 2021-11-05 18:07:09.000000000 +0100 +++ new/cachecontrol-0.12.11/cachecontrol/serialize.py 2022-04-19 19:20:49.000000000 +0200 @@ -44,7 +44,7 @@ # enough to have msgpack know the difference. data = { u"response": { - u"body": body, + u"body": body, # Empty bytestring if body is stored separately u"headers": dict( (text_type(k), text_type(v)) for k, v in response.headers.items() ), @@ -69,7 +69,7 @@ return b",".join([b"cc=4", msgpack.dumps(data, use_bin_type=True)]) - def loads(self, request, data): + def loads(self, request, data, body_file=None): # Short circuit if we've been given an empty set of data if not data: return @@ -92,14 +92,14 @@ # Dispatch to the actual load method for the given version try: - return getattr(self, "_loads_v{}".format(ver))(request, data) + return getattr(self, "_loads_v{}".format(ver))(request, data, body_file) except AttributeError: # This is a version we don't have a loads function for, so we'll # just treat it as a miss and return None return - def prepare_response(self, request, cached): + def prepare_response(self, request, cached, body_file=None): """Verify our vary headers match and construct a real urllib3 HTTPResponse object. """ @@ -125,7 +125,10 @@ cached["response"]["headers"] = headers try: - body = io.BytesIO(body_raw) + if body_file is None: + body = io.BytesIO(body_raw) + else: + body = body_file except TypeError: # This can happen if cachecontrol serialized to v1 format (pickle) # using Python 2. A Python 2 str(byte string) will be unpickled as @@ -137,21 +140,22 @@ return HTTPResponse(body=body, preload_content=False, **cached["response"]) - def _loads_v0(self, request, data): + def _loads_v0(self, request, data, body_file=None): # The original legacy cache data. This doesn't contain enough # information to construct everything we need, so we'll treat this as # a miss. return - def _loads_v1(self, request, data): + def _loads_v1(self, request, data, body_file=None): try: cached = pickle.loads(data) except ValueError: return - return self.prepare_response(request, cached) + return self.prepare_response(request, cached, body_file) - def _loads_v2(self, request, data): + def _loads_v2(self, request, data, body_file=None): + assert body_file is None try: cached = json.loads(zlib.decompress(data).decode("utf8")) except (ValueError, zlib.error): @@ -169,18 +173,18 @@ for k, v in cached["vary"].items() ) - return self.prepare_response(request, cached) + return self.prepare_response(request, cached, body_file) - def _loads_v3(self, request, data): + def _loads_v3(self, request, data, body_file): # Due to Python 2 encoding issues, it's impossible to know for sure # exactly how to load v3 entries, thus we'll treat these as a miss so # that they get rewritten out as v4 entries. return - def _loads_v4(self, request, data): + def _loads_v4(self, request, data, body_file=None): try: cached = msgpack.loads(data, raw=False) except ValueError: return - return self.prepare_response(request, cached) + return self.prepare_response(request, cached, body_file) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cachecontrol-0.12.10/docs/conf.py new/cachecontrol-0.12.11/docs/conf.py --- old/cachecontrol-0.12.10/docs/conf.py 2021-11-05 18:07:09.000000000 +0100 +++ new/cachecontrol-0.12.11/docs/conf.py 2022-04-19 19:20:49.000000000 +0200 @@ -52,9 +52,9 @@ # built documents. # # The short X.Y version. -version = "0.12.10" +version = "0.12.11" # The full version, including alpha/beta/rc tags. -release = "0.12.10" +release = "0.12.11" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cachecontrol-0.12.10/docs/release_notes.rst new/cachecontrol-0.12.11/docs/release_notes.rst --- old/cachecontrol-0.12.10/docs/release_notes.rst 2021-11-05 18:07:09.000000000 +0100 +++ new/cachecontrol-0.12.11/docs/release_notes.rst 2022-04-19 19:20:49.000000000 +0200 @@ -7,7 +7,12 @@ Release Notes =============== -0.13.0 +0.12.11 +======= + +* Added new variant of ``FileCache``, ``SeparateBodyFileCache``, which uses less memory by storing the body in a separate file than metadata, and streaming data in and out directly to/from that file. Implemented by [Itamar Turner-Trauring](https://pythonspeed.com), work sponsored by [G-Research](https://www.gresearch.co.uk/technology-innovation-and-open-source/). + +0.12.7 ====== * Dropped support for Python 2.7, 3.4, 3.5. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cachecontrol-0.12.10/docs/storage.rst new/cachecontrol-0.12.11/docs/storage.rst --- old/cachecontrol-0.12.10/docs/storage.rst 2021-11-05 18:07:09.000000000 +0100 +++ new/cachecontrol-0.12.11/docs/storage.rst 2022-04-19 19:20:49.000000000 +0200 @@ -56,17 +56,33 @@ forever_cache = FileCache('.web_cache', forever=True) sess = CacheControl(requests.Session(), forever_cache) +SeparateBodyFileCache +===================== -:A Note About Pickle: +This is similar to ``FileCache``, but far more memory efficient, and therefore recommended if you expect to be caching large downloads. +``FileCache`` results in memory usage that can be 2?? or 3?? of the downloaded file, whereas ``SeparateBodyFileCache`` should have fixed memory usage. - It should be noted that the `FileCache` uses pickle to store the - cached response. Prior to `requests 2.1`_, `requests.Response` - objects were not 'pickleable' due to the use of `IOBase` base - classes in `urllib3` `HTTPResponse` objects. In CacheControl we work - around this by patching the Response objects with the appropriate - `__getstate__` and `__setstate__` methods when the requests version - doesn't natively support Response pickling. +The body of the request is stored in a separate file than metadata, and streamed in and out. +It requires `lockfile`_ be installed as it prevents multiple threads from writing to the same file at the same time. + +.. note:: + + You can install this dependency automatically with pip + by requesting the *filecache* extra: :: + + pip install cachecontrol[filecache] + +Here is an example of using the cache:: + + import requests + from cachecontrol import CacheControl + from cachecontrol.caches SeparateBodyFileCache + + sess = CacheControl(requests.Session(), + cache=SeparatedBodyFileCache('.web_cache')) + +``SeparateBodyFileCache`` supports the same options as ``FileCache``. RedisCache diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cachecontrol-0.12.10/setup.py new/cachecontrol-0.12.11/setup.py --- old/cachecontrol-0.12.10/setup.py 2021-11-05 18:07:09.000000000 +0100 +++ new/cachecontrol-0.12.11/setup.py 2022-04-19 19:20:49.000000000 +0200 @@ -6,7 +6,7 @@ long_description = open("README.rst").read() -VERSION = "0.12.10" +VERSION = "0.12.11" setup_params = dict( name="CacheControl", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cachecontrol-0.12.10/tests/test_cache_control.py new/cachecontrol-0.12.11/tests/test_cache_control.py --- old/cachecontrol-0.12.10/tests/test_cache_control.py 2021-11-05 18:07:09.000000000 +0100 +++ new/cachecontrol-0.12.11/tests/test_cache_control.py 2022-04-19 19:20:49.000000000 +0200 @@ -21,7 +21,7 @@ def dumps(self, request, response): return response - def loads(self, request, data): + def loads(self, request, data, body_file=None): return data diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cachecontrol-0.12.10/tests/test_etag.py new/cachecontrol-0.12.11/tests/test_etag.py --- old/cachecontrol-0.12.10/tests/test_etag.py 2021-11-05 18:07:09.000000000 +0100 +++ new/cachecontrol-0.12.11/tests/test_etag.py 2022-04-19 19:20:49.000000000 +0200 @@ -18,7 +18,7 @@ def dumps(self, request, response, body=None): return response - def loads(self, request, data): + def loads(self, request, data, body_file=None): if data and getattr(data, "chunked", False): data.chunked = False return data diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cachecontrol-0.12.10/tests/test_max_age.py new/cachecontrol-0.12.11/tests/test_max_age.py --- old/cachecontrol-0.12.10/tests/test_max_age.py 2021-11-05 18:07:09.000000000 +0100 +++ new/cachecontrol-0.12.11/tests/test_max_age.py 2022-04-19 19:20:49.000000000 +0200 @@ -15,7 +15,7 @@ def dumps(self, request, response, body=None): return response - def loads(self, request, data): + def loads(self, request, data, body_file=None): if data and getattr(data, "chunked", False): data.chunked = False return data diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cachecontrol-0.12.10/tests/test_storage_filecache.py new/cachecontrol-0.12.11/tests/test_storage_filecache.py --- old/cachecontrol-0.12.10/tests/test_storage_filecache.py 2021-11-05 18:07:09.000000000 +0100 +++ new/cachecontrol-0.12.11/tests/test_storage_filecache.py 2022-04-19 19:20:49.000000000 +0200 @@ -13,7 +13,7 @@ import pytest import requests from cachecontrol import CacheControl -from cachecontrol.caches import FileCache +from cachecontrol.caches import FileCache, SeparateBodyFileCache from lockfile import LockFile from lockfile.mkdirlockfile import MkdirLockFile @@ -25,12 +25,14 @@ return "&{}={}".format(key, val) -class TestStorageFileCache(object): +class FileCacheTestsMixin(object): + + FileCacheClass = None # Either FileCache or SeparateBodyFileCache @pytest.fixture() def sess(self, url, tmpdir): self.url = url - self.cache = FileCache(str(tmpdir)) + self.cache = self.FileCacheClass(str(tmpdir)) sess = CacheControl(requests.Session(), cache=self.cache) yield sess @@ -94,7 +96,7 @@ def test_cant_use_dir_and_lock_class(self, tmpdir): with pytest.raises(ValueError): - FileCache(str(tmpdir), use_dir_lock=True, lock_class=object()) + self.FileCacheClass(str(tmpdir), use_dir_lock=True, lock_class=object()) @pytest.mark.parametrize( ("value", "expected"), @@ -102,16 +104,16 @@ ) def test_simple_lockfile_arg(self, tmpdir, value, expected): if value is not None: - cache = FileCache(str(tmpdir), use_dir_lock=value) + cache = self.FileCacheClass(str(tmpdir), use_dir_lock=value) else: - cache = FileCache(str(tmpdir)) + cache = self.FileCacheClass(str(tmpdir)) assert issubclass(cache.lock_class, expected) cache.close() def test_lock_class(self, tmpdir): lock_class = object() - cache = FileCache(str(tmpdir), lock_class=lock_class) + cache = self.FileCacheClass(str(tmpdir), lock_class=lock_class) assert cache.lock_class is lock_class cache.close() @@ -126,3 +128,58 @@ url = self.url + "".join(sample(string.ascii_lowercase, randint(2, 4))) sess.put(url) assert True # test verifies no exceptions were raised + + +class TestFileCache(FileCacheTestsMixin): + """ + Tests for ``FileCache``. + """ + + FileCacheClass = FileCache + + def test_body_stored_inline(self, sess): + """The body is stored together with the metadata.""" + url = self.url + "cache_60" + response = sess.get(url) + body = response.content + response2 = sess.get(url) + assert response2.from_cache + assert response2.content == body + + # OK now let's violate some abstraction boundaries to make sure body + # was stored in metadata file. + with open(self.cache._fn(url), "rb") as f: + assert body in f.read() + assert not os.path.exists(self.cache._fn(url) + ".body") + + +class TestSeparateBodyFileCache(FileCacheTestsMixin): + """ + Tests for ``SeparateBodyFileCache`` + """ + + FileCacheClass = SeparateBodyFileCache + + def test_body_actually_stored_separately(self, sess): + """ + Body is stored and can be retrieved from the SeparateBodyFileCache, with assurances + it's actually being loaded from separate file than metadata. + """ + url = self.url + "cache_60" + response = sess.get(url) + body = response.content + response2 = sess.get(url) + assert response2.from_cache + assert response2.content == body + + # OK now let's violate some abstraction boundaries to make sure body + # actually came from separate file. + with open(self.cache._fn(url), "rb") as f: + assert body not in f.read() + with open(self.cache._fn(url) + ".body", "rb") as f: + assert body == f.read() + with open(self.cache._fn(url) + ".body", "wb") as f: + f.write(b"CORRUPTED") + response2 = sess.get(url) + assert response2.from_cache + assert response2.content == b"CORRUPTED" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cachecontrol-0.12.10/tests/test_storage_redis.py new/cachecontrol-0.12.11/tests/test_storage_redis.py --- old/cachecontrol-0.12.10/tests/test_storage_redis.py 2021-11-05 18:07:09.000000000 +0100 +++ new/cachecontrol-0.12.11/tests/test_storage_redis.py 2022-04-19 19:20:49.000000000 +0200 @@ -14,6 +14,10 @@ self.conn = Mock() self.cache = RedisCache(self.conn) - def test_set_expiration(self): + def test_set_expiration_datetime(self): self.cache.set("foo", "bar", expires=datetime(2014, 2, 2)) assert self.conn.setex.called + + def test_set_expiration_int(self): + self.cache.set("foo", "bar", expires=600) + assert self.conn.setex.called