diff --git a/pipenv/__version__.py b/pipenv/__version__.py index d58e2474..9714b0cd 100644 --- a/pipenv/__version__.py +++ b/pipenv/__version__.py @@ -2,4 +2,4 @@ # // ) ) / / // ) ) //___) ) // ) ) || / / # //___/ / / / //___/ / // // / / || / / # // / / // ((____ // / / ||/ / -__version__ = '11.10.1' +__version__ = '11.10.2.dev1' diff --git a/pipenv/_compat.py b/pipenv/_compat.py index d6ae5644..6769d8d4 100644 --- a/pipenv/_compat.py +++ b/pipenv/_compat.py @@ -9,7 +9,7 @@ import io import os import six import warnings -from tempfile import _bin_openflags, gettempdir, _mkstemp_inner, mkdtemp, _text_openflags +from tempfile import _bin_openflags, gettempdir, _mkstemp_inner, mkdtemp from .utils import (logging, rmtree) try: @@ -258,13 +258,12 @@ def NamedTemporaryFile( if os.name == "nt" and delete: flags |= os.O_TEMPORARY if six.PY2: - flags = _text_openflags if 'b' not in mode else flags (fd, name) = _mkstemp_inner(dir, prefix, suffix, flags) else: (fd, name) = _mkstemp_inner(dir, prefix, suffix, flags, output_type) try: file = io.open( - fd, mode, buffering=buffering, newline=newline, encoding=encoding + fd, mode, buffering=buffering, newline=newline, encoding=encoding, ) return _TemporaryFileWrapper(file, name, delete) diff --git a/pipenv/core.py b/pipenv/core.py index 07393434..2110d704 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -25,7 +25,6 @@ import six from .cmdparse import ScriptEmptyError from .project import Project, SourceNotFound from .utils import ( - atomic_open_for_write, convert_deps_from_pip, convert_deps_to_pip, is_required_version, @@ -1168,13 +1167,7 @@ def do_lock( default_package ] if write: - # Write out the lockfile. - with atomic_open_for_write(project.lockfile_location) as f: - simplejson.dump( - lockfile, f, indent=4, separators=(',', ': '), sort_keys=True - ) - # Write newline at end of document. GH Issue #319. - f.write('\n') + project.write_lockfile(lockfile) click.echo( '{0}'.format( crayons.normal( @@ -2494,19 +2487,30 @@ def do_sync( unused=False, sequential=False, ): + # The lock file needs to exist because sync won't write to it. + if not project.lockfile_exists: + click.echo( + '{0}: Pipfile.lock is missing! You need to run {1} first.'.format( + crayons.red('Error', bold=True), + crayons.red('$ pipenv lock', bold=True), + ), + err=True, + ) + sys.exit(1) + + # Ensure that virtualenv is available. + ensure_project(three=three, python=python, validate=False) + + # Install everything. requirements_dir = TemporaryDirectory( suffix='-requirements', prefix='pipenv-' ) - # Ensure that virtualenv is available. - ensure_project(three=three, python=python, validate=False) - concurrent = (not sequential) - ensure_lockfile() - # Install everything. do_init( dev=dev, verbose=verbose, - concurrent=concurrent, + concurrent=(not sequential), requirements_dir=requirements_dir, + ignore_pipfile=True, # Don't check if Pipfile and lock match. ) requirements_dir.cleanup() click.echo(crayons.green('All dependencies are now up-to-date!')) diff --git a/pipenv/patched/notpip/_vendor/requests/LICENSE b/pipenv/patched/notpip/_vendor/requests/LICENSE index 8c1dd448..db78ea69 100644 --- a/pipenv/patched/notpip/_vendor/requests/LICENSE +++ b/pipenv/patched/notpip/_vendor/requests/LICENSE @@ -1,4 +1,4 @@ -Copyright 2016 Kenneth Reitz +Copyright 2017 Kenneth Reitz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pipenv/patched/notpip/_vendor/vendor.txt b/pipenv/patched/notpip/_vendor/vendor.txt new file mode 100644 index 00000000..53181c39 --- /dev/null +++ b/pipenv/patched/notpip/_vendor/vendor.txt @@ -0,0 +1,21 @@ +setuptools==39.1.0 +appdirs==1.4.0 +distlib==0.2.4 +distro==1.2.0 +html5lib==1.0b10 +six==1.10.0 +colorama==0.3.7 +requests==2.18.4 +chardet==3.0.4 +idna==2.6 +urllib3==1.22 +certifi==2018.1.18 +CacheControl==0.11.7 +lockfile==0.12.2 +ordereddict==1.1 +progress==1.2 +ipaddress==1.0.17 +packaging==16.8 +pyparsing==2.1.10 +retrying==1.3.3 +webencodings==0.5 diff --git a/pipenv/project.py b/pipenv/project.py index be489a10..0990afa1 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -import codecs +import io import json import os import re @@ -10,15 +10,18 @@ import contoml from first import first import pipfile import pipfile.api +import six import toml +import json as simplejson try: - import pathlib + from pathlib import Path except ImportError: - import pathlib2 as pathlib + from pathlib2 import Path from .cmdparse import Script from .utils import ( + atomic_open_for_write, mkdir_p, pep423_name, proper_case, @@ -46,7 +49,17 @@ from .environments import ( def _normalized(p): if p is None: return None - return normalize_drive(str(pathlib.Path(p).resolve())) + return normalize_drive(str(Path(p).resolve())) + + +DEFAULT_NEWLINES = u'\n' + + +def preferred_newlines(f): + if isinstance(f.newlines, six.text_type): + return f.newlines + + return DEFAULT_NEWLINES if PIPENV_PIPFILE: @@ -90,6 +103,8 @@ class Project(object): self._download_location = None self._proper_names_location = None self._pipfile_location = None + self._pipfile_newlines = DEFAULT_NEWLINES + self._lockfile_newlines = DEFAULT_NEWLINES self._requirements_location = None self._original_dir = os.path.abspath(os.curdir) self.which = which @@ -369,9 +384,7 @@ class Project(object): """Parse Pipfile into a TOMLFile and cache it (call clear_pipfile_cache() afterwards if mutating)""" - # Open the pipfile, read it into memory. - with open(self.pipfile_location) as f: - contents = f.read() + contents = self.read_pipfile() # use full contents to get around str/bytes 2/3 issues cache_key = (self.pipfile_location, contents) if cache_key not in _pipfile_cache: @@ -379,10 +392,17 @@ class Project(object): _pipfile_cache[cache_key] = parsed return _pipfile_cache[cache_key] + def read_pipfile(self): + # Open the pipfile, read it into memory. + with io.open(self.pipfile_location) as f: + contents = f.read() + self._pipfile_newlines = preferred_newlines(f) + + return contents + @property def pased_pure_pipfile(self): - with open(self.pipfile_location) as f: - contents = f.read() + contents = self.read_pipfile() return self._parse_pipfile(contents) @@ -474,14 +494,7 @@ class Project(object): @property def lockfile_content(self): - with open(self.lockfile_location) as lock: - j = json.load(lock) - - # Expand environment variables in Pipfile.lock at runtime. - for i, source in enumerate(j['_meta']['sources'][:]): - j['_meta']['sources'][i]['url'] = os.path.expandvars(j['_meta']['sources'][i]['url']) - - return j + return self.load_lockfile() @property def editable_packages(self): @@ -546,9 +559,8 @@ class Project(object): if not self.pipfile_exists: return True - with open(self.pipfile_location, 'r') as f: - if not f.read(): - return True + if not len(self.read_pipfile()): + return True return False @@ -572,7 +584,7 @@ class Project(object): u'name': source_name, } ) - + data = { u'source': sources, # Default packages. @@ -610,12 +622,29 @@ class Project(object): ) data[section][package].update(_data) formatted_data = toml.dumps(data).rstrip() + + if Path(path).absolute() == Path(self.pipfile_location).absolute(): + newlines = self._pipfile_newlines + else: + newlines = DEFAULT_NEWLINES formatted_data = cleanup_toml(formatted_data) - with open(path, 'w') as f: + with io.open(path, 'w', newline=newlines) as f: f.write(formatted_data) # pipfile is mutated! self.clear_pipfile_cache() + def write_lockfile(self, content): + """Write out the lockfile. + """ + newlines = self._lockfile_newlines + s = simplejson.dumps( # Send Unicode in to guarentee Unicode out. + content, indent=4, separators=(u',', u': '), sort_keys=True, + ) + with atomic_open_for_write(self.lockfile_location, newline=newlines) as f: + f.write(s) + if not s.endswith(u'\n'): + f.write(u'\n') # Write newline at end of document. GH #319. + @property def pipfile_sources(self): if 'source' in self.parsed_pipfile: @@ -632,7 +661,7 @@ class Project(object): @property def sources(self): - if self.lockfile_exists: + if self.lockfile_exists and hasattr(self.lockfile_content, 'keys'): meta_ = self.lockfile_content['_meta'] sources_ = meta_.get('sources') if sources_: @@ -735,13 +764,30 @@ class Project(object): if self.ensure_proper_casing(): self.write_toml(self.parsed_pipfile) + def load_lockfile(self, expand_env_vars=True): + with io.open(self.lockfile_location) as lock: + j = json.load(lock) + self._lockfile_newlines = preferred_newlines(lock) + # lockfile is just a string + if not j or not hasattr(j, 'keys'): + return j + + if expand_env_vars: + # Expand environment variables in Pipfile.lock at runtime. + for i, source in enumerate(j['_meta']['sources'][:]): + j['_meta']['sources'][i]['url'] = os.path.expandvars(j['_meta']['sources'][i]['url']) + + return j + def get_lockfile_hash(self): if not os.path.exists(self.lockfile_location): return - # Open the lockfile. - with codecs.open(self.lockfile_location, 'r') as f: - lockfile = json.load(f) - return lockfile['_meta'].get('hash', {}).get('sha256') + + lockfile = self.load_lockfile(expand_env_vars=False) + if '_meta' in lockfile and hasattr(lockfile, 'keys'): + return lockfile['_meta'].get('hash', {}).get('sha256') + # Lockfile exists but has no hash at all + return '' def calculate_pipfile_hash(self): # Update the lockfile if it is out-of-date. diff --git a/pipenv/utils.py b/pipenv/utils.py index e8c94215..46add117 100644 --- a/pipenv/utils.py +++ b/pipenv/utils.py @@ -657,6 +657,7 @@ def convert_deps_to_pip(deps, project=None, r=True, include_index=False): for dep in deps.keys(): # Default (e.g. '>1.10'). extra = deps[dep] if isinstance(deps[dep], six.string_types) else '' + extras = '' version = '' index = '' # Get rid of '*'. @@ -675,7 +676,7 @@ def convert_deps_to_pip(deps, project=None, r=True, include_index=False): ) # Support for extras (e.g. requests[socks]) if 'extras' in deps[dep]: - extra = '[{0}]'.format(','.join(deps[dep]['extras'])) + extras = '[{0}]'.format(','.join(deps[dep]['extras'])) if 'version' in deps[dep]: if not is_star(deps[dep]['version']): version = deps[dep]['version'] @@ -709,9 +710,14 @@ def convert_deps_to_pip(deps, project=None, r=True, include_index=False): # Support for version control maybe_vcs = [vcs for vcs in VCS_LIST if vcs in deps[dep]] vcs = maybe_vcs[0] if maybe_vcs else None + if not any(key in deps[dep] for key in ['path', 'vcs', 'file']): + extra += extras # Support for files. if 'file' in deps[dep]: - extra = '{1}{0}'.format(extra, deps[dep]['file']).strip() + dep_file = deps[dep]['file'] + if is_valid_url(dep_file) and dep_file.startswith('http'): + dep_file += '#egg={0}'.format(dep) + extra = '{0}{1}'.format(dep_file, extras).strip() # Flag the file as editable if it is a local relative path if 'editable' in deps[dep]: dep = '-e ' @@ -719,7 +725,7 @@ def convert_deps_to_pip(deps, project=None, r=True, include_index=False): dep = '' # Support for paths. elif 'path' in deps[dep]: - extra = '{1}{0}'.format(extra, deps[dep]['path']).strip() + extra = '{1}{0}'.format(extras, deps[dep]['path']).strip() # Flag the file as editable if it is a local relative path if 'editable' in deps[dep]: dep = '-e ' @@ -730,7 +736,7 @@ def convert_deps_to_pip(deps, project=None, r=True, include_index=False): # Support for @refs. if 'ref' in deps[dep]: extra += '@{0}'.format(deps[dep]['ref']) - extra += '#egg={0}'.format(dep) + extra += '#egg={0}{1}'.format(dep, extras) # Support for subdirectory if 'subdirectory' in deps[dep]: extra += '&subdirectory={0}'.format(deps[dep]['subdirectory']) @@ -740,6 +746,7 @@ def convert_deps_to_pip(deps, project=None, r=True, include_index=False): dep = '-e ' else: dep = '' + s = '{0}{1}{2}{3}{4} {5}'.format( dep, extra, version, specs, hash, index ).strip() @@ -1305,6 +1312,7 @@ def atomic_open_for_write(target, binary=False, newline=None, encoding=None): target with this new file. """ from ._compat import NamedTemporaryFile + mode = 'w+b' if binary else 'w' f = NamedTemporaryFile( dir=os.path.dirname(target), diff --git a/pipenv/vendor/pip9/_vendor/requests/LICENSE b/pipenv/vendor/pip9/_vendor/requests/LICENSE index 8c1dd448..db78ea69 100644 --- a/pipenv/vendor/pip9/_vendor/requests/LICENSE +++ b/pipenv/vendor/pip9/_vendor/requests/LICENSE @@ -1,4 +1,4 @@ -Copyright 2016 Kenneth Reitz +Copyright 2017 Kenneth Reitz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pipenv/vendor/pip9/_vendor/vendor.txt b/pipenv/vendor/pip9/_vendor/vendor.txt new file mode 100644 index 00000000..53181c39 --- /dev/null +++ b/pipenv/vendor/pip9/_vendor/vendor.txt @@ -0,0 +1,21 @@ +setuptools==39.1.0 +appdirs==1.4.0 +distlib==0.2.4 +distro==1.2.0 +html5lib==1.0b10 +six==1.10.0 +colorama==0.3.7 +requests==2.18.4 +chardet==3.0.4 +idna==2.6 +urllib3==1.22 +certifi==2018.1.18 +CacheControl==0.11.7 +lockfile==0.12.2 +ordereddict==1.1 +progress==1.2 +ipaddress==1.0.17 +packaging==16.8 +pyparsing==2.1.10 +retrying==1.3.3 +webencodings==0.5 diff --git a/pipenv/vendor/vendor_pip.txt b/pipenv/vendor/vendor_pip.txt new file mode 100644 index 00000000..53181c39 --- /dev/null +++ b/pipenv/vendor/vendor_pip.txt @@ -0,0 +1,21 @@ +setuptools==39.1.0 +appdirs==1.4.0 +distlib==0.2.4 +distro==1.2.0 +html5lib==1.0b10 +six==1.10.0 +colorama==0.3.7 +requests==2.18.4 +chardet==3.0.4 +idna==2.6 +urllib3==1.22 +certifi==2018.1.18 +CacheControl==0.11.7 +lockfile==0.12.2 +ordereddict==1.1 +progress==1.2 +ipaddress==1.0.17 +packaging==16.8 +pyparsing==2.1.10 +retrying==1.3.3 +webencodings==0.5 diff --git a/tasks/vendoring/__init__.py b/tasks/vendoring/__init__.py index 37db03fc..5280b799 100644 --- a/tasks/vendoring/__init__.py +++ b/tasks/vendoring/__init__.py @@ -17,13 +17,14 @@ import requests TASK_NAME = 'update' -LIBRARY_OVERRIDES = { +LIBRARY_DIRNAMES = { 'requirements-parser': 'requirements', 'backports.shutil_get_terminal_size': 'backports/shutil_get_terminal_size', 'backports.weakref': 'backports/weakref', 'shutil_backports': 'backports/shutil_get_terminal_size', 'python-dotenv': 'dotenv', - 'pip-tools': 'piptools' + 'pip-tools': 'piptools', + 'setuptools': 'pkg_resources', } # from time to time, remove the no longer needed ones @@ -38,7 +39,10 @@ HARDCODED_LICENSE_URLS = { 'semver': 'https://raw.githubusercontent.com/k-bx/python-semver/master/LICENSE.txt', 'crayons': 'https://raw.githubusercontent.com/kennethreitz/crayons/master/LICENSE', 'pip-tools': 'https://raw.githubusercontent.com/jazzband/pip-tools/master/LICENSE', - 'pew': 'https://raw.githubusercontent.com/berdario/pew/master/LICENSE' + 'pew': 'https://raw.githubusercontent.com/berdario/pew/master/LICENSE', + 'pytoml': 'https://github.com/avakar/pytoml/raw/master/LICENSE', + 'webencodings': 'https://github.com/SimonSapin/python-webencodings/raw/' + 'master/LICENSE', } FILE_WHITE_LIST = ( @@ -50,7 +54,8 @@ FILE_WHITE_LIST = ( 'README.md', 'appdirs.py', 'safety.zip', - 'cacert.pem' + 'cacert.pem', + 'vendor_pip.txt', ) LIBRARY_RENAMES = { @@ -212,14 +217,6 @@ cli(prog_name="safety") else: lib = yaml_build_dir / 'lib3' / 'yaml' shutil.copytree(str(lib.absolute()), str(safety_dir / 'yaml{0}'.format(version_choices[0]))) -# yaml_init = yaml_dir / '__init__.py' -# yaml_init.write_text(""" -# import sys -# if sys.version_info[0] == 3: -# from .yaml3 import * -# else: -# from .yaml2 import * -# """.strip()) requests_dir = safety_dir / 'requests' cacert = vendor_dir / 'requests' / 'cacert.pem' if not cacert.exists(): @@ -247,8 +244,8 @@ cli(prog_name="safety") def rename_if_needed(ctx, vendor_dir, item): rename_dict = LIBRARY_RENAMES if vendor_dir.name != 'patched' else PATCHED_RENAMES new_path = None - if item.name in rename_dict or item.name in LIBRARY_OVERRIDES: - new_name = rename_dict.get(item.name, LIBRARY_OVERRIDES.get(item.name)) + if item.name in rename_dict or item.name in LIBRARY_DIRNAMES: + new_name = rename_dict.get(item.name, LIBRARY_DIRNAMES.get(item.name)) new_path = item.parent / new_name log('Renaming %s => %s' % (item.name, new_path)) # handle existing directories @@ -307,7 +304,6 @@ def vendor(ctx, vendor_dir, rewrite=True): apply_patch(ctx, patch) # Global import rewrites - # log("Rewriting all imports related to vendored libs") log('Renaming specified libs...') for item in vendor_dir.iterdir(): if item.is_dir(): @@ -401,16 +397,17 @@ def find_and_extract_license(vendor_dir, tar, members): def license_fallback(vendor_dir, sdist_name): """Hardcoded license URLs. Check when updating if those are still needed""" - for libname, url in HARDCODED_LICENSE_URLS.items(): - if libname in sdist_name: - _, _, name = url.rpartition('/') - dest = license_destination(vendor_dir, libname, name) - r = requests.get(url, allow_redirects=True) - log('Downloading {}'.format(url)) - r.raise_for_status() - dest.write_bytes(r.content) - return - raise ValueError('No hardcoded URL for {} license'.format(sdist_name)) + libname = libname_from_dir(sdist_name) + if libname not in HARDCODED_LICENSE_URLS: + raise ValueError('No hardcoded URL for {} license'.format(libname)) + + url = HARDCODED_LICENSE_URLS[libname] + _, _, name = url.rpartition('/') + dest = license_destination(vendor_dir, libname, name) + r = requests.get(url, allow_redirects=True) + log('Downloading {}'.format(url)) + r.raise_for_status() + dest.write_bytes(r.content) def libname_from_dir(dirname): @@ -420,7 +417,7 @@ def libname_from_dir(dirname): if part[0].isdigit(): break parts.append(part) - return'-'.join(parts) + return '-'.join(parts) def license_destination(vendor_dir, libname, filename): @@ -432,16 +429,17 @@ def license_destination(vendor_dir, libname, filename): if lowercase.is_dir(): return lowercase / filename rename_dict = LIBRARY_RENAMES if vendor_dir.name != 'patched' else PATCHED_RENAMES + # Short circuit all logic if we are renaming the whole library if libname in rename_dict: return vendor_dir / rename_dict[libname] / filename - if libname in LIBRARY_OVERRIDES: - override = vendor_dir / LIBRARY_OVERRIDES[libname] + if libname in LIBRARY_DIRNAMES: + override = vendor_dir / LIBRARY_DIRNAMES[libname] if not override.exists() and override.parent.exists(): # for flattened subdeps, specifically backports/weakref.py - target_dir = vendor_dir / override.parent - target_file = '{0}.{1}'.format(override.name, filename) - return target_dir / target_file - return vendor_dir / LIBRARY_OVERRIDES[libname] / filename + return ( + vendor_dir / override.parent + ) / '{0}.{1}'.format(override.name, filename) + return vendor_dir / LIBRARY_DIRNAMES[libname] / filename # fallback to libname.LICENSE (used for nondirs) return vendor_dir / '{}.{}'.format(libname, filename) @@ -451,8 +449,6 @@ def extract_license_member(vendor_dir, tar, member, name): dirname = list(mpath.parents)[-2].name # -1 is . libname = libname_from_dir(dirname) dest = license_destination(vendor_dir, libname, mpath.name) - # dest_relative = dest.relative_to(Path.cwd()) - # log('Extracting {} into {}'.format(name, dest_relative)) log('Extracting {} into {}'.format(name, dest)) try: fileobj = tar.extractfile(member) @@ -461,36 +457,6 @@ def extract_license_member(vendor_dir, tar, member, name): dest.write_bytes(tar.read(member)) -@invoke.task -def update_stubs(ctx): - vendor_dir = _get_vendor_dir(ctx) - vendored_libs = detect_vendored_libs(vendor_dir) - - print("[vendoring.update_stubs] Add mypy stubs") - - extra_stubs_needed = { - # Some projects need stubs other than a simple .pyi - "six": ["six.__init__", "six.moves"], - # Some projects should not have stubs coz they're single file modules - "appdirs": [], - } - - for lib in vendored_libs: - if lib not in extra_stubs_needed: - (vendor_dir / (lib + ".pyi")).write_text("from %s import *" % lib) - continue - - for selector in extra_stubs_needed[lib]: - fname = selector.replace(".", os.sep) + ".pyi" - if selector.endswith(".__init__"): - selector = selector[:-9] - - f_path = vendor_dir / fname - if not f_path.parent.exists(): - f_path.parent.mkdir() - f_path.write_text("from %s import *" % selector) - - @invoke.task(name=TASK_NAME) def main(ctx): vendor_dir = _get_vendor_dir(ctx) @@ -502,5 +468,11 @@ def main(ctx): vendor(ctx, patched_dir, rewrite=False) download_licenses(ctx, vendor_dir) download_licenses(ctx, patched_dir, 'patched.txt') + for pip_dir in [vendor_dir / 'pip9', patched_dir / 'notpip']: + _vendor_dir = pip_dir / '_vendor' + vendor_src_file = vendor_dir / 'vendor_pip.txt' + vendor_file = _vendor_dir / 'vendor.txt' + vendor_file.write_bytes(vendor_src_file.read_bytes()) + download_licenses(ctx, _vendor_dir) # update_safety(ctx) log('Revendoring complete') diff --git a/tasks/vendoring/patches/patched/_post-pip-update-pypi-uri.patch b/tasks/vendoring/patches/patched/_post-pip-update-pypi-uri.patch index 58adff3e..d6d1ed93 100644 --- a/tasks/vendoring/patches/patched/_post-pip-update-pypi-uri.patch +++ b/tasks/vendoring/patches/patched/_post-pip-update-pypi-uri.patch @@ -55,3 +55,14 @@ index 48aaa35..bf1cba9 100644 url: url of the resource pointed to (href of the link) + +diff --git a/pipenv/patched/notpip/models/index.py b/pipenv/patched/notpip/models/index.py +index 25fd488d..db324287 100644 +--- a/pipenv/patched/notpip/models/index.py ++++ b/pipenv/patched/notpip/models/index.py +@@ -13,4 +13,4 @@ class Index(object): + return urllib_parse.urljoin(self.url, path) + + +-PyPI = Index('https://pypi.python.org/') ++PyPI = Index('https://pypi.org/') diff --git a/tasks/vendoring/patches/patched/pipfile-update-pypi-uri.patch b/tasks/vendoring/patches/patched/pipfile-update-pypi-uri.patch deleted file mode 100644 index ecbe2aac..00000000 --- a/tasks/vendoring/patches/patched/pipfile-update-pypi-uri.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/pipenv/patched/pipfile/api.py b/pipenv/patched/pipfile/api.py -index 18a1ea2..e8fa027 100644 ---- a/pipenv/patched/pipfile/api.py -+++ b/pipenv/patched/pipfile/api.py -@@ -10,7 +10,7 @@ import os - - - DEFAULT_SOURCE = { -- u'url': u'https://pypi.python.org/simple', -+ u'url': u'https://pypi.org/simple', - u'verify_ssl': True, - u'name': u'pypi', - } diff --git a/tasks/vendoring/patches/patched/pipfile.patch b/tasks/vendoring/patches/patched/pipfile.patch index 90a3d05b..4344e4bd 100644 --- a/tasks/vendoring/patches/patched/pipfile.patch +++ b/tasks/vendoring/patches/patched/pipfile.patch @@ -12,7 +12,7 @@ index 8a2a6a3..18a1ea2 100644 +DEFAULT_SOURCE = { -+ u'url': u'https://pypi.python.org/simple', ++ u'url': u'https://pypi.org/simple', + u'verify_ssl': True, + u'name': u'pypi', +} diff --git a/tasks/vendoring/patches/patched/piptools.patch b/tasks/vendoring/patches/patched/piptools.patch index 4946c491..951ce87d 100644 --- a/tasks/vendoring/patches/patched/piptools.patch +++ b/tasks/vendoring/patches/patched/piptools.patch @@ -85,7 +85,8 @@ index d3b7fe7..e1f63d2 100644 + + class PyPIRepository(BaseRepository): - DEFAULT_INDEX_URL = 'https://pypi.python.org/simple' +- DEFAULT_INDEX_URL = 'https://pypi.python.org/simple' ++ DEFAULT_INDEX_URL = 'https://pypi.org/simple' @@ -30,8 +69,9 @@ class PyPIRepository(BaseRepository): config), but any other PyPI mirror can be used if index_urls is diff --git a/tasks/vendoring/patches/patched/prettytoml-table-iter.patch b/tasks/vendoring/patches/patched/prettytoml-table-iter.patch index 5758e122..9ec52633 100644 --- a/tasks/vendoring/patches/patched/prettytoml-table-iter.patch +++ b/tasks/vendoring/patches/patched/prettytoml-table-iter.patch @@ -10,19 +10,20 @@ index 59fd5748..48663aed 100644 + from prettytoml.elements.common import ContainerElement from prettytoml.elements import traversal - - + + -class AbstractTable(ContainerElement, traversal.TraversalMixin): +class AbstractTable(ContainerElement, traversal.TraversalMixin, Mapping): """ Common code for handling tables as key-value pairs with metadata elements sprinkled all over. - + @@ -37,6 +42,9 @@ class AbstractTable(ContainerElement, traversal.TraversalMixin): def __len__(self): return len(tuple(self._enumerate_items())) - + + def __iter__(self): + return (key for key, _ in self.items()) + def __contains__(self, item): return item in self.keys() + diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 84cfa3c7..3a07d145 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -113,10 +113,14 @@ class _PipenvInstance(object): @property def lockfile(self): - p_path = os.sep.join([self.path, 'Pipfile.lock']) + p_path = self.lockfile_path with open(p_path, 'r') as f: return json.loads(f.read()) + @property + def lockfile_path(self): + return os.sep.join([self.path, 'Pipfile.lock']) + @pytest.fixture() def PipenvInstance(): diff --git a/tests/integration/test_project.py b/tests/integration/test_project.py index 62781c11..d299260e 100644 --- a/tests/integration/test_project.py +++ b/tests/integration/test_project.py @@ -1,4 +1,5 @@ # -*- coding=utf-8 -*- +import io import pytest import os from pipenv.project import Project @@ -73,3 +74,40 @@ six = {{version = "*", index = "pypi"}} assert sorted(source.items()) == sorted(project.get_source(url=url).items()) assert sorted(source.items()) == sorted(project.find_source(name).items()) assert sorted(source.items()) == sorted(project.find_source(url).items()) + + +@pytest.mark.install +@pytest.mark.project +@pytest.mark.parametrize('newlines', [u'\n', u'\r\n']) +def test_maintain_file_line_endings(PipenvInstance, pypi, newlines): + with PipenvInstance(pypi=pypi, chdir=True) as p: + # Initial pipfile + lockfile generation + c = p.pipenv('install pytz') + assert c.return_code == 0 + + # Rewrite each file with parameterized newlines + for fn in [p.pipfile_path, p.lockfile_path]: + with io.open(fn) as f: + contents = f.read() + written_newlines = f.newlines + + assert written_newlines == u'\n', '{0!r} != {1!r} for {2}'.format( + written_newlines, u'\n', fn, + ) + # message because of https://github.com/pytest-dev/pytest/issues/3443 + with io.open(fn, 'w', newline=newlines) as f: + f.write(contents) + + # Run pipenv install to programatically rewrite + c = p.pipenv('install chardet') + assert c.return_code == 0 + + # Make sure we kept the right newlines + for fn in [p.pipfile_path, p.lockfile_path]: + with io.open(fn) as f: + f.read() # Consumes the content to detect newlines. + actual_newlines = f.newlines + assert actual_newlines == newlines, '{0!r} != {1!r} for {2}'.format( + actual_newlines, newlines, fn, + ) + # message because of https://github.com/pytest-dev/pytest/issues/3443 diff --git a/tests/integration/test_sync.py b/tests/integration/test_sync.py new file mode 100644 index 00000000..1116ed83 --- /dev/null +++ b/tests/integration/test_sync.py @@ -0,0 +1,42 @@ +import pytest + + +@pytest.mark.sync +def test_sync_error_without_lockfile(PipenvInstance, pypi): + with PipenvInstance(pypi=pypi) as p: + with open(p.pipfile_path, 'w') as f: + f.write(""" +[packages] + """.strip()) + + c = p.pipenv('sync') + assert c.return_code != 0 + assert 'Pipfile.lock is missing!' in c.err + + +@pytest.mark.sync +@pytest.mark.lock +def test_sync_should_not_lock(PipenvInstance, pypi): + """Sync should not touch the lock file, even if Pipfile is changed. + """ + with PipenvInstance(pypi=pypi) as p: + with open(p.pipfile_path, 'w') as f: + f.write(""" +[packages] + """.strip()) + + # Perform initial lock. + c = p.pipenv('lock') + assert c.return_code == 0 + lockfile_content = p.lockfile + assert lockfile_content + + # Make sure sync does not trigger lockfile update. + with open(p.pipfile_path, 'w') as f: + f.write(""" +[packages] +six = "*" + """.strip()) + c = p.pipenv('sync') + assert c.return_code == 0 + assert lockfile_content == p.lockfile diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index b10e273f..1eedcccc 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -44,7 +44,21 @@ DEP_PIP_PAIRS = [ }}, '-e svn+svn://svn.myproject.org/svn/MyProject#egg=MyProject', ), - + ( + # Extras in url + {'discord.py': { + 'file': 'https://github.com/Rapptz/discord.py/archive/rewrite.zip', + 'extras': ['voice'] + }}, + 'https://github.com/Rapptz/discord.py/archive/rewrite.zip#egg=discord.py[voice]', + ), + ( + {'requests': { + 'git': 'https://github.com/requests/requests.git', + 'ref': 'master', 'extras': ['security'], + }}, + 'git+https://github.com/requests/requests.git@master#egg=requests[security]', + ), ] @@ -97,20 +111,6 @@ def test_convert_from_pip(expected, requirement): assert pipenv.utils.convert_deps_from_pip(requirement) == expected -@pytest.mark.utils -@pytest.mark.parametrize('expected, requirement', [ - ( # XXX: This should work the other way around as well, but does not atm. - {'requests': { - 'git': 'https://github.com/requests/requests.git', - 'ref': 'master', 'extras': ['security'], - }}, - 'git+https://github.com/requests/requests.git@master#egg=requests[security]', - ), -]) -def test_convert_from_pip_vcs_with_extra(expected, requirement): - assert pipenv.utils.convert_deps_from_pip(requirement) == expected - - @pytest.mark.utils def test_convert_from_pip_fail_if_no_egg(): """Parsing should fail without `#egg=`.