-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7062 from chrahunt/refactor/network-modules
Move SafeFileCache to network.cache
- Loading branch information
Showing
4 changed files
with
135 additions
and
124 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
"""HTTP cache implementation. | ||
""" | ||
import os | ||
from contextlib import contextmanager | ||
|
||
from pip._vendor.cachecontrol.cache import BaseCache | ||
from pip._vendor.cachecontrol.caches import FileCache | ||
|
||
from pip._internal.utils.filesystem import adjacent_tmp_file, replace | ||
from pip._internal.utils.misc import ensure_dir | ||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING | ||
|
||
if MYPY_CHECK_RUNNING: | ||
from typing import Optional | ||
|
||
|
||
@contextmanager | ||
def suppressed_cache_errors(): | ||
"""If we can't access the cache then we can just skip caching and process | ||
requests as if caching wasn't enabled. | ||
""" | ||
try: | ||
yield | ||
except (OSError, IOError): | ||
pass | ||
|
||
|
||
class SafeFileCache(BaseCache): | ||
""" | ||
A file based cache which is safe to use even when the target directory may | ||
not be accessible or writable. | ||
""" | ||
|
||
def __init__(self, directory): | ||
# type: (str) -> None | ||
assert directory is not None, "Cache directory must not be None." | ||
super(SafeFileCache, self).__init__() | ||
self.directory = directory | ||
|
||
def _get_cache_path(self, name): | ||
# type: (str) -> str | ||
# From cachecontrol.caches.file_cache.FileCache._fn, brought into our | ||
# class for backwards-compatibility and to avoid using a non-public | ||
# method. | ||
hashed = FileCache.encode(name) | ||
parts = list(hashed[:5]) + [hashed] | ||
return os.path.join(self.directory, *parts) | ||
|
||
def get(self, key): | ||
# type: (str) -> Optional[bytes] | ||
path = self._get_cache_path(key) | ||
with suppressed_cache_errors(): | ||
with open(path, 'rb') as f: | ||
return f.read() | ||
|
||
def set(self, key, value): | ||
# type: (str, bytes) -> None | ||
path = self._get_cache_path(key) | ||
with suppressed_cache_errors(): | ||
ensure_dir(os.path.dirname(path)) | ||
|
||
with adjacent_tmp_file(path) as f: | ||
f.write(value) | ||
|
||
replace(f.name, path) | ||
|
||
def delete(self, key): | ||
# type: (str) -> None | ||
path = self._get_cache_path(key) | ||
with suppressed_cache_errors(): | ||
os.remove(path) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import os | ||
|
||
import pytest | ||
from mock import Mock | ||
from pip._vendor.cachecontrol.caches import FileCache | ||
|
||
from pip._internal.network.cache import SafeFileCache | ||
|
||
|
||
@pytest.fixture(scope="function") | ||
def cache_tmpdir(tmpdir): | ||
cache_dir = tmpdir.joinpath("cache") | ||
cache_dir.mkdir(parents=True) | ||
yield cache_dir | ||
|
||
|
||
class TestSafeFileCache: | ||
""" | ||
The no_perms test are useless on Windows since SafeFileCache uses | ||
pip._internal.utils.filesystem.check_path_owner which is based on | ||
os.geteuid which is absent on Windows. | ||
""" | ||
|
||
def test_cache_roundtrip(self, cache_tmpdir): | ||
|
||
cache = SafeFileCache(cache_tmpdir) | ||
assert cache.get("test key") is None | ||
cache.set("test key", b"a test string") | ||
assert cache.get("test key") == b"a test string" | ||
cache.delete("test key") | ||
assert cache.get("test key") is None | ||
|
||
@pytest.mark.skipif("sys.platform == 'win32'") | ||
def test_safe_get_no_perms(self, cache_tmpdir, monkeypatch): | ||
os.chmod(cache_tmpdir, 000) | ||
|
||
monkeypatch.setattr(os.path, "exists", lambda x: True) | ||
|
||
cache = SafeFileCache(cache_tmpdir) | ||
cache.get("foo") | ||
|
||
@pytest.mark.skipif("sys.platform == 'win32'") | ||
def test_safe_set_no_perms(self, cache_tmpdir): | ||
os.chmod(cache_tmpdir, 000) | ||
|
||
cache = SafeFileCache(cache_tmpdir) | ||
cache.set("foo", b"bar") | ||
|
||
@pytest.mark.skipif("sys.platform == 'win32'") | ||
def test_safe_delete_no_perms(self, cache_tmpdir): | ||
os.chmod(cache_tmpdir, 000) | ||
|
||
cache = SafeFileCache(cache_tmpdir) | ||
cache.delete("foo") | ||
|
||
def test_cache_hashes_are_same(self, cache_tmpdir): | ||
cache = SafeFileCache(cache_tmpdir) | ||
key = "test key" | ||
fake_cache = Mock( | ||
FileCache, directory=cache.directory, encode=FileCache.encode | ||
) | ||
assert cache._get_cache_path(key) == FileCache._fn(fake_cache, key) |