forked from jazzband/pip-tools
/
local.py
102 lines (84 loc) · 3.56 KB
/
local.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import optparse
from contextlib import contextmanager
from typing import Iterator, Mapping, Optional, Set, cast
from pip._internal.index.package_finder import PackageFinder
from pip._internal.models.candidate import InstallationCandidate
from pip._internal.req import InstallRequirement
from pip._internal.utils.hashes import FAVORITE_HASH
from pip._vendor.requests import Session
from piptools.utils import as_tuple, key_from_ireq, make_install_requirement
from .base import BaseRepository
from .pypi import PyPIRepository
def ireq_satisfied_by_existing_pin(
ireq: InstallRequirement, existing_pin: InstallationCandidate
) -> bool:
"""
Return True if the given InstallationRequirement is satisfied by the
previously encountered version pin.
"""
version = next(iter(existing_pin.req.specifier)).version
result = ireq.req.specifier.contains(
version, prereleases=existing_pin.req.specifier.prereleases
)
return cast(bool, result)
class LocalRequirementsRepository(BaseRepository):
"""
The LocalRequirementsRepository proxied the _real_ repository by first
checking if a requirement can be satisfied by existing pins (i.e. the
result of a previous compile step).
In effect, if a requirement can be satisfied with a version pinned in the
requirements file, we prefer that version over the best match found in
PyPI. This keeps updates to the requirements.txt down to a minimum.
"""
def __init__(
self,
existing_pins: Mapping[str, InstallationCandidate],
proxied_repository: PyPIRepository,
reuse_hashes: bool = True,
):
self._reuse_hashes = reuse_hashes
self.repository = proxied_repository
self.existing_pins = existing_pins
@property
def options(self) -> optparse.Values:
return self.repository.options
@property
def finder(self) -> PackageFinder:
return self.repository.finder
@property
def session(self) -> Session:
return self.repository.session
def clear_caches(self) -> None:
self.repository.clear_caches()
def find_best_match(
self, ireq: InstallRequirement, prereleases: Optional[bool] = None
) -> InstallationCandidate:
key = key_from_ireq(ireq)
existing_pin = self.existing_pins.get(key)
if existing_pin and ireq_satisfied_by_existing_pin(ireq, existing_pin):
project, version, _ = as_tuple(existing_pin)
return make_install_requirement(project, version, ireq)
else:
return self.repository.find_best_match(ireq, prereleases)
def get_dependencies(self, ireq: InstallRequirement) -> Set[InstallRequirement]:
return self.repository.get_dependencies(ireq)
def get_hashes(self, ireq: InstallRequirement) -> Set[str]:
existing_pin = self._reuse_hashes and self.existing_pins.get(
key_from_ireq(ireq)
)
if existing_pin and ireq_satisfied_by_existing_pin(ireq, existing_pin):
hashes = existing_pin.hash_options
hexdigests = hashes.get(FAVORITE_HASH)
if hexdigests:
return {
":".join([FAVORITE_HASH, hexdigest]) for hexdigest in hexdigests
}
return self.repository.get_hashes(ireq)
@contextmanager
def allow_all_wheels(self) -> Iterator[None]:
with self.repository.allow_all_wheels():
yield
def copy_ireq_dependencies(
self, source: InstallRequirement, dest: InstallRequirement
) -> None:
self.repository.copy_ireq_dependencies(source, dest)