Merge branch 'docs/fix-manual' into consolidate-contributing-docs

This commit is contained in:
Frost Ming
2018-11-18 09:57:15 +08:00
committed by GitHub
363 changed files with 15784 additions and 12722 deletions
@@ -1,7 +1,8 @@
phases:
- phase:
jobs:
- job:
displayName: Docs
queue: Hosted Linux Preview
pool:
vmImage: ubuntu-16.04
steps:
- task: UsePythonVersion@0
@@ -16,5 +17,5 @@ phases:
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: docs'
inputs:
PathtoPublish: docs/build
ArtifactName: docs
pathToPublish: docs/build
artifactName: docs
@@ -8,7 +8,7 @@ steps:
- 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
export GIT_SSL_CAINFO=$(python -m certifi)
export LANG=C.UTF-8
python -m pip install check-manifest
check-manifest
@@ -8,23 +8,23 @@ steps:
- 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
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 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
@@ -0,0 +1,39 @@
parameters:
vmImage:
jobs:
- job: Vendor_Scripts
displayName: Test Vendor Scripts
pool:
vmImage: ${{ parameters.vmImage }}
strategy:
maxParallel: 4
matrix:
${{ if eq(parameters.vmImage, 'vs2017-win2016') }}:
# TODO remove once vs2017-win2016 has Python 3.7
Python37:
python.version: '>= 3.7.0-b2'
python.architecture: x64
${{ if ne(parameters.vmImage, 'vs2017-win2016' )}}:
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
@@ -1,49 +1,48 @@
parameters:
queue:
vmImage:
phases:
- phase: Test_Primary
jobs:
- job: Test_Primary
displayName: Test Primary
queue:
name: ${{ parameters.queue }}
parallel: 4
pool:
vmImage: ${{ parameters.vmImage }}
strategy:
maxParallel: 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
${{ if eq(parameters.vmImage, 'vs2017-win2016') }}:
# TODO remove once vs2017-win2016 has Python 3.7
Python37:
python.version: '>= 3.7.0-b2'
python.architecture: x64
${{ if ne(parameters.queue, 'Hosted VS2017' )}}:
${{ if ne(parameters.vmImage, 'vs2017-win2016' )}}:
Python37:
python.version: '>= 3.7'
python.architecture: x64
steps:
- ${{ if eq(parameters.queue, 'Hosted VS2017') }}:
- ${{ if eq(parameters.vmImage, 'vs2017-win2016') }}:
- template: ./run-tests-windows.yml
- ${{ if ne(parameters.queue, 'Hosted VS2017') }}:
- ${{ if ne(parameters.vmImage, 'vs2017-win2016') }}:
- template: ./run-tests.yml
- phase: Test_Secondary
- job: Test_Secondary
displayName: Test python3.6
# Run after Test_Primary so we don't devour time and jobs if tests are going to fail
# dependsOn: Test_Primary
queue:
name: ${{ parameters.queue }}
parallel: 4
# dependsOn: Test_Primary
pool:
vmImage: ${{ parameters.vmImage }}
strategy:
maxParallel: 4
matrix:
Python36:
python.version: '3.6'
python.architecture: x64
steps:
- ${{ if eq(parameters.queue, 'Hosted VS2017') }}:
- ${{ if eq(parameters.vmImage, 'vs2017-win2016') }}:
- template: ./run-tests-windows.yml
- ${{ if ne(parameters.queue, 'Hosted VS2017') }}:
- ${{ if ne(parameters.vmImage, 'vs2017-win2016') }}:
- template: ./run-tests.yml
+27
View File
@@ -0,0 +1,27 @@
name: Pipenv Build Rules
trigger:
batch: true
branches:
include:
- master
paths:
exclude:
- docs/*
- news/*
- README.md
- pipenv/*.txt
- CHANGELOG.rst
- CONTRIBUTING.md
- CODE_OF_CONDUCT.md
- .gitignore
- .gitattributes
- .editorconfig
jobs:
- template: jobs/test.yml
parameters:
vmImage: ubuntu-16.04
- template: jobs/run-vendor-scripts.yml
parameters:
vmImage: ubuntu-16.04
@@ -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,13 +3,16 @@ steps:
# Fix Git SSL errors
pip install certifi
python -m certifi > cacert.txt
$env:GIT_SSL_CAINFO = $(Get-Content cacert.txt)
Write-Host "##vso[task.setvariable variable=GIT_SSL_CAINFO]$(Get-Content cacert.txt)"
$env:GIT_SSL_CAINFO="$(Get-Content cacert.txt)"
# Shorten paths to get under MAX_PATH or else integration tests will fail
# https://bugs.python.org/issue18199
subst T: $env:TEMP
$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
subst T: "$env:TEMP"
Write-Host "##vso[task.setvariable variable=TEMP]T:\"
$env:TEMP='T:\'
Write-Host "##vso[task.setvariable variable=TMP]T:\"
$env:TMP='T:\'
D:\.venv\Scripts\pipenv run pytest -ra --ignore=pipenv\patched --ignore=pipenv\vendor --junitxml=test-results.xml tests
displayName: Run integration tests
- task: PublishTestResults@2
+23
View File
@@ -0,0 +1,23 @@
name: Pipenv Build Rules
trigger:
batch: true
branches:
include:
- master
paths:
exclude:
- docs/*
- news/*
- README.md
- pipenv/*.txt
- CHANGELOG.rst
- CONTRIBUTING.md
- CODE_OF_CONDUCT.md
- .gitignore
- .gitattributes
- .editorconfig
jobs:
- template: jobs/test.yml
parameters:
vmImage: vs2017-win2016
+6
View File
@@ -8,6 +8,9 @@
.AppleDouble
.LSOverride
# PyCharm
.idea/
# VSCode
.vscode
@@ -130,6 +133,9 @@ venv.bak/
.spyderproject
.spyproject
# PyCharm project settings
.idea
# Rope project settings
.ropeproject
-8
View File
@@ -1,8 +0,0 @@
phases:
- template: phases/test.yml
parameters:
queue: Hosted Linux Preview
- template: phases/run-vendor-scripts.yml
parameters:
queue: Hosted Linux Preview
-39
View File
@@ -1,39 +0,0 @@
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
-6
View File
@@ -1,6 +0,0 @@
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
-4
View File
@@ -1,4 +0,0 @@
phases:
- template: phases/test.yml
parameters:
queue: Hosted VS2017
+86
View File
@@ -1,3 +1,89 @@
2018.11.14 (2018-11-14)
=======================
Features & Improvements
-----------------------
- Improved exceptions and error handling on failures. `#1977 <https://github.com/pypa/pipenv/issues/1977>`_
- Added persistent settings for all CLI flags via ``PIPENV_{FLAG_NAME}`` environment variables by enabling ``auto_envvar_prefix=PIPENV`` in click (implements PEEP-0002). `#2200 <https://github.com/pypa/pipenv/issues/2200>`_
- Added improved messaging about available but skipped updates due to dependency conflicts when running ``pipenv update --outdated``. `#2411 <https://github.com/pypa/pipenv/issues/2411>`_
- Added environment variable `PIPENV_PYUP_API_KEY` to add ability
to override the bundled pyup.io API key. `#2825 <https://github.com/pypa/pipenv/issues/2825>`_
- Added additional output to ``pipenv update --outdated`` to indicate that the operation succeded and all packages were already up to date. `#2828 <https://github.com/pypa/pipenv/issues/2828>`_
- Updated ``crayons`` patch to enable colors on native powershell but swap native blue for magenta. `#3020 <https://github.com/pypa/pipenv/issues/3020>`_
- Added support for ``--bare`` to ``pipenv clean``, and fixed ``pipenv sync --bare`` to actually reduce output. `#3041 <https://github.com/pypa/pipenv/issues/3041>`_
- Added windows-compatible spinner via upgraded ``vistir`` dependency. `#3089 <https://github.com/pypa/pipenv/issues/3089>`_
- - Added support for python installations managed by ``asdf``. `#3096 <https://github.com/pypa/pipenv/issues/3096>`_
- Improved runtime performance of no-op commands such as ``pipenv --venv`` by around 2/3. `#3158 <https://github.com/pypa/pipenv/issues/3158>`_
- Do not show error but success for running ``pipenv uninstall --all`` in a fresh virtual environment. `#3170 <https://github.com/pypa/pipenv/issues/3170>`_
- Improved asynchronous installation and error handling via queued subprocess paralleization. `#3217 <https://github.com/pypa/pipenv/issues/3217>`_
Bug Fixes
---------
- Remote non-PyPI artifacts and local wheels and artifacts will now include their own hashes rather than including hashes from ``PyPI``. `#2394 <https://github.com/pypa/pipenv/issues/2394>`_
- Non-ascii characters will now be handled correctly when parsed by pipenv's ``ToML`` parsers. `#2737 <https://github.com/pypa/pipenv/issues/2737>`_
- Updated ``pipenv uninstall`` to respect the ``--skip-lock`` argument. `#2848 <https://github.com/pypa/pipenv/issues/2848>`_
- Fixed a bug which caused uninstallation to sometimes fail to successfullly remove packages from ``Pipfiles`` with comments on preceding or following lines. `#2885 <https://github.com/pypa/pipenv/issues/2885>`_,
`#3099 <https://github.com/pypa/pipenv/issues/3099>`_
- Pipenv will no longer fail when encountering python versions on Windows that have been uninstalled. `#2983 <https://github.com/pypa/pipenv/issues/2983>`_
- Fixed unnecessary extras are added when translating markers `#3026 <https://github.com/pypa/pipenv/issues/3026>`_
- Fixed a virtualenv creation issue which could cause new virtualenvs to inadvertently attempt to read and write to global site packages. `#3047 <https://github.com/pypa/pipenv/issues/3047>`_
- Fixed an issue with virtualenv path derivation which could cause errors, particularly for users on WSL bash. `#3055 <https://github.com/pypa/pipenv/issues/3055>`_
- Fixed a bug which caused ``Unexpected EOF`` errors to be thrown when ``pip`` was waiting for input from users who had put login credentials in environment variables. `#3088 <https://github.com/pypa/pipenv/issues/3088>`_
- Fixed a bug in ``requirementslib`` which prevented successful installation from mercurial repositories. `#3090 <https://github.com/pypa/pipenv/issues/3090>`_
- Fixed random resource warnings when using pyenv or any other subprocess calls. `#3094 <https://github.com/pypa/pipenv/issues/3094>`_
- - Fixed a bug which sometimes prevented cloning and parsing ``mercurial`` requirements. `#3096 <https://github.com/pypa/pipenv/issues/3096>`_
- Fixed an issue in ``delegator.py`` related to subprocess calls when using ``PopenSpawn`` to stream output, which sometimes threw unexpected ``EOF`` errors. `#3102 <https://github.com/pypa/pipenv/issues/3102>`_,
`#3114 <https://github.com/pypa/pipenv/issues/3114>`_,
`#3117 <https://github.com/pypa/pipenv/issues/3117>`_
- Fix the path casing issue that makes `pipenv clean` fail on Windows `#3104 <https://github.com/pypa/pipenv/issues/3104>`_
- Pipenv will avoid leaving build artifacts in the current working directory. `#3106 <https://github.com/pypa/pipenv/issues/3106>`_
- Fixed issues with broken subprocess calls leaking resource handles and causing random and sporadic failures. `#3109 <https://github.com/pypa/pipenv/issues/3109>`_
- Fixed an issue which caused ``pipenv clean`` to sometimes clean packages from the base ``site-packages`` folder or fail entirely. `#3113 <https://github.com/pypa/pipenv/issues/3113>`_
- Updated ``pythonfinder`` to correct an issue with unnesting of nested paths when searching for python versions. `#3121 <https://github.com/pypa/pipenv/issues/3121>`_
- Added additional logic for ignoring and replacing non-ascii characters when formatting console output on non-UTF-8 systems. `#3131 <https://github.com/pypa/pipenv/issues/3131>`_
- Fix virtual environment discovery when `PIPENV_VENV_IN_PROJECT` is set, but the in-project `.venv` is a file. `#3134 <https://github.com/pypa/pipenv/issues/3134>`_
- Hashes for remote and local non-PyPI artifacts will now be included in ``Pipfile.lock`` during resolution. `#3145 <https://github.com/pypa/pipenv/issues/3145>`_
- Fix project path hashing logic in purpose to prevent collisions of virtual environments. `#3151 <https://github.com/pypa/pipenv/issues/3151>`_
- Fix package installation when the virtual environment path contains parentheses. `#3158 <https://github.com/pypa/pipenv/issues/3158>`_
- Azure Pipelines YAML files are updated to use the latest syntax and product name. `#3164 <https://github.com/pypa/pipenv/issues/3164>`_
- Fixed new spinner success message to write only one success message during resolution. `#3183 <https://github.com/pypa/pipenv/issues/3183>`_
- Pipenv will now correctly respect the ``--pre`` option when used with ``pipenv install``. `#3185 <https://github.com/pypa/pipenv/issues/3185>`_
- Fix a bug where exception is raised when run pipenv graph in a project without created virtualenv `#3201 <https://github.com/pypa/pipenv/issues/3201>`_
- When sources are missing names, names will now be derived from the supplied URL. `#3216 <https://github.com/pypa/pipenv/issues/3216>`_
Vendored Libraries
------------------
- Updated ``pythonfinder`` to correct an issue with unnesting of nested paths when searching for python versions. `#3061 <https://github.com/pypa/pipenv/issues/3061>`_,
`#3121 <https://github.com/pypa/pipenv/issues/3121>`_
- Updated vendored dependencies:
- ``certifi 2018.08.24 => 2018.10.15``
- ``urllib3 1.23 => 1.24``
- ``requests 2.19.1 => 2.20.0``
- ``shellingham ``1.2.6 => 1.2.7``
- ``tomlkit 0.4.4. => 0.4.6``
- ``vistir 0.1.6 => 0.1.8``
- ``pythonfinder 0.1.2 => 0.1.3``
- ``requirementslib 1.1.9 => 1.1.10``
- ``backports.functools_lru_cache 1.5.0 (new)``
- ``cursor 1.2.0 (new)`` `#3089 <https://github.com/pypa/pipenv/issues/3089>`_
- Updated vendored dependencies:
- ``requests 2.19.1 => 2.20.1``
- ``tomlkit 0.4.46 => 0.5.2``
- ``vistir 0.1.6 => 0.2.4``
- ``pythonfinder 1.1.2 => 1.1.8``
- ``requirementslib 1.1.10 => 1.3.0`` `#3096 <https://github.com/pypa/pipenv/issues/3096>`_
- Switch to ``tomlkit`` for parsing and writing. Drop ``prettytoml`` and ``contoml`` from vendors. `#3191 <https://github.com/pypa/pipenv/issues/3191>`_
- Updated ``requirementslib`` to aid in resolution of local and remote archives. `#3196 <https://github.com/pypa/pipenv/issues/3196>`_
Improved Documentation
----------------------
- Expanded development and testing documentation for contributors to get started. `#3074 <https://github.com/pypa/pipenv/issues/3074>`_
2018.10.13 (2018-10-13)
=======================
+1
View File
@@ -28,6 +28,7 @@ recursive-exclude pipenv *.pyi
recursive-exclude pipenv *.typed
prune peeps
prune .azure-pipelines
prune .buildkite
prune .github
prune .vsts-ci
Generated
+124 -74
View File
@@ -83,10 +83,10 @@
},
"bleach": {
"hashes": [
"sha256:9c471c0dd9c820f6bf4ee5ca3e348ceccefbc1475d9a40c397ed5d04e0b42c54",
"sha256:b407b2612b37e6cdc6704f84cec18c1f140b78e6c625652a844e89d6b9855f6b"
"sha256:48d39675b80a75f6d1c3bdbffec791cf0bbbab665cf01e20da701c77de278718",
"sha256:73d26f018af5d5adcdabf5c1c974add4361a9c76af215fe32fdec8a6fc5fb9b9"
],
"version": "==3.0.0"
"version": "==3.0.2"
},
"cerberus": {
"hashes": [
@@ -96,10 +96,10 @@
},
"certifi": {
"hashes": [
"sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638",
"sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a"
"sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c",
"sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a"
],
"version": "==2018.8.24"
"version": "==2018.10.15"
},
"cffi": {
"hashes": [
@@ -188,11 +188,10 @@
},
"colorama": {
"hashes": [
"sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda",
"sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"
"sha256:a3d89af5db9e9806a779a50296b5fdb466e281147c2c235e8225ecc6dbf7bbf3",
"sha256:c9b54bebe91a6a803e0772c8561d53f2926bfeb17cd141fbabcb08424086595c"
],
"markers": "sys_platform == 'win32'",
"version": "==0.3.9"
"version": "==0.4.0"
},
"configparser": {
"hashes": [
@@ -201,6 +200,12 @@
"markers": "python_version < '3.2'",
"version": "==3.5.0"
},
"cursor": {
"hashes": [
"sha256:8ee9fe5b925e1001f6ae6c017e93682583d2b4d1ef7130a26cfcdf1651c0032c"
],
"version": "==1.2.0"
},
"distlib": {
"hashes": [
"sha256:57977cd7d9ea27986ec62f425630e4ddb42efe651ff80bc58ed8dbc3c7c21f19"
@@ -270,6 +275,12 @@
"markers": "python_version < '3.3'",
"version": "==1.0.2"
},
"future": {
"hashes": [
"sha256:e39ced1ab767b5936646cedba8bcce582398233d6a627067d4c6a454c90cfedb"
],
"version": "==0.16.0"
},
"futures": {
"hashes": [
"sha256:9ec02aa7d674acb8618afb127e27fde7fc68994c0437ad759fa094a574adb265",
@@ -278,12 +289,6 @@
"markers": "python_version < '3' and python_version >= '2.6'",
"version": "==3.2.0"
},
"future": {
"hashes": [
"sha256:e39ced1ab767b5936646cedba8bcce582398233d6a627067d4c6a454c90cfedb"
],
"version": "==0.16.0"
},
"idna": {
"hashes": [
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
@@ -325,9 +330,10 @@
},
"itsdangerous": {
"hashes": [
"sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519"
"sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
"sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
],
"version": "==0.24"
"version": "==1.1.0"
},
"jedi": {
"hashes": [
@@ -346,9 +352,36 @@
},
"markupsafe": {
"hashes": [
"sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
"sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432",
"sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b",
"sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9",
"sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af",
"sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834",
"sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd",
"sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d",
"sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7",
"sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b",
"sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3",
"sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c",
"sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2",
"sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7",
"sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36",
"sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1",
"sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e",
"sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1",
"sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c",
"sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856",
"sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550",
"sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492",
"sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672",
"sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401",
"sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6",
"sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6",
"sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c",
"sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd",
"sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1"
],
"version": "==1.0"
"version": "==1.1.0"
},
"mccabe": {
"hashes": [
@@ -402,17 +435,24 @@
},
"pbr": {
"hashes": [
"sha256:1be135151a0da949af8c5d0ee9013d9eafada71237eb80b3ba8896b4f12ec5dc",
"sha256:cf36765bf2218654ae824ec8e14257259ba44e43b117fd573c8d07a9895adbdd"
"sha256:f59d71442f9ece3dffc17bc36575768e1ee9967756e6b6535f0ee1f0054c3d68",
"sha256:f6d5b23f226a2ba58e14e49aa3b1bfaf814d0199144b95d78458212444de1387"
],
"version": "==4.3.0"
"version": "==5.1.1"
},
"pep517": {
"hashes": [
"sha256:cc663a438fdfe2e88d8d3c5ef2203ac858de34e31b6609b1fc505d611490a926",
"sha256:f79bb08fb064dfc5b141204bfeb56a4141a6d504677fab4723036a464fc25cc1"
],
"version": "==0.3"
},
"pip-shims": {
"hashes": [
"sha256:164b93bc94b207613d9632f28f4d55eba9301f9454aaaba335de36c24d92d106",
"sha256:27e2439aa93af8c1b8e58cf63a40cbcd26959b26424904f2e6d57837af8f76c5"
"sha256:3bc24ec050a6b9eea35419467237e4f47eaf806dadc9999bf887355c377edea7",
"sha256:edb4cf3c509eab2f36b55c1ac1a59a4c485ccd537cc87934d74950880f641256"
],
"version": "==0.3.0"
"version": "==0.3.2"
},
"pipenv": {
"editable": true,
@@ -426,6 +466,9 @@
"version": "==1.4.2"
},
"plette": {
"extras": [
"validation"
],
"hashes": [
"sha256:c0e3553c1e581d8423daccbd825789c6e7f29b7d9e00e5331b12e1642a1a26d3",
"sha256:dde5d525cf5f0cbad4d938c83b93db17887918daf63c13eafed257c4f61b07b4"
@@ -434,24 +477,24 @@
},
"pluggy": {
"hashes": [
"sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1",
"sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"
"sha256:447ba94990e8014ee25ec853339faf7b0fc8050cdc3289d4d71f7f410fb90095",
"sha256:bde19360a8ec4dfd8a20dcb811780a30998101f078fc7ded6162f0076f50508f"
],
"version": "==0.7.1"
"version": "==0.8.0"
},
"py": {
"hashes": [
"sha256:06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1",
"sha256:50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6"
"sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694",
"sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6"
],
"version": "==1.6.0"
"version": "==1.7.0"
},
"pycodestyle": {
"hashes": [
"sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766",
"sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9"
"sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83",
"sha256:cbfca99bd594a10f674d0cd97a3d802a1fdef635d4361e1a2658de47ed261e3a"
],
"version": "==2.3.1"
"version": "==2.4.0"
},
"pycparser": {
"hashes": [
@@ -461,10 +504,10 @@
},
"pyflakes": {
"hashes": [
"sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f",
"sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805"
"sha256:9a7662ec724d0120012f6e29d6248ae3727d821bba522a0e6b356eff19126a49",
"sha256:f661252913bc1dbe7fcfcbf0af0db3f42ab65aabd1a6ca68fe5d466bace94dae"
],
"version": "==1.6.0"
"version": "==2.0.0"
},
"pygments": {
"hashes": [
@@ -475,10 +518,10 @@
},
"pyparsing": {
"hashes": [
"sha256:bc6c7146b91af3f567cf6daeaec360bc07d45ffec4cf5353f4d7a208ce7ca30a",
"sha256:d29593d8ebe7b57d6967b62494f8c72b03ac0262b1eed63826c6f788b3606401"
"sha256:40856e74d4987de5d01761a22d1621ae1c7f8774585acae358aa5c5936c6c90b",
"sha256:f353aab21fd474459d97b709e527b5571314ee5f067441dc9f88e33eecd96592"
],
"version": "==2.2.2"
"version": "==2.3.0"
},
"pytest": {
"hashes": [
@@ -515,6 +558,12 @@
"index": "pypi",
"version": "==1.23.2"
},
"pytoml": {
"hashes": [
"sha256:ca2d0cb127c938b8b76a9a0d0f855cf930c1d50cc3a0af6d3595b566519a1013"
],
"version": "==0.1.20"
},
"pytz": {
"hashes": [
"sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053",
@@ -525,17 +574,17 @@
},
"readme-renderer": {
"hashes": [
"sha256:237ca8705ffea849870de41101dba41543561da05c0ae45b2f1c547efa9843d2",
"sha256:f75049a3a7afa57165551e030dd8f9882ebf688b9600535a3f7e23596651875d"
"sha256:bb16f55b259f27f75f640acf5e00cf897845a8b3e4731b5c1a436e4b8529202f",
"sha256:c8532b79afc0375a85f10433eca157d6b50f7d6990f337fa498c96cd4bfc203d"
],
"version": "==22.0"
"version": "==24.0"
},
"requests": {
"hashes": [
"sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
"sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
"sha256:65b3a120e4329e33c9889db89c80976c5272f56ea92d3e74da8a463992e3ff54",
"sha256:ea881206e59f41dbd0bd445437d792e43906703fff75ca8ff43ccdb11f33f263"
],
"version": "==2.19.1"
"version": "==2.20.1"
},
"requests-toolbelt": {
"hashes": [
@@ -546,10 +595,10 @@
},
"requirementslib": {
"hashes": [
"sha256:39fb4aab3ebd7f46b266ddc98a3ac731127ee35fe6cf1b3e11be7c6551cc2c9b",
"sha256:810d8961f333d8fef92400f58b25f80003151fb424a545e244073fc3d95ae2dd"
"sha256:441a5bfa487d3f3f5fd5d81c27071d9fd36bb385f538b3a87d20556a80b76f76",
"sha256:89e1e02ff0b52ce9c610124eb990ae706e0aee08beef8c718e7b87e470cdceeb"
],
"version": "==1.1.7"
"version": "==1.3.1.post1"
},
"resolvelib": {
"hashes": [
@@ -626,10 +675,10 @@
},
"tomlkit": {
"hashes": [
"sha256:8ab16e93162fc44d3ad83d2aa29a7140b8f7d996ae1790a73b9a7aed6fb504ac",
"sha256:ca181cee7aee805d455628f7c94eb8ae814763769a93e69157f250fe4ebe1926"
"sha256:82a8fbb8d8c6af72e96ba00b9db3e20ef61be6c79082552c9363f4559702258b",
"sha256:a43e0195edc9b3c198cd4b5f0f3d427a395d47c4a76ceba7cc875ed030756c39"
],
"version": "==0.4.4"
"version": "==0.5.2"
},
"towncrier": {
"editable": true,
@@ -638,10 +687,10 @@
},
"tqdm": {
"hashes": [
"sha256:18f1818ce951aeb9ea162ae1098b43f583f7d057b34d706f66939353d1208889",
"sha256:df02c0650160986bac0218bb07952245fc6960d23654648b5d5526ad5a4128c9"
"sha256:3c4d4a5a41ef162dd61f1edb86b0e1c7859054ab656b2e7c7b77e7fbf6d9f392",
"sha256:5b4d5549984503050883bc126280b386f5f4ca87e6c023c5d015655ad75bdebb"
],
"version": "==4.26.0"
"version": "==4.28.1"
},
"twine": {
"hashes": [
@@ -662,36 +711,37 @@
},
"urllib3": {
"hashes": [
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
"sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39",
"sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22"
],
"version": "==1.23"
"version": "==1.24.1"
},
"virtualenv": {
"hashes": [
"sha256:2ce32cd126117ce2c539f0134eb89de91a8413a29baac49cbab3eb50e2026669",
"sha256:ca07b4c0b54e14a91af9f34d0919790b016923d157afda5efdde55c96718f752"
"sha256:686176c23a538ecc56d27ed9d5217abd34644823d6391cbeb232f42bf722baad",
"sha256:f899fafcd92e1150f40c8215328be38ff24b519cd95357fa6e78e006c7638208"
],
"version": "==16.0.0"
"version": "==16.1.0"
},
"virtualenv-clone": {
"hashes": [
"sha256:4507071d81013fd03ea9930ec26bc8648b997927a11fa80e8ee81198b57e0ac7",
"sha256:b5cfe535d14dc68dfc1d1bb4ac1209ea28235b91156e2bba8e250d291c3fb4f8"
"sha256:afce268508aa5596c90dda234abe345deebc401a57d287bcbd76baa140a1aa58"
],
"version": "==0.3.0"
"version": "==0.4.0"
},
"vistir": {
"hashes": [
"sha256:8a360ac20cbcc0863d6dbbe7a52e8b2c9ebf48abd6833c3813a82c70708244af",
"sha256:bc6e10284792485c10585536e6aede9e38996c841cc9d2a67238cd05742c2d0b"
"extras": [
"spinner"
],
"version": "==0.1.6"
"hashes": [
"sha256:851bd783f2b85a372e563db741dc689cb9263ce2e067e387facdca0c36b6a6ea",
"sha256:b38ffc8ef83f85d81b4efa4cd31ea3bcd37bdb2bc9e8da9f20a40859bc44b57e"
],
"version": "==0.2.4"
},
"webencodings": {
"hashes": [
"sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
"sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
"sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"
],
"version": "==0.5.1"
},
@@ -704,10 +754,10 @@
},
"wheel": {
"hashes": [
"sha256:9fa1f772f1a2df2bd00ddb4fa57e1cc349301e1facb98fbe62329803a9ff1196",
"sha256:d215f4520a1ba1851a3c00ba2b4122665cd3d6b0834d2ba2816198b1e3024a0e"
"sha256:196c9842d79262bb66fcf59faa4bd0deb27da911dbc7c6cdca931080eb1f0783",
"sha256:c93e2d711f5f9841e17f53b0e6c0ff85593f3b416b6eec7a9452041a59a42688"
],
"version": "==0.32.1"
"version": "==0.32.2"
},
"yaspin": {
"hashes": [
+7 -3
View File
@@ -240,6 +240,9 @@ Example::
API access throttling based on overall usage rather than individual
client usage.
You can also use your own safety API key by setting the
environment variable ``PIPENV_PYUP_API_KEY``.
☤ Community Integrations
------------------------
@@ -350,12 +353,12 @@ To prevent pipenv from loading the ``.env`` file, set the ``PIPENV_DONT_LOAD_ENV
☤ Custom Script Shortcuts
-------------------------
Pipenv supports creating custom shortcuts in the (optional) ``[scripts]`` section of your Pipfile.
Pipenv supports creating custom shortcuts in the (optional) ``[scripts]`` section of your Pipfile.
You can then run ``pipenv run <shortcut name>`` in your terminal to run the command in the
context of your pipenv virtual environment even if you have not activated the pipenv shell first.
context of your pipenv virtual environment even if you have not activated the pipenv shell first.
For example, in your Pipfile::
For example, in your Pipfile::
[scripts]
printspam = "python -c \"print('I am a silly example, no one would need to do this')\""
@@ -394,6 +397,7 @@ Pipenv supports the usage of environment variables in values. For example::
Environment variables may be specified as ``${MY_ENVAR}`` or ``$MY_ENVAR``.
On Windows, ``%MY_ENVAR%`` is supported in addition to ``${MY_ENVAR}`` or ``$MY_ENVAR``.
.. _configuration-with-environment-variables:
☤ Configuration With Environment Variables
------------------------------------------
+12 -14
View File
@@ -182,12 +182,12 @@ in your ``Pipfile.lock`` for now, run ``pipenv lock --keep-outdated``. Make sur
☤ Specifying Versions of a Package
----------------------------------
You can specify versions of a package using the `Semantic Versioning scheme <https://semver.org/>`_
(i.e. ``major.minor.micro``).
You can specify versions of a package using the `Semantic Versioning scheme <https://semver.org/>`_
(i.e. ``major.minor.micro``).
For example, to install requests you can use: ::
$ pipenv install requests~=1.2 # equivalent to requests~=1.2.0
$ pipenv install requests~=1.2 # equivalent to requests~=1.2.0
Pipenv will install version ``1.2`` and any minor update, but not ``2.0``.
@@ -201,11 +201,11 @@ To make inclusive or exclusive version comparisons you can use: ::
$ pipenv install "requests>=1.4" # will install a version equal or larger than 1.4.0
$ pipenv install "requests<=2.13" # will install a version equal or lower than 2.13.0
$ pipenv install "requests>2.19" # will install 2.19.1 but not 2.19.0
$ pipenv install "requests>2.19" # will install 2.19.1 but not 2.19.0
.. note:: The use of ``" "`` around the package and version specification is highly recommended
.. note:: The use of ``" "`` around the package and version specification is highly recommended
to avoid issues with `Input and output redirection <https://robots.thoughtbot.com/input-output-redirection-in-the-shell>`_
in Unix-based operating systems.
in Unix-based operating systems.
The use of ``~=`` is preferred over the ``==`` identifier as the former prevents pipenv from updating the packages: ::
@@ -367,18 +367,18 @@ The only optional section is the ``@<branch_or_tag>`` section. When using git o
Note that it is **strongly recommended** that you install any version-controlled dependencies in editable mode, using ``pipenv install -e``, in order to ensure that dependency resolution can be performed with an up to date copy of the repository each time it is performed, and that it includes all known dependencies.
Below is an example usage which installs the git repository located at ``https://github.com/requests/requests.git`` from tag ``v2.19.1`` as package name ``requests``::
Below is an example usage which installs the git repository located at ``https://github.com/requests/requests.git`` from tag ``v2.20.1`` as package name ``requests``::
$ pipenv install -e git+https://github.com/requests/requests.git@v2.19#egg=requests
$ pipenv install -e git+https://github.com/requests/requests.git@v2.20.1#egg=requests
Creating a Pipfile for this project...
Installing -e git+https://github.com/requests/requests.git@v2.19.1#egg=requests...
Installing -e git+https://github.com/requests/requests.git@v2.20.1#egg=requests...
[...snipped...]
Adding -e git+https://github.com/requests/requests.git@v2.19.1#egg=requests to Pipfile's [packages]...
Adding -e git+https://github.com/requests/requests.git@v2.20.1#egg=requests to Pipfile's [packages]...
[...]
$ cat Pipfile
[packages]
requests = {git = "https://github.com/requests/requests.git", editable = true, ref = "2.19.1"}
requests = {git = "https://github.com/requests/requests.git", editable = true, ref = "v2.20.1"}
Valid values for ``<vcs_type>`` include ``git``, ``bzr``, ``svn``, and ``hg``. Valid values for ``<scheme>`` include ``http``, ``https``, ``ssh``, and ``file``. In specific cases you also have access to other schemes: ``svn`` may be combined with ``svn`` as a scheme, and ``bzr`` can be combined with ``sftp`` and ``lp``.
@@ -398,10 +398,8 @@ environment into production. You can use ``pipenv lock`` to compile your depende
your development environment and deploy the compiled ``Pipfile.lock`` to all of your
production environments for reproducible builds.
.. note:
.. note::
If you'd like a ``requirements.txt`` output of the lockfile, run ``$ pipenv lock -r``.
This will include all hashes, however (which is great!). To get a ``requirements.txt``
without hashes, use ``$ pipenv run pip freeze``.
.. _configuration-with-environment-variables:https://docs.pipenv.org/advanced/#configuration-with-environment-variables
+35
View File
@@ -235,3 +235,38 @@ be aware of the following things when filing bug reports:
If you do not provide all of these things, it will take us much longer to
fix your problem. If we ask you to clarify these and you never respond, we
will close your issue without fixing it.
Run the tests
-------------
Three ways of running the tests are as follows:
1. ``make test`` (which uses ``docker``)
2. ``./run-tests.sh`` or ``run-tests.bat``
3. Using pipenv::
pipenv install --dev
pipenv run pytest
For the last two, it is important that your environment is setup correctly, and
this may take some work, for example, on a specific Mac installation, the following
steps may be needed::
# Make sure the tests can access github
if [ "$SSH_AGENT_PID" = "" ]
then
eval `ssh-agent`
ssh-add
fi
# Use unix like utilities, installed with brew,
# e.g. brew install coreutils
for d in /usr/local/opt/*/libexec/gnubin /usr/local/opt/python/libexec/bin
do
[[ ":$PATH:" != *":$d:"* ]] && PATH="$d:${PATH}"
done
export PATH
# PIP_FIND_LINKS currently breaks test_uninstall.py
unset PIP_FIND_LINKS
+1 -1
View File
@@ -15,7 +15,7 @@ pip-tools==1.9.0
Pygments==2.2.0
pythonz-bd==1.11.4
pytz==2017.2
requests==2.12.0
requests==2.20.0
resumable-urlretrieve==0.1.5
semver==2.7.8
six==1.10.0
-1
View File
@@ -1 +0,0 @@
Updated ``crayons`` patch to enable colors on native powershell but swap native blue for magenta.
-1
View File
@@ -1 +0,0 @@
Fixed unnecessary extras are added when translating markers
-1
View File
@@ -1 +0,0 @@
- Fix PIPENV_VERBOSITY parsing logic
+1
View File
@@ -0,0 +1 @@
Pipenv will no longer disable user-mode installation when the ``--system`` flag is passed in.
+1
View File
@@ -0,0 +1 @@
Fixed an issue with attempting to render unicode output in non-unicode locales.
+1
View File
@@ -0,0 +1 @@
Fixed a bug which could cause failures to occur when parsing python entries from global pyenv version files.
+1 -3
View File
@@ -13,9 +13,7 @@
{% if definitions[category]['showcontent'] %}
{% for text, values in sections[section][category]|dictsort(by='value') %}
- {{ text }}{% if category != 'process' %}
{{ values|sort|join(',\n ') }}
{% endif %}
- {{ text }}{% if category != 'process' %}{{ values|sort|join(',\n ') }}{% endif %}
{% endfor %}
{% else %}
+26 -2
View File
@@ -1,25 +1,49 @@
# -*- coding=utf-8 -*-
# |~~\' |~~
# |__/||~~\|--|/~\\ /
# | ||__/|__| |\/
# |
import os
import sys
import warnings
from .__version__ import __version__
PIPENV_ROOT = os.path.dirname(os.path.realpath(__file__))
PIPENV_ROOT = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
PIPENV_VENDOR = os.sep.join([PIPENV_ROOT, "vendor"])
PIPENV_PATCHED = os.sep.join([PIPENV_ROOT, "patched"])
# Inject vendored directory into system path.
sys.path.insert(0, PIPENV_VENDOR)
# Inject patched directory into system path.
sys.path.insert(0, PIPENV_PATCHED)
from pipenv.vendor.urllib3.exceptions import DependencyWarning
from pipenv.vendor.vistir.compat import ResourceWarning, fs_str
warnings.filterwarnings("ignore", category=DependencyWarning)
warnings.filterwarnings("ignore", category=ResourceWarning)
warnings.filterwarnings("ignore", category=UserWarning)
if sys.version_info >= (3, 1) and sys.version_info <= (3, 6):
if sys.stdout.isatty() and sys.stderr.isatty():
import io
import atexit
stdout_wrapper = io.TextIOWrapper(sys.stdout.buffer, encoding='utf8')
atexit.register(stdout_wrapper.close)
stderr_wrapper = io.TextIOWrapper(sys.stderr.buffer, encoding='utf8')
atexit.register(stderr_wrapper.close)
sys.stdout = stdout_wrapper
sys.stderr = stderr_wrapper
os.environ["PIP_DISABLE_PIP_VERSION_CHECK"] = fs_str("1")
# Hack to make things work better.
try:
if "concurrency" in sys.modules:
del sys.modules["concurrency"]
except Exception:
pass
from .cli import cli
from . import resolver
+1 -1
View File
@@ -1,4 +1,4 @@
from .cli import cli
if __name__ == "__main__":
cli()
cli(auto_envvar_prefix="PIPENV")
+1 -1
View File
@@ -2,4 +2,4 @@
# // ) ) / / // ) ) //___) ) // ) ) || / /
# //___/ / / / //___/ / // // / / || / /
# // / / // ((____ // / / ||/ /
__version__ = "2018.10.14.dev0"
__version__ = "2018.11.15.dev0"
+120 -23
View File
@@ -4,6 +4,7 @@
Exposes a standard API that enables compatibility across python versions,
operating systems, etc.
"""
import functools
import importlib
import io
@@ -11,8 +12,8 @@ import os
import six
import sys
import warnings
import vistir
from tempfile import _bin_openflags, gettempdir, _mkstemp_inner, mkdtemp
from .utils import logging, rmtree
try:
from tempfile import _infer_return_type
@@ -53,33 +54,23 @@ except ImportError:
class finalize(object):
def __init__(self, *args, **kwargs):
from .utils import logging
logging.warn("weakref.finalize unavailable, not cleaning...")
def detach(self):
return False
if six.PY2:
class ResourceWarning(Warning):
pass
from vistir.compat import ResourceWarning
def pip_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
warnings.filterwarnings("ignore", category=ResourceWarning)
class TemporaryDirectory(object):
"""Create and return a temporary directory. This has the same
"""
Create and return a temporary directory. This has the same
behavior as mkdtemp but can be used as a context manager. For
example:
@@ -109,7 +100,7 @@ class TemporaryDirectory(object):
@classmethod
def _cleanup(cls, name, warn_message):
rmtree(name)
vistir.path.rmtree(name)
warnings.warn(warn_message, ResourceWarning)
def __repr__(self):
@@ -123,7 +114,7 @@ class TemporaryDirectory(object):
def cleanup(self):
if self._finalizer.detach():
rmtree(self.name)
vistir.path.rmtree(self.name)
def _sanitize_params(prefix, suffix, dir):
@@ -145,9 +136,11 @@ def _sanitize_params(prefix, suffix, dir):
class _TemporaryFileCloser:
"""A separate object allowing proper closing of a temporary file's
"""
A separate object allowing proper closing of a temporary file's
underlying file object, without adding a __del__ method to the
temporary file."""
temporary file.
"""
file = None # Set here since __del__ checks it
close_called = False
@@ -191,7 +184,9 @@ class _TemporaryFileCloser:
class _TemporaryFileWrapper:
"""Temporary file wrapper
"""
Temporary file wrapper
This class provides a wrapper around files opened for
temporary use. In particular, it seeks to automatically
remove the file when it is no longer needed.
@@ -267,7 +262,8 @@ def NamedTemporaryFile(
dir=None,
delete=True,
):
"""Create and return a temporary file.
"""
Create and return a temporary file.
Arguments:
'prefix', 'suffix', 'dir' -- as for mkstemp.
'mode' -- the mode argument to io.open (default "w+b").
@@ -300,3 +296,104 @@ def NamedTemporaryFile(
os.unlink(name)
os.close(fd)
raise
def getpreferredencoding():
import locale
# Borrowed from Invoke
# (see https://github.com/pyinvoke/invoke/blob/93af29d/invoke/runners.py#L881)
_encoding = locale.getpreferredencoding(False)
if six.PY2 and not sys.platform == "win32":
_default_encoding = locale.getdefaultlocale()[1]
if _default_encoding is not None:
_encoding = _default_encoding
return _encoding
DEFAULT_ENCODING = getpreferredencoding()
# From https://github.com/CarlFK/veyepar/blob/5c5de47/dj/scripts/fixunicode.py
# MIT LIcensed, thanks Carl!
def force_encoding():
try:
stdout_isatty = sys.stdout.isatty
stderr_isatty = sys.stderr.isatty
except AttributeError:
return DEFAULT_ENCODING, DEFAULT_ENCODING
else:
if not (stdout_isatty() and stderr_isatty()):
return DEFAULT_ENCODING, DEFAULT_ENCODING
stdout_encoding = sys.stdout.encoding
stderr_encoding = sys.stderr.encoding
if sys.platform == "win32" and sys.version_info >= (3, 1):
return DEFAULT_ENCODING, DEFAULT_ENCODING
if stdout_encoding.lower() != "utf-8" or stderr_encoding.lower() != "utf-8":
from ctypes import pythonapi, py_object, c_char_p
try:
PyFile_SetEncoding = pythonapi.PyFile_SetEncoding
except AttributeError:
return DEFAULT_ENCODING, DEFAULT_ENCODING
else:
PyFile_SetEncoding.argtypes = (py_object, c_char_p)
if stdout_encoding.lower() != "utf-8":
try:
was_set = PyFile_SetEncoding(sys.stdout, "utf-8")
except OSError:
was_set = False
if not was_set:
stdout_encoding = DEFAULT_ENCODING
else:
stdout_encoding = "utf-8"
if stderr_encoding.lower() != "utf-8":
try:
was_set = PyFile_SetEncoding(sys.stderr, "utf-8")
except OSError:
was_set = False
if not was_set:
stderr_encoding = DEFAULT_ENCODING
else:
stderr_encoding = "utf-8"
return stdout_encoding, stderr_encoding
OUT_ENCODING, ERR_ENCODING = force_encoding()
UNICODE_TO_ASCII_TRANSLATION_MAP = {
8230: u"...",
8211: u"-",
10004: u"x",
10008: u"Ok"
}
def decode_output(output):
if not isinstance(output, six.string_types):
return output
try:
output = output.encode(DEFAULT_ENCODING)
except (AttributeError, UnicodeDecodeError, UnicodeEncodeError):
if six.PY2:
output = unicode.translate(vistir.misc.to_text(output),
UNICODE_TO_ASCII_TRANSLATION_MAP)
else:
output = output.translate(UNICODE_TO_ASCII_TRANSLATION_MAP)
output = output.encode(DEFAULT_ENCODING, "replace")
return vistir.misc.to_text(output, encoding=DEFAULT_ENCODING, errors="replace")
return output
def fix_utf8(text):
if not isinstance(text, six.string_types):
return text
from ._compat import decode_output
try:
text = decode_output(text)
except UnicodeDecodeError:
if six.PY2:
text = unicode.translate(vistir.misc.to_text(text), UNICODE_TO_ASCII_TRANSLATION_MAP)
return text
+33 -14
View File
@@ -15,7 +15,6 @@ import click_completion
from click_didyoumean import DYMCommandCollection
from .. import environments
from ..__version__ import __version__
from .options import (
CONTEXT_SETTINGS, PipenvGroup, code_option, common_options, deploy_option,
@@ -98,16 +97,16 @@ def cli(
warn_in_virtualenv,
do_where,
project,
spinner,
cleanup_virtualenv,
ensure_project,
format_help,
do_clear,
)
from ..utils import create_spinner
if man:
if system_which("man"):
path = os.sep.join([os.path.dirname(__file__), "pipenv.1"])
path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "pipenv.1")
os.execle(system_which("man"), "man", path, os.environ)
return 0
else:
@@ -115,6 +114,7 @@ def cli(
return 1
if envs:
echo("The following environment variables can be set, to do various things:\n")
from .. import environments
for key in environments.__dict__:
if key.startswith("PIPENV"):
echo(" - {0}".format(crayons.normal(key, bold=True)))
@@ -161,7 +161,8 @@ def cli(
# --rm was passed…
elif rm:
# Abort if --system (or running in a virtualenv).
if environments.PIPENV_USE_SYSTEM:
from ..environments import PIPENV_USE_SYSTEM
if PIPENV_USE_SYSTEM:
echo(
crayons.red(
"You are attempting to remove a virtualenv that "
@@ -179,7 +180,7 @@ def cli(
)
)
)
with spinner():
with create_spinner(text="Running..."):
# Remove the virtualenv.
cleanup_virtualenv(bare=True)
return 0
@@ -253,7 +254,7 @@ def install(
@cli.command(short_help="Un-installs a provided package and removes it from Pipfile.")
@option("--lock", is_flag=True, default=True, help="Lock afterwards.")
@option("--skip-lock/--lock", is_flag=True, default=False, help="Lock afterwards.")
@option(
"--all-dev",
is_flag=True,
@@ -268,27 +269,29 @@ def install(
)
@uninstall_options
@pass_state
@pass_context
def uninstall(
ctx,
state,
lock=False,
skip_lock=False,
all_dev=False,
all=False,
**kwargs
):
"""Un-installs a provided package and removes it from Pipfile."""
from ..core import do_uninstall
retcode = do_uninstall(
packages=state.installstate.packages,
editable_packages=state.installstate.editables,
three=state.three,
python=state.python,
system=state.system,
lock=lock,
lock=not state.installstate.skip_lock,
all_dev=all_dev,
all=all,
keep_outdated=state.installstate.keep_outdated,
pypi_mirror=state.pypi_mirror,
ctx=ctx
)
if retcode:
sys.exit(retcode)
@@ -297,7 +300,9 @@ def uninstall(
@cli.command(short_help="Generates Pipfile.lock.")
@lock_options
@pass_state
@pass_context
def lock(
ctx,
state,
**kwargs
):
@@ -307,9 +312,14 @@ def lock(
# Ensure that virtualenv is available.
ensure_project(three=state.three, python=state.python, pypi_mirror=state.pypi_mirror)
if state.installstate.requirementstxt:
do_init(dev=state.installstate.dev, requirements=state.installstate.requirementstxt,
pypi_mirror=state.pypi_mirror, pre=state.installstate.pre)
do_init(
dev=state.installstate.dev,
requirements=state.installstate.requirementstxt,
pypi_mirror=state.pypi_mirror,
pre=state.installstate.pre,
)
do_lock(
ctx=ctx,
clear=state.clear,
pre=state.installstate.pre,
keep_outdated=state.installstate.keep_outdated,
@@ -533,12 +543,20 @@ def graph(bare=False, json=False, json_tree=False, reverse=False):
@argument("module", nargs=1)
@pass_state
def run_open(state, module, *args, **kwargs):
"""View a given module in your editor."""
"""View a given module in your editor.
This uses the EDITOR environment variable. You can temporarily override it,
for example:
EDITOR=atom pipenv open requests
"""
from ..core import which, ensure_project
# Ensure that virtualenv is available.
ensure_project(three=state.three, python=state.python, validate=False,
pypi_mirror=state.pypi_mirror)
ensure_project(
three=state.three, python=state.python,
validate=False, pypi_mirror=state.pypi_mirror,
)
c = delegator.run(
'{0} -c "import {1}; print({1}.__file__);"'.format(which("python"), module)
)
@@ -590,6 +608,7 @@ def sync(
@cli.command(short_help="Uninstalls all packages not specified in Pipfile.lock.")
@option("--bare", is_flag=True, default=False, help="Minimal output.")
@option("--dry-run", is_flag=True, default=False, help="Just output unneeded packages.")
@verbose_option
@three_option
+43 -42
View File
@@ -3,7 +3,7 @@ from __future__ import absolute_import
import os
from click import BOOL as click_booltype
import click.types
from click import (
BadParameter, Group, Option, argument, echo, make_pass_decorator, option
)
@@ -86,8 +86,8 @@ def index_option(f):
state.index = value
return value
return option('-i', '--index', expose_value=False, envvar="PIP_INDEX_URL",
help='Target PyPI-compatible package index url.', nargs=1,
callback=callback)(f)
help='Target PyPI-compatible package index url.', nargs=1,
callback=callback)(f)
def extra_index_option(f):
@@ -96,8 +96,8 @@ def extra_index_option(f):
state.extra_index_urls.extend(list(value))
return value
return option("--extra-index-url", multiple=True, expose_value=False,
help=u"URLs to the extra PyPI compatible indexes to query for package lookups.",
callback=callback, envvar="PIP_EXTRA_INDEX_URL")(f)
help=u"URLs to the extra PyPI compatible indexes to query for package lookups.",
callback=callback, envvar="PIP_EXTRA_INDEX_URL")(f)
def editable_option(f):
@@ -106,8 +106,8 @@ def editable_option(f):
state.installstate.editables.extend(value)
return value
return option('-e', '--editable', expose_value=False, multiple=True,
help='An editable python package URL or path, often to a VCS repo.',
callback=callback)(f)
help='An editable python package URL or path, often to a VCS repo.',
callback=callback, type=click.types.STRING)(f)
def sequential_option(f):
@@ -116,8 +116,8 @@ def sequential_option(f):
state.installstate.sequential = value
return value
return option("--sequential", is_flag=True, default=False, expose_value=False,
help="Install dependencies one-at-a-time, instead of concurrently.",
callback=callback, type=click_booltype)(f)
help="Install dependencies one-at-a-time, instead of concurrently.",
callback=callback, type=click.types.BOOL)(f)
def skip_lock_option(f):
@@ -126,8 +126,8 @@ def skip_lock_option(f):
state.installstate.skip_lock = value
return value
return option("--skip-lock", is_flag=True, default=False, expose_value=False,
help=u"Ignore locking mechanisms when installing—use the Pipfile, instead.",
callback=callback, type=click_booltype)(f)
help=u"Skip locking mechanisms and use the Pipfile instead during operation.",
envvar="PIPENV_SKIP_LOCK", callback=callback, type=click.types.BOOL)(f)
def keep_outdated_option(f):
@@ -136,8 +136,8 @@ def keep_outdated_option(f):
state.installstate.keep_outdated = value
return value
return option("--keep-outdated", is_flag=True, default=False, expose_value=False,
help=u"Keep out-dated dependencies from being updated in Pipfile.lock.",
callback=callback, type=click_booltype)(f)
help=u"Keep out-dated dependencies from being updated in Pipfile.lock.",
callback=callback, type=click.types.BOOL)(f)
def selective_upgrade_option(f):
@@ -145,9 +145,9 @@ def selective_upgrade_option(f):
state = ctx.ensure_object(State)
state.installstate.selective_upgrade = value
return value
return option("--selective-upgrade", is_flag=True, default=False, type=click_booltype,
help="Update specified packages.", callback=callback,
expose_value=False)(f)
return option("--selective-upgrade", is_flag=True, default=False, type=click.types.BOOL,
help="Update specified packages.", callback=callback,
expose_value=False)(f)
def ignore_pipfile_option(f):
@@ -156,8 +156,8 @@ def ignore_pipfile_option(f):
state.installstate.ignore_pipfile = value
return value
return option("--ignore-pipfile", is_flag=True, default=False, expose_value=False,
help="Ignore Pipfile when installing, using the Pipfile.lock.",
callback=callback)(f)
help="Ignore Pipfile when installing, using the Pipfile.lock.",
callback=callback, type=click.types.BOOL)(f)
def dev_option(f):
@@ -165,9 +165,9 @@ def dev_option(f):
state = ctx.ensure_object(State)
state.installstate.dev = value
return value
return option("--dev", "-d", is_flag=True, default=False, type=click_booltype,
help="Install both develop and default packages.", callback=callback,
expose_value=False)(f)
return option("--dev", "-d", is_flag=True, default=False, type=click.types.BOOL,
help="Install both develop and default packages.", callback=callback,
expose_value=False)(f)
def pre_option(f):
@@ -176,7 +176,7 @@ def pre_option(f):
state.installstate.pre = value
return value
return option("--pre", is_flag=True, default=False, help=u"Allow pre-releases.",
callback=callback, type=click_booltype, expose_value=False)(f)
callback=callback, type=click.types.BOOL, expose_value=False)(f)
def package_arg(f):
@@ -184,7 +184,8 @@ def package_arg(f):
state = ctx.ensure_object(State)
state.installstate.packages.extend(value)
return value
return argument('packages', nargs=-1, callback=callback, expose_value=False,)(f)
return argument('packages', nargs=-1, callback=callback, expose_value=False,
type=click.types.STRING)(f)
def three_option(f):
@@ -195,8 +196,8 @@ def three_option(f):
state.two = not value
return value
return option("--three/--two", is_flag=True, default=None,
help="Use Python 3/2 when creating virtualenv.", callback=callback,
expose_value=False)(f)
help="Use Python 3/2 when creating virtualenv.", callback=callback,
expose_value=False)(f)
def python_option(f):
@@ -206,8 +207,8 @@ def python_option(f):
state.python = validate_python_path(ctx, param, value)
return value
return option("--python", default=False, nargs=1, callback=callback,
help="Specify which version of Python virtualenv should use.",
expose_value=False)(f)
help="Specify which version of Python virtualenv should use.",
expose_value=False)(f)
def pypi_mirror_option(f):
@@ -217,7 +218,7 @@ def pypi_mirror_option(f):
state.pypi_mirror = validate_pypi_mirror(ctx, param, value)
return value
return option("--pypi-mirror", default=environments.PIPENV_PYPI_MIRROR, nargs=1,
callback=callback, help="Specify a PyPI mirror.", expose_value=False)(f)
callback=callback, help="Specify a PyPI mirror.", expose_value=False)(f)
def verbose_option(f):
@@ -227,7 +228,7 @@ def verbose_option(f):
state.verbose = True
setup_verbosity(ctx, param, value)
return option("--verbose", "-v", is_flag=True, expose_value=False,
callback=callback, help="Verbose mode.")(f)
callback=callback, help="Verbose mode.", type=click.types.BOOL)(f)
def site_packages_option(f):
@@ -235,9 +236,9 @@ def site_packages_option(f):
state = ctx.ensure_object(State)
state.site_packages = value
return value
return option("--site-packages", is_flag=True, default=False, type=click_booltype,
help="Enable site-packages for the virtualenv.", callback=callback,
expose_value=False)(f)
return option("--site-packages", is_flag=True, default=False, type=click.types.BOOL,
help="Enable site-packages for the virtualenv.", callback=callback,
expose_value=False)(f)
def clear_option(f):
@@ -245,9 +246,9 @@ def clear_option(f):
state = ctx.ensure_object(State)
state.clear = value
return value
return option("--clear", is_flag=True, callback=callback, type=click_booltype,
help="Clears caches (pipenv, pip, and pip-tools).",
expose_value=False)(f)
return option("--clear", is_flag=True, callback=callback, type=click.types.BOOL,
help="Clears caches (pipenv, pip, and pip-tools).",
expose_value=False)(f)
def system_option(f):
@@ -257,7 +258,7 @@ def system_option(f):
state.system = value
return value
return option("--system", is_flag=True, default=False, help="System pip management.",
callback=callback, type=click_booltype, expose_value=False)(f)
callback=callback, type=click.types.BOOL, expose_value=False)(f)
def requirementstxt_option(f):
@@ -267,7 +268,7 @@ def requirementstxt_option(f):
state.installstate.requirementstxt = value
return value
return option("--requirements", "-r", nargs=1, default=False, expose_value=False,
help="Import a requirements.txt file.", callback=callback)(f)
help="Import a requirements.txt file.", callback=callback)(f)
def requirements_flag(f):
@@ -277,7 +278,7 @@ def requirements_flag(f):
state.installstate.requirementstxt = value
return value
return option("--requirements", "-r", default=False, is_flag=True, expose_value=False,
help="Generate output in requirements.txt format.", callback=callback)(f)
help="Generate output in requirements.txt format.", callback=callback)(f)
def code_option(f):
@@ -295,9 +296,9 @@ def deploy_option(f):
state = ctx.ensure_object(State)
state.installstate.deploy = value
return value
return option("--deploy", is_flag=True, default=False, type=click_booltype,
help=u"Abort if the Pipfile.lock is out-of-date, or Python version is"
" wrong.", callback=callback, expose_value=False)(f)
return option("--deploy", is_flag=True, default=False, type=click.types.BOOL,
help=u"Abort if the Pipfile.lock is out-of-date, or Python version is"
" wrong.", callback=callback, expose_value=False)(f)
def setup_verbosity(ctx, param, value):
@@ -338,6 +339,7 @@ def common_options(f):
def install_base_options(f):
f = common_options(f)
f = dev_option(f)
f = pre_option(f)
f = keep_outdated_option(f)
return f
@@ -353,7 +355,6 @@ def uninstall_options(f):
def lock_options(f):
f = install_base_options(f)
f = requirements_flag(f)
f = pre_option(f)
return f
+17 -6
View File
@@ -1,3 +1,4 @@
import itertools
import re
import shlex
@@ -8,6 +9,12 @@ class ScriptEmptyError(ValueError):
pass
def _quote_if_contains(value, pattern):
if next(re.finditer(pattern, value), None):
return '"{0}"'.format(re.sub(r'(\\*)"', r'\1\1\\"', value))
return value
class Script(object):
"""Parse a script line (in Pipfile's [scripts] section).
@@ -56,17 +63,21 @@ class Script(object):
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
foul characters. 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.
Foul characters include:
* Whitespaces.
* Parentheses in the command. (pypa/pipenv#3168)
The intended use of this function is to pre-process an argument list
before passing it into ``subprocess.Popen(..., shell=True)``.
See also: https://docs.python.org/3/library/subprocess.html#converting-argument-sequence
"""
return " ".join(
arg if not next(re.finditer(r'\s', arg), None)
else '"{0}"'.format(re.sub(r'(\\*)"', r'\1\1\\"', arg))
for arg in self._parts
)
return " ".join(itertools.chain(
[_quote_if_contains(self.command, r'[\s()]')],
(_quote_if_contains(arg, r'\s') for arg in self.args),
))
+626 -530
View File
File diff suppressed because it is too large Load Diff
+630
View File
@@ -0,0 +1,630 @@
# -*- coding=utf-8 -*-
import contextlib
import importlib
import json
import os
import sys
import operator
import pkg_resources
import site
import six
from distutils.sysconfig import get_python_lib
from sysconfig import get_paths
from cached_property import cached_property
import vistir
import pipenv
from .utils import normalize_path
BASE_WORKING_SET = pkg_resources.WorkingSet(sys.path)
class Environment(object):
def __init__(self, prefix=None, is_venv=False, base_working_set=None, pipfile=None,
sources=None, project=None):
super(Environment, self).__init__()
self._modules = {'pkg_resources': pkg_resources, 'pipenv': pipenv}
self.base_working_set = base_working_set if base_working_set else BASE_WORKING_SET
prefix = normalize_path(prefix)
self.is_venv = not prefix == normalize_path(sys.prefix)
if not sources:
sources = []
self.project = project
if project and not sources:
sources = project.sources
self.sources = sources
if project and not pipfile:
pipfile = project.parsed_pipfile
self.pipfile = pipfile
self.extra_dists = []
prefix = prefix if prefix else sys.prefix
self.prefix = vistir.compat.Path(prefix)
self.sys_paths = get_paths()
def safe_import(self, name):
"""Helper utility for reimporting previously imported modules while inside the env"""
module = None
if name not in self._modules:
self._modules[name] = importlib.import_module(name)
module = self._modules[name]
if not module:
dist = next(iter(
dist for dist in self.base_working_set if dist.project_name == name
), None)
if dist:
dist.activate()
module = importlib.import_module(name)
if name in sys.modules:
try:
six.moves.reload_module(module)
six.moves.reload_module(sys.modules[name])
except TypeError:
del sys.modules[name]
sys.modules[name] = self._modules[name]
return module
@classmethod
def resolve_dist(cls, dist, working_set):
"""Given a local distribution and a working set, returns all dependencies from the set.
:param dist: A single distribution to find the dependencies of
:type dist: :class:`pkg_resources.Distribution`
:param working_set: A working set to search for all packages
:type working_set: :class:`pkg_resources.WorkingSet`
:return: A set of distributions which the package depends on, including the package
:rtype: set(:class:`pkg_resources.Distribution`)
"""
deps = set()
deps.add(dist)
try:
reqs = dist.requires()
except (AttributeError, OSError, IOError): # The METADATA file can't be found
return deps
for req in reqs:
dist = working_set.find(req)
deps |= cls.resolve_dist(dist, working_set)
return deps
def add_dist(self, dist_name):
dist = pkg_resources.get_distribution(pkg_resources.Requirement(dist_name))
extras = self.resolve_dist(dist, self.base_working_set)
if extras:
self.extra_dists.extend(extras)
@cached_property
def python_version(self):
with self.activated():
from sysconfig import get_python_version
py_version = get_python_version()
return py_version
@property
def python_info(self):
include_dir = self.prefix / "include"
python_path = next(iter(list(include_dir.iterdir())), None)
if python_path and python_path.name.startswith("python"):
python_version = python_path.name.replace("python", "")
py_version_short, abiflags = python_version[:3], python_version[3:]
return {"py_version_short": py_version_short, "abiflags": abiflags}
return {}
@cached_property
def base_paths(self):
"""
Returns the context appropriate paths for the environment.
:return: A dictionary of environment specific paths to be used for installation operations
:rtype: dict
.. note:: The implementation of this is borrowed from a combination of pip and
virtualenv and is likely to change at some point in the future.
>>> from pipenv.core import project
>>> from pipenv.environment import Environment
>>> env = Environment(prefix=project.virtualenv_location, is_venv=True, sources=project.sources)
>>> import pprint
>>> pprint.pprint(env.base_paths)
{'PATH': '/home/hawk/.virtualenvs/pipenv-MfOPs1lW/bin::/bin:/usr/bin',
'PYTHONPATH': '/home/hawk/.virtualenvs/pipenv-MfOPs1lW/lib/python3.7/site-packages',
'data': '/home/hawk/.virtualenvs/pipenv-MfOPs1lW',
'include': '/home/hawk/.pyenv/versions/3.7.1/include/python3.7m',
'libdir': '/home/hawk/.virtualenvs/pipenv-MfOPs1lW/lib/python3.7/site-packages',
'platinclude': '/home/hawk/.pyenv/versions/3.7.1/include/python3.7m',
'platlib': '/home/hawk/.virtualenvs/pipenv-MfOPs1lW/lib/python3.7/site-packages',
'platstdlib': '/home/hawk/.virtualenvs/pipenv-MfOPs1lW/lib/python3.7',
'prefix': '/home/hawk/.virtualenvs/pipenv-MfOPs1lW',
'purelib': '/home/hawk/.virtualenvs/pipenv-MfOPs1lW/lib/python3.7/site-packages',
'scripts': '/home/hawk/.virtualenvs/pipenv-MfOPs1lW/bin',
'stdlib': '/home/hawk/.pyenv/versions/3.7.1/lib/python3.7'}
"""
prefix = self.prefix.as_posix()
install_scheme = 'nt' if (os.name == 'nt') else 'posix_prefix'
paths = get_paths(install_scheme, vars={
'base': prefix,
'platbase': prefix,
})
paths["PATH"] = paths["scripts"] + os.pathsep + os.defpath
if "prefix" not in paths:
paths["prefix"] = prefix
purelib = get_python_lib(plat_specific=0, prefix=prefix)
platlib = get_python_lib(plat_specific=1, prefix=prefix)
if purelib == platlib:
lib_dirs = purelib
else:
lib_dirs = purelib + os.pathsep + platlib
paths["libdir"] = purelib
paths["purelib"] = purelib
paths["platlib"] = platlib
paths['PYTHONPATH'] = lib_dirs
paths["libdirs"] = lib_dirs
return paths
@cached_property
def script_basedir(self):
"""Path to the environment scripts dir"""
script_dir = self.base_paths["scripts"]
return script_dir
@property
def python(self):
"""Path to the environment python"""
py = vistir.compat.Path(self.base_paths["scripts"]).joinpath("python").as_posix()
if not py:
return vistir.compat.Path(sys.executable).as_posix()
return py
@cached_property
def sys_path(self):
"""The system path inside the environment
:return: The :data:`sys.path` from the environment
:rtype: list
"""
current_executable = vistir.compat.Path(sys.executable).as_posix()
if not self.python or self.python == current_executable:
return sys.path
elif any([sys.prefix == self.prefix, not self.is_venv]):
return sys.path
cmd_args = [self.python, "-c", "import json, sys; print(json.dumps(sys.path))"]
path, _ = vistir.misc.run(cmd_args, return_object=False, nospin=True, block=True, combine_stderr=False)
path = json.loads(path.strip())
return path
@cached_property
def sys_prefix(self):
"""The prefix run inside the context of the environment
:return: The python prefix inside the environment
:rtype: :data:`sys.prefix`
"""
command = [self.python, "-c" "import sys; print(sys.prefix)"]
c = vistir.misc.run(command, return_object=True, block=True, nospin=True)
sys_prefix = vistir.compat.Path(vistir.misc.to_text(c.out).strip()).as_posix()
return sys_prefix
@cached_property
def paths(self):
paths = {}
with vistir.contextmanagers.temp_environ(), vistir.contextmanagers.temp_path():
os.environ["PYTHONIOENCODING"] = vistir.compat.fs_str("utf-8")
os.environ["PYTHONDONTWRITEBYTECODE"] = vistir.compat.fs_str("1")
paths = self.base_paths
os.environ["PATH"] = paths["PATH"]
os.environ["PYTHONPATH"] = paths["PYTHONPATH"]
if "headers" not in paths:
paths["headers"] = paths["include"]
return paths
@property
def scripts_dir(self):
return self.paths["scripts"]
@property
def libdir(self):
purelib = self.paths.get("purelib", None)
if purelib and os.path.exists(purelib):
return "purelib", purelib
return "platlib", self.paths["platlib"]
def get_distributions(self):
"""Retrives the distributions installed on the library path of the environment
:return: A set of distributions found on the library path
:rtype: iterator
"""
pkg_resources = self.safe_import("pkg_resources")
return pkg_resources.find_distributions(self.paths["PYTHONPATH"])
def find_egg(self, egg_dist):
"""Find an egg by name in the given environment"""
site_packages = get_python_lib()
search_filename = "{0}.egg-link".format(egg_dist.project_name)
try:
user_site = site.getusersitepackages()
except AttributeError:
user_site = site.USER_SITE
search_locations = [site_packages, user_site]
for site_directory in search_locations:
egg = os.path.join(site_directory, search_filename)
if os.path.isfile(egg):
return egg
def locate_dist(self, dist):
"""Given a distribution, try to find a corresponding egg link first.
If the egg - link doesn 't exist, return the supplied distribution."""
location = self.find_egg(dist)
if not location:
return dist.location
def dist_is_in_project(self, dist):
"""Determine whether the supplied distribution is in the environment."""
from .project import _normalized
prefix = _normalized(self.base_paths["prefix"])
location = self.locate_dist(dist)
if not location:
return False
return _normalized(location).startswith(prefix)
def get_installed_packages(self):
"""Returns all of the installed packages in a given environment"""
workingset = self.get_working_set()
packages = [pkg for pkg in workingset if self.dist_is_in_project(pkg)]
return packages
@contextlib.contextmanager
def get_finder(self, pre=False):
from .vendor.pip_shims import Command, cmdoptions, index_group, PackageFinder
from .environments import PIPENV_CACHE_DIR
index_urls = [source.get("url") for source in self.sources]
class PipCommand(Command):
name = "PipCommand"
pip_command = PipCommand()
index_opts = cmdoptions.make_option_group(
index_group, pip_command.parser
)
cmd_opts = pip_command.cmd_opts
pip_command.parser.insert_option_group(0, index_opts)
pip_command.parser.insert_option_group(0, cmd_opts)
pip_args = self._modules["pipenv"].utils.prepare_pip_source_args(self.sources)
pip_options, _ = pip_command.parser.parse_args(pip_args)
pip_options.cache_dir = PIPENV_CACHE_DIR
pip_options.pre = self.pipfile.get("pre", pre)
with pip_command._build_session(pip_options) as session:
finder = PackageFinder(
find_links=pip_options.find_links,
index_urls=index_urls, allow_all_prereleases=pip_options.pre,
trusted_hosts=pip_options.trusted_hosts,
process_dependency_links=pip_options.process_dependency_links,
session=session
)
yield finder
def get_package_info(self, pre=False):
dependency_links = []
packages = self.get_installed_packages()
# This code is borrowed from pip's current implementation
for dist in packages:
if dist.has_metadata('dependency_links.txt'):
dependency_links.extend(dist.get_metadata_lines('dependency_links.txt'))
with self.get_finder() as finder:
finder.add_dependency_links(dependency_links)
for dist in packages:
typ = 'unknown'
all_candidates = finder.find_all_candidates(dist.key)
if not self.pipfile.get("pre", finder.allow_all_prereleases):
# Remove prereleases
all_candidates = [
candidate for candidate in all_candidates
if not candidate.version.is_prerelease
]
if not all_candidates:
continue
best_candidate = max(all_candidates, key=finder._candidate_sort_key)
remote_version = best_candidate.version
if best_candidate.location.is_wheel:
typ = 'wheel'
else:
typ = 'sdist'
# This is dirty but makes the rest of the code much cleaner
dist.latest_version = remote_version
dist.latest_filetype = typ
yield dist
def get_outdated_packages(self, pre=False):
return [
pkg for pkg in self.get_package_info(pre=pre)
if pkg.latest_version._version > pkg.parsed_version._version
]
def get_package_requirements(self):
from .vendor.pipdeptree import flatten, sorted_tree, build_dist_index, construct_tree
dist_index = build_dist_index(self.get_installed_packages())
tree = sorted_tree(construct_tree(dist_index))
branch_keys = set(r.key for r in flatten(tree.values()))
nodes = [p for p in tree.keys() if p.key not in branch_keys]
key_tree = dict((k.key, v) for k, v in tree.items())
get_children = lambda n: key_tree.get(n.key, [])
def aux(node, parent=None, chain=None):
if chain is None:
chain = [node.project_name]
d = node.as_dict()
if parent:
d['required_version'] = node.version_spec if node.version_spec else 'Any'
else:
d['required_version'] = d['installed_version']
d['dependencies'] = [
aux(c, parent=node, chain=chain+[c.project_name])
for c in get_children(node)
if c.project_name not in chain
]
return d
return [aux(p) for p in nodes]
def get_working_set(self):
"""Retrieve the working set of installed packages for the environment.
:return: The working set for the environment
:rtype: :class:`pkg_resources.WorkingSet`
"""
working_set = pkg_resources.WorkingSet(self.sys_path)
return working_set
def is_installed(self, pkgname):
"""Given a package name, returns whether it is installed in the environment
:param str pkgname: The name of a package
:return: Whether the supplied package is installed in the environment
:rtype: bool
"""
return any(d for d in self.get_distributions() if d.project_name == pkgname)
def run(self, cmd, cwd=os.curdir):
"""Run a command with :class:`~subprocess.Popen` in the context of the environment
:param cmd: A command to run in the environment
:type cmd: str or list
:param str cwd: The working directory in which to execute the command, defaults to :data:`os.curdir`
:return: A finished command object
:rtype: :class:`~subprocess.Popen`
"""
c = None
with self.activated():
script = vistir.cmdparse.Script.parse(cmd)
c = vistir.misc.run(script._parts, return_object=True, nospin=True, cwd=cwd)
return c
def run_py(self, cmd, cwd=os.curdir):
"""Run a python command in the enviornment context.
:param cmd: A command to run in the environment - runs with `python -c`
:type cmd: str or list
:param str cwd: The working directory in which to execute the command, defaults to :data:`os.curdir`
:return: A finished command object
:rtype: :class:`~subprocess.Popen`
"""
c = None
if isinstance(cmd, six.string_types):
script = vistir.cmdparse.Script.parse("{0} -c {1}".format(self.python, cmd))
else:
script = vistir.cmdparse.Script.parse([self.python, "-c"] + list(cmd))
with self.activated():
c = vistir.misc.run(script._parts, return_object=True, nospin=True, cwd=cwd)
return c
def run_activate_this(self):
"""Runs the environment's inline activation script"""
if self.is_venv:
activate_this = os.path.join(self.scripts_dir, "activate_this.py")
if not os.path.isfile(activate_this):
raise OSError("No such file: {0!s}".format(activate_this))
with open(activate_this, "r") as f:
code = compile(f.read(), activate_this, "exec")
exec(code, dict(__file__=activate_this))
@contextlib.contextmanager
def activated(self, include_extras=True, extra_dists=None):
"""Helper context manager to activate the environment.
This context manager will set the following variables for the duration
of its activation:
* sys.prefix
* sys.path
* os.environ["VIRTUAL_ENV"]
* os.environ["PATH"]
In addition, it will make any distributions passed into `extra_dists` available
on `sys.path` while inside the context manager, as well as making `passa` itself
available.
The environment's `prefix` as well as `scripts_dir` properties are both prepended
to `os.environ["PATH"]` to ensure that calls to `~Environment.run()` use the
environment's path preferentially.
"""
if not extra_dists:
extra_dists = []
original_path = sys.path
original_prefix = sys.prefix
parent_path = vistir.compat.Path(__file__).absolute().parent
vendor_dir = parent_path.joinpath("vendor").as_posix()
patched_dir = parent_path.joinpath("patched").as_posix()
parent_path = parent_path.as_posix()
prefix = self.prefix.as_posix()
with vistir.contextmanagers.temp_environ(), vistir.contextmanagers.temp_path():
os.environ["PATH"] = os.pathsep.join([
vistir.compat.fs_str(self.scripts_dir),
vistir.compat.fs_str(self.prefix.as_posix()),
os.environ.get("PATH", "")
])
os.environ["PYTHONIOENCODING"] = vistir.compat.fs_str("utf-8")
os.environ["PYTHONDONTWRITEBYTECODE"] = vistir.compat.fs_str("1")
os.environ["PATH"] = self.base_paths["PATH"]
os.environ["PYTHONPATH"] = self.base_paths["PYTHONPATH"]
if self.is_venv:
os.environ["VIRTUAL_ENV"] = vistir.compat.fs_str(prefix)
sys.path = self.sys_path
sys.prefix = self.sys_prefix
site.addsitedir(self.base_paths["purelib"])
if include_extras:
site.addsitedir(parent_path)
sys.path.extend([parent_path, patched_dir, vendor_dir])
extra_dists = list(self.extra_dists) + extra_dists
for extra_dist in extra_dists:
if extra_dist not in self.get_working_set():
extra_dist.activate(self.sys_path)
try:
yield
finally:
sys.path = original_path
sys.prefix = original_prefix
six.moves.reload_module(pkg_resources)
@cached_property
def finders(self):
from pipenv.vendor.pythonfinder import Finder
finders = [
Finder(path=self.base_paths["scripts"], global_search=gs, system=False)
for gs in (False, True)
]
return finders
@property
def finder(self):
return next(iter(self.finders), None)
def which(self, search, as_path=True):
find = operator.methodcaller("which", search)
result = next(iter(filter(None, (find(finder) for finder in self.finders))), None)
if not result:
result = self._which(search)
else:
if as_path:
result = str(result.path)
return result
def get_install_args(self, editable=False, setup_path=None):
install_arg = "install" if not editable else "develop"
install_keys = ["headers", "purelib", "platlib", "scripts", "data"]
install_args = [
self.environment.python, "-u", "-c", SETUPTOOLS_SHIM % setup_path,
install_arg, "--single-version-externally-managed", "--no-deps",
"--prefix={0}".format(self.base_paths["prefix"]), "--no-warn-script-location"
]
for key in install_keys:
install_args.append(
"--install-{0}={1}".format(key, self.base_paths[key])
)
return install_args
def install(self, requirements):
if not isinstance(requirements, (tuple, list)):
requirements = [requirements,]
with self.get_finder() as finder:
args = []
for format_control in ('no_binary', 'only_binary'):
formats = getattr(finder.format_control, format_control)
args.extend(('--' + format_control.replace('_', '-'),
','.join(sorted(formats or {':none:'}))))
if finder.index_urls:
args.extend(['-i', finder.index_urls[0]])
for extra_index in finder.index_urls[1:]:
args.extend(['--extra-index-url', extra_index])
else:
args.append('--no-index')
for link in finder.find_links:
args.extend(['--find-links', link])
for _, host, _ in finder.secure_origins:
args.extend(['--trusted-host', host])
if finder.allow_all_prereleases:
args.append('--pre')
if finder.process_dependency_links:
args.append('--process-dependency-links')
args.append('--')
args.extend(requirements)
out, _ = vistir.misc.run(args, return_object=False, nospin=True, block=True,
combine_stderr=False)
@contextlib.contextmanager
def uninstall(self, pkgname, *args, **kwargs):
"""A context manager which allows uninstallation of packages from the environment
:param str pkgname: The name of a package to uninstall
>>> env = Environment("/path/to/env/root")
>>> with env.uninstall("pytz", auto_confirm=True, verbose=False) as uninstaller:
cleaned = uninstaller.paths
>>> if cleaned:
print("uninstalled packages: %s" % cleaned)
"""
auto_confirm = kwargs.pop("auto_confirm", True)
verbose = kwargs.pop("verbose", False)
with self.activated():
monkey_patch = next(iter(
dist for dist in self.base_working_set
if dist.project_name == "recursive-monkey-patch"
), None)
if monkey_patch:
monkey_patch.activate()
pip_shims = self.safe_import("pip_shims")
pathset_base = pip_shims.UninstallPathSet
import recursive_monkey_patch
recursive_monkey_patch.monkey_patch(
PatchedUninstaller, pathset_base
)
dist = next(
iter(filter(lambda d: d.project_name == pkgname, self.get_working_set())),
None
)
pathset = pathset_base.from_dist(dist)
if pathset is not None:
pathset.remove(auto_confirm=auto_confirm, verbose=verbose)
try:
yield pathset
except Exception as e:
if pathset is not None:
pathset.rollback()
else:
if pathset is not None:
pathset.commit()
if pathset is None:
return
class PatchedUninstaller(object):
def _permitted(self, path):
return True
SETUPTOOLS_SHIM = (
"import setuptools, tokenize;__file__=%r;"
"f=getattr(tokenize, 'open', open)(__file__);"
"code=f.read().replace('\\r\\n', '\\n');"
"f.close();"
"exec(compile(code, __file__, 'exec'))"
)
+34 -3
View File
@@ -1,13 +1,18 @@
# -*- coding=utf-8 -*-
import os
import sys
from appdirs import user_cache_dir
from .vendor.vistir.misc import fs_str
from ._compat import fix_utf8
# HACK: avoid resolver.py uses the wrong byte code files.
# I hope I can remove this one day.
os.environ["PYTHONDONTWRITEBYTECODE"] = fs_str("1")
PIPENV_IS_CI = bool("CI" in os.environ or "TF_BUILD" in os.environ)
# HACK: Prevent invalid shebangs with Homebrew-installed Python:
# https://bugs.python.org/issue22490
os.environ.pop("__PYVENV_LAUNCHER__", None)
@@ -68,7 +73,7 @@ PIPENV_HIDE_EMOJIS = bool(os.environ.get("PIPENV_HIDE_EMOJIS"))
Default is to show emojis. This is automatically set on Windows.
"""
if os.name == "nt":
if os.name == "nt" or PIPENV_IS_CI:
PIPENV_HIDE_EMOJIS = True
PIPENV_IGNORE_VIRTUALENVS = bool(os.environ.get("PIPENV_IGNORE_VIRTUALENVS"))
@@ -94,7 +99,7 @@ Default is 3. See also ``PIPENV_NO_INHERIT``.
PIPENV_MAX_RETRIES = int(os.environ.get(
"PIPENV_MAX_RETRIES",
"1" if "CI" in os.environ else "0",
"1" if PIPENV_IS_CI else "0",
))
"""Specify how many retries Pipenv should attempt for network requests.
@@ -128,9 +133,18 @@ PIPENV_NOSPIN = bool(os.environ.get("PIPENV_NOSPIN"))
This can make the logs cleaner. Automatically set on Windows, and in CI
environments.
"""
if os.name == "nt" or "CI" in os.environ:
if PIPENV_IS_CI:
PIPENV_NOSPIN = True
PIPENV_SPINNER = "dots"
"""Sets the default spinner type.
Spinners are identitcal to the node.js spinners and can be found at
https://github.com/sindresorhus/cli-spinners
"""
if os.name == "nt":
PIPENV_SPINNER = "bouncingBar"
PIPENV_PIPFILE = os.environ.get("PIPENV_PIPFILE")
"""If set, this specifies a custom Pipfile location.
@@ -197,6 +211,18 @@ Default is to prompt the user for an answer if the current command line session
if interactive.
"""
PIPENV_SKIP_LOCK = False
"""If set, Pipenv won't lock dependencies automatically.
This might be desirable if a project has large number of dependencies,
because locking is an inherently slow operation.
Default is to lock dependencies and update ``Pipfile.lock`` on each run.
NOTE: This only affects the ``install`` and ``uninstall`` commands.
"""
PIPENV_PYUP_API_KEY = os.environ.get("PIPENV_PYUP_API_KEY", "1ab8d58f-5122e025-83674263-bc1e79e0")
# Internal, support running in a different Python from sys.executable.
PIPENV_PYTHON = os.environ.get("PIPENV_PYTHON")
@@ -248,3 +274,8 @@ def is_verbose(threshold=1):
def is_quiet(threshold=-1):
return PIPENV_VERBOSITY <= threshold
PIPENV_SPINNER_FAIL_TEXT = fix_utf8(u"{0}") if not PIPENV_HIDE_EMOJIS else ("{0}")
PIPENV_SPINNER_OK_TEXT = fix_utf8(u"{0}") if not PIPENV_HIDE_EMOJIS else ("{0}")
+306
View File
@@ -0,0 +1,306 @@
# -*- coding=utf-8 -*-
import itertools
import sys
from traceback import format_exception
from pprint import pformat
import six
from ._compat import fix_utf8
from .patched import crayons
from . import environments
from .vendor.click.utils import echo as click_echo
from .vendor.click._compat import get_text_stderr
from .vendor.click.exceptions import (
Abort,
BadOptionUsage,
BadParameter,
ClickException,
Exit,
FileError,
MissingParameter,
UsageError,
)
from .vendor.click.types import Path
def handle_exception(exc_type, exception, traceback, hook=sys.excepthook):
if environments.is_verbose() or not issubclass(exc_type, ClickException):
hook(exc_type, exception, traceback)
else:
exc = format_exception(exc_type, exception, traceback)
lines = itertools.chain.from_iterable([l.splitlines() for l in exc])
lines = list(lines)[-11:-1]
for line in lines:
line = line.strip("'").strip('"').strip("\n").strip()
if not line.startswith("File"):
line = " {0}".format(line)
else:
line = " {0}".format(line)
line = "[pipenv.exceptions.{0!s}]: {1}".format(
exception.__class__.__name__, line
)
click_echo(fix_utf8(line), err=True)
exception.show()
sys.excepthook = handle_exception
class PipenvException(ClickException):
message = "{0}: {{0}}".format(crayons.red("ERROR", bold=True))
def __init__(self, message=None, **kwargs):
if not message:
message = "Pipenv encountered a problem and had to exit."
extra = kwargs.pop("extra", [])
message = self.message.format(message)
ClickException.__init__(self, message)
self.extra = extra
def show(self, file=None):
if file is None:
file = get_text_stderr()
if self.extra:
if isinstance(self.extra, six.string_types):
self.extra = [self.extra,]
for extra in self.extra:
extra = "[pipenv.exceptions.{0!s}]: {1}".format(
self.__class__.__name__, extra
)
click_echo(extra, file=file)
click_echo(fix_utf8("{0}".format(self.message)), file=file)
class PipenvUsageError(UsageError):
def __init__(self, message=None, ctx=None, **kwargs):
formatted_message = "{0}: {1}"
msg_prefix = crayons.red("ERROR:", bold=True)
if not message:
message = "Pipenv encountered a problem and had to exit."
message = formatted_message.format(msg_prefix, crayons.white(message, bold=True))
self.message = message
extra = kwargs.pop("extra", [])
UsageError.__init__(self, fix_utf8(message), ctx)
self.extra = extra
def show(self, file=None):
if file is None:
file = get_text_stderr()
color = None
if self.ctx is not None:
color = self.ctx.color
if self.extra:
if isinstance(self.extra, six.string_types):
self.extra = [self.extra,]
for extra in self.extra:
if color:
extra = getattr(crayons, color, "blue")(extra)
click_echo(fix_utf8(extra), file=file)
hint = ''
if (self.cmd is not None and
self.cmd.get_help_option(self.ctx) is not None):
hint = ('Try "%s %s" for help.\n'
% (self.ctx.command_path, self.ctx.help_option_names[0]))
if self.ctx is not None:
click_echo(self.ctx.get_usage() + '\n%s' % hint, file=file, color=color)
click_echo(self.message, file=file)
class PipenvFileError(FileError):
formatted_message = "{0} {{0}} {{1}}".format(
crayons.red("ERROR:", bold=True)
)
def __init__(self, filename, message=None, **kwargs):
extra = kwargs.pop("extra", [])
if not message:
message = crayons.white("Please ensure that the file exists!", bold=True)
message = self.formatted_message.format(
crayons.white("{0} not found!".format(filename), bold=True),
message
)
FileError.__init__(self, filename=filename, hint=fix_utf8(message), **kwargs)
self.extra = extra
def show(self, file=None):
if file is None:
file = get_text_stderr()
if self.extra:
if isinstance(self.extra, six.string_types):
self.extra = [self.extra,]
for extra in self.extra:
click_echo(fix_utf8(extra), file=file)
click_echo(self.message, file=file)
class PipfileNotFound(PipenvFileError):
def __init__(self, filename="Pipfile", extra=None, **kwargs):
extra = kwargs.pop("extra", [])
message = ("{0} {1}".format(
crayons.red("Aborting!", bold=True),
crayons.white("Please ensure that the file exists and is located in your"
" project root directory.", bold=True)
)
)
super(PipfileNotFound, self).__init__(filename, message=fix_utf8(message), extra=extra, **kwargs)
class LockfileNotFound(PipenvFileError):
def __init__(self, filename="Pipfile.lock", extra=None, **kwargs):
extra = kwargs.pop("extra", [])
message = "{0} {1} {2}".format(
crayons.white("You need to run", bold=True),
crayons.red("$ pipenv lock", bold=True),
crayons.white("before you can continue.", bold=True)
)
super(LockfileNotFound, self).__init__(filename, message=fix_utf8(message), extra=extra, **kwargs)
class DeployException(PipenvUsageError):
def __init__(self, message=None, **kwargs):
if not message:
message = crayons.normal("Aborting deploy", bold=True)
extra = kwargs.pop("extra", [])
PipenvUsageError.__init__(message=fix_utf8(message), extra=extra, **kwargs)
class PipenvOptionsError(PipenvUsageError):
def __init__(self, option_name, message=None, ctx=None, **kwargs):
extra = kwargs.pop("extra", [])
PipenvUsageError.__init__(self, message=fix_utf8(message), ctx=ctx, **kwargs)
self.extra = extra
self.option_name = option_name
class SystemUsageError(PipenvOptionsError):
def __init__(self, option_name="system", message=None, ctx=None, **kwargs):
extra = kwargs.pop("extra", [])
extra += [
"{0}: --system is intended to be used for Pipfile installation, "
"not installation of specific packages. Aborting.".format(
crayons.red("Warning", bold=True)
),
]
message = crayons.blue("See also: {0}".format(crayons.white("-deploy flag.")))
super(SystemUsageError, self).__init__(option_name, message=message, ctx=ctx, extra=extra, **kwargs)
class PipfileException(PipenvFileError):
def __init__(self, hint=None, **kwargs):
from .core import project
if not hint:
hint = "{0} {1}".format(crayons.red("ERROR (PACKAGE NOT INSTALLED):"), hint)
filename = project.pipfile_location
extra = kwargs.pop("extra", [])
PipenvFileError.__init__(self, filename, fix_utf8(hint), extra=extra, **kwargs)
class SetupException(PipenvException):
def __init__(self, message=None, **kwargs):
PipenvException.__init__(self, message, **kwargs)
class VirtualenvException(PipenvException):
def __init__(self, message=None, **kwargs):
if not message:
message = (
"There was an unexpected error while activating your virtualenv. "
"Continuing anyway..."
)
PipenvException.__init__(self, fix_utf8(message), **kwargs)
class VirtualenvActivationException(VirtualenvException):
def __init__(self, message=None, **kwargs):
if not message:
message = (
"activate_this.py not found. Your environment is most certainly "
"not activated. Continuing anyway…"
)
self.message = message
VirtualenvException.__init__(self, fix_utf8(message), **kwargs)
class VirtualenvCreationException(VirtualenvException):
def __init__(self, message=None, **kwargs):
if not message:
message = "Failed to create virtual environment."
self.message = message
VirtualenvException.__init__(self, fix_utf8(message), **kwargs)
class UninstallError(PipenvException):
def __init__(self, package, command, return_values, return_code, **kwargs):
extra = [crayons.blue("Attempted to run command: {0}".format(
crayons.yellow("$ {0}".format(command), bold=True)
)),]
extra.extend([crayons.blue(line.strip()) for line in return_values.splitlines()])
if isinstance(package, (tuple, list)):
package = " ".join(package)
message = "{0} {1}...".format(
crayons.normal("Failed to uninstall package(s)"),
crayons.yellow(package, bold=True)
)
self.exit_code = return_code
PipenvException.__init__(self, message=fix_utf8(message), extra=extra)
self.extra = extra
class InstallError(PipenvException):
def __init__(self, package, **kwargs):
message = "{0} {1}".format(
crayons.red("ERROR:", bold=True),
crayons.yellow("Package installation failed...")
)
extra = kwargs.pop("extra", [])
PipenvException.__init__(self, message=fix_utf8(message), extra=extra, **kwargs)
class CacheError(PipenvException):
def __init__(self, path, **kwargs):
message = "{0} {1} {2}\n{0}".format(
crayons.red("ERROR:", bold=True),
crayons.blue("Corrupt cache file"),
crayons.white(path),
crayons.white('Consider trying "pipenv lock --clear" to clear the cache.')
)
super(PipenvException, self).__init__(message=fix_utf8(message))
class ResolutionFailure(PipenvException):
def __init__(self, message, no_version_found=False):
extra = (
"{0}: Your dependencies could not be resolved. You likely have a "
"mismatch in your sub-dependencies.\n "
"First try clearing your dependency cache with {1}, then try the original command again.\n "
"Alternatively, you can use {2} to bypass this mechanism, then run "
"{3} to inspect the situation.\n "
"Hint: try {4} if it is a pre-release dependency."
"".format(
crayons.red("Warning", bold=True),
crayons.red("$ pipenv lock --clear"),
crayons.red("$ pipenv install --skip-lock"),
crayons.red("$ pipenv graph"),
crayons.red("$ pipenv lock --pre"),
),
)
if "no version found at all" in message:
no_version_found = True
message = "{0} {1}".format(
crayons.red("ERROR:", bold=True), crayons.yellow(message)
)
if no_version_found:
message = "{0}\n{1}".format(
message,
crayons.blue(
"Please check your version specifier and version number. "
"See PEP440 for more information."
)
)
super(ResolutionFailure, self).__init__(fix_utf8(message), extra=extra)
-22
View File
@@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Jumpscale
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.
-48
View File
@@ -1,48 +0,0 @@
from ._version import VERSION
__version__ = VERSION
def loads(text):
"""
Parses TOML text into a dict-like object and returns it.
"""
from prettytoml.parser import parse_tokens
from prettytoml.lexer import tokenize as lexer
from .file import TOMLFile
tokens = tuple(lexer(text, is_top_level=True))
elements = parse_tokens(tokens)
return TOMLFile(elements)
def load(file_path):
"""
Parses a TOML file into a dict-like object and returns it.
"""
return loads(open(file_path).read())
def dumps(value):
"""
Dumps a data structure to TOML source code.
The given value must be either a dict of dict values, a dict, or a TOML file constructed by this module.
"""
from contoml.file.file import TOMLFile
if not isinstance(value, TOMLFile):
raise RuntimeError("Can only dump a TOMLFile instance loaded by load() or loads()")
return value.dumps()
def dump(obj, file_path, prettify=False):
"""
Dumps a data structure to the filesystem as TOML.
The given value must be either a dict of dict values, a dict, or a TOML file constructed by this module.
"""
with open(file_path, 'w') as fp:
fp.write(dumps(obj))
-1
View File
@@ -1 +0,0 @@
VERSION = 'master'
-3
View File
@@ -1,3 +0,0 @@
from .file import TOMLFile
-40
View File
@@ -1,40 +0,0 @@
from prettytoml.elements.table import TableElement
from prettytoml.errors import InvalidValueError
from contoml.file.freshtable import FreshTable
from prettytoml import util
class ArrayOfTables(list):
def __init__(self, toml_file, name, iterable=None):
if iterable:
list.__init__(self, iterable)
self._name = name
self._toml_file = toml_file
def append(self, value):
if isinstance(value, dict):
table = FreshTable(parent=self, name=self._name, is_array=True)
table._append_to_parent()
index = len(self._toml_file[self._name]) - 1
for key_seq, value in util.flatten_nested(value).items():
# self._toml_file._setitem_with_key_seq((self._name, index) + key_seq, value)
self._toml_file._array_setitem_with_key_seq(self._name, index, key_seq, value)
# for k, v in value.items():
# table[k] = v
else:
raise InvalidValueError('Can only append a dict to an array of tables')
def __getitem__(self, item):
try:
return list.__getitem__(self, item)
except IndexError:
if item == len(self):
return FreshTable(parent=self, name=self._name, is_array=True)
else:
raise
def append_fresh_table(self, fresh_table):
list.append(self, fresh_table)
if self._toml_file:
self._toml_file.append_fresh_table(fresh_table)
@@ -1,56 +0,0 @@
import operator
from functools import reduce
from contoml.file import raw
class CascadeDict:
"""
A dict-like object made up of one or more other dict-like objects where querying for an item cascade-gets
it from all the internal dicts in order of their listing, and setting an item sets it on the first dict listed.
"""
def __init__(self, *internal_dicts):
assert internal_dicts, 'internal_dicts cannot be empty'
self._internal_dicts = tuple(internal_dicts)
def cascaded_with(self, one_more_dict):
"""
Returns another instance with one more dict cascaded at the end.
"""
return CascadeDict(self._internal_dicts, one_more_dict,)
def __getitem__(self, item):
for d in self._internal_dicts:
try:
return d[item]
except KeyError:
pass
raise KeyError
def __setitem__(self, key, value):
self._internal_dicts[0][key] = value
def keys(self):
return set(reduce(operator.or_, (set(d.keys()) for d in self._internal_dicts)))
def items(self):
all_items = reduce(operator.add, (list(d.items()) for d in reversed(self._internal_dicts)))
unique_items = {k: v for k, v in all_items}.items()
return tuple(unique_items)
def __contains__(self, item):
for d in self._internal_dicts:
if item in d:
return True
return False
@property
def neutralized(self):
return {k: raw.to_raw(v) for k, v in self.items()}
@property
def primitive_value(self):
return self.neutralized
def __repr__(self):
return repr(self.primitive_value)
-293
View File
@@ -1,293 +0,0 @@
from prettytoml.errors import NoArrayFoundError, DuplicateKeysError, DuplicateTablesError
from contoml.file import structurer, toplevels, raw
from contoml.file.array import ArrayOfTables
from contoml.file.freshtable import FreshTable
import prettytoml.elements.factory as element_factory
import prettytoml.util as util
class TOMLFile:
"""
A TOMLFile object that tries its best to prserve formatting and order of mappings of the input source.
Raises InvalidTOMLFileError on invalid input elements.
Raises DuplicateKeysError, DuplicateTableError when appropriate.
"""
def __init__(self, _elements):
self._elements = []
self._navigable = {}
self.append_elements(_elements)
def __getitem__(self, item):
try:
value = self._navigable[item]
if isinstance(value, (list, tuple)):
return ArrayOfTables(toml_file=self, name=item, iterable=value)
else:
return value
except KeyError:
return FreshTable(parent=self, name=item, is_array=False)
def get(self, item, default=None):
"""This was not here for who knows why."""
if item not in self:
return default
else:
return self.__getitem__(item)
def __contains__(self, item):
return item in self.keys()
def _setitem_with_key_seq(self, key_seq, value):
"""
Sets a the value in the TOML file located by the given key sequence.
Example:
self._setitem(('key1', 'key2', 'key3'), 'text_value')
is equivalent to doing
self['key1']['key2']['key3'] = 'text_value'
"""
table = self
key_so_far = tuple()
for key in key_seq[:-1]:
key_so_far += (key,)
self._make_sure_table_exists(key_so_far)
table = table[key]
table[key_seq[-1]] = value
def _array_setitem_with_key_seq(self, array_name, index, key_seq, value):
"""
Sets a the array value in the TOML file located by the given key sequence.
Example:
self._array_setitem(array_name, index, ('key1', 'key2', 'key3'), 'text_value')
is equivalent to doing
self.array(array_name)[index]['key1']['key2']['key3'] = 'text_value'
"""
table = self.array(array_name)[index]
key_so_far = tuple()
for key in key_seq[:-1]:
key_so_far += (key,)
new_table = self._array_make_sure_table_exists(array_name, index, key_so_far)
if new_table is not None:
table = new_table
else:
table = table[key]
table[key_seq[-1]] = value
def _make_sure_table_exists(self, name_seq):
"""
Makes sure the table with the full name comprising of name_seq exists.
"""
t = self
for key in name_seq[:-1]:
t = t[key]
name = name_seq[-1]
if name not in t:
self.append_elements([element_factory.create_table_header_element(name_seq),
element_factory.create_table({})])
def _array_make_sure_table_exists(self, array_name, index, name_seq):
"""
Makes sure the table with the full name comprising of name_seq exists.
"""
t = self[array_name][index]
for key in name_seq[:-1]:
t = t[key]
name = name_seq[-1]
if name not in t:
new_table = element_factory.create_table({})
self.append_elements([element_factory.create_table_header_element((array_name,) + name_seq), new_table])
return new_table
def __delitem__(self, key):
table_element_index = self._elements.index(self._navigable[key])
self._elements[table_element_index] = element_factory.create_table({})
self._on_element_change()
def __setitem__(self, key, value):
# Setting an array-of-tables
if key and isinstance(value, (tuple, list)) and value and all(isinstance(v, dict) for v in value):
for table in value:
self.array(key).append(table)
# Or setting a whole single table
elif isinstance(value, dict):
if key and key in self:
del self[key]
for key_seq, child_value in util.flatten_nested({key: value}).items():
self._setitem_with_key_seq(key_seq, child_value)
# if key in self._navigable:
# del self[key]
# index = self._elements.index(self._navigable[key])
# self._elements = self._elements[:index] + [element_factory.create_table(value)] + self._elements[index+1:]
# else:
# if key:
# self._elements.append(element_factory.create_table_header_element(key))
# self._elements.append(element_factory.create_table(value))
# Or updating the anonymous section table
else:
# It's mea
self[''][key] = value
self._on_element_change()
def _detect_toplevels(self):
"""
Returns a sequence of TopLevel instances for the current state of this table.
"""
return tuple(e for e in toplevels.identify(self.elements) if isinstance(e, toplevels.Table))
def _update_table_fallbacks(self, table_toplevels):
"""
Updates the fallbacks on all the table elements to make relative table access possible.
Raises DuplicateKeysError if appropriate.
"""
if len(self.elements) <= 1:
return
def parent_of(toplevel):
# Returns an TopLevel parent of the given entry, or None.
for parent_toplevel in table_toplevels:
if toplevel.name.sub_names[:-1] == parent_toplevel.name.sub_names:
return parent_toplevel
for entry in table_toplevels:
if entry.name.is_qualified:
parent = parent_of(entry)
if parent:
child_name = entry.name.without_prefix(parent.name)
parent.table_element.set_fallback({child_name.sub_names[0]: entry.table_element})
def _recreate_navigable(self):
if self._elements:
self._navigable = structurer.structure(toplevels.identify(self._elements))
def array(self, name):
"""
Returns the array of tables with the given name.
"""
if name in self._navigable:
if isinstance(self._navigable[name], (list, tuple)):
return self[name]
else:
raise NoArrayFoundError
else:
return ArrayOfTables(toml_file=self, name=name)
def _on_element_change(self):
self._recreate_navigable()
table_toplevels = self._detect_toplevels()
self._update_table_fallbacks(table_toplevels)
def append_elements(self, elements):
"""
Appends more elements to the contained internal elements.
"""
self._elements = self._elements + list(elements)
self._on_element_change()
def prepend_elements(self, elements):
"""
Prepends more elements to the contained internal elements.
"""
self._elements = list(elements) + self._elements
self._on_element_change()
def dumps(self):
"""
Returns the TOML file serialized back to str.
"""
return ''.join(element.serialized() for element in self._elements)
def dump(self, file_path):
with open(file_path, mode='w') as fp:
fp.write(self.dumps())
def keys(self):
return set(self._navigable.keys()) | {''}
def values(self):
return self._navigable.values()
def items(self):
items = self._navigable.items()
def has_anonymous_entry():
return any(key == '' for (key, _) in items)
if has_anonymous_entry():
return items
else:
return list(items) + [('', self[''])]
@property
def primitive(self):
"""
Returns a primitive object representation for this container (which is a dict).
WARNING: The returned container does not contain any markup or formatting metadata.
"""
raw_container = raw.to_raw(self._navigable)
# Collapsing the anonymous table onto the top-level container is present
if '' in raw_container:
raw_container.update(raw_container[''])
del raw_container['']
return raw_container
def append_fresh_table(self, fresh_table):
"""
Gets called by FreshTable instances when they get written to.
"""
if fresh_table.name:
elements = []
if fresh_table.is_array:
elements += [element_factory.create_array_of_tables_header_element(fresh_table.name)]
else:
elements += [element_factory.create_table_header_element(fresh_table.name)]
elements += [fresh_table, element_factory.create_newline_element()]
self.append_elements(elements)
else:
# It's an anonymous table
self.prepend_elements([fresh_table, element_factory.create_newline_element()])
@property
def elements(self):
return self._elements
def __str__(self):
is_empty = (not self['']) and (not tuple(k for k in self.keys() if k))
def key_name(key):
return '[ANONYMOUS]' if not key else key
def pair(key, value):
return '%s = %s' % (key_name(key), str(value))
content_text = '' if is_empty else \
'\n\t' + ',\n\t'.join(pair(k, v) for (k, v) in self.items() if v) + '\n'
return "TOMLFile{%s}" % content_text
def __repr__(self):
return str(self)
-45
View File
@@ -1,45 +0,0 @@
from prettytoml.elements.table import TableElement
class FreshTable(TableElement):
"""
A fresh TableElement that appended itself to each of parents when it first gets written to at most once.
parents is a sequence of objects providing an append_fresh_table(TableElement) method
"""
def __init__(self, parent, name, is_array=False):
TableElement.__init__(self, sub_elements=[])
self._parent = parent
self._name = name
self._is_array = is_array
# As long as this flag is false, setitem() operations will append the table header and this table
# to the toml_file's elements
self.__appended = False
@property
def name(self):
return self._name
@property
def is_array(self):
return self._is_array
def _append_to_parent(self):
"""
Causes this ephemeral table to be persisted on the TOMLFile.
"""
if self.__appended:
return
if self._parent is not None:
self._parent.append_fresh_table(self)
self.__appended = True
def __setitem__(self, key, value):
TableElement.__setitem__(self, key, value)
self._append_to_parent()
-30
View File
@@ -1,30 +0,0 @@
import itertools
class PeekableIterator:
# Returned by peek() when the iterator is exhausted. Truthiness is False.
Nothing = tuple()
def __init__(self, iter):
self._iter = iter
def __next__(self):
return next(self._iter)
def next(self):
return self.__next__()
def __iter__(self):
return self
def peek(self):
"""
Returns PeekableIterator.Nothing when the iterator is exhausted.
"""
try:
v = next(self._iter)
self._iter = itertools.chain((v,), self._iter)
return v
except StopIteration:
return PeekableIterator.Nothing
-16
View File
@@ -1,16 +0,0 @@
from prettytoml.elements.abstracttable import AbstractTable
def to_raw(x):
from contoml.file.cascadedict import CascadeDict
if isinstance(x, AbstractTable):
return x.primitive_value
elif isinstance(x, CascadeDict):
return x.neutralized
elif isinstance(x, (list, tuple)):
return [to_raw(y) for y in x]
elif isinstance(x, dict):
return {k: to_raw(v) for (k, v) in x.items()}
else:
return x
-116
View File
@@ -1,116 +0,0 @@
from contoml.file import toplevels
from contoml.file.cascadedict import CascadeDict
class NamedDict(dict):
"""
A dict that can use Name instances as keys.
"""
def __init__(self, other_dict=None):
dict.__init__(self)
if other_dict:
for k, v in other_dict.items():
self[k] = v
def __setitem__(self, key, value):
"""
key can be an Name instance.
When key is a path in the form of an Name instance, all the parents and grandparents of the value are
created along the way as instances of NamedDict. If the parent of the value exists, it is replaced with a
CascadeDict() that cascades the old parent value with a new NamedDict that contains the given child name
and value.
"""
if isinstance(key, toplevels.Name):
if len(key.sub_names) == 1:
name = key.sub_names[0]
if name in self:
self[name] = CascadeDict(self[name], value)
else:
self[name] = value
elif len(key.sub_names) > 1:
name = key.sub_names[0]
rest_of_key = key.drop(1)
if name in self:
named_dict = NamedDict()
named_dict[rest_of_key] = value
self[name] = CascadeDict(self[name], named_dict)
else:
self[name] = NamedDict()
self[name][rest_of_key] = value
else:
return dict.__setitem__(self, key, value)
def __contains__(self, item):
try:
_ = self[item]
return True
except KeyError:
return False
def append(self, key, value):
"""
Makes sure the value pointed to by key exists and is a list and appends the given value to it.
"""
if key in self:
self[key].append(value)
else:
self[key] = [value]
def __getitem__(self, item):
if isinstance(item, toplevels.Name):
d = self
for name in item.sub_names:
d = d[name]
return d
else:
return dict.__getitem__(self, item)
def structure(table_toplevels):
"""
Accepts an ordered sequence of TopLevel instances and returns a navigable object structure representation of the
TOML file.
"""
table_toplevels = tuple(table_toplevels)
obj = NamedDict()
last_array_of_tables = None # The Name of the last array-of-tables header
for toplevel in table_toplevels:
if isinstance(toplevel, toplevels.AnonymousTable):
obj[''] = toplevel.table_element
elif isinstance(toplevel, toplevels.Table):
if last_array_of_tables and toplevel.name.is_prefixed_with(last_array_of_tables):
seq = obj[last_array_of_tables]
unprefixed_name = toplevel.name.without_prefix(last_array_of_tables)
seq[-1] = CascadeDict(seq[-1], NamedDict({unprefixed_name: toplevel.table_element}))
else:
obj[toplevel.name] = toplevel.table_element
else: # It's an ArrayOfTables
if last_array_of_tables and toplevel.name != last_array_of_tables and \
toplevel.name.is_prefixed_with(last_array_of_tables):
seq = obj[last_array_of_tables]
unprefixed_name = toplevel.name.without_prefix(last_array_of_tables)
if unprefixed_name in seq[-1]:
seq[-1][unprefixed_name].append(toplevel.table_element)
else:
cascaded_with = NamedDict({unprefixed_name: [toplevel.table_element]})
seq[-1] = CascadeDict(seq[-1], cascaded_with)
else:
obj.append(toplevel.name, toplevel.table_element)
last_array_of_tables = toplevel.name
return obj
@@ -1,25 +0,0 @@
from contoml.file.cascadedict import CascadeDict
def test_cascadedict():
d1 = {'a': 1, 'b': 2, 'c': 3}
d2 = {'b': 12, 'e': 4, 'f': 5}
cascade = CascadeDict(d1, d2)
# Test querying
assert cascade['a'] == 1
assert cascade['b'] == 2
assert cascade['c'] == 3
assert cascade['e'] == 4
assert cascade.keys() == {'a', 'b', 'c', 'e', 'f'}
assert set(cascade.items()) == {('a', 1), ('b', 2), ('c', 3), ('e', 4), ('f', 5)}
# Test mutating
cascade['a'] = 11
cascade['f'] = 'fff'
cascade['super'] = 'man'
assert d1['a'] == 11
assert d1['super'] == 'man'
assert d1['f'] == 'fff'
@@ -1,20 +0,0 @@
from prettytoml import parser, lexer
from contoml.file import toplevels
def test_entry_extraction():
text = open('sample.toml').read()
elements = parser.parse_tokens(lexer.tokenize(text))
e = tuple(toplevels.identify(elements))
assert len(e) == 13
assert isinstance(e[0], toplevels.AnonymousTable)
def test_entry_names():
name_a = toplevels.Name(('super', 'sub1'))
name_b = toplevels.Name(('super', 'sub1', 'sub2', 'sub3'))
assert name_b.is_prefixed_with(name_a)
assert name_b.without_prefix(name_a).sub_names == ('sub2', 'sub3')
@@ -1,12 +0,0 @@
from contoml.file.peekableit import PeekableIterator
def test_peekable_iterator():
peekable = PeekableIterator(i for i in (1, 2, 3, 4))
assert peekable.peek() == 1
assert peekable.peek() == 1
assert peekable.peek() == 1
assert [next(peekable), next(peekable), next(peekable), next(peekable)] == [1, 2, 3, 4]
@@ -1,41 +0,0 @@
from prettytoml import lexer, parser
from contoml.file import toplevels
from prettytoml.parser import elementsanitizer
from contoml.file.structurer import NamedDict, structure
from prettytoml.parser.tokenstream import TokenStream
def test_NamedDict():
d = NamedDict()
d[toplevels.Name(('super', 'sub1', 'sub2'))] = {'sub3': 12}
d[toplevels.Name(('super', 'sub1', 'sub2'))]['sub4'] = 42
assert d[toplevels.Name(('super', 'sub1', 'sub2', 'sub3'))] == 12
assert d[toplevels.Name(('super', 'sub1', 'sub2', 'sub4'))] == 42
def test_structure():
tokens = lexer.tokenize(open('sample.toml').read())
elements = elementsanitizer.sanitize(parser.parse_tokens(tokens))
entries_ = tuple(toplevels.identify(elements))
s = structure(entries_)
assert s['']['title'] == 'TOML Example'
assert s['owner']['name'] == 'Tom Preston-Werner'
assert s['database']['ports'][1] == 8001
assert s['servers']['alpha']['dc'] == 'eqdc10'
assert s['clients']['data'][1][0] == 1
assert s['clients']['key3'] == 'The quick brown fox jumps over the lazy dog.'
assert s['fruit'][0]['name'] == 'apple'
assert s['fruit'][0]['physical']['color'] == 'red'
assert s['fruit'][0]['physical']['shape'] == 'round'
assert s['fruit'][0]['variety'][0]['name'] == 'red delicious'
assert s['fruit'][0]['variety'][1]['name'] == 'granny smith'
assert s['fruit'][1]['name'] == 'banana'
assert s['fruit'][1]['variety'][0]['name'] == 'plantain'
assert s['fruit'][1]['variety'][0]['points'][2]['y'] == 4
-142
View File
@@ -1,142 +0,0 @@
"""
Top-level entries in a TOML file.
"""
from prettytoml import elements
from prettytoml.elements import TableElement, TableHeaderElement
from .peekableit import PeekableIterator
class TopLevel:
"""
A abstract top-level entry.
"""
def __init__(self, names, table_element):
self._table_element = table_element
self._names = Name(names)
@property
def table_element(self):
return self._table_element
@property
def name(self):
"""
The distinct name of a table entry as an Name instance.
"""
return self._names
class Name:
def __init__(self, names):
self._names = names
@property
def sub_names(self):
return self._names
def drop(self, n=0):
"""
Returns the name after dropping the first n entries of it.
"""
return Name(names=self._names[n:])
def is_prefixed_with(self, names):
if isinstance(names, Name):
return self.is_prefixed_with(names.sub_names)
for i, name in enumerate(names):
if self._names[i] != name:
return False
return True
def without_prefix(self, names):
if isinstance(names, Name):
return self.without_prefix(names.sub_names)
for i, name in enumerate(names):
if name != self._names[i]:
return Name(self._names[i:])
return Name(names=self.sub_names[len(names):])
@property
def is_qualified(self):
return len(self._names) > 1
def __str__(self):
return '.'.join(self.sub_names)
def __hash__(self):
return hash(str(self))
def __eq__(self, other):
return str(self) == str(other)
def __ne__(self, other):
return not self.__eq__(other)
class AnonymousTable(TopLevel):
def __init__(self, table_element):
TopLevel.__init__(self, ('',), table_element)
class Table(TopLevel):
def __init__(self, names, table_element):
TopLevel.__init__(self, names=names, table_element=table_element)
class ArrayOfTables(TopLevel):
def __init__(self, names, table_element):
TopLevel.__init__(self, names=names, table_element=table_element)
def _validate_file_elements(file_elements):
pass
def identify(file_elements):
"""
Outputs an ordered sequence of instances of TopLevel types.
Elements start with an optional TableElement, followed by zero or more pairs of (TableHeaderElement, TableElement).
"""
if not file_elements:
return
_validate_file_elements(file_elements)
# An iterator over enumerate(the non-metadata) elements
iterator = PeekableIterator((element_i, element) for (element_i, element) in enumerate(file_elements)
if element.type != elements.TYPE_METADATA)
try:
_, first_element = iterator.peek()
if isinstance(first_element, TableElement):
iterator.next()
yield AnonymousTable(first_element)
except KeyError:
pass
except StopIteration:
return
for element_i, element in iterator:
if not isinstance(element, TableHeaderElement):
continue
# If TableHeader of a regular table, return Table following it
if not element.is_array_of_tables:
table_element_i, table_element = next(iterator)
yield Table(names=element.names, table_element=table_element)
# If TableHeader of an array of tables, do your thing
else:
table_element_i, table_element = next(iterator)
yield ArrayOfTables(names=element.names, table_element=table_element)
+1 -1
View File
@@ -1 +1 @@
__version__ = "18.0"
__version__ = "18.1"
+6 -238
View File
@@ -4,7 +4,6 @@ from __future__ import absolute_import
import locale
import logging
import os
import optparse
import warnings
import sys
@@ -38,17 +37,12 @@ else:
else:
securetransport.inject_into_urllib3()
from pipenv.patched.notpip import __version__
from pipenv.patched.notpip._internal import cmdoptions
from pipenv.patched.notpip._internal.exceptions import CommandError, PipError
from pipenv.patched.notpip._internal.utils.misc import get_installed_distributions, get_prog
from pipenv.patched.notpip._internal.cli.autocompletion import autocomplete
from pipenv.patched.notpip._internal.cli.main_parser import parse_command
from pipenv.patched.notpip._internal.commands import commands_dict
from pipenv.patched.notpip._internal.exceptions import PipError
from pipenv.patched.notpip._internal.utils import deprecation
from pipenv.patched.notpip._internal.vcs import git, mercurial, subversion, bazaar # noqa
from pipenv.patched.notpip._internal.baseparser import (
ConfigOptionParser, UpdatingDefaultsHelpFormatter,
)
from pipenv.patched.notpip._internal.commands import get_summaries, get_similar_commands
from pipenv.patched.notpip._internal.commands import commands_dict
from pipenv.patched.notpip._vendor.urllib3.exceptions import InsecureRequestWarning
logger = logging.getLogger(__name__)
@@ -57,232 +51,6 @@ logger = logging.getLogger(__name__)
warnings.filterwarnings("ignore", category=InsecureRequestWarning)
def autocomplete():
"""Command and option completion for the main option parser (and options)
and its subcommands (and options).
Enable by sourcing one of the completion shell scripts (bash, zsh or fish).
"""
# Don't complete if user hasn't sourced bash_completion file.
if 'PIP_AUTO_COMPLETE' not in os.environ:
return
cwords = os.environ['COMP_WORDS'].split()[1:]
cword = int(os.environ['COMP_CWORD'])
try:
current = cwords[cword - 1]
except IndexError:
current = ''
subcommands = [cmd for cmd, summary in get_summaries()]
options = []
# subcommand
try:
subcommand_name = [w for w in cwords if w in subcommands][0]
except IndexError:
subcommand_name = None
parser = create_main_parser()
# subcommand options
if subcommand_name:
# special case: 'help' subcommand has no options
if subcommand_name == 'help':
sys.exit(1)
# special case: list locally installed dists for show and uninstall
should_list_installed = (
subcommand_name in ['show', 'uninstall'] and
not current.startswith('-')
)
if should_list_installed:
installed = []
lc = current.lower()
for dist in get_installed_distributions(local_only=True):
if dist.key.startswith(lc) and dist.key not in cwords[1:]:
installed.append(dist.key)
# if there are no dists installed, fall back to option completion
if installed:
for dist in installed:
print(dist)
sys.exit(1)
subcommand = commands_dict[subcommand_name]()
for opt in subcommand.parser.option_list_all:
if opt.help != optparse.SUPPRESS_HELP:
for opt_str in opt._long_opts + opt._short_opts:
options.append((opt_str, opt.nargs))
# filter out previously specified options from available options
prev_opts = [x.split('=')[0] for x in cwords[1:cword - 1]]
options = [(x, v) for (x, v) in options if x not in prev_opts]
# filter options by current input
options = [(k, v) for k, v in options if k.startswith(current)]
# get completion type given cwords and available subcommand options
completion_type = get_path_completion_type(
cwords, cword, subcommand.parser.option_list_all,
)
# get completion files and directories if ``completion_type`` is
# ``<file>``, ``<dir>`` or ``<path>``
if completion_type:
options = auto_complete_paths(current, completion_type)
options = ((opt, 0) for opt in options)
for option in options:
opt_label = option[0]
# append '=' to options which require args
if option[1] and option[0][:2] == "--":
opt_label += '='
print(opt_label)
else:
# show main parser options only when necessary
opts = [i.option_list for i in parser.option_groups]
opts.append(parser.option_list)
opts = (o for it in opts for o in it)
if current.startswith('-'):
for opt in opts:
if opt.help != optparse.SUPPRESS_HELP:
subcommands += opt._long_opts + opt._short_opts
else:
# get completion type given cwords and all available options
completion_type = get_path_completion_type(cwords, cword, opts)
if completion_type:
subcommands = auto_complete_paths(current, completion_type)
print(' '.join([x for x in subcommands if x.startswith(current)]))
sys.exit(1)
def get_path_completion_type(cwords, cword, opts):
"""Get the type of path completion (``file``, ``dir``, ``path`` or None)
:param cwords: same as the environmental variable ``COMP_WORDS``
:param cword: same as the environmental variable ``COMP_CWORD``
:param opts: The available options to check
:return: path completion type (``file``, ``dir``, ``path`` or None)
"""
if cword < 2 or not cwords[cword - 2].startswith('-'):
return
for opt in opts:
if opt.help == optparse.SUPPRESS_HELP:
continue
for o in str(opt).split('/'):
if cwords[cword - 2].split('=')[0] == o:
if any(x in ('path', 'file', 'dir')
for x in opt.metavar.split('/')):
return opt.metavar
def auto_complete_paths(current, completion_type):
"""If ``completion_type`` is ``file`` or ``path``, list all regular files
and directories starting with ``current``; otherwise only list directories
starting with ``current``.
:param current: The word to be completed
:param completion_type: path completion type(`file`, `path` or `dir`)i
:return: A generator of regular files and/or directories
"""
directory, filename = os.path.split(current)
current_path = os.path.abspath(directory)
# Don't complete paths if they can't be accessed
if not os.access(current_path, os.R_OK):
return
filename = os.path.normcase(filename)
# list all files that start with ``filename``
file_list = (x for x in os.listdir(current_path)
if os.path.normcase(x).startswith(filename))
for f in file_list:
opt = os.path.join(current_path, f)
comp_file = os.path.normcase(os.path.join(directory, f))
# complete regular files when there is not ``<dir>`` after option
# complete directories when there is ``<file>``, ``<path>`` or
# ``<dir>``after option
if completion_type != 'dir' and os.path.isfile(opt):
yield comp_file
elif os.path.isdir(opt):
yield os.path.join(comp_file, '')
def create_main_parser():
parser_kw = {
'usage': '\n%prog <command> [options]',
'add_help_option': False,
'formatter': UpdatingDefaultsHelpFormatter(),
'name': 'global',
'prog': get_prog(),
}
parser = ConfigOptionParser(**parser_kw)
parser.disable_interspersed_args()
pip_pkg_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
parser.version = 'pip %s from %s (python %s)' % (
__version__, pip_pkg_dir, sys.version[:3],
)
# add the general options
gen_opts = cmdoptions.make_option_group(cmdoptions.general_group, parser)
parser.add_option_group(gen_opts)
parser.main = True # so the help formatter knows
# create command listing for description
command_summaries = get_summaries()
description = [''] + ['%-27s %s' % (i, j) for i, j in command_summaries]
parser.description = '\n'.join(description)
return parser
def parseopts(args):
parser = create_main_parser()
# Note: parser calls disable_interspersed_args(), so the result of this
# call is to split the initial args into the general options before the
# subcommand and everything else.
# For example:
# args: ['--timeout=5', 'install', '--user', 'INITools']
# general_options: ['--timeout==5']
# args_else: ['install', '--user', 'INITools']
general_options, args_else = parser.parse_args(args)
# --version
if general_options.version:
sys.stdout.write(parser.version)
sys.stdout.write(os.linesep)
sys.exit()
# pip || pip help -> print_help()
if not args_else or (args_else[0] == 'help' and len(args_else) == 1):
parser.print_help()
sys.exit()
# the subcommand name
cmd_name = args_else[0]
if cmd_name not in commands_dict:
guess = get_similar_commands(cmd_name)
msg = ['unknown command "%s"' % cmd_name]
if guess:
msg.append('maybe you meant "%s"' % guess)
raise CommandError(' - '.join(msg))
# all the args without the subcommand
cmd_args = args[:]
cmd_args.remove(cmd_name)
return cmd_name, cmd_args
def check_isolated(args):
isolated = False
if "--isolated" in args:
isolated = True
return isolated
def main(args=None):
if args is None:
args = sys.argv[1:]
@@ -293,7 +61,7 @@ def main(args=None):
autocomplete()
try:
cmd_name, cmd_args = parseopts(args)
cmd_name, cmd_args = parse_command(args)
except PipError as exc:
sys.stderr.write("ERROR: %s" % exc)
sys.stderr.write(os.linesep)
@@ -306,5 +74,5 @@ def main(args=None):
except locale.Error as e:
# setlocale can apparently crash if locale are uninitialized
logger.debug("Ignoring error %s when setting locale", e)
command = commands_dict[cmd_name](isolated=check_isolated(cmd_args))
command = commands_dict[cmd_name](isolated=("--isolated" in cmd_args))
return command.main(cmd_args)
@@ -7,6 +7,8 @@ import sys
from distutils.sysconfig import get_python_lib
from sysconfig import get_paths
from pipenv.patched.notpip._vendor.pkg_resources import Requirement, VersionConflict, WorkingSet
from pipenv.patched.notpip._internal.utils.misc import call_subprocess
from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory
from pipenv.patched.notpip._internal.utils.ui import open_spinner
@@ -75,6 +77,20 @@ class BuildEnvironment(object):
def cleanup(self):
self._temp_dir.cleanup()
def missing_requirements(self, reqs):
"""Return a list of the requirements from reqs that are not present
"""
missing = []
with self:
ws = WorkingSet(os.environ["PYTHONPATH"].split(os.pathsep))
for req in reqs:
try:
if ws.find(Requirement.parse(req)) is None:
missing.append(req)
except VersionConflict:
missing.append(req)
return missing
def install_requirements(self, finder, requirements, message):
args = [
sys.executable, '-m', 'pip', 'install', '--ignore-installed',
+6 -6
View File
@@ -8,9 +8,9 @@ import os
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
from pipenv.patched.notpip._internal import index
from pipenv.patched.notpip._internal.compat import expanduser
from pipenv.patched.notpip._internal.download import path_to_url
from pipenv.patched.notpip._internal.models.link import Link
from pipenv.patched.notpip._internal.utils.compat import expanduser
from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory
from pipenv.patched.notpip._internal.wheel import InvalidWheelFilename, Wheel
@@ -22,7 +22,7 @@ class Cache(object):
:param cache_dir: The root of the cache.
:param format_control: A pip.index.FormatControl object to limit
:param format_control: An object of FormatControl class to limit
binaries being read from the cache.
:param allowed_formats: which formats of files the cache should store.
('binary' and 'source' are the only allowed values)
@@ -72,8 +72,8 @@ class Cache(object):
return []
canonical_name = canonicalize_name(package_name)
formats = index.fmt_ctl_formats(
self.format_control, canonical_name
formats = self.format_control.get_allowed_formats(
canonical_name
)
if not self.allowed_formats.intersection(formats):
return []
@@ -101,7 +101,7 @@ class Cache(object):
root = self.get_path_for_link(link)
path = os.path.join(root, candidate)
return index.Link(path_to_url(path))
return Link(path_to_url(path))
def cleanup(self):
pass
@@ -0,0 +1,4 @@
"""Subpackage containing all of pip's command line interface related code
"""
# This file intentionally does not import submodules
@@ -0,0 +1,152 @@
"""Logic that powers autocompletion installed by ``pip completion``.
"""
import optparse
import os
import sys
from pipenv.patched.notpip._internal.cli.main_parser import create_main_parser
from pipenv.patched.notpip._internal.commands import commands_dict, get_summaries
from pipenv.patched.notpip._internal.utils.misc import get_installed_distributions
def autocomplete():
"""Entry Point for completion of main and subcommand options.
"""
# Don't complete if user hasn't sourced bash_completion file.
if 'PIP_AUTO_COMPLETE' not in os.environ:
return
cwords = os.environ['COMP_WORDS'].split()[1:]
cword = int(os.environ['COMP_CWORD'])
try:
current = cwords[cword - 1]
except IndexError:
current = ''
subcommands = [cmd for cmd, summary in get_summaries()]
options = []
# subcommand
try:
subcommand_name = [w for w in cwords if w in subcommands][0]
except IndexError:
subcommand_name = None
parser = create_main_parser()
# subcommand options
if subcommand_name:
# special case: 'help' subcommand has no options
if subcommand_name == 'help':
sys.exit(1)
# special case: list locally installed dists for show and uninstall
should_list_installed = (
subcommand_name in ['show', 'uninstall'] and
not current.startswith('-')
)
if should_list_installed:
installed = []
lc = current.lower()
for dist in get_installed_distributions(local_only=True):
if dist.key.startswith(lc) and dist.key not in cwords[1:]:
installed.append(dist.key)
# if there are no dists installed, fall back to option completion
if installed:
for dist in installed:
print(dist)
sys.exit(1)
subcommand = commands_dict[subcommand_name]()
for opt in subcommand.parser.option_list_all:
if opt.help != optparse.SUPPRESS_HELP:
for opt_str in opt._long_opts + opt._short_opts:
options.append((opt_str, opt.nargs))
# filter out previously specified options from available options
prev_opts = [x.split('=')[0] for x in cwords[1:cword - 1]]
options = [(x, v) for (x, v) in options if x not in prev_opts]
# filter options by current input
options = [(k, v) for k, v in options if k.startswith(current)]
# get completion type given cwords and available subcommand options
completion_type = get_path_completion_type(
cwords, cword, subcommand.parser.option_list_all,
)
# get completion files and directories if ``completion_type`` is
# ``<file>``, ``<dir>`` or ``<path>``
if completion_type:
options = auto_complete_paths(current, completion_type)
options = ((opt, 0) for opt in options)
for option in options:
opt_label = option[0]
# append '=' to options which require args
if option[1] and option[0][:2] == "--":
opt_label += '='
print(opt_label)
else:
# show main parser options only when necessary
opts = [i.option_list for i in parser.option_groups]
opts.append(parser.option_list)
opts = (o for it in opts for o in it)
if current.startswith('-'):
for opt in opts:
if opt.help != optparse.SUPPRESS_HELP:
subcommands += opt._long_opts + opt._short_opts
else:
# get completion type given cwords and all available options
completion_type = get_path_completion_type(cwords, cword, opts)
if completion_type:
subcommands = auto_complete_paths(current, completion_type)
print(' '.join([x for x in subcommands if x.startswith(current)]))
sys.exit(1)
def get_path_completion_type(cwords, cword, opts):
"""Get the type of path completion (``file``, ``dir``, ``path`` or None)
:param cwords: same as the environmental variable ``COMP_WORDS``
:param cword: same as the environmental variable ``COMP_CWORD``
:param opts: The available options to check
:return: path completion type (``file``, ``dir``, ``path`` or None)
"""
if cword < 2 or not cwords[cword - 2].startswith('-'):
return
for opt in opts:
if opt.help == optparse.SUPPRESS_HELP:
continue
for o in str(opt).split('/'):
if cwords[cword - 2].split('=')[0] == o:
if not opt.metavar or any(
x in ('path', 'file', 'dir')
for x in opt.metavar.split('/')):
return opt.metavar
def auto_complete_paths(current, completion_type):
"""If ``completion_type`` is ``file`` or ``path``, list all regular files
and directories starting with ``current``; otherwise only list directories
starting with ``current``.
:param current: The word to be completed
:param completion_type: path completion type(`file`, `path` or `dir`)i
:return: A generator of regular files and/or directories
"""
directory, filename = os.path.split(current)
current_path = os.path.abspath(directory)
# Don't complete paths if they can't be accessed
if not os.access(current_path, os.R_OK):
return
filename = os.path.normcase(filename)
# list all files that start with ``filename``
file_list = (x for x in os.listdir(current_path)
if os.path.normcase(x).startswith(filename))
for f in file_list:
opt = os.path.join(current_path, f)
comp_file = os.path.normcase(os.path.join(directory, f))
# complete regular files when there is not ``<dir>`` after option
# complete directories when there is ``<file>``, ``<path>`` or
# ``<dir>``after option
if completion_type != 'dir' and os.path.isfile(opt):
yield comp_file
elif os.path.isdir(opt):
yield os.path.join(comp_file, '')
@@ -7,10 +7,14 @@ import optparse
import os
import sys
from pipenv.patched.notpip._internal import cmdoptions
from pipenv.patched.notpip._internal.baseparser import (
from pipenv.patched.notpip._internal.cli import cmdoptions
from pipenv.patched.notpip._internal.cli.parser import (
ConfigOptionParser, UpdatingDefaultsHelpFormatter,
)
from pipenv.patched.notpip._internal.cli.status_codes import (
ERROR, PREVIOUS_BUILD_DIR_ERROR, SUCCESS, UNKNOWN_ERROR,
VIRTUALENV_NOT_FOUND,
)
from pipenv.patched.notpip._internal.download import PipSession
from pipenv.patched.notpip._internal.exceptions import (
BadCommand, CommandError, InstallationError, PreviousBuildDirError,
@@ -18,12 +22,10 @@ from pipenv.patched.notpip._internal.exceptions import (
)
from pipenv.patched.notpip._internal.index import PackageFinder
from pipenv.patched.notpip._internal.locations import running_under_virtualenv
from pipenv.patched.notpip._internal.req.req_file import parse_requirements
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
from pipenv.patched.notpip._internal.status_codes import (
ERROR, PREVIOUS_BUILD_DIR_ERROR, SUCCESS, UNKNOWN_ERROR,
VIRTUALENV_NOT_FOUND,
from pipenv.patched.notpip._internal.req.constructors import (
install_req_from_editable, install_req_from_line,
)
from pipenv.patched.notpip._internal.req.req_file import parse_requirements
from pipenv.patched.notpip._internal.utils.logging import setup_logging
from pipenv.patched.notpip._internal.utils.misc import get_prog, normalize_path
from pipenv.patched.notpip._internal.utils.outdated import pip_version_check
@@ -168,12 +170,14 @@ class Command(object):
return UNKNOWN_ERROR
finally:
# Check if we're using the latest version of pip available
skip_version_check = (
options.disable_pip_version_check or
getattr(options, "no_index", False)
allow_version_check = (
# Does this command have the index_group options?
hasattr(options, "no_index") and
# Is this command allowed to perform this check?
not (options.disable_pip_version_check or options.no_index)
)
if not skip_version_check:
# Check if we're using the latest version of pip available
if allow_version_check:
session = self._build_session(
options,
retries=0,
@@ -208,7 +212,7 @@ class RequirementCommand(Command):
requirement_set.add_requirement(req_to_add)
for req in args:
req_to_add = InstallRequirement.from_line(
req_to_add = install_req_from_line(
req, None, isolated=options.isolated_mode,
wheel_cache=wheel_cache
)
@@ -216,7 +220,7 @@ class RequirementCommand(Command):
requirement_set.add_requirement(req_to_add)
for req in options.editables:
req_to_add = InstallRequirement.from_editable(
req_to_add = install_req_from_editable(
req,
isolated=options.isolated_mode,
wheel_cache=wheel_cache
@@ -13,10 +13,9 @@ import warnings
from functools import partial
from optparse import SUPPRESS_HELP, Option, OptionGroup
from pipenv.patched.notpip._internal.index import (
FormatControl, fmt_ctl_handle_mutual_exclude, fmt_ctl_no_binary,
)
from pipenv.patched.notpip._internal.exceptions import CommandError
from pipenv.patched.notpip._internal.locations import USER_CACHE_DIR, src_prefix
from pipenv.patched.notpip._internal.models.format_control import FormatControl
from pipenv.patched.notpip._internal.models.index import PyPI
from pipenv.patched.notpip._internal.utils.hashes import STRONG_HASHES
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
@@ -53,13 +52,52 @@ def check_install_build_global(options, check_options=None):
names = ["build_options", "global_options", "install_options"]
if any(map(getname, names)):
control = options.format_control
fmt_ctl_no_binary(control)
control.disallow_binaries()
warnings.warn(
'Disabling all use of wheels due to the use of --build-options '
'/ --global-options / --install-options.', stacklevel=2,
)
def check_dist_restriction(options, check_target=False):
"""Function for determining if custom platform options are allowed.
:param options: The OptionParser options.
:param check_target: Whether or not to check if --target is being used.
"""
dist_restriction_set = any([
options.python_version,
options.platform,
options.abi,
options.implementation,
])
binary_only = FormatControl(set(), {':all:'})
sdist_dependencies_allowed = (
options.format_control != binary_only and
not options.ignore_dependencies
)
# Installations or downloads using dist restrictions must not combine
# source distributions and dist-specific wheels, as they are not
# gauranteed to be locally compatible.
if dist_restriction_set and sdist_dependencies_allowed:
raise CommandError(
"When restricting platform and interpreter constraints using "
"--python-version, --platform, --abi, or --implementation, "
"either --no-deps must be set, or --only-binary=:all: must be "
"set and --no-binary must not be set (or must be set to "
":none:)."
)
if check_target:
if dist_restriction_set and not options.target_dir:
raise CommandError(
"Can not use any platform or abi specific options unless "
"installing via '--target'"
)
###########
# options #
###########
@@ -365,24 +403,25 @@ def _get_format_control(values, option):
def _handle_no_binary(option, opt_str, value, parser):
existing = getattr(parser.values, option.dest)
fmt_ctl_handle_mutual_exclude(
existing = _get_format_control(parser.values, option)
FormatControl.handle_mutual_excludes(
value, existing.no_binary, existing.only_binary,
)
def _handle_only_binary(option, opt_str, value, parser):
existing = getattr(parser.values, option.dest)
fmt_ctl_handle_mutual_exclude(
existing = _get_format_control(parser.values, option)
FormatControl.handle_mutual_excludes(
value, existing.only_binary, existing.no_binary,
)
def no_binary():
format_control = FormatControl(set(), set())
return Option(
"--no-binary", dest="format_control", action="callback",
callback=_handle_no_binary, type="str",
default=FormatControl(set(), set()),
default=format_control,
help="Do not use binary packages. Can be supplied multiple times, and "
"each time adds to the existing value. Accepts either :all: to "
"disable all binary packages, :none: to empty the set, or one or "
@@ -393,10 +432,11 @@ def no_binary():
def only_binary():
format_control = FormatControl(set(), set())
return Option(
"--only-binary", dest="format_control", action="callback",
callback=_handle_only_binary, type="str",
default=FormatControl(set(), set()),
default=format_control,
help="Do not use source packages. Can be supplied multiple times, and "
"each time adds to the existing value. Accepts either :all: to "
"disable all source packages, :none: to empty the set, or one or "
@@ -406,6 +446,61 @@ def only_binary():
)
platform = partial(
Option,
'--platform',
dest='platform',
metavar='platform',
default=None,
help=("Only use wheels compatible with <platform>. "
"Defaults to the platform of the running system."),
)
python_version = partial(
Option,
'--python-version',
dest='python_version',
metavar='python_version',
default=None,
help=("Only use wheels compatible with Python "
"interpreter version <version>. If not specified, then the "
"current system interpreter minor version is used. A major "
"version (e.g. '2') can be specified to match all "
"minor revs of that major version. A minor version "
"(e.g. '34') can also be specified."),
)
implementation = partial(
Option,
'--implementation',
dest='implementation',
metavar='implementation',
default=None,
help=("Only use wheels compatible with Python "
"implementation <implementation>, e.g. 'pp', 'jy', 'cp', "
" or 'ip'. If not specified, then the current "
"interpreter implementation is used. Use 'py' to force "
"implementation-agnostic wheels."),
)
abi = partial(
Option,
'--abi',
dest='abi',
metavar='abi',
default=None,
help=("Only use wheels compatible with Python "
"abi <abi>, e.g. 'pypy_41'. If not specified, then the "
"current interpreter abi tag is used. Generally "
"you will need to specify --implementation, "
"--platform, and --python-version when using "
"this option."),
)
def prefer_binary():
return Option(
"--prefer-binary",
@@ -501,7 +596,7 @@ no_clean = partial(
'--no-clean',
action='store_true',
default=False,
help="Don't clean up build directories)."
help="Don't clean up build directories."
) # type: Any
pre = partial(
@@ -0,0 +1,96 @@
"""A single place for constructing and exposing the main parser
"""
import os
import sys
from pipenv.patched.notpip import __version__
from pipenv.patched.notpip._internal.cli import cmdoptions
from pipenv.patched.notpip._internal.cli.parser import (
ConfigOptionParser, UpdatingDefaultsHelpFormatter,
)
from pipenv.patched.notpip._internal.commands import (
commands_dict, get_similar_commands, get_summaries,
)
from pipenv.patched.notpip._internal.exceptions import CommandError
from pipenv.patched.notpip._internal.utils.misc import get_prog
__all__ = ["create_main_parser", "parse_command"]
def create_main_parser():
"""Creates and returns the main parser for pip's CLI
"""
parser_kw = {
'usage': '\n%prog <command> [options]',
'add_help_option': False,
'formatter': UpdatingDefaultsHelpFormatter(),
'name': 'global',
'prog': get_prog(),
}
parser = ConfigOptionParser(**parser_kw)
parser.disable_interspersed_args()
pip_pkg_dir = os.path.abspath(os.path.join(
os.path.dirname(__file__), "..", "..",
))
parser.version = 'pip %s from %s (python %s)' % (
__version__, pip_pkg_dir, sys.version[:3],
)
# add the general options
gen_opts = cmdoptions.make_option_group(cmdoptions.general_group, parser)
parser.add_option_group(gen_opts)
parser.main = True # so the help formatter knows
# create command listing for description
command_summaries = get_summaries()
description = [''] + ['%-27s %s' % (i, j) for i, j in command_summaries]
parser.description = '\n'.join(description)
return parser
def parse_command(args):
parser = create_main_parser()
# Note: parser calls disable_interspersed_args(), so the result of this
# call is to split the initial args into the general options before the
# subcommand and everything else.
# For example:
# args: ['--timeout=5', 'install', '--user', 'INITools']
# general_options: ['--timeout==5']
# args_else: ['install', '--user', 'INITools']
general_options, args_else = parser.parse_args(args)
# --version
if general_options.version:
sys.stdout.write(parser.version)
sys.stdout.write(os.linesep)
sys.exit()
# pip || pip help -> print_help()
if not args_else or (args_else[0] == 'help' and len(args_else) == 1):
parser.print_help()
sys.exit()
# the subcommand name
cmd_name = args_else[0]
if cmd_name not in commands_dict:
guess = get_similar_commands(cmd_name)
msg = ['unknown command "%s"' % cmd_name]
if guess:
msg.append('maybe you meant "%s"' % guess)
raise CommandError(' - '.join(msg))
# all the args without the subcommand
cmd_args = args[:]
cmd_args.remove(cmd_name)
return cmd_name, cmd_args
@@ -9,8 +9,9 @@ from distutils.util import strtobool
from pipenv.patched.notpip._vendor.six import string_types
from pipenv.patched.notpip._internal.compat import get_terminal_size
from pipenv.patched.notpip._internal.cli.status_codes import UNKNOWN_ERROR
from pipenv.patched.notpip._internal.configuration import Configuration, ConfigurationError
from pipenv.patched.notpip._internal.utils.compat import get_terminal_size
logger = logging.getLogger(__name__)
@@ -192,7 +193,14 @@ class ConfigOptionParser(CustomOptionParser):
continue
if option.action in ('store_true', 'store_false', 'count'):
val = strtobool(val)
try:
val = strtobool(val)
except ValueError:
error_msg = invalid_config_error_message(
option.action, key, val
)
self.error(error_msg)
elif option.action == 'append':
val = val.split()
val = [self.check_default(option, key, v) for v in val]
@@ -225,7 +233,7 @@ class ConfigOptionParser(CustomOptionParser):
try:
self.config.load()
except ConfigurationError as err:
self.exit(2, err.args[0])
self.exit(UNKNOWN_ERROR, str(err))
defaults = self._update_defaults(self.defaults.copy()) # ours
for option in self._get_all_options():
@@ -237,4 +245,17 @@ class ConfigOptionParser(CustomOptionParser):
def error(self, msg):
self.print_usage(sys.stderr)
self.exit(2, "%s\n" % msg)
self.exit(UNKNOWN_ERROR, "%s\n" % msg)
def invalid_config_error_message(action, key, val):
"""Returns a better error message when invalid configuration option
is provided."""
if action in ('store_true', 'store_false'):
return ("{0} is not a valid value for {1} option, "
"please specify a boolean value like yes/no, "
"true/false or 1/0 instead.").format(val, key)
return ("{0} is not a valid value for {1} option, "
"please specify a numerical value like 1/0 "
"instead.").format(val, key)
@@ -21,7 +21,7 @@ from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import List, Type # noqa: F401
from pipenv.patched.notpip._internal.basecommand import Command # noqa: F401
from pipenv.patched.notpip._internal.cli.base_command import Command # noqa: F401
commands_order = [
InstallCommand,
@@ -1,6 +1,6 @@
import logging
from pipenv.patched.notpip._internal.basecommand import Command
from pipenv.patched.notpip._internal.cli.base_command import Command
from pipenv.patched.notpip._internal.operations.check import (
check_package_set, create_package_set_from_installed,
)
@@ -3,7 +3,7 @@ from __future__ import absolute_import
import sys
import textwrap
from pipenv.patched.notpip._internal.basecommand import Command
from pipenv.patched.notpip._internal.cli.base_command import Command
from pipenv.patched.notpip._internal.utils.misc import get_prog
BASE_COMPLETION = """
@@ -2,11 +2,11 @@ import logging
import os
import subprocess
from pipenv.patched.notpip._internal.basecommand import Command
from pipenv.patched.notpip._internal.cli.base_command import Command
from pipenv.patched.notpip._internal.cli.status_codes import ERROR, SUCCESS
from pipenv.patched.notpip._internal.configuration import Configuration, kinds
from pipenv.patched.notpip._internal.exceptions import PipError
from pipenv.patched.notpip._internal.locations import venv_config_file
from pipenv.patched.notpip._internal.status_codes import ERROR, SUCCESS
from pipenv.patched.notpip._internal.utils.misc import get_prog
logger = logging.getLogger(__name__)
@@ -3,10 +3,8 @@ from __future__ import absolute_import
import logging
import os
from pipenv.patched.notpip._internal import cmdoptions
from pipenv.patched.notpip._internal.basecommand import RequirementCommand
from pipenv.patched.notpip._internal.exceptions import CommandError
from pipenv.patched.notpip._internal.index import FormatControl
from pipenv.patched.notpip._internal.cli import cmdoptions
from pipenv.patched.notpip._internal.cli.base_command import RequirementCommand
from pipenv.patched.notpip._internal.operations.prepare import RequirementPreparer
from pipenv.patched.notpip._internal.req import RequirementSet
from pipenv.patched.notpip._internal.req.req_tracker import RequirementTracker
@@ -69,52 +67,10 @@ class DownloadCommand(RequirementCommand):
help=("Download packages into <dir>."),
)
cmd_opts.add_option(
'--platform',
dest='platform',
metavar='platform',
default=None,
help=("Only download wheels compatible with <platform>. "
"Defaults to the platform of the running system."),
)
cmd_opts.add_option(
'--python-version',
dest='python_version',
metavar='python_version',
default=None,
help=("Only download wheels compatible with Python "
"interpreter version <version>. If not specified, then the "
"current system interpreter minor version is used. A major "
"version (e.g. '2') can be specified to match all "
"minor revs of that major version. A minor version "
"(e.g. '34') can also be specified."),
)
cmd_opts.add_option(
'--implementation',
dest='implementation',
metavar='implementation',
default=None,
help=("Only download wheels compatible with Python "
"implementation <implementation>, e.g. 'pp', 'jy', 'cp', "
" or 'ip'. If not specified, then the current "
"interpreter implementation is used. Use 'py' to force "
"implementation-agnostic wheels."),
)
cmd_opts.add_option(
'--abi',
dest='abi',
metavar='abi',
default=None,
help=("Only download wheels compatible with Python "
"abi <abi>, e.g. 'pypy_41'. If not specified, then the "
"current interpreter abi tag is used. Generally "
"you will need to specify --implementation, "
"--platform, and --python-version when using "
"this option."),
)
cmd_opts.add_option(cmdoptions.platform())
cmd_opts.add_option(cmdoptions.python_version())
cmd_opts.add_option(cmdoptions.implementation())
cmd_opts.add_option(cmdoptions.abi())
index_opts = cmdoptions.make_option_group(
cmdoptions.index_group,
@@ -135,25 +91,7 @@ class DownloadCommand(RequirementCommand):
else:
python_versions = None
dist_restriction_set = any([
options.python_version,
options.platform,
options.abi,
options.implementation,
])
binary_only = FormatControl(set(), {':all:'})
no_sdist_dependencies = (
options.format_control != binary_only and
not options.ignore_dependencies
)
if dist_restriction_set and no_sdist_dependencies:
raise CommandError(
"When restricting platform and interpreter constraints using "
"--python-version, --platform, --abi, or --implementation, "
"either --no-deps must be set, or --only-binary=:all: must be "
"set and --no-binary must not be set (or must be set to "
":none:)."
)
cmdoptions.check_dist_restriction(options)
options.src_dir = os.path.abspath(options.src_dir)
options.download_dir = normalize_path(options.download_dir)
@@ -2,11 +2,11 @@ from __future__ import absolute_import
import sys
from pipenv.patched.notpip._internal import index
from pipenv.patched.notpip._internal.basecommand import Command
from pipenv.patched.notpip._internal.cache import WheelCache
from pipenv.patched.notpip._internal.compat import stdlib_pkgs
from pipenv.patched.notpip._internal.cli.base_command import Command
from pipenv.patched.notpip._internal.models.format_control import FormatControl
from pipenv.patched.notpip._internal.operations.freeze import freeze
from pipenv.patched.notpip._internal.utils.compat import stdlib_pkgs
DEV_PKGS = {'pip', 'setuptools', 'distribute', 'wheel'}
@@ -71,7 +71,7 @@ class FreezeCommand(Command):
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options, args):
format_control = index.FormatControl(set(), set())
format_control = FormatControl(set(), set())
wheel_cache = WheelCache(options.cache_dir, format_control)
skip = set(stdlib_pkgs)
if not options.freeze_all:
@@ -4,8 +4,8 @@ import hashlib
import logging
import sys
from pipenv.patched.notpip._internal.basecommand import Command
from pipenv.patched.notpip._internal.status_codes import ERROR
from pipenv.patched.notpip._internal.cli.base_command import Command
from pipenv.patched.notpip._internal.cli.status_codes import ERROR
from pipenv.patched.notpip._internal.utils.hashes import FAVORITE_HASH, STRONG_HASHES
from pipenv.patched.notpip._internal.utils.misc import read_chunks
@@ -1,6 +1,7 @@
from __future__ import absolute_import
from pipenv.patched.notpip._internal.basecommand import SUCCESS, Command
from pipenv.patched.notpip._internal.cli.base_command import Command
from pipenv.patched.notpip._internal.cli.status_codes import SUCCESS
from pipenv.patched.notpip._internal.exceptions import CommandError
@@ -9,9 +9,10 @@ from optparse import SUPPRESS_HELP
from pipenv.patched.notpip._vendor import pkg_resources
from pipenv.patched.notpip._internal import cmdoptions
from pipenv.patched.notpip._internal.basecommand import RequirementCommand
from pipenv.patched.notpip._internal.cache import WheelCache
from pipenv.patched.notpip._internal.cli import cmdoptions
from pipenv.patched.notpip._internal.cli.base_command import RequirementCommand
from pipenv.patched.notpip._internal.cli.status_codes import ERROR
from pipenv.patched.notpip._internal.exceptions import (
CommandError, InstallationError, PreviousBuildDirError,
)
@@ -21,7 +22,6 @@ from pipenv.patched.notpip._internal.operations.prepare import RequirementPrepar
from pipenv.patched.notpip._internal.req import RequirementSet, install_given_reqs
from pipenv.patched.notpip._internal.req.req_tracker import RequirementTracker
from pipenv.patched.notpip._internal.resolve import Resolver
from pipenv.patched.notpip._internal.status_codes import ERROR
from pipenv.patched.notpip._internal.utils.filesystem import check_path_owner
from pipenv.patched.notpip._internal.utils.misc import (
ensure_dir, get_installed_version,
@@ -83,6 +83,11 @@ class InstallCommand(RequirementCommand):
'<dir>. Use --upgrade to replace existing packages in <dir> '
'with new versions.'
)
cmd_opts.add_option(cmdoptions.platform())
cmd_opts.add_option(cmdoptions.python_version())
cmd_opts.add_option(cmdoptions.implementation())
cmd_opts.add_option(cmdoptions.abi())
cmd_opts.add_option(
'--user',
dest='use_user_site',
@@ -204,7 +209,6 @@ class InstallCommand(RequirementCommand):
def run(self, options, args):
cmdoptions.check_install_build_global(options)
upgrade_strategy = "to-satisfy-only"
if options.upgrade:
upgrade_strategy = options.upgrade_strategy
@@ -212,6 +216,13 @@ class InstallCommand(RequirementCommand):
if options.build_dir:
options.build_dir = os.path.abspath(options.build_dir)
cmdoptions.check_dist_restriction(options, check_target=True)
if options.python_version:
python_versions = [options.python_version]
else:
python_versions = None
options.src_dir = os.path.abspath(options.src_dir)
install_options = options.install_options or []
if options.use_user_site:
@@ -246,7 +257,14 @@ class InstallCommand(RequirementCommand):
global_options = options.global_options or []
with self._build_session(options) as session:
finder = self._build_package_finder(options, session)
finder = self._build_package_finder(
options=options,
session=session,
platform=options.platform,
python_versions=python_versions,
abi=options.abi,
implementation=options.implementation,
)
build_delete = (not (options.no_clean or options.build_dir))
wheel_cache = WheelCache(options.cache_dir, options.format_control)
@@ -266,6 +284,7 @@ class InstallCommand(RequirementCommand):
) as directory:
requirement_set = RequirementSet(
require_hashes=options.require_hashes,
check_supported_wheels=not options.target_dir,
)
try:
@@ -6,8 +6,8 @@ import logging
from pipenv.patched.notpip._vendor import six
from pipenv.patched.notpip._vendor.six.moves import zip_longest
from pipenv.patched.notpip._internal.basecommand import Command
from pipenv.patched.notpip._internal.cmdoptions import index_group, make_option_group
from pipenv.patched.notpip._internal.cli import cmdoptions
from pipenv.patched.notpip._internal.cli.base_command import Command
from pipenv.patched.notpip._internal.exceptions import CommandError
from pipenv.patched.notpip._internal.index import PackageFinder
from pipenv.patched.notpip._internal.utils.misc import (
@@ -102,7 +102,9 @@ class ListCommand(Command):
help='Include editable package from output.',
default=True,
)
index_opts = make_option_group(index_group, self.parser)
index_opts = cmdoptions.make_option_group(
cmdoptions.index_group, self.parser
)
self.parser.insert_option_group(0, index_opts)
self.parser.insert_option_group(0, cmd_opts)
@@ -11,12 +11,12 @@ from pipenv.patched.notpip._vendor.packaging.version import parse as parse_versi
# why we ignore the type on this import
from pipenv.patched.notpip._vendor.six.moves import xmlrpc_client # type: ignore
from pipenv.patched.notpip._internal.basecommand import SUCCESS, Command
from pipenv.patched.notpip._internal.compat import get_terminal_size
from pipenv.patched.notpip._internal.cli.base_command import Command
from pipenv.patched.notpip._internal.cli.status_codes import NO_MATCHES_FOUND, SUCCESS
from pipenv.patched.notpip._internal.download import PipXmlrpcTransport
from pipenv.patched.notpip._internal.exceptions import CommandError
from pipenv.patched.notpip._internal.models.index import PyPI
from pipenv.patched.notpip._internal.status_codes import NO_MATCHES_FOUND
from pipenv.patched.notpip._internal.utils.compat import get_terminal_size
from pipenv.patched.notpip._internal.utils.logging import indent_log
logger = logging.getLogger(__name__)
@@ -7,8 +7,8 @@ from email.parser import FeedParser # type: ignore
from pipenv.patched.notpip._vendor import pkg_resources
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
from pipenv.patched.notpip._internal.basecommand import Command
from pipenv.patched.notpip._internal.status_codes import ERROR, SUCCESS
from pipenv.patched.notpip._internal.cli.base_command import Command
from pipenv.patched.notpip._internal.cli.status_codes import ERROR, SUCCESS
logger = logging.getLogger(__name__)
@@ -2,9 +2,10 @@ from __future__ import absolute_import
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
from pipenv.patched.notpip._internal.basecommand import Command
from pipenv.patched.notpip._internal.cli.base_command import Command
from pipenv.patched.notpip._internal.exceptions import InstallationError
from pipenv.patched.notpip._internal.req import InstallRequirement, parse_requirements
from pipenv.patched.notpip._internal.req import parse_requirements
from pipenv.patched.notpip._internal.req.constructors import install_req_from_line
from pipenv.patched.notpip._internal.utils.misc import protect_pip_from_modification_on_windows
@@ -47,7 +48,7 @@ class UninstallCommand(Command):
with self._build_session(options) as session:
reqs_to_uninstall = {}
for name in args:
req = InstallRequirement.from_line(
req = install_req_from_line(
name, isolated=options.isolated_mode,
)
if req.name:
@@ -4,9 +4,9 @@ from __future__ import absolute_import
import logging
import os
from pipenv.patched.notpip._internal import cmdoptions
from pipenv.patched.notpip._internal.basecommand import RequirementCommand
from pipenv.patched.notpip._internal.cache import WheelCache
from pipenv.patched.notpip._internal.cli import cmdoptions
from pipenv.patched.notpip._internal.cli.base_command import RequirementCommand
from pipenv.patched.notpip._internal.exceptions import CommandError, PreviousBuildDirError
from pipenv.patched.notpip._internal.operations.prepare import RequirementPreparer
from pipenv.patched.notpip._internal.req import RequirementSet
@@ -18,7 +18,9 @@ import os
from pipenv.patched.notpip._vendor import six
from pipenv.patched.notpip._vendor.six.moves import configparser
from pipenv.patched.notpip._internal.exceptions import ConfigurationError
from pipenv.patched.notpip._internal.exceptions import (
ConfigurationError, ConfigurationFileCouldNotBeLoaded,
)
from pipenv.patched.notpip._internal.locations import (
legacy_config_file, new_config_file, running_under_virtualenv,
site_config_files, venv_config_file,
@@ -289,11 +291,16 @@ class Configuration(object):
try:
parser.read(fname)
except UnicodeDecodeError:
raise ConfigurationError((
"ERROR: "
"Configuration file contains invalid %s characters.\n"
"Please fix your configuration, located at %s\n"
) % (locale.getpreferredencoding(False), fname))
# See https://github.com/pypa/pip/issues/4963
raise ConfigurationFileCouldNotBeLoaded(
reason="contains invalid {} characters".format(
locale.getpreferredencoding(False)
),
fname=fname,
)
except configparser.Error as error:
# See https://github.com/pypa/pip/issues/4893
raise ConfigurationFileCouldNotBeLoaded(error=error)
return parser
def _load_environment_vars(self):
+2 -2
View File
@@ -323,7 +323,7 @@ class InsecureHTTPAdapter(HTTPAdapter):
conn.ca_certs = None
class PipSession(Session):
class PipSession(requests.Session):
timeout = None
@@ -753,7 +753,7 @@ def _copy_dist_from_dir(link_path, location):
# build an sdist
setup_py = 'setup.py'
sdist_args = [os.environ.get('PIP_PYTHON_PATH', sys.executable)]
sdist_args = [sys.executable]
sdist_args.append('-c')
sdist_args.append(SETUPTOOLS_SHIM % setup_py)
sdist_args.append('sdist')
@@ -247,3 +247,22 @@ class HashMismatch(HashError):
class UnsupportedPythonVersion(InstallationError):
"""Unsupported python version according to Requires-Python package
metadata."""
class ConfigurationFileCouldNotBeLoaded(ConfigurationError):
"""When there are errors while loading a configuration file
"""
def __init__(self, reason="could not be loaded", fname=None, error=None):
super(ConfigurationFileCouldNotBeLoaded, self).__init__(error)
self.reason = reason
self.fname = fname
self.error = error
def __str__(self):
if self.fname is not None:
message_part = " in {}.".format(self.fname)
else:
assert self.error is not None
message_part = ".\n{}\n".format(self.error.message)
return "Configuration file {}{}".format(self.reason, message_part)
+172 -398
View File
@@ -20,24 +20,27 @@ from pipenv.patched.notpip._vendor.requests.exceptions import SSLError
from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse
from pipenv.patched.notpip._vendor.six.moves.urllib import request as urllib_request
from pipenv.patched.notpip._internal.compat import ipaddress
from pipenv.patched.notpip._internal.download import HAS_TLS, is_url, path_to_url, url_to_path
from pipenv.patched.notpip._internal.exceptions import (
BestVersionAlreadyInstalled, DistributionNotFound, InvalidWheelFilename,
UnsupportedWheel,
)
from pipenv.patched.notpip._internal.models.candidate import InstallationCandidate
from pipenv.patched.notpip._internal.models.format_control import FormatControl
from pipenv.patched.notpip._internal.models.index import PyPI
from pipenv.patched.notpip._internal.models.link import Link
from pipenv.patched.notpip._internal.pep425tags import get_supported
from pipenv.patched.notpip._internal.utils.compat import ipaddress
from pipenv.patched.notpip._internal.utils.deprecation import deprecated
from pipenv.patched.notpip._internal.utils.logging import indent_log
from pipenv.patched.notpip._internal.utils.misc import (
ARCHIVE_EXTENSIONS, SUPPORTED_EXTENSIONS, cached_property, normalize_path,
remove_auth_from_url, splitext,
ARCHIVE_EXTENSIONS, SUPPORTED_EXTENSIONS, normalize_path,
remove_auth_from_url,
)
from pipenv.patched.notpip._internal.utils.packaging import check_requires_python
from pipenv.patched.notpip._internal.wheel import Wheel, wheel_ext
__all__ = ['FormatControl', 'fmt_ctl_handle_mutual_exclude', 'PackageFinder']
__all__ = ['FormatControl', 'PackageFinder']
SECURE_ORIGINS = [
@@ -56,46 +59,120 @@ SECURE_ORIGINS = [
logger = logging.getLogger(__name__)
class InstallationCandidate(object):
def _get_content_type(url, session):
"""Get the Content-Type of the given url, using a HEAD request"""
scheme, netloc, path, query, fragment = urllib_parse.urlsplit(url)
if scheme not in {'http', 'https'}:
# FIXME: some warning or something?
# assertion error?
return ''
def __init__(self, project, version, location, requires_python=None):
self.project = project
self.version = parse_version(version)
self.location = location
self._key = (self.project, self.version, self.location)
self.requires_python = requires_python
resp = session.head(url, allow_redirects=True)
resp.raise_for_status()
def __repr__(self):
return "<InstallationCandidate({!r}, {!r}, {!r})>".format(
self.project, self.version, self.location,
return resp.headers.get("Content-Type", "")
def _handle_get_page_fail(link, reason, url, meth=None):
if meth is None:
meth = logger.debug
meth("Could not fetch URL %s: %s - skipping", link, reason)
def _get_html_page(link, session=None):
if session is None:
raise TypeError(
"_get_html_page() missing 1 required keyword argument: 'session'"
)
def __hash__(self):
return hash(self._key)
url = link.url
url = url.split('#', 1)[0]
def __lt__(self, other):
return self._compare(other, lambda s, o: s < o)
# Check for VCS schemes that do not support lookup as web pages.
from pipenv.patched.notpip._internal.vcs import VcsSupport
for scheme in VcsSupport.schemes:
if url.lower().startswith(scheme) and url[len(scheme)] in '+:':
logger.debug('Cannot look at %s URL %s', scheme, link)
return None
def __le__(self, other):
return self._compare(other, lambda s, o: s <= o)
try:
filename = link.filename
for bad_ext in ARCHIVE_EXTENSIONS:
if filename.endswith(bad_ext):
content_type = _get_content_type(url, session=session)
if content_type.lower().startswith('text/html'):
break
else:
logger.debug(
'Skipping page %s because of Content-Type: %s',
link,
content_type,
)
return
def __eq__(self, other):
return self._compare(other, lambda s, o: s == o)
logger.debug('Getting page %s', url)
def __ge__(self, other):
return self._compare(other, lambda s, o: s >= o)
# Tack index.html onto file:// URLs that point to directories
(scheme, netloc, path, params, query, fragment) = \
urllib_parse.urlparse(url)
if (scheme == 'file' and
os.path.isdir(urllib_request.url2pathname(path))):
# add trailing slash if not present so urljoin doesn't trim
# final segment
if not url.endswith('/'):
url += '/'
url = urllib_parse.urljoin(url, 'index.html')
logger.debug(' file: URL is directory, getting %s', url)
def __gt__(self, other):
return self._compare(other, lambda s, o: s > o)
resp = session.get(
url,
headers={
"Accept": "text/html",
# We don't want to blindly returned cached data for
# /simple/, because authors generally expecting that
# twine upload && pip install will function, but if
# they've done a pip install in the last ~10 minutes
# it won't. Thus by setting this to zero we will not
# blindly use any cached data, however the benefit of
# using max-age=0 instead of no-cache, is that we will
# still support conditional requests, so we will still
# minimize traffic sent in cases where the page hasn't
# changed at all, we will just always incur the round
# trip for the conditional GET now instead of only
# once per 10 minutes.
# For more information, please see pypa/pip#5670.
"Cache-Control": "max-age=0",
},
)
resp.raise_for_status()
def __ne__(self, other):
return self._compare(other, lambda s, o: s != o)
# The check for archives above only works if the url ends with
# something that looks like an archive. However that is not a
# requirement of an url. Unless we issue a HEAD request on every
# url we cannot know ahead of time for sure if something is HTML
# or not. However we can check after we've downloaded it.
content_type = resp.headers.get('Content-Type', 'unknown')
if not content_type.lower().startswith("text/html"):
logger.debug(
'Skipping page %s because of Content-Type: %s',
link,
content_type,
)
return
def _compare(self, other, method):
if not isinstance(other, InstallationCandidate):
return NotImplemented
return method(self._key, other._key)
inst = HTMLPage(resp.content, resp.url, resp.headers)
except requests.HTTPError as exc:
_handle_get_page_fail(link, exc, url)
except SSLError as exc:
reason = "There was a problem confirming the ssl certificate: "
reason += str(exc)
_handle_get_page_fail(link, reason, url, meth=logger.info)
except requests.ConnectionError as exc:
_handle_get_page_fail(link, "connection error: %s" % exc, url)
except requests.Timeout:
_handle_get_page_fail(link, "timed out", url)
else:
return inst
class PackageFinder(object):
@@ -210,15 +287,15 @@ class PackageFinder(object):
return "\n".join(lines)
def add_dependency_links(self, links):
# # FIXME: this shouldn't be global list this, it should only
# # apply to requirements of the package that specifies the
# # dependency_links value
# # FIXME: also, we should track comes_from (i.e., use Link)
# FIXME: this shouldn't be global list this, it should only
# apply to requirements of the package that specifies the
# dependency_links value
# FIXME: also, we should track comes_from (i.e., use Link)
if self.process_dependency_links:
deprecated(
"Dependency Links processing has been deprecated and will be "
"removed in a future release.",
replacement=None,
replacement="PEP 508 URL dependencies",
gone_in="18.2",
issue=4187,
)
@@ -242,6 +319,7 @@ class PackageFinder(object):
return extras
@staticmethod
def _sort_locations(locations, expand_dir=False):
"""
@@ -467,7 +545,7 @@ class PackageFinder(object):
logger.debug('* %s', location)
canonical_name = canonicalize_name(project_name)
formats = fmt_ctl_formats(self.format_control, canonical_name)
formats = self.format_control.get_allowed_formats(canonical_name)
search = Search(project_name, canonical_name, formats)
find_links_versions = self._package_versions(
# We trust every directly linked archive in find_links
@@ -483,7 +561,7 @@ class PackageFinder(object):
continue
with indent_log():
page_versions.extend(
self._package_versions(page.links, search)
self._package_versions(page.iter_links(), search)
)
dependency_versions = self._package_versions(
@@ -626,7 +704,9 @@ class PackageFinder(object):
try:
page = self._get_page(location)
except requests.HTTPError as e:
except requests.HTTPError:
continue
if page is None:
continue
yield page
@@ -743,7 +823,7 @@ class PackageFinder(object):
return InstallationCandidate(search.supplied, version, link, link.requires_python)
def _get_page(self, link):
return HTMLPage.get_page(link, session=self.session)
return _get_html_page(link, session=self.session)
def egg_info_matches(
@@ -763,7 +843,7 @@ def egg_info_matches(
return None
if search_name is None:
full_match = match.group(0)
return full_match[full_match.index('-'):]
return full_match.split('-', 1)[-1]
name = match.group(0).lower()
# To match the "safe" name that pkg_resources creates:
name = name.replace('_', '-')
@@ -775,377 +855,71 @@ def egg_info_matches(
return None
def _determine_base_url(document, page_url):
"""Determine the HTML document's base URL.
This looks for a ``<base>`` tag in the HTML document. If present, its href
attribute denotes the base URL of anchor tags in the document. If there is
no such tag (or if it does not have a valid href attribute), the HTML
file's URL is used as the base URL.
:param document: An HTML document representation. The current
implementation expects the result of ``html5lib.parse()``.
:param page_url: The URL of the HTML document.
"""
for base in document.findall(".//base"):
href = base.get("href")
if href is not None:
return href
return page_url
def _get_encoding_from_headers(headers):
"""Determine if we have any encoding information in our headers.
"""
if headers and "Content-Type" in headers:
content_type, params = cgi.parse_header(headers["Content-Type"])
if "charset" in params:
return params['charset']
return None
_CLEAN_LINK_RE = re.compile(r'[^a-z0-9$&+,/:;=?@.#%_\\|-]', re.I)
def _clean_link(url):
"""Makes sure a link is fully encoded. That is, if a ' ' shows up in
the link, it will be rewritten to %20 (while not over-quoting
% or other characters)."""
return _CLEAN_LINK_RE.sub(lambda match: '%%%2x' % ord(match.group(0)), url)
class HTMLPage(object):
"""Represents one page, along with its URL"""
def __init__(self, content, url, headers=None):
# Determine if we have any encoding information in our headers
encoding = None
if headers and "Content-Type" in headers:
content_type, params = cgi.parse_header(headers["Content-Type"])
if "charset" in params:
encoding = params['charset']
self.content = content
self.parsed = html5lib.parse(
self.content,
transport_encoding=encoding,
namespaceHTMLElements=False,
)
self.url = url
self.headers = headers
def __str__(self):
return self.url
@classmethod
def get_page(cls, link, skip_archives=True, session=None):
if session is None:
raise TypeError(
"get_page() missing 1 required keyword argument: 'session'"
)
url = link.url
url = url.split('#', 1)[0]
# Check for VCS schemes that do not support lookup as web pages.
from pipenv.patched.notpip._internal.vcs import VcsSupport
for scheme in VcsSupport.schemes:
if url.lower().startswith(scheme) and url[len(scheme)] in '+:':
logger.debug('Cannot look at %s URL %s', scheme, link)
return None
try:
if skip_archives:
filename = link.filename
for bad_ext in ARCHIVE_EXTENSIONS:
if filename.endswith(bad_ext):
content_type = cls._get_content_type(
url, session=session,
)
if content_type.lower().startswith('text/html'):
break
else:
logger.debug(
'Skipping page %s because of Content-Type: %s',
link,
content_type,
)
return
logger.debug('Getting page %s', url)
# Tack index.html onto file:// URLs that point to directories
(scheme, netloc, path, params, query, fragment) = \
urllib_parse.urlparse(url)
if (scheme == 'file' and
os.path.isdir(urllib_request.url2pathname(path))):
# add trailing slash if not present so urljoin doesn't trim
# final segment
if not url.endswith('/'):
url += '/'
url = urllib_parse.urljoin(url, 'index.html')
logger.debug(' file: URL is directory, getting %s', url)
resp = session.get(
url,
headers={
"Accept": "text/html",
"Cache-Control": "max-age=600",
},
)
resp.raise_for_status()
# The check for archives above only works if the url ends with
# something that looks like an archive. However that is not a
# requirement of an url. Unless we issue a HEAD request on every
# url we cannot know ahead of time for sure if something is HTML
# or not. However we can check after we've downloaded it.
content_type = resp.headers.get('Content-Type', 'unknown')
if not content_type.lower().startswith("text/html"):
logger.debug(
'Skipping page %s because of Content-Type: %s',
link,
content_type,
)
return
inst = cls(resp.content, resp.url, resp.headers)
except requests.HTTPError as exc:
cls._handle_fail(link, exc, url)
except SSLError as exc:
reason = "There was a problem confirming the ssl certificate: "
reason += str(exc)
cls._handle_fail(link, reason, url, meth=logger.info)
except requests.ConnectionError as exc:
cls._handle_fail(link, "connection error: %s" % exc, url)
except requests.Timeout:
cls._handle_fail(link, "timed out", url)
else:
return inst
@staticmethod
def _handle_fail(link, reason, url, meth=None):
if meth is None:
meth = logger.debug
meth("Could not fetch URL %s: %s - skipping", link, reason)
@staticmethod
def _get_content_type(url, session):
"""Get the Content-Type of the given url, using a HEAD request"""
scheme, netloc, path, query, fragment = urllib_parse.urlsplit(url)
if scheme not in {'http', 'https'}:
# FIXME: some warning or something?
# assertion error?
return ''
resp = session.head(url, allow_redirects=True)
resp.raise_for_status()
return resp.headers.get("Content-Type", "")
@cached_property
def base_url(self):
bases = [
x for x in self.parsed.findall(".//base")
if x.get("href") is not None
]
if bases and bases[0].get("href"):
return bases[0].get("href")
else:
return self.url
@property
def links(self):
def iter_links(self):
"""Yields all links in the page"""
for anchor in self.parsed.findall(".//a"):
document = html5lib.parse(
self.content,
transport_encoding=_get_encoding_from_headers(self.headers),
namespaceHTMLElements=False,
)
base_url = _determine_base_url(document, self.url)
for anchor in document.findall(".//a"):
if anchor.get("href"):
href = anchor.get("href")
url = self.clean_link(
urllib_parse.urljoin(self.base_url, href)
)
url = _clean_link(urllib_parse.urljoin(base_url, href))
pyrequire = anchor.get('data-requires-python')
pyrequire = unescape(pyrequire) if pyrequire else None
yield Link(url, self, requires_python=pyrequire)
_clean_re = re.compile(r'[^a-z0-9$&+,/:;=?@.#%_\\|-]', re.I)
def clean_link(self, url):
"""Makes sure a link is fully encoded. That is, if a ' ' shows up in
the link, it will be rewritten to %20 (while not over-quoting
% or other characters)."""
return self._clean_re.sub(
lambda match: '%%%2x' % ord(match.group(0)), url)
class Link(object):
def __init__(self, url, comes_from=None, requires_python=None):
"""
Object representing a parsed link from https://pypi.org/simple/*
url:
url of the resource pointed to (href of the link)
comes_from:
instance of HTMLPage where the link was found, or string.
requires_python:
String containing the `Requires-Python` metadata field, specified
in PEP 345. This may be specified by a data-requires-python
attribute in the HTML link tag, as described in PEP 503.
"""
# url can be a UNC windows share
if url.startswith('\\\\'):
url = path_to_url(url)
self.url = url
self.comes_from = comes_from
self.requires_python = requires_python if requires_python else None
def __str__(self):
if self.requires_python:
rp = ' (requires-python:%s)' % self.requires_python
else:
rp = ''
if self.comes_from:
return '%s (from %s)%s' % (self.url, self.comes_from, rp)
else:
return str(self.url)
def __repr__(self):
return '<Link %s>' % self
def __eq__(self, other):
if not isinstance(other, Link):
return NotImplemented
return self.url == other.url
def __ne__(self, other):
if not isinstance(other, Link):
return NotImplemented
return self.url != other.url
def __lt__(self, other):
if not isinstance(other, Link):
return NotImplemented
return self.url < other.url
def __le__(self, other):
if not isinstance(other, Link):
return NotImplemented
return self.url <= other.url
def __gt__(self, other):
if not isinstance(other, Link):
return NotImplemented
return self.url > other.url
def __ge__(self, other):
if not isinstance(other, Link):
return NotImplemented
return self.url >= other.url
def __hash__(self):
return hash(self.url)
@property
def filename(self):
_, netloc, path, _, _ = urllib_parse.urlsplit(self.url)
name = posixpath.basename(path.rstrip('/')) or netloc
name = urllib_parse.unquote(name)
assert name, ('URL %r produced no filename' % self.url)
return name
@property
def scheme(self):
return urllib_parse.urlsplit(self.url)[0]
@property
def netloc(self):
return urllib_parse.urlsplit(self.url)[1]
@property
def path(self):
return urllib_parse.unquote(urllib_parse.urlsplit(self.url)[2])
def splitext(self):
return splitext(posixpath.basename(self.path.rstrip('/')))
@property
def ext(self):
return self.splitext()[1]
@property
def url_without_fragment(self):
scheme, netloc, path, query, fragment = urllib_parse.urlsplit(self.url)
return urllib_parse.urlunsplit((scheme, netloc, path, query, None))
_egg_fragment_re = re.compile(r'[#&]egg=([^&]*)')
@property
def egg_fragment(self):
match = self._egg_fragment_re.search(self.url)
if not match:
return None
return match.group(1)
_subdirectory_fragment_re = re.compile(r'[#&]subdirectory=([^&]*)')
@property
def subdirectory_fragment(self):
match = self._subdirectory_fragment_re.search(self.url)
if not match:
return None
return match.group(1)
_hash_re = re.compile(
r'(sha1|sha224|sha384|sha256|sha512|md5)=([a-f0-9]+)'
)
@property
def hash(self):
match = self._hash_re.search(self.url)
if match:
return match.group(2)
return None
@property
def hash_name(self):
match = self._hash_re.search(self.url)
if match:
return match.group(1)
return None
@property
def show_url(self):
return posixpath.basename(self.url.split('#', 1)[0].split('?', 1)[0])
@property
def is_wheel(self):
return self.ext == wheel_ext
@property
def is_artifact(self):
"""
Determines if this points to an actual artifact (e.g. a tarball) or if
it points to an "abstract" thing like a path or a VCS location.
"""
from pipenv.patched.notpip._internal.vcs import vcs
if self.scheme in vcs.all_schemes:
return False
return True
FormatControl = namedtuple('FormatControl', 'no_binary only_binary')
"""This object has two fields, no_binary and only_binary.
If a field is falsy, it isn't set. If it is {':all:'}, it should match all
packages except those listed in the other field. Only one field can be set
to {':all:'} at a time. The rest of the time exact package name matches
are listed, with any given package only showing up in one field at a time.
"""
def fmt_ctl_handle_mutual_exclude(value, target, other):
new = value.split(',')
while ':all:' in new:
other.clear()
target.clear()
target.add(':all:')
del new[:new.index(':all:') + 1]
if ':none:' not in new:
# Without a none, we want to discard everything as :all: covers it
return
for name in new:
if name == ':none:':
target.clear()
continue
name = canonicalize_name(name)
other.discard(name)
target.add(name)
def fmt_ctl_formats(fmt_ctl, canonical_name):
result = {"binary", "source"}
if canonical_name in fmt_ctl.only_binary:
result.discard('source')
elif canonical_name in fmt_ctl.no_binary:
result.discard('binary')
elif ':all:' in fmt_ctl.only_binary:
result.discard('source')
elif ':all:' in fmt_ctl.no_binary:
result.discard('binary')
return frozenset(result)
def fmt_ctl_no_binary(fmt_ctl):
fmt_ctl_handle_mutual_exclude(
':all:', fmt_ctl.no_binary, fmt_ctl.only_binary,
)
yield Link(url, self.url, requires_python=pyrequire)
Search = namedtuple('Search', 'supplied canonical formats')
+1 -1
View File
@@ -10,8 +10,8 @@ import sysconfig
from distutils import sysconfig as distutils_sysconfig
from distutils.command.install import SCHEME_KEYS # type: ignore
from pipenv.patched.notpip._internal.compat import WINDOWS, expanduser
from pipenv.patched.notpip._internal.utils import appdirs
from pipenv.patched.notpip._internal.utils.compat import WINDOWS, expanduser
# Application Directories
USER_CACHE_DIR = appdirs.user_cache_dir("pip")
@@ -0,0 +1,24 @@
from pipenv.patched.notpip._vendor.packaging.version import parse as parse_version
from pipenv.patched.notpip._internal.utils.models import KeyBasedCompareMixin
class InstallationCandidate(KeyBasedCompareMixin):
"""Represents a potential "candidate" for installation.
"""
def __init__(self, project, version, location, requires_python=None):
self.project = project
self.version = parse_version(version)
self.location = location
self.requires_python = requires_python
super(InstallationCandidate, self).__init__(
key=(self.project, self.version, self.location),
defining_class=InstallationCandidate
)
def __repr__(self):
return "<InstallationCandidate({!r}, {!r}, {!r})>".format(
self.project, self.version, self.location,
)
@@ -0,0 +1,62 @@
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
class FormatControl(object):
"""A helper class for controlling formats from which packages are installed.
If a field is falsy, it isn't set. If it is {':all:'}, it should match all
packages except those listed in the other field. Only one field can be set
to {':all:'} at a time. The rest of the time exact package name matches
are listed, with any given package only showing up in one field at a time.
"""
def __init__(self, no_binary=None, only_binary=None):
self.no_binary = set() if no_binary is None else no_binary
self.only_binary = set() if only_binary is None else only_binary
def __eq__(self, other):
return self.__dict__ == other.__dict__
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
return "{}({}, {})".format(
self.__class__.__name__,
self.no_binary,
self.only_binary
)
@staticmethod
def handle_mutual_excludes(value, target, other):
new = value.split(',')
while ':all:' in new:
other.clear()
target.clear()
target.add(':all:')
del new[:new.index(':all:') + 1]
# Without a none, we want to discard everything as :all: covers it
if ':none:' not in new:
return
for name in new:
if name == ':none:':
target.clear()
continue
name = canonicalize_name(name)
other.discard(name)
target.add(name)
def get_allowed_formats(self, canonical_name):
result = {"binary", "source"}
if canonical_name in self.only_binary:
result.discard('source')
elif canonical_name in self.no_binary:
result.discard('binary')
elif ':all:' in self.only_binary:
result.discard('source')
elif ':all:' in self.no_binary:
result.discard('binary')
return frozenset(result)
def disallow_binaries(self):
self.handle_mutual_excludes(
':all:', self.no_binary, self.only_binary,
)
@@ -1,15 +1,29 @@
from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse
class Index(object):
def __init__(self, url):
class PackageIndex(object):
"""Represents a Package Index and provides easier access to endpoints
"""
def __init__(self, url, file_storage_domain):
super(PackageIndex, self).__init__()
self.url = url
self.netloc = urllib_parse.urlsplit(url).netloc
self.simple_url = self.url_to_path('simple')
self.pypi_url = self.url_to_path('pypi')
self.simple_url = self._url_for_path('simple')
self.pypi_url = self._url_for_path('pypi')
def url_to_path(self, path):
# This is part of a temporary hack used to block installs of PyPI
# packages which depend on external urls only necessary until PyPI can
# block such packages themselves
self.file_storage_domain = file_storage_domain
def _url_for_path(self, path):
return urllib_parse.urljoin(self.url, path)
PyPI = Index('https://pypi.org/')
PyPI = PackageIndex(
'https://pypi.org/', file_storage_domain='files.pythonhosted.org'
)
TestPyPI = PackageIndex(
'https://test.pypi.org/', file_storage_domain='test-files.pythonhosted.org'
)
@@ -0,0 +1,141 @@
import posixpath
import re
from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse
from pipenv.patched.notpip._internal.download import path_to_url
from pipenv.patched.notpip._internal.utils.misc import splitext
from pipenv.patched.notpip._internal.utils.models import KeyBasedCompareMixin
from pipenv.patched.notpip._internal.wheel import wheel_ext
class Link(KeyBasedCompareMixin):
"""Represents a parsed link from a Package Index's simple URL
"""
def __init__(self, url, comes_from=None, requires_python=None):
"""
url:
url of the resource pointed to (href of the link)
comes_from:
instance of HTMLPage where the link was found, or string.
requires_python:
String containing the `Requires-Python` metadata field, specified
in PEP 345. This may be specified by a data-requires-python
attribute in the HTML link tag, as described in PEP 503.
"""
# url can be a UNC windows share
if url.startswith('\\\\'):
url = path_to_url(url)
self.url = url
self.comes_from = comes_from
self.requires_python = requires_python if requires_python else None
super(Link, self).__init__(
key=(self.url),
defining_class=Link
)
def __str__(self):
if self.requires_python:
rp = ' (requires-python:%s)' % self.requires_python
else:
rp = ''
if self.comes_from:
return '%s (from %s)%s' % (self.url, self.comes_from, rp)
else:
return str(self.url)
def __repr__(self):
return '<Link %s>' % self
@property
def filename(self):
_, netloc, path, _, _ = urllib_parse.urlsplit(self.url)
name = posixpath.basename(path.rstrip('/')) or netloc
name = urllib_parse.unquote(name)
assert name, ('URL %r produced no filename' % self.url)
return name
@property
def scheme(self):
return urllib_parse.urlsplit(self.url)[0]
@property
def netloc(self):
return urllib_parse.urlsplit(self.url)[1]
@property
def path(self):
return urllib_parse.unquote(urllib_parse.urlsplit(self.url)[2])
def splitext(self):
return splitext(posixpath.basename(self.path.rstrip('/')))
@property
def ext(self):
return self.splitext()[1]
@property
def url_without_fragment(self):
scheme, netloc, path, query, fragment = urllib_parse.urlsplit(self.url)
return urllib_parse.urlunsplit((scheme, netloc, path, query, None))
_egg_fragment_re = re.compile(r'[#&]egg=([^&]*)')
@property
def egg_fragment(self):
match = self._egg_fragment_re.search(self.url)
if not match:
return None
return match.group(1)
_subdirectory_fragment_re = re.compile(r'[#&]subdirectory=([^&]*)')
@property
def subdirectory_fragment(self):
match = self._subdirectory_fragment_re.search(self.url)
if not match:
return None
return match.group(1)
_hash_re = re.compile(
r'(sha1|sha224|sha384|sha256|sha512|md5)=([a-f0-9]+)'
)
@property
def hash(self):
match = self._hash_re.search(self.url)
if match:
return match.group(2)
return None
@property
def hash_name(self):
match = self._hash_re.search(self.url)
if match:
return match.group(1)
return None
@property
def show_url(self):
return posixpath.basename(self.url.split('#', 1)[0].split('?', 1)[0])
@property
def is_wheel(self):
return self.ext == wheel_ext
@property
def is_artifact(self):
"""
Determines if this points to an actual artifact (e.g. a tarball) or if
it points to an "abstract" thing like a path or a VCS location.
"""
from pipenv.patched.notpip._internal.vcs import vcs
if self.scheme in vcs.all_schemes:
return False
return True
@@ -10,11 +10,13 @@ from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
from pipenv.patched.notpip._vendor.pkg_resources import RequirementParseError
from pipenv.patched.notpip._internal.exceptions import InstallationError
from pipenv.patched.notpip._internal.req import InstallRequirement
from pipenv.patched.notpip._internal.req.constructors import (
install_req_from_editable, install_req_from_line,
)
from pipenv.patched.notpip._internal.req.req_file import COMMENT_RE
from pipenv.patched.notpip._internal.utils.deprecation import deprecated
from pipenv.patched.notpip._internal.utils.misc import (
dist_is_editable, get_installed_distributions,
dist_is_editable, get_installed_distributions, make_vcs_requirement_url,
)
logger = logging.getLogger(__name__)
@@ -99,13 +101,13 @@ def freeze(
line = line[2:].strip()
else:
line = line[len('--editable'):].strip().lstrip('=')
line_req = InstallRequirement.from_editable(
line_req = install_req_from_editable(
line,
isolated=isolated,
wheel_cache=wheel_cache,
)
else:
line_req = InstallRequirement.from_line(
line_req = install_req_from_line(
COMMENT_RE.sub('', line).strip(),
isolated=isolated,
wheel_cache=wheel_cache,
@@ -166,7 +168,13 @@ class FrozenRequirement(object):
_date_re = re.compile(r'-(20\d\d\d\d\d\d)$')
@classmethod
def from_dist(cls, dist, dependency_links):
def _init_args_from_dist(cls, dist, dependency_links):
"""
Compute and return arguments (req, editable, comments) to pass to
FrozenRequirement.__init__().
This method is for use in FrozenRequirement.from_dist().
"""
location = os.path.normcase(os.path.abspath(dist.location))
comments = []
from pipenv.patched.notpip._internal.vcs import vcs, get_src_requirement
@@ -231,12 +239,15 @@ class FrozenRequirement(object):
else:
rev = '{%s}' % date_match.group(1)
editable = True
req = '%s@%s#egg=%s' % (
svn_location,
rev,
cls.egg_name(dist)
)
return cls(dist.project_name, req, editable, comments)
egg_name = cls.egg_name(dist)
req = make_vcs_requirement_url(svn_location, rev, egg_name)
return (req, editable, comments)
@classmethod
def from_dist(cls, dist, dependency_links):
args = cls._init_args_from_dist(dist, dependency_links)
return cls(dist.project_name, *args)
@staticmethod
def egg_name(dist):
@@ -7,7 +7,6 @@ import os
from pipenv.patched.notpip._vendor import pkg_resources, requests
from pipenv.patched.notpip._internal.build_env import BuildEnvironment
from pipenv.patched.notpip._internal.compat import expanduser
from pipenv.patched.notpip._internal.download import (
is_dir_url, is_file_url, is_vcs_url, unpack_url, url_to_path,
)
@@ -15,6 +14,7 @@ from pipenv.patched.notpip._internal.exceptions import (
DirectoryUrlHashUnsupported, HashUnpinned, InstallationError,
PreviousBuildDirError, VcsHashUnsupported,
)
from pipenv.patched.notpip._internal.utils.compat import expanduser
from pipenv.patched.notpip._internal.utils.hashes import MissingHashes
from pipenv.patched.notpip._internal.utils.logging import indent_log
from pipenv.patched.notpip._internal.utils.misc import display_path, normalize_path, rmtree
@@ -65,7 +65,7 @@ class DistAbstraction(object):
"""Return a setuptools Dist object."""
raise NotImplementedError(self.dist)
def prep_for_dist(self, finder):
def prep_for_dist(self, finder, build_isolation):
"""Ensure that we can get a Dist for this requirement."""
raise NotImplementedError(self.dist)
@@ -93,36 +93,36 @@ class IsSDist(DistAbstraction):
return dist
def prep_for_dist(self, finder, build_isolation):
# Before calling "setup.py egg_info", we need to set-up the build
# environment.
build_requirements = self.req.get_pep_518_info()
should_isolate = build_isolation and build_requirements is not None
# Prepare for building. We need to:
# 1. Load pyproject.toml (if it exists)
# 2. Set up the build environment
self.req.load_pyproject_toml()
should_isolate = self.req.use_pep517 and build_isolation
if should_isolate:
# Haven't implemented PEP 517 yet, so spew a warning about it if
# build-requirements don't include setuptools and wheel.
missing_requirements = {'setuptools', 'wheel'} - {
pkg_resources.Requirement(r).key for r in build_requirements
}
if missing_requirements:
# Isolate in a BuildEnvironment and install the build-time
# requirements.
self.req.build_env = BuildEnvironment()
self.req.build_env.install_requirements(
finder, self.req.pyproject_requires,
"Installing build dependencies"
)
missing = []
if self.req.requirements_to_check:
check = self.req.requirements_to_check
missing = self.req.build_env.missing_requirements(check)
if missing:
logger.warning(
"Missing build requirements in pyproject.toml for %s.",
self.req,
)
logger.warning(
"This version of pip does not implement PEP 517 so it "
"cannot build a wheel without %s.",
" and ".join(map(repr, sorted(missing_requirements)))
"The project does not specify a build backend, and pip "
"cannot fall back to setuptools without %s.",
" and ".join(map(repr, sorted(missing)))
)
# Isolate in a BuildEnvironment and install the build-time
# requirements.
self.req.build_env = BuildEnvironment()
self.req.build_env.install_requirements(
finder, build_requirements,
"Installing build dependencies"
)
try:
self.req.run_egg_info()
except (OSError, TypeError):
@@ -136,7 +136,7 @@ class Installed(DistAbstraction):
def dist(self, finder):
return self.req.satisfied_by
def prep_for_dist(self, finder):
def prep_for_dist(self, finder, build_isolation):
pass
@@ -10,7 +10,12 @@ import sysconfig
import warnings
from collections import OrderedDict
import pipenv.patched.notpip._internal.utils.glibc
try:
import pipenv.patched.notpip._internal.utils.glibc
except ImportError:
import pipenv.patched.notpip.utils.glibc
from pipenv.patched.notpip._internal.utils.compat import get_extension_suffixes
logger = logging.getLogger(__name__)
@@ -252,10 +257,9 @@ def get_supported(versions=None, noarch=False, platform=None,
abis[0:0] = [abi]
abi3s = set()
import imp
for suffix in imp.get_suffixes():
if suffix[0].startswith('.abi'):
abi3s.add(suffix[0].split('.', 2)[1])
for suffix in get_extension_suffixes():
if suffix.startswith('.abi'):
abi3s.add(suffix.split('.', 2)[1])
abis.extend(sorted(list(abi3s)))
@@ -0,0 +1,144 @@
from __future__ import absolute_import
import io
import os
from pipenv.patched.notpip._vendor import pytoml, six
from pipenv.patched.notpip._internal.exceptions import InstallationError
def _is_list_of_str(obj):
return (
isinstance(obj, list) and
all(isinstance(item, six.string_types) for item in obj)
)
def load_pyproject_toml(use_pep517, pyproject_toml, setup_py, req_name):
"""Load the pyproject.toml file.
Parameters:
use_pep517 - Has the user requested PEP 517 processing? None
means the user hasn't explicitly specified.
pyproject_toml - Location of the project's pyproject.toml file
setup_py - Location of the project's setup.py file
req_name - The name of the requirement we're processing (for
error reporting)
Returns:
None if we should use the legacy code path, otherwise a tuple
(
requirements from pyproject.toml,
name of PEP 517 backend,
requirements we should check are installed after setting
up the build environment
)
"""
has_pyproject = os.path.isfile(pyproject_toml)
has_setup = os.path.isfile(setup_py)
if has_pyproject:
with io.open(pyproject_toml, encoding="utf-8") as f:
pp_toml = pytoml.load(f)
build_system = pp_toml.get("build-system")
else:
build_system = None
# The following cases must use PEP 517
# We check for use_pep517 equalling False because that
# means the user explicitly requested --no-use-pep517
if has_pyproject and not has_setup:
if use_pep517 is False:
raise InstallationError(
"Disabling PEP 517 processing is invalid: "
"project does not have a setup.py"
)
use_pep517 = True
elif build_system and "build-backend" in build_system:
if use_pep517 is False:
raise InstallationError(
"Disabling PEP 517 processing is invalid: "
"project specifies a build backend of {} "
"in pyproject.toml".format(
build_system["build-backend"]
)
)
use_pep517 = True
# If we haven't worked out whether to use PEP 517 yet,
# and the user hasn't explicitly stated a preference,
# we do so if the project has a pyproject.toml file.
elif use_pep517 is None:
use_pep517 = has_pyproject
# At this point, we know whether we're going to use PEP 517.
assert use_pep517 is not None
# If we're using the legacy code path, there is nothing further
# for us to do here.
if not use_pep517:
return None
if build_system is None:
# Either the user has a pyproject.toml with no build-system
# section, or the user has no pyproject.toml, but has opted in
# explicitly via --use-pep517.
# In the absence of any explicit backend specification, we
# assume the setuptools backend, and require wheel and a version
# of setuptools that supports that backend.
build_system = {
"requires": ["setuptools>=38.2.5", "wheel"],
"build-backend": "setuptools.build_meta",
}
# If we're using PEP 517, we have build system information (either
# from pyproject.toml, or defaulted by the code above).
# Note that at this point, we do not know if the user has actually
# specified a backend, though.
assert build_system is not None
# Ensure that the build-system section in pyproject.toml conforms
# to PEP 518.
error_template = (
"{package} has a pyproject.toml file that does not comply "
"with PEP 518: {reason}"
)
# Specifying the build-system table but not the requires key is invalid
if "requires" not in build_system:
raise InstallationError(
error_template.format(package=req_name, reason=(
"it has a 'build-system' table but not "
"'build-system.requires' which is mandatory in the table"
))
)
# Error out if requires is not a list of strings
requires = build_system["requires"]
if not _is_list_of_str(requires):
raise InstallationError(error_template.format(
package=req_name,
reason="'build-system.requires' is not a list of strings.",
))
backend = build_system.get("build-backend")
check = []
if backend is None:
# If the user didn't specify a backend, we assume they want to use
# the setuptools backend. But we can't be sure they have included
# a version of setuptools which supplies the backend, or wheel
# (which is neede by the backend) in their requirements. So we
# make a note to check that those requirements are present once
# we have set up the environment.
# TODO: Review this - it's quite a lot of work to check for a very
# specific case. The problem is, that case is potentially quite
# common - projects that adopted PEP 518 early for the ability to
# specify requirements to execute setup.py, but never considered
# needing to mention the build tools themselves. The original PEP
# 518 code had a similar check (but implemented in a different
# way).
backend = "setuptools.build_meta"
check = ["setuptools>=38.2.5", "wheel"]
return (requires, backend, check)
@@ -0,0 +1,298 @@
"""Backing implementation for InstallRequirement's various constructors
The idea here is that these formed a major chunk of InstallRequirement's size
so, moving them and support code dedicated to them outside of that class
helps creates for better understandability for the rest of the code.
These are meant to be used elsewhere within pip to create instances of
InstallRequirement.
"""
import logging
import os
import re
import traceback
from pipenv.patched.notpip._vendor.packaging.markers import Marker
from pipenv.patched.notpip._vendor.packaging.requirements import InvalidRequirement, Requirement
from pipenv.patched.notpip._vendor.packaging.specifiers import Specifier
from pipenv.patched.notpip._vendor.pkg_resources import RequirementParseError, parse_requirements
from pipenv.patched.notpip._internal.download import (
is_archive_file, is_url, path_to_url, url_to_path,
)
from pipenv.patched.notpip._internal.exceptions import InstallationError
from pipenv.patched.notpip._internal.models.index import PyPI, TestPyPI
from pipenv.patched.notpip._internal.models.link import Link
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
from pipenv.patched.notpip._internal.utils.misc import is_installable_dir
from pipenv.patched.notpip._internal.vcs import vcs
from pipenv.patched.notpip._internal.wheel import Wheel
__all__ = [
"install_req_from_editable", "install_req_from_line",
"parse_editable"
]
logger = logging.getLogger(__name__)
operators = Specifier._operators.keys()
def _strip_extras(path):
m = re.match(r'^(.+)(\[[^\]]+\])$', path)
extras = None
if m:
path_no_extras = m.group(1)
extras = m.group(2)
else:
path_no_extras = path
return path_no_extras, extras
def parse_editable(editable_req):
"""Parses an editable requirement into:
- a requirement name
- an URL
- extras
- editable options
Accepted requirements:
svn+http://blahblah@rev#egg=Foobar[baz]&subdirectory=version_subdir
.[some_extra]
"""
url = editable_req
# If a file path is specified with extras, strip off the extras.
url_no_extras, extras = _strip_extras(url)
if os.path.isdir(url_no_extras):
if not os.path.exists(os.path.join(url_no_extras, 'setup.py')):
raise InstallationError(
"Directory %r is not installable. File 'setup.py' not found." %
url_no_extras
)
# Treating it as code that has already been checked out
url_no_extras = path_to_url(url_no_extras)
if url_no_extras.lower().startswith('file:'):
package_name = Link(url_no_extras).egg_fragment
if extras:
return (
package_name,
url_no_extras,
Requirement("placeholder" + extras.lower()).extras,
)
else:
return package_name, url_no_extras, None
for version_control in vcs:
if url.lower().startswith('%s:' % version_control):
url = '%s+%s' % (version_control, url)
break
if '+' not in url:
raise InstallationError(
'%s should either be a path to a local project or a VCS url '
'beginning with svn+, git+, hg+, or bzr+' %
editable_req
)
vc_type = url.split('+', 1)[0].lower()
if not vcs.get_backend(vc_type):
error_message = 'For --editable=%s only ' % editable_req + \
', '.join([backend.name + '+URL' for backend in vcs.backends]) + \
' is currently supported'
raise InstallationError(error_message)
package_name = Link(url).egg_fragment
if not package_name:
raise InstallationError(
"Could not detect requirement name for '%s', please specify one "
"with #egg=your_package_name" % editable_req
)
return package_name, url, None
def deduce_helpful_msg(req):
"""Returns helpful msg in case requirements file does not exist,
or cannot be parsed.
:params req: Requirements file path
"""
msg = ""
if os.path.exists(req):
msg = " It does exist."
# Try to parse and check if it is a requirements file.
try:
with open(req, 'r') as fp:
# parse first line only
next(parse_requirements(fp.read()))
msg += " The argument you provided " + \
"(%s) appears to be a" % (req) + \
" requirements file. If that is the" + \
" case, use the '-r' flag to install" + \
" the packages specified within it."
except RequirementParseError:
logger.debug("Cannot parse '%s' as requirements \
file" % (req), exc_info=1)
else:
msg += " File '%s' does not exist." % (req)
return msg
# ---- The actual constructors follow ----
def install_req_from_editable(
editable_req, comes_from=None, isolated=False, options=None,
wheel_cache=None, constraint=False
):
name, url, extras_override = parse_editable(editable_req)
if url.startswith('file:'):
source_dir = url_to_path(url)
else:
source_dir = None
if name is not None:
try:
req = Requirement(name)
except InvalidRequirement:
raise InstallationError("Invalid requirement: '%s'" % name)
else:
req = None
return InstallRequirement(
req, comes_from, source_dir=source_dir,
editable=True,
link=Link(url),
constraint=constraint,
isolated=isolated,
options=options if options else {},
wheel_cache=wheel_cache,
extras=extras_override or (),
)
def install_req_from_line(
name, comes_from=None, isolated=False, options=None, wheel_cache=None,
constraint=False
):
"""Creates an InstallRequirement from a name, which might be a
requirement, directory containing 'setup.py', filename, or URL.
"""
if is_url(name):
marker_sep = '; '
else:
marker_sep = ';'
if marker_sep in name:
name, markers = name.split(marker_sep, 1)
markers = markers.strip()
if not markers:
markers = None
else:
markers = Marker(markers)
else:
markers = None
name = name.strip()
req = None
path = os.path.normpath(os.path.abspath(name))
link = None
extras = None
if is_url(name):
link = Link(name)
else:
p, extras = _strip_extras(path)
looks_like_dir = os.path.isdir(p) and (
os.path.sep in name or
(os.path.altsep is not None and os.path.altsep in name) or
name.startswith('.')
)
if looks_like_dir:
if not is_installable_dir(p):
raise InstallationError(
"Directory %r is not installable. Neither 'setup.py' "
"nor 'pyproject.toml' found." % name
)
link = Link(path_to_url(p))
elif is_archive_file(p):
if not os.path.isfile(p):
logger.warning(
'Requirement %r looks like a filename, but the '
'file does not exist',
name
)
link = Link(path_to_url(p))
# it's a local file, dir, or url
if link:
# Handle relative file URLs
if link.scheme == 'file' and re.search(r'\.\./', link.url):
link = Link(
path_to_url(os.path.normpath(os.path.abspath(link.path))))
# wheel file
if link.is_wheel:
wheel = Wheel(link.filename) # can raise InvalidWheelFilename
req = "%s==%s" % (wheel.name, wheel.version)
else:
# set the req to the egg fragment. when it's not there, this
# will become an 'unnamed' requirement
req = link.egg_fragment
# a requirement specifier
else:
req = name
if extras:
extras = Requirement("placeholder" + extras.lower()).extras
else:
extras = ()
if req is not None:
try:
req = Requirement(req)
except InvalidRequirement:
if os.path.sep in req:
add_msg = "It looks like a path."
add_msg += deduce_helpful_msg(req)
elif '=' in req and not any(op in req for op in operators):
add_msg = "= is not a valid operator. Did you mean == ?"
else:
add_msg = traceback.format_exc()
raise InstallationError(
"Invalid requirement: '%s'\n%s" % (req, add_msg)
)
return InstallRequirement(
req, comes_from, link=link, markers=markers,
isolated=isolated,
options=options if options else {},
wheel_cache=wheel_cache,
constraint=constraint,
extras=extras,
)
def install_req_from_req(
req, comes_from=None, isolated=False, wheel_cache=None
):
try:
req = Requirement(req)
except InvalidRequirement:
raise InstallationError("Invalid requirement: '%s'" % req)
domains_not_allowed = [
PyPI.file_storage_domain,
TestPyPI.file_storage_domain,
]
if req.url and comes_from.link.netloc in domains_not_allowed:
# Explicitly disallow pypi packages that depend on external urls
raise InstallationError(
"Packages installed from PyPI cannot depend on packages "
"which are not also hosted on PyPI.\n"
"%s depends on %s " % (comes_from.name, req)
)
return InstallRequirement(
req, comes_from, isolated=isolated, wheel_cache=wheel_cache
)
@@ -13,10 +13,12 @@ import sys
from pipenv.patched.notpip._vendor.six.moves import filterfalse
from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse
from pipenv.patched.notpip._internal import cmdoptions
from pipenv.patched.notpip._internal.cli import cmdoptions
from pipenv.patched.notpip._internal.download import get_file_content
from pipenv.patched.notpip._internal.exceptions import RequirementsFileParseError
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
from pipenv.patched.notpip._internal.req.constructors import (
install_req_from_editable, install_req_from_line,
)
__all__ = ['parse_requirements']
@@ -151,7 +153,7 @@ def process_line(line, filename, line_number, finder=None, comes_from=None,
for dest in SUPPORTED_OPTIONS_REQ_DEST:
if dest in opts.__dict__ and opts.__dict__[dest]:
req_options[dest] = opts.__dict__[dest]
yield InstallRequirement.from_line(
yield install_req_from_line(
args_str, line_comes_from, constraint=constraint,
isolated=isolated, options=req_options, wheel_cache=wheel_cache
)
@@ -159,7 +161,7 @@ def process_line(line, filename, line_number, finder=None, comes_from=None,
# yield an editable requirement
elif opts.editables:
isolated = options.isolated_mode if options else False
yield InstallRequirement.from_editable(
yield install_req_from_editable(
opts.editables[0], comes_from=line_comes_from,
constraint=constraint, isolated=isolated, wheel_cache=wheel_cache
)
@@ -1,66 +1,46 @@
from __future__ import absolute_import
import io
import logging
import os
import re
import shutil
import sys
import sysconfig
import traceback
import zipfile
from distutils.util import change_root
from email.parser import FeedParser # type: ignore
from pipenv.patched.notpip._vendor import pkg_resources, pytoml, six
from pipenv.patched.notpip._vendor.packaging import specifiers
from pipenv.patched.notpip._vendor.packaging.markers import Marker
from pipenv.patched.notpip._vendor.packaging.requirements import InvalidRequirement, Requirement
from pipenv.patched.notpip._vendor import pkg_resources, six
from pipenv.patched.notpip._vendor.packaging.requirements import Requirement
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
from pipenv.patched.notpip._vendor.packaging.version import Version
from pipenv.patched.notpip._vendor.packaging.version import parse as parse_version
from pipenv.patched.notpip._vendor.pkg_resources import RequirementParseError, parse_requirements
from pipenv.patched.notpip._vendor.pep517.wrappers import Pep517HookCaller
from pipenv.patched.notpip._internal import wheel
from pipenv.patched.notpip._internal.build_env import NoOpBuildEnvironment
from pipenv.patched.notpip._internal.compat import native_str
from pipenv.patched.notpip._internal.download import (
is_archive_file, is_url, path_to_url, url_to_path,
)
from pipenv.patched.notpip._internal.exceptions import InstallationError
from pipenv.patched.notpip._internal.locations import (
PIP_DELETE_MARKER_FILENAME, running_under_virtualenv,
)
from pipenv.patched.notpip._internal.models.link import Link
from pipenv.patched.notpip._internal.pyproject import load_pyproject_toml
from pipenv.patched.notpip._internal.req.req_uninstall import UninstallPathSet
from pipenv.patched.notpip._internal.utils.compat import native_str
from pipenv.patched.notpip._internal.utils.hashes import Hashes
from pipenv.patched.notpip._internal.utils.logging import indent_log
from pipenv.patched.notpip._internal.utils.misc import (
_make_build_dir, ask_path_exists, backup_dir, call_subprocess,
display_path, dist_in_site_packages, dist_in_usersite, ensure_dir,
get_installed_version, is_installable_dir, read_text_file, rmtree,
get_installed_version, rmtree,
)
from pipenv.patched.notpip._internal.utils.packaging import get_metadata
from pipenv.patched.notpip._internal.utils.setuptools_build import SETUPTOOLS_SHIM
from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory
from pipenv.patched.notpip._internal.utils.ui import open_spinner
from pipenv.patched.notpip._internal.vcs import vcs
from pipenv.patched.notpip._internal.wheel import Wheel, move_wheel_files
from pipenv.patched.notpip._internal.wheel import move_wheel_files
logger = logging.getLogger(__name__)
operators = specifiers.Specifier._operators.keys()
def _strip_extras(path):
m = re.match(r'^(.+)(\[[^\]]+\])$', path)
extras = None
if m:
path_no_extras = m.group(1)
extras = m.group(2)
else:
path_no_extras = path
return path_no_extras, extras
class InstallRequirement(object):
"""
@@ -87,7 +67,6 @@ class InstallRequirement(object):
if link is not None:
self.link = self.original_link = link
else:
from pipenv.patched.notpip._internal.index import Link
self.link = self.original_link = req and req.url and Link(req.url)
if extras:
@@ -128,155 +107,32 @@ class InstallRequirement(object):
self.isolated = isolated
self.build_env = NoOpBuildEnvironment()
# Constructors
# TODO: Move these out of this class into custom methods.
@classmethod
def from_editable(cls, editable_req, comes_from=None, isolated=False,
options=None, wheel_cache=None, constraint=False):
from pipenv.patched.notpip._internal.index import Link
# The static build requirements (from pyproject.toml)
self.pyproject_requires = None
name, url, extras_override = parse_editable(editable_req)
if url.startswith('file:'):
source_dir = url_to_path(url)
else:
source_dir = None
# Build requirements that we will check are available
# TODO: We don't do this for --no-build-isolation. Should we?
self.requirements_to_check = []
if name is not None:
try:
req = Requirement(name)
except InvalidRequirement:
raise InstallationError("Invalid requirement: '%s'" % name)
else:
req = None
return cls(
req, comes_from, source_dir=source_dir,
editable=True,
link=Link(url),
constraint=constraint,
isolated=isolated,
options=options if options else {},
wheel_cache=wheel_cache,
extras=extras_override or (),
)
# The PEP 517 backend we should use to build the project
self.pep517_backend = None
@classmethod
def from_req(cls, req, comes_from=None, isolated=False, wheel_cache=None):
try:
req = Requirement(req)
except InvalidRequirement:
raise InstallationError("Invalid requirement: '%s'" % req)
if req.url:
raise InstallationError(
"Direct url requirement (like %s) are not allowed for "
"dependencies" % req
)
return cls(req, comes_from, isolated=isolated, wheel_cache=wheel_cache)
@classmethod
def from_line(
cls, name, comes_from=None, isolated=False, options=None,
wheel_cache=None, constraint=False):
"""Creates an InstallRequirement from a name, which might be a
requirement, directory containing 'setup.py', filename, or URL.
"""
from pipenv.patched.notpip._internal.index import Link
if is_url(name):
marker_sep = '; '
else:
marker_sep = ';'
if marker_sep in name:
name, markers = name.split(marker_sep, 1)
markers = markers.strip()
if not markers:
markers = None
else:
markers = Marker(markers)
else:
markers = None
name = name.strip()
req = None
path = os.path.normpath(os.path.abspath(name))
link = None
extras = None
if is_url(name):
link = Link(name)
else:
p, extras = _strip_extras(path)
looks_like_dir = os.path.isdir(p) and (
os.path.sep in name or
(os.path.altsep is not None and os.path.altsep in name) or
name.startswith('.')
)
if looks_like_dir:
if not is_installable_dir(p):
raise InstallationError(
"Directory %r is not installable. File 'setup.py' "
"not found." % name
)
link = Link(path_to_url(p))
elif is_archive_file(p):
if not os.path.isfile(p):
logger.warning(
'Requirement %r looks like a filename, but the '
'file does not exist',
name
)
link = Link(path_to_url(p))
# it's a local file, dir, or url
if link:
# Handle relative file URLs
if link.scheme == 'file' and re.search(r'\.\./', link.url):
link = Link(
path_to_url(os.path.normpath(os.path.abspath(link.path))))
# wheel file
if link.is_wheel:
wheel = Wheel(link.filename) # can raise InvalidWheelFilename
req = "%s==%s" % (wheel.name, wheel.version)
else:
# set the req to the egg fragment. when it's not there, this
# will become an 'unnamed' requirement
req = link.egg_fragment
# a requirement specifier
else:
req = name
if extras:
extras = Requirement("placeholder" + extras.lower()).extras
else:
extras = ()
if req is not None:
try:
req = Requirement(req)
except InvalidRequirement:
if os.path.sep in req:
add_msg = "It looks like a path."
add_msg += deduce_helpful_msg(req)
elif '=' in req and not any(op in req for op in operators):
add_msg = "= is not a valid operator. Did you mean == ?"
else:
add_msg = traceback.format_exc()
raise InstallationError(
"Invalid requirement: '%s'\n%s" % (req, add_msg))
return cls(
req, comes_from, link=link, markers=markers,
isolated=isolated,
options=options if options else {},
wheel_cache=wheel_cache,
constraint=constraint,
extras=extras,
)
# Are we using PEP 517 for this requirement?
# After pyproject.toml has been loaded, the only valid values are True
# and False. Before loading, None is valid (meaning "use the default").
# Setting an explicit value before loading pyproject.toml is supported,
# but after loading this flag should be treated as read only.
self.use_pep517 = None
def __str__(self):
if self.req:
s = str(self.req)
if self.link:
s += ' from %s' % self.link.url
elif self.link:
s = self.link.url
else:
s = self.link.url if self.link else None
s = '<InstallRequirement>'
if self.satisfied_by is not None:
s += ' in %s' % display_path(self.satisfied_by.location)
if self.comes_from:
@@ -429,7 +285,7 @@ class InstallRequirement(object):
package is not available until we run egg_info, so the build_location
will return a temporary directory and store the _ideal_build_dir.
This is only called by self.egg_info_path to fix the temporary build
This is only called by self.run_egg_info to fix the temporary build
directory.
"""
if self.source_dir is not None:
@@ -557,48 +413,29 @@ class InstallRequirement(object):
return pp_toml
def get_pep_518_info(self):
"""Get PEP 518 build-time requirements.
def load_pyproject_toml(self):
"""Load the pyproject.toml file.
Returns the list of the packages required to build the project,
specified as per PEP 518 within the package. If `pyproject.toml` is not
present, returns None to signify not using the same.
After calling this routine, all of the attributes related to PEP 517
processing for this requirement have been set. In particular, the
use_pep517 attribute can be used to determine whether we should
follow the PEP 517 or legacy (setup.py) code path.
"""
# If pyproject.toml does not exist, don't do anything.
if not os.path.isfile(self.pyproject_toml):
return None
error_template = (
"{package} has a pyproject.toml file that does not comply "
"with PEP 518: {reason}"
pep517_data = load_pyproject_toml(
self.use_pep517,
self.pyproject_toml,
self.setup_py,
str(self)
)
with io.open(self.pyproject_toml, encoding="utf-8") as f:
pp_toml = pytoml.load(f)
# If there is no build-system table, just use setuptools and wheel.
if "build-system" not in pp_toml:
return ["setuptools", "wheel"]
# Specifying the build-system table but not the requires key is invalid
build_system = pp_toml["build-system"]
if "requires" not in build_system:
raise InstallationError(
error_template.format(package=self, reason=(
"it has a 'build-system' table but not "
"'build-system.requires' which is mandatory in the table"
))
)
# Error out if it's not a list of strings
requires = build_system["requires"]
if not _is_list_of_str(requires):
raise InstallationError(error_template.format(
package=self,
reason="'build-system.requires' is not a list of strings.",
))
return requires
if pep517_data is None:
self.use_pep517 = False
else:
self.use_pep517 = True
requires, backend, check = pep517_data
self.requirements_to_check = check
self.pyproject_requires = requires
self.pep517_backend = Pep517HookCaller(self.setup_py_dir, backend)
def run_egg_info(self):
assert self.source_dir
@@ -615,7 +452,8 @@ class InstallRequirement(object):
with indent_log():
script = SETUPTOOLS_SHIM % self.setup_py
base_cmd = [os.environ.get('PIP_PYTHON_PATH', sys.executable), '-c', script]
sys_executable = os.environ.get('PIP_PYTHON_PATH', sys.executable)
base_cmd = [sys_executable, '-c', script]
if self.isolated:
base_cmd += ["--no-user-cfg"]
egg_info_cmd = base_cmd + ['egg_info']
@@ -636,20 +474,20 @@ class InstallRequirement(object):
command_desc='python setup.py egg_info')
if not self.req:
if isinstance(parse_version(self.pkg_info()["Version"]), Version):
if isinstance(parse_version(self.metadata["Version"]), Version):
op = "=="
else:
op = "==="
self.req = Requirement(
"".join([
self.pkg_info()["Name"],
self.metadata["Name"],
op,
self.pkg_info()["Version"],
self.metadata["Version"],
])
)
self._correct_build_location()
else:
metadata_name = canonicalize_name(self.pkg_info()["Name"])
metadata_name = canonicalize_name(self.metadata["Name"])
if canonicalize_name(self.req.name) != metadata_name:
logger.warning(
'Running setup.py (path:%s) egg_info for package %s '
@@ -659,19 +497,8 @@ class InstallRequirement(object):
)
self.req = Requirement(metadata_name)
def egg_info_data(self, filename):
if self.satisfied_by is not None:
if not self.satisfied_by.has_metadata(filename):
return None
return self.satisfied_by.get_metadata(filename)
assert self.source_dir
filename = self.egg_info_path(filename)
if not os.path.exists(filename):
return None
data = read_text_file(filename)
return data
def egg_info_path(self, filename):
@property
def egg_info_path(self):
if self._egg_info_path is None:
if self.editable:
base = self.source_dir
@@ -709,8 +536,7 @@ class InstallRequirement(object):
if not filenames:
raise InstallationError(
"Files/directories (from %s) not found in %s"
% (filename, base)
"Files/directories not found in %s" % base
)
# if we have more than one match, we pick the toplevel one. This
# can easily be the case if there is a dist folder which contains
@@ -721,24 +547,18 @@ class InstallRequirement(object):
(os.path.altsep and x.count(os.path.altsep) or 0)
)
self._egg_info_path = os.path.join(base, filenames[0])
return os.path.join(self._egg_info_path, filename)
return self._egg_info_path
def pkg_info(self):
p = FeedParser()
data = self.egg_info_data('PKG-INFO')
if not data:
logger.warning(
'No PKG-INFO file found in %s',
display_path(self.egg_info_path('PKG-INFO')),
)
p.feed(data or '')
return p.close()
@property
def metadata(self):
if not hasattr(self, '_metadata'):
self._metadata = get_metadata(self.get_dist())
_requirements_section_re = re.compile(r'\[(.*?)\]')
return self._metadata
def get_dist(self):
"""Return a pkg_resources.Distribution built from self.egg_info_path"""
egg_info = self.egg_info_path('').rstrip(os.path.sep)
egg_info = self.egg_info_path.rstrip(os.path.sep)
base_dir = os.path.dirname(egg_info)
metadata = pkg_resources.PathMetadata(base_dir, egg_info)
dist_name = os.path.splitext(os.path.basename(egg_info))[0]
@@ -750,7 +570,7 @@ class InstallRequirement(object):
def assert_source_matches_version(self):
assert self.source_dir
version = self.pkg_info()['version']
version = self.metadata['version']
if self.req.specifier and version not in self.req.specifier:
logger.warning(
'Requested %s, but installing version %s',
@@ -794,10 +614,11 @@ class InstallRequirement(object):
with indent_log():
# FIXME: should we do --install-headers here too?
sys_executable = os.environ.get('PIP_PYTHON_PATH', sys.executable)
with self.build_env:
call_subprocess(
[
os.environ.get('PIP_PYTHON_PATH', sys.executable),
sys_executable,
'-c',
SETUPTOOLS_SHIM % self.setup_py
] +
@@ -877,7 +698,7 @@ class InstallRequirement(object):
def archive(self, build_dir):
assert self.source_dir
create_archive = True
archive_name = '%s-%s.zip' % (self.name, self.pkg_info()["version"])
archive_name = '%s-%s.zip' % (self.name, self.metadata["version"])
archive_path = os.path.join(build_dir, archive_name)
if os.path.exists(archive_path):
response = ask_path_exists(
@@ -1015,7 +836,8 @@ class InstallRequirement(object):
def get_install_args(self, global_options, record_filename, root, prefix,
pycompile):
install_args = [os.environ.get('PIP_PYTHON_PATH', sys.executable), "-u"]
sys_executable = os.environ.get('PIP_PYTHON_PATH', sys.executable)
install_args = [sys_executable, "-u"]
install_args.append('-c')
install_args.append(SETUPTOOLS_SHIM % self.setup_py)
install_args += list(global_options) + \
@@ -1039,104 +861,3 @@ class InstallRequirement(object):
py_ver_str, self.name)]
return install_args
def parse_editable(editable_req):
"""Parses an editable requirement into:
- a requirement name
- an URL
- extras
- editable options
Accepted requirements:
svn+http://blahblah@rev#egg=Foobar[baz]&subdirectory=version_subdir
.[some_extra]
"""
from pipenv.patched.notpip._internal.index import Link
url = editable_req
# If a file path is specified with extras, strip off the extras.
url_no_extras, extras = _strip_extras(url)
if os.path.isdir(url_no_extras):
if not os.path.exists(os.path.join(url_no_extras, 'setup.py')):
raise InstallationError(
"Directory %r is not installable. File 'setup.py' not found." %
url_no_extras
)
# Treating it as code that has already been checked out
url_no_extras = path_to_url(url_no_extras)
if url_no_extras.lower().startswith('file:'):
package_name = Link(url_no_extras).egg_fragment
if extras:
return (
package_name,
url_no_extras,
Requirement("placeholder" + extras.lower()).extras,
)
else:
return package_name, url_no_extras, None
for version_control in vcs:
if url.lower().startswith('%s:' % version_control):
url = '%s+%s' % (version_control, url)
break
if '+' not in url:
raise InstallationError(
'%s should either be a path to a local project or a VCS url '
'beginning with svn+, git+, hg+, or bzr+' %
editable_req
)
vc_type = url.split('+', 1)[0].lower()
if not vcs.get_backend(vc_type):
error_message = 'For --editable=%s only ' % editable_req + \
', '.join([backend.name + '+URL' for backend in vcs.backends]) + \
' is currently supported'
raise InstallationError(error_message)
package_name = Link(url).egg_fragment
if not package_name:
raise InstallationError(
"Could not detect requirement name for '%s', please specify one "
"with #egg=your_package_name" % editable_req
)
return package_name, url, None
def deduce_helpful_msg(req):
"""Returns helpful msg in case requirements file does not exist,
or cannot be parsed.
:params req: Requirements file path
"""
msg = ""
if os.path.exists(req):
msg = " It does exist."
# Try to parse and check if it is a requirements file.
try:
with open(req, 'r') as fp:
# parse first line only
next(parse_requirements(fp.read()))
msg += " The argument you provided " + \
"(%s) appears to be a" % (req) + \
" requirements file. If that is the" + \
" case, use the '-r' flag to install" + \
" the packages specified within it."
except RequirementParseError:
logger.debug("Cannot parse '%s' as requirements \
file" % (req), exc_info=1)
else:
msg += " File '%s' does not exist." % (req)
return msg
def _is_list_of_str(obj):
return (
isinstance(obj, list) and
all(isinstance(item, six.string_types) for item in obj)
)
+79 -57
View File
@@ -12,19 +12,22 @@ logger = logging.getLogger(__name__)
class RequirementSet(object):
def __init__(self, require_hashes=False, ignore_compatibility=True):
def __init__(self, require_hashes=False, check_supported_wheels=True, ignore_compatibility=True):
"""Create a RequirementSet.
"""
self.requirements = OrderedDict()
self.require_hashes = require_hashes
self.check_supported_wheels = check_supported_wheels
if ignore_compatibility:
self.check_supported_wheels = False
self.ignore_compatibility = True if (check_supported_wheels is False or ignore_compatibility is True) else False
# Mapping of alias: real_name
self.requirement_aliases = {}
self.unnamed_requirements = []
self.successfully_downloaded = []
self.reqs_to_cleanup = []
self.ignore_compatibility = ignore_compatibility
def __str__(self):
reqs = [req for req in self.requirements.values()
@@ -56,17 +59,22 @@ class RequirementSet(object):
requirement is applicable and has just been added.
"""
name = install_req.name
# If the markers do not match, ignore this requirement.
if not install_req.match_markers(extras_requested):
logger.info("Ignoring %s: markers '%s' don't match your "
"environment", install_req.name,
install_req.markers)
logger.info(
"Ignoring %s: markers '%s' don't match your environment",
name, install_req.markers,
)
return [], None
# This check has to come after we filter requirements with the
# environment markers.
# If the wheel is not supported, raise an error.
# Should check this after filtering out based on environment markers to
# allow specifying different wheels based on the environment/OS, in a
# single requirements file.
if install_req.link and install_req.link.is_wheel:
wheel = Wheel(install_req.link.filename)
if not wheel.supported() and not self.ignore_compatibility:
if self.check_supported_wheels and not wheel.supported():
raise InstallationError(
"%s is not a supported wheel on this platform." %
wheel.filename
@@ -78,59 +86,73 @@ class RequirementSet(object):
"a non direct req should have a parent"
)
# Unnamed requirements are scanned again and the requirement won't be
# added as a dependency until after scanning.
if not name:
# url or path requirement w/o an egg fragment
self.unnamed_requirements.append(install_req)
return [install_req], None
else:
try:
existing_req = self.get_requirement(name)
except KeyError:
existing_req = None
if (parent_req_name is None and existing_req and not
existing_req.constraint and
existing_req.extras == install_req.extras and not
existing_req.req.specifier == install_req.req.specifier):
raise InstallationError(
'Double requirement given: %s (already in %s, name=%r)'
% (install_req, existing_req, name))
if not existing_req:
# Add requirement
self.requirements[name] = install_req
# FIXME: what about other normalizations? E.g., _ vs. -?
if name.lower() != name:
self.requirement_aliases[name.lower()] = name
result = [install_req]
else:
# Assume there's no need to scan, and that we've already
# encountered this for scanning.
result = []
if not install_req.constraint and existing_req.constraint:
if (install_req.link and not (existing_req.link and
install_req.link.path == existing_req.link.path)):
self.reqs_to_cleanup.append(install_req)
raise InstallationError(
"Could not satisfy constraints for '%s': "
"installation from path or url cannot be "
"constrained to a version" % name,
)
# If we're now installing a constraint, mark the existing
# object for real installation.
existing_req.constraint = False
existing_req.extras = tuple(
sorted(set(existing_req.extras).union(
set(install_req.extras))))
logger.debug("Setting %s extras to: %s",
existing_req, existing_req.extras)
# And now we need to scan this.
result = [existing_req]
# Canonicalise to the already-added object for the backref
# check below.
install_req = existing_req
# We return install_req here to allow for the caller to add it to
# the dependency information for the parent package.
return result, install_req
try:
existing_req = self.get_requirement(name)
except KeyError:
existing_req = None
has_conflicting_requirement = (
parent_req_name is None and
existing_req and
not existing_req.constraint and
existing_req.extras == install_req.extras and
existing_req.req.specifier != install_req.req.specifier
)
if has_conflicting_requirement:
raise InstallationError(
"Double requirement given: %s (already in %s, name=%r)"
% (install_req, existing_req, name)
)
# When no existing requirement exists, add the requirement as a
# dependency and it will be scanned again after.
if not existing_req:
self.requirements[name] = install_req
# FIXME: what about other normalizations? E.g., _ vs. -?
if name.lower() != name:
self.requirement_aliases[name.lower()] = name
# We'd want to rescan this requirements later
return [install_req], install_req
# Assume there's no need to scan, and that we've already
# encountered this for scanning.
if install_req.constraint or not existing_req.constraint:
return [], existing_req
does_not_satisfy_constraint = (
install_req.link and
not (
existing_req.link and
install_req.link.path == existing_req.link.path
)
)
if does_not_satisfy_constraint:
self.reqs_to_cleanup.append(install_req)
raise InstallationError(
"Could not satisfy constraints for '%s': "
"installation from path or url cannot be "
"constrained to a version" % name,
)
# If we're now installing a constraint, mark the existing
# object for real installation.
existing_req.constraint = False
existing_req.extras = tuple(sorted(
set(existing_req.extras) | set(install_req.extras)
))
logger.debug(
"Setting %s extras to: %s",
existing_req, existing_req.extras,
)
# Return the existing requirement for addition to the parent and
# scanning again.
return [existing_req], existing_req
def has_requirement(self, project_name):
name = project_name.lower()
@@ -152,7 +174,7 @@ class RequirementSet(object):
return self.requirements[name]
if name in self.requirement_aliases:
return self.requirements[self.requirement_aliases[name]]
# raise KeyError("No project with the name %r" % project_name)
pass
def cleanup_files(self):
"""Clean up files, remove builds."""
@@ -9,9 +9,9 @@ import sysconfig
from pipenv.patched.notpip._vendor import pkg_resources
from pipenv.patched.notpip._internal.compat import WINDOWS, cache_from_source, uses_pycache
from pipenv.patched.notpip._internal.exceptions import UninstallationError
from pipenv.patched.notpip._internal.locations import bin_py, bin_user
from pipenv.patched.notpip._internal.utils.compat import WINDOWS, cache_from_source, uses_pycache
from pipenv.patched.notpip._internal.utils.logging import indent_log
from pipenv.patched.notpip._internal.utils.misc import (
FakeFile, ask, dist_in_usersite, dist_is_local, egg_link_path, is_local,
@@ -120,6 +120,8 @@ def compress_for_output_listing(paths):
folders.add(os.path.dirname(path))
files.add(path)
_normcased_files = set(map(os.path.normcase, files))
folders = compact(folders)
# This walks the tree using os.walk to not miss extra folders
@@ -130,8 +132,9 @@ def compress_for_output_listing(paths):
if fname.endswith(".pyc"):
continue
file_ = os.path.normcase(os.path.join(dirpath, fname))
if os.path.isfile(file_) and file_ not in files:
file_ = os.path.join(dirpath, fname)
if (os.path.isfile(file_) and
os.path.normcase(file_) not in _normcased_files):
# We are skipping this file. Add it to the set.
will_skip.add(file_)

Some files were not shown because too many files have changed in this diff Show More