Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support index preference. #264

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions pip/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,10 +350,9 @@ def _download_url(resp, link, temp_location):
total_length = 0
downloaded = 0
show_progress = total_length > 40*1000 or not total_length
show_url = link.show_url
show_url = link.url
try:
if show_progress:
## FIXME: the URL can get really long in this message:
if total_length:
logger.start_progress('Downloading %s (%s): ' % (show_url, format_size(total_length)))
else:
Expand Down
24 changes: 19 additions & 5 deletions pip/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os
import re
import mimetypes
import operator
import threading
import posixpath
import pkg_resources
Expand Down Expand Up @@ -48,6 +49,10 @@ def __init__(self, find_links, index_urls,
else:
self.mirror_urls = []

all_origins = self.index_urls + self.mirror_urls + self.find_links
self.origin_preferences = dict([(e[1], e[0]) for e in enumerate(all_origins)])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the fact dicts are unordered this needs to be something else, e.g. a tuple of two-tuples that can be later turned into a dict for easier access.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for looking at this, Jannis. I need to update this as Paul requested, to refamiliarize myself with my own patch, but I don't believe this is necessary. The origin_preferences dict is only used to get the numeric preference value for the index URL, then that's used to sort the applicable versions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, gotcha. thanks!

logger.debug('Origin preferences: %s' % self.origin_preferences)

def add_dependency_links(self, links):
## FIXME: this shouldn't be global list this, it should only
## apply to requirements of the package that specifies the
Expand Down Expand Up @@ -163,9 +168,18 @@ def mkurl_pypi_url(url):
logger.info("Ignoring link %s, version %s doesn't match %s"
% (link, version, ','.join([''.join(s) for s in req.req.specs])))
continue
applicable_versions.append((link, version))
applicable_versions = sorted(applicable_versions, key=lambda v: pkg_resources.parse_version(v[1]), reverse=True)
existing_applicable = bool([link for link, version in applicable_versions if link is Inf])
preference = None
for origin in self.origin_preferences.keys():
link_url = link.comes_from and link.comes_from.url or link.url
if link_url.startswith(origin):
preference = self.origin_preferences[origin]
break
applicable_versions.append((link, version, parsed_version, preference))
# sort applicable_versions by origin preference
applicable_versions = sorted(applicable_versions, key=operator.itemgetter(3))
# and then by version
applicable_versions = sorted(applicable_versions, key=operator.itemgetter(2), reverse=True)
existing_applicable = bool([link for link, version, parsed_version, preference in applicable_versions if link is Inf])
if not upgrade and existing_applicable:
if applicable_versions[0][1] is Inf:
logger.info('Existing installed version (%s) is most up-to-date and satisfies requirement'
Expand All @@ -184,8 +198,8 @@ def mkurl_pypi_url(url):
% (req.satisfied_by.version, ', '.join([version for link, version in applicable_versions[1:]]) or 'none'))
return None
if len(applicable_versions) > 1:
logger.info('Using version %s (newest of versions: %s)' %
(applicable_versions[0][1], ', '.join([version for link, version in applicable_versions])))
logger.info('Using version %s from %s (newest of versions:\n %s\n )' %
(applicable_versions[0][1], applicable_versions[0][0].url, '\n '.join([str(version) for version in applicable_versions])))
return applicable_versions[0][0]

def _find_url_name(self, index_url, url_name, req):
Expand Down
8 changes: 8 additions & 0 deletions pip/req.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ def __init__(self, req, comes_from, source_dir=None, editable=False,
req = pkg_resources.Requirement.parse(req)
self.req = req
self.comes_from = comes_from

# origin is used just to record provenance; comes_from is also used to
# make decisions about the type of packaging and setting it can break
# things
self.origin = None

self.source_dir = source_dir
self.editable = editable
self.url = url
Expand Down Expand Up @@ -953,6 +959,7 @@ def prepare_files(self, finder, force_root_egg_info=False, bundle=False):
if url:
try:
self.unpack_url(url, location, self.is_download)
req_to_install.origin = url.url
except HTTPError:
e = sys.exc_info()[1]
logger.fatal('Could not install requirement %s because of error %s'
Expand Down Expand Up @@ -1081,6 +1088,7 @@ def install(self, install_options, global_options=()):
logger.indent += 2
try:
for requirement in to_install:
logger.debug('Installing %s%s' % (requirement.name, requirement.origin and (' (origin: %s)' % requirement.origin) or requirement.comes_from and ' %s' % requirement.comes_from or ''))
if requirement.conflicts_with:
logger.notify('Found existing installation: %s'
% requirement.conflicts_with)
Expand Down
34 changes: 34 additions & 0 deletions tests/test_index_preference.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import os
from path import Path
import re
import shutil

from test_pip import here, reset_env, run_pip

def test_index_preference():
"""
Verify that indexes will be used in the order defined.
"""
e = reset_env()

# create two indexes by copying the test index in tests/in dex/FSPkg
index1 = os.path.join(e.scratch_path, 'index1')
shutil.copytree(os.path.join(here, 'in dex'), index1)

index2 = os.path.join(e.scratch_path, 'index2')
shutil.copytree(os.path.join(here, 'in dex'), index2)

# verify that the package is installed from the main index (index1)
result = run_pip('install', '-vvv', '--index-url', 'file://' + index1, '--extra-index-url', 'file://' + index2, 'FSPkg', expect_error=False)

index1_re = re.compile('^\s*Installing FSPkg \(origin: file://.*/scratch/index1/FSPkg/FSPkg-0.1dev.tar.gz\)\s*$', re.MULTILINE)
assert index1_re.search(result.stdout) is not None

# uninstall the package
result = run_pip('uninstall', '--yes', 'FSPkg', expect_error=False)

# verify that the package is installed from the main index (index2, now)
result = run_pip('install', '-vvv', '--index-url', 'file://' + index2, '--extra-index-url', 'file://' + index1, 'FSPkg', expect_error=False)

index2_re = re.compile('^\s*Installing FSPkg \(origin: file://.*/scratch/index2/FSPkg/FSPkg-0.1dev.tar.gz\)\s*$', re.MULTILINE)
assert index2_re.search(result.stdout) is not None