diff --git a/news/2926.bugfix b/news/2926.bugfix new file mode 100644 index 00000000000..2ed00a0973c --- /dev/null +++ b/news/2926.bugfix @@ -0,0 +1 @@ +Skip local directory by default for 'pip freeze' or when invoked as 'python -m pip freeze' diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py index 5ad0544be79..0be67aaa1a8 100644 --- a/src/pip/_internal/utils/misc.py +++ b/src/pip/_internal/utils/misc.py @@ -11,12 +11,12 @@ import io import logging import os -import posixpath import shutil import stat import sys from collections import deque +import posixpath from pip._vendor import pkg_resources # NOTE: retrying is not annotated in typeshed as on 2017-07-17, which is # why we ignore the type on this import. @@ -397,6 +397,14 @@ def dist_is_editable(dist): return False +def dist_in_curr_dir(dist): + ''' + Return True if given Distribution is installed in the current directory. + ''' + norm_path = normalize_path(dist_location(dist)) + return norm_path.startswith(normalize_path(os.getcwd())) + + def get_installed_distributions( local_only=True, # type: bool skip=stdlib_pkgs, # type: Container[str] @@ -456,12 +464,15 @@ def editables_only_test(d): def user_test(d): return True + curr_dir_test = dist_in_curr_dir + return [d for d in working_set if local_test(d) and d.key not in skip and editable_test(d) and editables_only_test(d) and - user_test(d) + user_test(d) and + not curr_dir_test(d) ] diff --git a/tests/functional/test_freeze.py b/tests/functional/test_freeze.py index 3542e7754ef..d0c20a1f9fe 100644 --- a/tests/functional/test_freeze.py +++ b/tests/functional/test_freeze.py @@ -79,34 +79,36 @@ def test_freeze_with_pip(script): assert 'pip==' in result.stdout +def _fake_install(pkgname, dest): + egg_info_path = os.path.join( + dest, '{}-1.0-py{}.{}.egg-info'.format( + pkgname.replace('-', '_'), + sys.version_info[0], + sys.version_info[1] + ) + ) + with open(egg_info_path, 'w') as egg_info_file: + egg_info_file.write(textwrap.dedent("""\ + Metadata-Version: 1.0 + Name: {} + Version: 1.0 + """.format(pkgname) + )) + return egg_info_path + + def test_freeze_with_invalid_names(script): """ Test that invalid names produce warnings and are passed over gracefully. """ - def fake_install(pkgname, dest): - egg_info_path = os.path.join( - dest, '{}-1.0-py{}.{}.egg-info'.format( - pkgname.replace('-', '_'), - sys.version_info[0], - sys.version_info[1] - ) - ) - with open(egg_info_path, 'w') as egg_info_file: - egg_info_file.write(textwrap.dedent("""\ - Metadata-Version: 1.0 - Name: {} - Version: 1.0 - """.format(pkgname) - )) - valid_pkgnames = ('middle-dash', 'middle_underscore', 'middle.dot') invalid_pkgnames = ( '-leadingdash', '_leadingunderscore', '.leadingdot', 'trailingdash-', 'trailingunderscore_', 'trailingdot.' ) for pkgname in valid_pkgnames + invalid_pkgnames: - fake_install(pkgname, script.site_packages_path) + _fake_install(pkgname, script.site_packages_path) result = script.pip('freeze', expect_stderr=True) for pkgname in valid_pkgnames: _check_output( @@ -129,6 +131,18 @@ def fake_install(pkgname, dest): _check_output(result.stderr, expected) +def test_freeze_skip_curr_dir(script): + """ + Test that an .egginfo is skipped when present in current directory + """ + + curr_dir = os.getcwd() + egg_info_path = _fake_install("foo-package", curr_dir) + result = script.pip("freeze") + os.remove(egg_info_path) + assert "foo-package==" not in result.stdout + + @pytest.mark.git def test_freeze_editable_not_vcs(script, tmpdir): """ diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index fa042519ee9..80a6fe8191a 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -188,6 +188,7 @@ def test_noegglink_in_sitepkgs_venv_global(self): assert egg_link_path(self.mock_dist) is None +@patch('pip._internal.utils.misc.dist_in_curr_dir') @patch('pip._internal.utils.misc.dist_in_usersite') @patch('pip._internal.utils.misc.dist_is_local') @patch('pip._internal.utils.misc.dist_is_editable') @@ -199,6 +200,7 @@ class Tests_get_installed_distributions: Mock(test_name="editable"), Mock(test_name="normal"), Mock(test_name="user"), + Mock(test_name="curr_dir") ] workingset_stdlib = [ @@ -221,13 +223,18 @@ def dist_is_local(self, dist): def dist_in_usersite(self, dist): return dist.test_name == "user" + def dist_in_curr_dir(self, dist): + return dist.test_name == "curr_dir" + @patch('pip._vendor.pkg_resources.working_set', workingset) def test_editables_only(self, mock_dist_is_editable, mock_dist_is_local, - mock_dist_in_usersite): + mock_dist_in_usersite, + mock_dist_in_curr_dir): mock_dist_is_editable.side_effect = self.dist_is_editable mock_dist_is_local.side_effect = self.dist_is_local mock_dist_in_usersite.side_effect = self.dist_in_usersite + mock_dist_in_curr_dir.side_effect = self.dist_in_curr_dir dists = get_installed_distributions(editables_only=True) assert len(dists) == 1, dists assert dists[0].test_name == "editable" @@ -235,10 +242,12 @@ def test_editables_only(self, mock_dist_is_editable, @patch('pip._vendor.pkg_resources.working_set', workingset) def test_exclude_editables(self, mock_dist_is_editable, mock_dist_is_local, - mock_dist_in_usersite): + mock_dist_in_usersite, + mock_dist_in_curr_dir): mock_dist_is_editable.side_effect = self.dist_is_editable mock_dist_is_local.side_effect = self.dist_is_local mock_dist_in_usersite.side_effect = self.dist_in_usersite + mock_dist_in_curr_dir.side_effect = self.dist_in_curr_dir dists = get_installed_distributions(include_editables=False) assert len(dists) == 1 assert dists[0].test_name == "normal" @@ -246,20 +255,24 @@ def test_exclude_editables(self, mock_dist_is_editable, @patch('pip._vendor.pkg_resources.working_set', workingset) def test_include_globals(self, mock_dist_is_editable, mock_dist_is_local, - mock_dist_in_usersite): + mock_dist_in_usersite, + mock_dist_in_curr_dir): mock_dist_is_editable.side_effect = self.dist_is_editable mock_dist_is_local.side_effect = self.dist_is_local mock_dist_in_usersite.side_effect = self.dist_in_usersite + mock_dist_in_curr_dir.side_effect = self.dist_in_curr_dir dists = get_installed_distributions(local_only=False) assert len(dists) == 4 @patch('pip._vendor.pkg_resources.working_set', workingset) def test_user_only(self, mock_dist_is_editable, mock_dist_is_local, - mock_dist_in_usersite): + mock_dist_in_usersite, + mock_dist_in_curr_dir): mock_dist_is_editable.side_effect = self.dist_is_editable mock_dist_is_local.side_effect = self.dist_is_local mock_dist_in_usersite.side_effect = self.dist_in_usersite + mock_dist_in_curr_dir.side_effect = self.dist_in_curr_dir dists = get_installed_distributions(local_only=False, user_only=True) assert len(dists) == 1 @@ -268,20 +281,24 @@ def test_user_only(self, mock_dist_is_editable, @patch('pip._vendor.pkg_resources.working_set', workingset_stdlib) def test_gte_py27_excludes(self, mock_dist_is_editable, mock_dist_is_local, - mock_dist_in_usersite): + mock_dist_in_usersite, + mock_dist_in_curr_dir): mock_dist_is_editable.side_effect = self.dist_is_editable mock_dist_is_local.side_effect = self.dist_is_local mock_dist_in_usersite.side_effect = self.dist_in_usersite + mock_dist_in_curr_dir.side_effect = self.dist_in_curr_dir dists = get_installed_distributions() assert len(dists) == 0 @patch('pip._vendor.pkg_resources.working_set', workingset_freeze) def test_freeze_excludes(self, mock_dist_is_editable, mock_dist_is_local, - mock_dist_in_usersite): + mock_dist_in_usersite, + mock_dist_in_curr_dir): mock_dist_is_editable.side_effect = self.dist_is_editable mock_dist_is_local.side_effect = self.dist_is_local mock_dist_in_usersite.side_effect = self.dist_in_usersite + mock_dist_in_curr_dir.side_effect = self.dist_in_curr_dir dists = get_installed_distributions( skip=('setuptools', 'pip', 'distribute')) assert len(dists) == 0