Source code for flakeheaven._logic._snapshot

# built-in
import contextlib
import json
import os
from datetime import timedelta
from hashlib import md5
from pathlib import Path
from time import time
from typing import Optional

# external
from flake8.checker import FileChecker
from flake8.options.manager import OptionManager


CACHE_PATH = Path(
    os.environ.get('FLAKEHEAVEN_CACHE', Path().resolve() / '.flakeheaven_cache'),
)

THRESHOLD = int(os.getenv('FLAKEHEAVEN_CACHE_TIMEOUT', timedelta(days=1).total_seconds()))


[docs]def prepare_cache(path=CACHE_PATH): if not path.exists(): path.mkdir(parents=True, exist_ok=True) return _time = time() for fpath in path.iterdir(): with contextlib.suppress(FileNotFoundError): if _time - fpath.stat().st_atime <= THRESHOLD: continue fpath.unlink()
[docs]class Snapshot: _exists: Optional[bool] = None _digest: Optional[str] = None _results = None def __init__(self, *, cache_path: Path, file_path: Path): self.cache_path = cache_path self.file_path = file_path
[docs] @classmethod def create(cls, checker: FileChecker, options: OptionManager) -> 'Snapshot': hasher = md5() # plugins config plugins = json.dumps(options.plugins, sort_keys=True) hasher.update(plugins.encode()) # file path file_path = Path(checker.filename).resolve() hasher.update(str(file_path).encode()) return cls( cache_path=CACHE_PATH / (hasher.hexdigest() + '.json'), file_path=file_path, )
[docs] def exists(self) -> bool: """Returns True if cache file exists and is actual.""" if self._exists is not None: return self._exists if not self.cache_path.exists(): self._exists = False return self._exists # digest is None for non-existent files (stdin) if self.digest is None: return False # check that file content wasn't changed since the snapshot cache = json.loads(self.cache_path.read_text()) self._exists = self.digest == cache['digest'] # if cache is valid results will be eventually requested. # let's save it for later use to avoid reading the cache twice if self._exists: self._results = cache['results'] return self._exists # type: ignore
@property def digest(self) -> Optional[str]: """Get hex digest for the current content of the file""" # we cache it because it requested twice: from `exists` and from `dumps` if self._digest is None: if not self.file_path.exists(): return None hasher = md5() hasher.update(self.file_path.read_bytes()) self._digest = hasher.hexdigest() return self._digest
[docs] def dump(self, results) -> None: self.cache_path.write_text(self.dumps(results=results))
[docs] def dumps(self, results) -> str: return json.dumps( dict(results=results, digest=self.digest), )
@property def results(self): """returns cached checks results for the given file""" # results could be cached from `.exists()`. # however, we don't want to cache the results on requets # because they are always requested only once if self._results is not None: return self._results return json.loads(self.cache_path.read_text())['results']