Skip to content

[WIP] Try to use user site-packages more often #4868

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions src/pip/_internal/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
from pip._internal.resolve import Resolver
from pip._internal.status_codes import ERROR
from pip._internal.utils.filesystem import check_path_owner
from pip._internal.utils.misc import ensure_dir, get_installed_version
from pip._internal.utils.misc import (
ensure_dir, get_installed_version, should_use_user_site,
)
from pip._internal.utils.temp_dir import TempDirectory
from pip._internal.wheel import WheelBuilder

Expand Down Expand Up @@ -79,10 +81,16 @@ def __init__(self, *args, **kw):
'--user',
dest='use_user_site',
action='store_true',
help="Install to the Python user install directory for your "
"platform. Typically ~/.local/, or %APPDATA%\\Python on "
"Windows. (See the Python documentation for site.USER_BASE "
"for full details.)")
help="Install into the user site packages."
)

cmd_opts.add_option(
'--global',
dest='use_user_site',
action='store_false',
help='Install into the global site packages.',
)

cmd_opts.add_option(
'--root',
dest='root_path',
Expand Down Expand Up @@ -192,7 +200,9 @@ def run(self, options, args):

options.src_dir = os.path.abspath(options.src_dir)
install_options = options.install_options or []
if options.use_user_site:

# Check if a user installation makes sense.
if options.use_user_site is True:
if options.prefix_path:
raise CommandError(
"Can not combine '--user' and '--prefix' as they imply "
Expand All @@ -206,6 +216,11 @@ def run(self, options, args):
install_options.append('--user')
install_options.append('--prefix=')

# If --user or --global is not passed, use some heuristic to determine
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesnt this have to go above to ensure the sanity check and the install option fixup?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep.

Note to self: Be more careful with your merges.

# whether the installations should be user or global.
if options.use_user_site is None:
options.use_user_site = should_use_user_site()

target_temp_dir = TempDirectory(kind="target")
if options.target_dir:
options.ignore_installed = True
Expand Down
52 changes: 50 additions & 2 deletions src/pip/_internal/utils/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
import posixpath
import re
import shutil
import site
import stat
import subprocess
import sys
import tarfile
import tempfile
import zipfile
from collections import deque

Expand All @@ -28,8 +30,8 @@
from pip._internal.compat import console_to_str, expanduser, stdlib_pkgs
from pip._internal.exceptions import InstallationError
from pip._internal.locations import (
running_under_virtualenv, site_packages, user_site, virtualenv_no_global,
write_delete_marker_file
distutils_scheme, running_under_virtualenv, site_packages, user_site,
virtualenv_no_global, write_delete_marker_file,
)

if PY2:
Expand Down Expand Up @@ -877,3 +879,49 @@ def enum(*sequential, **named):
reverse = dict((value, key) for key, value in enums.items())
enums['reverse_mapping'] = reverse
return type('Enum', (), enums)


def should_use_user_site():
"""Determines if pip uses user-site when not explicitly told
"""
# Doing a user installation when a virtualenv is active does not make sense
if running_under_virtualenv():
return False

# It won't make sense to install to user site-packages if they are disabled
if not site.ENABLE_USER_SITE:
return False

# XXX: Everything beyond this line is for addressing backwards
# compatibility concerns.
# Eventually, the following should be replaced with a return True
def _can_create_file_in(folder_path):
"""Returns whether a file can be created in the given folder
"""
# If the given folder does not exist, try to create it. If there's
# going to be a successful installation, the folder probably needs to
# be created anyway.
try:
ensure_dir(folder_path)
except EnvironmentError:
return False

# Try to create a temporary file in the folder and delete it.
try:
with tempfile.TemporaryFile(dir=folder_path):
pass
except EnvironmentError:
return False
else:
return True

# This is the "smart" portion. Check if there's any potential global folder
# for which pip doesn't have the required permissions. If there's any such
# location, pip should perform a user installation instead.
for folder in distutils_scheme("").values():
if not _can_create_file_in(folder):
return True

# Assume the user wants to do a global installation. This is the most
# backwards compatible policy; preserving behaviour for `sudo pip install`
return False