Skip to content

Feature/freeze subset recursively (rebase) #2234

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

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
23 changes: 23 additions & 0 deletions docs/reference/pip_freeze.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,26 @@ Examples

$ env1/bin/pip freeze > requirements.txt
$ env2/bin/pip install -r requirements.txt

3) Generate output for a single package only.

::

$ pip freeze Jinja2
Jinja2==2.7.2

4) Generate output for a subset of packages only.

::

$ pip freeze Jinja2 docutils
docutils==0.11
Jinja2==2.7.2

5) Generate output for a subset of packages and their (recursive) dependencies.

::

$ pip freeze --recursive Jinja2
Jinja2==2.7.2
MarkupSafe==0.19
1 change: 1 addition & 0 deletions docs/reference/pip_show.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ Examples
Version: 1.1.3
Location: /my/env/lib/pythonx.x/site-packages
Requires: Pygments, Jinja2, docutils
Requires recursive: docutils, Jinja2, MarkupSafe, Pygments
10 changes: 9 additions & 1 deletion pip/commands/freeze.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class FreezeCommand(Command):
"""
name = 'freeze'
usage = """
%prog [options]"""
%prog [options] [PACKAGE PACKAGE...]"""
summary = 'Output installed packages in requirements format.'
log_stream = "ext://sys.stderr"

Expand Down Expand Up @@ -50,11 +50,19 @@ def __init__(self, *args, **kw):
action='store_true',
default=False,
help='Only output packages installed in user-site.')
self.cmd_opts.add_option(
'--recursive',
dest='recursive',
action='store_true',
default=False,
help="Freeze packages and dependencies, recursively.")

self.parser.insert_option_group(0, self.cmd_opts)

