Merge branch 'master' into 1910-try_harder_to_cleanup_virtualenv

This commit is contained in:
Kyle Altendorf
2018-04-07 17:50:07 -07:00
committed by GitHub
53 changed files with 629 additions and 336 deletions
+6 -1
View File
@@ -9,6 +9,7 @@ env:
- TEST_SUITE='dotvenv or check or unused or requirements'
- TEST_SUITE='complex'
- TEST_SUITE='markers or run or project or utils'
- TEST_SUITE='not (dotvenv or check or unused or requirements or complex or markers or run or project or utils or install)'
# command to install dependencies
install:
@@ -18,6 +19,7 @@ install:
# command to run the dependencies
script:
- 'if [[ -n "$RUN_INTEGRATION_TESTS" ]]; then rm -fr ~/.cache/pip; fi'
- "pipenv run bash ./run-tests.sh"
jobs:
@@ -25,7 +27,10 @@ jobs:
- stage: integration
env: TEST_SUITE='cli'
- stage: takes-forever
env: TEST_SUITE='install'
env:
- TEST_SUITE='install'
- PYTEST_ADDOPTS='--cache-clear'
- RUN_INTEGRATION_TESTS=1
stages:
- integration
+3 -6
View File
@@ -1,5 +1,4 @@
[dev-packages]
pipenv = {path = ".", editable = true}
"flake8" = ">=3.3.0,<4"
pytest = "*"
@@ -11,16 +10,14 @@ pytest-xdist = "*"
click = "*"
pytest-pypy = {path = "./tests/pytest-pypi", editable = true}
pytest-tap = "*"
stdeb = {version="*", sys_platform="== 'linux'"}
flaky = "*"
stdeb = {version="*", markers="sys_platform == 'linux'"}
white = {version="*", markers="python_version >= '3.6'"}
[packages]
[scripts]
tests = "bash ./run-tests.sh"
[pipenv]
allow_prereleases = true
allow_prereleases = true
Generated
+126 -10
View File
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "8acf60476c4efca4f1be83dc747f18d4a663c3479e664e9e6865469f7ab9b958"
"sha256": "5941b36256503b9729e927f3249a366748da5c353b59c1e32ca7fd919b31a4d6"
},
"pipfile-spec": 6,
"requires": {},
@@ -29,6 +29,13 @@
],
"version": "==1.4"
},
"asn1crypto": {
"hashes": [
"sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87",
"sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49"
],
"version": "==0.24.0"
},
"attrs": {
"hashes": [
"sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9",
@@ -50,6 +57,39 @@
],
"version": "==2018.1.18"
},
"cffi": {
"hashes": [
"sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743",
"sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef",
"sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50",
"sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f",
"sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93",
"sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257",
"sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3",
"sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc",
"sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04",
"sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6",
"sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359",
"sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596",
"sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b",
"sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd",
"sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95",
"sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e",
"sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6",
"sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca",
"sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31",
"sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1",
"sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085",
"sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801",
"sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4",
"sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184",
"sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917",
"sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f",
"sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb"
],
"markers": "platform_python_implementation != 'pypy'",
"version": "==1.11.5"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
@@ -73,6 +113,35 @@
"markers": "sys_platform == 'win32'",
"version": "==0.3.9"
},
"configparser": {
"hashes": [
"sha256:5308b47021bc2340965c371f0f058cc6971a04502638d4244225c49d80db273a"
],
"markers": "python_version < '3.2'",
"version": "==3.5.0"
},
"cryptography": {
"hashes": [
"sha256:3f3b65d5a16e6b52fba63dc860b62ca9832f51f1a2ae5083c78b6840275f12dd",
"sha256:551a3abfe0c8c6833df4192a63371aa2ff43afd8f570ed345d31f251d78e7e04",
"sha256:5cb990056b7cadcca26813311187ad751ea644712022a3976443691168781b6f",
"sha256:60bda7f12ecb828358be53095fc9c6edda7de8f1ef571f96c00b2363643fa3cd",
"sha256:6fef51ec447fe9f8351894024e94736862900d3a9aa2961528e602eb65c92bdb",
"sha256:77d0ad229d47a6e0272d00f6bf8ac06ce14715a9fd02c9a97f5a2869aab3ccb2",
"sha256:808fe471b1a6b777f026f7dc7bd9a4959da4bfab64972f2bbe91e22527c1c037",
"sha256:9b62fb4d18529c84b961efd9187fecbb48e89aa1a0f9f4161c61b7fc42a101bd",
"sha256:9e5bed45ec6b4f828866ac6a6bedf08388ffcfa68abe9e94b34bb40977aba531",
"sha256:9fc295bf69130a342e7a19a39d7bbeb15c0bcaabc7382ec33ef3b2b7d18d2f63",
"sha256:abd070b5849ed64e6d349199bef955ee0ad99aefbad792f0c587f8effa681a5e",
"sha256:ba6a774749b6e510cffc2fb98535f717e0e5fd91c7c99a61d223293df79ab351",
"sha256:c332118647f084c983c6a3e1dba0f3bcb051f69d12baccac68db8d62d177eb8a",
"sha256:d6f46e862ee36df81e6342c2177ba84e70f722d9dc9c6c394f9f1f434c4a5563",
"sha256:db6013746f73bf8edd9c3d1d3f94db635b9422f503db3fc5ef105233d4c011ab",
"sha256:f57008eaff597c69cf692c3518f6d4800f0309253bb138b526a37fe9ef0c7471",
"sha256:f6c821ac253c19f2ad4c8691633ae1d1a17f120d5b01ea1d256d7b602bc59887"
],
"version": "==2.2.2"
},
"docutils": {
"hashes": [
"sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
@@ -81,6 +150,16 @@
],
"version": "==0.14"
},
"enum34": {
"hashes": [
"sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850",
"sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a",
"sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79",
"sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"
],
"markers": "python_version < '3'",
"version": "==1.1.6"
},
"execnet": {
"hashes": [
"sha256:a7a84d5fa07a089186a329528f127c9d73b9de57f1a1131b82bb5320ee651f6a",
@@ -96,6 +175,14 @@
"index": "pypi",
"version": "==3.5.0"
},
"flaky": {
"hashes": [
"sha256:4ad7880aef8c35a34ddb394d4fa33047765bca1e3d67d182bf6eba9c8eabf3a2",
"sha256:d0533f473a46b916e6db6e84e20b06d8a70656600a0c14e819b0760b63f70226"
],
"index": "pypi",
"version": "==3.4.0"
},
"flask": {
"hashes": [
"sha256:0749df235e3ff61ac108f69ac178c9770caeaccad2509cb762ce1f65570a8856",
@@ -103,6 +190,14 @@
],
"version": "==0.12.2"
},
"funcsigs": {
"hashes": [
"sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca",
"sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"
],
"markers": "python_version < '3.0'",
"version": "==1.0.2"
},
"idna": {
"hashes": [
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f",
@@ -117,6 +212,13 @@
],
"version": "==1.0.0"
},
"ipaddress": {
"hashes": [
"sha256:200d8686011d470b5e4de207d803445deee427455cd0cb7c982b68cf82524f81"
],
"markers": "python_version < '3'",
"version": "==1.0.19"
},
"itsdangerous": {
"hashes": [
"sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519"
@@ -159,19 +261,20 @@
],
"version": "==4.1.0"
},
"pathlib": {
"pathlib2": {
"hashes": [
"sha256:6940718dfc3eff4258203ad5021090933e5c04707d5ca8cc9e73c94a7894ea9f"
"sha256:24e0b33e1333b55e73c9d1e9a8342417d519f7789a9d3b440f4acd00ea45157e",
"sha256:deb3a960c1d55868dfbcac98432358b92ba89d95029cddd4040db1f27405055c"
],
"markers": "python_version < '3.4'",
"version": "==1.0.1"
"version": "==2.1.0"
},
"pbr": {
"hashes": [
"sha256:05f61c71aaefc02d8e37c0a3eeb9815ff526ea28b3b76324769e6158d7f95be1",
"sha256:60c25b7dfd054ef9bb0ae327af949dd4676aa09ac3a9471cdc871d8a9213f9ac"
"sha256:56b7a8ba7d64bf6135a9dfefb85a80d95924b3fde5ed6343a1a1d464a040dae3",
"sha256:de75cf1d510542c746beeff66b52241eb12c8f95f2ef846ee50ed5d72392caa4"
],
"version": "==3.1.1"
"version": "==4.0.1"
},
"pipenv": {
"editable": true,
@@ -204,6 +307,12 @@
],
"version": "==2.3.1"
},
"pycparser": {
"hashes": [
"sha256:99a8ca03e29851d96616ad0404b4aad7d9ee16f25c9f9708a11faf2810f7b226"
],
"version": "==2.18"
},
"pyflakes": {
"hashes": [
"sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f",
@@ -218,6 +327,13 @@
],
"version": "==2.2.0"
},
"pyopenssl": {
"hashes": [
"sha256:07a2de1a54de07448732a81e38a55df7da109b2f47f599f8bb35b0cbec69d4bd",
"sha256:2c10cfba46a52c0b0950118981d61e72c1e5b1aac451ca1bc77de1a679456773"
],
"version": "==17.5.0"
},
"pytest": {
"hashes": [
"sha256:6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c",
@@ -328,10 +444,10 @@
},
"tqdm": {
"hashes": [
"sha256:05e991ecb0f874046ddcb374396a626afd046fb4d31f73633ea752b844458a7a",
"sha256:2aea9f81fdf127048667e0ba22f5fc10ebc879fb838dc52dcf055242037ec1f7"
"sha256:4f2eb1d14804caf7095500fe11da0e481a47af912e7b57c93f886ac3c40a49dd",
"sha256:91ac47ec2ba6bb92b7ba37706f4dea37019ddd784b22fd279a4b12d93327191d"
],
"version": "==4.19.8"
"version": "==4.20.0"
},
"twine": {
"hashes": [
+89
View File
@@ -0,0 +1,89 @@
build: off
version: 1.0.{build}
skip_branch_with_pr: true
init:
- git config --global core.sharedRepository true
- git config --global core.longpaths true
- git config --global core.autocrlf input
environment:
PYPI_VENDOR_DIR: '.\tests\pypi\'
GIT_ASK_YESNO: 'false'
SHELL: 'windows'
PYTHON_ARCH: '64'
PYTHONIOENCODING: 'utf-8'
matrix:
- PYTHON: 'C:\Python27-x64'
PYTHON_VERSION: '2.7.x'
TEST_SUITE: 'cli'
- PYTHON: 'C:\Python27-x64'
PYTHON_VERSION: '2.7.x'
TEST_SUITE: 'dotvenv or check or unused or requirements'
- PYTHON: 'C:\Python27-x64'
PYTHON_VERSION: '2.7.x'
TEST_SUITE: 'complex'
- PYTHON: 'C:\Python27-x64'
PYTHON_VERSION: '2.7.x'
TEST_SUITE: 'markers or run or project or utils'
- PYTHON: 'C:\Python27-x64'
PYTHON_VERSION: '2.7.x'
TEST_SUITE: 'not (cli or dotvenv or check or unused or requirements or complex or markers or run or project or utils)'
- PYTHON: 'C:\Python27-x64'
PYTHON_VERSION: '2.7.x'
TEST_SUITE: 'install'
PYTEST_ADDOPTS: '--cache-clear'
RUN_INTEGRATION_TESTS: 'True'
- PYTHON: 'C:\Python36-x64'
PYTHON_VERSION: '3.6.x'
TEST_SUITE: 'cli'
- PYTHON: 'C:\Python36-x64'
PYTHON_VERSION: '3.6.x'
TEST_SUITE: 'dotvenv or check or unused or requirements'
- PYTHON: 'C:\Python36-x64'
PYTHON_VERSION: '3.6.x'
TEST_SUITE: 'complex'
- PYTHON: 'C:\Python36-x64'
PYTHON_VERSION: '3.6.x'
TEST_SUITE: 'markers or run or project or utils'
- PYTHON: 'C:\Python36-x64'
PYTHON_VERSION: '3.6.x'
TEST_SUITE: 'not (cli or dotvenv or check or unused or requirements or complex or markers or run or project or utils)'
- PYTHON: 'C:\Python36-x64'
PYTHON_VERSION: '3.6.x'
TEST_SUITE: 'install'
PYTEST_ADDOPTS: '--cache-clear'
RUN_INTEGRATION_TESTS: 'True'
install:
- 'set PATH=%PYTHON%;%PYTHON%\Scripts;%PATH%'
- '%PYTHON%\python.exe -m pip install --upgrade pip'
- '%PYTHON%\python.exe -m pip install -e .'
- '%PYTHON%\python.exe -m pipenv run pip install -e .'
- '%PYTHON%\python.exe -m pipenv install --dev'
- '%PYTHON%\python.exe -m pipenv --venv'
- '%PYTHON%\python.exe -m pipenv --py'
- '%PYTHON%\python.exe -m pipenv run python --version'
cache:
- '%LocalAppData%\pip\cache'
test_script:
- 'if "%RUN_INTEGRATION_TESTS%" == "True" (rmdir /s /q %LocalAppData%\pip\cache)'
- '%PYTHON%\python.exe -m pipenv run pytest -v -n auto -m "%TEST_SUITE%" tests'
+14 -14
View File
@@ -11,7 +11,7 @@ from click_didyoumean import DYMCommandCollection
from .__version__ import __version__
from .import environments
from . import environments
from .environments import *
# Enable shell completion.
@@ -23,7 +23,7 @@ class PipenvGroup(click.Group):
"""Custom Group class provides formatted main help"""
def get_help_option(self, ctx):
from .import core
from . import core
"""Override for showing formatted main help via --help and -h options"""
help_options = self.get_help_option_names(ctx)
@@ -148,7 +148,7 @@ def cli(
)
sys.exit(1)
sys.exit(0)
from .import core
from . import core
if man:
if core.system_which('man'):
path = os.sep.join([os.path.dirname(__file__), 'pipenv.1'])
@@ -344,7 +344,7 @@ def install(
keep_outdated=False,
selective_upgrade=False,
):
from .import core
from . import core
core.do_install(
package_name=package_name,
@@ -426,7 +426,7 @@ def uninstall(
verbose=False,
keep_outdated=False,
):
from .import core
from . import core
core.do_uninstall(
package_name=package_name,
@@ -499,7 +499,7 @@ def lock(
pre=False,
keep_outdated=False,
):
from .import core
from . import core
# Ensure that virtualenv is available.
core.ensure_project(three=three, python=python)
@@ -542,7 +542,7 @@ def lock(
def shell(
three=None, python=False, fancy=False, shell_args=None, anyway=False
):
from .import core
from . import core
# Prevent user from activating nested environments.
if 'PIPENV_ACTIVE' in os.environ:
@@ -594,7 +594,7 @@ def shell(
help="Specify which version of Python virtualenv should use.",
)
def run(command, args, three=None, python=False):
from .import core
from . import core
core.do_run(command=command, args=args, three=three, python=python)
@@ -633,7 +633,7 @@ def check(
style=False,
args=None,
):
from .import core
from . import core
core.do_check(
three=three, python=python, system=system, unused=unused, args=args
@@ -719,7 +719,7 @@ def update(
outdated=False,
more_packages=None,
):
from .import core
from . import core
core.ensure_project(three=three, python=python, warn=True)
if not outdated:
@@ -797,7 +797,7 @@ def update(
'--reverse', is_flag=True, default=False, help="Reversed dependency graph."
)
def graph(bare=False, json=False, reverse=False):
from .import core
from . import core
core.do_graph(bare=bare, json=json, reverse=reverse)
@@ -817,7 +817,7 @@ def graph(bare=False, json=False, reverse=False):
)
@click.argument('module', nargs=1)
def run_open(module, three=None, python=None):
from .import core
from . import core
# Ensure that virtualenv is available.
core.ensure_project(three=three, python=python, validate=False)
@@ -895,7 +895,7 @@ def sync(
package_name=None,
sequential=False,
):
from .import core
from . import core
core.do_sync(
ctx=ctx,
@@ -952,7 +952,7 @@ def clean(
user=False,
verbose=False,
):
from .import core
from . import core
core.do_clean(
ctx=ctx, three=three, python=python, dry_run=dry_run, verbose=verbose
+65
View File
@@ -0,0 +1,65 @@
import re
import shlex
import six
class ScriptEmptyError(ValueError):
pass
class Script(object):
"""Parse a script line (in Pipfile's [scripts] section).
This always works in POSIX mode, even on Windows.
"""
def __init__(self, command, args=None):
self._parts = [command]
if args:
self._parts.extend(args)
@classmethod
def parse(cls, value):
if isinstance(value, six.string_types):
value = shlex.split(value)
if not value:
raise ScriptEmptyError(value)
return cls(value[0], value[1:])
def __repr__(self):
return 'Script({0!r})'.format(self._parts)
@property
def command(self):
return self._parts[0]
@property
def args(self):
return self._parts[1:]
def extend(self, extra_args):
self._parts.extend(extra_args)
def cmdify(self):
"""Encode into a cmd-executable string.
This re-implements CreateProcess's quoting logic to turn a list of
arguments into one single string for the shell to interpret.
* All double quotes are escaped with a backslash.
* Existing backslashes before a quote are doubled, so they are all
escaped properly.
* Backslashes elsewhere are left as-is; cmd will interpret them
literally.
The result is then quoted into a pair of double quotes to be grouped.
The intended use of this function is to pre-process an argument list
before passing it into ``subprocess.Popen(..., shell=True)``.
See also: https://docs.python.org/3/library/subprocess.html#converting-argument-sequence
"""
return ' '.join(
'"{0}"'.format(re.sub(r'(\\*)"', r'\1\1\\"', arg))
for arg in self._parts
)
+18 -34
View File
@@ -4,7 +4,6 @@ import logging
import os
import sys
import shutil
import shlex
import signal
import time
import tempfile
@@ -25,6 +24,7 @@ from blindspin import spinner
from requests.packages import urllib3
from requests.packages.urllib3.exceptions import InsecureRequestWarning
from .cmdparse import ScriptEmptyError
from .project import Project
from .utils import (
convert_deps_from_pip,
@@ -2203,46 +2203,22 @@ def inline_activate_virtualenv():
)
def do_run_nt(command, args):
"""Run command by appending space-joined args to it!"""
def do_run_nt(script):
import subprocess
command = project.scripts.get(command, command)
# if you've passed something with crazy quoting...
# ...just don't. (or put it in a script!)
p = subprocess.Popen(
' '.join([command] + list(args)), shell=True, universal_newlines=True
)
p = subprocess.Popen(script.cmdify(), shell=True, universal_newlines=True)
p.communicate()
sys.exit(p.returncode)
def _get_command_posix(project, command, args):
"""Fully bake command into executable and args, based upon project"""
# Script was found…
if command in project.scripts:
command = project.scripts[command]
parsed_command = shlex.split(command)
executable = parsed_command[0]
# prepend arguments
args = list(parsed_command[1:]) + list(args)
return executable, args
def do_run_posix(command, args):
"""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.
"""
executable, args = _get_command_posix(project, command, args)
command_path = system_which(executable)
def do_run_posix(script, command):
command_path = system_which(script.command)
if not command_path:
if command in project.scripts:
if project.has_script(command):
click.echo(
'{0}: the command {1} (from {2}) could not be found within {3}.'
''.format(
crayons.red('Error', bold=True),
crayons.red(executable),
crayons.red(script.command),
crayons.normal(command, bold=True),
crayons.normal('PATH', bold=True),
),
@@ -2260,19 +2236,27 @@ def do_run_posix(command, args):
err=True,
)
sys.exit(1)
os.execl(command_path, command_path, *args)
os.execl(command_path, command_path, *script.args)
def do_run(command, args, three=None, python=False):
"""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)
load_dot_env()
# Activate virtualenv under the current interpreter's environment
inline_activate_virtualenv()
try:
script = project.build_script(command, args)
except ScriptEmptyError:
click.echo("Can't run script {0!r}—it's empty?", err=True)
if os.name == 'nt':
do_run_nt(command, args)
do_run_nt(script)
else:
do_run_posix(command, args)
do_run_posix(script, command=command)
def do_check(three=None, python=False, system=False, unused=False, args=None):
+4 -1
View File
@@ -6,7 +6,10 @@ from contextlib import contextmanager
from subprocess import check_call, Popen, PIPE
from collections import namedtuple
from functools import partial, wraps
from pathlib import Path
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
from tempfile import NamedTemporaryFile as _ntf
try:
from shutil import which
+4 -1
View File
@@ -8,7 +8,10 @@ import random
import textwrap
from functools import partial
from subprocess import CalledProcessError
from pathlib import Path
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
try:
from shutil import get_terminal_size
+15 -3
View File
@@ -16,6 +16,7 @@ import pipfile.api
import toml
from pip9 import ConfigOptionParser
from .cmdparse import Script
from .utils import (
mkdir_p,
convert_deps_from_pip,
@@ -388,9 +389,20 @@ class Project(object):
"""A dictionary of the settings added to the Pipfile."""
return self.parsed_pipfile.get('pipenv', {})
@property
def scripts(self):
return dict(self.parsed_pipfile.get('scripts', {}))
def has_script(self, name):
try:
return name in self.parsed_pipfile['scripts']
except KeyError:
return False
def build_script(self, name, extra_args=None):
try:
script = Script.parse(self.parsed_pipfile['scripts'][name])
except KeyError:
script = Script(name)
if extra_args:
script.extend(extra_args)
return script
def update_settings(self, d):
settings = self.settings
+14 -8
View File
@@ -60,14 +60,13 @@ if six.PY2:
specifiers = [k for k in lookup.keys()]
# List of version control systems we support.
VCS_LIST = ('git', 'svn', 'hg', 'bzr')
SCHEME_LIST = ('http://', 'https://', 'ftp://', 'file://')
SCHEME_LIST = ('http://', 'https://', 'ftp://', 'ftps://', 'file://')
requests = requests.Session()
def get_requirement(dep):
from pip9.req.req_install import _strip_extras
import requirements
"""Pre-clean requirement strings passed to the requirements parser.
Ensures that we can accept both local and relative paths, file and VCS URIs,
@@ -359,7 +358,7 @@ def actually_resolve_reps(
def venv_resolve_deps(
deps, which, project, pre=False, verbose=False, clear=False
):
from .import resolver
from . import resolver
import json
resolver = escape_grouped_arguments(resolver.__file__.rstrip('co'))
@@ -560,7 +559,7 @@ def convert_deps_from_pip(dep):
# Extras: e.g. #egg=requests[security]
if req.extras:
dependency[req.name].update({'extras': req.extras})
elif req.extras or req.specs:
elif req.extras or req.specs or hasattr(req, 'markers'):
specs = None
# Comparison operators: e.g. Django>1.10
if req.specs:
@@ -572,6 +571,10 @@ def convert_deps_from_pip(dep):
dependency[req.name] = extras
if specs:
dependency[req.name].update({'version': specs})
if hasattr(req, 'markers'):
if isinstance(dependency[req.name], six.string_types):
dependency[req.name] = {'version': specs}
dependency[req.name].update({'markers': req.markers})
# Bare dependencies: e.g. requests
else:
dependency[dep] = '*'
@@ -679,8 +682,7 @@ def convert_deps_to_pip(deps, project=None, r=True, include_index=False):
dep = ''
s = '{0}{1}{2}{3}{4} {5}'.format(
dep, extra, version, specs, hash, index
).strip(
)
).strip()
dependencies.append(s)
if not r:
return dependencies
@@ -754,7 +756,7 @@ def is_editable(pipfile_entry):
def is_vcs(pipfile_entry):
import requirements
from pipenv.vendor import requirements
"""Determine if dictionary entry from Pipfile is for a vcs dependency."""
if hasattr(pipfile_entry, 'keys'):
@@ -1019,6 +1021,10 @@ def find_windows_executable(bin_path, exe_name):
return find_executable(exe_name)
def path_to_url(path):
return Path(normalize_drive(os.path.abspath(path))).as_uri()
def get_converted_relative_path(path, relative_to=os.curdir):
"""Given a vague relative path, return the path relative to the given location"""
return os.path.join('.', os.path.relpath(path, start=relative_to))
@@ -1212,7 +1218,7 @@ class TemporaryDirectory(object):
import uuid
name = uuid.uuid4().hex
dir_name = os.path.sep.join([os.environ['RAM_DISK'].strip(), name])
dir_name = os.path.join(os.environ['RAM_DISK'].strip(), name)
os.mkdir(dir_name)
self.name = dir_name
else:
+2 -1
View File
@@ -2,4 +2,5 @@ from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
from . import shutil_get_terminal_size
from . import weakref
from . import weakref
from . import shlex
+9
View File
@@ -0,0 +1,9 @@
"""
Partial backport of python 3's shlex module.
Include's only `shlex.quote()` for backwards compatible functionality.
"""
__all__ = ['quote']
from .shlex import quote
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import re
import sys
__all__ = ['quote']
if sys.version_info >= (3, 1):
_find_unsafe = re.compile(r'[^\w@%+=:,./-]', re.ASCII).search
else:
_find_unsafe = re.compile(r'[^\w@%+=:,./-]').search
def quote(s):
"""Return a shell-escaped version of the string *s*."""
if not s:
return "''"
if _find_unsafe(s) is None:
return s
# use single quotes, and put single quotes into double quotes
# the string $'b is then quoted as '$'"'"'b'
return "'" + s.replace("'", "'\"'\"'") + "'"
+105 -208
View File
@@ -1,7 +1,3 @@
# Copyright (c) 2014-2017 Matthias C. M. Troffaes
# Copyright (c) 2012-2014 Antoine Pitrou and contributors
# Distributed under the terms of the MIT License.
import ctypes
import fnmatch
import functools
@@ -13,7 +9,8 @@ import re
import six
import sys
from collections import Sequence
from errno import EINVAL, ENOENT, ENOTDIR, EEXIST, EPERM, EACCES
from contextlib import contextmanager
from errno import EINVAL, ENOENT, ENOTDIR, EEXIST
from operator import attrgetter
from stat import (
S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO)
@@ -27,22 +24,23 @@ try:
intern = intern
except NameError:
intern = sys.intern
try:
basestring = basestring
except NameError:
basestring = str
supports_symlinks = True
if os.name == 'nt':
try:
import nt
except ImportError:
nt = None
else:
if sys.getwindowsversion()[:2] >= (6, 0) and sys.version_info >= (3, 2):
from nt import _getfinalpathname
else:
supports_symlinks = False
_getfinalpathname = None
else:
nt = None
try:
from os import scandir as os_scandir
except ImportError:
from scandir import scandir as os_scandir
__all__ = [
"PurePath", "PurePosixPath", "PureWindowsPath",
@@ -61,15 +59,12 @@ def _py2_fsencode(parts):
else part for part in parts]
def _try_except_fileexistserror(try_func, except_func, else_func=None):
def _try_except_fileexistserror(try_func, except_func):
if sys.version_info >= (3, 3):
try:
try_func()
except FileExistsError as exc:
except_func(exc)
else:
if else_func is not None:
else_func()
else:
try:
try_func()
@@ -78,45 +73,6 @@ def _try_except_fileexistserror(try_func, except_func, else_func=None):
raise
else:
except_func(exc)
else:
if else_func is not None:
else_func()
def _try_except_filenotfounderror(try_func, except_func):
if sys.version_info >= (3, 3):
try:
try_func()
except FileNotFoundError as exc:
except_func(exc)
else:
try:
try_func()
except EnvironmentError as exc:
if exc.errno != ENOENT:
raise
else:
except_func(exc)
def _try_except_permissionerror_iter(try_iter, except_iter):
if sys.version_info >= (3, 3):
try:
for x in try_iter():
yield x
except PermissionError as exc:
for x in except_iter(exc):
yield x
else:
try:
for x in try_iter():
yield x
except EnvironmentError as exc:
if exc.errno not in (EPERM, EACCES):
raise
else:
for x in except_iter(exc):
yield x
def _win32_get_unique_path_id(path):
@@ -264,7 +220,10 @@ class _WindowsFlavour(_Flavour):
is_supported = (os.name == 'nt')
drive_letters = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
drive_letters = (
set(chr(x) for x in range(ord('a'), ord('z') + 1)) |
set(chr(x) for x in range(ord('A'), ord('Z') + 1))
)
ext_namespace_prefix = '\\\\?\\'
reserved_names = (
@@ -324,28 +283,12 @@ class _WindowsFlavour(_Flavour):
def casefold_parts(self, parts):
return [p.lower() for p in parts]
def resolve(self, path, strict=False):
def resolve(self, path):
s = str(path)
if not s:
return os.getcwd()
previous_s = None
if _getfinalpathname is not None:
if strict:
return self._ext_to_normal(_getfinalpathname(s))
else:
# End of the path after the first one not found
tail_parts = []
while True:
try:
s = self._ext_to_normal(_getfinalpathname(s))
except FileNotFoundError:
previous_s = s
s, tail = os.path.split(s)
tail_parts.append(tail)
if previous_s == s:
return path
else:
return os.path.join(s, *reversed(tail_parts))
return self._ext_to_normal(_getfinalpathname(s))
# Means fallback on absolute
return None
@@ -450,7 +393,7 @@ class _PosixFlavour(_Flavour):
def casefold_parts(self, parts):
return parts
def resolve(self, path, strict=False):
def resolve(self, path):
sep = self.sep
accessor = path._accessor
seen = {}
@@ -481,10 +424,9 @@ class _PosixFlavour(_Flavour):
try:
target = accessor.readlink(newpath)
except OSError as e:
if e.errno != EINVAL and strict:
if e.errno != EINVAL:
raise
# Not a symlink, or non-strict mode. We just leave the path
# untouched.
# Not a symlink
path = newpath
else:
seen[newpath] = None # not resolved symlink
@@ -521,7 +463,6 @@ class _PosixFlavour(_Flavour):
raise RuntimeError("Can't determine home directory "
"for %r" % username)
_windows_flavour = _WindowsFlavour()
_posix_flavour = _PosixFlavour()
@@ -554,8 +495,6 @@ class _NormalAccessor(_Accessor):
listdir = _wrap_strfunc(os.listdir)
scandir = _wrap_strfunc(os_scandir)
chmod = _wrap_strfunc(os.chmod)
if hasattr(os, "lchmod"):
@@ -602,6 +541,27 @@ _normal_accessor = _NormalAccessor()
# Globbing helpers
#
@contextmanager
def _cached(func):
try:
func.__cached__
yield func
except AttributeError:
cache = {}
def wrapper(*args):
try:
return cache[args]
except KeyError:
value = cache[args] = func(*args)
return value
wrapper.__cached__ = True
try:
yield wrapper
finally:
cache.clear()
def _make_selector(pattern_parts):
pat = pattern_parts[0]
child_parts = pattern_parts[1:]
@@ -616,7 +576,6 @@ def _make_selector(pattern_parts):
cls = _PreciseSelector
return cls(pat, child_parts)
if hasattr(functools, "lru_cache"):
_make_selector = functools.lru_cache()(_make_selector)
@@ -630,10 +589,8 @@ class _Selector:
self.child_parts = child_parts
if child_parts:
self.successor = _make_selector(child_parts)
self.dironly = True
else:
self.successor = _TerminatingSelector()
self.dironly = False
def select_from(self, parent_path):
"""Iterate over all child paths of `parent_path` matched by this
@@ -641,15 +598,13 @@ class _Selector:
path_cls = type(parent_path)
is_dir = path_cls.is_dir
exists = path_cls.exists
scandir = parent_path._accessor.scandir
if not is_dir(parent_path):
return iter([])
return self._select_from(parent_path, is_dir, exists, scandir)
listdir = parent_path._accessor.listdir
return self._select_from(parent_path, is_dir, exists, listdir)
class _TerminatingSelector:
def _select_from(self, parent_path, is_dir, exists, scandir):
def _select_from(self, parent_path, is_dir, exists, listdir):
yield parent_path
@@ -659,20 +614,14 @@ class _PreciseSelector(_Selector):
self.name = name
_Selector.__init__(self, child_parts)
def _select_from(self, parent_path, is_dir, exists, scandir):
def try_iter():
path = parent_path._make_child_relpath(self.name)
if (is_dir if self.dironly else exists)(path):
for p in self.successor._select_from(
path, is_dir, exists, scandir):
yield p
def except_iter(exc):
def _select_from(self, parent_path, is_dir, exists, listdir):
if not is_dir(parent_path):
return
yield
for x in _try_except_permissionerror_iter(try_iter, except_iter):
yield x
path = parent_path._make_child_relpath(self.name)
if exists(path):
for p in self.successor._select_from(
path, is_dir, exists, listdir):
yield p
class _WildcardSelector(_Selector):
@@ -681,26 +630,17 @@ class _WildcardSelector(_Selector):
self.pat = re.compile(fnmatch.translate(pat))
_Selector.__init__(self, child_parts)
def _select_from(self, parent_path, is_dir, exists, scandir):
def try_iter():
cf = parent_path._flavour.casefold
entries = list(scandir(parent_path))
for entry in entries:
if not self.dironly or entry.is_dir():
name = entry.name
casefolded = cf(name)
if self.pat.match(casefolded):
path = parent_path._make_child_relpath(name)
for p in self.successor._select_from(
path, is_dir, exists, scandir):
yield p
def except_iter(exc):
def _select_from(self, parent_path, is_dir, exists, listdir):
if not is_dir(parent_path):
return
yield
for x in _try_except_permissionerror_iter(try_iter, except_iter):
yield x
cf = parent_path._flavour.casefold
for name in listdir(parent_path):
casefolded = cf(name)
if self.pat.match(casefolded):
path = parent_path._make_child_relpath(name)
for p in self.successor._select_from(
path, is_dir, exists, listdir):
yield p
class _RecursiveWildcardSelector(_Selector):
@@ -708,46 +648,31 @@ class _RecursiveWildcardSelector(_Selector):
def __init__(self, pat, child_parts):
_Selector.__init__(self, child_parts)
def _iterate_directories(self, parent_path, is_dir, scandir):
def _iterate_directories(self, parent_path, is_dir, listdir):
yield parent_path
for name in listdir(parent_path):
path = parent_path._make_child_relpath(name)
if is_dir(path):
for p in self._iterate_directories(path, is_dir, listdir):
yield p
def try_iter():
entries = list(scandir(parent_path))
for entry in entries:
if entry.is_dir() and not entry.is_symlink():
path = parent_path._make_child_relpath(entry.name)
for p in self._iterate_directories(path, is_dir, scandir):
yield p
def except_iter(exc):
def _select_from(self, parent_path, is_dir, exists, listdir):
if not is_dir(parent_path):
return
yield
for x in _try_except_permissionerror_iter(try_iter, except_iter):
yield x
def _select_from(self, parent_path, is_dir, exists, scandir):
def try_iter():
with _cached(listdir) as listdir:
yielded = set()
try:
successor_select = self.successor._select_from
for starting_point in self._iterate_directories(
parent_path, is_dir, scandir):
parent_path, is_dir, listdir):
for p in successor_select(
starting_point, is_dir, exists, scandir):
starting_point, is_dir, exists, listdir):
if p not in yielded:
yield p
yielded.add(p)
finally:
yielded.clear()
def except_iter(exc):
return
yield
for x in _try_except_permissionerror_iter(try_iter, except_iter):
yield x
#
# Public API
@@ -818,25 +743,13 @@ class PurePath(object):
for a in args:
if isinstance(a, PurePath):
parts += a._parts
elif isinstance(a, basestring):
# Force-cast str subclasses to str (issue #21127)
parts.append(str(a))
else:
if sys.version_info >= (3, 6):
a = os.fspath(a)
else:
# duck typing for older Python versions
if hasattr(a, "__fspath__"):
a = a.__fspath__()
if isinstance(a, str):
# Force-cast str subclasses to str (issue #21127)
parts.append(str(a))
# also handle unicode for PY2 (six.text_type = unicode)
elif six.PY2 and isinstance(a, six.text_type):
# cast to str using filesystem encoding
parts.append(a.encode(sys.getfilesystemencoding()))
else:
raise TypeError(
"argument should be a str object or an os.PathLike "
"object returning str, not %r"
% type(a))
raise TypeError(
"argument should be a path or str object, not %r"
% type(a))
return cls._flavour.parse_parts(parts)
@classmethod
@@ -870,7 +783,7 @@ class PurePath(object):
return cls._flavour.join(parts)
def _init(self):
# Overridden in concrete Path
# Overriden in concrete Path
pass
def _make_child(self, args):
@@ -889,9 +802,6 @@ class PurePath(object):
self._parts) or '.'
return self._str
def __fspath__(self):
return str(self)
def as_posix(self):
"""Return the string representation of the path with forward (/)
slashes."""
@@ -1072,7 +982,7 @@ class PurePath(object):
cf = self._flavour.casefold_parts
if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts):
formatted = self._format_parsed_parts(to_drv, to_root, to_parts)
raise ValueError("{0!r} does not start with {1!r}"
raise ValueError("{!r} does not start with {!r}"
.format(str(self), str(formatted)))
return self._from_parsed_parts('', root if n == 1 else '',
abs_parts[n:])
@@ -1160,12 +1070,6 @@ class PurePath(object):
return True
# Can't subclass os.PathLike from PurePath and keep the constructor
# optimizations in PurePath._parse_args().
if sys.version_info >= (3, 6):
os.PathLike.register(PurePath)
class PurePosixPath(PurePath):
_flavour = _posix_flavour
__slots__ = ()
@@ -1252,8 +1156,8 @@ class Path(PurePath):
return cls(cls()._flavour.gethomedir(None))
def samefile(self, other_path):
"""Return whether other_path is the same or not as this file
(as returned by os.path.samefile()).
"""Return whether `other_file` is the same or not as this file.
(as returned by os.path.samefile(file, other_file)).
"""
if hasattr(os.path, "samestat"):
st = self.stat()
@@ -1287,8 +1191,6 @@ class Path(PurePath):
"""Iterate over this subtree and yield all existing files (of any
kind, including directories) matching the given pattern.
"""
if not pattern:
raise ValueError("Unacceptable pattern: {0!r}".format(pattern))
pattern = self._flavour.casefold(pattern)
drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
if drv or root:
@@ -1327,7 +1229,7 @@ class Path(PurePath):
obj._init(template=self)
return obj
def resolve(self, strict=False):
def resolve(self):
"""
Make the path absolute, resolving all symlinks on the way and also
normalizing it (for example turning slashes into backslashes under
@@ -1335,7 +1237,7 @@ class Path(PurePath):
"""
if self._closed:
self._raise_closed()
s = self._flavour.resolve(self, strict=strict)
s = self._flavour.resolve(self)
if s is None:
# No symlink resolution => for consistency, raise an error if
# the path doesn't exist or is forbidden
@@ -1405,7 +1307,7 @@ class Path(PurePath):
if not isinstance(data, six.binary_type):
raise TypeError(
'data must be %s, not %s' %
(six.binary_type.__name__, data.__class__.__name__))
(six.binary_type.__class__.__name__, data.__class__.__name__))
with self.open(mode='wb') as f:
return f.write(data)
@@ -1416,7 +1318,7 @@ class Path(PurePath):
if not isinstance(data, six.text_type):
raise TypeError(
'data must be %s, not %s' %
(six.text_type.__name__, data.__class__.__name__))
(six.text_type.__class__.__name__, data.__class__.__name__))
with self.open(mode='w', encoding=encoding, errors=errors) as f:
return f.write(data)
@@ -1444,26 +1346,27 @@ class Path(PurePath):
os.close(fd)
def mkdir(self, mode=0o777, parents=False, exist_ok=False):
"""
Create a new directory at this given path.
"""
def helper(exc):
if not exist_ok or not self.is_dir():
raise exc
if self._closed:
self._raise_closed()
def _try_func():
self._accessor.mkdir(self, mode)
def _exc_func(exc):
if not parents or self.parent == self:
raise exc
self.parent.mkdir(parents=True, exist_ok=True)
self.mkdir(mode, parents=False, exist_ok=exist_ok)
try:
_try_except_filenotfounderror(_try_func, _exc_func)
except OSError:
if not exist_ok or not self.is_dir():
raise
if not parents:
_try_except_fileexistserror(
lambda: self._accessor.mkdir(self, mode),
helper)
else:
try:
_try_except_fileexistserror(
lambda: self._accessor.mkdir(self, mode),
helper)
except OSError as e:
if e.errno != ENOENT:
raise
self.parent.mkdir(parents=True)
self._accessor.mkdir(self, mode)
def chmod(self, mode):
"""
@@ -1661,9 +1564,3 @@ class PosixPath(Path, PurePosixPath):
class WindowsPath(Path, PureWindowsPath):
__slots__ = ()
def owner(self):
raise NotImplementedError("Path.owner() is unsupported on this system")
def group(self):
raise NotImplementedError("Path.group() is unsupported on this system")
+3 -3
View File
@@ -22,9 +22,9 @@ required = [
'setuptools>=36.2.1',
'virtualenv-clone>=0.2.5',
'virtualenv',
'pathlib;python_version<"3.4"',
'requests[security];python_version<"3.0"',
'ordereddict;python_version<"3.0"',
'pathlib2==2.1.0; python_version<"3.4"',
'requests[security]; python_version<"3.0"',
'ordereddict; python_version<"3.0"',
]
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+49
View File
@@ -0,0 +1,49 @@
import pytest
from pipenv.cmdparse import Script, ScriptEmptyError
@pytest.mark.run
@pytest.mark.script
def test_parse():
script = Script.parse(['python', '-c', "print('hello')"])
assert script.command == 'python'
assert script.args == ['-c', "print('hello')"], script
@pytest.mark.run
@pytest.mark.script
def test_parse_error():
with pytest.raises(ScriptEmptyError) as e:
Script.parse('')
assert str(e.value) == "[]"
@pytest.mark.run
def test_extend():
script = Script('python', ['-c', "print('hello')"])
script.extend(['--verbose'])
assert script.command == 'python'
assert script.args == ['-c', "print('hello')", "--verbose"], script
@pytest.mark.run
@pytest.mark.script
def test_cmdify():
script = Script('python', ['-c', "print('hello')"])
cmd = script.cmdify()
assert cmd == '"python" "-c" "print(\'hello\')"', script
@pytest.mark.run
@pytest.mark.script
def test_cmdify_complex():
script = Script.parse(' '.join([
'"C:\\Program Files\\Python36\\python.exe" -c',
""" "print(\'Double quote: \\\"\')" """.strip(),
]))
assert script.cmdify() == ' '.join([
'"C:\\Program Files\\Python36\\python.exe"',
'"-c"',
""" "print(\'Double quote: \\\"\')" """.strip(),
]), script
+80 -46
View File
@@ -5,7 +5,7 @@ import shutil
import json
import pytest
import warnings
from pipenv.core import activate_virtualenv, _get_command_posix
from pipenv.core import activate_virtualenv
from pipenv.utils import (
temp_environ, get_windows_path, mkdir_p, normalize_drive, TemporaryDirectory
)
@@ -15,6 +15,7 @@ from pipenv.vendor import requests
from pipenv.patched import pipfile
from pipenv.project import Project
from pipenv.vendor.six import PY2
from flaky import flaky
if PY2:
class ResourceWarning(Warning):
pass
@@ -237,8 +238,16 @@ class TestPipenv:
@pytest.mark.install
def test_install_parse_error(self, pypi):
with PipenvInstance(pypi=pypi) as p:
# Make sure unparseable packages don't wind up in the pipfile
# Escape $ for shell input
with open(p.pipfile_path, 'w') as f:
contents = """
[packages]
[dev-packages]
""".strip()
f.write(contents)
c = p.pipenv('install requests u/\\/p@r\$34b13+pkg')
assert c.return_code != 0
assert 'u/\\/p@r$34b13+pkg' not in p.pipfile['packages']
@@ -271,7 +280,9 @@ class TestPipenv:
assert 'urllib3' in p.lockfile['default']
assert 'certifi' in p.lockfile['default']
@pytest.mark.complex_lock
@pytest.mark.complex
@pytest.mark.lock
@pytest.mark.skip(reason='Does not work')
def test_complex_lock(self, pypi):
with PipenvInstance(pypi=pypi) as p:
c = p.pipenv('install apscheduler')
@@ -299,9 +310,10 @@ class TestPipenv:
@pytest.mark.dev
@pytest.mark.install
@flaky
def test_install_without_dev(self, pypi):
"""Ensure that running `pipenv install` doesn't install dev packages"""
with PipenvInstance(pypi=pypi) as p:
with PipenvInstance(pypi=pypi, chdir=True) as p:
with open(p.pipfile_path, 'w') as f:
contents = """
[packages]
@@ -324,6 +336,7 @@ records = "*"
@pytest.mark.cli
@pytest.mark.install
@flaky
def test_install_without_dev_section(self, pypi):
with PipenvInstance(pypi=pypi) as p:
with open(p.pipfile_path, 'w') as f:
@@ -419,8 +432,9 @@ tablib = "*"
@pytest.mark.extras
@pytest.mark.install
@flaky
def test_extras_install(self, pypi):
with PipenvInstance(pypi=pypi) as p:
with PipenvInstance(pypi=pypi, chdir=True) as p:
c = p.pipenv('install requests[socks]')
assert c.return_code == 0
assert 'requests' in p.pipfile['packages']
@@ -469,8 +483,9 @@ setup(
@pytest.mark.vcs
@pytest.mark.install
@needs_internet
@flaky
def test_basic_vcs_install(self, pip_src_dir, pypi):
with PipenvInstance(pypi=pypi) as p:
with PipenvInstance(pypi=pypi, chdir=True) as p:
c = p.pipenv('install git+https://github.com/requests/requests.git#egg=requests')
assert c.return_code == 0
# edge case where normal package starts with VCS name shouldn't be flagged as vcs
@@ -485,6 +500,7 @@ setup(
@pytest.mark.vcs
@pytest.mark.install
@needs_internet
@flaky
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')
@@ -500,6 +516,7 @@ setup(
@pytest.mark.install
@pytest.mark.pin
@flaky
def test_windows_pinned_pipfile(self, pypi):
with PipenvInstance(pypi=pypi) as p:
with open(p.pipfile_path, 'w') as f:
@@ -515,11 +532,12 @@ tablib = "<0.12"
@pytest.mark.run
@pytest.mark.install
@flaky
def test_multiprocess_bug_and_install(self, pypi):
with temp_environ():
os.environ['PIPENV_MAX_SUBPROCESS'] = '2'
with PipenvInstance(pypi=pypi) as p:
with PipenvInstance(pypi=pypi, chdir=True) as p:
with open(p.pipfile_path, 'w') as f:
contents = """
[packages]
@@ -545,9 +563,10 @@ tpfd = "*"
@pytest.mark.sequential
@pytest.mark.install
@flaky
def test_sequential_mode(self, pypi):
with PipenvInstance(pypi=pypi) as p:
with PipenvInstance(pypi=pypi, chdir=True) as p:
with open(p.pipfile_path, 'w') as f:
contents = """
[packages]
@@ -575,6 +594,7 @@ tpfd = "*"
@pytest.mark.resolver
@pytest.mark.backup_resolver
@needs_internet
@flaky
def test_backup_resolver(self):
with PipenvInstance() as p:
with open(p.pipfile_path, 'w') as f:
@@ -590,6 +610,7 @@ tpfd = "*"
@pytest.mark.run
@pytest.mark.markers
@flaky
def test_package_environment_markers(self, pypi):
with PipenvInstance(pypi=pypi) as p:
@@ -611,6 +632,7 @@ tablib = {version = "*", markers="os_name=='splashwear'"}
@pytest.mark.run
@pytest.mark.alt
@pytest.mark.install
@flaky
def test_specific_package_environment_markers(self, pypi):
with PipenvInstance(pypi=pypi) as p:
@@ -632,6 +654,7 @@ requests = {version = "*", os_name = "== 'splashwear'"}
@pytest.mark.markers
@pytest.mark.install
@flaky
def test_top_level_overrides_environment_markers(self, pypi):
"""Top-level environment markers should take precedence.
"""
@@ -651,6 +674,7 @@ funcsigs = {version = "*", os_name = "== 'splashwear'"}
@pytest.mark.markers
@pytest.mark.install
@flaky
def test_global_overrides_environment_markers(self, pypi):
"""Empty (unconditional) dependency should take precedence.
@@ -677,6 +701,7 @@ funcsigs = "*"
@pytest.mark.vcs
@pytest.mark.tablib
@needs_internet
@flaky
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.
@@ -691,7 +716,7 @@ funcsigs = "*"
@pytest.mark.run
@pytest.mark.alt
@pytest.mark.install
@flaky
def test_alternative_version_specifier(self, pypi):
with PipenvInstance(pypi=pypi) as p:
@@ -733,23 +758,20 @@ requests = {version = "*"}
assert normalize_drive(p.path) in p.pipenv('--venv').out
@pytest.mark.dotenv
@pytest.mark.dotvenv
def test_venv_at_project_root(self):
def _assert_venv_at_project_root(p):
c = p.pipenv('--venv')
assert c.return_code == 0
assert p.path in c.out
with temp_environ():
with PipenvInstance(chdir=True, pipfile=False) as p:
with PipenvInstance(chdir=True) as p:
os.environ['PIPENV_VENV_IN_PROJECT'] = '1'
c = p.pipenv('install')
assert c.return_code == 0
_assert_venv_at_project_root(p)
assert normalize_drive(p.path) in p.pipenv('--venv').out
del os.environ['PIPENV_VENV_IN_PROJECT']
os.mkdir('subdir')
os.chdir('subdir')
# should still detect installed
_assert_venv_at_project_root(p)
assert normalize_drive(p.path) in p.pipenv('--venv').out
@pytest.mark.dotvenv
def test_reuse_previous_venv(self, pypi):
@@ -794,9 +816,8 @@ requests = {version = "*"}
# If we can do this we can theoretically make a subshell
# This test doesn't work on *nix
if os.name == 'nt':
args = ['pewtwo', 'in', '.venv', 'pip', 'freeze']
process = subprocess.Popen(
args,
'pewtwo in .venv pip freeze',
shell=True,
universal_newlines=True,
stdin=subprocess.PIPE,
@@ -901,6 +922,7 @@ import records
@pytest.mark.code
@pytest.mark.virtualenv
@pytest.mark.project
def test_activate_virtualenv_no_source(self):
command = activate_virtualenv(source=False)
venv = Project().virtualenv_location
@@ -908,6 +930,7 @@ import records
assert command == '{0}/bin/activate'.format(venv)
@pytest.mark.lock
@pytest.mark.requirements
def test_lock_handle_eggs(self, pypi):
"""Ensure locking works with packages provoding egg formats.
"""
@@ -964,7 +987,7 @@ flask = "==0.12.2"
click = "==6.7"
[dev-packages]
requests = {git = "https://github.com/requests/requests", egg = "requests"}
requests = {git = "https://github.com/requests/requests.git"}
""".strip()
f.write(contents)
@@ -1005,13 +1028,13 @@ allow_prereleases = true
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):
@flaky
def test_complex_deps_lock_and_install_properly(self, pip_src_dir, pypi):
# This uses the real PyPI because Maya has too many dependencies...
with PipenvInstance() as p:
with PipenvInstance(chdir=True, pypi=pypi) as p:
with open(p.pipfile_path, 'w') as f:
contents = """
[packages]
@@ -1019,7 +1042,7 @@ maya = "*"
""".strip()
f.write(contents)
c = p.pipenv('lock')
c = p.pipenv('lock --verbose')
assert c.return_code == 0
c = p.pipenv('install')
@@ -1028,6 +1051,7 @@ maya = "*"
@pytest.mark.extras
@pytest.mark.lock
@pytest.mark.complex
@pytest.mark.skip(reason='This is toooo flaky; need to mock this')
@needs_internet
def test_complex_lock_deep_extras(self):
# records[pandas] requires tablib[pandas] which requires pandas.
@@ -1050,6 +1074,7 @@ records = {extras = ["pandas"], version = "==0.5.2"}
@pytest.mark.lock
@pytest.mark.deploy
@pytest.mark.cli
def test_deploy_works(self, pypi):
with PipenvInstance(pypi=pypi) as p:
@@ -1080,27 +1105,26 @@ requests = "==2.14.0"
@pytest.mark.files
@pytest.mark.urls
@needs_internet
@flaky
def test_urls_work(self, pypi):
with PipenvInstance(pypi=pypi) as p:
with PipenvInstance(chdir=True, pypi=pypi) as p:
c = p.pipenv('install https://github.com/divio/django-cms/archive/release/3.4.x.zip')
key = [k for k in p.pipfile['packages'].keys()][0]
dep = p.pipfile['packages'][key]
assert 'file' in dep
assert c.return_code == 0
key = [k for k in p.lockfile['default'].keys()][0]
dep = p.lockfile['default'][key]
dep = list(p.pipfile['packages'].values())[0]
assert 'file' in dep, p.pipfile
assert 'file' in dep
dep = list(p.lockfile['default'].values())[0]
assert 'file' in dep, p.lockfile
@pytest.mark.install
@pytest.mark.files
@pytest.mark.resolver
@pytest.mark.eggs
def test_local_package(self, pip_src_dir):
@flaky
def test_local_package(self, pip_src_dir, pypi):
"""This test ensures that local packages (directories with a setup.py)
installed in editable mode have their dependencies resolved as well"""
file_name = 'tablib-0.12.1.tar.gz'
@@ -1108,7 +1132,7 @@ requests = "==2.14.0"
# Not sure where travis/appveyor run tests from
test_dir = os.path.dirname(os.path.abspath(__file__))
source_path = os.path.abspath(os.path.join(test_dir, 'test_artifacts', file_name))
with PipenvInstance() as p:
with PipenvInstance(chdir=True, pypi=pypi) as p:
# This tests for a bug when installing a zipfile in the current dir
copy_to = os.path.join(p.path, file_name)
shutil.copy(source_path, copy_to)
@@ -1121,13 +1145,14 @@ requests = "==2.14.0"
@pytest.mark.install
@pytest.mark.files
def test_local_zipfiles(self):
@flaky
def test_local_zipfiles(self, pypi):
file_name = 'tablib-0.12.1.tar.gz'
# Not sure where travis/appveyor run tests from
test_dir = os.path.dirname(os.path.abspath(__file__))
source_path = os.path.abspath(os.path.join(test_dir, 'test_artifacts', file_name))
with PipenvInstance() as p:
with PipenvInstance(chdir=True, pypi=pypi) as p:
# This tests for a bug when installing a zipfile in the current dir
shutil.copy(source_path, os.path.join(p.path, file_name))
@@ -1148,6 +1173,7 @@ requests = "==2.14.0"
@pytest.mark.files
@pytest.mark.urls
@needs_internet
@flaky
def test_install_remote_requirements(self, pypi):
with PipenvInstance(pypi=pypi) as p:
# using a github hosted requirements.txt file
@@ -1166,6 +1192,7 @@ requests = "==2.14.0"
@pytest.mark.install
@pytest.mark.files
@flaky
def test_relative_paths(self, pypi):
file_name = 'tablib-0.12.1.tar.gz'
test_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)))
@@ -1187,6 +1214,7 @@ requests = "==2.14.0"
@pytest.mark.install
@pytest.mark.local_file
@flaky
def test_install_local_file_collision(self, pypi):
with PipenvInstance(pypi=pypi) as p:
target_package = 'alembic'
@@ -1199,13 +1227,15 @@ requests = "==2.14.0"
assert p.pipfile['packages'][target_package] == '*'
assert target_package in p.lockfile['default']
@pytest.mark.cli
@pytest.mark.clean
def test_clean_on_empty_venv(self, pypi):
with PipenvInstance(pypi=pypi) as p:
c = p.pipenv('clean')
assert c.return_code == 0
@pytest.mark.install
@pytest.mark.project
@flaky
def test_environment_variable_value_does_not_change_hash(self, pypi):
with PipenvInstance(chdir=True, pypi=pypi) as p:
with temp_environ():
@@ -1255,21 +1285,25 @@ multicommand = "bash -c \"cd docs && make html\""
assert c.return_code == 0
assert c.out == 'foo\n'
assert c.err == ''
if os.name != 'nt':
c = p.pipenv('run notfoundscript')
assert c.return_code == 1
assert c.out == ''
c = p.pipenv('run notfoundscript')
assert c.return_code == 1
assert c.out == ''
if os.name != 'nt': # TODO: Implement this message for Windows.
assert 'Error' in c.err
assert 'randomthingtotally (from notfoundscript)' in c.err
executable, argv = _get_command_posix(Project(), 'multicommand', [])
assert executable == 'bash'
assert argv == ['-c', 'cd docs && make html']
executable, argv = _get_command_posix(Project(), 'appendscript', ['a', 'b'])
assert executable == 'cmd'
assert argv == ['arg1', 'a', 'b']
project = Project()
script = project.build_script('multicommand')
assert script.command == 'bash'
assert script.args == ['-c', 'cd docs && make html']
script = project.build_script('appendscript', ['a', 'b'])
assert script.command == 'cmd'
assert script.args == ['arg1', 'a', 'b']
@pytest.mark.lock
@pytest.mark.complex
@flaky
@py3_only
def test_resolver_unique_markers(self, pypi):
"""vcrpy has a dependency on `yarl` which comes with a marker