Skip to content

Commit

Permalink
Default to --user install in certain conditions
Browse files Browse the repository at this point in the history
  • Loading branch information
takluyver committed Sep 9, 2019
1 parent a7d8d56 commit 7e832f2
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 2 deletions.
20 changes: 18 additions & 2 deletions src/pip/_internal/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import operator
import os
import shutil
import site
from optparse import SUPPRESS_HELP

from pip._vendor import pkg_resources
Expand All @@ -27,11 +28,11 @@
InstallationError,
PreviousBuildDirError,
)
from pip._internal.locations import distutils_scheme
from pip._internal.locations import distutils_scheme, site_packages
from pip._internal.operations.check import check_install_conflicts
from pip._internal.req import RequirementSet, install_given_reqs
from pip._internal.req.req_tracker import RequirementTracker
from pip._internal.utils.filesystem import check_path_owner
from pip._internal.utils.filesystem import check_path_owner, test_writable_dir
from pip._internal.utils.misc import (
ensure_dir,
get_installed_version,
Expand Down Expand Up @@ -304,6 +305,17 @@ def run(self, options, args):
install_options.append('--user')
install_options.append('--prefix=')

elif options.use_user_site is None:
if options.prefix_path or options.target_dir:
options.use_user_site = False
elif site_packages_writable(
root=options.root_path,
isolated=options.isolated_mode
):
options.use_user_site = False
elif site.ENABLE_USER_SITE:
options.use_user_site = True

target_temp_dir = TempDirectory(kind="target")
if options.target_dir:
options.ignore_installed = True
Expand Down Expand Up @@ -591,6 +603,10 @@ def get_lib_location_guesses(*args, **kwargs):
return [scheme['purelib'], scheme['platlib']]


def site_packages_writable(**kwargs):
return all(test_writable_dir(d) for d in get_lib_location_guesses(**kwargs))


def create_env_error_message(error, show_traceback, using_user_site):
"""Format an error message for an EnvironmentError
Expand Down
40 changes: 40 additions & 0 deletions src/pip/_internal/utils/filesystem.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import os.path
import random
import shutil
import stat

Expand Down Expand Up @@ -59,3 +60,42 @@ def copy2_fixed(src, dest):
def is_socket(path):
# type: (str) -> bool
return stat.S_ISSOCK(os.lstat(path).st_mode)


# test_writable_dir and _test_writable_dir_win are copied from Flit,
# with the author's agreement to also place them under pip's license.
def test_writable_dir(path):
"""Check if a directory is writable.
Uses os.access() on POSIX, tries creating files on Windows.
"""
if os.name == 'posix':
return os.access(path, os.W_OK)

return _test_writable_dir_win(path)


def _test_writable_dir_win(path):
# os.access doesn't work on Windows: http://bugs.python.org/issue2528
# and we can't use tempfile: http://bugs.python.org/issue22107
basename = 'accesstest_deleteme_fishfingers_custard_'
alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789'
for i in range(10):
name = basename + ''.join(random.choice(alphabet) for _ in range(6))
file = os.path.join(path, name)
try:
with open(file, mode='xb'):
pass
except FileExistsError:
continue
except PermissionError:
# This could be because there's a directory with the same name.
# But it's highly unlikely there's a directory called that,
# so we'll assume it's because the parent directory is not writable.
return False
else:
os.unlink(file)
return True

# This should never be reached
raise EnvironmentError('Unexpected condition testing for writable directory')

0 comments on commit 7e832f2

Please sign in to comment.