mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 14:50:16 +00:00
Fix deps and testing infrastructure
- Fix requirement parsing - Add appveyor config - cutover from pathlib to pathlib2 if needed - Pin pathlib2==2.1.0 to avoid scandir - Windows script runner fix - Backport `shlex.quote()` for use in `pipenv run` - Update tests and appveyor Signed-off-by: Dan Ryan <dan@danryan.co>
This commit is contained in:
@@ -11,7 +11,7 @@ pytest-xdist = "*"
|
||||
click = "*"
|
||||
pytest-pypy = {path = "./tests/pytest-pypi", editable = true}
|
||||
pytest-tap = "*"
|
||||
stdeb = {version="*", sys_platform="== 'linux'"}
|
||||
stdeb = {version="*", markers="sys_platform == 'linux'"}
|
||||
white = {version="*", markers="python_version >= '3.6'"}
|
||||
|
||||
[packages]
|
||||
@@ -23,4 +23,4 @@ tests = "bash ./run-tests.sh"
|
||||
|
||||
[pipenv]
|
||||
|
||||
allow_prereleases = true
|
||||
allow_prereleases = true
|
||||
|
||||
Generated
+132
-10
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "8acf60476c4efca4f1be83dc747f18d4a663c3479e664e9e6865469f7ab9b958"
|
||||
"sha256": "eab749ae861993f9e4b71bea88782c806a02fa82e3f45c82c4550ee459d8b886"
|
||||
},
|
||||
"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",
|
||||
@@ -43,6 +50,14 @@
|
||||
],
|
||||
"version": "==2.5.3"
|
||||
},
|
||||
"black": {
|
||||
"hashes": [
|
||||
"sha256:0461c7a52b5beb378936bf642753dec7a45305c96c6129d540b9c53227121a5a",
|
||||
"sha256:7183263650ba3071034e90b40a1ea74abccbd32cf525cef6d7914479dbe7f2fb"
|
||||
],
|
||||
"markers": "python_version > '3.5'",
|
||||
"version": "==18.3a0"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296",
|
||||
@@ -50,6 +65,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 +121,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 +158,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",
|
||||
@@ -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,26 @@
|
||||
],
|
||||
"version": "==4.1.0"
|
||||
},
|
||||
"pathlib": {
|
||||
"ordereddict": {
|
||||
"hashes": [
|
||||
"sha256:6940718dfc3eff4258203ad5021090933e5c04707d5ca8cc9e73c94a7894ea9f"
|
||||
"sha256:1c35b4ac206cef2d24816c89f89cf289dd3d38cf7c449bb3fab7bf6d43f01b1f"
|
||||
],
|
||||
"version": "==1.1"
|
||||
},
|
||||
"pathlib2": {
|
||||
"hashes": [
|
||||
"sha256:24e0b33e1333b55e73c9d1e9a8342417d519f7789a9d3b440f4acd00ea45157e",
|
||||
"sha256:deb3a960c1d55868dfbcac98432358b92ba89d95029cddd4040db1f27405055c"
|
||||
],
|
||||
"markers": "python_version < '3.4'",
|
||||
"version": "==1.0.1"
|
||||
"version": "==2.1.0"
|
||||
},
|
||||
"pbr": {
|
||||
"hashes": [
|
||||
"sha256:05f61c71aaefc02d8e37c0a3eeb9815ff526ea28b3b76324769e6158d7f95be1",
|
||||
"sha256:60c25b7dfd054ef9bb0ae327af949dd4676aa09ac3a9471cdc871d8a9213f9ac"
|
||||
"sha256:8b9a7c3704657cb0831b3ded0a6b61377947c8235b76649bb7de4bfe2e6cfa56",
|
||||
"sha256:cf66675e22ae91a4f20e4b8354f117d3e3d1de651513051d109cc39645fb3672"
|
||||
],
|
||||
"version": "==3.1.1"
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"pipenv": {
|
||||
"editable": true,
|
||||
@@ -204,6 +313,12 @@
|
||||
],
|
||||
"version": "==2.3.1"
|
||||
},
|
||||
"pycparser": {
|
||||
"hashes": [
|
||||
"sha256:99a8ca03e29851d96616ad0404b4aad7d9ee16f25c9f9708a11faf2810f7b226"
|
||||
],
|
||||
"version": "==2.18"
|
||||
},
|
||||
"pyflakes": {
|
||||
"hashes": [
|
||||
"sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f",
|
||||
@@ -218,6 +333,13 @@
|
||||
],
|
||||
"version": "==2.2.0"
|
||||
},
|
||||
"pyopenssl": {
|
||||
"hashes": [
|
||||
"sha256:07a2de1a54de07448732a81e38a55df7da109b2f47f599f8bb35b0cbec69d4bd",
|
||||
"sha256:2c10cfba46a52c0b0950118981d61e72c1e5b1aac451ca1bc77de1a679456773"
|
||||
],
|
||||
"version": "==17.5.0"
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c",
|
||||
@@ -328,10 +450,10 @@
|
||||
},
|
||||
"tqdm": {
|
||||
"hashes": [
|
||||
"sha256:05e991ecb0f874046ddcb374396a626afd046fb4d31f73633ea752b844458a7a",
|
||||
"sha256:2aea9f81fdf127048667e0ba22f5fc10ebc879fb838dc52dcf055242037ec1f7"
|
||||
"sha256:782aa84b61a5246c4f9e5b938875009e0b759d9a5c9d16b12e4f8deefdff7892",
|
||||
"sha256:eaf9c32a2cf7b9623eb272134b934eb354ef6304299611aa71e3cf47cf83c22b"
|
||||
],
|
||||
"version": "==4.19.8"
|
||||
"version": "==4.19.9"
|
||||
},
|
||||
"twine": {
|
||||
"hashes": [
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
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: "install"
|
||||
|
||||
- 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: "2.7.x"
|
||||
TEST_SUITE: "install"
|
||||
|
||||
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 install -e ."
|
||||
- "%PYTHON%\\python.exe -m pipenv install --dev"
|
||||
- echo "Running pipenv at:"
|
||||
- "%PYTHON%\\python.exe -m pipenv --venv"
|
||||
- echo "Using python:"
|
||||
- "%PYTHON%\\python.exe -m pipenv --py"
|
||||
|
||||
cache:
|
||||
- "%LocalAppData%\\pip\\cache"
|
||||
|
||||
test_script:
|
||||
- cmd: set PYPI_VENDOR_DIR=".\tests\pypi\" && %PYTHON%\\python.exe -m pipenv run pytest -v -n auto -m %TEST_SUITE% tests
|
||||
+14
-14
@@ -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
|
||||
|
||||
+12
-7
@@ -25,7 +25,8 @@ from blindspin import spinner
|
||||
from requests.packages import urllib3
|
||||
from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
||||
|
||||
from .project import Project
|
||||
from.project import Project
|
||||
from .requirements import PipenvRequirement
|
||||
from .utils import (
|
||||
convert_deps_from_pip,
|
||||
convert_deps_to_pip,
|
||||
@@ -1399,9 +1400,9 @@ def pip_install(
|
||||
f.write(package_name)
|
||||
# Install dependencies when a package is a VCS dependency.
|
||||
try:
|
||||
req = get_requirement(
|
||||
req = PipenvRequirement.from_line(
|
||||
package_name.split('--hash')[0].split('--trusted-host')[0]
|
||||
).vcs
|
||||
).requirement.vcs
|
||||
except (pip9._vendor.pyparsing.ParseException, ValueError) as e:
|
||||
click.echo('{0}: {1}'.format(crayons.red('WARNING'), e), err=True)
|
||||
click.echo(
|
||||
@@ -2198,13 +2199,16 @@ def inline_activate_virtualenv():
|
||||
def do_run_nt(command, args):
|
||||
"""Run command by appending space-joined args to it!"""
|
||||
import subprocess
|
||||
try:
|
||||
from shlex import quote as shellquote
|
||||
except ImportError:
|
||||
from pipenv.vendor.backports.shlex import quote as shellquote
|
||||
command = project.scripts.get(command, command)
|
||||
command = [command] + [shellquote(a) for a in args]
|
||||
|
||||
# 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(command, shell=True, universal_newlines=True)
|
||||
p.communicate()
|
||||
sys.exit(p.returncode)
|
||||
|
||||
@@ -2486,7 +2490,8 @@ def do_clean(
|
||||
)
|
||||
installed_package_names = []
|
||||
for installed in installed_packages:
|
||||
r = get_requirement(installed)
|
||||
pipenvreq = PipenvRequirement.from_line(installed)
|
||||
r = pipenvreq.requirement
|
||||
# Ignore editable installations.
|
||||
if not r.editable:
|
||||
installed_package_names.append(r.name.lower())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,609 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
import sys
|
||||
from pipenv import PIPENV_VENDOR, PIPENV_PATCHED
|
||||
|
||||
sys.path.insert(0, PIPENV_VENDOR)
|
||||
sys.path.insert(0, PIPENV_PATCHED)
|
||||
import hashlib
|
||||
import os
|
||||
import requirements
|
||||
import six
|
||||
from attr import attrs, attrib, Factory, validators
|
||||
from collections import defaultdict
|
||||
from pip9.index import Link
|
||||
from pip9.download import path_to_url, url_to_path
|
||||
from pip9.req.req_install import _strip_extras
|
||||
from pip9._vendor.distlib.markers import Evaluator
|
||||
from pipenv.utils import SCHEME_LIST, VCS_LIST, is_installable_file, is_vcs, multi_split, get_converted_relative_path, is_star, is_pinned, is_valid_url
|
||||
from first import first
|
||||
|
||||
try:
|
||||
from pathlib import Path
|
||||
except ImportError:
|
||||
from pathlib2 import Path
|
||||
|
||||
HASH_STRING = ' --hash={0}'
|
||||
|
||||
|
||||
def _validate_vcs(instance, attr, value):
|
||||
if value not in VCS_LIST:
|
||||
raise ValueError('Invalid vcs {0}'.format(value))
|
||||
|
||||
_optional_instance_of = lambda cls: validators.optional(validators.instance_of(cls))
|
||||
|
||||
|
||||
@attrs
|
||||
class Source(object):
|
||||
#: URL to PyPI instance
|
||||
url = attrib(default='')
|
||||
#: If False, skip SSL checks
|
||||
verify_ssl = attrib(default=True, validator=validators.optional(validators.instance_of(bool)))
|
||||
#: human name to refer to this source (can be referenced in packages or dev-packages)
|
||||
name = attrib(default='')
|
||||
|
||||
|
||||
@attrs
|
||||
class Requires(object):
|
||||
"""System-level requirements - see PEP508 for more detail"""
|
||||
os_name = attrib(default=None)
|
||||
sys_platform = attrib(default=None)
|
||||
platform_machine = attrib(default=None)
|
||||
platform_python_implementation = attrib(default=None)
|
||||
platform_release = attrib(default=None)
|
||||
platform_system = attrib(default=None)
|
||||
platform_version = attrib(default=None)
|
||||
python_version = attrib(default=None)
|
||||
python_full_version = attrib(default=None)
|
||||
implementation_name = attrib(default=None)
|
||||
implementation_version = attrib(default=None)
|
||||
|
||||
|
||||
@attrs
|
||||
class VCSRequirement(object):
|
||||
#: vcs reference name (branch / commit / tag)
|
||||
ref = attrib(default=None)
|
||||
#: path to hit - without any of the VCS prefixes (like git+ / http+ / etc)
|
||||
uri = attrib(default=None)
|
||||
subdirectory = attrib(default=None)
|
||||
vcs = attrib(validator=validators.optional(_validate_vcs), default=None)
|
||||
|
||||
|
||||
@attrs
|
||||
class PipfileRequirement(object):
|
||||
path = attrib(default=None)
|
||||
uri = attrib(default=None)
|
||||
name = attrib(default=None)
|
||||
extras = attrib(default=Factory(list))
|
||||
markers = attrib(default='')
|
||||
editable = attrib(default=False)
|
||||
vcs = attrib(validator=validators.optional(_validate_vcs), default=None)
|
||||
version = attrib(default='')
|
||||
index = attrib(default=None)
|
||||
_hash = attrib(default=None)
|
||||
hashes = attrib(default=Factory(list))
|
||||
ref = attrib(default=None)
|
||||
subdirectory = attrib(default=None)
|
||||
_link = attrib()
|
||||
|
||||
@_link.default
|
||||
def _init_link(self):
|
||||
if not self.vcs:
|
||||
return None
|
||||
|
||||
uri = self.uri if self.uri else path_to_url(os.path.abspath(self.path))
|
||||
return build_vcs_link(
|
||||
self.vcs,
|
||||
uri,
|
||||
name=self.name,
|
||||
ref=self.ref,
|
||||
subdirectory=self.subdirectory,
|
||||
)
|
||||
|
||||
def __attrs_post_init__(self):
|
||||
if self._hash and not self.hashes:
|
||||
self.hashes = [self._hash]
|
||||
if self.vcs and self.uri and self._link:
|
||||
self.uri = _strip_ssh_from_git_uri(self._link.url)
|
||||
|
||||
@classmethod
|
||||
def create(cls, name, pipfile):
|
||||
_pipfile = {}
|
||||
if hasattr(pipfile, 'keys'):
|
||||
_pipfile = dict(pipfile).copy()
|
||||
_pipfile['name'] = name
|
||||
_pipfile['version'] = cls._get_version(pipfile)
|
||||
editable = _pipfile.pop(
|
||||
'editable'
|
||||
) if 'editable' in _pipfile else False
|
||||
vcs_type = first([vcs for vcs in VCS_LIST if vcs in _pipfile])
|
||||
vcs = _pipfile.pop(vcs_type) if vcs_type else None
|
||||
_pipfile_vcs_key = None
|
||||
if vcs:
|
||||
_pipfile_vcs_key = 'uri' if is_valid_url(vcs) else 'path'
|
||||
_pipfile['editable'] = editable
|
||||
_pipfile['vcs'] = vcs_type
|
||||
if _pipfile_vcs_key and not _pipfile.get(_pipfile_vcs_key):
|
||||
_pipfile[_pipfile_vcs_key] = vcs
|
||||
markers = _pipfile.get('markers')
|
||||
_extra_markers = [k for k in _pipfile.keys() if k in Evaluator.allowed_values.keys()]
|
||||
if _extra_markers:
|
||||
markers = list(markers) if markers else []
|
||||
for marker in _extra_markers:
|
||||
markers.append(_pipfile.pop(marker))
|
||||
_pipfile['markers'] = ' and '.join(markers)
|
||||
return cls(**_pipfile)
|
||||
|
||||
@staticmethod
|
||||
def _get_version(pipfile_entry):
|
||||
if str(pipfile_entry) == '{}' or is_star(pipfile_entry):
|
||||
return ''
|
||||
|
||||
elif isinstance(pipfile_entry, six.string_types):
|
||||
return pipfile_entry
|
||||
|
||||
return pipfile_entry.get('version', '')
|
||||
|
||||
@property
|
||||
def pip_version(self):
|
||||
if is_star(self.version):
|
||||
return self.name
|
||||
|
||||
return '{0}{1}'.format(self.name, self.version)
|
||||
|
||||
@property
|
||||
def requirement(self):
|
||||
req_uri = self.uri
|
||||
if self.editable and self.path and not self.uri:
|
||||
req_uri = path_to_url(os.path.abspath(self.path))
|
||||
return PipenvRequirement._create_requirement(
|
||||
name=self.pip_version,
|
||||
path=self.path,
|
||||
uri=req_uri,
|
||||
markers=self.markers,
|
||||
extras=self.extras,
|
||||
index=self.index,
|
||||
hashes=self.hashes,
|
||||
vcs=self.vcs,
|
||||
editable=self.editable,
|
||||
link=self._link,
|
||||
line=None,
|
||||
)
|
||||
|
||||
|
||||
@attrs
|
||||
class PipenvRequirement(object):
|
||||
"""Requirement for Pipenv Use
|
||||
|
||||
Provides the following methods:
|
||||
- as_pipfile
|
||||
- as_lockfile
|
||||
- as_requirement
|
||||
- from_line
|
||||
- from_pipfile
|
||||
- resolve
|
||||
"""
|
||||
_editable_prefix = '-e '
|
||||
path = attrib(default=None)
|
||||
uri = attrib(default=None)
|
||||
name = attrib(default=None)
|
||||
extras = attrib(default=Factory(list))
|
||||
markers = attrib(default='')
|
||||
editable = attrib(default=False)
|
||||
vcs = attrib(validator=validators.optional(_validate_vcs), default=None)
|
||||
link = attrib(default=None)
|
||||
line = attrib(default=None)
|
||||
requirement = attrib(default=None)
|
||||
index = attrib(default=Factory(list))
|
||||
specs = attrib(default='')
|
||||
hashes = attrib(default=Factory(list))
|
||||
|
||||
@classmethod
|
||||
def create(cls, req):
|
||||
creation_attrs = {'requirement': req}
|
||||
for prop in [
|
||||
'name',
|
||||
'extras',
|
||||
'markers',
|
||||
'line',
|
||||
'link',
|
||||
'vcs',
|
||||
'editable',
|
||||
'uri',
|
||||
'path',
|
||||
'hashes',
|
||||
'index',
|
||||
]:
|
||||
creation_attrs[prop] = getattr(req, prop, None)
|
||||
return cls(**creation_attrs)
|
||||
|
||||
@property
|
||||
def original_line(self):
|
||||
_editable = ''
|
||||
if self.editable:
|
||||
_editable += self._editable_prefix
|
||||
if self.line and (self.path or self.uri):
|
||||
# original_line adds in -e if necessary
|
||||
if self.line.startswith(self._editable_prefix):
|
||||
return self.line
|
||||
|
||||
return '{0}{1}'.format(_editable, self.line)
|
||||
|
||||
return self.constructed_line
|
||||
|
||||
@property
|
||||
def constructed_line(self):
|
||||
_editable = ''
|
||||
if self.editable:
|
||||
_editable += self._editable_prefix
|
||||
line = ''
|
||||
if self.link:
|
||||
line = '{0}{1}'.format(_editable, self.link.url)
|
||||
elif self.path or self.uri:
|
||||
line = '{0}{1}'.format(_editable, self.path or self.uri)
|
||||
else:
|
||||
line += self.name
|
||||
if not self.vcs:
|
||||
line = '{0}{1}{2}{3}{4}'.format(
|
||||
line,
|
||||
self.extras_as_pip,
|
||||
self.specifiers_as_pip,
|
||||
self.markers_as_pip,
|
||||
self.hashes_as_pip,
|
||||
)
|
||||
else:
|
||||
line = self.line
|
||||
if _editable == self._editable_prefix and not self.line.startswith(
|
||||
_editable
|
||||
):
|
||||
line = '{0}{1}'.format(_editable, self.line)
|
||||
line = '{0}{1}{2}{3}'.format(
|
||||
line,
|
||||
self.extras_as_pip,
|
||||
self.markers_as_pip,
|
||||
self.hashes_as_pip,
|
||||
)
|
||||
return line
|
||||
|
||||
@property
|
||||
def extras_as_pip(self):
|
||||
if self.extras:
|
||||
return '[{0}]'.format(','.join(self.extras))
|
||||
|
||||
return ''
|
||||
|
||||
@property
|
||||
def markers_as_pip(self):
|
||||
if self.markers:
|
||||
return '; {0}'.format(self.markers)
|
||||
|
||||
return ''
|
||||
|
||||
@property
|
||||
def specifiers_as_pip(self):
|
||||
if hasattr(self.requirement, 'specs'):
|
||||
return ','.join([''.join(spec) for spec in self.requirement.specs])
|
||||
|
||||
return ''
|
||||
|
||||
@property
|
||||
def hashes_as_pip(self):
|
||||
if self.hashes:
|
||||
if isinstance(self.hashes, six.string_types):
|
||||
return HASH_STRING.format(self.hashes)
|
||||
|
||||
return ''.join([HASH_STRING.format(h) for h in self.hashes])
|
||||
|
||||
return ''
|
||||
|
||||
@classmethod
|
||||
def from_pipfile(cls, name, indexes, pipfile_entry):
|
||||
pipfile = PipfileRequirement.create(name, pipfile_entry)
|
||||
return cls.create(pipfile.requirement)
|
||||
|
||||
@classmethod
|
||||
def from_line(cls, line):
|
||||
"""Pre-clean requirement strings passed to the requirements parser.
|
||||
|
||||
Ensures that we can accept both local and relative paths, file and VCS URIs,
|
||||
remote URIs, and package names, and that we pass only valid requirement strings
|
||||
to the requirements parser. Performs necessary modifications to requirements
|
||||
object if the user input was a local relative path.
|
||||
|
||||
:param str dep: A requirement line
|
||||
:returns: :class:`requirements.Requirement` object
|
||||
"""
|
||||
hashes = None
|
||||
if '--hash=' in line:
|
||||
hashes = line.split(' --hash=')
|
||||
line, hashes = hashes[0], hashes[1:]
|
||||
editable = False
|
||||
original_line = line
|
||||
_editable = ''
|
||||
if line.startswith('-e '):
|
||||
editable = True
|
||||
_editable += cls._editable_prefix
|
||||
line = line.split(' ', 1)[1]
|
||||
line, markers = cls._split_markers(line)
|
||||
line, extras = _strip_extras(line)
|
||||
req_dict = defaultdict(None)
|
||||
vcs = None
|
||||
if is_installable_file(line):
|
||||
req_dict = cls._prep_path(line)
|
||||
req_dict['original_line'] = '{0}{1}'.format(
|
||||
_editable, req_dict['original_line']
|
||||
)
|
||||
elif is_vcs(line):
|
||||
req_dict = cls._prep_vcs(line)
|
||||
vcs = first(
|
||||
_split_vcs_method(
|
||||
req_dict.get('uri', req_dict.get('path', line))
|
||||
)
|
||||
)
|
||||
req_dict['original_line'] = '{0}{1}'.format(
|
||||
_editable, req_dict['original_line']
|
||||
)
|
||||
else:
|
||||
req_dict = {
|
||||
'line': line,
|
||||
'original_line': original_line,
|
||||
'name': multi_split(line, '!=<>~')[0],
|
||||
}
|
||||
return cls.create(
|
||||
cls._create_requirement(
|
||||
line=req_dict['original_line'],
|
||||
name=req_dict.get('name'),
|
||||
path=req_dict.get('path'),
|
||||
uri=req_dict.get('uri'),
|
||||
link=req_dict.get('link'),
|
||||
hashes=hashes,
|
||||
markers=markers,
|
||||
extras=extras,
|
||||
editable=editable,
|
||||
vcs=vcs,
|
||||
)
|
||||
)
|
||||
|
||||
def as_pipfile(self):
|
||||
""""Converts a requirement to a Pipfile-formatted one."""
|
||||
req_dict = {}
|
||||
req = self.requirement
|
||||
req_dict = {}
|
||||
if req.local_file:
|
||||
hashable_path = req.uri or req.path
|
||||
dict_key = 'file' if req.uri else 'path'
|
||||
hashed_path = hashlib.sha256(
|
||||
hashable_path.encode('utf-8')
|
||||
).hexdigest(
|
||||
)
|
||||
req_dict[dict_key] = hashable_path
|
||||
req_dict['name'] = hashed_path[
|
||||
len(hashed_path) - 7:
|
||||
] if not req.vcs else req.name
|
||||
elif req.vcs:
|
||||
if req.name is None:
|
||||
raise ValueError(
|
||||
'pipenv requires an #egg fragment for version controlled '
|
||||
'dependencies. Please install remote dependency '
|
||||
'in the form {0}#egg=<package-name>.'.format(req.uri)
|
||||
)
|
||||
|
||||
if req.uri and req.uri.startswith('{0}+'.format(req.vcs)):
|
||||
if req_dict.get('uri'):
|
||||
# req_dict['uri'] = req.uri[len(req.vcs) + 1:]
|
||||
del req_dict['uri']
|
||||
req_dict.update(
|
||||
{
|
||||
req.vcs: req.uri[
|
||||
len(req.vcs) + 1:
|
||||
] if req.uri else req.path
|
||||
}
|
||||
)
|
||||
if req.subdirectory:
|
||||
req_dict.update({'subdirectory': req.subdirectory})
|
||||
if req.revision:
|
||||
req_dict.update({'ref': req.revision})
|
||||
elif req.specs:
|
||||
# Comparison operators: e.g. Django>1.10
|
||||
specs = ','.join([''.join(spec) for spec in req.specs])
|
||||
req_dict.update({'version': specs})
|
||||
else:
|
||||
req_dict.update({'version': '*'})
|
||||
if self.extras:
|
||||
req_dict.update({'extras': self.extras})
|
||||
if req.editable:
|
||||
req_dict.update({'editable': req.editable})
|
||||
if self.hashes:
|
||||
hash_key = 'hashes'
|
||||
hashes = self.hashes
|
||||
if isinstance(hashes, six.string_types) or len(hashes) == 1:
|
||||
hash_key = 'hash'
|
||||
if len(hashes) == 1:
|
||||
hashes = first(hashes)
|
||||
req_dict.update({hash_key: hashes})
|
||||
if len(req_dict.keys()) == 1 and req_dict.get('version'):
|
||||
return {req.name: req_dict.get('version')}
|
||||
|
||||
return {req.name: req_dict}
|
||||
|
||||
def as_requirement(self, project=None, include_index=False):
|
||||
"""Creates a requirements.txt compatible output of the current dependency.
|
||||
|
||||
:param project: Pipenv Project, defaults to None
|
||||
:param project: :class:`pipenv.project.Project`, optional
|
||||
:param include_index: Whether to include the resolved index, defaults to False
|
||||
:param include_index: bool, optional
|
||||
"""
|
||||
line = self.constructed_line
|
||||
if include_index and not (self.requirement.local_file or self.vcs):
|
||||
from .utils import prepare_pip_source_args
|
||||
|
||||
if self.index:
|
||||
pip_src_args = [project.get_source(self.index)]
|
||||
else:
|
||||
pip_src_args = project.sources
|
||||
index_string = ' '.join(prepare_pip_source_args(pip_src_args))
|
||||
line = '{0} {1}'.format(line, index_string)
|
||||
return line
|
||||
|
||||
@staticmethod
|
||||
def _split_markers(line):
|
||||
"""Split markers from a dependency"""
|
||||
if not any(line.startswith(uri_prefix) for uri_prefix in SCHEME_LIST):
|
||||
marker_sep = ';'
|
||||
else:
|
||||
marker_sep = '; '
|
||||
markers = None
|
||||
if marker_sep in line:
|
||||
line, markers = line.split(marker_sep, 1)
|
||||
markers = markers.strip() if markers else None
|
||||
return line, markers
|
||||
|
||||
@staticmethod
|
||||
def _prep_path(line):
|
||||
_path = Path(line)
|
||||
link = Link(_path.absolute().as_uri())
|
||||
if _path.is_absolute() or _path.as_posix() == '.':
|
||||
path = _path.as_posix()
|
||||
else:
|
||||
path = get_converted_relative_path(line)
|
||||
name_or_url = link.egg_fragment if link.egg_fragment else link.url_without_fragment
|
||||
name = link.egg_fragment or link.show_url or link.filename
|
||||
return {
|
||||
'link': link,
|
||||
'path': path,
|
||||
'line': name_or_url,
|
||||
'original_line': line,
|
||||
'name': name,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _prep_vcs(line):
|
||||
# Generate a Link object for parsing egg fragments
|
||||
link = Link(line)
|
||||
# Save the original path to store in the pipfile
|
||||
original_uri = link.url
|
||||
# Construct the requirement using proper git+ssh:// replaced uris or names if available
|
||||
formatted_line = _clean_git_uri(line)
|
||||
return {
|
||||
'link': link,
|
||||
'uri': formatted_line,
|
||||
'line': original_uri,
|
||||
'original_line': line,
|
||||
'name': link.egg_fragment,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _create_requirement(
|
||||
cls,
|
||||
line=None,
|
||||
name=None,
|
||||
path=None,
|
||||
uri=None,
|
||||
extras=None,
|
||||
markers=None,
|
||||
editable=False,
|
||||
vcs=None,
|
||||
link=None,
|
||||
hashes=None,
|
||||
index=None,
|
||||
):
|
||||
_editable = cls._editable_prefix if editable else ''
|
||||
_line = line or uri or path or name
|
||||
# We don't want to only use the name on properly
|
||||
# formatted VCS inputs
|
||||
if vcs or is_vcs(_line):
|
||||
_line = uri or path or line
|
||||
_line = '{0}{1}'.format(_editable, _line)
|
||||
elif link and path and not uri:
|
||||
_line = link.url
|
||||
req = first(requirements.parse(_line))
|
||||
req.line = _line
|
||||
if editable:
|
||||
req.editable = True
|
||||
if req.name and not any(
|
||||
getattr(req, prop) for prop in ['uri', 'path']
|
||||
):
|
||||
if link and link.scheme.startswith('file') and path:
|
||||
req.path = path
|
||||
req.local_file = True
|
||||
elif link and uri:
|
||||
req.uri = link.url_without_fragment
|
||||
elif req.local_file and path and not req.vcs:
|
||||
req.uri = None
|
||||
req.path = path
|
||||
elif req.vcs and not req.local_file and uri != link.url:
|
||||
req.uri = _strip_ssh_from_git_uri(req.uri)
|
||||
req.line = line or _strip_ssh_from_git_uri(req.line)
|
||||
if markers:
|
||||
req.markers = markers
|
||||
if extras:
|
||||
# Bizarrely this is also what pip does...
|
||||
req.extras = first(
|
||||
requirements.parse(
|
||||
'fakepkg{0}'.format(_extras_to_string(extras))
|
||||
)
|
||||
).extras
|
||||
req.link = link
|
||||
if hashes:
|
||||
req.hashes = hashes
|
||||
if index:
|
||||
req.index = index
|
||||
return req
|
||||
|
||||
|
||||
def _strip_ssh_from_git_uri(uri):
|
||||
"""Return git+ssh:// formatted URI to git+git@ format"""
|
||||
if isinstance(uri, six.string_types):
|
||||
uri = uri.replace('git+ssh://', 'git+')
|
||||
return uri
|
||||
|
||||
|
||||
def _clean_git_uri(uri):
|
||||
"""Cleans VCS uris from pip9 format"""
|
||||
if isinstance(uri, six.string_types):
|
||||
# Add scheme for parsing purposes, this is also what pip does
|
||||
if uri.startswith('git+') and '://' not in uri:
|
||||
uri = uri.replace('git+', 'git+ssh://')
|
||||
return uri
|
||||
|
||||
|
||||
def _split_vcs_method(uri):
|
||||
"""Split a vcs+uri formatted uri into (vcs, uri)"""
|
||||
vcs_start = '{0}+'
|
||||
vcs = first(
|
||||
[vcs for vcs in VCS_LIST if uri.startswith(vcs_start.format(vcs))]
|
||||
)
|
||||
if vcs:
|
||||
vcs, uri = uri.split('+', 1)
|
||||
return vcs, uri
|
||||
|
||||
|
||||
def _extras_to_string(extras):
|
||||
"""Turn a list of extras into a string"""
|
||||
if isinstance(extras, six.string_types):
|
||||
if extras.startswith('['):
|
||||
return extras
|
||||
|
||||
else:
|
||||
extras = [extras]
|
||||
return '[{0}]'.format(','.join(extras))
|
||||
|
||||
|
||||
def build_vcs_link(
|
||||
vcs, uri, name=None, ref=None, subdirectory=None, extras= []
|
||||
):
|
||||
vcs_start = '{0}+'.format(vcs)
|
||||
if not uri.startswith(vcs_start):
|
||||
uri = '{0}{1}'.format(vcs_start, uri)
|
||||
uri = _clean_git_uri(uri)
|
||||
if ref:
|
||||
uri = '{0}@{1}'.format(uri, ref)
|
||||
if name:
|
||||
uri = '{0}#egg={1}'.format(uri, name)
|
||||
if extras:
|
||||
extras = _extras_to_string(extras)
|
||||
uri = '{0}{1}'.format(uri, extras)
|
||||
if subdirectory:
|
||||
uri = '{0}&subdirectory={1}'.format(uri, subdirectory)
|
||||
return Link(uri)
|
||||
+20
-231
@@ -65,8 +65,6 @@ 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.
|
||||
|
||||
@@ -78,79 +76,8 @@ def get_requirement(dep):
|
||||
:param str dep: A requirement line
|
||||
:returns: :class:`requirements.Requirement` object
|
||||
"""
|
||||
path = None
|
||||
uri = None
|
||||
cleaned_uri = None
|
||||
editable = False
|
||||
dep_link = None
|
||||
# check for editable dep / vcs dep
|
||||
if dep.startswith('-e '):
|
||||
editable = True
|
||||
# Use the user supplied path as the written dependency
|
||||
dep = dep.split(' ', 1)[1]
|
||||
# Split out markers if they are present - similar to how pip does it
|
||||
# See pip9.req.req_install.InstallRequirement.from_line
|
||||
if not any(dep.startswith(uri_prefix) for uri_prefix in SCHEME_LIST):
|
||||
marker_sep = ';'
|
||||
else:
|
||||
marker_sep = '; '
|
||||
if marker_sep in dep:
|
||||
dep, markers = dep.split(marker_sep, 1)
|
||||
markers = markers.strip()
|
||||
if not markers:
|
||||
markers = None
|
||||
else:
|
||||
markers = None
|
||||
# Strip extras from the requirement so we can make a properly parseable req
|
||||
dep, extras = _strip_extras(dep)
|
||||
# Only operate on local, existing, non-URI formatted paths which are installable
|
||||
if is_installable_file(dep):
|
||||
dep_path = Path(dep)
|
||||
dep_link = Link(dep_path.absolute().as_uri())
|
||||
if dep_path.is_absolute() or dep_path.as_posix() == '.':
|
||||
path = dep_path.as_posix()
|
||||
else:
|
||||
path = get_converted_relative_path(dep)
|
||||
dep = dep_link.egg_fragment if dep_link.egg_fragment else dep_link.url_without_fragment
|
||||
elif is_vcs(dep):
|
||||
# Generate a Link object for parsing egg fragments
|
||||
dep_link = Link(dep)
|
||||
# Save the original path to store in the pipfile
|
||||
uri = dep_link.url
|
||||
# Construct the requirement using proper git+ssh:// replaced uris or names if available
|
||||
cleaned_uri = clean_git_uri(dep)
|
||||
dep = cleaned_uri
|
||||
if editable:
|
||||
dep = '-e {0}'.format(dep)
|
||||
req = [r for r in requirements.parse(dep)][0]
|
||||
# if all we built was the requirement name and still need everything else
|
||||
if req.name and not any([req.uri, req.path]):
|
||||
if dep_link:
|
||||
if dep_link.scheme.startswith('file') and path and not req.path:
|
||||
req.path = path
|
||||
req.local_file = True
|
||||
req.uri = None
|
||||
else:
|
||||
req.uri = dep_link.url_without_fragment
|
||||
# If the result is a local file with a URI and we have a local path, unset the URI
|
||||
# and set the path instead -- note that local files may have 'path' set by accident
|
||||
elif req.local_file and path and not req.vcs:
|
||||
req.path = path
|
||||
req.uri = None
|
||||
elif req.vcs and req.uri and cleaned_uri and cleaned_uri != uri:
|
||||
req.uri = strip_ssh_from_git_uri(req.uri)
|
||||
req.line = strip_ssh_from_git_uri(req.line)
|
||||
req.editable = editable
|
||||
if markers:
|
||||
req.markers = markers
|
||||
if extras:
|
||||
# Bizarrely this is also what pip does...
|
||||
req.extras = [
|
||||
r for r in requirements.parse('fakepkg{0}'.format(extras))
|
||||
][
|
||||
0
|
||||
].extras
|
||||
return req
|
||||
from .requirements import PipenvRequirement
|
||||
return PipenvRequirement.from_line(dep).requirement
|
||||
|
||||
|
||||
def cleanup_toml(tml):
|
||||
@@ -359,7 +286,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'))
|
||||
@@ -514,73 +441,10 @@ def multi_split(s, split):
|
||||
|
||||
def convert_deps_from_pip(dep):
|
||||
""""Converts a pip-formatted dependency to a Pipfile-formatted one."""
|
||||
from .requirements import PipenvRequirement
|
||||
dependency = {}
|
||||
req = get_requirement(dep)
|
||||
extras = {'extras': req.extras}
|
||||
# File installs.
|
||||
if (req.uri or req.path or is_installable_file(req.name)) and not req.vcs:
|
||||
# Assign a package name to the file, last 7 of it's sha256 hex digest.
|
||||
if not req.uri and not req.path:
|
||||
req.path = os.path.abspath(req.name)
|
||||
hashable_path = req.uri if req.uri else req.path
|
||||
req.name = hashlib.sha256(hashable_path.encode('utf-8')).hexdigest()
|
||||
req.name = req.name[len(req.name) - 7:]
|
||||
# {path: uri} TOML (spec 4 I guess...)
|
||||
if req.uri:
|
||||
dependency[req.name] = {'file': hashable_path}
|
||||
else:
|
||||
dependency[req.name] = {'path': hashable_path}
|
||||
if req.extras:
|
||||
dependency[req.name].update(extras)
|
||||
# Add --editable if applicable
|
||||
if req.editable:
|
||||
dependency[req.name].update({'editable': True})
|
||||
# VCS Installs.
|
||||
elif req.vcs:
|
||||
if req.name is None:
|
||||
raise ValueError(
|
||||
'pipenv requires an #egg fragment for version controlled '
|
||||
'dependencies. Please install remote dependency '
|
||||
'in the form {0}#egg=<package-name>.'.format(req.uri)
|
||||
)
|
||||
|
||||
# Crop off the git+, etc part.
|
||||
if req.uri.startswith('{0}+'.format(req.vcs)):
|
||||
req.uri = req.uri[len(req.vcs) + 1:]
|
||||
dependency.setdefault(req.name, {}).update({req.vcs: req.uri})
|
||||
# Add --editable, if it's there.
|
||||
if req.editable:
|
||||
dependency[req.name].update({'editable': True})
|
||||
# Add subdirectory, if it's there
|
||||
if req.subdirectory:
|
||||
dependency[req.name].update({'subdirectory': req.subdirectory})
|
||||
# Add the specifier, if it was provided.
|
||||
if req.revision:
|
||||
dependency[req.name].update({'ref': req.revision})
|
||||
# Extras: e.g. #egg=requests[security]
|
||||
if req.extras:
|
||||
dependency[req.name].update({'extras': req.extras})
|
||||
elif req.extras or req.specs:
|
||||
specs = None
|
||||
# Comparison operators: e.g. Django>1.10
|
||||
if req.specs:
|
||||
r = multi_split(dep, '!=<>~')
|
||||
specs = dep[len(r[0]):]
|
||||
dependency[req.name] = specs
|
||||
# Extras: e.g. requests[socks]
|
||||
if req.extras:
|
||||
dependency[req.name] = extras
|
||||
if specs:
|
||||
dependency[req.name].update({'version': specs})
|
||||
# Bare dependencies: e.g. requests
|
||||
else:
|
||||
dependency[dep] = '*'
|
||||
# Cleanup when there's multiple values, e.g. -e.
|
||||
if len(dependency) > 1:
|
||||
for key in dependency.copy():
|
||||
if not hasattr(dependency[key], 'keys'):
|
||||
del dependency[key]
|
||||
return dependency
|
||||
req = PipenvRequirement.from_line(dep)
|
||||
return req.as_pipfile()
|
||||
|
||||
|
||||
def is_star(val):
|
||||
@@ -593,95 +457,16 @@ def is_pinned(val):
|
||||
|
||||
def convert_deps_to_pip(deps, project=None, r=True, include_index=False):
|
||||
""""Converts a Pipfile-formatted dependency to a pip-formatted one."""
|
||||
from .requirements import PipenvRequirement
|
||||
dependencies = []
|
||||
for dep in deps.keys():
|
||||
# Default (e.g. '>1.10').
|
||||
extra = deps[dep] if isinstance(deps[dep], six.string_types) else ''
|
||||
version = ''
|
||||
index = ''
|
||||
# Get rid of '*'.
|
||||
if is_star(deps[dep]) or str(extra) == '{}':
|
||||
extra = ''
|
||||
hash = ''
|
||||
# Support for single hash (spec 1).
|
||||
if 'hash' in deps[dep]:
|
||||
hash = ' --hash={0}'.format(deps[dep]['hash'])
|
||||
# Support for multiple hashes (spec 2).
|
||||
if 'hashes' in deps[dep]:
|
||||
hash = '{0} '.format(
|
||||
''.join(
|
||||
[' --hash={0} '.format(h) for h in deps[dep]['hashes']]
|
||||
)
|
||||
)
|
||||
# Support for extras (e.g. requests[socks])
|
||||
if 'extras' in deps[dep]:
|
||||
extra = '[{0}]'.format(','.join(deps[dep]['extras']))
|
||||
if 'version' in deps[dep]:
|
||||
if not is_star(deps[dep]['version']):
|
||||
version = deps[dep]['version']
|
||||
# For lockfile format.
|
||||
if 'markers' in deps[dep]:
|
||||
specs = '; {0}'.format(deps[dep]['markers'])
|
||||
else:
|
||||
# For pipfile format.
|
||||
specs = []
|
||||
for specifier in specifiers:
|
||||
if specifier in deps[dep]:
|
||||
if not is_star(deps[dep][specifier]):
|
||||
specs.append(
|
||||
'{0} {1}'.format(specifier, deps[dep][specifier])
|
||||
)
|
||||
if specs:
|
||||
specs = '; {0}'.format(' and '.join(specs))
|
||||
else:
|
||||
specs = ''
|
||||
if include_index and not is_file(deps[dep]) and not is_vcs(deps[dep]):
|
||||
pip_src_args = []
|
||||
if 'index' in deps[dep]:
|
||||
pip_src_args = [project.get_source(deps[dep]['index'])]
|
||||
else:
|
||||
pip_src_args = project.sources
|
||||
pip_args = prepare_pip_source_args(pip_src_args)
|
||||
index = ' '.join(pip_args)
|
||||
# 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
|
||||
# Support for files.
|
||||
if 'file' in deps[dep]:
|
||||
extra = '{1}{0}'.format(extra, deps[dep]['file']).strip()
|
||||
# Flag the file as editable if it is a local relative path
|
||||
if 'editable' in deps[dep]:
|
||||
dep = '-e '
|
||||
else:
|
||||
dep = ''
|
||||
# Support for paths.
|
||||
elif 'path' in deps[dep]:
|
||||
extra = '{1}{0}'.format(extra, deps[dep]['path']).strip()
|
||||
# Flag the file as editable if it is a local relative path
|
||||
if 'editable' in deps[dep]:
|
||||
dep = '-e '
|
||||
else:
|
||||
dep = ''
|
||||
if vcs:
|
||||
extra = '{0}+{1}'.format(vcs, deps[dep][vcs])
|
||||
# Support for @refs.
|
||||
if 'ref' in deps[dep]:
|
||||
extra += '@{0}'.format(deps[dep]['ref'])
|
||||
extra += '#egg={0}'.format(dep)
|
||||
# Support for subdirectory
|
||||
if 'subdirectory' in deps[dep]:
|
||||
extra += '&subdirectory={0}'.format(deps[dep]['subdirectory'])
|
||||
# Support for editable.
|
||||
if 'editable' in deps[dep]:
|
||||
# Support for --egg.
|
||||
dep = '-e '
|
||||
else:
|
||||
dep = ''
|
||||
s = '{0}{1}{2}{3}{4} {5}'.format(
|
||||
dep, extra, version, specs, hash, index
|
||||
).strip(
|
||||
)
|
||||
dependencies.append(s)
|
||||
for dep_name, dep in deps.items():
|
||||
indexes = project.sources if hasattr(project, 'sources') else None
|
||||
if hasattr(dep, 'keys') and dep.get('index'):
|
||||
indexes = project.get_source(dep['index'])
|
||||
new_dep = PipenvRequirement.from_pipfile(dep_name, indexes, dep)
|
||||
req = new_dep.as_requirement(project=project, include_index=include_index)
|
||||
req = req.strip()
|
||||
dependencies.append(req)
|
||||
if not r:
|
||||
return dependencies
|
||||
|
||||
@@ -754,7 +539,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 +804,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))
|
||||
|
||||
Vendored
+57
@@ -0,0 +1,57 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
from functools import partial
|
||||
|
||||
from . import converters, exceptions, filters, validators
|
||||
from ._config import get_run_validators, set_run_validators
|
||||
from ._funcs import asdict, assoc, astuple, evolve, has
|
||||
from ._make import (
|
||||
NOTHING, Attribute, Factory, attrib, attrs, fields, fields_dict,
|
||||
make_class, validate
|
||||
)
|
||||
|
||||
|
||||
__version__ = "18.1.0.dev0"
|
||||
|
||||
__title__ = "attrs"
|
||||
__description__ = "Classes Without Boilerplate"
|
||||
__uri__ = "http://www.attrs.org/"
|
||||
__doc__ = __description__ + " <" + __uri__ + ">"
|
||||
|
||||
__author__ = "Hynek Schlawack"
|
||||
__email__ = "hs@ox.cx"
|
||||
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2015 Hynek Schlawack"
|
||||
|
||||
|
||||
s = attributes = attrs
|
||||
ib = attr = attrib
|
||||
dataclass = partial(attrs, auto_attribs=True) # happy Easter ;)
|
||||
|
||||
__all__ = [
|
||||
"Attribute",
|
||||
"Factory",
|
||||
"NOTHING",
|
||||
"asdict",
|
||||
"assoc",
|
||||
"astuple",
|
||||
"attr",
|
||||
"attrib",
|
||||
"attributes",
|
||||
"attrs",
|
||||
"converters",
|
||||
"evolve",
|
||||
"exceptions",
|
||||
"fields",
|
||||
"fields_dict",
|
||||
"filters",
|
||||
"get_run_validators",
|
||||
"has",
|
||||
"ib",
|
||||
"make_class",
|
||||
"s",
|
||||
"set_run_validators",
|
||||
"validate",
|
||||
"validators",
|
||||
]
|
||||
Vendored
+146
@@ -0,0 +1,146 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import platform
|
||||
import sys
|
||||
import types
|
||||
import warnings
|
||||
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
PYPY = platform.python_implementation() == "PyPy"
|
||||
|
||||
|
||||
if PYPY or sys.version_info[:2] >= (3, 6):
|
||||
ordered_dict = dict
|
||||
else:
|
||||
from collections import OrderedDict
|
||||
ordered_dict = OrderedDict
|
||||
|
||||
|
||||
if PY2:
|
||||
from UserDict import IterableUserDict
|
||||
|
||||
# We 'bundle' isclass instead of using inspect as importing inspect is
|
||||
# fairly expensive (order of 10-15 ms for a modern machine in 2016)
|
||||
def isclass(klass):
|
||||
return isinstance(klass, (type, types.ClassType))
|
||||
|
||||
# TYPE is used in exceptions, repr(int) is different on Python 2 and 3.
|
||||
TYPE = "type"
|
||||
|
||||
def iteritems(d):
|
||||
return d.iteritems()
|
||||
|
||||
# Python 2 is bereft of a read-only dict proxy, so we make one!
|
||||
class ReadOnlyDict(IterableUserDict):
|
||||
"""
|
||||
Best-effort read-only dict wrapper.
|
||||
"""
|
||||
|
||||
def __setitem__(self, key, val):
|
||||
# We gently pretend we're a Python 3 mappingproxy.
|
||||
raise TypeError("'mappingproxy' object does not support item "
|
||||
"assignment")
|
||||
|
||||
def update(self, _):
|
||||
# We gently pretend we're a Python 3 mappingproxy.
|
||||
raise AttributeError("'mappingproxy' object has no attribute "
|
||||
"'update'")
|
||||
|
||||
def __delitem__(self, _):
|
||||
# We gently pretend we're a Python 3 mappingproxy.
|
||||
raise TypeError("'mappingproxy' object does not support item "
|
||||
"deletion")
|
||||
|
||||
def clear(self):
|
||||
# We gently pretend we're a Python 3 mappingproxy.
|
||||
raise AttributeError("'mappingproxy' object has no attribute "
|
||||
"'clear'")
|
||||
|
||||
def pop(self, key, default=None):
|
||||
# We gently pretend we're a Python 3 mappingproxy.
|
||||
raise AttributeError("'mappingproxy' object has no attribute "
|
||||
"'pop'")
|
||||
|
||||
def popitem(self):
|
||||
# We gently pretend we're a Python 3 mappingproxy.
|
||||
raise AttributeError("'mappingproxy' object has no attribute "
|
||||
"'popitem'")
|
||||
|
||||
def setdefault(self, key, default=None):
|
||||
# We gently pretend we're a Python 3 mappingproxy.
|
||||
raise AttributeError("'mappingproxy' object has no attribute "
|
||||
"'setdefault'")
|
||||
|
||||
def __repr__(self):
|
||||
# Override to be identical to the Python 3 version.
|
||||
return "mappingproxy(" + repr(self.data) + ")"
|
||||
|
||||
def metadata_proxy(d):
|
||||
res = ReadOnlyDict()
|
||||
res.data.update(d) # We blocked update, so we have to do it like this.
|
||||
return res
|
||||
|
||||
else:
|
||||
def isclass(klass):
|
||||
return isinstance(klass, type)
|
||||
|
||||
TYPE = "class"
|
||||
|
||||
def iteritems(d):
|
||||
return d.items()
|
||||
|
||||
def metadata_proxy(d):
|
||||
return types.MappingProxyType(dict(d))
|
||||
|
||||
|
||||
def import_ctypes():
|
||||
"""
|
||||
Moved into a function for testability.
|
||||
"""
|
||||
import ctypes
|
||||
return ctypes
|
||||
|
||||
|
||||
if not PY2:
|
||||
def just_warn(*args, **kw):
|
||||
"""
|
||||
We only warn on Python 3 because we are not aware of any concrete
|
||||
consequences of not setting the cell on Python 2.
|
||||
"""
|
||||
warnings.warn(
|
||||
"Missing ctypes. Some features like bare super() or accessing "
|
||||
"__class__ will not work with slots classes.",
|
||||
RuntimeWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
def just_warn(*args, **kw): # pragma: nocover
|
||||
"""
|
||||
We only warn on Python 3 because we are not aware of any concrete
|
||||
consequences of not setting the cell on Python 2.
|
||||
"""
|
||||
|
||||
|
||||
def make_set_closure_cell():
|
||||
"""
|
||||
Moved into a function for testability.
|
||||
"""
|
||||
if PYPY: # pragma: no cover
|
||||
def set_closure_cell(cell, value):
|
||||
cell.__setstate__((value,))
|
||||
else:
|
||||
try:
|
||||
ctypes = import_ctypes()
|
||||
|
||||
set_closure_cell = ctypes.pythonapi.PyCell_Set
|
||||
set_closure_cell.argtypes = (ctypes.py_object, ctypes.py_object)
|
||||
set_closure_cell.restype = ctypes.c_int
|
||||
except Exception:
|
||||
# We try best effort to set the cell, but sometimes it's not
|
||||
# possible. For example on Jython or on GAE.
|
||||
set_closure_cell = just_warn
|
||||
return set_closure_cell
|
||||
|
||||
|
||||
set_closure_cell = make_set_closure_cell()
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
|
||||
__all__ = ["set_run_validators", "get_run_validators"]
|
||||
|
||||
_run_validators = True
|
||||
|
||||
|
||||
def set_run_validators(run):
|
||||
"""
|
||||
Set whether or not validators are run. By default, they are run.
|
||||
"""
|
||||
if not isinstance(run, bool):
|
||||
raise TypeError("'run' must be bool.")
|
||||
global _run_validators
|
||||
_run_validators = run
|
||||
|
||||
|
||||
def get_run_validators():
|
||||
"""
|
||||
Return whether or not validators are run.
|
||||
"""
|
||||
return _run_validators
|
||||
Vendored
+212
@@ -0,0 +1,212 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import copy
|
||||
|
||||
from ._compat import iteritems
|
||||
from ._make import NOTHING, _obj_setattr, fields
|
||||
from .exceptions import AttrsAttributeNotFoundError
|
||||
|
||||
|
||||
def asdict(inst, recurse=True, filter=None, dict_factory=dict,
|
||||
retain_collection_types=False):
|
||||
"""
|
||||
Return the ``attrs`` attribute values of *inst* as a dict.
|
||||
|
||||
Optionally recurse into other ``attrs``-decorated classes.
|
||||
|
||||
:param inst: Instance of an ``attrs``-decorated class.
|
||||
:param bool recurse: Recurse into classes that are also
|
||||
``attrs``-decorated.
|
||||
:param callable filter: A callable whose return code determines whether an
|
||||
attribute or element is included (``True``) or dropped (``False``). Is
|
||||
called with the :class:`attr.Attribute` as the first argument and the
|
||||
value as the second argument.
|
||||
:param callable dict_factory: A callable to produce dictionaries from. For
|
||||
example, to produce ordered dictionaries instead of normal Python
|
||||
dictionaries, pass in ``collections.OrderedDict``.
|
||||
:param bool retain_collection_types: Do not convert to ``list`` when
|
||||
encountering an attribute whose type is ``tuple`` or ``set``. Only
|
||||
meaningful if ``recurse`` is ``True``.
|
||||
|
||||
:rtype: return type of *dict_factory*
|
||||
|
||||
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
|
||||
class.
|
||||
|
||||
.. versionadded:: 16.0.0 *dict_factory*
|
||||
.. versionadded:: 16.1.0 *retain_collection_types*
|
||||
"""
|
||||
attrs = fields(inst.__class__)
|
||||
rv = dict_factory()
|
||||
for a in attrs:
|
||||
v = getattr(inst, a.name)
|
||||
if filter is not None and not filter(a, v):
|
||||
continue
|
||||
if recurse is True:
|
||||
if has(v.__class__):
|
||||
rv[a.name] = asdict(v, recurse=True, filter=filter,
|
||||
dict_factory=dict_factory)
|
||||
elif isinstance(v, (tuple, list, set)):
|
||||
cf = v.__class__ if retain_collection_types is True else list
|
||||
rv[a.name] = cf([
|
||||
asdict(i, recurse=True, filter=filter,
|
||||
dict_factory=dict_factory)
|
||||
if has(i.__class__) else i
|
||||
for i in v
|
||||
])
|
||||
elif isinstance(v, dict):
|
||||
df = dict_factory
|
||||
rv[a.name] = df((
|
||||
asdict(kk, dict_factory=df) if has(kk.__class__) else kk,
|
||||
asdict(vv, dict_factory=df) if has(vv.__class__) else vv)
|
||||
for kk, vv in iteritems(v))
|
||||
else:
|
||||
rv[a.name] = v
|
||||
else:
|
||||
rv[a.name] = v
|
||||
return rv
|
||||
|
||||
|
||||
def astuple(inst, recurse=True, filter=None, tuple_factory=tuple,
|
||||
retain_collection_types=False):
|
||||
"""
|
||||
Return the ``attrs`` attribute values of *inst* as a tuple.
|
||||
|
||||
Optionally recurse into other ``attrs``-decorated classes.
|
||||
|
||||
:param inst: Instance of an ``attrs``-decorated class.
|
||||
:param bool recurse: Recurse into classes that are also
|
||||
``attrs``-decorated.
|
||||
:param callable filter: A callable whose return code determines whether an
|
||||
attribute or element is included (``True``) or dropped (``False``). Is
|
||||
called with the :class:`attr.Attribute` as the first argument and the
|
||||
value as the second argument.
|
||||
:param callable tuple_factory: A callable to produce tuples from. For
|
||||
example, to produce lists instead of tuples.
|
||||
:param bool retain_collection_types: Do not convert to ``list``
|
||||
or ``dict`` when encountering an attribute which type is
|
||||
``tuple``, ``dict`` or ``set``. Only meaningful if ``recurse`` is
|
||||
``True``.
|
||||
|
||||
:rtype: return type of *tuple_factory*
|
||||
|
||||
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
|
||||
class.
|
||||
|
||||
.. versionadded:: 16.2.0
|
||||
"""
|
||||
attrs = fields(inst.__class__)
|
||||
rv = []
|
||||
retain = retain_collection_types # Very long. :/
|
||||
for a in attrs:
|
||||
v = getattr(inst, a.name)
|
||||
if filter is not None and not filter(a, v):
|
||||
continue
|
||||
if recurse is True:
|
||||
if has(v.__class__):
|
||||
rv.append(astuple(v, recurse=True, filter=filter,
|
||||
tuple_factory=tuple_factory,
|
||||
retain_collection_types=retain))
|
||||
elif isinstance(v, (tuple, list, set)):
|
||||
cf = v.__class__ if retain is True else list
|
||||
rv.append(cf([
|
||||
astuple(j, recurse=True, filter=filter,
|
||||
tuple_factory=tuple_factory,
|
||||
retain_collection_types=retain)
|
||||
if has(j.__class__) else j
|
||||
for j in v
|
||||
]))
|
||||
elif isinstance(v, dict):
|
||||
df = v.__class__ if retain is True else dict
|
||||
rv.append(df(
|
||||
(
|
||||
astuple(
|
||||
kk,
|
||||
tuple_factory=tuple_factory,
|
||||
retain_collection_types=retain
|
||||
) if has(kk.__class__) else kk,
|
||||
astuple(
|
||||
vv,
|
||||
tuple_factory=tuple_factory,
|
||||
retain_collection_types=retain
|
||||
) if has(vv.__class__) else vv
|
||||
)
|
||||
for kk, vv in iteritems(v)))
|
||||
else:
|
||||
rv.append(v)
|
||||
else:
|
||||
rv.append(v)
|
||||
return rv if tuple_factory is list else tuple_factory(rv)
|
||||
|
||||
|
||||
def has(cls):
|
||||
"""
|
||||
Check whether *cls* is a class with ``attrs`` attributes.
|
||||
|
||||
:param type cls: Class to introspect.
|
||||
:raise TypeError: If *cls* is not a class.
|
||||
|
||||
:rtype: :class:`bool`
|
||||
"""
|
||||
return getattr(cls, "__attrs_attrs__", None) is not None
|
||||
|
||||
|
||||
def assoc(inst, **changes):
|
||||
"""
|
||||
Copy *inst* and apply *changes*.
|
||||
|
||||
:param inst: Instance of a class with ``attrs`` attributes.
|
||||
:param changes: Keyword changes in the new copy.
|
||||
|
||||
:return: A copy of inst with *changes* incorporated.
|
||||
|
||||
:raise attr.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't
|
||||
be found on *cls*.
|
||||
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
|
||||
class.
|
||||
|
||||
.. deprecated:: 17.1.0
|
||||
Use :func:`evolve` instead.
|
||||
"""
|
||||
import warnings
|
||||
warnings.warn("assoc is deprecated and will be removed after 2018/01.",
|
||||
DeprecationWarning, stacklevel=2)
|
||||
new = copy.copy(inst)
|
||||
attrs = fields(inst.__class__)
|
||||
for k, v in iteritems(changes):
|
||||
a = getattr(attrs, k, NOTHING)
|
||||
if a is NOTHING:
|
||||
raise AttrsAttributeNotFoundError(
|
||||
"{k} is not an attrs attribute on {cl}."
|
||||
.format(k=k, cl=new.__class__)
|
||||
)
|
||||
_obj_setattr(new, k, v)
|
||||
return new
|
||||
|
||||
|
||||
def evolve(inst, **changes):
|
||||
"""
|
||||
Create a new instance, based on *inst* with *changes* applied.
|
||||
|
||||
:param inst: Instance of a class with ``attrs`` attributes.
|
||||
:param changes: Keyword changes in the new copy.
|
||||
|
||||
:return: A copy of inst with *changes* incorporated.
|
||||
|
||||
:raise TypeError: If *attr_name* couldn't be found in the class
|
||||
``__init__``.
|
||||
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
|
||||
class.
|
||||
|
||||
.. versionadded:: 17.1.0
|
||||
"""
|
||||
cls = inst.__class__
|
||||
attrs = fields(cls)
|
||||
for a in attrs:
|
||||
if not a.init:
|
||||
continue
|
||||
attr_name = a.name # To deal with private attributes.
|
||||
init_name = attr_name if attr_name[0] != "_" else attr_name[1:]
|
||||
if init_name not in changes:
|
||||
changes[init_name] = getattr(inst, attr_name)
|
||||
return cls(**changes)
|
||||
Vendored
+1680
File diff suppressed because it is too large
Load Diff
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
"""
|
||||
Commonly useful converters.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
|
||||
def optional(converter):
|
||||
"""
|
||||
A converter that allows an attribute to be optional. An optional attribute
|
||||
is one which can be set to ``None``.
|
||||
|
||||
:param callable converter: the converter that is used for non-``None``
|
||||
values.
|
||||
|
||||
.. versionadded:: 17.1.0
|
||||
"""
|
||||
|
||||
def optional_converter(val):
|
||||
if val is None:
|
||||
return None
|
||||
return converter(val)
|
||||
|
||||
return optional_converter
|
||||
Vendored
+48
@@ -0,0 +1,48 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
|
||||
class FrozenInstanceError(AttributeError):
|
||||
"""
|
||||
A frozen/immutable instance has been attempted to be modified.
|
||||
|
||||
It mirrors the behavior of ``namedtuples`` by using the same error message
|
||||
and subclassing :exc:`AttributeError`.
|
||||
|
||||
.. versionadded:: 16.1.0
|
||||
"""
|
||||
msg = "can't set attribute"
|
||||
args = [msg]
|
||||
|
||||
|
||||
class AttrsAttributeNotFoundError(ValueError):
|
||||
"""
|
||||
An ``attrs`` function couldn't find an attribute that the user asked for.
|
||||
|
||||
.. versionadded:: 16.2.0
|
||||
"""
|
||||
|
||||
|
||||
class NotAnAttrsClassError(ValueError):
|
||||
"""
|
||||
A non-``attrs`` class has been passed into an ``attrs`` function.
|
||||
|
||||
.. versionadded:: 16.2.0
|
||||
"""
|
||||
|
||||
|
||||
class DefaultAlreadySetError(RuntimeError):
|
||||
"""
|
||||
A default has been set using ``attr.ib()`` and is attempted to be reset
|
||||
using the decorator.
|
||||
|
||||
.. versionadded:: 17.1.0
|
||||
"""
|
||||
|
||||
|
||||
class UnannotatedAttributeError(RuntimeError):
|
||||
"""
|
||||
A class with ``auto_attribs=True`` has an ``attr.ib()`` without a type
|
||||
annotation.
|
||||
|
||||
.. versionadded:: 17.3.0
|
||||
"""
|
||||
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
"""
|
||||
Commonly useful filters for :func:`attr.asdict`.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
from ._compat import isclass
|
||||
from ._make import Attribute
|
||||
|
||||
|
||||
def _split_what(what):
|
||||
"""
|
||||
Returns a tuple of `frozenset`s of classes and attributes.
|
||||
"""
|
||||
return (
|
||||
frozenset(cls for cls in what if isclass(cls)),
|
||||
frozenset(cls for cls in what if isinstance(cls, Attribute)),
|
||||
)
|
||||
|
||||
|
||||
def include(*what):
|
||||
"""
|
||||
Whitelist *what*.
|
||||
|
||||
:param what: What to whitelist.
|
||||
:type what: :class:`list` of :class:`type` or :class:`attr.Attribute`\ s
|
||||
|
||||
:rtype: :class:`callable`
|
||||
"""
|
||||
cls, attrs = _split_what(what)
|
||||
|
||||
def include_(attribute, value):
|
||||
return value.__class__ in cls or attribute in attrs
|
||||
|
||||
return include_
|
||||
|
||||
|
||||
def exclude(*what):
|
||||
"""
|
||||
Blacklist *what*.
|
||||
|
||||
:param what: What to blacklist.
|
||||
:type what: :class:`list` of classes or :class:`attr.Attribute`\ s.
|
||||
|
||||
:rtype: :class:`callable`
|
||||
"""
|
||||
cls, attrs = _split_what(what)
|
||||
|
||||
def exclude_(attribute, value):
|
||||
return value.__class__ not in cls and attribute not in attrs
|
||||
|
||||
return exclude_
|
||||
Vendored
+166
@@ -0,0 +1,166 @@
|
||||
"""
|
||||
Commonly useful validators.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
from ._make import _AndValidator, and_, attrib, attrs
|
||||
|
||||
|
||||
__all__ = [
|
||||
"and_",
|
||||
"in_",
|
||||
"instance_of",
|
||||
"optional",
|
||||
"provides",
|
||||
]
|
||||
|
||||
|
||||
@attrs(repr=False, slots=True, hash=True)
|
||||
class _InstanceOfValidator(object):
|
||||
type = attrib()
|
||||
|
||||
def __call__(self, inst, attr, value):
|
||||
"""
|
||||
We use a callable class to be able to change the ``__repr__``.
|
||||
"""
|
||||
if not isinstance(value, self.type):
|
||||
raise TypeError(
|
||||
"'{name}' must be {type!r} (got {value!r} that is a "
|
||||
"{actual!r})."
|
||||
.format(name=attr.name, type=self.type,
|
||||
actual=value.__class__, value=value),
|
||||
attr, self.type, value,
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
"<instance_of validator for type {type!r}>"
|
||||
.format(type=self.type)
|
||||
)
|
||||
|
||||
|
||||
def instance_of(type):
|
||||
"""
|
||||
A validator that raises a :exc:`TypeError` if the initializer is called
|
||||
with a wrong type for this particular attribute (checks are performed using
|
||||
:func:`isinstance` therefore it's also valid to pass a tuple of types).
|
||||
|
||||
:param type: The type to check for.
|
||||
:type type: type or tuple of types
|
||||
|
||||
:raises TypeError: With a human readable error message, the attribute
|
||||
(of type :class:`attr.Attribute`), the expected type, and the value it
|
||||
got.
|
||||
"""
|
||||
return _InstanceOfValidator(type)
|
||||
|
||||
|
||||
@attrs(repr=False, slots=True, hash=True)
|
||||
class _ProvidesValidator(object):
|
||||
interface = attrib()
|
||||
|
||||
def __call__(self, inst, attr, value):
|
||||
"""
|
||||
We use a callable class to be able to change the ``__repr__``.
|
||||
"""
|
||||
if not self.interface.providedBy(value):
|
||||
raise TypeError(
|
||||
"'{name}' must provide {interface!r} which {value!r} "
|
||||
"doesn't."
|
||||
.format(name=attr.name, interface=self.interface, value=value),
|
||||
attr, self.interface, value,
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
"<provides validator for interface {interface!r}>"
|
||||
.format(interface=self.interface)
|
||||
)
|
||||
|
||||
|
||||
def provides(interface):
|
||||
"""
|
||||
A validator that raises a :exc:`TypeError` if the initializer is called
|
||||
with an object that does not provide the requested *interface* (checks are
|
||||
performed using ``interface.providedBy(value)`` (see `zope.interface
|
||||
<https://zopeinterface.readthedocs.io/en/latest/>`_).
|
||||
|
||||
:param zope.interface.Interface interface: The interface to check for.
|
||||
|
||||
:raises TypeError: With a human readable error message, the attribute
|
||||
(of type :class:`attr.Attribute`), the expected interface, and the
|
||||
value it got.
|
||||
"""
|
||||
return _ProvidesValidator(interface)
|
||||
|
||||
|
||||
@attrs(repr=False, slots=True, hash=True)
|
||||
class _OptionalValidator(object):
|
||||
validator = attrib()
|
||||
|
||||
def __call__(self, inst, attr, value):
|
||||
if value is None:
|
||||
return
|
||||
|
||||
self.validator(inst, attr, value)
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
"<optional validator for {what} or None>"
|
||||
.format(what=repr(self.validator))
|
||||
)
|
||||
|
||||
|
||||
def optional(validator):
|
||||
"""
|
||||
A validator that makes an attribute optional. An optional attribute is one
|
||||
which can be set to ``None`` in addition to satisfying the requirements of
|
||||
the sub-validator.
|
||||
|
||||
:param validator: A validator (or a list of validators) that is used for
|
||||
non-``None`` values.
|
||||
:type validator: callable or :class:`list` of callables.
|
||||
|
||||
.. versionadded:: 15.1.0
|
||||
.. versionchanged:: 17.1.0 *validator* can be a list of validators.
|
||||
"""
|
||||
if isinstance(validator, list):
|
||||
return _OptionalValidator(_AndValidator(validator))
|
||||
return _OptionalValidator(validator)
|
||||
|
||||
|
||||
@attrs(repr=False, slots=True, hash=True)
|
||||
class _InValidator(object):
|
||||
options = attrib()
|
||||
|
||||
def __call__(self, inst, attr, value):
|
||||
if value not in self.options:
|
||||
raise ValueError(
|
||||
"'{name}' must be in {options!r} (got {value!r})"
|
||||
.format(name=attr.name, options=self.options, value=value)
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
"<in_ validator with options {options!r}>"
|
||||
.format(options=self.options)
|
||||
)
|
||||
|
||||
|
||||
def in_(options):
|
||||
"""
|
||||
A validator that raises a :exc:`ValueError` if the initializer is called
|
||||
with a value that does not belong in the options provided. The check is
|
||||
performed using ``value in options``.
|
||||
|
||||
:param options: Allowed options.
|
||||
:type options: list, tuple, :class:`enum.Enum`, ...
|
||||
|
||||
:raises ValueError: With a human readable error message, the attribute (of
|
||||
type :class:`attr.Attribute`), the expected options, and the value it
|
||||
got.
|
||||
|
||||
.. versionadded:: 17.1.0
|
||||
"""
|
||||
return _InValidator(options)
|
||||
Vendored
+2
-1
@@ -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
@@ -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
@@ -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("'", "'\"'\"'") + "'"
|
||||
Vendored
+105
-208
@@ -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")
|
||||
|
||||
@@ -22,7 +22,7 @@ required = [
|
||||
'setuptools>=36.2.1',
|
||||
'virtualenv-clone>=0.2.5',
|
||||
'virtualenv',
|
||||
'pathlib;python_version<"3.4"',
|
||||
'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.
@@ -964,7 +964,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)
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
import os
|
||||
import pytest
|
||||
from first import first
|
||||
from pipenv import requirements
|
||||
from pipenv.utils import get_requirement, convert_deps_from_pip, convert_deps_to_pip
|
||||
|
||||
|
||||
class TestRequirements:
|
||||
|
||||
@pytest.mark.requirement
|
||||
@pytest.mark.parametrize(
|
||||
'line, pipfile',
|
||||
[
|
||||
['requests', {'requests': '*'}],
|
||||
['requests[socks]', {'requests': {'extras': ['socks'], 'version': '*'}}],
|
||||
['django>1.10', {'django': '>1.10'}],
|
||||
['requests[socks]>1.10', {'requests': {'extras': ['socks'], 'version': '>1.10'}}],
|
||||
['-e git+git://github.com/pinax/pinax.git@1.4#egg=pinax', {'pinax': {'git': 'git://github.com/pinax/pinax.git', 'ref': '1.4', 'editable': True}}],
|
||||
['git+git://github.com/pinax/pinax.git@1.4#egg=pinax', {'pinax': {'git': 'git://github.com/pinax/pinax.git', 'ref': '1.4'}}],
|
||||
['FooProject==1.2 --hash=sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824', {'FooProject': {'version': '==1.2', 'hash': 'sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'}}],
|
||||
['FooProject[stuff]==1.2 --hash=sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824', {'FooProject': {'version': '==1.2', 'extras': ['stuff'], 'hash': 'sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'}}],
|
||||
['git+https://github.com/requests/requests.git@master#egg=requests[security]', {'requests': {'git': 'https://github.com/requests/requests.git', 'ref': 'master', 'extras': ['security']}}],
|
||||
['-e svn+svn://svn.myproject.org/svn/MyProject#egg=MyProject', {u'MyProject': {u'svn': u'svn://svn.myproject.org/svn/MyProject', 'editable': True}}],
|
||||
['hg+http://hg.myproject.org/MyProject@da39a3ee5e6b#egg=MyProject', {'MyProject': {'hg': 'http://hg.myproject.org/MyProject', 'ref': 'da39a3ee5e6b'}}]
|
||||
]
|
||||
)
|
||||
def test_pip_requirements(self, line, pipfile):
|
||||
from_line = requirements.PipenvRequirement.from_line(line)
|
||||
pipfile_pkgname = first([k for k in pipfile.keys()])
|
||||
pipfile_entry = pipfile[pipfile_pkgname]
|
||||
from_pipfile = requirements.PipenvRequirement.from_pipfile(pipfile_pkgname, [], pipfile_entry)
|
||||
assert from_line.as_pipfile() == pipfile
|
||||
assert from_pipfile.as_requirement() == line
|
||||
+1
-1
@@ -86,7 +86,7 @@ class TestUtils:
|
||||
# requests[socks]
|
||||
dep = 'requests[socks]'
|
||||
dep = pipenv.utils.convert_deps_from_pip(dep)
|
||||
assert dep == {'requests': {'extras': ['socks']}}
|
||||
assert dep == {'requests': {'extras': ['socks'], 'version': '*'}}
|
||||
# requests[socks] w/ version
|
||||
dep = 'requests[socks]==1.10'
|
||||
dep = pipenv.utils.convert_deps_from_pip(dep)
|
||||
|
||||
Reference in New Issue
Block a user