From 15bf4082955dd0bf0bed915a35f6b6f5b9e0e2d9 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 19 Jan 2012 13:09:54 -0500 Subject: [PATCH 001/132] Only warn for mis-versioned packages. Allows Package==dev in requirements. --- pip/req.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/pip/req.py b/pip/req.py index 5c479270de8..33e1b72a149 100644 --- a/pip/req.py +++ b/pip/req.py @@ -352,18 +352,9 @@ def installed_version(self): def assert_source_matches_version(self): assert self.source_dir - if self.comes_from is None: - # We don't check the versions of things explicitly installed. - # This makes, e.g., "pip Package==dev" possible - return version = self.installed_version if version not in self.req: - logger.fatal( - 'Source in %s has the version %s, which does not match the requirement %s' - % (display_path(self.source_dir), version, self)) - raise InstallationError( - 'Source in %s has version %s that conflicts with %s' - % (display_path(self.source_dir), version, self)) + logger.warn('Requested %s, but installing version %s' % (self, self.installed_version)) else: logger.debug('Source in %s has version %s, which satisfies requirement %s' % (display_path(self.source_dir), version, self)) @@ -445,7 +436,7 @@ def uninstall(self, auto_confirm=False): paths_to_remove.add(path) paths_to_remove.add(path + '.py') paths_to_remove.add(path + '.pyc') - + elif dist.location.endswith(easy_install_egg): # package installed by easy_install paths_to_remove.add(dist.location) From 390917d3a5e24875c0e09de9a53ca7bb58e71e6f Mon Sep 17 00:00:00 2001 From: Ben Rosser Date: Mon, 21 May 2012 22:10:50 -0400 Subject: [PATCH 002/132] Moved configuration and logging to .config for XDG compliance --- pip/locations.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pip/locations.py b/pip/locations.py index 25e4cfc20ab..50c516647a6 100644 --- a/pip/locations.py +++ b/pip/locations.py @@ -47,6 +47,12 @@ def running_under_virtualenv(): default_log_file = os.path.join(default_storage_dir, 'pip.log') else: bin_py = os.path.join(sys.prefix, 'bin') + + #Use ~/.config/pip instead of ~/.pip- cleaner home folder + #On some systems, we may have to create this, on others it probably exists + if not os.path.exists(os.path.join(user_dir, '.config')): + os.mkdir(os.path.join(user_dir, '.config')) + default_storage_dir = os.path.join(user_dir, '.pip') default_config_file = os.path.join(default_storage_dir, 'pip.conf') default_log_file = os.path.join(default_storage_dir, 'pip.log') From 5442218c6d98ff1fc8bc9269ee1f20e553103636 Mon Sep 17 00:00:00 2001 From: Ben Rosser Date: Mon, 21 May 2012 22:14:25 -0400 Subject: [PATCH 003/132] Fixed commit #1 to actually work --- pip/locations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pip/locations.py b/pip/locations.py index 50c516647a6..31eeadbde09 100644 --- a/pip/locations.py +++ b/pip/locations.py @@ -52,8 +52,8 @@ def running_under_virtualenv(): #On some systems, we may have to create this, on others it probably exists if not os.path.exists(os.path.join(user_dir, '.config')): os.mkdir(os.path.join(user_dir, '.config')) + default_storage_dir = os.path.join(user_dir, '.config', 'pip') - default_storage_dir = os.path.join(user_dir, '.pip') default_config_file = os.path.join(default_storage_dir, 'pip.conf') default_log_file = os.path.join(default_storage_dir, 'pip.log') # Forcing to use /usr/local/bin for standard Mac OS X framework installs From 882227a0e976c4bcbd67d11bcc2d5e2cba57da97 Mon Sep 17 00:00:00 2001 From: Ben Rosser Date: Mon, 21 May 2012 22:37:46 -0400 Subject: [PATCH 004/132] Switched to use XDG_CONFIG_DIR environment variable if possible (default to ~/.config if not) --- pip/locations.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pip/locations.py b/pip/locations.py index 31eeadbde09..4abe8c0a029 100644 --- a/pip/locations.py +++ b/pip/locations.py @@ -13,7 +13,6 @@ def running_under_virtualenv(): """ return hasattr(sys, 'real_prefix') - if running_under_virtualenv(): ## FIXME: is build/ a good name? build_prefix = os.path.join(sys.prefix, 'build') @@ -22,8 +21,8 @@ def running_under_virtualenv(): #Use tempfile to create a temporary folder build_prefix = tempfile.mkdtemp('-build', 'pip-') src_prefix = tempfile.mkdtemp('-src', 'pip-') - #This is a terrible hack- since pip relies on this directory not being created yet - #We will delete it now, and have pip recreate it later + # This is a terrible hack- since pip relies on this directory not being created yet + # We will delete it now, and have pip recreate it later os.rmdir(build_prefix) os.rmdir(src_prefix) @@ -47,12 +46,14 @@ def running_under_virtualenv(): default_log_file = os.path.join(default_storage_dir, 'pip.log') else: bin_py = os.path.join(sys.prefix, 'bin') - - #Use ~/.config/pip instead of ~/.pip- cleaner home folder - #On some systems, we may have to create this, on others it probably exists - if not os.path.exists(os.path.join(user_dir, '.config')): - os.mkdir(os.path.join(user_dir, '.config')) - default_storage_dir = os.path.join(user_dir, '.config', 'pip') + + # Use XDG_CONFIG_DIR instead of the ~/.pip + # On some systems, we may have to create this, on others it probably exists + xdg_dir = os.path.join(user_dir, '.config') + xdg_dir = os.environ.get('XDG_CONFIG_DIR', xdg_dir) + if not os.path.exists(xdg_dir): + os.mkdir(xdg_dir) + default_storage_dir = os.path.join(xdg_dir, 'pip') default_config_file = os.path.join(default_storage_dir, 'pip.conf') default_log_file = os.path.join(default_storage_dir, 'pip.log') From 3a23763dfe489faa70faa3404f04aa6f6402d04e Mon Sep 17 00:00:00 2001 From: Ben Rosser Date: Wed, 23 May 2012 15:26:01 -0400 Subject: [PATCH 005/132] Added migration path from ~/.pip to ~/.config/pip --- pip/locations.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/pip/locations.py b/pip/locations.py index 4abe8c0a029..c675c0eac39 100644 --- a/pip/locations.py +++ b/pip/locations.py @@ -2,6 +2,7 @@ import sys import os +import shutil import tempfile from pip.backwardcompat import get_python_lib @@ -47,16 +48,27 @@ def running_under_virtualenv(): else: bin_py = os.path.join(sys.prefix, 'bin') - # Use XDG_CONFIG_DIR instead of the ~/.pip + # Use XDG_CONFIG_HOME instead of the ~/.pip # On some systems, we may have to create this, on others it probably exists xdg_dir = os.path.join(user_dir, '.config') - xdg_dir = os.environ.get('XDG_CONFIG_DIR', xdg_dir) + xdg_dir = os.environ.get('XDG_CONFIG_HOME', xdg_dir) if not os.path.exists(xdg_dir): os.mkdir(xdg_dir) default_storage_dir = os.path.join(xdg_dir, 'pip') - default_config_file = os.path.join(default_storage_dir, 'pip.conf') default_log_file = os.path.join(default_storage_dir, 'pip.log') + + # Migration path for users- move things from the old dir if it exists + # If the new dir exists and has no pip.conf and the old dir does, move it + # When these checks are finished, delete the old directory + old_storage_dir = os.path.join(user_dir, '.pip') + if os.path.exists(old_storage_dir): + if not os.path.exists(default_storage_dir): + shutil.copytree(old_storage_dir, default_storage_dir) + elif os.path.exists(os.path.join(old_storage_dir, 'pip.conf')) and not os.path.exists(default_config_file): + shutil.copy2(os.path.join(old_storage_dir, 'pip.conf'), default_config_file) + shutil.rmtree(old_storage_dir) + # Forcing to use /usr/local/bin for standard Mac OS X framework installs # Also log to ~/Library/Logs/ for use with the Console.app log viewer if sys.platform[:6] == 'darwin' and sys.prefix[:16] == '/System/Library/': From 51051964dfb8c7826165faff3c336a8e027c4620 Mon Sep 17 00:00:00 2001 From: Ben Rosser Date: Wed, 30 May 2012 11:43:55 -0400 Subject: [PATCH 006/132] Minor fixes --- pip/locations.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pip/locations.py b/pip/locations.py index c675c0eac39..63be35b04c6 100644 --- a/pip/locations.py +++ b/pip/locations.py @@ -19,13 +19,13 @@ def running_under_virtualenv(): build_prefix = os.path.join(sys.prefix, 'build') src_prefix = os.path.join(sys.prefix, 'src') else: - #Use tempfile to create a temporary folder - build_prefix = tempfile.mkdtemp('-build', 'pip-') - src_prefix = tempfile.mkdtemp('-src', 'pip-') - # This is a terrible hack- since pip relies on this directory not being created yet - # We will delete it now, and have pip recreate it later - os.rmdir(build_prefix) - os.rmdir(src_prefix) + try: + ## FIXME: this isn't a very good default + build_prefix = os.path.join(os.getcwd(), 'build') + src_prefix = os.path.join(os.getcwd(), 'src') + except OSError: + # In case the current working directory has been renamed or deleted + sys.exit("The folder you are executing pip from can no longer be found.") # under Mac OS X + virtualenv sys.prefix is not properly resolved # it is something like /path/to/python/bin/.. @@ -62,11 +62,12 @@ def running_under_virtualenv(): # If the new dir exists and has no pip.conf and the old dir does, move it # When these checks are finished, delete the old directory old_storage_dir = os.path.join(user_dir, '.pip') + old_config_file = os.path.join(old_storage_dir, 'pip.conf') if os.path.exists(old_storage_dir): if not os.path.exists(default_storage_dir): shutil.copytree(old_storage_dir, default_storage_dir) - elif os.path.exists(os.path.join(old_storage_dir, 'pip.conf')) and not os.path.exists(default_config_file): - shutil.copy2(os.path.join(old_storage_dir, 'pip.conf'), default_config_file) + elif os.path.exists(old_config_file) and not os.path.exists(default_config_file): + shutil.copy2(old_config_file, default_config_file) shutil.rmtree(old_storage_dir) # Forcing to use /usr/local/bin for standard Mac OS X framework installs From 5d9c7e56f3bfee2104f3c7b2c13f21503667b21a Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 19 Jun 2012 19:10:09 -0400 Subject: [PATCH 007/132] Re-add import shutil that got lost in merge --- pip/locations.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pip/locations.py b/pip/locations.py index 7cbc35d658f..daf86139cea 100644 --- a/pip/locations.py +++ b/pip/locations.py @@ -3,6 +3,7 @@ import sys import site import os +import shutil import tempfile from pip.backwardcompat import get_python_lib From 0202fc839e8073fd674368e9574c99468d800f3b Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 23 Jun 2012 22:15:08 -0400 Subject: [PATCH 008/132] Documented change and added author --- AUTHORS.txt | 1 + docs/configuration.txt | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/AUTHORS.txt b/AUTHORS.txt index 515eb20e2f1..c1ea6f49f5b 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -3,6 +3,7 @@ Alex Morega Alexandre Conrad Antti Kaihola Armin Ronacher +Ben Rosser Brian Rosner Carl Meyer Christian Oudard diff --git a/docs/configuration.txt b/docs/configuration.txt index 35d0a347f41..a1302b04d8f 100644 --- a/docs/configuration.txt +++ b/docs/configuration.txt @@ -84,7 +84,8 @@ Location The names and locations of the configuration files vary slightly across platforms. -On Unix and Mac OS X the configuration file is: :file:`$HOME/.pip/pip.conf` +On Unix and Mac OS X the configuration file is: +:file:`$HOME/.config/pip/pip.conf` And on Windows, the configuration file is: :file:`%HOME%\\pip\\pip.ini` From 4807b902443360d2db2034dc92fc93402c0a984b Mon Sep 17 00:00:00 2001 From: Clay McClure Date: Thu, 12 Jul 2012 16:51:13 -0400 Subject: [PATCH 009/132] Correct a comment --- pip/req.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pip/req.py b/pip/req.py index 6571b17bbb5..25aae3f7bab 100644 --- a/pip/req.py +++ b/pip/req.py @@ -895,7 +895,7 @@ def uninstall(self, auto_confirm=False): req.commit_uninstall() def locate_files(self): - ## FIXME: duplicates code from install_files; relevant code should + ## FIXME: duplicates code from prepare_files; relevant code should ## probably be factored out into a separate method unnamed = list(self.unnamed_requirements) reqs = list(self.requirements.values()) From 9f628623bcfb5f660b966a493cf1e2c47d30eb02 Mon Sep 17 00:00:00 2001 From: Clay McClure Date: Thu, 12 Jul 2012 17:51:17 -0400 Subject: [PATCH 010/132] Add `mock` requirement --- tests/test_pip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_pip.py b/tests/test_pip.py index 10f075b98b9..41758523c35 100644 --- a/tests/test_pip.py +++ b/tests/test_pip.py @@ -663,5 +663,5 @@ def main(): if __name__ == '__main__': - sys.stderr.write("Run pip's tests using nosetests. Requires virtualenv, ScriptTest, and nose.\n") + sys.stderr.write("Run pip's tests using nosetests. Requires virtualenv, ScriptTest, mock, and nose.\n") sys.exit(1) From 6f3774de2663b6a13d7a54f624d3645297d595af Mon Sep 17 00:00:00 2001 From: Clay McClure Date: Thu, 12 Jul 2012 18:59:44 -0400 Subject: [PATCH 011/132] Ensure that $TMPDIR exists before running tests This is normally done by TestFileEnvironment if the `start_clear` parameter is True, but TestPipEnvironment and FastTestPipEnvironment explicitly set it to False. --- tests/test_pip.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_pip.py b/tests/test_pip.py index 41758523c35..114ec05d1df 100644 --- a/tests/test_pip.py +++ b/tests/test_pip.py @@ -362,6 +362,10 @@ def __init__(self, environ=None, use_distribute=None, sitecustomize=None): if sitecustomize: self._add_to_sitecustomize(sitecustomize) + # Ensure that $TMPDIR exists + if self.temp_path and not os.path.exists(self.temp_path): + os.makedirs(self.temp_path) + def _ignore_file(self, fn): if fn.endswith('__pycache__') or fn.endswith(".pyc"): result = True @@ -510,6 +514,10 @@ def __init__(self, environ=None, sitecustomize=None): assert self.root_path.exists + # Ensure that $TMPDIR exists + if self.temp_path and not os.path.exists(self.temp_path): + os.makedirs(self.temp_path) + def __del__(self): pass # shutil.rmtree(str(self.root_path), ignore_errors=True) From ac0fe94d5ced669bb1e5b5c0645b0597bf96c895 Mon Sep 17 00:00:00 2001 From: Clay McClure Date: Thu, 12 Jul 2012 19:16:16 -0400 Subject: [PATCH 012/132] Fix unicode tests to work with new temp file assertions --- tests/test_unicode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_unicode.py b/tests/test_unicode.py index d9196e7505b..e419ac717b9 100644 --- a/tests/test_unicode.py +++ b/tests/test_unicode.py @@ -20,6 +20,6 @@ def test_install_package_that_emits_unicode(): env = reset_env() to_install = os.path.abspath(os.path.join(here, 'packages', 'BrokenEmitsUTF8')) - result = run_pip('install', to_install, expect_error=True) + result = run_pip('install', to_install, expect_error=True, expect_temp=True, quiet=True) assert '__main__.FakeError: this package designed to fail on install' in result.stdout assert 'UnicodeDecodeError' not in result.stdout From c82f6b2599485302935402476174b825e8baa2ee Mon Sep 17 00:00:00 2001 From: Clay McClure Date: Thu, 12 Jul 2012 19:00:24 -0400 Subject: [PATCH 013/132] Add a test to ensure that the temp build dir is always removed --- tests/test_cleanup.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/test_cleanup.py b/tests/test_cleanup.py index 15a0508333b..548aa75eca0 100644 --- a/tests/test_cleanup.py +++ b/tests/test_cleanup.py @@ -96,6 +96,26 @@ def test_no_install_and_download_should_not_leave_build_dir(): assert not os.path.exists(env.venv_path/'/build'), "build/ dir should be deleted" +def test_temp_build_dir_removed(): + """ + It should remove the temp build dir created by pip. + + TestFileEnvironment provides a mechanism to ensure that + no temporary files are left after a test script executes, + so we can take advantage of that here by simply running + `run_pip` twice (which uses TestFileEnvironment). + """ + env = reset_env() + + # Install a requirement into a fresh environment. The temporary build + # directory should be removed. + result = run_pip('install', 'https://bitbucket.org/ianb/initools/get/tip.zip') + + # Now install the requirement again, exercising a different code path, + # and ensure that the temporary build directory is still removed. + result = run_pip('install', 'https://bitbucket.org/ianb/initools/get/tip.zip') + + def test_download_should_not_delete_existing_build_dir(): """ It should not delete build/ if existing before run the command From 723500188da5cf93e6a836d881ba9ff8646d8957 Mon Sep 17 00:00:00 2001 From: Clay McClure Date: Thu, 12 Jul 2012 19:01:27 -0400 Subject: [PATCH 014/132] Remove temp build dir even if the req is not installed (fixes #420) --- pip/req.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pip/req.py b/pip/req.py index 25aae3f7bab..cc7d4043338 100644 --- a/pip/req.py +++ b/pip/req.py @@ -1003,7 +1003,9 @@ def prepare_files(self, finder, force_root_egg_info=False, bundle=False): ##occurs when the script attempts to unpack the ##build directory + # NB: This call can result in the creation of a temporary build directory location = req_to_install.build_location(self.build_dir, not self.is_download) + ## FIXME: is the existance of the checkout good enough to use it? I don't think so. unpack = True url = None @@ -1084,7 +1086,7 @@ def prepare_files(self, finder, force_root_egg_info=False, bundle=False): self.add_requirement(subreq) if req_to_install.name not in self.requirements: self.requirements[req_to_install.name] = req_to_install - if self.is_download: + if self.is_download or req_to_install._temp_build_dir is not None: self.reqs_to_cleanup.append(req_to_install) else: self.reqs_to_cleanup.append(req_to_install) From 5b6d24f1bed76d335f8ec5060e68c4fa843637ad Mon Sep 17 00:00:00 2001 From: Marc Tamlyn Date: Fri, 13 Jul 2012 18:04:27 +0200 Subject: [PATCH 015/132] Minor tweak to handle not passing options to parse_requirements. Options is supposed to be an optional argument, this makes it optional. --- pip/req.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pip/req.py b/pip/req.py index 6571b17bbb5..347340cf420 100644 --- a/pip/req.py +++ b/pip/req.py @@ -1274,7 +1274,7 @@ def _write_delete_marker_message(filepath): def parse_requirements(filename, finder=None, comes_from=None, options=None): skip_match = None - skip_regex = options.skip_requirements_regex + skip_regex = options.skip_requirements_regex if options else None if skip_regex: skip_match = re.compile(skip_regex) filename, content = get_file_content(filename, comes_from=comes_from) From 888ae41ccf27a095cd3621a51c74a698b72490ce Mon Sep 17 00:00:00 2001 From: Phil Whelan Date: Wed, 18 Jul 2012 15:05:23 +0900 Subject: [PATCH 016/132] stop get_mirrors returning 216k invalid hostname from Japan --- pip/index.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pip/index.py b/pip/index.py index 7f3a50c8a6a..bf8b55cc15a 100644 --- a/pip/index.py +++ b/pip/index.py @@ -29,7 +29,7 @@ __all__ = ['PackageFinder'] -DEFAULT_MIRROR_URL = "last.pypi.python.org" +DEFAULT_MIRROR_HOSTNAME = "last.pypi.python.org" class PackageFinder(object): @@ -687,14 +687,17 @@ def get_mirrors(hostname=None): Originally written for the distutils2 project by Alexis Metaireau. """ if hostname is None: - hostname = DEFAULT_MIRROR_URL + hostname = DEFAULT_MIRROR_HOSTNAME # return the last mirror registered on PyPI. + last_mirror_hostname = None try: - hostname = socket.gethostbyname_ex(hostname)[0] + last_mirror_hostname = socket.gethostbyname_ex(hostname)[0] except socket.gaierror: return [] - end_letter = hostname.split(".", 1) + if not last_mirror_hostname or last_mirror_hostname == DEFAULT_MIRROR_HOSTNAME: + last_mirror_hostname = "z.pypi.python.org" + end_letter = last_mirror_hostname.split(".", 1) # determine the list from the last one. return ["%s.%s" % (s, end_letter[1]) for s in string_range(end_letter[0])] From 865af4605c46ba7554383c3281a41b16acad8b25 Mon Sep 17 00:00:00 2001 From: Clay McClure Date: Wed, 18 Jul 2012 11:10:07 -0400 Subject: [PATCH 017/132] Update AUTHORS.txt --- AUTHORS.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.txt b/AUTHORS.txt index 8dee04e4ecc..8fa2be86334 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -6,6 +6,7 @@ Armin Ronacher Brian Rosner Carl Meyer Christian Oudard +Clay McClure Cody Soyland Daniel Holth Dave Abrahams From 54401eeaa2894ec9d62859f0dfcd59e039ea80f5 Mon Sep 17 00:00:00 2001 From: Phil Whelan Date: Fri, 20 Jul 2012 15:50:39 +0900 Subject: [PATCH 018/132] test pip.index.get_mirrors() with 216k mirrors issue --- tests/test_index.py | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/tests/test_index.py b/tests/test_index.py index 6f9d216dc05..8e9588bb3e5 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -1,4 +1,6 @@ -from pip.index import package_to_requirement, HTMLPage +from pip.index import package_to_requirement, HTMLPage, get_mirrors +from string import ascii_lowercase +import socket def test_package_name_should_be_converted_to_requirement(): @@ -26,3 +28,38 @@ def test_html_page_should_be_able_to_scrap_rel_links(): assert len(links) == 1 assert links[0].url == 'http://supervisord.org/' +def test_get_mirrors(): + + def mock_gethostbyname_ex_good(hostname): + return ('g.pypi.python.org', [hostname], ['129.21.171.98']) + def mock_gethostbyname_ex_bad(hostname): + return (hostname, [hostname], ['129.21.171.98']) + + orig_gethostbyname_ex = socket.gethostbyname_ex + try: + # Test when the expected result comes back + # from socket.gethostbyname_ex + socket.gethostbyname_ex = mock_gethostbyname_ex_good + mirrors = get_mirrors() + # Expect [a-g].pypi.python.org, since last mirror + # is returned as g.pypi.python.org + assert len(mirrors) == 7 + for c in "abcdefg": + assert c + ".pypi.python.org" in mirrors + + # Test when the UNexpected result comes back + # from socket.gethostbyname_ex + # (seeing this in Japan and was resulting in 216k + # invaldi mirrors and a hot CPU) + socket.gethostbyname_ex = mock_gethostbyname_ex_bad + mirrors = get_mirrors() + # Falls back to [a-z].pypi.python.org + assert len(mirrors) == 26 + for c in ascii_lowercase: + assert c + ".pypi.python.org" in mirrors + except: + socket.gethostbyname_ex = orig_gethostbyname_ex + raise + else: + socket.gethostbyname_ex = orig_gethostbyname_ex + From 83652cfb8df6a3327e08c0d66c297b916c9a3a56 Mon Sep 17 00:00:00 2001 From: Phil Whelan Date: Sat, 28 Jul 2012 20:23:30 -0700 Subject: [PATCH 019/132] updated test_get_mirrors to use @patch and split into test_get_mirrors_no_cname --- tests/test_index.py | 62 +++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/tests/test_index.py b/tests/test_index.py index 8e9588bb3e5..c537172aae0 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -1,6 +1,6 @@ -from pip.index import package_to_requirement, HTMLPage, get_mirrors +from pip.index import package_to_requirement, HTMLPage, get_mirrors, DEFAULT_MIRROR_HOSTNAME from string import ascii_lowercase -import socket +from mock import patch def test_package_name_should_be_converted_to_requirement(): @@ -28,38 +28,28 @@ def test_html_page_should_be_able_to_scrap_rel_links(): assert len(links) == 1 assert links[0].url == 'http://supervisord.org/' -def test_get_mirrors(): - - def mock_gethostbyname_ex_good(hostname): - return ('g.pypi.python.org', [hostname], ['129.21.171.98']) - def mock_gethostbyname_ex_bad(hostname): - return (hostname, [hostname], ['129.21.171.98']) - - orig_gethostbyname_ex = socket.gethostbyname_ex - try: - # Test when the expected result comes back - # from socket.gethostbyname_ex - socket.gethostbyname_ex = mock_gethostbyname_ex_good - mirrors = get_mirrors() - # Expect [a-g].pypi.python.org, since last mirror - # is returned as g.pypi.python.org - assert len(mirrors) == 7 - for c in "abcdefg": - assert c + ".pypi.python.org" in mirrors - - # Test when the UNexpected result comes back - # from socket.gethostbyname_ex - # (seeing this in Japan and was resulting in 216k - # invaldi mirrors and a hot CPU) - socket.gethostbyname_ex = mock_gethostbyname_ex_bad - mirrors = get_mirrors() - # Falls back to [a-z].pypi.python.org - assert len(mirrors) == 26 - for c in ascii_lowercase: - assert c + ".pypi.python.org" in mirrors - except: - socket.gethostbyname_ex = orig_gethostbyname_ex - raise - else: - socket.gethostbyname_ex = orig_gethostbyname_ex +@patch('socket.gethostbyname_ex') +def test_get_mirrors(mock_gethostbyname_ex): + # Test when the expected result comes back + # from socket.gethostbyname_ex + mock_gethostbyname_ex.return_value = ('g.pypi.python.org', [DEFAULT_MIRROR_HOSTNAME], ['129.21.171.98']) + mirrors = get_mirrors() + # Expect [a-g].pypi.python.org, since last mirror + # is returned as g.pypi.python.org + assert len(mirrors) == 7 + for c in "abcdefg": + assert c + ".pypi.python.org" in mirrors + +@patch('socket.gethostbyname_ex') +def test_get_mirrors_no_cname(mock_gethostbyname_ex): + # Test when the UNexpected result comes back + # from socket.gethostbyname_ex + # (seeing this in Japan and was resulting in 216k + # invalid mirrors and a hot CPU) + mock_gethostbyname_ex.return_value = (DEFAULT_MIRROR_HOSTNAME, [DEFAULT_MIRROR_HOSTNAME], ['129.21.171.98']) + mirrors = get_mirrors() + # Falls back to [a-z].pypi.python.org + assert len(mirrors) == 26 + for c in ascii_lowercase: + assert c + ".pypi.python.org" in mirrors From cc0ec4e5cb22fe0638b34cbf989eea48f4dae4b1 Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Sat, 28 Jul 2012 20:34:06 -0700 Subject: [PATCH 020/132] assert_no_temp feature tests --- tests/test_pip.py | 4 ++-- tests/test_test.py | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/tests/test_pip.py b/tests/test_pip.py index 114ec05d1df..dba386fef4a 100644 --- a/tests/test_pip.py +++ b/tests/test_pip.py @@ -362,7 +362,7 @@ def __init__(self, environ=None, use_distribute=None, sitecustomize=None): if sitecustomize: self._add_to_sitecustomize(sitecustomize) - # Ensure that $TMPDIR exists + # Ensure that $TMPDIR exists (because we use start_clear=False, it's not created for us) if self.temp_path and not os.path.exists(self.temp_path): os.makedirs(self.temp_path) @@ -514,7 +514,7 @@ def __init__(self, environ=None, sitecustomize=None): assert self.root_path.exists - # Ensure that $TMPDIR exists + # Ensure that $TMPDIR exists (because we use start_clear=False, it's not created for us) if self.temp_path and not os.path.exists(self.temp_path): os.makedirs(self.temp_path) diff --git a/tests/test_test.py b/tests/test_test.py index a5293957526..498652d959f 100644 --- a/tests/test_test.py +++ b/tests/test_test.py @@ -66,3 +66,24 @@ def test_sitecustomize_not_growing_in_fast_environment(): size2 = os.stat(sc2).st_size assert size1==size2, "size before, %d != size after, %d" %(size1, size2) + +def test_tmp_dir_exists_in_env(): + """ + Test that $TMPDIR == env.temp_path and path exists, and env.assert_no_temp() passes + """ + #need these tests to ensure the assert_no_temp feature of scripttest is working + env = reset_env(use_distribute=True) + env.assert_no_temp() #this fails if env.tmp_path doesn't exist + assert env.environ['TMPDIR'] == env.temp_path + assert isdir(env.temp_path) + + +def test_tmp_dir_exists_in_fast_env(): + """ + Test that $TMPDIR == env.temp_path and path exists and env.assert_no_temp() passes (in fast env) + """ + #need these tests to ensure the assert_no_temp feature of scripttest is working + env = reset_env() + env.assert_no_temp() #this fails if env.tmp_path doesn't exist + assert env.environ['TMPDIR'] == env.temp_path + assert isdir(env.temp_path) From 8ed065a218d839a6f46f8319365a3ccd6f0cf7e4 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Sun, 29 Jul 2012 09:41:54 +0100 Subject: [PATCH 021/132] Update changelog. --- AUTHORS.txt | 1 + docs/news.txt | 3 +++ 2 files changed, 4 insertions(+) diff --git a/AUTHORS.txt b/AUTHORS.txt index 8dee04e4ecc..1c2882e64ab 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -40,6 +40,7 @@ Paul Nasrat Paul Oswald Paul van der Linden Peter Waller +Phil Whelan Piet Delport Qiangning Hong Rene Dudfield diff --git a/docs/news.txt b/docs/news.txt index cbe1bb3fc41..b2b983370a6 100644 --- a/docs/news.txt +++ b/docs/news.txt @@ -13,6 +13,9 @@ Beta and final releases planned for the second half of 2012. develop (unreleased) ------------------- +* Fixed issue #605 - pypi mirror support broken on some DNS responses. Thanks + philwhin. + * Fixed issue #355 - pip uninstall removes files it didn't install. Thanks pjdelport. From 67b4b5161335ed08d30932f7de5e7d48aed0897c Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Sun, 29 Jul 2012 10:04:52 +0100 Subject: [PATCH 022/132] Fix test on windows CI --- tests/test_requirements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_requirements.py b/tests/test_requirements.py index a50feeb26d5..64a88e37851 100644 --- a/tests/test_requirements.py +++ b/tests/test_requirements.py @@ -171,7 +171,7 @@ def test_parse_editable_local_extras(isdir_mock, exists_mock, getcwd_mock, normc normcase_mock.return_value = "/some/path/foo" assert_equal( parse_editable('foo[bar,baz]', 'git'), - (None, 'file://' + os.path.join("/some/path", 'foo'), ('bar', 'baz')) + (None, 'file:///some/path/foo', ('bar', 'baz')) ) def test_install_local_editable_with_extras(): From 57f9be568adb6ac827067d312a4cc709ddf126ef Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Sun, 29 Jul 2012 10:40:14 +0100 Subject: [PATCH 023/132] Issue #557 fix test failing on windows. More path equality fun, use normpath to ensure path sep correct. --- tests/test_uninstall.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_uninstall.py b/tests/test_uninstall.py index 3a0fac92cfa..408847487b4 100644 --- a/tests/test_uninstall.py +++ b/tests/test_uninstall.py @@ -1,6 +1,6 @@ import textwrap import sys -from os.path import join, abspath +from os.path import join, abspath, normpath from tempfile import mkdtemp from mock import Mock from nose.tools import assert_raises @@ -65,10 +65,10 @@ def test_uninstall_overlapping_package(): assert join(env.site_packages, 'parent') in result1.files_created, sorted(result1.files_created.keys()) result2 = run_pip('install', child_pkg, expect_error=False) assert join(env.site_packages, 'child') in result2.files_created, sorted(result2.files_created.keys()) - assert join(env.site_packages, 'parent/plugins/child_plugin.py') in result2.files_created, sorted(result2.files_created.keys()) + assert normpath(join(env.site_packages, 'parent/plugins/child_plugin.py')) in result2.files_created, sorted(result2.files_created.keys()) result3 = run_pip('uninstall', '-y', 'child', expect_error=False) assert join(env.site_packages, 'child') in result3.files_deleted, sorted(result3.files_created.keys()) - assert join(env.site_packages, 'parent/plugins/child_plugin.py') in result3.files_deleted, sorted(result3.files_deleted.keys()) + assert normpath(join(env.site_packages, 'parent/plugins/child_plugin.py')) in result3.files_deleted, sorted(result3.files_deleted.keys()) assert join(env.site_packages, 'parent') not in result3.files_deleted, sorted(result3.files_deleted.keys()) # Additional check: uninstalling 'child' should return things to the # previous state, without unintended side effects. From f24a8d0e4f827216c0dcb2a175453d4d3148db1e Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Sun, 29 Jul 2012 11:00:04 +0100 Subject: [PATCH 024/132] Enable pypy experimentally on travis --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 0642da6fe41..d1711ddc933 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ python: - 2.7 - 3.1 - 3.2 + - pypy before_install: - sudo apt-get install subversion bzr mercurial - echo -e "[web]\ncacerts = /etc/ssl/certs/ca-certificates.crt" >> ~/.hgrc @@ -15,5 +16,8 @@ notifications: branches: only: - develop +matrix: + allow_failures: + - python: pypy env: - PIP_USE_MIRRORS=true From d1a7ea25871bb2c6722bfc063c8439b0be9fc342 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Sun, 29 Jul 2012 11:37:56 +0100 Subject: [PATCH 025/132] Issue #597 pypy hashlib compat. Hash attribute name is a cpython implementation detail and not in the PEP. Use digest_size for some form of validation as that is all we can rely on. TESTED 2.7, 3.3, pypy 1.9 --- pip/download.py | 12 ++++++------ tests/test_hashes.py | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/pip/download.py b/pip/download.py index 6243a5b48c6..12bf82d5fdf 100644 --- a/pip/download.py +++ b/pip/download.py @@ -323,14 +323,14 @@ def is_file_url(link): def _check_hash(download_hash, link): - if download_hash.name != link.hash_name: - logger.fatal("Hash name of the package %s (%s) doesn't match the expected hash name %s!" - % (download_hash.name, link, link.hash_name)) + if download_hash.digest_size != hashlib.new(link.hash_name).digest_size: + logger.fatal("Hash digest size of the package %d (%s) doesn't match the expected hash name %s!" + % (download_hash.digest_size, link, link.hash_name)) raise InstallationError('Hash name mismatch for package %s' % link) if download_hash.hexdigest() != link.hash: - logger.fatal("%s hash of the package %s (%s) doesn't match the expected hash %s!" - % (download_hash.name, link, download_hash, link.hash)) - raise InstallationError('Bad %s hash for package %s' % (download_hash.name, link)) + logger.fatal("Hash of the package %s (%s) doesn't match the expected hash %s!" + % (link, download_hash, link.hash)) + raise InstallationError('Bad %s hash for package %s' % (link.hash_name, link)) def _get_hash_from_file(target_file, link): diff --git a/tests/test_hashes.py b/tests/test_hashes.py index 1e738f43b1d..79c4f164992 100644 --- a/tests/test_hashes.py +++ b/tests/test_hashes.py @@ -13,7 +13,7 @@ def test_get_hash_from_file_md5(): download_hash = _get_hash_from_file(file_path, file_link) - assert download_hash.name == "md5" + assert download_hash.digest_size == 16 assert download_hash.hexdigest() == "d41d8cd98f00b204e9800998ecf8427e" @@ -23,7 +23,7 @@ def test_get_hash_from_file_sha1(): download_hash = _get_hash_from_file(file_path, file_link) - assert download_hash.name == "sha1" + assert download_hash.digest_size == 20 assert download_hash.hexdigest() == "da39a3ee5e6b4b0d3255bfef95601890afd80709" @@ -33,7 +33,7 @@ def test_get_hash_from_file_sha224(): download_hash = _get_hash_from_file(file_path, file_link) - assert download_hash.name == "sha224" + assert download_hash.digest_size == 28 assert download_hash.hexdigest() == "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f" @@ -43,7 +43,7 @@ def test_get_hash_from_file_sha384(): download_hash = _get_hash_from_file(file_path, file_link) - assert download_hash.name == "sha384" + assert download_hash.digest_size == 48 assert download_hash.hexdigest() == "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b" @@ -53,7 +53,7 @@ def test_get_hash_from_file_sha256(): download_hash = _get_hash_from_file(file_path, file_link) - assert download_hash.name == "sha256" + assert download_hash.digest_size == 32 assert download_hash.hexdigest() == "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" @@ -63,7 +63,7 @@ def test_get_hash_from_file_sha512(): download_hash = _get_hash_from_file(file_path, file_link) - assert download_hash.name == "sha512" + assert download_hash.digest_size == 64 assert download_hash.hexdigest() == "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e" From bb5b92e75438dc1b2c7909bf8f088bd7280ac64c Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Sat, 28 Jul 2012 21:09:07 -0700 Subject: [PATCH 026/132] issue #420 test enhancement --- tests/test_cleanup.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/tests/test_cleanup.py b/tests/test_cleanup.py index 548aa75eca0..c57db91ee3d 100644 --- a/tests/test_cleanup.py +++ b/tests/test_cleanup.py @@ -17,6 +17,7 @@ def test_cleanup_after_install_from_pypi(): src = env.scratch_path/"src" assert not exists(build), "build/ dir still exists: %s" % build assert not exists(src), "unexpected src/ dir exists: %s" % src + env.assert_no_temp() def test_cleanup_after_install_editable_from_hg(): @@ -34,6 +35,7 @@ def test_cleanup_after_install_editable_from_hg(): src = env.venv_path/'src' assert not exists(build), "build/ dir still exists: %s" % build assert exists(src), "expected src/ dir doesn't exist: %s" % src + env.assert_no_temp() def test_cleanup_after_install_from_local_directory(): @@ -48,6 +50,7 @@ def test_cleanup_after_install_from_local_directory(): src = env.venv_path/'src' assert not exists(build), "unexpected build/ dir exists: %s" % build assert not exists(src), "unexpected src/ dir exist: %s" % src + env.assert_no_temp() def test_cleanup_after_create_bundle(): @@ -79,6 +82,7 @@ def test_cleanup_after_create_bundle(): src_bundle = env.scratch_path/"src-bundle" assert not exists(build_bundle), "build-bundle/ dir still exists: %s" % build_bundle assert not exists(src_bundle), "src-bundle/ dir still exists: %s" % src_bundle + env.assert_no_temp() # Make sure previously created src/ from editable still exists assert exists(src), "expected src dir doesn't exist: %s" % src @@ -96,24 +100,23 @@ def test_no_install_and_download_should_not_leave_build_dir(): assert not os.path.exists(env.venv_path/'/build'), "build/ dir should be deleted" -def test_temp_build_dir_removed(): +def test_cleanup_req_satisifed_no_name(): """ - It should remove the temp build dir created by pip. - - TestFileEnvironment provides a mechanism to ensure that - no temporary files are left after a test script executes, - so we can take advantage of that here by simply running - `run_pip` twice (which uses TestFileEnvironment). + Test cleanup when req is already satisfied, and req has no 'name' """ - env = reset_env() - - # Install a requirement into a fresh environment. The temporary build - # directory should be removed. - result = run_pip('install', 'https://bitbucket.org/ianb/initools/get/tip.zip') + #this test confirms Issue #420 is fixed + #reqs with no 'name' that were already satisfied were leaving behind tmp build dirs + #2 examples of reqs that would do this + # 1) https://bitbucket.org/ianb/initools/get/tip.zip + # 2) parent-0.1.tar.gz - # Now install the requirement again, exercising a different code path, - # and ensure that the temporary build directory is still removed. - result = run_pip('install', 'https://bitbucket.org/ianb/initools/get/tip.zip') + dist = abspath(join(here, 'packages', 'parent-0.1.tar.gz')) + env = reset_env() + result = run_pip('install', dist) + result = run_pip('install', dist) + build = env.venv_path/'build' + assert not exists(build), "unexpected build/ dir exists: %s" % build + env.assert_no_temp() def test_download_should_not_delete_existing_build_dir(): From 39db7ce97aa7b9fabf941ff4aafe39b2f95a1e5e Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Sun, 29 Jul 2012 21:21:25 -0700 Subject: [PATCH 027/132] --user installs are broken in pypy; skip testing it there. --- tests/test_user_site.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/test_user_site.py b/tests/test_user_site.py index 9b455898541..2d1eec924c8 100644 --- a/tests/test_user_site.py +++ b/tests/test_user_site.py @@ -35,6 +35,10 @@ def setup(self): # --user only works on 2.6 or higher if sys.version_info < (2, 6): raise SkipTest() + # --user option is broken in pypy + if hasattr(sys, "pypy_version_info"): + raise SkipTest() + def test_reset_env_system_site_packages_usersite(self): """ @@ -66,9 +70,6 @@ def test_install_subversion_usersite_editable_with_distribute(self): """ Test installing current directory ('.') into usersite after installing distribute """ - # FIXME distutils --user option seems to be broken in pypy - if hasattr(sys, "pypy_version_info"): - raise SkipTest() env = reset_env(use_distribute=True, system_site_packages=True) result = run_pip('install', '--user', '-e', '%s#egg=initools-dev' % @@ -80,9 +81,6 @@ def test_install_curdir_usersite(self): """ Test installing current directory ('.') into usersite """ - # FIXME distutils --user option seems to be broken in pypy - if hasattr(sys, "pypy_version_info"): - raise SkipTest() env = reset_env(use_distribute=True, system_site_packages=True) run_from = abspath(join(here, 'packages', 'FSPkg')) result = run_pip('install', '--user', curdir, cwd=run_from, expect_error=False) From 483adaaa638b032de1472e3e502fdc3dfd3a3cef Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Sun, 29 Jul 2012 21:22:25 -0700 Subject: [PATCH 028/132] patch for virtualenv issue #306 --- tests/test_pip.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_pip.py b/tests/test_pip.py index 10f075b98b9..d7ea4bc6ca3 100644 --- a/tests/test_pip.py +++ b/tests/test_pip.py @@ -314,6 +314,9 @@ def __init__(self, environ=None, use_distribute=None, sitecustomize=None): assert self.venv_path == virtualenv_paths[0] # sanity check for id, path in zip(('venv', 'lib', 'include', 'bin'), virtualenv_paths): + #fix for virtualenv issue #306 + if hasattr(sys, "pypy_version_info") and id == 'lib': + path = os.path.join(self.venv_path, 'lib-python', pyversion) setattr(self, id+'_path', Path(path)) setattr(self, id, relpath(self.root_path, path)) @@ -444,6 +447,9 @@ def __init__(self, environ=None, sitecustomize=None): virtualenv_paths = virtualenv.path_locations(self.venv_path) for id, path in zip(('venv', 'lib', 'include', 'bin'), virtualenv_paths): + #fix for virtualenv issue #306 + if hasattr(sys, "pypy_version_info") and id == 'lib': + path = os.path.join(self.venv_path, 'lib-python', pyversion) setattr(self, id+'_path', Path(path)) setattr(self, id, relpath(self.root_path, path)) From aa20274f4209aa54707137c32268f2e5f93a1a54 Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Sun, 29 Jul 2012 22:24:58 -0700 Subject: [PATCH 029/132] correct site-packages for pypy --- tests/test_pip.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_pip.py b/tests/test_pip.py index d7ea4bc6ca3..e708f1f94f6 100644 --- a/tests/test_pip.py +++ b/tests/test_pip.py @@ -455,7 +455,10 @@ def __init__(self, environ=None, sitecustomize=None): assert self.venv == TestPipEnvironment.venv # sanity check - self.site_packages = self.lib/'site-packages' + if hasattr(sys, "pypy_version_info"): + self.site_packages = self.venv/'site-packages' + else: + self.site_packages = self.lib/'site-packages' self.user_base_path = self.venv_path/'user' self.user_site_path = self.venv_path/'user'/'lib'/self.lib.name/'site-packages' From b7cca4e5052fcd0afb5a1b3abf9cb3618172e3d2 Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Sun, 29 Jul 2012 23:10:55 -0700 Subject: [PATCH 030/132] correct site-packages for pypy in 2nd location --- tests/test_pip.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_pip.py b/tests/test_pip.py index e708f1f94f6..4545cc8db4a 100644 --- a/tests/test_pip.py +++ b/tests/test_pip.py @@ -322,7 +322,10 @@ def __init__(self, environ=None, use_distribute=None, sitecustomize=None): assert self.venv == TestPipEnvironment.venv # sanity check - self.site_packages = self.lib/'site-packages' + if hasattr(sys, "pypy_version_info"): + self.site_packages = self.venv/'site-packages' + else: + self.site_packages = self.lib/'site-packages' self.user_base_path = self.venv_path/'user' self.user_site_path = self.venv_path/'user'/site_packages_suffix From 5a1c6093b62b4a64e48dd07e4d7188e5205fd809 Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Sat, 4 Aug 2012 18:48:16 -0700 Subject: [PATCH 031/132] pypy home scheme fix --- pip/backwardcompat.py | 9 +++++++++ pip/commands/install.py | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pip/backwardcompat.py b/pip/backwardcompat.py index a8b3b922cf1..7f81c370d31 100644 --- a/pip/backwardcompat.py +++ b/pip/backwardcompat.py @@ -1,5 +1,6 @@ """Stuff that differs in different Python versions""" +import os import sys import site @@ -98,3 +99,11 @@ def product(*args, **kwds): result = [x+[y] for x in result for y in pool] for prod in result: yield tuple(prod) + +def home_lib(home): + """Return the lib dir under the 'home' installation scheme""" + if hasattr(sys, 'pypy_version_info'): + lib = 'site-packages' + else: + lib = os.path.join('lib', 'python') + return os.path.join(home, lib) diff --git a/pip/commands/install.py b/pip/commands/install.py index 71ec42f5195..a067f31c95f 100644 --- a/pip/commands/install.py +++ b/pip/commands/install.py @@ -9,6 +9,7 @@ from pip.basecommand import Command from pip.index import PackageFinder from pip.exceptions import InstallationError, CommandError +from pip.backwardcompat import home_lib class InstallCommand(Command): @@ -276,7 +277,7 @@ def run(self, options, args): if options.target_dir: if not os.path.exists(options.target_dir): os.makedirs(options.target_dir) - lib_dir = os.path.join(temp_target_dir, "lib/python/") + lib_dir = home_lib(temp_target_dir) for item in os.listdir(lib_dir): shutil.move( os.path.join(lib_dir, item), From 0be1c8bbdabb2a0559b2f9e705af85ba19c5b07e Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Sat, 4 Aug 2012 18:48:46 -0700 Subject: [PATCH 032/132] pypy unicode test fix --- tests/test_unicode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_unicode.py b/tests/test_unicode.py index e419ac717b9..eb926494ec4 100644 --- a/tests/test_unicode.py +++ b/tests/test_unicode.py @@ -21,5 +21,5 @@ def test_install_package_that_emits_unicode(): env = reset_env() to_install = os.path.abspath(os.path.join(here, 'packages', 'BrokenEmitsUTF8')) result = run_pip('install', to_install, expect_error=True, expect_temp=True, quiet=True) - assert '__main__.FakeError: this package designed to fail on install' in result.stdout + assert 'FakeError: this package designed to fail on install' in result.stdout assert 'UnicodeDecodeError' not in result.stdout From 90b983f08d745d900a3184a6f67671d0ada91151 Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Sat, 4 Aug 2012 18:49:29 -0700 Subject: [PATCH 033/132] workaround for pip issue #626 --- pip/req.py | 11 ++++++++++- pip/util.py | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pip/req.py b/pip/req.py index cc7d4043338..f413b28e27c 100644 --- a/pip/req.py +++ b/pip/req.py @@ -13,7 +13,7 @@ DistributionNotFound) from pip.vcs import vcs from pip.log import logger -from pip.util import display_path, rmtree +from pip.util import display_path, rmtree, is_pypy from pip.util import ask, ask_path_exists, backup_dir from pip.util import is_installable_dir, is_local, dist_is_local, dist_in_usersite from pip.util import renames, normalize_path, egg_link_path, dist_in_site_packages @@ -1435,6 +1435,15 @@ def add(self, path): else: self._refuse.add(path) + #workaround for pip issue #626 (debian pypy creates __pycache__ folders) + if is_pypy: + head, tail = os.path.split(path) + tail_root, tail_ext = os.path.splitext(tail) + if tail_ext == '.py': + pycache_path = os.path.join(head, '__pycache__', tail_root + '.pypy-%d%d.pyc' % sys.pypy_version_info[:2]) + self.add(pycache_path) + + def add_pth(self, pth_file, entry): pth_file = normalize_path(pth_file) if self._permitted(pth_file): diff --git a/pip/util.py b/pip/util.py index 833a7adcc7a..8237db78adc 100644 --- a/pip/util.py +++ b/pip/util.py @@ -172,6 +172,7 @@ def is_svn_page(html): return (re.search(r'[^<]*Revision \d+:', html) and re.search(r'Powered by (?:<a[^>]*?>)?Subversion', html, re.I)) +is_pypy = hasattr(sys, 'pypy_version_info') def file_contents(filename): fp = open(filename, 'rb') From 5565b7e4fb6e366daa2ce9eaace3c66a697080b8 Mon Sep 17 00:00:00 2001 From: Marcus Smith <qwcode@gmail.com> Date: Sat, 4 Aug 2012 18:55:52 -0700 Subject: [PATCH 034/132] no need to allow pypy failures now --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d1711ddc933..24be5ebaf42 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,5 @@ branches: - develop matrix: allow_failures: - - python: pypy env: - PIP_USE_MIRRORS=true From 4d955d56af75be1b4a195623fd03b5ca677383c0 Mon Sep 17 00:00:00 2001 From: MiCHiLU <djmchl@gmail.com> Date: Mon, 13 Aug 2012 13:02:16 +0900 Subject: [PATCH 035/132] Fix issue #632 - IOError in zip command with --path option --- pip/commands/zip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pip/commands/zip.py b/pip/commands/zip.py index ebe1d791a4d..1c84210acc9 100644 --- a/pip/commands/zip.py +++ b/pip/commands/zip.py @@ -231,7 +231,7 @@ def remove_filename_from_pth(self, filename): def add_filename_to_pth(self, filename): path = os.path.dirname(filename) - dest = os.path.join(path, filename + '.pth') + dest = filename + '.pth' if path not in self.paths(): logger.warn('Adding .pth file %s, but it is not on sys.path' % display_path(dest)) if not self.simulate: From 0a18db27ba8b6de3005a1aa6716aba0d9d7ae3f1 Mon Sep 17 00:00:00 2001 From: Marcus Smith <qwcode@gmail.com> Date: Sat, 18 Aug 2012 12:03:22 -0700 Subject: [PATCH 036/132] edited and repackaged to exclude twisted import --- tests/packages/parent-0.1.tar.gz | Bin 569 -> 483 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/packages/parent-0.1.tar.gz b/tests/packages/parent-0.1.tar.gz index 7d8673cacaac2ff27fd8f3daf4b0fd847a3b3c08..130e756f49136d23a05f84a4e1d57df200e12e7c 100644 GIT binary patch literal 483 zcmV<90UZ7xiwFo*6)#W%18`w-Wo~pWFfK7JbYXG;?U+qV<S-b3d+x6Yco-LKZQ7=R zfjJ1`2d-6E+&wHZ#7-Q8lQx^AF8=$C?KmR?;wsZt;e8HG`?jTr=luv(O!LwPQHb}w zu816zkf#+>61`lX@o*gBB*QU@Q!m0X&WMMI*8{b&Oe5s|`CC*o{`zxo-T$Jfwg1br z<KQ$uy6D7zmZq=zkF$8gfASPU5%}+%b8gv;Sr+`{+K8$gBOFG4&LkhBHaGm$9hW5j zk6KEmAIIn_Kg%zE<^D+}d7#*wuR{m5y{q)*b-*PPi*@w7D0pdJymrV<p@p)|x1NKS z3ubRCE!Pnb=%)VL(+<YK`}=>Ak_`I)F#e}gQ3^YqhU&4W{@?h=lxA_3y3z0a6G9{C z|HJrKi+V0fvp;106P!?}|6%=auMfOg{}`|Qrx|trsav7``(S6O8@5%j|7+|2W_yGG z!}C9Na{%gp82{{Z_{ZSCXa9!VT5VnafB44~Z}?9r2LHWqt1F3S!r0o1g$avFb0nmy zv_(xm_|0+GM~;$7J{h$cGumrH5ADC9$;c(pGNf2>&*t1rey<)y%d>E~*%)1agZ%~o Z0000000000002Ng`~zFy9?k$L004c__c#Cm literal 569 zcmV-90>=FxiwFqe+WAic|72-%bT4pWa%FCGEif)IE_7jX0PUDvYuhjshJEc{A=pI{ zXi~|sWeDVAF!n*WG%&Valp?f5O~h<T$a1&-`zmpqgsfZW*4Q1K=faYYWsG^x(Rppd ztx&Fy2jo0B71)y|$!a8Ng7?RofRHq$!z3BTB)}x1co?80Xn?YC+#(bhDcqmd9lKt? zNIm{1n4SKGaHScT`;+KT<M_Awlay5YM>HnUO8<C>(*R-6|K!|Souj!dT<K(13})I2 zBy*#!L+gA9*T-&%Jc{RH+^I6AbG8oM30p|5#vPwPn~>qN?|dPO@%45o+RO$fD;H7~ zots`5057cmtLX{az|;5t7^gAp|Ly3%snPQ`^-nyYf4llKCY5xI{ktPL*8ig<I_Uo? zr5O5uWBuR#@CHE4H|@FZ|JB*FYUfk#mPN>z@uSZP@zV)4@BX@^%h<{$FxKn&)jZ1> zLt}J(gC5}(Qra!0S|F#r+Yo3etT;otz7vRNS}peHU>F*zfBx*B0LSWoyZ`U}KaIiv zx1|5`?fZYPKP8lc{_W`h?M2jo0{y>f{ihf2`X455FB|{=6Vd3v{|{3P`ZvNS;rN_8 z-v3(t$r&Lsz~RJOxDl#yBivr`NfLf8^PJoJ5&AN@m|R{>!uL8CedF!lF7&!|OKlI& z`y%Hu+eJUhnNY>vZEr*|v(mWrv+Ce9<L*}5d=~-F1^@s60000000000@Dv^ZYO94v H04M+eFo!E` From 848a3b4f261a6c5de4cb968e5399a7bde07465fb Mon Sep 17 00:00:00 2001 From: Phil Freo <phil@philfreo.com> Date: Sat, 18 Aug 2012 17:18:44 -0300 Subject: [PATCH 037/132] Fix typos in documentation --- pip/baseparser.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pip/baseparser.py b/pip/baseparser.py index 0742ef6f67f..310403a9929 100644 --- a/pip/baseparser.py +++ b/pip/baseparser.py @@ -285,10 +285,10 @@ def get_default_values(self): choices=['s', 'i', 'w', 'b'], default=[], action='append', - help="Default action when a path already exists." - "Use this option more then one time to specify " + help="Default action when a path already exists. " + "Use this option more than one time to specify " "another action if a certain option is not " - "available, choices: " + "available. Choices: " "(s)witch, (i)gnore, (w)ipe, (b)ackup") parser.disable_interspersed_args() From 0569324855d9d1e5efe6e9527d4c649a3472a82b Mon Sep 17 00:00:00 2001 From: Marcus Smith <qwcode@gmail.com> Date: Sat, 18 Aug 2012 15:26:17 -0700 Subject: [PATCH 038/132] handle __pycache__ file removal --- pip/backwardcompat.py | 3 +++ pip/req.py | 17 ++++++++--------- pip/util.py | 1 - tests/test_uninstall.py | 17 +++++++++++++++++ 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/pip/backwardcompat.py b/pip/backwardcompat.py index 7f81c370d31..788023fae90 100644 --- a/pip/backwardcompat.py +++ b/pip/backwardcompat.py @@ -1,11 +1,14 @@ """Stuff that differs in different Python versions""" import os +import imp import sys import site __all__ = ['WindowsError'] +uses_pycache = hasattr(imp,'cache_from_source') + try: WindowsError = WindowsError except NameError: diff --git a/pip/req.py b/pip/req.py index f413b28e27c..4179f7ad893 100644 --- a/pip/req.py +++ b/pip/req.py @@ -1,5 +1,6 @@ from email.parser import FeedParser import os +import imp import pkg_resources import re import sys @@ -13,13 +14,13 @@ DistributionNotFound) from pip.vcs import vcs from pip.log import logger -from pip.util import display_path, rmtree, is_pypy +from pip.util import display_path, rmtree from pip.util import ask, ask_path_exists, backup_dir from pip.util import is_installable_dir, is_local, dist_is_local, dist_in_usersite from pip.util import renames, normalize_path, egg_link_path, dist_in_site_packages from pip.util import make_path_relative from pip.util import call_subprocess -from pip.backwardcompat import (urlparse, urllib, +from pip.backwardcompat import (urlparse, urllib, uses_pycache, ConfigParser, string_types, HTTPError, get_python_version, b) from pip.index import Link @@ -448,6 +449,8 @@ def uninstall(self, auto_confirm=False): for installed_file in dist.get_metadata('installed-files.txt').splitlines(): path = os.path.normpath(os.path.join(egg_info_path, installed_file)) paths_to_remove.add(path) + #FIXME: need a test for this elif block + #occurs with --single-version-externally-managed/--record outside of pip elif dist.has_metadata('top_level.txt'): if dist.has_metadata('namespace_packages.txt'): namespaces = dist.get_metadata('namespace_packages.txt') @@ -1435,13 +1438,9 @@ def add(self, path): else: self._refuse.add(path) - #workaround for pip issue #626 (debian pypy creates __pycache__ folders) - if is_pypy: - head, tail = os.path.split(path) - tail_root, tail_ext = os.path.splitext(tail) - if tail_ext == '.py': - pycache_path = os.path.join(head, '__pycache__', tail_root + '.pypy-%d%d.pyc' % sys.pypy_version_info[:2]) - self.add(pycache_path) + # __pycache__ files can show up after 'installed-files.txt' is created, due to imports + if os.path.splitext(path)[1] == '.py' and uses_pycache: + self.add(imp.cache_from_source(path)) def add_pth(self, pth_file, entry): diff --git a/pip/util.py b/pip/util.py index 8237db78adc..833a7adcc7a 100644 --- a/pip/util.py +++ b/pip/util.py @@ -172,7 +172,6 @@ def is_svn_page(html): return (re.search(r'<title>[^<]*Revision \d+:', html) and re.search(r'Powered by (?:<a[^>]*?>)?Subversion', html, re.I)) -is_pypy = hasattr(sys, 'pypy_version_info') def file_contents(filename): fp = open(filename, 'rb') diff --git a/tests/test_uninstall.py b/tests/test_uninstall.py index 408847487b4..28339a6152d 100644 --- a/tests/test_uninstall.py +++ b/tests/test_uninstall.py @@ -18,6 +18,8 @@ def test_simple_uninstall(): env = reset_env() result = run_pip('install', 'INITools==0.2') assert join(env.site_packages, 'initools') in result.files_created, sorted(result.files_created.keys()) + #the import forces the generation of __pycache__ if the version of python supports it + env.run('python', '-c', "import initools") result2 = run_pip('uninstall', 'INITools', '-y') assert_all_changes(result, result2, [env.venv/'build', 'cache']) @@ -36,6 +38,19 @@ def test_uninstall_with_scripts(): assert_all_changes(result, result2, [env.venv/'build', 'cache']) +def test_uninstall_easy_install_after_import(): + """ + Uninstall an easy_installed package after it's been imported + + """ + env = reset_env() + result = env.run('easy_install', 'INITools==0.2', expect_stderr=True) + #the import forces the generation of __pycache__ if the version of python supports it + env.run('python', '-c', "import initools") + result2 = run_pip('uninstall', 'INITools', '-y') + assert_all_changes(result, result2, [env.venv/'build', 'cache']) + + def test_uninstall_namespace_package(): """ Uninstall a distribution with a namespace package without clobbering @@ -66,6 +81,8 @@ def test_uninstall_overlapping_package(): result2 = run_pip('install', child_pkg, expect_error=False) assert join(env.site_packages, 'child') in result2.files_created, sorted(result2.files_created.keys()) assert normpath(join(env.site_packages, 'parent/plugins/child_plugin.py')) in result2.files_created, sorted(result2.files_created.keys()) + #the import forces the generation of __pycache__ if the version of python supports it + env.run('python', '-c', "import parent.plugins.child_plugin, child") result3 = run_pip('uninstall', '-y', 'child', expect_error=False) assert join(env.site_packages, 'child') in result3.files_deleted, sorted(result3.files_created.keys()) assert normpath(join(env.site_packages, 'parent/plugins/child_plugin.py')) in result3.files_deleted, sorted(result3.files_deleted.keys()) From 7edd31d41b271463415b283862cd47af82e57f5a Mon Sep 17 00:00:00 2001 From: Jannis Leidel <jannis@leidel.info> Date: Sat, 1 Sep 2012 16:55:20 +0200 Subject: [PATCH 039/132] build/ IS a good name. --- pip/locations.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pip/locations.py b/pip/locations.py index 976538e7ac5..5fbacd5599f 100644 --- a/pip/locations.py +++ b/pip/locations.py @@ -14,26 +14,26 @@ def running_under_virtualenv(): """ return hasattr(sys, 'real_prefix') + def virtualenv_no_global(): """ Return True if in a venv and no system site packages. """ #this mirrors the logic in virtualenv.py for locating the no-global-site-packages.txt file site_mod_dir = os.path.dirname(os.path.abspath(site.__file__)) - no_global_file = os.path.join(site_mod_dir,'no-global-site-packages.txt') + no_global_file = os.path.join(site_mod_dir, 'no-global-site-packages.txt') if running_under_virtualenv() and os.path.isfile(no_global_file): return True if running_under_virtualenv(): - ## FIXME: is build/ a good name? build_prefix = os.path.join(sys.prefix, 'build') src_prefix = os.path.join(sys.prefix, 'src') else: # Use tempfile to create a temporary folder for build # Note: we are NOT using mkdtemp so we can have a consistent build dir build_prefix = os.path.join(tempfile.gettempdir(), 'pip-build') - + ## FIXME: keep src in cwd for now (it is not a temporary folder) try: src_prefix = os.path.join(os.getcwd(), 'src') From 57236914a5dd12d7949110d2c83f2d2f0d8046e0 Mon Sep 17 00:00:00 2001 From: Jannis Leidel <jannis@leidel.info> Date: Sat, 1 Sep 2012 17:56:47 +0200 Subject: [PATCH 040/132] Updated changelog. --- docs/news.txt | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/docs/news.txt b/docs/news.txt index b2b983370a6..194754c2a17 100644 --- a/docs/news.txt +++ b/docs/news.txt @@ -5,13 +5,16 @@ News Changelog ========= -Next release (1.2) schedule +Next release (1.3) schedule --------------------------- -Beta and final releases planned for the second half of 2012. +Beta and final releases planned for the end of 2012. -develop (unreleased) -------------------- +1.2 (2012-09-01) +---------------- + +* **Dropped support for Python 2.4** The minimum supported Python version is + now Python 2.5. * Fixed issue #605 - pypi mirror support broken on some DNS responses. Thanks philwhin. @@ -44,9 +47,6 @@ develop (unreleased) * Fixed issue #504 - allow package URLS to have querystrings. Thanks W. Trevor King. -* Dropped support for Python 2.4. The minimum supported Python version is - now Python 2.5. - * Fixed issue #58 - pip freeze now falls back to non-editable format rather than blowing up if it can't determine the origin repository of an editable. Thanks Rory McCann. @@ -63,9 +63,21 @@ develop (unreleased) * Fixed issue #427 - clearer error message on a malformed VCS url. Thanks Thomas Fenzl. -* Added support for using any of the built in guaranteed algorithms in ``hashlib`` - as a checksum hash. +* Added support for using any of the built in guaranteed algorithms in + ``hashlib`` as a checksum hash. + +* Fixed issue #321 - Raise an exception if current working directory can't be + found or accessed. + +* Fixed issue #82 - Removed special casing of the user directory and use the + Python default instead. + +* Fixed #436 - Only warn about version conflicts if there is actually one. + This re-enables using ``==dev`` in requirements files. + +* Moved tests to be run on Travis CI: http://travis-ci.org/pypa/pip +* Added a better help formatter. 1.1 (2012-02-16) ---------------- From 57557aeb9c007f917c3563f7491330be02547a21 Mon Sep 17 00:00:00 2001 From: Jannis Leidel <jannis@leidel.info> Date: Sat, 1 Sep 2012 19:16:01 +0200 Subject: [PATCH 041/132] Removed 2.4 a bit more. --- docs/contributing.txt | 6 +++--- setup.py | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/contributing.txt b/docs/contributing.txt index c3d0c135672..6caddd449db 100644 --- a/docs/contributing.txt +++ b/docs/contributing.txt @@ -60,10 +60,10 @@ Before sending us a pull request, please, be sure all tests pass. Supported Python versions ------------------------- -Pip supports Python versions 2.4, 2.5, 2.6, 2.7, 3.1, and 3.2, from a single +Pip supports Python versions 2.5, 2.6, 2.7, 3.1, and 3.2, from a single codebase (without use of 2to3 translation). Untested contributions frequently -break Python 2.4 or 3.x compatibility. Please run the tests on at least 2.4 and -3.2 and report your results when sending a pull request. +break Python 3.x compatibility. Please run the tests on at least 3.2 and +report your results when sending a pull request. Continuous Integration server ----------------------------- diff --git a/setup.py b/setup.py index 95a7c68ab35..f466d874357 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,6 @@ 'License :: OSI Approved :: MIT License', 'Topic :: Software Development :: Build Tools', 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.4', 'Programming Language :: Python :: 2.5', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', From 1ae434a05c1c7a1648b4c46f4162d98b941ac9ef Mon Sep 17 00:00:00 2001 From: Jannis Leidel <jannis@leidel.info> Date: Sat, 1 Sep 2012 19:16:10 +0200 Subject: [PATCH 042/132] Bumped version up to 1.2. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f466d874357..764a8b43b94 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import setup # If you change this version, change it also in docs/conf.py -version = "1.1.post2" +version = "1.2" doc_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "docs") index_filename = os.path.join(doc_dir, "index.txt") From 77376b7e6bc3c04cb596f1dd4d162928066cb9c8 Mon Sep 17 00:00:00 2001 From: Jannis Leidel <jannis@leidel.info> Date: Sat, 1 Sep 2012 19:33:05 +0200 Subject: [PATCH 043/132] :sparkles: Happy new year! :sparkles: --- LICENSE.txt | 2 +- docs/conf.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index 7951a03258d..c1024db5926 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2008-2011 The pip developers (see AUTHORS.txt file) +Copyright (c) 2008-2012 The pip developers (see AUTHORS.txt file) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/docs/conf.py b/docs/conf.py index 2c03957b156..6fb6f27a90f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -40,7 +40,7 @@ # General information about the project. project = 'pip' -copyright = '2008-2011, The pip developers' +copyright = '2008-2012, The pip developers' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the From d9599ec6df654329d967edc5253e377d7e2acebd Mon Sep 17 00:00:00 2001 From: Jannis Leidel <jannis@leidel.info> Date: Sat, 1 Sep 2012 19:33:52 +0200 Subject: [PATCH 044/132] Added a pip.__version__ variable that is used in the setup.py and the docs' conf.py. --- docs/conf.py | 15 +++++++++++---- pip/__init__.py | 10 +++++++--- setup.py | 33 +++++++++++++++++++-------------- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 6fb6f27a90f..8678de5f3f8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,12 +11,13 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import os +import sys # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.append(os.path.abspath('.')) +sys.path.insert(0, os.path.abspath(os.pardir)) #sys.path.append(os.path.join(os.path.dirname(__file__), '../')) # -- General configuration ----------------------------------------------------- @@ -47,8 +48,14 @@ # built documents. # # The short X.Y version. -release = "1.1.post2" -version = '.'.join(release.split('.')[:2]) +try: + from pip import __version__ + # The short X.Y version. + version = '.'.join(__version__.split('.')[:2]) + # The full version, including alpha/beta/rc tags. + release = __version__ +except ImportError: + version = release = 'dev' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/pip/__init__.py b/pip/__init__.py index 62e3a910f76..47fe3a7900e 100755 --- a/pip/__init__.py +++ b/pip/__init__.py @@ -14,6 +14,10 @@ from pip.vcs import git, mercurial, subversion, bazaar +# The version as used in the setup.py and the docs conf.py +__version__ = "1.2" + + def autocomplete(): """Command and option completion for the main option parser (and options) and its subcommands (and options). @@ -26,7 +30,7 @@ def autocomplete(): cwords = os.environ['COMP_WORDS'].split()[1:] cword = int(os.environ['COMP_CWORD']) try: - current = cwords[cword-1] + current = cwords[cword - 1] except IndexError: current = '' load_all_commands() @@ -59,7 +63,7 @@ def autocomplete(): for opt in subcommand.parser.option_list if opt.help != optparse.SUPPRESS_HELP] # filter out previously specified options from available options - prev_opts = [x.split('=')[0] for x in cwords[1:cword-1]] + prev_opts = [x.split('=')[0] for x in cwords[1:cword - 1]] options = [(x, v) for (x, v) in options if x not in prev_opts] # filter options by current input options = [(k, v) for k, v in options if k.startswith(current)] @@ -184,7 +188,7 @@ def __str__(self): req = self.req if self.editable: req = '-e %s' % req - return '\n'.join(list(self.comments)+[str(req)])+'\n' + return '\n'.join(list(self.comments) + [str(req)]) + '\n' if __name__ == '__main__': diff --git a/setup.py b/setup.py index 764a8b43b94..77b39595d02 100644 --- a/setup.py +++ b/setup.py @@ -1,13 +1,22 @@ -import sys +import codecs import os +import re +import sys from setuptools import setup -# If you change this version, change it also in docs/conf.py -version = "1.2" -doc_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "docs") -index_filename = os.path.join(doc_dir, "index.txt") -news_filename = os.path.join(doc_dir, "news.txt") +def read(*parts): + return codecs.open(os.path.join(os.path.abspath(os.path.dirname(__file__)), *parts), 'r').read() + + +def find_version(*file_paths): + version_file = read(*file_paths) + version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", + version_file, re.M) + if version_match: + return version_match.group(1) + raise RuntimeError("Unable to find version string.") + long_description = """ The main website for pip is `www.pip-installer.org @@ -16,17 +25,13 @@ of pip with ``easy_install pip==dev``. """ -f = open(index_filename) # remove the toctree from sphinx index, as it breaks long_description -parts = f.read().split("split here", 2) -long_description = parts[0] + long_description + parts[2] -f.close() -f = open(news_filename) -long_description += "\n\n" + f.read() -f.close() +parts = read("docs", "index.txt").split("split here", 2) +long_description = (parts[0] + long_description + parts[2] + + "\n\n" + read("docs", "news.txt")) setup(name="pip", - version=version, + version=find_version('pip', '__init__.py'), description="pip installs packages. Python packages. An easy_install replacement", long_description=long_description, classifiers=[ From 95ff6fc32b1a8ea1aba5c4d94adec623be7575d9 Mon Sep 17 00:00:00 2001 From: Jannis Leidel <jannis@leidel.info> Date: Sat, 1 Sep 2012 19:36:13 +0200 Subject: [PATCH 045/132] Moved __main__.py to correct location. --- __main__.py => pip/__main__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename __main__.py => pip/__main__.py (100%) diff --git a/__main__.py b/pip/__main__.py similarity index 100% rename from __main__.py rename to pip/__main__.py From 1525a63b5732e2a32f54ccc020301d7e6d191064 Mon Sep 17 00:00:00 2001 From: Jannis Leidel <jannis@leidel.info> Date: Sat, 1 Sep 2012 20:34:44 +0200 Subject: [PATCH 046/132] Added a get_prog util to correctly show the name of the program when using python -m pip. Also show the full path in the missing command error message. --- pip/__init__.py | 5 +++-- pip/__main__.py | 8 ++++---- pip/basecommand.py | 3 ++- pip/baseparser.py | 11 +++++++++-- pip/commands/install.py | 8 ++++---- pip/util.py | 11 ++++++++--- 6 files changed, 30 insertions(+), 16 deletions(-) diff --git a/pip/__init__.py b/pip/__init__.py index 47fe3a7900e..f094a1d4db8 100755 --- a/pip/__init__.py +++ b/pip/__init__.py @@ -10,7 +10,7 @@ from pip.baseparser import parser from pip.exceptions import InstallationError from pip.log import logger -from pip.util import get_installed_distributions +from pip.util import get_installed_distributions, get_prog from pip.vcs import git, mercurial, subversion, bazaar @@ -91,7 +91,8 @@ def main(initial_args=None): if options.help and not args: args = ['help'] if not args: - parser.error('You must give a command (use "pip help" to see a list of commands)') + parser.error('You must give a command ' + '(use "%s help" to see a list of commands)' % get_prog()) command = args[0].lower() load_command(command) if command not in command_dict: diff --git a/pip/__main__.py b/pip/__main__.py index 258e87cf68a..5ca3746342c 100644 --- a/pip/__main__.py +++ b/pip/__main__.py @@ -1,7 +1,7 @@ -if __name__ == '__main__': - import sys - from . import main +import sys +from .runner import run - exit = main() +if __name__ == '__main__': + exit = run() if exit: sys.exit(exit) diff --git a/pip/basecommand.py b/pip/basecommand.py index d189d398708..2cea2f714ef 100644 --- a/pip/basecommand.py +++ b/pip/basecommand.py @@ -15,6 +15,7 @@ CommandError) from pip.backwardcompat import StringIO from pip.status_codes import SUCCESS, ERROR, UNKNOWN_ERROR, VIRTUALENV_NOT_FOUND +from pip.util import get_prog __all__ = ['command_dict', 'Command', 'load_all_commands', @@ -35,7 +36,7 @@ def __init__(self): assert self.name self.parser = ConfigOptionParser( usage=self.usage, - prog='%s %s' % (sys.argv[0], self.name), + prog='%s %s' % (get_prog(), self.name), version=parser.version, formatter=UpdatingDefaultsHelpFormatter(), name=self.name) diff --git a/pip/baseparser.py b/pip/baseparser.py index 310403a9929..e3f37a42a33 100644 --- a/pip/baseparser.py +++ b/pip/baseparser.py @@ -7,9 +7,10 @@ from distutils.util import strtobool from pip.backwardcompat import ConfigParser, string_types from pip.locations import default_config_file, default_log_file +from pip.util import get_terminal_size, get_prog -class PipPrettyHelpFormatter(optparse.IndentedHelpFormatter): +class PrettyHelpFormatter(optparse.IndentedHelpFormatter): """A prettier/less verbose help formatter for optparse.""" def __init__(self, *args, **kw): @@ -179,6 +180,11 @@ def get_default_values(self): defaults[option.dest] = option.check_value(opt_str, default) return optparse.Values(defaults) + def error(self, msg): + self.print_usage(sys.stderr) + self.exit(2, "%s\n" % msg) + + try: pip_dist = pkg_resources.get_distribution('pip') version = '%s from %s (python %s)' % ( @@ -192,7 +198,8 @@ def get_default_values(self): version=version, add_help_option=False, formatter=UpdatingDefaultsHelpFormatter(), - name='global') + name='global', + prog=get_prog()) parser.add_option( '-h', '--help', diff --git a/pip/commands/install.py b/pip/commands/install.py index a067f31c95f..9900c434aca 100644 --- a/pip/commands/install.py +++ b/pip/commands/install.py @@ -37,7 +37,7 @@ def __init__(self): action='append', default=[], metavar='FILENAME', - help='Install all the packages listed in the given requirements file. ' + help='Install all the packages listed in the given requirements file. ' 'This option can be used multiple times.') self.parser.add_option( '-f', '--find-links', @@ -149,15 +149,15 @@ def __init__(self): dest='install_options', action='append', help="Extra arguments to be supplied to the setup.py install " - "command (use like --install-option=\"--install-scripts=/usr/local/bin\"). " - "Use multiple --install-option options to pass multiple options to setup.py install. " + "command (use like --install-option=\"--install-scripts=/usr/local/bin\"). " + "Use multiple --install-option options to pass multiple options to setup.py install. " "If you are using an option with a directory path, be sure to use absolute path.") self.parser.add_option( '--global-option', dest='global_options', action='append', - help="Extra global options to be supplied to the setup.py" + help="Extra global options to be supplied to the setup.py " "call before the install command") self.parser.add_option( diff --git a/pip/util.py b/pip/util.py index 833a7adcc7a..579e5944441 100644 --- a/pip/util.py +++ b/pip/util.py @@ -20,11 +20,17 @@ 'is_svn_page', 'file_contents', 'split_leading_dir', 'has_leading_dir', 'make_path_relative', 'normalize_path', - 'renames', 'get_terminal_size', + 'renames', 'get_terminal_size', 'get_prog', 'unzip_file', 'untar_file', 'create_download_cache_folder', 'cache_download', 'unpack_file', 'call_subprocess'] +def get_prog(): + if sys.argv and os.path.basename(sys.argv[0]) in ('__main__.py', '-c'): + return "%s -m pip" % sys.executable + return sys.argv[0] + + def rmtree(dir, ignore_errors=False): shutil.rmtree(dir, ignore_errors=ignore_errors, onerror=rmtree_errorhandler) @@ -344,8 +350,7 @@ def egg_link_path(dist): For #1 and #3, there could be odd cases, where there's an egg-link in 2 locations. This method will just return the first one found. """ - - sites=[] + sites = [] if running_under_virtualenv(): if virtualenv_no_global(): sites.append(site_packages) From 1447d7d2acb9ff8ec11cc2fa273c070c7362445f Mon Sep 17 00:00:00 2001 From: Jannis Leidel <jannis@leidel.info> Date: Sat, 1 Sep 2012 20:35:19 +0200 Subject: [PATCH 047/132] Use our own terminal size util. --- pip/baseparser.py | 50 +++++++++++++++++++++++------------------------ setup.py | 2 +- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/pip/baseparser.py b/pip/baseparser.py index e3f37a42a33..0ebdd8f393c 100644 --- a/pip/baseparser.py +++ b/pip/baseparser.py @@ -13,17 +13,11 @@ class PrettyHelpFormatter(optparse.IndentedHelpFormatter): """A prettier/less verbose help formatter for optparse.""" - def __init__(self, *args, **kw): - kw['max_help_position'] = 23 - kw['indent_increment'] = 1 - - # do as argparse does - try: - kw['width'] = int(os.environ['COLUMNS']) - 2 - except: - kw['width'] = 78 - - optparse.IndentedHelpFormatter.__init__(self, *args, **kw) + def __init__(self, *args, **kwargs): + kwargs['max_help_position'] = 30 + kwargs['indent_increment'] = 1 + kwargs['width'] = get_terminal_size()[0] - 2 + optparse.IndentedHelpFormatter.__init__(self, *args, **kwargs) def format_option_strings(self, option): return self._format_option_strings(option, ' <%s>', ', ') @@ -36,31 +30,34 @@ def _format_option_strings(self, option, mvarfmt=' <%s>', optsep=', '): :param mvarfmt: metavar format string - evaluated as mvarfmt % metavar :param optsep: separator """ - opts = [] - if option._short_opts: opts.append(option._short_opts[0]) - if option._long_opts: opts.append(option._long_opts[0]) - if len(opts) > 1: opts.insert(1, optsep) + if option._short_opts: + opts.append(option._short_opts[0]) + if option._long_opts: + opts.append(option._long_opts[0]) + if len(opts) > 1: + opts.insert(1, optsep) if option.takes_value(): metavar = option.metavar or option.dest.lower() - opts.append(mvarfmt % metavar) + opts.append(mvarfmt % metavar.upper()) return ''.join(opts) def format_heading(self, heading): - if heading == 'Options': return '' + if heading == 'Options': + return '' return heading + ':\n' def format_usage(self, usage): - # ensure there is only one newline between usage and the first heading - # if there is no description - + """ + ensure there is only one newline between usage and the first heading + if there is no description + """ msg = 'Usage: %s' % usage if self.parser.description: msg += '\n' - return msg def format_description(self, description): @@ -78,7 +75,7 @@ def format_epilog(self, epilog): return '' -class UpdatingDefaultsHelpFormatter(PipPrettyHelpFormatter): +class UpdatingDefaultsHelpFormatter(PrettyHelpFormatter): """Custom help formatter for use in ConfigOptionParser that updates the defaults before expanding them, allowing them to show up correctly in the help listing""" @@ -149,7 +146,7 @@ def normalize_keys(self, items): for key, val in items: key = key.replace('_', '-') if not key.startswith('--'): - key = '--%s' % key # only prefer long opts + key = '--%s' % key # only prefer long opts normalized[key] = val return normalized @@ -172,7 +169,7 @@ def get_default_values(self): # Old, pre-Optik 1.5 behaviour. return optparse.Values(self.defaults) - defaults = self.update_defaults(self.defaults.copy()) # ours + defaults = self.update_defaults(self.defaults.copy()) # ours for option in self._get_all_options(): default = defaults.get(option.dest) if isinstance(default, string_types): @@ -191,7 +188,8 @@ def error(self, msg): pip_dist, pip_dist.location, sys.version[:3]) except pkg_resources.DistributionNotFound: # when running pip.py without installing - version=None + version = None + parser = ConfigOptionParser( usage='%prog COMMAND [OPTIONS]', @@ -260,7 +258,7 @@ def error(self, msg): default='', help="Specify a proxy in the form user:passwd@proxy.server:port. " "Note that the user:password@ is optional and required only if you " - "are behind an authenticated proxy. If you provide " + "are behind an authenticated proxy. If you provide " "user@proxy.server:port then you will be prompted for a password.") parser.add_option( '--timeout', '--default-timeout', diff --git a/setup.py b/setup.py index 77b39595d02..37e19b17668 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ def find_version(*file_paths): long_description = """ The main website for pip is `www.pip-installer.org -<http://www.pip-installer.org>`_. You can also install +<http://www.pip-installer.org>`_. You can also install the `in-development version <https://github.com/pypa/pip/tarball/develop#egg=pip-dev>`_ of pip with ``easy_install pip==dev``. From 5c3f415f236ba3e39b9e285f36ad5372fd1110a0 Mon Sep 17 00:00:00 2001 From: Jannis Leidel <jannis@leidel.info> Date: Sat, 1 Sep 2012 20:58:39 +0200 Subject: [PATCH 048/132] Trying to fix a werid test failure. --- pip/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pip/util.py b/pip/util.py index 579e5944441..751ddf9dc13 100644 --- a/pip/util.py +++ b/pip/util.py @@ -26,7 +26,7 @@ def get_prog(): - if sys.argv and os.path.basename(sys.argv[0]) in ('__main__.py', '-c'): + if hasattr(sys, 'argv') and sys.argv and os.path.basename(sys.argv[0]) in ('__main__.py', '-c'): return "%s -m pip" % sys.executable return sys.argv[0] From 7492f9c23a79698d4f0d16bc547715eed35d1e53 Mon Sep 17 00:00:00 2001 From: Jannis Leidel <jannis@leidel.info> Date: Sat, 1 Sep 2012 21:06:29 +0200 Subject: [PATCH 049/132] Safeguard against sys.argv not existing. --- pip/util.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pip/util.py b/pip/util.py index 751ddf9dc13..579592be76e 100644 --- a/pip/util.py +++ b/pip/util.py @@ -28,7 +28,10 @@ def get_prog(): if hasattr(sys, 'argv') and sys.argv and os.path.basename(sys.argv[0]) in ('__main__.py', '-c'): return "%s -m pip" % sys.executable - return sys.argv[0] + try: + return sys.argv[0] + except (TypeError, IndexError): + return 'pip' def rmtree(dir, ignore_errors=False): From 7760a01ddc9d378391eac0c51cac406be9360b3b Mon Sep 17 00:00:00 2001 From: Jannis Leidel <jannis@leidel.info> Date: Sat, 1 Sep 2012 21:14:07 +0200 Subject: [PATCH 050/132] Simplified get_prog to not trigger any exception. --- pip/util.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pip/util.py b/pip/util.py index 579592be76e..f91b1d23fee 100644 --- a/pip/util.py +++ b/pip/util.py @@ -26,12 +26,12 @@ def get_prog(): - if hasattr(sys, 'argv') and sys.argv and os.path.basename(sys.argv[0]) in ('__main__.py', '-c'): - return "%s -m pip" % sys.executable try: - return sys.argv[0] - except (TypeError, IndexError): - return 'pip' + if os.path.basename(sys.argv[0]) in ('__main__.py', '-c'): + return "%s -m pip" % sys.executable + except (AttributeError, TypeError, IndexError): + pass + return 'pip' def rmtree(dir, ignore_errors=False): From f6aae6069f1b97f661edc7db54f7a075e4b81c85 Mon Sep 17 00:00:00 2001 From: Jannis Leidel <jannis@leidel.info> Date: Sat, 1 Sep 2012 21:21:30 +0200 Subject: [PATCH 051/132] Fixed a failing test. --- tests/test_completion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_completion.py b/tests/test_completion.py index 9381cf11d1b..9a8a451f0ab 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -45,7 +45,7 @@ def test_completion_for_unknown_shell(): Test getting completion for an unknown shell """ reset_env() - error_msg = 'error: no such option: --myfooshell' + error_msg = 'no such option: --myfooshell' result = run_pip('completion', '--myfooshell', expect_error=True) assert error_msg in result.stderr, 'tests for an unknown shell failed' From 99e28f072b4c3361addc82a9f709feb6cc4c4e2c Mon Sep 17 00:00:00 2001 From: Marcus Smith <qwcode@gmail.com> Date: Mon, 3 Sep 2012 10:55:10 -0700 Subject: [PATCH 052/132] just log warning and return when no paths, don't raise --- pip/req.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pip/req.py b/pip/req.py index 98665f86953..57141150dc7 100644 --- a/pip/req.py +++ b/pip/req.py @@ -1463,10 +1463,11 @@ def _stash(self, path): def remove(self, auto_confirm=False): """Remove paths in ``self.paths`` with confirmation (unless ``auto_confirm`` is True).""" - if not self.paths: - raise InstallationError("Can't uninstall '%s'. No files were found to uninstall." % self.dist.project_name) if not self._can_uninstall(): return + if not self.paths: + logger.notify("Can't uninstall '%s'. No files were found to uninstall." % self.dist.project_name) + return logger.notify('Uninstalling %s:' % self.dist.project_name) logger.indent += 2 paths = sorted(self.compact(self.paths)) From ab335810d2ca2fc894181db92cfc9ffd9990922d Mon Sep 17 00:00:00 2001 From: Marcus Smith <qwcode@gmail.com> Date: Mon, 3 Sep 2012 10:55:29 -0700 Subject: [PATCH 053/132] fix test for no paths in uninstall set --- tests/test_uninstall.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/test_uninstall.py b/tests/test_uninstall.py index 28339a6152d..089ac944cb6 100644 --- a/tests/test_uninstall.py +++ b/tests/test_uninstall.py @@ -2,7 +2,7 @@ import sys from os.path import join, abspath, normpath from tempfile import mkdtemp -from mock import Mock +from mock import Mock, patch from nose.tools import assert_raises from tests.test_pip import here, reset_env, run_pip, assert_all_changes, write_file, pyversion from tests.local_repos import local_repo, local_checkout @@ -200,13 +200,17 @@ def test_uninstall_as_egg(): assert_all_changes(result, result2, [env.venv/'build', 'cache']) -def test_uninstallpathset_no_paths(): +@patch('pip.req.logger') +def test_uninstallpathset_no_paths(mock_logger): """ - Test UninstallPathSet raises installation error when there are no paths (uses mocking) + Test UninstallPathSet logs warning when there are no paths to uninstall """ from pip.req import UninstallPathSet from pip.exceptions import InstallationError - mock_dist = Mock(project_name='pkg') - uninstall_set = UninstallPathSet(mock_dist) - assert_raises(InstallationError, uninstall_set.remove) + from pkg_resources import get_distribution + test_dist = get_distribution('pip') + uninstall_set = UninstallPathSet(test_dist) + uninstall_set.remove() #with no files added to set + mock_logger.notify.assert_any_call("Can't uninstall 'pip'. No files were found to uninstall.") + From cf23cb609f748d3330b6254b93652699d8052601 Mon Sep 17 00:00:00 2001 From: Marcus Smith <qwcode@gmail.com> Date: Tue, 4 Sep 2012 12:40:50 -0700 Subject: [PATCH 054/132] regression test for issue #656 --- tests/test_uninstall.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/test_uninstall.py b/tests/test_uninstall.py index 089ac944cb6..7a81960ef1b 100644 --- a/tests/test_uninstall.py +++ b/tests/test_uninstall.py @@ -203,7 +203,7 @@ def test_uninstall_as_egg(): @patch('pip.req.logger') def test_uninstallpathset_no_paths(mock_logger): """ - Test UninstallPathSet logs warning when there are no paths to uninstall + Test UninstallPathSet logs notification when there are no paths to uninstall """ from pip.req import UninstallPathSet @@ -214,3 +214,21 @@ def test_uninstallpathset_no_paths(mock_logger): uninstall_set.remove() #with no files added to set mock_logger.notify.assert_any_call("Can't uninstall 'pip'. No files were found to uninstall.") + +@patch('pip.req.logger') +def test_uninstallpathset_non_local(mock_logger): + """ + Test UninstallPathSet logs notification and returns (with no exception) when dist is non-local + + """ + from pip.req import UninstallPathSet + from pip.exceptions import InstallationError + from pkg_resources import get_distribution + test_dist = get_distribution('pip') + test_dist.location = '/NON_LOCAL' + uninstall_set = UninstallPathSet(test_dist) + uninstall_set.remove() #with no files added to set; which is the case when trying to remove non-local dists + mock_logger.notify.assert_any_call("Not uninstalling pip at /NON_LOCAL, outside environment %s" % sys.prefix) + + + From 597e684d0e9d95fdb59baffe67093b3594cca929 Mon Sep 17 00:00:00 2001 From: Marcus Smith <qwcode@gmail.com> Date: Tue, 4 Sep 2012 15:13:50 -0700 Subject: [PATCH 055/132] py2.7 freeze variants in travis --- tests/test_freeze.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_freeze.py b/tests/test_freeze.py index 4412c143540..7e2a8b3eae5 100644 --- a/tests/test_freeze.py +++ b/tests/test_freeze.py @@ -91,7 +91,7 @@ def test_freeze_git_clone(): expected = textwrap.dedent("""\ Script result: ...pip freeze -- stdout: -------------------- - -e %s@...#egg=pip_test_package-... + ...-e %s@...#egg=pip_test_package-... ...""" % local_checkout('git+http://github.com/pypa/pip-test-package.git')) _check_output(result, expected) @@ -101,7 +101,7 @@ def test_freeze_git_clone(): expected = textwrap.dedent("""\ Script result: pip freeze -f %(repo)s#egg=pip_test_package -- stdout: -------------------- - -f %(repo)s#egg=pip_test_package + -f %(repo)s#egg=pip_test_package... -e %(repo)s@...#egg=pip_test_package-dev ...""" % {'repo': local_checkout('git+http://github.com/pypa/pip-test-package.git')}) _check_output(result, expected) @@ -124,7 +124,7 @@ def test_freeze_mercurial_clone(): expected = textwrap.dedent("""\ Script result: ...pip freeze -- stdout: -------------------- - -e %s@...#egg=django_authority-... + ...-e %s@...#egg=django_authority-... ...""" % local_checkout('hg+http://bitbucket.org/jezdez/django-authority')) _check_output(result, expected) @@ -135,7 +135,7 @@ def test_freeze_mercurial_clone(): Script result: ...pip freeze -f %(repo)s#egg=django_authority -- stdout: -------------------- -f %(repo)s#egg=django_authority - -e %(repo)s@...#egg=django_authority-dev + ...-e %(repo)s@...#egg=django_authority-dev ...""" % {'repo': local_checkout('hg+http://bitbucket.org/jezdez/django-authority')}) _check_output(result, expected) @@ -156,7 +156,7 @@ def test_freeze_bazaar_clone(): expected = textwrap.dedent("""\ Script result: ...pip freeze -- stdout: -------------------- - -e %s@...#egg=django_wikiapp-... + ...-e %s@...#egg=django_wikiapp-... ...""" % local_checkout('bzr+http://bazaar.launchpad.net/%7Edjango-wikiapp/django-wikiapp/release-0.1')) _check_output(result, expected) @@ -168,7 +168,7 @@ def test_freeze_bazaar_clone(): Script result: ...pip freeze -f %(repo)s/#egg=django-wikiapp -- stdout: -------------------- -f %(repo)s/#egg=django-wikiapp - -e %(repo)s@...#egg=django_wikiapp-... + ...-e %(repo)s@...#egg=django_wikiapp-... ...""" % {'repo': local_checkout('bzr+http://bazaar.launchpad.net/%7Edjango-wikiapp/django-wikiapp/release-0.1')}) _check_output(result, expected) From 5230dd2016b874f782f3e6ffc59057349ac942c2 Mon Sep 17 00:00:00 2001 From: enoch <lanxenet@gmail.com> Date: Wed, 5 Sep 2012 18:04:09 +0800 Subject: [PATCH 056/132] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=85=8D=E7=BD=AEPAT?= =?UTF-8?q?HEXT=E6=9C=80=E5=90=8E=E4=B8=80=E4=B8=AA=E5=AD=97=E7=AC=A6?= =?UTF-8?q?=E5=A6=82=E6=9E=9C=E6=98=AF=E5=88=86=E5=8F=B7=E7=9A=84=E6=83=85?= =?UTF-8?q?=E5=86=B5=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pip/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pip/util.py b/pip/util.py index f91b1d23fee..fcdcb430047 100644 --- a/pip/util.py +++ b/pip/util.py @@ -87,7 +87,7 @@ def find_command(cmd, paths=None, pathext=None): # check if there are funny path extensions for executables, e.g. Windows if pathext is None: pathext = get_pathext() - pathext = [ext for ext in pathext.lower().split(os.pathsep)] + pathext = [ext for ext in pathext.lower().split(os.pathsep) if len(ext)] # don't use extensions if the command ends with one of them if os.path.splitext(cmd)[1].lower() in pathext: pathext = [''] From 0d27d5aa0a84ac2f29f3a3346bf3655a21889868 Mon Sep 17 00:00:00 2001 From: Garry Polley <garrympolley@gmail.com> Date: Wed, 5 Sep 2012 15:55:34 -0500 Subject: [PATCH 057/132] Fixing import error for tempfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A change was made to add extra logging whenever pip log files cannot be added.  Here is the changeset that caused the issue:  https://github.com/pypa/pip/commit/8ebc7a316016c4eb01cb77c8df1fe538c7bddf2a#pip/basecommand.py --- pip/basecommand.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pip/basecommand.py b/pip/basecommand.py index 2cea2f714ef..a4eece92bbf 100644 --- a/pip/basecommand.py +++ b/pip/basecommand.py @@ -4,6 +4,7 @@ from pkgutil import walk_packages import socket import sys +import tempfile import traceback import time From ac2c3fed396373b49bccd75d683eeb1406981918 Mon Sep 17 00:00:00 2001 From: Jannis Leidel <jannis@leidel.info> Date: Thu, 6 Sep 2012 10:30:13 +0200 Subject: [PATCH 058/132] Bumped up the version a notch and updated changelog. --- docs/news.txt | 7 +++++++ pip/__init__.py | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/news.txt b/docs/news.txt index 194754c2a17..ace21bc6be5 100644 --- a/docs/news.txt +++ b/docs/news.txt @@ -10,6 +10,13 @@ Next release (1.3) schedule Beta and final releases planned for the end of 2012. +1.2.1 (2012-09-06) +------------------ + +* Fixed a regression introduced in 1.2 about raising an exception when + not finding any files to uninstall in the current environment. Thanks for + the fix, Marcus Smith. + 1.2 (2012-09-01) ---------------- diff --git a/pip/__init__.py b/pip/__init__.py index f094a1d4db8..7b4f663303b 100755 --- a/pip/__init__.py +++ b/pip/__init__.py @@ -11,11 +11,11 @@ from pip.exceptions import InstallationError from pip.log import logger from pip.util import get_installed_distributions, get_prog -from pip.vcs import git, mercurial, subversion, bazaar +from pip.vcs import git, mercurial, subversion, bazaar # noqa # The version as used in the setup.py and the docs conf.py -__version__ = "1.2" +__version__ = "1.2.1" def autocomplete(): From 05ebae2a2083249581b07646e82fab068298eb75 Mon Sep 17 00:00:00 2001 From: Chris McDonough <chrism@plope.com> Date: Fri, 7 Sep 2012 10:45:39 -0400 Subject: [PATCH 059/132] Non-user-facing: setup.py dev in pip checkout will run setup.py develop and then cause test_requires requirements to be installed --- AUTHORS.txt | 1 + setup.cfg | 3 +++ setup.py | 10 ++++++++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/AUTHORS.txt b/AUTHORS.txt index 97c9c981113..cb5955f209f 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -56,3 +56,4 @@ Vitaly Babiy W Trevor King Wil Tan Hsiaoming Yang +Chris McDonough diff --git a/setup.cfg b/setup.cfg index ce26f6afe4d..cffcdfd46ba 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,5 @@ [nosetests] where=tests + +[aliases] +dev = develop easy_install pip[testing] diff --git a/setup.py b/setup.py index 37e19b17668..4ccc1025b2b 100644 --- a/setup.py +++ b/setup.py @@ -30,6 +30,8 @@ def find_version(*file_paths): long_description = (parts[0] + long_description + parts[2] + "\n\n" + read("docs", "news.txt")) +tests_require = ['nose', 'virtualenv>=1.7', 'scripttest>=1.1.1', 'mock'] + setup(name="pip", version=find_version('pip', '__init__.py'), description="pip installs packages. Python packages. An easy_install replacement", @@ -55,5 +57,9 @@ def find_version(*file_paths): packages=['pip', 'pip.commands', 'pip.vcs'], entry_points=dict(console_scripts=['pip=pip:main', 'pip-%s=pip:main' % sys.version[:3]]), test_suite='nose.collector', - tests_require=['nose', 'virtualenv>=1.7', 'scripttest>=1.1.1', 'mock'], - zip_safe=False) + tests_require=tests_require, + zip_safe=False, + extras_require = { + 'testing':tests_require, + }, + ) From 931408b965fa0abfe09546adaaed4b40285a49a5 Mon Sep 17 00:00:00 2001 From: Carl Meyer <carl@oddbird.net> Date: Fri, 7 Sep 2012 09:48:57 -0600 Subject: [PATCH 060/132] Bump develop branch to a post-release version. --- pip/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pip/__init__.py b/pip/__init__.py index 7b4f663303b..e0de1c61a52 100755 --- a/pip/__init__.py +++ b/pip/__init__.py @@ -15,7 +15,7 @@ # The version as used in the setup.py and the docs conf.py -__version__ = "1.2.1" +__version__ = "1.2.1.post1" def autocomplete(): From 469fa69793ed752ac6e21c383b3aa72945422fd1 Mon Sep 17 00:00:00 2001 From: Carl Meyer <carl@oddbird.net> Date: Fri, 7 Sep 2012 10:32:45 -0600 Subject: [PATCH 061/132] Fix a couple tests that were too sensitive to how pip is installed. --- tests/test_uninstall.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/tests/test_uninstall.py b/tests/test_uninstall.py index 7a81960ef1b..359d59688b1 100644 --- a/tests/test_uninstall.py +++ b/tests/test_uninstall.py @@ -1,9 +1,10 @@ +from __future__ import with_statement + import textwrap import sys from os.path import join, abspath, normpath from tempfile import mkdtemp -from mock import Mock, patch -from nose.tools import assert_raises +from mock import patch from tests.test_pip import here, reset_env, run_pip, assert_all_changes, write_file, pyversion from tests.local_repos import local_repo, local_checkout @@ -207,11 +208,13 @@ def test_uninstallpathset_no_paths(mock_logger): """ from pip.req import UninstallPathSet - from pip.exceptions import InstallationError from pkg_resources import get_distribution test_dist = get_distribution('pip') - uninstall_set = UninstallPathSet(test_dist) - uninstall_set.remove() #with no files added to set + # ensure that the distribution is "local" + with patch("pip.req.dist_is_local") as mock_dist_is_local: + mock_dist_is_local.return_value = True + uninstall_set = UninstallPathSet(test_dist) + uninstall_set.remove() #with no files added to set mock_logger.notify.assert_any_call("Can't uninstall 'pip'. No files were found to uninstall.") @@ -222,12 +225,16 @@ def test_uninstallpathset_non_local(mock_logger): """ from pip.req import UninstallPathSet - from pip.exceptions import InstallationError from pkg_resources import get_distribution test_dist = get_distribution('pip') - test_dist.location = '/NON_LOCAL' - uninstall_set = UninstallPathSet(test_dist) - uninstall_set.remove() #with no files added to set; which is the case when trying to remove non-local dists + test_dist.location = "/NON_LOCAL" + # ensure that the distribution is "non-local" + # setting location isn't enough, due to egg-link file checking for + # develop-installs + with patch("pip.req.dist_is_local") as mock_dist_is_local: + mock_dist_is_local.return_value = False + uninstall_set = UninstallPathSet(test_dist) + uninstall_set.remove() #with no files added to set; which is the case when trying to remove non-local dists mock_logger.notify.assert_any_call("Not uninstalling pip at /NON_LOCAL, outside environment %s" % sys.prefix) From 76bbecd42e7f38cab5a6c9edeb4926abafe7504b Mon Sep 17 00:00:00 2001 From: Rafael Caricio <rafael.jacinto@gmail.com> Date: Fri, 7 Sep 2012 15:56:00 -0400 Subject: [PATCH 062/132] Added the patch code sent by @vbabiy. --- pip/commands/status.py | 51 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 pip/commands/status.py diff --git a/pip/commands/status.py b/pip/commands/status.py new file mode 100644 index 00000000000..4faa9e347d0 --- /dev/null +++ b/pip/commands/status.py @@ -0,0 +1,51 @@ +import os +import pkg_resources +from pip.basecommand import Command +from pip.log import logger + + +class StatusCommand(Command): + name = 'status' + usage = '%prog QUERY' + summary = 'Output installed distributions (exact versions, files) to stdout' + + def __init__(self): + super(StatusCommand, self).__init__() + + def run(self, options, args): + if not args: + logger.warn('ERROR: Missing required argument (status query).') + return + query = args + + print_results(query) + + +def print_results(query): + """ + Gather details from installed distributions. Print distribution name, + version, location, and installed files. Installed files requires a + pip generated 'installed-files.txt' in the distributions '.egg-info' + directory. + """ + installed_packages = [p.project_name for p in pkg_resources.working_set] + for name in query: + if name in installed_packages: + dist = pkg_resources.get_distribution(name) + logger.notify("---") + logger.notify("Name: %s" % name) + logger.notify("Version: %s" % dist.version) + logger.notify("Location: %s" % dist.location) + logger.notify("Files:") + filelist = os.path.join( + dist.location, + dist.egg_name() + '.egg-info', + 'installed-files.txt') + if os.path.isfile(filelist): + for line in open(filelist): + logger.notify(" %s" % line.strip()) + else: + logger.notify("Cannot locate installed-files.txt") + + +StatusCommand() From 44470d1bd013e65b266a6d8ee4d40b7a5c403ef2 Mon Sep 17 00:00:00 2001 From: Rafael Caricio <rafael.jacinto@gmail.com> Date: Fri, 7 Sep 2012 16:01:34 -0400 Subject: [PATCH 063/132] Remove blank spaces in the end of the line. --- pip/basecommand.py | 2 +- tests/test_status.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 tests/test_status.py diff --git a/pip/basecommand.py b/pip/basecommand.py index a4eece92bbf..48c390d9cef 100644 --- a/pip/basecommand.py +++ b/pip/basecommand.py @@ -146,7 +146,7 @@ def main(self, args, initial_options): temp = tempfile.NamedTemporaryFile(delete=False) log_fn = temp.name log_fp = open_logfile(log_fn, 'w') - logger.fatal('Storing complete log in %s' % log_fn) + logger.fatal('Storing complete log in %s' % log_fn) log_fp.write(text) log_fp.close() return exit diff --git a/tests/test_status.py b/tests/test_status.py new file mode 100644 index 00000000000..addf94ec52f --- /dev/null +++ b/tests/test_status.py @@ -0,0 +1,2 @@ + +from tests.test_pip import reset_env, run_pip From 0f09b5ccd78abe6dd755814fb0bd7f85c773e624 Mon Sep 17 00:00:00 2001 From: Rafael Caricio <rafael.jacinto@gmail.com> Date: Fri, 7 Sep 2012 16:31:56 -0400 Subject: [PATCH 064/132] First test for status command. --- tests/test_status.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/test_status.py b/tests/test_status.py index addf94ec52f..f40f0676b48 100644 --- a/tests/test_status.py +++ b/tests/test_status.py @@ -1,2 +1,21 @@ - +import re +import pkg_resources +from pip import __version__ from tests.test_pip import reset_env, run_pip + +def test_status(): + """ + Test end to end test for status command. + + """ + dist = pkg_resources.get_distribution('pip') + reset_env() + result = run_pip('status', 'pip') + lines = result.stdout.split('\n') + assert 7 == len(lines) + assert '---', lines[0] + assert re.match('^Name\: pip$', lines[1]) + assert re.match('^Version\: %s$' % __version__, lines[2]) + assert 'Location: %s' % dist.location, lines[3] + assert 'Files:' == lines[4] + assert 'Cannot locate installed-files.txt' == lines[5] From b78dff8d2c646bce3e823e533a1175113c6bf9a0 Mon Sep 17 00:00:00 2001 From: Rafael Caricio <rafael.jacinto@gmail.com> Date: Fri, 7 Sep 2012 17:17:04 -0400 Subject: [PATCH 065/132] Refactory and support to package name in any case. --- pip/commands/status.py | 53 ++++++++++++++++++++++++++++-------------- tests/test_status.py | 22 +++++++++++++++++- 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/pip/commands/status.py b/pip/commands/status.py index 4faa9e347d0..548729ded81 100644 --- a/pip/commands/status.py +++ b/pip/commands/status.py @@ -18,34 +18,51 @@ def run(self, options, args): return query = args - print_results(query) + results = search_packages_info(query) + print_results(results) - -def print_results(query): +def search_packages_info(query): """ Gather details from installed distributions. Print distribution name, version, location, and installed files. Installed files requires a pip generated 'installed-files.txt' in the distributions '.egg-info' directory. """ - installed_packages = [p.project_name for p in pkg_resources.working_set] + installed_packages = dict([(p.project_name.lower(), p.project_name) for p in pkg_resources.working_set]) for name in query: - if name in installed_packages: - dist = pkg_resources.get_distribution(name) - logger.notify("---") - logger.notify("Name: %s" % name) - logger.notify("Version: %s" % dist.version) - logger.notify("Location: %s" % dist.location) - logger.notify("Files:") + normalized_name = name.lower() + if normalized_name in installed_packages: + dist = pkg_resources.get_distribution(installed_packages[normalized_name]) + package = { + 'name': dist.project_name, + 'version': dist.version, + 'location': dist.location + } filelist = os.path.join( - dist.location, - dist.egg_name() + '.egg-info', - 'installed-files.txt') + dist.location, + dist.egg_name() + '.egg-info', + 'installed-files.txt') if os.path.isfile(filelist): - for line in open(filelist): - logger.notify(" %s" % line.strip()) - else: - logger.notify("Cannot locate installed-files.txt") + package['files'] = filelist + yield package + + +def print_results(distributions): + """ + Print the informations from installed distributions found. + """ + for dist in distributions: + logger.notify("---") + logger.notify("Name: %s" % dist['name']) + logger.notify("Version: %s" % dist['version']) + logger.notify("Location: %s" % dist['location']) + logger.notify("Files:") + filelist = dist.get('files', None) + if filelist: + for line in open(filelist): + logger.notify(" %s" % line.strip()) + else: + logger.notify("Cannot locate installed-files.txt") StatusCommand() diff --git a/tests/test_status.py b/tests/test_status.py index f40f0676b48..f1c3ea9fe3b 100644 --- a/tests/test_status.py +++ b/tests/test_status.py @@ -1,6 +1,7 @@ import re import pkg_resources from pip import __version__ +from pip.commands.status import search_packages_info from tests.test_pip import reset_env, run_pip def test_status(): @@ -12,10 +13,29 @@ def test_status(): reset_env() result = run_pip('status', 'pip') lines = result.stdout.split('\n') - assert 7 == len(lines) + assert len(lines) == 7 assert '---', lines[0] assert re.match('^Name\: pip$', lines[1]) assert re.match('^Version\: %s$' % __version__, lines[2]) assert 'Location: %s' % dist.location, lines[3] assert 'Files:' == lines[4] assert 'Cannot locate installed-files.txt' == lines[5] + + +def test_find_package_not_found(): + """ + Test trying to get info about a inexistent package. + + """ + result = search_packages_info(['abcd3']) + assert len(list(result)) == 0 + + +def test_search_any_case(): + """ + Search for a package in any case. + + """ + result = list(search_packages_info(['PIP'])) + assert len(result) == 1 + assert 'pip' == result[0]['name'] From 776fcbce9f23e799cd3101ddfa0bb966898d7064 Mon Sep 17 00:00:00 2001 From: Rafael Caricio <rafael.jacinto@gmail.com> Date: Fri, 7 Sep 2012 17:28:18 -0400 Subject: [PATCH 066/132] Test search for more than one distribution. --- tests/test_status.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/test_status.py b/tests/test_status.py index f1c3ea9fe3b..476621a2ecf 100644 --- a/tests/test_status.py +++ b/tests/test_status.py @@ -4,6 +4,7 @@ from pip.commands.status import search_packages_info from tests.test_pip import reset_env, run_pip + def test_status(): """ Test end to end test for status command. @@ -22,6 +23,17 @@ def test_status(): assert 'Cannot locate installed-files.txt' == lines[5] +def test_missing_argument(): + """ + Test status command with no arguments. + + """ + dist = pkg_resources.get_distribution('pip') + reset_env() + result = run_pip('status') + assert 'ERROR: Missing required argument (status query).' in result.stdout + + def test_find_package_not_found(): """ Test trying to get info about a inexistent package. @@ -39,3 +51,12 @@ def test_search_any_case(): result = list(search_packages_info(['PIP'])) assert len(result) == 1 assert 'pip' == result[0]['name'] + + +def test_more_than_one_package(): + """ + Search for more than one package. + + """ + result = list(search_packages_info(['Pip', 'Nose', 'Virtualenv'])) + assert len(result) == 3 From 16f76e24bd8eba8006f2a2040b2ba60e50119865 Mon Sep 17 00:00:00 2001 From: Rafael Caricio <rafael.jacinto@gmail.com> Date: Fri, 7 Sep 2012 17:44:49 -0400 Subject: [PATCH 067/132] Only print some files. --- pip/commands/status.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pip/commands/status.py b/pip/commands/status.py index 548729ded81..301e081a7f8 100644 --- a/pip/commands/status.py +++ b/pip/commands/status.py @@ -28,11 +28,13 @@ def search_packages_info(query): pip generated 'installed-files.txt' in the distributions '.egg-info' directory. """ - installed_packages = dict([(p.project_name.lower(), p.project_name) for p in pkg_resources.working_set]) + installed_packages = dict([(p.project_name.lower(), p.project_name) \ + for p in pkg_resources.working_set]) for name in query: normalized_name = name.lower() if normalized_name in installed_packages: - dist = pkg_resources.get_distribution(installed_packages[normalized_name]) + dist = pkg_resources.get_distribution( \ + installed_packages[normalized_name]) package = { 'name': dist.project_name, 'version': dist.version, @@ -59,8 +61,11 @@ def print_results(distributions): logger.notify("Files:") filelist = dist.get('files', None) if filelist: - for line in open(filelist): + for i, line in enumerate(open(filelist)): logger.notify(" %s" % line.strip()) + if i > 20: + logger.notify(" and more.") + break else: logger.notify("Cannot locate installed-files.txt") From 63459a27073bfc0e84cc49486bd2769d68c94fa2 Mon Sep 17 00:00:00 2001 From: Rafael Caricio <rafael.jacinto@gmail.com> Date: Fri, 7 Sep 2012 18:18:48 -0400 Subject: [PATCH 068/132] Little refactory. --- pip/commands/status.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pip/commands/status.py b/pip/commands/status.py index 301e081a7f8..a17a1b1d57c 100644 --- a/pip/commands/status.py +++ b/pip/commands/status.py @@ -21,6 +21,7 @@ def run(self, options, args): results = search_packages_info(query) print_results(results) + def search_packages_info(query): """ Gather details from installed distributions. Print distribution name, @@ -59,9 +60,8 @@ def print_results(distributions): logger.notify("Version: %s" % dist['version']) logger.notify("Location: %s" % dist['location']) logger.notify("Files:") - filelist = dist.get('files', None) - if filelist: - for i, line in enumerate(open(filelist)): + if 'files' in dist: + for i, line in enumerate(open(dist['files'])): logger.notify(" %s" % line.strip()) if i > 20: logger.notify(" and more.") From d95f2e1b45036677df1abdec80d347aa4d489dd2 Mon Sep 17 00:00:00 2001 From: Chris McDonough <chrism@plope.com> Date: Sat, 8 Sep 2012 10:18:25 -0400 Subject: [PATCH 069/132] add a tox.ini --- .gitignore | 2 ++ tox.ini | 8 ++++++++ 2 files changed, 10 insertions(+) create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index 0fe94900b6b..e5df8c83215 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ build/* pip-log.txt pip.log *.~ +.tox + diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000000..731f8a69111 --- /dev/null +++ b/tox.ini @@ -0,0 +1,8 @@ +[tox] +envlist = + py25,py26,py27,py32,py33,pypy + +[testenv] +commands = + python setup.py dev + python setup.py nosetests From 6d5a9e2fb2d82a08e7ac8110b67d400f8f8ce7ed Mon Sep 17 00:00:00 2001 From: Carl Meyer <carl@oddbird.net> Date: Sat, 8 Sep 2012 15:31:59 -0600 Subject: [PATCH 070/132] Minor cleanup. --- pip/commands/status.py | 2 +- tests/test_status.py | 20 ++++++++------------ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/pip/commands/status.py b/pip/commands/status.py index a17a1b1d57c..05b022f9f30 100644 --- a/pip/commands/status.py +++ b/pip/commands/status.py @@ -14,7 +14,7 @@ def __init__(self): def run(self, options, args): if not args: - logger.warn('ERROR: Missing required argument (status query).') + logger.warn('ERROR: Please provide a project name or names.') return query = args diff --git a/tests/test_status.py b/tests/test_status.py index 476621a2ecf..05bf5c00cd5 100644 --- a/tests/test_status.py +++ b/tests/test_status.py @@ -1,5 +1,3 @@ -import re -import pkg_resources from pip import __version__ from pip.commands.status import search_packages_info from tests.test_pip import reset_env, run_pip @@ -10,17 +8,16 @@ def test_status(): Test end to end test for status command. """ - dist = pkg_resources.get_distribution('pip') reset_env() result = run_pip('status', 'pip') lines = result.stdout.split('\n') assert len(lines) == 7 - assert '---', lines[0] - assert re.match('^Name\: pip$', lines[1]) - assert re.match('^Version\: %s$' % __version__, lines[2]) - assert 'Location: %s' % dist.location, lines[3] - assert 'Files:' == lines[4] - assert 'Cannot locate installed-files.txt' == lines[5] + assert lines[0] == '---', lines[0] + assert lines[1] == 'Name: pip', lines[1] + assert lines[2] == 'Version: %s' % __version__, lines[2] + assert lines[3].startswith('Location: '), lines[3] + assert lines[4] == 'Files:', lines[4] + assert lines[5] == 'Cannot locate installed-files.txt', lines[5] def test_missing_argument(): @@ -28,15 +25,14 @@ def test_missing_argument(): Test status command with no arguments. """ - dist = pkg_resources.get_distribution('pip') reset_env() result = run_pip('status') - assert 'ERROR: Missing required argument (status query).' in result.stdout + assert 'ERROR: Please provide a project name or names.' in result.stdout def test_find_package_not_found(): """ - Test trying to get info about a inexistent package. + Test trying to get info about a nonexistent package. """ result = search_packages_info(['abcd3']) From 1d6f5af736b5265ebb25392c2ed27b0e482ac412 Mon Sep 17 00:00:00 2001 From: Carl Meyer <carl@oddbird.net> Date: Sat, 8 Sep 2012 15:34:24 -0600 Subject: [PATCH 071/132] Add basic docs. --- docs/usage.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/usage.txt b/docs/usage.txt index 283f25db1c9..32bceff3861 100644 --- a/docs/usage.txt +++ b/docs/usage.txt @@ -147,6 +147,13 @@ packages. With the ``--index`` option you can search in a different repository. +Checking installed package status +--------------------------------- + +To get info about an installed package, including its location and included +files, run ``pip status ProjectName``. + + Bundles ------- From 6315bf98770d4fbd3bcbc34728de36a9f90c7651 Mon Sep 17 00:00:00 2001 From: Carl Meyer <carl@oddbird.net> Date: Sat, 8 Sep 2012 15:41:07 -0600 Subject: [PATCH 072/132] Update AUTHORS and changelog. --- AUTHORS.txt | 3 ++- docs/news.txt | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/AUTHORS.txt b/AUTHORS.txt index cb5955f209f..e19dca319f0 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -5,6 +5,7 @@ Antti Kaihola Armin Ronacher Brian Rosner Carl Meyer +Chris McDonough Christian Oudard Clay McClure Cody Soyland @@ -44,6 +45,7 @@ Peter Waller Phil Whelan Piet Delport Qiangning Hong +Rafael Caricio Rene Dudfield Ronny Pfannschmidt Rory McCann @@ -56,4 +58,3 @@ Vitaly Babiy W Trevor King Wil Tan Hsiaoming Yang -Chris McDonough diff --git a/docs/news.txt b/docs/news.txt index ace21bc6be5..0c2177824d5 100644 --- a/docs/news.txt +++ b/docs/news.txt @@ -10,6 +10,12 @@ Next release (1.3) schedule Beta and final releases planned for the end of 2012. + +develop (unreleased) +-------------------- + +* Added "pip status" command to get information about an installed package. + 1.2.1 (2012-09-06) ------------------ From 51b928ff4b13264fb8502d8c319beb4028b1e43a Mon Sep 17 00:00:00 2001 From: Carl Meyer <carl@oddbird.net> Date: Sat, 8 Sep 2012 15:42:10 -0600 Subject: [PATCH 073/132] Update news entry. --- docs/news.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/news.txt b/docs/news.txt index 0c2177824d5..498b59cc673 100644 --- a/docs/news.txt +++ b/docs/news.txt @@ -14,7 +14,8 @@ Beta and final releases planned for the end of 2012. develop (unreleased) -------------------- -* Added "pip status" command to get information about an installed package. +* Added "pip status" command to get information about an installed + package. Fixes #131. Thanks Kelsey Hightower and Rafael Caricio. 1.2.1 (2012-09-06) ------------------ From 60860379d8974188be9fb7e787b8c490742677a2 Mon Sep 17 00:00:00 2001 From: Carl Meyer <carl@oddbird.net> Date: Sun, 9 Sep 2012 10:51:25 -0600 Subject: [PATCH 074/132] Add note in docs that bundle feature may go away. --- docs/usage.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/usage.txt b/docs/usage.txt index 32bceff3861..09a9d4c7d46 100644 --- a/docs/usage.txt +++ b/docs/usage.txt @@ -157,6 +157,13 @@ files, run ``pip status ProjectName``. Bundles ------- +.. note:: + + Pip bundles are poorly supported, poorly tested, not widely used, and + unnecessary (the same goals can be achieved by distributing a directory of + sdists and using `--find-links` to reference it). The feature will likely be + removed in a future version of pip. + Another way to distribute a set of libraries is a bundle format (specific to pip). This format is not stable at this time (there simply hasn't been any feedback, nor a great deal of thought). A bundle file contains all the From 04f01f227eaeb0252e8863266492db47b1d99fac Mon Sep 17 00:00:00 2001 From: Paul Moore <p.f.moore@gmail.com> Date: Sun, 9 Sep 2012 21:15:32 +0100 Subject: [PATCH 075/132] Support local file paths in --find-links arguments --- pip/index.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pip/index.py b/pip/index.py index bf8b55cc15a..4136d05dc10 100644 --- a/pip/index.py +++ b/pip/index.py @@ -79,8 +79,12 @@ def sort_path(path): files.append(url) for url in locations: + is_path = os.path.exists(url) if url.startswith('file:'): - path = url_to_path(url) + if is_path: + path = url + else: + path = url_to_path(url) if os.path.isdir(path): path = os.path.realpath(path) for item in os.listdir(path): From 895bfee76715393ff93791b13052d5128f8ac588 Mon Sep 17 00:00:00 2001 From: Carl Meyer <carl@oddbird.net> Date: Mon, 10 Sep 2012 00:48:48 -0600 Subject: [PATCH 076/132] Allow --no-index and --find-links relative path in reqs file. --- pip/index.py | 24 ++++++++++++------------ pip/req.py | 7 +++++++ tests/test_basic.py | 1 - tests/test_find_links.py | 38 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 13 deletions(-) create mode 100644 tests/test_find_links.py diff --git a/pip/index.py b/pip/index.py index 4136d05dc10..f6b13baec67 100644 --- a/pip/index.py +++ b/pip/index.py @@ -20,7 +20,7 @@ from pip.util import normalize_name, splitext from pip.exceptions import DistributionNotFound, BestVersionAlreadyInstalled from pip.backwardcompat import (WindowsError, BytesIO, - Queue, httplib, urlparse, + Queue, urlparse, URLError, HTTPError, u, product, url2pathname) from pip.backwardcompat import Empty as QueueEmpty @@ -80,17 +80,17 @@ def sort_path(path): for url in locations: is_path = os.path.exists(url) - if url.startswith('file:'): - if is_path: - path = url - else: - path = url_to_path(url) - if os.path.isdir(path): - path = os.path.realpath(path) - for item in os.listdir(path): - sort_path(os.path.join(path, item)) - elif os.path.isfile(path): - sort_path(path) + path = None + if is_path: + path = url + elif url.startswith('file:'): + path = url_to_path(url) + if path and os.path.isdir(path): + path = os.path.realpath(path) + for item in os.listdir(path): + sort_path(os.path.join(path, item)) + elif path and os.path.isfile(path): + sort_path(path) else: urls.append(url) return files, urls diff --git a/pip/req.py b/pip/req.py index 57141150dc7..3550cca21fc 100644 --- a/pip/req.py +++ b/pip/req.py @@ -1273,6 +1273,7 @@ def parse_requirements(filename, finder=None, comes_from=None, options=None): skip_regex = options.skip_requirements_regex if options else None if skip_regex: skip_match = re.compile(skip_regex) + reqs_file_dir = os.path.dirname(os.path.abspath(filename)) filename, content = get_file_content(filename, comes_from=comes_from) for line_number, line in enumerate(content.splitlines()): line_number += 1 @@ -1304,6 +1305,10 @@ def parse_requirements(filename, finder=None, comes_from=None, options=None): line = line[len('--find-links'):].strip().lstrip('=') ## FIXME: it would be nice to keep track of the source of ## the find_links: + # support a find-links local path relative to a requirements file + relative_to_reqs_file = os.path.join(reqs_file_dir, line) + if os.path.exists(relative_to_reqs_file): + line = relative_to_reqs_file if finder: finder.find_links.append(line) elif line.startswith('-i') or line.startswith('--index-url'): @@ -1317,6 +1322,8 @@ def parse_requirements(filename, finder=None, comes_from=None, options=None): line = line[len('--extra-index-url'):].strip().lstrip('=') if finder: finder.index_urls.append(line) + elif line.startswith('--no-index'): + finder.index_urls = [] else: comes_from = '-r %s (line %s)' % (filename, line_number) if line.startswith('-e') or line.startswith('--editable'): diff --git a/tests/test_basic.py b/tests/test_basic.py index a49824abfb4..e1b990a3539 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -5,7 +5,6 @@ import sys from os.path import abspath, join, curdir, pardir -from nose import SkipTest from nose.tools import assert_raises from mock import patch diff --git a/tests/test_find_links.py b/tests/test_find_links.py new file mode 100644 index 00000000000..b9f3cde559f --- /dev/null +++ b/tests/test_find_links.py @@ -0,0 +1,38 @@ +import textwrap + +from tests.test_pip import reset_env, run_pip, pyversion, here, write_file + + +def test_find_links_relative_path(): + """Test find-links as a relative path.""" + e = reset_env() + result = run_pip( + 'install', + 'parent==0.1', + '--no-index', + '--find-links', + 'packages/', + cwd=here) + egg_info_folder = e.site_packages / 'parent-0.1-py%s.egg-info' % pyversion + initools_folder = e.site_packages / 'parent' + assert egg_info_folder in result.files_created, str(result) + assert initools_folder in result.files_created, str(result) + + +def test_find_links_requirements_file_relative_path(): + """Test find-links as a relative path to a reqs file.""" + e = reset_env() + write_file('test-req.txt', textwrap.dedent(""" + --no-index + --find-links=../../../packages/ + parent==0.1 + """)) + result = run_pip( + 'install', + '-r', + e.scratch_path / "test-req.txt", + cwd=here) + egg_info_folder = e.site_packages / 'parent-0.1-py%s.egg-info' % pyversion + initools_folder = e.site_packages / 'parent' + assert egg_info_folder in result.files_created, str(result) + assert initools_folder in result.files_created, str(result) From cc86de13d8fc37ab42c023e3602f444b935b6397 Mon Sep 17 00:00:00 2001 From: Carl Meyer <carl@oddbird.net> Date: Mon, 10 Sep 2012 00:51:53 -0600 Subject: [PATCH 077/132] Update AUTHORS and changelog. --- AUTHORS.txt | 1 + docs/news.txt | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/AUTHORS.txt b/AUTHORS.txt index e19dca319f0..8dc3fe2c894 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -38,6 +38,7 @@ Nowell Strite Oliver Tonnhofer Olivier Girardot Patrick Jenkins +Paul Moore Paul Nasrat Paul Oswald Paul van der Linden diff --git a/docs/news.txt b/docs/news.txt index 498b59cc673..f1b5610415c 100644 --- a/docs/news.txt +++ b/docs/news.txt @@ -14,6 +14,11 @@ Beta and final releases planned for the end of 2012. develop (unreleased) -------------------- +* Added support for bare paths (including relative paths) as argument to + `--find-links`. Thanks Paul Moore for draft patch. + +* Added support for --no-index in requirements files. + * Added "pip status" command to get information about an installed package. Fixes #131. Thanks Kelsey Hightower and Rafael Caricio. From 8d194bfcfca239bc6ab8f30d4a51dd327b03675b Mon Sep 17 00:00:00 2001 From: Andrey Bulgakov <bulgakov@tranio.ru> Date: Fri, 17 Aug 2012 11:33:11 +0400 Subject: [PATCH 078/132] check if file was already downloaded In --download mode skip downloading when file already exists in target directory --- pip/download.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/pip/download.py b/pip/download.py index 12bf82d5fdf..ec88196a8de 100644 --- a/pip/download.py +++ b/pip/download.py @@ -429,6 +429,13 @@ def unpack_http_url(link, location, download_cache, download_dir=None): urllib.quote(target_url, '')) if not os.path.isdir(download_cache): create_download_cache_folder(download_cache) + + already_downloaded = None + if download_dir: + already_downloaded = os.path.join(download_dir, link.filename) + if not os.path.exists(already_downloaded): + already_downloaded = None + if (target_file and os.path.exists(target_file) and os.path.exists(target_file + '.content-type')): @@ -439,6 +446,12 @@ def unpack_http_url(link, location, download_cache, download_dir=None): download_hash = _get_hash_from_file(target_file, link) temp_location = target_file logger.notify('Using download cache from %s' % target_file) + elif already_downloaded: + temp_location = already_downloaded + content_type = mimetypes.guess_type(already_downloaded) + if link.md5_hash: + download_hash = _get_md5_from_file(target_file, link) + logger.notify('File was already downloaded %s' % already_downloaded) else: resp = _get_response_from_url(target_url, link) content_type = resp.info()['content-type'] @@ -463,12 +476,12 @@ def unpack_http_url(link, location, download_cache, download_dir=None): download_hash = _download_url(resp, link, temp_location) if link.hash and link.hash_name: _check_hash(download_hash, link) - if download_dir: + if download_dir and not already_downloaded: _copy_file(temp_location, download_dir, content_type, link) unpack_file(temp_location, location, content_type, link) if target_file and target_file != temp_location: cache_download(target_file, temp_location, content_type) - if target_file is None: + if target_file is None and not already_downloaded: os.unlink(temp_location) os.rmdir(temp_dir) From aa2b3522f62f3760465260e9236d4f2a043632cb Mon Sep 17 00:00:00 2001 From: andreiko <mail@andreiko.ru> Date: Sat, 18 Aug 2012 00:44:17 +0400 Subject: [PATCH 079/132] fixed typo: md5_hash -> hash --- pip/download.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pip/download.py b/pip/download.py index ec88196a8de..c1159beee42 100644 --- a/pip/download.py +++ b/pip/download.py @@ -449,7 +449,7 @@ def unpack_http_url(link, location, download_cache, download_dir=None): elif already_downloaded: temp_location = already_downloaded content_type = mimetypes.guess_type(already_downloaded) - if link.md5_hash: + if link.hash: download_hash = _get_md5_from_file(target_file, link) logger.notify('File was already downloaded %s' % already_downloaded) else: From a4da43ada4cd7bb5d38062a8453f4419e1c7de16 Mon Sep 17 00:00:00 2001 From: Andrey Bulgakov <bulgakov@tranio.ru> Date: Sat, 18 Aug 2012 01:01:29 +0400 Subject: [PATCH 080/132] _get_md5_from_file -> _get_hash_from_file --- pip/download.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pip/download.py b/pip/download.py index c1159beee42..7f57911c700 100644 --- a/pip/download.py +++ b/pip/download.py @@ -450,7 +450,7 @@ def unpack_http_url(link, location, download_cache, download_dir=None): temp_location = already_downloaded content_type = mimetypes.guess_type(already_downloaded) if link.hash: - download_hash = _get_md5_from_file(target_file, link) + download_hash = _get_hash_from_file(temp_location, link) logger.notify('File was already downloaded %s' % already_downloaded) else: resp = _get_response_from_url(target_url, link) From 6bdb5041f612c5d3551cd916129428301474822b Mon Sep 17 00:00:00 2001 From: Andrey Bulgakov <bulgakov@tranio.ru> Date: Sun, 9 Sep 2012 17:14:05 +0400 Subject: [PATCH 081/132] test for check-already-downloaded feature --- tests/test_download.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/test_download.py b/tests/test_download.py index 829d4495e61..a75c46e23be 100644 --- a/tests/test_download.py +++ b/tests/test_download.py @@ -39,3 +39,31 @@ def test_download_should_download_dependencies(): openid_tarball_prefix = str(Path('scratch')/ 'python-openid-') assert any(path.startswith(openid_tarball_prefix) for path in result.files_created) assert env.site_packages/ 'openid' not in result.files_created + + +def test_download_should_skip_existing_files(): + """ + It should not download files already existing in the scratch dir + """ + env = reset_env() + + write_file('test-req.txt', textwrap.dedent(""" + INITools==0.1 + """)) + + result = run_pip('install', '-r', env.scratch_path/ 'test-req.txt', '-d', '.', expect_error=True) + assert Path('scratch')/ 'INITools-0.1.tar.gz' in result.files_created + assert env.site_packages/ 'initools' not in result.files_created + + # adding second package to test-req.txt + write_file('test-req.txt', textwrap.dedent(""" + INITools==0.1 + python-openid==2.2.5 + """)) + + # only the second package should be downloaded + result = run_pip('install', '-r', env.scratch_path/ 'test-req.txt', '-d', '.', expect_error=True) + assert Path('scratch')/ 'python-openid-2.2.5.zip' in result.files_created + assert Path('scratch')/ 'INITools-0.1.tar.gz' not in result.files_created + assert env.site_packages/ 'initools' not in result.files_created + assert env.site_packages/ 'openid' not in result.files_created From 382465e4a2edb281bae9c92114f3167865cc03dc Mon Sep 17 00:00:00 2001 From: Carl Meyer <carl@oddbird.net> Date: Mon, 10 Sep 2012 02:57:15 -0600 Subject: [PATCH 082/132] Update AUTHORS and changelog. --- AUTHORS.txt | 1 + docs/news.txt | 3 +++ 2 files changed, 4 insertions(+) diff --git a/AUTHORS.txt b/AUTHORS.txt index 8dc3fe2c894..9d1b288f4bd 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -1,6 +1,7 @@ Alex Grönholm Alex Morega Alexandre Conrad +Andrey Bulgakov Antti Kaihola Armin Ronacher Brian Rosner diff --git a/docs/news.txt b/docs/news.txt index f1b5610415c..dc337d8b31c 100644 --- a/docs/news.txt +++ b/docs/news.txt @@ -14,6 +14,9 @@ Beta and final releases planned for the end of 2012. develop (unreleased) -------------------- +* Added check in ``install --download`` to prevent re-downloading if the target + file already exists. Thanks Andrey Bulgakov. + * Added support for bare paths (including relative paths) as argument to `--find-links`. Thanks Paul Moore for draft patch. From e51d0767384d0b82451c2627c631bed24466aa08 Mon Sep 17 00:00:00 2001 From: Andrey Bulgakov <bulgakov@tranio.ru> Date: Mon, 10 Sep 2012 13:49:12 +0400 Subject: [PATCH 083/132] Fixed test_download_should_skip_existing_files test --- tests/test_download.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_download.py b/tests/test_download.py index a75c46e23be..79ef96a0e65 100644 --- a/tests/test_download.py +++ b/tests/test_download.py @@ -63,7 +63,8 @@ def test_download_should_skip_existing_files(): # only the second package should be downloaded result = run_pip('install', '-r', env.scratch_path/ 'test-req.txt', '-d', '.', expect_error=True) - assert Path('scratch')/ 'python-openid-2.2.5.zip' in result.files_created + openid_tarball_prefix = str(Path('scratch')/ 'python-openid-') + assert any(path.startswith(openid_tarball_prefix) for path in result.files_created) assert Path('scratch')/ 'INITools-0.1.tar.gz' not in result.files_created assert env.site_packages/ 'initools' not in result.files_created assert env.site_packages/ 'openid' not in result.files_created From 040f729376c0846b83e2f48c98227dd59456cd4f Mon Sep 17 00:00:00 2001 From: Paul Moore <p.f.moore@gmail.com> Date: Mon, 10 Sep 2012 20:45:04 +0100 Subject: [PATCH 084/132] Fix for issue 670 (unorderable Link types under Python 3) --- tests/packages/duplicate-1.0.tar.gz | 0 tests/packages2/duplicate-1.0.tar.gz | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/packages/duplicate-1.0.tar.gz create mode 100644 tests/packages2/duplicate-1.0.tar.gz diff --git a/tests/packages/duplicate-1.0.tar.gz b/tests/packages/duplicate-1.0.tar.gz new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/packages2/duplicate-1.0.tar.gz b/tests/packages2/duplicate-1.0.tar.gz new file mode 100644 index 00000000000..e69de29bb2d From 1dee47918471973e0d82e037da5752cb384b0415 Mon Sep 17 00:00:00 2001 From: Paul Moore <p.f.moore@gmail.com> Date: Mon, 10 Sep 2012 21:04:00 +0100 Subject: [PATCH 085/132] Code changes for fix to issue 670 --- pip/index.py | 15 +++++++++++++++ tests/test_finder.py | 10 ++++++++++ 2 files changed, 25 insertions(+) diff --git a/pip/index.py b/pip/index.py index 4136d05dc10..8e6af27f019 100644 --- a/pip/index.py +++ b/pip/index.py @@ -598,6 +598,21 @@ def __repr__(self): def __eq__(self, other): return self.url == other.url + def __ne__(self, other): + return self.url != other.url + + def __lt__(self, other): + return self.url < other.url + + def __le__(self, other): + return self.url <= other.url + + def __gt__(self, other): + return self.url > other.url + + def __ge__(self, other): + return self.url >= other.url + def __hash__(self): return hash(self.url) diff --git a/tests/test_finder.py b/tests/test_finder.py index cdff3a9322b..afe8a422dfb 100644 --- a/tests/test_finder.py +++ b/tests/test_finder.py @@ -7,6 +7,7 @@ from tests.test_pip import here find_links = 'file://' + urllib.quote(str(Path(here).abspath/'packages').replace('\\', '/')) +find_links2 = 'file://' + urllib.quote(str(Path(here).abspath/'packages2').replace('\\', '/')) def test_no_mpkg(): @@ -25,3 +26,12 @@ def test_no_partial_name_match(): found = finder.find_requirement(req, False) assert found.url.endswith("gmpy-1.15.tar.gz"), found + +def test_duplicates_sort_ok(): + """Finder successfully finds one of a set of duplicates in different + locations""" + finder = PackageFinder([find_links, find_links2], []) + req = InstallRequirement.from_line("duplicate") + found = finder.find_requirement(req, False) + + assert found.url.endswith("duplicate-1.0.tar.gz"), found From 71286afc4db23d2d5441e1ecec63e31b884bd323 Mon Sep 17 00:00:00 2001 From: Rafael Caricio <rafael.jacinto@gmail.com> Date: Tue, 11 Sep 2012 16:35:03 -0300 Subject: [PATCH 086/132] Enable listing all the installed files. --- pip/commands/status.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pip/commands/status.py b/pip/commands/status.py index 05b022f9f30..0c02c67cd8e 100644 --- a/pip/commands/status.py +++ b/pip/commands/status.py @@ -61,11 +61,8 @@ def print_results(distributions): logger.notify("Location: %s" % dist['location']) logger.notify("Files:") if 'files' in dist: - for i, line in enumerate(open(dist['files'])): + for line in open(dist['files']): logger.notify(" %s" % line.strip()) - if i > 20: - logger.notify(" and more.") - break else: logger.notify("Cannot locate installed-files.txt") From 85bbe26f64074dbd1192a44a3f0759d547244686 Mon Sep 17 00:00:00 2001 From: Rafael Caricio <rafael.jacinto@gmail.com> Date: Tue, 11 Sep 2012 17:44:42 -0300 Subject: [PATCH 087/132] Make optional the full file listing. --- pip/commands/status.py | 23 +++++++++++++++-------- tests/test_status.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/pip/commands/status.py b/pip/commands/status.py index 0c02c67cd8e..c120e29885b 100644 --- a/pip/commands/status.py +++ b/pip/commands/status.py @@ -11,6 +11,12 @@ class StatusCommand(Command): def __init__(self): super(StatusCommand, self).__init__() + self.parser.add_option( + '-f', '--files', + dest='files', + action='store_true', + default=False, + help='If should show a full list of files for every installed package') def run(self, options, args): if not args: @@ -19,7 +25,7 @@ def run(self, options, args): query = args results = search_packages_info(query) - print_results(results) + print_results(results, options.files) def search_packages_info(query): @@ -50,7 +56,7 @@ def search_packages_info(query): yield package -def print_results(distributions): +def print_results(distributions, list_all_files): """ Print the informations from installed distributions found. """ @@ -59,12 +65,13 @@ def print_results(distributions): logger.notify("Name: %s" % dist['name']) logger.notify("Version: %s" % dist['version']) logger.notify("Location: %s" % dist['location']) - logger.notify("Files:") - if 'files' in dist: - for line in open(dist['files']): - logger.notify(" %s" % line.strip()) - else: - logger.notify("Cannot locate installed-files.txt") + if list_all_files: + logger.notify("Files:") + if 'files' in dist: + for line in open(dist['files']): + logger.notify(" %s" % line.strip()) + else: + logger.notify("Cannot locate installed-files.txt") StatusCommand() diff --git a/tests/test_status.py b/tests/test_status.py index 05bf5c00cd5..1eb083b90fb 100644 --- a/tests/test_status.py +++ b/tests/test_status.py @@ -1,3 +1,4 @@ +import re from pip import __version__ from pip.commands.status import search_packages_info from tests.test_pip import reset_env, run_pip @@ -11,6 +12,22 @@ def test_status(): reset_env() result = run_pip('status', 'pip') lines = result.stdout.split('\n') + assert len(lines) == 5 + assert lines[0] == '---', lines[0] + assert lines[1] == 'Name: pip', lines[1] + assert lines[2] == 'Version: %s' % __version__, lines[2] + assert lines[3].startswith('Location: '), lines[3] + + +def test_status_with_files_not_found(): + """ + Test for status command with installed files listing enabled and + installed-files.txt not found. + + """ + reset_env() + result = run_pip('status', '-f', 'pip') + lines = result.stdout.split('\n') assert len(lines) == 7 assert lines[0] == '---', lines[0] assert lines[1] == 'Name: pip', lines[1] @@ -20,6 +37,17 @@ def test_status(): assert lines[5] == 'Cannot locate installed-files.txt', lines[5] +def test_status_with_all_files(): + """ + Test listing all files in the status command. + + """ + reset_env() + result = run_pip('install', 'initools==0.2') + result = run_pip('status', '--files', 'initools') + assert re.search(r"Files:\n( .+\n)+", result.stdout) + + def test_missing_argument(): """ Test status command with no arguments. From 8c9a241fb27dd8a7c690f0efdfeffb05dd74256d Mon Sep 17 00:00:00 2001 From: Carl Meyer <carl@oddbird.net> Date: Wed, 12 Sep 2012 10:39:19 -0600 Subject: [PATCH 088/132] Rename 'status' command 'show'. --- docs/news.txt | 2 +- pip/commands/{status.py => show.py} | 10 +++++----- tests/{test_status.py => test_show.py} | 24 ++++++++++++------------ 3 files changed, 18 insertions(+), 18 deletions(-) rename pip/commands/{status.py => show.py} (92%) rename tests/{test_status.py => test_show.py} (76%) diff --git a/docs/news.txt b/docs/news.txt index dc337d8b31c..d512783b573 100644 --- a/docs/news.txt +++ b/docs/news.txt @@ -22,7 +22,7 @@ develop (unreleased) * Added support for --no-index in requirements files. -* Added "pip status" command to get information about an installed +* Added "pip show" command to get information about an installed package. Fixes #131. Thanks Kelsey Hightower and Rafael Caricio. 1.2.1 (2012-09-06) diff --git a/pip/commands/status.py b/pip/commands/show.py similarity index 92% rename from pip/commands/status.py rename to pip/commands/show.py index c120e29885b..bcde94d57c5 100644 --- a/pip/commands/status.py +++ b/pip/commands/show.py @@ -4,19 +4,19 @@ from pip.log import logger -class StatusCommand(Command): - name = 'status' +class ShowCommand(Command): + name = 'show' usage = '%prog QUERY' summary = 'Output installed distributions (exact versions, files) to stdout' def __init__(self): - super(StatusCommand, self).__init__() + super(ShowCommand, self).__init__() self.parser.add_option( '-f', '--files', dest='files', action='store_true', default=False, - help='If should show a full list of files for every installed package') + help='Show the full list of installed files for each package') def run(self, options, args): if not args: @@ -74,4 +74,4 @@ def print_results(distributions, list_all_files): logger.notify("Cannot locate installed-files.txt") -StatusCommand() +ShowCommand() diff --git a/tests/test_status.py b/tests/test_show.py similarity index 76% rename from tests/test_status.py rename to tests/test_show.py index 1eb083b90fb..62562d39bda 100644 --- a/tests/test_status.py +++ b/tests/test_show.py @@ -1,16 +1,16 @@ import re from pip import __version__ -from pip.commands.status import search_packages_info +from pip.commands.show import search_packages_info from tests.test_pip import reset_env, run_pip -def test_status(): +def test_show(): """ - Test end to end test for status command. + Test end to end test for show command. """ reset_env() - result = run_pip('status', 'pip') + result = run_pip('show', 'pip') lines = result.stdout.split('\n') assert len(lines) == 5 assert lines[0] == '---', lines[0] @@ -19,14 +19,14 @@ def test_status(): assert lines[3].startswith('Location: '), lines[3] -def test_status_with_files_not_found(): +def test_show_with_files_not_found(): """ - Test for status command with installed files listing enabled and + Test for show command with installed files listing enabled and installed-files.txt not found. """ reset_env() - result = run_pip('status', '-f', 'pip') + result = run_pip('show', '-f', 'pip') lines = result.stdout.split('\n') assert len(lines) == 7 assert lines[0] == '---', lines[0] @@ -37,24 +37,24 @@ def test_status_with_files_not_found(): assert lines[5] == 'Cannot locate installed-files.txt', lines[5] -def test_status_with_all_files(): +def test_show_with_all_files(): """ - Test listing all files in the status command. + Test listing all files in the show command. """ reset_env() result = run_pip('install', 'initools==0.2') - result = run_pip('status', '--files', 'initools') + result = run_pip('show', '--files', 'initools') assert re.search(r"Files:\n( .+\n)+", result.stdout) def test_missing_argument(): """ - Test status command with no arguments. + Test show command with no arguments. """ reset_env() - result = run_pip('status') + result = run_pip('show') assert 'ERROR: Please provide a project name or names.' in result.stdout From 297d9ffca066586aed02a618a692877dea731eb7 Mon Sep 17 00:00:00 2001 From: Roey Berman <roey.berman@gmail.com> Date: Thu, 13 Sep 2012 04:01:28 +0300 Subject: [PATCH 089/132] switch submodule init/update to a single line --- pip/vcs/git.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pip/vcs/git.py b/pip/vcs/git.py index b90cc1ef732..abb57ac7a81 100644 --- a/pip/vcs/git.py +++ b/pip/vcs/git.py @@ -214,8 +214,7 @@ def _get_revision_from_rev_parse(self, name, location): def update_submodules(self, location): if not os.path.exists(os.path.join(location, '.gitmodules')): return - call_subprocess([self.cmd, 'submodule', 'init', '-q'], cwd=location) - call_subprocess([self.cmd, 'submodule', 'update', '--recursive', '-q'], + call_subprocess([self.cmd, 'submodule', 'update', '--init', '--recursive', '-q'], cwd=location) vcs.register(Git) From 69508f3c68b299d34b758349fd1023bb817137f4 Mon Sep 17 00:00:00 2001 From: Marcus Smith <qwcode@gmail.com> Date: Thu, 13 Sep 2012 13:09:43 -0700 Subject: [PATCH 090/132] move to dedicated indexes test folder --- .../{ => indexes}/in dex/FSPkg/FSPkg-0.1dev.tar.gz | Bin tests/{ => indexes}/in dex/FSPkg/index.html | 0 tests/{ => indexes}/in dex/README.txt | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename tests/{ => indexes}/in dex/FSPkg/FSPkg-0.1dev.tar.gz (100%) rename tests/{ => indexes}/in dex/FSPkg/index.html (100%) rename tests/{ => indexes}/in dex/README.txt (100%) diff --git a/tests/in dex/FSPkg/FSPkg-0.1dev.tar.gz b/tests/indexes/in dex/FSPkg/FSPkg-0.1dev.tar.gz similarity index 100% rename from tests/in dex/FSPkg/FSPkg-0.1dev.tar.gz rename to tests/indexes/in dex/FSPkg/FSPkg-0.1dev.tar.gz diff --git a/tests/in dex/FSPkg/index.html b/tests/indexes/in dex/FSPkg/index.html similarity index 100% rename from tests/in dex/FSPkg/index.html rename to tests/indexes/in dex/FSPkg/index.html diff --git a/tests/in dex/README.txt b/tests/indexes/in dex/README.txt similarity index 100% rename from tests/in dex/README.txt rename to tests/indexes/in dex/README.txt From 0bb9c31aeab89eba17de26802e6b01c9cafeeb39 Mon Sep 17 00:00:00 2001 From: Maxime Rouyrre <rouyrre+git@gmail.com> Date: Fri, 14 Sep 2012 17:27:58 +0200 Subject: [PATCH 091/132] Explicitly ignore rel='download' links while looking for html pages. This way we avoid requesting archive headers just to see they're not HTML pages. --- pip/index.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pip/index.py b/pip/index.py index 8a4fb4c1e32..4fb5ab25319 100644 --- a/pip/index.py +++ b/pip/index.py @@ -250,7 +250,7 @@ def _get_queued_page(self, req, pending_queue, done, seen): if page is None: continue done.append(page) - for link in page.rel_links(): + for link in page.rel_links(rels=('homepage',)): pending_queue.put(link) _egg_fragment_re = re.compile(r'#egg=([^&]*)') @@ -534,8 +534,8 @@ def links(self): url = self.clean_link(urlparse.urljoin(self.base_url, url)) yield Link(url, self) - def rel_links(self): - for url in self.explicit_rel_links(): + def rel_links(self, rels=('homepage', 'download')): + for url in self.explicit_rel_links(rels): yield url for url in self.scraped_rel_links(): yield url From a2e489fdd52d98d0064e483058b365a367010a00 Mon Sep 17 00:00:00 2001 From: Marcus Smith <qwcode@gmail.com> Date: Sun, 16 Sep 2012 17:35:31 -0700 Subject: [PATCH 092/132] a new 'simple' dist for basic testing --- tests/packages/simple-1.0.tar.gz | Bin 0 -> 671 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/packages/simple-1.0.tar.gz diff --git a/tests/packages/simple-1.0.tar.gz b/tests/packages/simple-1.0.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..6d9f2dd8b34501359ea98605820b7e5e96af999b GIT binary patch literal 671 zcmV;Q0$}|giwFo%DppVe19NF@aBO8QF)lDJbYXG;?V3w(+CUVAz4ot|Dhm=R#`fUB z5)zB5ZzYAODD9%EEXN+`D1Ox*)AH{-7zmFfO;NE?s`G7(@7S<7_uM;oL?PlV;)dO} zOsy^~h0ECdYP-zZyspq}YEkA;rmmW0Q@cww!n6h`3+d;CXg_`iX~-Xc?y2=>O0m}e z>f)X8VeocXi~e25c~pO@#NO7wLz$})3;Ng2M=t%)m%j0t=Y>d<9#N^F5B!+-$RaoN zt(8$3$9_KRk?X<5VEARAzfWUsWd4M&LoZADJ<WIT4IcX<T1PKMz>{L*wb#4|a*@gE zw^qTc$d}_ZkJk|m)Z_nx%Q9;R<4M#0x7^N-{~gQf!2eC~l}{#~NXF?mUHTL6rW8>~ z`lPy1b%CqTR&`TM6EEjeF)syG5Jvnj{h!U6@Bh@f^S@=YdH?Ua4E}F|lNV-L<mQb? zOrA_hHdC!S(Z_ijlPVu%nnndtNs{Jr5jiJgk%V662e+#07rI_WPZPCe=(iT>)H>Hm z#dNWC=(kjU-33=~_P4(0^lZ6&hQe|r|5qQ|sv#KjhF~7l|NG5<%yt;&za#5^uz7I* z`L9bIiu#ZF5A)v<{XZIBe|q(1)Rw<w-TS}Y`Oj4~0se19|K+A%U#M?-Y8VvK^VXsJ z4MG1yrrcW#dB&5FC&A2%L~>g^_>fF9FXB@k{d=;3L;3$9<?1>AvFvUCQ=8g2|7irY zQ~3W8{~xR<Kka{axBqut2mWtPe^deZf9d}k{syrB`A^4T9i0ChNB@lp;DGx7W&NkF zP3Px-tc&yiMmPz}IG&;Y0{{R30000000000000000000000000008hD_zkS`JuLuG F00250TVDVG literal 0 HcmV?d00001 From da81ff2c38169ee636ad973ffb29512acde8549b Mon Sep 17 00:00:00 2001 From: Marcus Smith <qwcode@gmail.com> Date: Sun, 16 Sep 2012 17:36:21 -0700 Subject: [PATCH 093/132] a new simple file:// index for basic testing --- tests/indexes/simple/simple/index.html | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 tests/indexes/simple/simple/index.html diff --git a/tests/indexes/simple/simple/index.html b/tests/indexes/simple/simple/index.html new file mode 100644 index 00000000000..dba6cc3ebd6 --- /dev/null +++ b/tests/indexes/simple/simple/index.html @@ -0,0 +1,5 @@ +<html> + <body> + <a href="../../../packages/simple-1.0.tar.gz#md5=4bdf78ebb7911f215c1972cf71b378f0">simple-1.0.tar.gz</a> + </body> +</html> From 2a3a65e981e89d399b2365036f3f45bb27e41bcc Mon Sep 17 00:00:00 2001 From: Marcus Smith <qwcode@gmail.com> Date: Sun, 16 Sep 2012 17:38:06 -0700 Subject: [PATCH 094/132] an empty test file:// index with a dist in the root dir --- tests/indexes/empty_with_pkg/index.html | 0 tests/indexes/empty_with_pkg/simple-1.0.tar.gz | Bin 0 -> 671 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/indexes/empty_with_pkg/index.html create mode 100644 tests/indexes/empty_with_pkg/simple-1.0.tar.gz diff --git a/tests/indexes/empty_with_pkg/index.html b/tests/indexes/empty_with_pkg/index.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/indexes/empty_with_pkg/simple-1.0.tar.gz b/tests/indexes/empty_with_pkg/simple-1.0.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..6d9f2dd8b34501359ea98605820b7e5e96af999b GIT binary patch literal 671 zcmV;Q0$}|giwFo%DppVe19NF@aBO8QF)lDJbYXG;?V3w(+CUVAz4ot|Dhm=R#`fUB z5)zB5ZzYAODD9%EEXN+`D1Ox*)AH{-7zmFfO;NE?s`G7(@7S<7_uM;oL?PlV;)dO} zOsy^~h0ECdYP-zZyspq}YEkA;rmmW0Q@cww!n6h`3+d;CXg_`iX~-Xc?y2=>O0m}e z>f)X8VeocXi~e25c~pO@#NO7wLz$})3;Ng2M=t%)m%j0t=Y>d<9#N^F5B!+-$RaoN zt(8$3$9_KRk?X<5VEARAzfWUsWd4M&LoZADJ<WIT4IcX<T1PKMz>{L*wb#4|a*@gE zw^qTc$d}_ZkJk|m)Z_nx%Q9;R<4M#0x7^N-{~gQf!2eC~l}{#~NXF?mUHTL6rW8>~ z`lPy1b%CqTR&`TM6EEjeF)syG5Jvnj{h!U6@Bh@f^S@=YdH?Ua4E}F|lNV-L<mQb? zOrA_hHdC!S(Z_ijlPVu%nnndtNs{Jr5jiJgk%V662e+#07rI_WPZPCe=(iT>)H>Hm z#dNWC=(kjU-33=~_P4(0^lZ6&hQe|r|5qQ|sv#KjhF~7l|NG5<%yt;&za#5^uz7I* z`L9bIiu#ZF5A)v<{XZIBe|q(1)Rw<w-TS}Y`Oj4~0se19|K+A%U#M?-Y8VvK^VXsJ z4MG1yrrcW#dB&5FC&A2%L~>g^_>fF9FXB@k{d=;3L;3$9<?1>AvFvUCQ=8g2|7irY zQ~3W8{~xR<Kka{axBqut2mWtPe^deZf9d}k{syrB`A^4T9i0ChNB@lp;DGx7W&NkF zP3Px-tc&yiMmPz}IG&;Y0{{R30000000000000000000000000008hD_zkS`JuLuG F00250TVDVG literal 0 HcmV?d00001 From 4311632164f5878fbe8b60e85fd9bd90a030d91a Mon Sep 17 00:00:00 2001 From: Marcus Smith <qwcode@gmail.com> Date: Sun, 16 Sep 2012 17:38:59 -0700 Subject: [PATCH 095/132] have the 'in dex' file:// index use the simple dist --- tests/indexes/in dex/simple/index.html | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 tests/indexes/in dex/simple/index.html diff --git a/tests/indexes/in dex/simple/index.html b/tests/indexes/in dex/simple/index.html new file mode 100644 index 00000000000..dba6cc3ebd6 --- /dev/null +++ b/tests/indexes/in dex/simple/index.html @@ -0,0 +1,5 @@ +<html> + <body> + <a href="../../../packages/simple-1.0.tar.gz#md5=4bdf78ebb7911f215c1972cf71b378f0">simple-1.0.tar.gz</a> + </body> +</html> From 531e3bf44382048472e5055e3e537576cdd42bbd Mon Sep 17 00:00:00 2001 From: Marcus Smith <qwcode@gmail.com> Date: Sun, 16 Sep 2012 17:39:48 -0700 Subject: [PATCH 096/132] have the LocalExtras dist use the simple file:// index and simple dist --- tests/packages/LocalExtras/setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/packages/LocalExtras/setup.py b/tests/packages/LocalExtras/setup.py index 646c1671f57..c7965c30354 100644 --- a/tests/packages/LocalExtras/setup.py +++ b/tests/packages/LocalExtras/setup.py @@ -2,12 +2,12 @@ from setuptools import setup, find_packages HERE = os.path.dirname(__file__) -INDEX = os.path.join(HERE, '..', '..', 'in dex', 'FSPkg') +INDEX = "file://" + os.path.join(HERE, '..', '..', 'indexes', 'simple', 'simple') setup( name='LocalExtras', version='0.0.1', packages=find_packages(), - extras_require={ 'bar': ['FSPkg'] }, - dependency_links=['file://' + INDEX] + extras_require={ 'bar': ['simple'] }, + dependency_links=[INDEX] ) From af7bd9ccdbb719805ab8d4547e4694cade68cdfb Mon Sep 17 00:00:00 2001 From: Marcus Smith <qwcode@gmail.com> Date: Sun, 16 Sep 2012 17:41:27 -0700 Subject: [PATCH 097/132] index file:// urls that are dirs should not have listdir run against them --- pip/index.py | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/pip/index.py b/pip/index.py index 8a4fb4c1e32..ef5d8072644 100644 --- a/pip/index.py +++ b/pip/index.py @@ -60,8 +60,7 @@ def add_dependency_links(self, links): ## FIXME: also, we should track comes_from (i.e., use Link) self.dependency_links.extend(links) - @staticmethod - def _sort_locations(locations): + def _sort_locations(self, locations): """ Sort locations into "files" (archives) and "urls", and return a pair of lists (files,urls) @@ -69,8 +68,7 @@ def _sort_locations(locations): files = [] urls = [] - # puts the url for the given file path into the appropriate - # list + # puts the url for the given file path into the appropriate list def sort_path(path): url = path_to_url2(path) if mimetypes.guess_type(url, strict=False)[0] == 'text/html': @@ -79,20 +77,27 @@ def sort_path(path): files.append(url) for url in locations: - is_path = os.path.exists(url) - path = None - if is_path: - path = url - elif url.startswith('file:'): - path = url_to_path(url) - if path and os.path.isdir(path): - path = os.path.realpath(path) - for item in os.listdir(path): - sort_path(os.path.join(path, item)) - elif path and os.path.isfile(path): - sort_path(path) + + is_local_path = os.path.exists(url) + is_file_url = url.startswith('file:') + is_find_link = url in self.find_links + + if is_local_path or is_file_url: + if is_local_path: + path = url + else: + path = url_to_path(url) + if is_find_link and os.path.isdir(path): + path = os.path.realpath(path) + for item in os.listdir(path): + sort_path(os.path.join(path, item)) + elif os.path.isdir(path): # index file:// url + urls.append(url) + elif os.path.isfile(path): + sort_path(path) else: urls.append(url) + return files, urls def find_requirement(self, req, upgrade): From 534037906f2d4500aadaac30491bf438ae2840b6 Mon Sep 17 00:00:00 2001 From: Marcus Smith <qwcode@gmail.com> Date: Sun, 16 Sep 2012 17:42:54 -0700 Subject: [PATCH 098/132] file:// urls could have hash fragments --- pip/req.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pip/req.py b/pip/req.py index 3550cca21fc..3615a1d6c47 100644 --- a/pip/req.py +++ b/pip/req.py @@ -1134,7 +1134,8 @@ def unpack_url(self, link, location, only_download=False): loc = location if is_vcs_url(link): return unpack_vcs_link(link, loc, only_download) - elif is_file_url(link): + # a local file:// index could have links with hashes + elif not link.hash and is_file_url(link): return unpack_file_url(link, loc) else: if self.download_cache: From 4de9737c7cb6fbb79b5ad2db7ed9b5d5cccdb083 Mon Sep 17 00:00:00 2001 From: Marcus Smith <qwcode@gmail.com> Date: Sun, 16 Sep 2012 17:45:16 -0700 Subject: [PATCH 099/132] file:// related tests --- tests/test_index.py | 49 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/test_index.py b/tests/test_index.py index c537172aae0..7f10bfecad1 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -1,4 +1,9 @@ +import os +import urllib +from tests.path import Path from pip.index import package_to_requirement, HTMLPage, get_mirrors, DEFAULT_MIRROR_HOSTNAME +from pip.index import PackageFinder +from tests.test_pip import reset_env, run_pip, pyversion, here from string import ascii_lowercase from mock import patch @@ -53,3 +58,47 @@ def test_get_mirrors_no_cname(mock_gethostbyname_ex): for c in ascii_lowercase: assert c + ".pypi.python.org" in mirrors + +def test_sort_locations_file_index_url(): + """ + Test that a file:// index url (that's also a dir) doesn't get a listdir run + """ + index_url = 'file://' + os.path.join(here, 'indexes', 'empty_with_pkg') + indexes = [index_url] + finder = PackageFinder([], indexes) + files, urls = finder._sort_locations(indexes) + assert not files, "files should not have been found at index url: %s" % index_url + + +def test_sort_locations_file_find_link(): + """ + Test that a file:// find-link dir gets listdir run + """ + find_links_url = 'file://' + os.path.join(here, 'packages') + find_links = [find_links_url] + finder = PackageFinder(find_links, []) + files, urls = finder._sort_locations(find_links) + assert files, "files should have been found at find-links url: %s" % find_links_url + + +def test_install_from_file_index_hash_link(): + """ + Test that a pkg can be installed from a file:// index using a link with a hash + """ + env = reset_env() + index_url = 'file://' + os.path.join(here, 'indexes', 'simple') + result = run_pip('install', '-i', index_url, 'simple==1.0') + egg_info_folder = env.site_packages / 'simple-1.0-py%s.egg-info' % pyversion + assert egg_info_folder in result.files_created, str(result) + + +def test_file_index_url_quoting(): + """ + Test url quoting of file index url with a space + """ + index_url = 'file://' + urllib.quote(str(Path(here).abspath/'indexes'/'in dex').replace('\\', '/')) + env = reset_env() + result = run_pip('install', '-vvv', '--index-url', index_url, 'simple', expect_error=False) + assert (env.site_packages/'simple') in result.files_created, str(result.stdout) + assert (env.site_packages/'simple-1.0-py%s.egg-info' % pyversion) in result.files_created, str(result) + From 65605d5dab51cc5f5f677ee1cdebb92ef4ad5312 Mon Sep 17 00:00:00 2001 From: Marcus Smith <qwcode@gmail.com> Date: Sun, 16 Sep 2012 17:45:58 -0700 Subject: [PATCH 100/132] have the extra be 'simple' --- tests/test_requirements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_requirements.py b/tests/test_requirements.py index 64a88e37851..b6ed77b4fdd 100644 --- a/tests/test_requirements.py +++ b/tests/test_requirements.py @@ -180,4 +180,4 @@ def test_install_local_editable_with_extras(): res = run_pip('install', '-e', to_install + '[bar]', expect_error=False) assert env.site_packages/'easy-install.pth' in res.files_updated assert env.site_packages/'LocalExtras.egg-link' in res.files_created - assert env.site_packages/'fspkg' in res.files_created + assert env.site_packages/'simple' in res.files_created From 7486982f3b11ea3e68aff721d3ab5d43dc992ecb Mon Sep 17 00:00:00 2001 From: Marcus Smith <qwcode@gmail.com> Date: Sun, 16 Sep 2012 17:46:19 -0700 Subject: [PATCH 101/132] test moved to test_index.py --- tests/test_file_scheme_index.py | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 tests/test_file_scheme_index.py diff --git a/tests/test_file_scheme_index.py b/tests/test_file_scheme_index.py deleted file mode 100644 index 38f6654b924..00000000000 --- a/tests/test_file_scheme_index.py +++ /dev/null @@ -1,16 +0,0 @@ -from pip.backwardcompat import urllib -from tests.test_pip import here, reset_env, run_pip, pyversion -from tests.path import Path - -index_url = 'file://' + urllib.quote(str(Path(here).abspath/'in dex').replace('\\', '/')) - - -def test_install(): - """ - Test installing from a local index. - - """ - env = reset_env() - result = run_pip('install', '-vvv', '--index-url', index_url, 'FSPkg', expect_error=False) - assert (env.site_packages/'fspkg') in result.files_created, str(result.stdout) - assert (env.site_packages/'FSPkg-0.1dev-py%s.egg-info' % pyversion) in result.files_created, str(result) From 12ad1f18fe44b65c53700e5cca537797326ec9cb Mon Sep 17 00:00:00 2001 From: Marcus Smith <qwcode@gmail.com> Date: Sun, 16 Sep 2012 18:13:47 -0700 Subject: [PATCH 102/132] using 'simple' instead of 'FSPkg' in the 'in dex' index --- tests/indexes/in dex/FSPkg/FSPkg-0.1dev.tar.gz | Bin 1034 -> 0 bytes tests/indexes/in dex/FSPkg/index.html | 3 --- 2 files changed, 3 deletions(-) delete mode 100644 tests/indexes/in dex/FSPkg/FSPkg-0.1dev.tar.gz delete mode 100644 tests/indexes/in dex/FSPkg/index.html diff --git a/tests/indexes/in dex/FSPkg/FSPkg-0.1dev.tar.gz b/tests/indexes/in dex/FSPkg/FSPkg-0.1dev.tar.gz deleted file mode 100644 index 7fa7c10c2398d841d046838fa08b19188951cb90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1034 zcmV+l1oitLiwFREN#aWY1MQmcZ{ju>$NSoUg+(=SHv!Erf`o*&({{a0-Dq{)Z7;ei z@`#~Uox~R#uH0XLj+0P`D0C`Gwefsjki?IFC6B+h^Mv#3tNYp6Nv&9xHR$)TIr{lg zQy%jR+ja+*=Xrh4!ItgVwta&8^>!;~E)_|2S3<I&yYhXxqm=KkZ{8F>Y$xmE-1vVg zMUA82+5UG2u5bMBI!+(_-vqh;&2Nv+W?dFflS2=Lf1`iLv9$m7G~k2(n;`eUq-rjD zlj*FMHuUrGJ#UBqeYdLrf$N@NtCrqWICB5rXwe6m=x>oCv*4dO;~^g5kS@%%T*N_2 z7fiAw*2yZJQ}o(h1`f!&{>8GMHumV>c0IeQf8X&y|4lGWlL#C4t0dtPvq&VV%2PUc z%Hl8(WO7etRHD+hlpUN%gcdX{=7qM^v)c%nQg31s(NWtBtab-sLy>h<9Lwa8%1O$E z%1o_u#%Wf$q7hb9DqJ+&Ds%HBo&_64TCKm)lf_!Af)h@pWK*UaAKlz`@G1U4^?j(6 z#WRV`Be+va4A0KQQZTIpb(h4wB%PV-AHH+nk%?)LGpT~L+KT$`>GDgGhH}&vOqfo{ z)t!^Mt}Hc{N?Jf8!nm>gm<SpRPGXt0y2>|CwYlsW6TR)ZIGfN|>PNmxDLppY@ZTHy zFyZr%j&9oYYhZlU?%+0kAd%oyX6Jd=Z{L;`vDlO-s|Z4($mkEf2$UJVG7(5JrK5Ag zC4DTWMa8*ZIX=%>s{3(M7&1<A_xmn>|DaNWiwqHsl^ka2MZVjzCT&QUfk>FnkhvRe z)R7r+Zd@<D=J~g<TJ2`*{y(I8poKJ^ECbHsd)ZSD>IH4=-Tztk=KkNYd>iKfMgV;r zH(T|;KI^_v`x$%n@Aa$tw>)_NYlLSlsIT2gpf<MZKTcFv@9etS&h7whJbV3Lz5fj? zs}K5b0$Bf#o2~l4y7<ujb$ot#pf>jD->dHb?Y{5&p#LWLohp*O)p!5QBQry6_bfCf z5glSPD$vSc_3SJxd_9jMk}kFRiNFi`C5dQP=(8byDtLk3nGnbjYfEw7HQ^AGvKbCR zzvTgrA^tSJ7+?N1Mn6BsfOd4_qKL|vD)Xoey5dAllQep)>$M&5%52sD_2s9JKfS*` zz}DZePyeptRrK$Bt_}Kcg4LHlRDJ_MPb>!2^tC}!HEoL@Rr6HeF{}3VJ!sXY5)p8^ zpxhM9|GW?cEM_VQYWs@u|75HFH=ggy+t|PV_bd7zI0K0PHG&b(u`hy0%=-Vo>Y76L z>))xo|My)7_WzA=3fcz%0000000000000000000000000000000QgVx4Nr6VZvapL E0GQk!n*aa+ diff --git a/tests/indexes/in dex/FSPkg/index.html b/tests/indexes/in dex/FSPkg/index.html deleted file mode 100644 index cf4f404e32f..00000000000 --- a/tests/indexes/in dex/FSPkg/index.html +++ /dev/null @@ -1,3 +0,0 @@ -<html><head><title>Links for FSPkg

