Skip to content

Fix #4292: Fix freeze when package occurs more than once #4293

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 31, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions news/4293.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Fix for an incorrect ``freeze`` warning message due to a package being
included in multiple requirements files that were passed to ``freeze``.
Instead of warning incorrectly that the package is not installed, pip
now warns that the package was declared multiple times and lists the
name of each requirements file that contains the package in question.
30 changes: 24 additions & 6 deletions src/pip/_internal/operations/freeze.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from __future__ import absolute_import

import collections
import logging
import os
import re
import warnings

from pip._vendor import pkg_resources
from pip._vendor import pkg_resources, six
from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.pkg_resources import RequirementParseError

Expand Down Expand Up @@ -70,6 +71,9 @@ def freeze(
# requirements files, so we need to keep track of what has been emitted
# so that we don't emit it again if it's seen again
emitted_options = set()
# keep track of which files a requirement is in so that we can
# give an accurate warning if a requirement appears multiple times.
req_files = collections.defaultdict(list)
for req_file_path in requirement:
with open(req_file_path) as req_file:
for line in req_file:
Expand Down Expand Up @@ -119,14 +123,28 @@ def freeze(
" this warning)"
)
elif line_req.name not in installations:
logger.warning(
"Requirement file [%s] contains %s, but that "
"package is not installed",
req_file_path, COMMENT_RE.sub('', line).strip(),
)
# either it's not installed, or it is installed
# but has been processed already
if not req_files[line_req.name]:
logger.warning(
"Requirement file [%s] contains %s, but that "
"package is not installed",
req_file_path,
COMMENT_RE.sub('', line).strip(),
)
else:
req_files[line_req.name].append(req_file_path)
else:
yield str(installations[line_req.name]).rstrip()
del installations[line_req.name]
req_files[line_req.name].append(req_file_path)

# Warn about requirements that were included multiple times (in a
# single requirements file or in different requirements files).
for name, files in six.iteritems(req_files):
if len(files) > 1:
logger.warning("Requirement %s included multiple times [%s]",
name, ', '.join(sorted(set(files))))

yield(
'## The following requirements were added by '
Expand Down
72 changes: 72 additions & 0 deletions tests/functional/test_freeze.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,78 @@ def test_freeze_with_requirement_option_multiple(script):
assert result.stdout.count("--index-url http://ignore") == 1


def test_freeze_with_requirement_option_package_repeated_one_file(script):
"""
Test freezing with single requirements file that contains a package
multiple times
"""
script.scratch_path.join('hint1.txt').write(textwrap.dedent("""\
simple2
simple2
NoExist
""") + _freeze_req_opts)
result = script.pip_install_local('simple2==1.0')
result = script.pip_install_local('meta')
result = script.pip(
'freeze', '--requirement', 'hint1.txt',
expect_stderr=True,
)
expected_out = textwrap.dedent("""\
simple2==1.0
""")
expected_out += _freeze_req_opts
expected_out += "## The following requirements were added by pip freeze:"
expected_out += os.linesep + textwrap.dedent("""\
...meta==1.0...
""")
_check_output(result.stdout, expected_out)
err1 = ("Requirement file [hint1.txt] contains NoExist, "
"but that package is not installed\n")
err2 = "Requirement simple2 included multiple times [hint1.txt]\n"
assert err1 in result.stderr
assert err2 in result.stderr
# there shouldn't be any other 'is not installed' warnings
assert result.stderr.count('is not installed') == 1


def test_freeze_with_requirement_option_package_repeated_multi_file(script):
"""
Test freezing with multiple requirements file that contain a package
"""
script.scratch_path.join('hint1.txt').write(textwrap.dedent("""\
simple
""") + _freeze_req_opts)
script.scratch_path.join('hint2.txt').write(textwrap.dedent("""\
simple
NoExist
""") + _freeze_req_opts)
result = script.pip_install_local('simple==1.0')
result = script.pip_install_local('meta')
result = script.pip(
'freeze', '--requirement', 'hint1.txt',
'--requirement', 'hint2.txt',
expect_stderr=True,
)
expected_out = textwrap.dedent("""\
simple==1.0
""")
expected_out += _freeze_req_opts
expected_out += "## The following requirements were added by pip freeze:"
expected_out += os.linesep + textwrap.dedent("""\
...meta==1.0...
""")
_check_output(result.stdout, expected_out)

err1 = ("Requirement file [hint2.txt] contains NoExist, but that "
"package is not installed\n")
err2 = ("Requirement simple included multiple times "
"[hint1.txt, hint2.txt]\n")
assert err1 in result.stderr
assert err2 in result.stderr
# there shouldn't be any other 'is not installed' warnings
assert result.stderr.count('is not installed') == 1


@pytest.mark.network
def test_freeze_user(script, virtualenv, data):
"""
Expand Down