All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
1 """Caching of formatted files with feature-based invalidation."""
5 from pathlib import Path
7 from typing import Dict, Iterable, Set, Tuple
9 from platformdirs import user_cache_dir
11 from black.mode import Mode
13 from _black_version import version as __version__
19 CacheInfo = Tuple[Timestamp, FileSize]
20 Cache = Dict[str, CacheInfo]
23 def get_cache_dir() -> Path:
24 """Get the cache directory used by black.
26 Users can customize this directory on all systems using `BLACK_CACHE_DIR`
27 environment variable. By default, the cache directory is the user cache directory
28 under the black application.
30 This result is immediately set to a constant `black.cache.CACHE_DIR` as to avoid
33 # NOTE: Function mostly exists as a clean way to test getting the cache directory.
34 default_cache_dir = user_cache_dir("black", version=__version__)
35 cache_dir = Path(os.environ.get("BLACK_CACHE_DIR", default_cache_dir))
39 CACHE_DIR = get_cache_dir()
42 def read_cache(mode: Mode) -> Cache:
43 """Read the cache if it exists and is well formed.
45 If it is not well formed, the call to write_cache later should resolve the issue.
47 cache_file = get_cache_file(mode)
48 if not cache_file.exists():
51 with cache_file.open("rb") as fobj:
53 cache: Cache = pickle.load(fobj)
54 except (pickle.UnpicklingError, ValueError, IndexError):
60 def get_cache_file(mode: Mode) -> Path:
61 return CACHE_DIR / f"cache.{mode.get_cache_key()}.pickle"
64 def get_cache_info(path: Path) -> CacheInfo:
65 """Return the information used to check if a file is already formatted or not."""
67 return stat.st_mtime, stat.st_size
70 def filter_cached(cache: Cache, sources: Iterable[Path]) -> Tuple[Set[Path], Set[Path]]:
71 """Split an iterable of paths in `sources` into two sets.
73 The first contains paths of files that modified on disk or are not in the
74 cache. The other contains paths to non-modified files.
76 todo, done = set(), set()
78 res_src = src.resolve()
79 if cache.get(str(res_src)) != get_cache_info(res_src):
86 def write_cache(cache: Cache, sources: Iterable[Path], mode: Mode) -> None:
87 """Update the cache file."""
88 cache_file = get_cache_file(mode)
90 CACHE_DIR.mkdir(parents=True, exist_ok=True)
93 **{str(src.resolve()): get_cache_info(src) for src in sources},
95 with tempfile.NamedTemporaryFile(dir=str(cache_file.parent), delete=False) as f:
96 pickle.dump(new_cache, f, protocol=4)
97 os.replace(f.name, cache_file)