mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 22:50:18 +00:00
Merge branch 'master' into unused-vendors
This commit is contained in:
@@ -11,5 +11,5 @@ steps:
|
||||
export GIT_SSL_CAINFO=$(python -m certifi)
|
||||
export LANG=C.UTF-8
|
||||
python -m pip install --upgrade setuptools twine readme_renderer[md]
|
||||
python setup.py sdist
|
||||
python setup.py sdist bdist_wheel
|
||||
twine check dist/*
|
||||
|
||||
@@ -13,6 +13,13 @@ steps:
|
||||
|
||||
- template: ../steps/install-dependencies.yml
|
||||
|
||||
- template: ../steps/create-virtualenv.yml
|
||||
- template: ../steps/create-virtualenv-windows.yml
|
||||
|
||||
- template: ../steps/run-tests.yml
|
||||
- template: ../steps/run-tests-windows.yml
|
||||
|
||||
- task: PublishTestResults@2
|
||||
displayName: Publish Test Results
|
||||
inputs:
|
||||
testResultsFiles: '**/test-results.xml'
|
||||
testRunTitle: 'Python $(python.version)'
|
||||
condition: succeededOrFailed()
|
||||
|
||||
@@ -9,14 +9,9 @@ jobs:
|
||||
strategy:
|
||||
maxParallel: 4
|
||||
matrix:
|
||||
${{ if eq(parameters.vmImage, 'vs2017-win2016') }}:
|
||||
Python37:
|
||||
python.version: '>= 3.7.2'
|
||||
python.architecture: x64
|
||||
${{ if ne(parameters.vmImage, 'vs2017-win2016' )}}:
|
||||
Python37:
|
||||
python.version: '>= 3.7'
|
||||
python.architecture: x64
|
||||
Python37:
|
||||
python.version: '>= 3.7'
|
||||
python.architecture: x64
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
displayName: Use Python $(python.version)
|
||||
|
||||
@@ -12,37 +12,15 @@ jobs:
|
||||
Python27:
|
||||
python.version: '2.7'
|
||||
python.architecture: x64
|
||||
${{ if eq(parameters.vmImage, 'vs2017-win2016') }}:
|
||||
# TODO remove once vs2017-win2016 has Python 3.7
|
||||
Python37:
|
||||
python.version: '>= 3.7.2'
|
||||
python.architecture: x64
|
||||
${{ if ne(parameters.vmImage, 'vs2017-win2016' )}}:
|
||||
Python37:
|
||||
python.version: '>= 3.7'
|
||||
python.architecture: x64
|
||||
steps:
|
||||
- ${{ if eq(parameters.vmImage, 'vs2017-win2016') }}:
|
||||
- template: ./run-tests-windows.yml
|
||||
|
||||
- ${{ if ne(parameters.vmImage, 'vs2017-win2016') }}:
|
||||
- template: ./run-tests.yml
|
||||
|
||||
- job: Test_Secondary
|
||||
displayName: Test python3.6
|
||||
# Run after Test_Primary so we don't devour time and jobs if tests are going to fail
|
||||
# dependsOn: Test_Primary
|
||||
pool:
|
||||
vmImage: ${{ parameters.vmImage }}
|
||||
strategy:
|
||||
maxParallel: 4
|
||||
matrix:
|
||||
Python37:
|
||||
python.version: '>= 3.7'
|
||||
python.architecture: x64
|
||||
Python36:
|
||||
python.version: '3.6'
|
||||
python.version: '>= 3.6'
|
||||
python.architecture: x64
|
||||
steps:
|
||||
- ${{ if eq(parameters.vmImage, 'vs2017-win2016') }}:
|
||||
- ${{ if eq(parameters.vmImage, 'windows-2019') }}:
|
||||
- template: ./run-tests-windows.yml
|
||||
|
||||
- ${{ if ne(parameters.vmImage, 'vs2017-win2016') }}:
|
||||
- ${{ if ne(parameters.vmImage, 'windows-2019') }}:
|
||||
- template: ./run-tests.yml
|
||||
|
||||
@@ -9,6 +9,8 @@ steps:
|
||||
echo "Path $PATH"
|
||||
echo "Installing Pipenv…"
|
||||
pipenv install --deploy --dev
|
||||
pipenv run pip install -e "$(pwd)[test]" --upgrade
|
||||
pipenv run pip install -e "$(pwd)[tests]" --upgrade
|
||||
echo pipenv --venv && echo pipenv --py && echo pipenv run python --version
|
||||
displayName: Make Virtualenv
|
||||
env:
|
||||
PYTHONWARNINGS: 'ignore:DEPRECATION'
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
steps:
|
||||
|
||||
- script: |
|
||||
echo "##vso[task.setvariable variable=LANG]C.UTF-8"
|
||||
echo "##vso[task.setvariable variable=PIP_PROCESS_DEPENDENCY_LINKS]1"
|
||||
displayName: Set Environment Variables
|
||||
|
||||
- powershell: |
|
||||
pip install certifi
|
||||
$env:PYTHON_PATH=$(python -c "import sys; print(sys.executable)")
|
||||
$env:CERTIFI_CONTENT=$(python -m certifi)
|
||||
echo "##vso[task.setvariable variable=GIT_SSL_CAINFO]$env:CERTIFI_CONTENT"
|
||||
echo "##vso[task.setvariable variable=PY_EXE]$env:PYTHON_PATH"
|
||||
displayName: Set Python Path
|
||||
env:
|
||||
PYTHONWARNINGS: 'ignore:DEPRECATION'
|
||||
|
||||
- script: |
|
||||
echo "Python path: $(PY_EXE)"
|
||||
echo "GIT_SSL_CAINFO: $(GIT_SSL_CAINFO)"
|
||||
$(PY_EXE) -m pipenv install --deploy --dev
|
||||
env:
|
||||
PIPENV_DEFAULT_PYTHON_VERSION: '$(PIPENV_DEFAULT_PYTHON_VERSION)'
|
||||
PYTHONWARNINGS: 'ignore:DEPRECATION'
|
||||
PIPENV_NOSPIN: '1'
|
||||
displayName: Make Virtualenv
|
||||
@@ -1,31 +1,37 @@
|
||||
steps:
|
||||
|
||||
- powershell: |
|
||||
$env:PY_EXE=$(python -c "import sys; print(sys.executable)")
|
||||
if (!$env:PY_EXE) {
|
||||
$env:PY_EXE="python"
|
||||
}
|
||||
Write-Host "##vso[task.setvariable variable=PY_EXE]$env:PY_EXE"
|
||||
Write-Host "Found Python: $env:PY_EXE"
|
||||
Invoke-Expression "& '$env:PY_EXE' -m virtualenv D:\.venv"
|
||||
Write-Host "##vso[task.setvariable variable=VIRTUAL_ENV]D:\.venv"
|
||||
Invoke-Expression "& 'D:\.venv\Scripts\activate.ps1'"
|
||||
$env:VIRTUAL_ENV="D:\.venv"
|
||||
Write-Host "Installing local package..."
|
||||
Invoke-Expression "& '$env:PY_EXE' -m pip install -e .[test] --upgrade"
|
||||
Write-Host "upgrading local package in virtual env"
|
||||
$venv_scripts = Join-Path -path D:\.venv -childpath Scripts
|
||||
$venv_py = Join-Path -path $venv_scripts -childpath python.exe
|
||||
Write-Host "##vso[task.setvariable variable=VIRTUAL_ENV_PY]$venv_py"
|
||||
Invoke-Expression "& '$venv_py' -m pip install -e .[test] --upgrade"
|
||||
Write-Host "Installing pipenv development packages"
|
||||
Invoke-Expression "& '$venv_py' -m pipenv install --dev"
|
||||
Write-Host "Installing local package in pipenv environment"
|
||||
Invoke-Expression "& '$venv_py' -m pipenv run pip install -e .[test]"
|
||||
Write-Host "Printing metadata"
|
||||
Write-Host $(Invoke-Expression "& '$venv_py' -m pipenv --venv")
|
||||
Write-Host $(Invoke-Expression "& '$venv_py' -m pipenv --py")
|
||||
Write-Host $(Invoke-Expression "& '$venv_py' -m pipenv run python --version")
|
||||
displayName: Make Virtualenv
|
||||
- script: |
|
||||
echo "##vso[task.setvariable variable=LANG]C.UTF-8"
|
||||
echo "##vso[task.setvariable variable=PIP_PROCESS_DEPENDENCY_LINKS]1"
|
||||
displayName: Set Environment Variables
|
||||
|
||||
- ${{ if eq(parameters.vmImage, 'windows-2019') }}:
|
||||
- powershell: |
|
||||
pip install certifi
|
||||
$env:PYTHON_PATH=$(python -c "import sys; print(sys.executable)")
|
||||
$env:CERTIFI_CONTENT=$(python -m certifi)
|
||||
echo "##vso[task.setvariable variable=GIT_SSL_CAINFO]$env:CERTIFI_CONTENT"
|
||||
echo "##vso[task.setvariable variable=PY_EXE]$env:PYTHON_PATH"
|
||||
displayName: Set Python Path
|
||||
env:
|
||||
PYTHONWARNINGS: 'ignore:DEPRECATION'
|
||||
- ${{ if ne(parameters.vmImage, 'windows-2019') }}:
|
||||
- bash: |
|
||||
pip install certifi
|
||||
PYTHON_PATH=$(python -c 'import sys; print(sys.executable)')
|
||||
CERTIFI_CONTENT=$(python -m certifi)
|
||||
echo "##vso[task.setvariable variable=GIT_SSL_CAINFO]$CERTIFI_CONTENT"
|
||||
echo "##vso[task.setvariable variable=PY_EXE]$PYTHON_PATH"
|
||||
displayName: Set Python Path
|
||||
env:
|
||||
PYTHONWARNINGS: 'ignore:DEPRECATION'
|
||||
|
||||
- script: |
|
||||
echo "Python path: $(PY_EXE)"
|
||||
echo "GIT_SSL_CAINFO: $(GIT_SSL_CAINFO)"
|
||||
$(PY_EXE) -m pipenv install --deploy --dev
|
||||
env:
|
||||
PIPENV_DEFAULT_PYTHON_VERSION: $(PIPENV_DEFAULT_PYTHON_VERSION)
|
||||
PIPENV_DEFAULT_PYTHON_VERSION: '$(PIPENV_DEFAULT_PYTHON_VERSION)'
|
||||
PYTHONWARNINGS: 'ignore:DEPRECATION'
|
||||
PIPENV_NOSPIN: '1'
|
||||
displayName: Make Virtualenv
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
steps:
|
||||
- script: 'python -m pip install --upgrade pip setuptools wheel && python -m pip install -e .[test] --upgrade'
|
||||
- script: 'python -m pip install --upgrade pip setuptools wheel && python -m pip install -e .[tests] --upgrade'
|
||||
displayName: Upgrade Pip & Install Pipenv
|
||||
env:
|
||||
PYTHONWARNINGS: 'ignore:DEPRECATION'
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
- script: |
|
||||
# Fix Git SSL errors
|
||||
git submodule sync && git submodule update --init --recursive
|
||||
pipenv run pytest --junitxml=test-results.xml
|
||||
displayName: Run integration tests
|
||||
env:
|
||||
PYTHONWARNINGS: 'ignore:DEPRECATION'
|
||||
PY_EXE: $(PY_EXE)
|
||||
GIT_SSL_CAINFO: $(GIT_SSL_CAINFO)
|
||||
LANG: $(LANG)
|
||||
PIP_PROCESS_DEPENDENCY_LINKS: $(PIP_PROCESS_DEPENDENCY_LINKS)
|
||||
PIPENV_DEFAULT_PYTHON_VERSION: $(PIPENV_DEFAULT_PYTHON_VERSION)
|
||||
PYTHONWARNINGS: ignore:DEPRECATION
|
||||
PIPENV_NOSPIN: '1'
|
||||
@@ -0,0 +1,15 @@
|
||||
steps:
|
||||
- powershell: |
|
||||
subst T: "$env:TEMP"
|
||||
Write-Host "##vso[task.setvariable variable=TEMP]T:\"
|
||||
Write-Host "##vso[task.setvariable variable=TMP]T:\"
|
||||
displayName: Fix Temp Variable
|
||||
|
||||
- script: |
|
||||
git submodule sync && git submodule update --init --recursive
|
||||
pipenv run pytest -ra --ignore=pipenv\patched --ignore=pipenv\vendor --junitxml=test-results.xml tests
|
||||
displayName: Run integration tests
|
||||
env:
|
||||
PIPENV_DEFAULT_PYTHON_VERSION: $(PIPENV_DEFAULT_PYTHON_VERSION)
|
||||
PYTHONWARNINGS: 'ignore:DEPRECATION'
|
||||
PIPENV_NOSPIN: '1'
|
||||
@@ -1,24 +1,24 @@
|
||||
steps:
|
||||
- powershell: |
|
||||
# Fix Git SSL errors
|
||||
Invoke-Expression "& '$env:VIRTUAL_ENV_PY' -m pip install certifi"
|
||||
Invoke-Expression "& '$env:VIRTUAL_ENV_PY' -m certifi > cacert.txt"
|
||||
Write-Host "##vso[task.setvariable variable=GIT_SSL_CAINFO]$(Get-Content cacert.txt)"
|
||||
$env:GIT_SSL_CAINFO="$(Get-Content cacert.txt)"
|
||||
# Shorten paths to get under MAX_PATH or else integration tests will fail
|
||||
# https://bugs.python.org/issue18199
|
||||
subst T: "$env:TEMP"
|
||||
Write-Host "##vso[task.setvariable variable=TEMP]T:\"
|
||||
$env:TEMP='T:\'
|
||||
Write-Host "##vso[task.setvariable variable=TMP]T:\"
|
||||
$env:TMP='T:\'
|
||||
git submodule sync
|
||||
git submodule update --init --recursive
|
||||
Invoke-Expression "& '$env:VIRTUAL_ENV_PY' -m pipenv run pytest -ra --ignore=pipenv\patched --ignore=pipenv\vendor --junitxml=test-results.xml tests"
|
||||
displayName: Run integration tests
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
versionSpec: $(python.version)
|
||||
architecture: '$(python.architecture)'
|
||||
addToPath: true
|
||||
displayName: Use Python $(python.version)
|
||||
|
||||
- template: install-dependencies.yml
|
||||
|
||||
- script: |
|
||||
echo '##vso[task.setvariable variable=PIPENV_DEFAULT_PYTHON_VERSION]$(PYTHON_VERSION)'
|
||||
env:
|
||||
VIRTUAL_ENV: $(VIRTUAL_ENV)
|
||||
VIRTUAL_ENV_PY: $(VIRTUAL_ENV_PY)
|
||||
PYTHON_VERSION: $(python.version)
|
||||
|
||||
- template: create-virtualenv.yml
|
||||
|
||||
- ${{ if eq(parameters.vmImage, 'windows-2019') }}:
|
||||
- template: run-tests-windows.yml
|
||||
- ${{ if ne(parameters.vmImage, 'windows-2019') }}:
|
||||
- template: run-tests-linux.yml
|
||||
|
||||
- task: PublishTestResults@2
|
||||
displayName: Publish Test Results
|
||||
|
||||
@@ -21,4 +21,4 @@ trigger:
|
||||
jobs:
|
||||
- template: jobs/test.yml
|
||||
parameters:
|
||||
vmImage: vs2017-win2016
|
||||
vmImage: windows-2019
|
||||
|
||||
@@ -1,22 +1,13 @@
|
||||
[dev-packages]
|
||||
pipenv = {path = ".", editable = true, extras = ["test"]}
|
||||
"flake8" = ">=3.3.0,<4"
|
||||
sphinx = "<=1.5.5"
|
||||
twine = "*"
|
||||
pipenv = {path = ".", editable = true, extras = ["tests", "dev"]}
|
||||
sphinx-click = "*"
|
||||
click = "*"
|
||||
pytest_pypi = {path = "./tests/pytest-pypi", editable = true}
|
||||
stdeb = {version="*", markers="sys_platform == 'linux'"}
|
||||
black = {version="*", markers="python_version >= '3.6'"}
|
||||
pytz = "*"
|
||||
towncrier = {git = "https://github.com/hawkowl/towncrier.git", editable = true, ref = "master"}
|
||||
parver = "*"
|
||||
invoke = "*"
|
||||
jedi = "*"
|
||||
isort = "*"
|
||||
rope = "*"
|
||||
passa = {editable = true, git = "https://github.com/sarugaku/passa.git"}
|
||||
bs4 = "*"
|
||||
passa = {git = "https://github.com/sarugaku/passa.git"}
|
||||
|
||||
[packages]
|
||||
|
||||
|
||||
Generated
+176
-138
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "0cdfbd95f33a9edb69dede4bc868f24f7b770ba6b06ce73f6de5f175544e996d"
|
||||
"sha256": "f4d89c0aab5c4e865f8c96ba24613fb1e66bae803a3ceaeadb6abf0061898091"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
@@ -27,6 +27,7 @@
|
||||
"sha256:37228cda29411948b422fae072f57e31d3396d2ee1c9783775980ee9c9990af6",
|
||||
"sha256:58587dd4dc3daefad0487f6d9ae32b4542b185e1c36db6993290e7c41ca2b47c"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.5"
|
||||
},
|
||||
"appdirs": {
|
||||
@@ -55,6 +56,7 @@
|
||||
"sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79",
|
||||
"sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==19.1.0"
|
||||
},
|
||||
"babel": {
|
||||
@@ -62,6 +64,7 @@
|
||||
"sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669",
|
||||
"sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.6.0"
|
||||
},
|
||||
"backports.functools-lru-cache": {
|
||||
@@ -69,7 +72,7 @@
|
||||
"sha256:9d98697f088eb1b0fa451391f91afb5e3ebde16bbdb272819fd091151fda4f1a",
|
||||
"sha256:f0b0e4eba956de51238e17573b7087e852dfe9854afd2e9c873f73fc0ca0a6dd"
|
||||
],
|
||||
"markers": "python_version < '3'",
|
||||
"markers": "python_version == '2.7'",
|
||||
"version": "==1.5"
|
||||
},
|
||||
"backports.shutil-get-terminal-size": {
|
||||
@@ -77,7 +80,7 @@
|
||||
"sha256:0975ba55054c15e346944b38956a4c9cbee9009391e41b86c68990effb8c1f64",
|
||||
"sha256:713e7a8228ae80341c70586d1cc0a8caa5207346927e23d09dcbcaf18eadec80"
|
||||
],
|
||||
"markers": "python_version < '3.3'",
|
||||
"markers": "python_version == '2.7'",
|
||||
"version": "==1.0.0"
|
||||
},
|
||||
"backports.weakref": {
|
||||
@@ -85,7 +88,7 @@
|
||||
"sha256:81bc9b51c0abc58edc76aefbbc68c62a787918ffe943a37947e162c3f8e19e82",
|
||||
"sha256:bc4170a29915f8b22c9e7c4939701859650f2eb84184aee80da329ac0b9825c2"
|
||||
],
|
||||
"markers": "python_version < '3.3'",
|
||||
"markers": "python_version == '2.7'",
|
||||
"version": "==1.0.post1"
|
||||
},
|
||||
"beautifulsoup4": {
|
||||
@@ -98,25 +101,24 @@
|
||||
},
|
||||
"black": {
|
||||
"hashes": [
|
||||
"sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739",
|
||||
"sha256:e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"
|
||||
"sha256:09a9dcb7c46ed496a9850b76e4e825d6049ecd38b611f1224857a79bd985a8cf",
|
||||
"sha256:68950ffd4d9169716bcb8719a56c07a2f4485354fec061cdd5910aa07369731c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==18.9b0"
|
||||
"version": "==19.3b0"
|
||||
},
|
||||
"bleach": {
|
||||
"hashes": [
|
||||
"sha256:213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16",
|
||||
"sha256:3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==3.1.0"
|
||||
},
|
||||
"bs4": {
|
||||
"hashes": [
|
||||
"sha256:36ecea1fd7cc5c0c6e4a1ff075df26d50da647b75376626cc186e2212886dd3a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.0.1"
|
||||
},
|
||||
"cached-property": {
|
||||
@@ -128,16 +130,16 @@
|
||||
},
|
||||
"cerberus": {
|
||||
"hashes": [
|
||||
"sha256:f5c2e048fb15ecb3c088d192164316093fcfa602a74b3386eefb2983aa7e800a"
|
||||
"sha256:0be48fc0dc84f83202a5309c0aa17cd5393e70731a1698a50d118b762fbe6875"
|
||||
],
|
||||
"version": "==1.2"
|
||||
"version": "==1.3.1"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7",
|
||||
"sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033"
|
||||
"sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5",
|
||||
"sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae"
|
||||
],
|
||||
"version": "==2018.11.29"
|
||||
"version": "==2019.3.9"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
@@ -163,24 +165,17 @@
|
||||
},
|
||||
"configparser": {
|
||||
"hashes": [
|
||||
"sha256:5bd5fa2a491dc3cfe920a3f2a107510d65eceae10e9c6e547b90261a4710df32",
|
||||
"sha256:c114ff90ee2e762db972fa205f02491b1f5cf3ff950decd8542c62970c9bedac",
|
||||
"sha256:df28e045fbff307a28795b18df6ac8662be3219435560ddb068c283afab1ea7a"
|
||||
"sha256:8be81d89d6e7b4c0d4e44bcc525845f6da25821de80cb5e06e7e0238a2899e32",
|
||||
"sha256:da60d0014fd8c55eb48c1c5354352e363e2d30bbf7057e5e171a468390184c75"
|
||||
],
|
||||
"markers": "python_version < '3.2'",
|
||||
"version": "==3.7.1"
|
||||
},
|
||||
"cursor": {
|
||||
"hashes": [
|
||||
"sha256:33f279a17789c04efd27a92501a0dad62bb011f8a4cdff93867c798d26508940"
|
||||
],
|
||||
"version": "==1.3.4"
|
||||
"markers": "python_version == '2.7'",
|
||||
"version": "==3.7.4"
|
||||
},
|
||||
"distlib": {
|
||||
"hashes": [
|
||||
"sha256:57977cd7d9ea27986ec62f425630e4ddb42efe651ff80bc58ed8dbc3c7c21f19"
|
||||
"sha256:ecb3d0e4f71d0fa7f38db6bcc276c7c9a1c6638a516d726495934a553eb3fbe0"
|
||||
],
|
||||
"version": "==0.2.8"
|
||||
"version": "==0.2.9.post0"
|
||||
},
|
||||
"docutils": {
|
||||
"hashes": [
|
||||
@@ -195,6 +190,7 @@
|
||||
"sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19",
|
||||
"sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"
|
||||
],
|
||||
"markers": "python_version >= '2.7'",
|
||||
"version": "==0.3"
|
||||
},
|
||||
"enum34": {
|
||||
@@ -209,31 +205,33 @@
|
||||
},
|
||||
"execnet": {
|
||||
"hashes": [
|
||||
"sha256:a7a84d5fa07a089186a329528f127c9d73b9de57f1a1131b82bb5320ee651f6a",
|
||||
"sha256:fc155a6b553c66c838d1a22dba1dc9f5f505c43285a878c6f74a79c024750b83"
|
||||
"sha256:027ee5d961afa01e97b90d6ccc34b4ed976702bc58e7f092b3c513ea288cb6d2",
|
||||
"sha256:752a3786f17416d491f833a29217dda3ea4a471fc5269c492eebcee8cc4772d3"
|
||||
],
|
||||
"version": "==1.5.0"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.6.0"
|
||||
},
|
||||
"first": {
|
||||
"hashes": [
|
||||
"sha256:3bb3de3582cb27071cfb514f00ed784dc444b7f96dc21e140de65fe00585c95e",
|
||||
"sha256:41d5b64e70507d0c3ca742d68010a76060eea8a3d863e9b5130ab11a4a91aa0e"
|
||||
"sha256:8d8e46e115ea8ac652c76123c0865e3ff18372aef6f03c22809ceefcea9dec86",
|
||||
"sha256:ff285b08c55f8c97ce4ea7012743af2495c9f1291785f163722bd36f6af6d3bf"
|
||||
],
|
||||
"version": "==2.0.1"
|
||||
"version": "==2.0.2"
|
||||
},
|
||||
"flake8": {
|
||||
"hashes": [
|
||||
"sha256:c3ba1e130c813191db95c431a18cb4d20a468e98af7a77e2181b68574481ad36",
|
||||
"sha256:fd9ddf503110bf3d8b1d270e8c673aab29ccb3dd6abf29bae1f54e5116ab4a91"
|
||||
"sha256:859996073f341f2670741b51ec1e67a01da142831aa1fdc6242dbf88dffbe661",
|
||||
"sha256:a796a115208f5c03b18f332f7c11729812c8c3ded6c46319c59b53efd3819da8"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.7.5"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==3.7.7"
|
||||
},
|
||||
"flaky": {
|
||||
"hashes": [
|
||||
"sha256:12bd5e41f372b2190e8d754b6e5829c2f11dbc764e10b30f57e59f829c9ca1da",
|
||||
"sha256:a94931c46a33469ec26f09b652bc88f55a8f5cc77807b90ca7bbafef1108fd7d"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==3.5.3"
|
||||
},
|
||||
"flask": {
|
||||
@@ -248,7 +246,7 @@
|
||||
"sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca",
|
||||
"sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"
|
||||
],
|
||||
"markers": "python_version < '3.0'",
|
||||
"markers": "python_version < '3.3'",
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"functools32": {
|
||||
@@ -256,7 +254,7 @@
|
||||
"sha256:89d824aa6c358c421a234d7f9ee0bd75933a67c29588ce50aaa3acdf4d403fa0",
|
||||
"sha256:f6253dfbe0538ad2e387bd8fdfd9293c925d63553f5813c4e587745416501e6d"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version < '2.8'",
|
||||
"markers": "python_version < '3.2'",
|
||||
"version": "==3.2.3.post2"
|
||||
},
|
||||
"futures": {
|
||||
@@ -264,7 +262,7 @@
|
||||
"sha256:9ec02aa7d674acb8618afb127e27fde7fc68994c0437ad759fa094a574adb265",
|
||||
"sha256:ec0a6cb848cc212002b9828c3e34c675e0c9ff6741dc445cab6fdd4e1085d1f1"
|
||||
],
|
||||
"markers": "python_version < '3.0'",
|
||||
"markers": "python_version < '3.2'",
|
||||
"version": "==3.2.0"
|
||||
},
|
||||
"idna": {
|
||||
@@ -279,6 +277,7 @@
|
||||
"sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8",
|
||||
"sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"incremental": {
|
||||
@@ -294,39 +293,38 @@
|
||||
"sha256:dc492f8f17a0746e92081aec3f86ae0b4750bf41607ea2ad87e5a7b5705121b7",
|
||||
"sha256:eb6f9262d4d25b40330fb21d1e99bf0f85011ccc3526980f8a3eaedd4b43892e"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.2.0"
|
||||
},
|
||||
"isort": {
|
||||
"hashes": [
|
||||
"sha256:1153601da39a25b14ddc54955dbbacbb6b2d19135386699e2ad58517953b34af",
|
||||
"sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8",
|
||||
"sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497"
|
||||
"sha256:1349c6f7c2a0f7539f5f2ace51a9a8e4a37086ce4de6f78f5f53fb041d0a3cd5",
|
||||
"sha256:f09911f6eb114e5592abe635aded8bf3d2c3144ebcfcaf81ee32e7af7b7d1870"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.3.4"
|
||||
"version": "==4.3.18"
|
||||
},
|
||||
"itsdangerous": {
|
||||
"hashes": [
|
||||
"sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
|
||||
"sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"jedi": {
|
||||
"hashes": [
|
||||
"sha256:571702b5bd167911fe9036e5039ba67f820d6502832285cde8c881ab2b2149fd",
|
||||
"sha256:c8481b5e59d34a5c7c42e98f6625e633f6ef59353abea6437472c7ec2093f191"
|
||||
"sha256:2bb0603e3506f708e792c7f4ad8fc2a7a9d9c2d292a358fbbd58da531695595b",
|
||||
"sha256:2c6bcd9545c7d6440951b12b44d373479bf18123a401a52025cf98563fbd826c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.13.2"
|
||||
"version": "==0.13.3"
|
||||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
|
||||
"sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
|
||||
"sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013",
|
||||
"sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"
|
||||
],
|
||||
"version": "==2.10"
|
||||
"version": "==2.10.1"
|
||||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
@@ -359,6 +357,7 @@
|
||||
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
|
||||
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.1.1"
|
||||
},
|
||||
"mccabe": {
|
||||
@@ -370,10 +369,11 @@
|
||||
},
|
||||
"mock": {
|
||||
"hashes": [
|
||||
"sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1",
|
||||
"sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba"
|
||||
"sha256:83657d894c90d5681d62155c82bda9c1187827525880eda8ff5df4ec813437c3",
|
||||
"sha256:d157e52d4e5b938c550f39eb2fd15610db062441a9c2747d3dbfa9298211d0f8"
|
||||
],
|
||||
"version": "==2.0.0"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==3.0.5"
|
||||
},
|
||||
"more-itertools": {
|
||||
"hashes": [
|
||||
@@ -395,25 +395,25 @@
|
||||
"sha256:0c98a5d0be38ed775798ece1b9727178c4469d9c3b4ada66e8e6b7849f8732af",
|
||||
"sha256:9e1cbf8c12b1f1ce0bb5344b8d7ecf66a6f8a6e91bcb0c84593ed6d3ab5c4ab3"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==19.0"
|
||||
},
|
||||
"parso": {
|
||||
"hashes": [
|
||||
"sha256:4580328ae3f548b358f4901e38c0578229186835f0fa0846e47369796dd5bcc9",
|
||||
"sha256:68406ebd7eafe17f8e40e15a84b56848eccbf27d7c1feb89e93d8fca395706db"
|
||||
"sha256:17cc2d7a945eb42c3569d4564cdf49bde221bc2b552af3eca9c1aad517dcdd33",
|
||||
"sha256:2e9574cb12e7112a87253e14e2c380ce312060269d04bd018478a3c92ea9a376"
|
||||
],
|
||||
"version": "==0.3.4"
|
||||
"version": "==0.4.0"
|
||||
},
|
||||
"parver": {
|
||||
"hashes": [
|
||||
"sha256:1b37a691af145a3a193eff269d53ba5b2ab16dfbb65d47d85360755919f5fe4b",
|
||||
"sha256:72d056b8f8883ac90eef5554a9c8a47fac39d3b66479f3d2c8d5bc21b849cdba"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.2.1"
|
||||
},
|
||||
"passa": {
|
||||
"editable": true,
|
||||
"git": "https://github.com/sarugaku/passa.git",
|
||||
"ref": "a2ba0b30c86339cae5ef3a03046fc9c583452c40",
|
||||
"version": "==0.3.1.dev0"
|
||||
@@ -428,10 +428,10 @@
|
||||
},
|
||||
"pbr": {
|
||||
"hashes": [
|
||||
"sha256:8257baf496c8522437e8a6cfe0f15e00aedc6c0e0e7c9d55eeeeab31e0853843",
|
||||
"sha256:8c361cc353d988e4f5b998555c88098b9d5964c2e11acf7b0d21925a66bb5824"
|
||||
"sha256:6901995b9b686cb90cceba67a0f6d4d14ae003cd59bc12beb61549bdfbe3bc89",
|
||||
"sha256:d950c64aeea5456bbd147468382a5bb77fe692c13c9f00f0219814ce5b642755"
|
||||
],
|
||||
"version": "==5.1.3"
|
||||
"version": "==5.2.0"
|
||||
},
|
||||
"pep517": {
|
||||
"hashes": [
|
||||
@@ -445,12 +445,16 @@
|
||||
"sha256:3bc24ec050a6b9eea35419467237e4f47eaf806dadc9999bf887355c377edea7",
|
||||
"sha256:edb4cf3c509eab2f36b55c1ac1a59a4c485ccd537cc87934d74950880f641256"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.3.2"
|
||||
},
|
||||
"pipenv": {
|
||||
"editable": true,
|
||||
"path": ".",
|
||||
"version": "==2018.11.27.dev0"
|
||||
"extras": [
|
||||
"dev",
|
||||
"tests"
|
||||
],
|
||||
"path": "."
|
||||
},
|
||||
"pkginfo": {
|
||||
"hashes": [
|
||||
@@ -467,20 +471,23 @@
|
||||
"sha256:c0e3553c1e581d8423daccbd825789c6e7f29b7d9e00e5331b12e1642a1a26d3",
|
||||
"sha256:dde5d525cf5f0cbad4d938c83b93db17887918daf63c13eafed257c4f61b07b4"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.2.2"
|
||||
},
|
||||
"pluggy": {
|
||||
"hashes": [
|
||||
"sha256:19ecf9ce9db2fce065a7a0586e07cfb4ac8614fe96edf628a264b1c70116cf8f",
|
||||
"sha256:84d306a647cc805219916e62aab89caa97a33a1dd8c342e87a37f91073cd4746"
|
||||
"sha256:25a1bc1d148c9a640211872b4ff859878d422bccb59c9965e04eed468a0aa180",
|
||||
"sha256:964cedd2b27c492fbf0b7f58b3284a09cf7f99b0f715941fb24a439b3af1bd1a"
|
||||
],
|
||||
"version": "==0.9.0"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.11.0"
|
||||
},
|
||||
"py": {
|
||||
"hashes": [
|
||||
"sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa",
|
||||
"sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.8.0"
|
||||
},
|
||||
"pycodestyle": {
|
||||
@@ -488,6 +495,7 @@
|
||||
"sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56",
|
||||
"sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.5.0"
|
||||
},
|
||||
"pyflakes": {
|
||||
@@ -495,6 +503,7 @@
|
||||
"sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0",
|
||||
"sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.1.1"
|
||||
},
|
||||
"pygments": {
|
||||
@@ -506,17 +515,19 @@
|
||||
},
|
||||
"pyparsing": {
|
||||
"hashes": [
|
||||
"sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a",
|
||||
"sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3"
|
||||
"sha256:1873c03321fc118f4e9746baf201ff990ceb915f433f23b395f5580d1840cb2a",
|
||||
"sha256:9b6323ef4ab914af344ba97510e966d64ba91055d6b9afa6b30799340e89cc03"
|
||||
],
|
||||
"version": "==2.3.1"
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.4.0"
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:3f193df1cfe1d1609d4c583838bea3d532b18d6160fd3f55c9447fdca30848ec",
|
||||
"sha256:e246cf173c01169b9617fc07264b7b1316e78d7a650055235d6d897bc80d9660"
|
||||
"sha256:1a8aa4fa958f8f451ac5441f3ac130d9fc86ea38780dd2715e6d5c5882700b24",
|
||||
"sha256:b8bf138592384bd4e87338cb0f256bf5f615398a649d4bd83915f0e4047a5ca6"
|
||||
],
|
||||
"version": "==3.10.1"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==4.5.0"
|
||||
},
|
||||
"pytest-forked": {
|
||||
"hashes": [
|
||||
@@ -527,8 +538,7 @@
|
||||
},
|
||||
"pytest-pypi": {
|
||||
"editable": true,
|
||||
"path": "./tests/pytest-pypi",
|
||||
"version": "==0.1.1"
|
||||
"path": "./tests/pytest-pypi"
|
||||
},
|
||||
"pytest-tap": {
|
||||
"hashes": [
|
||||
@@ -539,10 +549,11 @@
|
||||
},
|
||||
"pytest-xdist": {
|
||||
"hashes": [
|
||||
"sha256:4a201bb3ee60f5dd6bb40c5209d4e491cecc4d5bafd656cfb10f86178786e568",
|
||||
"sha256:d03d1ff1b008458ed04fa73e642d840ac69b4107c168e06b71037c62d7813dd4"
|
||||
"sha256:b0bb4b0293ee8657b9eb3ff334a3b6aac4db74fd4a86b81e1982c879237a47eb",
|
||||
"sha256:f83a485293e81fd57c8a5a85a3f12473a532c5ca7dec518857cbb72766bb526c"
|
||||
],
|
||||
"version": "==1.26.1"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.28.0"
|
||||
},
|
||||
"pytoml": {
|
||||
"hashes": [
|
||||
@@ -552,11 +563,10 @@
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9",
|
||||
"sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c"
|
||||
"sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda",
|
||||
"sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2018.9"
|
||||
"version": "==2019.1"
|
||||
},
|
||||
"readme-renderer": {
|
||||
"hashes": [
|
||||
@@ -570,6 +580,7 @@
|
||||
"sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e",
|
||||
"sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.21.0"
|
||||
},
|
||||
"requests-toolbelt": {
|
||||
@@ -581,10 +592,11 @@
|
||||
},
|
||||
"requirementslib": {
|
||||
"hashes": [
|
||||
"sha256:04c19bfe6f2c4dbfe3cc5115744c2079b0f3ce52c61c9d130a1ccf2b9896f812",
|
||||
"sha256:fdfc75d0ce418e80fa3b573ff078a5732ec1f11415afc2d4280809a1b5b2575d"
|
||||
"sha256:a5bcff2861eea9358e90d6a4234f0bf4ad0ba730f86a4ea4680f53365b7b4735",
|
||||
"sha256:ae0c2fce1b33c9c7b171895ab11472bd7be9c45f6214aad97ceaf83511d78d93"
|
||||
],
|
||||
"version": "==1.4.2"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.5.0"
|
||||
},
|
||||
"resolvelib": {
|
||||
"hashes": [
|
||||
@@ -595,33 +607,36 @@
|
||||
},
|
||||
"rope": {
|
||||
"hashes": [
|
||||
"sha256:031eb54b3eeec89f4304ede816995ed2b93a21e6fba16bd02aff10a0d6c257b7"
|
||||
"sha256:6b728fdc3e98a83446c27a91fc5d56808a004f8beab7a31ab1d7224cecc7d969",
|
||||
"sha256:c5c5a6a87f7b1a2095fb311135e2a3d1f194f5ecb96900fdd0a9100881f48aaf",
|
||||
"sha256:f0dcf719b63200d492b85535ebe5ea9b29e0d0b8aebeb87fe03fc1a65924fdaf"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.12.0"
|
||||
"version": "==0.14.0"
|
||||
},
|
||||
"scandir": {
|
||||
"hashes": [
|
||||
"sha256:04b8adb105f2ed313a7c2ef0f1cf7aff4871aa7a1883fa4d8c44b5551ab052d6",
|
||||
"sha256:1444134990356c81d12f30e4b311379acfbbcd03e0bab591de2696a3b126d58e",
|
||||
"sha256:1b5c314e39f596875e5a95dd81af03730b338c277c54a454226978d5ba95dbb6",
|
||||
"sha256:346619f72eb0ddc4cf355ceffd225fa52506c92a2ff05318cfabd02a144e7c4e",
|
||||
"sha256:44975e209c4827fc18a3486f257154d34ec6eaec0f90fef0cca1caa482db7064",
|
||||
"sha256:61859fd7e40b8c71e609c202db5b0c1dbec0d5c7f1449dec2245575bdc866792",
|
||||
"sha256:a5e232a0bf188362fa00123cc0bb842d363a292de7126126df5527b6a369586a",
|
||||
"sha256:c14701409f311e7a9b7ec8e337f0815baf7ac95776cc78b419a1e6d49889a383",
|
||||
"sha256:c7708f29d843fc2764310732e41f0ce27feadde453261859ec0fca7865dfc41b",
|
||||
"sha256:c9009c527929f6e25604aec39b0a43c3f831d2947d89d6caaab22f057b7055c8",
|
||||
"sha256:f5c71e29b4e2af7ccdc03a020c626ede51da471173b4a6ad1e904f2b2e04b4bd"
|
||||
"sha256:2586c94e907d99617887daed6c1d102b5ca28f1085f90446554abf1faf73123e",
|
||||
"sha256:2ae41f43797ca0c11591c0c35f2f5875fa99f8797cb1a1fd440497ec0ae4b022",
|
||||
"sha256:2b8e3888b11abb2217a32af0766bc06b65cc4a928d8727828ee68af5a967fa6f",
|
||||
"sha256:2c712840c2e2ee8dfaf36034080108d30060d759c7b73a01a52251cc8989f11f",
|
||||
"sha256:4d4631f6062e658e9007ab3149a9b914f3548cb38bfb021c64f39a025ce578ae",
|
||||
"sha256:67f15b6f83e6507fdc6fca22fedf6ef8b334b399ca27c6b568cbfaa82a364173",
|
||||
"sha256:7d2d7a06a252764061a020407b997dd036f7bd6a175a5ba2b345f0a357f0b3f4",
|
||||
"sha256:8c5922863e44ffc00c5c693190648daa6d15e7c1207ed02d6f46a8dcc2869d32",
|
||||
"sha256:92c85ac42f41ffdc35b6da57ed991575bdbe69db895507af88b9f499b701c188",
|
||||
"sha256:b24086f2375c4a094a6b51e78b4cf7ca16c721dcee2eddd7aa6494b42d6d519d",
|
||||
"sha256:cb925555f43060a1745d0a321cca94bcea927c50114b623d73179189a4e100ac"
|
||||
],
|
||||
"markers": "python_version < '3.5'",
|
||||
"version": "==1.9.0"
|
||||
"version": "==1.10.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
|
||||
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.12.0"
|
||||
},
|
||||
"snowballstemmer": {
|
||||
@@ -633,26 +648,34 @@
|
||||
},
|
||||
"soupsieve": {
|
||||
"hashes": [
|
||||
"sha256:afa56bf14907bb09403e5d15fbed6275caa4174d36b975226e3b67a3bb6e2c4b",
|
||||
"sha256:eaed742b48b1f3e2d45ba6f79401b2ed5dc33b2123dfe216adb90d4bfa0ade26"
|
||||
"sha256:6898e82ecb03772a0d82bd0d0a10c0d6dcc342f77e0701d0ec4a8271be465ece",
|
||||
"sha256:b20eff5e564529711544066d7dc0f7661df41232ae263619dede5059799cdfca"
|
||||
],
|
||||
"version": "==1.8"
|
||||
"version": "==1.9.1"
|
||||
},
|
||||
"sphinx": {
|
||||
"hashes": [
|
||||
"sha256:11f271e7a9398385ed730e90f0bb41dc3815294bdcd395b46ed2d033bc2e7d87",
|
||||
"sha256:4064ea6c56feeb268838cb8fbbee507d0c3d5d92fa63a7df935a916b52c9e2f5"
|
||||
"sha256:9f3e17c64b34afc653d7c5ec95766e03043cc6d80b0de224f59b6b6e19d37c3c",
|
||||
"sha256:c7658aab75c920288a8cf6f09f244c6cfdae30d82d803ac1634d9f223a80ca08"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.5.5"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.8.5"
|
||||
},
|
||||
"sphinx-click": {
|
||||
"hashes": [
|
||||
"sha256:926da1a7c677ae1b35cf255269ff84fec65d0f92e4863acfa77b92cf8ae32275",
|
||||
"sha256:f0c03d6ea0e4258c9c09646b6f745090ea8dd13e7e045903e4b789dfc02f7846"
|
||||
"sha256:9ceb0ee39e9734f39f593f99499e2a2f8e62929ac0b5f040b1d0411c02efaab7",
|
||||
"sha256:c9c45127bbbf720f1d24476cb3e57c806dd270662ae63b53a4b23be6c334491e"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.0.1"
|
||||
"version": "==2.1.0"
|
||||
},
|
||||
"sphinxcontrib-websupport": {
|
||||
"hashes": [
|
||||
"sha256:4044751a075b6560f155c96f9fec6bc5198cd5307e5db9f77c7b1c5247ac9a09",
|
||||
"sha256:c1b918b1b41cde045cdb9755941086b4ce4ebbfd7bff41d10ffb6d325779cbf9"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.1.1.dev20190321"
|
||||
},
|
||||
"stdeb": {
|
||||
"hashes": [
|
||||
@@ -682,27 +705,30 @@
|
||||
"sha256:d6506342615d051bc961f70bfcfa3d29b6616cc08a3ddfd4bc24196f16fd4ec2",
|
||||
"sha256:f077456d35303e7908cc233b340f71e0bec96f63429997f38ca9272b7d64029e"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.5.3"
|
||||
},
|
||||
"towncrier": {
|
||||
"editable": true,
|
||||
"git": "https://github.com/hawkowl/towncrier.git",
|
||||
"ref": "ecd438c9c0ef132a92aba2eecc4dc672ccf9ec63",
|
||||
"hashes": [
|
||||
"sha256:48251a1ae66d2cf7e6fa5552016386831b3e12bb3b2d08eb70374508c17a8196",
|
||||
"sha256:de19da8b8cb44f18ea7ed3a3823087d2af8fcf497151bb9fd1e1b092ff56ed8d"
|
||||
],
|
||||
"version": "==19.2.0"
|
||||
},
|
||||
"tqdm": {
|
||||
"hashes": [
|
||||
"sha256:d385c95361699e5cf7622485d9b9eae2d4864b21cd5a2374a9c381ffed701021",
|
||||
"sha256:e22977e3ebe961f72362f6ddfb9197cc531c9737aaf5f607ef09740c849ecd05"
|
||||
"sha256:0a860bf2683fdbb4812fe539a6c22ea3f1777843ea985cb8c3807db448a0f7ab",
|
||||
"sha256:e288416eecd4df19d12407d0c913cbf77aa8009d7fddb18f632aded3bdbdda6b"
|
||||
],
|
||||
"version": "==4.31.1"
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==4.32.1"
|
||||
},
|
||||
"twine": {
|
||||
"hashes": [
|
||||
"sha256:0fb0bfa3df4f62076cab5def36b1a71a2e4acb4d1fa5c97475b048117b1a6446",
|
||||
"sha256:d6c29c933ecfc74e9b1d9fa13aa1f87c5d5770e119f5a4ce032092f0ff5b14dc"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.13.0"
|
||||
},
|
||||
"typing": {
|
||||
@@ -716,35 +742,45 @@
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39",
|
||||
"sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22"
|
||||
"sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4",
|
||||
"sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb"
|
||||
],
|
||||
"version": "==1.24.1"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' and python_version < '4'",
|
||||
"version": "==1.24.3"
|
||||
},
|
||||
"virtualenv": {
|
||||
"hashes": [
|
||||
"sha256:6aebaf4dd2568a0094225ebbca987859e369e3e5c22dc7d52e5406d504890417",
|
||||
"sha256:984d7e607b0a5d1329425dd8845bd971b957424b5ba664729fab51ab8c11bc39"
|
||||
"sha256:99acaf1e35c7ccf9763db9ba2accbca2f4254d61d1912c5ee364f9cc4a8942a0",
|
||||
"sha256:fe51cdbf04e5d8152af06c075404745a7419de27495a83f0d72518ad50be3ce8"
|
||||
],
|
||||
"version": "==16.4.3"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==16.6.0"
|
||||
},
|
||||
"virtualenv-clone": {
|
||||
"hashes": [
|
||||
"sha256:217bd3f0880c9f85672c0bcc9ad9e0354ab7dfa89c2f117e63aa878b4279f5bf",
|
||||
"sha256:316c8a05432a7adb5e461709759aca18c51433ffc2c33e2e80c9e51c452d339f",
|
||||
"sha256:f2a07ed255f3abaceef8c8442512d8cdb2ba9f867e212d8a51680c7790a85033"
|
||||
"sha256:532f789a5c88adf339506e3ca03326f20ee82fd08ee5586b44dc859b5b4468c5",
|
||||
"sha256:c88ae171a11b087ea2513f260cdac9232461d8e9369bcd1dc143fc399d220557"
|
||||
],
|
||||
"version": "==0.5.1"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.5.3"
|
||||
},
|
||||
"vistir": {
|
||||
"extras": [
|
||||
"spinner"
|
||||
],
|
||||
"hashes": [
|
||||
"sha256:1a3d16d541de7ff098037260506a9efc5f6967176137988bd2cbfdd13b240ba0",
|
||||
"sha256:68896b279f64ff078e06ffd41f77181ef7cdedbeaa5f453cae3cfdd97d41dbcf"
|
||||
"sha256:00af96b75157b299616f47657ed34368e92e01d039100368c9dcd94897e3c109",
|
||||
"sha256:bbe040ce656f1de9b5f75c953abe49af4d1ba6fdf8f1f4b8db3e63cfd2dad24a"
|
||||
],
|
||||
"version": "==0.3.1"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.4.1"
|
||||
},
|
||||
"wcwidth": {
|
||||
"hashes": [
|
||||
"sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e",
|
||||
"sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"
|
||||
],
|
||||
"version": "==0.1.7"
|
||||
},
|
||||
"webencodings": {
|
||||
"hashes": [
|
||||
@@ -755,24 +791,26 @@
|
||||
},
|
||||
"werkzeug": {
|
||||
"hashes": [
|
||||
"sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",
|
||||
"sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b"
|
||||
"sha256:865856ebb55c4dcd0630cdd8f3331a1847a819dda7e8c750d3db6f2aa6c0209c",
|
||||
"sha256:a0b915f0815982fb2a09161cb8f31708052d0951c3ba433ccc5e1aa276507ca6"
|
||||
],
|
||||
"version": "==0.14.1"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.15.4"
|
||||
},
|
||||
"wheel": {
|
||||
"hashes": [
|
||||
"sha256:66a8fd76f28977bb664b098372daef2b27f60dc4d1688cfab7b37a09448f0e9d",
|
||||
"sha256:8eb4a788b3aec8abf5ff68d4165441bc57420c9f64ca5f471f58c3969fe08668"
|
||||
"sha256:5e79117472686ac0c4aef5bad5172ea73a1c2d1646b808c35926bd26bdfb0c08",
|
||||
"sha256:62fcfa03d45b5b722539ccbc07b190e4bfff4bb9e3a4d470dd9f6a0981002565"
|
||||
],
|
||||
"version": "==0.33.1"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.33.4"
|
||||
},
|
||||
"yaspin": {
|
||||
"hashes": [
|
||||
"sha256:441f8a6761e347652d04614899fd0a9cfda7439e2d5682e664bd31230c656176",
|
||||
"sha256:d3ebcf8162e0ef8bb5484b8751d5b6d2fbf0720112c81f64614c308576a03b1d"
|
||||
"sha256:8e6d2e2b207ba18510f190e04a25273a32d1f192af9c9a77ebe46deaca799dfa",
|
||||
"sha256:94b7602f0dc59d26a15e63cefff6aaf644c58dd77fc4e1ef675d3ba2c302ed06"
|
||||
],
|
||||
"version": "==0.14.1"
|
||||
"version": "==0.14.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ Or, if you\'re using FreeBSD:
|
||||
|
||||
# pkg install py36-pipenv
|
||||
|
||||
Otherwise, refer to the [documentation](https://docs.pipenv.org/install/) for instructions.
|
||||
Otherwise, refer to the [documentation](https://docs.pipenv.org/en/latest/install/#installing-pipenv) for instructions.
|
||||
|
||||
✨🍰✨
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
name: Pipenv Build Rules
|
||||
trigger:
|
||||
batch: true
|
||||
branches:
|
||||
include:
|
||||
- master
|
||||
paths:
|
||||
exclude:
|
||||
- docs/*
|
||||
- news/*
|
||||
- peeps/*
|
||||
- README.md
|
||||
- pipenv/*.txt
|
||||
- CHANGELOG.rst
|
||||
- CONTRIBUTING.md
|
||||
- CODE_OF_CONDUCT.md
|
||||
- .gitignore
|
||||
- .gitattributes
|
||||
- .editorconfig
|
||||
|
||||
jobs:
|
||||
- job: TestLinux
|
||||
pool:
|
||||
vmImage: 'Ubuntu-16.04'
|
||||
strategy:
|
||||
matrix:
|
||||
Python27:
|
||||
python.version: '2.7'
|
||||
Python36:
|
||||
python.version: '3.6'
|
||||
Python37:
|
||||
python.version: '3.7'
|
||||
maxParallel: 4
|
||||
steps:
|
||||
- template: .azure-pipelines/steps/run-tests.yml
|
||||
- template: .azure-pipelines/jobs/run-vendor-scripts.yml
|
||||
|
||||
- job: TestWindows
|
||||
pool:
|
||||
vmImage: windows-2019
|
||||
strategy:
|
||||
matrix:
|
||||
Python27:
|
||||
python.version: '2.7'
|
||||
python.architecture: x64
|
||||
Python36:
|
||||
python.version: '3.6'
|
||||
python.architecture: x64
|
||||
Python37:
|
||||
python.version: '3.7'
|
||||
python.architecture: x64
|
||||
maxParallel: 4
|
||||
steps:
|
||||
- template: .azure-pipelines/steps/run-tests.yml
|
||||
|
||||
- job: TestMacOS
|
||||
pool:
|
||||
vmImage: macOS-10.13
|
||||
strategy:
|
||||
matrix:
|
||||
Python27:
|
||||
python.version: '2.7'
|
||||
Python36:
|
||||
python.version: '3.6'
|
||||
Python37:
|
||||
python.version: '3.7'
|
||||
maxParallel: 4
|
||||
steps:
|
||||
- template: .azure-pipelines/steps/run-tests.yml
|
||||
+15
-9
@@ -1,28 +1,34 @@
|
||||
Updated vendored dependencies:
|
||||
|
||||
- **attrs**: ``18.2.0`` => ``19.1.0``
|
||||
- **certifi**: ``2018.10.15`` => ``2018.11.29``
|
||||
- **certifi**: ``2018.10.15`` => ``2019.3.9``
|
||||
- **cached_property**: ``1.4.3`` => ``1.5.1``
|
||||
- **cerberus**: ``1.2.0`` => ``1.3.1``
|
||||
- **click-completion**: ``0.5.0`` => ``0.5.1``
|
||||
- **colorama**: ``0.3.9`` => ``0.4.1``
|
||||
- **distlib**: ``0.2.8`` => ``0.2.9``
|
||||
- **idna**: ``2.7`` => ``2.8``
|
||||
- **jinja2**: ``2.10.0`` => ``2.10.1``
|
||||
- **markupsafe**: ``1.0`` => ``1.1.1``
|
||||
- **orderedmultidict**: ``(new)`` => ``1.0``
|
||||
- **packaging**: ``18.0`` => ``19.0``
|
||||
- **parse**: ``1.9.0`` => ``1.11.1``
|
||||
- **parse**: ``1.9.0`` => ``1.12.0``
|
||||
- **pathlib2**: ``2.3.2`` => ``2.3.3``
|
||||
- **pep517**: ``(new)`` => ``0.5.0``
|
||||
- **pexpect**: ``4.6.0`` => ``4.7.0``
|
||||
- **pipdeptree**: ``0.13.0`` => ``0.13.2``
|
||||
- **pyparsing**: ``2.2.2`` => ``2.3.1``
|
||||
- **python-dotenv**: ``0.9.1`` => ``0.10.1``
|
||||
- **pythonfinder**: ``1.1.10`` => ``1.2.0``
|
||||
- **python-dotenv**: ``0.9.1`` => ``0.10.2``
|
||||
- **pythonfinder**: ``1.1.10`` => ``1.2.1``
|
||||
- **pytoml**: ``(new)`` => ``0.1.20``
|
||||
- **requests**: ``2.20.1`` => ``2.21.0``
|
||||
- **requirementslib**: ``1.3.3`` => ``1.4.2``
|
||||
- **shellingham**: ``1.2.7`` => ``1.2.8``
|
||||
- **requirementslib**: ``1.3.3`` => ``1.5.0``
|
||||
- **scandir**: ``1.9.0`` => ``1.10.0``
|
||||
- **shellingham**: ``1.2.7`` => ``1.3.1``
|
||||
- **six**: ``1.11.0`` => ``1.12.0``
|
||||
- **tomlkit**: ``0.5.2`` => ``0.5.3``
|
||||
- **urllib3**: ``1.24`` => ``1.24.1``
|
||||
- **vistir**: ``0.3.0`` => ``0.3.1``
|
||||
- **yaspin**: ``0.14.0`` => ``0.14.1``
|
||||
- **urllib3**: ``1.24`` => ``1.25.2``
|
||||
- **vistir**: ``0.3.0`` => ``0.4.1``
|
||||
- **yaspin**: ``0.14.0`` => ``0.14.3``
|
||||
|
||||
- Removed vendored dependency **cursor**.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Update pytest configuration to support pytest 4.
|
||||
+11
-15
@@ -36,23 +36,19 @@ try:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
from .vendor.vistir.misc import get_wrapped_stream
|
||||
if sys.version_info >= (3, 0):
|
||||
stdout = sys.stdout.buffer
|
||||
stderr = sys.stderr.buffer
|
||||
else:
|
||||
stdout = sys.stdout
|
||||
stderr = sys.stderr
|
||||
from pipenv.vendor.vistir.misc import get_text_stream
|
||||
stdout = get_text_stream("stdout")
|
||||
stderr = get_text_stream("stderr")
|
||||
|
||||
|
||||
sys.stderr = get_wrapped_stream(stderr)
|
||||
sys.stdout = get_wrapped_stream(stdout)
|
||||
from .vendor.colorama import AnsiToWin32
|
||||
if os.name == "nt":
|
||||
stderr_wrapper = AnsiToWin32(sys.stderr, autoreset=False, convert=None, strip=None)
|
||||
stdout_wrapper = AnsiToWin32(sys.stdout, autoreset=False, convert=None, strip=None)
|
||||
sys.stderr = stderr_wrapper.stream
|
||||
sys.stdout = stdout_wrapper.stream
|
||||
from pipenv.vendor.vistir.misc import _can_use_color, _wrap_for_color
|
||||
if _can_use_color(stdout):
|
||||
stdout = _wrap_for_color(stdout)
|
||||
if _can_use_color(stderr):
|
||||
stderr = _wrap_for_color(stderr)
|
||||
|
||||
sys.stdout = stdout
|
||||
sys.stderr = stderr
|
||||
|
||||
from .cli import cli
|
||||
from . import resolver
|
||||
|
||||
+19
-16
@@ -14,11 +14,11 @@ import urllib3.util as urllib3_util
|
||||
import vistir
|
||||
|
||||
import click_completion
|
||||
import crayons
|
||||
import delegator
|
||||
import dotenv
|
||||
import pipfile
|
||||
|
||||
from .patched import crayons
|
||||
from . import environments, exceptions, pep508checker, progress
|
||||
from ._compat import fix_utf8, decode_for_output
|
||||
from .cmdparse import Script
|
||||
@@ -746,7 +746,7 @@ def batch_install(deps_list, procs, failed_deps_queue,
|
||||
pypi_mirror=pypi_mirror,
|
||||
trusted_hosts=trusted_hosts,
|
||||
extra_indexes=extra_indexes,
|
||||
use_pep517=not retry,
|
||||
use_pep517=not failed,
|
||||
)
|
||||
if procs.qsize() < nprocs:
|
||||
c.dep = dep
|
||||
@@ -917,18 +917,17 @@ def do_create_virtualenv(python=None, site_packages=False, pypi_mirror=None):
|
||||
pip_config = {}
|
||||
|
||||
# Actually create the virtualenv.
|
||||
nospin = environments.PIPENV_NOSPIN
|
||||
with create_spinner("Creating virtual environment...") as sp:
|
||||
with create_spinner(u"Creating virtual environment...") as sp:
|
||||
c = vistir.misc.run(
|
||||
cmd, verbose=False, return_object=True, write_to_stdout=False,
|
||||
combine_stderr=False, block=True, nospin=True, env=pip_config,
|
||||
)
|
||||
click.echo(crayons.blue("{0}".format(c.out)), err=True)
|
||||
click.echo(crayons.blue(u"{0}".format(c.out)), err=True)
|
||||
if c.returncode != 0:
|
||||
sp.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format("Failed creating virtual environment"))
|
||||
sp.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format(u"Failed creating virtual environment"))
|
||||
error = c.err if environments.is_verbose() else exceptions.prettify_exc(c.err)
|
||||
raise exceptions.VirtualenvCreationException(
|
||||
extra=[crayons.red("{0}".format(error)),]
|
||||
extra=crayons.red("{0}".format(error))
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -1416,15 +1415,18 @@ def pip_install(
|
||||
name = requirement.name
|
||||
if requirement.extras:
|
||||
name = "{0}{1}".format(name, requirement.extras_as_pip)
|
||||
line = "-e {0}#egg={1}".format(vistir.path.path_to_url(repo.checkout_directory), requirement.name)
|
||||
line = "{0}{1}#egg={2}".format(
|
||||
line, vistir.path.path_to_url(repo.checkout_directory), requirement.name
|
||||
)
|
||||
if repo.subdirectory:
|
||||
line = "{0}&subdirectory={1}".format(line, repo.subdirectory)
|
||||
else:
|
||||
line = requirement.as_line(**line_kwargs)
|
||||
click.echo(
|
||||
"Writing requirement line to temporary file: {0!r}".format(line),
|
||||
err=True
|
||||
)
|
||||
if environments.is_verbose():
|
||||
click.echo(
|
||||
"Writing requirement line to temporary file: {0!r}".format(line),
|
||||
err=True
|
||||
)
|
||||
f.write(vistir.misc.to_bytes(line))
|
||||
r = f.name
|
||||
f.close()
|
||||
@@ -1441,10 +1443,11 @@ def pip_install(
|
||||
ignore_hashes = True if not requirement.hashes else ignore_hashes
|
||||
line = requirement.as_line(include_hashes=not ignore_hashes)
|
||||
line = "{0} {1}".format(line, " ".join(src))
|
||||
click.echo(
|
||||
"Writing requirement line to temporary file: {0!r}".format(line),
|
||||
err=True
|
||||
)
|
||||
if environments.is_verbose():
|
||||
click.echo(
|
||||
"Writing requirement line to temporary file: {0!r}".format(line),
|
||||
err=True
|
||||
)
|
||||
f.write(vistir.misc.to_bytes(line))
|
||||
r = f.name
|
||||
f.close()
|
||||
|
||||
+66
-17
@@ -9,7 +9,7 @@ import site
|
||||
import sys
|
||||
|
||||
from distutils.sysconfig import get_python_lib
|
||||
from sysconfig import get_paths
|
||||
from sysconfig import get_paths, get_python_version
|
||||
|
||||
import itertools
|
||||
import pkg_resources
|
||||
@@ -106,10 +106,14 @@ class Environment(object):
|
||||
@cached_property
|
||||
def python_version(self):
|
||||
with self.activated():
|
||||
from sysconfig import get_python_version
|
||||
py_version = get_python_version()
|
||||
sysconfig = self.safe_import("sysconfig")
|
||||
py_version = sysconfig.get_python_version()
|
||||
return py_version
|
||||
|
||||
def find_libdir(self):
|
||||
libdir = self.prefix / "lib"
|
||||
return next(iter(list(libdir.iterdir())), None)
|
||||
|
||||
@property
|
||||
def python_info(self):
|
||||
include_dir = self.prefix / "include"
|
||||
@@ -120,6 +124,16 @@ class Environment(object):
|
||||
return {"py_version_short": py_version_short, "abiflags": abiflags}
|
||||
return {}
|
||||
|
||||
def _replace_parent_version(self, path, replace_version):
|
||||
if not os.path.exists(path):
|
||||
base, leaf = os.path.split(path)
|
||||
base, parent = os.path.split(base)
|
||||
leaf = os.path.join(parent, leaf).replace(
|
||||
replace_version, self.python_info.get("py_version_short", get_python_version())
|
||||
)
|
||||
return os.path.join(base, leaf)
|
||||
return path
|
||||
|
||||
@cached_property
|
||||
def base_paths(self):
|
||||
"""
|
||||
@@ -156,18 +170,22 @@ class Environment(object):
|
||||
'base': prefix,
|
||||
'platbase': prefix,
|
||||
})
|
||||
current_version = get_python_version()
|
||||
for k in list(paths.keys()):
|
||||
if not os.path.exists(paths[k]):
|
||||
paths[k] = self._replace_parent_version(paths[k], current_version)
|
||||
if not os.path.exists(paths["purelib"]) and not os.path.exists(paths["platlib"]):
|
||||
paths = self.get_paths()
|
||||
paths["PATH"] = paths["scripts"] + os.pathsep + os.defpath
|
||||
if "prefix" not in paths:
|
||||
paths["prefix"] = prefix
|
||||
purelib = make_posix(get_python_lib(plat_specific=0, prefix=prefix))
|
||||
platlib = make_posix(get_python_lib(plat_specific=1, prefix=prefix))
|
||||
purelib = paths["purelib"] = make_posix(paths["purelib"])
|
||||
platlib = paths["platlib"] = make_posix(paths["platlib"])
|
||||
if purelib == platlib:
|
||||
lib_dirs = purelib
|
||||
else:
|
||||
lib_dirs = purelib + os.pathsep + platlib
|
||||
paths["libdir"] = purelib
|
||||
paths["purelib"] = purelib
|
||||
paths["platlib"] = platlib
|
||||
paths['PYTHONPATH'] = os.pathsep.join(["", ".", lib_dirs])
|
||||
paths["libdirs"] = lib_dirs
|
||||
return paths
|
||||
@@ -175,13 +193,18 @@ class Environment(object):
|
||||
@cached_property
|
||||
def script_basedir(self):
|
||||
"""Path to the environment scripts dir"""
|
||||
script_dir = self.base_paths["scripts"]
|
||||
return script_dir
|
||||
prefix = make_posix(self.prefix.as_posix())
|
||||
install_scheme = 'nt' if (os.name == 'nt') else 'posix_prefix'
|
||||
paths = get_paths(install_scheme, vars={
|
||||
'base': prefix,
|
||||
'platbase': prefix,
|
||||
})
|
||||
return paths["scripts"]
|
||||
|
||||
@property
|
||||
def python(self):
|
||||
"""Path to the environment python"""
|
||||
py = vistir.compat.Path(self.base_paths["scripts"]).joinpath("python").absolute().as_posix()
|
||||
py = vistir.compat.Path(self.script_basedir).joinpath("python").absolute().as_posix()
|
||||
if not py:
|
||||
return vistir.compat.Path(sys.executable).as_posix()
|
||||
return py
|
||||
@@ -209,6 +232,30 @@ class Environment(object):
|
||||
path = sys.path
|
||||
return path
|
||||
|
||||
def get_paths(self):
|
||||
"""
|
||||
Get the paths for the environment by running a subcommand
|
||||
|
||||
:return: The python paths for the environment
|
||||
:rtype: Dict[str, str]
|
||||
"""
|
||||
prefix = make_posix(self.prefix.as_posix())
|
||||
install_scheme = 'nt' if (os.name == 'nt') else 'posix_prefix'
|
||||
py_command = (
|
||||
"import sysconfig, json, distutils.sysconfig;"
|
||||
"paths = sysconfig.get_paths('{0}', vars={{'base': '{1}', 'platbase': '{1}'}}"
|
||||
");paths['purelib'] = distutils.sysconfig.get_python_lib(plat_specific=0, "
|
||||
"prefix='{1}');paths['platlib'] = distutils.sysconfig.get_python_lib("
|
||||
"plat_specific=1, prefix='{1}');print(json.dumps(paths))"
|
||||
)
|
||||
vistir.misc.echo("command: {0}".format(py_command.format(install_scheme, prefix)), fg="white", style="bold", err=True)
|
||||
command = [self.python, "-c", py_command.format(install_scheme, prefix)]
|
||||
c = vistir.misc.run(
|
||||
command, return_object=True, block=True, nospin=True, write_to_stdout=False
|
||||
)
|
||||
paths = json.loads(vistir.misc.to_text(c.out.strip()))
|
||||
return paths
|
||||
|
||||
@cached_property
|
||||
def sys_prefix(self):
|
||||
"""
|
||||
@@ -218,7 +265,7 @@ class Environment(object):
|
||||
:rtype: :data:`sys.prefix`
|
||||
"""
|
||||
|
||||
command = [self.python, "-c" "import sys; print(sys.prefix)"]
|
||||
command = [self.python, "-c", "import sys; print(sys.prefix)"]
|
||||
c = vistir.misc.run(command, return_object=True, block=True, nospin=True, write_to_stdout=False)
|
||||
sys_prefix = vistir.compat.Path(vistir.misc.to_text(c.out).strip()).as_posix()
|
||||
return sys_prefix
|
||||
@@ -258,7 +305,7 @@ class Environment(object):
|
||||
pkg for pkg in self.get_installed_packages() if pkg.key == "pip"
|
||||
), None)
|
||||
if pip is not None:
|
||||
pip_version = parse_version(pip.version)
|
||||
return parse_version(pip.version)
|
||||
return parse_version("18.0")
|
||||
|
||||
def get_distributions(self):
|
||||
@@ -442,7 +489,7 @@ class Environment(object):
|
||||
yield new_node
|
||||
|
||||
def reverse_dependencies(self):
|
||||
from vistir.misc import unnest
|
||||
from vistir.misc import unnest, chunked
|
||||
rdeps = {}
|
||||
for req in self.get_package_requirements():
|
||||
for d in self.reverse_dependency(req):
|
||||
@@ -454,18 +501,20 @@ class Environment(object):
|
||||
"required": d["required_version"]
|
||||
}
|
||||
}
|
||||
parents = set(d.get("parent", []))
|
||||
parents = tuple(d.get("parent", ()))
|
||||
pkg[name]["parents"] = parents
|
||||
if rdeps.get(name):
|
||||
if not (rdeps[name].get("required") or rdeps[name].get("installed")):
|
||||
rdeps[name].update(pkg[name])
|
||||
rdeps[name]["parents"] = rdeps[name].get("parents", set()) | parents
|
||||
rdeps[name]["parents"] = rdeps[name].get("parents", ()) + parents
|
||||
else:
|
||||
rdeps[name] = pkg[name]
|
||||
for k in list(rdeps.keys()):
|
||||
entry = rdeps[k]
|
||||
if entry.get("parents"):
|
||||
rdeps[k]["parents"] = set([p for p in unnest(entry["parents"])])
|
||||
rdeps[k]["parents"] = set([
|
||||
p for p, version in chunked(2, unnest(entry["parents"]))
|
||||
])
|
||||
return rdeps
|
||||
|
||||
def get_working_set(self):
|
||||
@@ -566,7 +615,7 @@ class Environment(object):
|
||||
prefix = self.prefix.as_posix()
|
||||
with vistir.contextmanagers.temp_environ(), vistir.contextmanagers.temp_path():
|
||||
os.environ["PATH"] = os.pathsep.join([
|
||||
vistir.compat.fs_str(self.scripts_dir),
|
||||
vistir.compat.fs_str(self.script_basedir),
|
||||
vistir.compat.fs_str(self.prefix.as_posix()),
|
||||
os.environ.get("PATH", "")
|
||||
])
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from io import UnsupportedOperation
|
||||
|
||||
from appdirs import user_cache_dir
|
||||
|
||||
from ._compat import fix_utf8
|
||||
from .vendor.vistir.misc import fs_str
|
||||
from .vendor.vistir.misc import _isatty, fs_str
|
||||
|
||||
|
||||
# HACK: avoid resolver.py uses the wrong byte code files.
|
||||
@@ -263,7 +265,10 @@ PIPENV_SHELL = (
|
||||
)
|
||||
|
||||
# Internal, to tell whether the command line session is interactive.
|
||||
SESSION_IS_INTERACTIVE = bool(os.isatty(sys.stdout.fileno()))
|
||||
try:
|
||||
SESSION_IS_INTERACTIVE = _isatty(sys.stdout.fileno())
|
||||
except UnsupportedOperation:
|
||||
SESSION_IS_INTERACTIVE = _isatty(sys.stdout)
|
||||
|
||||
|
||||
# Internal, consolidated verbosity representation as an integer. The default
|
||||
|
||||
+107
-66
@@ -1,36 +1,54 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
|
||||
import itertools
|
||||
import re
|
||||
import sys
|
||||
|
||||
from pprint import pformat
|
||||
from traceback import format_exception, format_tb
|
||||
from collections import namedtuple
|
||||
from traceback import format_tb
|
||||
|
||||
import six
|
||||
|
||||
from . import environments
|
||||
from ._compat import decode_for_output
|
||||
from .patched import crayons
|
||||
from .vendor.click._compat import get_text_stderr
|
||||
from .vendor.click.exceptions import (
|
||||
Abort, BadOptionUsage, BadParameter, ClickException, Exit, FileError,
|
||||
MissingParameter, UsageError
|
||||
ClickException, FileError, UsageError
|
||||
)
|
||||
from .vendor.click.types import Path
|
||||
from .vendor.click.utils import echo as click_echo
|
||||
from .vendor.vistir.misc import echo as click_echo
|
||||
import vistir
|
||||
|
||||
KNOWN_EXCEPTIONS = {
|
||||
"PermissionError": "Permission denied:",
|
||||
}
|
||||
ANSI_REMOVAL_RE = re.compile(r"\033\[((?:\d|;)*)([a-zA-Z])", re.MULTILINE)
|
||||
STRING_TYPES = (six.string_types, crayons.ColoredString)
|
||||
|
||||
if sys.version_info[:2] >= (3, 7):
|
||||
KnownException = namedtuple(
|
||||
'KnownException', ['exception_name', 'match_string', 'show_from_string', 'prefix'],
|
||||
defaults=[None, None, None, ""]
|
||||
)
|
||||
else:
|
||||
KnownException = namedtuple(
|
||||
'KnownException', ['exception_name', 'match_string', 'show_from_string', 'prefix'],
|
||||
)
|
||||
KnownException.__new__.__defaults__ = (None, None, None, "")
|
||||
|
||||
KNOWN_EXCEPTIONS = [
|
||||
KnownException("PermissionError", prefix="Permission Denied:"),
|
||||
KnownException(
|
||||
"VirtualenvCreationException",
|
||||
match_string="do_create_virtualenv",
|
||||
show_from_string=None
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def handle_exception(exc_type, exception, traceback, hook=sys.excepthook):
|
||||
if environments.is_verbose() or not issubclass(exc_type, ClickException):
|
||||
hook(exc_type, exception, traceback)
|
||||
else:
|
||||
exc = format_exception(exc_type, exception, traceback)
|
||||
tb = format_tb(traceback, limit=-6)
|
||||
lines = itertools.chain.from_iterable([frame.splitlines() for frame in tb])
|
||||
formatted_lines = []
|
||||
for line in lines:
|
||||
line = line.strip("'").strip('"').strip("\n").strip()
|
||||
if not line.startswith("File"):
|
||||
@@ -40,7 +58,10 @@ def handle_exception(exc_type, exception, traceback, hook=sys.excepthook):
|
||||
line = "[{0!s}]: {1}".format(
|
||||
exception.__class__.__name__, line
|
||||
)
|
||||
click_echo(decode_for_output(line), err=True)
|
||||
formatted_lines.append(line)
|
||||
# use new exception prettification rules to format exceptions according to
|
||||
# UX rules
|
||||
click_echo(decode_for_output(prettify_exc("\n".join(formatted_lines))), err=True)
|
||||
exception.show()
|
||||
|
||||
|
||||
@@ -60,9 +81,9 @@ class PipenvException(ClickException):
|
||||
|
||||
def show(self, file=None):
|
||||
if file is None:
|
||||
file = get_text_stderr()
|
||||
file = vistir.misc.get_text_stderr()
|
||||
if self.extra:
|
||||
if isinstance(self.extra, six.string_types):
|
||||
if isinstance(self.extra, STRING_TYPES):
|
||||
self.extra = [self.extra,]
|
||||
for extra in self.extra:
|
||||
extra = "[pipenv.exceptions.{0!s}]: {1}".format(
|
||||
@@ -84,7 +105,7 @@ class PipenvCmdError(PipenvException):
|
||||
|
||||
def show(self, file=None):
|
||||
if file is None:
|
||||
file = get_text_stderr()
|
||||
file = vistir.misc.get_text_stderr()
|
||||
click_echo("{0} {1}".format(
|
||||
crayons.red("Error running command: "),
|
||||
crayons.white(decode_for_output("$ {0}".format(self.cmd), file), bold=True)
|
||||
@@ -108,12 +129,12 @@ class JSONParseError(PipenvException):
|
||||
|
||||
def show(self, file=None):
|
||||
if file is None:
|
||||
file = get_text_stderr()
|
||||
file = vistir.misc.get_text_stderr()
|
||||
message = "{0}\n{1}".format(
|
||||
crayons.white("Failed parsing JSON results:", bold=True),
|
||||
decode_for_output(self.message.strip(), file)
|
||||
)
|
||||
click_echo(self.message, err=True)
|
||||
click_echo(message, err=True)
|
||||
if self.error_text:
|
||||
click_echo("{0} {1}".format(
|
||||
crayons.white("ERROR TEXT:", bold=True),
|
||||
@@ -136,12 +157,12 @@ class PipenvUsageError(UsageError):
|
||||
|
||||
def show(self, file=None):
|
||||
if file is None:
|
||||
file = get_text_stderr()
|
||||
file = vistir.misc.get_text_stderr()
|
||||
color = None
|
||||
if self.ctx is not None:
|
||||
color = self.ctx.color
|
||||
if self.extra:
|
||||
if isinstance(self.extra, six.string_types):
|
||||
if isinstance(self.extra, STRING_TYPES):
|
||||
self.extra = [self.extra,]
|
||||
for extra in self.extra:
|
||||
if color:
|
||||
@@ -175,9 +196,9 @@ class PipenvFileError(FileError):
|
||||
|
||||
def show(self, file=None):
|
||||
if file is None:
|
||||
file = get_text_stderr()
|
||||
file = vistir.misc.get_text_stderr()
|
||||
if self.extra:
|
||||
if isinstance(self.extra, six.string_types):
|
||||
if isinstance(self.extra, STRING_TYPES):
|
||||
self.extra = [self.extra,]
|
||||
for extra in self.extra:
|
||||
click_echo(decode_for_output(extra, file), file=file)
|
||||
@@ -187,13 +208,16 @@ class PipenvFileError(FileError):
|
||||
class PipfileNotFound(PipenvFileError):
|
||||
def __init__(self, filename="Pipfile", extra=None, **kwargs):
|
||||
extra = kwargs.pop("extra", [])
|
||||
message = ("{0} {1}".format(
|
||||
message = (
|
||||
"{0} {1}".format(
|
||||
crayons.red("Aborting!", bold=True),
|
||||
crayons.white("Please ensure that the file exists and is located in your"
|
||||
" project root directory.", bold=True)
|
||||
crayons.white(
|
||||
"Please ensure that the file exists and is located in your"
|
||||
" project root directory.", bold=True
|
||||
)
|
||||
)
|
||||
)
|
||||
super(PipfileNotFound, self).__init__(filename, message=decode_for_output(message), extra=extra, **kwargs)
|
||||
super(PipfileNotFound, self).__init__(filename, message=message, extra=extra, **kwargs)
|
||||
|
||||
|
||||
class LockfileNotFound(PipenvFileError):
|
||||
@@ -204,7 +228,7 @@ class LockfileNotFound(PipenvFileError):
|
||||
crayons.red("$ pipenv lock", bold=True),
|
||||
crayons.white("before you can continue.", bold=True)
|
||||
)
|
||||
super(LockfileNotFound, self).__init__(filename, message=decode_for_output(message), extra=extra, **kwargs)
|
||||
super(LockfileNotFound, self).__init__(filename, message=message, extra=extra, **kwargs)
|
||||
|
||||
|
||||
class DeployException(PipenvUsageError):
|
||||
@@ -212,13 +236,13 @@ class DeployException(PipenvUsageError):
|
||||
if not message:
|
||||
message = crayons.normal("Aborting deploy", bold=True)
|
||||
extra = kwargs.pop("extra", [])
|
||||
PipenvUsageError.__init__(self, message=decode_for_output(message), extra=extra, **kwargs)
|
||||
PipenvUsageError.__init__(self, message=message, extra=extra, **kwargs)
|
||||
|
||||
|
||||
class PipenvOptionsError(PipenvUsageError):
|
||||
def __init__(self, option_name, message=None, ctx=None, **kwargs):
|
||||
extra = kwargs.pop("extra", [])
|
||||
PipenvUsageError.__init__(self, message=decode_for_output(message), ctx=ctx, **kwargs)
|
||||
PipenvUsageError.__init__(self, message=message, ctx=ctx, **kwargs)
|
||||
self.extra = extra
|
||||
self.option_name = option_name
|
||||
|
||||
@@ -245,7 +269,7 @@ class PipfileException(PipenvFileError):
|
||||
hint = "{0} {1}".format(crayons.red("ERROR (PACKAGE NOT INSTALLED):"), hint)
|
||||
filename = project.pipfile_location
|
||||
extra = kwargs.pop("extra", [])
|
||||
PipenvFileError.__init__(self, filename, decode_for_output(hint), extra=extra, **kwargs)
|
||||
PipenvFileError.__init__(self, filename, hint, extra=extra, **kwargs)
|
||||
|
||||
|
||||
class SetupException(PipenvException):
|
||||
@@ -261,7 +285,7 @@ class VirtualenvException(PipenvException):
|
||||
"There was an unexpected error while activating your virtualenv. "
|
||||
"Continuing anyway..."
|
||||
)
|
||||
PipenvException.__init__(self, decode_for_output(message), **kwargs)
|
||||
PipenvException.__init__(self, message, **kwargs)
|
||||
|
||||
|
||||
class VirtualenvActivationException(VirtualenvException):
|
||||
@@ -272,7 +296,7 @@ class VirtualenvActivationException(VirtualenvException):
|
||||
"not activated. Continuing anyway…"
|
||||
)
|
||||
self.message = message
|
||||
VirtualenvException.__init__(self, decode_for_output(message), **kwargs)
|
||||
VirtualenvException.__init__(self, message, **kwargs)
|
||||
|
||||
|
||||
class VirtualenvCreationException(VirtualenvException):
|
||||
@@ -280,23 +304,35 @@ class VirtualenvCreationException(VirtualenvException):
|
||||
if not message:
|
||||
message = "Failed to create virtual environment."
|
||||
self.message = message
|
||||
VirtualenvException.__init__(self, decode_for_output(message), **kwargs)
|
||||
extra = kwargs.pop("extra", None)
|
||||
if extra is not None and isinstance(extra, STRING_TYPES):
|
||||
# note we need the format interpolation because ``crayons.ColoredString``
|
||||
# is not an actual string type but is only a preparation for interpolation
|
||||
# so replacement or parsing requires this step
|
||||
extra = ANSI_REMOVAL_RE.sub("", "{0}".format(extra))
|
||||
if "KeyboardInterrupt" in extra:
|
||||
extra = crayons.red("Virtualenv creation interrupted by user", bold=True)
|
||||
self.extra = extra = [extra,]
|
||||
VirtualenvException.__init__(self, message, extra=extra)
|
||||
|
||||
|
||||
class UninstallError(PipenvException):
|
||||
def __init__(self, package, command, return_values, return_code, **kwargs):
|
||||
extra = [crayons.blue("Attempted to run command: {0}".format(
|
||||
crayons.yellow("$ {0!r}".format(command), bold=True)
|
||||
)),]
|
||||
extra = [
|
||||
"{0} {1}".format(
|
||||
crayons.blue("Attempted to run command: "),
|
||||
crayons.yellow("$ {0!r}".format(command), bold=True
|
||||
)
|
||||
)]
|
||||
extra.extend([crayons.blue(line.strip()) for line in return_values.splitlines()])
|
||||
if isinstance(package, (tuple, list, set)):
|
||||
package = " ".join(package)
|
||||
message = "{0!s} {1!s}...".format(
|
||||
crayons.normal("Failed to uninstall package(s)"),
|
||||
crayons.yellow(str(package), bold=True)
|
||||
crayons.yellow("{0}!s".format(package), bold=True)
|
||||
)
|
||||
self.exit_code = return_code
|
||||
PipenvException.__init__(self, message=decode_for_output(message), extra=extra)
|
||||
PipenvException.__init__(self, message=message, extra=extra)
|
||||
self.extra = extra
|
||||
|
||||
|
||||
@@ -304,36 +340,34 @@ class InstallError(PipenvException):
|
||||
def __init__(self, package, **kwargs):
|
||||
package_message = ""
|
||||
if package is not None:
|
||||
package_message = crayons.normal("Couldn't install package {0}\n".format(
|
||||
crayons.white(package, bold=True)
|
||||
))
|
||||
message = "{0} {1} {2}".format(
|
||||
crayons.red("ERROR:", bold=True),
|
||||
package_message,
|
||||
package_message = "Couldn't install package: {0}\n".format(
|
||||
crayons.white("{0!s}".format(package), bold=True)
|
||||
)
|
||||
message = "{0} {1}".format(
|
||||
"{0}".format(package_message),
|
||||
crayons.yellow("Package installation failed...")
|
||||
)
|
||||
extra = kwargs.pop("extra", [])
|
||||
PipenvException.__init__(self, message=decode_for_output(message), extra=extra, **kwargs)
|
||||
PipenvException.__init__(self, message=message, extra=extra, **kwargs)
|
||||
|
||||
|
||||
class CacheError(PipenvException):
|
||||
def __init__(self, path, **kwargs):
|
||||
message = "{0} {1} {2}\n{0}".format(
|
||||
crayons.red("ERROR:", bold=True),
|
||||
message = "{0} {1}\n{2}".format(
|
||||
crayons.blue("Corrupt cache file"),
|
||||
crayons.white(path),
|
||||
crayons.white("{0!s}".format(path)),
|
||||
crayons.white('Consider trying "pipenv lock --clear" to clear the cache.')
|
||||
)
|
||||
PipenvException.__init__(self, message=decode_for_output(message))
|
||||
PipenvException.__init__(self, message=message)
|
||||
|
||||
|
||||
class DependencyConflict(PipenvException):
|
||||
def __init__(self, message):
|
||||
extra = [decode_for_output("{0} {1}".format(
|
||||
crayons.red("ERROR:", bold=True),
|
||||
crayons.white("A dependency conflict was detected and could not be resolved.", bold=True),
|
||||
)),]
|
||||
super(DependencyConflict, self).__init__(decode_for_output(message), extra=extra)
|
||||
extra = ["{0} {1}".format(
|
||||
crayons.red("The operation failed...", bold=True),
|
||||
crayons.red("A dependency conflict was detected and could not be resolved."),
|
||||
)]
|
||||
PipenvException.__init__(self, message, extra=extra)
|
||||
|
||||
|
||||
class ResolutionFailure(PipenvException):
|
||||
@@ -355,9 +389,7 @@ class ResolutionFailure(PipenvException):
|
||||
)
|
||||
if "no version found at all" in message:
|
||||
no_version_found = True
|
||||
message = "{0} {1}".format(
|
||||
crayons.red("ERROR:", bold=True), crayons.yellow(message)
|
||||
)
|
||||
message = crayons.yellow("{0}".format(message))
|
||||
if no_version_found:
|
||||
message = "{0}\n{1}".format(
|
||||
message,
|
||||
@@ -366,7 +398,7 @@ class ResolutionFailure(PipenvException):
|
||||
"See PEP440 for more information."
|
||||
)
|
||||
)
|
||||
super(ResolutionFailure, self).__init__(decode_for_output(message), extra=extra)
|
||||
PipenvException.__init__(self, message, extra=extra)
|
||||
|
||||
|
||||
class RequirementError(PipenvException):
|
||||
@@ -404,20 +436,29 @@ class RequirementError(PipenvException):
|
||||
crayons.normal(decode_for_output("Failed creating requirement instance")),
|
||||
crayons.white(decode_for_output("{0!r}".format(req_value)))
|
||||
)
|
||||
extra = [crayons.normal(decode_for_output(str(req)))]
|
||||
super(RequirementError, self).__init__(message, extra=extra)
|
||||
super(ResolutionFailure, self).__init__(fix_utf8(message), extra=extra)
|
||||
extra = [str(req)]
|
||||
PipenvException.__init__(self, message, extra=extra)
|
||||
|
||||
|
||||
def prettify_exc(error):
|
||||
"""Catch known errors and prettify them instead of showing the
|
||||
entire traceback, for better UX"""
|
||||
matched_exceptions = [k for k in KNOWN_EXCEPTIONS.keys() if k in error]
|
||||
if not matched_exceptions:
|
||||
return "{}".format(vistir.misc.decode_for_output(error))
|
||||
errors = []
|
||||
for match in matched_exceptions:
|
||||
_, error, info = error.rpartition(KNOWN_EXCEPTIONS[match])
|
||||
errors.append("{} {}".format(error, info))
|
||||
for exc in KNOWN_EXCEPTIONS:
|
||||
search_string = exc.match_string if exc.match_string else exc.exception_name
|
||||
split_string = exc.show_from_string if exc.show_from_string else exc.exception_name
|
||||
if search_string in error:
|
||||
# for known exceptions with no display rules and no prefix
|
||||
# we should simply show nothing
|
||||
if not exc.show_from_string and not exc.prefix:
|
||||
errors.append("")
|
||||
continue
|
||||
elif exc.prefix and exc.prefix in error:
|
||||
_, error, info = error.rpartition(exc.prefix)
|
||||
else:
|
||||
_, error, info = error.rpartition(split_string)
|
||||
errors.append("{0} {1}".format(error, info))
|
||||
if not errors:
|
||||
return "{}".format(vistir.misc.decode_for_output(error))
|
||||
|
||||
return "\n".join(errors)
|
||||
|
||||
@@ -19,8 +19,8 @@ from pipenv.patched.notpip._internal.exceptions import (
|
||||
UnsupportedPythonVersion,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.req.constructors import install_req_from_req_string
|
||||
from pipenv.patched.notpip._internal.utils.logging import indent_log
|
||||
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
|
||||
from pipenv.patched.notpip._internal.utils.logging import indent_log
|
||||
from pipenv.patched.notpip._internal.utils.misc import dist_in_usersite, ensure_dir
|
||||
from pipenv.patched.notpip._internal.utils.packaging import check_dist_requires_python
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
@@ -25,4 +25,3 @@ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ Copyright 2018 Kenneth Reitz
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2010-2017 Benjamin Peterson
|
||||
Copyright (c) 2010-2018 Benjamin Peterson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
This is the MIT license: http://www.opensource.org/licenses/mit-license.php
|
||||
MIT License
|
||||
|
||||
Copyright 2008-2016 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
Copyright (c) 2008-2019 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
software and associated documentation files (the "Software"), to deal in the Software
|
||||
without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
appdirs==1.4.3
|
||||
distlib==0.2.7
|
||||
distro==1.3.0
|
||||
html5lib==1.0.1
|
||||
six==1.11.0
|
||||
colorama==0.3.9
|
||||
CacheControl==0.12.5
|
||||
msgpack-python==0.5.6
|
||||
lockfile==0.12.2
|
||||
progress==1.4
|
||||
colorama==0.4.1
|
||||
distlib==0.2.8
|
||||
distro==1.4.0
|
||||
html5lib==1.0.1
|
||||
ipaddress==1.0.22 # Only needed on 2.6 and 2.7
|
||||
packaging==18.0
|
||||
pep517==0.2
|
||||
pyparsing==2.2.1
|
||||
pytoml==0.1.19
|
||||
retrying==1.3.3
|
||||
requests==2.19.1
|
||||
lockfile==0.12.2
|
||||
msgpack==0.5.6
|
||||
packaging==19.0
|
||||
pep517==0.5.0
|
||||
progress==1.5
|
||||
pyparsing==2.4.0
|
||||
pytoml==0.1.20
|
||||
requests==2.21.0
|
||||
certifi==2019.3.9
|
||||
chardet==3.0.4
|
||||
idna==2.7
|
||||
urllib3==1.23
|
||||
certifi==2018.8.24
|
||||
setuptools==40.4.3
|
||||
idna==2.8
|
||||
urllib3==1.25.2
|
||||
retrying==1.3.3
|
||||
setuptools==41.0.1
|
||||
six==1.12.0
|
||||
webencodings==0.5.1
|
||||
|
||||
@@ -14,8 +14,8 @@ from packaging.requirements import Requirement
|
||||
from packaging.specifiers import SpecifierSet, Specifier
|
||||
|
||||
os.environ["PIP_SHIMS_BASE_MODULE"] = str("pipenv.patched.notpip")
|
||||
from pip_shims.shims import VcsSupport, WheelCache, InstallationError, pip_version
|
||||
from pip_shims.shims import Resolver as PipResolver
|
||||
import pip_shims
|
||||
from pip_shims.shims import VcsSupport, WheelCache, InstallationError
|
||||
|
||||
|
||||
from .._compat import (
|
||||
@@ -112,7 +112,7 @@ class PyPIRepository(BaseRepository):
|
||||
}
|
||||
|
||||
# pip 19.0 has removed process_dependency_links from the PackageFinder constructor
|
||||
if pkg_resources.parse_version(pip_version) < pkg_resources.parse_version('19.0'):
|
||||
if pkg_resources.parse_version(pip_shims.shims.pip_version) < pkg_resources.parse_version('19.0'):
|
||||
finder_kwargs["process_dependency_links"] = pip_options.process_dependency_links
|
||||
|
||||
self.finder = PackageFinder(**finder_kwargs)
|
||||
@@ -279,7 +279,7 @@ class PyPIRepository(BaseRepository):
|
||||
'finder': self.finder,
|
||||
'session': self.session,
|
||||
'upgrade_strategy': "to-satisfy-only",
|
||||
'force_reinstall': True,
|
||||
'force_reinstall': False,
|
||||
'ignore_dependencies': False,
|
||||
'ignore_requires_python': True,
|
||||
'ignore_installed': True,
|
||||
@@ -299,7 +299,7 @@ class PyPIRepository(BaseRepository):
|
||||
reqset = RequirementSet()
|
||||
ireq.is_direct = True
|
||||
# reqset.add_requirement(ireq)
|
||||
resolver = PipResolver(**resolver_kwargs)
|
||||
resolver = pip_shims.shims.Resolver(**resolver_kwargs)
|
||||
resolver.require_hashes = False
|
||||
results = resolver._resolve_one(reqset, ireq)
|
||||
|
||||
@@ -309,6 +309,7 @@ class PyPIRepository(BaseRepository):
|
||||
cleanup_fn()
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
results = set(results) if results else set()
|
||||
return results, ireq
|
||||
|
||||
|
||||
+177
-59
@@ -133,12 +133,104 @@ class Entry(object):
|
||||
del entry_dict["name"]
|
||||
return entry_dict
|
||||
|
||||
def get_cleaned_dict(self):
|
||||
if self.is_updated:
|
||||
@classmethod
|
||||
def parse_pyparsing_exprs(cls, expr_iterable):
|
||||
from pipenv.vendor.pyparsing import Literal, MatchFirst
|
||||
keys = []
|
||||
expr_list = []
|
||||
expr = expr_iterable.copy()
|
||||
if isinstance(expr, Literal) or (
|
||||
expr.__class__.__name__ == Literal.__name__
|
||||
):
|
||||
keys.append(expr.match)
|
||||
elif isinstance(expr, MatchFirst) or (
|
||||
expr.__class__.__name__ == MatchFirst.__name__
|
||||
):
|
||||
expr_list = expr.exprs
|
||||
elif isinstance(expr, list):
|
||||
expr_list = expr
|
||||
if expr_list:
|
||||
for part in expr_list:
|
||||
keys.extend(cls.parse_pyparsing_exprs(part))
|
||||
return keys
|
||||
|
||||
@classmethod
|
||||
def get_markers_from_dict(cls, entry_dict):
|
||||
from pipenv.vendor.packaging import markers as packaging_markers
|
||||
from pipenv.vendor.requirementslib.models.markers import normalize_marker_str
|
||||
marker_keys = cls.parse_pyparsing_exprs(packaging_markers.VARIABLE)
|
||||
markers = set()
|
||||
keys_in_dict = [k for k in marker_keys if k in entry_dict]
|
||||
markers = {
|
||||
normalize_marker_str("{k} {v}".format(k=k, v=entry_dict.pop(k)))
|
||||
for k in keys_in_dict
|
||||
}
|
||||
if "markers" in entry_dict:
|
||||
markers.add(normalize_marker_str(entry_dict["markers"]))
|
||||
if None in markers:
|
||||
markers.remove(None)
|
||||
if markers:
|
||||
entry_dict["markers"] = " and ".join(list(markers))
|
||||
else:
|
||||
markers = None
|
||||
return markers, entry_dict
|
||||
|
||||
@property
|
||||
def markers(self):
|
||||
self._markers, self.entry_dict = self.get_markers_from_dict(self.entry_dict)
|
||||
return self._markers
|
||||
|
||||
@markers.setter
|
||||
def markers(self, markers):
|
||||
if not markers:
|
||||
marker_str = self.marker_to_str(markers)
|
||||
if marker_str:
|
||||
self._entry = self.entry.merge_markers(marker_str)
|
||||
self._markers = self.marker_to_str(self._entry.markers)
|
||||
entry_dict = self.entry_dict.copy()
|
||||
entry_dict["markers"] = self.marker_to_str(self._entry.markers)
|
||||
self.entry_dict = entry_dict
|
||||
|
||||
@property
|
||||
def original_markers(self):
|
||||
original_markers, lockfile_dict = self.get_markers_from_dict(
|
||||
self.lockfile_dict
|
||||
)
|
||||
self.lockfile_dict = lockfile_dict
|
||||
self._original_markers = self.marker_to_str(original_markers)
|
||||
return self._original_markers
|
||||
|
||||
@staticmethod
|
||||
def marker_to_str(marker):
|
||||
from pipenv.vendor.requirementslib.models.markers import normalize_marker_str
|
||||
if not marker:
|
||||
return None
|
||||
from pipenv.vendor import six
|
||||
from pipenv.vendor.vistir.compat import Mapping
|
||||
marker_str = None
|
||||
if isinstance(marker, Mapping):
|
||||
marker_dict, _ = Entry.get_markers_from_dict(marker)
|
||||
if marker_dict:
|
||||
marker_str = "{0}".format(marker_dict.popitem()[1])
|
||||
elif isinstance(marker, (list, set, tuple)):
|
||||
marker_str = " and ".join([normalize_marker_str(m) for m in marker if m])
|
||||
elif isinstance(marker, six.string_types):
|
||||
marker_str = "{0}".format(normalize_marker_str(marker))
|
||||
if isinstance(marker_str, six.string_types):
|
||||
return marker_str
|
||||
return None
|
||||
|
||||
def get_cleaned_dict(self, keep_outdated=False):
|
||||
if keep_outdated and self.is_updated:
|
||||
self.validate_constraints()
|
||||
self.ensure_least_updates_possible()
|
||||
elif not keep_outdated:
|
||||
self.validate_constraints()
|
||||
if self.entry.extras != self.lockfile_entry.extras:
|
||||
self._entry.req.extras.extend(self.lockfile_entry.req.extras)
|
||||
entry_extras = list(self.entry.extras)
|
||||
if self.lockfile_entry.extras:
|
||||
entry_extras.extend(list(self.lockfile_entry.extras))
|
||||
self._entry.req.extras = entry_extras
|
||||
self.entry_dict["extras"] = self.entry.extras
|
||||
entry_hashes = set(self.entry.hashes)
|
||||
locked_hashes = set(self.lockfile_entry.hashes)
|
||||
@@ -146,6 +238,7 @@ class Entry(object):
|
||||
self.entry_dict["hashes"] = list(entry_hashes | locked_hashes)
|
||||
self.entry_dict["name"] = self.name
|
||||
self.entry_dict["version"] = self.strip_version(self.entry_dict["version"])
|
||||
_, self.entry_dict = self.get_markers_from_dict(self.entry_dict)
|
||||
return self.entry_dict
|
||||
|
||||
@property
|
||||
@@ -202,10 +295,10 @@ class Entry(object):
|
||||
def clean_specifier(specifier):
|
||||
from pipenv.vendor.packaging.specifiers import Specifier
|
||||
if not any(specifier.startswith(k) for k in Specifier._operators.keys()):
|
||||
if specifier.strip().lower() in ["any", "*"]:
|
||||
if specifier.strip().lower() in ["any", "<any>", "*"]:
|
||||
return "*"
|
||||
specifier = "=={0}".format(specifier)
|
||||
elif specifier.startswith("==") and specifier.count("=") > 2:
|
||||
elif specifier.startswith("==") and specifier.count("=") > 3:
|
||||
specifier = "=={0}".format(specifier.lstrip("="))
|
||||
return specifier
|
||||
|
||||
@@ -255,7 +348,7 @@ class Entry(object):
|
||||
if not self._requires:
|
||||
self._requires = next(iter(
|
||||
self.project.environment.get_package_requirements(self.name)
|
||||
), None)
|
||||
), {})
|
||||
return self._requires
|
||||
|
||||
@property
|
||||
@@ -284,21 +377,25 @@ class Entry(object):
|
||||
return True
|
||||
|
||||
def get_dependency(self, name):
|
||||
return next(iter(
|
||||
dep for dep in self.requirements.get("dependencies", [])
|
||||
if dep.get("package_name", "") == name
|
||||
), {})
|
||||
if self.requirements:
|
||||
return next(iter(
|
||||
dep for dep in self.requirements.get("dependencies", [])
|
||||
if dep and dep.get("package_name", "") == name
|
||||
), {})
|
||||
return {}
|
||||
|
||||
def get_parent_deps(self, unnest=False):
|
||||
from pipenv.vendor.packaging.specifiers import Specifier
|
||||
parents = []
|
||||
for spec in self.reverse_deps.get(self.normalized_name, {}).get("parents", set()):
|
||||
spec_index = next(iter(c for c in Specifier._operators if c in spec), None)
|
||||
spec_match = next(iter(c for c in Specifier._operators if c in spec), None)
|
||||
name = spec
|
||||
parent = None
|
||||
if spec_index is not None:
|
||||
specifier = self.clean_specifier(spec[spec_index:])
|
||||
name = spec[:spec_index]
|
||||
if spec_match is not None:
|
||||
spec_index = spec.index(spec_match)
|
||||
specifier = self.clean_specifier(spec[spec_index:len(spec_match)]).strip()
|
||||
name_start = spec_index + len(spec_match)
|
||||
name = spec[name_start:].strip()
|
||||
parent = self.create_parent(name, specifier)
|
||||
else:
|
||||
name = spec
|
||||
@@ -373,7 +470,7 @@ class Entry(object):
|
||||
if c and c.name == self.entry.name
|
||||
}
|
||||
pipfile_constraint = self.get_pipfile_constraint()
|
||||
if pipfile_constraint:
|
||||
if pipfile_constraint and not (self.pipfile_entry.editable or pipfile_constraint.editable):
|
||||
constraints.add(pipfile_constraint)
|
||||
return constraints
|
||||
|
||||
@@ -415,10 +512,16 @@ class Entry(object):
|
||||
required = self.clean_specifier(required)
|
||||
parent_requires = self.make_requirement(self.name, required)
|
||||
parent_dependencies.add("{0} => {1} ({2})".format(p.name, self.name, required))
|
||||
if not parent_requires.requirement.specifier.contains(self.original_version):
|
||||
# use pre=True here or else prereleases dont satisfy constraints
|
||||
if parent_requires.requirement.specifier and (
|
||||
not parent_requires.requirement.specifier.contains(self.original_version, prereleases=True)
|
||||
):
|
||||
can_use_original = False
|
||||
if not parent_requires.requirement.specifier.contains(self.updated_version):
|
||||
has_mismatch = True
|
||||
if parent_requires.requirement.specifier and (
|
||||
not parent_requires.requirement.specifier.contains(self.updated_version, prereleases=True)
|
||||
):
|
||||
if not self.entry.editable and self.updated_version != self.original_version:
|
||||
has_mismatch = True
|
||||
if has_mismatch and not can_use_original:
|
||||
from pipenv.exceptions import DependencyConflict
|
||||
msg = (
|
||||
@@ -500,6 +603,24 @@ class Entry(object):
|
||||
return super(Entry, self).__getattribute__(key)
|
||||
|
||||
|
||||
def clean_results(results, resolver, project, dev=False):
|
||||
from pipenv.utils import translate_markers
|
||||
if not project.lockfile_exists:
|
||||
return results
|
||||
lockfile = project.lockfile_content
|
||||
section = "develop" if dev else "default"
|
||||
pipfile_section = "dev-packages" if dev else "packages"
|
||||
reverse_deps = project.environment.reverse_dependencies()
|
||||
new_results = [r for r in results if r["name"] not in lockfile[section]]
|
||||
for result in results:
|
||||
name = result.get("name")
|
||||
entry_dict = result.copy()
|
||||
entry = Entry(name, entry_dict, project, resolver, reverse_deps=reverse_deps, dev=dev)
|
||||
entry_dict = translate_markers(entry.get_cleaned_dict(keep_outdated=False))
|
||||
new_results.append(entry_dict)
|
||||
return new_results
|
||||
|
||||
|
||||
def clean_outdated(results, resolver, project, dev=False):
|
||||
from pipenv.vendor.requirementslib.models.requirements import Requirement
|
||||
if not project.lockfile_exists:
|
||||
@@ -520,24 +641,29 @@ def clean_outdated(results, resolver, project, dev=False):
|
||||
# TODO: Should this be the case for all locking?
|
||||
if entry.was_editable and not entry.is_editable:
|
||||
continue
|
||||
# if the entry has not changed versions since the previous lock,
|
||||
# don't introduce new markers since that is more restrictive
|
||||
if entry.has_markers and not entry.had_markers and not entry.is_updated:
|
||||
del entry.entry_dict["markers"]
|
||||
entry._entry.req.req.marker = None
|
||||
entry._entry.markers = ""
|
||||
# do make sure we retain the original markers for entries that are not changed
|
||||
elif entry.had_markers and not entry.has_markers and not entry.is_updated:
|
||||
if entry._entry and entry._entry.req and entry._entry.req.req and (
|
||||
entry.lockfile_entry and entry.lockfile_entry.req and
|
||||
entry.lockfile_entry.req.req and entry.lockfile_entry.req.req.marker
|
||||
):
|
||||
entry._entry.req.req.marker = entry.lockfile_entry.req.req.marker
|
||||
if entry.lockfile_entry and entry.lockfile_entry.markers:
|
||||
entry._entry.markers = entry.lockfile_entry.markers
|
||||
if entry.lockfile_dict and "markers" in entry.lockfile_dict:
|
||||
entry.entry_dict["markers"] = entry.lockfile_dict["markers"]
|
||||
entry_dict = entry.get_cleaned_dict()
|
||||
lockfile_entry = lockfile[section].get(name, None)
|
||||
if not lockfile_entry:
|
||||
alternate_section = "develop" if not dev else "default"
|
||||
if name in lockfile[alternate_section]:
|
||||
lockfile_entry = lockfile[alternate_section][name]
|
||||
if lockfile_entry and not entry.is_updated:
|
||||
old_markers = next(iter(m for m in (
|
||||
entry.lockfile_entry.markers, lockfile_entry.get("markers", None)
|
||||
) if m is not None), None)
|
||||
new_markers = entry_dict.get("markers", None)
|
||||
if old_markers:
|
||||
old_markers = Entry.marker_to_str(old_markers)
|
||||
if old_markers and not new_markers:
|
||||
entry.markers = old_markers
|
||||
elif new_markers and not old_markers:
|
||||
del entry.entry_dict["markers"]
|
||||
entry._entry.req.req.marker = None
|
||||
entry._entry.markers = None
|
||||
# if the entry has not changed versions since the previous lock,
|
||||
# don't introduce new markers since that is more restrictive
|
||||
# if entry.has_markers and not entry.had_markers and not entry.is_updated:
|
||||
# do make sure we retain the original markers for entries that are not changed
|
||||
entry_dict = entry.get_cleaned_dict(keep_outdated=True)
|
||||
new_results.append(entry_dict)
|
||||
return new_results
|
||||
|
||||
@@ -557,7 +683,6 @@ def parse_packages(packages, pre, clear, system, requirements_dir=None):
|
||||
sys.path.insert(0, req.req.setup_info.base_dir)
|
||||
req.req._setup_info.get_info()
|
||||
req.update_name_from_path(req.req.setup_info.base_dir)
|
||||
print(os.listdir(req.req.setup_info.base_dir))
|
||||
try:
|
||||
name, entry = req.pipfile_entry
|
||||
except Exception:
|
||||
@@ -582,6 +707,8 @@ def resolve_packages(pre, clear, verbose, system, write, requirements_dir, packa
|
||||
)
|
||||
|
||||
def resolve(packages, pre, project, sources, clear, system, requirements_dir=None):
|
||||
from pipenv.patched.piptools import logging as piptools_logging
|
||||
piptools_logging.log.verbosity = 1 if verbose else 0
|
||||
return resolve_deps(
|
||||
packages,
|
||||
which,
|
||||
@@ -611,6 +738,8 @@ def resolve_packages(pre, clear, verbose, system, write, requirements_dir, packa
|
||||
)
|
||||
if keep_outdated:
|
||||
results = clean_outdated(results, resolver, project)
|
||||
else:
|
||||
results = clean_results(results, resolver, project)
|
||||
if write:
|
||||
with open(write, "w") as fh:
|
||||
if not results:
|
||||
@@ -646,29 +775,18 @@ def main():
|
||||
_patch_path(pipenv_site=parsed.pipenv_site)
|
||||
import warnings
|
||||
from pipenv.vendor.vistir.compat import ResourceWarning
|
||||
from pipenv.vendor.vistir.misc import get_wrapped_stream
|
||||
from pipenv.vendor.vistir.misc import replace_with_text_stream
|
||||
warnings.simplefilter("ignore", category=ResourceWarning)
|
||||
import six
|
||||
if six.PY3:
|
||||
stdout = sys.stdout.buffer
|
||||
stderr = sys.stderr.buffer
|
||||
else:
|
||||
stdout = sys.stdout
|
||||
stderr = sys.stderr
|
||||
sys.stderr = get_wrapped_stream(stderr)
|
||||
sys.stdout = get_wrapped_stream(stdout)
|
||||
from pipenv.vendor import colorama
|
||||
if os.name == "nt" and (
|
||||
all(getattr(stream, method, None) for stream in [sys.stdout, sys.stderr] for method in ["write", "isatty"]) and
|
||||
all(stream.isatty() for stream in [sys.stdout, sys.stderr])
|
||||
):
|
||||
stderr_wrapper = colorama.AnsiToWin32(sys.stderr, autoreset=False, convert=None, strip=None)
|
||||
stdout_wrapper = colorama.AnsiToWin32(sys.stdout, autoreset=False, convert=None, strip=None)
|
||||
sys.stderr = stderr_wrapper.stream
|
||||
sys.stdout = stdout_wrapper.stream
|
||||
colorama.init(wrap=False)
|
||||
elif os.name != "nt":
|
||||
colorama.init()
|
||||
replace_with_text_stream("stdout")
|
||||
replace_with_text_stream("stderr")
|
||||
# from pipenv.vendor import colorama
|
||||
# if os.name == "nt" and (
|
||||
# all(getattr(stream, method, None) for stream in [sys.stdout, sys.stderr] for method in ["write", "isatty"]) and
|
||||
# all(stream.isatty() for stream in [sys.stdout, sys.stderr])
|
||||
# ):
|
||||
# colorama.init(wrap=False)
|
||||
# elif os.name != "nt":
|
||||
# colorama.init()
|
||||
os.environ["PIP_DISABLE_PIP_VERSION_CHECK"] = str("1")
|
||||
os.environ["PYTHONIOENCODING"] = str("utf-8")
|
||||
os.environ["PYTHONUNBUFFERED"] = str("1")
|
||||
|
||||
+267
-115
@@ -1,4 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function
|
||||
import contextlib
|
||||
import errno
|
||||
import logging
|
||||
@@ -38,6 +39,8 @@ from .vendor.urllib3 import util as urllib3_util
|
||||
if environments.MYPY_RUNNING:
|
||||
from typing import Tuple, Dict, Any, List, Union, Optional, Text
|
||||
from .vendor.requirementslib.models.requirements import Requirement, Line
|
||||
from .vendor.packaging.markers import Marker
|
||||
from .vendor.packaging.specifiers import Specifier
|
||||
from .project import Project
|
||||
|
||||
|
||||
@@ -292,11 +295,14 @@ def get_pipenv_sitedir():
|
||||
|
||||
|
||||
class Resolver(object):
|
||||
def __init__(self, constraints, req_dir, project, sources, clear=False, pre=False):
|
||||
def __init__(
|
||||
self, constraints, req_dir, project, sources, index_lookup=None,
|
||||
markers_lookup=None, skipped=None, clear=False, pre=False
|
||||
):
|
||||
from pipenv.patched.piptools import logging as piptools_logging
|
||||
if environments.is_verbose():
|
||||
logging.log.verbose = True
|
||||
piptools_logging.log.verbose = True
|
||||
piptools_logging.log.verbosity = environments.PIPENV_VERBOSITY
|
||||
self.initial_constraints = constraints
|
||||
self.req_dir = req_dir
|
||||
self.project = project
|
||||
@@ -306,6 +312,11 @@ class Resolver(object):
|
||||
self.clear = clear
|
||||
self.pre = pre
|
||||
self.results = None
|
||||
self.markers_lookup = markers_lookup if markers_lookup is not None else {}
|
||||
self.index_lookup = index_lookup if index_lookup is not None else {}
|
||||
self.skipped = skipped if skipped is not None else {}
|
||||
self.markers = {}
|
||||
self.requires_python_markers = {}
|
||||
self._pip_args = None
|
||||
self._constraints = None
|
||||
self._parsed_constraints = None
|
||||
@@ -343,7 +354,10 @@ class Resolver(object):
|
||||
index_lookup, # type: Dict[str, str]
|
||||
markers_lookup, # type: Dict[str, str]
|
||||
project, # type: Project
|
||||
sources # type: Dict[str, str]
|
||||
sources, # type: Dict[str, str]
|
||||
req_dir=None, # type: Optional[str]
|
||||
pre=False, # type: bool
|
||||
clear=False, # type: bool
|
||||
):
|
||||
# type: (...) -> Tuple[Set[str], Dict[str, Dict[str, Union[str, bool, List[str]]]], Dict[str, str], Dict[str, str]]
|
||||
constraints = set() # type: Set[str]
|
||||
@@ -352,6 +366,13 @@ class Resolver(object):
|
||||
index_lookup = {}
|
||||
if markers_lookup is None:
|
||||
markers_lookup = {}
|
||||
if not req_dir:
|
||||
from .vendor.vistir.path import create_tracked_tempdir
|
||||
req_dir = create_tracked_tempdir(prefix="pipenv-", suffix="-reqdir")
|
||||
transient_resolver = cls(
|
||||
[], req_dir, project, sources, index_lookup=index_lookup,
|
||||
markers_lookup=markers_lookup, clear=clear, pre=pre
|
||||
)
|
||||
for dep in deps:
|
||||
if not dep:
|
||||
continue
|
||||
@@ -360,7 +381,9 @@ class Resolver(object):
|
||||
)
|
||||
index_lookup.update(req_idx)
|
||||
markers_lookup.update(markers_idx)
|
||||
constraint_update, lockfile_update = cls.get_deps_from_req(req)
|
||||
constraint_update, lockfile_update = cls.get_deps_from_req(
|
||||
req, resolver=transient_resolver
|
||||
)
|
||||
constraints |= constraint_update
|
||||
skipped.update(lockfile_update)
|
||||
return constraints, skipped, index_lookup, markers_lookup
|
||||
@@ -417,12 +440,13 @@ class Resolver(object):
|
||||
return cls.get_deps_from_req(req)
|
||||
|
||||
@classmethod
|
||||
def get_deps_from_req(cls, req):
|
||||
# type: (Requirement) -> Tuple[Set[str], Dict[str, Dict[str, Union[str, bool, List[str]]]]]
|
||||
from requirementslib.models.utils import _requirement_to_str_lowercase_name
|
||||
def get_deps_from_req(cls, req, resolver=None):
|
||||
# type: (Requirement, Optional["Resolver"]) -> Tuple[Set[str], Dict[str, Dict[str, Union[str, bool, List[str]]]]]
|
||||
from .vendor.requirementslib.models.utils import _requirement_to_str_lowercase_name
|
||||
from .vendor.requirementslib.models.requirements import Requirement
|
||||
constraints = set() # type: Set[str]
|
||||
locked_deps = dict() # type: Dict[str, Dict[str, Union[str, bool, List[str]]]]
|
||||
if req.is_file_or_url or req.is_vcs and not req.is_wheel:
|
||||
if (req.is_file_or_url or req.is_vcs) and not req.is_wheel:
|
||||
# for local packages with setup.py files and potential direct url deps:
|
||||
if req.is_vcs:
|
||||
req_list, lockfile = get_vcs_deps(reqs=[req])
|
||||
@@ -437,6 +461,7 @@ class Resolver(object):
|
||||
except TypeError:
|
||||
raise RequirementError(req=req)
|
||||
setup_info = req.req.setup_info
|
||||
setup_info.get_info()
|
||||
locked_deps[pep423_name(name)] = entry
|
||||
requirements = [v for v in getattr(setup_info, "requires", {}).values()]
|
||||
for r in requirements:
|
||||
@@ -446,20 +471,22 @@ class Resolver(object):
|
||||
continue
|
||||
line = _requirement_to_str_lowercase_name(r)
|
||||
new_req, _, _ = cls.parse_line(line)
|
||||
if r.marker and not r.marker.evaluate():
|
||||
new_constraints = {}
|
||||
_, new_entry = req.pipfile_entry
|
||||
new_lock = {
|
||||
pep423_name(new_req.normalized_name): new_entry
|
||||
}
|
||||
else:
|
||||
new_constraints, new_lock = cls.get_deps_from_req(new_req)
|
||||
locked_deps.update(new_lock)
|
||||
constraints |= new_constraints
|
||||
else:
|
||||
if r is not None:
|
||||
line = _requirement_to_str_lowercase_name(r)
|
||||
constraints.add(line)
|
||||
if r.marker and not r.marker.evaluate():
|
||||
new_constraints = {}
|
||||
_, new_entry = req.pipfile_entry
|
||||
new_lock = {
|
||||
pep423_name(new_req.normalized_name): new_entry
|
||||
}
|
||||
else:
|
||||
new_constraints, new_lock = cls.get_deps_from_req(
|
||||
new_req, resolver
|
||||
)
|
||||
locked_deps.update(new_lock)
|
||||
constraints |= new_constraints
|
||||
# if there is no marker or there is a valid marker, add the constraint line
|
||||
elif r and (not r.marker or (r.marker and r.marker.evaluate())):
|
||||
line = _requirement_to_str_lowercase_name(r)
|
||||
constraints.add(line)
|
||||
# ensure the top level entry remains as provided
|
||||
# note that we shouldn't pin versions for editable vcs deps
|
||||
if (not req.is_vcs or (req.is_vcs and not req.editable)):
|
||||
@@ -477,10 +504,58 @@ class Resolver(object):
|
||||
req.req.setup_path is not None and os.path.exists(req.req.setup_path)):
|
||||
constraints.add(req.constraint_line)
|
||||
else:
|
||||
# if the dependency isn't installable, don't add it to constraints
|
||||
# and instead add it directly to the lock
|
||||
if req and req.requirement and (
|
||||
req.requirement.marker and not req.requirement.marker.evaluate()
|
||||
):
|
||||
pypi = resolver.repository if resolver else None
|
||||
best_match = pypi.find_best_match(req.ireq) if pypi else None
|
||||
if best_match:
|
||||
hashes = resolver.collect_hashes(best_match) if resolver else []
|
||||
new_req = Requirement.from_ireq(best_match)
|
||||
new_req = new_req.add_hashes(hashes)
|
||||
name, entry = new_req.pipfile_entry
|
||||
locked_deps[pep423_name(name)] = translate_markers(entry)
|
||||
return constraints, locked_deps
|
||||
constraints.add(req.constraint_line)
|
||||
return constraints, locked_deps
|
||||
return constraints, locked_deps
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
deps, # type: List[str]
|
||||
index_lookup=None, # type: Dict[str, str]
|
||||
markers_lookup=None, # type: Dict[str, str]
|
||||
project=None, # type: Project
|
||||
sources=None, # type: List[str]
|
||||
req_dir=None, # type: str
|
||||
clear=False, # type: bool
|
||||
pre=False # type: bool
|
||||
):
|
||||
# type: (...) -> "Resolver"
|
||||
from pipenv.vendor.vistir.path import create_tracked_tempdir
|
||||
if not req_dir:
|
||||
req_dir = create_tracked_tempdir(suffix="-requirements", prefix="pipenv-")
|
||||
if index_lookup is None:
|
||||
index_lookup = {}
|
||||
if markers_lookup is None:
|
||||
markers_lookup = {}
|
||||
if project is None:
|
||||
from pipenv.core import project
|
||||
project = project
|
||||
if sources is None:
|
||||
sources = project.sources
|
||||
constraints, skipped, index_lookup, markers_lookup = cls.get_metadata(
|
||||
deps, index_lookup, markers_lookup, project, sources, req_dir=req_dir,
|
||||
pre=pre, clear=clear
|
||||
)
|
||||
return Resolver(
|
||||
constraints, req_dir, project, sources, index_lookup=index_lookup,
|
||||
markers_lookup=markers_lookup, skipped=skipped, clear=clear, pre=pre
|
||||
)
|
||||
|
||||
@property
|
||||
def pip_command(self):
|
||||
if self._pip_command is None:
|
||||
@@ -529,16 +604,16 @@ class Resolver(object):
|
||||
pip_options, _ = self.pip_command.parser.parse_args(self.pip_args)
|
||||
pip_options.cache_dir = environments.PIPENV_CACHE_DIR
|
||||
self._pip_options = pip_options
|
||||
if environments.is_verbose():
|
||||
click_echo(
|
||||
crayons.blue("Using pip: {0}".format(" ".join(self.pip_args))), err=True
|
||||
)
|
||||
return self._pip_options
|
||||
|
||||
@property
|
||||
def session(self):
|
||||
if self._session is None:
|
||||
self._session = self.pip_command._build_session(self.pip_options)
|
||||
# if environments.is_verbose():
|
||||
# click_echo(
|
||||
# crayons.blue("Using pip: {0}".format(" ".join(self.pip_args))), err=True
|
||||
# )
|
||||
return self._session
|
||||
|
||||
@property
|
||||
@@ -602,6 +677,35 @@ class Resolver(object):
|
||||
self.resolved_tree.update(results)
|
||||
return self.resolved_tree
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def fetch_candidate(self, ireq):
|
||||
candidates = self.repository.find_all_candidates(ireq.name)
|
||||
matched_version = next(iter(sorted(
|
||||
ireq.specifier.filter((c.version for c in candidates), True), reverse=True)
|
||||
), None)
|
||||
if matched_version:
|
||||
matched_candidate = next(iter(
|
||||
c for c in candidates if c.version == matched_version
|
||||
))
|
||||
return matched_candidate
|
||||
return None
|
||||
|
||||
def resolve_constraints(self):
|
||||
new_tree = set()
|
||||
for result in self.resolved_tree:
|
||||
if result.markers:
|
||||
self.markers[result.name] = result.markers
|
||||
else:
|
||||
candidate = self.fetch_candidate(result)
|
||||
if getattr(candidate, "requires_python", None):
|
||||
marker = make_marker_from_specifier(candidate.requires_python)
|
||||
self.markers[result.name] = marker
|
||||
result.markers = marker
|
||||
if result.req:
|
||||
result.req.marker = marker
|
||||
new_tree.add(result)
|
||||
self.resolved_tree = new_tree
|
||||
|
||||
@classmethod
|
||||
def prepend_hash_types(cls, checksums):
|
||||
cleaned_checksums = []
|
||||
@@ -720,6 +824,89 @@ class Resolver(object):
|
||||
self.hashes[ireq] = self.get_hash(ireq, ireq_hashes=ireq_hashes)
|
||||
return self.hashes
|
||||
|
||||
def _clean_skipped_result(self, req, value):
|
||||
ref = None
|
||||
if req.is_vcs:
|
||||
ref = req.commit_hash
|
||||
ireq = req.as_ireq()
|
||||
entry = value.copy()
|
||||
entry["name"] = req.name
|
||||
if entry.get("editable", False) and entry.get("version"):
|
||||
del entry["version"]
|
||||
ref = ref if ref is not None else entry.get("ref")
|
||||
if ref:
|
||||
entry["ref"] = ref
|
||||
if self._should_include_hash(ireq):
|
||||
collected_hashes = self.collect_hashes(ireq)
|
||||
if collected_hashes:
|
||||
entry["hashes"] = sorted(set(collected_hashes))
|
||||
return req.name, entry
|
||||
|
||||
def clean_results(self):
|
||||
from pipenv.vendor.requirementslib.models.requirements import Requirement
|
||||
reqs = [(Requirement.from_ireq(ireq), ireq) for ireq in self.resolved_tree]
|
||||
results = {}
|
||||
for req, ireq in reqs:
|
||||
if (req.vcs and req.editable and not req.is_direct_url):
|
||||
continue
|
||||
collected_hashes = self.collect_hashes(ireq)
|
||||
req = req.add_hashes(collected_hashes)
|
||||
if not collected_hashes and self._should_include_hash(ireq):
|
||||
discovered_hashes = self.hashes.get(ireq, set()) | self.get_hash(ireq)
|
||||
if discovered_hashes:
|
||||
req = req.add_hashes(discovered_hashes)
|
||||
self.hashes[ireq] = collected_hashes = discovered_hashes
|
||||
if collected_hashes:
|
||||
collected_hashes = sorted(set(collected_hashes))
|
||||
name, entry = format_requirement_for_lockfile(
|
||||
req, self.markers_lookup, self.index_lookup, collected_hashes
|
||||
)
|
||||
entry = translate_markers(entry)
|
||||
if name in results:
|
||||
results[name].update(entry)
|
||||
else:
|
||||
results[name] = entry
|
||||
for k in list(self.skipped.keys()):
|
||||
req = Requirement.from_pipfile(k, self.skipped[k])
|
||||
name, entry = self._clean_skipped_result(req, self.skipped[k])
|
||||
entry = translate_markers(entry)
|
||||
if name in results:
|
||||
results[name].update(entry)
|
||||
else:
|
||||
results[name] = entry
|
||||
results = list(results.values())
|
||||
return results
|
||||
|
||||
|
||||
def format_requirement_for_lockfile(req, markers_lookup, index_lookup, hashes=None):
|
||||
if req.specifiers:
|
||||
version = str(req.get_version())
|
||||
else:
|
||||
version = None
|
||||
index = index_lookup.get(req.normalized_name)
|
||||
markers = markers_lookup.get(req.normalized_name)
|
||||
req.index = index
|
||||
name, pf_entry = req.pipfile_entry
|
||||
name = pep423_name(req.name)
|
||||
entry = {}
|
||||
if isinstance(pf_entry, six.string_types):
|
||||
entry["version"] = pf_entry.lstrip("=")
|
||||
else:
|
||||
entry.update(pf_entry)
|
||||
if version is not None:
|
||||
entry["version"] = version
|
||||
if req.line_instance.is_direct_url:
|
||||
entry["file"] = req.req.uri
|
||||
if hashes:
|
||||
entry["hashes"] = sorted(set(hashes))
|
||||
entry["name"] = name
|
||||
if index: # and index != next(iter(project.sources), {}).get("name"):
|
||||
entry.update({"index": index})
|
||||
if markers:
|
||||
entry.update({"markers": markers})
|
||||
entry = translate_markers(entry)
|
||||
return name, entry
|
||||
|
||||
|
||||
def _show_warning(message, category, filename, lineno, line):
|
||||
warnings.showwarning(message=message, category=category, filename=filename,
|
||||
@@ -738,87 +925,23 @@ def actually_resolve_deps(
|
||||
req_dir=None,
|
||||
):
|
||||
from pipenv.vendor.vistir.path import create_tracked_tempdir
|
||||
from pipenv.vendor.requirementslib.models.requirements import Requirement
|
||||
|
||||
if not req_dir:
|
||||
req_dir = create_tracked_tempdir(suffix="-requirements", prefix="pipenv-")
|
||||
warning_list = []
|
||||
|
||||
with warnings.catch_warnings(record=True) as warning_list:
|
||||
constraints, skipped, index_lookup, markers_lookup = Resolver.get_metadata(
|
||||
deps, index_lookup, markers_lookup, project, sources,
|
||||
resolver = Resolver.create(
|
||||
deps, index_lookup, markers_lookup, project, sources, req_dir, clear, pre
|
||||
)
|
||||
resolver = Resolver(constraints, req_dir, project, sources, clear=clear, pre=pre)
|
||||
resolved_tree = resolver.resolve()
|
||||
resolver.resolve()
|
||||
hashes = resolver.resolve_hashes()
|
||||
reqs = [(Requirement.from_ireq(ireq), ireq) for ireq in resolved_tree]
|
||||
results = {}
|
||||
for req, ireq in reqs:
|
||||
if (req.vcs and req.editable and not req.is_direct_url):
|
||||
continue
|
||||
collected_hashes = resolver.collect_hashes(ireq)
|
||||
if collected_hashes:
|
||||
req = req.add_hashes(collected_hashes)
|
||||
elif resolver._should_include_hash(ireq):
|
||||
existing_hashes = hashes.get(ireq, set())
|
||||
discovered_hashes = existing_hashes | resolver.get_hash(ireq)
|
||||
if discovered_hashes:
|
||||
req = req.add_hashes(discovered_hashes)
|
||||
resolver.hashes[ireq] = discovered_hashes
|
||||
if req.specifiers:
|
||||
version = str(req.get_version())
|
||||
else:
|
||||
version = None
|
||||
index = index_lookup.get(req.normalized_name)
|
||||
markers = markers_lookup.get(req.normalized_name)
|
||||
req.index = index
|
||||
name, pf_entry = req.pipfile_entry
|
||||
name = pep423_name(req.name)
|
||||
entry = {}
|
||||
if isinstance(pf_entry, six.string_types):
|
||||
entry["version"] = pf_entry.lstrip("=")
|
||||
else:
|
||||
entry.update(pf_entry)
|
||||
if version is not None:
|
||||
entry["version"] = version
|
||||
if req.line_instance.is_direct_url:
|
||||
entry["file"] = req.req.uri
|
||||
if collected_hashes:
|
||||
entry["hashes"] = sorted(set(collected_hashes))
|
||||
entry["name"] = name
|
||||
if index: # and index != next(iter(project.sources), {}).get("name"):
|
||||
entry.update({"index": index})
|
||||
if markers:
|
||||
entry.update({"markers": markers})
|
||||
entry = translate_markers(entry)
|
||||
if name in results:
|
||||
results[name].update(entry)
|
||||
else:
|
||||
results[name] = entry
|
||||
for k in list(skipped.keys()):
|
||||
req = Requirement.from_pipfile(k, skipped[k])
|
||||
ref = None
|
||||
if req.is_vcs:
|
||||
ref = req.commit_hash
|
||||
ireq = req.as_ireq()
|
||||
entry = skipped[k].copy()
|
||||
entry["name"] = req.name
|
||||
ref = ref if ref is not None else entry.get("ref")
|
||||
if ref:
|
||||
entry["ref"] = ref
|
||||
if resolver._should_include_hash(ireq):
|
||||
collected_hashes = resolver.collect_hashes(ireq)
|
||||
if collected_hashes:
|
||||
entry["hashes"] = sorted(set(collected_hashes))
|
||||
if k in results:
|
||||
results[k].update(entry)
|
||||
else:
|
||||
results[k] = entry
|
||||
results = list(results.values())
|
||||
resolver.resolve_constraints()
|
||||
results = resolver.clean_results()
|
||||
for warning in warning_list:
|
||||
_show_warning(warning.message, warning.category, warning.filename, warning.lineno,
|
||||
warning.line)
|
||||
return (results, hashes, markers_lookup, resolver, skipped)
|
||||
return (results, hashes, resolver.markers_lookup, resolver, resolver.skipped)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
@@ -842,33 +965,40 @@ def resolve(cmd, sp):
|
||||
from .cmdparse import Script
|
||||
from .vendor.pexpect.exceptions import EOF, TIMEOUT
|
||||
from .vendor.vistir.compat import to_native_string
|
||||
from .vendor.vistir.misc import echo
|
||||
EOF.__module__ = "pexpect.exceptions"
|
||||
from ._compat import decode_output
|
||||
c = delegator.run(Script.parse(cmd).cmdify(), block=False, env=os.environ.copy())
|
||||
if environments.is_verbose():
|
||||
c.subprocess.logfile = sys.stderr
|
||||
_out = decode_output("")
|
||||
result = None
|
||||
out = to_native_string("")
|
||||
while True:
|
||||
result = None
|
||||
try:
|
||||
result = c.expect(u"\n", timeout=environments.PIPENV_INSTALL_TIMEOUT)
|
||||
except (EOF, TIMEOUT):
|
||||
pass
|
||||
_out = c.subprocess.before
|
||||
if _out is not None:
|
||||
_out = decode_output("{0}".format(_out))
|
||||
if _out:
|
||||
_out = decode_output("{0}\n".format(_out))
|
||||
out += _out
|
||||
sp.text = to_native_string("{0}".format(_out[:100]))
|
||||
if environments.is_verbose():
|
||||
sp.hide_and_write(_out.rstrip())
|
||||
if result is None:
|
||||
# if environments.is_verbose():
|
||||
# sp.hide_and_write(_out.rstrip())
|
||||
_out = to_native_string("")
|
||||
if not result and not _out:
|
||||
break
|
||||
c.block()
|
||||
if c.return_code != 0:
|
||||
sp.red.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format(
|
||||
"Locking Failed!"
|
||||
))
|
||||
click_echo(c.out.strip(), err=True)
|
||||
click_echo(c.err.strip(), err=True)
|
||||
echo(c.out.strip(), err=True)
|
||||
if not environments.is_verbose():
|
||||
echo(out, err=True)
|
||||
echo(c.err.strip(), err=True)
|
||||
sys.exit(c.return_code)
|
||||
return c
|
||||
|
||||
@@ -922,6 +1052,7 @@ def prepare_lockfile(results, pipfile, lockfile):
|
||||
lockfile[name] = lockfile_entry[name]
|
||||
else:
|
||||
lockfile[name].update(lockfile_entry[name])
|
||||
lockfile[name] = translate_markers(lockfile[name])
|
||||
else:
|
||||
lockfile[name] = lockfile_entry[name]
|
||||
return lockfile
|
||||
@@ -1038,9 +1169,6 @@ def venv_resolve_deps(
|
||||
raise RuntimeError("There was a problem with locking.")
|
||||
if os.path.exists(target_file.name):
|
||||
os.unlink(target_file.name)
|
||||
if environments.is_verbose():
|
||||
click_echo(results, err=True)
|
||||
|
||||
if lockfile_section not in lockfile:
|
||||
lockfile[lockfile_section] = {}
|
||||
prepare_lockfile(results, pipfile, lockfile[lockfile_section])
|
||||
@@ -1607,23 +1735,24 @@ def translate_markers(pipfile_entry):
|
||||
"""
|
||||
if not isinstance(pipfile_entry, Mapping):
|
||||
raise TypeError("Entry is not a pipfile formatted mapping.")
|
||||
from .vendor.distlib.markers import DEFAULT_CONTEXT as marker_context
|
||||
from .vendor.packaging.markers import Marker
|
||||
from .vendor.packaging.markers import Marker, default_environment
|
||||
from .vendor.vistir.misc import dedup
|
||||
|
||||
allowed_marker_keys = ["markers"] + [k for k in marker_context.keys()]
|
||||
allowed_marker_keys = ["markers"] + list(default_environment().keys())
|
||||
provided_keys = list(pipfile_entry.keys()) if hasattr(pipfile_entry, "keys") else []
|
||||
pipfile_markers = [k for k in provided_keys if k in allowed_marker_keys]
|
||||
pipfile_markers = set(provided_keys) & set(allowed_marker_keys)
|
||||
new_pipfile = dict(pipfile_entry).copy()
|
||||
marker_set = set()
|
||||
if "markers" in new_pipfile:
|
||||
marker = str(Marker(new_pipfile.pop("markers")))
|
||||
if 'extra' not in marker:
|
||||
marker_set.add(marker)
|
||||
marker_str = new_pipfile.pop("markers")
|
||||
if marker_str is not None:
|
||||
marker = str(Marker(marker_str))
|
||||
if 'extra' not in marker:
|
||||
marker_set.add(marker)
|
||||
for m in pipfile_markers:
|
||||
entry = "{0}".format(pipfile_entry[m])
|
||||
if m != "markers":
|
||||
marker_set.add(str(Marker("{0}{1}".format(m, entry))))
|
||||
marker_set.add(str(Marker("{0} {1}".format(m, entry))))
|
||||
new_pipfile.pop(m)
|
||||
if marker_set:
|
||||
new_pipfile["markers"] = str(Marker(" or ".join(
|
||||
@@ -1639,7 +1768,7 @@ def clean_resolved_dep(dep, is_top_level=False, pipfile_entry=None):
|
||||
lockfile = {}
|
||||
# We use this to determine if there are any markers on top level packages
|
||||
# So we can make sure those win out during resolution if the packages reoccur
|
||||
if "version" in dep:
|
||||
if "version" in dep and dep["version"] and not dep.get("editable", False):
|
||||
version = "{0}".format(dep["version"])
|
||||
if not version.startswith("=="):
|
||||
version = "=={0}".format(version)
|
||||
@@ -1939,3 +2068,26 @@ def is_python_command(line):
|
||||
if line.startswith("py"):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def make_marker_from_specifier(spec):
|
||||
# type: (str) -> Optional[Marker]
|
||||
"""Given a python version specifier, create a marker
|
||||
|
||||
:param spec: A specifier
|
||||
:type spec: str
|
||||
:return: A new marker
|
||||
:rtype: Optional[:class:`packaging.marker.Marker`]
|
||||
"""
|
||||
from .vendor.packaging.specifiers import SpecifierSet, Specifier
|
||||
from .vendor.packaging.markers import Marker
|
||||
from .vendor.requirementslib.models.markers import cleanup_pyspecs, format_pyversion
|
||||
if not any(spec.startswith(k) for k in Specifier._operators.keys()):
|
||||
if spec.strip().lower() in ["any", "<any>", "*"]:
|
||||
return None
|
||||
spec = "=={0}".format(spec)
|
||||
elif spec.startswith("==") and spec.count("=") > 3:
|
||||
spec = "=={0}".format(spec.lstrip("="))
|
||||
specset = cleanup_pyspecs(SpecifierSet(spec))
|
||||
marker_str = " and ".join([format_pyversion(pv) for pv in specset])
|
||||
return Marker(marker_str)
|
||||
|
||||
Vendored
+1
-1
@@ -1,5 +1,5 @@
|
||||
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
|
||||
from . import weakref
|
||||
from . import enum
|
||||
from . import shutil_get_terminal_size
|
||||
from . import enum
|
||||
from . import functools_lru_cache
|
||||
|
||||
Vendored
+9
-6
@@ -10,20 +10,23 @@
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from pkg_resources import get_distribution, DistributionNotFound
|
||||
|
||||
from cerberus.validator import DocumentError, Validator
|
||||
from cerberus.schema import (rules_set_registry, schema_registry, Registry,
|
||||
SchemaError)
|
||||
from cerberus.schema import rules_set_registry, schema_registry, SchemaError
|
||||
from cerberus.utils import TypeDefinition
|
||||
|
||||
|
||||
__version__ = "1.2"
|
||||
try:
|
||||
__version__ = get_distribution("Cerberus").version
|
||||
except DistributionNotFound:
|
||||
__version__ = "unknown"
|
||||
|
||||
__all__ = [
|
||||
DocumentError.__name__,
|
||||
Registry.__name__,
|
||||
SchemaError.__name__,
|
||||
TypeDefinition.__name__,
|
||||
Validator.__name__,
|
||||
'schema_registry',
|
||||
'rules_set_registry'
|
||||
"schema_registry",
|
||||
"rules_set_registry",
|
||||
]
|
||||
|
||||
Vendored
+105
-97
@@ -3,12 +3,12 @@
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from collections import defaultdict, namedtuple, MutableMapping
|
||||
from collections import defaultdict, namedtuple
|
||||
from copy import copy, deepcopy
|
||||
from functools import wraps
|
||||
from pprint import pformat
|
||||
|
||||
from cerberus.platform import PYTHON_VERSION
|
||||
from cerberus.platform import PYTHON_VERSION, MutableMapping
|
||||
from cerberus.utils import compare_paths_lt, quote_string
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ UNALLOWED_VALUE = ErrorDefinition(0x44, 'allowed')
|
||||
UNALLOWED_VALUES = ErrorDefinition(0x45, 'allowed')
|
||||
FORBIDDEN_VALUE = ErrorDefinition(0x46, 'forbidden')
|
||||
FORBIDDEN_VALUES = ErrorDefinition(0x47, 'forbidden')
|
||||
MISSING_MEMBERS = ErrorDefinition(0x48, 'contains')
|
||||
|
||||
# other
|
||||
NORMALIZATION = ErrorDefinition(0x60, None)
|
||||
@@ -66,9 +67,10 @@ SETTING_DEFAULT_FAILED = ErrorDefinition(0x64, 'default_setter')
|
||||
ERROR_GROUP = ErrorDefinition(0x80, None)
|
||||
MAPPING_SCHEMA = ErrorDefinition(0x81, 'schema')
|
||||
SEQUENCE_SCHEMA = ErrorDefinition(0x82, 'schema')
|
||||
KEYSCHEMA = ErrorDefinition(0x83, 'keyschema')
|
||||
VALUESCHEMA = ErrorDefinition(0x84, 'valueschema')
|
||||
BAD_ITEMS = ErrorDefinition(0x8f, 'items')
|
||||
# TODO remove KEYSCHEMA AND VALUESCHEMA with next major release
|
||||
KEYSRULES = KEYSCHEMA = ErrorDefinition(0x83, 'keysrules')
|
||||
VALUESRULES = VALUESCHEMA = ErrorDefinition(0x84, 'valuesrules')
|
||||
BAD_ITEMS = ErrorDefinition(0x8F, 'items')
|
||||
|
||||
LOGICAL = ErrorDefinition(0x90, None)
|
||||
NONEOF = ErrorDefinition(0x91, 'noneof')
|
||||
@@ -79,8 +81,7 @@ ALLOF = ErrorDefinition(0x94, 'allof')
|
||||
|
||||
""" SchemaError messages """
|
||||
|
||||
SCHEMA_ERROR_DEFINITION_TYPE = \
|
||||
"schema definition for field '{0}' must be a dict"
|
||||
SCHEMA_ERROR_DEFINITION_TYPE = "schema definition for field '{0}' must be a dict"
|
||||
SCHEMA_ERROR_MISSING = "validation schema missing"
|
||||
|
||||
|
||||
@@ -89,8 +90,8 @@ SCHEMA_ERROR_MISSING = "validation schema missing"
|
||||
|
||||
class ValidationError(object):
|
||||
""" A simple class to store and query basic error information. """
|
||||
def __init__(self, document_path, schema_path, code, rule, constraint,
|
||||
value, info):
|
||||
|
||||
def __init__(self, document_path, schema_path, code, rule, constraint, value, info):
|
||||
self.document_path = document_path
|
||||
""" The path to the field within the document that caused the error.
|
||||
Type: :class:`tuple` """
|
||||
@@ -115,8 +116,7 @@ class ValidationError(object):
|
||||
|
||||
def __hash__(self):
|
||||
""" Expects that all other properties are transitively determined. """
|
||||
return hash(self.document_path) ^ hash(self.schema_path) \
|
||||
^ hash(self.code)
|
||||
return hash(self.document_path) ^ hash(self.schema_path) ^ hash(self.code)
|
||||
|
||||
def __lt__(self, other):
|
||||
if self.document_path != other.document_path:
|
||||
@@ -125,20 +125,24 @@ class ValidationError(object):
|
||||
return compare_paths_lt(self.schema_path, other.schema_path)
|
||||
|
||||
def __repr__(self):
|
||||
return "{class_name} @ {memptr} ( " \
|
||||
"document_path={document_path}," \
|
||||
"schema_path={schema_path}," \
|
||||
"code={code}," \
|
||||
"constraint={constraint}," \
|
||||
"value={value}," \
|
||||
"info={info} )"\
|
||||
.format(class_name=self.__class__.__name__, memptr=hex(id(self)), # noqa: E501
|
||||
document_path=self.document_path,
|
||||
schema_path=self.schema_path,
|
||||
code=hex(self.code),
|
||||
constraint=quote_string(self.constraint),
|
||||
value=quote_string(self.value),
|
||||
info=self.info)
|
||||
return (
|
||||
"{class_name} @ {memptr} ( "
|
||||
"document_path={document_path},"
|
||||
"schema_path={schema_path},"
|
||||
"code={code},"
|
||||
"constraint={constraint},"
|
||||
"value={value},"
|
||||
"info={info} )".format(
|
||||
class_name=self.__class__.__name__,
|
||||
memptr=hex(id(self)), # noqa: E501
|
||||
document_path=self.document_path,
|
||||
schema_path=self.schema_path,
|
||||
code=hex(self.code),
|
||||
constraint=quote_string(self.constraint),
|
||||
value=quote_string(self.value),
|
||||
info=self.info,
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def child_errors(self):
|
||||
@@ -190,11 +194,13 @@ class ErrorList(list):
|
||||
""" A list for :class:`~cerberus.errors.ValidationError` instances that
|
||||
can be queried with the ``in`` keyword for a particular
|
||||
:class:`~cerberus.errors.ErrorDefinition`. """
|
||||
|
||||
def __contains__(self, error_definition):
|
||||
for code in (x.code for x in self):
|
||||
if code == error_definition.code:
|
||||
return True
|
||||
return False
|
||||
if not isinstance(error_definition, ErrorDefinition):
|
||||
raise TypeError
|
||||
|
||||
wanted_code = error_definition.code
|
||||
return any(x.code == wanted_code for x in self)
|
||||
|
||||
|
||||
class ErrorTreeNode(MutableMapping):
|
||||
@@ -203,14 +209,10 @@ class ErrorTreeNode(MutableMapping):
|
||||
def __init__(self, path, parent_node):
|
||||
self.parent_node = parent_node
|
||||
self.tree_root = self.parent_node.tree_root
|
||||
self.path = path[:self.parent_node.depth + 1]
|
||||
self.path = path[: self.parent_node.depth + 1]
|
||||
self.errors = ErrorList()
|
||||
self.descendants = {}
|
||||
|
||||
def __add__(self, error):
|
||||
self.add(error)
|
||||
return self
|
||||
|
||||
def __contains__(self, item):
|
||||
if isinstance(item, ErrorDefinition):
|
||||
return item in self.errors
|
||||
@@ -228,6 +230,7 @@ class ErrorTreeNode(MutableMapping):
|
||||
for error in self.errors:
|
||||
if item.code == error.code:
|
||||
return error
|
||||
return None
|
||||
else:
|
||||
return self.descendants.get(item)
|
||||
|
||||
@@ -258,14 +261,16 @@ class ErrorTreeNode(MutableMapping):
|
||||
if key not in self.descendants:
|
||||
self[key] = ErrorTreeNode(error_path, self)
|
||||
|
||||
node = self[key]
|
||||
|
||||
if len(error_path) == self.depth + 1:
|
||||
self[key].errors.append(error)
|
||||
self[key].errors.sort()
|
||||
node.errors.append(error)
|
||||
node.errors.sort()
|
||||
if error.is_group_error:
|
||||
for child_error in error.child_errors:
|
||||
self.tree_root += child_error
|
||||
self.tree_root.add(child_error)
|
||||
else:
|
||||
self[key] += error
|
||||
node.add(error)
|
||||
|
||||
def _path_of_(self, error):
|
||||
return getattr(error, self.tree_type + '_path')
|
||||
@@ -274,14 +279,15 @@ class ErrorTreeNode(MutableMapping):
|
||||
class ErrorTree(ErrorTreeNode):
|
||||
""" Base class for :class:`~cerberus.errors.DocumentErrorTree` and
|
||||
:class:`~cerberus.errors.SchemaErrorTree`. """
|
||||
def __init__(self, errors=[]):
|
||||
|
||||
def __init__(self, errors=()):
|
||||
self.parent_node = None
|
||||
self.tree_root = self
|
||||
self.path = ()
|
||||
self.errors = ErrorList()
|
||||
self.descendants = {}
|
||||
for error in errors:
|
||||
self += error
|
||||
self.add(error)
|
||||
|
||||
def add(self, error):
|
||||
""" Add an error to the tree.
|
||||
@@ -323,18 +329,21 @@ class ErrorTree(ErrorTreeNode):
|
||||
class DocumentErrorTree(ErrorTree):
|
||||
""" Implements a dict-like class to query errors by indexes following the
|
||||
structure of a validated document. """
|
||||
|
||||
tree_type = 'document'
|
||||
|
||||
|
||||
class SchemaErrorTree(ErrorTree):
|
||||
""" Implements a dict-like class to query errors by indexes following the
|
||||
structure of the used schema. """
|
||||
|
||||
tree_type = 'schema'
|
||||
|
||||
|
||||
class BaseErrorHandler(object):
|
||||
""" Base class for all error handlers.
|
||||
Subclasses are identified as error-handlers with an instance-test. """
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
""" Optionally initialize a new instance. """
|
||||
pass
|
||||
@@ -411,9 +420,9 @@ def encode_unicode(f):
|
||||
This decorator ensures that if legacy Python is used unicode
|
||||
strings are encoded before passing to a function.
|
||||
"""
|
||||
|
||||
@wraps(f)
|
||||
def wrapped(obj, error):
|
||||
|
||||
def _encode(value):
|
||||
"""Helper encoding unicode strings into binary utf-8"""
|
||||
if isinstance(value, unicode): # noqa: F821
|
||||
@@ -436,56 +445,52 @@ class BasicErrorHandler(BaseErrorHandler):
|
||||
through :class:`str` a pretty-formatted representation of that
|
||||
tree is returned.
|
||||
"""
|
||||
messages = {0x00: "{0}",
|
||||
|
||||
0x01: "document is missing",
|
||||
0x02: "required field",
|
||||
0x03: "unknown field",
|
||||
0x04: "field '{0}' is required",
|
||||
0x05: "depends on these values: {constraint}",
|
||||
0x06: "{0} must not be present with '{field}'",
|
||||
|
||||
0x21: "'{0}' is not a document, must be a dict",
|
||||
0x22: "empty values not allowed",
|
||||
0x23: "null value not allowed",
|
||||
0x24: "must be of {constraint} type",
|
||||
0x25: "must be of dict type",
|
||||
0x26: "length of list should be {constraint}, it is {0}",
|
||||
0x27: "min length is {constraint}",
|
||||
0x28: "max length is {constraint}",
|
||||
|
||||
0x41: "value does not match regex '{constraint}'",
|
||||
0x42: "min value is {constraint}",
|
||||
0x43: "max value is {constraint}",
|
||||
0x44: "unallowed value {value}",
|
||||
0x45: "unallowed values {0}",
|
||||
0x46: "unallowed value {value}",
|
||||
0x47: "unallowed values {0}",
|
||||
|
||||
0x61: "field '{field}' cannot be coerced: {0}",
|
||||
0x62: "field '{field}' cannot be renamed: {0}",
|
||||
0x63: "field is read-only",
|
||||
0x64: "default value for '{field}' cannot be set: {0}",
|
||||
|
||||
0x81: "mapping doesn't validate subschema: {0}",
|
||||
0x82: "one or more sequence-items don't validate: {0}",
|
||||
0x83: "one or more keys of a mapping don't validate: {0}",
|
||||
0x84: "one or more values in a mapping don't validate: {0}",
|
||||
0x85: "one or more sequence-items don't validate: {0}",
|
||||
|
||||
0x91: "one or more definitions validate",
|
||||
0x92: "none or more than one rule validate",
|
||||
0x93: "no definitions validate",
|
||||
0x94: "one or more definitions don't validate"
|
||||
}
|
||||
messages = {
|
||||
0x00: "{0}",
|
||||
0x01: "document is missing",
|
||||
0x02: "required field",
|
||||
0x03: "unknown field",
|
||||
0x04: "field '{0}' is required",
|
||||
0x05: "depends on these values: {constraint}",
|
||||
0x06: "{0} must not be present with '{field}'",
|
||||
0x21: "'{0}' is not a document, must be a dict",
|
||||
0x22: "empty values not allowed",
|
||||
0x23: "null value not allowed",
|
||||
0x24: "must be of {constraint} type",
|
||||
0x25: "must be of dict type",
|
||||
0x26: "length of list should be {constraint}, it is {0}",
|
||||
0x27: "min length is {constraint}",
|
||||
0x28: "max length is {constraint}",
|
||||
0x41: "value does not match regex '{constraint}'",
|
||||
0x42: "min value is {constraint}",
|
||||
0x43: "max value is {constraint}",
|
||||
0x44: "unallowed value {value}",
|
||||
0x45: "unallowed values {0}",
|
||||
0x46: "unallowed value {value}",
|
||||
0x47: "unallowed values {0}",
|
||||
0x48: "missing members {0}",
|
||||
0x61: "field '{field}' cannot be coerced: {0}",
|
||||
0x62: "field '{field}' cannot be renamed: {0}",
|
||||
0x63: "field is read-only",
|
||||
0x64: "default value for '{field}' cannot be set: {0}",
|
||||
0x81: "mapping doesn't validate subschema: {0}",
|
||||
0x82: "one or more sequence-items don't validate: {0}",
|
||||
0x83: "one or more keys of a mapping don't validate: {0}",
|
||||
0x84: "one or more values in a mapping don't validate: {0}",
|
||||
0x85: "one or more sequence-items don't validate: {0}",
|
||||
0x91: "one or more definitions validate",
|
||||
0x92: "none or more than one rule validate",
|
||||
0x93: "no definitions validate",
|
||||
0x94: "one or more definitions don't validate",
|
||||
}
|
||||
|
||||
def __init__(self, tree=None):
|
||||
self.tree = {} if tree is None else tree
|
||||
|
||||
def __call__(self, errors=None):
|
||||
if errors is not None:
|
||||
self.clear()
|
||||
self.extend(errors)
|
||||
def __call__(self, errors):
|
||||
self.clear()
|
||||
self.extend(errors)
|
||||
return self.pretty_tree
|
||||
|
||||
def __str__(self):
|
||||
@@ -511,8 +516,9 @@ class BasicErrorHandler(BaseErrorHandler):
|
||||
elif error.is_group_error:
|
||||
self._insert_group_error(error)
|
||||
elif error.code in self.messages:
|
||||
self._insert_error(error.document_path,
|
||||
self._format_message(error.field, error))
|
||||
self._insert_error(
|
||||
error.document_path, self._format_message(error.field, error)
|
||||
)
|
||||
|
||||
def clear(self):
|
||||
self.tree = {}
|
||||
@@ -522,8 +528,8 @@ class BasicErrorHandler(BaseErrorHandler):
|
||||
|
||||
def _format_message(self, field, error):
|
||||
return self.messages[error.code].format(
|
||||
*error.info, constraint=error.constraint,
|
||||
field=field, value=error.value)
|
||||
*error.info, constraint=error.constraint, field=field, value=error.value
|
||||
)
|
||||
|
||||
def _insert_error(self, path, node):
|
||||
""" Adds an error or sub-tree to :attr:tree.
|
||||
@@ -559,14 +565,14 @@ class BasicErrorHandler(BaseErrorHandler):
|
||||
elif child_error.is_group_error:
|
||||
self._insert_group_error(child_error)
|
||||
else:
|
||||
self._insert_error(child_error.document_path,
|
||||
self._format_message(child_error.field,
|
||||
child_error))
|
||||
self._insert_error(
|
||||
child_error.document_path,
|
||||
self._format_message(child_error.field, child_error),
|
||||
)
|
||||
|
||||
def _insert_logic_error(self, error):
|
||||
field = error.field
|
||||
self._insert_error(error.document_path,
|
||||
self._format_message(field, error))
|
||||
self._insert_error(error.document_path, self._format_message(field, error))
|
||||
|
||||
for definition_errors in error.definitions_errors.values():
|
||||
for child_error in definition_errors:
|
||||
@@ -575,8 +581,10 @@ class BasicErrorHandler(BaseErrorHandler):
|
||||
elif child_error.is_group_error:
|
||||
self._insert_group_error(child_error)
|
||||
else:
|
||||
self._insert_error(child_error.document_path,
|
||||
self._format_message(field, child_error))
|
||||
self._insert_error(
|
||||
child_error.document_path,
|
||||
self._format_message(field, child_error),
|
||||
)
|
||||
|
||||
def _purge_empty_dicts(self, error_list):
|
||||
subtree = error_list[-1]
|
||||
|
||||
Vendored
+26
@@ -12,3 +12,29 @@ if PYTHON_VERSION < 3:
|
||||
else:
|
||||
_str_type = str
|
||||
_int_types = (int,)
|
||||
|
||||
|
||||
if PYTHON_VERSION < 3.3:
|
||||
from collections import ( # noqa: F401
|
||||
Callable,
|
||||
Container,
|
||||
Hashable,
|
||||
Iterable,
|
||||
Mapping,
|
||||
MutableMapping,
|
||||
Sequence,
|
||||
Set,
|
||||
Sized,
|
||||
)
|
||||
else:
|
||||
from collections.abc import ( # noqa: F401
|
||||
Callable,
|
||||
Container,
|
||||
Hashable,
|
||||
Iterable,
|
||||
Mapping,
|
||||
MutableMapping,
|
||||
Sequence,
|
||||
Set,
|
||||
Sized,
|
||||
)
|
||||
|
||||
Vendored
+176
-125
@@ -1,13 +1,23 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from collections import (Callable, Hashable, Iterable, Mapping,
|
||||
MutableMapping, Sequence)
|
||||
from copy import copy
|
||||
from warnings import warn
|
||||
|
||||
from cerberus import errors
|
||||
from cerberus.platform import _str_type
|
||||
from cerberus.utils import (get_Validator_class, validator_factory,
|
||||
mapping_hash, TypeDefinition)
|
||||
from cerberus.platform import (
|
||||
_str_type,
|
||||
Callable,
|
||||
Hashable,
|
||||
Mapping,
|
||||
MutableMapping,
|
||||
Sequence,
|
||||
)
|
||||
from cerberus.utils import (
|
||||
get_Validator_class,
|
||||
validator_factory,
|
||||
mapping_hash,
|
||||
TypeDefinition,
|
||||
)
|
||||
|
||||
|
||||
class _Abort(Exception):
|
||||
@@ -17,6 +27,7 @@ class _Abort(Exception):
|
||||
class SchemaError(Exception):
|
||||
""" Raised when the validation schema is missing, has the wrong format or
|
||||
contains errors. """
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@@ -26,18 +37,19 @@ class DefinitionSchema(MutableMapping):
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if 'SchemaValidator' not in globals():
|
||||
global SchemaValidator
|
||||
SchemaValidator = validator_factory('SchemaValidator',
|
||||
SchemaValidatorMixin)
|
||||
SchemaValidator = validator_factory('SchemaValidator', SchemaValidatorMixin)
|
||||
types_mapping = SchemaValidator.types_mapping.copy()
|
||||
types_mapping.update({
|
||||
'callable': TypeDefinition('callable', (Callable,), ()),
|
||||
'hashable': TypeDefinition('hashable', (Hashable,), ())
|
||||
})
|
||||
types_mapping.update(
|
||||
{
|
||||
'callable': TypeDefinition('callable', (Callable,), ()),
|
||||
'hashable': TypeDefinition('hashable', (Hashable,), ()),
|
||||
}
|
||||
)
|
||||
SchemaValidator.types_mapping = types_mapping
|
||||
|
||||
return super(DefinitionSchema, cls).__new__(cls)
|
||||
|
||||
def __init__(self, validator, schema={}):
|
||||
def __init__(self, validator, schema):
|
||||
"""
|
||||
:param validator: An instance of Validator-(sub-)class that uses this
|
||||
schema.
|
||||
@@ -45,8 +57,7 @@ class DefinitionSchema(MutableMapping):
|
||||
one.
|
||||
"""
|
||||
if not isinstance(validator, get_Validator_class()):
|
||||
raise RuntimeError('validator argument must be a Validator-'
|
||||
'instance.')
|
||||
raise RuntimeError('validator argument must be a Validator-' 'instance.')
|
||||
self.validator = validator
|
||||
|
||||
if isinstance(schema, _str_type):
|
||||
@@ -56,14 +67,16 @@ class DefinitionSchema(MutableMapping):
|
||||
try:
|
||||
schema = dict(schema)
|
||||
except Exception:
|
||||
raise SchemaError(
|
||||
errors.SCHEMA_ERROR_DEFINITION_TYPE.format(schema))
|
||||
raise SchemaError(errors.SCHEMA_ERROR_DEFINITION_TYPE.format(schema))
|
||||
|
||||
self.validation_schema = SchemaValidationSchema(validator)
|
||||
self.schema_validator = SchemaValidator(
|
||||
None, allow_unknown=self.validation_schema,
|
||||
None,
|
||||
allow_unknown=self.validation_schema,
|
||||
error_handler=errors.SchemaErrorHandler,
|
||||
target_schema=schema, target_validator=validator)
|
||||
target_schema=schema,
|
||||
target_validator=validator,
|
||||
)
|
||||
|
||||
schema = self.expand(schema)
|
||||
self.validate(schema)
|
||||
@@ -110,6 +123,10 @@ class DefinitionSchema(MutableMapping):
|
||||
schema = cls._expand_subschemas(schema)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# TODO remove this with the next major release
|
||||
schema = cls._rename_deprecated_rulenames(schema)
|
||||
|
||||
return schema
|
||||
|
||||
@classmethod
|
||||
@@ -119,13 +136,15 @@ class DefinitionSchema(MutableMapping):
|
||||
:param schema: The schema-definition to expand.
|
||||
:return: The expanded schema-definition.
|
||||
"""
|
||||
|
||||
def is_of_rule(x):
|
||||
return isinstance(x, _str_type) and \
|
||||
x.startswith(('allof_', 'anyof_', 'noneof_', 'oneof_'))
|
||||
return isinstance(x, _str_type) and x.startswith(
|
||||
('allof_', 'anyof_', 'noneof_', 'oneof_')
|
||||
)
|
||||
|
||||
for field in schema:
|
||||
for of_rule in (x for x in schema[field] if is_of_rule(x)):
|
||||
operator, rule = of_rule.split('_')
|
||||
operator, rule = of_rule.split('_', 1)
|
||||
schema[field].update({operator: []})
|
||||
for value in schema[field][of_rule]:
|
||||
schema[field][operator].append({rule: value})
|
||||
@@ -135,15 +154,15 @@ class DefinitionSchema(MutableMapping):
|
||||
@classmethod
|
||||
def _expand_subschemas(cls, schema):
|
||||
def has_schema_rule():
|
||||
return isinstance(schema[field], Mapping) and \
|
||||
'schema' in schema[field]
|
||||
return isinstance(schema[field], Mapping) and 'schema' in schema[field]
|
||||
|
||||
def has_mapping_schema():
|
||||
""" Tries to determine heuristically if the schema-constraints are
|
||||
aimed to mappings. """
|
||||
try:
|
||||
return all(isinstance(x, Mapping) for x
|
||||
in schema[field]['schema'].values())
|
||||
return all(
|
||||
isinstance(x, Mapping) for x in schema[field]['schema'].values()
|
||||
)
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
@@ -153,13 +172,12 @@ class DefinitionSchema(MutableMapping):
|
||||
elif has_mapping_schema():
|
||||
schema[field]['schema'] = cls.expand(schema[field]['schema'])
|
||||
else: # assumes schema-constraints for a sequence
|
||||
schema[field]['schema'] = \
|
||||
cls.expand({0: schema[field]['schema']})[0]
|
||||
schema[field]['schema'] = cls.expand({0: schema[field]['schema']})[0]
|
||||
|
||||
for rule in ('keyschema', 'valueschema'):
|
||||
# TODO remove the last two values in the tuple with the next major release
|
||||
for rule in ('keysrules', 'valuesrules', 'keyschema', 'valueschema'):
|
||||
if rule in schema[field]:
|
||||
schema[field][rule] = \
|
||||
cls.expand({0: schema[field][rule]})[0]
|
||||
schema[field][rule] = cls.expand({0: schema[field][rule]})[0]
|
||||
|
||||
for rule in ('allof', 'anyof', 'items', 'noneof', 'oneof'):
|
||||
if rule in schema[field]:
|
||||
@@ -171,6 +189,12 @@ class DefinitionSchema(MutableMapping):
|
||||
schema[field][rule] = new_rules_definition
|
||||
return schema
|
||||
|
||||
def get(self, item, default=None):
|
||||
return self.schema.get(item, default)
|
||||
|
||||
def items(self):
|
||||
return self.schema.items()
|
||||
|
||||
def update(self, schema):
|
||||
try:
|
||||
schema = self.expand(schema)
|
||||
@@ -178,31 +202,64 @@ class DefinitionSchema(MutableMapping):
|
||||
_new_schema.update(schema)
|
||||
self.validate(_new_schema)
|
||||
except ValueError:
|
||||
raise SchemaError(errors.SCHEMA_ERROR_DEFINITION_TYPE
|
||||
.format(schema))
|
||||
raise SchemaError(errors.SCHEMA_ERROR_DEFINITION_TYPE.format(schema))
|
||||
except Exception as e:
|
||||
raise e
|
||||
else:
|
||||
self.schema = _new_schema
|
||||
|
||||
# TODO remove with next major release
|
||||
@staticmethod
|
||||
def _rename_deprecated_rulenames(schema):
|
||||
for field, rules in schema.items():
|
||||
|
||||
if isinstance(rules, str): # registry reference
|
||||
continue
|
||||
|
||||
for old, new in (
|
||||
('keyschema', 'keysrules'),
|
||||
('validator', 'check_with'),
|
||||
('valueschema', 'valuesrules'),
|
||||
):
|
||||
|
||||
if old not in rules:
|
||||
continue
|
||||
|
||||
if new in rules:
|
||||
raise RuntimeError(
|
||||
"The rule '{new}' is also present with its old "
|
||||
"name '{old}' in the same set of rules."
|
||||
)
|
||||
|
||||
warn(
|
||||
"The rule '{old}' was renamed to '{new}'. The old name will "
|
||||
"not be available in the next major release of "
|
||||
"Cerberus.".format(old=old, new=new),
|
||||
DeprecationWarning,
|
||||
)
|
||||
schema[field][new] = schema[field][old]
|
||||
schema[field].pop(old)
|
||||
|
||||
return schema
|
||||
|
||||
def regenerate_validation_schema(self):
|
||||
self.validation_schema = SchemaValidationSchema(self.validator)
|
||||
|
||||
def validate(self, schema=None):
|
||||
""" Validates a schema that defines rules against supported rules.
|
||||
|
||||
:param schema: The schema to be validated as a legal cerberus schema
|
||||
according to the rules of the assigned Validator object.
|
||||
Raises a :class:`~cerberus.base.SchemaError` when an invalid
|
||||
schema is encountered. """
|
||||
if schema is None:
|
||||
schema = self.schema
|
||||
_hash = (mapping_hash(schema),
|
||||
mapping_hash(self.validator.types_mapping))
|
||||
_hash = (mapping_hash(schema), mapping_hash(self.validator.types_mapping))
|
||||
if _hash not in self.validator._valid_schemas:
|
||||
self._validate(schema)
|
||||
self.validator._valid_schemas.add(_hash)
|
||||
|
||||
def _validate(self, schema):
|
||||
""" Validates a schema that defines rules against supported rules.
|
||||
|
||||
:param schema: The schema to be validated as a legal cerberus schema
|
||||
according to the rules of this Validator object.
|
||||
"""
|
||||
if isinstance(schema, _str_type):
|
||||
schema = self.validator.schema_registry.get(schema, schema)
|
||||
|
||||
@@ -212,8 +269,7 @@ class DefinitionSchema(MutableMapping):
|
||||
schema = copy(schema)
|
||||
for field in schema:
|
||||
if isinstance(schema[field], _str_type):
|
||||
schema[field] = rules_set_registry.get(schema[field],
|
||||
schema[field])
|
||||
schema[field] = rules_set_registry.get(schema[field], schema[field])
|
||||
|
||||
if not self.schema_validator(schema, normalize=False):
|
||||
raise SchemaError(self.schema_validator.errors)
|
||||
@@ -236,31 +292,31 @@ class UnvalidatedSchema(DefinitionSchema):
|
||||
|
||||
class SchemaValidationSchema(UnvalidatedSchema):
|
||||
def __init__(self, validator):
|
||||
self.schema = {'allow_unknown': False,
|
||||
'schema': validator.rules,
|
||||
'type': 'dict'}
|
||||
self.schema = {
|
||||
'allow_unknown': False,
|
||||
'schema': validator.rules,
|
||||
'type': 'dict',
|
||||
}
|
||||
|
||||
|
||||
class SchemaValidatorMixin(object):
|
||||
""" This validator is extended to validate schemas passed to a Cerberus
|
||||
""" This validator mixin provides mechanics to validate schemas passed to a Cerberus
|
||||
validator. """
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault('known_rules_set_refs', set())
|
||||
kwargs.setdefault('known_schema_refs', set())
|
||||
super(SchemaValidatorMixin, self).__init__(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def known_rules_set_refs(self):
|
||||
""" The encountered references to rules set registry items. """
|
||||
return self._config.get('known_rules_set_refs', ())
|
||||
|
||||
@known_rules_set_refs.setter
|
||||
def known_rules_set_refs(self, value):
|
||||
self._config['known_rules_set_refs'] = value
|
||||
return self._config['known_rules_set_refs']
|
||||
|
||||
@property
|
||||
def known_schema_refs(self):
|
||||
""" The encountered references to schema registry items. """
|
||||
return self._config.get('known_schema_refs', ())
|
||||
|
||||
@known_schema_refs.setter
|
||||
def known_schema_refs(self, value):
|
||||
self._config['known_schema_refs'] = value
|
||||
return self._config['known_schema_refs']
|
||||
|
||||
@property
|
||||
def target_schema(self):
|
||||
@@ -272,35 +328,13 @@ class SchemaValidatorMixin(object):
|
||||
""" The validator whose schema is being validated. """
|
||||
return self._config['target_validator']
|
||||
|
||||
def _validate_logical(self, rule, field, value):
|
||||
""" {'allowed': ('allof', 'anyof', 'noneof', 'oneof')} """
|
||||
if not isinstance(value, Sequence):
|
||||
self._error(field, errors.BAD_TYPE)
|
||||
return
|
||||
|
||||
validator = self._get_child_validator(
|
||||
document_crumb=rule, allow_unknown=False,
|
||||
schema=self.target_validator.validation_rules)
|
||||
|
||||
for constraints in value:
|
||||
_hash = (mapping_hash({'turing': constraints}),
|
||||
mapping_hash(self.target_validator.types_mapping))
|
||||
if _hash in self.target_validator._valid_schemas:
|
||||
continue
|
||||
|
||||
validator(constraints, normalize=False)
|
||||
if validator._errors:
|
||||
self._error(validator._errors)
|
||||
else:
|
||||
self.target_validator._valid_schemas.add(_hash)
|
||||
|
||||
def _validator_bulk_schema(self, field, value):
|
||||
def _check_with_bulk_schema(self, field, value):
|
||||
# resolve schema registry reference
|
||||
if isinstance(value, _str_type):
|
||||
if value in self.known_rules_set_refs:
|
||||
return
|
||||
else:
|
||||
self.known_rules_set_refs += (value,)
|
||||
self.known_rules_set_refs.add(value)
|
||||
definition = self.target_validator.rules_set_registry.get(value)
|
||||
if definition is None:
|
||||
self._error(field, 'Rules set definition %s not found.' % value)
|
||||
@@ -308,28 +342,32 @@ class SchemaValidatorMixin(object):
|
||||
else:
|
||||
value = definition
|
||||
|
||||
_hash = (mapping_hash({'turing': value}),
|
||||
mapping_hash(self.target_validator.types_mapping))
|
||||
_hash = (
|
||||
mapping_hash({'turing': value}),
|
||||
mapping_hash(self.target_validator.types_mapping),
|
||||
)
|
||||
if _hash in self.target_validator._valid_schemas:
|
||||
return
|
||||
|
||||
validator = self._get_child_validator(
|
||||
document_crumb=field, allow_unknown=False,
|
||||
schema=self.target_validator.rules)
|
||||
document_crumb=field,
|
||||
allow_unknown=False,
|
||||
schema=self.target_validator.rules,
|
||||
)
|
||||
validator(value, normalize=False)
|
||||
if validator._errors:
|
||||
self._error(validator._errors)
|
||||
else:
|
||||
self.target_validator._valid_schemas.add(_hash)
|
||||
|
||||
def _validator_dependencies(self, field, value):
|
||||
def _check_with_dependencies(self, field, value):
|
||||
if isinstance(value, _str_type):
|
||||
pass
|
||||
elif isinstance(value, Mapping):
|
||||
validator = self._get_child_validator(
|
||||
document_crumb=field,
|
||||
schema={'valueschema': {'type': 'list'}},
|
||||
allow_unknown=True
|
||||
schema={'valuesrules': {'type': 'list'}},
|
||||
allow_unknown=True,
|
||||
)
|
||||
if not validator(value, normalize=False):
|
||||
self._error(validator._errors)
|
||||
@@ -338,54 +376,36 @@ class SchemaValidatorMixin(object):
|
||||
path = self.document_path + (field,)
|
||||
self._error(path, 'All dependencies must be a hashable type.')
|
||||
|
||||
def _validator_handler(self, field, value):
|
||||
if isinstance(value, Callable):
|
||||
return
|
||||
if isinstance(value, _str_type):
|
||||
if value not in self.target_validator.validators + \
|
||||
self.target_validator.coercers:
|
||||
self._error(field, '%s is no valid coercer' % value)
|
||||
elif isinstance(value, Iterable):
|
||||
for handler in value:
|
||||
self._validator_handler(field, handler)
|
||||
|
||||
def _validator_items(self, field, value):
|
||||
def _check_with_items(self, field, value):
|
||||
for i, schema in enumerate(value):
|
||||
self._validator_bulk_schema((field, i), schema)
|
||||
self._check_with_bulk_schema((field, i), schema)
|
||||
|
||||
def _validator_schema(self, field, value):
|
||||
def _check_with_schema(self, field, value):
|
||||
try:
|
||||
value = self._handle_schema_reference_for_validator(field, value)
|
||||
except _Abort:
|
||||
return
|
||||
|
||||
_hash = (mapping_hash(value),
|
||||
mapping_hash(self.target_validator.types_mapping))
|
||||
_hash = (mapping_hash(value), mapping_hash(self.target_validator.types_mapping))
|
||||
if _hash in self.target_validator._valid_schemas:
|
||||
return
|
||||
|
||||
validator = self._get_child_validator(
|
||||
document_crumb=field,
|
||||
schema=None, allow_unknown=self.root_allow_unknown)
|
||||
document_crumb=field, schema=None, allow_unknown=self.root_allow_unknown
|
||||
)
|
||||
validator(self._expand_rules_set_refs(value), normalize=False)
|
||||
if validator._errors:
|
||||
self._error(validator._errors)
|
||||
else:
|
||||
self.target_validator._valid_schemas.add(_hash)
|
||||
|
||||
def _handle_schema_reference_for_validator(self, field, value):
|
||||
if not isinstance(value, _str_type):
|
||||
return value
|
||||
if value in self.known_schema_refs:
|
||||
raise _Abort
|
||||
|
||||
self.known_schema_refs += (value,)
|
||||
definition = self.target_validator.schema_registry.get(value)
|
||||
if definition is None:
|
||||
path = self.document_path + (field,)
|
||||
self._error(path, 'Schema definition {} not found.'.format(value))
|
||||
raise _Abort
|
||||
return definition
|
||||
def _check_with_type(self, field, value):
|
||||
value = set((value,)) if isinstance(value, _str_type) else set(value)
|
||||
invalid_constraints = value - set(self.target_validator.types)
|
||||
if invalid_constraints:
|
||||
self._error(
|
||||
field, 'Unsupported types: {}'.format(', '.join(invalid_constraints))
|
||||
)
|
||||
|
||||
def _expand_rules_set_refs(self, schema):
|
||||
result = {}
|
||||
@@ -396,15 +416,46 @@ class SchemaValidatorMixin(object):
|
||||
result[k] = v
|
||||
return result
|
||||
|
||||
def _validator_type(self, field, value):
|
||||
value = (value,) if isinstance(value, _str_type) else value
|
||||
invalid_constraints = ()
|
||||
for constraint in value:
|
||||
if constraint not in self.target_validator.types:
|
||||
invalid_constraints += (constraint,)
|
||||
if invalid_constraints:
|
||||
def _handle_schema_reference_for_validator(self, field, value):
|
||||
if not isinstance(value, _str_type):
|
||||
return value
|
||||
if value in self.known_schema_refs:
|
||||
raise _Abort
|
||||
|
||||
self.known_schema_refs.add(value)
|
||||
definition = self.target_validator.schema_registry.get(value)
|
||||
if definition is None:
|
||||
path = self.document_path + (field,)
|
||||
self._error(path, 'Unsupported types: %s' % invalid_constraints)
|
||||
self._error(path, 'Schema definition {} not found.'.format(value))
|
||||
raise _Abort
|
||||
return definition
|
||||
|
||||
def _validate_logical(self, rule, field, value):
|
||||
""" {'allowed': ('allof', 'anyof', 'noneof', 'oneof')} """
|
||||
if not isinstance(value, Sequence):
|
||||
self._error(field, errors.BAD_TYPE)
|
||||
return
|
||||
|
||||
validator = self._get_child_validator(
|
||||
document_crumb=rule,
|
||||
allow_unknown=False,
|
||||
schema=self.target_validator.validation_rules,
|
||||
)
|
||||
|
||||
for constraints in value:
|
||||
_hash = (
|
||||
mapping_hash({'turing': constraints}),
|
||||
mapping_hash(self.target_validator.types_mapping),
|
||||
)
|
||||
if _hash in self.target_validator._valid_schemas:
|
||||
continue
|
||||
|
||||
validator(constraints, normalize=False)
|
||||
if validator._errors:
|
||||
self._error(validator._errors)
|
||||
else:
|
||||
self.target_validator._valid_schemas.add(_hash)
|
||||
|
||||
|
||||
####
|
||||
|
||||
|
||||
+29
-14
@@ -1,22 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from cerberus import errors, Validator, SchemaError, DocumentError
|
||||
from cerberus.tests.conftest import sample_schema
|
||||
|
||||
|
||||
def assert_exception(exception, document={}, schema=None, validator=None,
|
||||
msg=None):
|
||||
def assert_exception(exception, document={}, schema=None, validator=None, msg=None):
|
||||
""" Tests whether a specific exception is raised. Optionally also tests
|
||||
whether the exception message is as expected. """
|
||||
if validator is None:
|
||||
validator = Validator()
|
||||
if msg is None:
|
||||
with pytest.raises(exception) as excinfo:
|
||||
with pytest.raises(exception):
|
||||
validator(document, schema)
|
||||
else:
|
||||
with pytest.raises(exception, message=msg) as excinfo: # noqa: F841
|
||||
with pytest.raises(exception, match=re.escape(msg)):
|
||||
validator(document, schema)
|
||||
|
||||
|
||||
@@ -32,8 +33,15 @@ def assert_document_error(*args):
|
||||
assert_exception(DocumentError, *args)
|
||||
|
||||
|
||||
def assert_fail(document, schema=None, validator=None, update=False,
|
||||
error=None, errors=None, child_errors=None):
|
||||
def assert_fail(
|
||||
document,
|
||||
schema=None,
|
||||
validator=None,
|
||||
update=False,
|
||||
error=None,
|
||||
errors=None,
|
||||
child_errors=None,
|
||||
):
|
||||
""" Tests whether a validation fails. """
|
||||
if validator is None:
|
||||
validator = Validator(sample_schema)
|
||||
@@ -45,8 +53,7 @@ def assert_fail(document, schema=None, validator=None, update=False,
|
||||
|
||||
assert not (error is not None and errors is not None)
|
||||
assert not (errors is not None and child_errors is not None), (
|
||||
'child_errors can only be tested in '
|
||||
'conjunction with the error parameter'
|
||||
'child_errors can only be tested in ' 'conjunction with the error parameter'
|
||||
)
|
||||
assert not (child_errors is not None and error is None)
|
||||
if error is not None:
|
||||
@@ -99,7 +106,8 @@ def assert_has_error(_errors, d_path, s_path, error_def, constraint, info=()):
|
||||
else:
|
||||
break
|
||||
else:
|
||||
raise AssertionError("""
|
||||
raise AssertionError(
|
||||
"""
|
||||
Error with properties:
|
||||
document_path={doc_path}
|
||||
schema_path={schema_path}
|
||||
@@ -108,9 +116,15 @@ def assert_has_error(_errors, d_path, s_path, error_def, constraint, info=()):
|
||||
info={info}
|
||||
not found in errors:
|
||||
{errors}
|
||||
""".format(doc_path=d_path, schema_path=s_path,
|
||||
code=hex(error.code), info=info,
|
||||
constraint=constraint, errors=_errors))
|
||||
""".format(
|
||||
doc_path=d_path,
|
||||
schema_path=s_path,
|
||||
code=hex(error.code),
|
||||
info=info,
|
||||
constraint=constraint,
|
||||
errors=_errors,
|
||||
)
|
||||
)
|
||||
return i
|
||||
|
||||
|
||||
@@ -133,8 +147,9 @@ def assert_not_has_error(_errors, *args, **kwargs):
|
||||
|
||||
|
||||
def assert_bad_type(field, data_type, value):
|
||||
assert_fail({field: value},
|
||||
error=(field, (field, 'type'), errors.BAD_TYPE, data_type))
|
||||
assert_fail(
|
||||
{field: value}, error=(field, (field, 'type'), errors.BAD_TYPE, data_type)
|
||||
)
|
||||
|
||||
|
||||
def assert_normalized(document, expected, schema=None, validator=None):
|
||||
|
||||
+23
-76
@@ -23,67 +23,27 @@ def validator():
|
||||
|
||||
|
||||
sample_schema = {
|
||||
'a_string': {
|
||||
'type': 'string',
|
||||
'minlength': 2,
|
||||
'maxlength': 10
|
||||
},
|
||||
'a_binary': {
|
||||
'type': 'binary',
|
||||
'minlength': 2,
|
||||
'maxlength': 10
|
||||
},
|
||||
'a_nullable_integer': {
|
||||
'type': 'integer',
|
||||
'nullable': True
|
||||
},
|
||||
'an_integer': {
|
||||
'type': 'integer',
|
||||
'min': 1,
|
||||
'max': 100,
|
||||
},
|
||||
'a_restricted_integer': {
|
||||
'type': 'integer',
|
||||
'allowed': [-1, 0, 1],
|
||||
},
|
||||
'a_boolean': {
|
||||
'type': 'boolean',
|
||||
},
|
||||
'a_datetime': {
|
||||
'type': 'datetime',
|
||||
},
|
||||
'a_float': {
|
||||
'type': 'float',
|
||||
'min': 1,
|
||||
'max': 100,
|
||||
},
|
||||
'a_number': {
|
||||
'type': 'number',
|
||||
'min': 1,
|
||||
'max': 100,
|
||||
},
|
||||
'a_set': {
|
||||
'type': 'set',
|
||||
},
|
||||
'one_or_more_strings': {
|
||||
'type': ['string', 'list'],
|
||||
'schema': {'type': 'string'}
|
||||
},
|
||||
'a_string': {'type': 'string', 'minlength': 2, 'maxlength': 10},
|
||||
'a_binary': {'type': 'binary', 'minlength': 2, 'maxlength': 10},
|
||||
'a_nullable_integer': {'type': 'integer', 'nullable': True},
|
||||
'an_integer': {'type': 'integer', 'min': 1, 'max': 100},
|
||||
'a_restricted_integer': {'type': 'integer', 'allowed': [-1, 0, 1]},
|
||||
'a_boolean': {'type': 'boolean', 'meta': 'can haz two distinct states'},
|
||||
'a_datetime': {'type': 'datetime', 'meta': {'format': '%a, %d. %b %Y'}},
|
||||
'a_float': {'type': 'float', 'min': 1, 'max': 100},
|
||||
'a_number': {'type': 'number', 'min': 1, 'max': 100},
|
||||
'a_set': {'type': 'set'},
|
||||
'one_or_more_strings': {'type': ['string', 'list'], 'schema': {'type': 'string'}},
|
||||
'a_regex_email': {
|
||||
'type': 'string',
|
||||
'regex': '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
|
||||
'regex': r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$',
|
||||
},
|
||||
'a_readonly_string': {
|
||||
'type': 'string',
|
||||
'readonly': True,
|
||||
},
|
||||
'a_restricted_string': {
|
||||
'type': 'string',
|
||||
'allowed': ["agent", "client", "vendor"],
|
||||
},
|
||||
'an_array': {
|
||||
'a_readonly_string': {'type': 'string', 'readonly': True},
|
||||
'a_restricted_string': {'type': 'string', 'allowed': ['agent', 'client', 'vendor']},
|
||||
'an_array': {'type': 'list', 'allowed': ['agent', 'client', 'vendor']},
|
||||
'an_array_from_set': {
|
||||
'type': 'list',
|
||||
'allowed': ["agent", "client", "vendor"],
|
||||
'allowed': set(['agent', 'client', 'vendor']),
|
||||
},
|
||||
'a_list_of_dicts': {
|
||||
'type': 'list',
|
||||
@@ -97,38 +57,25 @@ sample_schema = {
|
||||
},
|
||||
'a_list_of_values': {
|
||||
'type': 'list',
|
||||
'items': [{'type': 'string'}, {'type': 'integer'}, ]
|
||||
},
|
||||
'a_list_of_integers': {
|
||||
'type': 'list',
|
||||
'schema': {'type': 'integer'},
|
||||
'items': [{'type': 'string'}, {'type': 'integer'}],
|
||||
},
|
||||
'a_list_of_integers': {'type': 'list', 'schema': {'type': 'integer'}},
|
||||
'a_dict': {
|
||||
'type': 'dict',
|
||||
'schema': {
|
||||
'address': {'type': 'string'},
|
||||
'city': {'type': 'string', 'required': True}
|
||||
'city': {'type': 'string', 'required': True},
|
||||
},
|
||||
},
|
||||
'a_dict_with_valueschema': {
|
||||
'type': 'dict',
|
||||
'valueschema': {'type': 'integer'}
|
||||
},
|
||||
'a_dict_with_keyschema': {
|
||||
'type': 'dict',
|
||||
'keyschema': {'type': 'string', 'regex': '[a-z]+'}
|
||||
},
|
||||
'a_dict_with_valuesrules': {'type': 'dict', 'valuesrules': {'type': 'integer'}},
|
||||
'a_list_length': {
|
||||
'type': 'list',
|
||||
'schema': {'type': 'integer'},
|
||||
'minlength': 2,
|
||||
'maxlength': 5,
|
||||
},
|
||||
'a_nullable_field_without_type': {
|
||||
'nullable': True
|
||||
},
|
||||
'a_not_nullable_field_without_type': {
|
||||
},
|
||||
'a_nullable_field_without_type': {'nullable': True},
|
||||
'a_not_nullable_field_without_type': {},
|
||||
}
|
||||
|
||||
sample_document = {'name': 'john doe'}
|
||||
|
||||
+49
-14
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from decimal import Decimal
|
||||
from pkg_resources import Distribution, DistributionNotFound
|
||||
|
||||
from pytest import mark
|
||||
|
||||
@@ -8,6 +9,37 @@ from cerberus import TypeDefinition, Validator
|
||||
from cerberus.tests import assert_fail, assert_success
|
||||
from cerberus.utils import validator_factory
|
||||
from cerberus.validator import BareValidator
|
||||
from cerberus.platform import PYTHON_VERSION
|
||||
|
||||
|
||||
if PYTHON_VERSION > 3 and PYTHON_VERSION < 3.4:
|
||||
from imp import reload
|
||||
elif PYTHON_VERSION >= 3.4:
|
||||
from importlib import reload
|
||||
else:
|
||||
pass # Python 2.x
|
||||
|
||||
|
||||
def test_pkgresources_version(monkeypatch):
|
||||
def create_fake_distribution(name):
|
||||
return Distribution(project_name="cerberus", version="1.2.3")
|
||||
|
||||
with monkeypatch.context() as m:
|
||||
cerberus = __import__("cerberus")
|
||||
m.setattr("pkg_resources.get_distribution", create_fake_distribution)
|
||||
reload(cerberus)
|
||||
assert cerberus.__version__ == "1.2.3"
|
||||
|
||||
|
||||
def test_version_not_found(monkeypatch):
|
||||
def raise_distribution_not_found(name):
|
||||
raise DistributionNotFound("pkg_resources cannot get distribution")
|
||||
|
||||
with monkeypatch.context() as m:
|
||||
cerberus = __import__("cerberus")
|
||||
m.setattr("pkg_resources.get_distribution", raise_distribution_not_found)
|
||||
reload(cerberus)
|
||||
assert cerberus.__version__ == "unknown"
|
||||
|
||||
|
||||
def test_clear_cache(validator):
|
||||
@@ -23,8 +55,11 @@ def test_docstring(validator):
|
||||
# Test that testing with the sample schema works as expected
|
||||
# as there might be rules with side-effects in it
|
||||
|
||||
@mark.parametrize('test,document', ((assert_fail, {'an_integer': 60}),
|
||||
(assert_success, {'an_integer': 110})))
|
||||
|
||||
@mark.parametrize(
|
||||
"test,document",
|
||||
((assert_fail, {"an_integer": 60}), (assert_success, {"an_integer": 110})),
|
||||
)
|
||||
def test_that_test_fails(test, document):
|
||||
try:
|
||||
test(document)
|
||||
@@ -35,42 +70,42 @@ def test_that_test_fails(test, document):
|
||||
|
||||
|
||||
def test_dynamic_types():
|
||||
decimal_type = TypeDefinition('decimal', (Decimal,), ())
|
||||
document = {'measurement': Decimal(0)}
|
||||
schema = {'measurement': {'type': 'decimal'}}
|
||||
decimal_type = TypeDefinition("decimal", (Decimal,), ())
|
||||
document = {"measurement": Decimal(0)}
|
||||
schema = {"measurement": {"type": "decimal"}}
|
||||
|
||||
validator = Validator()
|
||||
validator.types_mapping['decimal'] = decimal_type
|
||||
validator.types_mapping["decimal"] = decimal_type
|
||||
assert_success(document, schema, validator)
|
||||
|
||||
class MyValidator(Validator):
|
||||
types_mapping = Validator.types_mapping.copy()
|
||||
types_mapping['decimal'] = decimal_type
|
||||
types_mapping["decimal"] = decimal_type
|
||||
|
||||
validator = MyValidator()
|
||||
assert_success(document, schema, validator)
|
||||
|
||||
|
||||
def test_mro():
|
||||
assert Validator.__mro__ == (Validator, BareValidator, object), \
|
||||
Validator.__mro__
|
||||
assert Validator.__mro__ == (Validator, BareValidator, object), Validator.__mro__
|
||||
|
||||
|
||||
def test_mixin_init():
|
||||
class Mixin(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['test'] = True
|
||||
kwargs["test"] = True
|
||||
super(Mixin, self).__init__(*args, **kwargs)
|
||||
|
||||
MyValidator = validator_factory('MyValidator', Mixin)
|
||||
MyValidator = validator_factory("MyValidator", Mixin)
|
||||
validator = MyValidator()
|
||||
assert validator._config['test']
|
||||
assert validator._config["test"]
|
||||
|
||||
|
||||
def test_sub_init():
|
||||
class MyValidator(Validator):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['test'] = True
|
||||
kwargs["test"] = True
|
||||
super(MyValidator, self).__init__(*args, **kwargs)
|
||||
|
||||
validator = MyValidator()
|
||||
assert validator._config['test']
|
||||
assert validator._config["test"]
|
||||
|
||||
+33
-10
@@ -1,12 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from pytest import mark
|
||||
|
||||
import cerberus
|
||||
from cerberus.tests import assert_fail, assert_success
|
||||
from cerberus.tests.conftest import sample_schema
|
||||
|
||||
|
||||
def test_contextual_data_preservation():
|
||||
|
||||
class InheritedValidator(cerberus.Validator):
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'working_dir' in kwargs:
|
||||
@@ -18,9 +19,9 @@ def test_contextual_data_preservation():
|
||||
return True
|
||||
|
||||
assert 'test' in InheritedValidator.types
|
||||
v = InheritedValidator({'test': {'type': 'list',
|
||||
'schema': {'type': 'test'}}},
|
||||
working_dir='/tmp')
|
||||
v = InheritedValidator(
|
||||
{'test': {'type': 'list', 'schema': {'type': 'test'}}}, working_dir='/tmp'
|
||||
)
|
||||
assert_success({'test': ['foo']}, validator=v)
|
||||
|
||||
|
||||
@@ -42,25 +43,47 @@ def test_docstring_parsing():
|
||||
assert 'bar' in CustomValidator.validation_rules
|
||||
|
||||
|
||||
def test_issue_265():
|
||||
# TODO remove 'validator' as rule parameter with the next major release
|
||||
@mark.parametrize('rule', ('check_with', 'validator'))
|
||||
def test_check_with_method(rule):
|
||||
# https://github.com/pyeve/cerberus/issues/265
|
||||
class MyValidator(cerberus.Validator):
|
||||
def _check_with_oddity(self, field, value):
|
||||
if not value & 1:
|
||||
self._error(field, "Must be an odd number")
|
||||
|
||||
v = MyValidator(schema={'amount': {rule: 'oddity'}})
|
||||
assert_success(document={'amount': 1}, validator=v)
|
||||
assert_fail(
|
||||
document={'amount': 2},
|
||||
validator=v,
|
||||
error=('amount', (), cerberus.errors.CUSTOM, None, ('Must be an odd number',)),
|
||||
)
|
||||
|
||||
|
||||
# TODO remove test with the next major release
|
||||
@mark.parametrize('rule', ('check_with', 'validator'))
|
||||
def test_validator_method(rule):
|
||||
class MyValidator(cerberus.Validator):
|
||||
def _validator_oddity(self, field, value):
|
||||
if not value & 1:
|
||||
self._error(field, "Must be an odd number")
|
||||
|
||||
v = MyValidator(schema={'amount': {'validator': 'oddity'}})
|
||||
v = MyValidator(schema={'amount': {rule: 'oddity'}})
|
||||
assert_success(document={'amount': 1}, validator=v)
|
||||
assert_fail(document={'amount': 2}, validator=v,
|
||||
error=('amount', (), cerberus.errors.CUSTOM, None,
|
||||
('Must be an odd number',)))
|
||||
assert_fail(
|
||||
document={'amount': 2},
|
||||
validator=v,
|
||||
error=('amount', (), cerberus.errors.CUSTOM, None, ('Must be an odd number',)),
|
||||
)
|
||||
|
||||
|
||||
def test_schema_validation_can_be_disabled_in_schema_setter():
|
||||
|
||||
class NonvalidatingValidator(cerberus.Validator):
|
||||
"""
|
||||
Skips schema validation to speed up initialization
|
||||
"""
|
||||
|
||||
@cerberus.Validator.schema.setter
|
||||
def schema(self, schema):
|
||||
if schema is None:
|
||||
|
||||
+135
-72
@@ -24,14 +24,14 @@ def test__error_1():
|
||||
|
||||
|
||||
def test__error_2():
|
||||
v = Validator(schema={'foo': {'keyschema': {'type': 'integer'}}})
|
||||
v = Validator(schema={'foo': {'keysrules': {'type': 'integer'}}})
|
||||
v.document = {'foo': {'0': 'bar'}}
|
||||
v._error('foo', errors.KEYSCHEMA, ())
|
||||
v._error('foo', errors.KEYSRULES, ())
|
||||
error = v._errors[0]
|
||||
assert error.document_path == ('foo',)
|
||||
assert error.schema_path == ('foo', 'keyschema')
|
||||
assert error.schema_path == ('foo', 'keysrules')
|
||||
assert error.code == 0x83
|
||||
assert error.rule == 'keyschema'
|
||||
assert error.rule == 'keysrules'
|
||||
assert error.constraint == {'type': 'integer'}
|
||||
assert error.value == {'0': 'bar'}
|
||||
assert error.info == ((),)
|
||||
@@ -40,8 +40,10 @@ def test__error_2():
|
||||
|
||||
|
||||
def test__error_3():
|
||||
valids = [{'type': 'string', 'regex': '0x[0-9a-f]{2}'},
|
||||
{'type': 'integer', 'min': 0, 'max': 255}]
|
||||
valids = [
|
||||
{'type': 'string', 'regex': '0x[0-9a-f]{2}'},
|
||||
{'type': 'integer', 'min': 0, 'max': 255},
|
||||
]
|
||||
v = Validator(schema={'foo': {'oneof': valids}})
|
||||
v.document = {'foo': '0x100'}
|
||||
v._error('foo', errors.ONEOF, (), 0, 2)
|
||||
@@ -77,8 +79,9 @@ def test_error_tree_from_subschema(validator):
|
||||
assert 'bar' in s_error_tree['foo']['schema']
|
||||
assert 'type' in s_error_tree['foo']['schema']['bar']
|
||||
assert s_error_tree['foo']['schema']['bar']['type'].errors[0].value == 0
|
||||
assert s_error_tree.fetch_errors_from(
|
||||
('foo', 'schema', 'bar', 'type'))[0].value == 0
|
||||
assert (
|
||||
s_error_tree.fetch_errors_from(('foo', 'schema', 'bar', 'type'))[0].value == 0
|
||||
)
|
||||
|
||||
|
||||
def test_error_tree_from_anyof(validator):
|
||||
@@ -98,12 +101,17 @@ def test_error_tree_from_anyof(validator):
|
||||
|
||||
|
||||
def test_nested_error_paths(validator):
|
||||
schema = {'a_dict': {'keyschema': {'type': 'integer'},
|
||||
'valueschema': {'regex': '[a-z]*'}},
|
||||
'a_list': {'schema': {'type': 'string',
|
||||
'oneof_regex': ['[a-z]*$', '[A-Z]*']}}}
|
||||
document = {'a_dict': {0: 'abc', 'one': 'abc', 2: 'aBc', 'three': 'abC'},
|
||||
'a_list': [0, 'abc', 'abC']}
|
||||
schema = {
|
||||
'a_dict': {
|
||||
'keysrules': {'type': 'integer'},
|
||||
'valuesrules': {'regex': '[a-z]*'},
|
||||
},
|
||||
'a_list': {'schema': {'type': 'string', 'oneof_regex': ['[a-z]*$', '[A-Z]*']}},
|
||||
}
|
||||
document = {
|
||||
'a_dict': {0: 'abc', 'one': 'abc', 2: 'aBc', 'three': 'abC'},
|
||||
'a_list': [0, 'abc', 'abC'],
|
||||
}
|
||||
assert_fail(document, schema, validator=validator)
|
||||
|
||||
_det = validator.document_error_tree
|
||||
@@ -120,35 +128,59 @@ def test_nested_error_paths(validator):
|
||||
assert len(_det['a_dict'][2].errors) == 1
|
||||
assert len(_det['a_dict']['three'].errors) == 2
|
||||
|
||||
assert len(_set['a_dict']['keyschema'].errors) == 1
|
||||
assert len(_set['a_dict']['valueschema'].errors) == 1
|
||||
assert len(_set['a_dict']['keysrules'].errors) == 1
|
||||
assert len(_set['a_dict']['valuesrules'].errors) == 1
|
||||
|
||||
assert len(_set['a_dict']['keyschema']['type'].errors) == 2
|
||||
assert len(_set['a_dict']['valueschema']['regex'].errors) == 2
|
||||
assert len(_set['a_dict']['keysrules']['type'].errors) == 2
|
||||
assert len(_set['a_dict']['valuesrules']['regex'].errors) == 2
|
||||
|
||||
_ref_err = ValidationError(
|
||||
('a_dict', 'one'), ('a_dict', 'keyschema', 'type'),
|
||||
errors.BAD_TYPE.code, 'type', 'integer', 'one', ())
|
||||
('a_dict', 'one'),
|
||||
('a_dict', 'keysrules', 'type'),
|
||||
errors.BAD_TYPE.code,
|
||||
'type',
|
||||
'integer',
|
||||
'one',
|
||||
(),
|
||||
)
|
||||
assert _det['a_dict']['one'].errors[0] == _ref_err
|
||||
assert _set['a_dict']['keyschema']['type'].errors[0] == _ref_err
|
||||
assert _set['a_dict']['keysrules']['type'].errors[0] == _ref_err
|
||||
|
||||
_ref_err = ValidationError(
|
||||
('a_dict', 2), ('a_dict', 'valueschema', 'regex'),
|
||||
errors.REGEX_MISMATCH.code, 'regex', '[a-z]*$', 'aBc', ())
|
||||
('a_dict', 2),
|
||||
('a_dict', 'valuesrules', 'regex'),
|
||||
errors.REGEX_MISMATCH.code,
|
||||
'regex',
|
||||
'[a-z]*$',
|
||||
'aBc',
|
||||
(),
|
||||
)
|
||||
assert _det['a_dict'][2].errors[0] == _ref_err
|
||||
assert _set['a_dict']['valueschema']['regex'].errors[0] == _ref_err
|
||||
assert _set['a_dict']['valuesrules']['regex'].errors[0] == _ref_err
|
||||
|
||||
_ref_err = ValidationError(
|
||||
('a_dict', 'three'), ('a_dict', 'keyschema', 'type'),
|
||||
errors.BAD_TYPE.code, 'type', 'integer', 'three', ())
|
||||
('a_dict', 'three'),
|
||||
('a_dict', 'keysrules', 'type'),
|
||||
errors.BAD_TYPE.code,
|
||||
'type',
|
||||
'integer',
|
||||
'three',
|
||||
(),
|
||||
)
|
||||
assert _det['a_dict']['three'].errors[0] == _ref_err
|
||||
assert _set['a_dict']['keyschema']['type'].errors[1] == _ref_err
|
||||
assert _set['a_dict']['keysrules']['type'].errors[1] == _ref_err
|
||||
|
||||
_ref_err = ValidationError(
|
||||
('a_dict', 'three'), ('a_dict', 'valueschema', 'regex'),
|
||||
errors.REGEX_MISMATCH.code, 'regex', '[a-z]*$', 'abC', ())
|
||||
('a_dict', 'three'),
|
||||
('a_dict', 'valuesrules', 'regex'),
|
||||
errors.REGEX_MISMATCH.code,
|
||||
'regex',
|
||||
'[a-z]*$',
|
||||
'abC',
|
||||
(),
|
||||
)
|
||||
assert _det['a_dict']['three'].errors[1] == _ref_err
|
||||
assert _set['a_dict']['valueschema']['regex'].errors[1] == _ref_err
|
||||
assert _set['a_dict']['valuesrules']['regex'].errors[1] == _ref_err
|
||||
|
||||
assert len(_det['a_list'].errors) == 1
|
||||
assert len(_det['a_list'][0].errors) == 1
|
||||
@@ -161,34 +193,56 @@ def test_nested_error_paths(validator):
|
||||
assert len(_set['a_list']['schema']['oneof'][1]['regex'].errors) == 1
|
||||
|
||||
_ref_err = ValidationError(
|
||||
('a_list', 0), ('a_list', 'schema', 'type'), errors.BAD_TYPE.code,
|
||||
'type', 'string', 0, ())
|
||||
('a_list', 0),
|
||||
('a_list', 'schema', 'type'),
|
||||
errors.BAD_TYPE.code,
|
||||
'type',
|
||||
'string',
|
||||
0,
|
||||
(),
|
||||
)
|
||||
assert _det['a_list'][0].errors[0] == _ref_err
|
||||
assert _set['a_list']['schema']['type'].errors[0] == _ref_err
|
||||
|
||||
_ref_err = ValidationError(
|
||||
('a_list', 2), ('a_list', 'schema', 'oneof'), errors.ONEOF.code,
|
||||
'oneof', 'irrelevant_at_this_point', 'abC', ())
|
||||
('a_list', 2),
|
||||
('a_list', 'schema', 'oneof'),
|
||||
errors.ONEOF.code,
|
||||
'oneof',
|
||||
'irrelevant_at_this_point',
|
||||
'abC',
|
||||
(),
|
||||
)
|
||||
assert _det['a_list'][2].errors[0] == _ref_err
|
||||
assert _set['a_list']['schema']['oneof'].errors[0] == _ref_err
|
||||
|
||||
_ref_err = ValidationError(
|
||||
('a_list', 2), ('a_list', 'schema', 'oneof', 0, 'regex'),
|
||||
errors.REGEX_MISMATCH.code, 'regex', '[a-z]*$', 'abC', ())
|
||||
('a_list', 2),
|
||||
('a_list', 'schema', 'oneof', 0, 'regex'),
|
||||
errors.REGEX_MISMATCH.code,
|
||||
'regex',
|
||||
'[a-z]*$',
|
||||
'abC',
|
||||
(),
|
||||
)
|
||||
assert _det['a_list'][2].errors[1] == _ref_err
|
||||
assert _set['a_list']['schema']['oneof'][0]['regex'].errors[0] == _ref_err
|
||||
|
||||
_ref_err = ValidationError(
|
||||
('a_list', 2), ('a_list', 'schema', 'oneof', 1, 'regex'),
|
||||
errors.REGEX_MISMATCH.code, 'regex', '[a-z]*$', 'abC', ())
|
||||
('a_list', 2),
|
||||
('a_list', 'schema', 'oneof', 1, 'regex'),
|
||||
errors.REGEX_MISMATCH.code,
|
||||
'regex',
|
||||
'[a-z]*$',
|
||||
'abC',
|
||||
(),
|
||||
)
|
||||
assert _det['a_list'][2].errors[2] == _ref_err
|
||||
assert _set['a_list']['schema']['oneof'][1]['regex'].errors[0] == _ref_err
|
||||
|
||||
|
||||
def test_queries():
|
||||
schema = {'foo': {'type': 'dict',
|
||||
'schema':
|
||||
{'bar': {'type': 'number'}}}}
|
||||
schema = {'foo': {'type': 'dict', 'schema': {'bar': {'type': 'number'}}}}
|
||||
document = {'foo': {'bar': 'zero'}}
|
||||
validator = Validator(schema)
|
||||
validator(document)
|
||||
@@ -202,59 +256,68 @@ def test_queries():
|
||||
assert errors.MAPPING_SCHEMA in validator.document_error_tree['foo']
|
||||
assert errors.BAD_TYPE in validator.document_error_tree['foo']['bar']
|
||||
assert errors.MAPPING_SCHEMA in validator.schema_error_tree['foo']['schema']
|
||||
assert errors.BAD_TYPE in \
|
||||
validator.schema_error_tree['foo']['schema']['bar']['type']
|
||||
assert (
|
||||
errors.BAD_TYPE in validator.schema_error_tree['foo']['schema']['bar']['type']
|
||||
)
|
||||
|
||||
assert (validator.document_error_tree['foo'][errors.MAPPING_SCHEMA]
|
||||
.child_errors[0].code == errors.BAD_TYPE.code)
|
||||
assert (
|
||||
validator.document_error_tree['foo'][errors.MAPPING_SCHEMA].child_errors[0].code
|
||||
== errors.BAD_TYPE.code
|
||||
)
|
||||
|
||||
|
||||
def test_basic_error_handler():
|
||||
handler = errors.BasicErrorHandler()
|
||||
_errors, ref = [], {}
|
||||
|
||||
_errors.append(ValidationError(
|
||||
['foo'], ['foo'], 0x63, 'readonly', True, None, ()))
|
||||
_errors.append(ValidationError(['foo'], ['foo'], 0x63, 'readonly', True, None, ()))
|
||||
ref.update({'foo': [handler.messages[0x63]]})
|
||||
assert handler(_errors) == ref
|
||||
|
||||
_errors.append(ValidationError(
|
||||
['bar'], ['foo'], 0x42, 'min', 1, 2, ()))
|
||||
_errors.append(ValidationError(['bar'], ['foo'], 0x42, 'min', 1, 2, ()))
|
||||
ref.update({'bar': [handler.messages[0x42].format(constraint=1)]})
|
||||
assert handler(_errors) == ref
|
||||
|
||||
_errors.append(ValidationError(
|
||||
['zap', 'foo'], ['zap', 'schema', 'foo'], 0x24, 'type', 'string',
|
||||
True, ()))
|
||||
ref.update({'zap': [{'foo': [handler.messages[0x24].format(
|
||||
constraint='string')]}]})
|
||||
_errors.append(
|
||||
ValidationError(
|
||||
['zap', 'foo'], ['zap', 'schema', 'foo'], 0x24, 'type', 'string', True, ()
|
||||
)
|
||||
)
|
||||
ref.update({'zap': [{'foo': [handler.messages[0x24].format(constraint='string')]}]})
|
||||
assert handler(_errors) == ref
|
||||
|
||||
_errors.append(ValidationError(
|
||||
['zap', 'foo'], ['zap', 'schema', 'foo'], 0x41, 'regex',
|
||||
'^p[äe]ng$', 'boom', ()))
|
||||
ref['zap'][0]['foo'].append(
|
||||
handler.messages[0x41].format(constraint='^p[äe]ng$'))
|
||||
_errors.append(
|
||||
ValidationError(
|
||||
['zap', 'foo'],
|
||||
['zap', 'schema', 'foo'],
|
||||
0x41,
|
||||
'regex',
|
||||
'^p[äe]ng$',
|
||||
'boom',
|
||||
(),
|
||||
)
|
||||
)
|
||||
ref['zap'][0]['foo'].append(handler.messages[0x41].format(constraint='^p[äe]ng$'))
|
||||
assert handler(_errors) == ref
|
||||
|
||||
|
||||
def test_basic_error_of_errors(validator):
|
||||
schema = {'foo': {'oneof': [
|
||||
{'type': 'integer'},
|
||||
{'type': 'string'}
|
||||
]}}
|
||||
schema = {'foo': {'oneof': [{'type': 'integer'}, {'type': 'string'}]}}
|
||||
document = {'foo': 23.42}
|
||||
error = ('foo', ('foo', 'oneof'), errors.ONEOF,
|
||||
schema['foo']['oneof'], ())
|
||||
error = ('foo', ('foo', 'oneof'), errors.ONEOF, schema['foo']['oneof'], ())
|
||||
child_errors = [
|
||||
(error[0], error[1] + (0, 'type'), errors.BAD_TYPE, 'integer'),
|
||||
(error[0], error[1] + (1, 'type'), errors.BAD_TYPE, 'string')
|
||||
(error[0], error[1] + (1, 'type'), errors.BAD_TYPE, 'string'),
|
||||
]
|
||||
assert_fail(document, schema, validator=validator,
|
||||
error=error, child_errors=child_errors)
|
||||
assert_fail(
|
||||
document, schema, validator=validator, error=error, child_errors=child_errors
|
||||
)
|
||||
assert validator.errors == {
|
||||
'foo': [errors.BasicErrorHandler.messages[0x92],
|
||||
{'oneof definition 0': ['must be of integer type'],
|
||||
'oneof definition 1': ['must be of string type']}
|
||||
]
|
||||
'foo': [
|
||||
errors.BasicErrorHandler.messages[0x92],
|
||||
{
|
||||
'oneof definition 0': ['must be of integer type'],
|
||||
'oneof definition 1': ['must be of string type'],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
+213
-155
@@ -1,10 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from copy import deepcopy
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
from pytest import mark
|
||||
|
||||
from cerberus import Validator, errors
|
||||
from cerberus.tests import (assert_fail, assert_has_error, assert_normalized,
|
||||
assert_success)
|
||||
from cerberus.tests import (
|
||||
assert_fail,
|
||||
assert_has_error,
|
||||
assert_normalized,
|
||||
assert_success,
|
||||
)
|
||||
|
||||
|
||||
def must_not_be_called(*args, **kwargs):
|
||||
raise RuntimeError('This shall not be called.')
|
||||
|
||||
|
||||
def test_coerce():
|
||||
@@ -15,21 +26,31 @@ def test_coerce():
|
||||
|
||||
|
||||
def test_coerce_in_dictschema():
|
||||
schema = {'thing': {'type': 'dict',
|
||||
'schema': {'amount': {'coerce': int}}}}
|
||||
schema = {'thing': {'type': 'dict', 'schema': {'amount': {'coerce': int}}}}
|
||||
document = {'thing': {'amount': '2'}}
|
||||
expected = {'thing': {'amount': 2}}
|
||||
assert_normalized(document, expected, schema)
|
||||
|
||||
|
||||
def test_coerce_in_listschema():
|
||||
schema = {'things': {'type': 'list',
|
||||
'schema': {'coerce': int}}}
|
||||
schema = {'things': {'type': 'list', 'schema': {'coerce': int}}}
|
||||
document = {'things': ['1', '2', '3']}
|
||||
expected = {'things': [1, 2, 3]}
|
||||
assert_normalized(document, expected, schema)
|
||||
|
||||
|
||||
def test_coerce_in_listitems():
|
||||
schema = {'things': {'type': 'list', 'items': [{'coerce': int}, {'coerce': str}]}}
|
||||
document = {'things': ['1', 2]}
|
||||
expected = {'things': [1, '2']}
|
||||
assert_normalized(document, expected, schema)
|
||||
|
||||
validator = Validator(schema)
|
||||
document['things'].append(3)
|
||||
assert not validator(document)
|
||||
assert validator.document['things'] == document['things']
|
||||
|
||||
|
||||
def test_coerce_in_dictschema_in_listschema():
|
||||
item_schema = {'type': 'dict', 'schema': {'amount': {'coerce': int}}}
|
||||
schema = {'things': {'type': 'list', 'schema': item_schema}}
|
||||
@@ -39,9 +60,7 @@ def test_coerce_in_dictschema_in_listschema():
|
||||
|
||||
|
||||
def test_coerce_not_destructive():
|
||||
schema = {
|
||||
'amount': {'coerce': int}
|
||||
}
|
||||
schema = {'amount': {'coerce': int}}
|
||||
v = Validator(schema)
|
||||
doc = {'amount': '1'}
|
||||
v.validate(doc)
|
||||
@@ -52,16 +71,48 @@ def test_coerce_catches_ValueError():
|
||||
schema = {'amount': {'coerce': int}}
|
||||
_errors = assert_fail({'amount': 'not_a_number'}, schema)
|
||||
_errors[0].info = () # ignore exception message here
|
||||
assert_has_error(_errors, 'amount', ('amount', 'coerce'),
|
||||
errors.COERCION_FAILED, int)
|
||||
assert_has_error(
|
||||
_errors, 'amount', ('amount', 'coerce'), errors.COERCION_FAILED, int
|
||||
)
|
||||
|
||||
|
||||
def test_coerce_in_listitems_catches_ValueError():
|
||||
schema = {'things': {'type': 'list', 'items': [{'coerce': int}, {'coerce': str}]}}
|
||||
document = {'things': ['not_a_number', 2]}
|
||||
_errors = assert_fail(document, schema)
|
||||
_errors[0].info = () # ignore exception message here
|
||||
assert_has_error(
|
||||
_errors,
|
||||
('things', 0),
|
||||
('things', 'items', 'coerce'),
|
||||
errors.COERCION_FAILED,
|
||||
int,
|
||||
)
|
||||
|
||||
|
||||
def test_coerce_catches_TypeError():
|
||||
schema = {'name': {'coerce': str.lower}}
|
||||
_errors = assert_fail({'name': 1234}, schema)
|
||||
_errors[0].info = () # ignore exception message here
|
||||
assert_has_error(_errors, 'name', ('name', 'coerce'),
|
||||
errors.COERCION_FAILED, str.lower)
|
||||
assert_has_error(
|
||||
_errors, 'name', ('name', 'coerce'), errors.COERCION_FAILED, str.lower
|
||||
)
|
||||
|
||||
|
||||
def test_coerce_in_listitems_catches_TypeError():
|
||||
schema = {
|
||||
'things': {'type': 'list', 'items': [{'coerce': int}, {'coerce': str.lower}]}
|
||||
}
|
||||
document = {'things': ['1', 2]}
|
||||
_errors = assert_fail(document, schema)
|
||||
_errors[0].info = () # ignore exception message here
|
||||
assert_has_error(
|
||||
_errors,
|
||||
('things', 1),
|
||||
('things', 'items', 'coerce'),
|
||||
errors.COERCION_FAILED,
|
||||
str.lower,
|
||||
)
|
||||
|
||||
|
||||
def test_coerce_unknown():
|
||||
@@ -88,16 +139,16 @@ def test_custom_coerce_and_rename():
|
||||
|
||||
|
||||
def test_coerce_chain():
|
||||
drop_prefix = lambda x: x[2:]
|
||||
upper = lambda x: x.upper()
|
||||
drop_prefix = lambda x: x[2:] # noqa: E731
|
||||
upper = lambda x: x.upper() # noqa: E731
|
||||
schema = {'foo': {'coerce': [hex, drop_prefix, upper]}}
|
||||
assert_normalized({'foo': 15}, {'foo': 'F'}, schema)
|
||||
|
||||
|
||||
def test_coerce_chain_aborts(validator):
|
||||
def dont_do_me(value):
|
||||
raise AssertionError('The coercion chain did not abort after an '
|
||||
'error.')
|
||||
raise AssertionError('The coercion chain did not abort after an ' 'error.')
|
||||
|
||||
schema = {'foo': {'coerce': [hex, dont_do_me]}}
|
||||
validator({'foo': '0'}, schema)
|
||||
assert errors.COERCION_FAILED in validator._errors
|
||||
@@ -105,12 +156,12 @@ def test_coerce_chain_aborts(validator):
|
||||
|
||||
def test_coerce_non_digit_in_sequence(validator):
|
||||
# https://github.com/pyeve/cerberus/issues/211
|
||||
schema = {'data': {'type': 'list',
|
||||
'schema': {'type': 'integer', 'coerce': int}}}
|
||||
schema = {'data': {'type': 'list', 'schema': {'type': 'integer', 'coerce': int}}}
|
||||
document = {'data': ['q']}
|
||||
assert validator.validated(document, schema) is None
|
||||
assert (validator.validated(document, schema, always_return_document=True)
|
||||
== document) # noqa: W503
|
||||
assert (
|
||||
validator.validated(document, schema, always_return_document=True) == document
|
||||
) # noqa: W503
|
||||
|
||||
|
||||
def test_nullables_dont_fail_coerce():
|
||||
@@ -119,6 +170,18 @@ def test_nullables_dont_fail_coerce():
|
||||
assert_normalized(document, document, schema)
|
||||
|
||||
|
||||
def test_nullables_fail_coerce_on_non_null_values(validator):
|
||||
def failing_coercion(value):
|
||||
raise Exception("expected to fail")
|
||||
|
||||
schema = {'foo': {'coerce': failing_coercion, 'nullable': True, 'type': 'integer'}}
|
||||
document = {'foo': None}
|
||||
assert_normalized(document, document, schema)
|
||||
|
||||
validator({'foo': 2}, schema)
|
||||
assert errors.COERCION_FAILED in validator._errors
|
||||
|
||||
|
||||
def test_normalized():
|
||||
schema = {'amount': {'coerce': int}}
|
||||
document = {'amount': '2'}
|
||||
@@ -154,9 +217,13 @@ def test_purge_unknown():
|
||||
|
||||
|
||||
def test_purge_unknown_in_subschema():
|
||||
schema = {'foo': {'type': 'dict',
|
||||
'schema': {'foo': {'type': 'string'}},
|
||||
'purge_unknown': True}}
|
||||
schema = {
|
||||
'foo': {
|
||||
'type': 'dict',
|
||||
'schema': {'foo': {'type': 'string'}},
|
||||
'purge_unknown': True,
|
||||
}
|
||||
}
|
||||
document = {'foo': {'bar': ''}}
|
||||
expected = {'foo': {}}
|
||||
assert_normalized(document, expected, schema)
|
||||
@@ -175,8 +242,7 @@ def test_issue_147_complex():
|
||||
|
||||
|
||||
def test_issue_147_nested_dict():
|
||||
schema = {'thing': {'type': 'dict',
|
||||
'schema': {'amount': {'coerce': int}}}}
|
||||
schema = {'thing': {'type': 'dict', 'schema': {'amount': {'coerce': int}}}}
|
||||
ref_obj = '2'
|
||||
document = {'thing': {'amount': ref_obj}}
|
||||
normalized = Validator(schema).normalized(document)
|
||||
@@ -186,20 +252,21 @@ def test_issue_147_nested_dict():
|
||||
assert document['thing']['amount'] is ref_obj
|
||||
|
||||
|
||||
def test_coerce_in_valueschema():
|
||||
def test_coerce_in_valuesrules():
|
||||
# https://github.com/pyeve/cerberus/issues/155
|
||||
schema = {'thing': {'type': 'dict',
|
||||
'valueschema': {'coerce': int,
|
||||
'type': 'integer'}}}
|
||||
schema = {
|
||||
'thing': {'type': 'dict', 'valuesrules': {'coerce': int, 'type': 'integer'}}
|
||||
}
|
||||
document = {'thing': {'amount': '2'}}
|
||||
expected = {'thing': {'amount': 2}}
|
||||
assert_normalized(document, expected, schema)
|
||||
|
||||
|
||||
def test_coerce_in_keyschema():
|
||||
def test_coerce_in_keysrules():
|
||||
# https://github.com/pyeve/cerberus/issues/155
|
||||
schema = {'thing': {'type': 'dict',
|
||||
'keyschema': {'coerce': int, 'type': 'integer'}}}
|
||||
schema = {
|
||||
'thing': {'type': 'dict', 'keysrules': {'coerce': int, 'type': 'integer'}}
|
||||
}
|
||||
document = {'thing': {'5': 'foo'}}
|
||||
expected = {'thing': {5: 'foo'}}
|
||||
assert_normalized(document, expected, schema)
|
||||
@@ -207,8 +274,7 @@ def test_coerce_in_keyschema():
|
||||
|
||||
def test_coercion_of_sequence_items(validator):
|
||||
# https://github.com/pyeve/cerberus/issues/161
|
||||
schema = {'a_list': {'type': 'list', 'schema': {'type': 'float',
|
||||
'coerce': float}}}
|
||||
schema = {'a_list': {'type': 'list', 'schema': {'type': 'float', 'coerce': float}}}
|
||||
document = {'a_list': [3, 4, 5]}
|
||||
expected = {'a_list': [3.0, 4.0, 5.0]}
|
||||
assert_normalized(document, expected, schema, validator)
|
||||
@@ -216,110 +282,76 @@ def test_coercion_of_sequence_items(validator):
|
||||
assert isinstance(x, float)
|
||||
|
||||
|
||||
def test_default_missing():
|
||||
_test_default_missing({'default': 'bar_value'})
|
||||
|
||||
|
||||
def test_default_setter_missing():
|
||||
_test_default_missing({'default_setter': lambda doc: 'bar_value'})
|
||||
|
||||
|
||||
def _test_default_missing(default):
|
||||
@mark.parametrize(
|
||||
'default', ({'default': 'bar_value'}, {'default_setter': lambda doc: 'bar_value'})
|
||||
)
|
||||
def test_default_missing(default):
|
||||
bar_schema = {'type': 'string'}
|
||||
bar_schema.update(default)
|
||||
schema = {'foo': {'type': 'string'},
|
||||
'bar': bar_schema}
|
||||
schema = {'foo': {'type': 'string'}, 'bar': bar_schema}
|
||||
document = {'foo': 'foo_value'}
|
||||
expected = {'foo': 'foo_value', 'bar': 'bar_value'}
|
||||
assert_normalized(document, expected, schema)
|
||||
|
||||
|
||||
def test_default_existent():
|
||||
_test_default_existent({'default': 'bar_value'})
|
||||
|
||||
|
||||
def test_default_setter_existent():
|
||||
def raise_error(doc):
|
||||
raise RuntimeError('should not be called')
|
||||
_test_default_existent({'default_setter': raise_error})
|
||||
|
||||
|
||||
def _test_default_existent(default):
|
||||
@mark.parametrize(
|
||||
'default', ({'default': 'bar_value'}, {'default_setter': must_not_be_called})
|
||||
)
|
||||
def test_default_existent(default):
|
||||
bar_schema = {'type': 'string'}
|
||||
bar_schema.update(default)
|
||||
schema = {'foo': {'type': 'string'},
|
||||
'bar': bar_schema}
|
||||
schema = {'foo': {'type': 'string'}, 'bar': bar_schema}
|
||||
document = {'foo': 'foo_value', 'bar': 'non_default'}
|
||||
assert_normalized(document, document.copy(), schema)
|
||||
|
||||
|
||||
def test_default_none_nullable():
|
||||
_test_default_none_nullable({'default': 'bar_value'})
|
||||
|
||||
|
||||
def test_default_setter_none_nullable():
|
||||
def raise_error(doc):
|
||||
raise RuntimeError('should not be called')
|
||||
_test_default_none_nullable({'default_setter': raise_error})
|
||||
|
||||
|
||||
def _test_default_none_nullable(default):
|
||||
bar_schema = {'type': 'string',
|
||||
'nullable': True}
|
||||
@mark.parametrize(
|
||||
'default', ({'default': 'bar_value'}, {'default_setter': must_not_be_called})
|
||||
)
|
||||
def test_default_none_nullable(default):
|
||||
bar_schema = {'type': 'string', 'nullable': True}
|
||||
bar_schema.update(default)
|
||||
schema = {'foo': {'type': 'string'},
|
||||
'bar': bar_schema}
|
||||
schema = {'foo': {'type': 'string'}, 'bar': bar_schema}
|
||||
document = {'foo': 'foo_value', 'bar': None}
|
||||
assert_normalized(document, document.copy(), schema)
|
||||
|
||||
|
||||
def test_default_none_nonnullable():
|
||||
_test_default_none_nullable({'default': 'bar_value'})
|
||||
|
||||
|
||||
def test_default_setter_none_nonnullable():
|
||||
_test_default_none_nullable(
|
||||
{'default_setter': lambda doc: 'bar_value'})
|
||||
|
||||
|
||||
def _test_default_none_nonnullable(default):
|
||||
bar_schema = {'type': 'string',
|
||||
'nullable': False}
|
||||
@mark.parametrize(
|
||||
'default', ({'default': 'bar_value'}, {'default_setter': lambda doc: 'bar_value'})
|
||||
)
|
||||
def test_default_none_nonnullable(default):
|
||||
bar_schema = {'type': 'string', 'nullable': False}
|
||||
bar_schema.update(default)
|
||||
schema = {'foo': {'type': 'string'},
|
||||
'bar': bar_schema}
|
||||
document = {'foo': 'foo_value', 'bar': 'bar_value'}
|
||||
assert_normalized(document, document.copy(), schema)
|
||||
schema = {'foo': {'type': 'string'}, 'bar': bar_schema}
|
||||
document = {'foo': 'foo_value', 'bar': None}
|
||||
expected = {'foo': 'foo_value', 'bar': 'bar_value'}
|
||||
assert_normalized(document, expected, schema)
|
||||
|
||||
|
||||
def test_default_none_default_value():
|
||||
schema = {'foo': {'type': 'string'},
|
||||
'bar': {'type': 'string',
|
||||
'nullable': True,
|
||||
'default': None}}
|
||||
schema = {
|
||||
'foo': {'type': 'string'},
|
||||
'bar': {'type': 'string', 'nullable': True, 'default': None},
|
||||
}
|
||||
document = {'foo': 'foo_value'}
|
||||
expected = {'foo': 'foo_value', 'bar': None}
|
||||
assert_normalized(document, expected, schema)
|
||||
|
||||
|
||||
def test_default_missing_in_subschema():
|
||||
_test_default_missing_in_subschema({'default': 'bar_value'})
|
||||
|
||||
|
||||
def test_default_setter_missing_in_subschema():
|
||||
_test_default_missing_in_subschema(
|
||||
{'default_setter': lambda doc: 'bar_value'})
|
||||
|
||||
|
||||
def _test_default_missing_in_subschema(default):
|
||||
@mark.parametrize(
|
||||
'default', ({'default': 'bar_value'}, {'default_setter': lambda doc: 'bar_value'})
|
||||
)
|
||||
def test_default_missing_in_subschema(default):
|
||||
bar_schema = {'type': 'string'}
|
||||
bar_schema.update(default)
|
||||
schema = {'thing': {'type': 'dict',
|
||||
'schema': {'foo': {'type': 'string'},
|
||||
'bar': bar_schema}}}
|
||||
schema = {
|
||||
'thing': {
|
||||
'type': 'dict',
|
||||
'schema': {'foo': {'type': 'string'}, 'bar': bar_schema},
|
||||
}
|
||||
}
|
||||
document = {'thing': {'foo': 'foo_value'}}
|
||||
expected = {'thing': {'foo': 'foo_value',
|
||||
'bar': 'bar_value'}}
|
||||
expected = {'thing': {'foo': 'foo_value', 'bar': 'bar_value'}}
|
||||
assert_normalized(document, expected, schema)
|
||||
|
||||
|
||||
@@ -328,8 +360,7 @@ def test_depending_default_setters():
|
||||
'a': {'type': 'integer'},
|
||||
'b': {'type': 'integer', 'default_setter': lambda d: d['a'] + 1},
|
||||
'c': {'type': 'integer', 'default_setter': lambda d: d['b'] * 2},
|
||||
'd': {'type': 'integer',
|
||||
'default_setter': lambda d: d['b'] + d['c']}
|
||||
'd': {'type': 'integer', 'default_setter': lambda d: d['b'] + d['c']},
|
||||
}
|
||||
document = {'a': 1}
|
||||
expected = {'a': 1, 'b': 2, 'c': 4, 'd': 6}
|
||||
@@ -339,7 +370,7 @@ def test_depending_default_setters():
|
||||
def test_circular_depending_default_setters(validator):
|
||||
schema = {
|
||||
'a': {'type': 'integer', 'default_setter': lambda d: d['b'] + 1},
|
||||
'b': {'type': 'integer', 'default_setter': lambda d: d['a'] + 1}
|
||||
'b': {'type': 'integer', 'default_setter': lambda d: d['a'] + 1},
|
||||
}
|
||||
validator({}, schema)
|
||||
assert errors.SETTING_DEFAULT_FAILED in validator._errors
|
||||
@@ -353,14 +384,16 @@ def test_issue_250():
|
||||
'schema': {
|
||||
'type': 'dict',
|
||||
'allow_unknown': True,
|
||||
'schema': {'a': {'type': 'string'}}
|
||||
}
|
||||
'schema': {'a': {'type': 'string'}},
|
||||
},
|
||||
}
|
||||
}
|
||||
document = {'list': {'is_a': 'mapping'}}
|
||||
assert_fail(document, schema,
|
||||
error=('list', ('list', 'type'), errors.BAD_TYPE,
|
||||
schema['list']['type']))
|
||||
assert_fail(
|
||||
document,
|
||||
schema,
|
||||
error=('list', ('list', 'type'), errors.BAD_TYPE, schema['list']['type']),
|
||||
)
|
||||
|
||||
|
||||
def test_issue_250_no_type_pass_on_list():
|
||||
@@ -370,7 +403,7 @@ def test_issue_250_no_type_pass_on_list():
|
||||
'schema': {
|
||||
'allow_unknown': True,
|
||||
'type': 'dict',
|
||||
'schema': {'a': {'type': 'string'}}
|
||||
'schema': {'a': {'type': 'string'}},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -381,28 +414,25 @@ def test_issue_250_no_type_pass_on_list():
|
||||
def test_issue_250_no_type_fail_on_dict():
|
||||
# https://github.com/pyeve/cerberus/issues/250
|
||||
schema = {
|
||||
'list': {
|
||||
'schema': {
|
||||
'allow_unknown': True,
|
||||
'schema': {'a': {'type': 'string'}}
|
||||
}
|
||||
}
|
||||
'list': {'schema': {'allow_unknown': True, 'schema': {'a': {'type': 'string'}}}}
|
||||
}
|
||||
document = {'list': {'a': {'a': 'known'}}}
|
||||
assert_fail(document, schema,
|
||||
error=('list', ('list', 'schema'), errors.BAD_TYPE_FOR_SCHEMA,
|
||||
schema['list']['schema']))
|
||||
assert_fail(
|
||||
document,
|
||||
schema,
|
||||
error=(
|
||||
'list',
|
||||
('list', 'schema'),
|
||||
errors.BAD_TYPE_FOR_SCHEMA,
|
||||
schema['list']['schema'],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def test_issue_250_no_type_fail_pass_on_other():
|
||||
# https://github.com/pyeve/cerberus/issues/250
|
||||
schema = {
|
||||
'list': {
|
||||
'schema': {
|
||||
'allow_unknown': True,
|
||||
'schema': {'a': {'type': 'string'}}
|
||||
}
|
||||
}
|
||||
'list': {'schema': {'allow_unknown': True, 'schema': {'a': {'type': 'string'}}}}
|
||||
}
|
||||
document = {'list': 1}
|
||||
assert_normalized(document, document, schema)
|
||||
@@ -416,21 +446,20 @@ def test_allow_unknown_with_of_rules():
|
||||
{
|
||||
'type': 'dict',
|
||||
'allow_unknown': True,
|
||||
'schema': {'known': {'type': 'string'}}
|
||||
},
|
||||
{
|
||||
'type': 'dict',
|
||||
'schema': {'known': {'type': 'string'}}
|
||||
'schema': {'known': {'type': 'string'}},
|
||||
},
|
||||
{'type': 'dict', 'schema': {'known': {'type': 'string'}}},
|
||||
]
|
||||
}
|
||||
}
|
||||
# check regression and that allow unknown does not cause any different
|
||||
# than expected behaviour for one-of.
|
||||
document = {'test': {'known': 's'}}
|
||||
assert_fail(document, schema,
|
||||
error=('test', ('test', 'oneof'),
|
||||
errors.ONEOF, schema['test']['oneof']))
|
||||
assert_fail(
|
||||
document,
|
||||
schema,
|
||||
error=('test', ('test', 'oneof'), errors.ONEOF, schema['test']['oneof']),
|
||||
)
|
||||
# check that allow_unknown is actually applied
|
||||
document = {'test': {'known': 's', 'unknown': 'asd'}}
|
||||
assert_success(document, schema)
|
||||
@@ -439,18 +468,20 @@ def test_allow_unknown_with_of_rules():
|
||||
def test_271_normalising_tuples():
|
||||
# https://github.com/pyeve/cerberus/issues/271
|
||||
schema = {
|
||||
'my_field': {
|
||||
'type': 'list',
|
||||
'schema': {'type': ('string', 'number', 'dict')}
|
||||
}
|
||||
'my_field': {'type': 'list', 'schema': {'type': ('string', 'number', 'dict')}}
|
||||
}
|
||||
document = {'my_field': ('foo', 'bar', 42, 'albert',
|
||||
'kandinsky', {'items': 23})}
|
||||
document = {'my_field': ('foo', 'bar', 42, 'albert', 'kandinsky', {'items': 23})}
|
||||
assert_success(document, schema)
|
||||
|
||||
normalized = Validator(schema).normalized(document)
|
||||
assert normalized['my_field'] == ('foo', 'bar', 42, 'albert',
|
||||
'kandinsky', {'items': 23})
|
||||
assert normalized['my_field'] == (
|
||||
'foo',
|
||||
'bar',
|
||||
42,
|
||||
'albert',
|
||||
'kandinsky',
|
||||
{'items': 23},
|
||||
)
|
||||
|
||||
|
||||
def test_allow_unknown_wo_schema():
|
||||
@@ -472,14 +503,41 @@ def test_allow_unknown_with_purge_unknown_subdocument():
|
||||
schema = {
|
||||
'foo': {
|
||||
'type': 'dict',
|
||||
'schema': {
|
||||
'bar': {
|
||||
'type': 'string'
|
||||
}
|
||||
},
|
||||
'allow_unknown': True
|
||||
'schema': {'bar': {'type': 'string'}},
|
||||
'allow_unknown': True,
|
||||
}
|
||||
}
|
||||
document = {'foo': {'bar': 'baz', 'corge': False}, 'thud': 'xyzzy'}
|
||||
expected = {'foo': {'bar': 'baz', 'corge': False}}
|
||||
assert_normalized(document, expected, schema, validator)
|
||||
|
||||
|
||||
def test_purge_readonly():
|
||||
schema = {
|
||||
'description': {'type': 'string', 'maxlength': 500},
|
||||
'last_updated': {'readonly': True},
|
||||
}
|
||||
validator = Validator(schema=schema, purge_readonly=True)
|
||||
document = {'description': 'it is a thing'}
|
||||
expected = deepcopy(document)
|
||||
document['last_updated'] = 'future'
|
||||
assert_normalized(document, expected, validator=validator)
|
||||
|
||||
|
||||
def test_defaults_in_allow_unknown_schema():
|
||||
schema = {'meta': {'type': 'dict'}, 'version': {'type': 'string'}}
|
||||
allow_unknown = {
|
||||
'type': 'dict',
|
||||
'schema': {
|
||||
'cfg_path': {'type': 'string', 'default': 'cfg.yaml'},
|
||||
'package': {'type': 'string'},
|
||||
},
|
||||
}
|
||||
validator = Validator(schema=schema, allow_unknown=allow_unknown)
|
||||
|
||||
document = {'version': '1.2.3', 'plugin_foo': {'package': 'foo'}}
|
||||
expected = {
|
||||
'version': '1.2.3',
|
||||
'plugin_foo': {'package': 'foo', 'cfg_path': 'cfg.yaml'},
|
||||
}
|
||||
assert_normalized(document, expected, schema, validator)
|
||||
|
||||
+13
-11
@@ -1,14 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from cerberus import schema_registry, rules_set_registry, Validator
|
||||
from cerberus.tests import (assert_fail, assert_normalized,
|
||||
assert_schema_error, assert_success)
|
||||
from cerberus.tests import (
|
||||
assert_fail,
|
||||
assert_normalized,
|
||||
assert_schema_error,
|
||||
assert_success,
|
||||
)
|
||||
|
||||
|
||||
def test_schema_registry_simple():
|
||||
schema_registry.add('foo', {'bar': {'type': 'string'}})
|
||||
schema = {'a': {'schema': 'foo'},
|
||||
'b': {'schema': 'foo'}}
|
||||
schema = {'a': {'schema': 'foo'}, 'b': {'schema': 'foo'}}
|
||||
document = {'a': {'bar': 'a'}, 'b': {'bar': 'b'}}
|
||||
assert_success(document, schema)
|
||||
|
||||
@@ -33,23 +36,22 @@ def test_allow_unknown_as_reference():
|
||||
|
||||
|
||||
def test_recursion():
|
||||
rules_set_registry.add('self',
|
||||
{'type': 'dict', 'allow_unknown': 'self'})
|
||||
rules_set_registry.add('self', {'type': 'dict', 'allow_unknown': 'self'})
|
||||
v = Validator(allow_unknown='self')
|
||||
assert_success({0: {1: {2: {}}}}, {}, v)
|
||||
|
||||
|
||||
def test_references_remain_unresolved(validator):
|
||||
rules_set_registry.extend((('boolean', {'type': 'boolean'}),
|
||||
('booleans', {'valueschema': 'boolean'})))
|
||||
rules_set_registry.extend(
|
||||
(('boolean', {'type': 'boolean'}), ('booleans', {'valuesrules': 'boolean'}))
|
||||
)
|
||||
validator.schema = {'foo': 'booleans'}
|
||||
assert 'booleans' == validator.schema['foo']
|
||||
assert 'boolean' == rules_set_registry._storage['booleans']['valueschema']
|
||||
assert 'boolean' == rules_set_registry._storage['booleans']['valuesrules']
|
||||
|
||||
|
||||
def test_rules_registry_with_anyof_type():
|
||||
rules_set_registry.add('string_or_integer',
|
||||
{'anyof_type': ['string', 'integer']})
|
||||
rules_set_registry.add('string_or_integer', {'anyof_type': ['string', 'integer']})
|
||||
schema = {'soi': 'string_or_integer'}
|
||||
assert_success({'soi': 'hello'}, schema)
|
||||
|
||||
|
||||
+90
-27
@@ -1,5 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from cerberus import Validator, errors, SchemaError
|
||||
@@ -9,14 +11,14 @@ from cerberus.tests import assert_schema_error
|
||||
|
||||
def test_empty_schema():
|
||||
validator = Validator()
|
||||
with pytest.raises(SchemaError, message=errors.SCHEMA_ERROR_MISSING):
|
||||
with pytest.raises(SchemaError, match=errors.SCHEMA_ERROR_MISSING):
|
||||
validator({}, schema=None)
|
||||
|
||||
|
||||
def test_bad_schema_type(validator):
|
||||
schema = "this string should really be dict"
|
||||
exp_msg = errors.SCHEMA_ERROR_DEFINITION_TYPE.format(schema)
|
||||
with pytest.raises(SchemaError, message=exp_msg):
|
||||
msg = errors.SCHEMA_ERROR_DEFINITION_TYPE.format(schema)
|
||||
with pytest.raises(SchemaError, match=msg):
|
||||
validator.schema = schema
|
||||
|
||||
|
||||
@@ -28,23 +30,21 @@ def test_bad_schema_type_field(validator):
|
||||
|
||||
|
||||
def test_unknown_rule(validator):
|
||||
message = "{'foo': [{'unknown': ['unknown rule']}]}"
|
||||
with pytest.raises(SchemaError, message=message):
|
||||
msg = "{'foo': [{'unknown': ['unknown rule']}]}"
|
||||
with pytest.raises(SchemaError, match=re.escape(msg)):
|
||||
validator.schema = {'foo': {'unknown': 'rule'}}
|
||||
|
||||
|
||||
def test_unknown_type(validator):
|
||||
field = 'name'
|
||||
value = 'catch_me'
|
||||
message = str({field: [{'type': ['unallowed value %s' % value]}]})
|
||||
with pytest.raises(SchemaError, message=message):
|
||||
validator.schema = {'foo': {'unknown': 'rule'}}
|
||||
msg = str({'foo': [{'type': ['Unsupported types: unknown']}]})
|
||||
with pytest.raises(SchemaError, match=re.escape(msg)):
|
||||
validator.schema = {'foo': {'type': 'unknown'}}
|
||||
|
||||
|
||||
def test_bad_schema_definition(validator):
|
||||
field = 'name'
|
||||
message = str({field: ['must be of dict type']})
|
||||
with pytest.raises(SchemaError, message=message):
|
||||
msg = str({field: ['must be of dict type']})
|
||||
with pytest.raises(SchemaError, match=re.escape(msg)):
|
||||
validator.schema = {field: 'this should really be a dict'}
|
||||
|
||||
|
||||
@@ -61,14 +61,14 @@ def test_normalization_rules_are_invalid_in_of_rules():
|
||||
def test_anyof_allof_schema_validate():
|
||||
# make sure schema with 'anyof' and 'allof' constraints are checked
|
||||
# correctly
|
||||
schema = {'doc': {'type': 'dict',
|
||||
'anyof': [
|
||||
{'schema': [{'param': {'type': 'number'}}]}]}}
|
||||
schema = {
|
||||
'doc': {'type': 'dict', 'anyof': [{'schema': [{'param': {'type': 'number'}}]}]}
|
||||
}
|
||||
assert_schema_error({'doc': 'this is my document'}, schema)
|
||||
|
||||
schema = {'doc': {'type': 'dict',
|
||||
'allof': [
|
||||
{'schema': [{'param': {'type': 'number'}}]}]}}
|
||||
schema = {
|
||||
'doc': {'type': 'dict', 'allof': [{'schema': [{'param': {'type': 'number'}}]}]}
|
||||
}
|
||||
assert_schema_error({'doc': 'this is my document'}, schema)
|
||||
|
||||
|
||||
@@ -88,24 +88,87 @@ def test_validated_schema_cache():
|
||||
v = Validator({'foozifix': {'coerce': int}})
|
||||
assert len(v._valid_schemas) == cache_size
|
||||
|
||||
max_cache_size = 147
|
||||
assert cache_size <= max_cache_size, \
|
||||
"There's an unexpected high amount (%s) of cached valid " \
|
||||
"definition schemas. Unless you added further tests, " \
|
||||
"there are good chances that something is wrong. " \
|
||||
"If you added tests with new schemas, you can try to " \
|
||||
"adjust the variable `max_cache_size` according to " \
|
||||
max_cache_size = 160
|
||||
assert cache_size <= max_cache_size, (
|
||||
"There's an unexpected high amount (%s) of cached valid "
|
||||
"definition schemas. Unless you added further tests, "
|
||||
"there are good chances that something is wrong. "
|
||||
"If you added tests with new schemas, you can try to "
|
||||
"adjust the variable `max_cache_size` according to "
|
||||
"the added schemas." % cache_size
|
||||
)
|
||||
|
||||
|
||||
def test_expansion_in_nested_schema():
|
||||
schema = {'detroit': {'schema': {'anyof_regex': ['^Aladdin', 'Sane$']}}}
|
||||
v = Validator(schema)
|
||||
assert (v.schema['detroit']['schema'] ==
|
||||
{'anyof': [{'regex': '^Aladdin'}, {'regex': 'Sane$'}]})
|
||||
assert v.schema['detroit']['schema'] == {
|
||||
'anyof': [{'regex': '^Aladdin'}, {'regex': 'Sane$'}]
|
||||
}
|
||||
|
||||
|
||||
def test_unvalidated_schema_can_be_copied():
|
||||
schema = UnvalidatedSchema()
|
||||
schema_copy = schema.copy()
|
||||
assert schema_copy == schema
|
||||
|
||||
|
||||
# TODO remove with next major release
|
||||
def test_deprecated_rule_names_in_valueschema():
|
||||
def check_with(field, value, error):
|
||||
pass
|
||||
|
||||
schema = {
|
||||
"field_1": {
|
||||
"type": "dict",
|
||||
"valueschema": {
|
||||
"type": "dict",
|
||||
"keyschema": {"type": "string"},
|
||||
"valueschema": {"type": "string"},
|
||||
},
|
||||
},
|
||||
"field_2": {
|
||||
"type": "list",
|
||||
"items": [
|
||||
{"keyschema": {}},
|
||||
{"validator": check_with},
|
||||
{"valueschema": {}},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
validator = Validator(schema)
|
||||
|
||||
assert validator.schema == {
|
||||
"field_1": {
|
||||
"type": "dict",
|
||||
"valuesrules": {
|
||||
"type": "dict",
|
||||
"keysrules": {"type": "string"},
|
||||
"valuesrules": {"type": "string"},
|
||||
},
|
||||
},
|
||||
"field_2": {
|
||||
"type": "list",
|
||||
"items": [
|
||||
{"keysrules": {}},
|
||||
{"check_with": check_with},
|
||||
{"valuesrules": {}},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_anyof_check_with():
|
||||
def foo(field, value, error):
|
||||
pass
|
||||
|
||||
def bar(field, value, error):
|
||||
pass
|
||||
|
||||
schema = {'field': {'anyof_check_with': [foo, bar]}}
|
||||
validator = Validator(schema)
|
||||
|
||||
assert validator.schema == {
|
||||
'field': {'anyof': [{'check_with': foo}, {'check_with': bar}]}
|
||||
}
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
from cerberus.utils import compare_paths_lt
|
||||
|
||||
|
||||
def test_compare_paths():
|
||||
lesser = ('a_dict', 'keysrules')
|
||||
greater = ('a_dict', 'valuesrules')
|
||||
assert compare_paths_lt(lesser, greater)
|
||||
|
||||
lesser += ('type',)
|
||||
greater += ('regex',)
|
||||
assert compare_paths_lt(lesser, greater)
|
||||
+826
-457
File diff suppressed because it is too large
Load Diff
Vendored
+35
-24
@@ -1,12 +1,11 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from collections import Mapping, namedtuple, Sequence
|
||||
from collections import namedtuple
|
||||
|
||||
from cerberus.platform import _int_types, _str_type
|
||||
from cerberus.platform import _int_types, _str_type, Mapping, Sequence, Set
|
||||
|
||||
|
||||
TypeDefinition = namedtuple('TypeDefinition',
|
||||
'name,included_types,excluded_types')
|
||||
TypeDefinition = namedtuple('TypeDefinition', 'name,included_types,excluded_types')
|
||||
"""
|
||||
This class is used to define types that can be used as value in the
|
||||
:attr:`~cerberus.Validator.types_mapping` property.
|
||||
@@ -19,19 +18,33 @@ contained in ``excluded_types``.
|
||||
|
||||
|
||||
def compare_paths_lt(x, y):
|
||||
for i in range(min(len(x), len(y))):
|
||||
if isinstance(x[i], type(y[i])):
|
||||
if x[i] != y[i]:
|
||||
return x[i] < y[i]
|
||||
elif isinstance(x[i], _int_types):
|
||||
min_length = min(len(x), len(y))
|
||||
|
||||
if x[:min_length] == y[:min_length]:
|
||||
return len(x) == min_length
|
||||
|
||||
for i in range(min_length):
|
||||
a, b = x[i], y[i]
|
||||
|
||||
for _type in (_int_types, _str_type, tuple):
|
||||
if isinstance(a, _type):
|
||||
if isinstance(b, _type):
|
||||
break
|
||||
else:
|
||||
return True
|
||||
|
||||
if a == b:
|
||||
continue
|
||||
elif a < b:
|
||||
return True
|
||||
elif isinstance(y[i], _int_types):
|
||||
else:
|
||||
return False
|
||||
return len(x) < len(y)
|
||||
|
||||
raise RuntimeError
|
||||
|
||||
|
||||
def drop_item_from_tuple(t, i):
|
||||
return t[:i] + t[i + 1:]
|
||||
return t[:i] + t[i + 1 :]
|
||||
|
||||
|
||||
def get_Validator_class():
|
||||
@@ -50,26 +63,24 @@ def mapping_to_frozenset(mapping):
|
||||
equal. As it is used to identify equality of schemas, this can be
|
||||
considered okay as definitions are semantically equal regardless the
|
||||
container type. """
|
||||
mapping = mapping.copy()
|
||||
|
||||
aggregation = {}
|
||||
|
||||
for key, value in mapping.items():
|
||||
if isinstance(value, Mapping):
|
||||
mapping[key] = mapping_to_frozenset(value)
|
||||
aggregation[key] = mapping_to_frozenset(value)
|
||||
elif isinstance(value, Sequence):
|
||||
value = list(value)
|
||||
for i, item in enumerate(value):
|
||||
if isinstance(item, Mapping):
|
||||
value[i] = mapping_to_frozenset(item)
|
||||
mapping[key] = tuple(value)
|
||||
return frozenset(mapping.items())
|
||||
aggregation[key] = tuple(value)
|
||||
elif isinstance(value, Set):
|
||||
aggregation[key] = frozenset(value)
|
||||
else:
|
||||
aggregation[key] = value
|
||||
|
||||
|
||||
def isclass(obj):
|
||||
try:
|
||||
issubclass(obj, object)
|
||||
except TypeError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
return frozenset(aggregation.items())
|
||||
|
||||
|
||||
def quote_string(value):
|
||||
|
||||
Vendored
+507
-299
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
||||
from .core import where
|
||||
|
||||
__version__ = "2018.11.29"
|
||||
__version__ = "2019.03.09"
|
||||
|
||||
Vendored
+146
@@ -4510,3 +4510,149 @@ Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh
|
||||
jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw
|
||||
3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI
|
||||
# Subject: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI
|
||||
# Label: "emSign Root CA - G1"
|
||||
# Serial: 235931866688319308814040
|
||||
# MD5 Fingerprint: 9c:42:84:57:dd:cb:0b:a7:2e:95:ad:b6:f3:da:bc:ac
|
||||
# SHA1 Fingerprint: 8a:c7:ad:8f:73:ac:4e:c1:b5:75:4d:a5:40:f4:fc:cf:7c:b5:8e:8c
|
||||
# SHA256 Fingerprint: 40:f6:af:03:46:a9:9a:a1:cd:1d:55:5a:4e:9c:ce:62:c7:f9:63:46:03:ee:40:66:15:83:3d:c8:c8:d0:03:67
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD
|
||||
VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU
|
||||
ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH
|
||||
MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO
|
||||
MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv
|
||||
Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN
|
||||
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz
|
||||
f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO
|
||||
8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq
|
||||
d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM
|
||||
tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt
|
||||
Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB
|
||||
o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD
|
||||
AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x
|
||||
PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM
|
||||
wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d
|
||||
GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH
|
||||
6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby
|
||||
RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx
|
||||
iN66zB+Afko=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI
|
||||
# Subject: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI
|
||||
# Label: "emSign ECC Root CA - G3"
|
||||
# Serial: 287880440101571086945156
|
||||
# MD5 Fingerprint: ce:0b:72:d1:9f:88:8e:d0:50:03:e8:e3:b8:8b:67:40
|
||||
# SHA1 Fingerprint: 30:43:fa:4f:f2:57:dc:a0:c3:80:ee:2e:58:ea:78:b2:3f:e6:bb:c1
|
||||
# SHA256 Fingerprint: 86:a1:ec:ba:08:9c:4a:8d:3b:be:27:34:c6:12:ba:34:1d:81:3e:04:3c:f9:e8:a8:62:cd:5c:57:a3:6b:be:6b
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG
|
||||
EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo
|
||||
bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g
|
||||
RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ
|
||||
TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s
|
||||
b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw
|
||||
djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0
|
||||
WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS
|
||||
fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB
|
||||
zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq
|
||||
hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB
|
||||
CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD
|
||||
+JbNR6iC8hZVdyR+EhCVBCyj
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI
|
||||
# Subject: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI
|
||||
# Label: "emSign Root CA - C1"
|
||||
# Serial: 825510296613316004955058
|
||||
# MD5 Fingerprint: d8:e3:5d:01:21:fa:78:5a:b0:df:ba:d2:ee:2a:5f:68
|
||||
# SHA1 Fingerprint: e7:2e:f1:df:fc:b2:09:28:cf:5d:d4:d5:67:37:b1:51:cb:86:4f:01
|
||||
# SHA256 Fingerprint: 12:56:09:aa:30:1d:a0:a2:49:b9:7a:82:39:cb:6a:34:21:6f:44:dc:ac:9f:39:54:b1:42:92:f2:e8:c8:60:8f
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG
|
||||
A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg
|
||||
SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw
|
||||
MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln
|
||||
biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v
|
||||
dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ
|
||||
BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ
|
||||
HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH
|
||||
3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH
|
||||
GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c
|
||||
xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1
|
||||
aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq
|
||||
TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
|
||||
BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87
|
||||
/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4
|
||||
kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG
|
||||
YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT
|
||||
+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo
|
||||
WXzhriKi4gp6D/piq1JM4fHfyr6DDUI=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI
|
||||
# Subject: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI
|
||||
# Label: "emSign ECC Root CA - C3"
|
||||
# Serial: 582948710642506000014504
|
||||
# MD5 Fingerprint: 3e:53:b3:a3:81:ee:d7:10:f8:d3:b0:1d:17:92:f5:d5
|
||||
# SHA1 Fingerprint: b6:af:43:c2:9b:81:53:7d:f6:ef:6b:c3:1f:1f:60:15:0c:ee:48:66
|
||||
# SHA256 Fingerprint: bc:4d:80:9b:15:18:9d:78:db:3e:1d:8c:f4:f9:72:6a:79:5d:a1:64:3c:a5:f1:35:8e:1d:db:0e:dc:0d:7e:b3
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG
|
||||
EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx
|
||||
IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw
|
||||
MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln
|
||||
biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND
|
||||
IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci
|
||||
MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti
|
||||
sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O
|
||||
BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB
|
||||
Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c
|
||||
3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J
|
||||
0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=Hongkong Post Root CA 3 O=Hongkong Post
|
||||
# Subject: CN=Hongkong Post Root CA 3 O=Hongkong Post
|
||||
# Label: "Hongkong Post Root CA 3"
|
||||
# Serial: 46170865288971385588281144162979347873371282084
|
||||
# MD5 Fingerprint: 11:fc:9f:bd:73:30:02:8a:fd:3f:f3:58:b9:cb:20:f0
|
||||
# SHA1 Fingerprint: 58:a2:d0:ec:20:52:81:5b:c1:f3:f8:64:02:24:4e:c2:8e:02:4b:02
|
||||
# SHA256 Fingerprint: 5a:2f:c0:3f:0c:83:b0:90:bb:fa:40:60:4b:09:88:44:6c:76:36:18:3d:f9:84:6e:17:10:1a:44:7f:b8:ef:d6
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL
|
||||
BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ
|
||||
SG9uZyBLb25nMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25n
|
||||
a29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2MDMwMjI5NDZaFw00MjA2MDMwMjI5
|
||||
NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcT
|
||||
CUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMXSG9u
|
||||
Z2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
|
||||
AoICAQCziNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFO
|
||||
dem1p+/l6TWZ5Mwc50tfjTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mI
|
||||
VoBc+L0sPOFMV4i707mV78vH9toxdCim5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV
|
||||
9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOesL4jpNrcyCse2m5FHomY
|
||||
2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj0mRiikKY
|
||||
vLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+Tt
|
||||
bNe/JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZb
|
||||
x39ri1UbSsUgYT2uy1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+
|
||||
l2oBlKN8W4UdKjk60FSh0Tlxnf0h+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YK
|
||||
TE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsGxVd7GYYKecsAyVKvQv83j+Gj
|
||||
Hno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwIDAQABo2MwYTAP
|
||||
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e
|
||||
i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEw
|
||||
DQYJKoZIhvcNAQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG
|
||||
7BJ8dNVI0lkUmcDrudHr9EgwW62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCk
|
||||
MpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWldy8joRTnU+kLBEUx3XZL7av9YROXr
|
||||
gZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov+BS5gLNdTaqX4fnk
|
||||
GMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDceqFS
|
||||
3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJm
|
||||
Ozj/2ZQw9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+
|
||||
l6mc1X5VTMbeRRAc6uk7nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6c
|
||||
JfTzPV4e0hz5sy229zdcxsshTrD3mUcYhcErulWuBurQB7Lcq9CClnXO0lD+mefP
|
||||
L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa
|
||||
LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG
|
||||
mpv0
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Vendored
-5
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@@ -14,7 +13,3 @@ def where():
|
||||
f = os.path.dirname(__file__)
|
||||
|
||||
return os.path.join(f, 'cacert.pem')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(where())
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ from click_completion.core import completion_configuration, get_code, install, s
|
||||
from click_completion.lib import get_auto_shell
|
||||
from click_completion.patch import patch as _patch
|
||||
|
||||
__version__ = '0.5.0'
|
||||
__version__ = '0.5.1'
|
||||
|
||||
_initialized = False
|
||||
|
||||
|
||||
+8
-12
@@ -131,8 +131,9 @@ def get_choices(cli, prog_name, args, incomplete):
|
||||
choices.append((opt, None))
|
||||
if isinstance(ctx.command, MultiCommand):
|
||||
for name in ctx.command.list_commands(ctx):
|
||||
if match(name, incomplete):
|
||||
choices.append((name, ctx.command.get_command_short_help(ctx, name)))
|
||||
command = ctx.command.get_command(ctx, name)
|
||||
if match(name, incomplete) and not command.hidden:
|
||||
choices.append((name, command.get_short_help_str()))
|
||||
|
||||
for item, help in choices:
|
||||
yield (item, help)
|
||||
@@ -201,7 +202,7 @@ def do_fish_complete(cli, prog_name):
|
||||
|
||||
for item, help in get_choices(cli, prog_name, args, incomplete):
|
||||
if help:
|
||||
echo("%s\t%s" % (item, re.sub('\s', ' ', help)))
|
||||
echo("%s\t%s" % (item, re.sub(r'\s', ' ', help)))
|
||||
else:
|
||||
echo(item)
|
||||
|
||||
@@ -232,11 +233,11 @@ def do_zsh_complete(cli, prog_name):
|
||||
incomplete = ''
|
||||
|
||||
def escape(s):
|
||||
return s.replace('"', '""').replace("'", "''").replace('$', '\\$')
|
||||
return s.replace('"', '""').replace("'", "''").replace('$', '\\$').replace('`', '\\`')
|
||||
res = []
|
||||
for item, help in get_choices(cli, prog_name, args, incomplete):
|
||||
if help:
|
||||
res.append('"%s"\:"%s"' % (escape(item), escape(help)))
|
||||
res.append(r'"%s"\:"%s"' % (escape(item), escape(help)))
|
||||
else:
|
||||
res.append('"%s"' % escape(item))
|
||||
if res:
|
||||
@@ -349,13 +350,8 @@ def install(shell=None, prog_name=None, env_name=None, path=None, append=None, e
|
||||
path = path or os.path.expanduser('~') + '/.bash_completion'
|
||||
mode = mode or 'a'
|
||||
elif shell == 'zsh':
|
||||
ohmyzsh = os.path.expanduser('~') + '/.oh-my-zsh'
|
||||
if os.path.exists(ohmyzsh):
|
||||
path = path or ohmyzsh + '/completions/_%s' % prog_name
|
||||
mode = mode or 'w'
|
||||
else:
|
||||
path = path or os.path.expanduser('~') + '/.zshrc'
|
||||
mode = mode or 'a'
|
||||
path = path or os.path.expanduser('~') + '/.zshrc'
|
||||
mode = mode or 'a'
|
||||
elif shell == 'powershell':
|
||||
subprocess.check_call(['powershell', 'Set-ExecutionPolicy Unrestricted -Scope CurrentUser'])
|
||||
path = path or subprocess.check_output(['powershell', '-NoProfile', 'echo $profile']).strip() if install else ''
|
||||
|
||||
-1
@@ -3,6 +3,5 @@ _{{prog_name}}() {
|
||||
eval $(env COMMANDLINE="${words[1,$CURRENT]}" {{complete_var}}=complete-zsh {% for k, v in extra_env.items() %} {{k}}={{v}}{% endfor %} {{prog_name}})
|
||||
}
|
||||
if [[ "$(basename -- ${(%):-%x})" != "_{{prog_name}}" ]]; then
|
||||
autoload -U compinit && compinit
|
||||
compdef _{{prog_name}} {{prog_name}}
|
||||
fi
|
||||
|
||||
Vendored
+1
-1
@@ -6,7 +6,7 @@
|
||||
#
|
||||
import logging
|
||||
|
||||
__version__ = '0.2.8'
|
||||
__version__ = '0.2.9'
|
||||
|
||||
class DistlibException(Exception):
|
||||
pass
|
||||
|
||||
Vendored
+1
-1
@@ -22,7 +22,7 @@ from .util import cached_property, zip_dir, ServerProxy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_INDEX = 'https://pypi.python.org/pypi'
|
||||
DEFAULT_INDEX = 'https://pypi.org/pypi'
|
||||
DEFAULT_REALM = 'pypi'
|
||||
|
||||
class PackageIndex(object):
|
||||
|
||||
Vendored
+3
-3
@@ -36,7 +36,7 @@ logger = logging.getLogger(__name__)
|
||||
HASHER_HASH = re.compile(r'^(\w+)=([a-f0-9]+)')
|
||||
CHARSET = re.compile(r';\s*charset\s*=\s*(.*)\s*$', re.I)
|
||||
HTML_CONTENT_TYPE = re.compile('text/html|application/x(ht)?ml')
|
||||
DEFAULT_INDEX = 'https://pypi.python.org/pypi'
|
||||
DEFAULT_INDEX = 'https://pypi.org/pypi'
|
||||
|
||||
def get_all_distribution_names(url=None):
|
||||
"""
|
||||
@@ -197,7 +197,7 @@ class Locator(object):
|
||||
is_downloadable = basename.endswith(self.downloadable_extensions)
|
||||
if is_wheel:
|
||||
compatible = is_compatible(Wheel(basename), self.wheel_tags)
|
||||
return (t.scheme == 'https', 'pypi.python.org' in t.netloc,
|
||||
return (t.scheme == 'https', 'pypi.org' in t.netloc,
|
||||
is_downloadable, is_wheel, compatible, basename)
|
||||
|
||||
def prefer_url(self, url1, url2):
|
||||
@@ -1049,7 +1049,7 @@ class AggregatingLocator(Locator):
|
||||
# versions which don't conform to PEP 426 / PEP 440.
|
||||
default_locator = AggregatingLocator(
|
||||
JSONLocator(),
|
||||
SimpleScrapingLocator('https://pypi.python.org/simple/',
|
||||
SimpleScrapingLocator('https://pypi.org/simple/',
|
||||
timeout=3.0),
|
||||
scheme='legacy')
|
||||
|
||||
|
||||
Vendored
+5
-3
@@ -91,9 +91,11 @@ _426_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform',
|
||||
_426_MARKERS = ('Private-Version', 'Provides-Extra', 'Obsoleted-By',
|
||||
'Setup-Requires-Dist', 'Extension')
|
||||
|
||||
# See issue #106: Sometimes 'Requires' occurs wrongly in the metadata. Include
|
||||
# it in the tuple literal below to allow it (for now)
|
||||
_566_FIELDS = _426_FIELDS + ('Description-Content-Type', 'Requires')
|
||||
# See issue #106: Sometimes 'Requires' and 'Provides' occur wrongly in
|
||||
# the metadata. Include them in the tuple literal below to allow them
|
||||
# (for now).
|
||||
_566_FIELDS = _426_FIELDS + ('Description-Content-Type',
|
||||
'Requires', 'Provides')
|
||||
|
||||
_566_MARKERS = ('Description-Content-Type',)
|
||||
|
||||
|
||||
Vendored
+6
-20
@@ -39,27 +39,12 @@ _DEFAULT_MANIFEST = '''
|
||||
# check if Python is called on the first line with this expression
|
||||
FIRST_LINE_RE = re.compile(b'^#!.*pythonw?[0-9.]*([ \t].*)?$')
|
||||
SCRIPT_TEMPLATE = r'''# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from %(module)s import %(import_name)s
|
||||
if __name__ == '__main__':
|
||||
import sys, re
|
||||
|
||||
def _resolve(module, func):
|
||||
__import__(module)
|
||||
mod = sys.modules[module]
|
||||
parts = func.split('.')
|
||||
result = getattr(mod, parts.pop(0))
|
||||
for p in parts:
|
||||
result = getattr(result, p)
|
||||
return result
|
||||
|
||||
try:
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
|
||||
func = _resolve('%(module)s', '%(func)s')
|
||||
rc = func() # None interpreted as 0
|
||||
except Exception as e: # only supporting Python >= 2.6
|
||||
sys.stderr.write('%%s\n' %% e)
|
||||
rc = 1
|
||||
sys.exit(rc)
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(%(func)s())
|
||||
'''
|
||||
|
||||
|
||||
@@ -225,6 +210,7 @@ class ScriptMaker(object):
|
||||
|
||||
def _get_script_text(self, entry):
|
||||
return self.script_template % dict(module=entry.prefix,
|
||||
import_name=entry.suffix.split('.')[0],
|
||||
func=entry.suffix)
|
||||
|
||||
manifest = _DEFAULT_MANIFEST
|
||||
|
||||
Vendored
+5
-1
@@ -804,11 +804,15 @@ def ensure_slash(s):
|
||||
def parse_credentials(netloc):
|
||||
username = password = None
|
||||
if '@' in netloc:
|
||||
prefix, netloc = netloc.split('@', 1)
|
||||
prefix, netloc = netloc.rsplit('@', 1)
|
||||
if ':' not in prefix:
|
||||
username = prefix
|
||||
else:
|
||||
username, password = prefix.split(':', 1)
|
||||
if username:
|
||||
username = unquote(username)
|
||||
if password:
|
||||
password = unquote(password)
|
||||
return username, password, netloc
|
||||
|
||||
|
||||
|
||||
Vendored
+23
-7
@@ -433,6 +433,22 @@ class Wheel(object):
|
||||
self.build_zip(pathname, archive_paths)
|
||||
return pathname
|
||||
|
||||
def skip_entry(self, arcname):
|
||||
"""
|
||||
Determine whether an archive entry should be skipped when verifying
|
||||
or installing.
|
||||
"""
|
||||
# The signature file won't be in RECORD,
|
||||
# and we don't currently don't do anything with it
|
||||
# We also skip directories, as they won't be in RECORD
|
||||
# either. See:
|
||||
#
|
||||
# https://github.com/pypa/wheel/issues/294
|
||||
# https://github.com/pypa/wheel/issues/287
|
||||
# https://github.com/pypa/wheel/pull/289
|
||||
#
|
||||
return arcname.endswith(('/', '/RECORD.jws'))
|
||||
|
||||
def install(self, paths, maker, **kwargs):
|
||||
"""
|
||||
Install a wheel to the specified paths. If kwarg ``warner`` is
|
||||
@@ -514,9 +530,7 @@ class Wheel(object):
|
||||
u_arcname = arcname
|
||||
else:
|
||||
u_arcname = arcname.decode('utf-8')
|
||||
# The signature file won't be in RECORD,
|
||||
# and we don't currently don't do anything with it
|
||||
if u_arcname.endswith('/RECORD.jws'):
|
||||
if self.skip_entry(u_arcname):
|
||||
continue
|
||||
row = records[u_arcname]
|
||||
if row[2] and str(zinfo.file_size) != row[2]:
|
||||
@@ -786,13 +800,15 @@ class Wheel(object):
|
||||
u_arcname = arcname
|
||||
else:
|
||||
u_arcname = arcname.decode('utf-8')
|
||||
if '..' in u_arcname:
|
||||
# See issue #115: some wheels have .. in their entries, but
|
||||
# in the filename ... e.g. __main__..py ! So the check is
|
||||
# updated to look for .. in the directory portions
|
||||
p = u_arcname.split('/')
|
||||
if '..' in p:
|
||||
raise DistlibException('invalid entry in '
|
||||
'wheel: %r' % u_arcname)
|
||||
|
||||
# The signature file won't be in RECORD,
|
||||
# and we don't currently don't do anything with it
|
||||
if u_arcname.endswith('/RECORD.jws'):
|
||||
if self.skip_entry(u_arcname):
|
||||
continue
|
||||
row = records[u_arcname]
|
||||
if row[2] and str(zinfo.file_size) != row[2]:
|
||||
|
||||
Vendored
+6
@@ -1,12 +1,18 @@
|
||||
from .compat import IS_TYPE_CHECKING
|
||||
from .main import load_dotenv, get_key, set_key, unset_key, find_dotenv, dotenv_values
|
||||
|
||||
if IS_TYPE_CHECKING:
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
def load_ipython_extension(ipython):
|
||||
# type: (Any) -> None
|
||||
from .ipython import load_ipython_extension
|
||||
load_ipython_extension(ipython)
|
||||
|
||||
|
||||
def get_cli_string(path=None, action=None, key=None, value=None, quote=None):
|
||||
# type: (Optional[str], Optional[str], Optional[str], Optional[str], Optional[str]) -> str
|
||||
"""Returns a string suitable for running as a shell script.
|
||||
|
||||
Useful for converting a arguments passed to a fabric task
|
||||
|
||||
Vendored
+11
-1
@@ -8,9 +8,13 @@ except ImportError:
|
||||
'Run pip install "python-dotenv[cli]" to fix this.')
|
||||
sys.exit(1)
|
||||
|
||||
from .compat import IS_TYPE_CHECKING
|
||||
from .main import dotenv_values, get_key, set_key, unset_key, run_command
|
||||
from .version import __version__
|
||||
|
||||
if IS_TYPE_CHECKING:
|
||||
from typing import Any, List
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.option('-f', '--file', default=os.path.join(os.getcwd(), '.env'),
|
||||
@@ -22,6 +26,7 @@ from .version import __version__
|
||||
@click.version_option(version=__version__)
|
||||
@click.pass_context
|
||||
def cli(ctx, file, quote):
|
||||
# type: (click.Context, Any, Any) -> None
|
||||
'''This script is used to set, get or unset values from a .env file.'''
|
||||
ctx.obj = {}
|
||||
ctx.obj['FILE'] = file
|
||||
@@ -31,6 +36,7 @@ def cli(ctx, file, quote):
|
||||
@cli.command()
|
||||
@click.pass_context
|
||||
def list(ctx):
|
||||
# type: (click.Context) -> None
|
||||
'''Display all the stored key/value.'''
|
||||
file = ctx.obj['FILE']
|
||||
dotenv_as_dict = dotenv_values(file)
|
||||
@@ -43,6 +49,7 @@ def list(ctx):
|
||||
@click.argument('key', required=True)
|
||||
@click.argument('value', required=True)
|
||||
def set(ctx, key, value):
|
||||
# type: (click.Context, Any, Any) -> None
|
||||
'''Store the given key/value.'''
|
||||
file = ctx.obj['FILE']
|
||||
quote = ctx.obj['QUOTE']
|
||||
@@ -57,6 +64,7 @@ def set(ctx, key, value):
|
||||
@click.pass_context
|
||||
@click.argument('key', required=True)
|
||||
def get(ctx, key):
|
||||
# type: (click.Context, Any) -> None
|
||||
'''Retrieve the value for the given key.'''
|
||||
file = ctx.obj['FILE']
|
||||
stored_value = get_key(file, key)
|
||||
@@ -70,6 +78,7 @@ def get(ctx, key):
|
||||
@click.pass_context
|
||||
@click.argument('key', required=True)
|
||||
def unset(ctx, key):
|
||||
# type: (click.Context, Any) -> None
|
||||
'''Removes the given key.'''
|
||||
file = ctx.obj['FILE']
|
||||
quote = ctx.obj['QUOTE']
|
||||
@@ -84,13 +93,14 @@ def unset(ctx, key):
|
||||
@click.pass_context
|
||||
@click.argument('commandline', nargs=-1, type=click.UNPROCESSED)
|
||||
def run(ctx, commandline):
|
||||
# type: (click.Context, List[str]) -> None
|
||||
"""Run command with environment variables present."""
|
||||
file = ctx.obj['FILE']
|
||||
dotenv_as_dict = dotenv_values(file)
|
||||
if not commandline:
|
||||
click.echo('No command given.')
|
||||
exit(1)
|
||||
ret = run_command(commandline, dotenv_as_dict)
|
||||
ret = run_command(commandline, dotenv_as_dict) # type: ignore
|
||||
exit(ret)
|
||||
|
||||
|
||||
|
||||
Vendored
+19
-7
@@ -1,9 +1,21 @@
|
||||
import os
|
||||
import sys
|
||||
try:
|
||||
from StringIO import StringIO # noqa
|
||||
except ImportError:
|
||||
from io import StringIO # noqa
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
WIN = sys.platform.startswith('win')
|
||||
text_type = unicode if PY2 else str # noqa
|
||||
if sys.version_info >= (3, 0):
|
||||
from io import StringIO # noqa
|
||||
else:
|
||||
from StringIO import StringIO # noqa
|
||||
|
||||
PY2 = sys.version_info[0] == 2 # type: bool
|
||||
|
||||
|
||||
def is_type_checking():
|
||||
# type: () -> bool
|
||||
try:
|
||||
from typing import TYPE_CHECKING
|
||||
except ImportError:
|
||||
return False
|
||||
return TYPE_CHECKING
|
||||
|
||||
|
||||
IS_TYPE_CHECKING = os.environ.get("MYPY_RUNNING", is_type_checking())
|
||||
|
||||
Vendored
-54
@@ -1,54 +0,0 @@
|
||||
import os
|
||||
|
||||
|
||||
class UndefinedValueError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Undefined(object):
|
||||
"""Class to represent undefined type. """
|
||||
pass
|
||||
|
||||
|
||||
# Reference instance to represent undefined values
|
||||
undefined = Undefined()
|
||||
|
||||
|
||||
def _cast_boolean(value):
|
||||
"""
|
||||
Helper to convert config values to boolean as ConfigParser do.
|
||||
"""
|
||||
_BOOLEANS = {'1': True, 'yes': True, 'true': True, 'on': True,
|
||||
'0': False, 'no': False, 'false': False, 'off': False, '': False}
|
||||
value = str(value)
|
||||
if value.lower() not in _BOOLEANS:
|
||||
raise ValueError('Not a boolean: %s' % value)
|
||||
|
||||
return _BOOLEANS[value.lower()]
|
||||
|
||||
|
||||
def getenv(option, default=undefined, cast=undefined):
|
||||
"""
|
||||
Return the value for option or default if defined.
|
||||
"""
|
||||
|
||||
# We can't avoid __contains__ because value may be empty.
|
||||
if option in os.environ:
|
||||
value = os.environ[option]
|
||||
else:
|
||||
if isinstance(default, Undefined):
|
||||
raise UndefinedValueError('{} not found. Declare it as envvar or define a default value.'.format(option))
|
||||
|
||||
value = default
|
||||
|
||||
if isinstance(cast, Undefined):
|
||||
return value
|
||||
|
||||
if cast is bool:
|
||||
value = _cast_boolean(value)
|
||||
elif cast is list:
|
||||
value = [x for x in value.split(',') if x]
|
||||
else:
|
||||
value = cast(value)
|
||||
|
||||
return value
|
||||
Vendored
+3
-3
@@ -1,8 +1,8 @@
|
||||
from __future__ import print_function
|
||||
|
||||
from IPython.core.magic import Magics, line_magic, magics_class
|
||||
from IPython.core.magic_arguments import (argument, magic_arguments,
|
||||
parse_argstring)
|
||||
from IPython.core.magic import Magics, line_magic, magics_class # type: ignore
|
||||
from IPython.core.magic_arguments import (argument, magic_arguments, # type: ignore
|
||||
parse_argstring) # type: ignore
|
||||
|
||||
from .main import find_dotenv, load_dotenv
|
||||
|
||||
|
||||
Vendored
+91
-31
@@ -10,12 +10,27 @@ import sys
|
||||
from subprocess import Popen
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict, namedtuple
|
||||
from collections import OrderedDict
|
||||
from contextlib import contextmanager
|
||||
|
||||
from .compat import StringIO, PY2, WIN, text_type
|
||||
from .compat import StringIO, PY2, IS_TYPE_CHECKING
|
||||
|
||||
__posix_variable = re.compile(r'\$\{[^\}]*\}')
|
||||
if IS_TYPE_CHECKING: # pragma: no cover
|
||||
from typing import (
|
||||
Dict, Iterator, List, Match, Optional, Pattern, Union,
|
||||
Text, IO, Tuple
|
||||
)
|
||||
if sys.version_info >= (3, 6):
|
||||
_PathLike = os.PathLike
|
||||
else:
|
||||
_PathLike = Text
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
_StringIO = StringIO
|
||||
else:
|
||||
_StringIO = StringIO[Text]
|
||||
|
||||
__posix_variable = re.compile(r'\$\{[^\}]*\}') # type: Pattern[Text]
|
||||
|
||||
_binding = re.compile(
|
||||
r"""
|
||||
@@ -42,22 +57,31 @@ _binding = re.compile(
|
||||
)
|
||||
""".format(r'[^\S\r\n]'),
|
||||
re.MULTILINE | re.VERBOSE,
|
||||
)
|
||||
) # type: Pattern[Text]
|
||||
|
||||
_escape_sequence = re.compile(r"\\[\\'\"abfnrtv]")
|
||||
_escape_sequence = re.compile(r"\\[\\'\"abfnrtv]") # type: Pattern[Text]
|
||||
|
||||
|
||||
Binding = namedtuple('Binding', 'key value original')
|
||||
try:
|
||||
from typing import NamedTuple, Optional, Text
|
||||
Binding = NamedTuple("Binding", [("key", Optional[Text]),
|
||||
("value", Optional[Text]),
|
||||
("original", Text)])
|
||||
except ImportError:
|
||||
from collections import namedtuple
|
||||
Binding = namedtuple("Binding", ["key", "value", "original"])
|
||||
|
||||
|
||||
def decode_escapes(string):
|
||||
# type: (Text) -> Text
|
||||
def decode_match(match):
|
||||
return codecs.decode(match.group(0), 'unicode-escape')
|
||||
# type: (Match[Text]) -> Text
|
||||
return codecs.decode(match.group(0), 'unicode-escape') # type: ignore
|
||||
|
||||
return _escape_sequence.sub(decode_match, string)
|
||||
|
||||
|
||||
def is_surrounded_by(string, char):
|
||||
# type: (Text, Text) -> bool
|
||||
return (
|
||||
len(string) > 1
|
||||
and string[0] == string[-1] == char
|
||||
@@ -65,7 +89,9 @@ def is_surrounded_by(string, char):
|
||||
|
||||
|
||||
def parse_binding(string, position):
|
||||
# type: (Text, int) -> Tuple[Binding, int]
|
||||
match = _binding.match(string, position)
|
||||
assert match is not None
|
||||
(matched, key, value) = match.groups()
|
||||
if key is None or value is None:
|
||||
key = None
|
||||
@@ -80,6 +106,7 @@ def parse_binding(string, position):
|
||||
|
||||
|
||||
def parse_stream(stream):
|
||||
# type:(IO[Text]) -> Iterator[Binding]
|
||||
string = stream.read()
|
||||
position = 0
|
||||
length = len(string)
|
||||
@@ -88,26 +115,41 @@ def parse_stream(stream):
|
||||
yield binding
|
||||
|
||||
|
||||
def to_env(text):
|
||||
# type: (Text) -> str
|
||||
"""
|
||||
Encode a string the same way whether it comes from the environment or a `.env` file.
|
||||
"""
|
||||
if PY2:
|
||||
return text.encode(sys.getfilesystemencoding() or "utf-8")
|
||||
else:
|
||||
return text
|
||||
|
||||
|
||||
class DotEnv():
|
||||
|
||||
def __init__(self, dotenv_path, verbose=False):
|
||||
self.dotenv_path = dotenv_path
|
||||
self._dict = None
|
||||
self.verbose = verbose
|
||||
def __init__(self, dotenv_path, verbose=False, encoding=None):
|
||||
# type: (Union[Text, _PathLike, _StringIO], bool, Union[None, Text]) -> None
|
||||
self.dotenv_path = dotenv_path # type: Union[Text,_PathLike, _StringIO]
|
||||
self._dict = None # type: Optional[Dict[Text, Text]]
|
||||
self.verbose = verbose # type: bool
|
||||
self.encoding = encoding # type: Union[None, Text]
|
||||
|
||||
@contextmanager
|
||||
def _get_stream(self):
|
||||
# type: () -> Iterator[IO[Text]]
|
||||
if isinstance(self.dotenv_path, StringIO):
|
||||
yield self.dotenv_path
|
||||
elif os.path.isfile(self.dotenv_path):
|
||||
with io.open(self.dotenv_path) as stream:
|
||||
with io.open(self.dotenv_path, encoding=self.encoding) as stream:
|
||||
yield stream
|
||||
else:
|
||||
if self.verbose:
|
||||
warnings.warn("File doesn't exist {}".format(self.dotenv_path))
|
||||
warnings.warn("File doesn't exist {}".format(self.dotenv_path)) # type: ignore
|
||||
yield StringIO('')
|
||||
|
||||
def dict(self):
|
||||
# type: () -> Dict[Text, Text]
|
||||
"""Return dotenv as dict"""
|
||||
if self._dict:
|
||||
return self._dict
|
||||
@@ -117,29 +159,26 @@ class DotEnv():
|
||||
return self._dict
|
||||
|
||||
def parse(self):
|
||||
# type: () -> Iterator[Tuple[Text, Text]]
|
||||
with self._get_stream() as stream:
|
||||
for mapping in parse_stream(stream):
|
||||
if mapping.key is not None and mapping.value is not None:
|
||||
yield mapping.key, mapping.value
|
||||
|
||||
def set_as_environment_variables(self, override=False):
|
||||
# type: (bool) -> bool
|
||||
"""
|
||||
Load the current dotenv as system environemt variable.
|
||||
"""
|
||||
for k, v in self.dict().items():
|
||||
if k in os.environ and not override:
|
||||
continue
|
||||
# With Python2 on Windows, force environment variables to str to avoid
|
||||
# "TypeError: environment can only contain strings" in Python's subprocess.py.
|
||||
if PY2 and WIN:
|
||||
if isinstance(k, text_type) or isinstance(v, text_type):
|
||||
k = k.encode('ascii')
|
||||
v = v.encode('ascii')
|
||||
os.environ[k] = v
|
||||
os.environ[to_env(k)] = to_env(v)
|
||||
|
||||
return True
|
||||
|
||||
def get(self, key):
|
||||
# type: (Text) -> Optional[Text]
|
||||
"""
|
||||
"""
|
||||
data = self.dict()
|
||||
@@ -148,10 +187,13 @@ class DotEnv():
|
||||
return data[key]
|
||||
|
||||
if self.verbose:
|
||||
warnings.warn("key %s not found in %s." % (key, self.dotenv_path))
|
||||
warnings.warn("key %s not found in %s." % (key, self.dotenv_path)) # type: ignore
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_key(dotenv_path, key_to_get):
|
||||
# type: (Union[Text, _PathLike], Text) -> Optional[Text]
|
||||
"""
|
||||
Gets the value of a given key from the given .env
|
||||
|
||||
@@ -162,10 +204,11 @@ def get_key(dotenv_path, key_to_get):
|
||||
|
||||
@contextmanager
|
||||
def rewrite(path):
|
||||
# type: (_PathLike) -> Iterator[Tuple[IO[Text], IO[Text]]]
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(mode="w+", delete=False) as dest:
|
||||
with io.open(path) as source:
|
||||
yield (source, dest)
|
||||
yield (source, dest) # type: ignore
|
||||
except BaseException:
|
||||
if os.path.isfile(dest.name):
|
||||
os.unlink(dest.name)
|
||||
@@ -175,6 +218,7 @@ def rewrite(path):
|
||||
|
||||
|
||||
def set_key(dotenv_path, key_to_set, value_to_set, quote_mode="always"):
|
||||
# type: (_PathLike, Text, Text, Text) -> Tuple[Optional[bool], Text, Text]
|
||||
"""
|
||||
Adds or Updates a key/value to the given .env
|
||||
|
||||
@@ -183,7 +227,7 @@ def set_key(dotenv_path, key_to_set, value_to_set, quote_mode="always"):
|
||||
"""
|
||||
value_to_set = value_to_set.strip("'").strip('"')
|
||||
if not os.path.exists(dotenv_path):
|
||||
warnings.warn("can't write to %s - it doesn't exist." % dotenv_path)
|
||||
warnings.warn("can't write to %s - it doesn't exist." % dotenv_path) # type: ignore
|
||||
return None, key_to_set, value_to_set
|
||||
|
||||
if " " in value_to_set:
|
||||
@@ -207,6 +251,7 @@ def set_key(dotenv_path, key_to_set, value_to_set, quote_mode="always"):
|
||||
|
||||
|
||||
def unset_key(dotenv_path, key_to_unset, quote_mode="always"):
|
||||
# type: (_PathLike, Text, Text) -> Tuple[Optional[bool], Text]
|
||||
"""
|
||||
Removes a given key from the given .env
|
||||
|
||||
@@ -214,7 +259,7 @@ def unset_key(dotenv_path, key_to_unset, quote_mode="always"):
|
||||
If the given key doesn't exist in the .env, fails
|
||||
"""
|
||||
if not os.path.exists(dotenv_path):
|
||||
warnings.warn("can't delete from %s - it doesn't exist." % dotenv_path)
|
||||
warnings.warn("can't delete from %s - it doesn't exist." % dotenv_path) # type: ignore
|
||||
return None, key_to_unset
|
||||
|
||||
removed = False
|
||||
@@ -226,14 +271,16 @@ def unset_key(dotenv_path, key_to_unset, quote_mode="always"):
|
||||
dest.write(mapping.original)
|
||||
|
||||
if not removed:
|
||||
warnings.warn("key %s not removed from %s - key doesn't exist." % (key_to_unset, dotenv_path))
|
||||
warnings.warn("key %s not removed from %s - key doesn't exist." % (key_to_unset, dotenv_path)) # type: ignore
|
||||
return None, key_to_unset
|
||||
|
||||
return removed, key_to_unset
|
||||
|
||||
|
||||
def resolve_nested_variables(values):
|
||||
# type: (Dict[Text, Text]) -> Dict[Text, Text]
|
||||
def _replacement(name):
|
||||
# type: (Text) -> Text
|
||||
"""
|
||||
get appropriate value for a variable name.
|
||||
first search in environ, if not found,
|
||||
@@ -243,6 +290,7 @@ def resolve_nested_variables(values):
|
||||
return ret
|
||||
|
||||
def _re_sub_callback(match_object):
|
||||
# type: (Match[Text]) -> Text
|
||||
"""
|
||||
From a match object gets the variable name and returns
|
||||
the correct replacement
|
||||
@@ -258,6 +306,7 @@ def resolve_nested_variables(values):
|
||||
|
||||
|
||||
def _walk_to_root(path):
|
||||
# type: (Text) -> Iterator[Text]
|
||||
"""
|
||||
Yield directories starting from the given directory up to the root
|
||||
"""
|
||||
@@ -276,6 +325,7 @@ def _walk_to_root(path):
|
||||
|
||||
|
||||
def find_dotenv(filename='.env', raise_error_if_not_found=False, usecwd=False):
|
||||
# type: (Text, bool, bool) -> Text
|
||||
"""
|
||||
Search in increasingly higher folders for the given file
|
||||
|
||||
@@ -288,7 +338,14 @@ def find_dotenv(filename='.env', raise_error_if_not_found=False, usecwd=False):
|
||||
# will work for .py files
|
||||
frame = sys._getframe()
|
||||
# find first frame that is outside of this file
|
||||
while frame.f_code.co_filename == __file__:
|
||||
if PY2 and not __file__.endswith('.py'):
|
||||
# in Python2 __file__ extension could be .pyc or .pyo (this doesn't account
|
||||
# for edge case of Python compiled for non-standard extension)
|
||||
current_file = __file__.rsplit('.', 1)[0] + '.py'
|
||||
else:
|
||||
current_file = __file__
|
||||
|
||||
while frame.f_code.co_filename == current_file:
|
||||
frame = frame.f_back
|
||||
frame_filename = frame.f_code.co_filename
|
||||
path = os.path.dirname(os.path.abspath(frame_filename))
|
||||
@@ -304,17 +361,20 @@ def find_dotenv(filename='.env', raise_error_if_not_found=False, usecwd=False):
|
||||
return ''
|
||||
|
||||
|
||||
def load_dotenv(dotenv_path=None, stream=None, verbose=False, override=False):
|
||||
def load_dotenv(dotenv_path=None, stream=None, verbose=False, override=False, **kwargs):
|
||||
# type: (Union[Text, _PathLike, None], Optional[_StringIO], bool, bool, Union[None, Text]) -> bool
|
||||
f = dotenv_path or stream or find_dotenv()
|
||||
return DotEnv(f, verbose=verbose).set_as_environment_variables(override=override)
|
||||
return DotEnv(f, verbose=verbose, **kwargs).set_as_environment_variables(override=override)
|
||||
|
||||
|
||||
def dotenv_values(dotenv_path=None, stream=None, verbose=False):
|
||||
def dotenv_values(dotenv_path=None, stream=None, verbose=False, **kwargs):
|
||||
# type: (Union[Text, _PathLike, None], Optional[_StringIO], bool, Union[None, Text]) -> Dict[Text, Text]
|
||||
f = dotenv_path or stream or find_dotenv()
|
||||
return DotEnv(f, verbose=verbose).dict()
|
||||
return DotEnv(f, verbose=verbose, **kwargs).dict()
|
||||
|
||||
|
||||
def run_command(command, env):
|
||||
# type: (List[str], Dict[str, str]) -> int
|
||||
"""Run command in sub process.
|
||||
|
||||
Runs the command in a sub process with the variables from `env`
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
# Marker file for PEP 561
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
__version__ = "0.10.1"
|
||||
__version__ = "0.10.2"
|
||||
|
||||
Vendored
+4
-3
@@ -345,6 +345,7 @@ the pattern, the actual match represents the shortest successful match for
|
||||
|
||||
**Version history (in brief)**:
|
||||
|
||||
- 1.12.0 Do not assume closing brace when an opening one is found (thanks @mattsep)
|
||||
- 1.11.1 Revert having unicode char in docstring, it breaks Bamboo builds(?!)
|
||||
- 1.11.0 Implement `__contains__` for Result instances.
|
||||
- 1.10.0 Introduce a "letters" matcher, since "w" matches numbers
|
||||
@@ -415,7 +416,7 @@ See the end of the source file for the license of use.
|
||||
'''
|
||||
|
||||
from __future__ import absolute_import
|
||||
__version__ = '1.11.1'
|
||||
__version__ = '1.12.0'
|
||||
|
||||
# yes, I now have two problems
|
||||
import re
|
||||
@@ -431,7 +432,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def with_pattern(pattern, regex_group_count=None):
|
||||
"""Attach a regular expression pattern matcher to a custom type converter
|
||||
r"""Attach a regular expression pattern matcher to a custom type converter
|
||||
function.
|
||||
|
||||
This annotates the type converter with the :attr:`pattern` attribute.
|
||||
@@ -885,7 +886,7 @@ class Parser(object):
|
||||
e.append(r'\{')
|
||||
elif part == '}}':
|
||||
e.append(r'\}')
|
||||
elif part[0] == '{':
|
||||
elif part[0] == '{' and part[-1] == '}':
|
||||
# this will be a braces-delimited field to handle
|
||||
e.append(self._handle_field(part))
|
||||
else:
|
||||
|
||||
Vendored
+1
-1
@@ -75,7 +75,7 @@ if sys.platform != 'win32':
|
||||
from .pty_spawn import spawn, spawnu
|
||||
from .run import run, runu
|
||||
|
||||
__version__ = '4.6.0'
|
||||
__version__ = '4.7.0'
|
||||
__revision__ = ''
|
||||
__all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu',
|
||||
'which', 'split_command_line', '__version__', '__revision__']
|
||||
|
||||
Vendored
+22
-4
@@ -1,5 +1,6 @@
|
||||
import asyncio
|
||||
import errno
|
||||
import signal
|
||||
|
||||
from pexpect import EOF
|
||||
|
||||
@@ -29,6 +30,23 @@ def expect_async(expecter, timeout=None):
|
||||
transport.pause_reading()
|
||||
return expecter.timeout(e)
|
||||
|
||||
@asyncio.coroutine
|
||||
def repl_run_command_async(repl, cmdlines, timeout=-1):
|
||||
res = []
|
||||
repl.child.sendline(cmdlines[0])
|
||||
for line in cmdlines[1:]:
|
||||
yield from repl._expect_prompt(timeout=timeout, async_=True)
|
||||
res.append(repl.child.before)
|
||||
repl.child.sendline(line)
|
||||
|
||||
# Command was fully submitted, now wait for the next prompt
|
||||
prompt_idx = yield from repl._expect_prompt(timeout=timeout, async_=True)
|
||||
if prompt_idx == 1:
|
||||
# We got the continuation prompt - command was incomplete
|
||||
repl.child.kill(signal.SIGINT)
|
||||
yield from repl._expect_prompt(timeout=1, async_=True)
|
||||
raise ValueError("Continuation prompt found - input was incomplete:")
|
||||
return u''.join(res + [repl.child.before])
|
||||
|
||||
class PatternWaiter(asyncio.Protocol):
|
||||
transport = None
|
||||
@@ -41,7 +59,7 @@ class PatternWaiter(asyncio.Protocol):
|
||||
if not self.fut.done():
|
||||
self.fut.set_result(result)
|
||||
self.transport.pause_reading()
|
||||
|
||||
|
||||
def error(self, exc):
|
||||
if not self.fut.done():
|
||||
self.fut.set_exception(exc)
|
||||
@@ -49,7 +67,7 @@ class PatternWaiter(asyncio.Protocol):
|
||||
|
||||
def connection_made(self, transport):
|
||||
self.transport = transport
|
||||
|
||||
|
||||
def data_received(self, data):
|
||||
spawn = self.expecter.spawn
|
||||
s = spawn._decoder.decode(data)
|
||||
@@ -67,7 +85,7 @@ class PatternWaiter(asyncio.Protocol):
|
||||
except Exception as e:
|
||||
self.expecter.errored()
|
||||
self.error(e)
|
||||
|
||||
|
||||
def eof_received(self):
|
||||
# N.B. If this gets called, async will close the pipe (the spawn object)
|
||||
# for us
|
||||
@@ -78,7 +96,7 @@ class PatternWaiter(asyncio.Protocol):
|
||||
self.error(e)
|
||||
else:
|
||||
self.found(index)
|
||||
|
||||
|
||||
def connection_lost(self, exc):
|
||||
if isinstance(exc, OSError) and exc.errno == errno.EIO:
|
||||
# We may get here without eof_received being called, e.g on Linux
|
||||
|
||||
Vendored
+1
-1
@@ -244,7 +244,7 @@ class searcher_re(object):
|
||||
self.eof_index = -1
|
||||
self.timeout_index = -1
|
||||
self._searches = []
|
||||
for n, s in zip(list(range(len(patterns))), patterns):
|
||||
for n, s in enumerate(patterns):
|
||||
if s is EOF:
|
||||
self.eof_index = n
|
||||
continue
|
||||
|
||||
Vendored
+62
-40
@@ -430,61 +430,83 @@ class spawn(SpawnBase):
|
||||
available right away then one character will be returned immediately.
|
||||
It will not wait for 30 seconds for another 99 characters to come in.
|
||||
|
||||
This is a wrapper around os.read(). It uses select.select() to
|
||||
implement the timeout. '''
|
||||
On the other hand, if there are bytes available to read immediately,
|
||||
all those bytes will be read (up to the buffer size). So, if the
|
||||
buffer size is 1 megabyte and there is 1 megabyte of data available
|
||||
to read, the buffer will be filled, regardless of timeout.
|
||||
|
||||
This is a wrapper around os.read(). It uses select.select() or
|
||||
select.poll() to implement the timeout. '''
|
||||
|
||||
if self.closed:
|
||||
raise ValueError('I/O operation on closed file.')
|
||||
|
||||
if self.use_poll:
|
||||
def select(timeout):
|
||||
return poll_ignore_interrupts([self.child_fd], timeout)
|
||||
else:
|
||||
def select(timeout):
|
||||
return select_ignore_interrupts([self.child_fd], [], [], timeout)[0]
|
||||
|
||||
# If there is data available to read right now, read as much as
|
||||
# we can. We do this to increase performance if there are a lot
|
||||
# of bytes to be read. This also avoids calling isalive() too
|
||||
# often. See also:
|
||||
# * https://github.com/pexpect/pexpect/pull/304
|
||||
# * http://trac.sagemath.org/ticket/10295
|
||||
if select(0):
|
||||
try:
|
||||
incoming = super(spawn, self).read_nonblocking(size)
|
||||
except EOF:
|
||||
# Maybe the child is dead: update some attributes in that case
|
||||
self.isalive()
|
||||
raise
|
||||
while len(incoming) < size and select(0):
|
||||
try:
|
||||
incoming += super(spawn, self).read_nonblocking(size - len(incoming))
|
||||
except EOF:
|
||||
# Maybe the child is dead: update some attributes in that case
|
||||
self.isalive()
|
||||
# Don't raise EOF, just return what we read so far.
|
||||
return incoming
|
||||
return incoming
|
||||
|
||||
if timeout == -1:
|
||||
timeout = self.timeout
|
||||
|
||||
# Note that some systems such as Solaris do not give an EOF when
|
||||
# the child dies. In fact, you can still try to read
|
||||
# from the child_fd -- it will block forever or until TIMEOUT.
|
||||
# For this case, I test isalive() before doing any reading.
|
||||
# If isalive() is false, then I pretend that this is the same as EOF.
|
||||
if not self.isalive():
|
||||
# timeout of 0 means "poll"
|
||||
if self.use_poll:
|
||||
r = poll_ignore_interrupts([self.child_fd], timeout)
|
||||
else:
|
||||
r, w, e = select_ignore_interrupts([self.child_fd], [], [], 0)
|
||||
if not r:
|
||||
self.flag_eof = True
|
||||
raise EOF('End Of File (EOF). Braindead platform.')
|
||||
# The process is dead, but there may or may not be data
|
||||
# available to read. Note that some systems such as Solaris
|
||||
# do not give an EOF when the child dies. In fact, you can
|
||||
# still try to read from the child_fd -- it will block
|
||||
# forever or until TIMEOUT. For that reason, it's important
|
||||
# to do this check before calling select() with timeout.
|
||||
if select(0):
|
||||
return super(spawn, self).read_nonblocking(size)
|
||||
self.flag_eof = True
|
||||
raise EOF('End Of File (EOF). Braindead platform.')
|
||||
elif self.__irix_hack:
|
||||
# Irix takes a long time before it realizes a child was terminated.
|
||||
# Make sure that the timeout is at least 2 seconds.
|
||||
# FIXME So does this mean Irix systems are forced to always have
|
||||
# FIXME a 2 second delay when calling read_nonblocking? That sucks.
|
||||
if self.use_poll:
|
||||
r = poll_ignore_interrupts([self.child_fd], timeout)
|
||||
else:
|
||||
r, w, e = select_ignore_interrupts([self.child_fd], [], [], 2)
|
||||
if not r and not self.isalive():
|
||||
self.flag_eof = True
|
||||
raise EOF('End Of File (EOF). Slow platform.')
|
||||
if self.use_poll:
|
||||
r = poll_ignore_interrupts([self.child_fd], timeout)
|
||||
else:
|
||||
r, w, e = select_ignore_interrupts(
|
||||
[self.child_fd], [], [], timeout
|
||||
)
|
||||
if timeout is not None and timeout < 2:
|
||||
timeout = 2
|
||||
|
||||
if not r:
|
||||
if not self.isalive():
|
||||
# Some platforms, such as Irix, will claim that their
|
||||
# processes are alive; timeout on the select; and
|
||||
# then finally admit that they are not alive.
|
||||
self.flag_eof = True
|
||||
raise EOF('End of File (EOF). Very slow platform.')
|
||||
else:
|
||||
raise TIMEOUT('Timeout exceeded.')
|
||||
|
||||
if self.child_fd in r:
|
||||
# Because of the select(0) check above, we know that no data
|
||||
# is available right now. But if a non-zero timeout is given
|
||||
# (possibly timeout=None), we call select() with a timeout.
|
||||
if (timeout != 0) and select(timeout):
|
||||
return super(spawn, self).read_nonblocking(size)
|
||||
|
||||
raise ExceptionPexpect('Reached an unexpected state.') # pragma: no cover
|
||||
if not self.isalive():
|
||||
# Some platforms, such as Irix, will claim that their
|
||||
# processes are alive; timeout on the select; and
|
||||
# then finally admit that they are not alive.
|
||||
self.flag_eof = True
|
||||
raise EOF('End of File (EOF). Very slow platform.')
|
||||
else:
|
||||
raise TIMEOUT('Timeout exceeded.')
|
||||
|
||||
def write(self, s):
|
||||
'''This is similar to send() except that there is no return value.
|
||||
|
||||
Vendored
+58
-20
@@ -109,7 +109,7 @@ class pxssh (spawn):
|
||||
username = raw_input('username: ')
|
||||
password = getpass.getpass('password: ')
|
||||
s.login (hostname, username, password)
|
||||
|
||||
|
||||
`debug_command_string` is only for the test suite to confirm that the string
|
||||
generated for SSH is correct, using this will not allow you to do
|
||||
anything other than get a string back from `pxssh.pxssh.login()`.
|
||||
@@ -118,12 +118,12 @@ class pxssh (spawn):
|
||||
def __init__ (self, timeout=30, maxread=2000, searchwindowsize=None,
|
||||
logfile=None, cwd=None, env=None, ignore_sighup=True, echo=True,
|
||||
options={}, encoding=None, codec_errors='strict',
|
||||
debug_command_string=False):
|
||||
debug_command_string=False, use_poll=False):
|
||||
|
||||
spawn.__init__(self, None, timeout=timeout, maxread=maxread,
|
||||
searchwindowsize=searchwindowsize, logfile=logfile,
|
||||
cwd=cwd, env=env, ignore_sighup=ignore_sighup, echo=echo,
|
||||
encoding=encoding, codec_errors=codec_errors)
|
||||
encoding=encoding, codec_errors=codec_errors, use_poll=use_poll)
|
||||
|
||||
self.name = '<pxssh>'
|
||||
|
||||
@@ -154,7 +154,7 @@ class pxssh (spawn):
|
||||
# Unsetting SSH_ASKPASS on the remote side doesn't disable it! Annoying!
|
||||
#self.SSH_OPTS = "-x -o'RSAAuthentication=no' -o 'PubkeyAuthentication=no'"
|
||||
self.force_password = False
|
||||
|
||||
|
||||
self.debug_command_string = debug_command_string
|
||||
|
||||
# User defined SSH options, eg,
|
||||
@@ -220,7 +220,7 @@ class pxssh (spawn):
|
||||
can take 12 seconds. Low latency connections are more likely to fail
|
||||
with a low sync_multiplier. Best case sync time gets worse with a
|
||||
high sync multiplier (500 ms with default). '''
|
||||
|
||||
|
||||
# All of these timing pace values are magic.
|
||||
# I came up with these based on what seemed reliable for
|
||||
# connecting to a heavily loaded machine I have.
|
||||
@@ -253,20 +253,19 @@ class pxssh (spawn):
|
||||
### TODO: This is getting messy and I'm pretty sure this isn't perfect.
|
||||
### TODO: I need to draw a flow chart for this.
|
||||
### TODO: Unit tests for SSH tunnels, remote SSH command exec, disabling original prompt sync
|
||||
def login (self, server, username, password='', terminal_type='ansi',
|
||||
def login (self, server, username=None, password='', terminal_type='ansi',
|
||||
original_prompt=r"[#$]", login_timeout=10, port=None,
|
||||
auto_prompt_reset=True, ssh_key=None, quiet=True,
|
||||
sync_multiplier=1, check_local_ip=True,
|
||||
password_regex=r'(?i)(?:password:)|(?:passphrase for key)',
|
||||
ssh_tunnels={}, spawn_local_ssh=True,
|
||||
sync_original_prompt=True, ssh_config=None):
|
||||
sync_original_prompt=True, ssh_config=None, cmd='ssh'):
|
||||
'''This logs the user into the given server.
|
||||
|
||||
It uses
|
||||
'original_prompt' to try to find the prompt right after login. When it
|
||||
finds the prompt it immediately tries to reset the prompt to something
|
||||
more easily matched. The default 'original_prompt' is very optimistic
|
||||
and is easily fooled. It's more reliable to try to match the original
|
||||
It uses 'original_prompt' to try to find the prompt right after login.
|
||||
When it finds the prompt it immediately tries to reset the prompt to
|
||||
something more easily matched. The default 'original_prompt' is very
|
||||
optimistic and is easily fooled. It's more reliable to try to match the original
|
||||
prompt as exactly as possible to prevent false matches by server
|
||||
strings such as the "Message Of The Day". On many systems you can
|
||||
disable the MOTD on the remote server by creating a zero-length file
|
||||
@@ -284,27 +283,31 @@ class pxssh (spawn):
|
||||
uses a unique prompt in the :meth:`prompt` method. If the original prompt is
|
||||
not reset then this will disable the :meth:`prompt` method unless you
|
||||
manually set the :attr:`PROMPT` attribute.
|
||||
|
||||
|
||||
Set ``password_regex`` if there is a MOTD message with `password` in it.
|
||||
Changing this is like playing in traffic, don't (p)expect it to match straight
|
||||
away.
|
||||
|
||||
|
||||
If you require to connect to another SSH server from the your original SSH
|
||||
connection set ``spawn_local_ssh`` to `False` and this will use your current
|
||||
session to do so. Setting this option to `False` and not having an active session
|
||||
will trigger an error.
|
||||
|
||||
|
||||
Set ``ssh_key`` to a file path to an SSH private key to use that SSH key
|
||||
for the session authentication.
|
||||
Set ``ssh_key`` to `True` to force passing the current SSH authentication socket
|
||||
to the desired ``hostname``.
|
||||
|
||||
|
||||
Set ``ssh_config`` to a file path string of an SSH client config file to pass that
|
||||
file to the client to handle itself. You may set any options you wish in here, however
|
||||
doing so will require you to post extra information that you may not want to if you
|
||||
run into issues.
|
||||
|
||||
Alter the ``cmd`` to change the ssh client used, or to prepend it with network
|
||||
namespaces. For example ```cmd="ip netns exec vlan2 ssh"``` to execute the ssh in
|
||||
network namespace named ```vlan```.
|
||||
'''
|
||||
|
||||
|
||||
session_regex_array = ["(?i)are you sure you want to continue connecting", original_prompt, password_regex, "(?i)permission denied", "(?i)terminal type", TIMEOUT]
|
||||
session_init_regex_array = []
|
||||
session_init_regex_array.extend(session_regex_array)
|
||||
@@ -320,7 +323,7 @@ class pxssh (spawn):
|
||||
if ssh_config is not None:
|
||||
if spawn_local_ssh and not os.path.isfile(ssh_config):
|
||||
raise ExceptionPxssh('SSH config does not exist or is not a file.')
|
||||
ssh_options = ssh_options + '-F ' + ssh_config
|
||||
ssh_options = ssh_options + ' -F ' + ssh_config
|
||||
if port is not None:
|
||||
ssh_options = ssh_options + ' -p %s'%(str(port))
|
||||
if ssh_key is not None:
|
||||
@@ -331,7 +334,7 @@ class pxssh (spawn):
|
||||
if spawn_local_ssh and not os.path.isfile(ssh_key):
|
||||
raise ExceptionPxssh('private ssh key does not exist or is not a file.')
|
||||
ssh_options = ssh_options + ' -i %s' % (ssh_key)
|
||||
|
||||
|
||||
# SSH tunnels, make sure you know what you're putting into the lists
|
||||
# under each heading. Do not expect these to open 100% of the time,
|
||||
# The port you're requesting might be bound.
|
||||
@@ -354,7 +357,42 @@ class pxssh (spawn):
|
||||
if spawn_local_ssh==False:
|
||||
tunnel = quote(str(tunnel))
|
||||
ssh_options = ssh_options + ' -' + cmd_type + ' ' + str(tunnel)
|
||||
cmd = "ssh %s -l %s %s" % (ssh_options, username, server)
|
||||
|
||||
if username is not None:
|
||||
ssh_options = ssh_options + ' -l ' + username
|
||||
elif ssh_config is None:
|
||||
raise TypeError('login() needs either a username or an ssh_config')
|
||||
else: # make sure ssh_config has an entry for the server with a username
|
||||
with open(ssh_config, 'rt') as f:
|
||||
lines = [l.strip() for l in f.readlines()]
|
||||
|
||||
server_regex = r'^Host\s+%s\s*$' % server
|
||||
user_regex = r'^User\s+\w+\s*$'
|
||||
config_has_server = False
|
||||
server_has_username = False
|
||||
for line in lines:
|
||||
if not config_has_server and re.match(server_regex, line, re.IGNORECASE):
|
||||
config_has_server = True
|
||||
elif config_has_server and 'hostname' in line.lower():
|
||||
pass
|
||||
elif config_has_server and 'host' in line.lower():
|
||||
server_has_username = False # insurance
|
||||
break # we have left the relevant section
|
||||
elif config_has_server and re.match(user_regex, line, re.IGNORECASE):
|
||||
server_has_username = True
|
||||
break
|
||||
|
||||
if lines:
|
||||
del line
|
||||
|
||||
del lines
|
||||
|
||||
if not config_has_server:
|
||||
raise TypeError('login() ssh_config has no Host entry for %s' % server)
|
||||
elif not server_has_username:
|
||||
raise TypeError('login() ssh_config has no user entry for %s' % server)
|
||||
|
||||
cmd += " %s %s" % (ssh_options, server)
|
||||
if self.debug_command_string:
|
||||
return(cmd)
|
||||
|
||||
|
||||
Vendored
+11
-3
@@ -61,11 +61,11 @@ class REPLWrapper(object):
|
||||
self.child.expect(orig_prompt)
|
||||
self.child.sendline(prompt_change)
|
||||
|
||||
def _expect_prompt(self, timeout=-1):
|
||||
def _expect_prompt(self, timeout=-1, async_=False):
|
||||
return self.child.expect_exact([self.prompt, self.continuation_prompt],
|
||||
timeout=timeout)
|
||||
timeout=timeout, async_=async_)
|
||||
|
||||
def run_command(self, command, timeout=-1):
|
||||
def run_command(self, command, timeout=-1, async_=False):
|
||||
"""Send a command to the REPL, wait for and return output.
|
||||
|
||||
:param str command: The command to send. Trailing newlines are not needed.
|
||||
@@ -75,6 +75,10 @@ class REPLWrapper(object):
|
||||
:param int timeout: How long to wait for the next prompt. -1 means the
|
||||
default from the :class:`pexpect.spawn` object (default 30 seconds).
|
||||
None means to wait indefinitely.
|
||||
:param bool async_: On Python 3.4, or Python 3.3 with asyncio
|
||||
installed, passing ``async_=True`` will make this return an
|
||||
:mod:`asyncio` Future, which you can yield from to get the same
|
||||
result that this method would normally give directly.
|
||||
"""
|
||||
# Split up multiline commands and feed them in bit-by-bit
|
||||
cmdlines = command.splitlines()
|
||||
@@ -84,6 +88,10 @@ class REPLWrapper(object):
|
||||
if not cmdlines:
|
||||
raise ValueError("No command was given")
|
||||
|
||||
if async_:
|
||||
from ._async import repl_run_command_async
|
||||
return repl_run_command_async(self, cmdlines, timeout)
|
||||
|
||||
res = []
|
||||
self.child.sendline(cmdlines[0])
|
||||
for line in cmdlines[1:]:
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ from .exceptions import InvalidPythonVersion
|
||||
from .models import SystemPath, WindowsFinder
|
||||
from .pythonfinder import Finder
|
||||
|
||||
__version__ = "1.2.0"
|
||||
__version__ = "1.2.1"
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Steve Dower
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -172,7 +172,7 @@ class RegistryAccessor(object):
|
||||
items = info.items()
|
||||
else:
|
||||
raise TypeError('info must be a dictionary')
|
||||
|
||||
|
||||
self._set_all_values(self._root, self.subkey, items, errors)
|
||||
if len(errors) == 1:
|
||||
raise ValueError(errors[0])
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
-e git+https://github.com/zooba/pep514tools.git@320e48745660b696e2dcaee888fc2e516b435e48#egg=pep514tools
|
||||
git+https://github.com/zooba/pep514tools.git@master#egg=pep514tools
|
||||
|
||||
+5
@@ -35,6 +35,11 @@ else:
|
||||
|
||||
IGNORE_UNSUPPORTED = bool(os.environ.get("PYTHONFINDER_IGNORE_UNSUPPORTED", False))
|
||||
MYPY_RUNNING = os.environ.get("MYPY_RUNNING", is_type_checking())
|
||||
SUBPROCESS_TIMEOUT = os.environ.get("PYTHONFINDER_SUBPROCESS_TIMEOUT", 5)
|
||||
"""The default subprocess timeout for determining python versions
|
||||
|
||||
Set to **5** by default.
|
||||
"""
|
||||
|
||||
|
||||
def get_shim_paths():
|
||||
|
||||
+5
-4
@@ -89,6 +89,9 @@ class BasePath(object):
|
||||
self._children = {}
|
||||
for key in list(self._pythons.keys()):
|
||||
del self._pythons[key]
|
||||
self._pythons = None
|
||||
self._py_version = None
|
||||
self.path = None
|
||||
|
||||
@property
|
||||
def children(self):
|
||||
@@ -315,10 +318,8 @@ class BaseFinder(object):
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls, *args, **kwargs # type: Type[BaseFinderType] # type: Any # type: Any
|
||||
):
|
||||
# type: (...) -> BaseFinderType
|
||||
def create(cls, *args, **kwargs):
|
||||
# type: (Any, Any) -> BaseFinderType
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
|
||||
+183
-83
@@ -12,6 +12,7 @@ import attr
|
||||
import six
|
||||
from cached_property import cached_property
|
||||
from vistir.compat import Path, fs_str
|
||||
from vistir.misc import dedup
|
||||
|
||||
from .mixins import BaseFinder, BasePath
|
||||
from .python import PythonVersion
|
||||
@@ -22,6 +23,7 @@ from ..environment import (
|
||||
PYENV_INSTALLED,
|
||||
PYENV_ROOT,
|
||||
SHIM_PATHS,
|
||||
get_shim_paths,
|
||||
)
|
||||
from ..exceptions import InvalidPythonVersion
|
||||
from ..utils import (
|
||||
@@ -37,6 +39,7 @@ from ..utils import (
|
||||
parse_asdf_version_order,
|
||||
parse_pyenv_version_order,
|
||||
path_is_known_executable,
|
||||
split_version_and_name,
|
||||
unnest,
|
||||
)
|
||||
|
||||
@@ -76,10 +79,9 @@ class SystemPath(object):
|
||||
path_order = attr.ib(default=attr.Factory(list)) # type: List[str]
|
||||
python_version_dict = attr.ib() # type: DefaultDict[Tuple, List[PythonVersion]]
|
||||
only_python = attr.ib(default=False, type=bool)
|
||||
pyenv_finder = attr.ib(
|
||||
default=None, validator=optional_instance_of("PythonFinder")
|
||||
) # type: Optional[PythonFinder]
|
||||
pyenv_finder = attr.ib(default=None) # type: Optional[PythonFinder]
|
||||
asdf_finder = attr.ib(default=None) # type: Optional[PythonFinder]
|
||||
windows_finder = attr.ib(default=None) # type: Optional[WindowsFinder]
|
||||
system = attr.ib(default=False, type=bool)
|
||||
_version_dict = attr.ib(
|
||||
default=attr.Factory(defaultdict)
|
||||
@@ -91,31 +93,63 @@ class SystemPath(object):
|
||||
) # type: Dict[str, Union[WindowsFinder, PythonFinder]]
|
||||
|
||||
def _register_finder(self, finder_name, finder):
|
||||
# type: (str, Union[WindowsFinder, PythonFinder]) -> None
|
||||
# type: (str, Union[WindowsFinder, PythonFinder]) -> "SystemPath"
|
||||
if finder_name not in self.__finders:
|
||||
self.__finders[finder_name] = finder
|
||||
return self
|
||||
|
||||
def clear_caches(self):
|
||||
for key in ["executables", "python_executables", "version_dict", "path_entries"]:
|
||||
if key in self.__dict__:
|
||||
del self.__dict__[key]
|
||||
self._executables = []
|
||||
self._python_executables = {}
|
||||
self.python_version_dict = defaultdict(list)
|
||||
self._version_dict = defaultdict(list)
|
||||
for finder in list(self.__finders.keys()):
|
||||
del self.__finders[finder]
|
||||
self.__finders = {}
|
||||
return attr.evolve(
|
||||
self,
|
||||
executables=[],
|
||||
python_executables={},
|
||||
python_version_dict=defaultdict(list),
|
||||
version_dict=defaultdict(list),
|
||||
pyenv_finder=None,
|
||||
windows_finder=None,
|
||||
asdf_finder=None,
|
||||
path_order=[],
|
||||
paths=defaultdict(PathEntry),
|
||||
)
|
||||
|
||||
def __del__(self):
|
||||
self.clear_caches()
|
||||
for key in ["executables", "python_executables", "version_dict", "path_entries"]:
|
||||
try:
|
||||
del self.__dict__[key]
|
||||
except KeyError:
|
||||
pass
|
||||
for finder in list(self.__finders.keys()):
|
||||
del self.__finders[finder]
|
||||
self.__finders = {}
|
||||
self._python_executables = {}
|
||||
self._executables = []
|
||||
self.python_version_dict = defaultdict(list)
|
||||
self.version_dict = defaultdict(list)
|
||||
self.path_order = []
|
||||
self.pyenv_finder = None
|
||||
self.asdf_finder = None
|
||||
self.paths = defaultdict(PathEntry)
|
||||
self.__finders = {}
|
||||
|
||||
@property
|
||||
def finders(self):
|
||||
# type: () -> List[str]
|
||||
return [k for k in self.__finders.keys()]
|
||||
|
||||
@staticmethod
|
||||
def check_for_pyenv():
|
||||
return PYENV_INSTALLED or os.path.exists(normalize_path(PYENV_ROOT))
|
||||
|
||||
@staticmethod
|
||||
def check_for_asdf():
|
||||
return ASDF_INSTALLED or os.path.exists(normalize_path(ASDF_DATA_DIR))
|
||||
|
||||
@python_version_dict.default
|
||||
def create_python_version_dict(self):
|
||||
# type: () -> DefaultDict[Tuple, List[PythonVersion]]
|
||||
@@ -168,35 +202,69 @@ class SystemPath(object):
|
||||
self._version_dict[version].append(entry)
|
||||
return self._version_dict
|
||||
|
||||
def __attrs_post_init__(self):
|
||||
# type: () -> None
|
||||
#: slice in pyenv
|
||||
def _run_setup(self):
|
||||
# type: () -> "SystemPath"
|
||||
if not self.__class__ == SystemPath:
|
||||
return
|
||||
if os.name == "nt":
|
||||
self._setup_windows()
|
||||
if PYENV_INSTALLED:
|
||||
self._setup_pyenv()
|
||||
if ASDF_INSTALLED:
|
||||
self._setup_asdf()
|
||||
return self
|
||||
new_instance = self
|
||||
path_order = new_instance.path_order[:]
|
||||
path_entries = self.paths.copy()
|
||||
if self.global_search and "PATH" in os.environ:
|
||||
path_order = path_order + os.environ["PATH"].split(os.pathsep)
|
||||
path_order = list(dedup(path_order))
|
||||
path_instances = [
|
||||
ensure_path(p.strip('"'))
|
||||
for p in path_order
|
||||
if not any(
|
||||
is_in_path(normalize_path(str(p)), normalize_path(shim))
|
||||
for shim in SHIM_PATHS
|
||||
)
|
||||
]
|
||||
path_entries.update(
|
||||
{
|
||||
p.as_posix(): PathEntry.create(
|
||||
path=p.absolute(), is_root=True, only_python=self.only_python
|
||||
)
|
||||
for p in path_instances
|
||||
}
|
||||
)
|
||||
new_instance = attr.evolve(
|
||||
new_instance,
|
||||
path_order=[p.as_posix() for p in path_instances],
|
||||
paths=path_entries,
|
||||
)
|
||||
if os.name == "nt" and "windows" not in self.finders:
|
||||
new_instance = new_instance._setup_windows()
|
||||
#: slice in pyenv
|
||||
if self.check_for_pyenv() and "pyenv" not in self.finders:
|
||||
new_instance = new_instance._setup_pyenv()
|
||||
#: slice in asdf
|
||||
if self.check_for_asdf() and "asdf" not in self.finders:
|
||||
new_instance = new_instance._setup_asdf()
|
||||
venv = os.environ.get("VIRTUAL_ENV")
|
||||
if os.name == "nt":
|
||||
bin_dir = "Scripts"
|
||||
else:
|
||||
bin_dir = "bin"
|
||||
if venv and (self.system or self.global_search):
|
||||
if venv and (new_instance.system or new_instance.global_search):
|
||||
p = ensure_path(venv)
|
||||
self.path_order = [(p / bin_dir).as_posix()] + self.path_order
|
||||
self.paths[p] = self.get_path(p.joinpath(bin_dir))
|
||||
if self.system:
|
||||
path_order = [(p / bin_dir).as_posix()] + new_instance.path_order
|
||||
new_instance = attr.evolve(new_instance, path_order=path_order)
|
||||
paths = new_instance.paths.copy()
|
||||
paths[p] = new_instance.get_path(p.joinpath(bin_dir))
|
||||
new_instance = attr.evolve(new_instance, paths=paths)
|
||||
if new_instance.system:
|
||||
syspath = Path(sys.executable)
|
||||
syspath_bin = syspath.parent
|
||||
if syspath_bin.name != bin_dir and syspath_bin.joinpath(bin_dir).exists():
|
||||
syspath_bin = syspath_bin / bin_dir
|
||||
self.path_order = [syspath_bin.as_posix()] + self.path_order
|
||||
self.paths[syspath_bin] = PathEntry.create(
|
||||
path_order = [syspath_bin.as_posix()] + new_instance.path_order
|
||||
paths = new_instance.paths.copy()
|
||||
paths[syspath_bin] = PathEntry.create(
|
||||
path=syspath_bin, is_root=True, only_python=False
|
||||
)
|
||||
new_instance = attr.evolve(new_instance, path_order=path_order, paths=paths)
|
||||
return new_instance
|
||||
|
||||
def _get_last_instance(self, path):
|
||||
# type: (str) -> int
|
||||
@@ -210,7 +278,7 @@ class SystemPath(object):
|
||||
return path_index
|
||||
|
||||
def _slice_in_paths(self, start_idx, paths):
|
||||
# type: (int, List[Path]) -> None
|
||||
# type: (int, List[Path]) -> "SystemPath"
|
||||
before_path = [] # type: List[str]
|
||||
after_path = [] # type: List[str]
|
||||
if start_idx == 0:
|
||||
@@ -220,29 +288,35 @@ class SystemPath(object):
|
||||
else:
|
||||
before_path = self.path_order[: start_idx + 1]
|
||||
after_path = self.path_order[start_idx + 2 :]
|
||||
self.path_order = before_path + [p.as_posix() for p in paths] + after_path
|
||||
path_order = before_path + [p.as_posix() for p in paths] + after_path
|
||||
if path_order == self.path_order:
|
||||
return self
|
||||
return attr.evolve(self, path_order=path_order)
|
||||
|
||||
def _remove_path(self, path):
|
||||
# type: (str) -> None
|
||||
# type: (str) -> "SystemPath"
|
||||
path_copy = [p for p in reversed(self.path_order[:])]
|
||||
new_order = []
|
||||
target = normalize_path(path)
|
||||
path_map = {normalize_path(pth): pth for pth in self.paths.keys()}
|
||||
new_paths = self.paths.copy()
|
||||
if target in path_map:
|
||||
del self.paths[path_map[target]]
|
||||
del new_paths[path_map[target]]
|
||||
for current_path in path_copy:
|
||||
normalized = normalize_path(current_path)
|
||||
if normalized != target:
|
||||
new_order.append(normalized)
|
||||
new_order = [p for p in reversed(new_order)]
|
||||
self.path_order = new_order
|
||||
return attr.evolve(self, path_order=new_order, paths=new_paths)
|
||||
|
||||
def _setup_asdf(self):
|
||||
# type: () -> None
|
||||
# type: () -> "SystemPath"
|
||||
if "asdf" in self.finders and self.asdf_finder is not None:
|
||||
return self
|
||||
from .python import PythonFinder
|
||||
|
||||
os_path = os.environ["PATH"].split(os.pathsep)
|
||||
self.asdf_finder = PythonFinder.create(
|
||||
asdf_finder = PythonFinder.create(
|
||||
root=ASDF_DATA_DIR,
|
||||
ignore_unsupported=True,
|
||||
sort_function=parse_asdf_version_order,
|
||||
@@ -252,20 +326,24 @@ class SystemPath(object):
|
||||
try:
|
||||
asdf_index = self._get_last_instance(ASDF_DATA_DIR)
|
||||
except ValueError:
|
||||
pyenv_index = 0 if is_in_path(next(iter(os_path), ""), PYENV_ROOT) else -1
|
||||
asdf_index = 0 if is_in_path(next(iter(os_path), ""), ASDF_DATA_DIR) else -1
|
||||
if asdf_index is None:
|
||||
# we are in a virtualenv without global pyenv on the path, so we should
|
||||
# not write pyenv to the path here
|
||||
return
|
||||
root_paths = [p for p in self.asdf_finder.roots]
|
||||
self._slice_in_paths(asdf_index, [self.asdf_finder.root])
|
||||
self.paths[self.asdf_finder.root] = self.asdf_finder
|
||||
self.paths.update(self.asdf_finder.roots)
|
||||
self._remove_path(normalize_path(os.path.join(ASDF_DATA_DIR, "shims")))
|
||||
self._register_finder("asdf", self.asdf_finder)
|
||||
return self
|
||||
root_paths = [p for p in asdf_finder.roots]
|
||||
new_instance = self._slice_in_paths(asdf_index, [asdf_finder.root])
|
||||
paths = self.paths.copy()
|
||||
paths[asdf_finder.root] = asdf_finder
|
||||
paths.update(asdf_finder.roots)
|
||||
return (
|
||||
attr.evolve(new_instance, paths=paths, asdf_finder=asdf_finder)
|
||||
._remove_path(normalize_path(os.path.join(ASDF_DATA_DIR, "shims")))
|
||||
._register_finder("asdf", asdf_finder)
|
||||
)
|
||||
|
||||
def reload_finder(self, finder_name):
|
||||
# type: (str) -> None
|
||||
# type: (str) -> "SystemPath"
|
||||
if finder_name is None:
|
||||
raise TypeError("Must pass a string as the name of the target finder")
|
||||
finder_attr = "{0}_finder".format(finder_name)
|
||||
@@ -286,19 +364,21 @@ class SystemPath(object):
|
||||
finder_name == "asdf" and not ASDF_INSTALLED
|
||||
):
|
||||
# Don't allow loading of finders that aren't explicitly 'installed' as it were
|
||||
pass
|
||||
return self
|
||||
setattr(self, finder_attr, None)
|
||||
if finder_name in self.__finders:
|
||||
del self.__finders[finder_name]
|
||||
setup_fn()
|
||||
return setup_fn()
|
||||
|
||||
def _setup_pyenv(self):
|
||||
# type: () -> None
|
||||
# type: () -> "SystemPath"
|
||||
if "pyenv" in self.finders and self.pyenv_finder is not None:
|
||||
return self
|
||||
from .python import PythonFinder
|
||||
|
||||
os_path = os.environ["PATH"].split(os.pathsep)
|
||||
|
||||
self.pyenv_finder = PythonFinder.create(
|
||||
pyenv_finder = PythonFinder.create(
|
||||
root=PYENV_ROOT,
|
||||
sort_function=parse_pyenv_version_order,
|
||||
version_glob_path="versions/*",
|
||||
@@ -312,25 +392,37 @@ class SystemPath(object):
|
||||
if pyenv_index is None:
|
||||
# we are in a virtualenv without global pyenv on the path, so we should
|
||||
# not write pyenv to the path here
|
||||
return
|
||||
return self
|
||||
|
||||
root_paths = [p for p in self.pyenv_finder.roots]
|
||||
self._slice_in_paths(pyenv_index, [self.pyenv_finder.root])
|
||||
self.paths[self.pyenv_finder.root] = self.pyenv_finder
|
||||
self.paths.update(self.pyenv_finder.roots)
|
||||
self._remove_path(os.path.join(PYENV_ROOT, "shims"))
|
||||
self._register_finder("pyenv", self.pyenv_finder)
|
||||
root_paths = [p for p in pyenv_finder.roots]
|
||||
new_instance = self._slice_in_paths(pyenv_index, [pyenv_finder.root])
|
||||
paths = new_instance.paths.copy()
|
||||
paths[pyenv_finder.root] = pyenv_finder
|
||||
paths.update(pyenv_finder.roots)
|
||||
return (
|
||||
attr.evolve(new_instance, paths=paths, pyenv_finder=pyenv_finder)
|
||||
._remove_path(os.path.join(PYENV_ROOT, "shims"))
|
||||
._register_finder("pyenv", pyenv_finder)
|
||||
)
|
||||
|
||||
def _setup_windows(self):
|
||||
# type: () -> None
|
||||
# type: () -> "SystemPath"
|
||||
if "windows" in self.finders and self.windows_finder is not None:
|
||||
return self
|
||||
from .windows import WindowsFinder
|
||||
|
||||
self.windows_finder = WindowsFinder.create()
|
||||
root_paths = (p for p in self.windows_finder.paths if p.is_root)
|
||||
windows_finder = WindowsFinder.create()
|
||||
root_paths = (p for p in windows_finder.paths if p.is_root)
|
||||
path_addition = [p.path.as_posix() for p in root_paths]
|
||||
self.path_order = self.path_order[:] + path_addition
|
||||
self.paths.update({p.path: p for p in root_paths})
|
||||
self._register_finder("windows", self.windows_finder)
|
||||
new_path_order = self.path_order[:] + path_addition
|
||||
new_paths = self.paths.copy()
|
||||
new_paths.update({p.path: p for p in root_paths})
|
||||
return attr.evolve(
|
||||
self,
|
||||
windows_finder=windows_finder,
|
||||
path_order=new_path_order,
|
||||
paths=new_paths,
|
||||
)._register_finder("windows", windows_finder)
|
||||
|
||||
def get_path(self, path):
|
||||
# type: (Union[str, Path]) -> PathType
|
||||
@@ -350,7 +442,7 @@ class SystemPath(object):
|
||||
return _path
|
||||
|
||||
def _get_paths(self):
|
||||
# type: () -> Iterator
|
||||
# type: () -> Generator[Union[PathType, WindowsFinder], None, None]
|
||||
for path in self.path_order:
|
||||
try:
|
||||
entry = self.get_path(path)
|
||||
@@ -361,7 +453,7 @@ class SystemPath(object):
|
||||
|
||||
@cached_property
|
||||
def path_entries(self):
|
||||
# type: () -> List[Union[PathEntry, FinderType]]
|
||||
# type: () -> List[Union[PathType, WindowsFinder]]
|
||||
paths = list(self._get_paths())
|
||||
return paths
|
||||
|
||||
@@ -469,6 +561,7 @@ class SystemPath(object):
|
||||
dev=None, # type: Optional[bool]
|
||||
arch=None, # type: Optional[str]
|
||||
name=None, # type: Optional[str]
|
||||
sort_by_path=False, # type: bool
|
||||
):
|
||||
# type: (...) -> PathEntry
|
||||
"""Search for a specific python version on the path.
|
||||
@@ -481,29 +574,12 @@ class SystemPath(object):
|
||||
:param bool dev: Search for devreleases (default None) - prioritize releases if None
|
||||
:param str arch: Architecture to include, e.g. '64bit', defaults to None
|
||||
:param str name: The name of a python version, e.g. ``anaconda3-5.3.0``
|
||||
:param bool sort_by_path: Whether to sort by path -- default sort is by version(default: False)
|
||||
:return: A :class:`~pythonfinder.models.PathEntry` instance matching the version requested.
|
||||
:rtype: :class:`~pythonfinder.models.PathEntry`
|
||||
"""
|
||||
|
||||
if isinstance(major, six.string_types) and not minor and not patch:
|
||||
# Only proceed if this is in the format "x.y.z" or similar
|
||||
if major.isdigit() or (major.count(".") > 0 and major[0].isdigit()):
|
||||
version = major.split(".", 2)
|
||||
if isinstance(version, (tuple, list)):
|
||||
if len(version) > 3:
|
||||
major, minor, patch, rest = version
|
||||
elif len(version) == 3:
|
||||
major, minor, patch = version
|
||||
elif len(version) == 2:
|
||||
major, minor = version
|
||||
else:
|
||||
major = major[0]
|
||||
else:
|
||||
major = major
|
||||
name = None
|
||||
else:
|
||||
name = "{0!s}".format(major)
|
||||
major = None
|
||||
major, minor, patch, name = split_version_and_name(major, minor, patch, name)
|
||||
sub_finder = operator.methodcaller(
|
||||
"find_python_version", major, minor, patch, pre, dev, arch, name
|
||||
)
|
||||
@@ -521,6 +597,18 @@ class SystemPath(object):
|
||||
windows_finder_version = sub_finder(self.windows_finder)
|
||||
if windows_finder_version:
|
||||
return windows_finder_version
|
||||
if sort_by_path:
|
||||
paths = [self.get_path(k) for k in self.path_order]
|
||||
for path in paths:
|
||||
found_version = sub_finder(path)
|
||||
if found_version:
|
||||
return found_version
|
||||
if alternate_sub_finder:
|
||||
for path in paths:
|
||||
found_version = alternate_sub_finder(path)
|
||||
if found_version:
|
||||
return found_version
|
||||
|
||||
ver = next(iter(self.get_pythons(sub_finder)), None)
|
||||
if not ver and alternate_sub_finder is not None:
|
||||
ver = next(iter(self.get_pythons(alternate_sub_finder)), None)
|
||||
@@ -561,7 +649,19 @@ class SystemPath(object):
|
||||
if global_search:
|
||||
if "PATH" in os.environ:
|
||||
paths = os.environ["PATH"].split(os.pathsep)
|
||||
path_order = []
|
||||
if path:
|
||||
path_order = [path]
|
||||
path_instance = ensure_path(path)
|
||||
path_entries.update(
|
||||
{
|
||||
path_instance.as_posix(): PathEntry.create(
|
||||
path=path_instance.absolute(),
|
||||
is_root=True,
|
||||
only_python=only_python,
|
||||
)
|
||||
}
|
||||
)
|
||||
paths = [path] + paths
|
||||
paths = [p for p in paths if not any(is_in_path(p, shim) for shim in SHIM_PATHS)]
|
||||
_path_objects = [ensure_path(p.strip('"')) for p in paths]
|
||||
@@ -574,14 +674,16 @@ class SystemPath(object):
|
||||
for p in _path_objects
|
||||
}
|
||||
)
|
||||
return cls(
|
||||
instance = cls(
|
||||
paths=path_entries,
|
||||
path_order=paths,
|
||||
path_order=path_order,
|
||||
only_python=only_python,
|
||||
system=system,
|
||||
global_search=global_search,
|
||||
ignore_unsupported=ignore_unsupported,
|
||||
)
|
||||
instance = instance._run_setup()
|
||||
return instance
|
||||
|
||||
|
||||
@attr.s(slots=True)
|
||||
@@ -603,8 +705,6 @@ class PathEntry(BasePath):
|
||||
|
||||
def _gen_children(self):
|
||||
# type: () -> Iterator
|
||||
from ..environment import get_shim_paths
|
||||
|
||||
shim_paths = get_shim_paths()
|
||||
pass_name = self.name != self.path.name
|
||||
pass_args = {"is_root": False, "only_python": self.only_python}
|
||||
|
||||
+4
-4
@@ -229,10 +229,8 @@ class PythonFinder(BaseFinder, BasePath):
|
||||
return self.pythons
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls, root, sort_function, version_glob_path=None, ignore_unsupported=True
|
||||
): # type: ignore
|
||||
# type: (Type[PythonFinder], str, Callable, Optional[str], bool) -> PythonFinder
|
||||
def create(cls, root, sort_function, version_glob_path=None, ignore_unsupported=True):
|
||||
# type: (str, Callable, Optional[str], bool) -> PythonFinder
|
||||
root = ensure_path(root)
|
||||
if not version_glob_path:
|
||||
version_glob_path = "versions/*"
|
||||
@@ -593,6 +591,8 @@ class PythonVersion(object):
|
||||
raise TypeError("Must pass a valid path to parse.")
|
||||
if not isinstance(path, six.string_types):
|
||||
path = path.as_posix()
|
||||
# if not looks_like_python(path):
|
||||
# raise ValueError("Path %r does not look like a valid python path" % path)
|
||||
try:
|
||||
result_version = get_python_version(path)
|
||||
except Exception:
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Steve Dower
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+129
-64
@@ -1,6 +1,7 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
import importlib
|
||||
import operator
|
||||
import os
|
||||
|
||||
@@ -10,15 +11,16 @@ from vistir.compat import lru_cache
|
||||
|
||||
from . import environment
|
||||
from .exceptions import InvalidPythonVersion
|
||||
from .models import path as pyfinder_path
|
||||
from .utils import Iterable, filter_pythons, version_re
|
||||
|
||||
if environment.MYPY_RUNNING:
|
||||
from typing import Optional, Dict, Any, Union, List, Iterator
|
||||
from typing import Optional, Dict, Any, Union, List, Iterator, Text
|
||||
from .models.path import Path, PathEntry
|
||||
from .models.windows import WindowsFinder
|
||||
from .models.path import SystemPath
|
||||
|
||||
STRING_TYPE = Union[str, Text, bytes]
|
||||
|
||||
|
||||
class Finder(object):
|
||||
|
||||
@@ -33,9 +35,14 @@ class Finder(object):
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, path=None, system=False, global_search=True, ignore_unsupported=True
|
||||
self,
|
||||
path=None,
|
||||
system=False,
|
||||
global_search=True,
|
||||
ignore_unsupported=True,
|
||||
sort_by_path=False,
|
||||
):
|
||||
# type: (Optional[str], bool, bool, bool) -> None
|
||||
# type: (Optional[str], bool, bool, bool, bool) -> None
|
||||
"""Create a new :class:`~pythonfinder.pythonfinder.Finder` instance.
|
||||
|
||||
:param path: A bin-directory search location, defaults to None
|
||||
@@ -46,12 +53,14 @@ class Finder(object):
|
||||
:param global_search: bool, optional
|
||||
:param ignore_unsupported: Whether to ignore unsupported python versions, if False, an error is raised, defaults to True
|
||||
:param ignore_unsupported: bool, optional
|
||||
:param bool sort_by_path: Whether to always sort by path
|
||||
:returns: a :class:`~pythonfinder.pythonfinder.Finder` object.
|
||||
"""
|
||||
|
||||
self.path_prepend = path # type: Optional[str]
|
||||
self.global_search = global_search # type: bool
|
||||
self.system = system # type: bool
|
||||
self.sort_by_path = sort_by_path # type: bool
|
||||
self.ignore_unsupported = ignore_unsupported # type: bool
|
||||
self._system_path = None # type: Optional[SystemPath]
|
||||
self._windows_finder = None # type: Optional[WindowsFinder]
|
||||
@@ -68,6 +77,7 @@ class Finder(object):
|
||||
|
||||
def create_system_path(self):
|
||||
# type: () -> SystemPath
|
||||
pyfinder_path = importlib.import_module("pythonfinder.models.path")
|
||||
return pyfinder_path.SystemPath.create(
|
||||
path=self.path_prepend,
|
||||
system=self.system,
|
||||
@@ -84,19 +94,23 @@ class Finder(object):
|
||||
"""
|
||||
|
||||
if self._system_path is not None:
|
||||
self._system_path.clear_caches()
|
||||
self._system_path = None
|
||||
self._system_path = self._system_path.clear_caches()
|
||||
self._system_path = None
|
||||
pyfinder_path = importlib.import_module("pythonfinder.models.path")
|
||||
six.moves.reload_module(pyfinder_path)
|
||||
self._system_path = self.create_system_path()
|
||||
|
||||
def rehash(self):
|
||||
# type: () -> None
|
||||
# type: () -> "Finder"
|
||||
if not self._system_path:
|
||||
self._system_path = self.create_system_path()
|
||||
self.find_all_python_versions.cache_clear()
|
||||
self.find_python_version.cache_clear()
|
||||
self.reload_system_path()
|
||||
if self._windows_finder is not None:
|
||||
self._windows_finder = None
|
||||
filter_pythons.cache_clear()
|
||||
self.reload_system_path()
|
||||
return self
|
||||
|
||||
@property
|
||||
def system_path(self):
|
||||
@@ -118,11 +132,92 @@ class Finder(object):
|
||||
# type: (str) -> Optional[PathEntry]
|
||||
return self.system_path.which(exe)
|
||||
|
||||
@classmethod
|
||||
def parse_major(cls, major, minor=None, patch=None, pre=None, dev=None, arch=None):
|
||||
# type: (Optional[str], Optional[int], Optional[int], Optional[bool], Optional[bool], Optional[str]) -> Dict[str, Union[int, str, bool, None]]
|
||||
from .models import PythonVersion
|
||||
|
||||
major_is_str = major and isinstance(major, six.string_types)
|
||||
is_num = (
|
||||
major
|
||||
and major_is_str
|
||||
and all(part.isdigit() for part in major.split(".")[:2])
|
||||
)
|
||||
major_has_arch = (
|
||||
arch is None
|
||||
and major
|
||||
and major_is_str
|
||||
and "-" in major
|
||||
and major[0].isdigit()
|
||||
)
|
||||
name = None
|
||||
if major and major_has_arch:
|
||||
orig_string = "{0!s}".format(major)
|
||||
major, _, arch = major.rpartition("-")
|
||||
if arch:
|
||||
arch = arch.lower().lstrip("x").replace("bit", "")
|
||||
if not (arch.isdigit() and (int(arch) & int(arch) - 1) == 0):
|
||||
major = orig_string
|
||||
arch = None
|
||||
else:
|
||||
arch = "{0}bit".format(arch)
|
||||
try:
|
||||
version_dict = PythonVersion.parse(major)
|
||||
except (ValueError, InvalidPythonVersion):
|
||||
if name is None:
|
||||
name = "{0!s}".format(major)
|
||||
major = None
|
||||
version_dict = {}
|
||||
elif major and major[0].isalpha():
|
||||
return {"major": None, "name": major, "arch": arch}
|
||||
elif major and is_num:
|
||||
match = version_re.match(major)
|
||||
version_dict = match.groupdict() if match else {} # type: ignore
|
||||
version_dict.update(
|
||||
{
|
||||
"is_prerelease": bool(version_dict.get("prerel", False)),
|
||||
"is_devrelease": bool(version_dict.get("dev", False)),
|
||||
}
|
||||
)
|
||||
else:
|
||||
version_dict = {
|
||||
"major": major,
|
||||
"minor": minor,
|
||||
"patch": patch,
|
||||
"pre": pre,
|
||||
"dev": dev,
|
||||
"arch": arch,
|
||||
}
|
||||
if not version_dict.get("arch") and arch:
|
||||
version_dict["arch"] = arch
|
||||
version_dict["minor"] = (
|
||||
int(version_dict["minor"]) if version_dict.get("minor") is not None else minor
|
||||
)
|
||||
version_dict["patch"] = (
|
||||
int(version_dict["patch"]) if version_dict.get("patch") is not None else patch
|
||||
)
|
||||
version_dict["major"] = (
|
||||
int(version_dict["major"]) if version_dict.get("major") is not None else major
|
||||
)
|
||||
if not (version_dict["major"] or version_dict.get("name")):
|
||||
version_dict["major"] = major
|
||||
if name:
|
||||
version_dict["name"] = name
|
||||
return version_dict
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def find_python_version(
|
||||
self, major=None, minor=None, patch=None, pre=None, dev=None, arch=None, name=None
|
||||
self,
|
||||
major=None, # type: Optional[Union[str, int]]
|
||||
minor=None, # type: Optional[int]
|
||||
patch=None, # type: Optional[int]
|
||||
pre=None, # type: Optional[bool]
|
||||
dev=None, # type: Optional[bool]
|
||||
arch=None, # type: Optional[str]
|
||||
name=None, # type: Optional[str]
|
||||
sort_by_path=False, # type: bool
|
||||
):
|
||||
# type: (Optional[Union[str, int]], Optional[int], Optional[int], Optional[bool], Optional[bool], Optional[str], Optional[str]) -> PathEntry
|
||||
# type: (...) -> Optional[PathEntry]
|
||||
"""
|
||||
Find the python version which corresponds most closely to the version requested.
|
||||
|
||||
@@ -133,18 +228,19 @@ class Finder(object):
|
||||
:param Optional[bool] dev: If provided, whether to search dev-releases.
|
||||
:param Optional[str] arch: If provided, which architecture to search.
|
||||
:param Optional[str] name: *Name* of the target python, e.g. ``anaconda3-5.3.0``
|
||||
:param bool sort_by_path: Whether to sort by path -- default sort is by version(default: False)
|
||||
:return: A new *PathEntry* pointer at a matching python version, if one can be located.
|
||||
:rtype: :class:`pythonfinder.models.path.PathEntry`
|
||||
"""
|
||||
|
||||
from .models import PythonVersion
|
||||
|
||||
minor = int(minor) if minor is not None else minor
|
||||
patch = int(patch) if patch is not None else patch
|
||||
|
||||
version_dict = {
|
||||
"minor": minor,
|
||||
"patch": patch,
|
||||
"name": name,
|
||||
"arch": arch,
|
||||
} # type: Dict[str, Union[str, int, Any]]
|
||||
|
||||
if (
|
||||
@@ -154,60 +250,22 @@ class Finder(object):
|
||||
and dev is None
|
||||
and patch is None
|
||||
):
|
||||
if arch is None and "-" in major and major[0].isdigit():
|
||||
orig_string = "{0!s}".format(major)
|
||||
major, _, arch = major.rpartition("-")
|
||||
if arch.startswith("x"):
|
||||
arch = arch.lstrip("x")
|
||||
if arch.lower().endswith("bit"):
|
||||
arch = arch.lower().replace("bit", "")
|
||||
if not (arch.isdigit() and (int(arch) & int(arch) - 1) == 0):
|
||||
major = orig_string
|
||||
arch = None
|
||||
else:
|
||||
arch = "{0}bit".format(arch)
|
||||
try:
|
||||
version_dict = PythonVersion.parse(major)
|
||||
except (ValueError, InvalidPythonVersion):
|
||||
if name is None:
|
||||
name = "{0!s}".format(major)
|
||||
major = None
|
||||
version_dict = {}
|
||||
elif major[0].isalpha():
|
||||
name = "%s" % major
|
||||
major = None
|
||||
else:
|
||||
if "." in major and all(part.isdigit() for part in major.split(".")[:2]):
|
||||
match = version_re.match(major)
|
||||
version_dict = match.groupdict()
|
||||
version_dict["is_prerelease"] = bool(
|
||||
version_dict.get("prerel", False)
|
||||
)
|
||||
version_dict["is_devrelease"] = bool(version_dict.get("dev", False))
|
||||
else:
|
||||
version_dict = {
|
||||
"major": major,
|
||||
"minor": minor,
|
||||
"patch": patch,
|
||||
"pre": pre,
|
||||
"dev": dev,
|
||||
"arch": arch,
|
||||
}
|
||||
if version_dict.get("minor") is not None:
|
||||
minor = int(version_dict["minor"])
|
||||
if version_dict.get("patch") is not None:
|
||||
patch = int(version_dict["patch"])
|
||||
if version_dict.get("major") is not None:
|
||||
major = int(version_dict["major"])
|
||||
version_dict = self.parse_major(major, minor=minor, patch=patch, arch=arch)
|
||||
major = version_dict["major"]
|
||||
minor = version_dict.get("minor", minor) # type: ignore
|
||||
patch = version_dict.get("patch", patch) # type: ignore
|
||||
arch = version_dict.get("arch", arch) # type: ignore
|
||||
name = version_dict.get("name", name) # type: ignore
|
||||
_pre = version_dict.get("is_prerelease", pre)
|
||||
pre = bool(_pre) if _pre is not None else pre
|
||||
_dev = version_dict.get("is_devrelease", dev)
|
||||
dev = bool(_dev) if _dev is not None else dev
|
||||
arch = (
|
||||
version_dict.get("architecture", None) if arch is None else arch
|
||||
) # type: ignore
|
||||
if "architecture" in version_dict and isinstance(
|
||||
version_dict["architecture"], six.string_types
|
||||
):
|
||||
arch = version_dict["architecture"] # type: ignore
|
||||
if os.name == "nt" and self.windows_finder is not None:
|
||||
match = self.windows_finder.find_python_version(
|
||||
found = self.windows_finder.find_python_version(
|
||||
major=major,
|
||||
minor=minor,
|
||||
patch=patch,
|
||||
@@ -216,10 +274,17 @@ class Finder(object):
|
||||
arch=arch,
|
||||
name=name,
|
||||
)
|
||||
if match:
|
||||
return match
|
||||
if found:
|
||||
return found
|
||||
return self.system_path.find_python_version(
|
||||
major=major, minor=minor, patch=patch, pre=pre, dev=dev, arch=arch, name=name
|
||||
major=major,
|
||||
minor=minor,
|
||||
patch=patch,
|
||||
pre=pre,
|
||||
dev=dev,
|
||||
arch=arch,
|
||||
name=name,
|
||||
sort_by_path=self.sort_by_path,
|
||||
)
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
|
||||
Vendored
+49
-12
@@ -6,13 +6,14 @@ import itertools
|
||||
import os
|
||||
import re
|
||||
from fnmatch import fnmatch
|
||||
from threading import Timer
|
||||
|
||||
import attr
|
||||
import six
|
||||
import vistir
|
||||
from packaging.version import LegacyVersion, Version
|
||||
|
||||
from .environment import MYPY_RUNNING, PYENV_ROOT
|
||||
from .environment import MYPY_RUNNING, PYENV_ROOT, SUBPROCESS_TIMEOUT
|
||||
from .exceptions import InvalidPythonVersion
|
||||
|
||||
six.add_move(
|
||||
@@ -37,11 +38,12 @@ if MYPY_RUNNING:
|
||||
from .models.path import PathEntry
|
||||
|
||||
|
||||
version_re = re.compile(
|
||||
version_re_str = (
|
||||
r"(?P<major>\d+)(?:\.(?P<minor>\d+))?(?:\.(?P<patch>(?<=\.)[0-9]+))?\.?"
|
||||
r"(?:(?P<prerel>[abc]|rc|dev)(?:(?P<prerelversion>\d+(?:\.\d+)*))?)"
|
||||
r"?(?P<postdev>(\.post(?P<post>\d+))?(\.dev(?P<dev>\d+))?)?"
|
||||
)
|
||||
version_re = re.compile(version_re_str)
|
||||
|
||||
|
||||
PYTHON_IMPLEMENTATIONS = (
|
||||
@@ -53,13 +55,19 @@ PYTHON_IMPLEMENTATIONS = (
|
||||
"miniconda",
|
||||
"stackless",
|
||||
"activepython",
|
||||
"pyston",
|
||||
"micropython",
|
||||
)
|
||||
RE_MATCHER = re.compile(
|
||||
r"(({0})(?:\d?(?:\.\d[cpm]{{0,3}}))?(?:-?[\d\.]+)*[^z])".format(
|
||||
"|".join(PYTHON_IMPLEMENTATIONS)
|
||||
)
|
||||
KNOWN_EXTS = {"exe", "py", "fish", "sh", ""}
|
||||
KNOWN_EXTS = KNOWN_EXTS | set(
|
||||
filter(None, os.environ.get("PATHEXT", "").split(os.pathsep))
|
||||
)
|
||||
PY_MATCH_STR = r"((?P<implementation>{0})(?:\d?(?:\.\d[cpm]{{0,3}}))?(?:-?[\d\.]+)*[^z])".format(
|
||||
"|".join(PYTHON_IMPLEMENTATIONS)
|
||||
)
|
||||
EXE_MATCH_STR = r"{0}(?:\.(?P<ext>{1}))?".format(PY_MATCH_STR, "|".join(KNOWN_EXTS))
|
||||
RE_MATCHER = re.compile(r"({0}|{1})".format(version_re_str, PY_MATCH_STR))
|
||||
EXE_MATCHER = re.compile(EXE_MATCH_STR)
|
||||
RULES_BASE = [
|
||||
"*{0}",
|
||||
"*{0}?",
|
||||
@@ -71,11 +79,6 @@ RULES_BASE = [
|
||||
]
|
||||
RULES = [rule.format(impl) for impl in PYTHON_IMPLEMENTATIONS for rule in RULES_BASE]
|
||||
|
||||
KNOWN_EXTS = {"exe", "py", "fish", "sh", ""}
|
||||
KNOWN_EXTS = KNOWN_EXTS | set(
|
||||
filter(None, os.environ.get("PATHEXT", "").split(os.pathsep))
|
||||
)
|
||||
|
||||
MATCH_RULES = []
|
||||
for rule in RULES:
|
||||
MATCH_RULES.extend(
|
||||
@@ -87,7 +90,11 @@ for rule in RULES:
|
||||
def get_python_version(path):
|
||||
# type: (str) -> str
|
||||
"""Get python version string using subprocess from a given path."""
|
||||
version_cmd = [path, "-c", "import sys; print(sys.version.split()[0])"]
|
||||
version_cmd = [
|
||||
path,
|
||||
"-c",
|
||||
"import sys; print('.'.join([str(i) for i in sys.version_info[:3]]))",
|
||||
]
|
||||
try:
|
||||
c = vistir.misc.run(
|
||||
version_cmd,
|
||||
@@ -97,6 +104,7 @@ def get_python_version(path):
|
||||
combine_stderr=False,
|
||||
write_to_stdout=False,
|
||||
)
|
||||
timer = Timer(SUBPROCESS_TIMEOUT, c.kill)
|
||||
except OSError:
|
||||
raise InvalidPythonVersion("%s is not a valid python path" % path)
|
||||
if not c.out:
|
||||
@@ -326,6 +334,35 @@ def parse_asdf_version_order(filename=".tool-versions"):
|
||||
return []
|
||||
|
||||
|
||||
def split_version_and_name(
|
||||
major=None, # type: Optional[Union[str, int]]
|
||||
minor=None, # type: Optional[Union[str, int]]
|
||||
patch=None, # type: Optional[Union[str, int]]
|
||||
name=None, # type: Optional[str]
|
||||
):
|
||||
# type: (...) -> Tuple[Optional[Union[str, int]], Optional[Union[str, int]], Optional[Union[str, int]], Optional[str]]
|
||||
if isinstance(major, six.string_types) and not minor and not patch:
|
||||
# Only proceed if this is in the format "x.y.z" or similar
|
||||
if major.isdigit() or (major.count(".") > 0 and major[0].isdigit()):
|
||||
version = major.split(".", 2)
|
||||
if isinstance(version, (tuple, list)):
|
||||
if len(version) > 3:
|
||||
major, minor, patch, _ = version
|
||||
elif len(version) == 3:
|
||||
major, minor, patch = version
|
||||
elif len(version) == 2:
|
||||
major, minor = version
|
||||
else:
|
||||
major = major[0]
|
||||
else:
|
||||
major = major
|
||||
name = None
|
||||
else:
|
||||
name = "{0!s}".format(major)
|
||||
major = None
|
||||
return (major, minor, patch, name)
|
||||
|
||||
|
||||
# TODO: Reimplement in vistir
|
||||
def is_in_path(path, parent):
|
||||
return normalize_path(str(path)).startswith(normalize_path(str(parent)))
|
||||
|
||||
Vendored
+2
-2
@@ -57,10 +57,10 @@ def check_compatibility(urllib3_version, chardet_version):
|
||||
# Check urllib3 for compatibility.
|
||||
major, minor, patch = urllib3_version # noqa: F811
|
||||
major, minor, patch = int(major), int(minor), int(patch)
|
||||
# urllib3 >= 1.21.1, <= 1.24
|
||||
# urllib3 >= 1.21.1, <= 1.25
|
||||
assert major == 1
|
||||
assert minor >= 21
|
||||
assert minor <= 24
|
||||
assert minor <= 25
|
||||
|
||||
# Check chardet for compatibility.
|
||||
major, minor, patch = chardet_version.split('.')[:3]
|
||||
|
||||
+3
-3
@@ -5,10 +5,10 @@
|
||||
__title__ = 'requests'
|
||||
__description__ = 'Python HTTP for Humans.'
|
||||
__url__ = 'http://python-requests.org'
|
||||
__version__ = '2.21.0'
|
||||
__build__ = 0x022100
|
||||
__version__ = '2.22.0'
|
||||
__build__ = 0x022200
|
||||
__author__ = 'Kenneth Reitz'
|
||||
__author_email__ = 'me@kennethreitz.org'
|
||||
__license__ = 'Apache 2.0'
|
||||
__copyright__ = 'Copyright 2018 Kenneth Reitz'
|
||||
__copyright__ = 'Copyright 2019 Kenneth Reitz'
|
||||
__cake__ = u'\u2728 \U0001f370 \u2728'
|
||||
|
||||
Vendored
+2
-2
@@ -19,7 +19,7 @@ def request(method, url, **kwargs):
|
||||
:param method: method for the new :class:`Request` object.
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param params: (optional) Dictionary, list of tuples or bytes to send
|
||||
in the body of the :class:`Request`.
|
||||
in the query string for the :class:`Request`.
|
||||
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
|
||||
object to send in the body of the :class:`Request`.
|
||||
:param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
|
||||
@@ -65,7 +65,7 @@ def get(url, params=None, **kwargs):
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param params: (optional) Dictionary, list of tuples or bytes to send
|
||||
in the body of the :class:`Request`.
|
||||
in the query string for the :class:`Request`.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:return: :class:`Response <Response>` object
|
||||
:rtype: requests.Response
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ from .models.lockfile import Lockfile
|
||||
from .models.pipfile import Pipfile
|
||||
from .models.requirements import Requirement
|
||||
|
||||
__version__ = "1.4.2"
|
||||
__version__ = "1.5.1"
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
+525
-5
@@ -1,19 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import itertools
|
||||
import operator
|
||||
|
||||
import attr
|
||||
|
||||
import distlib.markers
|
||||
import packaging.version
|
||||
import six
|
||||
from packaging.markers import InvalidMarker, Marker
|
||||
from packaging.specifiers import Specifier, SpecifierSet
|
||||
from vistir.compat import Mapping, Set, lru_cache
|
||||
from vistir.misc import dedup
|
||||
|
||||
from ..exceptions import RequirementError
|
||||
from .utils import filter_none, validate_markers
|
||||
from ..environment import MYPY_RUNNING
|
||||
from ..exceptions import RequirementError
|
||||
|
||||
from six.moves import reduce # isort:skip
|
||||
|
||||
|
||||
if MYPY_RUNNING:
|
||||
from typing import Optional, List, Type, Any
|
||||
|
||||
|
||||
MAX_VERSIONS = {2: 7, 3: 10}
|
||||
|
||||
|
||||
def is_instance(item, cls):
|
||||
# type: (Any, Type) -> bool
|
||||
if isinstance(item, cls) or item.__class__.__name__ == cls.__name__:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@attr.s
|
||||
class PipenvMarkers(object):
|
||||
"""System-level requirements - see PEP508 for more detail"""
|
||||
|
||||
os_name = attr.ib(
|
||||
default=None, validator=attr.validators.optional(validate_markers)
|
||||
)
|
||||
os_name = attr.ib(default=None, validator=attr.validators.optional(validate_markers))
|
||||
sys_platform = attr.ib(
|
||||
default=None, validator=attr.validators.optional(validate_markers)
|
||||
)
|
||||
@@ -92,3 +115,500 @@ class PipenvMarkers(object):
|
||||
pass
|
||||
else:
|
||||
return combined_marker
|
||||
|
||||
|
||||
@lru_cache(maxsize=128)
|
||||
def _tuplize_version(version):
|
||||
return tuple(int(x) for x in filter(lambda i: i != "*", version.split(".")))
|
||||
|
||||
|
||||
@lru_cache(maxsize=128)
|
||||
def _format_version(version):
|
||||
if not isinstance(version, six.string_types):
|
||||
return ".".join(str(i) for i in version)
|
||||
return version
|
||||
|
||||
|
||||
# Prefer [x,y) ranges.
|
||||
REPLACE_RANGES = {">": ">=", "<=": "<"}
|
||||
|
||||
|
||||
@lru_cache(maxsize=128)
|
||||
def _format_pyspec(specifier):
|
||||
if isinstance(specifier, str):
|
||||
if not any(op in specifier for op in Specifier._operators.keys()):
|
||||
specifier = "=={0}".format(specifier)
|
||||
specifier = Specifier(specifier)
|
||||
version = getattr(specifier, "version", specifier).rstrip()
|
||||
if version and version.endswith("*"):
|
||||
if version.endswith(".*"):
|
||||
version = version.rstrip(".*")
|
||||
else:
|
||||
version = version.rstrip("*")
|
||||
specifier = Specifier("{0}{1}".format(specifier.operator, version))
|
||||
try:
|
||||
op = REPLACE_RANGES[specifier.operator]
|
||||
except KeyError:
|
||||
return specifier
|
||||
curr_tuple = _tuplize_version(version)
|
||||
try:
|
||||
next_tuple = (curr_tuple[0], curr_tuple[1] + 1)
|
||||
except IndexError:
|
||||
next_tuple = (curr_tuple[0], 1)
|
||||
if not next_tuple[1] <= MAX_VERSIONS[next_tuple[0]]:
|
||||
if specifier.operator == "<" and curr_tuple[1] <= MAX_VERSIONS[next_tuple[0]]:
|
||||
op = "<="
|
||||
next_tuple = (next_tuple[0], curr_tuple[1])
|
||||
else:
|
||||
return specifier
|
||||
specifier = Specifier("{0}{1}".format(op, _format_version(next_tuple)))
|
||||
return specifier
|
||||
|
||||
|
||||
@lru_cache(maxsize=128)
|
||||
def _get_specs(specset):
|
||||
if specset is None:
|
||||
return
|
||||
if is_instance(specset, Specifier):
|
||||
new_specset = SpecifierSet()
|
||||
specs = set()
|
||||
specs.add(specset)
|
||||
new_specset._specs = frozenset(specs)
|
||||
specset = new_specset
|
||||
if isinstance(specset, str):
|
||||
specset = SpecifierSet(specset)
|
||||
result = []
|
||||
for spec in set(specset):
|
||||
version = spec.version
|
||||
op = spec.operator
|
||||
if op in ("in", "not in"):
|
||||
versions = version.split(",")
|
||||
op = "==" if op == "in" else "!="
|
||||
for ver in versions:
|
||||
result.append((op, _tuplize_version(ver.strip())))
|
||||
else:
|
||||
result.append((spec.operator, _tuplize_version(spec.version)))
|
||||
return sorted(result, key=operator.itemgetter(1))
|
||||
|
||||
|
||||
@lru_cache(maxsize=128)
|
||||
def _group_by_op(specs):
|
||||
specs = [_get_specs(x) for x in list(specs)]
|
||||
flattened = [(op, version) for spec in specs for op, version in spec]
|
||||
specs = sorted(flattened)
|
||||
grouping = itertools.groupby(specs, key=operator.itemgetter(0))
|
||||
return grouping
|
||||
|
||||
|
||||
@lru_cache(maxsize=128)
|
||||
def cleanup_pyspecs(specs, joiner="or"):
|
||||
if isinstance(specs, six.string_types):
|
||||
specs = set([_format_pyspec(specs)])
|
||||
else:
|
||||
specs = {_format_pyspec(spec) for spec in specs}
|
||||
# for != operator we want to group by version
|
||||
# if all are consecutive, join as a list
|
||||
results = set()
|
||||
for op, versions in _group_by_op(tuple(specs)):
|
||||
versions = [version[1] for version in versions]
|
||||
versions = sorted(dedup(versions))
|
||||
# if we are doing an or operation, we need to use the min for >=
|
||||
# this way OR(>=2.6, >=2.7, >=3.6) picks >=2.6
|
||||
# if we do an AND operation we need to use MAX to be more selective
|
||||
if op in (">", ">="):
|
||||
if joiner == "or":
|
||||
results.add((op, _format_version(min(versions))))
|
||||
else:
|
||||
results.add((op, _format_version(max(versions))))
|
||||
# we use inverse logic here so we will take the max value if we are
|
||||
# using OR but the min value if we are using AND
|
||||
elif op in ("<=", "<"):
|
||||
if joiner == "or":
|
||||
results.add((op, _format_version(max(versions))))
|
||||
else:
|
||||
results.add((op, _format_version(min(versions))))
|
||||
# leave these the same no matter what operator we use
|
||||
elif op in ("!=", "==", "~="):
|
||||
version_list = sorted(
|
||||
"{0}".format(_format_version(version)) for version in versions
|
||||
)
|
||||
version = ", ".join(version_list)
|
||||
if len(version_list) == 1:
|
||||
results.add((op, version))
|
||||
elif op == "!=":
|
||||
results.add(("not in", version))
|
||||
elif op == "==":
|
||||
results.add(("in", version))
|
||||
else:
|
||||
specifier = SpecifierSet(
|
||||
",".join(sorted("{0}{1}".format(op, v) for v in version_list))
|
||||
)._specs
|
||||
for s in specifier:
|
||||
results.add((s._spec[0], s._spec[1]))
|
||||
else:
|
||||
if len(version) == 1:
|
||||
results.add((op, version))
|
||||
else:
|
||||
specifier = SpecifierSet("{0}".format(version))._specs
|
||||
for s in specifier:
|
||||
results.add((s._spec[0], s._spec[1]))
|
||||
return sorted(results, key=operator.itemgetter(1))
|
||||
|
||||
|
||||
def fix_version_tuple(version_tuple):
|
||||
op, version = version_tuple
|
||||
max_major = max(MAX_VERSIONS.keys())
|
||||
if version[0] > max_major:
|
||||
return (op, (max_major, MAX_VERSIONS[max_major]))
|
||||
max_allowed = MAX_VERSIONS[version[0]]
|
||||
if op == "<" and version[1] > max_allowed and version[1] - 1 <= max_allowed:
|
||||
op = "<="
|
||||
version = (version[0], version[1] - 1)
|
||||
return (op, version)
|
||||
|
||||
|
||||
@lru_cache(maxsize=128)
|
||||
def get_versions(specset, group_by_operator=True):
|
||||
specs = [_get_specs(x) for x in list(tuple(specset))]
|
||||
initial_sort_key = lambda k: (k[0], k[1])
|
||||
initial_grouping_key = operator.itemgetter(0)
|
||||
if not group_by_operator:
|
||||
initial_grouping_key = operator.itemgetter(1)
|
||||
initial_sort_key = operator.itemgetter(1)
|
||||
version_tuples = sorted(
|
||||
set((op, version) for spec in specs for op, version in spec), key=initial_sort_key
|
||||
)
|
||||
version_tuples = [fix_version_tuple(t) for t in version_tuples]
|
||||
op_groups = [
|
||||
(grp, list(map(operator.itemgetter(1), keys)))
|
||||
for grp, keys in itertools.groupby(version_tuples, key=initial_grouping_key)
|
||||
]
|
||||
versions = [
|
||||
(op, packaging.version.parse(".".join(str(v) for v in val)))
|
||||
for op, vals in op_groups
|
||||
for val in vals
|
||||
]
|
||||
return sorted(versions, key=operator.itemgetter(1))
|
||||
|
||||
|
||||
def _ensure_marker(marker):
|
||||
if not is_instance(marker, Marker):
|
||||
return Marker(str(marker))
|
||||
return marker
|
||||
|
||||
|
||||
def gen_marker(mkr):
|
||||
m = Marker("python_version == '1'")
|
||||
m._markers.pop()
|
||||
m._markers.append(mkr)
|
||||
return m
|
||||
|
||||
|
||||
def _strip_extra(elements):
|
||||
"""Remove the "extra == ..." operands from the list."""
|
||||
|
||||
return _strip_marker_elem("extra", elements)
|
||||
|
||||
|
||||
def _strip_pyversion(elements):
|
||||
return _strip_marker_elem("python_version", elements)
|
||||
|
||||
|
||||
def _strip_marker_elem(elem_name, elements):
|
||||
"""Remove the supplied element from the marker.
|
||||
|
||||
This is not a comprehensive implementation, but relies on an important
|
||||
characteristic of metadata generation: The element's operand is always
|
||||
associated with an "and" operator. This means that we can simply remove the
|
||||
operand and the "and" operator associated with it.
|
||||
"""
|
||||
|
||||
extra_indexes = []
|
||||
preceding_operators = ["and"] if elem_name == "extra" else ["and", "or"]
|
||||
for i, element in enumerate(elements):
|
||||
if isinstance(element, list):
|
||||
cancelled = _strip_marker_elem(elem_name, element)
|
||||
if cancelled:
|
||||
extra_indexes.append(i)
|
||||
elif isinstance(element, tuple) and element[0].value == elem_name:
|
||||
extra_indexes.append(i)
|
||||
for i in reversed(extra_indexes):
|
||||
del elements[i]
|
||||
if i > 0 and elements[i - 1] in preceding_operators:
|
||||
# Remove the "and" before it.
|
||||
del elements[i - 1]
|
||||
elif elements:
|
||||
# This shouldn't ever happen, but is included for completeness.
|
||||
# If there is not an "and" before this element, try to remove the
|
||||
# operator after it.
|
||||
del elements[0]
|
||||
return not elements
|
||||
|
||||
|
||||
def _get_stripped_marker(marker, strip_func):
|
||||
"""Build a new marker which is cleaned according to `strip_func`"""
|
||||
|
||||
if not marker:
|
||||
return None
|
||||
marker = _ensure_marker(marker)
|
||||
elements = marker._markers
|
||||
strip_func(elements)
|
||||
if elements:
|
||||
return marker
|
||||
return None
|
||||
|
||||
|
||||
def get_without_extra(marker):
|
||||
"""Build a new marker without the `extra == ...` part.
|
||||
|
||||
The implementation relies very deep into packaging's internals, but I don't
|
||||
have a better way now (except implementing the whole thing myself).
|
||||
|
||||
This could return `None` if the `extra == ...` part is the only one in the
|
||||
input marker.
|
||||
"""
|
||||
|
||||
return _get_stripped_marker(marker, _strip_extra)
|
||||
|
||||
|
||||
def get_without_pyversion(marker):
|
||||
"""Built a new marker without the `python_version` part.
|
||||
|
||||
This could return `None` if the `python_version` section is the only section in the
|
||||
marker.
|
||||
"""
|
||||
|
||||
return _get_stripped_marker(marker, _strip_pyversion)
|
||||
|
||||
|
||||
def _markers_collect_extras(markers, collection):
|
||||
# Optimization: the marker element is usually appended at the end.
|
||||
for el in reversed(markers):
|
||||
if isinstance(el, tuple) and el[0].value == "extra" and el[1].value == "==":
|
||||
collection.add(el[2].value)
|
||||
elif isinstance(el, list):
|
||||
_markers_collect_extras(el, collection)
|
||||
|
||||
|
||||
def _markers_collect_pyversions(markers, collection):
|
||||
local_collection = []
|
||||
marker_format_str = "{0}"
|
||||
for i, el in enumerate(reversed(markers)):
|
||||
if isinstance(el, tuple) and el[0].value == "python_version":
|
||||
new_marker = str(gen_marker(el))
|
||||
local_collection.append(marker_format_str.format(new_marker))
|
||||
elif isinstance(el, list):
|
||||
_markers_collect_pyversions(el, local_collection)
|
||||
if local_collection:
|
||||
# local_collection = "{0}".format(" ".join(local_collection))
|
||||
collection.extend(local_collection)
|
||||
|
||||
|
||||
def _markers_contains_extra(markers):
|
||||
# Optimization: the marker element is usually appended at the end.
|
||||
return _markers_contains_key(markers, "extra")
|
||||
|
||||
|
||||
def _markers_contains_pyversion(markers):
|
||||
return _markers_contains_key(markers, "python_version")
|
||||
|
||||
|
||||
def _markers_contains_key(markers, key):
|
||||
for element in reversed(markers):
|
||||
if isinstance(element, tuple) and element[0].value == key:
|
||||
return True
|
||||
elif isinstance(element, list):
|
||||
if _markers_contains_key(element, key):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@lru_cache(maxsize=128)
|
||||
def get_contained_extras(marker):
|
||||
"""Collect "extra == ..." operands from a marker.
|
||||
|
||||
Returns a list of str. Each str is a speficied extra in this marker.
|
||||
"""
|
||||
if not marker:
|
||||
return set()
|
||||
extras = set()
|
||||
marker = _ensure_marker(marker)
|
||||
_markers_collect_extras(marker._markers, extras)
|
||||
return extras
|
||||
|
||||
|
||||
def get_contained_pyversions(marker):
|
||||
"""Collect all `python_version` operands from a marker.
|
||||
"""
|
||||
|
||||
collection = []
|
||||
if not marker:
|
||||
return set()
|
||||
marker = _ensure_marker(marker)
|
||||
# Collect the (Variable, Op, Value) tuples and string joiners from the marker
|
||||
_markers_collect_pyversions(marker._markers, collection)
|
||||
marker_str = " and ".join(sorted(collection))
|
||||
if not marker_str:
|
||||
return set()
|
||||
# Use the distlib dictionary parser to create a dictionary 'trie' which is a bit
|
||||
# easier to reason about
|
||||
marker_dict = distlib.markers.parse_marker(marker_str)[0]
|
||||
version_set = set()
|
||||
pyversions, _ = parse_marker_dict(marker_dict)
|
||||
if isinstance(pyversions, set):
|
||||
version_set.update(pyversions)
|
||||
elif pyversions is not None:
|
||||
version_set.add(pyversions)
|
||||
# Each distinct element in the set was separated by an "and" operator in the marker
|
||||
# So we will need to reduce them with an intersection here rather than a union
|
||||
# in order to find the boundaries
|
||||
versions = set()
|
||||
if version_set:
|
||||
versions = reduce(lambda x, y: x & y, version_set)
|
||||
return versions
|
||||
|
||||
|
||||
@lru_cache(maxsize=128)
|
||||
def contains_extra(marker):
|
||||
"""Check whehter a marker contains an "extra == ..." operand.
|
||||
"""
|
||||
if not marker:
|
||||
return False
|
||||
marker = _ensure_marker(marker)
|
||||
return _markers_contains_extra(marker._markers)
|
||||
|
||||
|
||||
@lru_cache(maxsize=128)
|
||||
def contains_pyversion(marker):
|
||||
"""Check whether a marker contains a python_version operand.
|
||||
"""
|
||||
|
||||
if not marker:
|
||||
return False
|
||||
marker = _ensure_marker(marker)
|
||||
return _markers_contains_pyversion(marker._markers)
|
||||
|
||||
|
||||
def get_specset(marker_list):
|
||||
# type: (List) -> Optional[SpecifierSet]
|
||||
specset = set()
|
||||
_last_str = "and"
|
||||
for marker_parts in marker_list:
|
||||
if isinstance(marker_parts, tuple):
|
||||
variable, op, value = marker_parts
|
||||
if variable.value != "python_version":
|
||||
continue
|
||||
if op.value == "in":
|
||||
values = [v.strip() for v in value.value.split(",")]
|
||||
specset.update(Specifier("=={0}".format(v)) for v in values)
|
||||
elif op.value == "not in":
|
||||
values = [v.strip() for v in value.value.split(",")]
|
||||
bad_versions = ["3.0", "3.1", "3.2", "3.3"]
|
||||
if len(values) >= 2 and any(v in values for v in bad_versions):
|
||||
values = bad_versions
|
||||
specset.update(
|
||||
Specifier("!={0}".format(v.strip())) for v in sorted(bad_versions)
|
||||
)
|
||||
else:
|
||||
specset.add(Specifier("{0}{1}".format(op.value, value.value)))
|
||||
elif isinstance(marker_parts, list):
|
||||
parts = get_specset(marker_parts)
|
||||
if parts:
|
||||
specset.update(parts)
|
||||
elif isinstance(marker_parts, str):
|
||||
_last_str = marker_parts
|
||||
specifiers = SpecifierSet()
|
||||
specifiers._specs = frozenset(specset)
|
||||
return specifiers
|
||||
|
||||
|
||||
def parse_marker_dict(marker_dict):
|
||||
op = marker_dict["op"]
|
||||
lhs = marker_dict["lhs"]
|
||||
rhs = marker_dict["rhs"]
|
||||
# This is where the spec sets for each side land if we have an "or" operator
|
||||
side_spec_list = []
|
||||
side_markers_list = []
|
||||
finalized_marker = ""
|
||||
# And if we hit the end of the parse tree we use this format string to make a marker
|
||||
format_string = "{lhs} {op} {rhs}"
|
||||
specset = SpecifierSet()
|
||||
specs = set()
|
||||
# Essentially we will iterate over each side of the parsed marker if either one is
|
||||
# A mapping instance (i.e. a dictionary) and recursively parse and reduce the specset
|
||||
# Union the "and" specs, intersect the "or"s to find the most appropriate range
|
||||
if any(issubclass(type(side), Mapping) for side in (lhs, rhs)):
|
||||
for side in (lhs, rhs):
|
||||
side_specs = set()
|
||||
side_markers = set()
|
||||
if issubclass(type(side), Mapping):
|
||||
merged_side_specs, merged_side_markers = parse_marker_dict(side)
|
||||
side_specs.update(merged_side_specs)
|
||||
side_markers.update(merged_side_markers)
|
||||
else:
|
||||
marker = _ensure_marker(side)
|
||||
marker_parts = getattr(marker, "_markers", [])
|
||||
if marker_parts[0][0].value == "python_version":
|
||||
side_specs |= set(get_specset(marker_parts))
|
||||
else:
|
||||
side_markers.add(str(marker))
|
||||
side_spec_list.append(side_specs)
|
||||
side_markers_list.append(side_markers)
|
||||
if op == "and":
|
||||
# When we are "and"-ing things together, it probably makes the most sense
|
||||
# to reduce them here into a single PySpec instance
|
||||
specs = reduce(lambda x, y: set(x) | set(y), side_spec_list)
|
||||
markers = reduce(lambda x, y: set(x) | set(y), side_markers_list)
|
||||
if not specs and not markers:
|
||||
return specset, finalized_marker
|
||||
if markers and isinstance(markers, (tuple, list, Set)):
|
||||
finalized_marker = Marker(" and ".join([m for m in markers if m]))
|
||||
elif markers:
|
||||
finalized_marker = str(markers)
|
||||
specset._specs = frozenset(specs)
|
||||
return specset, finalized_marker
|
||||
# Actually when we "or" things as well we can also just turn them into a reduced
|
||||
# set using this logic now
|
||||
sides = reduce(lambda x, y: set(x) & set(y), side_spec_list)
|
||||
finalized_marker = " or ".join(
|
||||
[normalize_marker_str(m) for m in side_markers_list]
|
||||
)
|
||||
specset._specs = frozenset(sorted(sides))
|
||||
return specset, finalized_marker
|
||||
else:
|
||||
# At the tip of the tree we are dealing with strings all around and they just need
|
||||
# to be smashed together
|
||||
specs = set()
|
||||
if lhs == "python_version":
|
||||
format_string = "{lhs}{op}{rhs}"
|
||||
marker = Marker(format_string.format(**marker_dict))
|
||||
marker_parts = getattr(marker, "_markers", [])
|
||||
_set = get_specset(marker_parts)
|
||||
if _set:
|
||||
specs |= set(_set)
|
||||
specset._specs = frozenset(specs)
|
||||
return specset, finalized_marker
|
||||
|
||||
|
||||
def format_pyversion(parts):
|
||||
op, val = parts
|
||||
return "python_version {0} '{1}'".format(op, val)
|
||||
|
||||
|
||||
def normalize_marker_str(marker):
|
||||
marker_str = ""
|
||||
if not marker:
|
||||
return None
|
||||
if not is_instance(marker, Marker):
|
||||
marker = _ensure_marker(marker)
|
||||
pyversion = get_contained_pyversions(marker)
|
||||
marker = get_without_pyversion(marker)
|
||||
if pyversion:
|
||||
parts = cleanup_pyspecs(pyversion)
|
||||
marker_str = " and ".join([format_pyversion(pv) for pv in parts])
|
||||
if marker:
|
||||
if marker_str:
|
||||
marker_str = "{0!s} and {1!s}".format(marker_str, marker)
|
||||
else:
|
||||
marker_str = "{0!s}".format(marker)
|
||||
return marker_str.replace('"', "'")
|
||||
|
||||
+42
-26
@@ -7,22 +7,21 @@ import os
|
||||
import sys
|
||||
|
||||
import attr
|
||||
import tomlkit
|
||||
|
||||
import plette.models.base
|
||||
import plette.pipfiles
|
||||
|
||||
import tomlkit
|
||||
from vistir.compat import FileNotFoundError, Path
|
||||
|
||||
from ..exceptions import RequirementError
|
||||
from ..utils import is_editable, is_vcs, merge_items
|
||||
from .project import ProjectFile
|
||||
from .requirements import Requirement
|
||||
from .utils import optional_instance_of, get_url_name
|
||||
|
||||
from .utils import get_url_name, optional_instance_of, tomlkit_value_to_python
|
||||
from ..environment import MYPY_RUNNING
|
||||
from ..exceptions import RequirementError
|
||||
from ..utils import is_editable, is_vcs, merge_items
|
||||
|
||||
if MYPY_RUNNING:
|
||||
from typing import Union, Any, Dict, Iterable, Mapping, List, Text
|
||||
|
||||
package_type = Dict[Text, Dict[Text, Union[List[Text], Text]]]
|
||||
source_type = Dict[Text, Union[Text, bool]]
|
||||
sources_type = Iterable[source_type]
|
||||
@@ -46,7 +45,7 @@ def patch_plette():
|
||||
|
||||
def validate(cls, data):
|
||||
# type: (Any, Dict[Text, Any]) -> None
|
||||
if not cerberus: # Skip validation if Cerberus is not available.
|
||||
if not cerberus: # Skip validation if Cerberus is not available.
|
||||
return
|
||||
schema = cls.__SCHEMA__
|
||||
key = id(schema)
|
||||
@@ -156,10 +155,12 @@ class Pipfile(object):
|
||||
path = attr.ib(validator=is_path, type=Path)
|
||||
projectfile = attr.ib(validator=is_projectfile, type=ProjectFile)
|
||||
_pipfile = attr.ib(type=PipfileLoader)
|
||||
_pyproject = attr.ib(default=attr.Factory(tomlkit.document), type=tomlkit.toml_document.TOMLDocument)
|
||||
_pyproject = attr.ib(
|
||||
default=attr.Factory(tomlkit.document), type=tomlkit.toml_document.TOMLDocument
|
||||
)
|
||||
build_system = attr.ib(default=attr.Factory(dict), type=dict)
|
||||
requirements = attr.ib(default=attr.Factory(list), type=list)
|
||||
dev_requirements = attr.ib(default=attr.Factory(list), type=list)
|
||||
_requirements = attr.ib(default=attr.Factory(list), type=list)
|
||||
_dev_requirements = attr.ib(default=attr.Factory(list), type=list)
|
||||
|
||||
@path.default
|
||||
def _get_path(self):
|
||||
@@ -188,7 +189,9 @@ class Pipfile(object):
|
||||
deps.update(self.pipfile._data["dev-packages"])
|
||||
if only:
|
||||
return deps
|
||||
return merge_items([deps, self.pipfile._data["packages"]])
|
||||
return tomlkit_value_to_python(
|
||||
merge_items([deps, self.pipfile._data["packages"]])
|
||||
)
|
||||
|
||||
def get(self, k):
|
||||
# type: (Text) -> Any
|
||||
@@ -213,6 +216,7 @@ class Pipfile(object):
|
||||
if "-" in k:
|
||||
section, _, pkg_type = k.rpartition("-")
|
||||
vals = getattr(pipfile.get(section, {}), "_data", {})
|
||||
vals = tomlkit_value_to_python(vals)
|
||||
if pkg_type == "vcs":
|
||||
retval = {k: v for k, v in vals.items() if is_vcs(v)}
|
||||
elif pkg_type == "editable":
|
||||
@@ -254,11 +258,7 @@ class Pipfile(object):
|
||||
:return: A project file with the model and location for interaction
|
||||
:rtype: :class:`~requirementslib.models.project.ProjectFile`
|
||||
"""
|
||||
pf = ProjectFile.read(
|
||||
path,
|
||||
PipfileLoader,
|
||||
invalid_ok=True
|
||||
)
|
||||
pf = ProjectFile.read(path, PipfileLoader, invalid_ok=True)
|
||||
return pf
|
||||
|
||||
@classmethod
|
||||
@@ -303,18 +303,10 @@ class Pipfile(object):
|
||||
|
||||
projectfile = cls.load_projectfile(path, create=create)
|
||||
pipfile = projectfile.model
|
||||
dev_requirements = [
|
||||
Requirement.from_pipfile(k, getattr(v, "_data", v)) for k, v in pipfile.get("dev-packages", {}).items()
|
||||
]
|
||||
requirements = [
|
||||
Requirement.from_pipfile(k, getattr(v, "_data", v)) for k, v in pipfile.get("packages", {}).items()
|
||||
]
|
||||
creation_args = {
|
||||
"projectfile": projectfile,
|
||||
"pipfile": pipfile,
|
||||
"dev_requirements": dev_requirements,
|
||||
"requirements": requirements,
|
||||
"path": Path(projectfile.location)
|
||||
"path": Path(projectfile.location),
|
||||
}
|
||||
return cls(**creation_args)
|
||||
|
||||
@@ -333,6 +325,30 @@ class Pipfile(object):
|
||||
# type: () -> List[Requirement]
|
||||
return self.requirements
|
||||
|
||||
@property
|
||||
def dev_requirements(self):
|
||||
# type: () -> List[Requirement]
|
||||
if not self._dev_requirements:
|
||||
packages = tomlkit_value_to_python(self.pipfile.get("dev-packages", {}))
|
||||
self._dev_requirements = [
|
||||
Requirement.from_pipfile(k, v)
|
||||
for k, v in packages.items()
|
||||
if v is not None
|
||||
]
|
||||
return self._dev_requirements
|
||||
|
||||
@property
|
||||
def requirements(self):
|
||||
# type: () -> List[Requirement]
|
||||
if not self._requirements:
|
||||
packages = tomlkit_value_to_python(self.pipfile.get("packages", {}))
|
||||
self._requirements = [
|
||||
Requirement.from_pipfile(k, v)
|
||||
for k, v in packages.items()
|
||||
if v is not None
|
||||
]
|
||||
return self._requirements
|
||||
|
||||
def _read_pyproject(self):
|
||||
# type: () -> None
|
||||
pyproject = self.path.parent.joinpath("pyproject.toml")
|
||||
|
||||
+138
-264
@@ -4,7 +4,6 @@ from __future__ import absolute_import, print_function
|
||||
|
||||
import collections
|
||||
import copy
|
||||
import hashlib
|
||||
import os
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
@@ -40,7 +39,20 @@ from vistir.path import (
|
||||
normalize_path,
|
||||
)
|
||||
|
||||
from .setup_info import SetupInfo, _prepare_wheel_building_kwargs
|
||||
from .markers import (
|
||||
cleanup_pyspecs,
|
||||
contains_pyversion,
|
||||
format_pyversion,
|
||||
get_contained_pyversions,
|
||||
normalize_marker_str,
|
||||
)
|
||||
from .setup_info import (
|
||||
SetupInfo,
|
||||
_prepare_wheel_building_kwargs,
|
||||
ast_parse_setup_py,
|
||||
get_metadata,
|
||||
parse_setup_cfg,
|
||||
)
|
||||
from .url import URI
|
||||
from .utils import (
|
||||
DIRECT_URL_RE,
|
||||
@@ -74,6 +86,7 @@ from ..utils import (
|
||||
VCS_LIST,
|
||||
add_ssh_scheme_to_git_uri,
|
||||
get_setup_paths,
|
||||
is_installable_dir,
|
||||
is_installable_file,
|
||||
is_vcs,
|
||||
strip_ssh_from_git_uri,
|
||||
@@ -195,6 +208,18 @@ class Line(object):
|
||||
except Exception:
|
||||
return "<Line {0}>".format(self.__dict__.values())
|
||||
|
||||
@property
|
||||
def name_and_specifier(self):
|
||||
name_str, spec_str = "", ""
|
||||
if self.name:
|
||||
name_str = "{0}".format(self.name.lower())
|
||||
extras_str = extras_to_string(self.extras)
|
||||
if extras_str:
|
||||
name_str = "{0}{1}".format(name_str, extras_str)
|
||||
if self.specifier:
|
||||
spec_str = "{0}".format(self.specifier)
|
||||
return "{0}{1}".format(name_str, spec_str)
|
||||
|
||||
@classmethod
|
||||
def split_hashes(cls, line):
|
||||
# type: (S) -> Tuple[S, List[S]]
|
||||
@@ -216,6 +241,8 @@ class Line(object):
|
||||
def line_with_prefix(self):
|
||||
# type: () -> STRING_TYPE
|
||||
line = self.line
|
||||
if self.is_named:
|
||||
return self.name_and_specifier
|
||||
extras_str = extras_to_string(self.extras)
|
||||
if self.is_direct_url:
|
||||
line = self.link.url
|
||||
@@ -224,7 +251,7 @@ class Line(object):
|
||||
line = self.link.url
|
||||
if "git+file:/" in line and "git+file:///" not in line:
|
||||
line = line.replace("git+file:/", "git+file:///")
|
||||
else:
|
||||
elif extras_str not in line:
|
||||
line = "{0}{1}".format(line, extras_str)
|
||||
if self.editable:
|
||||
return "-e {0}".format(line)
|
||||
@@ -487,10 +514,10 @@ class Line(object):
|
||||
:returns: Nothing
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
line, hashes = self.split_hashes(self.line)
|
||||
self.hashes = hashes
|
||||
self.line = line
|
||||
return self
|
||||
|
||||
def parse_extras(self):
|
||||
# type: () -> None
|
||||
@@ -499,7 +526,6 @@ class Line(object):
|
||||
:returns: Nothing
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
extras = None
|
||||
if "@" in self.line or self.is_vcs or self.is_url:
|
||||
line = "{0}".format(self.line)
|
||||
@@ -525,11 +551,11 @@ class Line(object):
|
||||
extras_set |= name_extras
|
||||
if extras_set is not None:
|
||||
self.extras = tuple(sorted(extras_set))
|
||||
return self
|
||||
|
||||
def get_url(self):
|
||||
# type: () -> STRING_TYPE
|
||||
"""Sets ``self.name`` if given a **PEP-508** style URL"""
|
||||
|
||||
line = self.line
|
||||
try:
|
||||
parsed = URI.parse(line)
|
||||
@@ -569,6 +595,10 @@ class Line(object):
|
||||
if self._name is None and not self.is_named and not self.is_wheel:
|
||||
if self.setup_info:
|
||||
self._name = self.setup_info.name
|
||||
elif self.is_wheel:
|
||||
self._name = self._parse_wheel()
|
||||
if not self._name:
|
||||
self._name = self.ireq.name
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
@@ -781,6 +811,29 @@ class Line(object):
|
||||
self._vcsrepo = self._get_vcsrepo()
|
||||
return self._vcsrepo
|
||||
|
||||
@cached_property
|
||||
def metadata(self):
|
||||
# type: () -> Dict[Any, Any]
|
||||
if self.is_local and is_installable_dir(self.path):
|
||||
return get_metadata(self.path)
|
||||
return {}
|
||||
|
||||
@cached_property
|
||||
def parsed_setup_cfg(self):
|
||||
# type: () -> Dict[Any, Any]
|
||||
if self.is_local and is_installable_dir(self.path):
|
||||
if self.setup_cfg:
|
||||
return parse_setup_cfg(self.setup_cfg)
|
||||
return {}
|
||||
|
||||
@cached_property
|
||||
def parsed_setup_py(self):
|
||||
# type: () -> Dict[Any, Any]
|
||||
if self.is_local and is_installable_dir(self.path):
|
||||
if self.setup_py:
|
||||
return ast_parse_setup_py(self.setup_py)
|
||||
return {}
|
||||
|
||||
@vcsrepo.setter
|
||||
def vcsrepo(self, repo):
|
||||
# type (VCSRepository) -> None
|
||||
@@ -843,7 +896,6 @@ class Line(object):
|
||||
|
||||
def _parse_name_from_link(self):
|
||||
# type: () -> Optional[STRING_TYPE]
|
||||
|
||||
if self.link is None:
|
||||
return None
|
||||
if getattr(self.link, "egg_fragment", None):
|
||||
@@ -881,8 +933,29 @@ class Line(object):
|
||||
self._specifier = "{0}{1}".format(specifier, version)
|
||||
return name
|
||||
|
||||
def _parse_name_from_path(self):
|
||||
# type: () -> Optional[S]
|
||||
if self.path and self.is_local and is_installable_dir(self.path):
|
||||
metadata = get_metadata(self.path)
|
||||
if metadata:
|
||||
name = metadata.get("name", "")
|
||||
if name and name != "wheel":
|
||||
return name
|
||||
parsed_setup_cfg = self.parsed_setup_cfg
|
||||
if parsed_setup_cfg:
|
||||
name = parsed_setup_cfg.get("name", "")
|
||||
if name:
|
||||
return name
|
||||
|
||||
parsed_setup_py = self.parsed_setup_py
|
||||
if parsed_setup_py:
|
||||
name = parsed_setup_py.get("name", "")
|
||||
if name:
|
||||
return name
|
||||
return None
|
||||
|
||||
def parse_name(self):
|
||||
# type: () -> None
|
||||
# type: () -> "Line"
|
||||
if self._name is None:
|
||||
name = None
|
||||
if self.link is not None:
|
||||
@@ -895,13 +968,17 @@ class Line(object):
|
||||
if "&" in name:
|
||||
# subdirectory fragments might also be in here
|
||||
name, _, _ = name.partition("&")
|
||||
if self.is_named:
|
||||
if name is None and self.is_named:
|
||||
name = self._parse_name_from_line()
|
||||
elif name is None and self.is_file or self.is_url or self.is_path:
|
||||
if self.is_local:
|
||||
name = self._parse_name_from_path()
|
||||
if name is not None:
|
||||
name, extras = pip_shims.shims._strip_extras(name)
|
||||
if extras is not None and not self.extras:
|
||||
self.extras = tuple(sorted(set(parse_extras(extras))))
|
||||
self._name = name
|
||||
return self
|
||||
|
||||
def _parse_requirement_from_vcs(self):
|
||||
# type: () -> Optional[PackagingRequirement]
|
||||
@@ -939,7 +1016,7 @@ class Line(object):
|
||||
return self._requirement
|
||||
|
||||
def parse_requirement(self):
|
||||
# type: () -> None
|
||||
# type: () -> "Line"
|
||||
if self._name is None:
|
||||
self.parse_name()
|
||||
if not self._name and not self.is_vcs and not self.is_named:
|
||||
@@ -982,9 +1059,10 @@ class Line(object):
|
||||
"dependencies. Please install remote dependency "
|
||||
"in the form {0}#egg=<package-name>.".format(url)
|
||||
)
|
||||
return self
|
||||
|
||||
def parse_link(self):
|
||||
# type: () -> None
|
||||
# type: () -> "Line"
|
||||
parsed_url = None # type: Optional[URI]
|
||||
if not is_valid_url(self.line) and (
|
||||
self.line.startswith("./")
|
||||
@@ -1033,6 +1111,7 @@ class Line(object):
|
||||
self._link = parsed_link
|
||||
else:
|
||||
self._link = link
|
||||
return self
|
||||
|
||||
def parse_markers(self):
|
||||
# type: () -> None
|
||||
@@ -1110,8 +1189,7 @@ class Line(object):
|
||||
|
||||
def parse(self):
|
||||
# type: () -> None
|
||||
self.parse_hashes()
|
||||
self.line, self.markers = split_markers_from_line(self.line)
|
||||
self.line, self.markers = split_markers_from_line(self.parse_hashes().line)
|
||||
self.parse_extras()
|
||||
self.line = self.line.strip('"').strip("'").strip()
|
||||
if self.line.startswith("git+file:/") and not self.line.startswith(
|
||||
@@ -1184,8 +1262,8 @@ class NamedRequirement(object):
|
||||
return cls(**creation_kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_pipfile(cls, name, pipfile): # type: S # type: TPIPFILE
|
||||
# type: (...) -> NamedRequirement
|
||||
def from_pipfile(cls, name, pipfile):
|
||||
# type: (S, TPIPFILE) -> NamedRequirement
|
||||
creation_args = {} # type: TPIPFILE
|
||||
if hasattr(pipfile, "keys"):
|
||||
attr_fields = [field.name for field in attr.fields(cls)]
|
||||
@@ -1471,84 +1549,12 @@ class FileRequirement(object):
|
||||
@name.default
|
||||
def get_name(self):
|
||||
# type: () -> STRING_TYPE
|
||||
loc = self.path or self.uri
|
||||
if loc and not self._uri_scheme:
|
||||
self._uri_scheme = "path" if self.path else "file"
|
||||
name = None # type: Optional[STRING_TYPE]
|
||||
hashed_loc = None # type: Optional[STRING_TYPE]
|
||||
hashed_name = None # type: Optional[STRING_TYPE]
|
||||
if loc:
|
||||
hashed_loc = hashlib.sha256(loc.encode("utf-8")).hexdigest()
|
||||
hashed_name = hashed_loc[-7:]
|
||||
if (
|
||||
getattr(self, "req", None)
|
||||
and self.req is not None
|
||||
and getattr(self.req, "name")
|
||||
and self.req.name is not None
|
||||
):
|
||||
if self.is_direct_url and self.req.name != hashed_name:
|
||||
return self.req.name
|
||||
if self.link and self.link.egg_fragment and self.link.egg_fragment != hashed_name:
|
||||
if self.parsed_line and self.parsed_line.name:
|
||||
return self.parsed_line.name
|
||||
elif self.link and self.link.egg_fragment:
|
||||
return self.link.egg_fragment
|
||||
elif self.link and self.link.is_wheel:
|
||||
from pip_shims import Wheel
|
||||
|
||||
self._has_hashed_name = False
|
||||
return Wheel(self.link.filename).name
|
||||
elif self.link and (
|
||||
(self.link.scheme == "file" or self.editable)
|
||||
or (self.path and self.setup_path and os.path.isfile(str(self.setup_path)))
|
||||
):
|
||||
_ireq = None # type: Optional[InstallRequirement]
|
||||
target_path = "" # type: STRING_TYPE
|
||||
if self.setup_py_dir:
|
||||
target_path = Path(self.setup_py_dir).as_posix()
|
||||
elif self.path:
|
||||
target_path = Path(os.path.abspath(self.path)).as_posix()
|
||||
if self.editable:
|
||||
line = pip_shims.shims.path_to_url(target_path)
|
||||
if self.extras:
|
||||
line = "{0}[{1}]".format(line, ",".join(self.extras))
|
||||
_ireq = pip_shims.shims.install_req_from_editable(line)
|
||||
else:
|
||||
line = target_path
|
||||
if self.extras:
|
||||
line = "{0}[{1}]".format(line, ",".join(self.extras))
|
||||
_ireq = pip_shims.shims.install_req_from_line(line)
|
||||
if getattr(self, "req", None) is not None:
|
||||
_ireq.req = copy.deepcopy(self.req)
|
||||
if self.extras and _ireq and not _ireq.extras:
|
||||
_ireq.extras = set(self.extras)
|
||||
from .setup_info import SetupInfo
|
||||
|
||||
subdir = getattr(self, "subdirectory", None)
|
||||
if self.setup_info is not None:
|
||||
setupinfo = self.setup_info
|
||||
else:
|
||||
setupinfo = SetupInfo.from_ireq(_ireq, subdir=subdir)
|
||||
if setupinfo:
|
||||
self._setup_info = setupinfo
|
||||
self._setup_info.get_info()
|
||||
setupinfo_dict = setupinfo.as_dict()
|
||||
setup_name = setupinfo_dict.get("name", None)
|
||||
if setup_name:
|
||||
name = setup_name
|
||||
self._has_hashed_name = False
|
||||
build_requires = setupinfo_dict.get("build_requires")
|
||||
build_backend = setupinfo_dict.get("build_backend")
|
||||
if build_requires and not self.pyproject_requires:
|
||||
self.pyproject_requires = tuple(build_requires)
|
||||
if build_backend and not self.pyproject_backend:
|
||||
self.pyproject_backend = build_backend
|
||||
if not name or name.lower() == "unknown":
|
||||
self._has_hashed_name = True
|
||||
name = hashed_name
|
||||
name_in_link = getattr(self.link, "egg_fragment", "") if self.link else ""
|
||||
if not self._has_hashed_name and name_in_link != name and self.link is not None:
|
||||
self.link = create_link("{0}#egg={1}".format(self.link.url, name))
|
||||
if name is not None:
|
||||
return name
|
||||
return ""
|
||||
elif self.setup_info and self.setup_info.name:
|
||||
return self.setup_info.name
|
||||
|
||||
@link.default
|
||||
def get_link(self):
|
||||
@@ -1581,34 +1587,6 @@ class FileRequirement(object):
|
||||
if req:
|
||||
return req
|
||||
|
||||
req = init_requirement(normalize_name(self.name))
|
||||
if req is None:
|
||||
raise ValueError(
|
||||
"Failed to generate a requirement: missing name for {0!r}".format(self)
|
||||
)
|
||||
req.editable = False
|
||||
if self.link is not None:
|
||||
req.line = self.link.url_without_fragment
|
||||
elif self.uri is not None:
|
||||
req.line = self.uri
|
||||
else:
|
||||
req.line = self.name
|
||||
if self.path and self.link and self.link.scheme.startswith("file"):
|
||||
req.local_file = True
|
||||
req.path = self.path
|
||||
if self.editable:
|
||||
req.url = None
|
||||
else:
|
||||
req.url = self.link.url_without_fragment
|
||||
else:
|
||||
req.local_file = False
|
||||
req.path = None
|
||||
req.url = self.link.url_without_fragment
|
||||
if self.editable:
|
||||
req.editable = True
|
||||
req.link = self.link
|
||||
return req
|
||||
|
||||
@property
|
||||
def parsed_line(self):
|
||||
# type: () -> Optional[Line]
|
||||
@@ -1639,11 +1617,9 @@ class FileRequirement(object):
|
||||
if self.link is None:
|
||||
return False
|
||||
return (
|
||||
any(
|
||||
self.link.scheme.startswith(scheme)
|
||||
for scheme in ("http", "https", "ftp", "ftps", "uri")
|
||||
)
|
||||
and (self.link.is_artifact or self.link.is_wheel)
|
||||
self._parsed_line
|
||||
and not self._parsed_line.is_local
|
||||
and (self._parsed_line.is_artifact or self._parsed_line.is_wheel)
|
||||
and not self.editable
|
||||
)
|
||||
|
||||
@@ -1833,53 +1809,8 @@ class FileRequirement(object):
|
||||
@classmethod
|
||||
def from_line(cls, line, editable=None, extras=None, parsed_line=None):
|
||||
# type: (AnyStr, Optional[bool], Optional[Tuple[AnyStr, ...]], Optional[Line]) -> F
|
||||
line = line.strip('"').strip("'")
|
||||
link = None
|
||||
path = None
|
||||
editable = line.startswith("-e ")
|
||||
line = line.split(" ", 1)[1] if editable else line
|
||||
setup_path = None
|
||||
name = None
|
||||
req = None
|
||||
if not extras:
|
||||
extras = ()
|
||||
else:
|
||||
extras = tuple(extras)
|
||||
if not any([is_installable_file(line), is_valid_url(line), is_file_url(line)]):
|
||||
try:
|
||||
req = init_requirement(line)
|
||||
except Exception:
|
||||
raise RequirementError(
|
||||
"Supplied requirement is not installable: {0!r}".format(line)
|
||||
)
|
||||
else:
|
||||
name = getattr(req, "name", None)
|
||||
line = getattr(req, "url", None)
|
||||
vcs_type, prefer, relpath, path, uri, link = cls.get_link_from_line(line)
|
||||
arg_dict = {
|
||||
"path": relpath if relpath else path,
|
||||
"uri": unquote(link.url_without_fragment),
|
||||
"link": link,
|
||||
"editable": editable,
|
||||
"setup_path": setup_path,
|
||||
"uri_scheme": prefer,
|
||||
"line": line,
|
||||
"extras": extras,
|
||||
# "name": name,
|
||||
}
|
||||
if req is not None:
|
||||
arg_dict["req"] = req
|
||||
if parsed_line is not None:
|
||||
arg_dict["parsed_line"] = parsed_line
|
||||
if link and link.is_wheel:
|
||||
from pip_shims import Wheel
|
||||
|
||||
arg_dict["name"] = Wheel(link.filename).name
|
||||
elif name:
|
||||
arg_dict["name"] = name
|
||||
elif link.egg_fragment:
|
||||
arg_dict["name"] = link.egg_fragment
|
||||
return cls.create(**arg_dict)
|
||||
parsed_line = Line(line)
|
||||
file_req_from_parsed_line(parsed_line)
|
||||
|
||||
@classmethod
|
||||
def from_pipfile(cls, name, pipfile):
|
||||
@@ -1964,8 +1895,9 @@ class FileRequirement(object):
|
||||
line = "{0}&subdirectory={1}".format(line, pipfile["subdirectory"])
|
||||
if editable:
|
||||
line = "-e {0}".format(line)
|
||||
arg_dict["line"] = line
|
||||
return cls.create(**arg_dict) # type: ignore
|
||||
arg_dict["parsed_line"] = Line(line)
|
||||
arg_dict["setup_info"] = arg_dict["parsed_line"].setup_info
|
||||
return cls(**arg_dict) # type: ignore
|
||||
|
||||
@property
|
||||
def line_part(self):
|
||||
@@ -2344,7 +2276,7 @@ class VCSRequirement(FileRequirement):
|
||||
)
|
||||
if self.parsed_line and self._parsed_line:
|
||||
self._parsed_line.vcsrepo = vcsrepo
|
||||
if self.req:
|
||||
if self.req and not self.editable:
|
||||
self.req.specifier = SpecifierSet("=={0}".format(self.setup_info.version))
|
||||
try:
|
||||
yield self._repo
|
||||
@@ -2407,83 +2339,8 @@ class VCSRequirement(FileRequirement):
|
||||
@classmethod
|
||||
def from_line(cls, line, editable=None, extras=None, parsed_line=None):
|
||||
# type: (AnyStr, Optional[bool], Optional[Tuple[AnyStr, ...]], Optional[Line]) -> F
|
||||
relpath = None
|
||||
if parsed_line is None:
|
||||
parsed_line = Line(line)
|
||||
if editable:
|
||||
parsed_line.editable = editable
|
||||
if extras:
|
||||
parsed_line.extras = extras
|
||||
if line.startswith("-e "):
|
||||
editable = True
|
||||
line = line.split(" ", 1)[1]
|
||||
if "@" in line:
|
||||
parsed = urllib_parse.urlparse(add_ssh_scheme_to_git_uri(line))
|
||||
if not parsed.scheme:
|
||||
possible_name, _, line = line.partition("@")
|
||||
possible_name = possible_name.strip()
|
||||
line = line.strip()
|
||||
possible_name, extras = pip_shims.shims._strip_extras(possible_name)
|
||||
name = possible_name
|
||||
line = "{0}#egg={1}".format(line, name)
|
||||
vcs_type, prefer, relpath, path, uri, link = cls.get_link_from_line(line)
|
||||
if not extras and link.egg_fragment:
|
||||
name, extras = pip_shims.shims._strip_extras(link.egg_fragment)
|
||||
else:
|
||||
name, _ = pip_shims.shims._strip_extras(link.egg_fragment)
|
||||
parsed_extras = None # type: Optional[List[STRING_TYPE]]
|
||||
extras_tuple = None # type: Optional[Tuple[STRING_TYPE, ...]]
|
||||
if not extras:
|
||||
line, extras = pip_shims.shims._strip_extras(line)
|
||||
if extras:
|
||||
if isinstance(extras, six.string_types):
|
||||
parsed_extras = parse_extras(extras)
|
||||
if parsed_extras:
|
||||
extras_tuple = tuple(parsed_extras)
|
||||
subdirectory = link.subdirectory_fragment
|
||||
ref = None
|
||||
if uri:
|
||||
uri, ref = split_ref_from_uri(uri)
|
||||
if path is not None and "@" in path:
|
||||
path, _ref = split_ref_from_uri(path)
|
||||
if ref is None:
|
||||
ref = _ref
|
||||
if relpath and "@" in relpath:
|
||||
relpath, ref = split_ref_from_uri(relpath)
|
||||
|
||||
creation_args = {
|
||||
"name": name if name else parsed_line.name,
|
||||
"path": relpath or path,
|
||||
"editable": editable,
|
||||
"extras": extras_tuple,
|
||||
"link": link,
|
||||
"vcs_type": vcs_type,
|
||||
"line": line,
|
||||
"uri": uri,
|
||||
"uri_scheme": prefer,
|
||||
"parsed_line": parsed_line,
|
||||
}
|
||||
if relpath:
|
||||
creation_args["relpath"] = relpath
|
||||
# return cls.create(**creation_args)
|
||||
cls_inst = cls(
|
||||
name=name,
|
||||
ref=ref,
|
||||
vcs=vcs_type,
|
||||
subdirectory=subdirectory,
|
||||
link=link,
|
||||
path=relpath or path,
|
||||
editable=editable,
|
||||
uri=uri,
|
||||
extras=extras_tuple if extras_tuple else tuple(),
|
||||
base_line=line,
|
||||
parsed_line=parsed_line,
|
||||
)
|
||||
if cls_inst.req and (
|
||||
cls_inst._parsed_line.ireq and not cls_inst.parsed_line.ireq.req
|
||||
):
|
||||
cls_inst._parsed_line._ireq.req = cls_inst.req
|
||||
return cls_inst
|
||||
parsed_line = Line(line)
|
||||
return vcs_req_from_parsed_line(parsed_line)
|
||||
|
||||
@property
|
||||
def line_part(self):
|
||||
@@ -2695,12 +2552,12 @@ class Requirement(object):
|
||||
return ""
|
||||
|
||||
def update_name_from_path(self, path):
|
||||
from .setup_info import get_metadata
|
||||
|
||||
metadata = get_metadata(path)
|
||||
name = self.name
|
||||
if metadata is not None:
|
||||
name = metadata.get("name")
|
||||
metadata_name = metadata.get("name")
|
||||
if metadata_name and metadata_name != "wheel":
|
||||
name = metadata_name
|
||||
if name is not None:
|
||||
if self.req.name is None:
|
||||
self.req.name = name
|
||||
@@ -3238,15 +3095,32 @@ class Requirement(object):
|
||||
# type: (Union[AnyStr, Marker]) -> None
|
||||
if not isinstance(markers, Marker):
|
||||
markers = Marker(markers)
|
||||
_markers = set() # type: Set[Marker]
|
||||
if self.ireq and self.ireq.markers:
|
||||
_markers.add(Marker(self.ireq.markers))
|
||||
_markers.add(markers)
|
||||
new_markers = Marker(" or ".join([str(m) for m in sorted(_markers)]))
|
||||
self.markers = str(new_markers)
|
||||
if self.req and self.req.req:
|
||||
self.req.req.marker = new_markers
|
||||
return
|
||||
_markers = [] # type: List[Marker]
|
||||
ireq = self.as_ireq()
|
||||
if ireq and ireq.markers:
|
||||
ireq_marker = ireq.markers
|
||||
_markers.append(str(ireq_marker))
|
||||
_markers.append(str(markers))
|
||||
marker_str = " and ".join([normalize_marker_str(m) for m in _markers if m])
|
||||
new_marker = Marker(marker_str)
|
||||
line = copy.deepcopy(self._line_instance)
|
||||
line.markers = marker_str
|
||||
line.parsed_marker = new_marker
|
||||
if getattr(line, "_requirement", None) is not None:
|
||||
line._requirement.marker = new_marker
|
||||
if getattr(line, "_ireq", None) is not None and line._ireq.req:
|
||||
line._ireq.req.marker = new_marker
|
||||
new_ireq = getattr(self, "ireq", None)
|
||||
if new_ireq and new_ireq.req:
|
||||
new_ireq.req.marker = new_marker
|
||||
req = self.req
|
||||
if req.req:
|
||||
req_requirement = req.req
|
||||
req_requirement.marker = new_marker
|
||||
req = attr.evolve(req, req=req_requirement, parsed_line=line)
|
||||
return attr.evolve(
|
||||
self, markers=str(new_marker), ireq=new_ireq, req=req, line_instance=line
|
||||
)
|
||||
|
||||
|
||||
def file_req_from_parsed_line(parsed_line):
|
||||
|
||||
+668
-276
File diff suppressed because it is too large
Load Diff
+6
@@ -203,6 +203,12 @@ class URI(object):
|
||||
fragment = ""
|
||||
if parsed_dict["fragment"] is not None:
|
||||
fragment = "{0}".format(parsed_dict["fragment"])
|
||||
if fragment.startswith("egg="):
|
||||
name, extras = pip_shims.shims._strip_extras(name_with_extras)
|
||||
fragment_name, fragment_extras = pip_shims.shims._strip_extras(fragment)
|
||||
if fragment_extras and not extras:
|
||||
name_with_extras = "{0}{1}".format(name, fragment_extras)
|
||||
fragment = ""
|
||||
elif "&subdirectory" in parsed_dict["path"]:
|
||||
path, fragment = cls.parse_subdirectory(parsed_dict["path"])
|
||||
parsed_dict["path"] = path
|
||||
|
||||
+102
-22
@@ -17,14 +17,17 @@ from first import first
|
||||
from packaging.markers import InvalidMarker, Marker, Op, Value, Variable
|
||||
from packaging.specifiers import InvalidSpecifier, Specifier, SpecifierSet
|
||||
from packaging.version import parse as parse_version
|
||||
from plette.models import Package, PackageCollection
|
||||
from six.moves.urllib import parse as urllib_parse
|
||||
from tomlkit.container import Container
|
||||
from tomlkit.items import AoT, Array, Bool, InlineTable, Item, String, Table
|
||||
from urllib3 import util as urllib3_util
|
||||
from vistir.compat import lru_cache
|
||||
from vistir.misc import dedup
|
||||
from vistir.path import is_valid_url
|
||||
|
||||
from ..environment import MYPY_RUNNING
|
||||
from ..utils import SCHEME_LIST, VCS_LIST, add_ssh_scheme_to_git_uri, is_star
|
||||
from ..utils import SCHEME_LIST, VCS_LIST, is_star
|
||||
|
||||
if MYPY_RUNNING:
|
||||
from typing import (
|
||||
@@ -40,9 +43,9 @@ if MYPY_RUNNING:
|
||||
Text,
|
||||
AnyStr,
|
||||
Match,
|
||||
Iterable,
|
||||
Iterable, # noqa
|
||||
)
|
||||
from attr import _ValidatorType
|
||||
from attr import _ValidatorType # noqa
|
||||
from packaging.requirements import Requirement as PackagingRequirement
|
||||
from pkg_resources import Requirement as PkgResourcesRequirement
|
||||
from pkg_resources.extern.packaging.markers import (
|
||||
@@ -62,9 +65,13 @@ if MYPY_RUNNING:
|
||||
MarkerTuple = Tuple[TVariable, TOp, TValue]
|
||||
TRequirement = Union[PackagingRequirement, PkgResourcesRequirement]
|
||||
STRING_TYPE = Union[bytes, str, Text]
|
||||
TOML_DICT_TYPES = Union[Container, Package, PackageCollection, Table, InlineTable]
|
||||
S = TypeVar("S", bytes, str, Text)
|
||||
|
||||
|
||||
TOML_DICT_OBJECTS = (Container, Package, Table, InlineTable, PackageCollection)
|
||||
TOML_DICT_NAMES = [o.__class__.__name__ for o in TOML_DICT_OBJECTS]
|
||||
|
||||
HASH_STRING = " --hash={0}"
|
||||
|
||||
ALPHA_NUMERIC = r"[{0}{1}]".format(string.ascii_letters, string.digits)
|
||||
@@ -111,6 +118,60 @@ def create_link(link):
|
||||
return Link(link)
|
||||
|
||||
|
||||
def tomlkit_value_to_python(toml_value):
|
||||
# type: (Union[Array, AoT, TOML_DICT_TYPES, Item]) -> Union[List, Dict]
|
||||
value_type = type(toml_value).__name__
|
||||
if (
|
||||
isinstance(toml_value, TOML_DICT_OBJECTS + (dict,))
|
||||
or value_type in TOML_DICT_NAMES
|
||||
):
|
||||
return tomlkit_dict_to_python(toml_value)
|
||||
elif isinstance(toml_value, AoT) or value_type == "AoT":
|
||||
return [tomlkit_value_to_python(val) for val in toml_value._body]
|
||||
elif isinstance(toml_value, Array) or value_type == "Array":
|
||||
return [tomlkit_value_to_python(val) for val in list(toml_value)]
|
||||
elif isinstance(toml_value, String) or value_type == "String":
|
||||
return "{0!s}".format(toml_value)
|
||||
elif isinstance(toml_value, Bool) or value_type == "Bool":
|
||||
return toml_value.value
|
||||
elif isinstance(toml_value, Item):
|
||||
return toml_value.value
|
||||
return toml_value
|
||||
|
||||
|
||||
def tomlkit_dict_to_python(toml_dict):
|
||||
# type: (TOML_DICT_TYPES) -> Dict
|
||||
value_type = type(toml_dict).__name__
|
||||
if toml_dict is None:
|
||||
raise TypeError("Invalid type NoneType when converting toml dict to python")
|
||||
converted = None # type: Optional[Dict]
|
||||
if isinstance(toml_dict, (InlineTable, Table)) or value_type in (
|
||||
"InlineTable",
|
||||
"Table",
|
||||
):
|
||||
converted = toml_dict.value
|
||||
elif isinstance(toml_dict, (Package, PackageCollection)) or value_type in (
|
||||
"Package, PackageCollection"
|
||||
):
|
||||
converted = toml_dict._data
|
||||
if isinstance(converted, Container) or type(converted).__name__ == "Container":
|
||||
converted = converted.value
|
||||
elif isinstance(toml_dict, Container) or value_type == "Container":
|
||||
converted = toml_dict.value
|
||||
elif isinstance(toml_dict, dict):
|
||||
converted = toml_dict.copy()
|
||||
else:
|
||||
raise TypeError(
|
||||
"Invalid type for conversion: expected Container, Dict, or Table, "
|
||||
"got {0!r}".format(toml_dict)
|
||||
)
|
||||
if isinstance(converted, dict):
|
||||
return {k: tomlkit_value_to_python(v) for k, v in converted.items()}
|
||||
elif isinstance(converted, (TOML_DICT_OBJECTS)) or value_type in TOML_DICT_NAMES:
|
||||
return tomlkit_dict_to_python(converted)
|
||||
return converted
|
||||
|
||||
|
||||
def get_url_name(url):
|
||||
# type: (AnyStr) -> AnyStr
|
||||
"""
|
||||
@@ -142,7 +203,12 @@ def init_requirement(name):
|
||||
|
||||
def extras_to_string(extras):
|
||||
# type: (Iterable[S]) -> S
|
||||
"""Turn a list of extras into a string"""
|
||||
"""Turn a list of extras into a string
|
||||
|
||||
:param List[str]] extras: a list of extras to format
|
||||
:return: A string of extras
|
||||
:rtype: str
|
||||
"""
|
||||
if isinstance(extras, six.string_types):
|
||||
if extras.startswith("["):
|
||||
return extras
|
||||
@@ -155,8 +221,11 @@ def extras_to_string(extras):
|
||||
|
||||
def parse_extras(extras_str):
|
||||
# type: (AnyStr) -> List[AnyStr]
|
||||
"""
|
||||
Turn a string of extras into a parsed extras list
|
||||
"""Turn a string of extras into a parsed extras list
|
||||
|
||||
:param str extras_str: An extras string
|
||||
:return: A sorted list of extras
|
||||
:rtype: List[str]
|
||||
"""
|
||||
|
||||
from pkg_resources import Requirement
|
||||
@@ -167,8 +236,11 @@ def parse_extras(extras_str):
|
||||
|
||||
def specs_to_string(specs):
|
||||
# type: (List[Union[STRING_TYPE, Specifier]]) -> AnyStr
|
||||
"""
|
||||
Turn a list of specifier tuples into a string
|
||||
"""Turn a list of specifier tuples into a string
|
||||
|
||||
:param List[Union[Specifier, str]] specs: a list of specifiers to format
|
||||
:return: A string of specifiers
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
if specs:
|
||||
@@ -212,7 +284,8 @@ def build_vcs_uri(
|
||||
|
||||
def convert_direct_url_to_url(direct_url):
|
||||
# type: (AnyStr) -> AnyStr
|
||||
"""
|
||||
"""Converts direct URLs to standard, link-style URLs
|
||||
|
||||
Given a direct url as defined by *PEP 508*, convert to a :class:`~pip_shims.shims.Link`
|
||||
compatible URL by moving the name and extras into an **egg_fragment**.
|
||||
|
||||
@@ -253,6 +326,8 @@ def convert_direct_url_to_url(direct_url):
|
||||
def convert_url_to_direct_url(url, name=None):
|
||||
# type: (AnyStr, Optional[AnyStr]) -> AnyStr
|
||||
"""
|
||||
Converts normal link-style URLs to direct urls.
|
||||
|
||||
Given a :class:`~pip_shims.shims.Link` compatible URL, convert to a direct url as
|
||||
defined by *PEP 508* by extracting the name and extras from the **egg_fragment**.
|
||||
|
||||
@@ -303,7 +378,7 @@ def get_version(pipfile_entry):
|
||||
if str(pipfile_entry) == "{}" or is_star(pipfile_entry):
|
||||
return ""
|
||||
|
||||
elif hasattr(pipfile_entry, "keys") and "version" in pipfile_entry:
|
||||
if hasattr(pipfile_entry, "keys") and "version" in pipfile_entry:
|
||||
if is_star(pipfile_entry.get("version")):
|
||||
return ""
|
||||
return pipfile_entry.get("version", "").strip().lstrip("(").rstrip(")")
|
||||
@@ -316,6 +391,8 @@ def get_version(pipfile_entry):
|
||||
def strip_extras_markers_from_requirement(req):
|
||||
# type: (TRequirement) -> TRequirement
|
||||
"""
|
||||
Strips extras markers from requirement instances.
|
||||
|
||||
Given a :class:`~packaging.requirements.Requirement` instance with markers defining
|
||||
*extra == 'name'*, strip out the extras from the markers and return the cleaned
|
||||
requirement
|
||||
@@ -389,7 +466,6 @@ def get_pyproject(path):
|
||||
:return: A 2 tuple of build requirements and the build backend
|
||||
:rtype: Optional[Tuple[List[AnyStr], AnyStr]]
|
||||
"""
|
||||
|
||||
if not path:
|
||||
return
|
||||
from vistir.compat import Path
|
||||
@@ -519,8 +595,7 @@ def key_from_req(req):
|
||||
|
||||
|
||||
def _requirement_to_str_lowercase_name(requirement):
|
||||
"""
|
||||
Formats a packaging.requirements.Requirement with a lowercase name.
|
||||
"""Formats a packaging.requirements.Requirement with a lowercase name.
|
||||
|
||||
This is simply a copy of
|
||||
https://github.com/pypa/packaging/blob/16.8/packaging/requirements.py#L109-L124
|
||||
@@ -531,7 +606,6 @@ def _requirement_to_str_lowercase_name(requirement):
|
||||
important stuff that should not be lower-cased (such as the marker). See
|
||||
this issue for more information: https://github.com/pypa/pipenv/issues/2113.
|
||||
"""
|
||||
|
||||
parts = [requirement.name.lower()]
|
||||
|
||||
if requirement.extras:
|
||||
@@ -550,11 +624,15 @@ def _requirement_to_str_lowercase_name(requirement):
|
||||
|
||||
|
||||
def format_requirement(ireq):
|
||||
"""
|
||||
"""Formats an `InstallRequirement` instance as a string.
|
||||
|
||||
Generic formatter for pretty printing InstallRequirements to the terminal
|
||||
in a less verbose way than using its `__str__` method.
|
||||
"""
|
||||
|
||||
:param :class:`InstallRequirement` ireq: A pip **InstallRequirement** instance.
|
||||
:return: A formatted string for prettyprinting
|
||||
:rtype: str
|
||||
"""
|
||||
if ireq.editable:
|
||||
line = "-e {}".format(ireq.link)
|
||||
else:
|
||||
@@ -572,9 +650,13 @@ def format_requirement(ireq):
|
||||
|
||||
|
||||
def format_specifier(ireq):
|
||||
"""
|
||||
Generic formatter for pretty printing the specifier part of
|
||||
InstallRequirements to the terminal.
|
||||
"""Generic formatter for pretty printing specifiers.
|
||||
|
||||
Pretty-prints specifiers from InstallRequirements for output to terminal.
|
||||
|
||||
:param :class:`InstallRequirement` ireq: A pip **InstallRequirement** instance.
|
||||
:return: A string of specifiers in the given install requirement or <any>
|
||||
:rtype: str
|
||||
"""
|
||||
# TODO: Ideally, this is carried over to the pip library itself
|
||||
specs = ireq.specifier._specs if ireq.req is not None else []
|
||||
@@ -583,8 +665,7 @@ def format_specifier(ireq):
|
||||
|
||||
|
||||
def get_pinned_version(ireq):
|
||||
"""
|
||||
Get the pinned version of an InstallRequirement.
|
||||
"""Get the pinned version of an InstallRequirement.
|
||||
|
||||
An InstallRequirement is considered pinned if:
|
||||
|
||||
@@ -602,7 +683,6 @@ def get_pinned_version(ireq):
|
||||
Raises `TypeError` if the input is not a valid InstallRequirement, or
|
||||
`ValueError` if the InstallRequirement is not pinned.
|
||||
"""
|
||||
|
||||
try:
|
||||
specifier = ireq.specifier
|
||||
except AttributeError:
|
||||
|
||||
+15
-58
@@ -1,51 +1,40 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
import contextlib
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pip_shims.shims
|
||||
import six
|
||||
import six.moves
|
||||
import tomlkit
|
||||
import vistir
|
||||
from six.moves.urllib.parse import urlparse, urlsplit, urlunparse
|
||||
from vistir.compat import Path
|
||||
from vistir.path import create_tracked_tempdir, ensure_mkdir_p, is_valid_url
|
||||
from vistir.path import ensure_mkdir_p, is_valid_url
|
||||
|
||||
from .environment import MYPY_RUNNING
|
||||
|
||||
# fmt: off
|
||||
six.add_move(
|
||||
six.MovedAttribute("Mapping", "collections", "collections.abc")
|
||||
) # type: ignore # noqa # isort:skip
|
||||
six.add_move(
|
||||
six.MovedAttribute("Sequence", "collections", "collections.abc")
|
||||
) # type: ignore # noqa # isort:skip
|
||||
six.add_move(
|
||||
six.MovedAttribute("Set", "collections", "collections.abc")
|
||||
) # type: ignore # noqa # isort:skip
|
||||
six.add_move(
|
||||
six.MovedAttribute("ItemsView", "collections", "collections.abc")
|
||||
) # type: ignore # noqa
|
||||
six.add_move( # type: ignore
|
||||
six.MovedAttribute("Mapping", "collections", "collections.abc") # type: ignore
|
||||
) # noqa # isort:skip
|
||||
six.add_move( # type: ignore
|
||||
six.MovedAttribute("Sequence", "collections", "collections.abc") # type: ignore
|
||||
) # noqa # isort:skip
|
||||
six.add_move( # type: ignore
|
||||
six.MovedAttribute("Set", "collections", "collections.abc") # type: ignore
|
||||
) # noqa # isort:skip
|
||||
six.add_move( # type: ignore
|
||||
six.MovedAttribute("ItemsView", "collections", "collections.abc") # type: ignore
|
||||
) # noqa
|
||||
from six.moves import ItemsView, Mapping, Sequence, Set # type: ignore # noqa # isort:skip
|
||||
# fmt: on
|
||||
|
||||
|
||||
if MYPY_RUNNING:
|
||||
from typing import (
|
||||
Dict,
|
||||
Any,
|
||||
Optional,
|
||||
Union,
|
||||
Tuple,
|
||||
List,
|
||||
Iterable,
|
||||
Generator,
|
||||
Text,
|
||||
TypeVar,
|
||||
)
|
||||
from typing import Dict, Any, Optional, Union, Tuple, List, Iterable, Text, TypeVar
|
||||
|
||||
STRING_TYPE = Union[bytes, str, Text]
|
||||
S = TypeVar("S", bytes, str, Text)
|
||||
@@ -169,14 +158,6 @@ def is_editable(pipfile_entry):
|
||||
return False
|
||||
|
||||
|
||||
def multi_split(s, split):
|
||||
# type: (S, Iterable[S]) -> List[S]
|
||||
"""Splits on multiple given separators."""
|
||||
for r in split:
|
||||
s = s.replace(r, "|")
|
||||
return [i for i in s.split("|") if len(i) > 0]
|
||||
|
||||
|
||||
def is_star(val):
|
||||
# type: (PipfileType) -> bool
|
||||
return (isinstance(val, six.string_types) and val == "*") or (
|
||||
@@ -318,30 +299,6 @@ def _ensure_dir(path):
|
||||
return path
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def ensure_setup_py(base):
|
||||
# type: (STRING_TYPE) -> Generator[None, None, None]
|
||||
if not base:
|
||||
base = create_tracked_tempdir(prefix="requirementslib-setup")
|
||||
base_dir = Path(base)
|
||||
if base_dir.exists() and base_dir.name == "setup.py":
|
||||
base_dir = base_dir.parent
|
||||
elif not (base_dir.exists() and base_dir.is_dir()):
|
||||
base_dir = base_dir.parent
|
||||
if not (base_dir.exists() and base_dir.is_dir()):
|
||||
base_dir = base_dir.parent
|
||||
setup_py = base_dir.joinpath("setup.py")
|
||||
|
||||
is_new = False if setup_py.exists() else True
|
||||
if not setup_py.exists():
|
||||
setup_py.write_text(u"")
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
if is_new:
|
||||
setup_py.unlink()
|
||||
|
||||
|
||||
_UNSET = object()
|
||||
_REMAP_EXIT = object()
|
||||
|
||||
|
||||
Vendored
+2
-2
@@ -37,7 +37,7 @@ if _scandir is None and ctypes is None:
|
||||
warnings.warn("scandir can't find the compiled _scandir C module "
|
||||
"or ctypes, using slow generic fallback")
|
||||
|
||||
__version__ = '1.9.0'
|
||||
__version__ = '1.10.0'
|
||||
__all__ = ['scandir', 'walk']
|
||||
|
||||
# Windows FILE_ATTRIBUTE constants for interpreting the
|
||||
@@ -583,7 +583,7 @@ elif sys.platform.startswith(('linux', 'darwin', 'sunos5')) or 'bsd' in sys.plat
|
||||
if _scandir is not None:
|
||||
scandir = scandir_c
|
||||
DirEntry = DirEntry_c
|
||||
elif ctypes is not None:
|
||||
elif ctypes is not None and have_dirent_d_type:
|
||||
scandir = scandir_python
|
||||
DirEntry = PosixDirEntry
|
||||
else:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user