Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

--user fixes part 4, issue #440 #574

Merged
merged 2 commits into from

3 participants

@qwcode
Owner

this is part 4 of a breakup of pull request #511
full plan is here: https://gist.github.com/2822510

#440: pip install --user tries to uninstall system package

there are a few differences in how this was done in pull #511.
the most important difference deals with the tests correctly "simulating" a global-site/user-site conflict using virtualenvs. (see the comment in the last test added)

@travisbot

This pull request passes (merged 0aadda7 into 8d92fc2).

@pnasrat pnasrat commented on the diff
pip/util.py
@@ -294,6 +294,14 @@ def dist_is_local(dist):
return is_local(dist_location(dist))
+def dist_in_usersite(dist):
+ """
+ Return True if given Distribution is installed in user site.
+ """
+ if user_site:
+ return normalize_path(dist_location(dist)).startswith(normalize_path(user_site))
+
@pnasrat Owner
pnasrat added a note

Maybe explicit return False for the case rather than implicit return.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@pnasrat
Owner

Minor comment, thanks for breaking this out and adding thorough testing.

@travisbot

This pull request passes (merged 2b9e9b4 into 8d92fc2).

@pnasrat
Owner

Thanks. Looks great. Will merge. See my note on the gist plan for adding a pull request to add news/authors.

@pnasrat pnasrat merged commit ae867db into from
@qwcode
Owner

should I already be updating the news?, or can I assume all these parts will go in prior to the next release (assuming I keep working), and I can just add one news entry when I'm done that lists off all the --user fixes?

@pnasrat
Owner

I was assuming you'd do it at the end for a complete summary. The commit breakdown is detail for the git changelog.

