cache.py 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. """HTTP cache implementation.
  2. """
  3. import os
  4. from contextlib import contextmanager
  5. from typing import Generator, Optional
  6. from pip._vendor.cachecontrol.cache import BaseCache
  7. from pip._vendor.cachecontrol.caches import FileCache
  8. from pip._vendor.requests.models import Response
  9. from pip._internal.utils.filesystem import adjacent_tmp_file, replace
  10. from pip._internal.utils.misc import ensure_dir
  11. def is_from_cache(response: Response) -> bool:
  12. return getattr(response, "from_cache", False)
  13. @contextmanager
  14. def suppressed_cache_errors() -> Generator[None, None, None]:
  15. """If we can't access the cache then we can just skip caching and process
  16. requests as if caching wasn't enabled.
  17. """
  18. try:
  19. yield
  20. except OSError:
  21. pass
  22. class SafeFileCache(BaseCache):
  23. """
  24. A file based cache which is safe to use even when the target directory may
  25. not be accessible or writable.
  26. """
  27. def __init__(self, directory: str) -> None:
  28. assert directory is not None, "Cache directory must not be None."
  29. super().__init__()
  30. self.directory = directory
  31. def _get_cache_path(self, name: str) -> str:
  32. # From cachecontrol.caches.file_cache.FileCache._fn, brought into our
  33. # class for backwards-compatibility and to avoid using a non-public
  34. # method.
  35. hashed = FileCache.encode(name)
  36. parts = list(hashed[:5]) + [hashed]
  37. return os.path.join(self.directory, *parts)
  38. def get(self, key: str) -> Optional[bytes]:
  39. path = self._get_cache_path(key)
  40. with suppressed_cache_errors():
  41. with open(path, "rb") as f:
  42. return f.read()
  43. def set(self, key: str, value: bytes, expires: Optional[int] = None) -> None:
  44. path = self._get_cache_path(key)
  45. with suppressed_cache_errors():
  46. ensure_dir(os.path.dirname(path))
  47. with adjacent_tmp_file(path) as f:
  48. f.write(value)
  49. replace(f.name, path)
  50. def delete(self, key: str) -> None:
  51. path = self._get_cache_path(key)
  52. with suppressed_cache_errors():
  53. os.remove(path)