diff --git a/pipenv/cli.py b/pipenv/cli.py index 56e62969..449cd3c8 100644 --- a/pipenv/cli.py +++ b/pipenv/cli.py @@ -503,9 +503,6 @@ def lock( # Ensure that virtualenv is available. core.ensure_project(three=three, python=python) - # Load the --pre settings from the Pipfile. - if not pre: - pre = core.project.settings.get('pre') if requirements: core.do_init(dev=dev, requirements=requirements) core.do_lock( @@ -739,9 +736,6 @@ def update( crayons.white('.', bold=True), ) ) - # Load the --pre settings from the Pipfile. - if not pre: - pre = core.project.settings.get('pre') core.do_lock( verbose=verbose, clear=clear, pre=pre, keep_outdated=keep_outdated ) diff --git a/pipenv/core.py b/pipenv/core.py index a5f016b0..91f07f2a 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -1011,6 +1011,8 @@ def do_lock( from notpip._vendor.distlib.markers import Evaluator allowed_marker_keys = ['markers'] + [k for k in Evaluator.allowed_values.keys()] cached_lockfile = {} + if not pre: + pre = project.settings.get('allow_prereleases') if keep_outdated: if not project.lockfile_exists: click.echo( @@ -1634,8 +1636,11 @@ def warn_in_virtualenv(): click.echo( '{0}: Pipenv found itself running within a virtual environment, ' 'so it will automatically use that environment, instead of ' - 'creating its own for any project.'.format( - crayons.green('Courtesy Notice') + 'creating its own for any project. You can set ' + '{1} to force pipenv to ignore that environment and create ' + 'its own instead.'.format( + crayons.green('Courtesy Notice'), + crayons.normal('PIPENV_IGNORE_VIRTUALENVS=1', bold=True), ), err=True, ) @@ -1643,7 +1648,6 @@ def warn_in_virtualenv(): def ensure_lockfile(keep_outdated=False): """Ensures that the lockfile is up–to–date.""" - pre = project.settings.get('allow_prereleases') if not keep_outdated: keep_outdated = project.settings.get('keep_outdated') # Write out the lockfile if it doesn't exist, but not if the Pipfile is being ignored @@ -1660,9 +1664,9 @@ def ensure_lockfile(keep_outdated=False): ), err=True, ) - do_lock(pre=pre, keep_outdated=keep_outdated) + do_lock(keep_outdated=keep_outdated) else: - do_lock(pre=pre, keep_outdated=keep_outdated) + do_lock(keep_outdated=keep_outdated) def do_py(system=False): @@ -2017,8 +2021,6 @@ def do_uninstall( system = True # Ensure that virtualenv is available. ensure_project(three=three, python=python) - # Load the --pre settings from the Pipfile. - pre = project.settings.get('allow_prereleases') package_names = (package_name,) + more_packages pipfile_remove = True # Un-install all dependencies, if --all was provided. @@ -2085,7 +2087,7 @@ def do_uninstall( project.remove_package_from_pipfile(package_name, dev=True) project.remove_package_from_pipfile(package_name, dev=False) if lock: - do_lock(system=system, pre=pre, keep_outdated=keep_outdated) + do_lock(system=system, keep_outdated=keep_outdated) def do_shell(three=None, python=False, fancy=False, shell_args=None): @@ -2183,7 +2185,7 @@ def inline_activate_virtualenv(): activate_this = which('activate_this.py') with open(activate_this) as f: code = compile(f.read(), activate_this, 'exec') - exec (code, dict(__file__=activate_this)) + exec(code, dict(__file__=activate_this)) # Catch all errors, just in case. except Exception: click.echo( diff --git a/pipenv/patched/contoml/file/file.py b/pipenv/patched/contoml/file/file.py index 16017b99..99ce1483 100755 --- a/pipenv/patched/contoml/file/file.py +++ b/pipenv/patched/contoml/file/file.py @@ -231,7 +231,7 @@ class TOMLFile: if has_anonymous_entry(): return items else: - return items + [('', self[''])] + return list(items) + [('', self[''])] @property def primitive(self): diff --git a/pipenv/patched/pipfile/api.py b/pipenv/patched/pipfile/api.py index 08aa49c3..18a1ea23 100644 --- a/pipenv/patched/pipfile/api.py +++ b/pipenv/patched/pipfile/api.py @@ -9,6 +9,13 @@ import sys import os +DEFAULT_SOURCE = { + u'url': u'https://pypi.python.org/simple', + u'verify_ssl': True, + u'name': u'pypi', +} + + def format_full_version(info): version = '{0.major}.{0.minor}.{0.micro}'.format(info) kind = info.releaselevel @@ -89,7 +96,7 @@ class PipfileParser(object): # Load the default configuration. default_config = { - u'source': [{u'url': u'https://pypi.python.org/simple', u'verify_ssl': True, 'name': "pypi"}], + u'source': [DEFAULT_SOURCE], u'packages': {}, u'requires': {}, u'dev-packages': {} diff --git a/pipenv/project.py b/pipenv/project.py index 4b8d1bb7..9da86876 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -12,6 +12,7 @@ import hashlib import contoml import delegator import pipfile +import pipfile.api import toml from pip9 import ConfigOptionParser @@ -51,6 +52,26 @@ if PIPENV_PIPFILE: _pipfile_cache = {} +if PIPENV_TEST_INDEX: + DEFAULT_SOURCE = { + u'url': PIPENV_TEST_INDEX, + u'verify_ssl': True, + u'name': u'custom', + } +else: + DEFAULT_SOURCE = { + u'url': u'https://pypi.python.org/simple', + u'verify_ssl': True, + u'name': u'pypi', + } + +pipfile.api.DEFAULT_SOURCE = DEFAULT_SOURCE + + +class SourceNotFound(KeyError): + pass + + class Project(object): """docstring for Project""" @@ -490,35 +511,20 @@ class Project(object): config_parser = ConfigOptionParser(name=self.name) install = dict(config_parser.get_config_section('install')) indexes = install.get('extra-index-url', '').lstrip('\n').split('\n') - if PIPENV_TEST_INDEX: - sources = [ - { - u'url': PIPENV_TEST_INDEX, - u'verify_ssl': True, - u'name': u'custom', - } - ] - else: - # Default source. - pypi_source = { - u'url': u'https://pypi.python.org/simple', - u'verify_ssl': True, - u'name': 'pypi', - } - sources = [pypi_source] - for i, index in enumerate(indexes): - if not index: - continue + sources = [DEFAULT_SOURCE] + for i, index in enumerate(indexes): + if not index: + continue - source_name = 'pip_index_{}'.format(i) - verify_ssl = index.startswith('https') - sources.append( - { - u'url': index, - u'verify_ssl': verify_ssl, - u'name': source_name, - } - ) + source_name = 'pip_index_{}'.format(i) + verify_ssl = index.startswith('https') + sources.append( + { + u'url': index, + u'verify_ssl': verify_ssl, + u'name': source_name, + } + ) data = { u'source': sources, # Default packages. @@ -565,15 +571,8 @@ class Project(object): if 'source' in self.parsed_pipfile: return self.parsed_pipfile['source'] - else: - return [ - { - u'url': u'https://pypi.python.org/simple', - u'verify_ssl': True, - 'name': 'pypi', - } - ] + return [DEFAULT_SOURCE] def get_source(self, name=None, url=None): for source in self.sources: @@ -584,6 +583,7 @@ class Project(object): elif url: if source.get('url') in url: return source + raise SourceNotFound(name or url) def destroy_lockfile(self): """Deletes the lockfile.""" diff --git a/pipenv/utils.py b/pipenv/utils.py index 741826f3..7d60e440 100644 --- a/pipenv/utils.py +++ b/pipenv/utils.py @@ -1207,7 +1207,7 @@ class TemporaryDirectory(object): in it are removed. """ - def __init__(self, suffix=None, prefix=None, dir=None): + def __init__(self, suffix, prefix, dir=None): if 'RAM_DISK' in os.environ: import uuid diff --git a/tests/pypi/multidict/multidict-4.1.0.tar.gz b/tests/pypi/multidict/multidict-4.1.0.tar.gz new file mode 100644 index 00000000..89adbb22 Binary files /dev/null and b/tests/pypi/multidict/multidict-4.1.0.tar.gz differ diff --git a/tests/pypi/sqlalchemy/SQLAlchemy-1.2.0b3.tar.gz b/tests/pypi/sqlalchemy/SQLAlchemy-1.2.0b3.tar.gz new file mode 100644 index 00000000..40eafa66 Binary files /dev/null and b/tests/pypi/sqlalchemy/SQLAlchemy-1.2.0b3.tar.gz differ diff --git a/tests/pypi/tablib/tablib-0.11.5.tar.gz b/tests/pypi/tablib/tablib-0.11.5.tar.gz new file mode 100644 index 00000000..460cd0a7 Binary files /dev/null and b/tests/pypi/tablib/tablib-0.11.5.tar.gz differ diff --git a/tests/pypi/tablib/tablib-0.12.0.tar.gz b/tests/pypi/tablib/tablib-0.12.0.tar.gz new file mode 100644 index 00000000..36ab89f2 Binary files /dev/null and b/tests/pypi/tablib/tablib-0.12.0.tar.gz differ diff --git a/tests/pypi/vcrpy/vcrpy-1.11.0.tar.gz b/tests/pypi/vcrpy/vcrpy-1.11.0.tar.gz new file mode 100644 index 00000000..605bce97 Binary files /dev/null and b/tests/pypi/vcrpy/vcrpy-1.11.0.tar.gz differ diff --git a/tests/pypi/wrapt/wrapt-1.10.11.tar.gz b/tests/pypi/wrapt/wrapt-1.10.11.tar.gz new file mode 100644 index 00000000..7f6870e2 Binary files /dev/null and b/tests/pypi/wrapt/wrapt-1.10.11.tar.gz differ diff --git a/tests/pytest-pypi/pytest_pypi.egg-info/PKG-INFO b/tests/pytest-pypi/pytest_pypi.egg-info/PKG-INFO index 966cd7ad..50322734 100644 --- a/tests/pytest-pypi/pytest_pypi.egg-info/PKG-INFO +++ b/tests/pytest-pypi/pytest_pypi.egg-info/PKG-INFO @@ -6,7 +6,6 @@ Home-page: https://github.com/kennethreitz/pytest-pypi Author: Kenneth Reitz Author-email: me@kennethreitz.org License: MIT -Description-Content-Type: UNKNOWN Description: pytest-httpbin ============== diff --git a/tests/test_pipenv.py b/tests/test_pipenv.py index 42865df2..5c12a9b4 100644 --- a/tests/test_pipenv.py +++ b/tests/test_pipenv.py @@ -11,6 +11,7 @@ from pipenv.utils import ( ) from pipenv.vendor import toml from pipenv.vendor import delegator +from pipenv.vendor import requests from pipenv.patched import pipfile from pipenv.project import Project from pipenv.vendor.six import PY2 @@ -23,15 +24,31 @@ try: except ImportError: from pipenv.vendor.pathlib2 import Path -py3_only = pytest.mark.skipif(sys.version_info < (3, 0), reason="requires Python3") -nix_only = pytest.mark.skipif(os.name != 'nt', reason="doesn't run on windows") - os.environ['PIPENV_DONT_USE_PYENV'] = '1' os.environ['PIPENV_IGNORE_VIRTUALENVS'] = '1' os.environ['PIPENV_VENV_IN_PROJECT'] = '1' os.environ['PYPI_VENDOR_DIR'] = os.path.sep.join([os.path.dirname(__file__), 'pypi']) +def check_internet(): + try: + # Kenneth represents the Internet LGTM. + resp = requests.get('http://httpbin.org/ip', timeout=1.0) + resp.raise_for_status() + except Exception: + warnings.warn('Cannot connect to HTTPBin...', ResourceWarning) + warnings.warn('Will skip tests requiring Internet', ResourceWarning) + return False + return True + + +WE_HAVE_INTERNET = check_internet() + +needs_internet = pytest.mark.skipif(not WE_HAVE_INTERNET, reason='requires internet') +py3_only = pytest.mark.skipif(sys.version_info < (3, 0), reason="requires Python3") +nix_only = pytest.mark.skipif(os.name != 'nt', reason="doesn't run on windows") + + @pytest.fixture(scope='module') def pip_src_dir(request): old_src_dir = os.environ.get('PIP_SRC', '') @@ -46,13 +63,16 @@ def pip_src_dir(request): return request -class PipenvInstance(): +VERBOSE_COMMANDS = ('install', 'lock', 'uninstall') + + +class PipenvInstance(object): """An instance of a Pipenv Project...""" def __init__(self, pypi=None, pipfile=True, chdir=False): self.pypi = pypi self.original_umask = os.umask(0o007) self.original_dir = os.path.abspath(os.curdir) - self._path = TemporaryDirectory(suffix='project', prefix='pipenv') + self._path = TemporaryDirectory(suffix='-project', prefix='pipenv-') self.path = self._path.name # set file creation perms self.pipfile_path = None @@ -91,7 +111,11 @@ class PipenvInstance(): if self.pipfile_path: os.environ['PIPENV_PIPFILE'] = self.pipfile_path - c = delegator.run('pipenv {0}'.format(cmd), block=block) + with TemporaryDirectory(prefix='pipenv-', suffix='-cache') as tempdir: + os.environ['PIPENV_CACHE_DIR'] = tempdir.name + c = delegator.run('pipenv {0}'.format(cmd), block=block) + if 'PIPENV_CACHE_DIR' in os.environ: + del os.environ['PIPENV_CACHE_DIR'] if 'PIPENV_PIPFILE' in os.environ: del os.environ['PIPENV_PIPFILE'] @@ -182,6 +206,7 @@ class TestPipenv: assert 'Warning: Using both --reverse and --json together is not supported.' in c.err @pytest.mark.cli + @needs_internet def test_pipenv_check(self, pypi): with PipenvInstance(pypi=pypi) as p: p.pipenv('install requests==1.0.0') @@ -354,7 +379,8 @@ tablib = "*" shutil.copy(source_path, os.path.join(p.path, file_name)) os.mkdir(os.path.join(p.path, "tablib")) c = p.pipenv('install {}'.format(file_name)) - c = p.pipenv('uninstall --all --verbose') + assert c.return_code == 0 + c = p.pipenv('uninstall --all') assert c.return_code == 0 assert 'tablib' in c.out assert 'tablib' not in p.pipfile['packages'] @@ -442,7 +468,8 @@ setup( @pytest.mark.vcs @pytest.mark.install - def test_basic_vcs_install(self, pypi): + @needs_internet + def test_basic_vcs_install(self, pip_src_dir, pypi): with PipenvInstance(pypi=pypi) as p: c = p.pipenv('install git+https://github.com/requests/requests.git#egg=requests') assert c.return_code == 0 @@ -457,6 +484,7 @@ setup( @pytest.mark.e @pytest.mark.vcs @pytest.mark.install + @needs_internet def test_editable_vcs_install(self, pip_src_dir, pypi): with PipenvInstance(pypi=pypi) as p: c = p.pipenv('install -e git+https://github.com/requests/requests.git#egg=requests') @@ -546,8 +574,9 @@ tpfd = "*" @pytest.mark.install @pytest.mark.resolver @pytest.mark.backup_resolver - def test_backup_resolver(self, pypi): - with PipenvInstance(pypi=pypi) as p: + @needs_internet + def test_backup_resolver(self): + with PipenvInstance() as p: with open(p.pipfile_path, 'w') as f: contents = """ [packages] @@ -561,8 +590,6 @@ tpfd = "*" @pytest.mark.run @pytest.mark.markers - @pytest.mark.install - @pytest.mark.failed def test_package_environment_markers(self, pypi): with PipenvInstance(pypi=pypi) as p: @@ -605,10 +632,10 @@ requests = {version = "*", os_name = "== 'splashwear'"} @pytest.mark.markers @pytest.mark.install - def test_top_level_overrides_environment_markers(self): + def test_top_level_overrides_environment_markers(self, pypi): """Top-level environment markers should take precedence. """ - with PipenvInstance() as p: + with PipenvInstance(pypi=pypi) as p: with open(p.pipfile_path, 'w') as f: contents = """ [packages] @@ -624,7 +651,7 @@ funcsigs = {version = "*", os_name = "== 'splashwear'"} @pytest.mark.markers @pytest.mark.install - def test_global_overrides_environment_markers(self): + def test_global_overrides_environment_markers(self, pypi): """Empty (unconditional) dependency should take precedence. If a dependency is specified without environment markers, it should @@ -632,7 +659,7 @@ funcsigs = {version = "*", os_name = "== 'splashwear'"} APScheduler requires funcsigs only on Python 2, but since funcsigs is also specified as an unconditional dep, its markers should be empty. """ - with PipenvInstance() as p: + with PipenvInstance(pypi=pypi) as p: with open(p.pipfile_path, 'w') as f: contents = """ [packages] @@ -649,8 +676,11 @@ funcsigs = "*" @pytest.mark.install @pytest.mark.vcs @pytest.mark.tablib - def test_install_editable_git_tag(self, pip_src_dir, pypi): - with PipenvInstance(pypi=pypi) as p: + @needs_internet + def test_install_editable_git_tag(self, pip_src_dir): + # This uses the real PyPI since we need Internet to access the Git + # dependency anyway. + with PipenvInstance() as p: c = p.pipenv('install -e git+https://github.com/kennethreitz/tablib.git@v0.12.1#egg=tablib') assert c.return_code == 0 assert 'tablib' in p.pipfile['packages'] @@ -923,9 +953,11 @@ flask = "==0.12.2" @pytest.mark.lock @pytest.mark.complex - def test_complex_lock_with_vcs_deps(self, pip_src_dir, pypi): - - with PipenvInstance(pypi=pypi) as p: + @needs_internet + def test_complex_lock_with_vcs_deps(self, pip_src_dir): + # This uses the real PyPI since we need Internet to access the Git + # dependency anyway. + with PipenvInstance() as p: with open(p.pipfile_path, 'w') as f: contents = """ [packages] @@ -955,14 +987,34 @@ requests = {git = "https://github.com/requests/requests", egg = "requests"} @pytest.mark.lock @pytest.mark.requirements - @pytest.mark.complex - @pytest.mark.maya - def test_complex_deps_lock_and_install_properly(self, pypi): + def test_lock_with_prereleases(self, pypi): with PipenvInstance(pypi=pypi) as p: with open(p.pipfile_path, 'w') as f: contents = """ [packages] +sqlalchemy = "==1.2.0b3" + +[pipenv] +allow_prereleases = true + """.strip() + f.write(contents) + + c = p.pipenv('lock') + assert c.return_code == 0 + assert p.lockfile['default']['sqlalchemy']['version'] == '==1.2.0b3' + + @pytest.mark.lock + @pytest.mark.requirements + @pytest.mark.complex + @pytest.mark.maya + @needs_internet + def test_complex_deps_lock_and_install_properly(self): + # This uses the real PyPI because Maya has too many dependencies... + with PipenvInstance() as p: + with open(p.pipfile_path, 'w') as f: + contents = """ +[packages] maya = "*" """.strip() f.write(contents) @@ -975,12 +1027,13 @@ maya = "*" @pytest.mark.extras @pytest.mark.lock - @pytest.mark.requirements @pytest.mark.complex - def test_complex_lock_deep_extras(self, pypi): + @needs_internet + def test_complex_lock_deep_extras(self): # records[pandas] requires tablib[pandas] which requires pandas. + # This uses the real PyPI; Pandas has too many requirements to mock. - with PipenvInstance(pypi=pypi) as p: + with PipenvInstance() as p: with open(p.pipfile_path, 'w') as f: contents = """ [packages] @@ -988,12 +1041,12 @@ records = {extras = ["pandas"], version = "==0.5.2"} """.strip() f.write(contents) + c = p.pipenv('install') + assert c.return_code == 0 c = p.pipenv('lock') assert c.return_code == 0 assert 'tablib' in p.lockfile['default'] assert 'pandas' in p.lockfile['default'] - c = p.pipenv('install') - assert c.return_code == 0 @pytest.mark.lock @pytest.mark.deploy @@ -1009,9 +1062,10 @@ flask = "==0.12.2" pytest = "==3.1.1" """.strip() f.write(contents) - - p.pipenv('lock') - + c = p.pipenv('install') + assert c.return_code == 0 + c = p.pipenv('lock') + assert c.return_code == 0 with open(p.pipfile_path, 'w') as f: contents = """ [packages] @@ -1025,6 +1079,7 @@ requests = "==2.14.0" @pytest.mark.install @pytest.mark.files @pytest.mark.urls + @needs_internet def test_urls_work(self, pypi): with PipenvInstance(pypi=pypi) as p: @@ -1077,6 +1132,7 @@ requests = "==2.14.0" shutil.copy(source_path, os.path.join(p.path, file_name)) c = p.pipenv('install {}'.format(file_name)) + assert c.return_code == 0 key = [k for k in p.pipfile['packages'].keys()][0] dep = p.pipfile['packages'][key] @@ -1091,6 +1147,7 @@ requests = "==2.14.0" @pytest.mark.install @pytest.mark.files @pytest.mark.urls + @needs_internet def test_install_remote_requirements(self, pypi): with PipenvInstance(pypi=pypi) as p: # using a github hosted requirements.txt file @@ -1158,10 +1215,8 @@ requests = "==2.14.0" url = 'https://${PYPI_USERNAME}:${PYPI_PASSWORD}@pypi.python.org/simple' verify_ssl = true name = 'pypi' - [requires] python_version = '2.7' - [packages] flask = "==0.12.2" """)