diff --git a/.coveragerc b/.coveragerc index 5bac98ea1..d6c1ccb12 100644 --- a/.coveragerc +++ b/.coveragerc @@ -7,3 +7,6 @@ omit = [report] include = piptools/*, tests/* +exclude_lines = + # Don't complain about typing imports + if MYPY: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8fa258ac5..85268fee4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,3 +20,10 @@ repos: - id: bandit language_version: python3 exclude: ^tests/ + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.740 + hooks: + - id: mypy + language_version: python3 + # To prevent "Duplicate module named 'setup'" error + exclude: ^tests/test_data/packages/ diff --git a/piptools/_compat/typing.py b/piptools/_compat/typing.py new file mode 100644 index 000000000..0f829064b --- /dev/null +++ b/piptools/_compat/typing.py @@ -0,0 +1,2 @@ +# The pattern is taken from https://github.com/python/mypy/issues/3216 +MYPY = False diff --git a/piptools/cache.py b/piptools/cache.py index f31f74250..5c5fb3454 100644 --- a/piptools/cache.py +++ b/piptools/cache.py @@ -7,16 +7,23 @@ from pip._vendor.packaging.requirements import Requirement +from ._compat.typing import MYPY from .exceptions import PipToolsError from .locations import CACHE_DIR from .utils import as_tuple, key_from_req, lookup_table +if MYPY: + from typing import Dict, List, Optional, Set, Tuple + from ._compat import InstallRequirement + class CorruptCacheError(PipToolsError): def __init__(self, path): + # type: (str) -> None self.path = path def __str__(self): + # type: () -> str lines = [ "The dependency cache seems to have been corrupted.", "Inspect, or delete, the following file:", @@ -26,6 +33,7 @@ def __str__(self): def read_cache_file(cache_file_path): + # type: (str) -> Dict[str, dict] with open(cache_file_path, "r") as cache_file: try: doc = json.load(cache_file) @@ -49,7 +57,10 @@ class DependencyCache(object): Where X.Y indicates the Python version. """ + _cache = None # type: Dict[str, dict] + def __init__(self, cache_dir=None): + # type: (Optional[str]) -> None if cache_dir is None: cache_dir = CACHE_DIR if not os.path.isdir(cache_dir): @@ -58,10 +69,10 @@ def __init__(self, cache_dir=None): cache_filename = "depcache-py{}.json".format(py_version) self._cache_file = os.path.join(cache_dir, cache_filename) - self._cache = None @property def cache(self): + # type: () -> Dict[str, dict] """ The dictionary that is the actual in-memory cache. This property lazily loads the cache from disk. @@ -71,6 +82,7 @@ def cache(self): return self._cache def as_cache_key(self, ireq): + # type: (InstallRequirement) -> Tuple[str, str] """ Given a requirement, return its cache key. This behavior is a little weird in order to allow backwards compatibility with cache files. For a requirement @@ -91,6 +103,7 @@ def as_cache_key(self, ireq): return name, "{}{}".format(version, extras_string) def read_cache(self): + # type: () -> None """Reads the cached contents into memory.""" if os.path.exists(self._cache_file): self._cache = read_cache_file(self._cache_file) @@ -98,30 +111,36 @@ def read_cache(self): self._cache = {} def write_cache(self): + # type: () -> None """Writes the cache to disk as JSON.""" doc = {"__format__": 1, "dependencies": self._cache} with open(self._cache_file, "w") as f: json.dump(doc, f, sort_keys=True) def clear(self): + # type: () -> None self._cache = {} self.write_cache() def __contains__(self, ireq): + # type: (InstallRequirement) -> bool pkgname, pkgversion_and_extras = self.as_cache_key(ireq) return pkgversion_and_extras in self.cache.get(pkgname, {}) def __getitem__(self, ireq): + # type: (InstallRequirement) -> List[str] pkgname, pkgversion_and_extras = self.as_cache_key(ireq) return self.cache[pkgname][pkgversion_and_extras] def __setitem__(self, ireq, values): + # type: (InstallRequirement, List[str]) -> None pkgname, pkgversion_and_extras = self.as_cache_key(ireq) self.cache.setdefault(pkgname, {}) self.cache[pkgname][pkgversion_and_extras] = values self.write_cache() def reverse_dependencies(self, ireqs): + # type: (List[InstallRequirement]) -> Dict[str, Set[str]] """ Returns a lookup table of reverse dependencies for all the given ireqs. @@ -134,6 +153,7 @@ def reverse_dependencies(self, ireqs): return self._reverse_dependencies(ireqs_as_cache_values) def _reverse_dependencies(self, cache_keys): + # type: (List[Tuple[str, str]]) -> Dict[str, Set[str]] """ Returns a lookup table of reverse dependencies for all the given cache keys. diff --git a/setup.cfg b/setup.cfg index 5b598a2de..8ee679e81 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,3 +25,10 @@ line_length = 88 multi_line_output = 3 default_section=THIRDPARTY known_first_party = piptools, tests, examples + +[mypy] +follow_imports = silent +ignore_missing_imports = True + +[mypy-tests.*] +ignore_errors = True