def run(self, options, args):
freeze_kwargs = dict(
only_dists=args,
recursive=options.recursive,
requirement=options.requirement,
find_links=options.find_links,
local_only=options.local,
Expand Down
5 changes: 5 additions & 0 deletions pip/commands/show.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from pip.basecommand import Command
from pip.status_codes import SUCCESS, ERROR
from pip.utils import get_recursive_dependencies
from pip._vendor import pkg_resources


Expand Down Expand Up @@ -116,6 +117,10 @@ def print_results(distributions, list_all_files):
logger.info("License: %s" % dist.get('license'))
logger.info("Location: %s" % dist['location'])
logger.info("Requires: %s" % ', '.join(dist['requires']))
logger.info("Requires recursive: %s" % ', '.join(
sorted(
get_recursive_dependencies([dist['name']]),
key=lambda name: name.lower())))
if list_all_files:
logger.info("Files:")
if dist['files'] is not None:
Expand Down
26 changes: 19 additions & 7 deletions pip/operations/freeze.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import pip
from pip.compat import stdlib_pkgs
from pip.req import InstallRequirement
from pip.utils import get_installed_distributions
from pip.utils import get_installed_distributions, get_recursive_dependencies
from pip._vendor import pkg_resources


Expand All @@ -17,6 +17,8 @@


def freeze(
only_dists=None,
recursive=False,
requirement=None,
find_links=None, local_only=None, user_only=None, skip_regex=None,
find_tags=False,
Expand All @@ -41,15 +43,25 @@ def freeze(
for link in find_links:
yield '-f %s' % link
installations = {}

if only_dists:
# Freeze only a subset of installed packages.
if recursive: # Freeze dependencies, recursively.
only_dists.extend(get_recursive_dependencies(only_dists))
only_dists = [pkg_resources.safe_extra(name)
for name in only_dists]

for dist in get_installed_distributions(local_only=local_only,
skip=freeze_excludes,
user_only=user_only):
req = pip.FrozenRequirement.from_dist(
dist,
dependency_links,
find_tags=find_tags,
)
installations[req.name] = req
safe_name = pkg_resources.safe_extra(dist.project_name)
if not only_dists or safe_name in only_dists:
req = pip.FrozenRequirement.from_dist(
dist,
dependency_links,
find_tags=find_tags,
)
installations[req.name] = req

if requirement:
with open(requirement) as req_file:
Expand Down
17 changes: 17 additions & 0 deletions pip/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,23 @@ def get_installed_distributions(local_only=True,
]


def get_recursive_dependencies(names):
"""Return set of dependencies of dists in ``names``, recursively."""
dependencies = set()
installed = dict(
[(p.project_name.lower(), p) for p in pkg_resources.working_set])
query_names = [name.lower() for name in names]
for pkg in query_names:
dist = installed.get(pkg)
if dist:
for dep in dist.requires():
dep_dist = pkg_resources.get_distribution(dep.project_name)
dependencies.add(dep_dist.project_name)
if dependencies:
dependencies.update(get_recursive_dependencies(dependencies))
return dependencies


def egg_link_path(dist):
"""
Return the path for the .egg-link file if it exists, otherwise, None.
Expand Down
56 changes: 56 additions & 0 deletions tests/functional/test_freeze.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,3 +392,59 @@ def test_freeze_user(script, virtualenv):
<BLANKLINE>""")
_check_output(result, expected)
assert 'simple2' not in result.stdout


def test_freeze_distributions(script):
"""Test that output is filtered if arguments are specified."""
# Install some distributions.
script.scratch_path.join("initools-req.txt").write(textwrap.dedent("""\
simple==2.0
requiresupper==1.0
"""))
script.pip_install_local(
'-r', script.scratch_path / 'initools-req.txt',
)
# Freeze a single package.
result = script.pip('freeze', 'simple', expect_stderr=True)
expected = textwrap.dedent("""\
Script result: pip freeze simple
-- stdout: --------------------
simple==2.0
<BLANKLINE>""")
_check_output(result, expected)
# Freeze a single package does not show dependencies by default.
result = script.pip('freeze', 'requiresupper', expect_stderr=True)
expected = textwrap.dedent("""\
Script result: pip freeze requiresupper
-- stdout: --------------------
requiresupper==1.0
<BLANKLINE>""")
_check_output(result, expected)
# Freeze multiple packages.
result = script.pip('freeze', 'simple', 'requiresupper',
expect_stderr=True)
expected = textwrap.dedent("""\
Script result: pip freeze simple requiresupper
-- stdout: --------------------
requiresupper==1.0
simple==2.0
<BLANKLINE>""")
_check_output(result, expected)
# Freeze is case insensitive.
result = script.pip('freeze', 'SiMPLe', expect_stderr=True)
expected = textwrap.dedent("""\
Script result: pip freeze SiMPLe
-- stdout: --------------------
simple==2.0
<BLANKLINE>""")
_check_output(result, expected)
# Freeze is optionally recursive.
result = script.pip('freeze', '--recursive', 'requiresupper',
expect_stderr=True)
expected = textwrap.dedent("""\
Script result: pip freeze --recursive requiresupper
-- stdout: --------------------
requiresupper==1.0
Upper==2.0
<BLANKLINE>""")
_check_output(result, expected)
6 changes: 4 additions & 2 deletions tests/functional/test_show.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ def test_show(script):
"""
result = script.pip('show', 'pip')
lines = result.stdout.split('\n')
assert len(lines) == 17
assert len(lines) == 18
assert lines[0] == '---', lines[0]
assert 'Name: pip' in lines
assert 'Version: %s' % __version__ in lines
assert any(line.startswith('Location: ') for line in lines)
assert 'Requires: ' in lines
assert 'Requires recursive: ' in lines


def test_show_with_files_not_found(script, data):
Expand All @@ -27,12 +28,13 @@ def test_show_with_files_not_found(script, data):
script.pip('install', '-e', editable)
result = script.pip('show', '-f', 'SetupPyUTF8')
lines = result.stdout.split('\n')
assert len(lines) == 14
assert len(lines) == 15
assert lines[0] == '---', lines[0]
assert 'Name: SetupPyUTF8' in lines
assert 'Version: 0.0.0' in lines
assert any(line.startswith('Location: ') for line in lines)
assert 'Requires: ' in lines
assert 'Requires recursive: ' in lines
assert 'Files:' in lines
assert 'Cannot locate installed-files.txt' in lines

Expand Down