diff --git a/HISTORY.txt b/HISTORY.txt index 07603beb..356b1151 100644 --- a/HISTORY.txt +++ b/HISTORY.txt @@ -1,3 +1,15 @@ +6.1.5: + - Grab hashes for un-grabbable hashes. +6.1.4: + - New update via $ pipenv --update, instead. +6.1.3: + - Skip validation of Pipfiles, massive speedup for far-away users. + - Other speed-ups. +6.1.1: + - Bug fix. +6.1.0: + - Self–updating! Very fancy. $ pipenv update. + - Verbose mode for update, install. 6.0.3: - Major bug fix. - Fix for Daniel Ryan's weird corner case. diff --git a/appveyor.yml b/appveyor.yml index 7b79fe82..77a41ad4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -48,7 +48,7 @@ install: # about it being out of date. - "pip install --disable-pip-version-check --user --upgrade pip" - "pip install -e . --upgrade" - - "pipenv install --dev" + - "pipenv install --dev --skip-lock" test_script: - "pipenv run pytest test_windows/ tests/test_utils.py tests/test_project.py" diff --git a/docs/advanced.rst b/docs/advanced.rst index 3c180b0b..b9b57778 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -253,7 +253,6 @@ $ pipenv lock variables. To activate them, simply create the variable in your shell and pipenv will detect it. - - ``PIPENV_SKIP_VALIDATION`` — Tells Pipenv to skip ``Pipfile`` validation (case-checking) — useful for slow internet connections. - ``PIPENV_SHELL_COMPAT`` — Toggle from our default ``pipenv shell`` mode to classic. (Suggested for use with pyenv). diff --git a/pipenv/__version__.py b/pipenv/__version__.py index e7b7493f..2251d726 100644 --- a/pipenv/__version__.py +++ b/pipenv/__version__.py @@ -3,4 +3,4 @@ # //___/ / / / //___/ / // // / / || / / # // / / // ((____ // / / ||/ / -__version__ = '6.0.3' +__version__ = '6.1.5' diff --git a/pipenv/cli.py b/pipenv/cli.py index 772d1082..39ead9c2 100644 --- a/pipenv/cli.py +++ b/pipenv/cli.py @@ -73,14 +73,45 @@ def check_for_updates(): current = semver.parse_version_info(__version__) if latest > current: - click.echo('{0}: {1} is now available. You get bonus points for upgrading!'.format( + click.echo('{0}: {1} is now available. You get bonus points for upgrading ($ {})!'.format( crayons.green('Courtesy Notice'), - crayons.yellow('Pipenv v{v.major}.{v.minor}.{v.patch}'.format(v=latest)), + crayons.yellow('Pipenv {v.major}.{v.minor}.{v.patch}'.format(v=latest)), + crayons.red('pipenv --update') ), err=True) except Exception: pass +def enhance(user=False): + r = requests.get('https://pypi.python.org/pypi/pipenv/json', timeout=0.5) + latest = sorted([semver.parse_version_info(v) for v in list(r.json()['releases'].keys())])[-1] + current = semver.parse_version_info(__version__) + + if current < latest: + + import site + + click.echo('{0}: {1} is now available. Automatically upgrading!'.format( + crayons.green('Courtesy Notice'), + crayons.yellow('Pipenv {v.major}.{v.minor}.{v.patch}'.format(v=latest)), + ), err=True) + + # Resolve user site, enable user mode automatically. + if site.ENABLE_USER_SITE and site.USER_SITE in sys.modules['pipenv'].__file__: + args = ['install', '--upgrade', 'pipenv'] + else: + args = ['install', '--user', '--upgrade', 'pipenv'] + + sys.modules['pip'].main(args) + + click.echo('{0} to {1}!'.format( + crayons.green('Pipenv updated'), + crayons.yellow('{v.major}.{v.minor}.{v.patch}'.format(v=latest)) + )) + else: + click.echo(crayons.green('All good!')) + + def cleanup_virtualenv(bare=True): """Removes the virtualenv directory from the system.""" @@ -104,7 +135,10 @@ def ensure_latest_pip(): if 'however' in c.err: # If version is out of date, update. click.echo(crayons.yellow('Pip is out of date... updating to latest.')) - c = delegator.run('"{0}" install pip --upgrade'.format(which_pip()), block=False) + + windows = '-m' if os.name == 'nt' else '' + + c = delegator.run('"{0}" install {1} pip --upgrade'.format(which_pip()), windows, block=False) click.echo(crayons.blue(c.out)) @@ -135,6 +169,8 @@ def ensure_pipfile(validate=True): else: project.add_package_to_pipfile(str(package.req)) + project.recase_pipfile() + else: click.echo(crayons.yellow('Creating a Pipfile for this project...'), err=True) # Create the pipfile if it doesn't exist. @@ -242,7 +278,7 @@ def do_where(virtualenv=False, bare=True): click.echo(location) -def do_install_dependencies(dev=False, only=False, bare=False, requirements=False, allow_global=False, ignore_hashes=False, skip_lock=False): +def do_install_dependencies(dev=False, only=False, bare=False, requirements=False, allow_global=False, ignore_hashes=False, skip_lock=False, verbose=False): """"Executes the install functionality.""" if requirements: @@ -259,6 +295,7 @@ def do_install_dependencies(dev=False, only=False, bare=False, requirements=Fals with open(project.lockfile_location) as f: lockfile = split_vcs(json.load(f)) + # Allow pip to resolve dependencies when in skip-lock mode. no_deps = (not skip_lock) # Install default dependencies, always. @@ -289,7 +326,7 @@ def do_install_dependencies(dev=False, only=False, bare=False, requirements=Fals # pip install: for dep, ignore_hash in progress.bar(deps_list): - c = pip_install(dep, ignore_hashes=ignore_hash, allow_global=allow_global, no_deps=no_deps) + c = pip_install(dep, ignore_hashes=ignore_hash, allow_global=allow_global, no_deps=no_deps, verbose=verbose) if c.return_code != 0: click.echo(crayons.red('An error occured while installing!')) @@ -620,7 +657,7 @@ def do_purge(bare=False, downloads=False, allow_global=False): click.echo(crayons.yellow('Environment now purged and fresh!')) -def do_init(dev=False, requirements=False, allow_global=False, ignore_hashes=False, no_hashes=True, ignore_pipfile=False, skip_lock=False): +def do_init(dev=False, requirements=False, allow_global=False, ignore_hashes=False, no_hashes=True, ignore_pipfile=False, skip_lock=False, verbose=False): """Executes the init functionality.""" ensure_pipfile() @@ -663,14 +700,14 @@ def do_init(dev=False, requirements=False, allow_global=False, ignore_hashes=Fal ignore_hashes = False do_install_dependencies(dev=dev, requirements=requirements, allow_global=allow_global, - ignore_hashes=ignore_hashes, skip_lock=skip_lock) + ignore_hashes=ignore_hashes, skip_lock=skip_lock, verbose=verbose) # Activate virtualenv instructions. if not allow_global: do_activate_virtualenv() -def pip_install(package_name=None, r=None, allow_global=False, ignore_hashes=False, no_deps=True): +def pip_install(package_name=None, r=None, allow_global=False, ignore_hashes=False, no_deps=True, verbose=False): # Create files for hash mode. if (not ignore_hashes) and (r is None): @@ -702,6 +739,10 @@ def pip_install(package_name=None, r=None, allow_global=False, ignore_hashes=Fal no_deps = '--no-deps' if no_deps else '' pip_command = '"{0}" install {3} {1} -i {2}'.format(which_pip(allow_global=allow_global), install_reqs, source['url'], no_deps) + + if verbose: + click.echo('$ {0}'.format(pip_command), err=True) + c = delegator.run(pip_command) if c.return_code == 0: @@ -804,6 +845,7 @@ def easter_egg(package_name): @click.group(invoke_without_command=True) +@click.option('--update', is_flag=True, default=False, help="Upate pipenv & pip.") @click.option('--where', is_flag=True, default=False, help="Output project home information.") @click.option('--venv', is_flag=True, default=False, help="Output virtualenv information.") @click.option('--rm', is_flag=True, default=False, help="Remove the virtualenv.") @@ -813,9 +855,18 @@ def easter_egg(package_name): @click.option('--help', '-h', is_flag=True, default=None, help="Show this message then exit.") @click.version_option(prog_name=crayons.yellow('pipenv'), version=__version__) @click.pass_context -def cli(ctx, where=False, venv=False, rm=False, bare=False, three=False, python=False, help=False): +def cli(ctx, where=False, venv=False, rm=False, bare=False, three=False, python=False, help=False, update=False): - check_for_updates() + if not update: + check_for_updates() + else: + # Update pip to latest version. + ensure_latest_pip() + + # Upgrade self to latest version. + enhance() + + sys.exit() if ctx.invoked_subcommand is None: # --where was passed... @@ -869,9 +920,10 @@ def cli(ctx, where=False, venv=False, rm=False, bare=False, three=False, python= @click.option('--three/--two', is_flag=True, default=None, help="Use Python 3/2 when creating virtualenv.") @click.option('--python', default=False, nargs=1, help="Specify which version of Python virtualenv should use.") @click.option('--system', is_flag=True, default=False, help="System pip management.") +@click.option('--verbose', is_flag=True, default=False, help="Verbose mode.") @click.option('--ignore-pipfile', is_flag=True, default=False, help="Ignore Pipfile when installing, using the Pipfile.lock.") @click.option('--skip-lock', is_flag=True, default=False, help=u"Ignore locking mechanisms when installing—use the Pipfile, instead.") -def install(package_name=False, more_packages=False, dev=False, three=False, python=False, system=False, lock=True, hashes=True, ignore_pipfile=False, skip_lock=False): +def install(package_name=False, more_packages=False, dev=False, three=False, python=False, system=False, lock=True, hashes=True, ignore_pipfile=False, skip_lock=False, verbose=False): # Automatically use an activated virtualenv. if PIPENV_USE_SYSTEM: @@ -895,7 +947,7 @@ def install(package_name=False, more_packages=False, dev=False, three=False, pyt if package_name is False: click.echo(crayons.yellow('No package provided, installing all dependencies.'), err=True) - do_init(dev=dev, allow_global=system, ignore_hashes=not hashes, ignore_pipfile=ignore_pipfile, skip_lock=skip_lock) + do_init(dev=dev, allow_global=system, ignore_hashes=not hashes, ignore_pipfile=ignore_pipfile, skip_lock=skip_lock, verbose=verbose) sys.exit(0) for package_name in package_names: @@ -903,7 +955,7 @@ def install(package_name=False, more_packages=False, dev=False, three=False, pyt # pip install: with spinner(): - c = pip_install(package_name, ignore_hashes=True, allow_global=system, no_deps=False) + c = pip_install(package_name, ignore_hashes=True, allow_global=system, no_deps=False, verbose=verbose) click.echo(crayons.blue(format_pip_output(c.out))) @@ -1152,6 +1204,7 @@ def run(command, args, three=None, python=False): @click.option('--three/--two', is_flag=True, default=None, help="Use Python 3/2 when creating virtualenv.") @click.option('--python', default=False, nargs=1, help="Specify which version of Python virtualenv should use.") def check(three=None, python=False): + # Ensure that virtualenv is available. ensure_project(three=three, python=python, validate=False) @@ -1181,27 +1234,29 @@ def check(three=None, python=False): click.echo(crayons.green('Passed!')) -@click.command(help="Updates pip to latest version, uninstalls all packages, and re-installs package(s) in [packages] to latest compatible versions.") +@click.command(help="Updates Pipenv & pip to latest, uninstalls all packages, and re-installs package(s) in [packages] to latest compatible versions.") +@click.option('--verbose', '-v', is_flag=True, default=False, help="Verbose mode.") @click.option('--dev', '-d', is_flag=True, default=False, help="Additionally install package(s) in [dev-packages].") @click.option('--three/--two', is_flag=True, default=None, help="Use Python 3/2 when creating virtualenv.") @click.option('--python', default=False, nargs=1, help="Specify which version of Python virtualenv should use.") @click.option('--dry-run', is_flag=True, default=False, help="Just output outdated packages.") @click.option('--bare', is_flag=True, default=False, help="Minimal output.") -def update(dev=False, three=None, python=None, dry_run=False, bare=False): +def update(dev=False, three=None, python=None, dry_run=False, bare=False, dont_upgrade=False, user=False, verbose=False): # Ensure that virtualenv is available. ensure_project(three=three, python=python, validate=False) - # --dry-run if dry_run: + dont_upgrade = True updates = False # Dev packages if not bare: click.echo(crayons.yellow('Checking dependencies...'), err=True) - packages = project.dev_packages - packages.update(project.packages) + packages = project.packages + if dev: + packages.update(project.dev_packages) installed_packages = {} deps = convert_deps_to_pip(packages, r=False) @@ -1220,26 +1275,26 @@ def update(dev=False, three=None, python=None, dry_run=False, bare=False): name = result['name'] installed = result['version'] - latest = installed_packages[name] - if installed != latest: - if not bare: - click.echo('{0}=={1} is availble ({2} installed)!'.format(name, latest, installed)) - else: - click.echo('{0}=={1}'.format(name, latest)) - updates = True + try: + latest = installed_packages[name] + if installed != latest: + if not bare: + click.echo('{0}=={1} is available ({2} installed)!'.format(name, latest, installed)) + else: + click.echo('{0}=={1}'.format(name, latest)) + updates = True + except KeyError: + pass if not updates and not bare: click.echo(crayons.green('All good!')) sys.exit(int(updates)) - # Update pip to latest version. - ensure_latest_pip() - click.echo(crayons.yellow('Updating all dependencies from Pipfile...')) do_purge() - do_init(dev=dev) + do_init(dev=dev, verbose=verbose) click.echo(crayons.yellow('All dependencies are now up-to-date!')) diff --git a/pipenv/environments.py b/pipenv/environments.py index e86f48ac..bf0baddc 100644 --- a/pipenv/environments.py +++ b/pipenv/environments.py @@ -29,7 +29,7 @@ PIPENV_USE_SYSTEM = os.environ.get('VIRTUAL_ENV') if 'PIPENV_IGNORE_VIRTUALENVS' PIPENV_USE_HASHES = True # Tells pipenv to skip case-checking (slow internet connections). -PIPENV_SKIP_VALIDATION = os.environ.get('PIPENV_SKIP_VALIDATION') +PIPENV_SKIP_VALIDATION = True # Use shell compatibility mode when using venv in project mode. if PIPENV_VENV_IN_PROJECT: diff --git a/pipenv/project.py b/pipenv/project.py index 9654a217..09cec0d8 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -273,4 +273,7 @@ class Project(object): p[key][package_name] = package[package_name] # Write Pipfile. - self.write_toml(recase_file(p)) + self.write_toml(p) + + def recase_pipfile(self): + self.write_toml(recase_file(self._pipfile)) diff --git a/pipenv/utils.py b/pipenv/utils.py index 840af4fe..51cefd20 100644 --- a/pipenv/utils.py +++ b/pipenv/utils.py @@ -23,7 +23,7 @@ class PipCommand(pip.basecommand.Command): def shellquote(s): - return "'" + s.replace("'", "'\\''") + "'" + return "'" + s.replace("'", "'\\''") + "'" def clean_pkg_version(version): @@ -55,18 +55,13 @@ def resolve_deps(deps, sources=None, verbose=False, hashes=False): if verbose: logging.log.verbose = True - r = Resolver(constraints=constraints, repository=pypi) + resolver = Resolver(constraints=constraints, repository=pypi) results = [] - _hashes = r.resolve_hashes(r.resolve()) - # convert to a dictionary indexed by package names instead of install req objects - resolved_hashes = {} - for req, _hash in _hashes.items(): - resolved_hashes[pep423_name(req.name)] = { - 'version': clean_pkg_version(req.specifier), - 'hashes': list(_hash) - } - for result in r.resolve(): + # pre-resolve instead of iterating to avoid asking pypi for hashes of editable packages + resolved_tree = resolver.resolve() + + for result in resolved_tree: name = pep423_name(result.name) version = clean_pkg_version(result.specifier) @@ -78,9 +73,10 @@ def resolve_deps(deps, sources=None, verbose=False, hashes=False): collected_hashes.append(release['digests']['sha256']) collected_hashes = ['sha256:' + s for s in collected_hashes] - # Add pypi resolved hashes - if name in resolved_hashes and resolved_hashes[name]['version'] == version: - collected_hashes.extend(resolved_hashes[name]['hashes']) + + # Collect un-collectable hashes. + if not collected_hashes: + collected_hashes = list(resolver.resolve_hashes([result]).items()[0][1]) results.append({'name': name, 'version': version, 'hashes': collected_hashes}) except ValueError: @@ -361,4 +357,3 @@ def find_requirements(max_depth=3): if os.path.isfile(r): return r raise RuntimeError('No requirements.txt found!') - diff --git a/tests/test_pipenv.py b/tests/test_pipenv.py index 459acc48..4842f27c 100644 --- a/tests/test_pipenv.py +++ b/tests/test_pipenv.py @@ -182,8 +182,10 @@ class TestPipenv(): # Build the environment. os.environ['PIPENV_VENV_IN_PROJECT'] = '1' + delegator.run('touch Pipfile') # Install packages for test. + # print(delegator.run('pipenv install pep8').err) assert delegator.run('pipenv install pep8').return_code == 0 assert delegator.run('pipenv install pytest').return_code == 0 diff --git a/tests/test_project.py b/tests/test_project.py index 04391496..f50e7613 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -65,12 +65,12 @@ class TestProject(): delegator.run('rm -fr test_add_to_pipfile') # Confirm Flask added to packages. - assert 'Flask' in p['packages'] - assert p['packages']['Flask'] == '*' + assert 'flask' in p['packages'] + assert p['packages']['flask'] == '*' # Confirm Django added to dev-packages. - assert 'Django' in p['dev-packages'] - assert p['dev-packages']['Django'] == '==1.10.1' + assert 'django' in p['dev-packages'] + assert p['dev-packages']['django'] == '==1.10.1' # Confirm casing is normalized. assert 'click-completion' in p['packages']