Skip to content
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

Add support for pip freeze to use multiple --requirement files #3703

Merged
merged 1 commit into from May 26, 2016
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 6 additions & 5 deletions pip/commands/freeze.py
Expand Up @@ -29,12 +29,13 @@ def __init__(self, *args, **kw):

self.cmd_opts.add_option(
'-r', '--requirement',
dest='requirement',
action='store',
default=None,
dest='requirements',
action='append',
default=[],
metavar='file',
help="Use the order in the given requirements file and its "
"comments when generating output.")
"comments when generating output. This option can be "
"used multiple times.")
self.cmd_opts.add_option(
'-f', '--find-links',
dest='find_links',
Expand Down Expand Up @@ -73,7 +74,7 @@ def run(self, options, args):
skip.update(DEV_PKGS)

freeze_kwargs = dict(
requirement=options.requirement,
requirement=options.requirements,
find_links=options.find_links,
local_only=options.local,
user_only=options.user,
Expand Down
111 changes: 60 additions & 51 deletions pip/operations/freeze.py
Expand Up @@ -49,60 +49,69 @@ def freeze(
installations[req.name] = req

if requirement:
with open(requirement) as req_file:
for line in req_file:
if (not line.strip() or
line.strip().startswith('#') or
(skip_match and skip_match(line)) or
line.startswith((
'-r', '--requirement',
'-Z', '--always-unzip',
'-f', '--find-links',
'-i', '--index-url',
'--pre',
'--trusted-host',
'--process-dependency-links',
'--extra-index-url'))):
yield line.rstrip()
continue
# the options that don't get turned into an InstallRequirement
# should only be emitted once, even if the same option is in multiple
# 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()
for req_file_path in requirement:
with open(req_file_path) as req_file:
for line in req_file:
if (not line.strip() or
line.strip().startswith('#') or
(skip_match and skip_match(line)) or
line.startswith((
'-r', '--requirement',
'-Z', '--always-unzip',
'-f', '--find-links',
'-i', '--index-url',
'--pre',
'--trusted-host',
'--process-dependency-links',
'--extra-index-url'))):
line = line.rstrip()
if line not in emitted_options:
emitted_options.add(line)
yield line
continue

if line.startswith('-e') or line.startswith('--editable'):
if line.startswith('-e'):
line = line[2:].strip()
if line.startswith('-e') or line.startswith('--editable'):
if line.startswith('-e'):
line = line[2:].strip()
else:
line = line[len('--editable'):].strip().lstrip('=')
line_req = InstallRequirement.from_editable(
line,
default_vcs=default_vcs,
isolated=isolated,
wheel_cache=wheel_cache,
)
else:
line = line[len('--editable'):].strip().lstrip('=')
line_req = InstallRequirement.from_editable(
line,
default_vcs=default_vcs,
isolated=isolated,
wheel_cache=wheel_cache,
)
else:
line_req = InstallRequirement.from_line(
line,
isolated=isolated,
wheel_cache=wheel_cache,
)
line_req = InstallRequirement.from_line(
line,
isolated=isolated,
wheel_cache=wheel_cache,
)

if not line_req.name:
logger.info(
"Skipping line because it's not clear what it "
"would install: %s",
line.strip(),
)
logger.info(
" (add #egg=PackageName to the URL to avoid"
" this warning)"
)
elif line_req.name not in installations:
logger.warning(
"Requirement file contains %s, but that package is"
" not installed",
line.strip(),
)
else:
yield str(installations[line_req.name]).rstrip()
del installations[line_req.name]
if not line_req.name:
logger.info(
"Skipping line in requirement file [%s] because "
"it's not clear what it would install: %s",
req_file_path, line.strip(),
)
logger.info(
" (add #egg=PackageName to the URL to avoid"
" 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, line.strip(),
)
else:
yield str(installations[line_req.name]).rstrip()
del installations[line_req.name]

yield(
'## The following requirements were added by '
Expand Down
100 changes: 78 additions & 22 deletions tests/functional/test_freeze.py
@@ -1,4 +1,5 @@
import sys
import os
import re
import textwrap
import pytest
Expand Down Expand Up @@ -298,46 +299,101 @@ def test_freeze_with_local_option(script):
_check_output(result.stdout, expected)


# used by the test_freeze_with_requirement_* tests below
_freeze_req_opts = textwrap.dedent("""\
# Unchanged requirements below this line
-r ignore.txt
--requirement ignore.txt
-Z ignore
--always-unzip ignore
-f http://ignore
-i http://ignore
--pre
--trusted-host url
--process-dependency-links
--extra-index-url http://ignore
--find-links http://ignore
--index-url http://ignore
""")


def test_freeze_with_requirement_option(script):
"""
Test that new requirements are created correctly with --requirement hints

"""
ignores = textwrap.dedent("""\
# Unchanged requirements below this line
-r ignore.txt
--requirement ignore.txt
-Z ignore
--always-unzip ignore
-f http://ignore
-i http://ignore
--pre
--trusted-host url
--process-dependency-links
--extra-index-url http://ignore
--find-links http://ignore
--index-url http://ignore
""")

script.scratch_path.join("hint.txt").write(textwrap.dedent("""\
INITools==0.1
NoExist==4.2
simple==3.0; python_version > '1.0'
""") + ignores)
""") + _freeze_req_opts)
result = script.pip_install_local('initools==0.2')
result = script.pip_install_local('simple')
result = script.pip(
'freeze', '--requirement', 'hint.txt',
expect_stderr=True,
)
expected = """\
INITools==0.2
simple==3.0
""" + ignores + "## The following requirements were added by pip freeze:..."
expected = textwrap.dedent("""\
INITools==0.2
simple==3.0
""")
expected += _freeze_req_opts
expected += "## The following requirements were added by pip freeze:..."
_check_output(result.stdout, expected)
assert (
"Requirement file contains NoExist==4.2, but that package is not "
"installed"
"Requirement file [hint.txt] contains NoExist==4.2, but that package "
"is not installed"
) in result.stderr


def test_freeze_with_requirement_option_multiple(script):
"""
Test that new requirements are created correctly with multiple
--requirement hints

"""
script.scratch_path.join('hint1.txt').write(textwrap.dedent("""\
INITools==0.1
NoExist==4.2
simple==3.0; python_version > '1.0'
""") + _freeze_req_opts)
script.scratch_path.join('hint2.txt').write(textwrap.dedent("""\
NoExist2==2.0
simple2==1.0
""") + _freeze_req_opts)
result = script.pip_install_local('initools==0.2')
result = script.pip_install_local('simple')
result = script.pip_install_local('simple2==1.0')
result = script.pip_install_local('meta')
result = script.pip(
'freeze', '--requirement', 'hint1.txt', '--requirement', 'hint2.txt',
expect_stderr=True,
)
expected = textwrap.dedent("""\
INITools==0.2
simple==1.0
""")
expected += _freeze_req_opts
expected += textwrap.dedent("""\
simple2==1.0
""")
expected += "## The following requirements were added by pip freeze:"
expected += os.linesep + textwrap.dedent("""\
...meta==1.0...
""")
_check_output(result.stdout, expected)
assert (
"Requirement file [hint1.txt] contains NoExist==4.2, but that "
"package is not installed"
) in result.stderr
assert (
"Requirement file [hint2.txt] contains NoExist2==2.0, but that "
"package is not installed"
) in result.stderr
# any options like '--index-url http://ignore' should only be emitted once
# even if they are listed in multiple requirements files
assert result.stdout.count("--index-url http://ignore") == 1


def test_freeze_user(script, virtualenv):
Expand Down