Merge branch 'master' into master

This commit is contained in:
Dan Ryan
2018-08-29 02:01:36 -04:00
committed by GitHub
148 changed files with 11091 additions and 2453 deletions
+26
View File
@@ -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
+20
View File
@@ -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
+8
View File
@@ -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
+14
View File
@@ -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
+12
View File
@@ -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
+37
View File
@@ -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()
+39
View File
@@ -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
+49
View File
@@ -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
+6
View File
@@ -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
+3
View File
@@ -0,0 +1,3 @@
steps:
- script: 'python -m pip install --upgrade pip && python -m pip install -e .'
displayName: Upgrade Pip & Install Pipenv
+20
View File
@@ -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()
+4
View File
@@ -0,0 +1,4 @@
phases:
- template: phases/test.yml
parameters:
queue: Hosted VS2017
+32 -5
View File
@@ -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
+1
View File
@@ -20,6 +20,7 @@ invoke = "*"
jedi = "*"
isort = "*"
rope = "*"
passa = {editable = true, git = "https://github.com/sarugaku/passa.git"}
[packages]
Generated
+152 -52
View File
@@ -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"
}
}
}
}
+1 -1
View File
@@ -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
View File
@@ -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::
+2
View File
@@ -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.
+14
View File
@@ -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``
+1
View File
@@ -0,0 +1 @@
Stopped recommending `--system` for Docker contexts.
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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():
+1 -1
View File
@@ -2,4 +2,4 @@
__author__ = """pyup.io"""
__email__ = 'support@pyup.io'
__version__ = '1.8.1'
__version__ = '1.8.4'
+4 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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"),
+2 -2
View File
@@ -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
+1
View File
@@ -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
+32
View File
@@ -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.
+3
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -1,3 +1,3 @@
from .core import where, old_where
__version__ = "2018.04.16"
__version__ = "2018.08.24"
+63 -163
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+17 -1
View File
@@ -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()
+50 -2
View File
@@ -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
+1
View File
@@ -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
+145
View File
@@ -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__
+13
View File
@@ -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.
+79
View File
@@ -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,
)
+163
View File
@@ -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
+35
View File
@@ -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
+1 -1
View File
@@ -24,7 +24,7 @@ import pkg_resources
# from graphviz import backend, Digraph
__version__ = '0.12.1'
__version__ = '0.13.0'
flatten = chain.from_iterable
+13
View File
@@ -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.
+9
View File
@@ -0,0 +1,9 @@
__all__ = [
"__version__",
"Lockfile", "Pipfile",
]
__version__ = '0.1.1'
from .lockfiles import Lockfile
from .pipfiles import Pipfile
+151
View File
@@ -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
View File
@@ -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,
)
+132
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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"])
+161
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+5 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
-e git+https://github.com/zooba/pep514tools.git@320e48745660b696e2dcaee888fc2e516b435e48#egg=pep514tools
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+12 -37
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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, []
-50
View File
@@ -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
View File
@@ -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)
-30
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,5 +1,5 @@
# -*- coding=utf-8 -*-
__version__ = "1.0.11"
__version__ = '1.1.5'
from .exceptions import RequirementError
-56
View File
@@ -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
View File
@@ -1 +0,0 @@
# -*- coding=utf-8 -*-
-3
View File
@@ -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
-230
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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()
+650
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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