We probably need to release August time for python 3.3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
3  pip/backwardcompat.py
@@ -1,6 +1,7 @@
"""Stuff that differs in different Python versions"""
import sys
+import site
__all__ = ['WindowsError']
@@ -84,6 +85,8 @@ def fwrite(f, s):
from distutils.sysconfig import get_python_lib, get_python_version
+#site.USER_SITE was created in py2.6
+user_site = getattr(site,'USER_SITE',None)
def product(*args, **kwds):
# product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
View
3  pip/commands/install.py
@@ -217,7 +217,8 @@ def run(self, options, args):
as_egg=options.as_egg,
ignore_installed=options.ignore_installed,
ignore_dependencies=options.ignore_dependencies,
- force_reinstall=options.force_reinstall)
+ force_reinstall=options.force_reinstall,
+ use_user_site=options.use_user_site)
for name in args:
requirement_set.add_requirement(
InstallRequirement.from_line(name, None))
View
16 pip/req.py
@@ -15,7 +15,7 @@
from pip.log import logger
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
+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
from pip.util import make_path_relative
from pip.util import call_subprocess
@@ -62,6 +62,7 @@ def __init__(self, req, comes_from, source_dir=None, editable=False,
self.install_succeeded = None
# UninstallPathSet of uninstalled distribution (for possible rollback)
self.uninstalled = None
+ self.use_user_site = False
@classmethod
def from_editable(cls, editable_req, comes_from=None, default_vcs=None):
@@ -564,7 +565,7 @@ def install(self, install_options, global_options=()):
if self.editable:
self.install_editable(install_options, global_options)
return
-
+
temp_location = tempfile.mkdtemp('-record', 'pip-')
record_filename = os.path.join(temp_location, 'install-record.txt')
try:
@@ -680,7 +681,12 @@ def check_if_exists(self):
except pkg_resources.DistributionNotFound:
return False
except pkg_resources.VersionConflict:
- self.conflicts_with = pkg_resources.get_distribution(self.req.project_name)
+ existing_dist = pkg_resources.get_distribution(self.req.project_name)
+ if self.use_user_site:
+ if dist_in_usersite(existing_dist):
+ self.conflicts_with = existing_dist
+ else:
+ self.conflicts_with = existing_dist
return True
@property
@@ -798,7 +804,7 @@ class RequirementSet(object):
def __init__(self, build_dir, src_dir, download_dir, download_cache=None,
upgrade=False, ignore_installed=False, as_egg=False,
- ignore_dependencies=False, force_reinstall=False):
+ ignore_dependencies=False, force_reinstall=False, use_user_site=False):
self.build_dir = build_dir
self.src_dir = src_dir
self.download_dir = download_dir
@@ -815,6 +821,7 @@ def __init__(self, build_dir, src_dir, download_dir, download_cache=None,
self.successfully_installed = []
self.reqs_to_cleanup = []
self.as_egg = as_egg
+ self.use_user_site = use_user_site
def __str__(self):
reqs = [req for req in self.requirements.values()
@@ -825,6 +832,7 @@ def __str__(self):
def add_requirement(self, install_req):
name = install_req.name
install_req.as_egg = self.as_egg
+ install_req.use_user_site = self.use_user_site
if not name:
self.unnamed_requirements.append(install_req)
else:
View
12 pip/util.py
@@ -9,7 +9,7 @@
import tarfile
import subprocess
from pip.exceptions import InstallationError, BadCommand
-from pip.backwardcompat import WindowsError, string_types, raw_input, console_to_str
+from pip.backwardcompat import WindowsError, string_types, raw_input, console_to_str, user_site
from pip.locations import site_packages, running_under_virtualenv
from pip.log import logger
@@ -294,6 +294,16 @@ def dist_is_local(dist):
return is_local(dist_location(dist))
+def dist_in_usersite(dist):
+ """
+ Return True if given Distribution is installed in user site.
+ """
+ if user_site:
+ return normalize_path(dist_location(dist)).startswith(normalize_path(user_site))
+ else:
@pnasrat Owner
pnasrat added a note

Maybe explicit return False for the case rather than implicit return.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ return False
+
+
def get_installed_distributions(local_only=True, skip=('setuptools', 'pip', 'python')):
"""
Return a list of installed Distribution objects.
View
72 tests/test_user_site.py
@@ -94,3 +94,75 @@ def test_install_user_venv_nositepkgs_fails(self):
result = run_pip('install', '--user', curdir, cwd=run_from, expect_error=True)
assert "Can not perform a '--user' install. User site-packages are not visible in this virtualenv." in result.stdout
+
+ def test_install_user_conflict_in_usersite(self):
+ """
+ Test user install with conflict in usersite updates usersite.
+ """
+
+ env = reset_env(system_site_packages=True)
+ result1 = run_pip('install', '--user', 'INITools==0.3')
+ result2 = run_pip('install', '--user', 'INITools==0.1')
+
+ #usersite has 0.1
+ egg_info_folder = env.user_site / 'INITools-0.1-py%s.egg-info' % pyversion
+ initools_v3_file = env.root_path / env.user_site / 'initools' / 'configparser.py' #file only in 0.3
+ assert egg_info_folder in result2.files_created, str(result2)
+ assert not isfile(initools_v3_file), initools_v3_file
+
+
+ def test_install_user_conflict_in_site(self):
+ """
+ Test user install with conflict in site ignores site and installs to usersite
+ """
+
+ #the test framework only supports testing using virtualenvs
+ #this test will use a --system_site_packages virtualenv to achieve the conflict scenario.
+
+ env = reset_env(system_site_packages=True)
+ result1 = run_pip('install', 'INITools==0.2')
+ result2 = run_pip('install', '--user', 'INITools==0.1')
+
+ #usersite has 0.1
+ egg_info_folder = env.user_site / 'INITools-0.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)
+ 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.
+ """
+
+ #the test framework only supports testing using virtualenvs
+ #this test will use a --system_site_packages virtualenv to achieve the conflict scenario.
+
+ env = reset_env(system_site_packages=True)
+
+ # the sys.path ordering for virtualenvs with --system-site-packages is this: virtualenv site, usersite, global site
+ # given this ordering you *can't* use it to simulate the scenario for this test.
+ # this test will add the usersite to PYTHONPATH to simulate the desired ordering
+ env.environ["PYTHONPATH"] = env.root_path / env.user_site
+
+ result1 = run_pip('install', 'INITools==0.2')
+ result2 = run_pip('install', '--user', 'INITools==0.3')
+ result3 = run_pip('install', '--user', 'INITools==0.1')
+
+ #usersite has 0.1
+ egg_info_folder = env.user_site / 'INITools-0.1-py%s.egg-info' % pyversion
+ initools_v3_file = env.root_path / env.user_site / 'initools' / 'configparser.py' #file only in 0.3
+ assert egg_info_folder in result3.files_created, str(result3)
+ assert not isfile(initools_v3_file), initools_v3_file
+
+ #site still has 0.2 (can't just 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)
+ assert isdir(initools_folder)
Something went wrong with that request. Please try again.