diff --git a/news/4273.bugfix.rst b/news/4273.bugfix.rst new file mode 100644 index 00000000..a6140178 --- /dev/null +++ b/news/4273.bugfix.rst @@ -0,0 +1 @@ +Fix a bug that Pipenv rejects to work under the root directory. diff --git a/news/4276.bugfix.rst b/news/4276.bugfix.rst new file mode 100644 index 00000000..960ab08b --- /dev/null +++ b/news/4276.bugfix.rst @@ -0,0 +1 @@ +Fix a bug that system-wide packages will be considered when inside a venv. diff --git a/news/4276.feature.rst b/news/4276.feature.rst new file mode 100644 index 00000000..c1851a02 --- /dev/null +++ b/news/4276.feature.rst @@ -0,0 +1 @@ +Pipenv will now detect existing ``venv`` and ``virtualenv`` based virtual environments more robustly. diff --git a/pipenv/core.py b/pipenv/core.py index 1938e7f5..040db059 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -126,8 +126,11 @@ def do_clear(): try: vistir.path.rmtree(PIPENV_CACHE_DIR, onerror=vistir.path.handle_remove_readonly) + # Other processes may be writing into this directory simultaneously. vistir.path.rmtree( - locations.USER_CACHE_DIR, onerror=vistir.path.handle_remove_readonly + locations.USER_CACHE_DIR, + ignore_errors=environments.PIPENV_IS_CI, + onerror=vistir.path.handle_remove_readonly ) except OSError as e: # Ignore FileNotFoundError. This is needed for Python 2.7. @@ -568,14 +571,6 @@ def ensure_project( system = True if not project.pipfile_exists and deploy: raise exceptions.PipfileNotFound - # Fail if working under / - if not project.name: - click.echo( - "{0}: Pipenv is not intended to work under the root directory, " - "please choose another path.".format(crayons.red("ERROR")), - err=True - ) - sys.exit(1) # Skip virtualenv creation when --system was used. if not system: ensure_virtualenv( @@ -993,8 +988,10 @@ def do_create_virtualenv(python=None, site_packages=None, pypi_mirror=None): f.write(vistir.misc.fs_str(project.project_directory)) from .environment import Environment sources = project.pipfile_sources + # project.get_location_for_virtualenv is only for if we are creating a new virtualenv + # whereas virtualenv_location is for the current path to the runtime project._environment = Environment( - prefix=project.get_location_for_virtualenv(), + prefix=project.virtualenv_location, is_venv=True, sources=sources, pipfile=project.parsed_pipfile, diff --git a/pipenv/environment.py b/pipenv/environment.py index 09ea73a3..d47b2037 100644 --- a/pipenv/environment.py +++ b/pipenv/environment.py @@ -25,6 +25,9 @@ from .vendor import vistir from .utils import normalize_path, make_posix +if False: + from typing import Optional + BASE_WORKING_SET = pkg_resources.WorkingSet(sys.path) # TODO: Unittests for this class @@ -469,7 +472,7 @@ class Environment(object): ), None) if pip is not None: return parse_version(pip.version) - return parse_version("19.3") + return parse_version("20.2") def expand_egg_links(self): """ diff --git a/pipenv/environments.py b/pipenv/environments.py index 88752231..b9d89806 100644 --- a/pipenv/environments.py +++ b/pipenv/environments.py @@ -81,7 +81,7 @@ PIPENV_IS_CI = bool("CI" in os.environ or "TF_BUILD" in os.environ) # HACK: Prevent invalid shebangs with Homebrew-installed Python: # https://bugs.python.org/issue22490 -os.environ.pop("__PYVENV_LAUNCHER__", None) +_OSX_VENV = os.environ.pop("__PYVENV_LAUNCHER__", None) # Load patched pip instead of system pip os.environ["PIP_SHIMS_BASE_MODULE"] = fs_str("pipenv.patched.notpip") @@ -326,7 +326,7 @@ PIPENV_TEST_INDEX = os.environ.get("PIPENV_TEST_INDEX") PIPENV_USE_SYSTEM = False PIPENV_VIRTUALENV = None if "PIPENV_ACTIVE" not in os.environ and not PIPENV_IGNORE_VIRTUALENVS: - PIPENV_VIRTUALENV = os.environ.get("VIRTUAL_ENV") + PIPENV_VIRTUALENV = os.environ.get("VIRTUAL_ENV") or _OSX_VENV PIPENV_USE_SYSTEM = bool(PIPENV_VIRTUALENV) # Internal, tells Pipenv to skip case-checking (slow internet connections). @@ -371,6 +371,18 @@ def is_quiet(threshold=-1): return PIPENV_VERBOSITY <= threshold +def _is_using_venv(): + # type: () -> bool + """Check for venv-based virtual environment which sets sys.base_prefix""" + return _OSX_VENV is not None or sys.prefix != getattr(sys, "base_prefix", sys.prefix) + + +def _is_using_virtualenv(): + # type: () -> bool + """Check for virtualenv-based environment which sets sys.real_prefix""" + return getattr(sys, "real_prefix", None) is not None + + def is_in_virtualenv(): """ Check virtualenv membership dynamically @@ -385,7 +397,9 @@ def is_in_virtualenv(): ignore_virtualenvs = bool(os.environ.get("PIPENV_IGNORE_VIRTUALENVS", False)) if not pipenv_active and not ignore_virtualenvs: - virtual_env = os.environ.get("VIRTUAL_ENV") + virtual_env = any([ + _is_using_virtualenv(), _is_using_venv(), os.environ.get("VIRTUAL_ENV") + ]) use_system = bool(virtual_env) return (use_system or virtual_env) and not (pipenv_active or ignore_virtualenvs) diff --git a/pipenv/project.py b/pipenv/project.py index 40b6b263..796489f7 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -330,11 +330,11 @@ class Project(object): def get_environment(self, allow_global=False): # type: (bool) -> Environment - if allow_global: + is_venv = is_in_virtualenv() + if allow_global and not is_venv: prefix = sys.prefix else: prefix = self.virtualenv_location - is_venv = is_in_virtualenv() sources = self.sources if self.sources else [DEFAULT_SOURCE] environment = Environment( prefix=prefix, is_venv=is_venv, sources=sources, pipfile=self.parsed_pipfile, @@ -682,18 +682,10 @@ class Project(object): def create_pipfile(self, python=None): """Creates the Pipfile, filled with juicy defaults.""" - from .vendor.pip_shims.shims import ( - ConfigOptionParser, make_option_group, index_group - ) - - config_parser = ConfigOptionParser(name=self.name) - config_parser.add_option_group(make_option_group(index_group, config_parser)) - install = config_parser.option_groups[0] - indexes = ( - " ".join(install.get_option("--extra-index-url").default) - .lstrip("\n") - .split("\n") - ) + from .vendor.pip_shims.shims import InstallCommand + # Inherit the pip's index configuration of install command. + command = InstallCommand() + indexes = command.cmd_opts.get_option("--extra-index-url").default sources = [DEFAULT_SOURCE] for i, index in enumerate(indexes): if not index: