Add PackageFinder.create(), and simplify PackageFinder().

This commit is contained in:
Chris Jerdonek 2019-05-17 00:13:40 -07:00
parent bc8857d6ff
commit cad71a7117
11 changed files with 129 additions and 75 deletions

View File

@ -340,7 +340,7 @@ class RequirementCommand(Command):
)
index_urls = []
return PackageFinder(
return PackageFinder.create(
find_links=options.find_links,
format_control=options.format_control,
index_urls=index_urls,

View File

@ -113,7 +113,7 @@ class ListCommand(Command):
"""
Create a package finder appropriate to this list command.
"""
return PackageFinder(
return PackageFinder.create(
find_links=options.find_links,
index_urls=index_urls,
allow_all_prereleases=options.pre,

View File

@ -513,6 +513,39 @@ class PackageFinder(object):
def __init__(
self,
candidate_evaluator, # type: CandidateEvaluator
find_links, # type: List[str]
index_urls, # type: List[str]
secure_origins, # type: List[SecureOrigin]
session, # type: PipSession
allow_all_prereleases=False, # type: bool
format_control=None, # type: Optional[FormatControl]
):
# type: (...) -> None
"""
This constructor is primarily meant to be used by the create() class
method and from tests.
:param candidate_evaluator: A CandidateEvaluator object.
:param session: The Session to use to make requests.
:param allow_all_prereleases: Whether to allow all pre-releases.
:param format_control: A FormatControl object, used to control
the selection of source packages / binary packages when consulting
the index and links.
"""
format_control = format_control or FormatControl(set(), set())
self.candidate_evaluator = candidate_evaluator
self.find_links = find_links
self.index_urls = index_urls
self.secure_origins = secure_origins
self.session = session
self.allow_all_prereleases = allow_all_prereleases
self.format_control = format_control
@classmethod
def create(
cls,
find_links, # type: List[str]
index_urls, # type: List[str]
allow_all_prereleases=False, # type: bool
@ -525,9 +558,12 @@ class PackageFinder(object):
implementation=None, # type: Optional[str]
prefer_binary=False # type: bool
):
# type: (...) -> None
# type: (...) -> PackageFinder
"""Create a PackageFinder.
:param trusted_hosts: Domains that we won't emit warnings for when
not using HTTPS.
:param session: The Session to use to make requests.
:param format_control: A FormatControl object or None. Used to control
the selection of source packages / binary packages when consulting
the index and links.
@ -547,7 +583,7 @@ class PackageFinder(object):
"""
if session is None:
raise TypeError(
"PackageFinder() missing 1 required keyword argument: "
"PackageFinder.create() missing 1 required keyword argument: "
"'session'"
)
@ -556,30 +592,19 @@ class PackageFinder(object):
# it and if it exists, use the normalized version.
# This is deliberately conservative - it might be fine just to
# blindly normalize anything starting with a ~...
self.find_links = [] # type: List[str]
built_find_links = [] # type: List[str]
for link in find_links:
if link.startswith('~'):
new_link = normalize_path(link)
if os.path.exists(new_link):
link = new_link
self.find_links.append(link)
built_find_links.append(link)
self.index_urls = index_urls
self.format_control = format_control or FormatControl(set(), set())
# Domains that we won't emit warnings for when not using HTTPS
self.secure_origins = [
secure_origins = [
("*", host, "*")
for host in (trusted_hosts if trusted_hosts else [])
] # type: List[SecureOrigin]
# Do we want to allow _all_ pre-releases?
self.allow_all_prereleases = allow_all_prereleases
# The Session we'll use to make requests
self.session = session
# The valid tags to check potential found wheel candidates against
valid_tags = get_supported(
versions=versions,
@ -587,14 +612,14 @@ class PackageFinder(object):
abi=abi,
impl=implementation,
)
self.candidate_evaluator = CandidateEvaluator(
candidate_evaluator = CandidateEvaluator(
valid_tags=valid_tags, prefer_binary=prefer_binary,
)
# If we don't have TLS enabled, then WARN if anyplace we're looking
# relies on TLS.
if not HAS_TLS:
for link in itertools.chain(self.index_urls, self.find_links):
for link in itertools.chain(index_urls, built_find_links):
parsed = urllib_parse.urlparse(link)
if parsed.scheme == "https":
logger.warning(
@ -604,6 +629,16 @@ class PackageFinder(object):
)
break
return cls(
candidate_evaluator=candidate_evaluator,
find_links=built_find_links,
index_urls=index_urls,
secure_origins=secure_origins,
session=session,
allow_all_prereleases=allow_all_prereleases,
format_control=format_control,
)
def get_formatted_locations(self):
# type: () -> str
lines = []

View File

@ -122,7 +122,7 @@ def pip_version_check(session, options):
# Refresh the version if we need to or just see if we need to warn
if pypi_version is None:
# Lets use PackageFinder to see what the latest pip version is
finder = PackageFinder(
finder = PackageFinder.create(
find_links=options.find_links,
index_urls=[options.index_url] + options.extra_index_urls,
allow_all_prereleases=False, # Explicitly set to False

View File

@ -23,7 +23,7 @@ def test_backend(tmpdir, data):
req = InstallRequirement(None, None, source_dir=project_dir)
req.load_pyproject_toml()
env = BuildEnvironment()
finder = PackageFinder([data.backends], [], session=PipSession())
finder = PackageFinder.create([data.backends], [], session=PipSession())
env.install_requirements(finder, ["dummy_backend"], 'normal', "Installing")
conflicting, missing = env.check_requirements(["dummy_backend"])
assert not conflicting and not missing

View File

@ -27,7 +27,7 @@ def run_with_build_env(script, setup_script_contents,
from pip._internal.download import PipSession
from pip._internal.index import PackageFinder
finder = PackageFinder([%r], [], session=PipSession())
finder = PackageFinder.create([%r], [], session=PipSession())
build_env = BuildEnvironment()
try:
@ -59,7 +59,9 @@ def test_build_env_allow_empty_requirements_install():
def test_build_env_allow_only_one_install(script):
create_basic_wheel_for_package(script, 'foo', '1.0')
create_basic_wheel_for_package(script, 'bar', '1.0')
finder = PackageFinder([script.scratch_path], [], session=PipSession())
finder = PackageFinder.create(
[script.scratch_path], [], session=PipSession(),
)
build_env = BuildEnvironment()
for prefix in ('normal', 'overlay'):
build_env.install_requirements(finder, ['foo'], prefix,

View File

@ -19,7 +19,7 @@ from pip._internal.req.constructors import install_req_from_line
def test_no_mpkg(data):
"""Finder skips zipfiles with "macosx10" in the name."""
finder = PackageFinder([data.find_links], [], session=PipSession())
finder = PackageFinder.create([data.find_links], [], session=PipSession())
req = install_req_from_line("pkgwithmpkg")
found = finder.find_requirement(req, False)
@ -28,7 +28,7 @@ def test_no_mpkg(data):
def test_no_partial_name_match(data):
"""Finder requires the full project name to match, not just beginning."""
finder = PackageFinder([data.find_links], [], session=PipSession())
finder = PackageFinder.create([data.find_links], [], session=PipSession())
req = install_req_from_line("gmpy")
found = finder.find_requirement(req, False)
@ -39,7 +39,7 @@ def test_tilde():
"""Finder can accept a path with ~ in it and will normalize it."""
session = PipSession()
with patch('pip._internal.index.os.path.exists', return_value=True):
finder = PackageFinder(['~/python-pkgs'], [], session=session)
finder = PackageFinder.create(['~/python-pkgs'], [], session=session)
req = install_req_from_line("gmpy")
with pytest.raises(DistributionNotFound):
finder.find_requirement(req, False)
@ -48,7 +48,7 @@ def test_tilde():
def test_duplicates_sort_ok(data):
"""Finder successfully finds one of a set of duplicates in different
locations"""
finder = PackageFinder(
finder = PackageFinder.create(
[data.find_links, data.find_links2],
[],
session=PipSession(),
@ -62,7 +62,7 @@ def test_duplicates_sort_ok(data):
def test_finder_detects_latest_find_links(data):
"""Test PackageFinder detects latest using find-links"""
req = install_req_from_line('simple', None)
finder = PackageFinder([data.find_links], [], session=PipSession())
finder = PackageFinder.create([data.find_links], [], session=PipSession())
link = finder.find_requirement(req, False)
assert link.url.endswith("simple-3.0.tar.gz")
@ -70,7 +70,7 @@ def test_finder_detects_latest_find_links(data):
def test_incorrect_case_file_index(data):
"""Test PackageFinder detects latest using wrong case"""
req = install_req_from_line('dinner', None)
finder = PackageFinder([], [data.find_links3], session=PipSession())
finder = PackageFinder.create([], [data.find_links3], session=PipSession())
link = finder.find_requirement(req, False)
assert link.url.endswith("Dinner-2.0.tar.gz")
@ -87,7 +87,7 @@ def test_finder_detects_latest_already_satisfied_find_links(data):
version=latest_version
)
req.satisfied_by = satisfied_by
finder = PackageFinder([data.find_links], [], session=PipSession())
finder = PackageFinder.create([data.find_links], [], session=PipSession())
with pytest.raises(BestVersionAlreadyInstalled):
finder.find_requirement(req, True)
@ -105,7 +105,7 @@ def test_finder_detects_latest_already_satisfied_pypi_links():
version=latest_version,
)
req.satisfied_by = satisfied_by
finder = PackageFinder(
finder = PackageFinder.create(
[],
["http://pypi.org/simple/"],
session=PipSession(),
@ -125,7 +125,7 @@ class TestWheel:
req = install_req_from_line("invalid")
# data.find_links contains "invalid.whl", which is an invalid wheel
finder = PackageFinder(
finder = PackageFinder.create(
[data.find_links],
[],
session=PipSession(),
@ -149,7 +149,7 @@ class TestWheel:
)
req = install_req_from_line("simple.dist")
finder = PackageFinder(
finder = PackageFinder.create(
[data.find_links],
[],
session=PipSession(),
@ -171,7 +171,7 @@ class TestWheel:
)
req = install_req_from_line("simple.dist")
finder = PackageFinder(
finder = PackageFinder.create(
[data.find_links],
[],
session=PipSession(),
@ -187,7 +187,7 @@ class TestWheel:
`test_link_sorting` also covers this at lower level
"""
req = install_req_from_line("priority")
finder = PackageFinder(
finder = PackageFinder.create(
[data.find_links],
[],
session=PipSession(),
@ -208,7 +208,7 @@ class TestWheel:
version=latest_version,
)
req.satisfied_by = satisfied_by
finder = PackageFinder(
finder = PackageFinder.create(
[data.find_links],
[],
session=PipSession(),
@ -275,7 +275,7 @@ class TestWheel:
Link("simplewheel-1.0-py2.py3-none-any.whl"),
),
]
finder = PackageFinder([], [], session=PipSession())
finder = PackageFinder.create([], [], session=PipSession())
sort_key = finder.candidate_evaluator._sort_key
results = sorted(links, key=sort_key, reverse=True)
results2 = sorted(reversed(links), key=sort_key, reverse=True)
@ -285,7 +285,7 @@ class TestWheel:
def test_finder_priority_file_over_page(data):
"""Test PackageFinder prefers file links over equivalent page links"""
req = install_req_from_line('gmpy==1.15', None)
finder = PackageFinder(
finder = PackageFinder.create(
[data.find_links],
["http://pypi.org/simple/"],
session=PipSession(),
@ -305,7 +305,7 @@ def test_finder_priority_nonegg_over_eggfragments():
req = install_req_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())
finder = PackageFinder.create(links, [], session=PipSession())
with patch.object(finder, "_get_pages", lambda x, y: []):
all_versions = finder.find_all_candidates(req.name)
@ -317,7 +317,7 @@ def test_finder_priority_nonegg_over_eggfragments():
assert link.url.endswith('tar.gz')
links.reverse()
finder = PackageFinder(links, [], session=PipSession())
finder = PackageFinder.create(links, [], session=PipSession())
with patch.object(finder, "_get_pages", lambda x, y: []):
all_versions = finder.find_all_candidates(req.name)
@ -336,20 +336,22 @@ def test_finder_only_installs_stable_releases(data):
req = install_req_from_line("bar", None)
# using a local index (that has pre & dev releases)
finder = PackageFinder([], [data.index_url("pre")], session=PipSession())
finder = PackageFinder.create(
[], [data.index_url("pre")], session=PipSession(),
)
link = finder.find_requirement(req, False)
assert link.url.endswith("bar-1.0.tar.gz"), link.url
# using find-links
links = ["https://foo/bar-1.0.tar.gz", "https://foo/bar-2.0b1.tar.gz"]
finder = PackageFinder(links, [], session=PipSession())
finder = PackageFinder.create(links, [], session=PipSession())
with patch.object(finder, "_get_pages", lambda x, y: []):
link = finder.find_requirement(req, False)
assert link.url == "https://foo/bar-1.0.tar.gz"
links.reverse()
finder = PackageFinder(links, [], session=PipSession())
finder = PackageFinder.create(links, [], session=PipSession())
with patch.object(finder, "_get_pages", lambda x, y: []):
link = finder.find_requirement(req, False)
@ -368,9 +370,9 @@ def test_finder_only_installs_data_require(data):
"""
# using a local index (that has pre & dev releases)
finder = PackageFinder([],
[data.index_url("datarequire")],
session=PipSession())
finder = PackageFinder.create(
[], [data.index_url("datarequire")], session=PipSession(),
)
links = finder.find_all_candidates("fakepackage")
expected = ['1.0.0', '9.9.9']
@ -390,7 +392,7 @@ def test_finder_installs_pre_releases(data):
req = install_req_from_line("bar", None)
# using a local index (that has pre & dev releases)
finder = PackageFinder(
finder = PackageFinder.create(
[], [data.index_url("pre")],
allow_all_prereleases=True,
session=PipSession(),
@ -400,7 +402,7 @@ def test_finder_installs_pre_releases(data):
# using find-links
links = ["https://foo/bar-1.0.tar.gz", "https://foo/bar-2.0b1.tar.gz"]
finder = PackageFinder(
finder = PackageFinder.create(
links, [],
allow_all_prereleases=True,
session=PipSession(),
@ -411,7 +413,7 @@ def test_finder_installs_pre_releases(data):
assert link.url == "https://foo/bar-2.0b1.tar.gz"
links.reverse()
finder = PackageFinder(
finder = PackageFinder.create(
links, [],
allow_all_prereleases=True,
session=PipSession(),
@ -430,7 +432,7 @@ def test_finder_installs_dev_releases(data):
req = install_req_from_line("bar", None)
# using a local index (that has dev releases)
finder = PackageFinder(
finder = PackageFinder.create(
[], [data.index_url("dev")],
allow_all_prereleases=True,
session=PipSession(),
@ -446,14 +448,14 @@ def test_finder_installs_pre_releases_with_version_spec():
req = install_req_from_line("bar>=0.0.dev0", None)
links = ["https://foo/bar-1.0.tar.gz", "https://foo/bar-2.0b1.tar.gz"]
finder = PackageFinder(links, [], session=PipSession())
finder = PackageFinder.create(links, [], session=PipSession())
with patch.object(finder, "_get_pages", lambda x, y: []):
link = finder.find_requirement(req, False)
assert link.url == "https://foo/bar-2.0b1.tar.gz"
links.reverse()
finder = PackageFinder(links, [], session=PipSession())
finder = PackageFinder.create(links, [], session=PipSession())
with patch.object(finder, "_get_pages", lambda x, y: []):
link = finder.find_requirement(req, False)
@ -516,7 +518,7 @@ class TestCandidateEvaluator(object):
def test_get_index_urls_locations():
"""Check that the canonical name is on all indexes"""
finder = PackageFinder(
finder = PackageFinder.create(
[], ['file://index1/', 'file://index2'], session=PipSession())
locations = finder._get_index_urls_locations(
install_req_from_line('Complex_Name').name)
@ -526,26 +528,26 @@ def test_get_index_urls_locations():
def test_find_all_candidates_nothing():
"""Find nothing without anything"""
finder = PackageFinder([], [], session=PipSession())
finder = PackageFinder.create([], [], session=PipSession())
assert not finder.find_all_candidates('pip')
def test_find_all_candidates_find_links(data):
finder = PackageFinder(
finder = PackageFinder.create(
[data.find_links], [], session=PipSession())
versions = finder.find_all_candidates('simple')
assert [str(v.version) for v in versions] == ['3.0', '2.0', '1.0']
def test_find_all_candidates_index(data):
finder = PackageFinder(
finder = PackageFinder.create(
[], [data.index_url('simple')], session=PipSession())
versions = finder.find_all_candidates('simple')
assert [str(v.version) for v in versions] == ['1.0']
def test_find_all_candidates_find_links_and_index(data):
finder = PackageFinder(
finder = PackageFinder.create(
[data.find_links], [data.index_url('simple')], session=PipSession())
versions = finder.find_all_candidates('simple')
# first the find-links versions then the page versions

View File

@ -16,7 +16,7 @@ def test_sort_locations_file_expand_dir(data):
"""
Test that a file:// dir gets listdir run with expand_dir
"""
finder = PackageFinder([data.find_links], [], session=PipSession())
finder = PackageFinder.create([data.find_links], [], session=PipSession())
files, urls = finder._sort_locations([data.find_links], expand_dir=True)
assert files and not urls, (
"files and not urls should have been found at find-links url: %s" %
@ -29,7 +29,7 @@ def test_sort_locations_file_not_find_link(data):
Test that a file:// url dir that's not a find-link, doesn't get a listdir
run
"""
finder = PackageFinder([], [], session=PipSession())
finder = PackageFinder.create([], [], session=PipSession())
files, urls = finder._sort_locations([data.index_url("empty_with_pkg")])
assert urls and not files, "urls, but not files should have been found"
@ -38,7 +38,7 @@ def test_sort_locations_non_existing_path():
"""
Test that a non-existing path is ignored.
"""
finder = PackageFinder([], [], session=PipSession())
finder = PackageFinder.create([], [], session=PipSession())
files, urls = finder._sort_locations(
[os.path.join('this', 'doesnt', 'exist')])
assert not urls and not files, "nothing should have been found"
@ -144,7 +144,7 @@ class MockLogger(object):
],
)
def test_secure_origin(location, trusted, expected):
finder = PackageFinder([], [], session=[], trusted_hosts=trusted)
finder = PackageFinder.create([], [], session=[], trusted_hosts=trusted)
logger = MockLogger()
finder._validate_secure_origin(logger, location)
assert logger.called == expected
@ -159,7 +159,7 @@ def test_get_formatted_locations_basic_auth():
'https://pypi.org/simple',
'https://user:pass@repo.domain.com',
]
finder = PackageFinder([], index_urls, session=[])
finder = PackageFinder.create([], index_urls, session=[])
result = finder.get_formatted_locations()
assert 'user' in result

View File

@ -71,7 +71,9 @@ class TestRequirementSet(object):
req = install_req_from_line('simple')
req.is_direct = True
reqset.add_requirement(req)
finder = PackageFinder([data.find_links], [], session=PipSession())
finder = PackageFinder.create(
[data.find_links], [], session=PipSession(),
)
resolver = self._basic_resolver(finder)
assert_raises_regexp(
PreviousBuildDirError,
@ -93,7 +95,9 @@ class TestRequirementSet(object):
)
req.is_direct = True
reqset.add_requirement(req)
finder = PackageFinder([data.find_links], [], session=PipSession())
finder = PackageFinder.create(
[data.find_links], [], session=PipSession(),
)
resolver = self._basic_resolver(finder)
resolver.resolve(reqset)
# This is hacky but does test both case in py2 and py3
@ -130,7 +134,7 @@ class TestRequirementSet(object):
'packages/source/p/peep/peep-3.1.1.tar.gz',
lineno=4,
))
finder = PackageFinder(
finder = PackageFinder.create(
[],
['https://pypi.org/simple/'],
session=PipSession(),
@ -160,7 +164,9 @@ class TestRequirementSet(object):
'simple==1.0', lineno=1
))
finder = PackageFinder([data.find_links], [], session=PipSession())
finder = PackageFinder.create(
[data.find_links], [], session=PipSession(),
)
resolver = self._basic_resolver(finder)
assert_raises_regexp(
@ -179,7 +185,7 @@ class TestRequirementSet(object):
"""
req_set = RequirementSet(require_hashes=False)
session = PipSession()
finder = PackageFinder([data.find_links], [], session=session)
finder = PackageFinder.create([data.find_links], [], session=session)
command = InstallCommand()
with requirements_file('--require-hashes', tmpdir) as reqs_file:
options, args = command.parse_args(['-r', reqs_file])
@ -207,7 +213,9 @@ class TestRequirementSet(object):
'file://%s' % (dir_path,),
lineno=2,
))
finder = PackageFinder([data.find_links], [], session=PipSession())
finder = PackageFinder.create(
[data.find_links], [], session=PipSession(),
)
resolver = self._basic_resolver(finder)
sep = os.path.sep
if sep == '\\':
@ -241,7 +249,9 @@ class TestRequirementSet(object):
'123f6a7e44a9115db1ef945d4d92c123dfe21815a06',
lineno=2,
))
finder = PackageFinder([data.find_links], [], session=PipSession())
finder = PackageFinder.create(
[data.find_links], [], session=PipSession(),
)
resolver = self._basic_resolver(finder)
assert_raises_regexp(
HashErrors,
@ -260,7 +270,9 @@ class TestRequirementSet(object):
reqset.add_requirement(get_processed_req_from_line(
'%s --hash=sha256:badbad' % file_url, lineno=1,
))
finder = PackageFinder([data.find_links], [], session=PipSession())
finder = PackageFinder.create(
[data.find_links], [], session=PipSession(),
)
resolver = self._basic_resolver(finder)
assert_raises_regexp(
HashErrors,
@ -276,7 +288,9 @@ class TestRequirementSet(object):
"""Make sure unhashed, unpinned, or otherwise unrepeatable
dependencies get complained about when --require-hashes is on."""
reqset = RequirementSet()
finder = PackageFinder([data.find_links], [], session=PipSession())
finder = PackageFinder.create(
[data.find_links], [], session=PipSession(),
)
resolver = self._basic_resolver(finder)
reqset.add_requirement(get_processed_req_from_line(
'TopoRequires2==0.0.1 ' # requires TopoRequires

View File

@ -30,7 +30,7 @@ def session():
@pytest.fixture
def finder(session):
return PackageFinder([], [], session=session)
return PackageFinder.create([], [], session=session)
@pytest.fixture

View File

@ -33,8 +33,9 @@ class MockPackageFinder(object):
BASE_URL.format('1.0')),
]
def __init__(self, *args, **kwargs):
pass
@classmethod
def create(cls, *args, **kwargs):
return cls()
def find_candidates(self, project_name):
return MockFoundCandidates(self.INSTALLATION_CANDIDATES[0])