Links for FSPkg

FSPkg-0.1dev.tar.gz
-Source
- From e066d36de818920e5380bc244d8bfa438e207d40 Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Sun, 16 Sep 2012 22:41:51 -0700 Subject: [PATCH 103/132] don't add local dirs that are not findlinks to url list --- pip/index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pip/index.py b/pip/index.py index ef5d8072644..78a6f6338ea 100644 --- a/pip/index.py +++ b/pip/index.py @@ -91,7 +91,7 @@ def sort_path(path): path = os.path.realpath(path) for item in os.listdir(path): sort_path(os.path.join(path, item)) - elif os.path.isdir(path): # index file:// url + elif is_file_url and os.path.isdir(path): urls.append(url) elif os.path.isfile(path): sort_path(path) From bb23766d2d686cfd8d7afd30461955feb013ceec Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Sun, 16 Sep 2012 22:42:30 -0700 Subject: [PATCH 104/132] correct import for py3x and better assertions --- tests/test_index.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/tests/test_index.py b/tests/test_index.py index 7f10bfecad1..9553483d623 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -1,5 +1,5 @@ import os -import urllib +from pip.backwardcompat import urllib from tests.path import Path from pip.index import package_to_requirement, HTMLPage, get_mirrors, DEFAULT_MIRROR_HOSTNAME from pip.index import PackageFinder @@ -59,17 +59,6 @@ def test_get_mirrors_no_cname(mock_gethostbyname_ex): assert c + ".pypi.python.org" in mirrors -def test_sort_locations_file_index_url(): - """ - Test that a file:// index url (that's also a dir) doesn't get a listdir run - """ - index_url = 'file://' + os.path.join(here, 'indexes', 'empty_with_pkg') - indexes = [index_url] - finder = PackageFinder([], indexes) - files, urls = finder._sort_locations(indexes) - assert not files, "files should not have been found at index url: %s" % index_url - - def test_sort_locations_file_find_link(): """ Test that a file:// find-link dir gets listdir run @@ -78,7 +67,17 @@ def test_sort_locations_file_find_link(): find_links = [find_links_url] finder = PackageFinder(find_links, []) files, urls = finder._sort_locations(find_links) - assert files, "files should have been found at find-links url: %s" % find_links_url + assert files and not urls, "files and not urls should have been found at find-links url: %s" % find_links_url + + +def test_sort_locations_file_not_find_link(): + """ + Test that a file:// url dir that's not a find-link, doesn't get a listdir run + """ + index_url = 'file://' + os.path.join(here, 'indexes', 'empty_with_pkg') + finder = PackageFinder([], []) + files, urls = finder._sort_locations([index_url]) + assert urls and not files, "urls, but not files should have been found" def test_install_from_file_index_hash_link(): From 28aeb03b0893323acef26c082035ab6bd7ec8010 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Fri, 21 Sep 2012 11:15:24 -0600 Subject: [PATCH 105/132] Add dependencies information to 'show' command. --- docs/usage.txt | 6 +++--- pip/commands/show.py | 11 ++++++----- tests/test_show.py | 10 ++++++---- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/usage.txt b/docs/usage.txt index 09a9d4c7d46..02309f2d658 100644 --- a/docs/usage.txt +++ b/docs/usage.txt @@ -26,8 +26,8 @@ local or remote:: .. _setuptools extras: http://peak.telecommunity.com/DevCenter/setuptools#declaring-extras-optional-features-with-their-own-dependencies -Edit mode -********* +Editable mode +************* Packages normally_ install under ``site-packages``, but when you're making changes, it makes more sense to run the package straight from the @@ -151,7 +151,7 @@ Checking installed package status --------------------------------- To get info about an installed package, including its location and included -files, run ``pip status ProjectName``. +files, run ``pip show ProjectName``. Bundles diff --git a/pip/commands/show.py b/pip/commands/show.py index bcde94d57c5..b5357f104e0 100644 --- a/pip/commands/show.py +++ b/pip/commands/show.py @@ -35,17 +35,17 @@ def search_packages_info(query): pip generated 'installed-files.txt' in the distributions '.egg-info' directory. """ - installed_packages = dict([(p.project_name.lower(), p.project_name) \ - for p in pkg_resources.working_set]) + installed_packages = dict( + [(p.project_name.lower(), p) for p in pkg_resources.working_set]) for name in query: normalized_name = name.lower() if normalized_name in installed_packages: - dist = pkg_resources.get_distribution( \ - installed_packages[normalized_name]) + dist = installed_packages[normalized_name] package = { 'name': dist.project_name, 'version': dist.version, - 'location': dist.location + 'location': dist.location, + 'requires': [dep.project_name for dep in dist.requires()], } filelist = os.path.join( dist.location, @@ -65,6 +65,7 @@ def print_results(distributions, list_all_files): logger.notify("Name: %s" % dist['name']) logger.notify("Version: %s" % dist['version']) logger.notify("Location: %s" % dist['location']) + logger.notify("Requires: %s" % ', '.join(dist['requires'])) if list_all_files: logger.notify("Files:") if 'files' in dist: diff --git a/tests/test_show.py b/tests/test_show.py index 62562d39bda..774fe9a7b79 100644 --- a/tests/test_show.py +++ b/tests/test_show.py @@ -12,11 +12,12 @@ def test_show(): reset_env() result = run_pip('show', 'pip') lines = result.stdout.split('\n') - assert len(lines) == 5 + assert len(lines) == 6 assert lines[0] == '---', lines[0] assert lines[1] == 'Name: pip', lines[1] assert lines[2] == 'Version: %s' % __version__, lines[2] assert lines[3].startswith('Location: '), lines[3] + assert lines[4] == 'Requires: ' def test_show_with_files_not_found(): @@ -28,13 +29,14 @@ def test_show_with_files_not_found(): reset_env() result = run_pip('show', '-f', 'pip') lines = result.stdout.split('\n') - assert len(lines) == 7 + assert len(lines) == 8 assert lines[0] == '---', lines[0] assert lines[1] == 'Name: pip', lines[1] assert lines[2] == 'Version: %s' % __version__, lines[2] assert lines[3].startswith('Location: '), lines[3] - assert lines[4] == 'Files:', lines[4] - assert lines[5] == 'Cannot locate installed-files.txt', lines[5] + assert lines[4] == 'Requires: ' + assert lines[5] == 'Files:', lines[4] + assert lines[6] == 'Cannot locate installed-files.txt', lines[5] def test_show_with_all_files(): From b1e375402a5595f0ef0dc4bb31dacb23c6918bb3 Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Sun, 23 Sep 2012 14:45:03 -0700 Subject: [PATCH 106/132] add simple test dists, v2 and v3 --- tests/packages/simple-2.0.tar.gz | Bin 0 -> 673 bytes tests/packages/simple-3.0.tar.gz | Bin 0 -> 676 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/packages/simple-2.0.tar.gz create mode 100644 tests/packages/simple-3.0.tar.gz diff --git a/tests/packages/simple-2.0.tar.gz b/tests/packages/simple-2.0.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..c53a523f145b266d077eb67496b129639b71c032 GIT binary patch literal 673 zcmV;S0$%+eiwFo3c3w~d19NF@aBO8QGA=MKbYXG;?V3$*+CUJ7efF=|DhCoN#`fY5 z35i41ZzYAODD9!DEXQ8xD*mdyrsdyvF%T0HnxbN(RPS@Kch-i(%saC)Q1Cd5m`1yX zt~7-qacz5XHC@}-yslA9YEaj5dj_S7VN%mI6=EwbP!__^2~mFh4APM8e(tILXG&4~ z|LWqM_F?#T)QJBb%i8IG>d-C!_o(A2#DM=B=OYt-=nG%_%<_V#NuQWqLmm1t>yu?{ zsCPRuNYrr|$9_KVlk4HdaP(!UzE5MOW&V`arkADop61(Yjm18XYU?ErSW;}f_L>zz z&NESc*V=d$`C^jhacxmRGx{%>D6?)bnYOKe!|iS9-;yN&`fr1;Y&!LLGD*Lw!k>CK zC67WfAk{+V1t#x}YEjG*FK08pNClMEG%Bo+eYGpl5}S%y>R zTqPB=<pe+i5eo+1I*ZX05{kP)()uCTqdSCZsGsuPK z)ux99LH$FdJlG3a#*&aF!Q6{@a$7w5kVrExVlx)~d$55+>Hjg$H8cM)%=`MMCZ(AF zv;xX0^uI&@2TRIN>)+k3|Cs-@>VISb=zpdE2L1-H|NN(CITZ7s-_|K(x&c z?P_&F72LLqtHEu$d0nF!j52O>B=*Gabx+MdQ?knc zR~PTvABJy7wfMhhSv&pDdTh)8ChKvHQ24)gJ__l2u5{a)F(!V>O(gaeX@)V z{cc7Di9XK5&`lS8ay`5lj=l`__i-rNi8~XO>18gz$LaRCEkf51D(j^`5mC1B*lUqZ zQa_R9yVl05z?IWD4J(TV>d}8DWS(>;(^=E{r`+7qzok+D^xp(u#cbyI(KP<1OLykn zx{%Rx4eoK(#HA{CTI2=Twvf3j%4{xhr6KjTIH=M?q75l&uo^DOP& z_)%9xbCN7nsgCq%8i%Be2N}mfMpTf*sa#sl$<&WLCvhjYs_JLDURqBhwPhHzmf_So z*Gb8Ixpf${RD9hT*G~@a{m$vxYWfU`;G&+El2-%T>l@e zAKbtH>oJ=l|D*py|93?Fk4M*^UcDK2-)B5Mz^`BD<`ftwv$O6#+O8+%{2C)D6k7@Nxoc|oh{~H~^0sa3s z`QNq};|43v|7`sK-wY>S9)=6#e*gdg00000000000000000000000000001<1HS=- KtwNLlPyhfZmR5KG literal 0 HcmV?d00001 From 4ea13f474cfadb65b71eb3a78e3152718e5a4bf9 Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Sun, 23 Sep 2012 14:46:22 -0700 Subject: [PATCH 107/132] Inf py3x compatibility and tests --- pip/util.py | 25 +++++++++++++++++++++---- tests/test_util.py | 8 ++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/pip/util.py b/pip/util.py index fcdcb430047..7db95745cc6 100644 --- a/pip/util.py +++ b/pip/util.py @@ -136,10 +136,27 @@ def ask(message, options): class _Inf(object): """I am bigger than everything!""" - def __cmp__(self, a): - if self is a: - return 0 - return 1 + + def __eq__(self, other): + if self is other: + return True + else: + return False + + def __ne__(self, other): + return not self.__eq__(other) + + def __lt__(self, other): + return False + + def __le__(self, other): + return False + + def __gt__(self, other): + return True + + def __ge__(self, other): + return True def __repr__(self): return 'Inf' diff --git a/tests/test_util.py b/tests/test_util.py index 2907cc06714..305591b3708 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -8,6 +8,7 @@ from nose.tools import eq_ from tests.path import Path from pip.util import egg_link_path +from pip.util import Inf class Tests_EgglinkPath: @@ -138,3 +139,10 @@ def test_noegglink_in_sitepkgs_venv_global(self): self.mock_isfile.return_value = False eq_(egg_link_path(self.mock_dist), None) +def test_Inf_greater(): + """Test Inf compares greater.""" + assert Inf > object() + +def test_Inf_equals_Inf(): + """Test Inf compares greater.""" + assert Inf == Inf From 93bdb858d87c5814b9af42fe6ba41b3d902b43ba Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Sun, 23 Sep 2012 14:58:14 -0700 Subject: [PATCH 108/132] fix finder version sorting with Inf object --- pip/index.py | 27 ++++++++++++++------------ tests/test_finder.py | 44 +++++++++++++++++++++++++++++++++++++++++-- tests/test_index.py | 6 +++++- tests/test_upgrade.py | 9 +++++---- 4 files changed, 67 insertions(+), 19 deletions(-) diff --git a/pip/index.py b/pip/index.py index 78a6f6338ea..55c49bf1bbb 100644 --- a/pip/index.py +++ b/pip/index.py @@ -165,7 +165,7 @@ def mkurl_pypi_url(url): logger.fatal('Could not find any downloads that satisfy the requirement %s' % req) raise DistributionNotFound('No distributions at all found for %s' % req) if req.satisfied_by is not None: - found_versions.append((req.satisfied_by.parsed_version, Inf, req.satisfied_by.version)) + found_versions.append((req.satisfied_by.parsed_version, InfLink, req.satisfied_by.version)) if file_versions: file_versions.sort(reverse=True) logger.info('Local files found: %s' % ', '.join([url_to_path(link.url) for parsed, link, version in file_versions])) @@ -177,31 +177,31 @@ def mkurl_pypi_url(url): logger.info("Ignoring link %s, version %s doesn't match %s" % (link, version, ','.join([''.join(s) for s in req.req.specs]))) continue - applicable_versions.append((link, version)) - applicable_versions = sorted(applicable_versions, key=lambda v: pkg_resources.parse_version(v[1]), reverse=True) - existing_applicable = bool([link for link, version in applicable_versions if link is Inf]) + applicable_versions.append((parsed_version, link, version)) + applicable_versions = sorted(applicable_versions, reverse=True) + existing_applicable = bool([link for parsed_version, link, version in applicable_versions if link is InfLink]) if not upgrade and existing_applicable: - if applicable_versions[0][1] is Inf: + if applicable_versions[0][1] is InfLink: logger.info('Existing installed version (%s) is most up-to-date and satisfies requirement' % req.satisfied_by.version) - raise BestVersionAlreadyInstalled else: logger.info('Existing installed version (%s) satisfies requirement (most up-to-date version is %s)' - % (req.satisfied_by.version, applicable_versions[0][1])) + % (req.satisfied_by.version, applicable_versions[0][2])) return None if not applicable_versions: logger.fatal('Could not find a version that satisfies the requirement %s (from versions: %s)' % (req, ', '.join([version for parsed_version, link, version in found_versions]))) raise DistributionNotFound('No distributions matching the version for %s' % req) - if applicable_versions[0][0] is Inf: + if applicable_versions[0][1] is InfLink: # We have an existing version, and its the best version logger.info('Installed version (%s) is most up-to-date (past versions: %s)' - % (req.satisfied_by.version, ', '.join([version for link, version in applicable_versions[1:]]) or 'none')) + % (req.satisfied_by.version, ', '.join([version for parsed_version, link, version in applicable_versions[1:]]) or 'none')) raise BestVersionAlreadyInstalled if len(applicable_versions) > 1: logger.info('Using version %s (newest of versions: %s)' % - (applicable_versions[0][1], ', '.join([version for link, version in applicable_versions]))) - return applicable_versions[0][0] + (applicable_versions[0][2], ', '.join([version for parsed_version, link, version in applicable_versions]))) + return applicable_versions[0][1] + def _find_url_name(self, index_url, url_name, req): """Finds the true URL name of a package, when the given name isn't quite correct. @@ -595,7 +595,7 @@ def __str__(self): if self.comes_from: return '%s (from %s)' % (self.url, self.comes_from) else: - return self.url + return str(self.url) def __repr__(self): return '' % self @@ -673,6 +673,9 @@ def hash_name(self): def show_url(self): return posixpath.basename(self.url.split('#', 1)[0].split('?', 1)[0]) +#An "Infinite Link" that compares greater than other links +InfLink = Link(Inf) + def get_requirement_from_url(url): """Get a requirement from the URL, if possible. This looks for #egg diff --git a/tests/test_finder.py b/tests/test_finder.py index afe8a422dfb..dfd3b65127e 100644 --- a/tests/test_finder.py +++ b/tests/test_finder.py @@ -1,10 +1,12 @@ +from pkg_resources import parse_version from pip.backwardcompat import urllib - from pip.req import InstallRequirement from pip.index import PackageFinder - +from pip.exceptions import BestVersionAlreadyInstalled from tests.path import Path from tests.test_pip import here +from nose.tools import assert_raises +from mock import Mock find_links = 'file://' + urllib.quote(str(Path(here).abspath/'packages').replace('\\', '/')) find_links2 = 'file://' + urllib.quote(str(Path(here).abspath/'packages2').replace('\\', '/')) @@ -35,3 +37,41 @@ def test_duplicates_sort_ok(): found = finder.find_requirement(req, False) assert found.url.endswith("duplicate-1.0.tar.gz"), found + + +def test_finder_detects_latest_find_links(): + """Test PackageFinder detects latest using find-links""" + req = InstallRequirement.from_line('simple', None) + finder = PackageFinder([find_links], []) + link = finder.find_requirement(req, False) + assert link.url.endswith("simple-3.0.tar.gz") + + +def test_finder_detects_latest_already_satisfied_find_links(): + """Test PackageFinder detects latest already satisified using find-links""" + req = InstallRequirement.from_line('simple', None) + #the latest simple in local pkgs is 3.0 + latest_version = "3.0" + satisfied_by = Mock( + location = "/path", + parsed_version = parse_version(latest_version), + version = latest_version + ) + req.satisfied_by = satisfied_by + finder = PackageFinder([find_links], []) + assert_raises(BestVersionAlreadyInstalled, finder.find_requirement, req, True) + + +def test_finder_detects_latest_already_satisfied_pypi_links(): + """Test PackageFinder detects latest already satisified using pypi links""" + req = InstallRequirement.from_line('initools', None) + #the latest initools on pypi is 0.3.1 + latest_version = "0.3.1" + satisfied_by = Mock( + location = "/path", + parsed_version = parse_version(latest_version), + version = latest_version + ) + req.satisfied_by = satisfied_by + finder = PackageFinder([], ["http://pypi.python.org/simple"]) + assert_raises(BestVersionAlreadyInstalled, finder.find_requirement, req, True) diff --git a/tests/test_index.py b/tests/test_index.py index 9553483d623..a107b2c58ba 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -2,7 +2,7 @@ from pip.backwardcompat import urllib from tests.path import Path from pip.index import package_to_requirement, HTMLPage, get_mirrors, DEFAULT_MIRROR_HOSTNAME -from pip.index import PackageFinder +from pip.index import PackageFinder, Link, InfLink from tests.test_pip import reset_env, run_pip, pyversion, here from string import ascii_lowercase from mock import patch @@ -101,3 +101,7 @@ def test_file_index_url_quoting(): assert (env.site_packages/'simple') in result.files_created, str(result.stdout) assert (env.site_packages/'simple-1.0-py%s.egg-info' % pyversion) in result.files_created, str(result) + +def test_inflink_greater(): + """Test InfLink compares greater.""" + assert InfLink > Link(object()) diff --git a/tests/test_upgrade.py b/tests/test_upgrade.py index 24449aec076..d67c02865fd 100644 --- a/tests/test_upgrade.py +++ b/tests/test_upgrade.py @@ -49,11 +49,12 @@ def test_upgrade_with_newest_already_installed(): not be reinstalled and the user should be informed. """ + find_links = 'file://' + join(here, 'packages') env = reset_env() - run_pip('install', 'INITools') - result = run_pip('install', '--upgrade', 'INITools') - assert not result.files_created, 'pip install --upgrade INITools upgraded when it should not have' - assert 'already up-to-date' in result.stdout + run_pip('install', '-f', find_links, '--no-index', 'simple') + result = run_pip('install', '--upgrade', '-f', find_links, '--no-index', 'simple') + assert not result.files_created, 'simple upgraded when it should not have' + assert 'already up-to-date' in result.stdout, result.stdout def test_upgrade_force_reinstall_newest(): From 4342328a6a369d276866a61cac3cbcf2ae7130e0 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Fri, 28 Sep 2012 17:14:31 -0700 Subject: [PATCH 109/132] Log URL that we're downloading from at info level (-v) rather than debug level (-vv). --- pip/download.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pip/download.py b/pip/download.py index 7f57911c700..b47627ecf9d 100644 --- a/pip/download.py +++ b/pip/download.py @@ -374,7 +374,7 @@ def _download_url(resp, link, temp_location): logger.start_progress('Downloading %s (unknown size): ' % show_url) else: logger.notify('Downloading %s' % show_url) - logger.debug('Downloading from URL %s' % link) + logger.info('Downloading from URL %s' % link) while True: chunk = resp.read(4096) From c5e0d3521a26400032c53371916e615de658e617 Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Sat, 29 Sep 2012 19:39:54 -0700 Subject: [PATCH 110/132] test resource readme files --- tests/indexes/README.txt | 15 ++++++++++ tests/packages/README.txt | 63 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 tests/indexes/README.txt diff --git a/tests/indexes/README.txt b/tests/indexes/README.txt new file mode 100644 index 00000000000..21384d8807b --- /dev/null +++ b/tests/indexes/README.txt @@ -0,0 +1,15 @@ + +Details on Test Indexes +======================= + +empty_with_pkg +-------------- +empty index, but there's a package in the dir + +in dex +------ +for testing url quoting with indexes + +simple +------ +contains index page for "simple" page diff --git a/tests/packages/README.txt b/tests/packages/README.txt index 069b32c1437..0d25eb59d91 100644 --- a/tests/packages/README.txt +++ b/tests/packages/README.txt @@ -1,9 +1,70 @@ -This package exists for testing uninstall-rollback. +Details on Test Packages +======================== + +broken-0.1.tar.gz +----------------- +This package exists for testing uninstall-rollback. + +broken-0.2broken.tar.gz +----------------------- Version 0.2broken has a setup.py crafted to fail on install (and only on install). If any earlier step would fail (i.e. egg-info-generation), the already-installed version would never be uninstalled, so uninstall-rollback would not come into play. +BrokenEmitsUTF8 +--------------- +for generating unicode error in py3.x + +duplicate-1.0.tar.gz +-------------------- +for testing finding dupes across multiple find-links + +FSPkg +----- +for installing from the file system + +gmpy-1.15.tar.gz +---------------- +hash testing (altough this pkg isn't needed explicitly) + +gmpy2-2.0.tar.gz +---------------- +for testing finder logic when name *contains* the name of the package specified + +HackedEggInfo +------------- +has it's own egg_info class + +LineEndings +----------- +contains DOS line endings + +LocalExtras +----------- +has an extra in a local file:// dependency link + +parent/child-0.1.tar.gz +----------------------- The parent-0.1.tar.gz and child-0.1.tar.gz packages are used by test_uninstall:test_uninstall_overlapping_package. + +paxpkg.tar.bz2 +-------------- +tar with pax headers + +pkgwithmpkg-1.0.tar.gz; pkgwithmpkg-1.0-py2.7-macosx10.7.mpkg.zip +----------------------------------------------------------------- +used for osx test case (tests.test_finder:test_no_mpkg) + +simple-[123].0.tar.gz +--------------------- +contains "simple" package; good for basic testing and version logic. + + + + + + + From 62795f05a5753455bb780fbb60cab197762bb866 Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Sat, 29 Sep 2012 19:40:16 -0700 Subject: [PATCH 111/132] pip project readme --- README.rst | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 README.rst diff --git a/README.rst b/README.rst new file mode 100644 index 00000000000..251efd998aa --- /dev/null +++ b/README.rst @@ -0,0 +1,7 @@ +pip +=== + +.. image:: https://secure.travis-ci.org/pypa/pip.png?branch=develop + :target: http://travis-ci.org/pypa/pip + +For documentation, see http://www.pip-installer.org From fa42a411b628acd6be33e7645973b9b514c20edc Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Sat, 29 Sep 2012 19:44:13 -0700 Subject: [PATCH 112/132] fix readme typo --- tests/indexes/README.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/indexes/README.txt b/tests/indexes/README.txt index 21384d8807b..8e430effdba 100644 --- a/tests/indexes/README.txt +++ b/tests/indexes/README.txt @@ -12,4 +12,4 @@ for testing url quoting with indexes simple ------ -contains index page for "simple" page +contains index page for "simple" pkg From 4dceb6ce870bf02698dee137a187d81932f38ad2 Mon Sep 17 00:00:00 2001 From: Maxime Rouyrre Date: Tue, 2 Oct 2012 03:28:44 +0200 Subject: [PATCH 113/132] Test HTMLPage link filter by rel attribute. cf. commit 0bb9c31aeab89eba17de26802e6b01c9cafeeb39 --- tests/test_index.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/test_index.py b/tests/test_index.py index c537172aae0..389c4fccaa8 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -28,6 +28,30 @@ def test_html_page_should_be_able_to_scrap_rel_links(): assert len(links) == 1 assert links[0].url == 'http://supervisord.org/' + +def test_html_page_should_be_able_to_filter_links_by_rel(): + """ + Test selecting links by the rel attribute + """ + page = HTMLPage(""" + Some page + Download URL + Homepage + """, "archive") + + links = list(page.rel_links()) + urls = [l.url for l in links] + hlinks = list(page.rel_links(('homepage',))) + dlinks = list(page.rel_links(('download',))) + assert len(links) == 2 + assert 'http://example.com/archive-1.2.3.tar.gz' in urls + assert 'http://example.com/home.html' in urls + assert len(hlinks) == 1 + assert hlinks[0].url == 'http://example.com/home.html' + assert len(dlinks) == 1 + assert dlinks[0].url == 'http://example.com/archive-1.2.3.tar.gz' + + @patch('socket.gethostbyname_ex') def test_get_mirrors(mock_gethostbyname_ex): # Test when the expected result comes back From 6a483a3778fb6ebe7b7e7557e32383ba78d4801e Mon Sep 17 00:00:00 2001 From: Maxime Rouyrre Date: Tue, 2 Oct 2012 03:33:29 +0200 Subject: [PATCH 114/132] Update method description to match expected behaviour. --- pip/index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pip/index.py b/pip/index.py index 4fb5ab25319..15f829b793e 100644 --- a/pip/index.py +++ b/pip/index.py @@ -219,7 +219,7 @@ def _find_url_name(self, index_url, url_name, req): def _get_pages(self, locations, req): """Yields (page, page_url) from the given locations, skipping - locations that have errors, and adding download/homepage links""" + locations that have errors, and adding homepage links""" pending_queue = Queue() for location in locations: pending_queue.put(location) From 47af6d4094eb63a75d941b6e01fb0838e8b193d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Tue, 2 Oct 2012 21:19:56 +0300 Subject: [PATCH 115/132] Append / to the PyPI URL to reduce unnecessary redirects --- pip/index.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/pip/index.py b/pip/index.py index 55c49bf1bbb..7f212897ec6 100644 --- a/pip/index.py +++ b/pip/index.py @@ -101,12 +101,22 @@ def sort_path(path): return files, urls def find_requirement(self, req, upgrade): + def mkurl_pypi_url(url): + loc = posixpath.join(url, url_name) + # For maximum compatibility with easy_install, ensure the path + # ends in a trailing slash. Although this isn't in the spec + # (and PyPI can handle it without the slash) some other index + # implementations might break if they relied on easy_install's behavior. + if not loc.endswith('/'): + loc = loc + '/' + return loc + url_name = req.url_name # Only check main index if index URL is given: main_index_url = None if self.index_urls: # Check that we have the url_name correctly spelled: - main_index_url = Link(posixpath.join(self.index_urls[0], url_name)) + main_index_url = Link(mkurl_pypi_url(self.index_urls[0])) # This will also cache the page, so it's okay that we get it again later: page = self._get_page(main_index_url, req) if page is None: @@ -116,15 +126,6 @@ def find_requirement(self, req, upgrade): # adding more index URLs from requirements files all_index_urls = self.index_urls + self.mirror_urls - def mkurl_pypi_url(url): - loc = posixpath.join(url, url_name) - # For maximum compatibility with easy_install, ensure the path - # ends in a trailing slash. Although this isn't in the spec - # (and PyPI can handle it without the slash) some other index - # implementations might break if they relied on easy_install's behavior. - if not loc.endswith('/'): - loc = loc + '/' - return loc if url_name is not None: locations = [ mkurl_pypi_url(url) From c1f77192793a65d23217ddd2d6e8bf2a57f76e4d Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Wed, 3 Oct 2012 18:51:49 -0700 Subject: [PATCH 116/132] use local packages in req order test --- tests/test_requirements.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/tests/test_requirements.py b/tests/test_requirements.py index b6ed77b4fdd..47a274824cc 100644 --- a/tests/test_requirements.py +++ b/tests/test_requirements.py @@ -80,20 +80,23 @@ def test_multiple_requirements_files(): def test_respect_order_in_requirements_file(): env = reset_env() write_file('frameworks-req.txt', textwrap.dedent("""\ - bidict - ordereddict - initools + parent + child + simple """)) - result = run_pip('install', '-r', env.scratch_path / 'frameworks-req.txt') + + find_links = 'file://' + os.path.join(here, 'packages') + result = run_pip('install', '--no-index', '-f', find_links, '-r', env.scratch_path / 'frameworks-req.txt') + downloaded = [line for line in result.stdout.split('\n') if 'Downloading/unpacking' in line] - assert 'bidict' in downloaded[0], 'First download should ' \ - 'be "bidict" but was "%s"' % downloaded[0] - assert 'ordereddict' in downloaded[1], 'Second download should ' \ - 'be "ordereddict" but was "%s"' % downloaded[1] - assert 'initools' in downloaded[2], 'Third download should ' \ - 'be "initools" but was "%s"' % downloaded[2] + assert 'parent' in downloaded[0], 'First download should ' \ + 'be "parent" but was "%s"' % downloaded[0] + assert 'child' in downloaded[1], 'Second download should ' \ + 'be "child" but was "%s"' % downloaded[1] + assert 'simple' in downloaded[2], 'Third download should ' \ + 'be "simple" but was "%s"' % downloaded[2] def test_requirements_data_structure_keeps_order(): From 6364145586233f78573dabfa7e0e65e6cf0565d4 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Thu, 4 Oct 2012 23:27:54 +0300 Subject: [PATCH 117/132] =?UTF-8?q?Updated=20authors=20file=20with=20Aziz?= =?UTF-8?q?=20K=C3=B6ksal=20for=20the=20tests/path.py=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit He allowed us to release the tests/path.py file under the MIT license a while ago https://groups.google.com/group/python-virtualenv/msg/ea453f14a2cd1329?dmode=source&output=gplain&noredirect&pli=1 --- AUTHORS.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.txt b/AUTHORS.txt index 9d1b288f4bd..b567d7ee1b7 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -4,6 +4,7 @@ Alexandre Conrad Andrey Bulgakov Antti Kaihola Armin Ronacher +Aziz Köksal Brian Rosner Carl Meyer Chris McDonough From cd679a14aafa99c8368011d09e3fdc563a7a0751 Mon Sep 17 00:00:00 2001 From: Ben Rosser Date: Thu, 4 Oct 2012 20:30:36 -0400 Subject: [PATCH 118/132] Added changelog entry to news.txt --- docs/news.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/news.txt b/docs/news.txt index f3d34a6dc76..99e59489e31 100644 --- a/docs/news.txt +++ b/docs/news.txt @@ -20,7 +20,10 @@ develop (unreleased) Hsiaoming Yang and Markus Hametner. * Use a temporary directory as the default build location outside of a - virtualenv. Fixes issues #339 and #381. Thanks TC01. + virtualenv. Fixes issues #339 and #381. Thanks Ben Rosser. + +* Moved pip configuration data to ~/.config/pip, the XDG standard for config + files on Unix/Linux systems. Thanks Ben Rosser. * Added support for specifying extras with local editables. Thanks Nick Stenning. From 40ec94fc8ee7393db8b458b889e1a9fcb834eaa6 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 5 Oct 2012 20:05:20 +0200 Subject: [PATCH 119/132] Fixed the argument signature of two InstallationError instances. --- pip/req.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pip/req.py b/pip/req.py index 3615a1d6c47..e814d399002 100644 --- a/pip/req.py +++ b/pip/req.py @@ -95,7 +95,7 @@ def from_line(cls, name, comes_from=None): link = Link(name) elif os.path.isdir(path) and (os.path.sep in name or name.startswith('.')): if not is_installable_dir(path): - raise InstallationError("Directory %r is not installable. File 'setup.py' not found.", name) + raise InstallationError("Directory %r is not installable. File 'setup.py' not found." % name) link = Link(path_to_url(name)) elif is_archive_file(path): if not os.path.isfile(path): @@ -1356,7 +1356,7 @@ def parse_editable(editable_req, default_vcs=None): if os.path.isdir(url_no_extras): if not os.path.exists(os.path.join(url_no_extras, 'setup.py')): - raise InstallationError("Directory %r is not installable. File 'setup.py' not found.", url_no_extras) + raise InstallationError("Directory %r is not installable. File 'setup.py' not found." % url_no_extras) # Treating it as code that has already been checked out url_no_extras = path_to_url(url_no_extras) From 061954f29de70ed9bc9e58845f73acd2d5637e67 Mon Sep 17 00:00:00 2001 From: hetmankp Date: Mon, 1 Oct 2012 18:27:35 +1000 Subject: [PATCH 120/132] Added --root command line option, works the same as for distutils --- pip/commands/install.py | 9 ++++++++- pip/req.py | 20 +++++++++++++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/pip/commands/install.py b/pip/commands/install.py index 9900c434aca..99947bdff9e 100644 --- a/pip/commands/install.py +++ b/pip/commands/install.py @@ -172,6 +172,13 @@ def __init__(self): action='store_true', help="Install as self contained egg file, like easy_install does.") + self.parser.add_option( + '--root', + dest='root_path', + metavar='DIR', + default=None, + help="Install everything relative to this alternate root directory") + def _build_package_finder(self, options, index_urls): """ Create a package finder appropriate to this install command. @@ -258,7 +265,7 @@ def run(self, options, args): requirement_set.locate_files() if not options.no_install and not self.bundle: - requirement_set.install(install_options, global_options) + requirement_set.install(install_options, global_options, root=options.root_path) installed = ' '.join([req.name for req in requirement_set.successfully_installed]) if installed: diff --git a/pip/req.py b/pip/req.py index e814d399002..fe67478374c 100644 --- a/pip/req.py +++ b/pip/req.py @@ -8,6 +8,7 @@ import tempfile import zipfile +from distutils.util import change_root from pip.locations import bin_py, running_under_virtualenv from pip.exceptions import (InstallationError, UninstallationError, BestVersionAlreadyInstalled, @@ -557,7 +558,7 @@ def _clean_zip_name(self, name, prefix): name = name.replace(os.path.sep, '/') return name - def install(self, install_options, global_options=()): + def install(self, install_options, global_options=(), root=None): if self.editable: self.install_editable(install_options, global_options) return @@ -576,6 +577,9 @@ def install(self, install_options, global_options=()): if not self.as_egg: install_args += ['--single-version-externally-managed'] + if root is not None: + install_args += ['--root', root] + if running_under_virtualenv(): ## FIXME: I'm not sure if this is a reasonable location; probably not ## but we can't put it in the default location, as that is a virtualenv symlink that isn't writable @@ -598,11 +602,17 @@ def install(self, install_options, global_options=()): # so we unable to save the installed-files.txt return + def prepend_root(path): + if root is None or not os.path.isabs(path): + return path + else: + return change_root(root, path) + f = open(record_filename) for line in f: line = line.strip() if line.endswith('.egg-info'): - egg_info_dir = line + egg_info_dir = prepend_root(line) break else: logger.warn('Could not find .egg-info directory in install record for %s' % self) @@ -616,7 +626,7 @@ def install(self, install_options, global_options=()): filename = line.strip() if os.path.isdir(filename): filename += os.path.sep - new_lines.append(make_path_relative(filename, egg_info_dir)) + new_lines.append(make_path_relative(prepend_root(filename), egg_info_dir)) f.close() f = open(os.path.join(egg_info_dir, 'installed-files.txt'), 'w') f.write('\n'.join(new_lines)+'\n') @@ -1145,7 +1155,7 @@ def unpack_url(self, link, location, only_download=False): _write_delete_marker_message(os.path.join(location, PIP_DELETE_MARKER_FILENAME)) return retval - def install(self, install_options, global_options=()): + def install(self, install_options, global_options=(), *args, **kwargs): """Install everything in this set (after having downloaded and unpacked the packages)""" to_install = [r for r in self.requirements.values() if not r.satisfied_by] @@ -1164,7 +1174,7 @@ def install(self, install_options, global_options=()): finally: logger.indent -= 2 try: - requirement.install(install_options, global_options) + requirement.install(install_options, global_options, *args, **kwargs) except: # if install did not succeed, rollback previous uninstall if requirement.conflicts_with and not requirement.install_succeeded: From 1faa0c1876a0cd1ea64e975134c0e935b1f48602 Mon Sep 17 00:00:00 2001 From: hetmankp Date: Tue, 2 Oct 2012 13:55:56 +1000 Subject: [PATCH 121/132] Simple test to check --root option functionality --- tests/test_basic.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_basic.py b/tests/test_basic.py index e1b990a3539..4478941fbe4 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -491,6 +491,17 @@ def test_install_package_with_target(): assert Path('scratch')/'target'/'initools' in result.files_created, str(result) +def test_install_package_with_root(): + """ + Test installing a package using pip install --root + """ + env = reset_env() + root_dir = env.scratch_path/'root' + result = run_pip('install', '--root', root_dir, '--install-option=--home=', "initools==0.1") + + assert Path('scratch')/'root'/'lib'/'python'/'initools' in result.files_created, str(result) + + def test_find_command_folder_in_path(): """ If a folder named e.g. 'git' is in PATH, and find_command is looking for From 294d4aefbc61c219f05c67d9a147bed91d53e5ce Mon Sep 17 00:00:00 2001 From: hetmankp Date: Wed, 3 Oct 2012 16:07:21 +1000 Subject: [PATCH 122/132] PyPy is special and overrides default distutils directory scheme, alter test to suit --- tests/test_basic.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_basic.py b/tests/test_basic.py index 4478941fbe4..d5a054b14fb 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -497,7 +497,8 @@ def test_install_package_with_root(): """ env = reset_env() root_dir = env.scratch_path/'root' - result = run_pip('install', '--root', root_dir, '--install-option=--home=', "initools==0.1") + result = run_pip('install', '--root', root_dir, '--install-option=--home=', + '--install-option=--install-lib=/lib/python', "initools==0.1") assert Path('scratch')/'root'/'lib'/'python'/'initools' in result.files_created, str(result) From 758c3368e9955ff6589ad4f0f31ad5168937b4c5 Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Wed, 10 Oct 2012 21:47:27 -0700 Subject: [PATCH 123/132] finder link priority --- pip/index.py | 15 +++++++++------ pip/util.py | 3 ++- tests/test_finder.py | 17 +++++++++++++++++ 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/pip/index.py b/pip/index.py index 7f212897ec6..e52945497e1 100644 --- a/pip/index.py +++ b/pip/index.py @@ -101,6 +101,7 @@ def sort_path(path): return files, urls def find_requirement(self, req, upgrade): + def mkurl_pypi_url(url): loc = posixpath.join(url, url_name) # For maximum compatibility with easy_install, ensure the path @@ -166,12 +167,13 @@ def mkurl_pypi_url(url): logger.fatal('Could not find any downloads that satisfy the requirement %s' % req) raise DistributionNotFound('No distributions at all found for %s' % req) if req.satisfied_by is not None: - found_versions.append((req.satisfied_by.parsed_version, InfLink, req.satisfied_by.version)) + #make sure the installed dist has priority (over equivalent "found" versions) + found_versions.insert(0, (req.satisfied_by.parsed_version, InfLink, req.satisfied_by.version)) if file_versions: file_versions.sort(reverse=True) logger.info('Local files found: %s' % ', '.join([url_to_path(link.url) for parsed, link, version in file_versions])) - found_versions = file_versions + found_versions - all_versions = found_versions + page_versions + dependency_versions + #this is an intentional priority ordering + all_versions = found_versions + file_versions + page_versions + dependency_versions applicable_versions = [] for (parsed_version, link, version) in all_versions: if version not in req.req: @@ -179,7 +181,8 @@ def mkurl_pypi_url(url): % (link, version, ','.join([''.join(s) for s in req.req.specs]))) continue applicable_versions.append((parsed_version, link, version)) - applicable_versions = sorted(applicable_versions, reverse=True) + #bring the latest version to the front, but maintains the priority ordering as secondary + applicable_versions = sorted(applicable_versions, key=lambda v: v[0], reverse=True) existing_applicable = bool([link for parsed_version, link, version in applicable_versions if link is InfLink]) if not upgrade and existing_applicable: if applicable_versions[0][1] is InfLink: @@ -191,7 +194,7 @@ def mkurl_pypi_url(url): return None if not applicable_versions: logger.fatal('Could not find a version that satisfies the requirement %s (from versions: %s)' - % (req, ', '.join([version for parsed_version, link, version in found_versions]))) + % (req, ', '.join([version for parsed_version, link, version in all_versions]))) raise DistributionNotFound('No distributions matching the version for %s' % req) if applicable_versions[0][1] is InfLink: # We have an existing version, and its the best version @@ -675,7 +678,7 @@ def show_url(self): return posixpath.basename(self.url.split('#', 1)[0].split('?', 1)[0]) #An "Infinite Link" that compares greater than other links -InfLink = Link(Inf) +InfLink = Link(Inf) #this object is not currently used as a sortable def get_requirement_from_url(url): diff --git a/pip/util.py b/pip/util.py index 7db95745cc6..c37432da40c 100644 --- a/pip/util.py +++ b/pip/util.py @@ -161,7 +161,8 @@ def __ge__(self, other): def __repr__(self): return 'Inf' -Inf = _Inf() + +Inf = _Inf() #this object is not currently used as a sortable in our code del _Inf diff --git a/tests/test_finder.py b/tests/test_finder.py index dfd3b65127e..0cb60d16f36 100644 --- a/tests/test_finder.py +++ b/tests/test_finder.py @@ -75,3 +75,20 @@ def test_finder_detects_latest_already_satisfied_pypi_links(): req.satisfied_by = satisfied_by finder = PackageFinder([], ["http://pypi.python.org/simple"]) assert_raises(BestVersionAlreadyInstalled, finder.find_requirement, req, True) + + +def test_finder_priority_file_over_page(): + """Test PackageFinder prefers file links over equivalent page links""" + req = InstallRequirement.from_line('gmpy==1.15', None) + finder = PackageFinder([find_links], ["http://pypi.python.org/simple"]) + link = finder.find_requirement(req, False) + assert link.url.startswith("file://") + + +def test_finder_priority_page_over_deplink(): + """Test PackageFinder prefers page links over equivalent dependency links""" + req = InstallRequirement.from_line('gmpy==1.15', None) + finder = PackageFinder([], ["http://pypi.python.org/simple"]) + finder.add_dependency_links(['http://c.pypi.python.org/simple/gmpy/']) + link = finder.find_requirement(req, False) + assert link.url.startswith("http://pypi") From 82452a139c8774e7383140cb5220ad9e8509a584 Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Wed, 10 Oct 2012 22:29:16 -0700 Subject: [PATCH 124/132] finder link priority test; non-egg over egg fragment url --- tests/test_finder.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_finder.py b/tests/test_finder.py index 0cb60d16f36..415e9a9dce9 100644 --- a/tests/test_finder.py +++ b/tests/test_finder.py @@ -92,3 +92,21 @@ def test_finder_priority_page_over_deplink(): finder.add_dependency_links(['http://c.pypi.python.org/simple/gmpy/']) link = finder.find_requirement(req, False) assert link.url.startswith("http://pypi") + + +def test_finder_priority_nonegg_over_eggfragments(): + """Test PackageFinder prefers non-egg links over "#egg=" links""" + req = InstallRequirement.from_line('bar==1.0', None) + links = ['http://foo/bar.py#egg=bar-1.0', 'http://foo/bar-1.0.tar.gz'] + + finder = PackageFinder(links, []) + link = finder.find_requirement(req, False) + assert link.url.endswith('tar.gz') + + links.reverse() + finder = PackageFinder(links, []) + link = finder.find_requirement(req, False) + assert link.url.endswith('tar.gz') + + + From c770ad66db9f0b74a42592d2d9ff6f92deb6714e Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Sat, 13 Oct 2012 23:03:09 -0700 Subject: [PATCH 125/132] let installed version be it's own item --- pip/index.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pip/index.py b/pip/index.py index e52945497e1..d3cc491f6fc 100644 --- a/pip/index.py +++ b/pip/index.py @@ -166,14 +166,14 @@ def mkurl_pypi_url(url): if not found_versions and not page_versions and not dependency_versions and not file_versions: logger.fatal('Could not find any downloads that satisfy the requirement %s' % req) raise DistributionNotFound('No distributions at all found for %s' % req) + installed_version = [] if req.satisfied_by is not None: - #make sure the installed dist has priority (over equivalent "found" versions) - found_versions.insert(0, (req.satisfied_by.parsed_version, InfLink, req.satisfied_by.version)) + installed_version = [(req.satisfied_by.parsed_version, InfLink, req.satisfied_by.version)] if file_versions: file_versions.sort(reverse=True) logger.info('Local files found: %s' % ', '.join([url_to_path(link.url) for parsed, link, version in file_versions])) #this is an intentional priority ordering - all_versions = found_versions + file_versions + page_versions + dependency_versions + all_versions = installed_version + file_versions + found_versions + page_versions + dependency_versions applicable_versions = [] for (parsed_version, link, version) in all_versions: if version not in req.req: From 403f8d53a52abfdfdd7b9ad674c7479b72746eb9 Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Wed, 17 Oct 2012 22:59:43 -0700 Subject: [PATCH 126/132] --user installs work properly with --upgrade --- docs/news.txt | 2 ++ pip/req.py | 12 +++++++++--- tests/test_user_site.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/docs/news.txt b/docs/news.txt index d512783b573..fe1dbb61415 100644 --- a/docs/news.txt +++ b/docs/news.txt @@ -14,6 +14,8 @@ Beta and final releases planned for the end of 2012. develop (unreleased) -------------------- +* --user/--upgrade install options now work together; thanks eevee. + * Added check in ``install --download`` to prevent re-downloading if the target file already exists. Thanks Andrey Bulgakov. diff --git a/pip/req.py b/pip/req.py index e814d399002..6fa43ac489b 100644 --- a/pip/req.py +++ b/pip/req.py @@ -903,7 +903,9 @@ def locate_files(self): req_to_install.check_if_exists() if req_to_install.satisfied_by: if self.upgrade: - req_to_install.conflicts_with = req_to_install.satisfied_by + #don't uninstall conflict if user install and and conflict is not user install + if not (self.use_user_site and not dist_in_usersite(req_to_install.satisfied_by)): + req_to_install.conflicts_with = req_to_install.satisfied_by req_to_install.satisfied_by = None else: install_needed = False @@ -955,7 +957,9 @@ def prepare_files(self, finder, force_root_egg_info=False, bundle=False): req_to_install.url = url.url if not best_installed: - req_to_install.conflicts_with = req_to_install.satisfied_by + #don't uninstall conflict if user install and conflict is not user install + if not (self.use_user_site and not dist_in_usersite(req_to_install.satisfied_by)): + req_to_install.conflicts_with = req_to_install.satisfied_by req_to_install.satisfied_by = None else: install = False @@ -1054,7 +1058,9 @@ def prepare_files(self, finder, force_root_egg_info=False, bundle=False): req_to_install.check_if_exists() if req_to_install.satisfied_by: if self.upgrade or self.ignore_installed: - req_to_install.conflicts_with = req_to_install.satisfied_by + #don't uninstall conflict if user install and and conflict is not user install + if not (self.use_user_site and not dist_in_usersite(req_to_install.satisfied_by)): + req_to_install.conflicts_with = req_to_install.satisfied_by req_to_install.satisfied_by = None else: install = False diff --git a/tests/test_user_site.py b/tests/test_user_site.py index 2d1eec924c8..e4f5c73a8f5 100644 --- a/tests/test_user_site.py +++ b/tests/test_user_site.py @@ -148,6 +148,37 @@ def test_install_user_conflict_in_globalsite(self): assert isdir(initools_folder) + def test_upgrade_user_conflict_in_globalsite(self): + """ + Test user install/upgrade with conflict in global site ignores site and installs to usersite + """ + + # the test framework only supports testing using virtualenvs + # the sys.path ordering for virtualenvs with --system-site-packages is this: virtualenv-site, user-site, global-site + # this test will use 2 modifications to simulate the user-site/global-site relationship + # 1) a monkey patch which will make it appear INITools==0.2 is not in in the virtualenv site + # if we don't patch this, pip will return an installation error: "Will not install to the usersite because it will lack sys.path precedence..." + # 2) adding usersite to PYTHONPATH, so usersite as sys.path precedence over the virtualenv site + + env = reset_env(system_site_packages=True, sitecustomize=patch_dist_in_site_packages) + env.environ["PYTHONPATH"] = env.root_path / env.user_site + + result1 = run_pip('install', 'INITools==0.2') + result2 = run_pip('install', '--user', '--upgrade', 'INITools') + + #usersite has 0.3.1 + egg_info_folder = env.user_site / 'INITools-0.3.1-py%s.egg-info' % pyversion + initools_folder = env.user_site / 'initools' + assert egg_info_folder in result2.files_created, str(result2) + assert initools_folder in result2.files_created, str(result2) + + #site still has 0.2 (can't look in result1; have to check) + egg_info_folder = env.root_path / env.site_packages / 'INITools-0.2-py%s.egg-info' % pyversion + initools_folder = env.root_path / env.site_packages / 'initools' + assert isdir(egg_info_folder), result2.stdout + assert isdir(initools_folder) + + def test_install_user_conflict_in_globalsite_and_usersite(self): """ Test user install with conflict in globalsite and usersite ignores global site and updates usersite. From a10a7443226258fd890992283b5d84ecb21074ab Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Wed, 24 Oct 2012 11:27:58 +0100 Subject: [PATCH 127/132] Trigger travis build with one line change. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 24be5ebaf42..ed114e9a4ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,3 +20,4 @@ matrix: allow_failures: env: - PIP_USE_MIRRORS=true + From 91c102ddfa456e3484723ce4a620bb8dfb807c6a Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Wed, 24 Oct 2012 12:42:57 +0100 Subject: [PATCH 128/132] Allow failures for slow py 3.1 whilst debugging --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ed114e9a4ae..3dfbc53b8d1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,6 @@ branches: - develop matrix: allow_failures: + - python: 3.1 env: - PIP_USE_MIRRORS=true - From 0b2a01bb46923e5a9138237b2cc47b6ed22bc9ca Mon Sep 17 00:00:00 2001 From: hetmankp Date: Mon, 29 Oct 2012 12:59:52 +1100 Subject: [PATCH 129/132] Added author name and description of changes --- AUTHORS.txt | 1 + docs/news.txt | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/AUTHORS.txt b/AUTHORS.txt index b567d7ee1b7..636fcd25439 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -47,6 +47,7 @@ Paul van der Linden Peter Waller Phil Whelan Piet Delport +Przemek Wrzos Qiangning Hong Rafael Caricio Rene Dudfield diff --git a/docs/news.txt b/docs/news.txt index d512783b573..1f92adecb13 100644 --- a/docs/news.txt +++ b/docs/news.txt @@ -25,6 +25,10 @@ develop (unreleased) * Added "pip show" command to get information about an installed package. Fixes #131. Thanks Kelsey Hightower and Rafael Caricio. +* Added `--root` option for "pip install" to specify root directory. Behaves + like the same option in distutils but also plays nice with pip's egg-info. + (Issue #253) + 1.2.1 (2012-09-06) ------------------ From 843ffb38fec150a6ec4a4fce4ebf14178e9ed094 Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Wed, 7 Nov 2012 19:24:44 -0800 Subject: [PATCH 130/132] correct extras syntax in docs --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 78502e46853..90491298d91 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -104,7 +104,7 @@ A line beginning with ``#`` is treated as a comment and ignored. You can also request `extras`_ in the requirements file:: - MyPackage==3.0 [PDF] + MyPackage[PDF]==3.0 .. _extras: http://peak.telecommunity.com/DevCenter/setuptools#declaring-extras-optional-features-with-their-own-dependencies From dc5e825d4e915ff39a2368f9fb4d2c15ca358aa9 Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Tue, 13 Nov 2012 21:29:58 -0800 Subject: [PATCH 131/132] upper case url reqs should be recognized as same as other lower case forms --- pip/req.py | 4 ++-- tests/packages/README.txt | 5 +++++ tests/packages/Upper-1.0.tar.gz | Bin 0 -> 675 bytes tests/packages/Upper-2.0.tar.gz | Bin 0 -> 677 bytes tests/packages/requiresupper-1.0.tar.gz | Bin 0 -> 740 bytes tests/test_requirements.py | 23 +++++++++++++++++++++++ 6 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 tests/packages/Upper-1.0.tar.gz create mode 100644 tests/packages/Upper-2.0.tar.gz create mode 100644 tests/packages/requiresupper-1.0.tar.gz diff --git a/pip/req.py b/pip/req.py index dbe7af27b7f..559c1fc58f9 100644 --- a/pip/req.py +++ b/pip/req.py @@ -1094,8 +1094,8 @@ def prepare_files(self, finder, force_root_egg_info=False, bundle=False): subreq = InstallRequirement(req, req_to_install) reqs.append(subreq) self.add_requirement(subreq) - if req_to_install.name not in self.requirements: - self.requirements[req_to_install.name] = req_to_install + if not self.has_requirement(req_to_install.name): + self.add_requirement(req_to_install) if self.is_download or req_to_install._temp_build_dir is not None: self.reqs_to_cleanup.append(req_to_install) else: diff --git a/tests/packages/README.txt b/tests/packages/README.txt index 0d25eb59d91..fdf925d6e96 100644 --- a/tests/packages/README.txt +++ b/tests/packages/README.txt @@ -62,6 +62,11 @@ simple-[123].0.tar.gz --------------------- contains "simple" package; good for basic testing and version logic. +Upper-[12].0.tar.gz and requiresuppper-1.0.tar.gz +-------------------------------------------------- +'requiresupper' requires 'upper' +used for testing case mismatch case for url requirements + diff --git a/tests/packages/Upper-1.0.tar.gz b/tests/packages/Upper-1.0.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..e0d28fdc7629b893b60b6f93f6ea4f87920c10f3 GIT binary patch literal 675 zcmV;U0$lwciwFoPB%@FQ166QvWpXVsE-)^1VR8WNnA>ifKoEwx<|(%7g%c^5&e-{R3I9QtbXJ==UWh~c>KGl`h zpc2n1XUgO;{&zR; zwU5Jhqjuu&+VQu>BB!_n8F`XNo2mIZURF}*6qk2G&yYb*((cw@bdri>R`kG)~VG>=Ky8djb?kWH3{33QdgD)=)VK7oLnOO0Q%S0o#5%eaj^e4x8}c=Tg`uM=)W6YzR=4e*Y6{) zGrl0%Qts8cI?L09)CnQdG%iS#WN9u|mMb!gc<5)r^g(X>3stSH7d%MVpjW3;?@A># zi`D30(3AP~Xk0%yc=DQy%fIeR6r=O#f3+EF;~U4%e=Vcx|1tmVKK^ZN#t-*@UKM}S zu}#x6%xeB?S_bNWC-pxplVpjS3jhEB0000000000000000000000000006*q;19?F JlAr)k006`tSZ)9S literal 0 HcmV?d00001 diff --git a/tests/packages/Upper-2.0.tar.gz b/tests/packages/Upper-2.0.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..81ff441daa652886493beabd5bd9f1247dbfa9c5 GIT binary patch literal 677 zcmV;W0$TkaiwFpmCZkXS166QvWpXVtE-)^1VR8WNm`!h+KoEvG^DDOMA&C^sf{l?P z`B3#+N$seX+e1|$0&5oq77B}7|NAcM`Xk7eWy#>CdY=n0vlvS=@9gYMvW(@1-Jzz| z7*ygqPIa|hhi+e2XqHVK+PAy5-P5RLxh~a+qqRU;2tOx8`}r$KL$-h2A^$UFvX1}# z?R(?n@ZG4H`1gCgz45pERxSQrxn3g_@o$_@O!%QMeB%qtizwwc#OhFe=qK!ksNB%k z4w)qSxJ(j1Ur68Zb~ySv)IX#NGcte1Hl|ml_>t!IXM-hv6mP6|QNVby_1YU&1bLJR z^{tihKK8{l&6ABq1I_5aV4}=A!F1NT{;Auo>%VVV(0?0zW3!nT@oD;97yit9D5E$e z1EL;eU10K_s)u6Ey`0UXsun0Cl=y$?U)^4{kOrg>(3VYp7O8nPm8)MWEtZj z+(}bvm5NR40BucV07fZ`EnMOSHGCz2f$NoasmGzSQ2^(Cfbh^0K ziDJGS9SkmHeyxqG7Y9#%=JM*V`wGSAJo;ZA#+vxX@$=uVRrUXv|F$3hCJy6=`#-me zzwPvF+qLX!{wos!^}m(+AC^h7K+OdJ000000000000000000000000000000;5qOI L40zPm08jt`7jt3< literal 0 HcmV?d00001 diff --git a/tests/packages/requiresupper-1.0.tar.gz b/tests/packages/requiresupper-1.0.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..fe121a90f23a2a8b5baa769178bb02093de3340e GIT binary patch literal 740 zcmVCb9HcVWpXVsE-)^1VR8WNnO#rYFcgM!onPT; z7X^tncG4sgQZJ@`3s%q&+XX_AY20FwFA}F)|NWA5rA-l(t|4OLeQr`8JF?ny-q)_3 zirZYKBFmFRq+QmdhSoT=z_so2$y}SRJ{M@rq&9U-%dv_$vm8b>VrwmsXNsqUXt%#c zu`eDTx2gX>CB2vb)1%MbuftC#&F0_f_aDr^>9V!__g!jhgd+dX&NrdB&lT_f5NRgk z=#UgOpbvQ{4oR&x^xA;4JPdg{J0$1Bqv6TVq5dTfMK|FSu?)S-)peY%x4R0ii5G~e2>zvBq5mgl zs3H9q%{Be^O~-=%+W_k*^#3INuexr->p!LT`ubm;|3UvPeEl-ZkOB0+N&h?Ss5a`K zIqUj&i{a3J+xf?40QxuX{t-4d$433T*4q7#-M1L@-v$?j!F#2_U+RiaysKOWJ{gdz z;d3R5&$Mb})5uH3RF-FeRS;(J3f6z4DgAGE|L@qQh5MhEkpGqAzvuIR>X!L8?S8TG zV`h2(=fMBl;jN#C;S635|F7@=c98ql`Zry7>Hj7x_kWg!^PhI~|HjC()VPw7A)+Zs zX6ibQ^l=)8q$&gz$3aG9n8c}?M-IqXM!uKu(G8!7OrKvI?L|BkgHElSItMzbn9i?` z2c4q6?t(HZe)%Bq7OxbW%8SnYz01Xz#Uz8;lzaPsyZ7N+00000000000000000000 W0000000019Cw~A7nWV}9Pyhfz=YPKd literal 0 HcmV?d00001 diff --git a/tests/test_requirements.py b/tests/test_requirements.py index 47a274824cc..a21478ac6e3 100644 --- a/tests/test_requirements.py +++ b/tests/test_requirements.py @@ -184,3 +184,26 @@ def test_install_local_editable_with_extras(): assert env.site_packages/'easy-install.pth' in res.files_updated assert env.site_packages/'LocalExtras.egg-link' in res.files_created assert env.site_packages/'simple' in res.files_created + + +def test_url_req_case_mismatch(): + """ + tar ball url requirements (with no egg fragment), that happen to have upper case project names, + should be considered equal to later requirements that reference the project name using lower case. + + tests/packages contains Upper-1.0.tar.gz and Upper-2.0.tar.gz + 'requiresupper' has install_requires = ['upper'] + """ + env = reset_env() + find_links = 'file://' + os.path.join(here, 'packages') + Upper = os.path.join(find_links, 'Upper-1.0.tar.gz') + result = run_pip('install', '--no-index', '-f', find_links, Upper, 'requiresupper') + + #only Upper-1.0.tar.gz should get installed. + egg_folder = env.site_packages / 'Upper-1.0-py%s.egg-info' % pyversion + assert egg_folder in result.files_created, str(result) + egg_folder = env.site_packages / 'Upper-2.0-py%s.egg-info' % pyversion + assert egg_folder not in result.files_created, str(result) + + + From d882827e3f986f15ebdd3e9e82db9bc39890db88 Mon Sep 17 00:00:00 2001 From: Marcus Smith Date: Wed, 14 Nov 2012 12:44:05 -0800 Subject: [PATCH 132/132] comments on 'unnamed' requirements --- pip/req.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pip/req.py b/pip/req.py index 559c1fc58f9..16c3cff6abc 100644 --- a/pip/req.py +++ b/pip/req.py @@ -107,7 +107,7 @@ def from_line(cls, name, comes_from=None): # Otherwise, assume the name is the req for the non URL/path/archive case. if link and req is None: url = link.url_without_fragment - req = link.egg_fragment + req = link.egg_fragment #when fragment is None, this will become an 'unnamed' requirement # Handle relative file URLs if link.scheme == 'file' and re.search(r'\.\./', url): @@ -843,6 +843,7 @@ def add_requirement(self, install_req): install_req.as_egg = self.as_egg install_req.use_user_site = self.use_user_site if not name: + #url or path requirement w/o an egg fragment self.unnamed_requirements.append(install_req) else: if self.has_requirement(name): @@ -1095,6 +1096,7 @@ def prepare_files(self, finder, force_root_egg_info=False, bundle=False): reqs.append(subreq) self.add_requirement(subreq) if not self.has_requirement(req_to_install.name): + #'unnamed' requirements will get added here self.add_requirement(req_to_install) if self.is_download or req_to_install._temp_build_dir is not None: self.reqs_to_cleanup.append(req_to_install)