Repository: jazzband/pip-tools

Issue:
pip-compile raises an exception when setup.py has a URL in install_requires directive
<!-- Describe the issue briefly here. -->


#### Environment Versions

1. OS Type: macOS
1. Python version: `3.8.0`
1. pip version: `21.0.1`
1. pip-tools version: 9aeeed133504cfbeafb5e640290cd3c85ed3c651

#### Steps to replicate

1. Create `setup.py`
```python
# setup.py
from setuptools import setup

setup(
    name="foo",
    version="0.1",
    install_requires=["six @ git+https://github.com/benjaminp/six#egg=six"],
)
```
2. Run `pip-compile`

#### Expected result

Works with `pip-tools==5.5.0`:

```ini
$ pip-compile
#
# This file is autogenerated by pip-compile
# To update, run:
#
#    pip-compile
#
git+https://github.com/benjaminp/six#egg=six
    # via foo (setup.py)
```

#### Actual result

```python
$ pip-compile
Traceback (most recent call last):
  File "/Users/albert/Projects/pip-tools/.venv/bin/pip-compile", line 33, in <module>
    sys.exit(load_entry_point('pip-tools', 'console_scripts', 'pip-compile')())
  File "/Users/albert/Projects/pip-tools/.venv/lib/python3.8/site-packages/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/Users/albert/Projects/pip-tools/.venv/lib/python3.8/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/Users/albert/Projects/pip-tools/.venv/lib/python3.8/site-packages/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/albert/Projects/pip-tools/.venv/lib/python3.8/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/Users/albert/Projects/pip-tools/.venv/lib/python3.8/site-packages/click/decorators.py", line 21, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/Users/albert/Projects/pip-tools/piptools/scripts/compile.py", line 361, in cli
    [
  File "/Users/albert/Projects/pip-tools/piptools/scripts/compile.py", line 362, in <listcomp>
    install_req_from_req_string(req, comes_from=comes_from)
  File "/Users/albert/Projects/pip-tools/.venv/lib/python3.8/site-packages/pip/_internal/req/constructors.py", line 422, in install_req_from_req_string
    if (req.url and comes_from and comes_from.link and
AttributeError: 'str' object has no attribute 'link'
```

#### Additional context
- introduced in #1311
- [install_req_from_req_string(comes_from=...)](https://github.com/pypa/pip/blob/31eb524ff0d18d6190b30a0c7821c0c857a7523c/src/pip/_internal/req/constructors.py#L415) accepts only optional `InsallRequirement`
- [install_req_from_line](https://github.com/pypa/pip/blob/31eb524ff0d18d6190b30a0c7821c0c857a7523c/src/pip/_internal/req/constructors.py#L382) can be used instead of `install_req_from_req_string`

/cc @astrojuanlu 



Discussion:

Resolving patch:
diff --git a/piptools/scripts/compile.py b/piptools/scripts/compile.py
--- a/piptools/scripts/compile.py
+++ b/piptools/scripts/compile.py
@@ -7,10 +7,7 @@
 import click
 from click.utils import safecall
 from pip._internal.commands import create_command
-from pip._internal.req.constructors import (
-    install_req_from_line,
-    install_req_from_req_string,
-)
+from pip._internal.req.constructors import install_req_from_line
 from pip._internal.utils.misc import redact_auth_from_url
 from pip._vendor.pep517 import meta
 
@@ -359,7 +356,7 @@ def cli(
             comes_from = f"{dist.metadata.get_all('Name')[0]} ({src_file})"
             constraints.extend(
                 [
-                    install_req_from_req_string(req, comes_from=comes_from)
+                    install_req_from_line(req, comes_from=comes_from)
                     for req in dist.requires or []
                 ]
             )


Test patch:
diff --git a/tests/test_cli_compile.py b/tests/test_cli_compile.py
--- a/tests/test_cli_compile.py
+++ b/tests/test_cli_compile.py
@@ -39,42 +39,48 @@ def test_command_line_overrides_pip_conf(pip_with_index_conf, runner):
 
 
 @pytest.mark.network
-def test_command_line_setuptools_read(runner, make_pip_conf):
-    make_pip_conf(
-        dedent(
-            """\
-            [global]
-            disable-pip-version-check = True
-            """
-        )
+@pytest.mark.parametrize(
+    ("install_requires", "expected_output"),
+    (
+        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",
+            id="zip URL",
+        ),
+        pytest.param(
+            "pip-tools @ git+https://github.com/jazzband/pip-tools@7d86c8d3",
+            "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/"
+            "89872db07ae70770fba97205b0737c17ef013d0d1c790"
+            "899c16bb8bac419/pip_tools-3.6.1-py2.py3-none-any.whl",
+            id="wheel URL",
+        ),
+    ),
+)
+def test_command_line_setuptools_read(
+    runner, make_pip_conf, make_package, install_requires, expected_output
+):
+    package_dir = make_package(
+        name="fake-setuptools-a",
+        install_requires=(install_requires,),
     )
 
-    with open("setup.py", "w") as package:
-        package.write(
-            dedent(
-                """\
-                from setuptools import setup
-                setup(
-                    name="fake-setuptools-a",
-                    install_requires=["small-fake-a==0.1"]
-                )
-                """
-            )
-        )
     out = runner.invoke(
         cli,
-        ["--no-header", "--no-emit-find-links", "--find-links", MINIMAL_WHEELS_PATH],
+        (str(package_dir / "setup.py"), "--find-links", MINIMAL_WHEELS_PATH),
     )
 
-    assert out.stderr == dedent(
-        """\
-        small-fake-a==0.1
-            # via fake-setuptools-a (setup.py)
-        """
-    )
+    assert expected_output in out.stderr.splitlines()
 
     # check that pip-compile generated a configuration file
-    assert os.path.exists("requirements.txt")
+    assert (package_dir / "requirements.txt").exists()
 
 
 @pytest.mark.network