diff --git a/piptools/utils.py b/piptools/utils.py index 280614d00..3ae79b807 100644 --- a/piptools/utils.py +++ b/piptools/utils.py @@ -115,7 +115,14 @@ def format_requirement( if ireq.editable: line = f"-e {ireq.link.url}" elif is_url_requirement(ireq): - line = ireq.link.url + if ireq.name: + line = ( + ireq.link.url + if ireq.link.egg_fragment + else f"{ireq.name.lower()} @ {ireq.link.url}" + ) + else: + line = ireq.link.url else: line = str(ireq.req).lower() diff --git a/tests/test_cli_compile.py b/tests/test_cli_compile.py index a879018e6..d2a7bf10a 100644 --- a/tests/test_cli_compile.py +++ b/tests/test_cli_compile.py @@ -1,6 +1,8 @@ import os +import shutil import subprocess import sys +from pathlib import Path from textwrap import dedent from unittest import mock @@ -47,19 +49,19 @@ def test_command_line_overrides_pip_conf(pip_with_index_conf, runner): pytest.param("small-fake-a==0.1", "small-fake-a==0.1", id="regular"), pytest.param( "pip-tools @ https://github.com/jazzband/pip-tools/archive/7d86c8d3.zip", - "https://github.com/jazzband/pip-tools/archive/7d86c8d3.zip", + "pip-tools @ https://github.com/jazzband/pip-tools/archive/7d86c8d3.zip", id="zip URL", ), pytest.param( "pip-tools @ git+https://github.com/jazzband/pip-tools@7d86c8d3", - "git+https://github.com/jazzband/pip-tools@7d86c8d3", + "pip-tools @ git+https://github.com/jazzband/pip-tools@7d86c8d3", id="scm URL", ), pytest.param( "pip-tools @ https://files.pythonhosted.org/packages/06/96/" "89872db07ae70770fba97205b0737c17ef013d0d1c790" "899c16bb8bac419/pip_tools-3.6.1-py2.py3-none-any.whl", - "https://files.pythonhosted.org/packages/06/96/" + "pip-tools @ https://files.pythonhosted.org/packages/06/96/" "89872db07ae70770fba97205b0737c17ef013d0d1c790" "899c16bb8bac419/pip_tools-3.6.1-py2.py3-none-any.whl", id="wheel URL", @@ -532,6 +534,20 @@ def test_locally_available_editable_package_is_not_archived_in_cache_dir( "899c16bb8bac419/pip_tools-3.6.1-py2.py3-none-any.whl", "\nclick==", ), + ( + "pytest-django @ git+git://github.com/pytest-dev/pytest-django" + "@21492afc88a19d4ca01cd0ac392a5325b14f95c7" + "#egg=pytest-django", + "git+git://github.com/pytest-dev/pytest-django" + "@21492afc88a19d4ca01cd0ac392a5325b14f95c7#egg=pytest-django", + ), + ( + "git+git://github.com/open-telemetry/opentelemetry-python.git" + "@v1.3.0#subdirectory=opentelemetry-api" + "&egg=opentelemetry-api", + "git+git://github.com/open-telemetry/opentelemetry-python.git" + "@v1.3.0#subdirectory=opentelemetry-api", + ), ), ) @pytest.mark.parametrize("generate_hashes", ((True,), (False,))) @@ -596,6 +612,40 @@ def test_local_url_package( assert dependency in out.stderr +@pytest.mark.parametrize( + ("line", "dependency", "rewritten_line"), + ( + pytest.param( + os.path.join( + MINIMAL_WHEELS_PATH, "small_fake_with_deps-0.1-py2.py3-none-any.whl" + ), + "\nfile:small_fake_with_deps-0.1-py2.py3-none-any.whl", + "file:small_fake_with_deps-0.1-py2.py3-none-any.whl", + id="Relative URL", + ), + pytest.param( + os.path.join( + MINIMAL_WHEELS_PATH, "small_fake_with_deps-0.1-py2.py3-none-any.whl" + ), + "\nsmall-fake-with-deps" + " @ file://{absolute_path}/small_fake_with_deps-0.1-py2.py3-none-any.whl", + "small-fake-with-deps" + " @ file://{absolute_path}/small_fake_with_deps-0.1-py2.py3-none-any.whl", + id="Relative URL", + ), + ), +) +def test_relative_url_package(pip_conf, runner, line, dependency, rewritten_line): + dependency = dependency.format(absolute_path=Path(".").absolute()) + rewritten_line = rewritten_line.format(absolute_path=Path(".").absolute()) + shutil.copy(line, ".") + with open("requirements.in", "w") as req_in: + req_in.write(dependency) + out = runner.invoke(cli, ["-n", "--rebuild"]) + assert out.exit_code == 0 + assert rewritten_line in out.stderr + + def test_input_file_without_extension(pip_conf, runner): """ piptools can compile a file without an extension, diff --git a/tests/test_data/packages/fake_with_deps/setup.py b/tests/test_data/packages/fake_with_deps/setup.py index 44a9e8959..709b59097 100644 --- a/tests/test_data/packages/fake_with_deps/setup.py +++ b/tests/test_data/packages/fake_with_deps/setup.py @@ -18,5 +18,6 @@ "SQLAlchemy!=0.9.5,<2.0.0,>=0.7.8,>=1.0.0", "python-memcached>=1.57,<2.0", "xmltodict<=0.11,>=0.4.6", + "requests @ git+git://github.com/psf/requests@v2.25.1", ], ) diff --git a/tests/test_utils.py b/tests/test_utils.py index 73873dabf..49a6f3c8c 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -32,9 +32,74 @@ def test_format_requirement(from_line): assert format_requirement(ireq) == "test==1.2" -def test_format_requirement_url(from_line): - ireq = from_line("https://example.com/example.zip") - assert format_requirement(ireq) == "https://example.com/example.zip" +@pytest.mark.parametrize( + ("line", "expected"), + ( + pytest.param( + "https://example.com/example.zip", + "https://example.com/example.zip", + id="simple url", + ), + pytest.param( + "example @ https://example.com/example.zip", + "example @ https://example.com/example.zip", + id="direct reference", + ), + pytest.param( + "Example @ https://example.com/example.zip", + "example @ https://example.com/example.zip", + id="direct reference lowered case", + ), + pytest.param( + "example @ https://example.com/example.zip#egg=example", + "https://example.com/example.zip#egg=example", + id="url with egg in fragment", + ), + pytest.param( + "example @ https://example.com/example.zip#subdirectory=test&egg=example", + "https://example.com/example.zip#subdirectory=test&egg=example", + id="url with subdirectory and egg in fragment", + ), + pytest.param( + "example @ https://example.com/example.zip?egg=test#subdirectory=project_a", + "example @ https://example.com/example.zip?egg=test#subdirectory=project_a", + id="url with egg in query", + ), + pytest.param( + "file:./vendor/package.zip", + "file:./vendor/package.zip", + id="relative path", + ), + pytest.param( + "file:vendor/package.zip", + "file:vendor/package.zip", + id="relative path", + ), + pytest.param( + "file:vendor/package.zip#egg=example", + "file:vendor/package.zip#egg=example", + id="relative path with egg", + ), + pytest.param( + "file:///vendor/package.zip", + "file:///vendor/package.zip", + id="full path without direct reference", + ), + pytest.param( + "package @ file:///vendor/package.zip", + "package @ file:///vendor/package.zip", + id="full path with direct reference", + ), + pytest.param( + "package @ file:///vendor/package.zip#egg=example", + "file:///vendor/package.zip#egg=example", + id="full path with direct reference and egg", + ), + ), +) +def test_format_requirement_url(from_line, line, expected): + ireq = from_line(line) + assert format_requirement(ireq) == expected def test_format_requirement_editable_vcs(from_editable):