def test_finder_priority_nonegg_over_eggfragments(): """Test PackageFinder prefers non-egg links over "#egg=" links""" req = InstallRequirement.from_line('bar==1.0', None) links = ['http://foo/bar.py#egg=bar-1.0', 'http://foo/bar-1.0.tar.gz'] finder = PackageFinder(links, [], session=PipSession()) with patch.object(finder, "_get_pages", lambda x, y: []): all_versions = finder._find_all_versions(req.name) assert all_versions[0].location.url.endswith('tar.gz') assert all_versions[1].location.url.endswith('#egg=bar-1.0') link = finder.find_requirement(req, False) assert link.url.endswith('tar.gz') links.reverse() finder = PackageFinder(links, [], session=PipSession()) with patch.object(finder, "_get_pages", lambda x, y: []): all_versions = finder._find_all_versions(req.name) assert all_versions[0].location.url.endswith('tar.gz') assert all_versions[1].location.url.endswith('#egg=bar-1.0') link = finder.find_requirement(req, False) assert link.url.endswith('tar.gz')
def test_finder_priority_nonegg_over_eggfragments(): """Test PackageFinder prefers non-egg links over "#egg=" links""" req = InstallRequirement.from_line('bar==1.0', None) links = ['http://foo/bar.py#egg=bar-1.0', 'http://foo/bar-1.0.tar.gz'] finder = PackageFinder(links, [], session=PipSession()) with patch.object(finder, "_get_pages", lambda x, y: []): all_versions = finder._find_all_versions(req.name) assert all_versions[0].location.url.endswith('tar.gz') assert all_versions[1].location.url.endswith('#egg=bar-1.0') link = finder.find_requirement(req, False) assert link.url.endswith('tar.gz') links.reverse() finder = PackageFinder(links, [], session=PipSession()) with patch.object(finder, "_get_pages", lambda x, y: []): all_versions = finder._find_all_versions(req.name) assert all_versions[0].location.url.endswith('tar.gz') assert all_versions[1].location.url.endswith('#egg=bar-1.0') link = finder.find_requirement(req, False) assert link.url.endswith('tar.gz')
def test_find_all_versions_find_links_and_index(data): finder = PackageFinder([data.find_links], [data.index_url('simple')], session=PipSession()) versions = finder._find_all_versions( InstallRequirement.from_line('simple')) # first the find-links versions then the page versions assert [str(v.version) for v in versions] == ['3.0', '2.0', '1.0', '1.0']
def pip_package_versions(index, package): format_control = FormatControl(no_binary=(':all:'), only_binary=()) session = PipSession() finder = PackageFinder([], [index], format_control=format_control, session=session, allow_external=[package], allow_unverified=[package]) return sorted((PipPackage(str(pv.version), pv.location) for pv in finder._find_all_versions(package)), # pylint: disable=protected-access key=lambda pp: (pp.version, {'.tar.gz': 1, '.zip': 2, '.tar.bz2': 3}.get(pp.link.ext, 10000)))
def test_finder_priority_file_over_page(data): """Test PackageFinder prefers file links over equivalent page links""" req = InstallRequirement.from_line('gmpy==1.15', None) finder = PackageFinder( [data.find_links], ["http://pypi.python.org/simple"], session=PipSession(), ) all_versions = finder._find_all_versions(req.name) # 1 file InstallationCandidate followed by all https ones assert all_versions[0].location.scheme == 'file' assert all(version.location.scheme == 'https' for version in all_versions[1:]), all_versions link = finder.find_requirement(req, False) assert link.url.startswith("file://")
def test_finder_priority_file_over_page(data): """Test PackageFinder prefers file links over equivalent page links""" req = InstallRequirement.from_line('gmpy==1.15', None) finder = PackageFinder( [data.find_links], ["http://pypi.python.org/simple"], session=PipSession(), ) all_versions = finder._find_all_versions(req.name) # 1 file InstallationCandidate followed by all https ones assert all_versions[0].location.scheme == 'file' assert all(version.location.scheme == 'https' for version in all_versions[1:]), all_versions link = finder.find_requirement(req, False) assert link.url.startswith("file://")
def test_finder_priority_page_over_deplink(): """ Test PackageFinder prefers page links over equivalent dependency links """ req = InstallRequirement.from_line('pip==1.5.6', None) finder = PackageFinder( [], ["https://pypi.python.org/simple"], process_dependency_links=True, session=PipSession(), ) finder.add_dependency_links([ 'https://warehouse.python.org/packages/source/p/pip/pip-1.5.6.tar.gz']) all_versions = finder._find_all_versions(req.name) # Check that the dependency_link is last assert all_versions[-1].location.url.startswith('https://warehouse') link = finder.find_requirement(req, False) assert link.url.startswith("https://pypi"), link
def test_finder_priority_page_over_deplink(): """ Test PackageFinder prefers page links over equivalent dependency links """ req = InstallRequirement.from_line('pip==1.5.6', None) finder = PackageFinder( [], ["https://pypi.python.org/simple"], process_dependency_links=True, session=PipSession(), ) finder.add_dependency_links([ 'https://warehouse.python.org/packages/source/p/pip/pip-1.5.6.tar.gz']) all_versions = finder._find_all_versions(req.name) # Check that the dependency_link is last assert all_versions[-1].location.url.startswith('https://warehouse') link = finder.find_requirement(req, False) assert link.url.startswith("https://pypi"), link
class PyPIRepository(BaseRepository): DEFAULT_INDEX_URL = 'https://pypi.python.org/simple' """ The PyPIRepository will use the provided Finder instance to lookup packages. Typically, it looks up packages on PyPI (the default implicit config), but any other PyPI mirror can be used if index_urls is changed/configured on the Finder. """ def __init__(self, pip_options, session): self.session = session index_urls = [pip_options.index_url] + pip_options.extra_index_urls if pip_options.no_index: index_urls = [] self.finder = PackageFinder( find_links=pip_options.find_links, index_urls=index_urls, trusted_hosts=pip_options.trusted_hosts, allow_all_prereleases=pip_options.pre, process_dependency_links=pip_options.process_dependency_links, session=self.session, ) # Caches # stores project_name => InstallationCandidate mappings for all # versions reported by PyPI, so we only have to ask once for each # project self._available_candidates_cache = {} # stores InstallRequirement => list(InstallRequirement) mappings # of all secondary dependencies for the given requirement, so we # only have to go to disk once for each requirement self._dependencies_cache = {} # Setup file paths self.freshen_build_caches() self._download_dir = fs_str(os.path.join(CACHE_DIR, 'pkgs')) self._wheel_download_dir = fs_str(os.path.join(CACHE_DIR, 'wheels')) def freshen_build_caches(self): """ Start with fresh build/source caches. Will remove any old build caches from disk automatically. """ self._build_dir = TemporaryDirectory(fs_str('build')) self._source_dir = TemporaryDirectory(fs_str('source')) @property def build_dir(self): return self._build_dir.name @property def source_dir(self): return self._source_dir.name def clear_caches(self): rmtree(self._download_dir, ignore_errors=True) rmtree(self._wheel_download_dir, ignore_errors=True) def find_all_candidates(self, req_name): if req_name not in self._available_candidates_cache: # pip 8 changed the internal API, making this a public method if pip_version_info >= (8, 0): candidates = self.finder.find_all_candidates(req_name) else: candidates = self.finder._find_all_versions(req_name) self._available_candidates_cache[req_name] = candidates return self._available_candidates_cache[req_name] def find_best_match(self, ireq, prereleases=None): """ Returns a Version object that indicates the best match for the given InstallRequirement according to the external repository. """ if ireq.editable: return ireq # return itself as the best match all_candidates = self.find_all_candidates(ireq.name) candidates_by_version = lookup_table(all_candidates, key=lambda c: c.version, unique=True) matching_versions = ireq.specifier.filter( (candidate.version for candidate in all_candidates), prereleases=prereleases) # Reuses pip's internal candidate sort key to sort matching_candidates = [ candidates_by_version[ver] for ver in matching_versions ] if not matching_candidates: raise NoCandidateFound(ireq, all_candidates) best_candidate = max(matching_candidates, key=self.finder._candidate_sort_key) # Turn the candidate into a pinned InstallRequirement return make_install_requirement(best_candidate.project, best_candidate.version, ireq.extras, constraint=ireq.constraint) def get_dependencies(self, ireq): """ Given a pinned or an editable InstallRequirement, returns a set of dependencies (also InstallRequirements, but not necessarily pinned). They indicate the secondary dependencies for the given requirement. """ if not (ireq.editable or is_pinned_requirement(ireq)): raise TypeError( 'Expected pinned or editable InstallRequirement, got {}'. format(ireq)) if ireq not in self._dependencies_cache: if ireq.editable and (ireq.source_dir and os.path.exists(ireq.source_dir)): # No download_dir for locally available editable requirements. # If a download_dir is passed, pip will unnecessarely # archive the entire source directory download_dir = None elif ireq.link and not ireq.link.is_artifact: # No download_dir for VCS sources. This also works around pip # using git-checkout-index, which gets rid of the .git dir. download_dir = None else: download_dir = self._download_dir if not os.path.isdir(download_dir): os.makedirs(download_dir) if not os.path.isdir(self._wheel_download_dir): os.makedirs(self._wheel_download_dir) reqset = RequirementSet( self.build_dir, self.source_dir, download_dir=download_dir, wheel_download_dir=self._wheel_download_dir, session=self.session) self._dependencies_cache[ireq] = reqset._prepare_file( self.finder, ireq) return set(self._dependencies_cache[ireq]) def get_hashes(self, ireq): """ Given a pinned InstallRequire, returns a set of hashes that represent all of the files for a given requirement. It is not acceptable for an editable or unpinned requirement to be passed to this function. """ if not is_pinned_requirement(ireq): raise TypeError( "Expected pinned requirement, not unpinned or editable, got {}" .format(ireq)) # We need to get all of the candidates that match our current version # pin, these will represent all of the files that could possibly # satisfy this constraint. all_candidates = self.find_all_candidates(ireq.name) candidates_by_version = lookup_table(all_candidates, key=lambda c: c.version) matching_versions = list( ireq.specifier.filter( (candidate.version for candidate in all_candidates))) matching_candidates = candidates_by_version[matching_versions[0]] return { self._get_file_hash(candidate.location) for candidate in matching_candidates } def _get_file_hash(self, location): h = hashlib.new(FAVORITE_HASH) with open_local_or_remote_file(location, self.session) as fp: for chunk in iter(lambda: fp.read(8096), b""): h.update(chunk) return ":".join([FAVORITE_HASH, h.hexdigest()]) @contextmanager def allow_all_wheels(self): """ Monkey patches pip.Wheel to allow wheels from all platforms and Python versions. This also saves the candidate cache and set a new one, or else the results from the previous non-patched calls will interfere. """ def _wheel_supported(self, tags=None): # Ignore current platform. Support everything. return True def _wheel_support_index_min(self, tags=None): # All wheels are equal priority for sorting. return 0 original_wheel_supported = Wheel.supported original_support_index_min = Wheel.support_index_min original_cache = self._available_candidates_cache Wheel.supported = _wheel_supported Wheel.support_index_min = _wheel_support_index_min self._available_candidates_cache = {} try: yield finally: Wheel.supported = original_wheel_supported Wheel.support_index_min = original_support_index_min self._available_candidates_cache = original_cache
def test_find_all_versions_nothing(data): """Find nothing without anything""" finder = PackageFinder([], [], session=PipSession()) assert not finder._find_all_versions(InstallRequirement.from_line('pip'))
class PyPIRepository(BaseRepository): DEFAULT_INDEX_URL = 'https://pypi.python.org/simple' """ The PyPIRepository will use the provided Finder instance to lookup packages. Typically, it looks up packages on PyPI (the default implicit config), but any other PyPI mirror can be used if index_urls is changed/configured on the Finder. """ def __init__(self, pip_options, session): self.session = session index_urls = [pip_options.index_url] + pip_options.extra_index_urls if pip_options.no_index: index_urls = [] self.finder = PackageFinder( find_links=pip_options.find_links, index_urls=index_urls, trusted_hosts=pip_options.trusted_hosts, allow_all_prereleases=pip_options.pre, process_dependency_links=pip_options.process_dependency_links, session=self.session, ) # Caches # stores project_name => InstallationCandidate mappings for all # versions reported by PyPI, so we only have to ask once for each # project self._available_candidates_cache = {} # stores InstallRequirement => list(InstallRequirement) mappings # of all secondary dependencies for the given requirement, so we # only have to go to disk once for each requirement self._dependencies_cache = {} # Setup file paths self.freshen_build_caches() self._download_dir = os.path.join(CACHE_DIR, 'pkgs') self._wheel_download_dir = os.path.join(CACHE_DIR, 'wheels') def freshen_build_caches(self): """ Start with fresh build/source caches. Will remove any old build caches from disk automatically. """ self._build_dir = TemporaryDirectory('build') self._source_dir = TemporaryDirectory('source') @property def build_dir(self): return self._build_dir.name @property def source_dir(self): return self._source_dir.name def clear_caches(self): rmtree(self._download_dir, ignore_errors=True) rmtree(self._wheel_download_dir, ignore_errors=True) def find_all_candidates(self, req_name): if req_name not in self._available_candidates_cache: # pip 8 changed the internal API, making this a public method if pip_version_info >= (8, 0): candidates = self.finder.find_all_candidates(req_name) else: candidates = self.finder._find_all_versions(req_name) self._available_candidates_cache[req_name] = candidates return self._available_candidates_cache[req_name] def find_best_match(self, ireq, prereleases=None): """ Returns a Version object that indicates the best match for the given InstallRequirement according to the external repository. """ if ireq.editable: return ireq # return itself as the best match all_candidates = self.find_all_candidates(ireq.name) candidates_by_version = lookup_table(all_candidates, key=lambda c: c.version, unique=True) matching_versions = ireq.specifier.filter((candidate.version for candidate in all_candidates), prereleases=prereleases) # Reuses pip's internal candidate sort key to sort matching_candidates = [candidates_by_version[ver] for ver in matching_versions] if not matching_candidates: raise NoCandidateFound(ireq, all_candidates) best_candidate = max(matching_candidates, key=self.finder._candidate_sort_key) # Turn the candidate into a pinned InstallRequirement return make_install_requirement( best_candidate.project, best_candidate.version, ireq.extras, constraint=ireq.constraint ) def get_dependencies(self, ireq): """ Given a pinned or an editable InstallRequirement, returns a set of dependencies (also InstallRequirements, but not necessarily pinned). They indicate the secondary dependencies for the given requirement. """ if not (ireq.editable or is_pinned_requirement(ireq)): raise TypeError('Expected pinned or editable InstallRequirement, got {}'.format(ireq)) if ireq not in self._dependencies_cache: if ireq.link and not ireq.link.is_artifact: # No download_dir for VCS sources. This also works around pip # using git-checkout-index, which gets rid of the .git dir. download_dir = None else: download_dir = self._download_dir if not os.path.isdir(download_dir): os.makedirs(download_dir) if not os.path.isdir(self._wheel_download_dir): os.makedirs(self._wheel_download_dir) reqset = RequirementSet(self.build_dir, self.source_dir, download_dir=download_dir, wheel_download_dir=self._wheel_download_dir, session=self.session) self._dependencies_cache[ireq] = reqset._prepare_file(self.finder, ireq) return set(self._dependencies_cache[ireq]) def get_hashes(self, ireq): """ Given a pinned InstallRequire, returns a set of hashes that represent all of the files for a given requirement. It is not acceptable for an editable or unpinned requirement to be passed to this function. """ if not is_pinned_requirement(ireq): raise TypeError( "Expected pinned requirement, not unpinned or editable, got {}".format(ireq)) # We need to get all of the candidates that match our current version # pin, these will represent all of the files that could possibly # satisify this constraint. all_candidates = self.find_all_candidates(ireq.name) candidates_by_version = lookup_table(all_candidates, key=lambda c: c.version) matching_versions = list( ireq.specifier.filter((candidate.version for candidate in all_candidates))) matching_candidates = candidates_by_version[matching_versions[0]] return { self._get_file_hash(candidate.location) for candidate in matching_candidates } def _get_file_hash(self, location): with TemporaryDirectory() as tmpdir: unpack_url( location, self.build_dir, download_dir=tmpdir, only_download=True, session=self.session ) files = os.listdir(tmpdir) assert len(files) == 1 filename = os.path.abspath(os.path.join(tmpdir, files[0])) h = hashlib.new(FAVORITE_HASH) with open(filename, "rb") as fp: for chunk in iter(lambda: fp.read(8096), b""): h.update(chunk) return ":".join([FAVORITE_HASH, h.hexdigest()])
def test_find_all_versions_index(data): finder = PackageFinder( [], [data.index_url('simple')], session=PipSession()) versions = finder._find_all_versions('simple') assert [str(v.version) for v in versions] == ['1.0']
def test_find_all_versions_find_links(data): finder = PackageFinder( [data.find_links], [], session=PipSession()) versions = finder._find_all_versions('simple') assert [str(v.version) for v in versions] == ['3.0', '2.0', '1.0']
def test_find_all_versions_nothing(data): """Find nothing without anything""" finder = PackageFinder([], [], session=PipSession()) assert not finder._find_all_versions('pip')
class PyPIRepository(BaseRepository): DEFAULT_INDEX_URL = 'https://pypi.python.org/simple' """ The PyPIRepository will use the provided Finder instance to lookup packages. Typically, it looks up packages on PyPI (the default implicit config), but any other PyPI mirror can be used if index_urls is changed/configured on the Finder. """ def __init__(self, pip_options, session): self.session = session index_urls = [pip_options.index_url] + pip_options.extra_index_urls if pip_options.no_index: index_urls = [] self.finder = PackageFinder( find_links=pip_options.find_links, index_urls=index_urls, trusted_hosts=pip_options.trusted_hosts, allow_all_prereleases=pip_options.pre, process_dependency_links=pip_options.process_dependency_links, session=self.session, ) # Caches # stores project_name => InstallationCandidate mappings for all # versions reported by PyPI, so we only have to ask once for each # project self._available_candidates_cache = {} # Setup file paths self.freshen_build_caches() self._download_dir = os.path.join(CACHE_DIR, 'pkgs') self._wheel_download_dir = os.path.join(CACHE_DIR, 'wheels') def freshen_build_caches(self): """ Start with fresh build/source caches. Will remove any old build caches from disk automatically. """ self._build_dir = TemporaryDirectory('build') self._source_dir = TemporaryDirectory('source') @property def build_dir(self): return self._build_dir.name @property def source_dir(self): return self._source_dir.name def clear_caches(self): rmtree(self._download_dir, ignore_errors=True) rmtree(self._wheel_download_dir, ignore_errors=True) def find_all_candidates(self, req_name): if req_name not in self._available_candidates_cache: # pip 8 changed the internal API, making this a public method if pip_version_info >= (8, 0): candidates = self.finder.find_all_candidates(req_name) else: candidates = self.finder._find_all_versions(req_name) self._available_candidates_cache[req_name] = candidates return self._available_candidates_cache[req_name] def find_best_match(self, ireq, prereleases=None): """ Returns a Version object that indicates the best match for the given InstallRequirement according to the external repository. """ if ireq.editable: return ireq # return itself as the best match all_candidates = self.find_all_candidates(ireq.name) candidates_by_version = lookup_table(all_candidates, key=lambda c: c.version, unique=True) matching_versions = ireq.specifier.filter((candidate.version for candidate in all_candidates), prereleases=prereleases) # Reuses pip's internal candidate sort key to sort matching_candidates = [candidates_by_version[ver] for ver in matching_versions] if not matching_candidates: raise NoCandidateFound(ireq, all_candidates) best_candidate = max(matching_candidates, key=self.finder._candidate_sort_key) # Turn the candidate into a pinned InstallRequirement return make_install_requirement( best_candidate.project, best_candidate.version, ireq.extras ) def get_dependencies(self, ireq): """ Given a pinned or an editable InstallRequirement, returns a set of dependencies (also InstallRequirements, but not necessarily pinned). They indicate the secondary dependencies for the given requirement. """ if not (ireq.editable or is_pinned_requirement(ireq)): raise TypeError('Expected pinned or editable InstallRequirement, got {}'.format(ireq)) if not os.path.isdir(self._download_dir): os.makedirs(self._download_dir) if not os.path.isdir(self._wheel_download_dir): os.makedirs(self._wheel_download_dir) reqset = RequirementSet(self.build_dir, self.source_dir, download_dir=self._download_dir, wheel_download_dir=self._wheel_download_dir, session=self.session) dependencies = reqset._prepare_file(self.finder, ireq) return set(dependencies)
def test_find_all_versions_nothing(data): """Find nothing without anything""" finder = PackageFinder([], [], session=PipSession()) assert not finder._find_all_versions('pip')
def test_find_all_versions_find_links(data): finder = PackageFinder( [data.find_links], [], session=PipSession()) versions = finder._find_all_versions('simple') assert [str(v.version) for v in versions] == ['3.0', '2.0', '1.0']
def test_find_all_versions_index(data): finder = PackageFinder( [], [data.index_url('simple')], session=PipSession()) versions = finder._find_all_versions('simple') assert [str(v.version) for v in versions] == ['1.0']
def test_find_all_versions_find_links_and_index(data): finder = PackageFinder( [data.find_links], [data.index_url('simple')], session=PipSession()) versions = finder._find_all_versions('simple') # first the find-links versions then the page versions assert [str(v.version) for v in versions] == ['3.0', '2.0', '1.0', '1.0']
class PyPIRepository(BaseRepository): DEFAULT_INDEX_URL = 'https://pypi.python.org/simple' """ The PyPIRepository will use the provided Finder instance to lookup packages. Typically, it looks up packages on PyPI (the default implicit config), but any other PyPI mirror can be used if index_urls is changed/configured on the Finder. """ def __init__(self, pip_options, session): self.session = session index_urls = [pip_options.index_url] + pip_options.extra_index_urls if pip_options.no_index: index_urls = [] self.finder = PackageFinder( find_links=pip_options.find_links, index_urls=index_urls, trusted_hosts=pip_options.trusted_hosts, allow_all_prereleases=pip_options.pre, process_dependency_links=pip_options.process_dependency_links, session=self.session, ) # Caches # stores project_name => InstallationCandidate mappings for all # versions reported by PyPI, so we only have to ask once for each # project self._available_candidates_cache = {} # stores InstallRequirement => list(InstallRequirement) mappings # of all secondary dependencies for the given requirement, so we # only have to go to disk once for each requirement self._dependencies_cache = {} # Setup file paths self.freshen_build_caches() self._download_dir = os.path.join(CACHE_DIR, 'pkgs') self._wheel_download_dir = os.path.join(CACHE_DIR, 'wheels') def freshen_build_caches(self): """ Start with fresh build/source caches. Will remove any old build caches from disk automatically. """ self._build_dir = TemporaryDirectory('build') self._source_dir = TemporaryDirectory('source') @property def build_dir(self): return self._build_dir.name @property def source_dir(self): return self._source_dir.name def clear_caches(self): rmtree(self._download_dir, ignore_errors=True) rmtree(self._wheel_download_dir, ignore_errors=True) def find_all_candidates(self, req_name): if req_name not in self._available_candidates_cache: # pip 8 changed the internal API, making this a public method if pip_version_info >= (8, 0): candidates = self.finder.find_all_candidates(req_name) else: candidates = self.finder._find_all_versions(req_name) self._available_candidates_cache[req_name] = candidates return self._available_candidates_cache[req_name] def find_best_match(self, ireq, prereleases=None): """ Returns a Version object that indicates the best match for the given InstallRequirement according to the external repository. """ if ireq.editable: return ireq # return itself as the best match all_candidates = self.find_all_candidates(ireq.name) candidates_by_version = lookup_table(all_candidates, key=lambda c: c.version, unique=True) matching_versions = ireq.specifier.filter((candidate.version for candidate in all_candidates), prereleases=prereleases) # Reuses pip's internal candidate sort key to sort matching_candidates = [candidates_by_version[ver] for ver in matching_versions] if not matching_candidates: raise NoCandidateFound(ireq, all_candidates) best_candidate = max(matching_candidates, key=self.finder._candidate_sort_key) # Turn the candidate into a pinned InstallRequirement return make_install_requirement( best_candidate.project, best_candidate.version, ireq.extras, constraint=ireq.constraint ) def get_dependencies(self, ireq): """ Given a pinned or an editable InstallRequirement, returns a set of dependencies (also InstallRequirements, but not necessarily pinned). They indicate the secondary dependencies for the given requirement. """ if not (ireq.editable or is_pinned_requirement(ireq)): raise TypeError('Expected pinned or editable InstallRequirement, got {}'.format(ireq)) if ireq not in self._dependencies_cache: if not os.path.isdir(self._download_dir): os.makedirs(self._download_dir) if not os.path.isdir(self._wheel_download_dir): os.makedirs(self._wheel_download_dir) reqset = RequirementSet(self.build_dir, self.source_dir, download_dir=self._download_dir, wheel_download_dir=self._wheel_download_dir, session=self.session) self._dependencies_cache[ireq] = reqset._prepare_file(self.finder, ireq) return set(self._dependencies_cache[ireq]) def get_hashes(self, ireq): """ Given a pinned InstallRequire, returns a set of hashes that represent all of the files for a given requirement. It is not acceptable for an editable or unpinned requirement to be passed to this function. """ if ireq.editable or not is_pinned_requirement(ireq): raise TypeError( "Expected pinned requirement, not unpinned or editable, got {}".format(ireq)) # We need to get all of the candidates that match our current version # pin, these will represent all of the files that could possibly # satisify this constraint. all_candidates = self.find_all_candidates(ireq.name) candidates_by_version = lookup_table(all_candidates, key=lambda c: c.version) matching_versions = list( ireq.specifier.filter((candidate.version for candidate in all_candidates))) matching_candidates = candidates_by_version[matching_versions[0]] return { self._get_file_hash(candidate.location) for candidate in matching_candidates } def _get_file_hash(self, location): with TemporaryDirectory() as tmpdir: unpack_url( location, self.build_dir, download_dir=tmpdir, only_download=True, session=self.session ) files = os.listdir(tmpdir) assert len(files) == 1 filename = os.path.abspath(os.path.join(tmpdir, files[0])) h = hashlib.new(FAVORITE_HASH) with open(filename, "rb") as fp: for chunk in iter(lambda: fp.read(8096), b""): h.update(chunk) return ":".join([FAVORITE_HASH, h.hexdigest()])
class PyPIRepository(BaseRepository): DEFAULT_INDEX_URL = 'https://pypi.python.org/simple' """ The PyPIRepository will use the provided Finder instance to lookup packages. Typically, it looks up packages on PyPI (the default implicit config), but any other PyPI mirror can be used if index_urls is changed/configured on the Finder. """ def __init__(self, pip_options, session): self.session = session index_urls = [pip_options.index_url] + pip_options.extra_index_urls if pip_options.no_index: index_urls = [] self.finder = PackageFinder( find_links=pip_options.find_links, index_urls=index_urls, trusted_hosts=pip_options.trusted_hosts, allow_all_prereleases=pip_options.pre, process_dependency_links=pip_options.process_dependency_links, session=self.session, ) # Caches # stores project_name => InstallationCandidate mappings for all # versions reported by PyPI, so we only have to ask once for each # project self._available_candidates_cache = {} # Setup file paths self.freshen_build_caches() self._download_dir = os.path.join(CACHE_DIR, 'pkgs') self._wheel_download_dir = os.path.join(CACHE_DIR, 'wheels') def freshen_build_caches(self): """ Start with fresh build/source caches. Will remove any old build caches from disk automatically. """ self._build_dir = TemporaryDirectory('build') self._source_dir = TemporaryDirectory('source') @property def build_dir(self): return self._build_dir.name @property def source_dir(self): return self._source_dir.name def clear_caches(self): rmtree(self._download_dir, ignore_errors=True) rmtree(self._wheel_download_dir, ignore_errors=True) def find_all_candidates(self, req_name): if req_name not in self._available_candidates_cache: # pip 8 changed the internal API, making this a public method if pip_version_info >= (8, 0): candidates = self.finder.find_all_candidates(req_name) else: candidates = self.finder._find_all_versions(req_name) self._available_candidates_cache[req_name] = candidates return self._available_candidates_cache[req_name] def find_best_match(self, ireq, prereleases=None): """ Returns a Version object that indicates the best match for the given InstallRequirement according to the external repository. """ if ireq.editable: return ireq # return itself as the best match all_candidates = self.find_all_candidates(ireq.name) candidates_by_version = lookup_table(all_candidates, key=lambda c: c.version, unique=True) matching_versions = ireq.specifier.filter( (candidate.version for candidate in all_candidates), prereleases=prereleases) # Reuses pip's internal candidate sort key to sort matching_candidates = [ candidates_by_version[ver] for ver in matching_versions ] if not matching_candidates: raise NoCandidateFound(ireq, all_candidates) best_candidate = max(matching_candidates, key=self.finder._candidate_sort_key) # Turn the candidate into a pinned InstallRequirement return make_install_requirement(best_candidate.project, best_candidate.version, ireq.extras) def get_dependencies(self, ireq): """ Given a pinned or an editable InstallRequirement, returns a set of dependencies (also InstallRequirements, but not necessarily pinned). They indicate the secondary dependencies for the given requirement. """ if not (ireq.editable or is_pinned_requirement(ireq)): raise TypeError( 'Expected pinned or editable InstallRequirement, got {}'. format(ireq)) if not os.path.isdir(self._download_dir): os.makedirs(self._download_dir) if not os.path.isdir(self._wheel_download_dir): os.makedirs(self._wheel_download_dir) reqset = RequirementSet(self.build_dir, self.source_dir, download_dir=self._download_dir, wheel_download_dir=self._wheel_download_dir, session=self.session) dependencies = reqset._prepare_file(self.finder, ireq) return set(dependencies)
class PyPIRepository(BaseRepository): DEFAULT_INDEX_URL = 'https://pypi.python.org/simple' """ The PyPIRepository will use the provided Finder instance to lookup packages. Typically, it looks up packages on PyPI (the default implicit config), but any other PyPI mirror can be used if index_urls is changed/configured on the Finder. """ def __init__(self, pip_options, session): self.session = session index_urls = [pip_options.index_url] + pip_options.extra_index_urls if pip_options.no_index: index_urls = [] self.finder = PackageFinder( find_links=pip_options.find_links, index_urls=index_urls, trusted_hosts=pip_options.trusted_hosts, allow_all_prereleases=pip_options.pre, process_dependency_links=pip_options.process_dependency_links, session=self.session, ) # Caches # stores project_name => InstallationCandidate mappings for all # versions reported by PyPI, so we only have to ask once for each # project self._available_candidates_cache = {} # stores InstallRequirement => list(InstallRequirement) mappings # of all secondary dependencies for the given requirement, so we # only have to go to disk once for each requirement self._dependencies_cache = {} # Setup file paths self.freshen_build_caches() self._download_dir = fs_str(os.path.join(CACHE_DIR, 'pkgs')) self._wheel_download_dir = fs_str(os.path.join(CACHE_DIR, 'wheels')) def freshen_build_caches(self): """ Start with fresh build/source caches. Will remove any old build caches from disk automatically. """ self._build_dir = TemporaryDirectory(fs_str('build')) self._source_dir = TemporaryDirectory(fs_str('source')) @property def build_dir(self): return self._build_dir.name @property def source_dir(self): return self._source_dir.name def clear_caches(self): rmtree(self._download_dir, ignore_errors=True) rmtree(self._wheel_download_dir, ignore_errors=True) def find_all_candidates(self, req_name): if req_name not in self._available_candidates_cache: # pip 8 changed the internal API, making this a public method if pip_version_info >= (8, 0): candidates = self.finder.find_all_candidates(req_name) else: candidates = self.finder._find_all_versions(req_name) self._available_candidates_cache[req_name] = candidates return self._available_candidates_cache[req_name] def find_best_match(self, ireq, prereleases=None): """ Returns a Version object that indicates the best match for the given InstallRequirement according to the external repository. """ if ireq.editable or is_vcs_link(ireq): return ireq # return itself as the best match all_candidates = self.find_all_candidates(ireq.name) candidates_by_version = lookup_table(all_candidates, key=lambda c: c.version, unique=True) matching_versions = ireq.specifier.filter((candidate.version for candidate in all_candidates), prereleases=prereleases) # Reuses pip's internal candidate sort key to sort matching_candidates = [candidates_by_version[ver] for ver in matching_versions] if not matching_candidates: raise NoCandidateFound(ireq, all_candidates, self.finder.index_urls) best_candidate = max(matching_candidates, key=self.finder._candidate_sort_key) # Turn the candidate into a pinned InstallRequirement return make_install_requirement( best_candidate.project, best_candidate.version, ireq.extras, constraint=ireq.constraint ) def _get_dependencies(self, ireq): """ :type ireq: pip.req.InstallRequirement """ deps = self._dependencies_cache.get(getattr(ireq.link, 'url', None)) if not deps: if ireq.editable and (ireq.source_dir and os.path.exists(ireq.source_dir)): # No download_dir for locally available editable requirements. # If a download_dir is passed, pip will unnecessarely # archive the entire source directory download_dir = None elif ireq.link and not ireq.link.is_artifact: # No download_dir for VCS sources. This also works around pip # using git-checkout-index, which gets rid of the .git dir. download_dir = None else: download_dir = self._download_dir if not os.path.isdir(download_dir): os.makedirs(download_dir) if not os.path.isdir(self._wheel_download_dir): os.makedirs(self._wheel_download_dir) reqset = RequirementSet(self.build_dir, self.source_dir, download_dir=download_dir, wheel_download_dir=self._wheel_download_dir, session=self.session, ignore_installed=True) deps = reqset._prepare_file(self.finder, ireq) if ireq.req and ireq._temp_build_dir and ireq._ideal_build_dir: # Move the temporary build directory under self.build_dir ireq.source_dir = None ireq._correct_build_location() assert ireq.link.url self._dependencies_cache[ireq.link.url] = deps return set(deps) def get_hashes(self, ireq): """ Given an InstallRequirement, return a set of hashes that represent all of the files for a given requirement. Editable requirements return an empty set. Unpinned requirements raise a TypeError. """ if ireq.editable: return set() check_is_hashable(ireq) if ireq.link and ireq.link.is_artifact: return {self._get_file_hash(ireq.link)} # We need to get all of the candidates that match our current version # pin, these will represent all of the files that could possibly # satisfy this constraint. all_candidates = self.find_all_candidates(ireq.name) candidates_by_version = lookup_table(all_candidates, key=lambda c: c.version) matching_versions = list( ireq.specifier.filter((candidate.version for candidate in all_candidates))) matching_candidates = candidates_by_version[matching_versions[0]] return { self._get_file_hash(candidate.location) for candidate in matching_candidates } def _get_file_hash(self, location): h = hashlib.new(FAVORITE_HASH) with open_local_or_remote_file(location, self.session) as fp: for chunk in iter(lambda: fp.read(8096), b""): h.update(chunk) return ":".join([FAVORITE_HASH, h.hexdigest()])