From 0503c2046f857ec0756c6f5e215b2ec8d420f7c3 Mon Sep 17 00:00:00 2001 From: Jacob Henner Date: Thu, 28 Jun 2018 10:23:27 -0400 Subject: [PATCH] Use --pypi-mirror during virtualenv initialization Adds support for the --pypi-mirror parameter for all operations which may result in a virtualenv initialization. When a virtualenv is initialized, pip attempts to download several dependencies from PyPI. If PyPI is unavailable, virtualenv silently uses local packages instead, which is acceptable in most cases. However, in some environments connection attempts to PyPI will stall rather than fail, causing a pipenv timeout. By using the mirror specified by --pypi-mirror, we can ensure virtualenv will attempt to download dependencies from an accessible mirror instead of PyPI. - Fixes #2455 --- pipenv/cli.py | 58 +++++++++++++++++++++++++++++++++++++++++--------- pipenv/core.py | 35 ++++++++++++++++-------------- 2 files changed, 67 insertions(+), 26 deletions(-) diff --git a/pipenv/cli.py b/pipenv/cli.py index 5e7b470bda..bb41c103a5 100644 --- a/pipenv/cli.py +++ b/pipenv/cli.py @@ -145,6 +145,13 @@ def validate_pypi_mirror(ctx, param, value): default=False, help="Enable site-packages for the virtualenv.", ) +@option( + '--pypi-mirror', + default=environments.PIPENV_PYPI_MIRROR, + nargs=1, + callback=validate_pypi_mirror, + help="Specify a PyPI mirror.", +) @version_option( prog_name=crayons.normal('pipenv', bold=True), version=__version__ ) @@ -163,6 +170,7 @@ def cli( envs=False, man=False, completion=False, + pypi_mirror=None, ): if completion: # Handle this ASAP to make shell startup fast. from . import shells @@ -272,7 +280,7 @@ def cli( # --two / --three was passed... if (python or three is not None) or site_packages: ensure_project( - three=three, python=python, warn=True, site_packages=site_packages + three=three, python=python, warn=True, site_packages=site_packages, pypi_mirror=pypi_mirror ) # Check this again before exiting for empty ``pipenv`` command. elif ctx.invoked_subcommand is None: @@ -571,7 +579,7 @@ def lock( from .core import ensure_project, do_init, do_lock # Ensure that virtualenv is available. - ensure_project(three=three, python=python) + ensure_project(three=three, python=python, pypi_mirror=pypi_mirror) if requirements: do_init(dev=dev, requirements=requirements, pypi_mirror=pypi_mirror) do_lock( @@ -608,9 +616,16 @@ def lock( default=False, help="Always spawn a subshell, even if one is already spawned.", ) +@option( + '--pypi-mirror', + default=environments.PIPENV_PYPI_MIRROR, + nargs=1, + callback=validate_pypi_mirror, + help="Specify a PyPI mirror.", +) @argument('shell_args', nargs=-1) def shell( - three=None, python=False, fancy=False, shell_args=None, anyway=False + three=None, python=False, fancy=False, shell_args=None, anyway=False, pypi_mirror=None ): from .core import load_dot_env, do_shell # Prevent user from activating nested environments. @@ -635,7 +650,7 @@ def shell( if os.name == 'nt': fancy = True do_shell( - three=three, python=python, fancy=fancy, shell_args=shell_args + three=three, python=python, fancy=fancy, shell_args=shell_args, pypi_mirror=pypi_mirror ) @@ -663,9 +678,16 @@ def shell( callback=validate_python_path, help="Specify which version of Python virtualenv should use.", ) -def run(command, args, three=None, python=False): +@option( + '--pypi-mirror', + default=environments.PIPENV_PYPI_MIRROR, + nargs=1, + callback=validate_pypi_mirror, + help="Specify a PyPI mirror.", +) +def run(command, args, three=None, python=False, pypi_mirror=None): from .core import do_run - do_run(command=command, args=args, three=three, python=python) + do_run(command=command, args=args, three=three, python=python, pypi_mirror=pypi_mirror) @command( @@ -700,6 +722,13 @@ def run(command, args, three=None, python=False): multiple=True, help="Ignore specified vulnerability during safety checks." ) +@option( + '--pypi-mirror', + default=environments.PIPENV_PYPI_MIRROR, + nargs=1, + callback=validate_pypi_mirror, + help="Specify a PyPI mirror.", +) @argument('args', nargs=-1) def check( three=None, @@ -709,6 +738,7 @@ def check( style=False, ignore=None, args=None, + pypi_mirror=None, ): from .core import do_check do_check( @@ -717,7 +747,8 @@ def check( system=system, unused=unused, ignore=ignore, - args=args + args=args, + pypi_mirror=pypi_mirror ) @@ -819,7 +850,7 @@ def update( project, ) - ensure_project(three=three, python=python, warn=True) + ensure_project(three=three, python=python, warn=True, pypi_mirror=pypi_mirror) if not outdated: outdated = bool(dry_run) if outdated: @@ -894,12 +925,19 @@ def graph(bare=False, json=False, json_tree=False, reverse=False): callback=validate_python_path, help="Specify which version of Python virtualenv should use.", ) +@option( + '--pypi-mirror', + default=environments.PIPENV_PYPI_MIRROR, + nargs=1, + callback=validate_pypi_mirror, + help="Specify a PyPI mirror.", +) @argument('module', nargs=1) -def run_open(module, three=None, python=None): +def run_open(module, three=None, python=None, pypi_mirror=None): from .core import which, ensure_project # Ensure that virtualenv is available. - ensure_project(three=three, python=python, validate=False) + ensure_project(three=three, python=python, validate=False, pypi_mirror=pypi_mirror) c = delegator.run( '{0} -c "import {1}; print({1}.__file__);"'.format( which('python'), module diff --git a/pipenv/core.py b/pipenv/core.py index 7b30af909a..7dba3c9d2a 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -548,7 +548,7 @@ def activate_pyenv(): return path_to_python -def ensure_virtualenv(three=None, python=None, site_packages=False): +def ensure_virtualenv(three=None, python=None, site_packages=False, pypi_mirror=None): """Creates a virtualenv, if one doesn't exist.""" def abort(): @@ -571,7 +571,7 @@ def abort(): ) ) sys.exit(1) - do_create_virtualenv(python=python, site_packages=site_packages) + do_create_virtualenv(python=python, site_packages=site_packages, pypi_mirror=pypi_mirror) except KeyboardInterrupt: # If interrupted, cleanup the virtualenv. cleanup_virtualenv(bare=False) @@ -599,7 +599,7 @@ def abort(): cleanup_virtualenv(bare=True) # Call this function again. ensure_virtualenv( - three=three, python=python, site_packages=site_packages + three=three, python=python, site_packages=site_packages, pypi_mirror=pypi_mirror ) @@ -612,6 +612,7 @@ def ensure_project( site_packages=False, deploy=False, skip_requirements=False, + pypi_mirror=None, ): """Ensures both Pipfile and virtualenv exist for the project.""" # Automatically use an activated virtualenv. @@ -622,7 +623,7 @@ def ensure_project( # Skip virtualenv creation when --system was used. if not system: ensure_virtualenv( - three=three, python=python, site_packages=site_packages + three=three, python=python, site_packages=site_packages, pypi_mirror=pypi_mirror ) if warn: # Warn users if they are using the wrong version of Python. @@ -885,7 +886,7 @@ def convert_three_to_python(three, python): return python -def do_create_virtualenv(python=None, site_packages=False): +def do_create_virtualenv(python=None, site_packages=False, pypi_mirror=None): """Creates a virtualenv.""" click.echo( crayons.normal(u'Creating a virtualenv for this project...', bold=True), @@ -933,7 +934,8 @@ def do_create_virtualenv(python=None, site_packages=False): # Actually create the virtualenv. with spinner(): try: - c = delegator.run(cmd, block=False, timeout=PIPENV_TIMEOUT) + pip_config = {'PIP_INDEX_URL': fs_str(pypi_mirror)} if pypi_mirror else {} + c = delegator.run(cmd, block=False, timeout=PIPENV_TIMEOUT, env=pip_config) except OSError: click.echo( '{0}: it looks like {1} is not in your {2}. ' @@ -1264,7 +1266,7 @@ def do_init( if not system: if not project.virtualenv_exists: try: - do_create_virtualenv() + do_create_virtualenv(pypi_mirror=pypi_mirror) except KeyboardInterrupt: cleanup_virtualenv(bare=False) sys.exit(1) @@ -1789,6 +1791,7 @@ def do_install( warn=True, deploy=deploy, skip_requirements=skip_requirements, + pypi_mirror=pypi_mirror, ) # Load the --pre settings from the Pipfile. if not pre: @@ -2097,7 +2100,7 @@ def do_uninstall( if PIPENV_USE_SYSTEM: system = True # Ensure that virtualenv is available. - ensure_project(three=three, python=python) + ensure_project(three=three, python=python, pypi_mirror=pypi_mirror) package_names = (package_name,) + more_packages pipfile_remove = True # Un-install all dependencies, if --all was provided. @@ -2166,11 +2169,11 @@ def do_uninstall( do_lock(system=system, keep_outdated=keep_outdated, pypi_mirror=pypi_mirror) -def do_shell(three=None, python=False, fancy=False, shell_args=None): +def do_shell(three=None, python=False, fancy=False, shell_args=None, pypi_mirror=None): from .patched.pew import pew # Ensure that virtualenv is available. - ensure_project(three=three, python=python, validate=False) + ensure_project(three=three, python=python, validate=False, pypi_mirror=pypi_mirror) # Set an environment variable, so we know we're in the environment. os.environ['PIPENV_ACTIVE'] = '1' compat = (not fancy) @@ -2320,13 +2323,13 @@ def do_run_posix(script, command): os.execl(command_path, command_path, *script.args) -def do_run(command, args, three=None, python=False): +def do_run(command, args, three=None, python=False, pypi_mirror=None): """Attempt to run command either pulling from project or interpreting as executable. Args are appended to the command in [scripts] section of project if found. """ # Ensure that virtualenv is available. - ensure_project(three=three, python=python, validate=False) + ensure_project(three=three, python=python, validate=False, pypi_mirror=pypi_mirror) load_dot_env() # Activate virtualenv under the current interpreter's environment inline_activate_virtualenv() @@ -2340,10 +2343,10 @@ def do_run(command, args, three=None, python=False): do_run_posix(script, command=command) -def do_check(three=None, python=False, system=False, unused=False, ignore=None, args=None): +def do_check(three=None, python=False, system=False, unused=False, ignore=None, args=None, pypi_mirror=None): if not system: # Ensure that virtualenv is available. - ensure_project(three=three, python=python, validate=False, warn=False) + ensure_project(three=three, python=python, validate=False, warn=False, pypi_mirror=pypi_mirror) if not args: args = [] if unused: @@ -2586,7 +2589,7 @@ def do_sync( sys.exit(1) # Ensure that virtualenv is available if not system. - ensure_project(three=three, python=python, validate=False, deploy=deploy) + ensure_project(three=three, python=python, validate=False, deploy=deploy, pypi_mirror=pypi_mirror) # Install everything. requirements_dir = TemporaryDirectory( @@ -2610,7 +2613,7 @@ def do_clean( ctx, three=None, python=None, dry_run=False, bare=False, verbose=False, pypi_mirror=None ): # Ensure that virtualenv is available. - ensure_project(three=three, python=python, validate=False) + ensure_project(three=three, python=python, validate=False, pypi_mirror=pypi_mirror) ensure_lockfile(pypi_mirror=pypi_mirror) installed_package_names = []