pip/tests/functional/test_download.py

1476 lines
40 KiB
Python

import http.server
import os
import re
import shutil
import textwrap
from hashlib import sha256
from pathlib import Path
from typing import Callable, List, Tuple
import pytest
from pip._internal.cli.status_codes import ERROR
from pip._internal.utils.urls import path_to_url
from tests.lib import (
PipTestEnvironment,
ScriptFactory,
TestData,
TestPipResult,
create_basic_sdist_for_package,
create_really_basic_wheel,
)
from tests.lib.server import MockServer, file_response
def fake_wheel(data: TestData, wheel_path: str) -> None:
wheel_name = os.path.basename(wheel_path)
name, version, rest = wheel_name.split("-", 2)
wheel_data = create_really_basic_wheel(name, version)
data.packages.joinpath(wheel_path).write_bytes(wheel_data)
@pytest.mark.network
def test_download_if_requested(script: PipTestEnvironment) -> None:
"""
It should download (in the scratch path) and not install if requested.
"""
result = script.pip("download", "-d", "pip_downloads", "INITools==0.1")
result.did_create(Path("scratch") / "pip_downloads" / "INITools-0.1.tar.gz")
result.did_not_create(script.site_packages / "initools")
@pytest.mark.network
def test_basic_download_setuptools(script: PipTestEnvironment) -> None:
"""
It should download (in the scratch path) and not install if requested.
"""
result = script.pip("download", "setuptools")
setuptools_prefix = str(Path("scratch") / "setuptools")
assert any(os.fspath(p).startswith(setuptools_prefix) for p in result.files_created)
def test_download_wheel(script: PipTestEnvironment, data: TestData) -> None:
"""
Test using "pip download" to download a *.whl archive.
"""
result = script.pip(
"download", "--no-index", "-f", data.packages, "-d", ".", "meta"
)
result.did_create(Path("scratch") / "meta-1.0-py2.py3-none-any.whl")
result.did_not_create(script.site_packages / "piptestpackage")
@pytest.mark.network
def test_single_download_from_requirements_file(script: PipTestEnvironment) -> None:
"""
It should support download (in the scratch path) from PyPI from a
requirements file
"""
script.scratch_path.joinpath("test-req.txt").write_text(
textwrap.dedent(
"""
INITools==0.1
"""
)
)
result = script.pip(
"download",
"-r",
script.scratch_path / "test-req.txt",
"-d",
".",
)
result.did_create(Path("scratch") / "INITools-0.1.tar.gz")
result.did_not_create(script.site_packages / "initools")
@pytest.mark.network
def test_basic_download_should_download_dependencies(
script: PipTestEnvironment,
) -> None:
"""
It should download dependencies (in the scratch path)
"""
result = script.pip("download", "Paste[openid]==1.7.5.1", "-d", ".")
result.did_create(Path("scratch") / "Paste-1.7.5.1.tar.gz")
openid_tarball_prefix = str(Path("scratch") / "python-openid-")
assert any(
os.fspath(path).startswith(openid_tarball_prefix)
for path in result.files_created
)
result.did_not_create(script.site_packages / "openid")
def test_download_wheel_archive(script: PipTestEnvironment, data: TestData) -> None:
"""
It should download a wheel archive path
"""
wheel_filename = "colander-0.9.9-py2.py3-none-any.whl"
wheel_path = "/".join((data.find_links, wheel_filename))
result = script.pip("download", wheel_path, "-d", ".", "--no-deps")
result.did_create(Path("scratch") / wheel_filename)
def test_download_should_download_wheel_deps(
script: PipTestEnvironment, data: TestData
) -> None:
"""
It should download dependencies for wheels(in the scratch path)
"""
wheel_filename = "colander-0.9.9-py2.py3-none-any.whl"
dep_filename = "translationstring-1.1.tar.gz"
wheel_path = "/".join((data.find_links, wheel_filename))
result = script.pip(
"download", wheel_path, "-d", ".", "--find-links", data.find_links, "--no-index"
)
result.did_create(Path("scratch") / wheel_filename)
result.did_create(Path("scratch") / dep_filename)
@pytest.mark.network
def test_download_should_skip_existing_files(script: PipTestEnvironment) -> None:
"""
It should not download files already existing in the scratch dir
"""
script.scratch_path.joinpath("test-req.txt").write_text(
textwrap.dedent(
"""
INITools==0.1
"""
)
)
result = script.pip(
"download",
"-r",
script.scratch_path / "test-req.txt",
"-d",
".",
)
result.did_create(Path("scratch") / "INITools-0.1.tar.gz")
result.did_not_create(script.site_packages / "initools")
# adding second package to test-req.txt
script.scratch_path.joinpath("test-req.txt").write_text(
textwrap.dedent(
"""
INITools==0.1
python-openid==2.2.5
"""
)
)
# only the second package should be downloaded
result = script.pip(
"download",
"-r",
script.scratch_path / "test-req.txt",
"-d",
".",
)
openid_tarball_prefix = str(Path("scratch") / "python-openid-")
assert any(
os.fspath(path).startswith(openid_tarball_prefix)
for path in result.files_created
)
result.did_not_create(Path("scratch") / "INITools-0.1.tar.gz")
result.did_not_create(script.site_packages / "initools")
result.did_not_create(script.site_packages / "openid")
@pytest.mark.network
def test_download_vcs_link(script: PipTestEnvironment) -> None:
"""
It should allow -d flag for vcs links, regression test for issue #798.
"""
result = script.pip(
"download", "-d", ".", "git+https://github.com/pypa/pip-test-package.git"
)
result.did_create(Path("scratch") / "pip-test-package-0.1.1.zip")
result.did_not_create(script.site_packages / "piptestpackage")
def test_only_binary_set_then_download_specific_platform(
script: PipTestEnvironment, data: TestData
) -> None:
"""
Confirm that specifying an interpreter/platform constraint
is allowed when ``--only-binary=:all:`` is set.
"""
fake_wheel(data, "fake-1.0-py2.py3-none-any.whl")
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--platform",
"linux_x86_64",
"fake",
)
result.did_create(Path("scratch") / "fake-1.0-py2.py3-none-any.whl")
def test_no_deps_set_then_download_specific_platform(
script: PipTestEnvironment, data: TestData
) -> None:
"""
Confirm that specifying an interpreter/platform constraint
is allowed when ``--no-deps`` is set.
"""
fake_wheel(data, "fake-1.0-py2.py3-none-any.whl")
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--no-deps",
"--dest",
".",
"--platform",
"linux_x86_64",
"fake",
)
result.did_create(Path("scratch") / "fake-1.0-py2.py3-none-any.whl")
def test_download_specific_platform_fails(
script: PipTestEnvironment, data: TestData
) -> None:
"""
Confirm that specifying an interpreter/platform constraint
enforces that ``--no-deps`` or ``--only-binary=:all:`` is set.
"""
fake_wheel(data, "fake-1.0-py2.py3-none-any.whl")
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--dest",
".",
"--platform",
"linux_x86_64",
"fake",
expect_error=True,
)
assert "--only-binary=:all:" in result.stderr
def test_no_binary_set_then_download_specific_platform_fails(
script: PipTestEnvironment, data: TestData
) -> None:
"""
Confirm that specifying an interpreter/platform constraint
enforces that ``--only-binary=:all:`` is set without ``--no-binary``.
"""
fake_wheel(data, "fake-1.0-py2.py3-none-any.whl")
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--no-binary=fake",
"--dest",
".",
"--platform",
"linux_x86_64",
"fake",
expect_error=True,
)
assert "--only-binary=:all:" in result.stderr
def test_download_specify_platform(script: PipTestEnvironment, data: TestData) -> None:
"""
Test using "pip download --platform" to download a .whl archive
supported for a specific platform
"""
fake_wheel(data, "fake-1.0-py2.py3-none-any.whl")
# Confirm that universal wheels are returned even for specific
# platforms.
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--platform",
"linux_x86_64",
"fake",
)
result.did_create(Path("scratch") / "fake-1.0-py2.py3-none-any.whl")
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--platform",
"macosx_10_9_x86_64",
"fake",
)
data.reset()
fake_wheel(data, "fake-1.0-py2.py3-none-macosx_10_9_x86_64.whl")
fake_wheel(data, "fake-2.0-py2.py3-none-linux_x86_64.whl")
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--platform",
"macosx_10_10_x86_64",
"fake",
)
result.did_create(Path("scratch") / "fake-1.0-py2.py3-none-macosx_10_9_x86_64.whl")
# OSX platform wheels are not backward-compatible.
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--platform",
"macosx_10_8_x86_64",
"fake",
expect_error=True,
)
# No linux wheel provided for this version.
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--platform",
"linux_x86_64",
"fake==1",
expect_error=True,
)
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--platform",
"linux_x86_64",
"fake==2",
)
result.did_create(Path("scratch") / "fake-2.0-py2.py3-none-linux_x86_64.whl")
# Test with multiple supported platforms specified.
data.reset()
fake_wheel(data, "fake-3.0-py2.py3-none-linux_x86_64.whl")
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--platform",
"manylinux1_x86_64",
"--platform",
"linux_x86_64",
"--platform",
"any",
"fake==3",
)
result.did_create(Path("scratch") / "fake-3.0-py2.py3-none-linux_x86_64.whl")
class TestDownloadPlatformManylinuxes:
"""
"pip download --platform" downloads a .whl archive supported for
manylinux platforms.
"""
@pytest.mark.parametrize(
"platform",
[
"linux_x86_64",
"manylinux1_x86_64",
"manylinux2010_x86_64",
"manylinux2014_x86_64",
],
)
def test_download_universal(
self, platform: str, script: PipTestEnvironment, data: TestData
) -> None:
"""
Universal wheels are returned even for specific platforms.
"""
fake_wheel(data, "fake-1.0-py2.py3-none-any.whl")
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--platform",
platform,
"fake",
)
result.did_create(Path("scratch") / "fake-1.0-py2.py3-none-any.whl")
@pytest.mark.parametrize(
"wheel_abi,platform",
[
("manylinux1_x86_64", "manylinux1_x86_64"),
("manylinux1_x86_64", "manylinux2010_x86_64"),
("manylinux2010_x86_64", "manylinux2010_x86_64"),
("manylinux1_x86_64", "manylinux2014_x86_64"),
("manylinux2010_x86_64", "manylinux2014_x86_64"),
("manylinux2014_x86_64", "manylinux2014_x86_64"),
],
)
def test_download_compatible_manylinuxes(
self,
wheel_abi: str,
platform: str,
script: PipTestEnvironment,
data: TestData,
) -> None:
"""
Earlier manylinuxes are compatible with later manylinuxes.
"""
wheel = f"fake-1.0-py2.py3-none-{wheel_abi}.whl"
fake_wheel(data, wheel)
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--platform",
platform,
"fake",
)
result.did_create(Path("scratch") / wheel)
def test_explicit_platform_only(
self, data: TestData, script: PipTestEnvironment
) -> None:
"""
When specifying the platform, manylinux1 needs to be the
explicit platform--it won't ever be added to the compatible
tags.
"""
fake_wheel(data, "fake-1.0-py2.py3-none-linux_x86_64.whl")
script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--platform",
"linux_x86_64",
"fake",
)
def test_download__python_version(script: PipTestEnvironment, data: TestData) -> None:
"""
Test using "pip download --python-version" to download a .whl archive
supported for a specific interpreter
"""
fake_wheel(data, "fake-1.0-py2.py3-none-any.whl")
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--python-version",
"2",
"fake",
)
result.did_create(Path("scratch") / "fake-1.0-py2.py3-none-any.whl")
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--python-version",
"3",
"fake",
)
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--python-version",
"27",
"fake",
)
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--python-version",
"33",
"fake",
)
data.reset()
fake_wheel(data, "fake-1.0-py2-none-any.whl")
fake_wheel(data, "fake-2.0-py3-none-any.whl")
# No py3 provided for version 1.
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--python-version",
"3",
"fake==1.0",
expect_error=True,
)
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--python-version",
"2",
"fake",
)
result.did_create(Path("scratch") / "fake-1.0-py2-none-any.whl")
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--python-version",
"26",
"fake",
)
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--python-version",
"3",
"fake",
)
result.did_create(Path("scratch") / "fake-2.0-py3-none-any.whl")
def make_wheel_with_python_requires(
script: PipTestEnvironment, package_name: str, python_requires: str
) -> Path:
"""
Create a wheel using the given python_requires.
:return: the path to the wheel file.
"""
package_dir = script.scratch_path / package_name
package_dir.mkdir()
text = textwrap.dedent(
"""\
from setuptools import setup
setup(name='{}',
python_requires='{}',
version='1.0')
"""
).format(package_name, python_requires)
package_dir.joinpath("setup.py").write_text(text)
script.run(
"python",
"setup.py",
"bdist_wheel",
"--universal",
cwd=package_dir,
)
file_name = f"{package_name}-1.0-py2.py3-none-any.whl"
return package_dir / "dist" / file_name
def test_download__python_version_used_for_python_requires(
script: PipTestEnvironment, data: TestData
) -> None:
"""
Test that --python-version is used for the Requires-Python check.
"""
wheel_path = make_wheel_with_python_requires(
script,
"mypackage",
python_requires="==3.2",
)
wheel_dir = os.path.dirname(wheel_path)
def make_args(python_version: str) -> List[str]:
return [
"download",
"--no-index",
"--find-links",
wheel_dir,
"--only-binary=:all:",
"--dest",
".",
"--python-version",
python_version,
"mypackage==1.0",
]
args = make_args("33")
result = script.pip(*args, expect_error=True)
expected_err = (
"ERROR: Package 'mypackage' requires a different Python: "
"3.3.0 not in '==3.2'"
)
assert expected_err in result.stderr, f"stderr: {result.stderr}"
# Now try with a --python-version that satisfies the Requires-Python.
args = make_args("32")
script.pip(*args) # no exception
def test_download_ignore_requires_python_dont_fail_with_wrong_python(
script: PipTestEnvironment,
) -> None:
"""
Test that --ignore-requires-python ignores Requires-Python check.
"""
wheel_path = make_wheel_with_python_requires(
script,
"mypackage",
python_requires="==999",
)
wheel_dir = os.path.dirname(wheel_path)
result = script.pip(
"download",
"--ignore-requires-python",
"--no-index",
"--find-links",
wheel_dir,
"--only-binary=:all:",
"--dest",
".",
"mypackage==1.0",
)
result.did_create(Path("scratch") / "mypackage-1.0-py2.py3-none-any.whl")
def test_download_specify_abi(script: PipTestEnvironment, data: TestData) -> None:
"""
Test using "pip download --abi" to download a .whl archive
supported for a specific abi
"""
fake_wheel(data, "fake-1.0-py2.py3-none-any.whl")
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--implementation",
"fk",
"--abi",
"fake_abi",
"fake",
)
result.did_create(Path("scratch") / "fake-1.0-py2.py3-none-any.whl")
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--implementation",
"fk",
"--abi",
"none",
"fake",
)
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--abi",
"cp27m",
"fake",
)
data.reset()
fake_wheel(data, "fake-1.0-fk2-fakeabi-fake_platform.whl")
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--python-version",
"2",
"--implementation",
"fk",
"--platform",
"fake_platform",
"--abi",
"fakeabi",
"fake",
)
result.did_create(Path("scratch") / "fake-1.0-fk2-fakeabi-fake_platform.whl")
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--implementation",
"fk",
"--platform",
"fake_platform",
"--abi",
"none",
"fake",
expect_error=True,
)
data.reset()
fake_wheel(data, "fake-1.0-fk2-otherabi-fake_platform.whl")
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--python-version",
"2",
"--implementation",
"fk",
"--platform",
"fake_platform",
"--abi",
"fakeabi",
"--abi",
"otherabi",
"--abi",
"none",
"fake",
)
result.did_create(Path("scratch") / "fake-1.0-fk2-otherabi-fake_platform.whl")
def test_download_specify_implementation(
script: PipTestEnvironment, data: TestData
) -> None:
"""
Test using "pip download --abi" to download a .whl archive
supported for a specific abi
"""
fake_wheel(data, "fake-1.0-py2.py3-none-any.whl")
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--implementation",
"fk",
"fake",
)
result.did_create(Path("scratch") / "fake-1.0-py2.py3-none-any.whl")
data.reset()
fake_wheel(data, "fake-1.0-fk3-none-any.whl")
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--implementation",
"fk",
"--python-version",
"3",
"fake",
)
result.did_create(Path("scratch") / "fake-1.0-fk3-none-any.whl")
result = script.pip(
"download",
"--no-index",
"--find-links",
data.find_links,
"--only-binary=:all:",
"--dest",
".",
"--implementation",
"fk",
"--python-version",
"2",
"fake",
expect_error=True,
)
def test_download_exit_status_code_when_no_requirements(
script: PipTestEnvironment,
) -> None:
"""
Test download exit status code when no requirements specified
"""
result = script.pip("download", expect_error=True)
assert "You must give at least one requirement to download" in result.stderr
assert result.returncode == ERROR
def test_download_exit_status_code_when_blank_requirements_file(
script: PipTestEnvironment,
) -> None:
"""
Test download exit status code when blank requirements file specified
"""
script.scratch_path.joinpath("blank.txt").write_text("\n")
script.pip("download", "-r", "blank.txt")
def test_download_prefer_binary_when_tarball_higher_than_wheel(
script: PipTestEnvironment, data: TestData
) -> None:
fake_wheel(data, "source-0.8-py2.py3-none-any.whl")
result = script.pip(
"download",
"--prefer-binary",
"--no-index",
"-f",
data.packages,
"-d",
".",
"source",
)
result.did_create(Path("scratch") / "source-0.8-py2.py3-none-any.whl")
result.did_not_create(Path("scratch") / "source-1.0.tar.gz")
def test_prefer_binary_tarball_higher_than_wheel_req_file(
script: PipTestEnvironment, data: TestData
) -> None:
fake_wheel(data, "source-0.8-py2.py3-none-any.whl")
script.scratch_path.joinpath("test-req.txt").write_text(
textwrap.dedent(
"""
--prefer-binary
source
"""
)
)
result = script.pip(
"download",
"-r",
script.scratch_path / "test-req.txt",
"--no-index",
"-f",
data.packages,
"-d",
".",
)
result.did_create(Path("scratch") / "source-0.8-py2.py3-none-any.whl")
result.did_not_create(Path("scratch") / "source-1.0.tar.gz")
def test_download_prefer_binary_when_wheel_doesnt_satisfy_req(
script: PipTestEnvironment, data: TestData
) -> None:
fake_wheel(data, "source-0.8-py2.py3-none-any.whl")
script.scratch_path.joinpath("test-req.txt").write_text(
textwrap.dedent(
"""
source>0.9
"""
)
)
result = script.pip(
"download",
"--prefer-binary",
"--no-index",
"-f",
data.packages,
"-d",
".",
"-r",
script.scratch_path / "test-req.txt",
)
result.did_create(Path("scratch") / "source-1.0.tar.gz")
result.did_not_create(Path("scratch") / "source-0.8-py2.py3-none-any.whl")
def test_prefer_binary_when_wheel_doesnt_satisfy_req_req_file(
script: PipTestEnvironment, data: TestData
) -> None:
fake_wheel(data, "source-0.8-py2.py3-none-any.whl")
script.scratch_path.joinpath("test-req.txt").write_text(
textwrap.dedent(
"""
--prefer-binary
source>0.9
"""
)
)
result = script.pip(
"download",
"--no-index",
"-f",
data.packages,
"-d",
".",
"-r",
script.scratch_path / "test-req.txt",
)
result.did_create(Path("scratch") / "source-1.0.tar.gz")
result.did_not_create(Path("scratch") / "source-0.8-py2.py3-none-any.whl")
def test_download_prefer_binary_when_only_tarball_exists(
script: PipTestEnvironment, data: TestData
) -> None:
result = script.pip(
"download",
"--prefer-binary",
"--no-index",
"-f",
data.packages,
"-d",
".",
"source",
)
result.did_create(Path("scratch") / "source-1.0.tar.gz")
def test_prefer_binary_when_only_tarball_exists_req_file(
script: PipTestEnvironment, data: TestData
) -> None:
script.scratch_path.joinpath("test-req.txt").write_text(
textwrap.dedent(
"""
--prefer-binary
source
"""
)
)
result = script.pip(
"download",
"--no-index",
"-f",
data.packages,
"-d",
".",
"-r",
script.scratch_path / "test-req.txt",
)
result.did_create(Path("scratch") / "source-1.0.tar.gz")
@pytest.fixture(scope="session")
def shared_script(
tmpdir_factory: pytest.TempPathFactory, script_factory: ScriptFactory
) -> PipTestEnvironment:
tmpdir = tmpdir_factory.mktemp("download_shared_script")
script = script_factory(tmpdir.joinpath("workspace"))
return script
def test_download_file_url(
shared_script: PipTestEnvironment, shared_data: TestData, tmpdir: Path
) -> None:
download_dir = tmpdir / "download"
download_dir.mkdir()
downloaded_path = download_dir / "simple-1.0.tar.gz"
simple_pkg = shared_data.packages / "simple-1.0.tar.gz"
shared_script.pip(
"download",
"-d",
str(download_dir),
"--no-index",
simple_pkg.as_uri(),
)
assert downloaded_path.exists()
assert simple_pkg.read_bytes() == downloaded_path.read_bytes()
def test_download_file_url_existing_ok_download(
shared_script: PipTestEnvironment, shared_data: TestData, tmpdir: Path
) -> None:
download_dir = tmpdir / "download"
download_dir.mkdir()
downloaded_path = download_dir / "simple-1.0.tar.gz"
fake_existing_package = shared_data.packages / "simple-2.0.tar.gz"
shutil.copy(str(fake_existing_package), str(downloaded_path))
downloaded_path_bytes = downloaded_path.read_bytes()
simple_pkg = shared_data.packages / "simple-1.0.tar.gz"
url = f"{simple_pkg.as_uri()}#sha256={sha256(downloaded_path_bytes).hexdigest()}"
shared_script.pip("download", "-d", str(download_dir), url)
assert downloaded_path_bytes == downloaded_path.read_bytes()
def test_download_file_url_existing_bad_download(
shared_script: PipTestEnvironment, shared_data: TestData, tmpdir: Path
) -> None:
download_dir = tmpdir / "download"
download_dir.mkdir()
downloaded_path = download_dir / "simple-1.0.tar.gz"
fake_existing_package = shared_data.packages / "simple-2.0.tar.gz"
shutil.copy(str(fake_existing_package), str(downloaded_path))
simple_pkg = shared_data.packages / "simple-1.0.tar.gz"
simple_pkg_bytes = simple_pkg.read_bytes()
url = f"{simple_pkg.as_uri()}#sha256={sha256(simple_pkg_bytes).hexdigest()}"
result = shared_script.pip(
"download",
"-d",
str(download_dir),
url,
allow_stderr_warning=True, # bad hash
)
assert simple_pkg_bytes == downloaded_path.read_bytes()
assert "WARNING: Previously-downloaded file" in result.stderr
assert "has bad hash. Re-downloading." in result.stderr
def test_download_http_url_bad_hash(
shared_script: PipTestEnvironment,
shared_data: TestData,
tmpdir: Path,
mock_server: MockServer,
) -> None:
"""
If already-downloaded file has bad checksum, re-download.
"""
download_dir = tmpdir / "download"
download_dir.mkdir()
downloaded_path = download_dir / "simple-1.0.tar.gz"
fake_existing_package = shared_data.packages / "simple-2.0.tar.gz"
shutil.copy(str(fake_existing_package), str(downloaded_path))
simple_pkg = shared_data.packages / "simple-1.0.tar.gz"
simple_pkg_bytes = simple_pkg.read_bytes()
digest = sha256(simple_pkg_bytes).hexdigest()
mock_server.set_responses([file_response(simple_pkg)])
mock_server.start()
base_address = f"http://{mock_server.host}:{mock_server.port}"
url = f"{base_address}/simple-1.0.tar.gz#sha256={digest}"
result = shared_script.pip(
"download",
"-d",
str(download_dir),
url,
allow_stderr_warning=True, # bad hash
)
assert simple_pkg_bytes == downloaded_path.read_bytes()
assert "WARNING: Previously-downloaded file" in result.stderr
assert "has bad hash. Re-downloading." in result.stderr
mock_server.stop()
requests = mock_server.get_requests()
assert len(requests) == 1
assert requests[0]["PATH_INFO"] == "/simple-1.0.tar.gz"
assert requests[0]["HTTP_ACCEPT_ENCODING"] == "identity"
def test_download_editable(
script: PipTestEnvironment, data: TestData, tmpdir: Path
) -> None:
"""
Test 'pip download' of editables in requirement file.
"""
editable_path = str(data.src / "simplewheel-1.0").replace(os.path.sep, "/")
requirements_path = tmpdir / "requirements.txt"
requirements_path.write_text("-e " + editable_path + "\n")
download_dir = tmpdir / "download_dir"
script.pip(
"download", "--no-deps", "-r", str(requirements_path), "-d", str(download_dir)
)
downloads = os.listdir(download_dir)
assert len(downloads) == 1
assert downloads[0].endswith(".zip")
def test_download_use_pep517_propagation(
script: PipTestEnvironment, tmpdir: Path, common_wheels: Path
) -> None:
"""
Check that --use-pep517 applies not just to the requirements specified
on the command line, but to their dependencies too.
"""
create_basic_sdist_for_package(script, "fake_proj", "1.0", depends=["fake_dep"])
# If --use-pep517 is in effect, then setup.py should be running in an isolated
# environment that doesn't have pip in it.
create_basic_sdist_for_package(
script,
"fake_dep",
"1.0",
setup_py_prelude=textwrap.dedent(
"""\
try:
import pip
except ImportError:
pass
else:
raise Exception(f"not running in isolation")
"""
),
)
download_dir = tmpdir / "download_dir"
script.pip(
"download",
f"--dest={download_dir}",
"--no-index",
f"--find-links={common_wheels}",
f"--find-links={script.scratch_path}",
"--use-pep517",
"fake_proj",
)
downloads = os.listdir(download_dir)
assert len(downloads) == 2
@pytest.fixture(scope="function")
def download_local_html_index(
script: PipTestEnvironment,
html_index_for_packages: Path,
tmpdir: Path,
) -> Callable[..., Tuple[TestPipResult, Path]]:
"""Execute `pip download` against a generated PyPI index."""
download_dir = tmpdir / "download_dir"
def run_for_generated_index(
args: List[str],
allow_error: bool = False,
) -> Tuple[TestPipResult, Path]:
"""
Produce a PyPI directory structure pointing to the specified packages, then
execute `pip download -i ...` pointing to our generated index.
"""
pip_args = [
"download",
"-d",
str(download_dir),
"-i",
path_to_url(str(html_index_for_packages)),
*args,
]
result = script.pip(*pip_args, allow_error=allow_error)
return (result, download_dir)
return run_for_generated_index
@pytest.fixture(scope="function")
def download_server_html_index(
script: PipTestEnvironment,
tmpdir: Path,
html_index_with_onetime_server: http.server.ThreadingHTTPServer,
) -> Callable[..., Tuple[TestPipResult, Path]]:
"""Execute `pip download` against a generated PyPI index."""
download_dir = tmpdir / "download_dir"
def run_for_generated_index(
args: List[str],
allow_error: bool = False,
) -> Tuple[TestPipResult, Path]:
"""
Produce a PyPI directory structure pointing to the specified packages, then
execute `pip download -i ...` pointing to our generated index.
"""
pip_args = [
"download",
"-d",
str(download_dir),
"-i",
"http://localhost:8000",
*args,
]
result = script.pip(*pip_args, allow_error=allow_error)
return (result, download_dir)
return run_for_generated_index
@pytest.mark.parametrize(
"requirement_to_download, expected_outputs",
[
("simple2==1.0", ["simple-1.0.tar.gz", "simple2-1.0.tar.gz"]),
("simple==2.0", ["simple-2.0.tar.gz"]),
(
"colander",
["colander-0.9.9-py2.py3-none-any.whl", "translationstring-1.1.tar.gz"],
),
(
"compilewheel",
["compilewheel-1.0-py2.py3-none-any.whl", "simple-1.0.tar.gz"],
),
],
)
def test_download_metadata(
download_local_html_index: Callable[..., Tuple[TestPipResult, Path]],
requirement_to_download: str,
expected_outputs: List[str],
) -> None:
"""Verify that if a data-dist-info-metadata attribute is present, then it is used
instead of the actual dist's METADATA."""
_, download_dir = download_local_html_index(
[requirement_to_download],
)
assert sorted(os.listdir(download_dir)) == expected_outputs
@pytest.mark.parametrize(
"requirement_to_download, expected_outputs, doubled_path",
[
(
"simple2==1.0",
["simple-1.0.tar.gz", "simple2-1.0.tar.gz"],
"/simple2/simple2-1.0.tar.gz",
),
("simple==2.0", ["simple-2.0.tar.gz"], "/simple/simple-2.0.tar.gz"),
(
"colander",
["colander-0.9.9-py2.py3-none-any.whl", "translationstring-1.1.tar.gz"],
"/colander/colander-0.9.9-py2.py3-none-any.whl",
),
(
"compilewheel",
[
"compilewheel-1.0-py2.py3-none-any.whl",
"simple-1.0.tar.gz",
],
"/compilewheel/compilewheel-1.0-py2.py3-none-any.whl",
),
],
)
def test_download_metadata_server(
download_server_html_index: Callable[..., Tuple[TestPipResult, Path]],
requirement_to_download: str,
expected_outputs: List[str],
doubled_path: str,
) -> None:
"""Verify that if a data-dist-info-metadata attribute is present, then it is used
instead of the actual dist's METADATA.
Additionally, verify that each dist is downloaded exactly once using a mock server.
This is a regression test for issue https://github.com/pypa/pip/issues/11847.
"""
_, download_dir = download_server_html_index(
[requirement_to_download, "--no-cache-dir"],
)
assert sorted(os.listdir(download_dir)) == expected_outputs
shutil.rmtree(download_dir)
result, _ = download_server_html_index(
[requirement_to_download, "--no-cache-dir"],
allow_error=True,
)
assert result.returncode != 0
expected_msg = f"File {doubled_path} not available more than once!"
assert expected_msg in result.stderr
@pytest.mark.parametrize(
"requirement_to_download, real_hash",
[
(
"simple==3.0",
"95e0f200b6302989bcf2cead9465cf229168295ea330ca30d1ffeab5c0fed996",
),
(
"has-script",
"16ba92d7f6f992f6de5ecb7d58c914675cf21f57f8e674fb29dcb4f4c9507e5b",
),
],
)
def test_incorrect_metadata_hash(
download_local_html_index: Callable[..., Tuple[TestPipResult, Path]],
requirement_to_download: str,
real_hash: str,
) -> None:
"""Verify that if a hash for data-dist-info-metadata is provided, it must match the
actual hash of the metadata file."""
result, _ = download_local_html_index(
[requirement_to_download],
allow_error=True,
)
assert result.returncode != 0
expected_msg = f"""\
Expected sha256 WRONG-HASH
Got {real_hash}"""
assert expected_msg in result.stderr
@pytest.mark.parametrize(
"requirement_to_download, expected_url",
[
("simple2==2.0", "simple2-2.0.tar.gz.metadata"),
("priority", "priority-1.0-py2.py3-none-any.whl.metadata"),
],
)
def test_metadata_not_found(
download_local_html_index: Callable[..., Tuple[TestPipResult, Path]],
requirement_to_download: str,
expected_url: str,
) -> None:
"""Verify that if a data-dist-info-metadata attribute is provided, that pip will
fetch the .metadata file at the location specified by PEP 658, and error
if unavailable."""
result, _ = download_local_html_index(
[requirement_to_download],
allow_error=True,
)
assert result.returncode != 0
expected_re = re.escape(expected_url)
pattern = re.compile(
f"ERROR: 404 Client Error: FileNotFoundError for url:.*{expected_re}"
)
assert pattern.search(result.stderr), (pattern, result.stderr)
def test_produces_error_for_mismatched_package_name_in_metadata(
download_local_html_index: Callable[..., Tuple[TestPipResult, Path]],
) -> None:
"""Verify that the package name from the metadata matches the requested package."""
result, _ = download_local_html_index(
["simple2==3.0"],
allow_error=True,
)
assert result.returncode != 0
assert (
"simple2-3.0.tar.gz has inconsistent Name: expected 'simple2', but metadata "
"has 'not-simple2'"
) in result.stdout
@pytest.mark.parametrize(
"requirement",
(
"requires-simple-extra==0.1",
"REQUIRES_SIMPLE-EXTRA==0.1",
"REQUIRES....simple-_-EXTRA==0.1",
),
)
def test_canonicalizes_package_name_before_verifying_metadata(
download_local_html_index: Callable[..., Tuple[TestPipResult, Path]],
requirement: str,
) -> None:
"""Verify that the package name from the command line and the package's
METADATA are both canonicalized before comparison.
Regression test for https://github.com/pypa/pip/issues/12038
"""
result, download_dir = download_local_html_index(
[requirement],
allow_error=True,
)
assert result.returncode == 0
assert os.listdir(download_dir) == [
"requires_simple_extra-0.1-py2.py3-none-any.whl",
]