mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 14:50:16 +00:00
Merge branch 'master' into master
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.toml]
|
||||
indent_size = 2
|
||||
|
||||
[*.yaml]
|
||||
indent_size = 2
|
||||
|
||||
# Makefiles always use tabs for indentation
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
# Batch files use tabs for indentation
|
||||
[*.bat]
|
||||
indent_style = tab
|
||||
@@ -0,0 +1,20 @@
|
||||
phases:
|
||||
- phase:
|
||||
displayName: Docs
|
||||
queue: Hosted Linux Preview
|
||||
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
versionSpec: '3.7'
|
||||
|
||||
- template: steps/install-dependencies.yml
|
||||
|
||||
- bash: tox -e docs
|
||||
displayName: Build docs
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: 'Publish Artifact: docs'
|
||||
inputs:
|
||||
PathtoPublish: docs/build
|
||||
ArtifactName: docs
|
||||
@@ -0,0 +1,8 @@
|
||||
phases:
|
||||
- template: phases/test.yml
|
||||
parameters:
|
||||
queue: Hosted Linux Preview
|
||||
|
||||
- template: phases/run-vendor-scripts.yml
|
||||
parameters:
|
||||
queue: Hosted Linux Preview
|
||||
@@ -0,0 +1,14 @@
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
displayName: Use Python $(python.version)
|
||||
inputs:
|
||||
versionSpec: '$(python.version)'
|
||||
architecture: '$(python.architecture)'
|
||||
|
||||
- template: ../steps/install-dependencies.yml
|
||||
|
||||
- bash: |
|
||||
export GIT_SSL_CAINFO=$(python -m certifi)
|
||||
export LANG=C.UTF-8
|
||||
python -m pip install check-manifest
|
||||
check-manifest
|
||||
@@ -0,0 +1,12 @@
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
displayName: Use Python $(python.version)
|
||||
inputs:
|
||||
versionSpec: '$(python.version)'
|
||||
architecture: '$(python.architecture)'
|
||||
|
||||
- template: ../steps/install-dependencies.yml
|
||||
|
||||
- template: ../steps/create-virtualenv.yml
|
||||
|
||||
- template: ../steps/run-tests.yml
|
||||
@@ -0,0 +1,37 @@
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
displayName: Use Python $(python.version)
|
||||
inputs:
|
||||
versionSpec: '$(python.version)'
|
||||
architecture: '$(python.architecture)'
|
||||
|
||||
- template: ../steps/install-dependencies.yml
|
||||
|
||||
- bash: |
|
||||
mkdir -p "$AGENT_HOMEDIRECTORY/.virtualenvs"
|
||||
mkdir -p "$WORKON_HOME"
|
||||
pip install certifi
|
||||
export GIT_SSL_CAINFO=$(python -m certifi)
|
||||
export LANG=C.UTF-8
|
||||
export PIP_PROCESS_DEPENDENCY_LINKS="1"
|
||||
echo "Path: $PATH"
|
||||
echo "Installing Pipenv…"
|
||||
pip install -e "$(pwd)" --upgrade
|
||||
pipenv install --deploy --dev
|
||||
echo pipenv --venv && echo pipenv --py && echo pipenv run python --version
|
||||
displayName: Make Virtualenv
|
||||
|
||||
- script: |
|
||||
# Fix Git SSL errors
|
||||
export GIT_SSL_CAINFO=$(python -m certifi)
|
||||
export LANG=C.UTF-8
|
||||
export PIP_PROCESS_DEPENDENCY_LINKS="1"
|
||||
pipenv run pytest --junitxml=test-results.xml
|
||||
displayName: Run integration tests
|
||||
|
||||
- task: PublishTestResults@2
|
||||
displayName: Publish Test Results
|
||||
inputs:
|
||||
testResultsFiles: '**/test-results.xml'
|
||||
testRunTitle: 'Python $(python.version)'
|
||||
condition: succeededOrFailed()
|
||||
@@ -0,0 +1,39 @@
|
||||
parameters:
|
||||
queue:
|
||||
|
||||
phases:
|
||||
- phase: Vendor_Scripts
|
||||
displayName: Test Vendor Scripts
|
||||
|
||||
queue:
|
||||
name: ${{ parameters.queue }}
|
||||
parallel: 4
|
||||
matrix:
|
||||
${{ if eq(parameters.queue, 'Hosted VS2017') }}:
|
||||
# TODO remove once Hosted VS2017 has Python 3.7
|
||||
Python37:
|
||||
python.version: '>= 3.7.0-b2'
|
||||
python.architecture: x64
|
||||
${{ if ne(parameters.queue, 'Hosted VS2017' )}}:
|
||||
Python37:
|
||||
python.version: '>= 3.7'
|
||||
python.architecture: x64
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
displayName: Use Python $(python.version)
|
||||
inputs:
|
||||
versionSpec: '$(python.version)'
|
||||
architecture: '$(python.architecture)'
|
||||
|
||||
- template: ../steps/install-dependencies.yml
|
||||
|
||||
- bash: |
|
||||
mkdir -p "$AGENT_HOMEDIRECTORY/.virtualenvs"
|
||||
mkdir -p "$WORKON_HOME"
|
||||
pip install certifi
|
||||
export GIT_SSL_CAINFO=$(python -m certifi)
|
||||
export LANG=C.UTF-8
|
||||
python -m pip install --upgrade invoke requests parver
|
||||
python -m invoke vendoring.update
|
||||
|
||||
- template: ./run-manifest-check.yml
|
||||
@@ -0,0 +1,49 @@
|
||||
parameters:
|
||||
queue:
|
||||
|
||||
phases:
|
||||
- phase: Test_Primary
|
||||
displayName: Test Primary
|
||||
|
||||
queue:
|
||||
name: ${{ parameters.queue }}
|
||||
parallel: 4
|
||||
matrix:
|
||||
Python27:
|
||||
python.version: '2.7'
|
||||
python.architecture: x64
|
||||
${{ if eq(parameters.queue, 'Hosted VS2017') }}:
|
||||
# TODO remove once Hosted VS2017 has Python 3.7
|
||||
Python37:
|
||||
python.version: '>= 3.7.0-b2'
|
||||
python.architecture: x64
|
||||
${{ if ne(parameters.queue, 'Hosted VS2017' )}}:
|
||||
Python37:
|
||||
python.version: '>= 3.7'
|
||||
python.architecture: x64
|
||||
steps:
|
||||
- ${{ if eq(parameters.queue, 'Hosted VS2017') }}:
|
||||
- template: ./run-tests-windows.yml
|
||||
|
||||
- ${{ if ne(parameters.queue, 'Hosted VS2017') }}:
|
||||
- template: ./run-tests.yml
|
||||
|
||||
- phase: 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
|
||||
|
||||
queue:
|
||||
name: ${{ parameters.queue }}
|
||||
parallel: 4
|
||||
matrix:
|
||||
Python36:
|
||||
python.version: '3.6'
|
||||
python.architecture: x64
|
||||
steps:
|
||||
- ${{ if eq(parameters.queue, 'Hosted VS2017') }}:
|
||||
- template: ./run-tests-windows.yml
|
||||
|
||||
- ${{ if ne(parameters.queue, 'Hosted VS2017') }}:
|
||||
- template: ./run-tests.yml
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
steps:
|
||||
- script: |
|
||||
virtualenv D:\.venv
|
||||
D:\.venv\Scripts\pip.exe install -e . && D:\.venv\Scripts\pipenv install --dev
|
||||
echo D:\.venv\Scripts\pipenv --venv && echo D:\.venv\Scripts\pipenv --py && echo D:\.venv\Scripts\pipenv run python --version
|
||||
displayName: Make Virtualenv
|
||||
@@ -0,0 +1,3 @@
|
||||
steps:
|
||||
- script: 'python -m pip install --upgrade pip && python -m pip install -e .'
|
||||
displayName: Upgrade Pip & Install Pipenv
|
||||
@@ -0,0 +1,20 @@
|
||||
steps:
|
||||
- powershell: |
|
||||
# Fix Git SSL errors
|
||||
pip install certifi
|
||||
python -m certifi > 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
|
||||
$env:TEMP = "T:\"
|
||||
$env:TMP = "T:\"
|
||||
D:\.venv\Scripts\pipenv run pytest -n 4 --ignore=pipenv\patched --ignore=pipenv\vendor --junitxml=test-results.xml tests
|
||||
displayName: Run integration tests
|
||||
|
||||
- task: PublishTestResults@2
|
||||
displayName: Publish Test Results
|
||||
inputs:
|
||||
testResultsFiles: '**/test-results.xml'
|
||||
testRunTitle: 'Python $(python.version)'
|
||||
condition: succeededOrFailed()
|
||||
@@ -0,0 +1,4 @@
|
||||
phases:
|
||||
- template: phases/test.yml
|
||||
parameters:
|
||||
queue: Hosted VS2017
|
||||
+32
-5
@@ -1,6 +1,33 @@
|
||||
include README.md CHANGELOG.rst LICENSE NOTICES HISTORY.txt pipenv/patched/safety.zip
|
||||
include pipenv/vendor/pipreqs/stdlib
|
||||
include pipenv/vendor/pipreqs/mapping
|
||||
include pipenv/vendor/click_completion/*.j2
|
||||
include LICENSE README.md CONTRIBUTING.md CODE_OF_CONDUCT.md CHANGELOG.rst NOTICES HISTORY.txt
|
||||
include Makefile pyproject.toml get-pipenv.py
|
||||
include examples/Pipfil*
|
||||
recursive-include pipenv LICENSE LICENSE* *LICENSE* *COPYING* t32.exe t64.exe w32.exe w64.exe cacert.pem
|
||||
recursive-include pipenv *.cfg
|
||||
recursive-include pipenv/vendor *.c *.j2
|
||||
recursive-include pipenv *.md *.APACHE *.BSD
|
||||
recursive-include pipenv Makefile
|
||||
recursive-include pipenv/vendor vendor.txt
|
||||
recursive-include pipenv README
|
||||
include pipenv/patched/notpip/_vendor/vendor.txt
|
||||
include pipenv/patched/safety.zip pipenv/patched/patched.txt
|
||||
include pipenv/vendor/pipreqs/stdlib pipenv/vendor/pipreqs/mapping
|
||||
include pipenv/vendor/*.txt pipenv/vendor/pexpect/bashrc.sh
|
||||
include pipenv/vendor/Makefile
|
||||
include pipenv/pipenv.1
|
||||
recursive-include pipenv *LICENSE* *COPYING* t32.exe t64.exe w32.exe w64.exe cacert.pem
|
||||
exclude .editorconfig .travis.yml .env appveyor.yml tox.ini pytest.ini
|
||||
exclude Pipfile* CHANGELOG.draft.rst
|
||||
exclude docker-compose.yml Dockerfile
|
||||
exclude run-tests.sh run-tests.bat
|
||||
|
||||
recursive-include docs Makefile *.rst *.py *.bat
|
||||
recursive-include docs/_templates *.html
|
||||
recursive-include docs/_static *.js *.css *.png
|
||||
recursive-exclude docs requirements*.txt
|
||||
|
||||
prune .buildkite
|
||||
prune .github
|
||||
prune .vsts-ci
|
||||
prune docs/build
|
||||
prune news
|
||||
prune tasks
|
||||
prune tests
|
||||
|
||||
@@ -20,6 +20,7 @@ invoke = "*"
|
||||
jedi = "*"
|
||||
isort = "*"
|
||||
rope = "*"
|
||||
passa = {editable = true, git = "https://github.com/sarugaku/passa.git"}
|
||||
|
||||
[packages]
|
||||
|
||||
|
||||
Generated
+152
-52
@@ -1,15 +1,17 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "88d256c1798cc297772ecd3d2152013e0b28201a5364a1c0f8e4dde79b6e200c"
|
||||
"sha256": "6acc712d82698e574727d19b22d05bf46565ecaa414e288fd0d79e385f8fdd10"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
"sources": [{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}]
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {},
|
||||
"develop": {
|
||||
@@ -22,10 +24,11 @@
|
||||
},
|
||||
"apipkg": {
|
||||
"hashes": [
|
||||
"sha256:2e38399dbe842891fe85392601aab8f40a8f4cc5a9053c326de35a1cc0297ac6",
|
||||
"sha256:65d2aa68b28e7d31233bb2ba8eb31cda40e4671f8ac2d6b241e358c9652a74b9"
|
||||
"sha256:37228cda29411948b422fae072f57e31d3396d2ee1c9783775980ee9c9990af6",
|
||||
"sha256:58587dd4dc3daefad0487f6d9ae32b4542b185e1c36db6993290e7c41ca2b47c"
|
||||
],
|
||||
"version": "==1.4"
|
||||
"markers": "python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.3.*' and python_version != '3.0.*'",
|
||||
"version": "==1.5"
|
||||
},
|
||||
"appdirs": {
|
||||
"hashes": [
|
||||
@@ -34,12 +37,20 @@
|
||||
],
|
||||
"version": "==1.4.3"
|
||||
},
|
||||
"argparse": {
|
||||
"hashes": [
|
||||
"sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4",
|
||||
"sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314"
|
||||
],
|
||||
"markers": "python_version == '2.6'",
|
||||
"version": "==1.4.0"
|
||||
},
|
||||
"arpeggio": {
|
||||
"hashes": [
|
||||
"sha256:828ea85ca3c7a99125dc83000ca170c1ea1105c75cbf67a513c2e16e05e36f67",
|
||||
"sha256:984a53471327bbb69ed528cac98fa6d42c1676300d047fc13fada69dd5f84ce4"
|
||||
"sha256:a5258b84f76661d558492fa87e42db634df143685a0e51802d59cae7daad8732",
|
||||
"sha256:dc5c0541e7cc2c6033dc0338133436abfac53655624784736e9bc8bd35e56583"
|
||||
],
|
||||
"version": "==1.8.0"
|
||||
"version": "==1.9.0"
|
||||
},
|
||||
"atomicwrites": {
|
||||
"hashes": [
|
||||
@@ -71,12 +82,18 @@
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==18.6b4"
|
||||
},
|
||||
"cerberus": {
|
||||
"hashes": [
|
||||
"sha256:f5c2e048fb15ecb3c088d192164316093fcfa602a74b3386eefb2983aa7e800a"
|
||||
],
|
||||
"version": "==1.2"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7",
|
||||
"sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0"
|
||||
"sha256:4c1d68a1408dd090d2f3a869aa94c3947cc1d967821d1ed303208c9f41f0f2f4",
|
||||
"sha256:b6e8b28b2b7e771a41ecdd12d4d43262ecab52adebbafa42c77d6b57fb6ad3a4"
|
||||
],
|
||||
"version": "==2018.4.16"
|
||||
"version": "==2018.8.13"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
@@ -108,6 +125,12 @@
|
||||
"markers": "python_version < '3.2'",
|
||||
"version": "==3.5.0"
|
||||
},
|
||||
"distlib": {
|
||||
"hashes": [
|
||||
"sha256:cd502c66fc27c535bab62dc4f482e403e2369c2c05281a79cc2d4e2f42a87f20"
|
||||
],
|
||||
"version": "==0.2.7"
|
||||
},
|
||||
"docutils": {
|
||||
"hashes": [
|
||||
"sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
|
||||
@@ -131,8 +154,16 @@
|
||||
"sha256:a7a84d5fa07a089186a329528f127c9d73b9de57f1a1131b82bb5320ee651f6a",
|
||||
"sha256:fc155a6b553c66c838d1a22dba1dc9f5f505c43285a878c6f74a79c024750b83"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.3.*' and python_version != '3.0.*'",
|
||||
"version": "==1.5.0"
|
||||
},
|
||||
"first": {
|
||||
"hashes": [
|
||||
"sha256:3bb3de3582cb27071cfb514f00ed784dc444b7f96dc21e140de65fe00585c95e",
|
||||
"sha256:41d5b64e70507d0c3ca742d68010a76060eea8a3d863e9b5130ab11a4a91aa0e"
|
||||
],
|
||||
"version": "==2.0.1"
|
||||
},
|
||||
"flake8": {
|
||||
"hashes": [
|
||||
"sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0",
|
||||
@@ -161,9 +192,17 @@
|
||||
"sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca",
|
||||
"sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"
|
||||
],
|
||||
"markers": "python_version < '3.0'",
|
||||
"markers": "python_version < '3.3'",
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"futures": {
|
||||
"hashes": [
|
||||
"sha256:9ec02aa7d674acb8618afb127e27fde7fc68994c0437ad759fa094a574adb265",
|
||||
"sha256:ec0a6cb848cc212002b9828c3e34c675e0c9ff6741dc445cab6fdd4e1085d1f1"
|
||||
],
|
||||
"markers": "python_version < '3' and python_version >= '2.6'",
|
||||
"version": "==3.2.0"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
|
||||
@@ -187,12 +226,12 @@
|
||||
},
|
||||
"invoke": {
|
||||
"hashes": [
|
||||
"sha256:21274204515dca62206470b088bbcf9d41ffda82b3715b90e01d71b7a4681921",
|
||||
"sha256:4a4cc031db311cbfb3fdd8ec93a06c892533c27b931f4be14b24c97cd042b14e",
|
||||
"sha256:621b6564f992c37166e16090d7e7cccb3b922e03a58e980dfa5e543a931b652f"
|
||||
"sha256:1c2cf54c9b9af973ad9704d8ba81b225117cab612568cacbfb3fc42958cc20a9",
|
||||
"sha256:334495ea16e73948894e9535019f87a88a44b73e7977492b12c2d1b5085f8197",
|
||||
"sha256:54bdd3fd0245abd1185e05359fd2e4f26be0657cfe7d7bb1bed735e054fa53ab"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.0.0"
|
||||
"version": "==1.1.1"
|
||||
},
|
||||
"isort": {
|
||||
"hashes": [
|
||||
@@ -245,13 +284,21 @@
|
||||
"index": "pypi",
|
||||
"version": "==2.0.0"
|
||||
},
|
||||
"modutil": {
|
||||
"hashes": [
|
||||
"sha256:2c85c1666649e92e56de17c00e1e831313602d9b55e8661d39c01e39003b45f7",
|
||||
"sha256:cc3dad264e36ed359fdd67c4588959d2996bd0402ad9c9d974ca906821537218"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==2.0.0"
|
||||
},
|
||||
"more-itertools": {
|
||||
"hashes": [
|
||||
"sha256:2b6b9893337bfd9166bee6a62c2b0c9fe7735dcf85948b387ec8cba30e85d8e8",
|
||||
"sha256:6703844a52d3588f951883005efcf555e49566a48afd4db4e965d69b883980d3",
|
||||
"sha256:a18d870ef2ffca2b8463c0070ad17b5978056f403fb64e3f15fe62a52db21cc0"
|
||||
"sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092",
|
||||
"sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e",
|
||||
"sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d"
|
||||
],
|
||||
"version": "==4.2.0"
|
||||
"version": "==4.3.0"
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
@@ -262,10 +309,10 @@
|
||||
},
|
||||
"parso": {
|
||||
"hashes": [
|
||||
"sha256:8105449d86d858e53ce3e0044ede9dd3a395b1c9716c696af8aa3787158ab806",
|
||||
"sha256:d250235e52e8f9fc5a80cc2a5f804c9fefd886b2e67a2b1099cf085f403f8e33"
|
||||
"sha256:35704a43a3c113cce4de228ddb39aab374b8004f4f2407d070b6a2ca784ce8a2",
|
||||
"sha256:895c63e93b94ac1e1690f5fdd40b65f07c8171e3e53cbd7793b5b96c0e0a7f24"
|
||||
],
|
||||
"version": "==0.3.0"
|
||||
"version": "==0.3.1"
|
||||
},
|
||||
"parver": {
|
||||
"hashes": [
|
||||
@@ -275,12 +322,24 @@
|
||||
"index": "pypi",
|
||||
"version": "==0.1.1"
|
||||
},
|
||||
"passa": {
|
||||
"editable": true,
|
||||
"git": "https://github.com/sarugaku/passa.git",
|
||||
"ref": "54e65e01744cafbcab44eb15422e1604b615caae"
|
||||
},
|
||||
"pbr": {
|
||||
"hashes": [
|
||||
"sha256:3747c6f017f2dc099986c325239661948f9f5176f6880d9fdef164cb664cd665",
|
||||
"sha256:a9c27eb8f0e24e786e544b2dbaedb729c9d8546342b5a6818d8eda098ad4340d"
|
||||
"sha256:1b8be50d938c9bb75d0eaf7eda111eec1bf6dc88a62a6412e33bf077457e0f45",
|
||||
"sha256:b486975c0cafb6beeb50ca0e17ba047647f229087bd74e37f4a7e2cac17d2caa"
|
||||
],
|
||||
"version": "==4.0.4"
|
||||
"version": "==4.2.0"
|
||||
},
|
||||
"pip-shims": {
|
||||
"hashes": [
|
||||
"sha256:9c8a568b4a8ce4000a2982224f48a35736fca81214dfdb30dcae24287866a7e4",
|
||||
"sha256:ebc2bb29ddd21fa00c0cf28a5d8c725100f2f7ee98703aba237efd02e205c1c1"
|
||||
],
|
||||
"version": "==0.1.2"
|
||||
},
|
||||
"pipenv": {
|
||||
"editable": true,
|
||||
@@ -293,21 +352,26 @@
|
||||
],
|
||||
"version": "==1.4.2"
|
||||
},
|
||||
"plette": {
|
||||
"hashes": [
|
||||
"sha256:3c2bbf439dad64d9a89459c20305b976d797ae1c2ad48a540e7022b377717851",
|
||||
"sha256:cc9490a009494395dea286f4f488e0839ac06e1361d4951cc12fe621b6f0a68c"
|
||||
],
|
||||
"markers": "python_version >= '2.6'",
|
||||
"version": "==0.1.1"
|
||||
},
|
||||
"pluggy": {
|
||||
"hashes": [
|
||||
"sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff",
|
||||
"sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c",
|
||||
"sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5"
|
||||
"sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1",
|
||||
"sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.0.*' and python_version != '3.3.*'",
|
||||
"version": "==0.6.0"
|
||||
"version": "==0.7.1"
|
||||
},
|
||||
"py": {
|
||||
"hashes": [
|
||||
"sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7",
|
||||
"sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.0.*' and python_version != '3.3.*'",
|
||||
"version": "==1.5.4"
|
||||
},
|
||||
"pycodestyle": {
|
||||
@@ -340,17 +404,18 @@
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:8ea01fc4fcc8e1b1e305252b4bc80a1528019ab99fd3b88666c9dc38d754406c",
|
||||
"sha256:90898786b3d0b880b47645bae7b51aa9bbf1e9d1e4510c2cfd15dd65c70ea0cd"
|
||||
"sha256:3459a123ad5532852d36f6f4501dfe1acf4af1dd9541834a164666aa40395b02",
|
||||
"sha256:96bfd45dbe863b447a3054145cd78a9d7f31475d2bce6111b133c0cc4f305118"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.6.2"
|
||||
"version": "==3.7.2"
|
||||
},
|
||||
"pytest-forked": {
|
||||
"hashes": [
|
||||
"sha256:e4500cd0509ec4a26535f7d4112a8cc0f17d3a41c29ffd4eab479d2a55b30805",
|
||||
"sha256:f275cb48a73fc61a6710726348e1da6d68a978f0ec0c54ece5a5fae5977e5a08"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.3.*' and python_version != '3.0.*'",
|
||||
"version": "==0.2"
|
||||
},
|
||||
"pytest-pypy": {
|
||||
@@ -367,11 +432,11 @@
|
||||
},
|
||||
"pytest-xdist": {
|
||||
"hashes": [
|
||||
"sha256:be2662264b035920ba740ed6efb1c816a83c8a22253df7766d129f6a7bfdbd35",
|
||||
"sha256:e8f5744acc270b3e7d915bdb4d5f471670f049b6fbd163d4cbd52203b075d30f"
|
||||
"sha256:3308c4f6221670432d01e0b393b333d77c1fd805532e1d64450e8140855eb51b",
|
||||
"sha256:cce08b4b7f56d34d43b365e2b3667ebb8edcf91d01c2a8fccf45c56d37e71bc1"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.22.2"
|
||||
"version": "==1.22.5"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
@@ -395,12 +460,26 @@
|
||||
],
|
||||
"version": "==0.8.0"
|
||||
},
|
||||
"requirementslib": {
|
||||
"hashes": [
|
||||
"sha256:698a566272669a470a8a439250353b0b628468ea0879f4f32e22245c2b9d9e44",
|
||||
"sha256:c7031b128e13eb8d8847f9ce409f3bfab49e8d111e7e8fca432ff8a737820653"
|
||||
],
|
||||
"version": "==1.1.1"
|
||||
},
|
||||
"resolvelib": {
|
||||
"hashes": [
|
||||
"sha256:d52f2c0762deeb2a4cc34a84371a7a5ac85e111bdc69ce9ae729d8d636606ad6",
|
||||
"sha256:eb759d43bbf50de9bf36afb9f6c269fabf9ff49084dbfad4ba67252d134bf4b5"
|
||||
],
|
||||
"version": "==0.2.1"
|
||||
},
|
||||
"rope": {
|
||||
"hashes": [
|
||||
"sha256:a09edfd2034fd50099a67822f9bd851fbd0f4e98d3b87519f6267b60e50d80d1"
|
||||
"sha256:a108c445e1cd897fe19272ab7877d172e7faf3d4148c80e7d20faba42ea8f7b2"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.10.7"
|
||||
"version": "==0.11.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
@@ -437,7 +516,6 @@
|
||||
"sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd",
|
||||
"sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.0.*' and python_version != '3.3.*'",
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"stdeb": {
|
||||
@@ -461,18 +539,26 @@
|
||||
],
|
||||
"version": "==0.9.4"
|
||||
},
|
||||
"tomlkit": {
|
||||
"hashes": [
|
||||
"sha256:4f112445d6e52a038adf23b027ccb11905fdf88976990116e8f7b171b768cedb",
|
||||
"sha256:8b84ac193aa6366769f89541cf213efe9784ac125f08164974400c43f18fcd9f"
|
||||
],
|
||||
"markers": "python_version != '3.2.*' and python_version != '3.3.*' and python_version != '3.1.*' and python_version != '3.0.*' and python_version >= '2.7'",
|
||||
"version": "==0.4.2"
|
||||
},
|
||||
"towncrier": {
|
||||
"editable": true,
|
||||
"git": "https://github.com/hawkowl/towncrier.git",
|
||||
"ref": "491eec5e07e971894aa41b7c6f7892023d81bb81"
|
||||
"ref": "3d600a813b8bb4277b8bd77360e54531ce274b58"
|
||||
},
|
||||
"tqdm": {
|
||||
"hashes": [
|
||||
"sha256:224291ee0d8c52d91b037fd90806f48c79bcd9994d3b0abc9e44b946a908fccd",
|
||||
"sha256:77b8424d41b31e68f437c6dd9cd567aebc9a860507cb42fbd880a5f822d966fe"
|
||||
"sha256:5ef526702c0d265d5a960a3b27f3971fac13c26cf0fb819294bfa71fc6026c88",
|
||||
"sha256:a3364bd83ce4777320b862e3c8a93d7da91e20a95f06ef79bed7dd71c654cafa"
|
||||
],
|
||||
"markers": "python_version != '3.1.*' and python_version != '3.0.*' and python_version >= '2.6'",
|
||||
"version": "==4.23.4"
|
||||
"version": "==4.25.0"
|
||||
},
|
||||
"twine": {
|
||||
"hashes": [
|
||||
@@ -496,7 +582,6 @@
|
||||
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
|
||||
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
|
||||
],
|
||||
"markers": "python_version != '3.1.*' and python_version < '4' and python_version != '3.2.*' and python_version >= '2.6' and python_version != '3.0.*' and python_version != '3.3.*'",
|
||||
"version": "==1.23"
|
||||
},
|
||||
"virtualenv": {
|
||||
@@ -504,7 +589,7 @@
|
||||
"sha256:2ce32cd126117ce2c539f0134eb89de91a8413a29baac49cbab3eb50e2026669",
|
||||
"sha256:ca07b4c0b54e14a91af9f34d0919790b016923d157afda5efdde55c96718f752"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.1.*' and python_version != '3.2.*'",
|
||||
"markers": "python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.1.*'",
|
||||
"version": "==16.0.0"
|
||||
},
|
||||
"virtualenv-clone": {
|
||||
@@ -514,12 +599,27 @@
|
||||
],
|
||||
"version": "==0.3.0"
|
||||
},
|
||||
"vistir": {
|
||||
"hashes": [
|
||||
"sha256:011e52dd2e09f948f638262dc39fef38998d134538705a810e88ad6d7bb94c1c",
|
||||
"sha256:f447923d4c59e8d50add4a9d8275b25a1f038f1a1a00ded50ee3c3d00a3c7f5d"
|
||||
],
|
||||
"version": "==0.1.4"
|
||||
},
|
||||
"werkzeug": {
|
||||
"hashes": [
|
||||
"sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",
|
||||
"sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b"
|
||||
],
|
||||
"version": "==0.14.1"
|
||||
},
|
||||
"wheel": {
|
||||
"hashes": [
|
||||
"sha256:0a2e54558a0628f2145d2fc822137e322412115173e8a2ddbe1c9024338ae83c",
|
||||
"sha256:80044e51ec5bbf6c894ba0bc48d26a8c20a9ba629f4ca19ea26ecfcf87685f5f"
|
||||
],
|
||||
"markers": "python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.3.*' and python_version != '3.1.*'",
|
||||
"version": "==0.31.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ Installing from git:
|
||||
|
||||
You can install packages with pipenv from git and other version control systems using URLs formatted according to the following rule:
|
||||
|
||||
<vcs_type>+<scheme>://<location>/<user_or_organizatoin>/<repository>@<branch_or_tag>#<package_name>
|
||||
<vcs_type>+<scheme>://<location>/<user_or_organization>/<repository>@<branch_or_tag>#<package_name>
|
||||
|
||||
The only optional section is the `@<branch_or_tag>` section. When using git over SSH, you may use the shorthand vcs and scheme alias `git+git@<location>:<user_or_organization>/<repository>@<branch_or_tag>#<package_name>`. Note that this is translated to `git+ssh://git@<location>` when parsed.
|
||||
|
||||
|
||||
+1
-1
@@ -110,7 +110,7 @@ You can tell Pipenv to install a Pipfile's contents into its parent system with
|
||||
|
||||
$ pipenv install --system
|
||||
|
||||
This is useful for Docker containers, and deployment infrastructure (e.g. Heroku does this).
|
||||
This is useful for managing the system Python, and deployment infrastructure (e.g. Heroku does this).
|
||||
|
||||
Also useful for deployment is the ``--deploy`` flag::
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
Fixed a bug which caused attempted hashing of ``ssh://`` style URIs which could cause failures during installation of private ssh repositories.
|
||||
- Corrected path conversion issues which caused certain editable VCS paths to be converted to ``ssh://`` URIs improperly.
|
||||
@@ -0,0 +1,14 @@
|
||||
- Vendored new libraries ``vistir`` and ``pip-shims``, ``tomlkit``, ``modutil``, and ``plette``.
|
||||
|
||||
- Update vendored libraries:
|
||||
- ``scandir`` to ``1.9.0``
|
||||
- ``click-completion`` to ``0.4.1``
|
||||
- ``semver`` to ``2.8.1``
|
||||
- ``shellingham`` to ``1.2.4``
|
||||
- ``pytoml`` to ``0.1.18``
|
||||
- ``certifi`` to ``2018.8.24``
|
||||
- ``ptyprocess`` to ``0.6.0``
|
||||
- ``requirementslib`` to ``1.1.5``
|
||||
- ``pythonfinder`` to ``1.0.2``
|
||||
- ``pipdeptree`` to ``0.13.0``
|
||||
- ``python-dotenv`` to ``0.9.1``
|
||||
@@ -0,0 +1 @@
|
||||
Stopped recommending `--system` for Docker contexts.
|
||||
@@ -72,7 +72,7 @@ class HashCache(SafeFileCache):
|
||||
# hash url WITH fragment
|
||||
hash_value = self.get(new_location.url)
|
||||
if not hash_value:
|
||||
hash_value = self._get_file_hash(new_location)
|
||||
hash_value = self._get_file_hash(new_location) if not new_location.url.startswith("ssh") else None
|
||||
hash_value = hash_value.encode('utf8')
|
||||
if can_hash:
|
||||
self.set(new_location.url, hash_value)
|
||||
|
||||
@@ -341,7 +341,7 @@ def fs_str(string):
|
||||
_fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
|
||||
|
||||
|
||||
# Borrowed from Pew.
|
||||
# Borrowed from pew to avoid importing pew which imports psutil
|
||||
# See https://github.com/berdario/pew/blob/master/pew/_utils.py#L82
|
||||
@contextmanager
|
||||
def temp_environ():
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
__author__ = """pyup.io"""
|
||||
__email__ = 'support@pyup.io'
|
||||
__version__ = '1.8.1'
|
||||
__version__ = '1.8.4'
|
||||
|
||||
@@ -113,7 +113,10 @@ class SheetReport(object):
|
||||
for chunk in [descr[i:i + 76] for i in range(0, len(descr), 76)]:
|
||||
|
||||
for line in chunk.splitlines():
|
||||
table.append("│ {:76} │".format(line))
|
||||
try:
|
||||
table.append("│ {:76} │".format(line.encode('utf-8')))
|
||||
except TypeError:
|
||||
table.append("│ {:76} │".format(line))
|
||||
# append the REPORT_SECTION only if this isn't the last entry
|
||||
if n + 1 < len(vulns):
|
||||
table.append(SheetReport.REPORT_SECTION)
|
||||
|
||||
+1
-1
@@ -24,7 +24,6 @@ from .utils import (
|
||||
proper_case,
|
||||
find_requirements,
|
||||
is_editable,
|
||||
is_vcs,
|
||||
cleanup_toml,
|
||||
is_installable_file,
|
||||
is_valid_url,
|
||||
@@ -45,6 +44,7 @@ from .environments import (
|
||||
PIPENV_PYTHON,
|
||||
PIPENV_DEFAULT_PYTHON_VERSION,
|
||||
)
|
||||
from requirementslib.utils import is_vcs
|
||||
|
||||
|
||||
def _normalized(p):
|
||||
|
||||
+3
-16
@@ -362,7 +362,8 @@ def venv_resolve_deps(
|
||||
"--system" if allow_global else "",
|
||||
)
|
||||
with temp_environ():
|
||||
os.environ["PIPENV_PACKAGES"] = "\n".join(deps)
|
||||
os.environ = {fs_str(k): fs_str(val) for k, val in os.environ.items()}
|
||||
os.environ["PIPENV_PACKAGES"] = str("\n".join(deps))
|
||||
if pypi_mirror:
|
||||
os.environ["PIPENV_PYPI_MIRROR"] = str(pypi_mirror)
|
||||
os.environ["PIPENV_VERBOSITY"] = str(environments.PIPENV_VERBOSITY)
|
||||
@@ -605,21 +606,6 @@ def is_editable(pipfile_entry):
|
||||
return False
|
||||
|
||||
|
||||
def is_vcs(pipfile_entry):
|
||||
from .vendor import requirements
|
||||
|
||||
"""Determine if dictionary entry from Pipfile is for a vcs dependency."""
|
||||
if hasattr(pipfile_entry, "keys"):
|
||||
return any(key for key in pipfile_entry.keys() if key in VCS_LIST)
|
||||
|
||||
elif isinstance(pipfile_entry, six.string_types):
|
||||
return bool(
|
||||
requirements.requirement.VCS_REGEX.match(clean_git_uri(pipfile_entry))
|
||||
)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def is_installable_file(path):
|
||||
"""Determine if a path can potentially be installed"""
|
||||
from .patched.notpip._internal.utils.misc import is_installable_dir
|
||||
@@ -757,6 +743,7 @@ def split_section(input_file, section_suffix, test_function):
|
||||
|
||||
def split_file(file_dict):
|
||||
"""Split VCS and editable dependencies out from file."""
|
||||
from .vendor.requirementslib.utils import is_vcs
|
||||
sections = {
|
||||
"vcs": is_vcs,
|
||||
"editable": lambda x: hasattr(x, "keys") and x.get("editable"),
|
||||
|
||||
Vendored
+2
-2
@@ -13,8 +13,8 @@ See <http://github.com/ActiveState/appdirs> for details and usage.
|
||||
# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html
|
||||
# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
||||
|
||||
__version__ = "1.4.4"
|
||||
__version_info__ = tuple(int(segment) for segment in __version__.split("."))
|
||||
__version_info__ = (1, 4, 3)
|
||||
__version__ = '.'.join(map(str, __version_info__))
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
Vendored
+1
@@ -3,4 +3,5 @@
|
||||
from pkgutil import extend_path
|
||||
__path__ = extend_path(__path__, __name__)
|
||||
from . import weakref
|
||||
from . import enum
|
||||
from . import shutil_get_terminal_size
|
||||
|
||||
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
Copyright (c) 2013, Ethan Furman.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
|
||||
Neither the name Ethan Furman nor the names of any
|
||||
contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR 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.
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
enum34 is the new Python stdlib enum module available in Python 3.4
|
||||
backported for previous versions of Python from 2.4 to 3.3.
|
||||
tested on 2.6, 2.7, and 3.3+
|
||||
+837
@@ -0,0 +1,837 @@
|
||||
"""Python Enumerations"""
|
||||
|
||||
import sys as _sys
|
||||
|
||||
__all__ = ['Enum', 'IntEnum', 'unique']
|
||||
|
||||
version = 1, 1, 6
|
||||
|
||||
pyver = float('%s.%s' % _sys.version_info[:2])
|
||||
|
||||
try:
|
||||
any
|
||||
except NameError:
|
||||
def any(iterable):
|
||||
for element in iterable:
|
||||
if element:
|
||||
return True
|
||||
return False
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
OrderedDict = None
|
||||
|
||||
try:
|
||||
basestring
|
||||
except NameError:
|
||||
# In Python 2 basestring is the ancestor of both str and unicode
|
||||
# in Python 3 it's just str, but was missing in 3.1
|
||||
basestring = str
|
||||
|
||||
try:
|
||||
unicode
|
||||
except NameError:
|
||||
# In Python 3 unicode no longer exists (it's just str)
|
||||
unicode = str
|
||||
|
||||
class _RouteClassAttributeToGetattr(object):
|
||||
"""Route attribute access on a class to __getattr__.
|
||||
|
||||
This is a descriptor, used to define attributes that act differently when
|
||||
accessed through an instance and through a class. Instance access remains
|
||||
normal, but access to an attribute through a class will be routed to the
|
||||
class's __getattr__ method; this is done by raising AttributeError.
|
||||
|
||||
"""
|
||||
def __init__(self, fget=None):
|
||||
self.fget = fget
|
||||
|
||||
def __get__(self, instance, ownerclass=None):
|
||||
if instance is None:
|
||||
raise AttributeError()
|
||||
return self.fget(instance)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
raise AttributeError("can't set attribute")
|
||||
|
||||
def __delete__(self, instance):
|
||||
raise AttributeError("can't delete attribute")
|
||||
|
||||
|
||||
def _is_descriptor(obj):
|
||||
"""Returns True if obj is a descriptor, False otherwise."""
|
||||
return (
|
||||
hasattr(obj, '__get__') or
|
||||
hasattr(obj, '__set__') or
|
||||
hasattr(obj, '__delete__'))
|
||||
|
||||
|
||||
def _is_dunder(name):
|
||||
"""Returns True if a __dunder__ name, False otherwise."""
|
||||
return (name[:2] == name[-2:] == '__' and
|
||||
name[2:3] != '_' and
|
||||
name[-3:-2] != '_' and
|
||||
len(name) > 4)
|
||||
|
||||
|
||||
def _is_sunder(name):
|
||||
"""Returns True if a _sunder_ name, False otherwise."""
|
||||
return (name[0] == name[-1] == '_' and
|
||||
name[1:2] != '_' and
|
||||
name[-2:-1] != '_' and
|
||||
len(name) > 2)
|
||||
|
||||
|
||||
def _make_class_unpicklable(cls):
|
||||
"""Make the given class un-picklable."""
|
||||
def _break_on_call_reduce(self, protocol=None):
|
||||
raise TypeError('%r cannot be pickled' % self)
|
||||
cls.__reduce_ex__ = _break_on_call_reduce
|
||||
cls.__module__ = '<unknown>'
|
||||
|
||||
|
||||
class _EnumDict(dict):
|
||||
"""Track enum member order and ensure member names are not reused.
|
||||
|
||||
EnumMeta will use the names found in self._member_names as the
|
||||
enumeration member names.
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
super(_EnumDict, self).__init__()
|
||||
self._member_names = []
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"""Changes anything not dundered or not a descriptor.
|
||||
|
||||
If a descriptor is added with the same name as an enum member, the name
|
||||
is removed from _member_names (this may leave a hole in the numerical
|
||||
sequence of values).
|
||||
|
||||
If an enum member name is used twice, an error is raised; duplicate
|
||||
values are not checked for.
|
||||
|
||||
Single underscore (sunder) names are reserved.
|
||||
|
||||
Note: in 3.x __order__ is simply discarded as a not necessary piece
|
||||
leftover from 2.x
|
||||
|
||||
"""
|
||||
if pyver >= 3.0 and key in ('_order_', '__order__'):
|
||||
return
|
||||
elif key == '__order__':
|
||||
key = '_order_'
|
||||
if _is_sunder(key):
|
||||
if key != '_order_':
|
||||
raise ValueError('_names_ are reserved for future Enum use')
|
||||
elif _is_dunder(key):
|
||||
pass
|
||||
elif key in self._member_names:
|
||||
# descriptor overwriting an enum?
|
||||
raise TypeError('Attempted to reuse key: %r' % key)
|
||||
elif not _is_descriptor(value):
|
||||
if key in self:
|
||||
# enum overwriting a descriptor?
|
||||
raise TypeError('Key already defined as: %r' % self[key])
|
||||
self._member_names.append(key)
|
||||
super(_EnumDict, self).__setitem__(key, value)
|
||||
|
||||
|
||||
# Dummy value for Enum as EnumMeta explicity checks for it, but of course until
|
||||
# EnumMeta finishes running the first time the Enum class doesn't exist. This
|
||||
# is also why there are checks in EnumMeta like `if Enum is not None`
|
||||
Enum = None
|
||||
|
||||
|
||||
class EnumMeta(type):
|
||||
"""Metaclass for Enum"""
|
||||
@classmethod
|
||||
def __prepare__(metacls, cls, bases):
|
||||
return _EnumDict()
|
||||
|
||||
def __new__(metacls, cls, bases, classdict):
|
||||
# an Enum class is final once enumeration items have been defined; it
|
||||
# cannot be mixed with other types (int, float, etc.) if it has an
|
||||
# inherited __new__ unless a new __new__ is defined (or the resulting
|
||||
# class will fail).
|
||||
if type(classdict) is dict:
|
||||
original_dict = classdict
|
||||
classdict = _EnumDict()
|
||||
for k, v in original_dict.items():
|
||||
classdict[k] = v
|
||||
|
||||
member_type, first_enum = metacls._get_mixins_(bases)
|
||||
__new__, save_new, use_args = metacls._find_new_(classdict, member_type,
|
||||
first_enum)
|
||||
# save enum items into separate mapping so they don't get baked into
|
||||
# the new class
|
||||
members = dict((k, classdict[k]) for k in classdict._member_names)
|
||||
for name in classdict._member_names:
|
||||
del classdict[name]
|
||||
|
||||
# py2 support for definition order
|
||||
_order_ = classdict.get('_order_')
|
||||
if _order_ is None:
|
||||
if pyver < 3.0:
|
||||
try:
|
||||
_order_ = [name for (name, value) in sorted(members.items(), key=lambda item: item[1])]
|
||||
except TypeError:
|
||||
_order_ = [name for name in sorted(members.keys())]
|
||||
else:
|
||||
_order_ = classdict._member_names
|
||||
else:
|
||||
del classdict['_order_']
|
||||
if pyver < 3.0:
|
||||
_order_ = _order_.replace(',', ' ').split()
|
||||
aliases = [name for name in members if name not in _order_]
|
||||
_order_ += aliases
|
||||
|
||||
# check for illegal enum names (any others?)
|
||||
invalid_names = set(members) & set(['mro'])
|
||||
if invalid_names:
|
||||
raise ValueError('Invalid enum member name(s): %s' % (
|
||||
', '.join(invalid_names), ))
|
||||
|
||||
# save attributes from super classes so we know if we can take
|
||||
# the shortcut of storing members in the class dict
|
||||
base_attributes = set([a for b in bases for a in b.__dict__])
|
||||
# create our new Enum type
|
||||
enum_class = super(EnumMeta, metacls).__new__(metacls, cls, bases, classdict)
|
||||
enum_class._member_names_ = [] # names in random order
|
||||
if OrderedDict is not None:
|
||||
enum_class._member_map_ = OrderedDict()
|
||||
else:
|
||||
enum_class._member_map_ = {} # name->value map
|
||||
enum_class._member_type_ = member_type
|
||||
|
||||
# Reverse value->name map for hashable values.
|
||||
enum_class._value2member_map_ = {}
|
||||
|
||||
# instantiate them, checking for duplicates as we go
|
||||
# we instantiate first instead of checking for duplicates first in case
|
||||
# a custom __new__ is doing something funky with the values -- such as
|
||||
# auto-numbering ;)
|
||||
if __new__ is None:
|
||||
__new__ = enum_class.__new__
|
||||
for member_name in _order_:
|
||||
value = members[member_name]
|
||||
if not isinstance(value, tuple):
|
||||
args = (value, )
|
||||
else:
|
||||
args = value
|
||||
if member_type is tuple: # special case for tuple enums
|
||||
args = (args, ) # wrap it one more time
|
||||
if not use_args or not args:
|
||||
enum_member = __new__(enum_class)
|
||||
if not hasattr(enum_member, '_value_'):
|
||||
enum_member._value_ = value
|
||||
else:
|
||||
enum_member = __new__(enum_class, *args)
|
||||
if not hasattr(enum_member, '_value_'):
|
||||
enum_member._value_ = member_type(*args)
|
||||
value = enum_member._value_
|
||||
enum_member._name_ = member_name
|
||||
enum_member.__objclass__ = enum_class
|
||||
enum_member.__init__(*args)
|
||||
# If another member with the same value was already defined, the
|
||||
# new member becomes an alias to the existing one.
|
||||
for name, canonical_member in enum_class._member_map_.items():
|
||||
if canonical_member.value == enum_member._value_:
|
||||
enum_member = canonical_member
|
||||
break
|
||||
else:
|
||||
# Aliases don't appear in member names (only in __members__).
|
||||
enum_class._member_names_.append(member_name)
|
||||
# performance boost for any member that would not shadow
|
||||
# a DynamicClassAttribute (aka _RouteClassAttributeToGetattr)
|
||||
if member_name not in base_attributes:
|
||||
setattr(enum_class, member_name, enum_member)
|
||||
# now add to _member_map_
|
||||
enum_class._member_map_[member_name] = enum_member
|
||||
try:
|
||||
# This may fail if value is not hashable. We can't add the value
|
||||
# to the map, and by-value lookups for this value will be
|
||||
# linear.
|
||||
enum_class._value2member_map_[value] = enum_member
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
|
||||
# If a custom type is mixed into the Enum, and it does not know how
|
||||
# to pickle itself, pickle.dumps will succeed but pickle.loads will
|
||||
# fail. Rather than have the error show up later and possibly far
|
||||
# from the source, sabotage the pickle protocol for this class so
|
||||
# that pickle.dumps also fails.
|
||||
#
|
||||
# However, if the new class implements its own __reduce_ex__, do not
|
||||
# sabotage -- it's on them to make sure it works correctly. We use
|
||||
# __reduce_ex__ instead of any of the others as it is preferred by
|
||||
# pickle over __reduce__, and it handles all pickle protocols.
|
||||
unpicklable = False
|
||||
if '__reduce_ex__' not in classdict:
|
||||
if member_type is not object:
|
||||
methods = ('__getnewargs_ex__', '__getnewargs__',
|
||||
'__reduce_ex__', '__reduce__')
|
||||
if not any(m in member_type.__dict__ for m in methods):
|
||||
_make_class_unpicklable(enum_class)
|
||||
unpicklable = True
|
||||
|
||||
|
||||
# double check that repr and friends are not the mixin's or various
|
||||
# things break (such as pickle)
|
||||
for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'):
|
||||
class_method = getattr(enum_class, name)
|
||||
obj_method = getattr(member_type, name, None)
|
||||
enum_method = getattr(first_enum, name, None)
|
||||
if name not in classdict and class_method is not enum_method:
|
||||
if name == '__reduce_ex__' and unpicklable:
|
||||
continue
|
||||
setattr(enum_class, name, enum_method)
|
||||
|
||||
# method resolution and int's are not playing nice
|
||||
# Python's less than 2.6 use __cmp__
|
||||
|
||||
if pyver < 2.6:
|
||||
|
||||
if issubclass(enum_class, int):
|
||||
setattr(enum_class, '__cmp__', getattr(int, '__cmp__'))
|
||||
|
||||
elif pyver < 3.0:
|
||||
|
||||
if issubclass(enum_class, int):
|
||||
for method in (
|
||||
'__le__',
|
||||
'__lt__',
|
||||
'__gt__',
|
||||
'__ge__',
|
||||
'__eq__',
|
||||
'__ne__',
|
||||
'__hash__',
|
||||
):
|
||||
setattr(enum_class, method, getattr(int, method))
|
||||
|
||||
# replace any other __new__ with our own (as long as Enum is not None,
|
||||
# anyway) -- again, this is to support pickle
|
||||
if Enum is not None:
|
||||
# if the user defined their own __new__, save it before it gets
|
||||
# clobbered in case they subclass later
|
||||
if save_new:
|
||||
setattr(enum_class, '__member_new__', enum_class.__dict__['__new__'])
|
||||
setattr(enum_class, '__new__', Enum.__dict__['__new__'])
|
||||
return enum_class
|
||||
|
||||
def __bool__(cls):
|
||||
"""
|
||||
classes/types should always be True.
|
||||
"""
|
||||
return True
|
||||
|
||||
def __call__(cls, value, names=None, module=None, type=None, start=1):
|
||||
"""Either returns an existing member, or creates a new enum class.
|
||||
|
||||
This method is used both when an enum class is given a value to match
|
||||
to an enumeration member (i.e. Color(3)) and for the functional API
|
||||
(i.e. Color = Enum('Color', names='red green blue')).
|
||||
|
||||
When used for the functional API: `module`, if set, will be stored in
|
||||
the new class' __module__ attribute; `type`, if set, will be mixed in
|
||||
as the first base class.
|
||||
|
||||
Note: if `module` is not set this routine will attempt to discover the
|
||||
calling module by walking the frame stack; if this is unsuccessful
|
||||
the resulting class will not be pickleable.
|
||||
|
||||
"""
|
||||
if names is None: # simple value lookup
|
||||
return cls.__new__(cls, value)
|
||||
# otherwise, functional API: we're creating a new Enum type
|
||||
return cls._create_(value, names, module=module, type=type, start=start)
|
||||
|
||||
def __contains__(cls, member):
|
||||
return isinstance(member, cls) and member.name in cls._member_map_
|
||||
|
||||
def __delattr__(cls, attr):
|
||||
# nicer error message when someone tries to delete an attribute
|
||||
# (see issue19025).
|
||||
if attr in cls._member_map_:
|
||||
raise AttributeError(
|
||||
"%s: cannot delete Enum member." % cls.__name__)
|
||||
super(EnumMeta, cls).__delattr__(attr)
|
||||
|
||||
def __dir__(self):
|
||||
return (['__class__', '__doc__', '__members__', '__module__'] +
|
||||
self._member_names_)
|
||||
|
||||
@property
|
||||
def __members__(cls):
|
||||
"""Returns a mapping of member name->value.
|
||||
|
||||
This mapping lists all enum members, including aliases. Note that this
|
||||
is a copy of the internal mapping.
|
||||
|
||||
"""
|
||||
return cls._member_map_.copy()
|
||||
|
||||
def __getattr__(cls, name):
|
||||
"""Return the enum member matching `name`
|
||||
|
||||
We use __getattr__ instead of descriptors or inserting into the enum
|
||||
class' __dict__ in order to support `name` and `value` being both
|
||||
properties for enum members (which live in the class' __dict__) and
|
||||
enum members themselves.
|
||||
|
||||
"""
|
||||
if _is_dunder(name):
|
||||
raise AttributeError(name)
|
||||
try:
|
||||
return cls._member_map_[name]
|
||||
except KeyError:
|
||||
raise AttributeError(name)
|
||||
|
||||
def __getitem__(cls, name):
|
||||
return cls._member_map_[name]
|
||||
|
||||
def __iter__(cls):
|
||||
return (cls._member_map_[name] for name in cls._member_names_)
|
||||
|
||||
def __reversed__(cls):
|
||||
return (cls._member_map_[name] for name in reversed(cls._member_names_))
|
||||
|
||||
def __len__(cls):
|
||||
return len(cls._member_names_)
|
||||
|
||||
__nonzero__ = __bool__
|
||||
|
||||
def __repr__(cls):
|
||||
return "<enum %r>" % cls.__name__
|
||||
|
||||
def __setattr__(cls, name, value):
|
||||
"""Block attempts to reassign Enum members.
|
||||
|
||||
A simple assignment to the class namespace only changes one of the
|
||||
several possible ways to get an Enum member from the Enum class,
|
||||
resulting in an inconsistent Enumeration.
|
||||
|
||||
"""
|
||||
member_map = cls.__dict__.get('_member_map_', {})
|
||||
if name in member_map:
|
||||
raise AttributeError('Cannot reassign members.')
|
||||
super(EnumMeta, cls).__setattr__(name, value)
|
||||
|
||||
def _create_(cls, class_name, names=None, module=None, type=None, start=1):
|
||||
"""Convenience method to create a new Enum class.
|
||||
|
||||
`names` can be:
|
||||
|
||||
* A string containing member names, separated either with spaces or
|
||||
commas. Values are auto-numbered from 1.
|
||||
* An iterable of member names. Values are auto-numbered from 1.
|
||||
* An iterable of (member name, value) pairs.
|
||||
* A mapping of member name -> value.
|
||||
|
||||
"""
|
||||
if pyver < 3.0:
|
||||
# if class_name is unicode, attempt a conversion to ASCII
|
||||
if isinstance(class_name, unicode):
|
||||
try:
|
||||
class_name = class_name.encode('ascii')
|
||||
except UnicodeEncodeError:
|
||||
raise TypeError('%r is not representable in ASCII' % class_name)
|
||||
metacls = cls.__class__
|
||||
if type is None:
|
||||
bases = (cls, )
|
||||
else:
|
||||
bases = (type, cls)
|
||||
classdict = metacls.__prepare__(class_name, bases)
|
||||
_order_ = []
|
||||
|
||||
# special processing needed for names?
|
||||
if isinstance(names, basestring):
|
||||
names = names.replace(',', ' ').split()
|
||||
if isinstance(names, (tuple, list)) and isinstance(names[0], basestring):
|
||||
names = [(e, i+start) for (i, e) in enumerate(names)]
|
||||
|
||||
# Here, names is either an iterable of (name, value) or a mapping.
|
||||
item = None # in case names is empty
|
||||
for item in names:
|
||||
if isinstance(item, basestring):
|
||||
member_name, member_value = item, names[item]
|
||||
else:
|
||||
member_name, member_value = item
|
||||
classdict[member_name] = member_value
|
||||
_order_.append(member_name)
|
||||
# only set _order_ in classdict if name/value was not from a mapping
|
||||
if not isinstance(item, basestring):
|
||||
classdict['_order_'] = ' '.join(_order_)
|
||||
enum_class = metacls.__new__(metacls, class_name, bases, classdict)
|
||||
|
||||
# TODO: replace the frame hack if a blessed way to know the calling
|
||||
# module is ever developed
|
||||
if module is None:
|
||||
try:
|
||||
module = _sys._getframe(2).f_globals['__name__']
|
||||
except (AttributeError, ValueError):
|
||||
pass
|
||||
if module is None:
|
||||
_make_class_unpicklable(enum_class)
|
||||
else:
|
||||
enum_class.__module__ = module
|
||||
|
||||
return enum_class
|
||||
|
||||
@staticmethod
|
||||
def _get_mixins_(bases):
|
||||
"""Returns the type for creating enum members, and the first inherited
|
||||
enum class.
|
||||
|
||||
bases: the tuple of bases that was given to __new__
|
||||
|
||||
"""
|
||||
if not bases or Enum is None:
|
||||
return object, Enum
|
||||
|
||||
|
||||
# double check that we are not subclassing a class with existing
|
||||
# enumeration members; while we're at it, see if any other data
|
||||
# type has been mixed in so we can use the correct __new__
|
||||
member_type = first_enum = None
|
||||
for base in bases:
|
||||
if (base is not Enum and
|
||||
issubclass(base, Enum) and
|
||||
base._member_names_):
|
||||
raise TypeError("Cannot extend enumerations")
|
||||
# base is now the last base in bases
|
||||
if not issubclass(base, Enum):
|
||||
raise TypeError("new enumerations must be created as "
|
||||
"`ClassName([mixin_type,] enum_type)`")
|
||||
|
||||
# get correct mix-in type (either mix-in type of Enum subclass, or
|
||||
# first base if last base is Enum)
|
||||
if not issubclass(bases[0], Enum):
|
||||
member_type = bases[0] # first data type
|
||||
first_enum = bases[-1] # enum type
|
||||
else:
|
||||
for base in bases[0].__mro__:
|
||||
# most common: (IntEnum, int, Enum, object)
|
||||
# possible: (<Enum 'AutoIntEnum'>, <Enum 'IntEnum'>,
|
||||
# <class 'int'>, <Enum 'Enum'>,
|
||||
# <class 'object'>)
|
||||
if issubclass(base, Enum):
|
||||
if first_enum is None:
|
||||
first_enum = base
|
||||
else:
|
||||
if member_type is None:
|
||||
member_type = base
|
||||
|
||||
return member_type, first_enum
|
||||
|
||||
if pyver < 3.0:
|
||||
@staticmethod
|
||||
def _find_new_(classdict, member_type, first_enum):
|
||||
"""Returns the __new__ to be used for creating the enum members.
|
||||
|
||||
classdict: the class dictionary given to __new__
|
||||
member_type: the data type whose __new__ will be used by default
|
||||
first_enum: enumeration to check for an overriding __new__
|
||||
|
||||
"""
|
||||
# now find the correct __new__, checking to see of one was defined
|
||||
# by the user; also check earlier enum classes in case a __new__ was
|
||||
# saved as __member_new__
|
||||
__new__ = classdict.get('__new__', None)
|
||||
if __new__:
|
||||
return None, True, True # __new__, save_new, use_args
|
||||
|
||||
N__new__ = getattr(None, '__new__')
|
||||
O__new__ = getattr(object, '__new__')
|
||||
if Enum is None:
|
||||
E__new__ = N__new__
|
||||
else:
|
||||
E__new__ = Enum.__dict__['__new__']
|
||||
# check all possibles for __member_new__ before falling back to
|
||||
# __new__
|
||||
for method in ('__member_new__', '__new__'):
|
||||
for possible in (member_type, first_enum):
|
||||
try:
|
||||
target = possible.__dict__[method]
|
||||
except (AttributeError, KeyError):
|
||||
target = getattr(possible, method, None)
|
||||
if target not in [
|
||||
None,
|
||||
N__new__,
|
||||
O__new__,
|
||||
E__new__,
|
||||
]:
|
||||
if method == '__member_new__':
|
||||
classdict['__new__'] = target
|
||||
return None, False, True
|
||||
if isinstance(target, staticmethod):
|
||||
target = target.__get__(member_type)
|
||||
__new__ = target
|
||||
break
|
||||
if __new__ is not None:
|
||||
break
|
||||
else:
|
||||
__new__ = object.__new__
|
||||
|
||||
# if a non-object.__new__ is used then whatever value/tuple was
|
||||
# assigned to the enum member name will be passed to __new__ and to the
|
||||
# new enum member's __init__
|
||||
if __new__ is object.__new__:
|
||||
use_args = False
|
||||
else:
|
||||
use_args = True
|
||||
|
||||
return __new__, False, use_args
|
||||
else:
|
||||
@staticmethod
|
||||
def _find_new_(classdict, member_type, first_enum):
|
||||
"""Returns the __new__ to be used for creating the enum members.
|
||||
|
||||
classdict: the class dictionary given to __new__
|
||||
member_type: the data type whose __new__ will be used by default
|
||||
first_enum: enumeration to check for an overriding __new__
|
||||
|
||||
"""
|
||||
# now find the correct __new__, checking to see of one was defined
|
||||
# by the user; also check earlier enum classes in case a __new__ was
|
||||
# saved as __member_new__
|
||||
__new__ = classdict.get('__new__', None)
|
||||
|
||||
# should __new__ be saved as __member_new__ later?
|
||||
save_new = __new__ is not None
|
||||
|
||||
if __new__ is None:
|
||||
# check all possibles for __member_new__ before falling back to
|
||||
# __new__
|
||||
for method in ('__member_new__', '__new__'):
|
||||
for possible in (member_type, first_enum):
|
||||
target = getattr(possible, method, None)
|
||||
if target not in (
|
||||
None,
|
||||
None.__new__,
|
||||
object.__new__,
|
||||
Enum.__new__,
|
||||
):
|
||||
__new__ = target
|
||||
break
|
||||
if __new__ is not None:
|
||||
break
|
||||
else:
|
||||
__new__ = object.__new__
|
||||
|
||||
# if a non-object.__new__ is used then whatever value/tuple was
|
||||
# assigned to the enum member name will be passed to __new__ and to the
|
||||
# new enum member's __init__
|
||||
if __new__ is object.__new__:
|
||||
use_args = False
|
||||
else:
|
||||
use_args = True
|
||||
|
||||
return __new__, save_new, use_args
|
||||
|
||||
|
||||
########################################################
|
||||
# In order to support Python 2 and 3 with a single
|
||||
# codebase we have to create the Enum methods separately
|
||||
# and then use the `type(name, bases, dict)` method to
|
||||
# create the class.
|
||||
########################################################
|
||||
temp_enum_dict = {}
|
||||
temp_enum_dict['__doc__'] = "Generic enumeration.\n\n Derive from this class to define new enumerations.\n\n"
|
||||
|
||||
def __new__(cls, value):
|
||||
# all enum instances are actually created during class construction
|
||||
# without calling this method; this method is called by the metaclass'
|
||||
# __call__ (i.e. Color(3) ), and by pickle
|
||||
if type(value) is cls:
|
||||
# For lookups like Color(Color.red)
|
||||
value = value.value
|
||||
#return value
|
||||
# by-value search for a matching enum member
|
||||
# see if it's in the reverse mapping (for hashable values)
|
||||
try:
|
||||
if value in cls._value2member_map_:
|
||||
return cls._value2member_map_[value]
|
||||
except TypeError:
|
||||
# not there, now do long search -- O(n) behavior
|
||||
for member in cls._member_map_.values():
|
||||
if member.value == value:
|
||||
return member
|
||||
raise ValueError("%s is not a valid %s" % (value, cls.__name__))
|
||||
temp_enum_dict['__new__'] = __new__
|
||||
del __new__
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s.%s: %r>" % (
|
||||
self.__class__.__name__, self._name_, self._value_)
|
||||
temp_enum_dict['__repr__'] = __repr__
|
||||
del __repr__
|
||||
|
||||
def __str__(self):
|
||||
return "%s.%s" % (self.__class__.__name__, self._name_)
|
||||
temp_enum_dict['__str__'] = __str__
|
||||
del __str__
|
||||
|
||||
if pyver >= 3.0:
|
||||
def __dir__(self):
|
||||
added_behavior = [
|
||||
m
|
||||
for cls in self.__class__.mro()
|
||||
for m in cls.__dict__
|
||||
if m[0] != '_' and m not in self._member_map_
|
||||
]
|
||||
return (['__class__', '__doc__', '__module__', ] + added_behavior)
|
||||
temp_enum_dict['__dir__'] = __dir__
|
||||
del __dir__
|
||||
|
||||
def __format__(self, format_spec):
|
||||
# mixed-in Enums should use the mixed-in type's __format__, otherwise
|
||||
# we can get strange results with the Enum name showing up instead of
|
||||
# the value
|
||||
|
||||
# pure Enum branch
|
||||
if self._member_type_ is object:
|
||||
cls = str
|
||||
val = str(self)
|
||||
# mix-in branch
|
||||
else:
|
||||
cls = self._member_type_
|
||||
val = self.value
|
||||
return cls.__format__(val, format_spec)
|
||||
temp_enum_dict['__format__'] = __format__
|
||||
del __format__
|
||||
|
||||
|
||||
####################################
|
||||
# Python's less than 2.6 use __cmp__
|
||||
|
||||
if pyver < 2.6:
|
||||
|
||||
def __cmp__(self, other):
|
||||
if type(other) is self.__class__:
|
||||
if self is other:
|
||||
return 0
|
||||
return -1
|
||||
return NotImplemented
|
||||
raise TypeError("unorderable types: %s() and %s()" % (self.__class__.__name__, other.__class__.__name__))
|
||||
temp_enum_dict['__cmp__'] = __cmp__
|
||||
del __cmp__
|
||||
|
||||
else:
|
||||
|
||||
def __le__(self, other):
|
||||
raise TypeError("unorderable types: %s() <= %s()" % (self.__class__.__name__, other.__class__.__name__))
|
||||
temp_enum_dict['__le__'] = __le__
|
||||
del __le__
|
||||
|
||||
def __lt__(self, other):
|
||||
raise TypeError("unorderable types: %s() < %s()" % (self.__class__.__name__, other.__class__.__name__))
|
||||
temp_enum_dict['__lt__'] = __lt__
|
||||
del __lt__
|
||||
|
||||
def __ge__(self, other):
|
||||
raise TypeError("unorderable types: %s() >= %s()" % (self.__class__.__name__, other.__class__.__name__))
|
||||
temp_enum_dict['__ge__'] = __ge__
|
||||
del __ge__
|
||||
|
||||
def __gt__(self, other):
|
||||
raise TypeError("unorderable types: %s() > %s()" % (self.__class__.__name__, other.__class__.__name__))
|
||||
temp_enum_dict['__gt__'] = __gt__
|
||||
del __gt__
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
if type(other) is self.__class__:
|
||||
return self is other
|
||||
return NotImplemented
|
||||
temp_enum_dict['__eq__'] = __eq__
|
||||
del __eq__
|
||||
|
||||
def __ne__(self, other):
|
||||
if type(other) is self.__class__:
|
||||
return self is not other
|
||||
return NotImplemented
|
||||
temp_enum_dict['__ne__'] = __ne__
|
||||
del __ne__
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._name_)
|
||||
temp_enum_dict['__hash__'] = __hash__
|
||||
del __hash__
|
||||
|
||||
def __reduce_ex__(self, proto):
|
||||
return self.__class__, (self._value_, )
|
||||
temp_enum_dict['__reduce_ex__'] = __reduce_ex__
|
||||
del __reduce_ex__
|
||||
|
||||
# _RouteClassAttributeToGetattr is used to provide access to the `name`
|
||||
# and `value` properties of enum members while keeping some measure of
|
||||
# protection from modification, while still allowing for an enumeration
|
||||
# to have members named `name` and `value`. This works because enumeration
|
||||
# members are not set directly on the enum class -- __getattr__ is
|
||||
# used to look them up.
|
||||
|
||||
@_RouteClassAttributeToGetattr
|
||||
def name(self):
|
||||
return self._name_
|
||||
temp_enum_dict['name'] = name
|
||||
del name
|
||||
|
||||
@_RouteClassAttributeToGetattr
|
||||
def value(self):
|
||||
return self._value_
|
||||
temp_enum_dict['value'] = value
|
||||
del value
|
||||
|
||||
@classmethod
|
||||
def _convert(cls, name, module, filter, source=None):
|
||||
"""
|
||||
Create a new Enum subclass that replaces a collection of global constants
|
||||
"""
|
||||
# convert all constants from source (or module) that pass filter() to
|
||||
# a new Enum called name, and export the enum and its members back to
|
||||
# module;
|
||||
# also, replace the __reduce_ex__ method so unpickling works in
|
||||
# previous Python versions
|
||||
module_globals = vars(_sys.modules[module])
|
||||
if source:
|
||||
source = vars(source)
|
||||
else:
|
||||
source = module_globals
|
||||
members = dict((name, value) for name, value in source.items() if filter(name))
|
||||
cls = cls(name, members, module=module)
|
||||
cls.__reduce_ex__ = _reduce_ex_by_name
|
||||
module_globals.update(cls.__members__)
|
||||
module_globals[name] = cls
|
||||
return cls
|
||||
temp_enum_dict['_convert'] = _convert
|
||||
del _convert
|
||||
|
||||
Enum = EnumMeta('Enum', (object, ), temp_enum_dict)
|
||||
del temp_enum_dict
|
||||
|
||||
# Enum has now been created
|
||||
###########################
|
||||
|
||||
class IntEnum(int, Enum):
|
||||
"""Enum where members are also (and must be) ints"""
|
||||
|
||||
def _reduce_ex_by_name(self, proto):
|
||||
return self.name
|
||||
|
||||
def unique(enumeration):
|
||||
"""Class decorator that ensures only unique members exist in an enumeration."""
|
||||
duplicates = []
|
||||
for name, member in enumeration.__members__.items():
|
||||
if name != member.name:
|
||||
duplicates.append((name, member.name))
|
||||
if duplicates:
|
||||
duplicate_names = ', '.join(
|
||||
["%s -> %s" % (alias, name) for (alias, name) in duplicates]
|
||||
)
|
||||
raise ValueError('duplicate names found in %r: %s' %
|
||||
(enumeration, duplicate_names)
|
||||
)
|
||||
return enumeration
|
||||
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
||||
from .core import where, old_where
|
||||
|
||||
__version__ = "2018.04.16"
|
||||
__version__ = "2018.08.24"
|
||||
|
||||
Vendored
+63
-163
@@ -3692,169 +3692,6 @@ lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof
|
||||
TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=Certplus Root CA G1 O=Certplus
|
||||
# Subject: CN=Certplus Root CA G1 O=Certplus
|
||||
# Label: "Certplus Root CA G1"
|
||||
# Serial: 1491911565779898356709731176965615564637713
|
||||
# MD5 Fingerprint: 7f:09:9c:f7:d9:b9:5c:69:69:56:d5:37:3e:14:0d:42
|
||||
# SHA1 Fingerprint: 22:fd:d0:b7:fd:a2:4e:0d:ac:49:2c:a0:ac:a6:7b:6a:1f:e3:f7:66
|
||||
# SHA256 Fingerprint: 15:2a:40:2b:fc:df:2c:d5:48:05:4d:22:75:b3:9c:7f:ca:3e:c0:97:80:78:b0:f0:ea:76:e5:61:a6:c7:43:3e
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFazCCA1OgAwIBAgISESBVg+QtPlRWhS2DN7cs3EYRMA0GCSqGSIb3DQEBDQUA
|
||||
MD4xCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2Vy
|
||||
dHBsdXMgUm9vdCBDQSBHMTAeFw0xNDA1MjYwMDAwMDBaFw0zODAxMTUwMDAwMDBa
|
||||
MD4xCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2Vy
|
||||
dHBsdXMgUm9vdCBDQSBHMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
|
||||
ANpQh7bauKk+nWT6VjOaVj0W5QOVsjQcmm1iBdTYj+eJZJ+622SLZOZ5KmHNr49a
|
||||
iZFluVj8tANfkT8tEBXgfs+8/H9DZ6itXjYj2JizTfNDnjl8KvzsiNWI7nC9hRYt
|
||||
6kuJPKNxQv4c/dMcLRC4hlTqQ7jbxofaqK6AJc96Jh2qkbBIb6613p7Y1/oA/caP
|
||||
0FG7Yn2ksYyy/yARujVjBYZHYEMzkPZHogNPlk2dT8Hq6pyi/jQu3rfKG3akt62f
|
||||
6ajUeD94/vI4CTYd0hYCyOwqaK/1jpTvLRN6HkJKHRUxrgwEV/xhc/MxVoYxgKDE
|
||||
EW4wduOU8F8ExKyHcomYxZ3MVwia9Az8fXoFOvpHgDm2z4QTd28n6v+WZxcIbekN
|
||||
1iNQMLAVdBM+5S//Ds3EC0pd8NgAM0lm66EYfFkuPSi5YXHLtaW6uOrc4nBvCGrc
|
||||
h2c0798wct3zyT8j/zXhviEpIDCB5BmlIOklynMxdCm+4kLV87ImZsdo/Rmz5yCT
|
||||
mehd4F6H50boJZwKKSTUzViGUkAksnsPmBIgJPaQbEfIDbsYIC7Z/fyL8inqh3SV
|
||||
4EJQeIQEQWGw9CEjjy3LKCHyamz0GqbFFLQ3ZU+V/YDI+HLlJWvEYLF7bY5KinPO
|
||||
WftwenMGE9nTdDckQQoRb5fc5+R+ob0V8rqHDz1oihYHAgMBAAGjYzBhMA4GA1Ud
|
||||
DwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSowcCbkahDFXxd
|
||||
Bie0KlHYlwuBsTAfBgNVHSMEGDAWgBSowcCbkahDFXxdBie0KlHYlwuBsTANBgkq
|
||||
hkiG9w0BAQ0FAAOCAgEAnFZvAX7RvUz1isbwJh/k4DgYzDLDKTudQSk0YcbX8ACh
|
||||
66Ryj5QXvBMsdbRX7gp8CXrc1cqh0DQT+Hern+X+2B50ioUHj3/MeXrKls3N/U/7
|
||||
/SMNkPX0XtPGYX2eEeAC7gkE2Qfdpoq3DIMku4NQkv5gdRE+2J2winq14J2by5BS
|
||||
S7CTKtQ+FjPlnsZlFT5kOwQ/2wyPX1wdaR+v8+khjPPvl/aatxm2hHSco1S1cE5j
|
||||
2FddUyGbQJJD+tZ3VTNPZNX70Cxqjm0lpu+F6ALEUz65noe8zDUa3qHpimOHZR4R
|
||||
Kttjd5cUvpoUmRGywO6wT/gUITJDT5+rosuoD6o7BlXGEilXCNQ314cnrUlZp5Gr
|
||||
RHpejXDbl85IULFzk/bwg2D5zfHhMf1bfHEhYxQUqq/F3pN+aLHsIqKqkHWetUNy
|
||||
6mSjhEv9DKgma3GX7lZjZuhCVPnHHd/Qj1vfyDBviP4NxDMcU6ij/UgQ8uQKTuEV
|
||||
V/xuZDDCVRHc6qnNSlSsKWNEz0pAoNZoWRsz+e86i9sgktxChL8Bq4fA1SCC28a5
|
||||
g4VCXA9DO2pJNdWY9BW/+mGBDAkgGNLQFwzLSABQ6XaCjGTXOqAHVcweMcDvOrRl
|
||||
++O/QmueD6i9a5jc2NvLi6Td11n0bt3+qsOR0C5CB8AMTVPNJLFMWx5R9N/pkvo=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=Certplus Root CA G2 O=Certplus
|
||||
# Subject: CN=Certplus Root CA G2 O=Certplus
|
||||
# Label: "Certplus Root CA G2"
|
||||
# Serial: 1492087096131536844209563509228951875861589
|
||||
# MD5 Fingerprint: a7:ee:c4:78:2d:1b:ee:2d:b9:29:ce:d6:a7:96:32:31
|
||||
# SHA1 Fingerprint: 4f:65:8e:1f:e9:06:d8:28:02:e9:54:47:41:c9:54:25:5d:69:cc:1a
|
||||
# SHA256 Fingerprint: 6c:c0:50:41:e6:44:5e:74:69:6c:4c:fb:c9:f8:0f:54:3b:7e:ab:bb:44:b4:ce:6f:78:7c:6a:99:71:c4:2f:17
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICHDCCAaKgAwIBAgISESDZkc6uo+jF5//pAq/Pc7xVMAoGCCqGSM49BAMDMD4x
|
||||
CzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2VydHBs
|
||||
dXMgUm9vdCBDQSBHMjAeFw0xNDA1MjYwMDAwMDBaFw0zODAxMTUwMDAwMDBaMD4x
|
||||
CzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2VydHBs
|
||||
dXMgUm9vdCBDQSBHMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABM0PW1aC3/BFGtat
|
||||
93nwHcmsltaeTpwftEIRyoa/bfuFo8XlGVzX7qY/aWfYeOKmycTbLXku54uNAm8x
|
||||
Ik0G42ByRZ0OQneezs/lf4WbGOT8zC5y0xaTTsqZY1yhBSpsBqNjMGEwDgYDVR0P
|
||||
AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNqDYwJ5jtpMxjwj
|
||||
FNiPwyCrKGBZMB8GA1UdIwQYMBaAFNqDYwJ5jtpMxjwjFNiPwyCrKGBZMAoGCCqG
|
||||
SM49BAMDA2gAMGUCMHD+sAvZ94OX7PNVHdTcswYO/jOYnYs5kGuUIe22113WTNch
|
||||
p+e/IQ8rzfcq3IUHnQIxAIYUFuXcsGXCwI4Un78kFmjlvPl5adytRSv3tjFzzAal
|
||||
U5ORGpOucGpnutee5WEaXw==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=OpenTrust Root CA G1 O=OpenTrust
|
||||
# Subject: CN=OpenTrust Root CA G1 O=OpenTrust
|
||||
# Label: "OpenTrust Root CA G1"
|
||||
# Serial: 1492036577811947013770400127034825178844775
|
||||
# MD5 Fingerprint: 76:00:cc:81:29:cd:55:5e:88:6a:7a:2e:f7:4d:39:da
|
||||
# SHA1 Fingerprint: 79:91:e8:34:f7:e2:ee:dd:08:95:01:52:e9:55:2d:14:e9:58:d5:7e
|
||||
# SHA256 Fingerprint: 56:c7:71:28:d9:8c:18:d9:1b:4c:fd:ff:bc:25:ee:91:03:d4:75:8e:a2:ab:ad:82:6a:90:f3:45:7d:46:0e:b4
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFbzCCA1egAwIBAgISESCzkFU5fX82bWTCp59rY45nMA0GCSqGSIb3DQEBCwUA
|
||||
MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9w
|
||||
ZW5UcnVzdCBSb290IENBIEcxMB4XDTE0MDUyNjA4NDU1MFoXDTM4MDExNTAwMDAw
|
||||
MFowQDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwU
|
||||
T3BlblRydXN0IFJvb3QgQ0EgRzEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
|
||||
AoICAQD4eUbalsUwXopxAy1wpLuwxQjczeY1wICkES3d5oeuXT2R0odsN7faYp6b
|
||||
wiTXj/HbpqbfRm9RpnHLPhsxZ2L3EVs0J9V5ToybWL0iEA1cJwzdMOWo010hOHQX
|
||||
/uMftk87ay3bfWAfjH1MBcLrARYVmBSO0ZB3Ij/swjm4eTrwSSTilZHcYTSSjFR0
|
||||
77F9jAHiOH3BX2pfJLKOYheteSCtqx234LSWSE9mQxAGFiQD4eCcjsZGT44ameGP
|
||||
uY4zbGneWK2gDqdkVBFpRGZPTBKnjix9xNRbxQA0MMHZmf4yzgeEtE7NCv82TWLx
|
||||
p2NX5Ntqp66/K7nJ5rInieV+mhxNaMbBGN4zK1FGSxyO9z0M+Yo0FMT7MzUj8czx
|
||||
Kselu7Cizv5Ta01BG2Yospb6p64KTrk5M0ScdMGTHPjgniQlQ/GbI4Kq3ywgsNw2
|
||||
TgOzfALU5nsaqocTvz6hdLubDuHAk5/XpGbKuxs74zD0M1mKB3IDVedzagMxbm+W
|
||||
G+Oin6+Sx+31QrclTDsTBM8clq8cIqPQqwWyTBIjUtz9GVsnnB47ev1CI9sjgBPw
|
||||
vFEVVJSmdz7QdFG9URQIOTfLHzSpMJ1ShC5VkLG631UAC9hWLbFJSXKAqWLXwPYY
|
||||
EQRVzXR7z2FwefR7LFxckvzluFqrTJOVoSfupb7PcSNCupt2LQIDAQABo2MwYTAO
|
||||
BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUl0YhVyE1
|
||||
2jZVx/PxN3DlCPaTKbYwHwYDVR0jBBgwFoAUl0YhVyE12jZVx/PxN3DlCPaTKbYw
|
||||
DQYJKoZIhvcNAQELBQADggIBAB3dAmB84DWn5ph76kTOZ0BP8pNuZtQ5iSas000E
|
||||
PLuHIT839HEl2ku6q5aCgZG27dmxpGWX4m9kWaSW7mDKHyP7Rbr/jyTwyqkxf3kf
|
||||
gLMtMrpkZ2CvuVnN35pJ06iCsfmYlIrM4LvgBBuZYLFGZdwIorJGnkSI6pN+VxbS
|
||||
FXJfLkur1J1juONI5f6ELlgKn0Md/rcYkoZDSw6cMoYsYPXpSOqV7XAp8dUv/TW0
|
||||
V8/bhUiZucJvbI/NeJWsZCj9VrDDb8O+WVLhX4SPgPL0DTatdrOjteFkdjpY3H1P
|
||||
XlZs5VVZV6Xf8YpmMIzUUmI4d7S+KNfKNsSbBfD4Fdvb8e80nR14SohWZ25g/4/I
|
||||
i+GOvUKpMwpZQhISKvqxnUOOBZuZ2mKtVzazHbYNeS2WuOvyDEsMpZTGMKcmGS3t
|
||||
TAZQMPH9WD25SxdfGbRqhFS0OE85og2WaMMolP3tLR9Ka0OWLpABEPs4poEL0L91
|
||||
09S5zvE/bw4cHjdx5RiHdRk/ULlepEU0rbDK5uUTdg8xFKmOLZTW1YVNcxVPS/Ky
|
||||
Pu1svf0OnWZzsD2097+o4BGkxK51CUpjAEggpsadCwmKtODmzj7HPiY46SvepghJ
|
||||
AwSQiumPv+i2tCqjI40cHLI5kqiPAlxAOXXUc0ECd97N4EOH1uS6SsNsEn/+KuYj
|
||||
1oxx
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=OpenTrust Root CA G2 O=OpenTrust
|
||||
# Subject: CN=OpenTrust Root CA G2 O=OpenTrust
|
||||
# Label: "OpenTrust Root CA G2"
|
||||
# Serial: 1492012448042702096986875987676935573415441
|
||||
# MD5 Fingerprint: 57:24:b6:59:24:6b:ae:c8:fe:1c:0c:20:f2:c0:4e:eb
|
||||
# SHA1 Fingerprint: 79:5f:88:60:c5:ab:7c:3d:92:e6:cb:f4:8d:e1:45:cd:11:ef:60:0b
|
||||
# SHA256 Fingerprint: 27:99:58:29:fe:6a:75:15:c1:bf:e8:48:f9:c4:76:1d:b1:6c:22:59:29:25:7b:f4:0d:08:94:f2:9e:a8:ba:f2
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFbzCCA1egAwIBAgISESChaRu/vbm9UpaPI+hIvyYRMA0GCSqGSIb3DQEBDQUA
|
||||
MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9w
|
||||
ZW5UcnVzdCBSb290IENBIEcyMB4XDTE0MDUyNjAwMDAwMFoXDTM4MDExNTAwMDAw
|
||||
MFowQDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwU
|
||||
T3BlblRydXN0IFJvb3QgQ0EgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
|
||||
AoICAQDMtlelM5QQgTJT32F+D3Y5z1zCU3UdSXqWON2ic2rxb95eolq5cSG+Ntmh
|
||||
/LzubKh8NBpxGuga2F8ORAbtp+Dz0mEL4DKiltE48MLaARf85KxP6O6JHnSrT78e
|
||||
CbY2albz4e6WiWYkBuTNQjpK3eCasMSCRbP+yatcfD7J6xcvDH1urqWPyKwlCm/6
|
||||
1UWY0jUJ9gNDlP7ZvyCVeYCYitmJNbtRG6Q3ffyZO6v/v6wNj0OxmXsWEH4db0fE
|
||||
FY8ElggGQgT4hNYdvJGmQr5J1WqIP7wtUdGejeBSzFfdNTVY27SPJIjki9/ca1TS
|
||||
gSuyzpJLHB9G+h3Ykst2Z7UJmQnlrBcUVXDGPKBWCgOz3GIZ38i1MH/1PCZ1Eb3X
|
||||
G7OHngevZXHloM8apwkQHZOJZlvoPGIytbU6bumFAYueQ4xncyhZW+vj3CzMpSZy
|
||||
YhK05pyDRPZRpOLAeiRXyg6lPzq1O4vldu5w5pLeFlwoW5cZJ5L+epJUzpM5ChaH
|
||||
vGOz9bGTXOBut9Dq+WIyiET7vycotjCVXRIouZW+j1MY5aIYFuJWpLIsEPUdN6b4
|
||||
t/bQWVyJ98LVtZR00dX+G7bw5tYee9I8y6jj9RjzIR9u701oBnstXW5DiabA+aC/
|
||||
gh7PU3+06yzbXfZqfUAkBXKJOAGTy3HCOV0GEfZvePg3DTmEJwIDAQABo2MwYTAO
|
||||
BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUajn6QiL3
|
||||
5okATV59M4PLuG53hq8wHwYDVR0jBBgwFoAUajn6QiL35okATV59M4PLuG53hq8w
|
||||
DQYJKoZIhvcNAQENBQADggIBAJjLq0A85TMCl38th6aP1F5Kr7ge57tx+4BkJamz
|
||||
Gj5oXScmp7oq4fBXgwpkTx4idBvpkF/wrM//T2h6OKQQbA2xx6R3gBi2oihEdqc0
|
||||
nXGEL8pZ0keImUEiyTCYYW49qKgFbdEfwFFEVn8nNQLdXpgKQuswv42hm1GqO+qT
|
||||
RmTFAHneIWv2V6CG1wZy7HBGS4tz3aAhdT7cHcCP009zHIXZ/n9iyJVvttN7jLpT
|
||||
wm+bREx50B1ws9efAvSyB7DH5fitIw6mVskpEndI2S9G/Tvw/HRwkqWOOAgfZDC2
|
||||
t0v7NqwQjqBSM2OdAzVWxWm9xiNaJ5T2pBL4LTM8oValX9YZ6e18CL13zSdkzJTa
|
||||
TkZQh+D5wVOAHrut+0dSixv9ovneDiK3PTNZbNTe9ZUGMg1RGUFcPk8G97krgCf2
|
||||
o6p6fAbhQ8MTOWIaNr3gKC6UAuQpLmBVrkA9sHSSXvAgZJY/X0VdiLWK2gKgW0VU
|
||||
3jg9CcCoSmVGFvyqv1ROTVu+OEO3KMqLM6oaJbolXCkvW0pujOotnCr2BXbgd5eA
|
||||
iN1nE28daCSLT7d0geX0YJ96Vdc+N9oWaz53rK4YcJUIeSkDiv7BO7M/Gg+kO14f
|
||||
WKGVyasvc0rQLW6aWQ9VGHgtPFGml4vmu7JwqkwR3v98KzfUetF3NI/n+UL3PIEM
|
||||
S1IK
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=OpenTrust Root CA G3 O=OpenTrust
|
||||
# Subject: CN=OpenTrust Root CA G3 O=OpenTrust
|
||||
# Label: "OpenTrust Root CA G3"
|
||||
# Serial: 1492104908271485653071219941864171170455615
|
||||
# MD5 Fingerprint: 21:37:b4:17:16:92:7b:67:46:70:a9:96:d7:a8:13:24
|
||||
# SHA1 Fingerprint: 6e:26:64:f3:56:bf:34:55:bf:d1:93:3f:7c:01:de:d8:13:da:8a:a6
|
||||
# SHA256 Fingerprint: b7:c3:62:31:70:6e:81:07:8c:36:7c:b8:96:19:8f:1e:32:08:dd:92:69:49:dd:8f:57:09:a4:10:f7:5b:62:92
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICITCCAaagAwIBAgISESDm+Ez8JLC+BUCs2oMbNGA/MAoGCCqGSM49BAMDMEAx
|
||||
CzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9wZW5U
|
||||
cnVzdCBSb290IENBIEczMB4XDTE0MDUyNjAwMDAwMFoXDTM4MDExNTAwMDAwMFow
|
||||
QDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwUT3Bl
|
||||
blRydXN0IFJvb3QgQ0EgRzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARK7liuTcpm
|
||||
3gY6oxH84Bjwbhy6LTAMidnW7ptzg6kjFYwvWYpa3RTqnVkrQ7cG7DK2uu5Bta1d
|
||||
oYXM6h0UZqNnfkbilPPntlahFVmhTzeXuSIevRHr9LIfXsMUmuXZl5mjYzBhMA4G
|
||||
A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRHd8MUi2I5
|
||||
DMlv4VBN0BBY3JWIbTAfBgNVHSMEGDAWgBRHd8MUi2I5DMlv4VBN0BBY3JWIbTAK
|
||||
BggqhkjOPQQDAwNpADBmAjEAj6jcnboMBBf6Fek9LykBl7+BFjNAk2z8+e2AcG+q
|
||||
j9uEwov1NcoG3GRvaBbhj5G5AjEA2Euly8LQCGzpGPta3U1fJAuwACEl74+nBCZx
|
||||
4nxp5V2a+EEfOzmTk51V6s2N8fvB
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=ISRG Root X1 O=Internet Security Research Group
|
||||
# Subject: CN=ISRG Root X1 O=Internet Security Research Group
|
||||
# Label: "ISRG Root X1"
|
||||
@@ -4398,3 +4235,66 @@ MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX
|
||||
ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg
|
||||
h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6
|
||||
# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6
|
||||
# Label: "GlobalSign Root CA - R6"
|
||||
# Serial: 1417766617973444989252670301619537
|
||||
# MD5 Fingerprint: 4f:dd:07:e4:d4:22:64:39:1e:0c:37:42:ea:d1:c6:ae
|
||||
# SHA1 Fingerprint: 80:94:64:0e:b5:a7:a1:ca:11:9c:1f:dd:d5:9f:81:02:63:a7:fb:d1
|
||||
# SHA256 Fingerprint: 2c:ab:ea:fe:37:d0:6c:a2:2a:ba:73:91:c0:03:3d:25:98:29:52:c4:53:64:73:49:76:3a:3a:b5:ad:6c:cf:69
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg
|
||||
MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh
|
||||
bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx
|
||||
MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET
|
||||
MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ
|
||||
KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI
|
||||
xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k
|
||||
ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD
|
||||
aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw
|
||||
LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw
|
||||
1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX
|
||||
k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2
|
||||
SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h
|
||||
bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n
|
||||
WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY
|
||||
rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce
|
||||
MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD
|
||||
AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu
|
||||
bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN
|
||||
nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt
|
||||
Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61
|
||||
55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj
|
||||
vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf
|
||||
cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz
|
||||
oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp
|
||||
nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs
|
||||
pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v
|
||||
JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R
|
||||
8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4
|
||||
5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Issuer: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed
|
||||
# Subject: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed
|
||||
# Label: "OISTE WISeKey Global Root GC CA"
|
||||
# Serial: 44084345621038548146064804565436152554
|
||||
# MD5 Fingerprint: a9:d6:b9:2d:2f:93:64:f8:a5:69:ca:91:e9:68:07:23
|
||||
# SHA1 Fingerprint: e0:11:84:5e:34:de:be:88:81:b9:9c:f6:16:26:d1:96:1f:c3:b9:31
|
||||
# SHA256 Fingerprint: 85:60:f9:1c:36:24:da:ba:95:70:b5:fe:a0:db:e3:6f:f1:1a:83:23:be:94:86:85:4f:b3:f3:4a:55:71:19:8d
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw
|
||||
CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91
|
||||
bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg
|
||||
Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ
|
||||
BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu
|
||||
ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS
|
||||
b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni
|
||||
eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W
|
||||
p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E
|
||||
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T
|
||||
rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV
|
||||
57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg
|
||||
Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
+34
-361
@@ -3,300 +3,44 @@
|
||||
|
||||
from __future__ import print_function, absolute_import
|
||||
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
import shlex
|
||||
import subprocess
|
||||
|
||||
import click
|
||||
import six
|
||||
|
||||
from click import echo, MultiCommand, Option, Argument, ParamType
|
||||
|
||||
__version__ = '0.3.1'
|
||||
|
||||
_invalid_ident_char_re = re.compile(r'[^a-zA-Z0-9_]')
|
||||
|
||||
|
||||
class CompletionConfiguration(object):
|
||||
def __init__(self):
|
||||
self.complete_options = False
|
||||
|
||||
|
||||
completion_configuration = CompletionConfiguration()
|
||||
|
||||
|
||||
def resolve_ctx(cli, prog_name, args):
|
||||
ctx = cli.make_context(prog_name, list(args), resilient_parsing=True)
|
||||
while ctx.args + ctx.protected_args and isinstance(ctx.command, MultiCommand):
|
||||
a = ctx.protected_args + ctx.args
|
||||
cmd = ctx.command.get_command(ctx, a[0])
|
||||
if cmd is None:
|
||||
return None
|
||||
ctx = cmd.make_context(a[0], a[1:], parent=ctx, resilient_parsing=True)
|
||||
return ctx
|
||||
|
||||
|
||||
def startswith(string, incomplete):
|
||||
"""Returns True when string starts with incomplete
|
||||
|
||||
It might be overridden with a fuzzier version - for example a case insensitive version"""
|
||||
return string.startswith(incomplete)
|
||||
|
||||
|
||||
def get_choices(cli, prog_name, args, incomplete):
|
||||
ctx = resolve_ctx(cli, prog_name, args)
|
||||
if ctx is None:
|
||||
return
|
||||
|
||||
optctx = None
|
||||
if args:
|
||||
for param in ctx.command.get_params(ctx):
|
||||
if isinstance(param, Option) and not param.is_flag and args[-1] in param.opts + param.secondary_opts:
|
||||
optctx = param
|
||||
|
||||
choices = []
|
||||
if optctx:
|
||||
choices += [c if isinstance(c, tuple) else (c, None) for c in optctx.type.complete(ctx, incomplete)]
|
||||
else:
|
||||
for param in ctx.command.get_params(ctx):
|
||||
if isinstance(param, Argument):
|
||||
choices += [c if isinstance(c, tuple) else (c, None) for c in param.type.complete(ctx, incomplete)]
|
||||
if (completion_configuration.complete_options or incomplete and not incomplete[:1].isalnum()) and isinstance(param, Option):
|
||||
for opt in param.opts:
|
||||
if startswith(opt, incomplete):
|
||||
choices.append((opt, param.help))
|
||||
for opt in param.secondary_opts:
|
||||
if startswith(opt, incomplete):
|
||||
# don't put the doc so fish won't group the primary and
|
||||
# and secondary options
|
||||
choices.append((opt, None))
|
||||
if isinstance(ctx.command, MultiCommand):
|
||||
for name in ctx.command.list_commands(ctx):
|
||||
if startswith(name, incomplete):
|
||||
choices.append((name, ctx.command.get_command_short_help(ctx, name)))
|
||||
|
||||
for item, help in choices:
|
||||
yield (item, help)
|
||||
|
||||
|
||||
def split_args(line):
|
||||
"""Version of shlex.split that silently accept incomplete strings."""
|
||||
lex = shlex.shlex(line, posix=True)
|
||||
lex.whitespace_split = True
|
||||
lex.commenters = ''
|
||||
res = []
|
||||
from click import ParamType
|
||||
if six.PY3:
|
||||
try:
|
||||
while True:
|
||||
res.append(next(lex))
|
||||
except ValueError: # No closing quotation
|
||||
pass
|
||||
except StopIteration: # End of loop
|
||||
pass
|
||||
if lex.token:
|
||||
res.append(lex.token)
|
||||
return res
|
||||
from enum import Enum
|
||||
except ImportError:
|
||||
from pipenv.vendor.backports.enum import Enum
|
||||
else:
|
||||
from pipenv.vendor.backports.enum import Enum
|
||||
|
||||
from click_completion.core import completion_configuration, get_code, install, shells, resolve_ctx, get_choices, \
|
||||
startswith, Shell
|
||||
from click_completion.lib import get_auto_shell
|
||||
from click_completion.patch import patch as _patch
|
||||
|
||||
def decode_args(strings):
|
||||
res = []
|
||||
for s in strings:
|
||||
s = split_args(s)
|
||||
s = s[0] if s else ''
|
||||
res.append(s)
|
||||
return res
|
||||
|
||||
|
||||
def do_bash_complete(cli, prog_name):
|
||||
comp_words = os.environ['COMP_WORDS']
|
||||
try:
|
||||
cwords = shlex.split(comp_words)
|
||||
quoted = False
|
||||
except ValueError: # No closing quotation
|
||||
cwords = split_args(comp_words)
|
||||
quoted = True
|
||||
cword = int(os.environ['COMP_CWORD'])
|
||||
args = cwords[1:cword]
|
||||
try:
|
||||
incomplete = cwords[cword]
|
||||
except IndexError:
|
||||
incomplete = ''
|
||||
choices = get_choices(cli, prog_name, args, incomplete)
|
||||
|
||||
if quoted:
|
||||
echo('\t'.join(opt for opt, _ in choices), nl=False)
|
||||
else:
|
||||
echo('\t'.join(re.sub(r"""([\s\\"'])""", r'\\\1', opt) for opt, _ in choices), nl=False)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def do_fish_complete(cli, prog_name):
|
||||
commandline = os.environ['COMMANDLINE']
|
||||
args = split_args(commandline)[1:]
|
||||
if args and not commandline.endswith(' '):
|
||||
incomplete = args[-1]
|
||||
args = args[:-1]
|
||||
else:
|
||||
incomplete = ''
|
||||
|
||||
for item, help in get_choices(cli, prog_name, args, incomplete):
|
||||
if help:
|
||||
echo("%s\t%s" % (item, re.sub('\s', ' ', help)))
|
||||
else:
|
||||
echo(item)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def do_zsh_complete(cli, prog_name):
|
||||
commandline = os.environ['COMMANDLINE']
|
||||
args = split_args(commandline)[1:]
|
||||
if args and not commandline.endswith(' '):
|
||||
incomplete = args[-1]
|
||||
args = args[:-1]
|
||||
else:
|
||||
incomplete = ''
|
||||
|
||||
def escape(s):
|
||||
return s.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)))
|
||||
else:
|
||||
res.append('"%s"' % escape(item))
|
||||
if res:
|
||||
echo("_arguments '*: :((%s))'" % '\n'.join(res))
|
||||
else:
|
||||
echo("_files")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def do_powershell_complete(cli, prog_name):
|
||||
commandline = os.environ['COMMANDLINE']
|
||||
args = split_args(commandline)[1:]
|
||||
quote = single_quote
|
||||
incomplete = ''
|
||||
if args and not commandline.endswith(' '):
|
||||
incomplete = args[-1]
|
||||
args = args[:-1]
|
||||
quote_pos = commandline.rfind(incomplete) - 1
|
||||
if quote_pos >= 0 and commandline[quote_pos] == '"':
|
||||
quote = double_quote
|
||||
|
||||
for item, help in get_choices(cli, prog_name, args, incomplete):
|
||||
echo(quote(item))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
find_unsafe = re.compile(r'[^\w@%+=:,./-]').search
|
||||
|
||||
|
||||
def single_quote(s):
|
||||
"""Return a shell-escaped version of the string *s*."""
|
||||
if not s:
|
||||
return "''"
|
||||
if find_unsafe(s) is None:
|
||||
return s
|
||||
|
||||
# use single quotes, and put single quotes into double quotes
|
||||
# the string $'b is then quoted as '$'"'"'b'
|
||||
return "'" + s.replace("'", "'\"'\"'") + "'"
|
||||
|
||||
|
||||
def double_quote(s):
|
||||
'''Return a shell-escaped version of the string *s*.'''
|
||||
if not s:
|
||||
return '""'
|
||||
if find_unsafe(s) is None:
|
||||
return s
|
||||
|
||||
# use double quotes, and put double quotes into single quotes
|
||||
# the string $"b is then quoted as "$"'"'"b"
|
||||
return '"' + s.replace('"', '"\'"\'"') + '"'
|
||||
|
||||
|
||||
# extend click completion features
|
||||
|
||||
def param_type_complete(self, ctx, incomplete):
|
||||
return []
|
||||
|
||||
|
||||
def choice_complete(self, ctx, incomplete):
|
||||
return [c for c in self.choices if c.startswith(incomplete)]
|
||||
|
||||
|
||||
def multicommand_get_command_short_help(self, ctx, cmd_name):
|
||||
return self.get_command(ctx, cmd_name).short_help
|
||||
|
||||
|
||||
def _shellcomplete(cli, prog_name, complete_var=None):
|
||||
"""Internal handler for the bash completion support."""
|
||||
if complete_var is None:
|
||||
complete_var = '_%s_COMPLETE' % (prog_name.replace('-', '_')).upper()
|
||||
complete_instr = os.environ.get(complete_var)
|
||||
if not complete_instr:
|
||||
return
|
||||
|
||||
if complete_instr == 'source':
|
||||
echo(get_code(prog_name=prog_name, env_name=complete_var))
|
||||
elif complete_instr == 'source-bash':
|
||||
echo(get_code('bash', prog_name, complete_var))
|
||||
elif complete_instr == 'source-fish':
|
||||
echo(get_code('fish', prog_name, complete_var))
|
||||
elif complete_instr == 'source-powershell':
|
||||
echo(get_code('powershell', prog_name, complete_var))
|
||||
elif complete_instr == 'source-zsh':
|
||||
echo(get_code('zsh', prog_name, complete_var))
|
||||
elif complete_instr in ['complete', 'complete-bash']:
|
||||
# keep 'complete' for bash for backward compatibility
|
||||
do_bash_complete(cli, prog_name)
|
||||
elif complete_instr == 'complete-fish':
|
||||
do_fish_complete(cli, prog_name)
|
||||
elif complete_instr == 'complete-powershell':
|
||||
do_powershell_complete(cli, prog_name)
|
||||
elif complete_instr == 'complete-zsh':
|
||||
do_zsh_complete(cli, prog_name)
|
||||
elif complete_instr == 'install':
|
||||
shell, path = install(prog_name=prog_name, env_name=complete_var)
|
||||
click.echo('%s completion installed in %s' % (shell, path))
|
||||
elif complete_instr == 'install-bash':
|
||||
shell, path = install(shell='bash', prog_name=prog_name, env_name=complete_var)
|
||||
click.echo('%s completion installed in %s' % (shell, path))
|
||||
elif complete_instr == 'install-fish':
|
||||
shell, path = install(shell='fish', prog_name=prog_name, env_name=complete_var)
|
||||
click.echo('%s completion installed in %s' % (shell, path))
|
||||
elif complete_instr == 'install-zsh':
|
||||
shell, path = install(shell='zsh', prog_name=prog_name, env_name=complete_var)
|
||||
click.echo('%s completion installed in %s' % (shell, path))
|
||||
elif complete_instr == 'install-powershell':
|
||||
shell, path = install(shell='powershell', prog_name=prog_name, env_name=complete_var)
|
||||
click.echo('%s completion installed in %s' % (shell, path))
|
||||
sys.exit()
|
||||
|
||||
__version__ = '0.4.1'
|
||||
|
||||
_initialized = False
|
||||
|
||||
|
||||
def init(complete_options=False):
|
||||
def init(complete_options=False, match_incomplete=None):
|
||||
"""Initialize the enhanced click completion
|
||||
|
||||
Args:
|
||||
complete_options (bool): always complete the options, even when the user hasn't typed a first dash
|
||||
Parameters
|
||||
----------
|
||||
complete_options : bool
|
||||
always complete the options, even when the user hasn't typed a first dash (Default value = False)
|
||||
match_incomplete : func
|
||||
a function with two parameters choice and incomplete. Must return True
|
||||
if incomplete is a correct match for choice, False otherwise.
|
||||
"""
|
||||
global _initialized
|
||||
if not _initialized:
|
||||
import click
|
||||
click.types.ParamType.complete = param_type_complete
|
||||
click.types.Choice.complete = choice_complete
|
||||
click.core.MultiCommand.get_command_short_help = multicommand_get_command_short_help
|
||||
click.core._bashcomplete = _shellcomplete
|
||||
_patch()
|
||||
completion_configuration.complete_options = complete_options
|
||||
if match_incomplete is not None:
|
||||
completion_configuration.match_incomplete = match_incomplete
|
||||
_initialized = True
|
||||
|
||||
|
||||
@@ -305,11 +49,19 @@ class DocumentedChoice(ParamType):
|
||||
supported values. All of these values have to be strings. Each value may
|
||||
be associated to a help message that will be display in the error message
|
||||
and during the completion.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
choices : dict or Enum
|
||||
A dictionary with the possible choice as key, and the corresponding help string as value
|
||||
"""
|
||||
name = 'choice'
|
||||
|
||||
def __init__(self, choices):
|
||||
self.choices = dict(choices)
|
||||
if isinstance(choices, Enum):
|
||||
self.choices = dict((choice.name, choice.value) for choice in choices)
|
||||
else:
|
||||
self.choices = dict(choices)
|
||||
|
||||
def get_metavar(self, param):
|
||||
return '[%s]' % '|'.join(self.choices.keys())
|
||||
@@ -338,84 +90,5 @@ class DocumentedChoice(ParamType):
|
||||
return 'DocumentedChoice(%r)' % list(self.choices.keys())
|
||||
|
||||
def complete(self, ctx, incomplete):
|
||||
return [(c, v) for c, v in six.iteritems(self.choices) if startswith(c, incomplete)]
|
||||
|
||||
|
||||
def get_code(shell=None, prog_name=None, env_name=None, extra_env=None):
|
||||
"""Return the specified completion code"""
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
if shell in [None, 'auto']:
|
||||
shell = get_auto_shell()
|
||||
prog_name = prog_name or click.get_current_context().find_root().info_name
|
||||
env_name = env_name or '_%s_COMPLETE' % prog_name.upper().replace('-', '_')
|
||||
extra_env = extra_env if extra_env else {}
|
||||
env = Environment(loader=FileSystemLoader(os.path.dirname(__file__)))
|
||||
template = env.get_template('%s.j2' % shell)
|
||||
return template.render(prog_name=prog_name, complete_var=env_name, extra_env=extra_env)
|
||||
|
||||
|
||||
def get_auto_shell():
|
||||
"""Return the shell that is calling this process"""
|
||||
try:
|
||||
import psutil
|
||||
parent = psutil.Process(os.getpid()).parent()
|
||||
if platform.system() == 'Windows':
|
||||
parent = parent.parent() or parent
|
||||
return parent.name().replace('.exe', '')
|
||||
except ImportError:
|
||||
raise click.UsageError("Please explicitly give the shell type or install the psutil package to activate the"
|
||||
" automatic shell detection.")
|
||||
|
||||
|
||||
def install(shell=None, prog_name=None, env_name=None, path=None, append=None, extra_env=None):
|
||||
"""Install the completion"""
|
||||
prog_name = prog_name or click.get_current_context().find_root().info_name
|
||||
shell = shell or get_auto_shell()
|
||||
if append is None and path is not None:
|
||||
append = True
|
||||
if append is not None:
|
||||
mode = 'a' if append else 'w'
|
||||
else:
|
||||
mode = None
|
||||
|
||||
if shell == 'fish':
|
||||
path = path or os.path.expanduser('~') + '/.config/fish/completions/%s.fish' % prog_name
|
||||
mode = mode or 'w'
|
||||
elif shell == 'bash':
|
||||
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'
|
||||
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 ''
|
||||
mode = mode or 'a'
|
||||
else:
|
||||
raise click.ClickException('%s is not supported.' % shell)
|
||||
|
||||
if append is not None:
|
||||
mode = 'a' if append else 'w'
|
||||
else:
|
||||
mode = mode
|
||||
d = os.path.dirname(path)
|
||||
if not os.path.exists(d):
|
||||
os.makedirs(d)
|
||||
f = open(path, mode)
|
||||
f.write(get_code(shell, prog_name, env_name, extra_env))
|
||||
f.write("\n")
|
||||
f.close()
|
||||
return shell, path
|
||||
|
||||
|
||||
shells = {
|
||||
'bash': 'Bourne again shell',
|
||||
'fish': 'Friendly interactive shell',
|
||||
'zsh': 'Z shell',
|
||||
'powershell': 'Windows PowerShell'
|
||||
}
|
||||
match = completion_configuration.match_incomplete
|
||||
return [(c, v) for c, v in six.iteritems(self.choices) if match(c, incomplete)]
|
||||
|
||||
+391
@@ -0,0 +1,391 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from __future__ import print_function, absolute_import
|
||||
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
|
||||
import click
|
||||
from click import Option, Argument, MultiCommand, echo
|
||||
import six
|
||||
if six.PY3:
|
||||
try:
|
||||
from enum import Enum
|
||||
except ImportError:
|
||||
from pipenv.vendor.backports.enum import Enum
|
||||
else:
|
||||
from pipenv.vendor.backports.enum import Enum
|
||||
|
||||
from click_completion.lib import resolve_ctx, split_args, single_quote, double_quote, get_auto_shell
|
||||
|
||||
|
||||
def startswith(string, incomplete):
|
||||
"""Returns True when string starts with incomplete
|
||||
|
||||
It might be overridden with a fuzzier version - for example a case insensitive version
|
||||
|
||||
Parameters
|
||||
----------
|
||||
string : str
|
||||
The string to check
|
||||
incomplete : str
|
||||
The incomplete string to compare to the begining of string
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if string starts with incomplete, False otherwise
|
||||
"""
|
||||
return string.startswith(incomplete)
|
||||
|
||||
|
||||
class CompletionConfiguration(object):
|
||||
"""A class to hold the completion configuration
|
||||
|
||||
Attributes
|
||||
----------
|
||||
|
||||
complete_options : bool
|
||||
Wether to complete the options or not. By default, the options are only completed after the user has entered
|
||||
a first dash '-'. Change this value to True to always complete the options, even without first typing any
|
||||
character.
|
||||
match_incomplete : func
|
||||
A function use to check whether a parameter match an incomplete argument typed by the user
|
||||
"""
|
||||
def __init__(self):
|
||||
self.complete_options = False
|
||||
self.match_incomplete = startswith
|
||||
|
||||
|
||||
def match(string, incomplete):
|
||||
import click_completion
|
||||
# backward compatibility handling
|
||||
if click_completion.startswith != startswith:
|
||||
fn = click_completion.startswith
|
||||
else:
|
||||
fn = completion_configuration.match_incomplete
|
||||
return fn(string, incomplete)
|
||||
|
||||
|
||||
def get_choices(cli, prog_name, args, incomplete):
|
||||
"""
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cli : click.Command
|
||||
The main click Command of the program
|
||||
prog_name : str
|
||||
The program name on the command line
|
||||
args : [str]
|
||||
The arguments already written by the user on the command line
|
||||
incomplete : str
|
||||
The partial argument to complete
|
||||
|
||||
Returns
|
||||
-------
|
||||
[(str, str)]
|
||||
A list of completion results. The first element of each tuple is actually the argument to complete, the second
|
||||
element is an help string for this argument.
|
||||
"""
|
||||
ctx = resolve_ctx(cli, prog_name, args)
|
||||
if ctx is None:
|
||||
return
|
||||
optctx = None
|
||||
if args:
|
||||
options = [param
|
||||
for param in ctx.command.get_params(ctx)
|
||||
if isinstance(param, Option)]
|
||||
arguments = [param
|
||||
for param in ctx.command.get_params(ctx)
|
||||
if isinstance(param, Argument)]
|
||||
for param in options:
|
||||
if not param.is_flag and args[-1] in param.opts + param.secondary_opts:
|
||||
optctx = param
|
||||
if optctx is None:
|
||||
for param in arguments:
|
||||
if (
|
||||
not incomplete.startswith("-")
|
||||
and (
|
||||
ctx.params.get(param.name) in (None, ())
|
||||
or param.nargs == -1
|
||||
)
|
||||
):
|
||||
optctx = param
|
||||
break
|
||||
choices = []
|
||||
if optctx:
|
||||
choices += [c if isinstance(c, tuple) else (c, None) for c in optctx.type.complete(ctx, incomplete)]
|
||||
else:
|
||||
for param in ctx.command.get_params(ctx):
|
||||
if (completion_configuration.complete_options or incomplete and not incomplete[:1].isalnum()) and isinstance(param, Option):
|
||||
for opt in param.opts:
|
||||
if match(opt, incomplete):
|
||||
choices.append((opt, param.help))
|
||||
for opt in param.secondary_opts:
|
||||
if match(opt, incomplete):
|
||||
# don't put the doc so fish won't group the primary and
|
||||
# and secondary options
|
||||
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)))
|
||||
|
||||
for item, help in choices:
|
||||
yield (item, help)
|
||||
|
||||
|
||||
def do_bash_complete(cli, prog_name):
|
||||
"""Do the completion for bash
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cli : click.Command
|
||||
The main click Command of the program
|
||||
prog_name : str
|
||||
The program name on the command line
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if the completion was successful, False otherwise
|
||||
"""
|
||||
comp_words = os.environ['COMP_WORDS']
|
||||
try:
|
||||
cwords = shlex.split(comp_words)
|
||||
quoted = False
|
||||
except ValueError: # No closing quotation
|
||||
cwords = split_args(comp_words)
|
||||
quoted = True
|
||||
cword = int(os.environ['COMP_CWORD'])
|
||||
args = cwords[1:cword]
|
||||
try:
|
||||
incomplete = cwords[cword]
|
||||
except IndexError:
|
||||
incomplete = ''
|
||||
choices = get_choices(cli, prog_name, args, incomplete)
|
||||
|
||||
if quoted:
|
||||
echo('\t'.join(opt for opt, _ in choices), nl=False)
|
||||
else:
|
||||
echo('\t'.join(re.sub(r"""([\s\\"'()])""", r'\\\1', opt) for opt, _ in choices), nl=False)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def do_fish_complete(cli, prog_name):
|
||||
"""Do the fish completion
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cli : click.Command
|
||||
The main click Command of the program
|
||||
prog_name : str
|
||||
The program name on the command line
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if the completion was successful, False otherwise
|
||||
"""
|
||||
commandline = os.environ['COMMANDLINE']
|
||||
args = split_args(commandline)[1:]
|
||||
if args and not commandline.endswith(' '):
|
||||
incomplete = args[-1]
|
||||
args = args[:-1]
|
||||
else:
|
||||
incomplete = ''
|
||||
|
||||
for item, help in get_choices(cli, prog_name, args, incomplete):
|
||||
if help:
|
||||
echo("%s\t%s" % (item, re.sub('\s', ' ', help)))
|
||||
else:
|
||||
echo(item)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def do_zsh_complete(cli, prog_name):
|
||||
"""Do the zsh completion
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cli : click.Command
|
||||
The main click Command of the program
|
||||
prog_name : str
|
||||
The program name on the command line
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if the completion was successful, False otherwise
|
||||
"""
|
||||
commandline = os.environ['COMMANDLINE']
|
||||
args = split_args(commandline)[1:]
|
||||
if args and not commandline.endswith(' '):
|
||||
incomplete = args[-1]
|
||||
args = args[:-1]
|
||||
else:
|
||||
incomplete = ''
|
||||
|
||||
def escape(s):
|
||||
return s.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)))
|
||||
else:
|
||||
res.append('"%s"' % escape(item))
|
||||
if res:
|
||||
echo("_arguments '*: :((%s))'" % '\n'.join(res))
|
||||
else:
|
||||
echo("_files")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def do_powershell_complete(cli, prog_name):
|
||||
"""Do the powershell completion
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cli : click.Command
|
||||
The main click Command of the program
|
||||
prog_name : str
|
||||
The program name on the command line
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if the completion was successful, False otherwise
|
||||
"""
|
||||
commandline = os.environ['COMMANDLINE']
|
||||
args = split_args(commandline)[1:]
|
||||
quote = single_quote
|
||||
incomplete = ''
|
||||
if args and not commandline.endswith(' '):
|
||||
incomplete = args[-1]
|
||||
args = args[:-1]
|
||||
quote_pos = commandline.rfind(incomplete) - 1
|
||||
if quote_pos >= 0 and commandline[quote_pos] == '"':
|
||||
quote = double_quote
|
||||
|
||||
for item, help in get_choices(cli, prog_name, args, incomplete):
|
||||
echo(quote(item))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def get_code(shell=None, prog_name=None, env_name=None, extra_env=None):
|
||||
"""Returns the completion code to be evaluated by the shell
|
||||
|
||||
Parameters
|
||||
----------
|
||||
shell : Shell
|
||||
The shell type (Default value = None)
|
||||
prog_name : str
|
||||
The program name on the command line (Default value = None)
|
||||
env_name : str
|
||||
The environment variable used to control the completion (Default value = None)
|
||||
extra_env : dict
|
||||
Some extra environment variables to be added to the generated code (Default value = None)
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The code to be evaluated by the shell
|
||||
"""
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
if shell in [None, 'auto']:
|
||||
shell = get_auto_shell()
|
||||
if not isinstance(shell, Shell):
|
||||
shell = Shell[shell]
|
||||
prog_name = prog_name or click.get_current_context().find_root().info_name
|
||||
env_name = env_name or '_%s_COMPLETE' % prog_name.upper().replace('-', '_')
|
||||
extra_env = extra_env if extra_env else {}
|
||||
env = Environment(loader=FileSystemLoader(os.path.dirname(__file__)))
|
||||
template = env.get_template('%s.j2' % shell.name)
|
||||
return template.render(prog_name=prog_name, complete_var=env_name, extra_env=extra_env)
|
||||
|
||||
|
||||
def install(shell=None, prog_name=None, env_name=None, path=None, append=None, extra_env=None):
|
||||
"""Install the completion
|
||||
|
||||
Parameters
|
||||
----------
|
||||
shell : Shell
|
||||
The shell type targeted. It will be guessed with get_auto_shell() if the value is None (Default value = None)
|
||||
prog_name : str
|
||||
The program name on the command line. It will be automatically computed if the value is None
|
||||
(Default value = None)
|
||||
env_name : str
|
||||
The environment variable name used to control the completion. It will be automatically computed if the value is
|
||||
None (Default value = None)
|
||||
path : str
|
||||
The installation path of the code to be evaluated by the shell. The standard installation path is used if the
|
||||
value is None (Default value = None)
|
||||
append : bool
|
||||
Whether to append the content to the file or to override it. The default behavior depends on the shell type
|
||||
(Default value = None)
|
||||
extra_env : dict
|
||||
A set of environment variables and their values to be added to the generated code (Default value = None)
|
||||
"""
|
||||
prog_name = prog_name or click.get_current_context().find_root().info_name
|
||||
shell = shell or get_auto_shell()
|
||||
if append is None and path is not None:
|
||||
append = True
|
||||
if append is not None:
|
||||
mode = 'a' if append else 'w'
|
||||
else:
|
||||
mode = None
|
||||
|
||||
if shell == 'fish':
|
||||
path = path or os.path.expanduser('~') + '/.config/fish/completions/%s.fish' % prog_name
|
||||
mode = mode or 'w'
|
||||
elif shell == 'bash':
|
||||
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'
|
||||
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 ''
|
||||
mode = mode or 'a'
|
||||
else:
|
||||
raise click.ClickException('%s is not supported.' % shell)
|
||||
|
||||
if append is not None:
|
||||
mode = 'a' if append else 'w'
|
||||
else:
|
||||
mode = mode
|
||||
d = os.path.dirname(path)
|
||||
if not os.path.exists(d):
|
||||
os.makedirs(d)
|
||||
f = open(path, mode)
|
||||
f.write(get_code(shell, prog_name, env_name, extra_env))
|
||||
f.write("\n")
|
||||
f.close()
|
||||
return shell, path
|
||||
|
||||
|
||||
class Shell(Enum):
|
||||
bash = 'Bourne again shell'
|
||||
fish = 'Friendly interactive shell'
|
||||
zsh = 'Z shell'
|
||||
powershell = 'Windows PowerShell'
|
||||
|
||||
|
||||
# deprecated - use Shell instead
|
||||
shells = dict((shell.name, shell.value) for shell in Shell)
|
||||
|
||||
|
||||
completion_configuration = CompletionConfiguration()
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from __future__ import print_function, absolute_import
|
||||
|
||||
import re
|
||||
import shlex
|
||||
|
||||
import click
|
||||
import shellingham
|
||||
from click import MultiCommand
|
||||
|
||||
find_unsafe = re.compile(r'[^\w@%+=:,./-]').search
|
||||
|
||||
|
||||
def single_quote(s):
|
||||
"""Escape a string with single quotes in order to be parsed as a single element by shlex
|
||||
|
||||
Parameters
|
||||
----------
|
||||
s : str
|
||||
The string to quote
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The quoted string
|
||||
"""
|
||||
if not s:
|
||||
return "''"
|
||||
if find_unsafe(s) is None:
|
||||
return s
|
||||
|
||||
# use single quotes, and put single quotes into double quotes
|
||||
# the string $'b is then quoted as '$'"'"'b'
|
||||
return "'" + s.replace("'", "'\"'\"'") + "'"
|
||||
|
||||
|
||||
def double_quote(s):
|
||||
"""Escape a string with double quotes in order to be parsed as a single element by shlex
|
||||
|
||||
Parameters
|
||||
----------
|
||||
s : str
|
||||
The string to quote
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The quoted string
|
||||
"""
|
||||
if not s:
|
||||
return '""'
|
||||
if find_unsafe(s) is None:
|
||||
return s
|
||||
|
||||
# use double quotes, and put double quotes into single quotes
|
||||
# the string $"b is then quoted as "$"'"'"b"
|
||||
return '"' + s.replace('"', '"\'"\'"') + '"'
|
||||
|
||||
|
||||
def resolve_ctx(cli, prog_name, args):
|
||||
"""
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cli : click.Command
|
||||
The main click Command of the program
|
||||
prog_name : str
|
||||
The program name on the command line
|
||||
args : [str]
|
||||
The arguments already written by the user on the command line
|
||||
|
||||
Returns
|
||||
-------
|
||||
click.core.Context
|
||||
A new context corresponding to the current command
|
||||
"""
|
||||
ctx = cli.make_context(prog_name, list(args), resilient_parsing=True)
|
||||
while ctx.args + ctx.protected_args and isinstance(ctx.command, MultiCommand):
|
||||
a = ctx.protected_args + ctx.args
|
||||
cmd = ctx.command.get_command(ctx, a[0])
|
||||
if cmd is None:
|
||||
return None
|
||||
ctx = cmd.make_context(a[0], a[1:], parent=ctx, resilient_parsing=True)
|
||||
return ctx
|
||||
|
||||
|
||||
def split_args(line):
|
||||
"""Version of shlex.split that silently accept incomplete strings.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
line : str
|
||||
The string to split
|
||||
|
||||
Returns
|
||||
-------
|
||||
[str]
|
||||
The line split in separated arguments
|
||||
"""
|
||||
lex = shlex.shlex(line, posix=True)
|
||||
lex.whitespace_split = True
|
||||
lex.commenters = ''
|
||||
res = []
|
||||
try:
|
||||
while True:
|
||||
res.append(next(lex))
|
||||
except ValueError: # No closing quotation
|
||||
pass
|
||||
except StopIteration: # End of loop
|
||||
pass
|
||||
if lex.token:
|
||||
res.append(lex.token)
|
||||
return res
|
||||
|
||||
|
||||
def get_auto_shell():
|
||||
"""Returns the current shell
|
||||
|
||||
This feature depends on psutil and will not work if it is not available"""
|
||||
return shellingham.detect_shell()[0]
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from __future__ import print_function, absolute_import
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import click
|
||||
from click import echo
|
||||
|
||||
from click_completion.core import do_bash_complete, do_fish_complete, do_zsh_complete, do_powershell_complete,\
|
||||
get_code, install, completion_configuration
|
||||
|
||||
"""All the code used to monkey patch click"""
|
||||
|
||||
|
||||
def param_type_complete(self, ctx, incomplete):
|
||||
"""Returns a set of possible completions values, along with their documentation string
|
||||
|
||||
Default implementation of the complete method for click.types.ParamType just returns an empty list
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ctx : click.core.Context
|
||||
The current context
|
||||
incomplete :
|
||||
The string to complete
|
||||
|
||||
Returns
|
||||
-------
|
||||
[(str, str)]
|
||||
A list of completion results. The first element of each tuple is actually the argument to complete, the second
|
||||
element is an help string for this argument.
|
||||
"""
|
||||
return []
|
||||
|
||||
|
||||
def choice_complete(self, ctx, incomplete):
|
||||
"""Returns the completion results for click.core.Choice
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ctx : click.core.Context
|
||||
The current context
|
||||
incomplete :
|
||||
The string to complete
|
||||
|
||||
Returns
|
||||
-------
|
||||
[(str, str)]
|
||||
A list of completion results
|
||||
"""
|
||||
return [
|
||||
(c, None) for c in self.choices
|
||||
if completion_configuration.match_incomplete(c, incomplete)
|
||||
]
|
||||
|
||||
|
||||
def multicommand_get_command_short_help(self, ctx, cmd_name):
|
||||
"""Returns the short help of a subcommand
|
||||
|
||||
It allows MultiCommand subclasses to implement more efficient ways to provide the subcommand short help, for
|
||||
example by leveraging some caching.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ctx : click.core.Context
|
||||
The current context
|
||||
cmd_name :
|
||||
The sub command name
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The sub command short help
|
||||
"""
|
||||
return self.get_command(ctx, cmd_name).short_help
|
||||
|
||||
|
||||
def _shellcomplete(cli, prog_name, complete_var=None):
|
||||
"""Internal handler for the bash completion support.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cli : click.Command
|
||||
The main click Command of the program
|
||||
prog_name : str
|
||||
The program name on the command line
|
||||
complete_var : str
|
||||
The environment variable name used to control the completion behavior (Default value = None)
|
||||
"""
|
||||
if complete_var is None:
|
||||
complete_var = '_%s_COMPLETE' % (prog_name.replace('-', '_')).upper()
|
||||
complete_instr = os.environ.get(complete_var)
|
||||
if not complete_instr:
|
||||
return
|
||||
|
||||
if complete_instr == 'source':
|
||||
echo(get_code(prog_name=prog_name, env_name=complete_var))
|
||||
elif complete_instr == 'source-bash':
|
||||
echo(get_code('bash', prog_name, complete_var))
|
||||
elif complete_instr == 'source-fish':
|
||||
echo(get_code('fish', prog_name, complete_var))
|
||||
elif complete_instr == 'source-powershell':
|
||||
echo(get_code('powershell', prog_name, complete_var))
|
||||
elif complete_instr == 'source-zsh':
|
||||
echo(get_code('zsh', prog_name, complete_var))
|
||||
elif complete_instr in ['complete', 'complete-bash']:
|
||||
# keep 'complete' for bash for backward compatibility
|
||||
do_bash_complete(cli, prog_name)
|
||||
elif complete_instr == 'complete-fish':
|
||||
do_fish_complete(cli, prog_name)
|
||||
elif complete_instr == 'complete-powershell':
|
||||
do_powershell_complete(cli, prog_name)
|
||||
elif complete_instr == 'complete-zsh':
|
||||
do_zsh_complete(cli, prog_name)
|
||||
elif complete_instr == 'install':
|
||||
shell, path = install(prog_name=prog_name, env_name=complete_var)
|
||||
click.echo('%s completion installed in %s' % (shell, path))
|
||||
elif complete_instr == 'install-bash':
|
||||
shell, path = install(shell='bash', prog_name=prog_name, env_name=complete_var)
|
||||
click.echo('%s completion installed in %s' % (shell, path))
|
||||
elif complete_instr == 'install-fish':
|
||||
shell, path = install(shell='fish', prog_name=prog_name, env_name=complete_var)
|
||||
click.echo('%s completion installed in %s' % (shell, path))
|
||||
elif complete_instr == 'install-zsh':
|
||||
shell, path = install(shell='zsh', prog_name=prog_name, env_name=complete_var)
|
||||
click.echo('%s completion installed in %s' % (shell, path))
|
||||
elif complete_instr == 'install-powershell':
|
||||
shell, path = install(shell='powershell', prog_name=prog_name, env_name=complete_var)
|
||||
click.echo('%s completion installed in %s' % (shell, path))
|
||||
sys.exit()
|
||||
|
||||
|
||||
def patch():
|
||||
"""Patch click"""
|
||||
import click
|
||||
click.types.ParamType.complete = param_type_complete
|
||||
click.types.Choice.complete = choice_complete
|
||||
click.core.MultiCommand.get_command_short_help = multicommand_get_command_short_help
|
||||
click.core._bashcomplete = _shellcomplete
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
_{{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
|
||||
if [[ "$(basename -- ${(%):-%x})" != "_{{prog_name}}" ]]; then
|
||||
autoload -U compinit && compinit
|
||||
compdef _{{prog_name}} {{prog_name}}
|
||||
fi
|
||||
|
||||
Vendored
+17
-1
@@ -8,7 +8,8 @@ except ImportError:
|
||||
'Run pip install "python-dotenv[cli]" to fix this.')
|
||||
sys.exit(1)
|
||||
|
||||
from .main import dotenv_values, get_key, set_key, unset_key
|
||||
from .main import dotenv_values, get_key, set_key, unset_key, run_command
|
||||
from .version import __version__
|
||||
|
||||
|
||||
@click.group()
|
||||
@@ -18,6 +19,7 @@ from .main import dotenv_values, get_key, set_key, unset_key
|
||||
@click.option('-q', '--quote', default='always',
|
||||
type=click.Choice(['always', 'never', 'auto']),
|
||||
help="Whether to quote or not the variable values. Default mode is always. This does not affect parsing.")
|
||||
@click.version_option(version=__version__)
|
||||
@click.pass_context
|
||||
def cli(ctx, file, quote):
|
||||
'''This script is used to set, get or unset values from a .env file.'''
|
||||
@@ -78,5 +80,19 @@ def unset(ctx, key):
|
||||
exit(1)
|
||||
|
||||
|
||||
@cli.command(context_settings={'ignore_unknown_options': True})
|
||||
@click.pass_context
|
||||
@click.argument('commandline', nargs=-1, type=click.UNPROCESSED)
|
||||
def run(ctx, commandline):
|
||||
"""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)
|
||||
exit(ret)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
|
||||
Vendored
+50
-2
@@ -7,6 +7,7 @@ import io
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
@@ -30,7 +31,7 @@ def parse_line(line):
|
||||
k, v = line.split('=', 1)
|
||||
|
||||
if k.startswith('export '):
|
||||
k = k.lstrip('export ')
|
||||
(_, _, k) = k.partition('export ')
|
||||
|
||||
# Remove any leading and trailing spaces in key, value
|
||||
k, v = k.strip(), v.strip()
|
||||
@@ -238,7 +239,11 @@ def find_dotenv(filename='.env', raise_error_if_not_found=False, usecwd=False):
|
||||
path = os.getcwd()
|
||||
else:
|
||||
# will work for .py files
|
||||
frame_filename = sys._getframe().f_back.f_code.co_filename
|
||||
frame = sys._getframe()
|
||||
# find first frame that is outside of this file
|
||||
while frame.f_code.co_filename == __file__:
|
||||
frame = frame.f_back
|
||||
frame_filename = frame.f_code.co_filename
|
||||
path = os.path.dirname(os.path.abspath(frame_filename))
|
||||
|
||||
for dirname in _walk_to_root(path):
|
||||
@@ -260,3 +265,46 @@ def load_dotenv(dotenv_path=None, stream=None, verbose=False, override=False):
|
||||
def dotenv_values(dotenv_path=None, stream=None, verbose=False):
|
||||
f = dotenv_path or stream or find_dotenv()
|
||||
return DotEnv(f, verbose=verbose).dict()
|
||||
|
||||
|
||||
def run_command(command, env):
|
||||
"""Run command in sub process.
|
||||
|
||||
Runs the command in a sub process with the variables from `env`
|
||||
added in the current environment variables.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
command: List[str]
|
||||
The command and it's parameters
|
||||
env: Dict
|
||||
The additional environment variables
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The return code of the command
|
||||
|
||||
"""
|
||||
# copy the current environment variables and add the vales from
|
||||
# `env`
|
||||
cmd_env = os.environ.copy()
|
||||
cmd_env.update(env)
|
||||
|
||||
p = Popen(command,
|
||||
stdin=PIPE,
|
||||
stdout=PIPE,
|
||||
stderr=STDOUT,
|
||||
universal_newlines=True,
|
||||
bufsize=0,
|
||||
shell=False,
|
||||
env=cmd_env)
|
||||
try:
|
||||
out, _ = p.communicate()
|
||||
print(out)
|
||||
except Exception:
|
||||
warnings.warn('An error occured, running the command:')
|
||||
out, _ = p.communicate()
|
||||
warnings.warn(out)
|
||||
|
||||
return p.returncode
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
__version__ = "0.9.1"
|
||||
@@ -1,19 +1,25 @@
|
||||
Copyright (c) Kenneth Reitz and individual contributors.
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2018, Brett Cannon
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
Vendored
+145
@@ -0,0 +1,145 @@
|
||||
"""Help for working with modules."""
|
||||
__version__ = "2.0.0"
|
||||
|
||||
import importlib
|
||||
import importlib.machinery
|
||||
import importlib.util
|
||||
import types
|
||||
|
||||
|
||||
STANDARD_MODULE_ATTRS = frozenset(['__all__', '__builtins__', '__cached__',
|
||||
'__doc__', '__file__', '__loader__',
|
||||
'__name__', '__package__', '__spec__',
|
||||
'__getattr__'])
|
||||
|
||||
|
||||
class ModuleAttributeError(AttributeError):
|
||||
"""An AttributeError specifically for modules.
|
||||
|
||||
The module_name and 'attribute' attributes are set to strings representing
|
||||
the module the attribute was searched on and the missing attribute,
|
||||
respectively.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, module_name, attribute):
|
||||
self.module_name = module_name
|
||||
self.attribute = attribute
|
||||
super().__init__(f"module {module_name!r} has no attribute {attribute!r}")
|
||||
|
||||
|
||||
|
||||
def lazy_import(module_name, to_import):
|
||||
"""Return the importing module and a callable for lazy importing.
|
||||
|
||||
The module named by module_name represents the module performing the
|
||||
import to help facilitate resolving relative imports.
|
||||
|
||||
to_import is an iterable of the modules to be potentially imported (absolute
|
||||
or relative). The `as` form of importing is also supported,
|
||||
e.g. `pkg.mod as spam`.
|
||||
|
||||
This function returns a tuple of two items. The first is the importer
|
||||
module for easy reference within itself. The second item is a callable to be
|
||||
set to `__getattr__`.
|
||||
"""
|
||||
module = importlib.import_module(module_name)
|
||||
import_mapping = {}
|
||||
for name in to_import:
|
||||
importing, _, binding = name.partition(' as ')
|
||||
if not binding:
|
||||
_, _, binding = importing.rpartition('.')
|
||||
import_mapping[binding] = importing
|
||||
|
||||
def __getattr__(name):
|
||||
if name not in import_mapping:
|
||||
raise ModuleAttributeError(module_name, name)
|
||||
importing = import_mapping[name]
|
||||
# imortlib.import_module() implicitly sets submodules on this module as
|
||||
# appropriate for direct imports.
|
||||
imported = importlib.import_module(importing,
|
||||
module.__spec__.parent)
|
||||
setattr(module, name, imported)
|
||||
return imported
|
||||
|
||||
return module, __getattr__
|
||||
|
||||
|
||||
def filtered_attrs(module, *, modules=False, private=False, dunder=False,
|
||||
common=False):
|
||||
"""Return a collection of attributes on 'module'.
|
||||
|
||||
If 'modules' is false then module instances are excluded. If 'private' is
|
||||
false then attributes starting with, but not ending in, '_' will be
|
||||
excluded. With 'dunder' set to false then attributes starting and ending
|
||||
with '_' are left out. The 'common' argument controls whether attributes
|
||||
found in STANDARD_MODULE_ATTRS are returned.
|
||||
|
||||
"""
|
||||
attr_names = set()
|
||||
for name, value in module.__dict__.items():
|
||||
if not common and name in STANDARD_MODULE_ATTRS:
|
||||
continue
|
||||
if name.startswith('_'):
|
||||
if name.endswith('_'):
|
||||
if not dunder:
|
||||
continue
|
||||
elif not private:
|
||||
continue
|
||||
if not modules and isinstance(value, types.ModuleType):
|
||||
continue
|
||||
attr_names.add(name)
|
||||
return frozenset(attr_names)
|
||||
|
||||
|
||||
def calc___all__(module_name, **kwargs):
|
||||
"""Return a sorted list of defined attributes on 'module_name'.
|
||||
|
||||
All values specified in **kwargs are directly passed to filtered_attrs().
|
||||
|
||||
"""
|
||||
module = importlib.import_module(module_name)
|
||||
return sorted(filtered_attrs(module, **kwargs))
|
||||
|
||||
|
||||
def filtered_dir(module_name, *, additions={}, **kwargs):
|
||||
"""Return a callable appropriate for __dir__().
|
||||
|
||||
All values specified in **kwargs get passed directly to filtered_attrs().
|
||||
The 'additions' argument should be an iterable which is added to the final
|
||||
results.
|
||||
|
||||
"""
|
||||
module = importlib.import_module(module_name)
|
||||
|
||||
def __dir__():
|
||||
attr_names = set(filtered_attrs(module, **kwargs))
|
||||
attr_names.update(additions)
|
||||
return sorted(attr_names)
|
||||
|
||||
return __dir__
|
||||
|
||||
|
||||
def chained___getattr__(module_name, *getattrs):
|
||||
"""Create a callable which calls each __getattr__ in sequence.
|
||||
|
||||
Any raised ModuleAttributeError which matches module_name and the
|
||||
attribute being searched for will be caught and the search will continue.
|
||||
All other exceptions will be allowed to propagate. If no callable
|
||||
successfully returns a value, ModuleAttributeError will be raised.
|
||||
|
||||
"""
|
||||
def __getattr__(name):
|
||||
"""Call each __getattr__ function in sequence."""
|
||||
for getattr_ in getattrs:
|
||||
try:
|
||||
return getattr_(name)
|
||||
except ModuleAttributeError as exc:
|
||||
if exc.module_name == module_name and exc.attribute == name:
|
||||
continue
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
raise ModuleAttributeError(module_name, name)
|
||||
|
||||
return __getattr__
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
Copyright (c) 2018, Dan Ryan <dan@danryan.co>
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
Vendored
+79
@@ -0,0 +1,79 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
|
||||
__version__ = '0.1.2'
|
||||
|
||||
__all__ = [
|
||||
"__version__",
|
||||
"_strip_extras",
|
||||
"cmdoptions",
|
||||
"Command",
|
||||
"ConfigOptionParser",
|
||||
"DistributionNotFound",
|
||||
"FAVORITE_HASH",
|
||||
"FormatControl",
|
||||
"get_installed_distributions",
|
||||
"index_group",
|
||||
"InstallRequirement",
|
||||
"is_archive_file",
|
||||
"is_file_url",
|
||||
"is_installable_dir",
|
||||
"unpack_url",
|
||||
"Link",
|
||||
"make_abstract_dist",
|
||||
"make_option_group",
|
||||
"PackageFinder",
|
||||
"parse_requirements",
|
||||
"parse_version",
|
||||
"path_to_url",
|
||||
"pip_version",
|
||||
"PipError",
|
||||
"RequirementPreparer",
|
||||
"RequirementSet",
|
||||
"RequirementTracker",
|
||||
"Resolver",
|
||||
"SafeFileCache",
|
||||
"url_to_path",
|
||||
"USER_CACHE_DIR",
|
||||
"VcsSupport",
|
||||
"Wheel",
|
||||
"WheelCache",
|
||||
"WheelBuilder"
|
||||
]
|
||||
|
||||
from .shims import (
|
||||
_strip_extras,
|
||||
cmdoptions,
|
||||
Command,
|
||||
ConfigOptionParser,
|
||||
DistributionNotFound,
|
||||
FAVORITE_HASH,
|
||||
FormatControl,
|
||||
get_installed_distributions,
|
||||
index_group,
|
||||
InstallRequirement,
|
||||
is_archive_file,
|
||||
is_file_url,
|
||||
unpack_url,
|
||||
is_installable_dir,
|
||||
Link,
|
||||
make_abstract_dist,
|
||||
make_option_group,
|
||||
PackageFinder,
|
||||
parse_requirements,
|
||||
parse_version,
|
||||
path_to_url,
|
||||
pip_version,
|
||||
PipError,
|
||||
RequirementPreparer,
|
||||
RequirementSet,
|
||||
RequirementTracker,
|
||||
Resolver,
|
||||
SafeFileCache,
|
||||
url_to_path,
|
||||
USER_CACHE_DIR,
|
||||
VcsSupport,
|
||||
Wheel,
|
||||
WheelCache,
|
||||
WheelBuilder,
|
||||
)
|
||||
Vendored
+163
@@ -0,0 +1,163 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from collections import namedtuple
|
||||
from contextlib import contextmanager
|
||||
from .utils import _parse, get_package, STRING_TYPES
|
||||
import importlib
|
||||
import os
|
||||
from pipenv.patched.notpip import __version__ as pip_version
|
||||
import sys
|
||||
|
||||
|
||||
has_modutil = False
|
||||
if sys.version_info[:2] >= (3, 7):
|
||||
try:
|
||||
import modutil
|
||||
except ImportError:
|
||||
has_modutil = False
|
||||
else:
|
||||
has_modutil = True
|
||||
|
||||
|
||||
BASE_IMPORT_PATH = os.environ.get("PIP_SHIMS_BASE_MODULE", "pip")
|
||||
path_info = namedtuple("PathInfo", "path start_version end_version")
|
||||
parsed_pip_version = _parse(pip_version)
|
||||
|
||||
|
||||
def is_valid(path_info_tuple):
|
||||
if (
|
||||
path_info_tuple.start_version <= parsed_pip_version
|
||||
and path_info_tuple.end_version >= parsed_pip_version
|
||||
):
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def get_ordered_paths(module_paths, base_path):
|
||||
if not isinstance(module_paths, list):
|
||||
module_paths = [module_paths]
|
||||
prefix_order = [pth.format(base_path) for pth in ["{0}._internal", "{0}"]]
|
||||
if _parse(pip_version) < _parse("10.0.0"):
|
||||
prefix_order = reversed(prefix_order)
|
||||
paths = sorted(module_paths, key=is_valid, reverse=True)
|
||||
search_order = [
|
||||
"{0}.{1}".format(p, pth.path)
|
||||
for p in prefix_order
|
||||
for pth in paths
|
||||
if pth is not None
|
||||
]
|
||||
return search_order
|
||||
|
||||
|
||||
def do_import(module_paths, base_path=BASE_IMPORT_PATH):
|
||||
search_order = get_ordered_paths(module_paths, base_path)
|
||||
imported = None
|
||||
if has_modutil:
|
||||
pkgs = [get_package(pkg) for pkg in search_order]
|
||||
imports = [
|
||||
modutil.lazy_import(__name__, {to_import}) for to_import, pkg in pkgs
|
||||
]
|
||||
imp_getattrs = [imp_getattr for mod, imp_getattr in imports]
|
||||
chained = modutil.chained___getattr__(__name__, *imp_getattrs)
|
||||
imported = None
|
||||
for to_import, pkg in pkgs:
|
||||
_, _, module_name = to_import.rpartition(".")
|
||||
try:
|
||||
imported = chained(module_name)
|
||||
except (modutil.ModuleAttributeError, ImportError):
|
||||
continue
|
||||
else:
|
||||
if not imported:
|
||||
continue
|
||||
return getattr(imported, pkg)
|
||||
if not imported:
|
||||
return
|
||||
return imported
|
||||
for to_import in search_order:
|
||||
to_import, package = get_package(to_import)
|
||||
try:
|
||||
imported = importlib.import_module(to_import)
|
||||
except ImportError:
|
||||
continue
|
||||
else:
|
||||
return getattr(imported, package)
|
||||
return imported
|
||||
|
||||
|
||||
def pip_import(import_name, *module_paths):
|
||||
paths = []
|
||||
for pip_path in module_paths:
|
||||
if not isinstance(pip_path, (list, tuple)):
|
||||
module_path, start_version, end_version = module_paths
|
||||
new_path = path_info(module_path, _parse(start_version), _parse(end_version))
|
||||
paths.append(new_path)
|
||||
break
|
||||
else:
|
||||
module_path, start_version, end_version = pip_path
|
||||
paths.append(path_info(module_path, _parse(start_version), _parse(end_version)))
|
||||
return do_import(paths)
|
||||
|
||||
|
||||
parse_version = pip_import("parse_version", "index.parse_version", "7", "9999")
|
||||
_strip_extras = pip_import("_strip_extras", "req.req_install._strip_extras", "7", "9999")
|
||||
cmdoptions = pip_import(
|
||||
"", ("cli.cmdoptions", "18.1", "9999"), ("cmdoptions", "7.0.0", "18.0"),
|
||||
)
|
||||
Command = pip_import("Command",
|
||||
("cli.base_command.Command", "18.1", "9999"),
|
||||
("basecommand.Command", "7.0.0", "18.0"),
|
||||
)
|
||||
ConfigOptionParser = pip_import("ConfigOptionParser",
|
||||
("cli.parser.ConfigOptionParser", "18.1", "9999"),
|
||||
("baseparser.ConfigOptionParser", "7.0.0", "18.0"),
|
||||
)
|
||||
DistributionNotFound = pip_import("DistributionNotFound", "exceptions.DistributionNotFound", "7.0.0", "9999")
|
||||
FAVORITE_HASH = pip_import("FAVORITE_HASH", "utils.hashes.FAVORITE_HASH", "7.0.0", "9999")
|
||||
FormatControl = pip_import("FormatControl", "index.FormatControl", "7.0.0", "9999")
|
||||
get_installed_distributions = pip_import("get_installed_distributions",
|
||||
("utils.misc.get_installed_distributions", "10", "9999"),
|
||||
("utils.get_installed_distributions", "7", "9.0.3")
|
||||
)
|
||||
index_group = pip_import("index_group",
|
||||
("cli.cmdoptions.index_group", "18.1", "9999"),
|
||||
("cmdoptions.index_group", "7.0.0", "18.0"),
|
||||
)
|
||||
InstallRequirement = pip_import("InstallRequirement", "req.req_install.InstallRequirement", "7.0.0", "9999")
|
||||
is_archive_file = pip_import("is_archive_file", "download.is_archive_file", "7.0.0", "9999")
|
||||
is_file_url = pip_import("is_file_url", "download.is_file_url", "7.0.0", "9999")
|
||||
unpack_url = pip_import("unpack_url", "download.unpack_url", "7.0.0", "9999")
|
||||
is_installable_dir = pip_import("is_installable_dir",
|
||||
("utils.misc.is_installable_dir", "10.0.0", "9999"),
|
||||
("utils.is_installable_dir", "7.0.0", "9.0.3"),
|
||||
)
|
||||
Link = pip_import("Link", "index.Link", "7.0.0", "9999")
|
||||
make_abstract_dist = pip_import("make_abstract_dist",
|
||||
("operations.prepare.make_abstract_dist", "10.0.0", "9999"),
|
||||
("req.req_set.make_abstract_dist", "7.0.0", "9.0.3"),
|
||||
)
|
||||
make_option_group = pip_import("make_option_group",
|
||||
("cli.cmdoptions.make_option_group", "18.1", "9999"),
|
||||
("cmdoptions.make_option_group", "7.0.0", "18.0"),
|
||||
)
|
||||
PackageFinder = pip_import("PackageFinder", "index.PackageFinder", "7.0.0", "9999")
|
||||
parse_requirements = pip_import("parse_requirements", "req.req_file.parse_requirements", "7.0.0", "9999")
|
||||
parse_version = pip_import("parse_version", "index.parse_version", "7.0.0", "9999")
|
||||
path_to_url = pip_import("path_to_url", "download.path_to_url", "7.0.0", "9999")
|
||||
PipError = pip_import("PipError", "exceptions.PipError", "7.0.0", "9999")
|
||||
RequirementPreparer = pip_import("RequirementPreparer", "operations.prepare.RequirementPreparer", "7", "9999")
|
||||
RequirementSet = pip_import("RequirementSet", "req.req_set.RequirementSet", "7.0.0", "9999")
|
||||
RequirementTracker = pip_import("RequirementTracker", "req.req_tracker.RequirementTracker", "7.0.0", "9999")
|
||||
Resolver = pip_import("Resolver", "resolve.Resolver", "7.0.0", "9999")
|
||||
SafeFileCache = pip_import("SafeFileCache", "download.SafeFileCache", "7.0.0", "9999")
|
||||
url_to_path = pip_import("url_to_path", "download.url_to_path", "7.0.0", "9999")
|
||||
USER_CACHE_DIR = pip_import("USER_CACHE_DIR", "locations.USER_CACHE_DIR", "7.0.0", "9999")
|
||||
VcsSupport = pip_import("VcsSupport", "vcs.VcsSupport", "7.0.0", "9999")
|
||||
Wheel = pip_import("Wheel", "wheel.Wheel", "7.0.0", "9999")
|
||||
WheelCache = pip_import("WheelCache", ("cache.WheelCache", "10.0.0", "9999"), ("wheel.WheelCache", "7", "9.0.3"))
|
||||
WheelBuilder = pip_import("WheelBuilder", "wheel.WheelBuilder", "7.0.0", "9999")
|
||||
|
||||
|
||||
if not RequirementTracker:
|
||||
|
||||
@contextmanager
|
||||
def RequirementTracker():
|
||||
yield
|
||||
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from functools import wraps
|
||||
import sys
|
||||
|
||||
STRING_TYPES = (str,)
|
||||
if sys.version_info < (3, 0):
|
||||
STRING_TYPES = STRING_TYPES + (unicode,)
|
||||
|
||||
|
||||
def memoize(obj):
|
||||
cache = obj.cache = {}
|
||||
|
||||
@wraps(obj)
|
||||
def memoizer(*args, **kwargs):
|
||||
key = str(args) + str(kwargs)
|
||||
if key not in cache:
|
||||
cache[key] = obj(*args, **kwargs)
|
||||
return cache[key]
|
||||
return memoizer
|
||||
|
||||
|
||||
@memoize
|
||||
def _parse(version):
|
||||
if isinstance(version, STRING_TYPES):
|
||||
return tuple((int(i) for i in version.split(".")))
|
||||
return version
|
||||
|
||||
|
||||
def get_package(module, subimport=None):
|
||||
package = None
|
||||
if subimport:
|
||||
package = subimport
|
||||
else:
|
||||
module, _, package = module.rpartition(".")
|
||||
return module, package
|
||||
Vendored
+1
-1
@@ -24,7 +24,7 @@ import pkg_resources
|
||||
# from graphviz import backend, Digraph
|
||||
|
||||
|
||||
__version__ = '0.12.1'
|
||||
__version__ = '0.13.0'
|
||||
|
||||
|
||||
flatten = chain.from_iterable
|
||||
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
Copyright (c) 2018, Tzu-ping Chung <uranusjr@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
__all__ = [
|
||||
"__version__",
|
||||
"Lockfile", "Pipfile",
|
||||
]
|
||||
|
||||
__version__ = '0.1.1'
|
||||
|
||||
from .lockfiles import Lockfile
|
||||
from .pipfiles import Pipfile
|
||||
Vendored
+151
@@ -0,0 +1,151 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
|
||||
import six
|
||||
|
||||
from .models import DataView, Meta, PackageCollection
|
||||
|
||||
|
||||
class _LockFileEncoder(json.JSONEncoder):
|
||||
"""A specilized JSON encoder to convert loaded data into a lock file.
|
||||
|
||||
This adds a few characteristics to the encoder:
|
||||
|
||||
* The JSON is always prettified with indents and spaces.
|
||||
* The output is always UTF-8-encoded text, never binary, even on Python 2.
|
||||
"""
|
||||
def __init__(self):
|
||||
super(_LockFileEncoder, self).__init__(
|
||||
indent=4, separators=(",", ": "), sort_keys=True,
|
||||
)
|
||||
|
||||
def encode(self, obj):
|
||||
content = super(_LockFileEncoder, self).encode(obj)
|
||||
if not isinstance(content, six.text_type):
|
||||
content = content.decode("utf-8")
|
||||
content += "\n"
|
||||
return content
|
||||
|
||||
def iterencode(self, obj):
|
||||
for chunk in super(_LockFileEncoder, self).iterencode(obj):
|
||||
if not isinstance(chunk, six.text_type):
|
||||
chunk = chunk.decode("utf-8")
|
||||
yield chunk
|
||||
yield "\n"
|
||||
|
||||
|
||||
LOCKFILE_SECTIONS = {
|
||||
"_meta": Meta,
|
||||
"default": PackageCollection,
|
||||
"develop": PackageCollection,
|
||||
}
|
||||
|
||||
PIPFILE_SPEC_CURRENT = 6
|
||||
|
||||
|
||||
class Lockfile(DataView):
|
||||
"""Representation of a Pipfile.lock.
|
||||
"""
|
||||
__SCHEMA__ = {
|
||||
"_meta": {"type": "dict", "required": True},
|
||||
"default": {"type": "dict", "required": True},
|
||||
"develop": {"type": "dict", "required": True},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def validate(cls, data):
|
||||
super(Lockfile, cls).validate(data)
|
||||
for key, klass in LOCKFILE_SECTIONS.items():
|
||||
klass.validate(data[key])
|
||||
|
||||
@classmethod
|
||||
def load(cls, f, encoding=None):
|
||||
if encoding is None:
|
||||
data = json.load(f)
|
||||
else:
|
||||
data = json.loads(f.read().decode(encoding))
|
||||
return cls(data)
|
||||
|
||||
@classmethod
|
||||
def with_meta_from(cls, pipfile):
|
||||
data = {
|
||||
"_meta": {
|
||||
"hash": pipfile.get_hash()._data,
|
||||
"pipfile-spec": PIPFILE_SPEC_CURRENT,
|
||||
"requires": pipfile._data.get("requires", {}).copy(),
|
||||
"sources": pipfile.sources._data.copy(),
|
||||
},
|
||||
"default": {},
|
||||
"develop": {},
|
||||
}
|
||||
return cls(data)
|
||||
|
||||
def __getitem__(self, key):
|
||||
value = self._data[key]
|
||||
try:
|
||||
return LOCKFILE_SECTIONS[key](value)
|
||||
except KeyError:
|
||||
return value
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if isinstance(value, DataView):
|
||||
self._data[key] = value._data
|
||||
else:
|
||||
self._data[key] = value
|
||||
|
||||
def is_up_to_date(self, pipfile):
|
||||
return self.meta.hash == pipfile.get_hash()
|
||||
|
||||
def dump(self, f, encoding=None):
|
||||
encoder = _LockFileEncoder()
|
||||
if encoding is None:
|
||||
for chunk in encoder.iterencode(self._data):
|
||||
f.write(chunk)
|
||||
else:
|
||||
content = encoder.encode(self._data)
|
||||
f.write(content.encode(encoding))
|
||||
|
||||
@property
|
||||
def meta(self):
|
||||
try:
|
||||
return self["_meta"]
|
||||
except KeyError:
|
||||
raise AttributeError("meta")
|
||||
|
||||
@meta.setter
|
||||
def meta(self, value):
|
||||
self["_meta"] = value
|
||||
|
||||
@property
|
||||
def _meta(self):
|
||||
try:
|
||||
return self["_meta"]
|
||||
except KeyError:
|
||||
raise AttributeError("meta")
|
||||
|
||||
@_meta.setter
|
||||
def _meta(self, value):
|
||||
self["_meta"] = value
|
||||
|
||||
@property
|
||||
def default(self):
|
||||
try:
|
||||
return self["default"]
|
||||
except KeyError:
|
||||
raise AttributeError("default")
|
||||
|
||||
@default.setter
|
||||
def default(self, value):
|
||||
self["default"] = value
|
||||
|
||||
@property
|
||||
def develop(self):
|
||||
try:
|
||||
return self["develop"]
|
||||
except KeyError:
|
||||
raise AttributeError("develop")
|
||||
|
||||
@develop.setter
|
||||
def develop(self, value):
|
||||
self["develop"] = value
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
__all__ = [
|
||||
"DataView", "DataViewCollection", "DataViewMapping", "DataViewSequence",
|
||||
"validate", "ValidationError",
|
||||
"Hash", "Package", "Requires", "Source", "Script",
|
||||
"Meta", "PackageCollection", "ScriptCollection", "SourceCollection",
|
||||
]
|
||||
|
||||
from .base import (
|
||||
DataView, DataViewCollection, DataViewMapping, DataViewSequence,
|
||||
validate, ValidationError,
|
||||
)
|
||||
|
||||
from .hashes import Hash
|
||||
from .packages import Package
|
||||
from .scripts import Script
|
||||
from .sources import Source
|
||||
|
||||
from .sections import (
|
||||
Meta, Requires, PackageCollection, ScriptCollection, SourceCollection,
|
||||
)
|
||||
Vendored
+132
@@ -0,0 +1,132 @@
|
||||
try:
|
||||
import cerberus
|
||||
except ImportError:
|
||||
cerberus = None
|
||||
|
||||
|
||||
class ValidationError(ValueError):
|
||||
def __init__(self, value, validator):
|
||||
super(ValidationError, self).__init__(value)
|
||||
self.validator = validator
|
||||
|
||||
|
||||
VALIDATORS = {}
|
||||
|
||||
|
||||
def validate(cls, data):
|
||||
if not cerberus: # Skip validation if Cerberus is not available.
|
||||
return
|
||||
schema = cls.__SCHEMA__
|
||||
key = id(schema)
|
||||
try:
|
||||
v = VALIDATORS[key]
|
||||
except KeyError:
|
||||
v = VALIDATORS[key] = cerberus.Validator(schema, allow_unknown=True)
|
||||
if v.validate(data, normalize=False):
|
||||
return
|
||||
raise ValidationError(data, v)
|
||||
|
||||
|
||||
class DataView(object):
|
||||
"""A "view" to a data.
|
||||
|
||||
Validates the input mapping on creation. A subclass is expected to
|
||||
provide a `__SCHEMA__` class attribute specifying a validator schema,
|
||||
or a concrete Cerberus validator object.
|
||||
"""
|
||||
def __init__(self, data):
|
||||
self.validate(data)
|
||||
self._data = data
|
||||
|
||||
def __repr__(self):
|
||||
return "{0}({1!r})".format(type(self).__name__, self._data)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, type(self)):
|
||||
raise TypeError("cannot compare {0!r} with {1!r}".format(
|
||||
type(self).__name__, type(other).__name__,
|
||||
))
|
||||
return self._data == other._data
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._data[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._data[key] = value
|
||||
|
||||
def get(self, key, default=None):
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
@classmethod
|
||||
def validate(cls, data):
|
||||
return validate(cls, data)
|
||||
|
||||
|
||||
class DataViewCollection(DataView):
|
||||
"""A collection of dataview.
|
||||
|
||||
Subclasses are expected to assign a class attribute `item_class` to specify
|
||||
how items should be coerced when accessed. The item class should conform to
|
||||
the `DataView` protocol.
|
||||
|
||||
You should not instantiate an instance from this class, but from one of its
|
||||
subclasses instead.
|
||||
"""
|
||||
item_class = None
|
||||
|
||||
def __repr__(self):
|
||||
return "{0}({1!r})".format(type(self).__name__, self._data)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._data)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.item_class(self._data[key])
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if isinstance(value, self.item_class):
|
||||
value = value._data
|
||||
self._data[key] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self._data[key]
|
||||
|
||||
|
||||
class DataViewMapping(DataViewCollection):
|
||||
"""A mapping of dataview.
|
||||
|
||||
The keys are primitive values, while values are instances of `item_class`.
|
||||
"""
|
||||
@classmethod
|
||||
def validate(cls, data):
|
||||
for d in data.values():
|
||||
cls.item_class.validate(d)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._data)
|
||||
|
||||
def keys(self):
|
||||
return self._data.keys()
|
||||
|
||||
def values(self):
|
||||
return [self[k] for k in self._data]
|
||||
|
||||
def items(self):
|
||||
return [(k, self[k]) for k in self._data]
|
||||
|
||||
|
||||
class DataViewSequence(DataViewCollection):
|
||||
"""A sequence of dataview.
|
||||
|
||||
Each entry is an instance of `item_class`.
|
||||
"""
|
||||
@classmethod
|
||||
def validate(cls, data):
|
||||
for d in data:
|
||||
cls.item_class.validate(d)
|
||||
|
||||
def __iter__(self):
|
||||
return (self.item_class(d) for d in self._data)
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
from .base import DataView
|
||||
|
||||
|
||||
class Hash(DataView):
|
||||
"""A hash.
|
||||
"""
|
||||
__SCHEMA__ = {
|
||||
"__hash__": {
|
||||
"type": "list", "minlength": 1, "maxlength": 1,
|
||||
"schema": {
|
||||
"type": "list", "minlength": 2, "maxlength": 2,
|
||||
"schema": {"type": "string"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def validate(cls, data):
|
||||
super(Hash, cls).validate({"__hash__": list(data.items())})
|
||||
|
||||
@classmethod
|
||||
def from_hash(cls, ins):
|
||||
"""Interpolation to the hash result of `hashlib`.
|
||||
"""
|
||||
return cls({ins.name: ins.hexdigest()})
|
||||
|
||||
@classmethod
|
||||
def from_line(cls, value):
|
||||
try:
|
||||
name, value = value.split(":", 1)
|
||||
except ValueError:
|
||||
name = "sha256"
|
||||
return cls({name: value})
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Hash):
|
||||
raise TypeError("cannot compare Hash with {0!r}".format(
|
||||
type(other).__name__,
|
||||
))
|
||||
return self._data == other._data
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return next(iter(self._data.keys()))
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return next(iter(self._data.values()))
|
||||
|
||||
def as_line(self):
|
||||
return "{0[0]}:{0[1]}".format(next(iter(self._data.items())))
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import six
|
||||
|
||||
from .base import DataView
|
||||
|
||||
|
||||
class Package(DataView):
|
||||
"""A package requirement specified in a Pipfile.
|
||||
|
||||
This is the base class of variants appearing in either `[packages]` or
|
||||
`[dev-packages]` sections of a Pipfile.
|
||||
"""
|
||||
# The extra layer is intentional. Cerberus does not allow top-level keys
|
||||
# to have oneof_schema (at least I can't do it), so we wrap this in a
|
||||
# top-level key. The Requirement model class implements extra hacks to
|
||||
# make this work.
|
||||
__SCHEMA__ = {
|
||||
"__package__": {
|
||||
"oneof_type": ["string", "dict"],
|
||||
},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def validate(cls, data):
|
||||
# HACK: Make this validatable for Cerberus. See comments in validation
|
||||
# side for more information.
|
||||
return super(Package, cls).validate({"__package__": data})
|
||||
|
||||
def __getattr__(self, key):
|
||||
if isinstance(self._data, six.string_types):
|
||||
if key == "version":
|
||||
return self._data
|
||||
raise AttributeError(key)
|
||||
try:
|
||||
return self._data[key]
|
||||
except KeyError:
|
||||
pass
|
||||
raise AttributeError(key)
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if key == "_data":
|
||||
super(Package, self).__setattr__(key, value)
|
||||
elif key == "version" and isinstance(self._data, six.string_types):
|
||||
self._data = value
|
||||
else:
|
||||
self._data[key] = value
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
import re
|
||||
import shlex
|
||||
|
||||
import six
|
||||
|
||||
from .base import DataView
|
||||
|
||||
|
||||
class Script(DataView):
|
||||
"""Parse a script line (in Pipfile's [scripts] section).
|
||||
|
||||
This always works in POSIX mode, even on Windows.
|
||||
"""
|
||||
# This extra layer is intentional. Cerberus does not allow validation of
|
||||
# non-mapping inputs, so we wrap this in a top-level key. The Script model
|
||||
# class implements extra hacks to make this work.
|
||||
__SCHEMA__ = {
|
||||
"__script__": {
|
||||
"oneof_type": ["string", "list"], "required": True, "empty": False,
|
||||
"schema": {"type": "string"},
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, data):
|
||||
super(Script, self).__init__(data)
|
||||
if isinstance(data, six.string_types):
|
||||
data = shlex.split(data)
|
||||
self._parts = [data[0]]
|
||||
self._parts.extend(data[1:])
|
||||
|
||||
@classmethod
|
||||
def validate(cls, data):
|
||||
# HACK: Make this validatable for Cerberus. See comments in validation
|
||||
# side for more information.
|
||||
return super(Script, cls).validate({"__script__": data})
|
||||
|
||||
def __repr__(self):
|
||||
return "Script({0!r})".format(self._parts)
|
||||
|
||||
@property
|
||||
def command(self):
|
||||
return self._parts[0]
|
||||
|
||||
@property
|
||||
def args(self):
|
||||
return self._parts[1:]
|
||||
|
||||
def cmdify(self, extra_args=None):
|
||||
"""Encode into a cmd-executable string.
|
||||
|
||||
This re-implements CreateProcess's quoting logic to turn a list of
|
||||
arguments into one single string for the shell to interpret.
|
||||
|
||||
* All double quotes are escaped with a backslash.
|
||||
* Existing backslashes before a quote are doubled, so they are all
|
||||
escaped properly.
|
||||
* Backslashes elsewhere are left as-is; cmd will interpret them
|
||||
literally.
|
||||
|
||||
The result is then quoted into a pair of double quotes to be grouped.
|
||||
|
||||
An argument is intentionally not quoted if it does not contain
|
||||
whitespaces. This is done to be compatible with Windows built-in
|
||||
commands that don't work well with quotes, e.g. everything with `echo`,
|
||||
and DOS-style (forward slash) switches.
|
||||
|
||||
The intended use of this function is to pre-process an argument list
|
||||
before passing it into ``subprocess.Popen(..., shell=True)``.
|
||||
|
||||
See also: https://docs.python.org/3/library/subprocess.html
|
||||
"""
|
||||
parts = list(self._parts)
|
||||
if extra_args:
|
||||
parts.extend(extra_args)
|
||||
return " ".join(
|
||||
arg if not next(re.finditer(r'\s', arg), None)
|
||||
else '"{0}"'.format(re.sub(r'(\\*)"', r'\1\1\\"', arg))
|
||||
for arg in parts
|
||||
)
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
from .base import DataView, DataViewMapping, DataViewSequence
|
||||
from .hashes import Hash
|
||||
from .packages import Package
|
||||
from .scripts import Script
|
||||
from .sources import Source
|
||||
|
||||
|
||||
class PackageCollection(DataViewMapping):
|
||||
item_class = Package
|
||||
|
||||
|
||||
class ScriptCollection(DataViewMapping):
|
||||
item_class = Script
|
||||
|
||||
|
||||
class SourceCollection(DataViewSequence):
|
||||
item_class = Source
|
||||
|
||||
|
||||
class Requires(DataView):
|
||||
"""Representation of the `[requires]` section in a Pipfile.
|
||||
"""
|
||||
__SCHEMA__ = {
|
||||
"python_version": {
|
||||
"type": "string",
|
||||
"excludes": ["python_full_version"],
|
||||
},
|
||||
"python_full_version": {
|
||||
"type": "string",
|
||||
"excludes": ["python_version"],
|
||||
},
|
||||
}
|
||||
|
||||
@property
|
||||
def python_version(self):
|
||||
try:
|
||||
return self._data["python_version"]
|
||||
except KeyError:
|
||||
raise AttributeError("python_version")
|
||||
|
||||
@property
|
||||
def python_full_version(self):
|
||||
try:
|
||||
return self._data["python_full_version"]
|
||||
except KeyError:
|
||||
raise AttributeError("python_full_version")
|
||||
|
||||
|
||||
META_SECTIONS = {
|
||||
"hash": Hash,
|
||||
"requires": Requires,
|
||||
"sources": SourceCollection,
|
||||
}
|
||||
|
||||
|
||||
class Meta(DataView):
|
||||
"""Representation of the `_meta` section in a Pipfile.lock.
|
||||
"""
|
||||
__SCHEMA__ = {
|
||||
"hash": {"type": "dict", "required": True},
|
||||
"pipfile-spec": {"type": "integer", "required": True, "min": 0},
|
||||
"requires": {"type": "dict", "required": True},
|
||||
"sources": {"type": "list", "required": True},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def validate(cls, data):
|
||||
super(Meta, cls).validate(data)
|
||||
for key, klass in META_SECTIONS.items():
|
||||
klass.validate(data[key])
|
||||
|
||||
def __getitem__(self, key):
|
||||
value = super(Meta, self).__getitem__(key)
|
||||
try:
|
||||
return META_SECTIONS[key](value)
|
||||
except KeyError:
|
||||
return value
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if isinstance(value, DataView):
|
||||
self._data[key] = value._data
|
||||
else:
|
||||
self._data[key] = value
|
||||
|
||||
@property
|
||||
def hash_(self):
|
||||
return self["hash"]
|
||||
|
||||
@hash_.setter
|
||||
def hash_(self, value):
|
||||
self["hash"] = value
|
||||
|
||||
@property
|
||||
def hash(self):
|
||||
return self["hash"]
|
||||
|
||||
@hash.setter
|
||||
def hash(self, value):
|
||||
self["hash"] = value
|
||||
|
||||
@property
|
||||
def pipfile_spec(self):
|
||||
return self["pipfile-spec"]
|
||||
|
||||
@pipfile_spec.setter
|
||||
def pipfile_spec(self, value):
|
||||
self["pipfile-spec"] = value
|
||||
|
||||
@property
|
||||
def requires(self):
|
||||
return self["requires"]
|
||||
|
||||
@requires.setter
|
||||
def requires(self, value):
|
||||
self["requires"] = value
|
||||
|
||||
@property
|
||||
def sources(self):
|
||||
return self["sources"]
|
||||
|
||||
@sources.setter
|
||||
def sources(self, value):
|
||||
self["sources"] = value
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import os
|
||||
|
||||
from .base import DataView
|
||||
|
||||
|
||||
class Source(DataView):
|
||||
"""Information on a "simple" Python package index.
|
||||
|
||||
This could be PyPI, or a self-hosted index server, etc. The server
|
||||
specified by the `url` attribute is expected to provide the "simple"
|
||||
package API.
|
||||
"""
|
||||
__SCHEMA__ = {
|
||||
"name": {"type": "string", "required": True},
|
||||
"url": {"type": "string", "required": True},
|
||||
"verify_ssl": {"type": "boolean", "required": True},
|
||||
}
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._data["name"]
|
||||
|
||||
@name.setter
|
||||
def name(self, value):
|
||||
self._data["name"] = value
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return self._data["url"]
|
||||
|
||||
@url.setter
|
||||
def url(self, value):
|
||||
self._data["url"] = value
|
||||
|
||||
@property
|
||||
def verify_ssl(self):
|
||||
return self._data["verify_ssl"]
|
||||
|
||||
@verify_ssl.setter
|
||||
def verify_ssl(self, value):
|
||||
self._data["verify_ssl"] = value
|
||||
|
||||
@property
|
||||
def url_expanded(self):
|
||||
return os.path.expandvars(self._data["url"])
|
||||
Vendored
+161
@@ -0,0 +1,161 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
import six
|
||||
import tomlkit
|
||||
|
||||
from .models import (
|
||||
DataView, Hash, Requires,
|
||||
PackageCollection, ScriptCollection, SourceCollection,
|
||||
)
|
||||
|
||||
|
||||
PIPFILE_SECTIONS = {
|
||||
"source": SourceCollection,
|
||||
"packages": PackageCollection,
|
||||
"dev-packages": PackageCollection,
|
||||
"requires": Requires,
|
||||
"scripts": ScriptCollection,
|
||||
}
|
||||
|
||||
DEFAULT_SOURCE_TOML = """\
|
||||
[[source]]
|
||||
name = "pypi"
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
"""
|
||||
|
||||
|
||||
class Pipfile(DataView):
|
||||
"""Representation of a Pipfile.
|
||||
"""
|
||||
__SCHEMA__ = {}
|
||||
|
||||
@classmethod
|
||||
def validate(cls, data):
|
||||
# HACK: DO NOT CALL `super().validate()` here!!
|
||||
# Cerberus seems to break TOML Kit's inline table preservation if it
|
||||
# is not at the top-level. Fortunately the spec doesn't have nested
|
||||
# non-inlined tables, so we're OK as long as validation is only
|
||||
# performed at section-level. validation is performed.
|
||||
for key, klass in PIPFILE_SECTIONS.items():
|
||||
if key not in data:
|
||||
continue
|
||||
klass.validate(data[key])
|
||||
|
||||
@classmethod
|
||||
def load(cls, f, encoding=None):
|
||||
content = f.read()
|
||||
if encoding is not None:
|
||||
content = content.decode(encoding)
|
||||
data = tomlkit.loads(content)
|
||||
if "source" not in data:
|
||||
# HACK: There is no good way to prepend a section to an existing
|
||||
# TOML document, but there's no good way to copy non-structural
|
||||
# content from one TOML document to another either. Modify the
|
||||
# TOML content directly, and load the new in-memory document.
|
||||
sep = "" if content.startswith("\n") else "\n"
|
||||
content = DEFAULT_SOURCE_TOML + sep + content
|
||||
data = tomlkit.loads(content)
|
||||
return cls(data)
|
||||
|
||||
def __getitem__(self, key):
|
||||
value = self._data[key]
|
||||
try:
|
||||
return PIPFILE_SECTIONS[key](value)
|
||||
except KeyError:
|
||||
return value
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if isinstance(value, DataView):
|
||||
self._data[key] = value._data
|
||||
else:
|
||||
self._data[key] = value
|
||||
|
||||
def get_hash(self):
|
||||
data = {
|
||||
"_meta": {
|
||||
"sources": self._data["source"],
|
||||
"requires": self._data.get("requires", {}),
|
||||
},
|
||||
"default": self._data.get("packages", {}),
|
||||
"develop": self._data.get("dev-packages", {}),
|
||||
}
|
||||
content = json.dumps(data, sort_keys=True, separators=(",", ":"))
|
||||
if isinstance(content, six.text_type):
|
||||
content = content.encode("utf-8")
|
||||
return Hash.from_hash(hashlib.sha256(content))
|
||||
|
||||
def dump(self, f, encoding=None):
|
||||
content = tomlkit.dumps(self._data)
|
||||
if encoding is not None:
|
||||
content = content.encode(encoding)
|
||||
f.write(content)
|
||||
|
||||
@property
|
||||
def sources(self):
|
||||
try:
|
||||
return self["source"]
|
||||
except KeyError:
|
||||
raise AttributeError("sources")
|
||||
|
||||
@sources.setter
|
||||
def sources(self, value):
|
||||
self["source"] = value
|
||||
|
||||
@property
|
||||
def source(self):
|
||||
try:
|
||||
return self["source"]
|
||||
except KeyError:
|
||||
raise AttributeError("source")
|
||||
|
||||
@source.setter
|
||||
def source(self, value):
|
||||
self["source"] = value
|
||||
|
||||
@property
|
||||
def packages(self):
|
||||
try:
|
||||
return self["packages"]
|
||||
except KeyError:
|
||||
raise AttributeError("packages")
|
||||
|
||||
@packages.setter
|
||||
def packages(self, value):
|
||||
self["packages"] = value
|
||||
|
||||
@property
|
||||
def dev_packages(self):
|
||||
try:
|
||||
return self["dev-packages"]
|
||||
except KeyError:
|
||||
raise AttributeError("dev-packages")
|
||||
|
||||
@dev_packages.setter
|
||||
def dev_packages(self, value):
|
||||
self["dev-packages"] = value
|
||||
|
||||
@property
|
||||
def requires(self):
|
||||
try:
|
||||
return self["requires"]
|
||||
except KeyError:
|
||||
raise AttributeError("requires")
|
||||
|
||||
@requires.setter
|
||||
def requires(self, value):
|
||||
self["requires"] = value
|
||||
|
||||
@property
|
||||
def scripts(self):
|
||||
try:
|
||||
return self["scripts"]
|
||||
except KeyError:
|
||||
raise AttributeError("scripts")
|
||||
|
||||
@scripts.setter
|
||||
def scripts(self, value):
|
||||
self["scripts"] = value
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
"""Run a subprocess in a pseudo terminal"""
|
||||
from .ptyprocess import PtyProcess, PtyProcessUnicode, PtyProcessError
|
||||
|
||||
__version__ = '0.5.2'
|
||||
__version__ = '0.6.0'
|
||||
|
||||
+3
-2
@@ -4,6 +4,7 @@ import os
|
||||
import errno
|
||||
|
||||
from pty import (STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO, CHILD)
|
||||
from .util import PtyProcessError
|
||||
|
||||
def fork_pty():
|
||||
'''This implements a substitute for the forkpty system call. This
|
||||
@@ -63,7 +64,7 @@ def pty_make_controlling_tty(tty_fd):
|
||||
try:
|
||||
fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
|
||||
os.close(fd)
|
||||
raise ExceptionPexpect("OSError of errno.ENXIO should be raised.")
|
||||
raise PtyProcessError("OSError of errno.ENXIO should be raised.")
|
||||
except OSError as err:
|
||||
if err.errno != errno.ENXIO:
|
||||
raise
|
||||
@@ -74,4 +75,4 @@ def pty_make_controlling_tty(tty_fd):
|
||||
|
||||
# Verify we now have a controlling tty.
|
||||
fd = os.open("/dev/tty", os.O_WRONLY)
|
||||
os.close(fd)
|
||||
os.close(fd)
|
||||
|
||||
+18
-11
@@ -19,7 +19,7 @@ except ImportError:
|
||||
# Constants
|
||||
from pty import (STDIN_FILENO, CHILD)
|
||||
|
||||
from .util import which
|
||||
from .util import which, PtyProcessError
|
||||
|
||||
_platform = sys.platform.lower()
|
||||
|
||||
@@ -60,11 +60,18 @@ def _make_eof_intr():
|
||||
# inherit EOF and INTR definitions from controlling process.
|
||||
try:
|
||||
from termios import VEOF, VINTR
|
||||
try:
|
||||
fd = sys.__stdin__.fileno()
|
||||
except ValueError:
|
||||
# ValueError: I/O operation on closed file
|
||||
fd = sys.__stdout__.fileno()
|
||||
fd = None
|
||||
for name in 'stdin', 'stdout':
|
||||
stream = getattr(sys, '__%s__' % name, None)
|
||||
if stream is None or not hasattr(stream, 'fileno'):
|
||||
continue
|
||||
try:
|
||||
fd = stream.fileno()
|
||||
except ValueError:
|
||||
continue
|
||||
if fd is None:
|
||||
# no fd, raise ValueError to fallback on CEOF, CINTR
|
||||
raise ValueError("No stream has a fileno")
|
||||
intr = ord(termios.tcgetattr(fd)[6][VINTR])
|
||||
eof = ord(termios.tcgetattr(fd)[6][VEOF])
|
||||
except (ImportError, OSError, IOError, ValueError, termios.error):
|
||||
@@ -81,14 +88,11 @@ def _make_eof_intr():
|
||||
_INTR = _byte(intr)
|
||||
_EOF = _byte(eof)
|
||||
|
||||
class PtyProcessError(Exception):
|
||||
"""Generic error class for this package."""
|
||||
|
||||
# setecho and setwinsize are pulled out here because on some platforms, we need
|
||||
# to do this from the child before we exec()
|
||||
|
||||
def _setecho(fd, state):
|
||||
errmsg = 'setecho() may not be called on this platform'
|
||||
errmsg = 'setecho() may not be called on this platform (it may still be possible to enable/disable echo when spawning the child process)'
|
||||
|
||||
try:
|
||||
attr = termios.tcgetattr(fd)
|
||||
@@ -251,7 +255,10 @@ class PtyProcess(object):
|
||||
|
||||
# Do not allow child to inherit open file descriptors from parent,
|
||||
# with the exception of the exec_err_pipe_write of the pipe
|
||||
max_fd = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
|
||||
# Impose ceiling on max_fd: AIX bugfix for users with unlimited
|
||||
# nofiles where resource.RLIMIT_NOFILE is 2^63-1 and os.closerange()
|
||||
# occasionally raises out of range error
|
||||
max_fd = min(1048576, resource.getrlimit(resource.RLIMIT_NOFILE)[0])
|
||||
os.closerange(3, exec_err_pipe_write)
|
||||
os.closerange(exec_err_pipe_write+1, max_fd)
|
||||
|
||||
|
||||
Vendored
+5
-1
@@ -64,4 +64,8 @@ except ImportError:
|
||||
name = os.path.join(dir, thefile)
|
||||
if _access_check(name, mode):
|
||||
return name
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
class PtyProcessError(Exception):
|
||||
"""Generic error class for this package."""
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
from __future__ import print_function, absolute_import
|
||||
|
||||
__version__ = "1.0.0"
|
||||
__version__ = '1.0.2'
|
||||
|
||||
__all__ = ["Finder", "WindowsFinder", "SystemPath", "InvalidPythonVersion"]
|
||||
from .pythonfinder import Finder
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
# Taken from pip: https://github.com/pypa/pip/blob/95bcf8c5f6394298035a7332c441868f3b0169f4/src/pip/_vendor/Makefile
|
||||
all: clean vendor
|
||||
|
||||
clean:
|
||||
@# Delete vendored items
|
||||
find . -maxdepth 1 -mindepth 1 -type d -exec rm -rf {} \;
|
||||
|
||||
vendor:
|
||||
@# Install vendored libraries
|
||||
pip install -t . -r vendor.txt
|
||||
|
||||
@# Cleanup .egg-info directories
|
||||
rm -rf *.egg-info
|
||||
rm -rf *.dist-info
|
||||
@@ -0,0 +1 @@
|
||||
-e git+https://github.com/zooba/pep514tools.git@320e48745660b696e2dcaee888fc2e516b435e48#egg=pep514tools
|
||||
Vendored
+1
-1
@@ -11,7 +11,7 @@ from .pythonfinder import Finder
|
||||
# @click.group(invoke_without_command=True, context_settings=CONTEXT_SETTINGS)
|
||||
@click.command()
|
||||
@click.option("--find", default=False, nargs=1, help="Find a specific python version.")
|
||||
@click.option("--which", default=False, nargs=1, help="Run the which commend.")
|
||||
@click.option("--which", default=False, nargs=1, help="Run the which command.")
|
||||
@click.option(
|
||||
"--findall", is_flag=True, default=False, help="Find all python versions."
|
||||
)
|
||||
|
||||
+9
-109
@@ -1,10 +1,17 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import print_function, absolute_import
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
import abc
|
||||
import operator
|
||||
import six
|
||||
|
||||
from itertools import chain
|
||||
|
||||
import six
|
||||
|
||||
from ..utils import KNOWN_EXTS, unnest
|
||||
from .path import SystemPath
|
||||
from .python import PythonVersion
|
||||
from .windows import WindowsFinder
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
@@ -24,110 +31,3 @@ class BaseFinder(object):
|
||||
@property
|
||||
def expanded_paths(self):
|
||||
return (p.paths.values() for p in self.version_paths)
|
||||
|
||||
|
||||
class BasePath(object):
|
||||
def which(self, name):
|
||||
"""Search in this path for an executable.
|
||||
|
||||
:param executable: The name of an executable to search for.
|
||||
:type executable: str
|
||||
:returns: :class:`~pythonfinder.models.PathEntry` instance.
|
||||
"""
|
||||
|
||||
valid_names = [name] + [
|
||||
"{0}.{1}".format(name, ext).lower() if ext else "{0}".format(name).lower()
|
||||
for ext in KNOWN_EXTS
|
||||
]
|
||||
children = self.children
|
||||
found = next(
|
||||
(
|
||||
children[(self.path / child).as_posix()]
|
||||
for child in valid_names
|
||||
if (self.path / child).as_posix() in children
|
||||
),
|
||||
None,
|
||||
)
|
||||
return found
|
||||
|
||||
def find_all_python_versions(
|
||||
self, major=None, minor=None, patch=None, pre=None, dev=None, arch=None
|
||||
):
|
||||
"""Search for a specific python version on the path. Return all copies
|
||||
|
||||
:param major: Major python version to search for.
|
||||
:type major: int
|
||||
:param int minor: Minor python version to search for, defaults to None
|
||||
:param int patch: Patch python version to search for, defaults to None
|
||||
:param bool pre: Search for prereleases (default None) - prioritize releases if None
|
||||
:param bool dev: Search for devreleases (default None) - prioritize releases if None
|
||||
:param str arch: Architecture to include, e.g. '64bit', defaults to None
|
||||
:return: A list of :class:`~pythonfinder.models.PathEntry` instances matching the version requested.
|
||||
:rtype: List[:class:`~pythonfinder.models.PathEntry`]
|
||||
"""
|
||||
|
||||
call_method = (
|
||||
"find_all_python_versions" if self.is_dir else "find_python_version"
|
||||
)
|
||||
sub_finder = operator.methodcaller(
|
||||
call_method, major, minor=minor, patch=patch, pre=pre, dev=dev, arch=arch
|
||||
)
|
||||
if not self.is_dir:
|
||||
return sub_finder(self)
|
||||
path_filter = filter(None, (sub_finder(p) for p in self.children.values()))
|
||||
version_sort = operator.attrgetter("as_python.version_sort")
|
||||
return [c for c in sorted(path_filter, key=version_sort, reverse=True)]
|
||||
|
||||
def find_python_version(
|
||||
self, major=None, minor=None, patch=None, pre=None, dev=None, arch=None
|
||||
):
|
||||
"""Search or self for the specified Python version and return the first match.
|
||||
|
||||
:param major: Major version number.
|
||||
:type major: int
|
||||
:param int minor: Minor python version to search for, defaults to None
|
||||
:param int patch: Patch python version to search for, defaults to None
|
||||
:param bool pre: Search for prereleases (default None) - prioritize releases if None
|
||||
:param bool dev: Search for devreleases (default None) - prioritize releases if None
|
||||
:param str arch: Architecture to include, e.g. '64bit', defaults to None
|
||||
:returns: A :class:`~pythonfinder.models.PathEntry` instance matching the version requested.
|
||||
"""
|
||||
|
||||
version_matcher = operator.methodcaller(
|
||||
"matches",
|
||||
major=major,
|
||||
minor=minor,
|
||||
patch=patch,
|
||||
pre=pre,
|
||||
dev=dev,
|
||||
arch=arch,
|
||||
)
|
||||
is_py = operator.attrgetter("is_python")
|
||||
py_version = operator.attrgetter("as_python")
|
||||
if not self.is_dir:
|
||||
if self.is_python and self.as_python and version_matcher(self.as_python):
|
||||
return self
|
||||
return
|
||||
finder = (
|
||||
(child, child.as_python)
|
||||
for child in unnest(self.pythons.values())
|
||||
if child.as_python
|
||||
)
|
||||
py_filter = filter(
|
||||
None, filter(lambda child: version_matcher(child[1]), finder)
|
||||
)
|
||||
version_sort = operator.attrgetter("version_sort")
|
||||
return next(
|
||||
(
|
||||
c[0]
|
||||
for c in sorted(
|
||||
py_filter, key=lambda child: child[1].version_sort, reverse=True
|
||||
)
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
|
||||
from .path import SystemPath
|
||||
from .windows import WindowsFinder
|
||||
from .python import PythonVersion
|
||||
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import abc
|
||||
import operator
|
||||
import six
|
||||
|
||||
from ..utils import KNOWN_EXTS, unnest
|
||||
|
||||
|
||||
class BasePath(object):
|
||||
def which(self, name):
|
||||
"""Search in this path for an executable.
|
||||
|
||||
:param executable: The name of an executable to search for.
|
||||
:type executable: str
|
||||
:returns: :class:`~pythonfinder.models.PathEntry` instance.
|
||||
"""
|
||||
|
||||
valid_names = [name] + [
|
||||
"{0}.{1}".format(name, ext).lower() if ext else "{0}".format(name).lower()
|
||||
for ext in KNOWN_EXTS
|
||||
]
|
||||
children = self.children
|
||||
found = next(
|
||||
(
|
||||
children[(self.path / child).as_posix()]
|
||||
for child in valid_names
|
||||
if (self.path / child).as_posix() in children
|
||||
),
|
||||
None,
|
||||
)
|
||||
return found
|
||||
|
||||
def find_all_python_versions(
|
||||
self, major=None, minor=None, patch=None, pre=None, dev=None, arch=None
|
||||
):
|
||||
"""Search for a specific python version on the path. Return all copies
|
||||
|
||||
:param major: Major python version to search for.
|
||||
:type major: int
|
||||
:param int minor: Minor python version to search for, defaults to None
|
||||
:param int patch: Patch python version to search for, defaults to None
|
||||
:param bool pre: Search for prereleases (default None) - prioritize releases if None
|
||||
:param bool dev: Search for devreleases (default None) - prioritize releases if None
|
||||
:param str arch: Architecture to include, e.g. '64bit', defaults to None
|
||||
:return: A list of :class:`~pythonfinder.models.PathEntry` instances matching the version requested.
|
||||
:rtype: List[:class:`~pythonfinder.models.PathEntry`]
|
||||
"""
|
||||
|
||||
call_method = (
|
||||
"find_all_python_versions" if self.is_dir else "find_python_version"
|
||||
)
|
||||
sub_finder = operator.methodcaller(
|
||||
call_method, major, minor=minor, patch=patch, pre=pre, dev=dev, arch=arch
|
||||
)
|
||||
if not self.is_dir:
|
||||
return sub_finder(self)
|
||||
path_filter = filter(None, (sub_finder(p) for p in self.children.values()))
|
||||
version_sort = operator.attrgetter("as_python.version_sort")
|
||||
return [c for c in sorted(path_filter, key=version_sort, reverse=True)]
|
||||
|
||||
def find_python_version(
|
||||
self, major=None, minor=None, patch=None, pre=None, dev=None, arch=None
|
||||
):
|
||||
"""Search or self for the specified Python version and return the first match.
|
||||
|
||||
:param major: Major version number.
|
||||
:type major: int
|
||||
:param int minor: Minor python version to search for, defaults to None
|
||||
:param int patch: Patch python version to search for, defaults to None
|
||||
:param bool pre: Search for prereleases (default None) - prioritize releases if None
|
||||
:param bool dev: Search for devreleases (default None) - prioritize releases if None
|
||||
:param str arch: Architecture to include, e.g. '64bit', defaults to None
|
||||
:returns: A :class:`~pythonfinder.models.PathEntry` instance matching the version requested.
|
||||
"""
|
||||
|
||||
version_matcher = operator.methodcaller(
|
||||
"matches",
|
||||
major=major,
|
||||
minor=minor,
|
||||
patch=patch,
|
||||
pre=pre,
|
||||
dev=dev,
|
||||
arch=arch,
|
||||
)
|
||||
is_py = operator.attrgetter("is_python")
|
||||
py_version = operator.attrgetter("as_python")
|
||||
if not self.is_dir:
|
||||
if self.is_python and self.as_python and version_matcher(self.as_python):
|
||||
return self
|
||||
return
|
||||
finder = (
|
||||
(child, child.as_python)
|
||||
for child in unnest(self.pythons.values())
|
||||
if child.as_python
|
||||
)
|
||||
py_filter = filter(
|
||||
None, filter(lambda child: version_matcher(child[1]), finder)
|
||||
)
|
||||
version_sort = operator.attrgetter("version_sort")
|
||||
return next(
|
||||
(
|
||||
c[0]
|
||||
for c in sorted(
|
||||
py_filter, key=lambda child: child[1].version_sort, reverse=True
|
||||
)
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseFinder(object):
|
||||
def get_versions(self):
|
||||
"""Return the available versions from the finder"""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def create(cls):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def version_paths(self):
|
||||
return self.versions.values()
|
||||
|
||||
@property
|
||||
def expanded_paths(self):
|
||||
return (p.paths.values() for p in self.version_paths)
|
||||
+15
-18
@@ -1,31 +1,28 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import print_function, absolute_import
|
||||
import attr
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
import copy
|
||||
import operator
|
||||
import os
|
||||
import sys
|
||||
|
||||
from collections import defaultdict
|
||||
from cached_property import cached_property
|
||||
from itertools import chain
|
||||
from . import BasePath
|
||||
from .python import PythonVersion
|
||||
|
||||
import attr
|
||||
|
||||
from cached_property import cached_property
|
||||
|
||||
from vistir.compat import Path, fs_str
|
||||
|
||||
from .mixins import BasePath
|
||||
from ..environment import PYENV_INSTALLED, PYENV_ROOT
|
||||
from ..exceptions import InvalidPythonVersion
|
||||
from ..utils import (
|
||||
optional_instance_of,
|
||||
filter_pythons,
|
||||
path_is_known_executable,
|
||||
looks_like_python,
|
||||
ensure_path,
|
||||
fs_str,
|
||||
unnest,
|
||||
ensure_path, filter_pythons, looks_like_python, optional_instance_of,
|
||||
path_is_known_executable, unnest
|
||||
)
|
||||
|
||||
try:
|
||||
from pathlib import Path
|
||||
except ImportError:
|
||||
from pathlib2 import Path
|
||||
from .python import PythonVersion
|
||||
|
||||
|
||||
@attr.s
|
||||
@@ -251,7 +248,7 @@ class SystemPath(object):
|
||||
if major and minor and patch:
|
||||
_tuple_pre = pre if pre is not None else False
|
||||
_tuple_dev = dev if dev is not None else False
|
||||
version_tuple = (major, minor_, patch, _tuple_pre, _tuple_dev)
|
||||
version_tuple = (major, minor, patch, _tuple_pre, _tuple_dev)
|
||||
version_tuple_pre = (major, minor, patch, True, False)
|
||||
if os.name == "nt" and self.windows_finder:
|
||||
windows_finder_version = sub_finder(self.windows_finder)
|
||||
|
||||
+9
-10
@@ -1,17 +1,16 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import print_function, absolute_import
|
||||
import attr
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
from collections import defaultdict
|
||||
from . import BaseFinder
|
||||
|
||||
import attr
|
||||
|
||||
from vistir.compat import Path
|
||||
|
||||
from ..utils import ensure_path, optional_instance_of
|
||||
from .mixins import BaseFinder
|
||||
from .path import VersionPath
|
||||
from .python import PythonVersion
|
||||
from ..utils import optional_instance_of, ensure_path
|
||||
|
||||
|
||||
try:
|
||||
from pathlib import Path
|
||||
except ImportError:
|
||||
from pathlib2 import Path
|
||||
|
||||
|
||||
@attr.s
|
||||
|
||||
+16
-12
@@ -1,18 +1,22 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import print_function, absolute_import
|
||||
import attr
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
import copy
|
||||
from collections import defaultdict
|
||||
import platform
|
||||
from packaging.version import parse as parse_version, Version
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
import attr
|
||||
|
||||
from packaging.version import Version
|
||||
from packaging.version import parse as parse_version
|
||||
|
||||
from vistir.compat import Path
|
||||
|
||||
from ..environment import SYSTEM_ARCH
|
||||
from ..utils import _filter_none, optional_instance_of, get_python_version, ensure_path
|
||||
|
||||
|
||||
try:
|
||||
from pathlib import Path
|
||||
except ImportError:
|
||||
from pathlib2 import Path
|
||||
from ..utils import (
|
||||
_filter_none, ensure_path, get_python_version, optional_instance_of
|
||||
)
|
||||
|
||||
|
||||
@attr.s
|
||||
@@ -31,7 +35,7 @@ class PythonVersion(object):
|
||||
@property
|
||||
def version_sort(self):
|
||||
"""version_sort tuple for sorting against other instances of the same class.
|
||||
|
||||
|
||||
Returns a tuple of the python version but includes a point for non-dev,
|
||||
and a point for non-prerelease versions. So released versions will have 2 points
|
||||
for this value. E.g. `(3, 6, 6, 2)` is a release, `(3, 6, 6, 1)` is a prerelease,
|
||||
|
||||
+9
-5
@@ -1,13 +1,17 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import print_function, absolute_import
|
||||
import attr
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
import operator
|
||||
|
||||
from collections import defaultdict
|
||||
from . import BaseFinder
|
||||
from .path import PathEntry
|
||||
from .python import PythonVersion, VersionMap
|
||||
|
||||
import attr
|
||||
|
||||
from ..exceptions import InvalidPythonVersion
|
||||
from ..utils import ensure_path
|
||||
from .mixins import BaseFinder
|
||||
from .path import PathEntry
|
||||
from .python import PythonVersion, VersionMap
|
||||
|
||||
|
||||
@attr.s
|
||||
|
||||
Vendored
+12
-37
@@ -1,19 +1,22 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import print_function, absolute_import
|
||||
import attr
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
import locale
|
||||
import os
|
||||
import six
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from fnmatch import fnmatch
|
||||
from .exceptions import InvalidPythonVersion
|
||||
from itertools import chain
|
||||
|
||||
try:
|
||||
from pathlib import Path
|
||||
except ImportError:
|
||||
from pathlib2 import Path
|
||||
import attr
|
||||
import six
|
||||
|
||||
import vistir
|
||||
|
||||
from vistir.compat import Path
|
||||
|
||||
from .exceptions import InvalidPythonVersion
|
||||
|
||||
|
||||
PYTHON_IMPLEMENTATIONS = ("python", "ironpython", "jython", "pypy")
|
||||
@@ -24,25 +27,11 @@ KNOWN_EXTS = KNOWN_EXTS | set(
|
||||
)
|
||||
|
||||
|
||||
def _run(cmd):
|
||||
"""Use `subprocess.check_output` to get the output of a command and decode it.
|
||||
|
||||
:param list cmd: A list representing the command you want to run.
|
||||
:returns: A 2-tuple of (output, error)
|
||||
"""
|
||||
encoding = locale.getdefaultlocale()[1] or "utf-8"
|
||||
c = subprocess.Popen(
|
||||
cmd, env=os.environ.copy(), stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
out, err = c.communicate()
|
||||
return out.decode(encoding).strip(), err.decode(encoding).strip()
|
||||
|
||||
|
||||
def get_python_version(path):
|
||||
"""Get python version string using subprocess from a given path."""
|
||||
version_cmd = [path, "-c", "import sys; print(sys.version.split()[0])"]
|
||||
try:
|
||||
out, _ = _run(version_cmd)
|
||||
out, _ = vistir.misc.run(version_cmd)
|
||||
except OSError:
|
||||
raise InvalidPythonVersion("%s is not a valid python path" % path)
|
||||
if not out:
|
||||
@@ -123,20 +112,6 @@ def filter_pythons(path):
|
||||
return filter(lambda x: path_is_python(x), path.iterdir())
|
||||
|
||||
|
||||
def fs_str(string):
|
||||
"""Encodes a string into the proper filesystem encoding
|
||||
|
||||
Borrowed from pip-tools
|
||||
"""
|
||||
if isinstance(string, str):
|
||||
return string
|
||||
assert not isinstance(string, bytes)
|
||||
return string.encode(_fs_encoding)
|
||||
|
||||
|
||||
_fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
|
||||
|
||||
|
||||
def unnest(item):
|
||||
if isinstance(next((i for i in item), None), (list, tuple)):
|
||||
return chain(*filter(None, item))
|
||||
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
License
|
||||
=======
|
||||
|
||||
Requirements Parser is licensed under the BSD license.
|
||||
|
||||
Copyright (c) 2012 - 2013, David Fischer
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
- Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY David Fischer ''AS IS'' AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL David Fischer BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 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.
|
||||
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
from .parser import parse # noqa
|
||||
|
||||
_MAJOR = 0
|
||||
_MINOR = 2
|
||||
_PATCH = 0
|
||||
|
||||
|
||||
def version_tuple():
|
||||
'''
|
||||
Returns a 3-tuple of ints that represent the version
|
||||
'''
|
||||
return (_MAJOR, _MINOR, _PATCH)
|
||||
|
||||
|
||||
def version():
|
||||
'''
|
||||
Returns a string representation of the version
|
||||
'''
|
||||
return '%d.%d.%d' % (version_tuple())
|
||||
|
||||
|
||||
__version__ = version()
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
import re
|
||||
|
||||
# Copied from pipenv.patched.notpip
|
||||
# https://github.com/pypa/pip/blob/281eb61b09d87765d7c2b92f6982b3fe76ccb0af/pip/index.py#L947
|
||||
HASH_ALGORITHMS = set(['sha1', 'sha224', 'sha384', 'sha256', 'sha512', 'md5'])
|
||||
|
||||
extras_require_search = re.compile(
|
||||
r'(?P<name>.+)\[(?P<extras>[^\]]+)\]').search
|
||||
|
||||
|
||||
def parse_fragment(fragment_string):
|
||||
"""Takes a fragment string nd returns a dict of the components"""
|
||||
fragment_string = fragment_string.lstrip('#')
|
||||
|
||||
try:
|
||||
return dict(
|
||||
key_value_string.split('=')
|
||||
for key_value_string in fragment_string.split('&')
|
||||
)
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
'Invalid fragment string {fragment_string}'.format(
|
||||
fragment_string=fragment_string
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def get_hash_info(d):
|
||||
"""Returns the first matching hashlib name and value from a dict"""
|
||||
for key in d.keys():
|
||||
if key.lower() in HASH_ALGORITHMS:
|
||||
return key, d[key]
|
||||
|
||||
return None, None
|
||||
|
||||
|
||||
def parse_extras_require(egg):
|
||||
if egg is not None:
|
||||
match = extras_require_search(egg)
|
||||
if match is not None:
|
||||
name = match.group('name')
|
||||
extras = match.group('extras')
|
||||
return name, [extra.strip() for extra in extras.split(',')]
|
||||
return egg, []
|
||||
Vendored
-50
@@ -1,50 +0,0 @@
|
||||
import os
|
||||
import warnings
|
||||
|
||||
from .requirement import Requirement
|
||||
|
||||
|
||||
def parse(reqstr):
|
||||
"""
|
||||
Parse a requirements file into a list of Requirements
|
||||
|
||||
See: pip/req.py:parse_requirements()
|
||||
|
||||
:param reqstr: a string or file like object containing requirements
|
||||
:returns: a *generator* of Requirement objects
|
||||
"""
|
||||
filename = getattr(reqstr, 'name', None)
|
||||
try:
|
||||
# Python 2.x compatibility
|
||||
if not isinstance(reqstr, basestring):
|
||||
reqstr = reqstr.read()
|
||||
except NameError:
|
||||
# Python 3.x only
|
||||
if not isinstance(reqstr, str):
|
||||
reqstr = reqstr.read()
|
||||
|
||||
for line in reqstr.splitlines():
|
||||
line = line.strip()
|
||||
if line == '':
|
||||
continue
|
||||
elif not line or line.startswith('#'):
|
||||
# comments are lines that start with # only
|
||||
continue
|
||||
elif line.startswith('-r') or line.startswith('--requirement'):
|
||||
_, new_filename = line.split()
|
||||
new_file_path = os.path.join(os.path.dirname(filename or '.'),
|
||||
new_filename)
|
||||
with open(new_file_path) as f:
|
||||
for requirement in parse(f):
|
||||
yield requirement
|
||||
elif line.startswith('-f') or line.startswith('--find-links') or \
|
||||
line.startswith('-i') or line.startswith('--index-url') or \
|
||||
line.startswith('--extra-index-url') or \
|
||||
line.startswith('--no-index'):
|
||||
warnings.warn('Private repos not supported. Skipping.')
|
||||
continue
|
||||
elif line.startswith('-Z') or line.startswith('--always-unzip'):
|
||||
warnings.warn('Unused option --always-unzip. Skipping.')
|
||||
continue
|
||||
else:
|
||||
yield Requirement.parse(line)
|
||||
-220
@@ -1,220 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
import re
|
||||
from pkg_resources import Requirement as Req
|
||||
|
||||
from .fragment import get_hash_info, parse_fragment, parse_extras_require
|
||||
from .vcs import VCS, VCS_SCHEMES
|
||||
|
||||
|
||||
URI_REGEX = re.compile(
|
||||
r'^(?P<scheme>https?|file|ftps?)://(?P<path>[^#]+)'
|
||||
r'(#(?P<fragment>\S+))?'
|
||||
)
|
||||
|
||||
VCS_REGEX = re.compile(
|
||||
r'^(?P<scheme>{0})://'.format(r'|'.join(
|
||||
[scheme.replace('+', r'\+') for scheme in VCS_SCHEMES])) +
|
||||
r'((?P<login>[^/@]+)@)?'
|
||||
r'(?P<path>[^#@]+)'
|
||||
r'(@(?P<revision>[^#]+))?'
|
||||
r'(#(?P<fragment>\S+))?'
|
||||
)
|
||||
|
||||
# This matches just about everyting
|
||||
LOCAL_REGEX = re.compile(
|
||||
r'^((?P<scheme>file)://)?'
|
||||
r'(?P<path>[^#]+)' +
|
||||
r'(#(?P<fragment>\S+))?'
|
||||
)
|
||||
|
||||
|
||||
class Requirement(object):
|
||||
"""
|
||||
Represents a single requirement
|
||||
|
||||
Typically instances of this class are created with ``Requirement.parse``.
|
||||
For local file requirements, there's no verification that the file
|
||||
exists. This class attempts to be *dict-like*.
|
||||
|
||||
See: http://www.pip-installer.org/en/latest/logic.html
|
||||
|
||||
**Members**:
|
||||
|
||||
* ``line`` - the actual requirement line being parsed
|
||||
* ``editable`` - a boolean whether this requirement is "editable"
|
||||
* ``local_file`` - a boolean whether this requirement is a local file/path
|
||||
* ``specifier`` - a boolean whether this requirement used a requirement
|
||||
specifier (eg. "django>=1.5" or "requirements")
|
||||
* ``vcs`` - a string specifying the version control system
|
||||
* ``revision`` - a version control system specifier
|
||||
* ``name`` - the name of the requirement
|
||||
* ``uri`` - the URI if this requirement was specified by URI
|
||||
* ``subdirectory`` - the subdirectory fragment of the URI
|
||||
* ``path`` - the local path to the requirement
|
||||
* ``hash_name`` - the type of hashing algorithm indicated in the line
|
||||
* ``hash`` - the hash value indicated by the requirement line
|
||||
* ``extras`` - a list of extras for this requirement
|
||||
(eg. "mymodule[extra1, extra2]")
|
||||
* ``specs`` - a list of specs for this requirement
|
||||
(eg. "mymodule>1.5,<1.6" => [('>', '1.5'), ('<', '1.6')])
|
||||
"""
|
||||
|
||||
def __init__(self, line):
|
||||
# Do not call this private method
|
||||
self.line = line
|
||||
self.editable = False
|
||||
self.local_file = False
|
||||
self.specifier = False
|
||||
self.vcs = None
|
||||
self.name = None
|
||||
self.subdirectory = None
|
||||
self.uri = None
|
||||
self.path = None
|
||||
self.revision = None
|
||||
self.hash_name = None
|
||||
self.hash = None
|
||||
self.extras = []
|
||||
self.specs = []
|
||||
|
||||
def __repr__(self):
|
||||
return '<Requirement: "{0}">'.format(self.line)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return getattr(self, key)
|
||||
|
||||
def keys(self):
|
||||
return self.__dict__.keys()
|
||||
|
||||
@classmethod
|
||||
def parse_editable(cls, line):
|
||||
"""
|
||||
Parses a Requirement from an "editable" requirement which is either
|
||||
a local project path or a VCS project URI.
|
||||
|
||||
See: pip/req.py:from_editable()
|
||||
|
||||
:param line: an "editable" requirement
|
||||
:returns: a Requirement instance for the given line
|
||||
:raises: ValueError on an invalid requirement
|
||||
"""
|
||||
|
||||
req = cls('-e {0}'.format(line))
|
||||
req.editable = True
|
||||
vcs_match = VCS_REGEX.match(line)
|
||||
local_match = LOCAL_REGEX.match(line)
|
||||
|
||||
if vcs_match is not None:
|
||||
groups = vcs_match.groupdict()
|
||||
if groups.get('login'):
|
||||
req.uri = '{scheme}://{login}@{path}'.format(**groups)
|
||||
else:
|
||||
req.uri = '{scheme}://{path}'.format(**groups)
|
||||
req.revision = groups['revision']
|
||||
if groups['fragment']:
|
||||
fragment = parse_fragment(groups['fragment'])
|
||||
egg = fragment.get('egg')
|
||||
req.name, req.extras = parse_extras_require(egg)
|
||||
req.hash_name, req.hash = get_hash_info(fragment)
|
||||
req.subdirectory = fragment.get('subdirectory')
|
||||
for vcs in VCS:
|
||||
if req.uri.startswith(vcs):
|
||||
req.vcs = vcs
|
||||
else:
|
||||
assert local_match is not None, 'This should match everything'
|
||||
groups = local_match.groupdict()
|
||||
req.local_file = True
|
||||
if groups['fragment']:
|
||||
fragment = parse_fragment(groups['fragment'])
|
||||
egg = fragment.get('egg')
|
||||
req.name, req.extras = parse_extras_require(egg)
|
||||
req.hash_name, req.hash = get_hash_info(fragment)
|
||||
req.subdirectory = fragment.get('subdirectory')
|
||||
req.path = groups['path']
|
||||
|
||||
return req
|
||||
|
||||
@classmethod
|
||||
def parse_line(cls, line):
|
||||
"""
|
||||
Parses a Requirement from a non-editable requirement.
|
||||
|
||||
See: pip/req.py:from_line()
|
||||
|
||||
:param line: a "non-editable" requirement
|
||||
:returns: a Requirement instance for the given line
|
||||
:raises: ValueError on an invalid requirement
|
||||
"""
|
||||
|
||||
req = cls(line)
|
||||
|
||||
vcs_match = VCS_REGEX.match(line)
|
||||
uri_match = URI_REGEX.match(line)
|
||||
local_match = LOCAL_REGEX.match(line)
|
||||
|
||||
if vcs_match is not None:
|
||||
groups = vcs_match.groupdict()
|
||||
if groups.get('login'):
|
||||
req.uri = '{scheme}://{login}@{path}'.format(**groups)
|
||||
else:
|
||||
req.uri = '{scheme}://{path}'.format(**groups)
|
||||
req.revision = groups['revision']
|
||||
if groups['fragment']:
|
||||
fragment = parse_fragment(groups['fragment'])
|
||||
egg = fragment.get('egg')
|
||||
req.name, req.extras = parse_extras_require(egg)
|
||||
req.hash_name, req.hash = get_hash_info(fragment)
|
||||
req.subdirectory = fragment.get('subdirectory')
|
||||
for vcs in VCS:
|
||||
if req.uri.startswith(vcs):
|
||||
req.vcs = vcs
|
||||
elif uri_match is not None:
|
||||
groups = uri_match.groupdict()
|
||||
req.uri = '{scheme}://{path}'.format(**groups)
|
||||
if groups['fragment']:
|
||||
fragment = parse_fragment(groups['fragment'])
|
||||
egg = fragment.get('egg')
|
||||
req.name, req.extras = parse_extras_require(egg)
|
||||
req.hash_name, req.hash = get_hash_info(fragment)
|
||||
req.subdirectory = fragment.get('subdirectory')
|
||||
if groups['scheme'] == 'file':
|
||||
req.local_file = True
|
||||
elif '#egg=' in line:
|
||||
# Assume a local file match
|
||||
assert local_match is not None, 'This should match everything'
|
||||
groups = local_match.groupdict()
|
||||
req.local_file = True
|
||||
if groups['fragment']:
|
||||
fragment = parse_fragment(groups['fragment'])
|
||||
egg = fragment.get('egg')
|
||||
name, extras = parse_extras_require(egg)
|
||||
req.name = fragment.get('egg')
|
||||
req.hash_name, req.hash = get_hash_info(fragment)
|
||||
req.subdirectory = fragment.get('subdirectory')
|
||||
req.path = groups['path']
|
||||
else:
|
||||
# This is a requirement specifier.
|
||||
# Delegate to pkg_resources and hope for the best
|
||||
req.specifier = True
|
||||
pkg_req = Req.parse(line)
|
||||
req.name = pkg_req.unsafe_name
|
||||
req.extras = list(pkg_req.extras)
|
||||
req.specs = pkg_req.specs
|
||||
return req
|
||||
|
||||
@classmethod
|
||||
def parse(cls, line):
|
||||
"""
|
||||
Parses a Requirement from a line of a requirement file.
|
||||
|
||||
:param line: a line of a requirement file
|
||||
:returns: a Requirement instance for the given line
|
||||
:raises: ValueError on an invalid requirement
|
||||
"""
|
||||
|
||||
if line.startswith('-e') or line.startswith('--editable'):
|
||||
# Editable installs are either a local project path
|
||||
# or a VCS project URI
|
||||
return cls.parse_editable(
|
||||
re.sub(r'^(-e|--editable=?)\s*', '', line))
|
||||
|
||||
return cls.parse_line(line)
|
||||
Vendored
-30
@@ -1,30 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
VCS = [
|
||||
'git',
|
||||
'hg',
|
||||
'svn',
|
||||
'bzr',
|
||||
]
|
||||
|
||||
VCS_SCHEMES = [
|
||||
'git',
|
||||
'git+https',
|
||||
'git+ssh',
|
||||
'git+git',
|
||||
'hg+http',
|
||||
'hg+https',
|
||||
'hg+static-http',
|
||||
'hg+ssh',
|
||||
'svn',
|
||||
'svn+svn',
|
||||
'svn+http',
|
||||
'svn+https',
|
||||
'svn+ssh',
|
||||
'bzr+http',
|
||||
'bzr+https',
|
||||
'bzr+ssh',
|
||||
'bzr+sftp',
|
||||
'bzr+ftp',
|
||||
'bzr+lp',
|
||||
]
|
||||
+21
-3
@@ -1,3 +1,21 @@
|
||||
This software is made available under the terms of *either* of the licenses
|
||||
found in LICENSE.APACHE or LICENSE.BSD. Contributions to this software is made
|
||||
under the terms of *both* these licenses.
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2018 Dan Ryan.
|
||||
|
||||
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.
|
||||
|
||||
-177
@@ -1,177 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
__version__ = "1.0.11"
|
||||
__version__ = '1.1.5'
|
||||
|
||||
|
||||
from .exceptions import RequirementError
|
||||
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
import importlib
|
||||
import six
|
||||
|
||||
# Use these imports as compatibility imports
|
||||
try:
|
||||
from pathlib import Path
|
||||
except ImportError:
|
||||
from pathlib2 import Path
|
||||
|
||||
try:
|
||||
from urllib.parse import urlparse, unquote
|
||||
except ImportError:
|
||||
from urlparse import urlparse, unquote
|
||||
|
||||
if six.PY2:
|
||||
|
||||
class FileNotFoundError(IOError):
|
||||
pass
|
||||
|
||||
|
||||
else:
|
||||
|
||||
class FileNotFoundError(FileNotFoundError):
|
||||
pass
|
||||
|
||||
|
||||
def do_import(module_path, subimport=None, old_path=None):
|
||||
internal = "pip._internal.{0}".format(module_path)
|
||||
old_path = old_path or module_path
|
||||
pip9 = "pip.{0}".format(old_path)
|
||||
try:
|
||||
_tmp = importlib.import_module(internal)
|
||||
except ImportError:
|
||||
_tmp = importlib.import_module(pip9)
|
||||
if subimport:
|
||||
return getattr(_tmp, subimport, _tmp)
|
||||
return _tmp
|
||||
|
||||
|
||||
InstallRequirement = do_import("req.req_install", "InstallRequirement")
|
||||
user_cache_dir = do_import("utils.appdirs", "user_cache_dir")
|
||||
FAVORITE_HASH = do_import("utils.hashes", "FAVORITE_HASH")
|
||||
is_file_url = do_import("download", "is_file_url")
|
||||
url_to_path = do_import("download", "url_to_path")
|
||||
path_to_url = do_import("download", "path_to_url")
|
||||
is_archive_file = do_import("download", "is_archive_file")
|
||||
_strip_extras = do_import("req.req_install", "_strip_extras")
|
||||
Link = do_import("index", "Link")
|
||||
Wheel = do_import("wheel", "Wheel")
|
||||
is_installable_file = do_import("utils.misc", "is_installable_file", old_path="utils")
|
||||
is_installable_dir = do_import("utils.misc", "is_installable_dir", old_path="utils")
|
||||
make_abstract_dist = do_import(
|
||||
"operations.prepare", "make_abstract_dist", old_path="req.req_set"
|
||||
)
|
||||
VcsSupport = do_import("vcs", "VcsSupport")
|
||||
@@ -1 +0,0 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
@@ -1,3 +0,0 @@
|
||||
This software is made available under the terms of *either* of the licenses
|
||||
found in LICENSE.APACHE or LICENSE.BSD. Contributions to this software is made
|
||||
under the terms of *both* these licenses.
|
||||
@@ -1,177 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -1,23 +0,0 @@
|
||||
Copyright (c) Kenneth Reitz and individual contributors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
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.
|
||||
@@ -1,21 +0,0 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__all__ = [
|
||||
"__title__", "__summary__", "__uri__", "__version__", "__author__",
|
||||
"__email__", "__license__", "__copyright__",
|
||||
]
|
||||
|
||||
__title__ = "pipfile"
|
||||
__summary__ = ""
|
||||
__uri__ = "https://github.com/pypa/pipfile"
|
||||
|
||||
__version__ = "0.0.2"
|
||||
|
||||
__author__ = "Kenneth Reitz and individual contributors"
|
||||
__email__ = "me@kennethreitz.org"
|
||||
|
||||
__license__ = "BSD or Apache License, Version 2.0"
|
||||
__copyright__ = "Copyright 2017 %s" % __author__
|
||||
@@ -1,11 +0,0 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
from .__about__ import (
|
||||
__author__, __copyright__, __email__, __license__, __summary__, __title__,
|
||||
__uri__, __version__
|
||||
)
|
||||
|
||||
from .api import load, Pipfile
|
||||
@@ -1,230 +0,0 @@
|
||||
import toml
|
||||
|
||||
import codecs
|
||||
import json
|
||||
import hashlib
|
||||
import platform
|
||||
import six
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
DEFAULT_SOURCE = {
|
||||
u'url': u'https://pypi.org/simple',
|
||||
u'verify_ssl': True,
|
||||
u'name': u'pypi',
|
||||
}
|
||||
|
||||
|
||||
def format_full_version(info):
|
||||
version = '{0.major}.{0.minor}.{0.micro}'.format(info)
|
||||
kind = info.releaselevel
|
||||
if kind != 'final':
|
||||
version += kind[0] + str(info.serial)
|
||||
return version
|
||||
|
||||
|
||||
def walk_up(bottom):
|
||||
"""mimic os.walk, but walk 'up' instead of down the directory tree.
|
||||
From: https://gist.github.com/zdavkeos/1098474
|
||||
"""
|
||||
|
||||
bottom = os.path.realpath(bottom)
|
||||
|
||||
# get files in current dir
|
||||
try:
|
||||
names = os.listdir(bottom)
|
||||
except Exception:
|
||||
return
|
||||
|
||||
dirs, nondirs = [], []
|
||||
for name in names:
|
||||
if os.path.isdir(os.path.join(bottom, name)):
|
||||
dirs.append(name)
|
||||
else:
|
||||
nondirs.append(name)
|
||||
|
||||
yield bottom, dirs, nondirs
|
||||
|
||||
new_path = os.path.realpath(os.path.join(bottom, '..'))
|
||||
|
||||
# see if we are at the top
|
||||
if new_path == bottom:
|
||||
return
|
||||
|
||||
for x in walk_up(new_path):
|
||||
yield x
|
||||
|
||||
|
||||
class PipfileParser(object):
|
||||
def __init__(self, filename='Pipfile'):
|
||||
self.filename = filename
|
||||
self.sources = []
|
||||
self.groups = {
|
||||
'default': [],
|
||||
'develop': []
|
||||
}
|
||||
self.group_stack = ['default']
|
||||
self.requirements = []
|
||||
|
||||
def __repr__(self):
|
||||
return '<PipfileParser path={0!r}'.format(self.filename)
|
||||
|
||||
def inject_environment_variables(self, d):
|
||||
"""
|
||||
Recursively injects environment variables into TOML values
|
||||
"""
|
||||
|
||||
if not d:
|
||||
return d
|
||||
if isinstance(d, six.string_types):
|
||||
return os.path.expandvars(d)
|
||||
for k, v in d.items():
|
||||
if isinstance(v, six.string_types):
|
||||
d[k] = os.path.expandvars(v)
|
||||
elif isinstance(v, dict):
|
||||
d[k] = self.inject_environment_variables(v)
|
||||
elif isinstance(v, list):
|
||||
d[k] = [self.inject_environment_variables(e) for e in v]
|
||||
|
||||
return d
|
||||
|
||||
def parse(self, inject_env=True):
|
||||
# Open the Pipfile.
|
||||
with open(self.filename) as f:
|
||||
content = f.read()
|
||||
|
||||
# Load the default configuration.
|
||||
default_config = {
|
||||
u'source': [DEFAULT_SOURCE],
|
||||
u'packages': {},
|
||||
u'requires': {},
|
||||
u'dev-packages': {}
|
||||
}
|
||||
|
||||
config = {}
|
||||
config.update(default_config)
|
||||
|
||||
# Deserialize the TOML, and parse for Environment Variables
|
||||
parsed = toml.loads(content)
|
||||
|
||||
if inject_env:
|
||||
injected_toml = self.inject_environment_variables(parsed)
|
||||
|
||||
# Load the Pipfile's configuration.
|
||||
config.update(injected_toml)
|
||||
else:
|
||||
config.update(parsed)
|
||||
|
||||
# Structure the data for output.
|
||||
data = {
|
||||
'_meta': {
|
||||
'sources': config['source'],
|
||||
'requires': config['requires']
|
||||
},
|
||||
}
|
||||
|
||||
# TODO: Validate given data here.
|
||||
self.groups['default'] = config['packages']
|
||||
self.groups['develop'] = config['dev-packages']
|
||||
|
||||
# Update the data structure with group information.
|
||||
data.update(self.groups)
|
||||
return data
|
||||
|
||||
|
||||
class Pipfile(object):
|
||||
def __init__(self, filename):
|
||||
super(Pipfile, self).__init__()
|
||||
self.filename = filename
|
||||
self.data = None
|
||||
|
||||
@staticmethod
|
||||
def find(max_depth=3):
|
||||
"""Returns the path of a Pipfile in parent directories."""
|
||||
i = 0
|
||||
for c, d, f in walk_up(os.getcwd()):
|
||||
i += 1
|
||||
|
||||
if i < max_depth:
|
||||
if 'Pipfile':
|
||||
p = os.path.join(c, 'Pipfile')
|
||||
if os.path.isfile(p):
|
||||
return p
|
||||
raise RuntimeError('No Pipfile found!')
|
||||
|
||||
@classmethod
|
||||
def load(klass, filename, inject_env=True):
|
||||
"""Load a Pipfile from a given filename."""
|
||||
p = PipfileParser(filename=filename)
|
||||
pipfile = klass(filename=filename)
|
||||
pipfile.data = p.parse(inject_env=inject_env)
|
||||
return pipfile
|
||||
|
||||
@property
|
||||
def hash(self):
|
||||
"""Returns the SHA256 of the pipfile's data."""
|
||||
content = json.dumps(self.data, sort_keys=True, separators=(",", ":"))
|
||||
return hashlib.sha256(content.encode("utf8")).hexdigest()
|
||||
|
||||
@property
|
||||
def contents(self):
|
||||
"""Returns the contents of the pipfile."""
|
||||
with codecs.open(self.filename, 'r', 'utf-8') as f:
|
||||
return f.read()
|
||||
|
||||
def lock(self):
|
||||
"""Returns a JSON representation of the Pipfile."""
|
||||
data = self.data
|
||||
data['_meta']['hash'] = {"sha256": self.hash}
|
||||
data['_meta']['pipfile-spec'] = 6
|
||||
return json.dumps(data, indent=4, separators=(',', ': '))
|
||||
|
||||
def assert_requirements(self):
|
||||
""""Asserts PEP 508 specifiers."""
|
||||
|
||||
# Support for 508's implementation_version.
|
||||
if hasattr(sys, 'implementation'):
|
||||
implementation_version = format_full_version(sys.implementation.version)
|
||||
else:
|
||||
implementation_version = "0"
|
||||
|
||||
# Default to cpython for 2.7.
|
||||
if hasattr(sys, 'implementation'):
|
||||
implementation_name = sys.implementation.name
|
||||
else:
|
||||
implementation_name = 'cpython'
|
||||
|
||||
lookup = {
|
||||
'os_name': os.name,
|
||||
'sys_platform': sys.platform,
|
||||
'platform_machine': platform.machine(),
|
||||
'platform_python_implementation': platform.python_implementation(),
|
||||
'platform_release': platform.release(),
|
||||
'platform_system': platform.system(),
|
||||
'platform_version': platform.version(),
|
||||
'python_version': platform.python_version()[:3],
|
||||
'python_full_version': platform.python_version(),
|
||||
'implementation_name': implementation_name,
|
||||
'implementation_version': implementation_version
|
||||
}
|
||||
|
||||
# Assert each specified requirement.
|
||||
for marker, specifier in self.data['_meta']['requires'].items():
|
||||
|
||||
if marker in lookup:
|
||||
try:
|
||||
assert lookup[marker] == specifier
|
||||
except AssertionError:
|
||||
raise AssertionError('Specifier {!r} does not match {!r}.'.format(marker, specifier))
|
||||
|
||||
|
||||
def load(pipfile_path=None, inject_env=True):
|
||||
"""Loads a pipfile from a given path.
|
||||
If none is provided, one will try to be found.
|
||||
"""
|
||||
|
||||
if pipfile_path is None:
|
||||
pipfile_path = Pipfile.find()
|
||||
|
||||
return Pipfile.load(filename=pipfile_path, inject_env=inject_env)
|
||||
+9
@@ -1,5 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
import errno
|
||||
import six
|
||||
|
||||
|
||||
if six.PY2:
|
||||
class FileExistsError(OSError):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.errno = errno.EEXIST
|
||||
super(FileExistsError, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class RequirementError(Exception):
|
||||
|
||||
+2
-1
@@ -2,9 +2,10 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
__all__ = ["Requirement", "Lockfile", "Pipfile", "RequirementError"]
|
||||
__all__ = ["Requirement", "Lockfile", "Pipfile", "DependencyResolver"]
|
||||
|
||||
|
||||
from .requirements import Requirement
|
||||
from .lockfile import Lockfile
|
||||
from .pipfile import Pipfile
|
||||
from .resolvers import DependencyResolver
|
||||
|
||||
+266
@@ -0,0 +1,266 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import copy
|
||||
import errno
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import six
|
||||
import sys
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
import requests
|
||||
import vistir
|
||||
|
||||
from appdirs import user_cache_dir
|
||||
from packaging.requirements import Requirement
|
||||
|
||||
from pip_shims.shims import (
|
||||
FAVORITE_HASH, Link, SafeFileCache, VcsSupport, is_file_url, url_to_path
|
||||
)
|
||||
from .utils import as_tuple, key_from_req, lookup_table
|
||||
|
||||
|
||||
if six.PY2:
|
||||
from ..exceptions import FileExistsError
|
||||
|
||||
|
||||
CACHE_DIR = os.environ.get("PIPENV_CACHE_DIR", user_cache_dir("pipenv"))
|
||||
|
||||
|
||||
# Pip-tools cache implementation
|
||||
class CorruptCacheError(Exception):
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
|
||||
def __str__(self):
|
||||
lines = [
|
||||
'The dependency cache seems to have been corrupted.',
|
||||
'Inspect, or delete, the following file:',
|
||||
' {}'.format(self.path),
|
||||
]
|
||||
return os.linesep.join(lines)
|
||||
|
||||
|
||||
def read_cache_file(cache_file_path):
|
||||
with open(cache_file_path, 'r') as cache_file:
|
||||
try:
|
||||
doc = json.load(cache_file)
|
||||
except ValueError:
|
||||
raise CorruptCacheError(cache_file_path)
|
||||
|
||||
# Check version and load the contents
|
||||
assert doc['__format__'] == 1, 'Unknown cache file format'
|
||||
return doc['dependencies']
|
||||
|
||||
|
||||
class DependencyCache(object):
|
||||
"""
|
||||
Creates a new persistent dependency cache for the current Python version.
|
||||
The cache file is written to the appropriate user cache dir for the
|
||||
current platform, i.e.
|
||||
|
||||
~/.cache/pip-tools/depcache-pyX.Y.json
|
||||
|
||||
Where X.Y indicates the Python version.
|
||||
"""
|
||||
def __init__(self, cache_dir=None):
|
||||
if cache_dir is None:
|
||||
cache_dir = CACHE_DIR
|
||||
if not vistir.compat.Path(CACHE_DIR).absolute().is_dir():
|
||||
try:
|
||||
vistir.path.mkdir_p(os.path.abspath(cache_dir))
|
||||
except (FileExistsError, OSError):
|
||||
pass
|
||||
|
||||
py_version = '.'.join(str(digit) for digit in sys.version_info[:2])
|
||||
cache_filename = 'depcache-py{}.json'.format(py_version)
|
||||
|
||||
self._cache_file = os.path.join(cache_dir, cache_filename)
|
||||
self._cache = None
|
||||
|
||||
@property
|
||||
def cache(self):
|
||||
"""
|
||||
The dictionary that is the actual in-memory cache. This property
|
||||
lazily loads the cache from disk.
|
||||
"""
|
||||
if self._cache is None:
|
||||
self.read_cache()
|
||||
return self._cache
|
||||
|
||||
def as_cache_key(self, ireq):
|
||||
"""
|
||||
Given a requirement, return its cache key. This behavior is a little weird in order to allow backwards
|
||||
compatibility with cache files. For a requirement without extras, this will return, for example:
|
||||
|
||||
("ipython", "2.1.0")
|
||||
|
||||
For a requirement with extras, the extras will be comma-separated and appended to the version, inside brackets,
|
||||
like so:
|
||||
|
||||
("ipython", "2.1.0[nbconvert,notebook]")
|
||||
"""
|
||||
name, version, extras = as_tuple(ireq)
|
||||
if not extras:
|
||||
extras_string = ""
|
||||
else:
|
||||
extras_string = "[{}]".format(",".join(extras))
|
||||
return name, "{}{}".format(version, extras_string)
|
||||
|
||||
def read_cache(self):
|
||||
"""Reads the cached contents into memory."""
|
||||
if os.path.exists(self._cache_file):
|
||||
self._cache = read_cache_file(self._cache_file)
|
||||
else:
|
||||
self._cache = {}
|
||||
|
||||
def write_cache(self):
|
||||
"""Writes the cache to disk as JSON."""
|
||||
doc = {
|
||||
'__format__': 1,
|
||||
'dependencies': self._cache,
|
||||
}
|
||||
with open(self._cache_file, 'w') as f:
|
||||
json.dump(doc, f, sort_keys=True)
|
||||
|
||||
def clear(self):
|
||||
self._cache = {}
|
||||
self.write_cache()
|
||||
|
||||
def __contains__(self, ireq):
|
||||
pkgname, pkgversion_and_extras = self.as_cache_key(ireq)
|
||||
return pkgversion_and_extras in self.cache.get(pkgname, {})
|
||||
|
||||
def __getitem__(self, ireq):
|
||||
pkgname, pkgversion_and_extras = self.as_cache_key(ireq)
|
||||
return self.cache[pkgname][pkgversion_and_extras]
|
||||
|
||||
def __setitem__(self, ireq, values):
|
||||
pkgname, pkgversion_and_extras = self.as_cache_key(ireq)
|
||||
self.cache.setdefault(pkgname, {})
|
||||
self.cache[pkgname][pkgversion_and_extras] = values
|
||||
self.write_cache()
|
||||
|
||||
def __delitem__(self, ireq):
|
||||
pkgname, pkgversion_and_extras = self.as_cache_key(ireq)
|
||||
try:
|
||||
del self.cache[pkgname][pkgversion_and_extras]
|
||||
except KeyError:
|
||||
return
|
||||
self.write_cache()
|
||||
|
||||
def get(self, ireq, default=None):
|
||||
pkgname, pkgversion_and_extras = self.as_cache_key(ireq)
|
||||
return self.cache.get(pkgname, {}).get(pkgversion_and_extras, default)
|
||||
|
||||
def reverse_dependencies(self, ireqs):
|
||||
"""
|
||||
Returns a lookup table of reverse dependencies for all the given ireqs.
|
||||
|
||||
Since this is all static, it only works if the dependency cache
|
||||
contains the complete data, otherwise you end up with a partial view.
|
||||
This is typically no problem if you use this function after the entire
|
||||
dependency tree is resolved.
|
||||
"""
|
||||
ireqs_as_cache_values = [self.as_cache_key(ireq) for ireq in ireqs]
|
||||
return self._reverse_dependencies(ireqs_as_cache_values)
|
||||
|
||||
def _reverse_dependencies(self, cache_keys):
|
||||
"""
|
||||
Returns a lookup table of reverse dependencies for all the given cache keys.
|
||||
|
||||
Example input:
|
||||
|
||||
[('pep8', '1.5.7'),
|
||||
('flake8', '2.4.0'),
|
||||
('mccabe', '0.3'),
|
||||
('pyflakes', '0.8.1')]
|
||||
|
||||
Example output:
|
||||
|
||||
{'pep8': ['flake8'],
|
||||
'flake8': [],
|
||||
'mccabe': ['flake8'],
|
||||
'pyflakes': ['flake8']}
|
||||
|
||||
"""
|
||||
# First, collect all the dependencies into a sequence of (parent, child) tuples, like [('flake8', 'pep8'),
|
||||
# ('flake8', 'mccabe'), ...]
|
||||
return lookup_table((key_from_req(Requirement(dep_name)), name)
|
||||
for name, version_and_extras in cache_keys
|
||||
for dep_name in self.cache[name][version_and_extras])
|
||||
|
||||
|
||||
class HashCache(SafeFileCache):
|
||||
"""Caches hashes of PyPI artifacts so we do not need to re-download them
|
||||
|
||||
Hashes are only cached when the URL appears to contain a hash in it and the cache key includes
|
||||
the hash value returned from the server). This ought to avoid ssues where the location on the
|
||||
server changes."""
|
||||
def __init__(self, *args, **kwargs):
|
||||
session = kwargs.pop('session', requests.session())
|
||||
self.session = session
|
||||
kwargs.setdefault('directory', os.path.join(CACHE_DIR, 'hash-cache'))
|
||||
super(HashCache, self).__init__(*args, **kwargs)
|
||||
|
||||
def get_hash(self, location):
|
||||
# if there is no location hash (i.e., md5 / sha256 / etc) we on't want to store it
|
||||
hash_value = None
|
||||
vcs = VcsSupport()
|
||||
orig_scheme = location.scheme
|
||||
new_location = copy.deepcopy(location)
|
||||
if orig_scheme in vcs.all_schemes:
|
||||
new_location.url = new_location.url.split("+", 1)[-1]
|
||||
can_hash = new_location.hash
|
||||
if can_hash:
|
||||
# hash url WITH fragment
|
||||
hash_value = self.get(new_location.url)
|
||||
if not hash_value:
|
||||
hash_value = self._get_file_hash(new_location)
|
||||
hash_value = hash_value.encode('utf8')
|
||||
if can_hash:
|
||||
self.set(new_location.url, hash_value)
|
||||
return hash_value.decode('utf8')
|
||||
|
||||
def _get_file_hash(self, location):
|
||||
h = hashlib.new(FAVORITE_HASH)
|
||||
with open_local_or_remote_file(location, self.session) as fp:
|
||||
for chunk in iter(lambda: fp.read(8096), b""):
|
||||
h.update(chunk)
|
||||
return ":".join([FAVORITE_HASH, h.hexdigest()])
|
||||
|
||||
|
||||
@contextmanager
|
||||
def open_local_or_remote_file(link, session):
|
||||
"""
|
||||
Open local or remote file for reading.
|
||||
|
||||
:type link: pip._internal.index.Link
|
||||
:type session: requests.Session
|
||||
:raises ValueError: If link points to a local directory.
|
||||
:return: a context manager to the opened file-like object
|
||||
"""
|
||||
if isinstance(link, Link):
|
||||
url = link.url_without_fragment
|
||||
else:
|
||||
url = link
|
||||
|
||||
if is_file_url(link):
|
||||
# Local URL
|
||||
local_path = url_to_path(url)
|
||||
if os.path.isdir(local_path):
|
||||
raise ValueError("Cannot open directory for read: {}".format(url))
|
||||
else:
|
||||
with open(local_path, 'rb') as local_file:
|
||||
yield local_file
|
||||
else:
|
||||
# Remote URL
|
||||
headers = {"Accept-Encoding": "identity"}
|
||||
response = session.get(url, headers=headers, stream=True)
|
||||
try:
|
||||
yield response.raw
|
||||
finally:
|
||||
response.close()
|
||||
@@ -0,0 +1,650 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
|
||||
import contextlib
|
||||
import copy
|
||||
import functools
|
||||
import os
|
||||
|
||||
import attr
|
||||
import packaging.markers
|
||||
import packaging.version
|
||||
import requests
|
||||
|
||||
from first import first
|
||||
from packaging.utils import canonicalize_name
|
||||
|
||||
from pip_shims import (
|
||||
FormatControl, InstallRequirement, PackageFinder, RequirementPreparer,
|
||||
RequirementSet, RequirementTracker, Resolver, WheelCache, pip_version
|
||||
)
|
||||
from vistir.compat import JSONDecodeError, TemporaryDirectory, fs_str
|
||||
from vistir.contextmanagers import cd, temp_environ
|
||||
from vistir.misc import partialclass
|
||||
|
||||
from ..utils import get_pip_command, prepare_pip_source_args, _ensure_dir
|
||||
from .cache import CACHE_DIR, DependencyCache
|
||||
from .utils import (
|
||||
clean_requires_python, fix_requires_python_marker, format_requirement,
|
||||
full_groupby, is_pinned_requirement, key_from_ireq,
|
||||
make_install_requirement, name_from_req, version_from_ireq
|
||||
)
|
||||
|
||||
|
||||
PKGS_DOWNLOAD_DIR = fs_str(os.path.join(CACHE_DIR, "pkgs"))
|
||||
WHEEL_DOWNLOAD_DIR = fs_str(os.path.join(CACHE_DIR, "wheels"))
|
||||
|
||||
DEPENDENCY_CACHE = DependencyCache()
|
||||
WHEEL_CACHE = WheelCache(CACHE_DIR, FormatControl(set(), set()))
|
||||
|
||||
|
||||
def _get_filtered_versions(ireq, versions, prereleases):
|
||||
return set(ireq.specifier.filter(versions, prereleases=prereleases))
|
||||
|
||||
|
||||
def find_all_matches(finder, ireq, pre=False):
|
||||
"""Find all matching dependencies using the supplied finder and the
|
||||
given ireq.
|
||||
|
||||
:param finder: A package finder for discovering matching candidates.
|
||||
:type finder: :class:`~pip._internal.index.PackageFinder`
|
||||
:param ireq: An install requirement.
|
||||
:type ireq: :class:`~pip._internal.req.req_install.InstallRequirement`
|
||||
:return: A list of matching candidates.
|
||||
:rtype: list[:class:`~pip._internal.index.InstallationCandidate`]
|
||||
"""
|
||||
candidates = clean_requires_python(finder.find_all_candidates(ireq.name))
|
||||
versions = {candidate.version for candidate in candidates}
|
||||
allowed_versions = _get_filtered_versions(ireq, versions, pre)
|
||||
if not pre and not allowed_versions:
|
||||
allowed_versions = _get_filtered_versions(ireq, versions, True)
|
||||
candidates = {c for c in candidates if c.version in allowed_versions}
|
||||
return candidates
|
||||
|
||||
|
||||
@attr.s
|
||||
class AbstractDependency(object):
|
||||
name = attr.ib()
|
||||
specifiers = attr.ib()
|
||||
markers = attr.ib()
|
||||
candidates = attr.ib()
|
||||
requirement = attr.ib()
|
||||
parent = attr.ib()
|
||||
finder = attr.ib()
|
||||
dep_dict = attr.ib(default=attr.Factory(dict))
|
||||
|
||||
@property
|
||||
def version_set(self):
|
||||
"""Return the set of versions for the candidates in this abstract dependency.
|
||||
|
||||
:return: A set of matching versions
|
||||
:rtype: set(str)
|
||||
"""
|
||||
|
||||
if len(self.candidates) == 1:
|
||||
return set()
|
||||
return set(packaging.version.parse(version_from_ireq(c)) for c in self.candidates)
|
||||
|
||||
def compatible_versions(self, other):
|
||||
"""Find compatible version numbers between this abstract
|
||||
dependency and another one.
|
||||
|
||||
:param other: An abstract dependency to compare with.
|
||||
:type other: :class:`~requirementslib.models.dependency.AbstractDependency`
|
||||
:return: A set of compatible version strings
|
||||
:rtype: set(str)
|
||||
"""
|
||||
|
||||
if len(self.candidates) == 1 and first(self.candidates).editable:
|
||||
return self
|
||||
elif len(other.candidates) == 1 and first(other.candidates).editable:
|
||||
return other
|
||||
return self.version_set & other.version_set
|
||||
|
||||
def compatible_abstract_dep(self, other):
|
||||
"""Merge this abstract dependency with another one.
|
||||
|
||||
Return the result of the merge as a new abstract dependency.
|
||||
|
||||
:param other: An abstract dependency to merge with
|
||||
:type other: :class:`~requirementslib.models.dependency.AbstractDependency`
|
||||
:return: A new, combined abstract dependency
|
||||
:rtype: :class:`~requirementslib.models.dependency.AbstractDependency`
|
||||
"""
|
||||
|
||||
from .requirements import Requirement
|
||||
|
||||
if len(self.candidates) == 1 and first(self.candidates).editable:
|
||||
return self
|
||||
elif len(other.candidates) == 1 and first(other.candidates).editable:
|
||||
return other
|
||||
new_specifiers = self.specifiers & other.specifiers
|
||||
markers = set(self.markers,) if self.markers else set()
|
||||
if other.markers:
|
||||
markers.add(other.markers)
|
||||
new_markers = packaging.markers.Marker(" or ".join(str(m) for m in sorted(markers)))
|
||||
new_ireq = copy.deepcopy(self.requirement.ireq)
|
||||
new_ireq.req.specifier = new_specifiers
|
||||
new_ireq.req.marker = new_markers
|
||||
new_requirement = Requirement.from_line(format_requirement(new_ireq))
|
||||
compatible_versions = self.compatible_versions(other)
|
||||
if isinstance(compatible_versions, AbstractDependency):
|
||||
return compatible_versions
|
||||
candidates = [
|
||||
c
|
||||
for c in self.candidates
|
||||
if packaging.version.parse(version_from_ireq(c)) in compatible_versions
|
||||
]
|
||||
dep_dict = {}
|
||||
candidate_strings = [format_requirement(c) for c in candidates]
|
||||
for c in candidate_strings:
|
||||
if c in self.dep_dict:
|
||||
dep_dict[c] = self.dep_dict.get(c)
|
||||
return AbstractDependency(
|
||||
name=self.name,
|
||||
specifiers=new_specifiers,
|
||||
markers=new_markers,
|
||||
candidates=candidates,
|
||||
requirement=new_requirement,
|
||||
parent=self.parent,
|
||||
dep_dict=dep_dict,
|
||||
finder=self.finder
|
||||
)
|
||||
|
||||
def get_deps(self, candidate):
|
||||
"""Get the dependencies of the supplied candidate.
|
||||
|
||||
:param candidate: An installrequirement
|
||||
:type candidate: :class:`~pip._internal.req.req_install.InstallRequirement`
|
||||
:return: A list of abstract dependencies
|
||||
:rtype: list[:class:`~requirementslib.models.dependency.AbstractDependency`]
|
||||
"""
|
||||
|
||||
key = format_requirement(candidate)
|
||||
if key not in self.dep_dict:
|
||||
from .requirements import Requirement
|
||||
|
||||
req = Requirement.from_line(key)
|
||||
req.merge_markers(self.markers)
|
||||
self.dep_dict[key] = req.get_abstract_dependencies()
|
||||
return self.dep_dict[key]
|
||||
|
||||
@classmethod
|
||||
def from_requirement(cls, requirement, parent=None):
|
||||
"""Creates a new :class:`~requirementslib.models.dependency.AbstractDependency`
|
||||
from a :class:`~requirementslib.models.requirements.Requirement` object.
|
||||
|
||||
This class is used to find all candidates matching a given set of specifiers
|
||||
and a given requirement.
|
||||
|
||||
:param requirement: A requirement for resolution
|
||||
:type requirement: :class:`~requirementslib.models.requirements.Requirement` object.
|
||||
"""
|
||||
name = requirement.normalized_name
|
||||
specifiers = requirement.ireq.specifier if not requirement.editable else ""
|
||||
markers = requirement.ireq.markers
|
||||
extras = requirement.ireq.extras
|
||||
is_pinned = is_pinned_requirement(requirement.ireq)
|
||||
is_constraint = bool(parent)
|
||||
finder = get_finder(sources=None)
|
||||
candidates = []
|
||||
if not is_pinned and not requirement.editable:
|
||||
for r in requirement.find_all_matches(finder=finder):
|
||||
req = make_install_requirement(
|
||||
name, r.version, extras=extras, markers=markers, constraint=is_constraint,
|
||||
)
|
||||
req.req.link = r.location
|
||||
req.parent = parent
|
||||
candidates.append(req)
|
||||
candidates = sorted(
|
||||
set(candidates), key=lambda k: packaging.version.parse(version_from_ireq(k)),
|
||||
)
|
||||
else:
|
||||
candidates = [requirement.ireq]
|
||||
return cls(
|
||||
name=name,
|
||||
specifiers=specifiers,
|
||||
markers=markers,
|
||||
candidates=candidates,
|
||||
requirement=requirement,
|
||||
parent=parent,
|
||||
finder=finder,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, line, parent=None):
|
||||
from .requirements import Requirement
|
||||
|
||||
req = Requirement.from_line(line)
|
||||
abstract_dep = cls.from_requirement(req, parent=parent)
|
||||
return abstract_dep
|
||||
|
||||
|
||||
def get_abstract_dependencies(reqs, sources=None, parent=None):
|
||||
"""Get all abstract dependencies for a given list of requirements.
|
||||
|
||||
Given a set of requirements, convert each requirement to an Abstract Dependency.
|
||||
|
||||
:param reqs: A list of Requirements
|
||||
:type reqs: list[:class:`~requirementslib.models.requirements.Requirement`]
|
||||
:param sources: Pipfile-formatted sources, defaults to None
|
||||
:param sources: list[dict], optional
|
||||
:param parent: The parent of this list of dependencies, defaults to None
|
||||
:param parent: :class:`~requirementslib.models.requirements.Requirement`, optional
|
||||
:return: A list of Abstract Dependencies
|
||||
:rtype: list[:class:`~requirementslib.models.dependency.AbstractDependency`]
|
||||
"""
|
||||
|
||||
deps = []
|
||||
from .requirements import Requirement
|
||||
|
||||
for req in reqs:
|
||||
if isinstance(req, InstallRequirement):
|
||||
requirement = Requirement.from_line(
|
||||
"{0}{1}".format(req.name, req.specifier)
|
||||
)
|
||||
if req.link:
|
||||
requirement.req.link = req.link
|
||||
requirement.markers = req.markers
|
||||
requirement.req.markers = req.markers
|
||||
requirement.extras = req.extras
|
||||
requirement.req.extras = req.extras
|
||||
elif isinstance(req, Requirement):
|
||||
requirement = copy.deepcopy(req)
|
||||
else:
|
||||
requirement = Requirement.from_line(req)
|
||||
dep = AbstractDependency.from_requirement(requirement, parent=parent)
|
||||
deps.append(dep)
|
||||
return deps
|
||||
|
||||
|
||||
def get_dependencies(ireq, sources=None, parent=None):
|
||||
"""Get all dependencies for a given install requirement.
|
||||
|
||||
:param ireq: A single InstallRequirement
|
||||
:type ireq: :class:`~pip._internal.req.req_install.InstallRequirement`
|
||||
:param sources: Pipfile-formatted sources, defaults to None
|
||||
:type sources: list[dict], optional
|
||||
:param parent: The parent of this list of dependencies, defaults to None
|
||||
:type parent: :class:`~pip._internal.req.req_install.InstallRequirement`
|
||||
:return: A set of dependency lines for generating new InstallRequirements.
|
||||
:rtype: set(str)
|
||||
"""
|
||||
if not isinstance(ireq, InstallRequirement):
|
||||
name = getattr(
|
||||
ireq, "project_name",
|
||||
getattr(ireq, "project", ireq.name),
|
||||
)
|
||||
version = getattr(ireq, "version")
|
||||
ireq = InstallRequirement.from_line("{0}=={1}".format(name, version))
|
||||
pip_options = get_pip_options(sources=sources)
|
||||
getters = [
|
||||
get_dependencies_from_cache,
|
||||
get_dependencies_from_wheel_cache,
|
||||
get_dependencies_from_json,
|
||||
functools.partial(get_dependencies_from_index, pip_options=pip_options)
|
||||
]
|
||||
for getter in getters:
|
||||
deps = getter(ireq)
|
||||
if deps is not None:
|
||||
return deps
|
||||
raise RuntimeError('failed to get dependencies for {}'.format(ireq))
|
||||
|
||||
|
||||
def get_dependencies_from_wheel_cache(ireq):
|
||||
"""Retrieves dependencies for the given install requirement from the wheel cache.
|
||||
|
||||
:param ireq: A single InstallRequirement
|
||||
:type ireq: :class:`~pip._internal.req.req_install.InstallRequirement`
|
||||
:return: A set of dependency lines for generating new InstallRequirements.
|
||||
:rtype: set(str) or None
|
||||
"""
|
||||
|
||||
if ireq.editable or not is_pinned_requirement(ireq):
|
||||
return
|
||||
matches = WHEEL_CACHE.get(ireq.link, name_from_req(ireq.req))
|
||||
if matches:
|
||||
matches = set(matches)
|
||||
if not DEPENDENCY_CACHE.get(ireq):
|
||||
DEPENDENCY_CACHE[ireq] = [format_requirement(m) for m in matches]
|
||||
return matches
|
||||
return
|
||||
|
||||
|
||||
def _marker_contains_extra(ireq):
|
||||
# TODO: Implement better parsing logic avoid false-positives.
|
||||
return "extra" in repr(ireq.markers)
|
||||
|
||||
|
||||
def get_dependencies_from_json(ireq):
|
||||
"""Retrieves dependencies for the given install requirement from the json api.
|
||||
|
||||
:param ireq: A single InstallRequirement
|
||||
:type ireq: :class:`~pip._internal.req.req_install.InstallRequirement`
|
||||
:return: A set of dependency lines for generating new InstallRequirements.
|
||||
:rtype: set(str) or None
|
||||
"""
|
||||
|
||||
if ireq.editable or not is_pinned_requirement(ireq):
|
||||
return
|
||||
|
||||
# It is technically possible to parse extras out of the JSON API's
|
||||
# requirement format, but it is such a chore let's just use the simple API.
|
||||
if ireq.extras:
|
||||
return
|
||||
|
||||
session = requests.session()
|
||||
version = str(ireq.req.specifier).lstrip("=")
|
||||
|
||||
def gen(ireq):
|
||||
info = None
|
||||
info = session.get(
|
||||
"https://pypi.org/pypi/{0}/{1}/json".format(ireq.req.name, version)
|
||||
).json()["info"]
|
||||
requires_dist = info.get("requires_dist", info.get("requires"))
|
||||
if not requires_dist: # The API can return None for this.
|
||||
return
|
||||
for requires in requires_dist:
|
||||
i = InstallRequirement.from_line(requires)
|
||||
# See above, we don't handle requirements with extras.
|
||||
if not _marker_contains_extra(i):
|
||||
yield format_requirement(i)
|
||||
|
||||
if ireq not in DEPENDENCY_CACHE:
|
||||
try:
|
||||
reqs = DEPENDENCY_CACHE[ireq] = list(gen(ireq))
|
||||
except JSONDecodeError:
|
||||
return
|
||||
req_iter = iter(reqs)
|
||||
else:
|
||||
req_iter = gen(ireq)
|
||||
return set(req_iter)
|
||||
|
||||
|
||||
def get_dependencies_from_cache(ireq):
|
||||
"""Retrieves dependencies for the given install requirement from the dependency cache.
|
||||
|
||||
:param ireq: A single InstallRequirement
|
||||
:type ireq: :class:`~pip._internal.req.req_install.InstallRequirement`
|
||||
:return: A set of dependency lines for generating new InstallRequirements.
|
||||
:rtype: set(str) or None
|
||||
"""
|
||||
if ireq.editable or not is_pinned_requirement(ireq):
|
||||
return
|
||||
if ireq not in DEPENDENCY_CACHE:
|
||||
return
|
||||
cached = set(DEPENDENCY_CACHE[ireq])
|
||||
|
||||
# Preserving sanity: Run through the cache and make sure every entry if
|
||||
# valid. If this fails, something is wrong with the cache. Drop it.
|
||||
try:
|
||||
broken = False
|
||||
for line in cached:
|
||||
dep_ireq = InstallRequirement.from_line(line)
|
||||
name = canonicalize_name(dep_ireq.name)
|
||||
if _marker_contains_extra(dep_ireq):
|
||||
broken = True # The "extra =" marker breaks everything.
|
||||
elif name == canonicalize_name(ireq.name):
|
||||
broken = True # A package cannot depend on itself.
|
||||
if broken:
|
||||
break
|
||||
except Exception:
|
||||
broken = True
|
||||
|
||||
if broken:
|
||||
del DEPENDENCY_CACHE[ireq]
|
||||
return
|
||||
|
||||
return cached
|
||||
|
||||
|
||||
def is_python(section):
|
||||
return section.startswith('[') and ':' in section
|
||||
|
||||
|
||||
def get_dependencies_from_index(dep, sources=None, pip_options=None, wheel_cache=None):
|
||||
"""Retrieves dependencies for the given install requirement from the pip resolver.
|
||||
|
||||
:param dep: A single InstallRequirement
|
||||
:type dep: :class:`~pip._internal.req.req_install.InstallRequirement`
|
||||
:param sources: Pipfile-formatted sources, defaults to None
|
||||
:type sources: list[dict], optional
|
||||
:return: A set of dependency lines for generating new InstallRequirements.
|
||||
:rtype: set(str) or None
|
||||
"""
|
||||
|
||||
finder = get_finder(sources=sources, pip_options=pip_options)
|
||||
if not wheel_cache:
|
||||
wheel_cache = WHEEL_CACHE
|
||||
dep.is_direct = True
|
||||
reqset = RequirementSet()
|
||||
reqset.add_requirement(dep)
|
||||
requirements = None
|
||||
setup_requires = {}
|
||||
with temp_environ(), start_resolver(finder=finder, wheel_cache=wheel_cache) as resolver:
|
||||
os.environ['PIP_EXISTS_ACTION'] = 'i'
|
||||
dist = None
|
||||
if dep.editable and not dep.prepared and not dep.req:
|
||||
with cd(dep.setup_py_dir):
|
||||
from setuptools.dist import distutils
|
||||
try:
|
||||
dist = distutils.core.run_setup(dep.setup_py)
|
||||
except (ImportError, TypeError, AttributeError):
|
||||
dist = None
|
||||
else:
|
||||
setup_requires[dist.get_name()] = dist.setup_requires
|
||||
if not dist:
|
||||
try:
|
||||
dist = dep.get_dist()
|
||||
except (TypeError, ValueError, AttributeError):
|
||||
pass
|
||||
else:
|
||||
setup_requires[dist.get_name()] = dist.setup_requires
|
||||
resolver.require_hashes = False
|
||||
try:
|
||||
results = resolver._resolve_one(reqset, dep)
|
||||
except Exception:
|
||||
# FIXME: Needs to bubble the exception somehow to the user.
|
||||
results = []
|
||||
finally:
|
||||
try:
|
||||
wheel_cache.cleanup()
|
||||
except AttributeError:
|
||||
pass
|
||||
resolver_requires_python = getattr(resolver, "requires_python", None)
|
||||
requires_python = getattr(reqset, "requires_python", resolver_requires_python)
|
||||
if requires_python:
|
||||
add_marker = fix_requires_python_marker(requires_python)
|
||||
reqset.remove(dep)
|
||||
if dep.req.marker:
|
||||
dep.req.marker._markers.extend(['and',].extend(add_marker._markers))
|
||||
else:
|
||||
dep.req.marker = add_marker
|
||||
reqset.add(dep)
|
||||
requirements = set()
|
||||
for r in results:
|
||||
if requires_python:
|
||||
if r.req.marker:
|
||||
r.req.marker._markers.extend(['and',].extend(add_marker._markers))
|
||||
else:
|
||||
r.req.marker = add_marker
|
||||
requirements.add(format_requirement(r))
|
||||
for section in setup_requires:
|
||||
python_version = section
|
||||
not_python = not is_python(section)
|
||||
|
||||
# This is for cleaning up :extras: formatted markers
|
||||
# by adding them to the results of the resolver
|
||||
# since any such extra would have been returned as a result anyway
|
||||
for value in setup_requires[section]:
|
||||
|
||||
# This is a marker.
|
||||
if is_python(section):
|
||||
python_version = value[1:-1]
|
||||
else:
|
||||
not_python = True
|
||||
|
||||
if ':' not in value and not_python:
|
||||
try:
|
||||
requirement_str = "{0}{1}".format(value, python_version).replace(":", ";")
|
||||
requirements.add(format_requirement(make_install_requirement(requirement_str).ireq))
|
||||
# Anything could go wrong here -- can't be too careful.
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not dep.editable and is_pinned_requirement(dep) and requirements is not None:
|
||||
DEPENDENCY_CACHE[dep] = list(requirements)
|
||||
return requirements
|
||||
|
||||
|
||||
def get_pip_options(args=[], sources=None, pip_command=None):
|
||||
"""Build a pip command from a list of sources
|
||||
|
||||
:param args: positional arguments passed through to the pip parser
|
||||
:param sources: A list of pipfile-formatted sources, defaults to None
|
||||
:param sources: list[dict], optional
|
||||
:param pip_command: A pre-built pip command instance
|
||||
:type pip_command: :class:`~pip._internal.cli.base_command.Command`
|
||||
:return: An instance of pip_options using the supplied arguments plus sane defaults
|
||||
:rtype: :class:`~pip._internal.cli.cmdoptions`
|
||||
"""
|
||||
|
||||
if not pip_command:
|
||||
pip_command = get_pip_command()
|
||||
if not sources:
|
||||
sources = [
|
||||
{"url": "https://pypi.org/simple", "name": "pypi", "verify_ssl": True}
|
||||
]
|
||||
_ensure_dir(CACHE_DIR)
|
||||
pip_args = args
|
||||
pip_args = prepare_pip_source_args(sources, pip_args)
|
||||
pip_options, _ = pip_command.parser.parse_args(pip_args)
|
||||
pip_options.cache_dir = CACHE_DIR
|
||||
return pip_options
|
||||
|
||||
|
||||
def get_finder(sources=None, pip_command=None, pip_options=None):
|
||||
"""Get a package finder for looking up candidates to install
|
||||
|
||||
:param sources: A list of pipfile-formatted sources, defaults to None
|
||||
:param sources: list[dict], optional
|
||||
:param pip_command: A pip command instance, defaults to None
|
||||
:type pip_command: :class:`~pip._internal.cli.base_command.Command`
|
||||
:param pip_options: A pip options, defaults to None
|
||||
:type pip_options: :class:`~pip._internal.cli.cmdoptions`
|
||||
:return: A package finder
|
||||
:rtype: :class:`~pip._internal.index.PackageFinder`
|
||||
"""
|
||||
|
||||
if not pip_command:
|
||||
pip_command = get_pip_command()
|
||||
if not sources:
|
||||
sources = [
|
||||
{"url": "https://pypi.org/simple", "name": "pypi", "verify_ssl": True}
|
||||
]
|
||||
if not pip_options:
|
||||
pip_options = get_pip_options(sources=sources, pip_command=pip_command)
|
||||
session = pip_command._build_session(pip_options)
|
||||
finder = PackageFinder(
|
||||
find_links=[],
|
||||
index_urls=[s.get("url") for s in sources],
|
||||
trusted_hosts=[],
|
||||
allow_all_prereleases=pip_options.pre,
|
||||
session=session,
|
||||
)
|
||||
return finder
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def start_resolver(finder=None, wheel_cache=None):
|
||||
"""Context manager to produce a resolver.
|
||||
|
||||
:param finder: A package finder to use for searching the index
|
||||
:type finder: :class:`~pip._internal.index.PackageFinder`
|
||||
:return: A 3-tuple of finder, preparer, resolver
|
||||
:rtype: (:class:`~pip._internal.operations.prepare.RequirementPreparer`, :class:`~pip._internal.resolve.Resolver`)
|
||||
"""
|
||||
|
||||
pip_command = get_pip_command()
|
||||
pip_options = get_pip_options(pip_command=pip_command)
|
||||
|
||||
if not finder:
|
||||
finder = get_finder(pip_command=pip_command, pip_options=pip_options)
|
||||
|
||||
if not wheel_cache:
|
||||
wheel_cache = WHEEL_CACHE
|
||||
_ensure_dir(fs_str(os.path.join(wheel_cache.cache_dir, "wheels")))
|
||||
|
||||
download_dir = PKGS_DOWNLOAD_DIR
|
||||
_ensure_dir(download_dir)
|
||||
|
||||
_build_dir = TemporaryDirectory(fs_str("build"))
|
||||
_source_dir = TemporaryDirectory(fs_str("source"))
|
||||
preparer = partialclass(
|
||||
RequirementPreparer,
|
||||
build_dir=_build_dir.name,
|
||||
src_dir=_source_dir.name,
|
||||
download_dir=download_dir,
|
||||
wheel_download_dir=WHEEL_DOWNLOAD_DIR,
|
||||
progress_bar="off",
|
||||
build_isolation=False,
|
||||
)
|
||||
resolver = partialclass(
|
||||
Resolver,
|
||||
finder=finder,
|
||||
session=finder.session,
|
||||
upgrade_strategy="to-satisfy-only",
|
||||
force_reinstall=True,
|
||||
ignore_dependencies=False,
|
||||
ignore_requires_python=True,
|
||||
ignore_installed=True,
|
||||
isolated=False,
|
||||
wheel_cache=wheel_cache,
|
||||
use_user_site=False,
|
||||
)
|
||||
if packaging.version.parse(pip_version) >= packaging.version.parse('18'):
|
||||
with RequirementTracker() as req_tracker:
|
||||
preparer = preparer(req_tracker=req_tracker)
|
||||
yield resolver(preparer=preparer)
|
||||
else:
|
||||
preparer = preparer()
|
||||
yield resolver(preparer=preparer)
|
||||
|
||||
|
||||
def get_grouped_dependencies(constraints):
|
||||
# We need to track what contributed a specifierset
|
||||
# as well as which specifiers were required by the root node
|
||||
# in order to resolve any conflicts when we are deciding which thing to backtrack on
|
||||
# then we take the loose match (which _is_ flexible) and start moving backwards in
|
||||
# versions by popping them off of a stack and checking for the conflicting package
|
||||
for _, ireqs in full_groupby(constraints, key=key_from_ireq):
|
||||
ireqs = list(ireqs)
|
||||
editable_ireq = first(ireqs, key=lambda ireq: ireq.editable)
|
||||
if editable_ireq:
|
||||
yield editable_ireq # ignore all the other specs: the editable one is the one that counts
|
||||
continue
|
||||
ireqs = iter(ireqs)
|
||||
# deepcopy the accumulator so as to not modify the self.our_constraints invariant
|
||||
combined_ireq = copy.deepcopy(next(ireqs))
|
||||
for ireq in ireqs:
|
||||
# NOTE we may be losing some info on dropped reqs here
|
||||
try:
|
||||
combined_ireq.req.specifier &= ireq.req.specifier
|
||||
except TypeError:
|
||||
if ireq.req.specifier._specs and not combined_ireq.req.specifier._specs:
|
||||
combined_ireq.req.specifier._specs = ireq.req.specifier._specs
|
||||
combined_ireq.constraint &= ireq.constraint
|
||||
if not combined_ireq.markers:
|
||||
combined_ireq.markers = ireq.markers
|
||||
else:
|
||||
_markers = combined_ireq.markers._markers
|
||||
if not isinstance(_markers[0], (tuple, list)):
|
||||
combined_ireq.markers._markers = [
|
||||
_markers,
|
||||
"and",
|
||||
ireq.markers._markers,
|
||||
]
|
||||
# Return a sorted, de-duped tuple of extras
|
||||
combined_ireq.extras = tuple(
|
||||
sorted(set(tuple(combined_ireq.extras) + tuple(ireq.extras)))
|
||||
)
|
||||
yield combined_ireq
|
||||
+74
-25
@@ -1,24 +1,54 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
import attr
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
import plette.lockfiles
|
||||
import six
|
||||
|
||||
from vistir.compat import Path
|
||||
from vistir.contextmanagers import atomic_open_for_write
|
||||
|
||||
from .requirements import Requirement
|
||||
from .utils import optional_instance_of
|
||||
from .._compat import Path, FileNotFoundError
|
||||
|
||||
|
||||
@attr.s
|
||||
class Lockfile(object):
|
||||
dev_requirements = attr.ib(default=attr.Factory(list))
|
||||
requirements = attr.ib(default=attr.Factory(list))
|
||||
path = attr.ib(default=None, validator=optional_instance_of(Path))
|
||||
pipfile_hash = attr.ib(default=None)
|
||||
DEFAULT_NEWLINES = u"\n"
|
||||
|
||||
|
||||
def preferred_newlines(f):
|
||||
if isinstance(f.newlines, six.text_type):
|
||||
return f.newlines
|
||||
return DEFAULT_NEWLINES
|
||||
|
||||
|
||||
class Lockfile(plette.lockfiles.Lockfile):
|
||||
def __init__(self, *args, **kwargs):
|
||||
path = kwargs.pop("path", None)
|
||||
self._requirements = kwargs.pop("requirements", [])
|
||||
self._dev_requirements = kwargs.pop("dev_requirements", [])
|
||||
self.path = Path(path) if path else None
|
||||
self.newlines = u"\n"
|
||||
super(Lockfile, self).__init__(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def load(cls, path):
|
||||
if not path:
|
||||
path = os.curdir
|
||||
path = Path(path).absolute()
|
||||
if path.is_dir():
|
||||
path = path / "Pipfile.lock"
|
||||
elif path.name == "Pipfile":
|
||||
path = path.parent / "Pipfile.lock"
|
||||
if not path.exists():
|
||||
raise OSError("Path does not exist: %s" % path)
|
||||
return cls.create(path.parent, lockfile_name=path.name)
|
||||
|
||||
@classmethod
|
||||
def create(cls, project_path, lockfile_name="Pipfile.lock"):
|
||||
"""Create a new lockfile instance
|
||||
|
||||
:param project_path: Path to the project root
|
||||
:param project_path: Path to project root
|
||||
:type project_path: str or :class:`~pathlib.Path`
|
||||
:returns: List[:class:`~requirementslib.Requirement`] objects
|
||||
"""
|
||||
@@ -26,22 +56,41 @@ class Lockfile(object):
|
||||
if not isinstance(project_path, Path):
|
||||
project_path = Path(project_path)
|
||||
lockfile_path = project_path / lockfile_name
|
||||
requirements = []
|
||||
dev_requirements = []
|
||||
if not lockfile_path.exists():
|
||||
raise FileNotFoundError("No such lockfile: %s" % lockfile_path)
|
||||
|
||||
with lockfile_path.open(encoding="utf-8") as f:
|
||||
lockfile = json.loads(f.read())
|
||||
for k in lockfile["develop"].keys():
|
||||
dev_requirements.append(Requirement.from_pipfile(k, lockfile["develop"][k]))
|
||||
for k in lockfile["default"].keys():
|
||||
requirements.append(Requirement.from_pipfile(k, lockfile["default"][k]))
|
||||
return cls(
|
||||
path=lockfile_path,
|
||||
requirements=requirements,
|
||||
dev_requirements=dev_requirements,
|
||||
)
|
||||
lockfile = super(Lockfile, cls).load(f)
|
||||
lockfile.newlines = preferred_newlines(f)
|
||||
lockfile.path = lockfile_path
|
||||
return lockfile
|
||||
|
||||
def get_requirements(self, dev=False):
|
||||
section = self.develop if dev else self.default
|
||||
for k in section.keys():
|
||||
yield Requirement.from_pipfile(k, section[k]._data)
|
||||
|
||||
@property
|
||||
def dev_requirements(self):
|
||||
if not self._dev_requirements:
|
||||
self._dev_requirements = list(self.get_requirements(dev=True))
|
||||
return self._dev_requirements
|
||||
|
||||
@property
|
||||
def requirements(self):
|
||||
if not self._requirements:
|
||||
self._requirements = list(self.get_requirements(dev=False))
|
||||
return self._requirements
|
||||
|
||||
@property
|
||||
def dev_requirements_list(self):
|
||||
return [{name: entry._data} for name, entry in self.develop.items()]
|
||||
|
||||
@property
|
||||
def requirements_list(self):
|
||||
return [{name: entry._data} for name, entry in self.develop.items()]
|
||||
|
||||
def write(self):
|
||||
open_kwargs = {"newline": self.newlines}
|
||||
with atomic_open_for_write(self.path.as_posix(), **open_kwargs) as f:
|
||||
super(Lockfile, self).dump(f, encoding="utf-8")
|
||||
|
||||
def as_requirements(self, include_hashes=False, dev=False):
|
||||
"""Returns a list of requirements in pip-style format"""
|
||||
|
||||
+8
-7
@@ -1,10 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import attr
|
||||
import six
|
||||
from packaging.markers import Marker, InvalidMarker
|
||||
from .baserequirement import BaseRequirement
|
||||
from .utils import validate_markers, filter_none
|
||||
|
||||
from packaging.markers import InvalidMarker, Marker
|
||||
|
||||
from ..exceptions import RequirementError
|
||||
from .baserequirement import BaseRequirement
|
||||
from .utils import filter_none, validate_markers
|
||||
|
||||
|
||||
@attr.s
|
||||
@@ -84,10 +85,10 @@ class PipenvMarkers(BaseRequirement):
|
||||
markers = []
|
||||
for marker in marker_strings:
|
||||
markers.append(marker)
|
||||
marker = ''
|
||||
combined_marker = None
|
||||
try:
|
||||
marker = cls.make_marker(" and ".join(markers))
|
||||
combined_marker = cls.make_marker(" and ".join(markers))
|
||||
except RequirementError:
|
||||
pass
|
||||
else:
|
||||
return marker
|
||||
return combined_marker
|
||||
|
||||
+41
-164
@@ -1,100 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import attr
|
||||
import contoml
|
||||
import os
|
||||
import toml
|
||||
from .._vendor import pipfile
|
||||
|
||||
from vistir.compat import Path
|
||||
|
||||
from .requirements import Requirement
|
||||
from .utils import optional_instance_of, filter_none
|
||||
from .._compat import Path, FileNotFoundError
|
||||
from ..exceptions import RequirementError
|
||||
import plette.pipfiles
|
||||
|
||||
|
||||
@attr.s
|
||||
class Source(object):
|
||||
#: URL to PyPI instance
|
||||
url = attr.ib(default="pypi")
|
||||
#: If False, skip SSL checks
|
||||
verify_ssl = attr.ib(default=True, validator=optional_instance_of(bool))
|
||||
#: human name to refer to this source (can be referenced in packages or dev-packages)
|
||||
name = attr.ib(default="")
|
||||
|
||||
def get_dict(self):
|
||||
return attr.asdict(self)
|
||||
|
||||
@property
|
||||
def expanded(self):
|
||||
source_dict = attr.asdict(self).copy()
|
||||
source_dict["url"] = os.path.expandvars(source_dict.get("url"))
|
||||
return source_dict
|
||||
|
||||
|
||||
@attr.s
|
||||
class Section(object):
|
||||
ALLOWED_NAMES = ("packages", "dev-packages")
|
||||
#: Name of the pipfile section
|
||||
name = attr.ib(default="packages")
|
||||
#: A list of requirements that are contained by the section
|
||||
requirements = attr.ib(default=list)
|
||||
|
||||
def get_dict(self):
|
||||
_dict = {}
|
||||
for req in self.requirements:
|
||||
_dict.update(req.as_pipfile())
|
||||
return {self.name: _dict}
|
||||
|
||||
@property
|
||||
def vcs_requirements(self):
|
||||
return [req for req in self.requirements if req.is_vcs]
|
||||
|
||||
@property
|
||||
def editable_requirements(self):
|
||||
return [req for req in self.requirements if req.editable]
|
||||
|
||||
|
||||
@attr.s
|
||||
class RequiresSection(object):
|
||||
python_version = attr.ib(default=None)
|
||||
python_full_version = attr.ib(default=None)
|
||||
|
||||
def get_dict(self):
|
||||
requires = attr.asdict(self, filter=filter_none)
|
||||
if not requires:
|
||||
return {}
|
||||
return {"requires": requires}
|
||||
|
||||
|
||||
@attr.s
|
||||
class PipenvSection(object):
|
||||
allow_prereleases = attr.ib(default=False)
|
||||
|
||||
def get_dict(self):
|
||||
if self.allow_prereleases:
|
||||
return {"pipenv": attr.asdict(self)}
|
||||
return {}
|
||||
|
||||
|
||||
@attr.s
|
||||
class Pipfile(object):
|
||||
#: Path to the pipfile
|
||||
path = attr.ib(default=None, converter=Path, validator=optional_instance_of(Path))
|
||||
#: Sources listed in the pipfile
|
||||
sources = attr.ib(default=attr.Factory(list))
|
||||
#: Sections contained by the pipfile
|
||||
sections = attr.ib(default=attr.Factory(list))
|
||||
#: Scripts found in the pipfile
|
||||
scripts = attr.ib(default=attr.Factory(dict))
|
||||
#: This section stores information about what python version is required
|
||||
requires = attr.ib(default=attr.Factory(RequiresSection))
|
||||
#: This section stores information about pipenv such as prerelease requirements
|
||||
pipenv = attr.ib(default=attr.Factory(PipenvSection))
|
||||
#: This is the sha256 hash of the pipfile (without environment interpolation)
|
||||
pipfile_hash = attr.ib()
|
||||
|
||||
@pipfile_hash.default
|
||||
def get_hash(self):
|
||||
p = pipfile.load(self.path.as_posix(), inject_env=False)
|
||||
return p.hash
|
||||
class Pipfile(plette.pipfiles.Pipfile):
|
||||
|
||||
@property
|
||||
def requires_python(self):
|
||||
@@ -102,50 +16,7 @@ class Pipfile(object):
|
||||
|
||||
@property
|
||||
def allow_prereleases(self):
|
||||
return self.pipenv.allow_prereleases
|
||||
|
||||
def get_sources(self):
|
||||
"""Return a dictionary with a list of dictionaries of pipfile sources"""
|
||||
_dict = {}
|
||||
for src in self.sources:
|
||||
_dict.update(src.get_dict())
|
||||
return {"source": _dict} if _dict else {}
|
||||
|
||||
def get_sections(self):
|
||||
"""Return a dictionary with both pipfile sections and requirements"""
|
||||
_dict = {}
|
||||
for section in self.sections:
|
||||
_dict.update(section.get_dict())
|
||||
return _dict
|
||||
|
||||
def get_pipenv(self):
|
||||
pipenv_dict = self.pipenv.get_dict()
|
||||
if pipenv_dict:
|
||||
return pipenv_dict
|
||||
|
||||
def get_requires(self):
|
||||
req_dict = self.requires.get_dict()
|
||||
return req_dict if req_dict else {}
|
||||
|
||||
def get_dict(self):
|
||||
_dict = attr.asdict(self, recurse=False)
|
||||
for k in ["path", "pipfile_hash", "sources", "sections", "requires", "pipenv"]:
|
||||
if k in _dict:
|
||||
_dict.pop(k)
|
||||
return _dict
|
||||
|
||||
def dump(self, to_dict=False):
|
||||
"""Dumps the pipfile to a toml string
|
||||
"""
|
||||
|
||||
_dict = self.get_sources()
|
||||
_dict.update(self.get_sections())
|
||||
_dict.update(self.get_dict())
|
||||
_dict.update(self.get_pipenv())
|
||||
_dict.update(self.get_requires())
|
||||
if to_dict:
|
||||
return _dict
|
||||
return contoml.dumps(_dict)
|
||||
return self.get("pipenv", {}).get("allow_prereleases", False)
|
||||
|
||||
@classmethod
|
||||
def load(cls, path):
|
||||
@@ -156,34 +27,40 @@ class Pipfile(object):
|
||||
raise FileNotFoundError("%s is not a valid project path!" % path)
|
||||
elif not pipfile_path.exists() or not pipfile_path.is_file():
|
||||
raise RequirementError("%s is not a valid Pipfile" % pipfile_path)
|
||||
pipfile_dict = toml.load(pipfile_path.as_posix())
|
||||
sections = [cls.get_section(pipfile_dict, s) for s in Section.ALLOWED_NAMES]
|
||||
pipenv = pipfile_dict.get("pipenv", {})
|
||||
requires = pipfile_dict.get("requires", {})
|
||||
creation_dict = {
|
||||
"path": pipfile_path,
|
||||
"sources": [Source(**src) for src in pipfile_dict.get("source", [])],
|
||||
"sections": sections,
|
||||
"scripts": pipfile_dict.get("scripts"),
|
||||
}
|
||||
if requires:
|
||||
creation_dict["requires"] = RequiresSection(**requires)
|
||||
if pipenv:
|
||||
creation_dict["pipenv"] = PipenvSection(**pipenv)
|
||||
return cls(**creation_dict)
|
||||
with pipfile_path.open(encoding="utf-8") as fp:
|
||||
pipfile = super(Pipfile, cls).load(fp)
|
||||
pipfile.dev_requirements = [
|
||||
Requirement.from_pipfile(k, v) for k, v in pipfile.dev_packages.items()
|
||||
]
|
||||
pipfile.requirements = [
|
||||
Requirement.from_pipfile(k, v) for k, v in pipfile.packages.items()
|
||||
]
|
||||
pipfile.path = pipfile_path
|
||||
return pipfile
|
||||
|
||||
@staticmethod
|
||||
def get_section(pf_dict, section):
|
||||
"""Get section objects from a pipfile dictionary
|
||||
# def resolve(self):
|
||||
# It would be nice to still use this api someday
|
||||
# option_sources = [s.expanded for s in self.sources]
|
||||
# pip_args = []
|
||||
# if self.pipenv.allow_prereleases:
|
||||
# pip_args.append('--pre')
|
||||
# pip_options = get_pip_options(pip_args, sources=option_sources)
|
||||
# finder = get_finder(sources=option_sources, pip_options=pip_options)
|
||||
# resolver = DependencyResolver.create(finder=finder, allow_prereleases=self.pipenv.allow_prereleases)
|
||||
# pkg_dict = {}
|
||||
# for pkg in self.dev_packages.requirements + self.packages.requirements:
|
||||
# pkg_dict[pkg.name] = pkg
|
||||
# resolver.resolve(list(pkg_dict.values()))
|
||||
# return resolver
|
||||
|
||||
:param pf_dict: A toml loaded pipfile dictionary
|
||||
:type pf_dict: dict
|
||||
:returns: Section objects
|
||||
"""
|
||||
sect = pf_dict.get(section)
|
||||
requirements = []
|
||||
if section not in Section.ALLOWED_NAMES:
|
||||
raise ValueError("Not a valid pipfile section name: %s" % section)
|
||||
for name, pf_entry in sect.items():
|
||||
requirements.append(Requirement.from_pipfile(name, pf_entry))
|
||||
return Section(name=section, requirements=requirements)
|
||||
@property
|
||||
def dev_packages(self, as_requirements=True):
|
||||
if as_requirements:
|
||||
return self.dev_requirements
|
||||
return self.dev_packages
|
||||
|
||||
@property
|
||||
def packages(self, as_requirements=True):
|
||||
if as_requirements:
|
||||
return self.requirements
|
||||
return self.packages
|
||||
|
||||
+317
-116
@@ -1,55 +1,46 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
|
||||
import attr
|
||||
import collections
|
||||
import hashlib
|
||||
import os
|
||||
import requirements
|
||||
|
||||
import attr
|
||||
import atexit
|
||||
|
||||
from first import first
|
||||
from packaging.markers import Marker
|
||||
from packaging.specifiers import Specifier, SpecifierSet
|
||||
from packaging.utils import canonicalize_name
|
||||
from six.moves.urllib import parse as urllib_parse
|
||||
from six.moves.urllib.parse import unquote
|
||||
|
||||
from pip_shims.shims import (
|
||||
InstallRequirement, Link, Wheel, _strip_extras, parse_version, path_to_url,
|
||||
url_to_path
|
||||
)
|
||||
from vistir.compat import FileNotFoundError, Path, TemporaryDirectory
|
||||
from vistir.misc import dedup
|
||||
from vistir.path import get_converted_relative_path, is_valid_url, is_file_url, mkdir_p
|
||||
|
||||
from ..exceptions import RequirementError
|
||||
from ..utils import VCS_LIST, is_vcs, is_installable_file
|
||||
from .baserequirement import BaseRequirement
|
||||
from .dependencies import (
|
||||
AbstractDependency, find_all_matches, get_abstract_dependencies,
|
||||
get_dependencies, get_finder
|
||||
)
|
||||
from .markers import PipenvMarkers
|
||||
from .utils import (
|
||||
HASH_STRING,
|
||||
extras_to_string,
|
||||
get_version,
|
||||
specs_to_string,
|
||||
validate_specifiers,
|
||||
validate_path,
|
||||
validate_vcs,
|
||||
build_vcs_link,
|
||||
add_ssh_scheme_to_git_uri,
|
||||
strip_ssh_from_git_uri,
|
||||
split_vcs_method_from_uri,
|
||||
filter_none,
|
||||
optional_instance_of,
|
||||
split_markers_from_line,
|
||||
parse_extras,
|
||||
)
|
||||
from .._compat import (
|
||||
Link,
|
||||
path_to_url,
|
||||
url_to_path,
|
||||
_strip_extras,
|
||||
InstallRequirement,
|
||||
Path,
|
||||
urlparse,
|
||||
unquote,
|
||||
Wheel,
|
||||
FileNotFoundError,
|
||||
)
|
||||
from ..exceptions import RequirementError
|
||||
from ..utils import (
|
||||
VCS_LIST,
|
||||
is_installable_file,
|
||||
is_vcs,
|
||||
is_valid_url,
|
||||
pep423_name,
|
||||
get_converted_relative_path,
|
||||
HASH_STRING, add_ssh_scheme_to_git_uri, build_vcs_link, filter_none,
|
||||
format_requirement, get_version, init_requirement,
|
||||
is_pinned_requirement, make_install_requirement, optional_instance_of,
|
||||
parse_extras, specs_to_string, split_markers_from_line,
|
||||
split_vcs_method_from_uri, strip_ssh_from_git_uri, validate_path,
|
||||
validate_specifiers, validate_vcs, extras_to_string
|
||||
)
|
||||
from .vcs import VCSRepository
|
||||
from packaging.requirements import Requirement as PackagingRequirement
|
||||
|
||||
|
||||
@attr.s
|
||||
@@ -58,25 +49,31 @@ class NamedRequirement(BaseRequirement):
|
||||
version = attr.ib(validator=attr.validators.optional(validate_specifiers))
|
||||
req = attr.ib()
|
||||
extras = attr.ib(default=attr.Factory(list))
|
||||
editable = attr.ib(default=False)
|
||||
|
||||
@req.default
|
||||
def get_requirement(self):
|
||||
from pkg_resources import RequirementParseError
|
||||
try:
|
||||
req = first(requirements.parse("{0}{1}".format(self.name, self.version)))
|
||||
except RequirementParseError:
|
||||
raise RequirementError(
|
||||
"Error parsing requirement: %s%s" % (self.name, self.version)
|
||||
)
|
||||
req = init_requirement("{0}{1}".format(canonicalize_name(self.name), self.version))
|
||||
return req
|
||||
|
||||
@classmethod
|
||||
def from_line(cls, line):
|
||||
req = first(requirements.parse(line))
|
||||
req = init_requirement(line)
|
||||
specifiers = None
|
||||
if req.specifier:
|
||||
specifiers = specs_to_string(req.specs)
|
||||
return cls(name=req.name, version=specifiers, req=req)
|
||||
specifiers = specs_to_string(req.specifier)
|
||||
req.line = line
|
||||
name = getattr(req, "name", None)
|
||||
if not name:
|
||||
name = getattr(req, "project_name", None)
|
||||
req.name = name
|
||||
if not name:
|
||||
name = getattr(req, "key", line)
|
||||
req.name = name
|
||||
extras = None
|
||||
if req.extras:
|
||||
extras = list(req.extras)
|
||||
return cls(name=name, version=specifiers, req=req, extras=extras)
|
||||
|
||||
@classmethod
|
||||
def from_pipfile(cls, name, pipfile):
|
||||
@@ -85,13 +82,17 @@ class NamedRequirement(BaseRequirement):
|
||||
creation_args = {k: v for k, v in pipfile.items() if k in cls.attr_fields()}
|
||||
creation_args["name"] = name
|
||||
version = get_version(pipfile)
|
||||
extras = creation_args.get("extras", None)
|
||||
creation_args["version"] = version
|
||||
creation_args["req"] = first(requirements.parse("{0}{1}".format(name, version)))
|
||||
req = init_requirement("{0}{1}".format(name, version))
|
||||
if extras:
|
||||
req.extras += tuple(extras,)
|
||||
creation_args["req"] = req
|
||||
return cls(**creation_args)
|
||||
|
||||
@property
|
||||
def line_part(self):
|
||||
return "{self.name}".format(self=self)
|
||||
return "{0}".format(canonicalize_name(self.name))
|
||||
|
||||
@property
|
||||
def pipfile_part(self):
|
||||
@@ -243,17 +244,17 @@ class FileRequirement(BaseRequirement):
|
||||
and self.setup_path
|
||||
and self.setup_path.exists()
|
||||
):
|
||||
from distutils.core import run_setup
|
||||
from setuptools.dist import distutils
|
||||
|
||||
old_curdir = os.path.abspath(os.getcwd())
|
||||
try:
|
||||
os.chdir(str(self.setup_path.parent))
|
||||
dist = run_setup(self.setup_path.as_posix(), stop_after="init")
|
||||
dist = distutils.core.run_setup(self.setup_path.as_posix())
|
||||
name = dist.get_name()
|
||||
except (FileNotFoundError, IOError) as e:
|
||||
dist = None
|
||||
except Exception as e:
|
||||
from .._compat import InstallRequirement, make_abstract_dist
|
||||
from pip_shims.shims import InstallRequirement, make_abstract_dist
|
||||
|
||||
try:
|
||||
if not isinstance(Path, self.path):
|
||||
@@ -289,14 +290,18 @@ class FileRequirement(BaseRequirement):
|
||||
|
||||
@req.default
|
||||
def get_requirement(self):
|
||||
prefix = "-e " if self.editable else ""
|
||||
line = "{0}{1}".format(prefix, self.link.url)
|
||||
req = first(requirements.parse(line))
|
||||
req = init_requirement(canonicalize_name(self.name))
|
||||
req.editable = False
|
||||
req.line = self.link.url_without_fragment
|
||||
if self.path and self.link and self.link.scheme.startswith("file"):
|
||||
req.local_file = True
|
||||
req.path = self.path
|
||||
req.uri = None
|
||||
req.url = None
|
||||
self._uri_scheme = "file"
|
||||
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
|
||||
@@ -330,7 +335,7 @@ class FileRequirement(BaseRequirement):
|
||||
editable = line.startswith("-e ")
|
||||
line = line.split(" ", 1)[1] if editable else line
|
||||
setup_path = None
|
||||
if not any([is_installable_file(line), is_valid_url(line)]):
|
||||
if not any([is_installable_file(line), is_valid_url(line), is_file_url(line)]):
|
||||
raise RequirementError(
|
||||
"Supplied requirement is not installable: {0!r}".format(line)
|
||||
)
|
||||
@@ -473,17 +478,6 @@ class VCSRequirement(FileRequirement):
|
||||
name = attr.ib()
|
||||
link = attr.ib()
|
||||
req = attr.ib()
|
||||
_INCLUDE_FIELDS = (
|
||||
"editable",
|
||||
"uri",
|
||||
"path",
|
||||
"vcs",
|
||||
"ref",
|
||||
"subdirectory",
|
||||
"name",
|
||||
"link",
|
||||
"req",
|
||||
)
|
||||
|
||||
def __attrs_post_init__(self):
|
||||
split = urllib_parse.urlsplit(self.uri)
|
||||
@@ -522,16 +516,66 @@ class VCSRequirement(FileRequirement):
|
||||
uri = "{0}+{1}".format(self.vcs, uri)
|
||||
return uri
|
||||
|
||||
def get_commit_hash(self, src_dir=None):
|
||||
src_dir = os.environ.get('SRC_DIR', None) if not src_dir else src_dir
|
||||
if not src_dir:
|
||||
_src_dir = TemporaryDirectory()
|
||||
atexit.register(_src_dir.cleanup)
|
||||
src_dir = _src_dir.name
|
||||
checkout_dir = Path(src_dir).joinpath(self.name).as_posix()
|
||||
vcsrepo = VCSRepository(
|
||||
url=self.link.url,
|
||||
name=self.name,
|
||||
ref=self.ref if self.ref else None,
|
||||
checkout_directory=checkout_dir,
|
||||
vcs_type=self.vcs
|
||||
)
|
||||
vcsrepo.obtain()
|
||||
return vcsrepo.get_commit_hash()
|
||||
|
||||
def update_repo(self, src_dir=None, ref=None):
|
||||
src_dir = os.environ.get('SRC_DIR', None) if not src_dir else src_dir
|
||||
if not src_dir:
|
||||
_src_dir = TemporaryDirectory()
|
||||
atexit.register(_src_dir.cleanup)
|
||||
src_dir = _src_dir.name
|
||||
checkout_dir = Path(src_dir).joinpath(self.name).as_posix()
|
||||
ref = self.ref if not ref else ref
|
||||
vcsrepo = VCSRepository(
|
||||
url=self.link.url,
|
||||
name=self.name,
|
||||
ref=ref if ref else None,
|
||||
checkout_directory=checkout_dir,
|
||||
vcs_type=self.vcs
|
||||
)
|
||||
if not os.path.exists(checkout_dir):
|
||||
vcsrepo.obtain()
|
||||
else:
|
||||
vcsrepo.update()
|
||||
return vcsrepo.get_commit_hash()
|
||||
|
||||
@req.default
|
||||
def get_requirement(self):
|
||||
prefix = "-e " if self.editable else ""
|
||||
line = "{0}{1}".format(prefix, self.link.url)
|
||||
req = first(requirements.parse(line))
|
||||
name = self.name or self.link.egg_fragment
|
||||
url = self.uri or self.link.url_without_fragment
|
||||
if not name:
|
||||
raise ValueError(
|
||||
"pipenv requires an #egg fragment for version controlled "
|
||||
"dependencies. Please install remote dependency "
|
||||
"in the form {0}#egg=<package-name>.".format(url)
|
||||
)
|
||||
req = init_requirement(canonicalize_name(self.name))
|
||||
req.editable = self.editable
|
||||
req.url = self.uri
|
||||
req.line = self.link.url
|
||||
if self.ref:
|
||||
req.revision = self.ref
|
||||
if self.extras:
|
||||
req.extras = self.extras
|
||||
req.vcs = self.vcs
|
||||
if self.path and self.link and self.link.scheme.startswith("file"):
|
||||
req.local_file = True
|
||||
req.path = self.path
|
||||
if self.editable:
|
||||
req.editable = True
|
||||
req.link = self.link
|
||||
if (
|
||||
self.uri != unquote(self.link.url_without_fragment)
|
||||
@@ -539,19 +583,7 @@ class VCSRequirement(FileRequirement):
|
||||
and "git+git@" in self.uri
|
||||
):
|
||||
req.line = self.uri
|
||||
req.uri = self.uri
|
||||
if not req.name:
|
||||
raise ValueError(
|
||||
"pipenv requires an #egg fragment for version controlled "
|
||||
"dependencies. Please install remote dependency "
|
||||
"in the form {0}#egg=<package-name>.".format(req.uri)
|
||||
)
|
||||
if self.vcs and not req.vcs:
|
||||
req.vcs = self.vcs
|
||||
if self.ref and not req.revision:
|
||||
req.revision = self.ref
|
||||
if self.extras and not req.extras:
|
||||
req.extras = self.extras
|
||||
req.url = self.uri
|
||||
return req
|
||||
|
||||
@classmethod
|
||||
@@ -564,12 +596,17 @@ class VCSRequirement(FileRequirement):
|
||||
if k in pipfile
|
||||
]
|
||||
for key in pipfile_keys:
|
||||
if key == "extras":
|
||||
extras = pipfile.get(key, None)
|
||||
if extras:
|
||||
pipfile[key] = sorted(dedup([extra.lower() for extra in extras]))
|
||||
if key in VCS_LIST:
|
||||
creation_args["vcs"] = key
|
||||
composed_uri = add_ssh_scheme_to_git_uri(
|
||||
"{0}+{1}".format(key, pipfile.get(key))
|
||||
).split("+", 1)[1]
|
||||
is_url = is_valid_url(pipfile.get(key)) or is_valid_url(composed_uri)
|
||||
url_keys = [pipfile.get(key), composed_uri]
|
||||
is_url = any(validity_fn(url_key) for url_key in url_keys for validity_fn in [is_valid_url, is_file_url])
|
||||
target_key = "uri" if is_url else "path"
|
||||
creation_args[target_key] = pipfile.get(key)
|
||||
else:
|
||||
@@ -612,8 +649,12 @@ class VCSRequirement(FileRequirement):
|
||||
def line_part(self):
|
||||
"""requirements.txt compatible line part sans-extras"""
|
||||
if self.req:
|
||||
return self.req.line
|
||||
base = "{0}".format(self.link)
|
||||
base = self.req.line
|
||||
if base and self.extras and not extras_to_string(self.extras) in base:
|
||||
if self.subdirectory:
|
||||
base = "{0}".format(self.get_link().url)
|
||||
else:
|
||||
base = "{0}{1}".format(base, extras_to_string(sorted(self.extras)))
|
||||
if self.editable:
|
||||
base = "-e {0}".format(base)
|
||||
return base
|
||||
@@ -650,8 +691,8 @@ class Requirement(object):
|
||||
editable = attr.ib(default=None)
|
||||
hashes = attr.ib(default=attr.Factory(list), converter=list)
|
||||
extras = attr.ib(default=attr.Factory(list))
|
||||
abstract_dep = attr.ib(default=None)
|
||||
_ireq = None
|
||||
_INCLUDE_FIELDS = ("name", "markers", "index", "editable", "hashes", "extras")
|
||||
|
||||
@name.default
|
||||
def get_name(self):
|
||||
@@ -678,14 +719,20 @@ class Requirement(object):
|
||||
@property
|
||||
def extras_as_pip(self):
|
||||
if self.extras:
|
||||
return "[{0}]".format(",".join(self.extras))
|
||||
return "[{0}]".format(",".join(sorted([extra.lower() for extra in self.extras])))
|
||||
|
||||
return ""
|
||||
|
||||
@property
|
||||
def commit_hash(self):
|
||||
if not self.is_vcs:
|
||||
return None
|
||||
return self.req.get_commit_hash()
|
||||
|
||||
@specifiers.default
|
||||
def get_specifiers(self):
|
||||
if self.req and self.req.req.specifier:
|
||||
return specs_to_string(self.req.req.specs)
|
||||
return specs_to_string(self.req.req.specifier)
|
||||
return
|
||||
|
||||
@property
|
||||
@@ -702,10 +749,15 @@ class Requirement(object):
|
||||
|
||||
@property
|
||||
def normalized_name(self):
|
||||
return pep423_name(self.name)
|
||||
return canonicalize_name(self.name)
|
||||
|
||||
def copy(self):
|
||||
return attr.evolve(self)
|
||||
|
||||
@classmethod
|
||||
def from_line(cls, line):
|
||||
if isinstance(line, InstallRequirement):
|
||||
line = format_requirement(line)
|
||||
hashes = None
|
||||
if "--hash=" in line:
|
||||
hashes = line.split(" --hash=")
|
||||
@@ -714,6 +766,7 @@ class Requirement(object):
|
||||
line = line.split(" ", 1)[1] if editable else line
|
||||
line, markers = split_markers_from_line(line)
|
||||
line, extras = _strip_extras(line)
|
||||
specifiers = ''
|
||||
if extras:
|
||||
extras = parse_extras(extras)
|
||||
line = line.strip('"').strip("'").strip()
|
||||
@@ -721,9 +774,10 @@ class Requirement(object):
|
||||
vcs = None
|
||||
# Installable local files and installable non-vcs urls are handled
|
||||
# as files, generally speaking
|
||||
if is_installable_file(line) or (is_valid_url(line) and not is_vcs(line)):
|
||||
line_is_vcs = is_vcs(line)
|
||||
if is_installable_file(line) or ((is_file_url(line) or is_valid_url(line)) and not line_is_vcs):
|
||||
r = FileRequirement.from_line(line_with_prefix)
|
||||
elif is_vcs(line):
|
||||
elif line_is_vcs:
|
||||
r = VCSRequirement.from_line(line_with_prefix, extras=extras)
|
||||
vcs = r.vcs
|
||||
elif line == "." and not is_installable_file(line):
|
||||
@@ -739,6 +793,7 @@ class Requirement(object):
|
||||
spec_idx = min((line.index(match) for match in spec_matches))
|
||||
name = line[:spec_idx]
|
||||
version = line[spec_idx:]
|
||||
specifiers = version
|
||||
if not extras:
|
||||
name, extras = _strip_extras(name)
|
||||
if extras:
|
||||
@@ -746,8 +801,19 @@ class Requirement(object):
|
||||
if version:
|
||||
name = "{0}{1}".format(name, version)
|
||||
r = NamedRequirement.from_line(line)
|
||||
req_markers = None
|
||||
if markers:
|
||||
r.req.markers = markers
|
||||
req_markers = PackagingRequirement("fakepkg; {0}".format(markers))
|
||||
r.req.marker = getattr(req_markers, "marker", None)
|
||||
r.req.local_file = getattr(r.req, "local_file", False)
|
||||
name = getattr(r.req, "name", None)
|
||||
if not name:
|
||||
name = getattr(r.req, "project_name", None)
|
||||
r.req.name = name
|
||||
if not name:
|
||||
name = getattr(r.req, "key", None)
|
||||
if name:
|
||||
r.req.name = name
|
||||
args = {
|
||||
"name": r.name,
|
||||
"vcs": vcs,
|
||||
@@ -756,15 +822,26 @@ class Requirement(object):
|
||||
"editable": editable,
|
||||
}
|
||||
if extras:
|
||||
extras = sorted(dedup([extra.lower() for extra in extras]))
|
||||
args["extras"] = extras
|
||||
r.req.extras = extras
|
||||
r.extras = extras
|
||||
elif r.extras:
|
||||
args["extras"] = r.extras
|
||||
args["extras"] = sorted(dedup([extra.lower() for extra in r.extras]))
|
||||
if hashes:
|
||||
args["hashes"] = hashes
|
||||
return cls(**args)
|
||||
|
||||
@classmethod
|
||||
def from_ireq(cls, ireq):
|
||||
return cls.from_line(format_requirement(ireq))
|
||||
|
||||
@classmethod
|
||||
def from_metadata(cls, name, version, extras, markers):
|
||||
return cls.from_ireq(make_install_requirement(
|
||||
name, version, extras=extras, markers=markers,
|
||||
))
|
||||
|
||||
@classmethod
|
||||
def from_pipfile(cls, name, pipfile):
|
||||
_pipfile = {}
|
||||
@@ -780,8 +857,14 @@ class Requirement(object):
|
||||
else:
|
||||
r = NamedRequirement.from_pipfile(name, pipfile)
|
||||
markers = PipenvMarkers.from_pipfile(name, _pipfile)
|
||||
req_markers = None
|
||||
if markers:
|
||||
markers = str(markers)
|
||||
req_markers = PackagingRequirement("fakepkg; {0}".format(markers))
|
||||
r.req.marker = getattr(req_markers, "marker", None)
|
||||
r.req.specifier = SpecifierSet(_pipfile["version"])
|
||||
extras = _pipfile.get("extras")
|
||||
r.req.extras = sorted(dedup([extra.lower() for extra in extras])) if extras else []
|
||||
args = {
|
||||
"name": r.name,
|
||||
"vcs": vcs,
|
||||
@@ -793,9 +876,12 @@ class Requirement(object):
|
||||
}
|
||||
if any(key in _pipfile for key in ["hash", "hashes"]):
|
||||
args["hashes"] = _pipfile.get("hashes", [pipfile.get("hash")])
|
||||
return cls(**args)
|
||||
cls_inst = cls(**args)
|
||||
if cls_inst.is_named:
|
||||
cls_inst.req.req.line = cls_inst.as_line()
|
||||
return cls_inst
|
||||
|
||||
def as_line(self, sources=None):
|
||||
def as_line(self, sources=None, include_hashes=True, include_extras=True):
|
||||
"""Format this requirement as a line in requirements.txt.
|
||||
|
||||
If `sources` provided, it should be an sequence of mappings, containing
|
||||
@@ -804,22 +890,55 @@ class Requirement(object):
|
||||
If `sources` is omitted or falsy, no index information will be included
|
||||
in the requirement line.
|
||||
"""
|
||||
line = "{0}{1}{2}{3}{4}".format(
|
||||
include_specifiers = True if self.specifiers else False
|
||||
if self.is_vcs:
|
||||
include_extras = False
|
||||
if self.is_file_or_url or self.is_vcs:
|
||||
include_specifiers = False
|
||||
parts = [
|
||||
self.req.line_part,
|
||||
self.extras_as_pip if not self.is_vcs else "",
|
||||
self.specifiers if self.specifiers else "",
|
||||
self.extras_as_pip if include_extras else "",
|
||||
self.specifiers if include_specifiers else "",
|
||||
self.markers_as_pip,
|
||||
self.hashes_as_pip,
|
||||
)
|
||||
]
|
||||
if include_hashes:
|
||||
parts.append(self.hashes_as_pip)
|
||||
if sources and not (self.requirement.local_file or self.vcs):
|
||||
from ..utils import prepare_pip_source_args
|
||||
|
||||
if self.index:
|
||||
sources = [s for s in sources if s.get("name") == self.index]
|
||||
index_string = " ".join(prepare_pip_source_args(sources))
|
||||
line = "{0} {1}".format(line, index_string)
|
||||
parts.extend([" ", index_string])
|
||||
line = "".join(parts)
|
||||
return line
|
||||
|
||||
def get_markers(self):
|
||||
markers = self.markers
|
||||
if markers:
|
||||
fake_pkg = PackagingRequirement('fakepkg; {0}'.format(markers))
|
||||
markers = fake_pkg.markers
|
||||
return markers
|
||||
|
||||
def get_specifier(self):
|
||||
return Specifier(self.specifiers)
|
||||
|
||||
def get_version(self):
|
||||
return parse_version(self.get_specifier().version)
|
||||
|
||||
def get_requirement(self):
|
||||
req_line = self.req.req.line
|
||||
if req_line.startswith('-e '):
|
||||
_, req_line = req_line.split(" ", 1)
|
||||
req = init_requirement(self.name)
|
||||
req.line = req_line
|
||||
req.specifier = SpecifierSet(self.specifiers if self.specifiers else '')
|
||||
if self.is_vcs or self.is_file_or_url:
|
||||
req.url = self.req.link.url_without_fragment
|
||||
req.marker = self.get_markers()
|
||||
req.extras = set(self.extras) if self.extras else set()
|
||||
return req
|
||||
|
||||
@property
|
||||
def constraint_line(self):
|
||||
return self.as_line()
|
||||
@@ -839,6 +958,8 @@ class Requirement(object):
|
||||
if k in good_keys
|
||||
}
|
||||
name = self.name
|
||||
if 'markers' in req_dict and req_dict['markers']:
|
||||
req_dict['markers'] = req_dict['markers'].replace('"', "'")
|
||||
base_dict = {
|
||||
k: v
|
||||
for k, v in self.req.pipfile_part[name].items()
|
||||
@@ -850,23 +971,103 @@ class Requirement(object):
|
||||
conflicts = [k for k in (conflicting_keys[1:],) if k in base_dict]
|
||||
for k in conflicts:
|
||||
base_dict.pop(k)
|
||||
if "hashes" in base_dict and len(base_dict["hashes"]) == 1:
|
||||
base_dict["hash"] = base_dict.pop("hashes")[0]
|
||||
if "hashes" in base_dict:
|
||||
_hashes = base_dict.pop("hashes")
|
||||
hashes = []
|
||||
for _hash in _hashes:
|
||||
try:
|
||||
hashes.append(_hash.as_line())
|
||||
except AttributeError:
|
||||
hashes.append(_hash)
|
||||
base_dict["hashes"] = sorted(hashes)
|
||||
if len(base_dict.keys()) == 1 and "version" in base_dict:
|
||||
base_dict = base_dict.get("version")
|
||||
return {name: base_dict}
|
||||
|
||||
def as_ireq(self):
|
||||
ireq_line = self.as_line(include_hashes=False)
|
||||
if self.editable or self.req.editable:
|
||||
if ireq_line.startswith("-e "):
|
||||
ireq_line = ireq_line[len("-e "):]
|
||||
ireq = InstallRequirement.from_editable(ireq_line)
|
||||
else:
|
||||
ireq = InstallRequirement.from_line(ireq_line)
|
||||
if not getattr(ireq, "req", None):
|
||||
ireq.req = self.req.req
|
||||
else:
|
||||
ireq.req.extras = self.req.req.extras
|
||||
ireq.req.marker = self.req.req.marker
|
||||
return ireq
|
||||
|
||||
@property
|
||||
def pipfile_entry(self):
|
||||
return self.as_pipfile().copy().popitem()
|
||||
|
||||
@property
|
||||
def ireq(self):
|
||||
if not self._ireq:
|
||||
ireq_line = self.as_line()
|
||||
if ireq_line.startswith("-e "):
|
||||
ireq_line = ireq_line[len("-e ") :]
|
||||
self._ireq = InstallRequirement.from_editable(ireq_line)
|
||||
else:
|
||||
self._ireq = InstallRequirement.from_line(ireq_line)
|
||||
return self._ireq
|
||||
return self.as_ireq()
|
||||
|
||||
def get_dependencies(self, sources=None):
|
||||
"""Retrieve the dependencies of the current requirement.
|
||||
|
||||
Retrieves dependencies of the current requirement. This only works on pinned
|
||||
requirements.
|
||||
|
||||
:param sources: Pipfile-formatted sources, defaults to None
|
||||
:param sources: list[dict], optional
|
||||
:return: A set of requirement strings of the dependencies of this requirement.
|
||||
:rtype: set(str)
|
||||
"""
|
||||
if not sources:
|
||||
sources = [{
|
||||
'name': 'pypi',
|
||||
'url': 'https://pypi.org/simple',
|
||||
'verify_ssl': True,
|
||||
}]
|
||||
return get_dependencies(self.as_ireq(), sources=sources)
|
||||
|
||||
def get_abstract_dependencies(self, sources=None):
|
||||
"""Retrieve the abstract dependencies of this requirement.
|
||||
|
||||
Returns the abstract dependencies of the current requirement in order to resolve.
|
||||
|
||||
:param sources: A list of sources (pipfile format), defaults to None
|
||||
:param sources: list, optional
|
||||
:return: A list of abstract (unpinned) dependencies
|
||||
:rtype: list[ :class:`~requirementslib.models.dependency.AbstractDependency` ]
|
||||
"""
|
||||
|
||||
if not self.abstract_dep:
|
||||
parent = getattr(self, 'parent', None)
|
||||
self.abstract_dep = AbstractDependency.from_requirement(self, parent=parent)
|
||||
if not sources:
|
||||
sources = [{'url': 'https://pypi.org/simple', 'name': 'pypi', 'verify_ssl': True},]
|
||||
if is_pinned_requirement(self.ireq):
|
||||
deps = self.get_dependencies()
|
||||
else:
|
||||
ireq = sorted(self.find_all_matches(), key=lambda k: k.version)
|
||||
deps = get_dependencies(ireq.pop(), sources=sources)
|
||||
return get_abstract_dependencies(deps, sources=sources, parent=self.abstract_dep)
|
||||
|
||||
def find_all_matches(self, sources=None, finder=None):
|
||||
"""Find all matching candidates for the current requirement.
|
||||
|
||||
Consults a finder to find all matching candidates.
|
||||
|
||||
:param sources: Pipfile-formatted sources, defaults to None
|
||||
:param sources: list[dict], optional
|
||||
:return: A list of Installation Candidates
|
||||
:rtype: list[ :class:`~pip._internal.index.InstallationCandidate` ]
|
||||
"""
|
||||
if not finder:
|
||||
finder = get_finder(sources=sources)
|
||||
return find_all_matches(finder, self.as_ireq())
|
||||
|
||||
def merge_markers(self, markers):
|
||||
if not isinstance(markers, Marker):
|
||||
markers = Marker(markers)
|
||||
_markers = set(Marker(self.ireq.markers)) if self.ireq.markers else set(markers)
|
||||
_markers.add(markers)
|
||||
new_markers = Marker(" or ".join([str(m) for m in sorted(_markers)]))
|
||||
self.markers = str(new_markers)
|
||||
self.req.req.marker = new_markers
|
||||
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from contextlib import contextmanager
|
||||
|
||||
import attr
|
||||
import six
|
||||
|
||||
from pip_shims.shims import VcsSupport, Wheel
|
||||
|
||||
from ..utils import log
|
||||
from .cache import HashCache
|
||||
from .dependencies import AbstractDependency, find_all_matches, get_finder
|
||||
from .utils import format_requirement, is_pinned_requirement, version_from_ireq
|
||||
|
||||
|
||||
class ResolutionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@attr.s
|
||||
class DependencyResolver(object):
|
||||
pinned_deps = attr.ib(default=attr.Factory(dict))
|
||||
#: A dictionary of abstract dependencies by name
|
||||
dep_dict = attr.ib(default=attr.Factory(dict))
|
||||
#: A dictionary of sets of version numbers that are valid for a candidate currently
|
||||
candidate_dict = attr.ib(default=attr.Factory(dict))
|
||||
#: A historical record of pins
|
||||
pin_history = attr.ib(default=attr.Factory(dict))
|
||||
#: Whether to allow prerelease dependencies
|
||||
allow_prereleases = attr.ib(default=False)
|
||||
#: Stores hashes for each dependency
|
||||
hashes = attr.ib(default=attr.Factory(dict))
|
||||
#: A hash cache
|
||||
hash_cache = attr.ib(default=attr.Factory(HashCache))
|
||||
#: A finder for searching the index
|
||||
finder = attr.ib(default=None)
|
||||
#: Whether to include hashes even from incompatible wheels
|
||||
include_incompatible_hashes = attr.ib(default=True)
|
||||
#: A cache for storing available canddiates when using all wheels
|
||||
_available_candidates_cache = attr.ib(default=attr.Factory(dict))
|
||||
|
||||
@classmethod
|
||||
def create(cls, finder=None, allow_prereleases=False, get_all_hashes=True):
|
||||
if not finder:
|
||||
finder_args = []
|
||||
if allow_prereleases:
|
||||
finder_args.append('--pre')
|
||||
finder = get_finder(*finder_args)
|
||||
creation_kwargs = {
|
||||
'allow_prereleases': allow_prereleases,
|
||||
'include_incompatible_hashes': get_all_hashes,
|
||||
'finder': finder,
|
||||
'hash_cache': HashCache(),
|
||||
}
|
||||
resolver = cls(**creation_kwargs)
|
||||
return resolver
|
||||
|
||||
@property
|
||||
def dependencies(self):
|
||||
return list(self.dep_dict.values())
|
||||
|
||||
@property
|
||||
def resolution(self):
|
||||
return list(self.pinned_deps.values())
|
||||
|
||||
def add_abstract_dep(self, dep):
|
||||
"""Add an abstract dependency by either creating a new entry or
|
||||
merging with an old one.
|
||||
|
||||
:param dep: An abstract dependency to add
|
||||
:type dep: :class:`~requirementslib.models.dependency.AbstractDependency`
|
||||
:raises ResolutionError: Raised when the given dependency is not compatible with
|
||||
an existing abstract dependency.
|
||||
"""
|
||||
|
||||
if dep.name in self.dep_dict:
|
||||
compatible_versions = self.dep_dict[dep.name].compatible_versions(dep)
|
||||
if compatible_versions:
|
||||
self.candidate_dict[dep.name] = compatible_versions
|
||||
self.dep_dict[dep.name] = self.dep_dict[
|
||||
dep.name
|
||||
].compatible_abstract_dep(dep)
|
||||
else:
|
||||
raise ResolutionError
|
||||
else:
|
||||
self.candidate_dict[dep.name] = dep.version_set
|
||||
self.dep_dict[dep.name] = dep
|
||||
|
||||
def pin_deps(self):
|
||||
"""Pins the current abstract dependencies and adds them to the history dict.
|
||||
|
||||
Adds any new dependencies to the abstract dependencies already present by
|
||||
merging them together to form new, compatible abstract dependencies.
|
||||
"""
|
||||
|
||||
for name in list(self.dep_dict.keys()):
|
||||
candidates = self.dep_dict[name].candidates[:]
|
||||
abs_dep = self.dep_dict[name]
|
||||
while candidates:
|
||||
pin = candidates.pop()
|
||||
# Move on from existing pins if the new pin isn't compatible
|
||||
if name in self.pinned_deps:
|
||||
if self.pinned_deps[name].editable:
|
||||
continue
|
||||
old_version = version_from_ireq(self.pinned_deps[name])
|
||||
if not pin.editable:
|
||||
new_version = version_from_ireq(pin)
|
||||
if (new_version != old_version and
|
||||
new_version not in self.candidate_dict[name]):
|
||||
continue
|
||||
pin.parent = abs_dep.parent
|
||||
pin_subdeps = self.dep_dict[name].get_deps(pin)
|
||||
backup = self.dep_dict.copy(), self.candidate_dict.copy()
|
||||
try:
|
||||
for pin_dep in pin_subdeps:
|
||||
self.add_abstract_dep(pin_dep)
|
||||
except ResolutionError:
|
||||
self.dep_dict, self.candidate_dict = backup
|
||||
continue
|
||||
else:
|
||||
self.pinned_deps[name] = pin
|
||||
break
|
||||
|
||||
def resolve(self, root_nodes, max_rounds=20):
|
||||
"""Resolves dependencies using a backtracking resolver and multiple endpoints.
|
||||
|
||||
Note: this resolver caches aggressively.
|
||||
Runs for *max_rounds* or until any two pinning rounds yield the same outcome.
|
||||
|
||||
:param root_nodes: A list of the root requirements.
|
||||
:type root_nodes: list[:class:`~requirementslib.models.requirements.Requirement`]
|
||||
:param max_rounds: The max number of resolution rounds, defaults to 20
|
||||
:param max_rounds: int, optional
|
||||
:raises RuntimeError: Raised when max rounds is exceeded without a resolution.
|
||||
"""
|
||||
if self.dep_dict:
|
||||
raise RuntimeError("Do not use the same resolver more than once")
|
||||
|
||||
if not self.hash_cache:
|
||||
self.hash_cache = HashCache()
|
||||
|
||||
# Coerce input into AbstractDependency instances.
|
||||
# We accept str, Requirement, and AbstractDependency as input.
|
||||
for dep in root_nodes:
|
||||
if isinstance(dep, six.string_types):
|
||||
dep = AbstractDependency.from_string(dep)
|
||||
elif not isinstance(dep, AbstractDependency):
|
||||
dep = AbstractDependency.from_requirement(dep)
|
||||
self.add_abstract_dep(dep)
|
||||
|
||||
for round_ in range(max_rounds):
|
||||
self.pin_deps()
|
||||
self.pin_history[round_] = self.pinned_deps.copy()
|
||||
|
||||
if round_ > 0:
|
||||
previous_round = set(self.pin_history[round_ - 1].values())
|
||||
current_values = set(self.pin_history[round_].values())
|
||||
difference = current_values - previous_round
|
||||
else:
|
||||
difference = set(self.pin_history[round_].values())
|
||||
|
||||
log.debug("\n")
|
||||
log.debug("{:=^30}".format(" Round {0} ".format(round_)))
|
||||
log.debug("\n")
|
||||
if difference:
|
||||
log.debug("New Packages: ")
|
||||
for d in difference:
|
||||
log.debug("{:>30}".format(format_requirement(d)))
|
||||
elif round_ >= 3:
|
||||
log.debug("Stable Pins: ")
|
||||
for d in current_values:
|
||||
log.debug("{:>30}".format(format_requirement(d)))
|
||||
return
|
||||
else:
|
||||
log.debug("No New Packages.")
|
||||
# TODO: Raise a better error.
|
||||
raise RuntimeError("cannot resolve after {} rounds".format(max_rounds))
|
||||
|
||||
def get_hashes(self):
|
||||
for dep in self.pinned_deps.values():
|
||||
if dep.name not in self.hashes:
|
||||
self.hashes[dep.name] = self.get_hashes_for_one(dep)
|
||||
return self.hashes.copy()
|
||||
|
||||
def get_hashes_for_one(self, ireq):
|
||||
if not self.finder:
|
||||
finder_args = []
|
||||
if self.allow_prereleases:
|
||||
finder_args.append('--pre')
|
||||
self.finder = get_finder(*finder_args)
|
||||
|
||||
if ireq.editable:
|
||||
return set()
|
||||
|
||||
vcs = VcsSupport()
|
||||
if ireq.link and ireq.link.scheme in vcs.all_schemes and 'ssh' in ireq.link.scheme:
|
||||
return set()
|
||||
|
||||
if not is_pinned_requirement(ireq):
|
||||
raise TypeError(
|
||||
"Expected pinned requirement, got {}".format(ireq))
|
||||
|
||||
matching_candidates = set()
|
||||
with self.allow_all_wheels():
|
||||
matching_candidates = (
|
||||
find_all_matches(self.finder, ireq, pre=self.allow_prereleases)
|
||||
)
|
||||
|
||||
return {
|
||||
self.hash_cache.get_hash(candidate.location)
|
||||
for candidate in matching_candidates
|
||||
}
|
||||
|
||||
@contextmanager
|
||||
def allow_all_wheels(self):
|
||||
"""
|
||||
Monkey patches pip.Wheel to allow wheels from all platforms and Python versions.
|
||||
|
||||
This also saves the candidate cache and set a new one, or else the results from the
|
||||
previous non-patched calls will interfere.
|
||||
"""
|
||||
def _wheel_supported(self, tags=None):
|
||||
# Ignore current platform. Support everything.
|
||||
return True
|
||||
|
||||
def _wheel_support_index_min(self, tags=None):
|
||||
# All wheels are equal priority for sorting.
|
||||
return 0
|
||||
|
||||
original_wheel_supported = Wheel.supported
|
||||
original_support_index_min = Wheel.support_index_min
|
||||
|
||||
Wheel.supported = _wheel_supported
|
||||
Wheel.support_index_min = _wheel_support_index_min
|
||||
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
Wheel.supported = original_wheel_supported
|
||||
Wheel.support_index_min = original_support_index_min
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user