Repository: jazzband/pip-tools (python)

Issue:
pip-compile bumps setuptools within commented-out spec
If you have a `requirements.in` which contains a package which depends on `setuptools`, for example:
```
$ cat requirements.in 
protobuf  # or google-api-core, ipython, pandas-gbq, pydata-google-auth
```
Then after running `pip-compile`, you get:
```
$ cat requirements.txt
#
# This file is autogenerated by pip-compile
# To update, run:
#
#    pip-compile
#
protobuf==3.10.0
six==1.12.0               # via protobuf

# The following packages are considered to be unsafe in a requirements file:
# setuptools==41.4.0        # via protobuf
```

As you can see, `pip-compile` refuses to include `setuptools` directly in a generated `requirements.txt`, instead adding a section with the comment "The following packages are considered to be unsafe in a requirements file:".
This behaviour is fine in the general (and I don't disagree with the comment or behaviour).

However because the commented out `setuptools` line includes a version number, when `setuptools` releases an update then the `requirements.txt` file ends up looking out of date.

Say you had committed a `requirements.txt` which looked like:
```
# requirements...

# The following packages are considered to be unsafe in a requirements file:
# setuptools==41.4.0        # via protobuf
```

Then `setuptools` releases `41.5.0` and now when you run `pip-compile` you get:
```
# requirements...

# The following packages are considered to be unsafe in a requirements file:
# setuptools==41.5.0        # via protobuf
```

This is inconvenient if you happen to regenerate your `requirements.txt` at some point after an upstream releases, however if you're also using `pip-compile` to check that your `requirements.txt` is in step with your `requirements.in` (see #882) in CI then it means that your build now fails for what is essentially a non-change.

I'm not sure what the right solution is here, though perhaps if there was a way for pip-compile to understand its own commented out dependency that might help?

Pull request:
Unpin commented out unsafe packages
Resolves #973. See the proposal in https://github.com/jazzband/pip-tools/issues/973#issuecomment-547010219.

**Changelog-friendly one-liner**: Unpin commented out unsafe packages in `requirements.txt`.

##### Contributor checklist

- [X] Provided the tests for the changes.
- [X] Gave a clear one-line description in the PR (that the maintainers can add to CHANGELOG.md on release).
- [x] Assign the PR to an existing or new milestone for the target version (following [Semantic Versioning](https://blog.versioneye.com/2014/01/16/semantic-versioning/)).

Patch:
diff --git a/piptools/writer.py b/piptools/writer.py
index f54b58f..47cfbbc 100644
--- a/piptools/writer.py
+++ b/piptools/writer.py
@@ -189,17 +189,18 @@ class OutputWriter(object):
                 yield MESSAGE_UNSAFE_PACKAGES
 
             for ireq in unsafe_requirements:
-                req = self._format_requirement(
-                    ireq,
-                    reverse_dependencies,
-                    primary_packages,
-                    marker=markers.get(key_from_ireq(ireq)),
-                    hashes=hashes,
-                )
+                ireq_key = key_from_ireq(ireq)
                 if not self.allow_unsafe:
-                    yield comment("# {}".format(req))
+                    yield comment("# {}".format(ireq_key))
                 else:
-                    yield req
+                    line = self._format_requirement(
+                        ireq,
+                        reverse_dependencies,
+                        primary_packages,
+                        marker=markers.get(ireq_key),
+                        hashes=hashes,
+                    )
+                    yield line
 
         # Yield even when there's no real content, so that blank files are written
         if not yielded:


Test patch:
diff --git a/tests/test_cli_compile.py b/tests/test_cli_compile.py
index 60624b9..e454565 100644
--- a/tests/test_cli_compile.py
+++ b/tests/test_cli_compile.py
@@ -691,8 +691,7 @@ def test_annotate_option(pip_conf, runner, option, expected):
 
 
 @pytest.mark.parametrize(
-    "option, expected",
-    [("--allow-unsafe", "\nsetuptools=="), (None, "\n# setuptools==")],
+    "option, expected", [("--allow-unsafe", "\nsetuptools=="), (None, "\n# setuptools")]
 )
 @pytest.mark.network
 def test_allow_unsafe_option(runner, option, expected):
diff --git a/tests/test_writer.py b/tests/test_writer.py
index 167226a..f64e638 100644
--- a/tests/test_writer.py
+++ b/tests/test_writer.py
@@ -129,7 +129,7 @@ def test_iter_lines__unsafe_dependencies(writer, from_line, allow_unsafe):
         "test==1.2",
         "",
         MESSAGE_UNSAFE_PACKAGES,
-        "setuptools==1.10.0" if allow_unsafe else comment("# setuptools==1.10.0"),
+        "setuptools==1.10.0" if allow_unsafe else comment("# setuptools"),
     )
     assert tuple(lines) == expected_lines
 
@@ -147,7 +147,7 @@ def test_iter_lines__unsafe_with_hashes(writer, from_line):
         "test==1.2 \\\n    --hash=FAKEHASH",
         "",
         MESSAGE_UNSAFE_PACKAGES_UNPINNED,
-        comment("# setuptools==1.10.0"),
+        comment("# setuptools"),
     )
     assert tuple(lines) == expected_lines