mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 22:50:18 +00:00
Merge branch 'docs/fix-manual' into consolidate-contributing-docs
This commit is contained in:
@@ -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
|
||||
+4
-4
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,4 +0,0 @@
|
||||
phases:
|
||||
- template: phases/test.yml
|
||||
parameters:
|
||||
queue: Hosted VS2017
|
||||
@@ -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)
|
||||
=======================
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
Updated ``crayons`` patch to enable colors on native powershell but swap native blue for magenta.
|
||||
@@ -1 +0,0 @@
|
||||
Fixed unnecessary extras are added when translating markers
|
||||
@@ -1 +0,0 @@
|
||||
- Fix PIPENV_VERBOSITY parsing logic
|
||||
@@ -0,0 +1 @@
|
||||
Pipenv will no longer disable user-mode installation when the ``--system`` flag is passed in.
|
||||
@@ -0,0 +1 @@
|
||||
Fixed an issue with attempting to render unicode output in non-unicode locales.
|
||||
@@ -0,0 +1 @@
|
||||
Fixed a bug which could cause failures to occur when parsing python entries from global pyenv version files.
|
||||
@@ -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
@@ -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
@@ -1,4 +1,4 @@
|
||||
from .cli import cli
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
cli(auto_envvar_prefix="PIPENV")
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
# // ) ) / / // ) ) //___) ) // ) ) || / /
|
||||
# //___/ / / / //___/ / // // / / || / /
|
||||
# // / / // ((____ // / / ||/ /
|
||||
__version__ = "2018.10.14.dev0"
|
||||
__version__ = "2018.11.15.dev0"
|
||||
|
||||
+120
-23
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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}")
|
||||
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
VERSION = 'master'
|
||||
@@ -1,3 +0,0 @@
|
||||
|
||||
|
||||
from .file import TOMLFile
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 @@
|
||||
__version__ = "18.0"
|
||||
__version__ = "18.1"
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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, '')
|
||||
+18
-14
@@ -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
|
||||
+106
-11
@@ -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
|
||||
+25
-4
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user