mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 22:50:18 +00:00
Merge branch 'master' into peep-006-update-proposal
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
# Taken from https://github.com/pypa/pip/blob/ceaf75b9ede9a9c25bcee84fe512fa6774889685/.azure-pipelines/scripts/New-RAMDisk.ps1
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true,
|
||||
HelpMessage="Drive letter to use for the RAMDisk")]
|
||||
[String]$drive,
|
||||
[Parameter(HelpMessage="Size to allocate to the RAMDisk")]
|
||||
[UInt64]$size=1GB
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
Write-Output "Installing FS-iSCSITarget-Server"
|
||||
Install-WindowsFeature -Name FS-iSCSITarget-Server
|
||||
|
||||
Write-Output "Starting MSiSCSI"
|
||||
Start-Service MSiSCSI
|
||||
$retry = 10
|
||||
do {
|
||||
$service = Get-Service MSiSCSI
|
||||
if ($service.Status -eq "Running") {
|
||||
break;
|
||||
}
|
||||
$retry--
|
||||
Start-Sleep -Milliseconds 500
|
||||
} until ($retry -eq 0)
|
||||
|
||||
$service = Get-Service MSiSCSI
|
||||
if ($service.Status -ne "Running") {
|
||||
throw "MSiSCSI is not running"
|
||||
}
|
||||
|
||||
Write-Output "Configuring Firewall"
|
||||
Get-NetFirewallServiceFilter -Service MSiSCSI | Enable-NetFirewallRule
|
||||
|
||||
Write-Output "Configuring RAMDisk"
|
||||
# Must use external-facing IP address, otherwise New-IscsiTargetPortal is
|
||||
# unable to connect.
|
||||
$ip = (
|
||||
Get-NetIPAddress -AddressFamily IPv4 |
|
||||
Where-Object {$_.IPAddress -ne "127.0.0.1"}
|
||||
)[0].IPAddress
|
||||
if (
|
||||
-not (Get-IscsiServerTarget -ComputerName localhost | Where-Object {$_.TargetName -eq "ramdisks"})
|
||||
) {
|
||||
New-IscsiServerTarget `
|
||||
-ComputerName localhost `
|
||||
-TargetName ramdisks `
|
||||
-InitiatorId IPAddress:$ip
|
||||
}
|
||||
|
||||
$newVirtualDisk = New-IscsiVirtualDisk `
|
||||
-ComputerName localhost `
|
||||
-Path ramdisk:local$drive.vhdx `
|
||||
-Size $size
|
||||
Add-IscsiVirtualDiskTargetMapping `
|
||||
-ComputerName localhost `
|
||||
-TargetName ramdisks `
|
||||
-Path ramdisk:local$drive.vhdx
|
||||
|
||||
Write-Output "Connecting to iSCSI"
|
||||
New-IscsiTargetPortal -TargetPortalAddress $ip
|
||||
Get-IscsiTarget | Where-Object {!$_.IsConnected} | Connect-IscsiTarget
|
||||
|
||||
Write-Output "Configuring disk"
|
||||
$newDisk = Get-IscsiConnection |
|
||||
Get-Disk |
|
||||
Where-Object {$_.SerialNumber -eq $newVirtualDisk.SerialNumber}
|
||||
|
||||
Set-Disk -InputObject $newDisk -IsOffline $false
|
||||
Initialize-Disk -InputObject $newDisk -PartitionStyle MBR
|
||||
New-Partition -InputObject $newDisk -UseMaximumSize -DriveLetter $drive
|
||||
|
||||
Format-Volume -DriveLetter $drive -NewFileSystemLabel Temp -FileSystem NTFS
|
||||
@@ -6,16 +6,17 @@ steps:
|
||||
addToPath: true
|
||||
displayName: Use Python $(python.version)
|
||||
|
||||
- template: install-dependencies.yml
|
||||
- bash: |
|
||||
PYTHON_PATH=$(python -c 'import sys; print(sys.executable)')
|
||||
echo "##vso[task.setvariable variable=PY_EXE]$PYTHON_PATH"
|
||||
displayName: Set Python Path
|
||||
|
||||
- script: |
|
||||
echo '##vso[task.setvariable variable=PIPENV_DEFAULT_PYTHON_VERSION]'$(python.version)
|
||||
env:
|
||||
PYTHON_VERSION: $(python.version)
|
||||
|
||||
- template: create-virtualenv.yml
|
||||
parameters:
|
||||
python_version: $(python.version)
|
||||
- template: install-dependencies.yml
|
||||
|
||||
- script: |
|
||||
python -m pip install --upgrade wheel pip setuptools twine readme_renderer[md]
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
parameters:
|
||||
python_version: ''
|
||||
|
||||
steps:
|
||||
|
||||
- script: |
|
||||
echo "##vso[task.setvariable variable=LANG]C.UTF-8"
|
||||
echo "##vso[task.setvariable variable=PIP_PROCESS_DEPENDENCY_LINKS]1"
|
||||
displayName: Set Environment Variables
|
||||
|
||||
- ${{ if eq(parameters.vmImage, 'windows-2019') }}:
|
||||
- powershell: |
|
||||
pip install certifi
|
||||
$env:PYTHON_PATH=$(python -c "import sys; print(sys.executable)")
|
||||
$env:CERTIFI_CONTENT=$(python -m certifi)
|
||||
echo "##vso[task.setvariable variable=GIT_SSL_CAINFO]$env:CERTIFI_CONTENT"
|
||||
echo "##vso[task.setvariable variable=PY_EXE]$env:PYTHON_PATH"
|
||||
displayName: Set Python Path
|
||||
env:
|
||||
PYTHONWARNINGS: 'ignore:DEPRECATION'
|
||||
- ${{ if ne(parameters.vmImage, 'windows-2019') }}:
|
||||
- bash: |
|
||||
pip install certifi
|
||||
PYTHON_PATH=$(python -c 'import sys; print(sys.executable)')
|
||||
CERTIFI_CONTENT=$(python -m certifi)
|
||||
echo "##vso[task.setvariable variable=GIT_SSL_CAINFO]$CERTIFI_CONTENT"
|
||||
echo "##vso[task.setvariable variable=PY_EXE]$PYTHON_PATH"
|
||||
displayName: Set Python Path
|
||||
env:
|
||||
PYTHONWARNINGS: 'ignore:DEPRECATION'
|
||||
|
||||
- script: |
|
||||
echo "Python path: $(PY_EXE)"
|
||||
echo "GIT_SSL_CAINFO: $(GIT_SSL_CAINFO)"
|
||||
echo "PIPENV PYTHON VERSION: $(python.version)"
|
||||
echo "python_version: ${{ parameters.python_version }}"
|
||||
git submodule sync
|
||||
git submodule update --init --recursive
|
||||
$(PY_EXE) -m pipenv install --deploy --dev --python="$(PY_EXE)"
|
||||
env:
|
||||
PIPENV_DEFAULT_PYTHON_VERSION: ${{ parameters.python_version }}
|
||||
PYTHONWARNINGS: 'ignore:DEPRECATION'
|
||||
PIPENV_NOSPIN: '1'
|
||||
displayName: Make Virtualenv
|
||||
@@ -1,5 +1,31 @@
|
||||
parameters:
|
||||
python_version: ''
|
||||
|
||||
steps:
|
||||
- script: 'python -m pip install --upgrade pip setuptools wheel -e .[dev,tests] --upgrade'
|
||||
|
||||
- script: |
|
||||
echo "##vso[task.setvariable variable=LANG]C.UTF-8"
|
||||
echo "##vso[task.setvariable variable=PIP_PROCESS_DEPENDENCY_LINKS]1"
|
||||
displayName: Set Environment Variables
|
||||
|
||||
- script: |
|
||||
echo "Python path: $(PY_EXE)"
|
||||
echo "GIT_SSL_CAINFO: $(GIT_SSL_CAINFO)"
|
||||
echo "PIPENV PYTHON VERSION: $(python.version)"
|
||||
echo "python_version: ${{ parameters.python_version }}"
|
||||
git submodule sync
|
||||
git submodule update --init --recursive
|
||||
$(PY_EXE) -m pip install --upgrade --upgrade-strategy=eager pip setuptools wheel
|
||||
$(PY_EXE) -m pip install "virtualenv<20"
|
||||
env:
|
||||
PIPENV_DEFAULT_PYTHON_VERSION: ${{ parameters.python_version }}
|
||||
PYTHONWARNINGS: 'ignore:DEPRECATION'
|
||||
PIPENV_NOSPIN: '1'
|
||||
displayName: Make Virtualenv
|
||||
|
||||
- script: |
|
||||
$(PY_EXE) -m pip install -e . --upgrade
|
||||
$(PY_EXE) -m pipenv install --deploy --dev --python="$(PY_EXE)"
|
||||
displayName: Upgrade Pip & Install Pipenv
|
||||
env:
|
||||
PYTHONWARNINGS: 'ignore:DEPRECATION'
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
steps:
|
||||
- script: |
|
||||
# When you paste this, please make sure the indentation is preserved
|
||||
# Fail out if any setups fail
|
||||
set -e
|
||||
|
||||
# Delete old Pythons
|
||||
rm -rf $AGENT_TOOLSDIRECTORY/Python/2.7.16
|
||||
rm -rf $AGENT_TOOLSDIRECTORY/Python/3.5.7
|
||||
rm -rf $AGENT_TOOLSDIRECTORY/Python/3.7.3
|
||||
[ -e $AGENT_TOOLSDIRECTORY/Python/3.7.2 ] && [ -e $AGENT_TOOLSDIRECTORY/Python/3.5.5 ] && [ -e $AGENT_TOOLSDIRECTORY/Python/2.7.15 ] && exit 0
|
||||
# Download new Pythons
|
||||
azcopy --recursive \
|
||||
--source https://vstsagenttools.blob.core.windows.net/tools/hostedtoolcache/linux/Python/2.7.15 \
|
||||
--destination $AGENT_TOOLSDIRECTORY/Python/2.7.15
|
||||
|
||||
azcopy --recursive \
|
||||
--source https://vstsagenttools.blob.core.windows.net/tools/hostedtoolcache/linux/Python/3.5.5 \
|
||||
--destination $AGENT_TOOLSDIRECTORY/Python/3.5.5
|
||||
|
||||
azcopy --recursive \
|
||||
--source https://vstsagenttools.blob.core.windows.net/tools/hostedtoolcache/linux/Python/3.7.2 \
|
||||
--destination $AGENT_TOOLSDIRECTORY/Python/3.7.2
|
||||
|
||||
# Install new Pythons
|
||||
original_directory=$PWD
|
||||
setups=$(find $AGENT_TOOLSDIRECTORY/Python -name setup.sh)
|
||||
for setup in $setups; do
|
||||
chmod +x $setup;
|
||||
cd $(dirname $setup);
|
||||
./$(basename $setup);
|
||||
cd $original_directory;
|
||||
done;
|
||||
displayName: 'Workaround: roll back Python versions'
|
||||
@@ -2,13 +2,29 @@ parameters:
|
||||
python_version: ''
|
||||
|
||||
steps:
|
||||
|
||||
- bash: |
|
||||
pip install certifi
|
||||
PYTHON_PATH=$(python -c 'import sys; print(sys.executable)')
|
||||
CERTIFI_CONTENT=$(python -m certifi)
|
||||
echo "##vso[task.setvariable variable=GIT_SSL_CAINFO]$CERTIFI_CONTENT"
|
||||
echo "##vso[task.setvariable variable=PY_EXE]$PYTHON_PATH"
|
||||
displayName: Set Python Path
|
||||
env:
|
||||
PYTHONWARNINGS: 'ignore:DEPRECATION'
|
||||
|
||||
- template: install-dependencies.yml
|
||||
parameters:
|
||||
python_version: ${{ parameters.python_version }}
|
||||
|
||||
- script: |
|
||||
# Fix Git SSL errors
|
||||
echo "Using pipenv python version: $(PIPENV_DEFAULT_PYTHON_VERSION)"
|
||||
git submodule sync && git submodule update --init --recursive
|
||||
pipenv run pytest --junitxml=test-results.xml
|
||||
pipenv run pytest -n 4 --junitxml=junit/test-results.xml
|
||||
displayName: Run integration tests
|
||||
env:
|
||||
PYTHONWARNINGS: ignore:DEPRECATION
|
||||
PIPENV_NOSPIN: '1'
|
||||
PIPENV_DEFAULT_PYTHON_VERSION: ${{ parameters.python_version }}
|
||||
GIT_SSH_COMMAND: ssh -o StrictHostKeyChecking=accept-new -o CheckHostIP=no
|
||||
|
||||
@@ -1,21 +1,56 @@
|
||||
parameters:
|
||||
python_version: ''
|
||||
python_architecture: ''
|
||||
|
||||
steps:
|
||||
- powershell: |
|
||||
subst T: "$env:TEMP"
|
||||
Write-Host "##vso[task.setvariable variable=TEMP]T:\"
|
||||
Write-Host "##vso[task.setvariable variable=TMP]T:\"
|
||||
Write-Host "##vso[task.setvariable variable=PIPENV_DEFAULT_PYTHON_VERSION]$env:PYTHON_VERSION"
|
||||
Write-Host "##vso[task.setvariable variable=PIPENV_NOSPIN]1"
|
||||
displayName: Fix Temp Variable
|
||||
env:
|
||||
PYTHON_VERSION: ${{ parameters.python_version }}
|
||||
- task: PowerShell@2
|
||||
inputs:
|
||||
filePath: .azure-pipelines/scripts/New-RAMDisk.ps1
|
||||
arguments: "-Drive R -Size 2GB"
|
||||
displayName: Setup RAMDisk
|
||||
|
||||
- script: |
|
||||
git submodule sync && git submodule update --init --recursive
|
||||
pipenv run pytest -ra --ignore=pipenv\patched --ignore=pipenv\vendor --junitxml=test-results.xml tests
|
||||
displayName: Run integration tests
|
||||
env:
|
||||
PYTHONWARNINGS: 'ignore:DEPRECATION'
|
||||
PIPENV_NOSPIN: '1'
|
||||
- powershell: |
|
||||
mkdir R:\virtualenvs
|
||||
$acl = Get-Acl "R:\"
|
||||
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
|
||||
"Everyone", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow"
|
||||
)
|
||||
$acl.AddAccessRule($rule)
|
||||
Set-Acl "R:\" $acl
|
||||
displayName: Set RAMDisk Permissions
|
||||
|
||||
- powershell: |
|
||||
Write-Host "##vso[task.setvariable variable=TEMP]R:\"
|
||||
Write-Host "##vso[task.setvariable variable=TMP]R:\"
|
||||
Write-Host "##vso[task.setvariable variable=WORKON_HOME]R:\virtualenvs"
|
||||
Write-Host "##vso[task.setvariable variable=PIPENV_DEFAULT_PYTHON_VERSION]$env:PYTHON_VERSION"
|
||||
Write-Host "##vso[task.setvariable variable=PIPENV_NOSPIN]1"
|
||||
displayName: Fix Temp Variable
|
||||
env:
|
||||
PYTHON_VERSION: ${{ parameters.python_version }}
|
||||
|
||||
- powershell: |
|
||||
pip install certifi
|
||||
$env:PYTHON_PATH=$(python -c "import sys; print(sys.executable)")
|
||||
$env:CERTIFI_CONTENT=$(python -m certifi)
|
||||
echo "##vso[task.setvariable variable=GIT_SSL_CAINFO]$env:CERTIFI_CONTENT"
|
||||
echo "##vso[task.setvariable variable=PY_EXE]$env:PYTHON_PATH"
|
||||
displayName: Set Python Path
|
||||
env:
|
||||
PYTHONWARNINGS: 'ignore:DEPRECATION'
|
||||
|
||||
- template: install-dependencies.yml
|
||||
parameters:
|
||||
python_version: ${{ parameters.python_version }}
|
||||
|
||||
- script: |
|
||||
git submodule sync
|
||||
git submodule update --init --recursive
|
||||
pipenv run pytest -ra -n 4 --junit-xml=junit/test-results.xml tests/
|
||||
failOnStderr: false
|
||||
displayName: Run integration tests
|
||||
env:
|
||||
TEMP: 'R:\'
|
||||
PYTHONWARNINGS: 'ignore:DEPRECATION'
|
||||
PIPENV_NOSPIN: 1
|
||||
GIT_SSH_COMMAND: ssh -o StrictHostKeyChecking=accept-new -o CheckHostIP=no
|
||||
|
||||
@@ -6,22 +6,16 @@ steps:
|
||||
addToPath: true
|
||||
displayName: Use Python $(python.version)
|
||||
|
||||
- template: install-dependencies.yml
|
||||
|
||||
- script: |
|
||||
echo '##vso[task.setvariable variable=PIPENV_DEFAULT_PYTHON_VERSION]'$(python.version)
|
||||
env:
|
||||
PYTHON_VERSION: $(python.version)
|
||||
|
||||
- template: create-virtualenv.yml
|
||||
parameters:
|
||||
python_version: $(python.version)
|
||||
|
||||
- ${{ if eq(parameters.vmImage, 'windows-2019') }}:
|
||||
- ${{ if eq(parameters.vmImage, 'windows-latest') }}:
|
||||
- template: run-tests-windows.yml
|
||||
parameters:
|
||||
python_version: $(python.version)
|
||||
- ${{ if ne(parameters.vmImage, 'windows-2019') }}:
|
||||
- ${{ if ne(parameters.vmImage, 'windows-latest') }}:
|
||||
- template: run-tests-linux.yml
|
||||
parameters:
|
||||
python_version: $(python.version)
|
||||
@@ -29,6 +23,6 @@ steps:
|
||||
- task: PublishTestResults@2
|
||||
displayName: Publish Test Results
|
||||
inputs:
|
||||
testResultsFiles: '**/test-results.xml'
|
||||
testResultsFiles: '**/junit/*.xml'
|
||||
testRunTitle: 'Python $(python.version)'
|
||||
condition: succeededOrFailed()
|
||||
|
||||
@@ -9,25 +9,24 @@ steps:
|
||||
addToPath: true
|
||||
displayName: Use Python $(python.version)
|
||||
|
||||
- bash: |
|
||||
python -m pip install --upgrade --upgrade-strategy=eager pip requests certifi wheel setuptools
|
||||
PYTHON_PATH=$(python -c 'import sys; print(sys.executable)')
|
||||
CERTIFI_CONTENT=$(python -m certifi)
|
||||
echo "##vso[task.setvariable variable=PY_EXE]$PYTHON_PATH"
|
||||
echo "##vso[task.setvariable variable=GIT_SSL_CAINFO]$CERTIFI_CONTENT"
|
||||
displayName: Set Python Path
|
||||
|
||||
- template: install-dependencies.yml
|
||||
|
||||
- script: |
|
||||
echo '##vso[task.setvariable variable=PIPENV_DEFAULT_PYTHON_VERSION]'$(python.version)
|
||||
env:
|
||||
PYTHON_VERSION: $(python.version)
|
||||
|
||||
- template: create-virtualenv.yml
|
||||
parameters:
|
||||
python_version: $(python.version)
|
||||
|
||||
- script: |
|
||||
python -m pip install --upgrade invoke requests parver bs4 vistir towncrier pip setuptools wheel --upgrade-strategy=eager
|
||||
python -m pip install --upgrade invoke parver bs4 vistir towncrier --upgrade-strategy=eager
|
||||
python -m invoke vendoring.update
|
||||
displayName: Run Vendor Scripts
|
||||
env:
|
||||
PY_EXE: $(PY_EXE)
|
||||
GIT_SSL_CAINFO: $(GIT_SSL_CAINFO)
|
||||
LANG: $(LANG)
|
||||
PIPENV_DEFAULT_PYTHON_VERSION: '${{ parameters.python_version }}'
|
||||
PIPENV_DEFAULT_PYTHON_VERSION: $(python.version)
|
||||
PYTHONWARNINGS: ignore:DEPRECATION
|
||||
PIPENV_NOSPIN: '1'
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{matrix.os}} / ${{ matrix.python-version }}
|
||||
runs-on: ${{ matrix.os }}-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: [2.7, 3.6, 3.7, 3.8]
|
||||
os: [MacOS, Ubuntu, Windows]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Get python path
|
||||
id: python-path
|
||||
run: |
|
||||
echo ::set-output name=path::$(python -c "import sys; print(sys.executable)")
|
||||
|
||||
- name: Install latest pip, setuptools, wheel
|
||||
run: |
|
||||
python -m pip install --upgrade pip setuptools wheel --upgrade-strategy=eager
|
||||
- name: Install dependencies
|
||||
env:
|
||||
PIPENV_DEFAULT_PYTHON_VERSION: ${{ matrix.python-version }}
|
||||
PYTHONWARNINGS: ignore:DEPRECATION
|
||||
PYTHONIOENCODING: 'utf-8'
|
||||
GIT_ASK_YESNO: 'false'
|
||||
run: |
|
||||
git submodule sync
|
||||
git submodule update --init --recursive
|
||||
python -m pip install "virtualenv<20"
|
||||
python -m pip install -e . --upgrade
|
||||
pipenv install --deploy --dev --python=${{ steps.python-path.outputs.path }}
|
||||
- name: Run tests
|
||||
env:
|
||||
PIPENV_DEFAULT_PYTHON_VERSION: ${{ matrix.python-version }}
|
||||
PYTHONWARNINGS: ignore:DEPRECATION
|
||||
PIPENV_NOSPIN: '1'
|
||||
CI: '1'
|
||||
GIT_ASK_YESNO: 'false'
|
||||
PYPI_VENDOR_DIR: './tests/pypi/'
|
||||
PYTHONIOENCODING: 'utf-8'
|
||||
GIT_SSH_COMMAND: ssh -o StrictHostKeyChecking=accept-new -o CheckHostIP=no
|
||||
run: |
|
||||
pipenv run pytest -ra -n 4 tests
|
||||
Generated
+357
-196
@@ -5,11 +5,13 @@
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
"sources": [{
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}]
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {},
|
||||
"develop": {
|
||||
@@ -37,10 +39,10 @@
|
||||
},
|
||||
"arpeggio": {
|
||||
"hashes": [
|
||||
"sha256:a5258b84f76661d558492fa87e42db634df143685a0e51802d59cae7daad8732",
|
||||
"sha256:dc5c0541e7cc2c6033dc0338133436abfac53655624784736e9bc8bd35e56583"
|
||||
"sha256:948ce06163a48a72c97f4fe79ad3d1c1330b6fec4f22ece182fb60ef60bd022b",
|
||||
"sha256:b9178917594bb9758002faed31e1e1c968b5ea7f2a8f78fd4a5b8fecaccfcfcd"
|
||||
],
|
||||
"version": "==1.9.0"
|
||||
"version": "==1.9.2"
|
||||
},
|
||||
"atomicwrites": {
|
||||
"hashes": [
|
||||
@@ -51,51 +53,51 @@
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79",
|
||||
"sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"
|
||||
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
|
||||
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==19.1.0"
|
||||
"version": "==19.3.0"
|
||||
},
|
||||
"babel": {
|
||||
"hashes": [
|
||||
"sha256:af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab",
|
||||
"sha256:e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28"
|
||||
"sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38",
|
||||
"sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.7.0"
|
||||
"version": "==2.8.0"
|
||||
},
|
||||
"backports.functools-lru-cache": {
|
||||
"hashes": [
|
||||
"sha256:9d98697f088eb1b0fa451391f91afb5e3ebde16bbdb272819fd091151fda4f1a",
|
||||
"sha256:f0b0e4eba956de51238e17573b7087e852dfe9854afd2e9c873f73fc0ca0a6dd"
|
||||
"sha256:0bada4c2f8a43d533e4ecb7a12214d9420e66eb206d54bf2d682581ca4b80848",
|
||||
"sha256:8fde5f188da2d593bd5bc0be98d9abc46c95bb8a9dde93429570192ee6cc2d4a"
|
||||
],
|
||||
"markers": "python_version < '3'",
|
||||
"version": "==1.5"
|
||||
"markers": "python_version < '3.2'",
|
||||
"version": "==1.6.1"
|
||||
},
|
||||
"beautifulsoup4": {
|
||||
"hashes": [
|
||||
"sha256:034740f6cb549b4e932ae1ab975581e6103ac8f942200a0e9759065984391858",
|
||||
"sha256:945065979fb8529dd2f37dbb58f00b661bdbcbebf954f93b32fdf5263ef35348",
|
||||
"sha256:ba6d5c59906a85ac23dadfe5c88deaf3e179ef565f4898671253e50a78680718"
|
||||
"sha256:05fd825eb01c290877657a56df4c6e4c311b3965bda790c613a3d6fb01a5462a",
|
||||
"sha256:9fbb4d6e48ecd30bcacc5b63b94088192dcda178513b2ae3c394229f8911b887",
|
||||
"sha256:e1505eeed31b0f4ce2dbb3bc8eb256c04cc2b3b72af7d551a4ab6efd5cbe5dae"
|
||||
],
|
||||
"version": "==4.7.1"
|
||||
"version": "==4.8.2"
|
||||
},
|
||||
"black": {
|
||||
"hashes": [
|
||||
"sha256:09a9dcb7c46ed496a9850b76e4e825d6049ecd38b611f1224857a79bd985a8cf",
|
||||
"sha256:68950ffd4d9169716bcb8719a56c07a2f4485354fec061cdd5910aa07369731c"
|
||||
"sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b",
|
||||
"sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==19.3b0"
|
||||
"version": "==19.10b0"
|
||||
},
|
||||
"bleach": {
|
||||
"hashes": [
|
||||
"sha256:213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16",
|
||||
"sha256:3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa"
|
||||
"sha256:cc8da25076a1fe56c3ac63671e2194458e0c4d9c7becfd52ca251650d517903c",
|
||||
"sha256:e78e426105ac07026ba098f04de8abe9b6e3e98b5befbf89b51a5ef0a4292b03"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==3.1.0"
|
||||
"version": "==3.1.4"
|
||||
},
|
||||
"bs4": {
|
||||
"hashes": [
|
||||
@@ -105,10 +107,43 @@
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939",
|
||||
"sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695"
|
||||
"sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3",
|
||||
"sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"
|
||||
],
|
||||
"version": "==2019.6.16"
|
||||
"version": "==2019.11.28"
|
||||
},
|
||||
"cffi": {
|
||||
"hashes": [
|
||||
"sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff",
|
||||
"sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b",
|
||||
"sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac",
|
||||
"sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0",
|
||||
"sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384",
|
||||
"sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26",
|
||||
"sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6",
|
||||
"sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b",
|
||||
"sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e",
|
||||
"sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd",
|
||||
"sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2",
|
||||
"sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66",
|
||||
"sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc",
|
||||
"sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8",
|
||||
"sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55",
|
||||
"sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4",
|
||||
"sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5",
|
||||
"sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d",
|
||||
"sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78",
|
||||
"sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa",
|
||||
"sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793",
|
||||
"sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f",
|
||||
"sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a",
|
||||
"sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f",
|
||||
"sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30",
|
||||
"sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f",
|
||||
"sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3",
|
||||
"sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"
|
||||
],
|
||||
"version": "==1.14.0"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
@@ -127,34 +162,61 @@
|
||||
},
|
||||
"click": {
|
||||
"hashes": [
|
||||
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
|
||||
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
|
||||
"sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc",
|
||||
"sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==7.0"
|
||||
"version": "==7.1.1"
|
||||
},
|
||||
"colorama": {
|
||||
"hashes": [
|
||||
"sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d",
|
||||
"sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"
|
||||
"sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff",
|
||||
"sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"
|
||||
],
|
||||
"version": "==0.4.1"
|
||||
"version": "==0.4.3"
|
||||
},
|
||||
"configparser": {
|
||||
"hashes": [
|
||||
"sha256:8be81d89d6e7b4c0d4e44bcc525845f6da25821de80cb5e06e7e0238a2899e32",
|
||||
"sha256:da60d0014fd8c55eb48c1c5354352e363e2d30bbf7057e5e171a468390184c75"
|
||||
"sha256:254c1d9c79f60c45dfde850850883d5aaa7f19a23f13561243a050d5a7c3fe4c",
|
||||
"sha256:c7d282687a5308319bf3d2e7706e575c635b0a470342641c93bea0ea3b5331df"
|
||||
],
|
||||
"markers": "python_version < '3'",
|
||||
"version": "==3.7.4"
|
||||
"markers": "python_version < '3.2'",
|
||||
"version": "==4.0.2"
|
||||
},
|
||||
"contextlib2": {
|
||||
"hashes": [
|
||||
"sha256:509f9419ee91cdd00ba34443217d5ca51f5a364a404e1dce9e8979cea969ca48",
|
||||
"sha256:f5260a6e679d2ff42ec91ec5252f4eeffdcf21053db9113bd0a8e4d953769c00"
|
||||
"sha256:01f490098c18b19d2bd5bb5dc445b2054d2fa97f09a4280ba2c5f3c394c8162e",
|
||||
"sha256:3355078a159fbb44ee60ea80abd0d87b80b78c248643b49aa6d94673b413609b"
|
||||
],
|
||||
"markers": "python_version < '3'",
|
||||
"version": "==0.5.5"
|
||||
"version": "==0.6.0.post1"
|
||||
},
|
||||
"cryptography": {
|
||||
"hashes": [
|
||||
"sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c",
|
||||
"sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595",
|
||||
"sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad",
|
||||
"sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651",
|
||||
"sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2",
|
||||
"sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff",
|
||||
"sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d",
|
||||
"sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42",
|
||||
"sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d",
|
||||
"sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e",
|
||||
"sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912",
|
||||
"sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793",
|
||||
"sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13",
|
||||
"sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7",
|
||||
"sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0",
|
||||
"sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879",
|
||||
"sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f",
|
||||
"sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9",
|
||||
"sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2",
|
||||
"sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf",
|
||||
"sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.8"
|
||||
},
|
||||
"decorator": {
|
||||
"hashes": [
|
||||
@@ -163,13 +225,19 @@
|
||||
],
|
||||
"version": "==4.4.0"
|
||||
},
|
||||
"distlib": {
|
||||
"hashes": [
|
||||
"sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"
|
||||
],
|
||||
"version": "==0.3.0"
|
||||
},
|
||||
"docutils": {
|
||||
"hashes": [
|
||||
"sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
|
||||
"sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274",
|
||||
"sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6"
|
||||
"sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af",
|
||||
"sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"
|
||||
],
|
||||
"version": "==0.14"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.16"
|
||||
},
|
||||
"entrypoints": {
|
||||
"hashes": [
|
||||
@@ -181,51 +249,58 @@
|
||||
},
|
||||
"enum34": {
|
||||
"hashes": [
|
||||
"sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850",
|
||||
"sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a",
|
||||
"sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79",
|
||||
"sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"
|
||||
"sha256:a98a201d6de3f2ab3db284e70a33b0f896fbf35f8086594e8c9e74b909058d53",
|
||||
"sha256:c3858660960c984d6ab0ebad691265180da2b43f07e061c0f8dca9ef3cffd328",
|
||||
"sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248"
|
||||
],
|
||||
"markers": "python_version < '3'",
|
||||
"version": "==1.1.6"
|
||||
"version": "==1.1.10"
|
||||
},
|
||||
"execnet": {
|
||||
"hashes": [
|
||||
"sha256:027ee5d961afa01e97b90d6ccc34b4ed976702bc58e7f092b3c513ea288cb6d2",
|
||||
"sha256:752a3786f17416d491f833a29217dda3ea4a471fc5269c492eebcee8cc4772d3"
|
||||
"sha256:cacb9df31c9680ec5f95553976c4da484d407e85e41c83cb812aa014f0eddc50",
|
||||
"sha256:d4efd397930c46415f62f8a31388d6be4f27a91d7550eb79bc64a756e0056547"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.6.0"
|
||||
"version": "==1.7.1"
|
||||
},
|
||||
"filelock": {
|
||||
"hashes": [
|
||||
"sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59",
|
||||
"sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"
|
||||
],
|
||||
"version": "==3.0.12"
|
||||
},
|
||||
"flake8": {
|
||||
"hashes": [
|
||||
"sha256:859996073f341f2670741b51ec1e67a01da142831aa1fdc6242dbf88dffbe661",
|
||||
"sha256:a796a115208f5c03b18f332f7c11729812c8c3ded6c46319c59b53efd3819da8"
|
||||
"sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb",
|
||||
"sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==3.7.7"
|
||||
"version": "==3.7.9"
|
||||
},
|
||||
"flaky": {
|
||||
"hashes": [
|
||||
"sha256:12bd5e41f372b2190e8d754b6e5829c2f11dbc764e10b30f57e59f829c9ca1da",
|
||||
"sha256:a94931c46a33469ec26f09b652bc88f55a8f5cc77807b90ca7bbafef1108fd7d"
|
||||
"sha256:5471615b32b0f8086573de924475b1f0d31e0e8655a089eb9c38a0fbff3f11aa",
|
||||
"sha256:8cd5455bb00c677f787da424eaf8c4a58a922d0e97126d3085db5b279a98b698"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==3.5.3"
|
||||
"version": "==3.6.1"
|
||||
},
|
||||
"flask": {
|
||||
"hashes": [
|
||||
"sha256:ad7c6d841e64296b962296c2c2dabc6543752985727af86a975072dea984b6f3",
|
||||
"sha256:e7d32475d1de5facaa55e3958bc4ec66d3762076b074296aa50ef8fdc5b9df61"
|
||||
"sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52",
|
||||
"sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"
|
||||
],
|
||||
"version": "==1.0.3"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.1.1"
|
||||
},
|
||||
"funcsigs": {
|
||||
"hashes": [
|
||||
"sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca",
|
||||
"sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"
|
||||
],
|
||||
"markers": "python_version < '3.0'",
|
||||
"markers": "python_version < '3.3'",
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"functools32": {
|
||||
@@ -245,34 +320,43 @@
|
||||
},
|
||||
"futures": {
|
||||
"hashes": [
|
||||
"sha256:9ec02aa7d674acb8618afb127e27fde7fc68994c0437ad759fa094a574adb265",
|
||||
"sha256:ec0a6cb848cc212002b9828c3e34c675e0c9ff6741dc445cab6fdd4e1085d1f1"
|
||||
"sha256:49b3f5b064b6e3afc3316421a3f25f66c137ae88f068abbf72830170033c5e16",
|
||||
"sha256:7e033af76a5e35f58e56da7a91e687706faf4e7bdfb2cbc3f2cca6b9bcda9794"
|
||||
],
|
||||
"markers": "python_version < '3.2'",
|
||||
"version": "==3.2.0"
|
||||
"version": "==3.3.0"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
|
||||
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
|
||||
"sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
|
||||
"sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
|
||||
],
|
||||
"version": "==2.8"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.9"
|
||||
},
|
||||
"imagesize": {
|
||||
"hashes": [
|
||||
"sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8",
|
||||
"sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5"
|
||||
"sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1",
|
||||
"sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.1.0"
|
||||
"version": "==1.2.0"
|
||||
},
|
||||
"importlib-metadata": {
|
||||
"hashes": [
|
||||
"sha256:6dfd58dfe281e8d240937776065dd3624ad5469c835248219bd16cf2e12dbeb7",
|
||||
"sha256:cb6ee23b46173539939964df59d3d72c3e0c1b5d54b84f1d8a7e912fe43612db"
|
||||
"sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f",
|
||||
"sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.18"
|
||||
"markers": "python_version < '3.8'",
|
||||
"version": "==1.6.0"
|
||||
},
|
||||
"importlib-resources": {
|
||||
"hashes": [
|
||||
"sha256:4019b6a9082d8ada9def02bece4a76b131518866790d58fdda0b5f8c603b36c2",
|
||||
"sha256:dd98ceeef3f5ad2ef4cc287b8586da4ebad15877f351e9688987ad663a0a29b8"
|
||||
],
|
||||
"markers": "python_version < '3.7'",
|
||||
"version": "==1.4.0"
|
||||
},
|
||||
"incremental": {
|
||||
"hashes": [
|
||||
@@ -283,19 +367,19 @@
|
||||
},
|
||||
"invoke": {
|
||||
"hashes": [
|
||||
"sha256:4f4de934b15c2276caa4fbc5a3b8a61c0eb0b234f2be1780d2b793321995c2d6",
|
||||
"sha256:dc492f8f17a0746e92081aec3f86ae0b4750bf41607ea2ad87e5a7b5705121b7",
|
||||
"sha256:eb6f9262d4d25b40330fb21d1e99bf0f85011ccc3526980f8a3eaedd4b43892e"
|
||||
"sha256:87b3ef9d72a1667e104f89b159eaf8a514dbf2f3576885b2bbdefe74c3fb2132",
|
||||
"sha256:93e12876d88130c8e0d7fd6618dd5387d6b36da55ad541481dfa5e001656f134",
|
||||
"sha256:de3f23bfe669e3db1085789fd859eb8ca8e0c5d9c20811e2407fa042e8a5e15d"
|
||||
],
|
||||
"version": "==1.2.0"
|
||||
"version": "==1.4.1"
|
||||
},
|
||||
"isort": {
|
||||
"hashes": [
|
||||
"sha256:c40744b6bc5162bbb39c1257fe298b7a393861d50978b565f3ccd9cb9de0182a",
|
||||
"sha256:f57abacd059dc3bd666258d1efb0377510a89777fda3e3274e3c01f7c03ae22d"
|
||||
"sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1",
|
||||
"sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.3.20"
|
||||
"version": "==4.3.21"
|
||||
},
|
||||
"itsdangerous": {
|
||||
"hashes": [
|
||||
@@ -307,18 +391,19 @@
|
||||
},
|
||||
"jedi": {
|
||||
"hashes": [
|
||||
"sha256:2bb0603e3506f708e792c7f4ad8fc2a7a9d9c2d292a358fbbd58da531695595b",
|
||||
"sha256:2c6bcd9545c7d6440951b12b44d373479bf18123a401a52025cf98563fbd826c"
|
||||
"sha256:b4f4052551025c6b0b0b193b29a6ff7bdb74c52450631206c262aef9f7159ad2",
|
||||
"sha256:d5c871cb9360b414f981e7072c52c33258d598305280fef91c6cae34739d65d5"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.13.3"
|
||||
"version": "==0.16.0"
|
||||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
"sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013",
|
||||
"sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"
|
||||
"sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250",
|
||||
"sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49"
|
||||
],
|
||||
"version": "==2.10.1"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.11.1"
|
||||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
@@ -326,13 +411,16 @@
|
||||
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
|
||||
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
|
||||
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
|
||||
"sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
|
||||
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
|
||||
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
|
||||
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
|
||||
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
|
||||
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
|
||||
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
|
||||
"sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
|
||||
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
|
||||
"sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
|
||||
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
|
||||
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
|
||||
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
|
||||
@@ -349,7 +437,9 @@
|
||||
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
|
||||
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
|
||||
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
|
||||
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"
|
||||
"sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
|
||||
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
|
||||
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.1.1"
|
||||
@@ -386,46 +476,54 @@
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
"sha256:0c98a5d0be38ed775798ece1b9727178c4469d9c3b4ada66e8e6b7849f8732af",
|
||||
"sha256:9e1cbf8c12b1f1ce0bb5344b8d7ecf66a6f8a6e91bcb0c84593ed6d3ab5c4ab3"
|
||||
"sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3",
|
||||
"sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==19.0"
|
||||
"version": "==20.3"
|
||||
},
|
||||
"parso": {
|
||||
"hashes": [
|
||||
"sha256:5052bb33be034cba784193e74b1cde6ebf29ae8b8c1e4ad94df0c4209bfc4826",
|
||||
"sha256:db5881df1643bf3e66c097bfd8935cf03eae73f4cb61ae4433c9ea4fb6613446"
|
||||
"sha256:0c5659e0c6eba20636f99a04f469798dca8da279645ce5c387315b2c23912157",
|
||||
"sha256:8515fc12cfca6ee3aa59138741fc5624d62340c97e401c74875769948d4f2995"
|
||||
],
|
||||
"version": "==0.5.0"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.6.2"
|
||||
},
|
||||
"parver": {
|
||||
"hashes": [
|
||||
"sha256:1b37a691af145a3a193eff269d53ba5b2ab16dfbb65d47d85360755919f5fe4b",
|
||||
"sha256:72d056b8f8883ac90eef5554a9c8a47fac39d3b66479f3d2c8d5bc21b849cdba"
|
||||
"sha256:b57d94e6f389f9db399bfc3ee4c4066f4cfb374ffef5727d5ae6a9c04eb8d228",
|
||||
"sha256:bb9d19637c17819e276b5cf04e2dbfb81c4e2136da8873cc70dcd0e4fd3d14a3"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.2.1"
|
||||
"version": "==0.3.0"
|
||||
},
|
||||
"passa": {
|
||||
"git": "https://github.com/sarugaku/passa.git",
|
||||
"ref": "a2ba0b30c86339cae5ef3a03046fc9c583452c40",
|
||||
"ref": "2ac00f16cd5a8f07d679a3ab02b7cc13c6f42bee",
|
||||
"version": "==0.3.1.dev0"
|
||||
},
|
||||
"pathlib2": {
|
||||
"hashes": [
|
||||
"sha256:25199318e8cc3c25dcb45cbe084cc061051336d5a9ea2a12448d3d8cb748f742",
|
||||
"sha256:5887121d7f7df3603bca2f710e7219f3eca0eb69e0b7cc6e0a022e155ac931a7"
|
||||
"sha256:0ec8205a157c80d7acc301c0b18fbd5d44fe655968f5d947b6ecef5290fc35db",
|
||||
"sha256:6cd9a47b597b37cc57de1c05e56fb1a1c9cc9fab04fe78c29acd090418529868"
|
||||
],
|
||||
"markers": "python_version < '3.6'",
|
||||
"version": "==2.3.3"
|
||||
"markers": "python_version < '3'",
|
||||
"version": "==2.3.5"
|
||||
},
|
||||
"pathspec": {
|
||||
"hashes": [
|
||||
"sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424",
|
||||
"sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"
|
||||
],
|
||||
"version": "==0.7.0"
|
||||
},
|
||||
"pbr": {
|
||||
"hashes": [
|
||||
"sha256:9181e2a34d80f07a359ff1d0504fad3a47e00e1cf2c475b0aa7dcb030af54c40",
|
||||
"sha256:94bdc84da376b3dd5061aa0c3b6faffe943ee2e56fa4ff9bd63e1643932f34fc"
|
||||
"sha256:139d2625547dbfa5fb0b81daebb39601c478c21956dc57e2e07b74450a8c506b",
|
||||
"sha256:61aa52a0f18b71c5cc58232d2cf8f8d09cd67fcad60b742a60124cb8d6951488"
|
||||
],
|
||||
"version": "==5.3.1"
|
||||
"version": "==5.4.4"
|
||||
},
|
||||
"pipenv": {
|
||||
"editable": true,
|
||||
@@ -444,19 +542,19 @@
|
||||
},
|
||||
"pluggy": {
|
||||
"hashes": [
|
||||
"sha256:0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc",
|
||||
"sha256:b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c"
|
||||
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
|
||||
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.12.0"
|
||||
"version": "==0.13.1"
|
||||
},
|
||||
"py": {
|
||||
"hashes": [
|
||||
"sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa",
|
||||
"sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"
|
||||
"sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa",
|
||||
"sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.8.0"
|
||||
"version": "==1.8.1"
|
||||
},
|
||||
"pycodestyle": {
|
||||
"hashes": [
|
||||
@@ -466,6 +564,14 @@
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.5.0"
|
||||
},
|
||||
"pycparser": {
|
||||
"hashes": [
|
||||
"sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
|
||||
"sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.20"
|
||||
},
|
||||
"pyflakes": {
|
||||
"hashes": [
|
||||
"sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0",
|
||||
@@ -476,75 +582,102 @@
|
||||
},
|
||||
"pygments": {
|
||||
"hashes": [
|
||||
"sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127",
|
||||
"sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"
|
||||
"sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b",
|
||||
"sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.4.2"
|
||||
"version": "==2.5.2"
|
||||
},
|
||||
"pyparsing": {
|
||||
"hashes": [
|
||||
"sha256:1873c03321fc118f4e9746baf201ff990ceb915f433f23b395f5580d1840cb2a",
|
||||
"sha256:9b6323ef4ab914af344ba97510e966d64ba91055d6b9afa6b30799340e89cc03"
|
||||
"sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f",
|
||||
"sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.4.0"
|
||||
"version": "==2.4.6"
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:4a784f1d4f2ef198fe9b7aef793e9fa1a3b2f84e822d9b3a64a181293a572d45",
|
||||
"sha256:926855726d8ae8371803f7b2e6ec0a69953d9c6311fa7c3b6c1b929ff92d27da"
|
||||
"sha256:19e8f75eac01dd3f211edd465b39efbcbdc8fc5f7866d7dd49fedb30d8adf339",
|
||||
"sha256:c77a5f30a90e0ce24db9eaa14ddfd38d4afb5ea159309bdd2dae55b931bc9324"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==4.6.3"
|
||||
"version": "==4.6.9"
|
||||
},
|
||||
"pytest-forked": {
|
||||
"hashes": [
|
||||
"sha256:5fe33fbd07d7b1302c95310803a5e5726a4ff7f19d5a542b7ce57c76fed8135f",
|
||||
"sha256:d352aaced2ebd54d42a65825722cb433004b4446ab5d2044851d9cc7a00c9e38"
|
||||
"sha256:1805699ed9c9e60cb7a8179b8d4fa2b8898098e82d229b0825d8095f0f261100",
|
||||
"sha256:1ae25dba8ee2e56fb47311c9638f9e58552691da87e82d25b0ce0e4bf52b7d87"
|
||||
],
|
||||
"version": "==1.0.2"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.1.3"
|
||||
},
|
||||
"pytest-pypi": {
|
||||
"editable": true,
|
||||
"path": "./tests/pytest-pypi"
|
||||
},
|
||||
"pytest-tap": {
|
||||
"pytest-timeout": {
|
||||
"hashes": [
|
||||
"sha256:3b05ec931424bbe44e944726b68f7ef185bb6d25ce9ce21ac52c9af7ffa9b506",
|
||||
"sha256:ca063de56298034302f3cbce55c87a27d7bfa7af7de591cdb9ec6ce45fea5467"
|
||||
"sha256:80faa19cd245a42b87a51699d640c00d937c02b749052bfca6bae8bdbe12c48e",
|
||||
"sha256:95ca727d4a1dace6ec5f0534d2940b8417ff8b782f7eef0ea09240bdd94d95c2"
|
||||
],
|
||||
"version": "==2.3"
|
||||
"version": "==1.3.4"
|
||||
},
|
||||
"pytest-xdist": {
|
||||
"hashes": [
|
||||
"sha256:3489d91516d7847db5eaecff7a2e623dba68984835dbe6cedb05ae126c4fb17f",
|
||||
"sha256:501795cb99e567746f30fe78850533d4cd500c93794128e6ab9988e92a17b1f8"
|
||||
"sha256:0f46020d3d9619e6d17a65b5b989c1ebbb58fc7b1da8fb126d70f4bac4dfeed1",
|
||||
"sha256:7dc0d027d258cd0defc618fb97055fbd1002735ca7a6d17037018cf870e24011"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.29.0"
|
||||
"version": "==1.31.0"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda",
|
||||
"sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141"
|
||||
"sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
|
||||
"sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"
|
||||
],
|
||||
"version": "==2019.1"
|
||||
"version": "==2019.3"
|
||||
},
|
||||
"readme-renderer": {
|
||||
"hashes": [
|
||||
"sha256:bb16f55b259f27f75f640acf5e00cf897845a8b3e4731b5c1a436e4b8529202f",
|
||||
"sha256:c8532b79afc0375a85f10433eca157d6b50f7d6990f337fa498c96cd4bfc203d"
|
||||
"sha256:1b6d8dd1673a0b293766b4106af766b6eff3654605f9c4f239e65de6076bc222",
|
||||
"sha256:e67d64242f0174a63c3b727801a2fff4c1f38ebe5d71d95ff7ece081945a6cd4"
|
||||
],
|
||||
"version": "==24.0"
|
||||
"version": "==25.0"
|
||||
},
|
||||
"regex": {
|
||||
"hashes": [
|
||||
"sha256:01b2d70cbaed11f72e57c1cfbaca71b02e3b98f739ce33f5f26f71859ad90431",
|
||||
"sha256:046e83a8b160aff37e7034139a336b660b01dbfe58706f9d73f5cdc6b3460242",
|
||||
"sha256:113309e819634f499d0006f6200700c8209a2a8bf6bd1bdc863a4d9d6776a5d1",
|
||||
"sha256:200539b5124bc4721247a823a47d116a7a23e62cc6695744e3eb5454a8888e6d",
|
||||
"sha256:25f4ce26b68425b80a233ce7b6218743c71cf7297dbe02feab1d711a2bf90045",
|
||||
"sha256:269f0c5ff23639316b29f31df199f401e4cb87529eafff0c76828071635d417b",
|
||||
"sha256:5de40649d4f88a15c9489ed37f88f053c15400257eeb18425ac7ed0a4e119400",
|
||||
"sha256:7f78f963e62a61e294adb6ff5db901b629ef78cb2a1cfce3cf4eeba80c1c67aa",
|
||||
"sha256:82469a0c1330a4beb3d42568f82dffa32226ced006e0b063719468dcd40ffdf0",
|
||||
"sha256:8c2b7fa4d72781577ac45ab658da44c7518e6d96e2a50d04ecb0fd8f28b21d69",
|
||||
"sha256:974535648f31c2b712a6b2595969f8ab370834080e00ab24e5dbb9d19b8bfb74",
|
||||
"sha256:99272d6b6a68c7ae4391908fc15f6b8c9a6c345a46b632d7fdb7ef6c883a2bbb",
|
||||
"sha256:9b64a4cc825ec4df262050c17e18f60252cdd94742b4ba1286bcfe481f1c0f26",
|
||||
"sha256:9e9624440d754733eddbcd4614378c18713d2d9d0dc647cf9c72f64e39671be5",
|
||||
"sha256:9ff16d994309b26a1cdf666a6309c1ef51ad4f72f99d3392bcd7b7139577a1f2",
|
||||
"sha256:b33ebcd0222c1d77e61dbcd04a9fd139359bded86803063d3d2d197b796c63ce",
|
||||
"sha256:bba52d72e16a554d1894a0cc74041da50eea99a8483e591a9edf1025a66843ab",
|
||||
"sha256:bed7986547ce54d230fd8721aba6fd19459cdc6d315497b98686d0416efaff4e",
|
||||
"sha256:c7f58a0e0e13fb44623b65b01052dae8e820ed9b8b654bb6296bc9c41f571b70",
|
||||
"sha256:d58a4fa7910102500722defbde6e2816b0372a4fcc85c7e239323767c74f5cbc",
|
||||
"sha256:f1ac2dc65105a53c1c2d72b1d3e98c2464a133b4067a51a3d2477b28449709a0"
|
||||
],
|
||||
"version": "==2020.2.20"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
|
||||
"sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
|
||||
"sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee",
|
||||
"sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.22.0"
|
||||
"version": "==2.23.0"
|
||||
},
|
||||
"requests-toolbelt": {
|
||||
"hashes": [
|
||||
@@ -562,12 +695,12 @@
|
||||
},
|
||||
"rope": {
|
||||
"hashes": [
|
||||
"sha256:6b728fdc3e98a83446c27a91fc5d56808a004f8beab7a31ab1d7224cecc7d969",
|
||||
"sha256:c5c5a6a87f7b1a2095fb311135e2a3d1f194f5ecb96900fdd0a9100881f48aaf",
|
||||
"sha256:f0dcf719b63200d492b85535ebe5ea9b29e0d0b8aebeb87fe03fc1a65924fdaf"
|
||||
"sha256:52423a7eebb5306a6d63bdc91a7c657db51ac9babfb8341c9a1440831ecf3203",
|
||||
"sha256:ae1fa2fd56f64f4cc9be46493ce54bed0dd12dee03980c61a4393d89d84029ad",
|
||||
"sha256:d2830142c2e046f5fc26a022fe680675b6f48f81c7fc1f03a950706e746e9dfe"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.14.0"
|
||||
"version": "==0.16.0"
|
||||
},
|
||||
"scandir": {
|
||||
"hashes": [
|
||||
@@ -586,27 +719,35 @@
|
||||
"markers": "python_version < '3.5'",
|
||||
"version": "==1.10.0"
|
||||
},
|
||||
"singledispatch": {
|
||||
"hashes": [
|
||||
"sha256:5b06af87df13818d14f08a028e42f566640aef80805c3b50c5056b086e3c2b9c",
|
||||
"sha256:833b46966687b3de7f438c761ac475213e53b306740f1abfaa86e1d1aae56aa8"
|
||||
],
|
||||
"markers": "python_version < '3.4'",
|
||||
"version": "==3.4.0.3"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
|
||||
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
|
||||
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
|
||||
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.12.0"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.14.0"
|
||||
},
|
||||
"snowballstemmer": {
|
||||
"hashes": [
|
||||
"sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128",
|
||||
"sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89"
|
||||
"sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0",
|
||||
"sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"
|
||||
],
|
||||
"version": "==1.2.1"
|
||||
"version": "==2.0.0"
|
||||
},
|
||||
"soupsieve": {
|
||||
"hashes": [
|
||||
"sha256:72b5f1aea9101cf720a36bb2327ede866fd6f1a07b1e87c92a1cc18113cbc946",
|
||||
"sha256:e4e9c053d59795e440163733a7fec6c5972210e1790c507e4c7b051d6c5259de"
|
||||
"sha256:bdb0d917b03a1369ce964056fc195cfdff8819c40de04695a80bc813c3cfa1f5",
|
||||
"sha256:e2c1c5dee4a1c36bcb790e0fabd5492d874b8ebd4617622c4f6a731701060dda"
|
||||
],
|
||||
"version": "==1.9.2"
|
||||
"version": "==1.9.5"
|
||||
},
|
||||
"sphinx": {
|
||||
"hashes": [
|
||||
@@ -618,11 +759,11 @@
|
||||
},
|
||||
"sphinx-click": {
|
||||
"hashes": [
|
||||
"sha256:2c7847607d07bc0ddf28acff3aa639b2660d06c5d95d1efe89eca6494fc750de",
|
||||
"sha256:814b2463b576dfafaf4a6f8ed9585f6d9696073ed5e4cca5b59d2dc9d29d3bc0"
|
||||
"sha256:793c68b41c4a9435f953e2a27f9bf5883729037b7431f32b2776257c2966bd1b",
|
||||
"sha256:8c6274666730686a65efbae0b4465879b030372333de3114aeb63c44204da32e"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.2.0"
|
||||
"version": "==2.3.1"
|
||||
},
|
||||
"sphinxcontrib-websupport": {
|
||||
"hashes": [
|
||||
@@ -634,17 +775,10 @@
|
||||
},
|
||||
"stdeb": {
|
||||
"hashes": [
|
||||
"sha256:0ed2c2cc6b8ba21da7d646c6f37ca60b22e9e4950e3cec6bcd9c2e7e57e3747e"
|
||||
"sha256:4d8351209dda2d26066980222e0d1855a315a68f9af48f0c10d743089afe7d4b"
|
||||
],
|
||||
"markers": "sys_platform == 'linux'",
|
||||
"version": "==0.8.5"
|
||||
},
|
||||
"tap.py": {
|
||||
"hashes": [
|
||||
"sha256:8ad62ba6898fcef4913c67d468d0c4beae3109b74c03363538145e31b1840b29",
|
||||
"sha256:f6532fd7483c5fdc2ed13575fa4494e7d037f797f8a2c6f8809a859be61271f5"
|
||||
],
|
||||
"version": "==2.5"
|
||||
"version": "==0.9.0"
|
||||
},
|
||||
"termcolor": {
|
||||
"hashes": [
|
||||
@@ -669,59 +803,86 @@
|
||||
},
|
||||
"tqdm": {
|
||||
"hashes": [
|
||||
"sha256:14a285392c32b6f8222ecfbcd217838f88e11630affe9006cd0e94c7eff3cb61",
|
||||
"sha256:25d4c0ea02a305a688e7e9c2cdc8f862f989ef2a4701ab28ee963295f5b109ab"
|
||||
"sha256:03d2366c64d44c7f61e74c700d9b202d57e9efe355ea5c28814c52bfe7a50b8c",
|
||||
"sha256:be5ddeec77d78ba781ea41eacb2358a77f74cc2407f54b82222d7ee7dc8c8ccf"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==4.32.2"
|
||||
"version": "==4.44.1"
|
||||
},
|
||||
"twine": {
|
||||
"hashes": [
|
||||
"sha256:0fb0bfa3df4f62076cab5def36b1a71a2e4acb4d1fa5c97475b048117b1a6446",
|
||||
"sha256:d6c29c933ecfc74e9b1d9fa13aa1f87c5d5770e119f5a4ce032092f0ff5b14dc"
|
||||
"sha256:630fadd6e342e725930be6c696537e3f9ccc54331742b16245dab292a17d0460",
|
||||
"sha256:a3d22aab467b4682a22de4a422632e79d07eebd07ff2a7079effb13f8a693787"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.13.0"
|
||||
"version": "==1.15.0"
|
||||
},
|
||||
"typed-ast": {
|
||||
"hashes": [
|
||||
"sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355",
|
||||
"sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919",
|
||||
"sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa",
|
||||
"sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652",
|
||||
"sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75",
|
||||
"sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01",
|
||||
"sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d",
|
||||
"sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1",
|
||||
"sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907",
|
||||
"sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c",
|
||||
"sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3",
|
||||
"sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b",
|
||||
"sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614",
|
||||
"sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb",
|
||||
"sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b",
|
||||
"sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41",
|
||||
"sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6",
|
||||
"sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34",
|
||||
"sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe",
|
||||
"sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4",
|
||||
"sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"
|
||||
],
|
||||
"markers": "python_version >= '3.4'",
|
||||
"version": "==1.4.1"
|
||||
},
|
||||
"typing": {
|
||||
"hashes": [
|
||||
"sha256:4027c5f6127a6267a435201981ba156de91ad0d1d98e9ddc2aa173453453492d",
|
||||
"sha256:57dcf675a99b74d64dacf6fba08fb17cf7e3d5fdff53d4a30ea2a5e7e52543d4",
|
||||
"sha256:a4c8473ce11a65999c8f59cb093e70686b6c84c98df58c1dae9b3b196089858a"
|
||||
"sha256:91dfe6f3f706ee8cc32d38edbbf304e9b7583fb37108fef38229617f8b3eba23",
|
||||
"sha256:c8cabb5ab8945cd2f54917be357d134db9cc1eb039e59d1606dc1e60cb1d9d36",
|
||||
"sha256:f38d83c5a7a7086543a0f649564d661859c5146a85775ab90c0d2f93ffaa9714"
|
||||
],
|
||||
"markers": "python_version < '3.5'",
|
||||
"version": "==3.6.6"
|
||||
"version": "==3.7.4.1"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1",
|
||||
"sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232"
|
||||
"sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
|
||||
"sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' and python_version < '4'",
|
||||
"version": "==1.25.3"
|
||||
"version": "==1.25.8"
|
||||
},
|
||||
"virtualenv": {
|
||||
"hashes": [
|
||||
"sha256:6cb2e4c18d22dbbe283d0a0c31bb7d90771a606b2cb3415323eea008eaee6a9d",
|
||||
"sha256:909fe0d3f7c9151b2df0a2cb53e55bdb7b0d61469353ff7a49fd47b0f0ab9285"
|
||||
"sha256:55059a7a676e4e19498f1aad09b8313a38fcc0cdbe4fdddc0e9b06946d21b4bb",
|
||||
"sha256:0d62c70883c0342d59c11d0ddac0d954d0431321a41ab20851facf2b222598f3"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==16.7.2"
|
||||
"version": "==16.7.9"
|
||||
},
|
||||
"virtualenv-clone": {
|
||||
"hashes": [
|
||||
"sha256:532f789a5c88adf339506e3ca03326f20ee82fd08ee5586b44dc859b5b4468c5",
|
||||
"sha256:c88ae171a11b087ea2513f260cdac9232461d8e9369bcd1dc143fc399d220557"
|
||||
"sha256:07e74418b7cc64f4fda987bf5bc71ebd59af27a7bc9e8a8ee9fd54b1f2390a27",
|
||||
"sha256:665e48dd54c84b98b71a657acb49104c54e7652bce9c1c4f6c6976ed4c827a29"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.5.3"
|
||||
"version": "==0.5.4"
|
||||
},
|
||||
"wcwidth": {
|
||||
"hashes": [
|
||||
"sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e",
|
||||
"sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"
|
||||
"sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1",
|
||||
"sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1"
|
||||
],
|
||||
"version": "==0.1.7"
|
||||
"version": "==0.1.9"
|
||||
},
|
||||
"webencodings": {
|
||||
"hashes": [
|
||||
@@ -732,19 +893,19 @@
|
||||
},
|
||||
"werkzeug": {
|
||||
"hashes": [
|
||||
"sha256:865856ebb55c4dcd0630cdd8f3331a1847a819dda7e8c750d3db6f2aa6c0209c",
|
||||
"sha256:a0b915f0815982fb2a09161cb8f31708052d0951c3ba433ccc5e1aa276507ca6"
|
||||
"sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43",
|
||||
"sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.15.4"
|
||||
"version": "==1.0.1"
|
||||
},
|
||||
"zipp": {
|
||||
"hashes": [
|
||||
"sha256:8c1019c6aad13642199fbe458275ad6a84907634cc9f0989877ccc4a2840139d",
|
||||
"sha256:ca943a7e809cc12257001ccfb99e3563da9af99d52f261725e96dfe0f9275bc3"
|
||||
"sha256:c70410551488251b0fee67b460fb9a536af8d6f9f008ad10ac51f615b6a521b1",
|
||||
"sha256:e0d9e63797e483a30d27e09fffd308c59a700d365ec34e93cc100844168bf921"
|
||||
],
|
||||
"markers": "python_version >= '2.7'",
|
||||
"version": "==0.5.1"
|
||||
"markers": "python_version < '3.8'",
|
||||
"version": "==1.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+58
-43
@@ -1,4 +1,4 @@
|
||||
name: Pipenv Build Rules
|
||||
name: CI
|
||||
trigger:
|
||||
batch: true
|
||||
branches:
|
||||
@@ -6,10 +6,10 @@ trigger:
|
||||
- master
|
||||
paths:
|
||||
exclude:
|
||||
- docs/
|
||||
- news/
|
||||
- peeps/
|
||||
- examples/
|
||||
- docs/*
|
||||
- news/*
|
||||
- peeps/*
|
||||
- examples/*
|
||||
- pytest.ini
|
||||
- README.md
|
||||
- pipenv/*.txt
|
||||
@@ -24,84 +24,99 @@ variables:
|
||||
- group: CI
|
||||
|
||||
jobs:
|
||||
- job: TestLinux
|
||||
pool:
|
||||
vmImage: 'Ubuntu-16.04'
|
||||
strategy:
|
||||
matrix:
|
||||
Python27:
|
||||
python.version: '2.7'
|
||||
python.architecture: x64
|
||||
Python36:
|
||||
python.version: '3.6'
|
||||
python.architecture: x64
|
||||
Python37:
|
||||
python.version: '3.7'
|
||||
python.architecture: x64
|
||||
maxParallel: 4
|
||||
steps:
|
||||
- template: .azure-pipelines/steps/run-tests.yml
|
||||
parameters:
|
||||
vmImage: 'Ubuntu-16.04'
|
||||
|
||||
- job: TestVendoring
|
||||
displayName: Vendoring
|
||||
pool:
|
||||
vmImage: 'Ubuntu-16.04'
|
||||
vmImage: 'Ubuntu-latest'
|
||||
variables:
|
||||
python.version: '3.7'
|
||||
python.architecture: x64
|
||||
steps:
|
||||
- template: .azure-pipelines/steps/run-vendor-scripts.yml
|
||||
parameters:
|
||||
vmImage: 'Ubuntu-16.04'
|
||||
vmImage: 'Ubuntu-latest'
|
||||
|
||||
- job: TestPackaging
|
||||
displayName: Packaging
|
||||
pool:
|
||||
vmImage: 'Ubuntu-16.04'
|
||||
vmImage: 'Ubuntu-latest'
|
||||
variables:
|
||||
python.version: '3.7'
|
||||
python.architecture: x64
|
||||
steps:
|
||||
- template: .azure-pipelines/steps/build-package.yml
|
||||
parameters:
|
||||
vmImage: 'Ubuntu-16.04'
|
||||
vmImage: 'Ubuntu-latest'
|
||||
|
||||
- job: TestLinux
|
||||
displayName: Linux /
|
||||
pool:
|
||||
vmImage: 'Ubuntu-latest'
|
||||
strategy:
|
||||
matrix:
|
||||
"2.7":
|
||||
python.version: '2.7'
|
||||
python.architecture: x64
|
||||
"3.6":
|
||||
python.version: '3.6'
|
||||
python.architecture: x64
|
||||
"3.7":
|
||||
python.version: '3.7'
|
||||
python.architecture: x64
|
||||
"3.8":
|
||||
python.version: '3.8'
|
||||
python.architecture: x64
|
||||
maxParallel: 8
|
||||
steps:
|
||||
- template: .azure-pipelines/steps/run-tests.yml
|
||||
parameters:
|
||||
vmImage: 'Ubuntu-latest'
|
||||
|
||||
- job: TestWindows
|
||||
displayName: Windows /
|
||||
timeoutInMinutes: 0
|
||||
pool:
|
||||
vmImage: windows-2019
|
||||
vmImage: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
Python27:
|
||||
"2.7":
|
||||
python.version: '2.7'
|
||||
python.architecture: x64
|
||||
Python36:
|
||||
"3.6":
|
||||
python.version: '3.6'
|
||||
python.architecture: x64
|
||||
Python37:
|
||||
"3.7":
|
||||
python.version: '3.7'
|
||||
python.architecture: x64
|
||||
maxParallel: 4
|
||||
"3.8":
|
||||
python.version: '3.8'
|
||||
python.architecture: x64
|
||||
maxParallel: 8
|
||||
steps:
|
||||
- template: .azure-pipelines/steps/run-tests.yml
|
||||
parameters:
|
||||
vmImage: windows-2019
|
||||
- template: .azure-pipelines/steps/run-tests.yml
|
||||
parameters:
|
||||
vmImage: windows-latest
|
||||
|
||||
- job: TestMacOS
|
||||
displayName: MacOS /
|
||||
pool:
|
||||
vmImage: macOS-10.13
|
||||
vmImage: macOS-latest
|
||||
strategy:
|
||||
matrix:
|
||||
Python27:
|
||||
"2.7":
|
||||
python.version: '2.7'
|
||||
python.architecture: x64
|
||||
Python36:
|
||||
"3.6":
|
||||
python.version: '3.6'
|
||||
python.architecture: x64
|
||||
Python37:
|
||||
"3.7":
|
||||
python.version: '3.7'
|
||||
python.architecture: x64
|
||||
maxParallel: 4
|
||||
"3.8":
|
||||
python.version: '3.8'
|
||||
python.architecture: x64
|
||||
maxParallel: 8
|
||||
steps:
|
||||
- template: .azure-pipelines/steps/run-tests.yml
|
||||
parameters:
|
||||
vmImage: macOS-10.13
|
||||
vmImage: macOS-latest
|
||||
|
||||
+4
-4
@@ -10,10 +10,10 @@ This document covers some of Pipenv's more basic features.
|
||||
☤ Example Pipfile & Pipfile.lock
|
||||
--------------------------------
|
||||
|
||||
Pipfiles contain information for the dependencies of the project, and supercede
|
||||
the requirements.txt present in Python projects. You should add Pipfile in the
|
||||
Git repository letting users who clone the repository the only thing required would be
|
||||
installing Pipenv in the machine and type ``pipenv install``. Pipenv is a reference
|
||||
Pipfiles contain information for the dependencies of the project, and supersedes
|
||||
the requirements.txt file used in most Python projects. You should add a Pipfile in the
|
||||
Git repository letting users who clone the repository know the only thing required would be
|
||||
installing Pipenv in the machine and typing ``pipenv install``. Pipenv is a reference
|
||||
implementation for using Pipfile.
|
||||
|
||||
.. _example_files:
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
Update vendored dependencies and invocations
|
||||
|
||||
- Update vendored and patched dependencies
|
||||
- Update patches on `piptools`, `pip`, `pip-shims`, `tomlkit`
|
||||
- Fix invocations of dependencies
|
||||
- Fix custom `InstallCommand` instantiation
|
||||
- Update `PackageFinder` usage
|
||||
- Fix `Bool` stringify attempts from `tomlkit`
|
||||
|
||||
Updated vendored dependencies:
|
||||
- **attrs**: ``18.2.0`` => ``19.1.0``
|
||||
- **certifi**: ``2018.10.15`` => ``2019.3.9``
|
||||
- **cached_property**: ``1.4.3`` => ``1.5.1``
|
||||
- **cerberus**: ``1.2.0`` => ``1.3.1``
|
||||
- **click**: ``7.0.0`` => ``7.1.1``
|
||||
- **click-completion**: ``0.5.0`` => ``0.5.1``
|
||||
- **colorama**: ``0.3.9`` => ``0.4.3``
|
||||
- **contextlib2**: ``(new)`` => ``0.6.0.post1``
|
||||
- **distlib**: ``0.2.8`` => ``0.2.9``
|
||||
- **funcsigs**: ``(new)`` => ``1.0.2``
|
||||
- **importlib_metadata** ``1.3.0`` => ``1.5.1``
|
||||
- **importlib-resources**: ``(new)`` => ``1.4.0``
|
||||
- **idna**: ``2.7`` => ``2.9``
|
||||
- **jinja2**: ``2.10.0`` => ``2.11.1``
|
||||
- **markupsafe**: ``1.0`` => ``1.1.1``
|
||||
- **more-itertools**: ``(new)`` => ``5.0.0``
|
||||
- **orderedmultidict**: ``(new)`` => ``1.0``
|
||||
- **packaging**: ``18.0`` => ``19.0``
|
||||
- **parse**: ``1.9.0`` => ``1.15.0``
|
||||
- **pathlib2**: ``2.3.2`` => ``2.3.3``
|
||||
- **pep517**: ``(new)`` => ``0.5.0``
|
||||
- **pexpect**: ``4.6.0`` => ``4.8.0``
|
||||
- **pip-shims**: ``0.2.0`` => ``0.5.1``
|
||||
- **pipdeptree**: ``0.13.0`` => ``0.13.2``
|
||||
- **pyparsing**: ``2.2.2`` => ``2.4.6``
|
||||
- **python-dotenv**: ``0.9.1`` => ``0.10.2``
|
||||
- **pythonfinder**: ``1.1.10`` => ``1.2.2``
|
||||
- **pytoml**: ``(new)`` => ``0.1.20``
|
||||
- **requests**: ``2.20.1`` => ``2.23.0``
|
||||
- **requirementslib**: ``1.3.3`` => ``1.5.4``
|
||||
- **scandir**: ``1.9.0`` => ``1.10.0``
|
||||
- **shellingham**: ``1.2.7`` => ``1.3.2``
|
||||
- **six**: ``1.11.0`` => ``1.14.0``
|
||||
- **tomlkit**: ``0.5.2`` => ``0.5.11``
|
||||
- **urllib3**: ``1.24`` => ``1.25.8``
|
||||
- **vistir**: ``0.3.0`` => ``0.5.0``
|
||||
- **yaspin**: ``0.14.0`` => ``0.14.3``
|
||||
- **zipp**: ``0.6.0``
|
||||
|
||||
- Removed vendored dependency **cursor**.
|
||||
+26
-12
@@ -675,7 +675,17 @@ def _cleanup_procs(procs, failed_deps_queue, retry=True):
|
||||
click.echo(crayons.blue(c.out.strip() or c.err.strip()))
|
||||
# The Installation failed…
|
||||
if failed:
|
||||
if not retry:
|
||||
if "does not match installed location" in c.err:
|
||||
project.environment.expand_egg_links()
|
||||
click.echo("{0}".format(
|
||||
crayons.yellow(
|
||||
"Failed initial installation: Failed to overwrite existing "
|
||||
"package, likely due to path aliasing. Expanding and trying "
|
||||
"again!"
|
||||
)
|
||||
))
|
||||
dep = c.dep.copy()
|
||||
elif not retry:
|
||||
# The Installation failed…
|
||||
# We echo both c.out and c.err because pip returns error details on out.
|
||||
err = c.err.strip().splitlines() if c.err else []
|
||||
@@ -683,16 +693,17 @@ def _cleanup_procs(procs, failed_deps_queue, retry=True):
|
||||
err_lines = [line for message in [out, err] for line in message]
|
||||
# Return the subprocess' return code.
|
||||
raise exceptions.InstallError(c.dep.name, extra=err_lines)
|
||||
else:
|
||||
# Alert the user.
|
||||
dep = c.dep.copy()
|
||||
click.echo(
|
||||
"{0} {1}! Will try again.".format(
|
||||
crayons.red("An error occurred while installing"),
|
||||
crayons.green(dep.as_line()),
|
||||
), err=True
|
||||
)
|
||||
# Save the Failed Dependency for later.
|
||||
dep = c.dep.copy()
|
||||
failed_deps_queue.put(dep)
|
||||
# Alert the user.
|
||||
click.echo(
|
||||
"{0} {1}! Will try again.".format(
|
||||
crayons.red("An error occurred while installing"),
|
||||
crayons.green(dep.as_line()),
|
||||
), err=True
|
||||
)
|
||||
|
||||
|
||||
def batch_install(deps_list, procs, failed_deps_queue,
|
||||
@@ -1317,7 +1328,8 @@ def get_pip_args(
|
||||
"no_use_pep517": [],
|
||||
"no_deps": ["--no-deps"],
|
||||
"selective_upgrade": [
|
||||
"--upgrade-strategy=only-if-needed", "--exists_action={0}".format(PIP_EXISTS_ACTION or "i")
|
||||
"--upgrade-strategy=only-if-needed",
|
||||
"--exists-action={0}".format(PIP_EXISTS_ACTION or "i")
|
||||
],
|
||||
"src_dir": src_dir,
|
||||
}
|
||||
@@ -1329,6 +1341,8 @@ def get_pip_args(
|
||||
for key in arg_map.keys():
|
||||
if key in locals() and locals().get(key):
|
||||
arg_set.extend(arg_map.get(key))
|
||||
elif key == "selective_upgrade" and not locals().get(key):
|
||||
arg_set.append("--exists-action=i")
|
||||
return list(vistir.misc.dedup(arg_set))
|
||||
|
||||
|
||||
@@ -1406,7 +1420,7 @@ def pip_install(
|
||||
trusted_hosts=None,
|
||||
use_pep517=True
|
||||
):
|
||||
from pipenv.patched.notpip._internal import logger as piplogger
|
||||
piplogger = logging.getLogger("pipenv.patched.notpip._internal.commands.install")
|
||||
src_dir = None
|
||||
if not trusted_hosts:
|
||||
trusted_hosts = []
|
||||
@@ -1466,7 +1480,7 @@ def pip_install(
|
||||
)
|
||||
pip_command.extend(pip_args)
|
||||
if r:
|
||||
pip_command.extend(["-r", r])
|
||||
pip_command.extend(["-r", vistir.path.normalize_path(r)])
|
||||
elif line:
|
||||
pip_command.extend(line)
|
||||
pip_command.extend(prepare_pip_source_args(sources))
|
||||
|
||||
+31
-29
@@ -466,7 +466,29 @@ class Environment(object):
|
||||
), None)
|
||||
if pip is not None:
|
||||
return parse_version(pip.version)
|
||||
return parse_version("18.0")
|
||||
return parse_version("19.3")
|
||||
|
||||
def expand_egg_links(self):
|
||||
"""
|
||||
Expand paths specified in egg-link files to prevent pip errors during
|
||||
reinstall
|
||||
"""
|
||||
prefixes = [
|
||||
vistir.compat.Path(prefix)
|
||||
for prefix in self.base_paths["libdirs"].split(os.pathsep)
|
||||
if vistir.path.is_in_path(prefix, self.prefix.as_posix())
|
||||
]
|
||||
for loc in prefixes:
|
||||
if not loc.exists():
|
||||
continue
|
||||
for pth in loc.iterdir():
|
||||
if not pth.suffix == ".egg-link":
|
||||
continue
|
||||
contents = [
|
||||
vistir.path.normalize_path(line.strip())
|
||||
for line in pth.read_text().splitlines()
|
||||
]
|
||||
pth.write_text("\n".join(contents))
|
||||
|
||||
def get_distributions(self):
|
||||
"""
|
||||
@@ -529,42 +551,21 @@ class Environment(object):
|
||||
@contextlib.contextmanager
|
||||
def get_finder(self, pre=False):
|
||||
from .vendor.pip_shims.shims import (
|
||||
Command, cmdoptions, index_group, PackageFinder, parse_version, pip_version
|
||||
InstallCommand, get_package_finder
|
||||
)
|
||||
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_command = InstallCommand()
|
||||
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_args = {
|
||||
"find_links": pip_options.find_links,
|
||||
"index_urls": index_urls,
|
||||
"allow_all_prereleases": pip_options.pre,
|
||||
"trusted_hosts": pip_options.trusted_hosts,
|
||||
"session": session
|
||||
}
|
||||
if parse_version(pip_version) < parse_version("19.0"):
|
||||
finder_args.update(
|
||||
{"process_dependency_links": pip_options.process_dependency_links}
|
||||
)
|
||||
finder = PackageFinder(**finder_args)
|
||||
finder = get_package_finder(install_cmd=pip_command, options=pip_options, session=session)
|
||||
yield finder
|
||||
|
||||
def get_package_info(self, pre=False):
|
||||
from .vendor.pip_shims.shims import pip_version, parse_version
|
||||
from .vendor.pip_shims.shims import pip_version, parse_version, CandidateEvaluator
|
||||
dependency_links = []
|
||||
packages = self.get_installed_packages()
|
||||
# This code is borrowed from pip's current implementation
|
||||
@@ -591,9 +592,10 @@ class Environment(object):
|
||||
|
||||
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:
|
||||
candidate_evaluator = finder.make_candidate_evaluator(project_name=dist.key)
|
||||
best_candidate_result = candidate_evaluator.compute_best_candidate(all_candidates)
|
||||
remote_version = best_candidate_result.best_candidate.version
|
||||
if best_candidate_result.best_candidate.link.is_wheel:
|
||||
typ = 'wheel'
|
||||
else:
|
||||
typ = 'sdist'
|
||||
|
||||
@@ -233,7 +233,7 @@ class LockfileNotFound(PipenvFileError):
|
||||
class DeployException(PipenvUsageError):
|
||||
def __init__(self, message=None, **kwargs):
|
||||
if not message:
|
||||
message = crayons.normal("Aborting deploy", bold=True)
|
||||
message = str(crayons.normal("Aborting deploy", bold=True))
|
||||
extra = kwargs.pop("extra", [])
|
||||
PipenvUsageError.__init__(self, message=message, extra=extra, **kwargs)
|
||||
|
||||
@@ -256,7 +256,9 @@ class SystemUsageError(PipenvOptionsError):
|
||||
),
|
||||
]
|
||||
if message is None:
|
||||
message = crayons.blue("See also: {0}".format(crayons.white("--deploy flag.")))
|
||||
message = str(
|
||||
crayons.blue("See also: {0}".format(crayons.white("--deploy flag.")))
|
||||
)
|
||||
super(SystemUsageError, self).__init__(option_name, message=message, ctx=ctx, extra=extra, **kwargs)
|
||||
|
||||
|
||||
@@ -310,7 +312,9 @@ class VirtualenvCreationException(VirtualenvException):
|
||||
# so replacement or parsing requires this step
|
||||
extra = ANSI_REMOVAL_RE.sub("", "{0}".format(extra))
|
||||
if "KeyboardInterrupt" in extra:
|
||||
extra = crayons.red("Virtualenv creation interrupted by user", bold=True)
|
||||
extra = str(
|
||||
crayons.red("Virtualenv creation interrupted by user", bold=True)
|
||||
)
|
||||
self.extra = extra = [extra]
|
||||
VirtualenvException.__init__(self, message, extra=extra)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2008-2018 The pip developers (see AUTHORS.txt file)
|
||||
Copyright (c) 2008-2019 The pip developers (see AUTHORS.txt file)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "19.0.3"
|
||||
__version__ = "19.3.1"
|
||||
|
||||
@@ -13,7 +13,7 @@ if __package__ == '':
|
||||
path = os.path.dirname(os.path.dirname(__file__))
|
||||
sys.path.insert(0, path)
|
||||
|
||||
from pipenv.patched.notpip._internal import main as _main # isort:skip # noqa
|
||||
from pipenv.patched.notpip._internal.main import main as _main # isort:skip # noqa
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(_main())
|
||||
|
||||
@@ -1,78 +1,2 @@
|
||||
#!/usr/bin/env python
|
||||
from __future__ import absolute_import
|
||||
|
||||
import locale
|
||||
import logging
|
||||
import os
|
||||
import warnings
|
||||
|
||||
import sys
|
||||
|
||||
# 2016-06-17 barry@debian.org: urllib3 1.14 added optional support for socks,
|
||||
# but if invoked (i.e. imported), it will issue a warning to stderr if socks
|
||||
# isn't available. requests unconditionally imports urllib3's socks contrib
|
||||
# module, triggering this warning. The warning breaks DEP-8 tests (because of
|
||||
# the stderr output) and is just plain annoying in normal usage. I don't want
|
||||
# to add socks as yet another dependency for pip, nor do I want to allow-stder
|
||||
# in the DEP-8 tests, so just suppress the warning. pdb tells me this has to
|
||||
# be done before the import of pip.vcs.
|
||||
from pipenv.patched.notpip._vendor.urllib3.exceptions import DependencyWarning
|
||||
warnings.filterwarnings("ignore", category=DependencyWarning) # noqa
|
||||
|
||||
# We want to inject the use of SecureTransport as early as possible so that any
|
||||
# references or sessions or what have you are ensured to have it, however we
|
||||
# only want to do this in the case that we're running on macOS and the linked
|
||||
# OpenSSL is too old to handle TLSv1.2
|
||||
try:
|
||||
import ssl
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
# Checks for OpenSSL 1.0.1 on MacOS
|
||||
if sys.platform == "darwin" and ssl.OPENSSL_VERSION_NUMBER < 0x1000100f:
|
||||
try:
|
||||
from pipenv.patched.notpip._vendor.urllib3.contrib import securetransport
|
||||
except (ImportError, OSError):
|
||||
pass
|
||||
else:
|
||||
securetransport.inject_into_urllib3()
|
||||
|
||||
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._vendor.urllib3.exceptions import InsecureRequestWarning
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Hide the InsecureRequestWarning from urllib3
|
||||
warnings.filterwarnings("ignore", category=InsecureRequestWarning)
|
||||
|
||||
|
||||
def main(args=None):
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
|
||||
# Configure our deprecation warnings to be sent through loggers
|
||||
deprecation.install_warning_logger()
|
||||
|
||||
autocomplete()
|
||||
|
||||
try:
|
||||
cmd_name, cmd_args = parse_command(args)
|
||||
except PipError as exc:
|
||||
sys.stderr.write("ERROR: %s" % exc)
|
||||
sys.stderr.write(os.linesep)
|
||||
sys.exit(1)
|
||||
|
||||
# Needed for locale.getpreferredencoding(False) to work
|
||||
# in pip._internal.utils.encoding.auto_decode
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
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=("--isolated" in cmd_args))
|
||||
return command.main(cmd_args)
|
||||
import pipenv.patched.notpip._internal.utils.inject_securetransport # noqa
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
"""Build Environment used for isolation during sdist building
|
||||
"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
@@ -12,14 +16,14 @@ from sysconfig import get_paths
|
||||
from pipenv.patched.notpip._vendor.pkg_resources import Requirement, VersionConflict, WorkingSet
|
||||
|
||||
from pipenv.patched.notpip import __file__ as pip_location
|
||||
from pipenv.patched.notpip._internal.utils.misc import call_subprocess
|
||||
from pipenv.patched.notpip._internal.utils.subprocess import call_subprocess
|
||||
from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
from pipenv.patched.notpip._internal.utils.ui import open_spinner
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Tuple, Set, Iterable, Optional, List # noqa: F401
|
||||
from pipenv.patched.notpip._internal.index import PackageFinder # noqa: F401
|
||||
from typing import Tuple, Set, Iterable, Optional, List
|
||||
from pipenv.patched.notpip._internal.index import PackageFinder
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -51,7 +55,6 @@ class BuildEnvironment(object):
|
||||
def __init__(self):
|
||||
# type: () -> None
|
||||
self._temp_dir = TempDirectory(kind="build-env")
|
||||
self._temp_dir.create()
|
||||
|
||||
self._prefixes = OrderedDict((
|
||||
(name, _Prefix(os.path.join(self._temp_dir.path, name)))
|
||||
@@ -166,8 +169,9 @@ class BuildEnvironment(object):
|
||||
prefix.setup = True
|
||||
if not requirements:
|
||||
return
|
||||
sys_executable = os.environ.get('PIP_PYTHON_PATH', sys.executable)
|
||||
args = [
|
||||
sys.executable, os.path.dirname(pip_location), 'install',
|
||||
sys_executable, os.path.dirname(pip_location), 'install',
|
||||
'--ignore-installed', '--no-user', '--prefix', prefix.path,
|
||||
'--no-warn-script-location',
|
||||
] # type: List[str]
|
||||
@@ -177,22 +181,25 @@ class BuildEnvironment(object):
|
||||
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:]:
|
||||
|
||||
index_urls = finder.index_urls
|
||||
if index_urls:
|
||||
args.extend(['-i', index_urls[0]])
|
||||
for extra_index in 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:
|
||||
|
||||
for host in finder.trusted_hosts:
|
||||
args.extend(['--trusted-host', host])
|
||||
if finder.allow_all_prereleases:
|
||||
args.append('--pre')
|
||||
args.append('--')
|
||||
args.extend(requirements)
|
||||
with open_spinner(message) as spinner:
|
||||
call_subprocess(args, show_stdout=False, spinner=spinner)
|
||||
call_subprocess(args, spinner=spinner)
|
||||
|
||||
|
||||
class NoOpBuildEnvironment(BuildEnvironment):
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
"""Cache Management
|
||||
"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
|
||||
import errno
|
||||
import hashlib
|
||||
import logging
|
||||
@@ -8,16 +11,17 @@ import os
|
||||
|
||||
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
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.utils.typing import MYPY_CHECK_RUNNING
|
||||
from pipenv.patched.notpip._internal.utils.urls import path_to_url
|
||||
from pipenv.patched.notpip._internal.wheel import InvalidWheelFilename, Wheel
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Optional, Set, List, Any # noqa: F401
|
||||
from pipenv.patched.notpip._internal.index import FormatControl # noqa: F401
|
||||
from typing import Optional, Set, List, Any
|
||||
from pipenv.patched.notpip._internal.index import FormatControl
|
||||
from pipenv.patched.notpip._internal.pep425tags import Pep425Tag
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -100,8 +104,13 @@ class Cache(object):
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get(self, link, package_name):
|
||||
# type: (Link, Optional[str]) -> Link
|
||||
def get(
|
||||
self,
|
||||
link, # type: Link
|
||||
package_name, # type: Optional[str]
|
||||
supported_tags, # type: List[Pep425Tag]
|
||||
):
|
||||
# type: (...) -> Link
|
||||
"""Returns a link to a cached item if it exists, otherwise returns the
|
||||
passed link.
|
||||
"""
|
||||
@@ -150,8 +159,13 @@ class SimpleWheelCache(Cache):
|
||||
# Store wheels within the root cache_dir
|
||||
return os.path.join(self.cache_dir, "wheels", *parts)
|
||||
|
||||
def get(self, link, package_name):
|
||||
# type: (Link, Optional[str]) -> Link
|
||||
def get(
|
||||
self,
|
||||
link, # type: Link
|
||||
package_name, # type: Optional[str]
|
||||
supported_tags, # type: List[Pep425Tag]
|
||||
):
|
||||
# type: (...) -> Link
|
||||
candidates = []
|
||||
|
||||
for wheel_name in self._get_candidates(link, package_name):
|
||||
@@ -159,10 +173,12 @@ class SimpleWheelCache(Cache):
|
||||
wheel = Wheel(wheel_name)
|
||||
except InvalidWheelFilename:
|
||||
continue
|
||||
if not wheel.supported():
|
||||
if not wheel.supported(supported_tags):
|
||||
# Built for a different python/arch/etc
|
||||
continue
|
||||
candidates.append((wheel.support_index_min(), wheel_name))
|
||||
candidates.append(
|
||||
(wheel.support_index_min(supported_tags), wheel_name)
|
||||
)
|
||||
|
||||
if not candidates:
|
||||
return link
|
||||
@@ -177,7 +193,6 @@ class EphemWheelCache(SimpleWheelCache):
|
||||
def __init__(self, format_control):
|
||||
# type: (FormatControl) -> None
|
||||
self._temp_dir = TempDirectory(kind="ephem-wheel-cache")
|
||||
self._temp_dir.create()
|
||||
|
||||
super(EphemWheelCache, self).__init__(
|
||||
self._temp_dir.path, format_control
|
||||
@@ -211,12 +226,26 @@ class WheelCache(Cache):
|
||||
# type: (Link) -> str
|
||||
return self._ephem_cache.get_path_for_link(link)
|
||||
|
||||
def get(self, link, package_name):
|
||||
# type: (Link, Optional[str]) -> Link
|
||||
retval = self._wheel_cache.get(link, package_name)
|
||||
if retval is link:
|
||||
retval = self._ephem_cache.get(link, package_name)
|
||||
return retval
|
||||
def get(
|
||||
self,
|
||||
link, # type: Link
|
||||
package_name, # type: Optional[str]
|
||||
supported_tags, # type: List[Pep425Tag]
|
||||
):
|
||||
# type: (...) -> Link
|
||||
retval = self._wheel_cache.get(
|
||||
link=link,
|
||||
package_name=package_name,
|
||||
supported_tags=supported_tags,
|
||||
)
|
||||
if retval is not link:
|
||||
return retval
|
||||
|
||||
return self._ephem_cache.get(
|
||||
link=link,
|
||||
package_name=package_name,
|
||||
supported_tags=supported_tags,
|
||||
)
|
||||
|
||||
def cleanup(self):
|
||||
# type: () -> None
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
"""Logic that powers autocompletion installed by ``pip completion``.
|
||||
"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
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.commands import commands_dict, create_command
|
||||
from pipenv.patched.notpip._internal.utils.misc import get_installed_distributions
|
||||
|
||||
|
||||
@@ -23,7 +26,7 @@ def autocomplete():
|
||||
except IndexError:
|
||||
current = ''
|
||||
|
||||
subcommands = [cmd for cmd, summary in get_summaries()]
|
||||
subcommands = list(commands_dict)
|
||||
options = []
|
||||
# subcommand
|
||||
try:
|
||||
@@ -54,7 +57,7 @@ def autocomplete():
|
||||
print(dist)
|
||||
sys.exit(1)
|
||||
|
||||
subcommand = commands_dict[subcommand_name]()
|
||||
subcommand = create_command(subcommand_name)
|
||||
|
||||
for opt in subcommand.parser.option_list_all:
|
||||
if opt.help != optparse.SUPPRESS_HELP:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Base Command class, and related routines"""
|
||||
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
import logging
|
||||
@@ -10,61 +11,59 @@ import sys
|
||||
import traceback
|
||||
|
||||
from pipenv.patched.notpip._internal.cli import cmdoptions
|
||||
from pipenv.patched.notpip._internal.cli.command_context import CommandContextMixIn
|
||||
from pipenv.patched.notpip._internal.cli.parser import (
|
||||
ConfigOptionParser, UpdatingDefaultsHelpFormatter,
|
||||
ConfigOptionParser,
|
||||
UpdatingDefaultsHelpFormatter,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.cli.status_codes import (
|
||||
ERROR, PREVIOUS_BUILD_DIR_ERROR, SUCCESS, UNKNOWN_ERROR,
|
||||
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,
|
||||
BadCommand,
|
||||
CommandError,
|
||||
InstallationError,
|
||||
PreviousBuildDirError,
|
||||
UninstallationError,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.index import PackageFinder
|
||||
from pipenv.patched.notpip._internal.locations import running_under_virtualenv
|
||||
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.deprecation import deprecated
|
||||
from pipenv.patched.notpip._internal.utils.logging import BrokenStdoutLoggingError, setup_logging
|
||||
from pipenv.patched.notpip._internal.utils.misc import (
|
||||
get_prog, normalize_path, redact_password_from_url,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.outdated import pip_version_check
|
||||
from pipenv.patched.notpip._internal.utils.misc import get_prog
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
from pipenv.patched.notpip._internal.utils.virtualenv import running_under_virtualenv
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Optional, List, Tuple, Any # noqa: F401
|
||||
from optparse import Values # noqa: F401
|
||||
from pipenv.patched.notpip._internal.cache import WheelCache # noqa: F401
|
||||
from pipenv.patched.notpip._internal.req.req_set import RequirementSet # noqa: F401
|
||||
from typing import List, Tuple, Any
|
||||
from optparse import Values
|
||||
|
||||
__all__ = ['Command']
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Command(object):
|
||||
name = None # type: Optional[str]
|
||||
usage = None # type: Optional[str]
|
||||
hidden = False # type: bool
|
||||
class Command(CommandContextMixIn):
|
||||
usage = None # type: str
|
||||
ignore_require_venv = False # type: bool
|
||||
|
||||
def __init__(self, isolated=False):
|
||||
# type: (bool) -> None
|
||||
def __init__(self, name, summary, isolated=False):
|
||||
# type: (str, str, bool) -> None
|
||||
super(Command, self).__init__()
|
||||
parser_kw = {
|
||||
'usage': self.usage,
|
||||
'prog': '%s %s' % (get_prog(), self.name),
|
||||
'prog': '%s %s' % (get_prog(), name),
|
||||
'formatter': UpdatingDefaultsHelpFormatter(),
|
||||
'add_help_option': False,
|
||||
'name': self.name,
|
||||
'name': name,
|
||||
'description': self.__doc__,
|
||||
'isolated': isolated,
|
||||
}
|
||||
|
||||
self.name = name
|
||||
self.summary = summary
|
||||
self.parser = ConfigOptionParser(**parser_kw)
|
||||
|
||||
# Commands should add options to this option group
|
||||
@@ -78,53 +77,34 @@ class Command(object):
|
||||
)
|
||||
self.parser.add_option_group(gen_opts)
|
||||
|
||||
def handle_pip_version_check(self, options):
|
||||
# type: (Values) -> None
|
||||
"""
|
||||
This is a no-op so that commands by default do not do the pip version
|
||||
check.
|
||||
"""
|
||||
# Make sure we do the pip version check if the index_group options
|
||||
# are present.
|
||||
assert not hasattr(options, 'no_index')
|
||||
|
||||
def run(self, options, args):
|
||||
# type: (Values, List[Any]) -> Any
|
||||
raise NotImplementedError
|
||||
|
||||
def _build_session(self, options, retries=None, timeout=None):
|
||||
# type: (Values, Optional[int], Optional[int]) -> PipSession
|
||||
session = PipSession(
|
||||
cache=(
|
||||
normalize_path(os.path.join(options.cache_dir, "http"))
|
||||
if options.cache_dir else None
|
||||
),
|
||||
retries=retries if retries is not None else options.retries,
|
||||
insecure_hosts=options.trusted_hosts,
|
||||
)
|
||||
|
||||
# Handle custom ca-bundles from the user
|
||||
if options.cert:
|
||||
session.verify = options.cert
|
||||
|
||||
# Handle SSL client certificate
|
||||
if options.client_cert:
|
||||
session.cert = options.client_cert
|
||||
|
||||
# Handle timeouts
|
||||
if options.timeout or timeout:
|
||||
session.timeout = (
|
||||
timeout if timeout is not None else options.timeout
|
||||
)
|
||||
|
||||
# Handle configured proxies
|
||||
if options.proxy:
|
||||
session.proxies = {
|
||||
"http": options.proxy,
|
||||
"https": options.proxy,
|
||||
}
|
||||
|
||||
# Determine if we can prompt the user for authentication or not
|
||||
session.auth.prompting = not options.no_input
|
||||
|
||||
return session
|
||||
|
||||
def parse_args(self, args):
|
||||
# type: (List[str]) -> Tuple
|
||||
# factored out for testability
|
||||
return self.parser.parse_args(args)
|
||||
|
||||
def main(self, args):
|
||||
# type: (List[str]) -> int
|
||||
try:
|
||||
with self.main_context():
|
||||
return self._main(args)
|
||||
finally:
|
||||
logging.shutdown()
|
||||
|
||||
def _main(self, args):
|
||||
# type: (List[str]) -> int
|
||||
options, args = self.parse_args(args)
|
||||
|
||||
@@ -137,17 +117,11 @@ class Command(object):
|
||||
user_log_file=options.log,
|
||||
)
|
||||
|
||||
if sys.version_info[:2] == (3, 4):
|
||||
deprecated(
|
||||
"Python 3.4 support has been deprecated. pip 19.1 will be the "
|
||||
"last one supporting it. Please upgrade your Python as Python "
|
||||
"3.4 won't be maintained after March 2019 (cf PEP 429).",
|
||||
replacement=None,
|
||||
gone_in='19.2',
|
||||
)
|
||||
elif sys.version_info[:2] == (2, 7):
|
||||
if sys.version_info[:2] == (2, 7):
|
||||
message = (
|
||||
"A future version of pip will drop support for Python 2.7."
|
||||
"A future version of pip will drop support for Python 2.7. "
|
||||
"More details about Python 2 support in pip, can be found at "
|
||||
"https://pip.pypa.io/en/latest/development/release-process/#python-2-support" # noqa
|
||||
)
|
||||
if platform.python_implementation() == "CPython":
|
||||
message = (
|
||||
@@ -192,7 +166,7 @@ class Command(object):
|
||||
|
||||
return ERROR
|
||||
except CommandError as exc:
|
||||
logger.critical('ERROR: %s', exc)
|
||||
logger.critical('%s', exc)
|
||||
logger.debug('Exception information:', exc_info=True)
|
||||
|
||||
return ERROR
|
||||
@@ -214,128 +188,6 @@ class Command(object):
|
||||
|
||||
return UNKNOWN_ERROR
|
||||
finally:
|
||||
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)
|
||||
)
|
||||
# Check if we're using the latest version of pip available
|
||||
if allow_version_check:
|
||||
session = self._build_session(
|
||||
options,
|
||||
retries=0,
|
||||
timeout=min(5, options.timeout)
|
||||
)
|
||||
with session:
|
||||
pip_version_check(session, options)
|
||||
|
||||
# Shutdown the logging module
|
||||
logging.shutdown()
|
||||
self.handle_pip_version_check(options)
|
||||
|
||||
return SUCCESS
|
||||
|
||||
|
||||
class RequirementCommand(Command):
|
||||
|
||||
@staticmethod
|
||||
def populate_requirement_set(requirement_set, # type: RequirementSet
|
||||
args, # type: List[str]
|
||||
options, # type: Values
|
||||
finder, # type: PackageFinder
|
||||
session, # type: PipSession
|
||||
name, # type: str
|
||||
wheel_cache # type: Optional[WheelCache]
|
||||
):
|
||||
# type: (...) -> None
|
||||
"""
|
||||
Marshal cmd line args into a requirement set.
|
||||
"""
|
||||
# NOTE: As a side-effect, options.require_hashes and
|
||||
# requirement_set.require_hashes may be updated
|
||||
|
||||
for filename in options.constraints:
|
||||
for req_to_add in parse_requirements(
|
||||
filename,
|
||||
constraint=True, finder=finder, options=options,
|
||||
session=session, wheel_cache=wheel_cache):
|
||||
req_to_add.is_direct = True
|
||||
requirement_set.add_requirement(req_to_add)
|
||||
|
||||
for req in args:
|
||||
req_to_add = install_req_from_line(
|
||||
req, None, isolated=options.isolated_mode,
|
||||
use_pep517=options.use_pep517,
|
||||
wheel_cache=wheel_cache
|
||||
)
|
||||
req_to_add.is_direct = True
|
||||
requirement_set.add_requirement(req_to_add)
|
||||
|
||||
for req in options.editables:
|
||||
req_to_add = install_req_from_editable(
|
||||
req,
|
||||
isolated=options.isolated_mode,
|
||||
use_pep517=options.use_pep517,
|
||||
wheel_cache=wheel_cache
|
||||
)
|
||||
req_to_add.is_direct = True
|
||||
requirement_set.add_requirement(req_to_add)
|
||||
|
||||
for filename in options.requirements:
|
||||
for req_to_add in parse_requirements(
|
||||
filename,
|
||||
finder=finder, options=options, session=session,
|
||||
wheel_cache=wheel_cache,
|
||||
use_pep517=options.use_pep517):
|
||||
req_to_add.is_direct = True
|
||||
requirement_set.add_requirement(req_to_add)
|
||||
# If --require-hashes was a line in a requirements file, tell
|
||||
# RequirementSet about it:
|
||||
requirement_set.require_hashes = options.require_hashes
|
||||
|
||||
if not (args or options.editables or options.requirements):
|
||||
opts = {'name': name}
|
||||
if options.find_links:
|
||||
raise CommandError(
|
||||
'You must give at least one requirement to %(name)s '
|
||||
'(maybe you meant "pip %(name)s %(links)s"?)' %
|
||||
dict(opts, links=' '.join(options.find_links)))
|
||||
else:
|
||||
raise CommandError(
|
||||
'You must give at least one requirement to %(name)s '
|
||||
'(see "pip help %(name)s")' % opts)
|
||||
|
||||
def _build_package_finder(
|
||||
self,
|
||||
options, # type: Values
|
||||
session, # type: PipSession
|
||||
platform=None, # type: Optional[str]
|
||||
python_versions=None, # type: Optional[List[str]]
|
||||
abi=None, # type: Optional[str]
|
||||
implementation=None # type: Optional[str]
|
||||
):
|
||||
# type: (...) -> PackageFinder
|
||||
"""
|
||||
Create a package finder appropriate to this requirement command.
|
||||
"""
|
||||
index_urls = [options.index_url] + options.extra_index_urls
|
||||
if options.no_index:
|
||||
logger.debug(
|
||||
'Ignoring indexes: %s',
|
||||
','.join(redact_password_from_url(url) for url in index_urls),
|
||||
)
|
||||
index_urls = []
|
||||
|
||||
return PackageFinder(
|
||||
find_links=options.find_links,
|
||||
format_control=options.format_control,
|
||||
index_urls=index_urls,
|
||||
trusted_hosts=options.trusted_hosts,
|
||||
allow_all_prereleases=options.pre,
|
||||
session=session,
|
||||
platform=platform,
|
||||
versions=python_versions,
|
||||
abi=abi,
|
||||
implementation=implementation,
|
||||
prefer_binary=options.prefer_binary,
|
||||
)
|
||||
|
||||
@@ -5,28 +5,37 @@ The principle here is to define options once, but *not* instantiate them
|
||||
globally. One reason being that options with action='append' can carry state
|
||||
between parses. pip parses general options twice internally, and shouldn't
|
||||
pass on state. To be consistent, all options will follow this design.
|
||||
|
||||
"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import textwrap
|
||||
import warnings
|
||||
from distutils.util import strtobool
|
||||
from functools import partial
|
||||
from optparse import SUPPRESS_HELP, Option, OptionGroup
|
||||
from textwrap import dedent
|
||||
|
||||
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.locations import USER_CACHE_DIR, get_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.models.target_python import TargetPython
|
||||
from pipenv.patched.notpip._internal.utils.hashes import STRONG_HASHES
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
from pipenv.patched.notpip._internal.utils.ui import BAR_TYPES
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Any, Callable, Dict, List, Optional, Union # noqa: F401
|
||||
from optparse import OptionParser, Values # noqa: F401
|
||||
from pipenv.patched.notpip._internal.cli.parser import ConfigOptionParser # noqa: F401
|
||||
from typing import Any, Callable, Dict, Optional, Tuple
|
||||
from optparse import OptionParser, Values
|
||||
from pipenv.patched.notpip._internal.cli.parser import ConfigOptionParser
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def raise_option_error(parser, option, msg):
|
||||
@@ -101,7 +110,7 @@ def check_dist_restriction(options, check_target=False):
|
||||
|
||||
# Installations or downloads using dist restrictions must not combine
|
||||
# source distributions and dist-specific wheels, as they are not
|
||||
# gauranteed to be locally compatible.
|
||||
# guaranteed to be locally compatible.
|
||||
if dist_restriction_set and sdist_dependencies_allowed:
|
||||
raise CommandError(
|
||||
"When restricting platform and interpreter constraints using "
|
||||
@@ -275,7 +284,7 @@ def exists_action():
|
||||
action='append',
|
||||
metavar='action',
|
||||
help="Default action when a path already exists: "
|
||||
"(s)witch, (i)gnore, (w)ipe, (b)ackup, (a)bort).",
|
||||
"(s)witch, (i)gnore, (w)ipe, (b)ackup, (a)bort.",
|
||||
)
|
||||
|
||||
|
||||
@@ -305,7 +314,7 @@ index_url = partial(
|
||||
dest='index_url',
|
||||
metavar='URL',
|
||||
default=PyPI.simple_url,
|
||||
help="Base URL of Python Package Index (default %default). "
|
||||
help="Base URL of the Python Package Index (default %default). "
|
||||
"This should point to a repository compliant with PEP 503 "
|
||||
"(the simple repository API) or a local directory laid out "
|
||||
"in the same format.",
|
||||
@@ -357,8 +366,8 @@ def trusted_host():
|
||||
action="append",
|
||||
metavar="HOSTNAME",
|
||||
default=[],
|
||||
help="Mark this host as trusted, even though it does not have valid "
|
||||
"or any HTTPS.",
|
||||
help="Mark this host or host:port pair as trusted, even though it "
|
||||
"does not have valid or any HTTPS.",
|
||||
)
|
||||
|
||||
|
||||
@@ -406,7 +415,7 @@ src = partial(
|
||||
'--src', '--source', '--source-dir', '--source-directory',
|
||||
dest='src_dir',
|
||||
metavar='dir',
|
||||
default=src_prefix,
|
||||
default=get_src_prefix(),
|
||||
help='Directory to check out editable projects into. '
|
||||
'The default in a virtualenv is "<venv path>/src". '
|
||||
'The default for global installs is "<current dir>/src".'
|
||||
@@ -445,9 +454,9 @@ def no_binary():
|
||||
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 "
|
||||
"more package names with commas between them. Note that some "
|
||||
"packages are tricky to compile and may fail to install when "
|
||||
"this option is used on them.",
|
||||
"more package names with commas between them (no colons). Note "
|
||||
"that some packages are tricky to compile and may fail to "
|
||||
"install when this option is used on them.",
|
||||
)
|
||||
|
||||
|
||||
@@ -478,18 +487,69 @@ platform = partial(
|
||||
) # type: Callable[..., Option]
|
||||
|
||||
|
||||
# This was made a separate function for unit-testing purposes.
|
||||
def _convert_python_version(value):
|
||||
# type: (str) -> Tuple[Tuple[int, ...], Optional[str]]
|
||||
"""
|
||||
Convert a version string like "3", "37", or "3.7.3" into a tuple of ints.
|
||||
|
||||
:return: A 2-tuple (version_info, error_msg), where `error_msg` is
|
||||
non-None if and only if there was a parsing error.
|
||||
"""
|
||||
if not value:
|
||||
# The empty string is the same as not providing a value.
|
||||
return (None, None)
|
||||
|
||||
parts = value.split('.')
|
||||
if len(parts) > 3:
|
||||
return ((), 'at most three version parts are allowed')
|
||||
|
||||
if len(parts) == 1:
|
||||
# Then we are in the case of "3" or "37".
|
||||
value = parts[0]
|
||||
if len(value) > 1:
|
||||
parts = [value[0], value[1:]]
|
||||
|
||||
try:
|
||||
version_info = tuple(int(part) for part in parts)
|
||||
except ValueError:
|
||||
return ((), 'each version part must be an integer')
|
||||
|
||||
return (version_info, None)
|
||||
|
||||
|
||||
def _handle_python_version(option, opt_str, value, parser):
|
||||
# type: (Option, str, str, OptionParser) -> None
|
||||
"""
|
||||
Handle a provided --python-version value.
|
||||
"""
|
||||
version_info, error_msg = _convert_python_version(value)
|
||||
if error_msg is not None:
|
||||
msg = (
|
||||
'invalid --python-version value: {!r}: {}'.format(
|
||||
value, error_msg,
|
||||
)
|
||||
)
|
||||
raise_option_error(parser, option=option, msg=msg)
|
||||
|
||||
parser.values.python_version = version_info
|
||||
|
||||
|
||||
python_version = partial(
|
||||
Option,
|
||||
'--python-version',
|
||||
dest='python_version',
|
||||
metavar='python_version',
|
||||
action='callback',
|
||||
callback=_handle_python_version, type='str',
|
||||
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."),
|
||||
help=dedent("""\
|
||||
The Python interpreter version to use for wheel and "Requires-Python"
|
||||
compatibility checks. Defaults to a version derived from the running
|
||||
interpreter. The version can be specified using up to three dot-separated
|
||||
integers (e.g. "3" for 3.0.0, "3.7" for 3.7.0, or "3.7.3"). A major-minor
|
||||
version can also be given as a string without dots (e.g. "37" for 3.7.0).
|
||||
"""),
|
||||
) # type: Callable[..., Option]
|
||||
|
||||
|
||||
@@ -522,6 +582,26 @@ abi = partial(
|
||||
) # type: Callable[..., Option]
|
||||
|
||||
|
||||
def add_target_python_options(cmd_opts):
|
||||
# type: (OptionGroup) -> None
|
||||
cmd_opts.add_option(platform())
|
||||
cmd_opts.add_option(python_version())
|
||||
cmd_opts.add_option(implementation())
|
||||
cmd_opts.add_option(abi())
|
||||
|
||||
|
||||
def make_target_python(options):
|
||||
# type: (Values) -> TargetPython
|
||||
target_python = TargetPython(
|
||||
platform=options.platform,
|
||||
py_version_info=options.python_version,
|
||||
abi=options.abi,
|
||||
implementation=options.implementation,
|
||||
)
|
||||
|
||||
return target_python
|
||||
|
||||
|
||||
def prefer_binary():
|
||||
# type: () -> Option
|
||||
return Option(
|
||||
@@ -543,7 +623,8 @@ cache_dir = partial(
|
||||
) # type: Callable[..., Option]
|
||||
|
||||
|
||||
def no_cache_dir_callback(option, opt, value, parser):
|
||||
def _handle_no_cache_dir(option, opt, value, parser):
|
||||
# type: (Option, str, str, OptionParser) -> None
|
||||
"""
|
||||
Process a value provided for the --no-cache-dir option.
|
||||
|
||||
@@ -575,7 +656,7 @@ no_cache = partial(
|
||||
"--no-cache-dir",
|
||||
dest="cache_dir",
|
||||
action="callback",
|
||||
callback=no_cache_dir_callback,
|
||||
callback=_handle_no_cache_dir,
|
||||
help="Disable the cache.",
|
||||
) # type: Callable[..., Option]
|
||||
|
||||
@@ -620,7 +701,8 @@ no_build_isolation = partial(
|
||||
) # type: Callable[..., Option]
|
||||
|
||||
|
||||
def no_use_pep517_callback(option, opt, value, parser):
|
||||
def _handle_no_use_pep517(option, opt, value, parser):
|
||||
# type: (Option, str, str, OptionParser) -> None
|
||||
"""
|
||||
Process a value provided for the --no-use-pep517 option.
|
||||
|
||||
@@ -658,7 +740,7 @@ no_use_pep517 = partial(
|
||||
'--no-use-pep517',
|
||||
dest='use_pep517',
|
||||
action='callback',
|
||||
callback=no_use_pep517_callback,
|
||||
callback=_handle_no_use_pep517,
|
||||
default=None,
|
||||
help=SUPPRESS_HELP
|
||||
) # type: Any
|
||||
@@ -724,12 +806,12 @@ always_unzip = partial(
|
||||
) # type: Callable[..., Option]
|
||||
|
||||
|
||||
def _merge_hash(option, opt_str, value, parser):
|
||||
def _handle_merge_hash(option, opt_str, value, parser):
|
||||
# type: (Option, str, str, OptionParser) -> None
|
||||
"""Given a value spelled "algo:digest", append the digest to a list
|
||||
pointed to in a dict by the algo name."""
|
||||
if not parser.values.hashes:
|
||||
parser.values.hashes = {} # type: ignore
|
||||
parser.values.hashes = {}
|
||||
try:
|
||||
algo, digest = value.split(':', 1)
|
||||
except ValueError:
|
||||
@@ -749,7 +831,7 @@ hash = partial(
|
||||
# __dict__ copying in process_line().
|
||||
dest='hashes',
|
||||
action='callback',
|
||||
callback=_merge_hash,
|
||||
callback=_handle_merge_hash,
|
||||
type='string',
|
||||
help="Verify that the package's archive matches this "
|
||||
'hash before installing. Example: --hash=sha256:abcdef...',
|
||||
@@ -768,6 +850,24 @@ require_hashes = partial(
|
||||
) # type: Callable[..., Option]
|
||||
|
||||
|
||||
list_path = partial(
|
||||
Option,
|
||||
'--path',
|
||||
dest='path',
|
||||
action='append',
|
||||
help='Restrict to the specified installation path for listing '
|
||||
'packages (can be used multiple times).'
|
||||
) # type: Callable[..., Option]
|
||||
|
||||
|
||||
def check_list_path_option(options):
|
||||
# type: (Values) -> None
|
||||
if options.path and (options.user or options.local):
|
||||
raise CommandError(
|
||||
"Cannot combine '--path' with '--user' or '--local'"
|
||||
)
|
||||
|
||||
|
||||
##########
|
||||
# groups #
|
||||
##########
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
from pipenv.patched.notpip._vendor.contextlib2 import ExitStack
|
||||
|
||||
|
||||
class CommandContextMixIn(object):
|
||||
def __init__(self):
|
||||
super(CommandContextMixIn, self).__init__()
|
||||
self._in_main_context = False
|
||||
self._main_context = ExitStack()
|
||||
|
||||
@contextmanager
|
||||
def main_context(self):
|
||||
assert not self._in_main_context
|
||||
|
||||
self._in_main_context = True
|
||||
try:
|
||||
with self._main_context:
|
||||
yield
|
||||
finally:
|
||||
self._in_main_context = False
|
||||
|
||||
def enter_context(self, context_provider):
|
||||
assert self._in_main_context
|
||||
|
||||
return self._main_context.enter_context(context_provider)
|
||||
@@ -4,20 +4,18 @@
|
||||
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,
|
||||
ConfigOptionParser,
|
||||
UpdatingDefaultsHelpFormatter,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.commands import commands_dict, get_similar_commands
|
||||
from pipenv.patched.notpip._internal.exceptions import CommandError
|
||||
from pipenv.patched.notpip._internal.utils.misc import get_prog
|
||||
from pipenv.patched.notpip._internal.utils.misc import get_pip_version, get_prog
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Tuple, List # noqa: F401
|
||||
from typing import Tuple, List
|
||||
|
||||
|
||||
__all__ = ["create_main_parser", "parse_command"]
|
||||
@@ -39,12 +37,7 @@ def create_main_parser():
|
||||
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],
|
||||
)
|
||||
parser.version = get_pip_version()
|
||||
|
||||
# add the general options
|
||||
gen_opts = cmdoptions.make_option_group(cmdoptions.general_group, parser)
|
||||
@@ -54,8 +47,10 @@ def create_main_parser():
|
||||
parser.main = True # type: ignore
|
||||
|
||||
# create command listing for description
|
||||
command_summaries = get_summaries()
|
||||
description = [''] + ['%-27s %s' % (i, j) for i, j in command_summaries]
|
||||
description = [''] + [
|
||||
'%-27s %s' % (name, command_info.summary)
|
||||
for name, command_info in commands_dict.items()
|
||||
]
|
||||
parser.description = '\n'.join(description)
|
||||
|
||||
return parser
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
"""Base option parser setup"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
"""Contains the Command base classes that depend on PipSession.
|
||||
|
||||
The classes in this module are in a separate module so the commands not
|
||||
needing download / PackageFinder capability don't unnecessarily import the
|
||||
PackageFinder machinery and all its vendored dependencies, etc.
|
||||
"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
import os
|
||||
from functools import partial
|
||||
|
||||
from pipenv.patched.notpip._internal.cli.base_command import Command
|
||||
from pipenv.patched.notpip._internal.cli.command_context import CommandContextMixIn
|
||||
from pipenv.patched.notpip._internal.exceptions import CommandError
|
||||
from pipenv.patched.notpip._internal.index import PackageFinder
|
||||
from pipenv.patched.notpip._internal.legacy_resolve import Resolver
|
||||
from pipenv.patched.notpip._internal.models.selection_prefs import SelectionPreferences
|
||||
from pipenv.patched.notpip._internal.network.session import PipSession
|
||||
from pipenv.patched.notpip._internal.operations.prepare import RequirementPreparer
|
||||
from pipenv.patched.notpip._internal.req.constructors import (
|
||||
install_req_from_editable,
|
||||
install_req_from_line,
|
||||
install_req_from_req_string,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.req.req_file import parse_requirements
|
||||
from pipenv.patched.notpip._internal.self_outdated_check import (
|
||||
make_link_collector,
|
||||
pip_self_version_check,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.misc import normalize_path
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from optparse import Values
|
||||
from typing import List, Optional, Tuple
|
||||
from pipenv.patched.notpip._internal.cache import WheelCache
|
||||
from pipenv.patched.notpip._internal.models.target_python import TargetPython
|
||||
from pipenv.patched.notpip._internal.req.req_set import RequirementSet
|
||||
from pipenv.patched.notpip._internal.req.req_tracker import RequirementTracker
|
||||
from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory
|
||||
|
||||
|
||||
class SessionCommandMixin(CommandContextMixIn):
|
||||
|
||||
"""
|
||||
A class mixin for command classes needing _build_session().
|
||||
"""
|
||||
def __init__(self):
|
||||
super(SessionCommandMixin, self).__init__()
|
||||
self._session = None # Optional[PipSession]
|
||||
|
||||
@classmethod
|
||||
def _get_index_urls(cls, options):
|
||||
"""Return a list of index urls from user-provided options."""
|
||||
index_urls = []
|
||||
if not getattr(options, "no_index", False):
|
||||
url = getattr(options, "index_url", None)
|
||||
if url:
|
||||
index_urls.append(url)
|
||||
urls = getattr(options, "extra_index_urls", None)
|
||||
if urls:
|
||||
index_urls.extend(urls)
|
||||
# Return None rather than an empty list
|
||||
return index_urls or None
|
||||
|
||||
def get_default_session(self, options):
|
||||
# type: (Values) -> PipSession
|
||||
"""Get a default-managed session."""
|
||||
if self._session is None:
|
||||
self._session = self.enter_context(self._build_session(options))
|
||||
return self._session
|
||||
|
||||
def _build_session(self, options, retries=None, timeout=None):
|
||||
# type: (Values, Optional[int], Optional[int]) -> PipSession
|
||||
session = PipSession(
|
||||
cache=(
|
||||
normalize_path(os.path.join(options.cache_dir, "http"))
|
||||
if options.cache_dir else None
|
||||
),
|
||||
retries=retries if retries is not None else options.retries,
|
||||
trusted_hosts=options.trusted_hosts,
|
||||
index_urls=self._get_index_urls(options),
|
||||
)
|
||||
|
||||
# Handle custom ca-bundles from the user
|
||||
if options.cert:
|
||||
session.verify = options.cert
|
||||
|
||||
# Handle SSL client certificate
|
||||
if options.client_cert:
|
||||
session.cert = options.client_cert
|
||||
|
||||
# Handle timeouts
|
||||
if options.timeout or timeout:
|
||||
session.timeout = (
|
||||
timeout if timeout is not None else options.timeout
|
||||
)
|
||||
|
||||
# Handle configured proxies
|
||||
if options.proxy:
|
||||
session.proxies = {
|
||||
"http": options.proxy,
|
||||
"https": options.proxy,
|
||||
}
|
||||
|
||||
# Determine if we can prompt the user for authentication or not
|
||||
session.auth.prompting = not options.no_input
|
||||
|
||||
return session
|
||||
|
||||
|
||||
class IndexGroupCommand(Command, SessionCommandMixin):
|
||||
|
||||
"""
|
||||
Abstract base class for commands with the index_group options.
|
||||
|
||||
This also corresponds to the commands that permit the pip version check.
|
||||
"""
|
||||
|
||||
def handle_pip_version_check(self, options):
|
||||
# type: (Values) -> None
|
||||
"""
|
||||
Do the pip version check if not disabled.
|
||||
|
||||
This overrides the default behavior of not doing the check.
|
||||
"""
|
||||
# Make sure the index_group options are present.
|
||||
assert hasattr(options, 'no_index')
|
||||
|
||||
if options.disable_pip_version_check or options.no_index:
|
||||
return
|
||||
|
||||
# Otherwise, check if we're using the latest version of pip available.
|
||||
session = self._build_session(
|
||||
options,
|
||||
retries=0,
|
||||
timeout=min(5, options.timeout)
|
||||
)
|
||||
with session:
|
||||
pip_self_version_check(session, options)
|
||||
|
||||
|
||||
class RequirementCommand(IndexGroupCommand):
|
||||
|
||||
@staticmethod
|
||||
def make_requirement_preparer(
|
||||
temp_build_dir, # type: TempDirectory
|
||||
options, # type: Values
|
||||
req_tracker, # type: RequirementTracker
|
||||
download_dir=None, # type: str
|
||||
wheel_download_dir=None, # type: str
|
||||
):
|
||||
# type: (...) -> RequirementPreparer
|
||||
"""
|
||||
Create a RequirementPreparer instance for the given parameters.
|
||||
"""
|
||||
temp_build_dir_path = temp_build_dir.path
|
||||
assert temp_build_dir_path is not None
|
||||
return RequirementPreparer(
|
||||
build_dir=temp_build_dir_path,
|
||||
src_dir=options.src_dir,
|
||||
download_dir=download_dir,
|
||||
wheel_download_dir=wheel_download_dir,
|
||||
progress_bar=options.progress_bar,
|
||||
build_isolation=options.build_isolation,
|
||||
req_tracker=req_tracker,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def make_resolver(
|
||||
preparer, # type: RequirementPreparer
|
||||
session, # type: PipSession
|
||||
finder, # type: PackageFinder
|
||||
options, # type: Values
|
||||
wheel_cache=None, # type: Optional[WheelCache]
|
||||
use_user_site=False, # type: bool
|
||||
ignore_installed=True, # type: bool
|
||||
ignore_requires_python=False, # type: bool
|
||||
force_reinstall=False, # type: bool
|
||||
upgrade_strategy="to-satisfy-only", # type: str
|
||||
use_pep517=None, # type: Optional[bool]
|
||||
py_version_info=None # type: Optional[Tuple[int, ...]]
|
||||
):
|
||||
# type: (...) -> Resolver
|
||||
"""
|
||||
Create a Resolver instance for the given parameters.
|
||||
"""
|
||||
make_install_req = partial(
|
||||
install_req_from_req_string,
|
||||
isolated=options.isolated_mode,
|
||||
wheel_cache=wheel_cache,
|
||||
use_pep517=use_pep517,
|
||||
)
|
||||
return Resolver(
|
||||
preparer=preparer,
|
||||
session=session,
|
||||
finder=finder,
|
||||
make_install_req=make_install_req,
|
||||
use_user_site=use_user_site,
|
||||
ignore_dependencies=options.ignore_dependencies,
|
||||
ignore_installed=ignore_installed,
|
||||
ignore_requires_python=ignore_requires_python,
|
||||
force_reinstall=force_reinstall,
|
||||
upgrade_strategy=upgrade_strategy,
|
||||
py_version_info=py_version_info
|
||||
)
|
||||
|
||||
def populate_requirement_set(
|
||||
self,
|
||||
requirement_set, # type: RequirementSet
|
||||
args, # type: List[str]
|
||||
options, # type: Values
|
||||
finder, # type: PackageFinder
|
||||
session, # type: PipSession
|
||||
wheel_cache, # type: Optional[WheelCache]
|
||||
):
|
||||
# type: (...) -> None
|
||||
"""
|
||||
Marshal cmd line args into a requirement set.
|
||||
"""
|
||||
# NOTE: As a side-effect, options.require_hashes and
|
||||
# requirement_set.require_hashes may be updated
|
||||
|
||||
for filename in options.constraints:
|
||||
for req_to_add in parse_requirements(
|
||||
filename,
|
||||
constraint=True, finder=finder, options=options,
|
||||
session=session, wheel_cache=wheel_cache):
|
||||
req_to_add.is_direct = True
|
||||
requirement_set.add_requirement(req_to_add)
|
||||
|
||||
for req in args:
|
||||
req_to_add = install_req_from_line(
|
||||
req, None, isolated=options.isolated_mode,
|
||||
use_pep517=options.use_pep517,
|
||||
wheel_cache=wheel_cache
|
||||
)
|
||||
req_to_add.is_direct = True
|
||||
requirement_set.add_requirement(req_to_add)
|
||||
|
||||
for req in options.editables:
|
||||
req_to_add = install_req_from_editable(
|
||||
req,
|
||||
isolated=options.isolated_mode,
|
||||
use_pep517=options.use_pep517,
|
||||
wheel_cache=wheel_cache
|
||||
)
|
||||
req_to_add.is_direct = True
|
||||
requirement_set.add_requirement(req_to_add)
|
||||
|
||||
for filename in options.requirements:
|
||||
for req_to_add in parse_requirements(
|
||||
filename,
|
||||
finder=finder, options=options, session=session,
|
||||
wheel_cache=wheel_cache,
|
||||
use_pep517=options.use_pep517):
|
||||
req_to_add.is_direct = True
|
||||
requirement_set.add_requirement(req_to_add)
|
||||
# If --require-hashes was a line in a requirements file, tell
|
||||
# RequirementSet about it:
|
||||
requirement_set.require_hashes = options.require_hashes
|
||||
|
||||
if not (args or options.editables or options.requirements):
|
||||
opts = {'name': self.name}
|
||||
if options.find_links:
|
||||
raise CommandError(
|
||||
'You must give at least one requirement to %(name)s '
|
||||
'(maybe you meant "pip %(name)s %(links)s"?)' %
|
||||
dict(opts, links=' '.join(options.find_links)))
|
||||
else:
|
||||
raise CommandError(
|
||||
'You must give at least one requirement to %(name)s '
|
||||
'(see "pip help %(name)s")' % opts)
|
||||
|
||||
def _build_package_finder(
|
||||
self,
|
||||
options, # type: Values
|
||||
session, # type: PipSession
|
||||
target_python=None, # type: Optional[TargetPython]
|
||||
ignore_requires_python=None, # type: Optional[bool]
|
||||
):
|
||||
# type: (...) -> PackageFinder
|
||||
"""
|
||||
Create a package finder appropriate to this requirement command.
|
||||
|
||||
:param ignore_requires_python: Whether to ignore incompatible
|
||||
"Requires-Python" values in links. Defaults to False.
|
||||
"""
|
||||
link_collector = make_link_collector(session, options=options)
|
||||
selection_prefs = SelectionPreferences(
|
||||
allow_yanked=True,
|
||||
format_control=options.format_control,
|
||||
allow_all_prereleases=options.pre,
|
||||
prefer_binary=options.prefer_binary,
|
||||
ignore_requires_python=ignore_requires_python,
|
||||
)
|
||||
|
||||
return PackageFinder.create(
|
||||
link_collector=link_collector,
|
||||
selection_prefs=selection_prefs,
|
||||
target_python=target_python,
|
||||
)
|
||||
@@ -0,0 +1,548 @@
|
||||
"""
|
||||
The main purpose of this module is to expose LinkCollector.collect_links().
|
||||
"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
import cgi
|
||||
import itertools
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
|
||||
from pipenv.patched.notpip._vendor import html5lib, requests
|
||||
from pipenv.patched.notpip._vendor.distlib.compat import unescape
|
||||
from pipenv.patched.notpip._vendor.requests.exceptions import HTTPError, RetryError, 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.models.link import Link
|
||||
from pipenv.patched.notpip._internal.utils.filetypes import ARCHIVE_EXTENSIONS
|
||||
from pipenv.patched.notpip._internal.utils.misc import redact_auth_from_url
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
from pipenv.patched.notpip._internal.utils.urls import path_to_url, url_to_path
|
||||
from pipenv.patched.notpip._internal.vcs import is_url, vcs
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import (
|
||||
Callable, Dict, Iterable, List, MutableMapping, Optional, Sequence,
|
||||
Tuple, Union,
|
||||
)
|
||||
import xml.etree.ElementTree
|
||||
|
||||
from pipenv.patched.notpip._vendor.requests import Response
|
||||
|
||||
from pipenv.patched.notpip._internal.models.search_scope import SearchScope
|
||||
from pipenv.patched.notpip._internal.network.session import PipSession
|
||||
|
||||
HTMLElement = xml.etree.ElementTree.Element
|
||||
ResponseHeaders = MutableMapping[str, str]
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _match_vcs_scheme(url):
|
||||
# type: (str) -> Optional[str]
|
||||
"""Look for VCS schemes in the URL.
|
||||
|
||||
Returns the matched VCS scheme, or None if there's no match.
|
||||
"""
|
||||
for scheme in vcs.schemes:
|
||||
if url.lower().startswith(scheme) and url[len(scheme)] in '+:':
|
||||
return scheme
|
||||
return None
|
||||
|
||||
|
||||
def _is_url_like_archive(url):
|
||||
# type: (str) -> bool
|
||||
"""Return whether the URL looks like an archive.
|
||||
"""
|
||||
filename = Link(url).filename
|
||||
for bad_ext in ARCHIVE_EXTENSIONS:
|
||||
if filename.endswith(bad_ext):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class _NotHTML(Exception):
|
||||
def __init__(self, content_type, request_desc):
|
||||
# type: (str, str) -> None
|
||||
super(_NotHTML, self).__init__(content_type, request_desc)
|
||||
self.content_type = content_type
|
||||
self.request_desc = request_desc
|
||||
|
||||
|
||||
def _ensure_html_header(response):
|
||||
# type: (Response) -> None
|
||||
"""Check the Content-Type header to ensure the response contains HTML.
|
||||
|
||||
Raises `_NotHTML` if the content type is not text/html.
|
||||
"""
|
||||
content_type = response.headers.get("Content-Type", "")
|
||||
if not content_type.lower().startswith("text/html"):
|
||||
raise _NotHTML(content_type, response.request.method)
|
||||
|
||||
|
||||
class _NotHTTP(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def _ensure_html_response(url, session):
|
||||
# type: (str, PipSession) -> None
|
||||
"""Send a HEAD request to the URL, and ensure the response contains HTML.
|
||||
|
||||
Raises `_NotHTTP` if the URL is not available for a HEAD request, or
|
||||
`_NotHTML` if the content type is not text/html.
|
||||
"""
|
||||
scheme, netloc, path, query, fragment = urllib_parse.urlsplit(url)
|
||||
if scheme not in {'http', 'https'}:
|
||||
raise _NotHTTP()
|
||||
|
||||
resp = session.head(url, allow_redirects=True)
|
||||
resp.raise_for_status()
|
||||
|
||||
_ensure_html_header(resp)
|
||||
|
||||
|
||||
def _get_html_response(url, session):
|
||||
# type: (str, PipSession) -> Response
|
||||
"""Access an HTML page with GET, and return the response.
|
||||
|
||||
This consists of three parts:
|
||||
|
||||
1. If the URL looks suspiciously like an archive, send a HEAD first to
|
||||
check the Content-Type is HTML, to avoid downloading a large file.
|
||||
Raise `_NotHTTP` if the content type cannot be determined, or
|
||||
`_NotHTML` if it is not HTML.
|
||||
2. Actually perform the request. Raise HTTP exceptions on network failures.
|
||||
3. Check the Content-Type header to make sure we got HTML, and raise
|
||||
`_NotHTML` otherwise.
|
||||
"""
|
||||
if _is_url_like_archive(url):
|
||||
_ensure_html_response(url, session=session)
|
||||
|
||||
logger.debug('Getting page %s', redact_auth_from_url(url))
|
||||
|
||||
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()
|
||||
|
||||
# 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.
|
||||
_ensure_html_header(resp)
|
||||
|
||||
return resp
|
||||
|
||||
|
||||
def _get_encoding_from_headers(headers):
|
||||
# type: (ResponseHeaders) -> Optional[str]
|
||||
"""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
|
||||
|
||||
|
||||
def _determine_base_url(document, page_url):
|
||||
# type: (HTMLElement, str) -> str
|
||||
"""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 _clean_link(url):
|
||||
# type: (str) -> str
|
||||
"""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)."""
|
||||
# Split the URL into parts according to the general structure
|
||||
# `scheme://netloc/path;parameters?query#fragment`. Note that the
|
||||
# `netloc` can be empty and the URI will then refer to a local
|
||||
# filesystem path.
|
||||
result = urllib_parse.urlparse(url)
|
||||
# In both cases below we unquote prior to quoting to make sure
|
||||
# nothing is double quoted.
|
||||
if result.netloc == "":
|
||||
# On Windows the path part might contain a drive letter which
|
||||
# should not be quoted. On Linux where drive letters do not
|
||||
# exist, the colon should be quoted. We rely on urllib.request
|
||||
# to do the right thing here.
|
||||
path = urllib_request.pathname2url(
|
||||
urllib_request.url2pathname(result.path))
|
||||
else:
|
||||
# In addition to the `/` character we protect `@` so that
|
||||
# revision strings in VCS URLs are properly parsed.
|
||||
path = urllib_parse.quote(urllib_parse.unquote(result.path), safe="/@")
|
||||
return urllib_parse.urlunparse(result._replace(path=path))
|
||||
|
||||
|
||||
def _create_link_from_element(
|
||||
anchor, # type: HTMLElement
|
||||
page_url, # type: str
|
||||
base_url, # type: str
|
||||
):
|
||||
# type: (...) -> Optional[Link]
|
||||
"""
|
||||
Convert an anchor element in a simple repository page to a Link.
|
||||
"""
|
||||
href = anchor.get("href")
|
||||
if not href:
|
||||
return None
|
||||
|
||||
url = _clean_link(urllib_parse.urljoin(base_url, href))
|
||||
pyrequire = anchor.get('data-requires-python')
|
||||
pyrequire = unescape(pyrequire) if pyrequire else None
|
||||
|
||||
yanked_reason = anchor.get('data-yanked')
|
||||
if yanked_reason:
|
||||
# This is a unicode string in Python 2 (and 3).
|
||||
yanked_reason = unescape(yanked_reason)
|
||||
|
||||
link = Link(
|
||||
url,
|
||||
comes_from=page_url,
|
||||
requires_python=pyrequire,
|
||||
yanked_reason=yanked_reason,
|
||||
)
|
||||
|
||||
return link
|
||||
|
||||
|
||||
def parse_links(page):
|
||||
# type: (HTMLPage) -> Iterable[Link]
|
||||
"""
|
||||
Parse an HTML document, and yield its anchor elements as Link objects.
|
||||
"""
|
||||
document = html5lib.parse(
|
||||
page.content,
|
||||
transport_encoding=page.encoding,
|
||||
namespaceHTMLElements=False,
|
||||
)
|
||||
|
||||
url = page.url
|
||||
base_url = _determine_base_url(document, url)
|
||||
for anchor in document.findall(".//a"):
|
||||
link = _create_link_from_element(
|
||||
anchor,
|
||||
page_url=url,
|
||||
base_url=base_url,
|
||||
)
|
||||
if link is None:
|
||||
continue
|
||||
yield link
|
||||
|
||||
|
||||
class HTMLPage(object):
|
||||
"""Represents one page, along with its URL"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
content, # type: bytes
|
||||
encoding, # type: Optional[str]
|
||||
url, # type: str
|
||||
):
|
||||
# type: (...) -> None
|
||||
"""
|
||||
:param encoding: the encoding to decode the given content.
|
||||
:param url: the URL from which the HTML was downloaded.
|
||||
"""
|
||||
self.content = content
|
||||
self.encoding = encoding
|
||||
self.url = url
|
||||
|
||||
def __str__(self):
|
||||
return redact_auth_from_url(self.url)
|
||||
|
||||
|
||||
def _handle_get_page_fail(
|
||||
link, # type: Link
|
||||
reason, # type: Union[str, Exception]
|
||||
meth=None # type: Optional[Callable[..., None]]
|
||||
):
|
||||
# type: (...) -> None
|
||||
if meth is None:
|
||||
meth = logger.debug
|
||||
meth("Could not fetch URL %s: %s - skipping", link, reason)
|
||||
|
||||
|
||||
def _make_html_page(response):
|
||||
# type: (Response) -> HTMLPage
|
||||
encoding = _get_encoding_from_headers(response.headers)
|
||||
return HTMLPage(response.content, encoding=encoding, url=response.url)
|
||||
|
||||
|
||||
def _get_html_page(link, session=None):
|
||||
# type: (Link, Optional[PipSession]) -> Optional[HTMLPage]
|
||||
if session is None:
|
||||
raise TypeError(
|
||||
"_get_html_page() missing 1 required keyword argument: 'session'"
|
||||
)
|
||||
|
||||
url = link.url.split('#', 1)[0]
|
||||
|
||||
# Check for VCS schemes that do not support lookup as web pages.
|
||||
vcs_scheme = _match_vcs_scheme(url)
|
||||
if vcs_scheme:
|
||||
logger.debug('Cannot look at %s URL %s', vcs_scheme, link)
|
||||
return None
|
||||
|
||||
# Tack index.html onto file:// URLs that point to directories
|
||||
scheme, _, path, _, _, _ = 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)
|
||||
|
||||
try:
|
||||
resp = _get_html_response(url, session=session)
|
||||
except _NotHTTP:
|
||||
logger.debug(
|
||||
'Skipping page %s because it looks like an archive, and cannot '
|
||||
'be checked by HEAD.', link,
|
||||
)
|
||||
except _NotHTML as exc:
|
||||
logger.debug(
|
||||
'Skipping page %s because the %s request got Content-Type: %s',
|
||||
link, exc.request_desc, exc.content_type,
|
||||
)
|
||||
except HTTPError as exc:
|
||||
_handle_get_page_fail(link, exc)
|
||||
except RetryError as exc:
|
||||
_handle_get_page_fail(link, exc)
|
||||
except SSLError as exc:
|
||||
reason = "There was a problem confirming the ssl certificate: "
|
||||
reason += str(exc)
|
||||
_handle_get_page_fail(link, reason, meth=logger.info)
|
||||
except requests.ConnectionError as exc:
|
||||
_handle_get_page_fail(link, "connection error: %s" % exc)
|
||||
except requests.Timeout:
|
||||
_handle_get_page_fail(link, "timed out")
|
||||
else:
|
||||
return _make_html_page(resp)
|
||||
return None
|
||||
|
||||
|
||||
def _remove_duplicate_links(links):
|
||||
# type: (Iterable[Link]) -> List[Link]
|
||||
"""
|
||||
Return a list of links, with duplicates removed and ordering preserved.
|
||||
"""
|
||||
# We preserve the ordering when removing duplicates because we can.
|
||||
return list(OrderedDict.fromkeys(links))
|
||||
|
||||
|
||||
def group_locations(locations, expand_dir=False):
|
||||
# type: (Sequence[str], bool) -> Tuple[List[str], List[str]]
|
||||
"""
|
||||
Divide a list of locations into two groups: "files" (archives) and "urls."
|
||||
|
||||
:return: A pair of lists (files, urls).
|
||||
"""
|
||||
files = []
|
||||
urls = []
|
||||
|
||||
# puts the url for the given file path into the appropriate list
|
||||
def sort_path(path):
|
||||
url = path_to_url(path)
|
||||
if mimetypes.guess_type(url, strict=False)[0] == 'text/html':
|
||||
urls.append(url)
|
||||
else:
|
||||
files.append(url)
|
||||
|
||||
for url in locations:
|
||||
|
||||
is_local_path = os.path.exists(url)
|
||||
is_file_url = url.startswith('file:')
|
||||
|
||||
if is_local_path or is_file_url:
|
||||
if is_local_path:
|
||||
path = url
|
||||
else:
|
||||
path = url_to_path(url)
|
||||
if os.path.isdir(path):
|
||||
if expand_dir:
|
||||
path = os.path.realpath(path)
|
||||
for item in os.listdir(path):
|
||||
sort_path(os.path.join(path, item))
|
||||
elif is_file_url:
|
||||
urls.append(url)
|
||||
else:
|
||||
logger.warning(
|
||||
"Path '{0}' is ignored: "
|
||||
"it is a directory.".format(path),
|
||||
)
|
||||
elif os.path.isfile(path):
|
||||
sort_path(path)
|
||||
else:
|
||||
logger.warning(
|
||||
"Url '%s' is ignored: it is neither a file "
|
||||
"nor a directory.", url,
|
||||
)
|
||||
elif is_url(url):
|
||||
# Only add url with clear scheme
|
||||
urls.append(url)
|
||||
else:
|
||||
logger.warning(
|
||||
"Url '%s' is ignored. It is either a non-existing "
|
||||
"path or lacks a specific scheme.", url,
|
||||
)
|
||||
|
||||
return files, urls
|
||||
|
||||
|
||||
class CollectedLinks(object):
|
||||
|
||||
"""
|
||||
Encapsulates all the Link objects collected by a call to
|
||||
LinkCollector.collect_links(), stored separately as--
|
||||
|
||||
(1) links from the configured file locations,
|
||||
(2) links from the configured find_links, and
|
||||
(3) a dict mapping HTML page url to links from that page.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
files, # type: List[Link]
|
||||
find_links, # type: List[Link]
|
||||
pages, # type: Dict[str, List[Link]]
|
||||
):
|
||||
# type: (...) -> None
|
||||
"""
|
||||
:param files: Links from file locations.
|
||||
:param find_links: Links from find_links.
|
||||
:param pages: A dict mapping HTML page url to links from that page.
|
||||
"""
|
||||
self.files = files
|
||||
self.find_links = find_links
|
||||
self.pages = pages
|
||||
|
||||
|
||||
class LinkCollector(object):
|
||||
|
||||
"""
|
||||
Responsible for collecting Link objects from all configured locations,
|
||||
making network requests as needed.
|
||||
|
||||
The class's main method is its collect_links() method.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
session, # type: PipSession
|
||||
search_scope, # type: SearchScope
|
||||
):
|
||||
# type: (...) -> None
|
||||
self.search_scope = search_scope
|
||||
self.session = session
|
||||
|
||||
@property
|
||||
def find_links(self):
|
||||
# type: () -> List[str]
|
||||
return self.search_scope.find_links
|
||||
|
||||
def _get_pages(self, locations):
|
||||
# type: (Iterable[Link]) -> Iterable[HTMLPage]
|
||||
"""
|
||||
Yields (page, page_url) from the given locations, skipping
|
||||
locations that have errors.
|
||||
"""
|
||||
for location in locations:
|
||||
page = _get_html_page(location, session=self.session)
|
||||
if page is None:
|
||||
continue
|
||||
|
||||
yield page
|
||||
|
||||
def collect_links(self, project_name):
|
||||
# type: (str) -> CollectedLinks
|
||||
"""Find all available links for the given project name.
|
||||
|
||||
:return: All the Link objects (unfiltered), as a CollectedLinks object.
|
||||
"""
|
||||
search_scope = self.search_scope
|
||||
index_locations = search_scope.get_index_urls_locations(project_name)
|
||||
index_file_loc, index_url_loc = group_locations(index_locations)
|
||||
fl_file_loc, fl_url_loc = group_locations(
|
||||
self.find_links, expand_dir=True,
|
||||
)
|
||||
|
||||
file_links = [
|
||||
Link(url) for url in itertools.chain(index_file_loc, fl_file_loc)
|
||||
]
|
||||
|
||||
# We trust every directly linked archive in find_links
|
||||
find_link_links = [Link(url, '-f') for url in self.find_links]
|
||||
|
||||
# We trust every url that the user has given us whether it was given
|
||||
# via --index-url or --find-links.
|
||||
# We want to filter out anything that does not have a secure origin.
|
||||
url_locations = [
|
||||
link for link in itertools.chain(
|
||||
(Link(url) for url in index_url_loc),
|
||||
(Link(url) for url in fl_url_loc),
|
||||
)
|
||||
if self.session.is_secure_origin(link)
|
||||
]
|
||||
|
||||
url_locations = _remove_duplicate_links(url_locations)
|
||||
lines = [
|
||||
'{} location(s) to search for versions of {}:'.format(
|
||||
len(url_locations), project_name,
|
||||
),
|
||||
]
|
||||
for link in url_locations:
|
||||
lines.append('* {}'.format(link))
|
||||
logger.debug('\n'.join(lines))
|
||||
|
||||
pages_links = {}
|
||||
for page in self._get_pages(url_locations):
|
||||
pages_links[page.url] = list(parse_links(page))
|
||||
|
||||
return CollectedLinks(
|
||||
files=file_links,
|
||||
find_links=find_link_links,
|
||||
pages=pages_links,
|
||||
)
|
||||
@@ -1,57 +1,103 @@
|
||||
"""
|
||||
Package containing all pip commands
|
||||
"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from pipenv.patched.notpip._internal.commands.completion import CompletionCommand
|
||||
from pipenv.patched.notpip._internal.commands.configuration import ConfigurationCommand
|
||||
from pipenv.patched.notpip._internal.commands.download import DownloadCommand
|
||||
from pipenv.patched.notpip._internal.commands.freeze import FreezeCommand
|
||||
from pipenv.patched.notpip._internal.commands.hash import HashCommand
|
||||
from pipenv.patched.notpip._internal.commands.help import HelpCommand
|
||||
from pipenv.patched.notpip._internal.commands.list import ListCommand
|
||||
from pipenv.patched.notpip._internal.commands.check import CheckCommand
|
||||
from pipenv.patched.notpip._internal.commands.search import SearchCommand
|
||||
from pipenv.patched.notpip._internal.commands.show import ShowCommand
|
||||
from pipenv.patched.notpip._internal.commands.install import InstallCommand
|
||||
from pipenv.patched.notpip._internal.commands.uninstall import UninstallCommand
|
||||
from pipenv.patched.notpip._internal.commands.wheel import WheelCommand
|
||||
import importlib
|
||||
from collections import OrderedDict, namedtuple
|
||||
|
||||
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.cli.base_command import Command # noqa: F401
|
||||
|
||||
commands_order = [
|
||||
InstallCommand,
|
||||
DownloadCommand,
|
||||
UninstallCommand,
|
||||
FreezeCommand,
|
||||
ListCommand,
|
||||
ShowCommand,
|
||||
CheckCommand,
|
||||
ConfigurationCommand,
|
||||
SearchCommand,
|
||||
WheelCommand,
|
||||
HashCommand,
|
||||
CompletionCommand,
|
||||
HelpCommand,
|
||||
] # type: List[Type[Command]]
|
||||
|
||||
commands_dict = {c.name: c for c in commands_order}
|
||||
from typing import Any
|
||||
from pipenv.patched.notpip._internal.cli.base_command import Command
|
||||
|
||||
|
||||
def get_summaries(ordered=True):
|
||||
"""Yields sorted (command name, command summary) tuples."""
|
||||
CommandInfo = namedtuple('CommandInfo', 'module_path, class_name, summary')
|
||||
|
||||
if ordered:
|
||||
cmditems = _sort_commands(commands_dict, commands_order)
|
||||
else:
|
||||
cmditems = commands_dict.items()
|
||||
# The ordering matters for help display.
|
||||
# Also, even though the module path starts with the same
|
||||
# "pipenv.patched.notpip._internal.commands" prefix in each case, we include the full path
|
||||
# because it makes testing easier (specifically when modifying commands_dict
|
||||
# in test setup / teardown by adding info for a FakeCommand class defined
|
||||
# in a test-related module).
|
||||
# Finally, we need to pass an iterable of pairs here rather than a dict
|
||||
# so that the ordering won't be lost when using Python 2.7.
|
||||
commands_dict = OrderedDict([
|
||||
('install', CommandInfo(
|
||||
'pipenv.patched.notpip._internal.commands.install', 'InstallCommand',
|
||||
'Install packages.',
|
||||
)),
|
||||
('download', CommandInfo(
|
||||
'pipenv.patched.notpip._internal.commands.download', 'DownloadCommand',
|
||||
'Download packages.',
|
||||
)),
|
||||
('uninstall', CommandInfo(
|
||||
'pipenv.patched.notpip._internal.commands.uninstall', 'UninstallCommand',
|
||||
'Uninstall packages.',
|
||||
)),
|
||||
('freeze', CommandInfo(
|
||||
'pipenv.patched.notpip._internal.commands.freeze', 'FreezeCommand',
|
||||
'Output installed packages in requirements format.',
|
||||
)),
|
||||
('list', CommandInfo(
|
||||
'pipenv.patched.notpip._internal.commands.list', 'ListCommand',
|
||||
'List installed packages.',
|
||||
)),
|
||||
('show', CommandInfo(
|
||||
'pipenv.patched.notpip._internal.commands.show', 'ShowCommand',
|
||||
'Show information about installed packages.',
|
||||
)),
|
||||
('check', CommandInfo(
|
||||
'pipenv.patched.notpip._internal.commands.check', 'CheckCommand',
|
||||
'Verify installed packages have compatible dependencies.',
|
||||
)),
|
||||
('config', CommandInfo(
|
||||
'pipenv.patched.notpip._internal.commands.configuration', 'ConfigurationCommand',
|
||||
'Manage local and global configuration.',
|
||||
)),
|
||||
('search', CommandInfo(
|
||||
'pipenv.patched.notpip._internal.commands.search', 'SearchCommand',
|
||||
'Search PyPI for packages.',
|
||||
)),
|
||||
('wheel', CommandInfo(
|
||||
'pipenv.patched.notpip._internal.commands.wheel', 'WheelCommand',
|
||||
'Build wheels from your requirements.',
|
||||
)),
|
||||
('hash', CommandInfo(
|
||||
'pipenv.patched.notpip._internal.commands.hash', 'HashCommand',
|
||||
'Compute hashes of package archives.',
|
||||
)),
|
||||
('completion', CommandInfo(
|
||||
'pipenv.patched.notpip._internal.commands.completion', 'CompletionCommand',
|
||||
'A helper command used for command completion.',
|
||||
)),
|
||||
('debug', CommandInfo(
|
||||
'pipenv.patched.notpip._internal.commands.debug', 'DebugCommand',
|
||||
'Show information useful for debugging.',
|
||||
)),
|
||||
('help', CommandInfo(
|
||||
'pipenv.patched.notpip._internal.commands.help', 'HelpCommand',
|
||||
'Show help for commands.',
|
||||
)),
|
||||
]) # type: OrderedDict[str, CommandInfo]
|
||||
|
||||
for name, command_class in cmditems:
|
||||
yield (name, command_class.summary)
|
||||
|
||||
def create_command(name, **kwargs):
|
||||
# type: (str, **Any) -> Command
|
||||
"""
|
||||
Create an instance of the Command class with the given name.
|
||||
"""
|
||||
module_path, class_name, summary = commands_dict[name]
|
||||
module = importlib.import_module(module_path)
|
||||
command_class = getattr(module, class_name)
|
||||
command = command_class(name=name, summary=summary, **kwargs)
|
||||
|
||||
return command
|
||||
|
||||
|
||||
def get_similar_commands(name):
|
||||
@@ -66,14 +112,3 @@ def get_similar_commands(name):
|
||||
return close_commands[0]
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def _sort_commands(cmddict, order):
|
||||
def keyfn(key):
|
||||
try:
|
||||
return order.index(key[1])
|
||||
except ValueError:
|
||||
# unordered items should come last
|
||||
return 0xff
|
||||
|
||||
return sorted(cmddict.items(), key=keyfn)
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
import logging
|
||||
|
||||
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,
|
||||
check_package_set,
|
||||
create_package_set_from_installed,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.misc import write_output
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CheckCommand(Command):
|
||||
"""Verify installed packages have compatible dependencies."""
|
||||
name = 'check'
|
||||
|
||||
usage = """
|
||||
%prog [options]"""
|
||||
summary = 'Verify installed packages have compatible dependencies.'
|
||||
|
||||
def run(self, options, args):
|
||||
package_set, parsing_probs = create_package_set_from_installed()
|
||||
@@ -22,7 +26,7 @@ class CheckCommand(Command):
|
||||
for project_name in missing:
|
||||
version = package_set[project_name].version
|
||||
for dependency in missing[project_name]:
|
||||
logger.info(
|
||||
write_output(
|
||||
"%s %s requires %s, which is not installed.",
|
||||
project_name, version, dependency[0],
|
||||
)
|
||||
@@ -30,7 +34,7 @@ class CheckCommand(Command):
|
||||
for project_name in conflicting:
|
||||
version = package_set[project_name].version
|
||||
for dep_name, dep_version, req in conflicting[project_name]:
|
||||
logger.info(
|
||||
write_output(
|
||||
"%s %s has requirement %s, but you have %s %s.",
|
||||
project_name, version, req, dep_name, dep_version,
|
||||
)
|
||||
@@ -38,4 +42,4 @@ class CheckCommand(Command):
|
||||
if missing or conflicting or parsing_probs:
|
||||
return 1
|
||||
else:
|
||||
logger.info("No broken requirements found.")
|
||||
write_output("No broken requirements found.")
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
@@ -16,7 +19,7 @@ COMPLETION_SCRIPTS = {
|
||||
{
|
||||
COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \\
|
||||
COMP_CWORD=$COMP_CWORD \\
|
||||
PIP_AUTO_COMPLETE=1 $1 ) )
|
||||
PIP_AUTO_COMPLETE=1 $1 2>/dev/null ) )
|
||||
}
|
||||
complete -o default -F _pip_completion %(prog)s
|
||||
""",
|
||||
@@ -27,7 +30,7 @@ COMPLETION_SCRIPTS = {
|
||||
read -cn cword
|
||||
reply=( $( COMP_WORDS="$words[*]" \\
|
||||
COMP_CWORD=$(( cword-1 )) \\
|
||||
PIP_AUTO_COMPLETE=1 $words[1] ) )
|
||||
PIP_AUTO_COMPLETE=1 $words[1] 2>/dev/null ))
|
||||
}
|
||||
compctl -K _pip_completion %(prog)s
|
||||
""",
|
||||
@@ -47,8 +50,7 @@ COMPLETION_SCRIPTS = {
|
||||
|
||||
class CompletionCommand(Command):
|
||||
"""A helper command to be used for command completion."""
|
||||
name = 'completion'
|
||||
summary = 'A helper command used for command completion.'
|
||||
|
||||
ignore_require_venv = True
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
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.configuration import (
|
||||
Configuration,
|
||||
get_configuration_files,
|
||||
kinds,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.exceptions import PipError
|
||||
from pipenv.patched.notpip._internal.locations import venv_config_file
|
||||
from pipenv.patched.notpip._internal.utils.misc import get_prog
|
||||
from pipenv.patched.notpip._internal.utils.misc import get_prog, write_output
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -23,13 +29,13 @@ class ConfigurationCommand(Command):
|
||||
set: Set the name=value
|
||||
unset: Unset the value associated with name
|
||||
|
||||
If none of --user, --global and --venv are passed, a virtual
|
||||
If none of --user, --global and --site are passed, a virtual
|
||||
environment configuration file is used if one is active and the file
|
||||
exists. Otherwise, all modifications happen on the to the user file by
|
||||
default.
|
||||
"""
|
||||
|
||||
name = 'config'
|
||||
ignore_require_venv = True
|
||||
usage = """
|
||||
%prog [<file-option>] list
|
||||
%prog [<file-option>] [--editor <editor-path>] edit
|
||||
@@ -39,8 +45,6 @@ class ConfigurationCommand(Command):
|
||||
%prog [<file-option>] unset name
|
||||
"""
|
||||
|
||||
summary = "Manage local and global configuration."
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ConfigurationCommand, self).__init__(*args, **kwargs)
|
||||
|
||||
@@ -74,11 +78,11 @@ class ConfigurationCommand(Command):
|
||||
)
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
'--venv',
|
||||
dest='venv_file',
|
||||
'--site',
|
||||
dest='site_file',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Use the virtualenv configuration file only'
|
||||
help='Use the current environment configuration file only'
|
||||
)
|
||||
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
@@ -127,40 +131,42 @@ class ConfigurationCommand(Command):
|
||||
return SUCCESS
|
||||
|
||||
def _determine_file(self, options, need_value):
|
||||
file_options = {
|
||||
kinds.USER: options.user_file,
|
||||
kinds.GLOBAL: options.global_file,
|
||||
kinds.VENV: options.venv_file
|
||||
}
|
||||
file_options = [key for key, value in (
|
||||
(kinds.USER, options.user_file),
|
||||
(kinds.GLOBAL, options.global_file),
|
||||
(kinds.SITE, options.site_file),
|
||||
) if value]
|
||||
|
||||
if sum(file_options.values()) == 0:
|
||||
if not file_options:
|
||||
if not need_value:
|
||||
return None
|
||||
# Default to user, unless there's a virtualenv file.
|
||||
elif os.path.exists(venv_config_file):
|
||||
return kinds.VENV
|
||||
# Default to user, unless there's a site file.
|
||||
elif any(
|
||||
os.path.exists(site_config_file)
|
||||
for site_config_file in get_configuration_files()[kinds.SITE]
|
||||
):
|
||||
return kinds.SITE
|
||||
else:
|
||||
return kinds.USER
|
||||
elif sum(file_options.values()) == 1:
|
||||
# There's probably a better expression for this.
|
||||
return [key for key in file_options if file_options[key]][0]
|
||||
elif len(file_options) == 1:
|
||||
return file_options[0]
|
||||
|
||||
raise PipError(
|
||||
"Need exactly one file to operate upon "
|
||||
"(--user, --venv, --global) to perform."
|
||||
"(--user, --site, --global) to perform."
|
||||
)
|
||||
|
||||
def list_values(self, options, args):
|
||||
self._get_n_args(args, "list", n=0)
|
||||
|
||||
for key, value in sorted(self.configuration.items()):
|
||||
logger.info("%s=%r", key, value)
|
||||
write_output("%s=%r", key, value)
|
||||
|
||||
def get_name(self, options, args):
|
||||
key = self._get_n_args(args, "get [name]", n=1)
|
||||
value = self.configuration.get_value(key)
|
||||
|
||||
logger.info("%s", value)
|
||||
write_output("%s", value)
|
||||
|
||||
def set_name_value(self, options, args):
|
||||
key, value = self._get_n_args(args, "set [name] [value]", n=2)
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import locale
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from pipenv.patched.notpip._internal.cli import cmdoptions
|
||||
from pipenv.patched.notpip._internal.cli.base_command import Command
|
||||
from pipenv.patched.notpip._internal.cli.cmdoptions import make_target_python
|
||||
from pipenv.patched.notpip._internal.cli.status_codes import SUCCESS
|
||||
from pipenv.patched.notpip._internal.utils.logging import indent_log
|
||||
from pipenv.patched.notpip._internal.utils.misc import get_pip_version
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
from pipenv.patched.notpip._internal.wheel import format_tag
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Any, List
|
||||
from optparse import Values
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def show_value(name, value):
|
||||
# type: (str, str) -> None
|
||||
logger.info('{}: {}'.format(name, value))
|
||||
|
||||
|
||||
def show_sys_implementation():
|
||||
# type: () -> None
|
||||
logger.info('sys.implementation:')
|
||||
if hasattr(sys, 'implementation'):
|
||||
implementation = sys.implementation # type: ignore
|
||||
implementation_name = implementation.name
|
||||
else:
|
||||
implementation_name = ''
|
||||
|
||||
with indent_log():
|
||||
show_value('name', implementation_name)
|
||||
|
||||
|
||||
def show_tags(options):
|
||||
# type: (Values) -> None
|
||||
tag_limit = 10
|
||||
|
||||
target_python = make_target_python(options)
|
||||
tags = target_python.get_tags()
|
||||
|
||||
# Display the target options that were explicitly provided.
|
||||
formatted_target = target_python.format_given()
|
||||
suffix = ''
|
||||
if formatted_target:
|
||||
suffix = ' (target: {})'.format(formatted_target)
|
||||
|
||||
msg = 'Compatible tags: {}{}'.format(len(tags), suffix)
|
||||
logger.info(msg)
|
||||
|
||||
if options.verbose < 1 and len(tags) > tag_limit:
|
||||
tags_limited = True
|
||||
tags = tags[:tag_limit]
|
||||
else:
|
||||
tags_limited = False
|
||||
|
||||
with indent_log():
|
||||
for tag in tags:
|
||||
logger.info(format_tag(tag))
|
||||
|
||||
if tags_limited:
|
||||
msg = (
|
||||
'...\n'
|
||||
'[First {tag_limit} tags shown. Pass --verbose to show all.]'
|
||||
).format(tag_limit=tag_limit)
|
||||
logger.info(msg)
|
||||
|
||||
|
||||
class DebugCommand(Command):
|
||||
"""
|
||||
Display debug information.
|
||||
"""
|
||||
|
||||
usage = """
|
||||
%prog <options>"""
|
||||
ignore_require_venv = True
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(DebugCommand, self).__init__(*args, **kw)
|
||||
|
||||
cmd_opts = self.cmd_opts
|
||||
cmdoptions.add_target_python_options(cmd_opts)
|
||||
self.parser.insert_option_group(0, cmd_opts)
|
||||
|
||||
def run(self, options, args):
|
||||
# type: (Values, List[Any]) -> int
|
||||
logger.warning(
|
||||
"This command is only meant for debugging. "
|
||||
"Do not use this with automation for parsing and getting these "
|
||||
"details, since the output and options of this command may "
|
||||
"change without notice."
|
||||
)
|
||||
show_value('pip version', get_pip_version())
|
||||
show_value('sys.version', sys.version)
|
||||
show_value('sys.executable', sys.executable)
|
||||
show_value('sys.getdefaultencoding', sys.getdefaultencoding())
|
||||
show_value('sys.getfilesystemencoding', sys.getfilesystemencoding())
|
||||
show_value(
|
||||
'locale.getpreferredencoding', locale.getpreferredencoding(),
|
||||
)
|
||||
show_value('sys.platform', sys.platform)
|
||||
show_sys_implementation()
|
||||
|
||||
show_tags(options)
|
||||
|
||||
return SUCCESS
|
||||
@@ -1,16 +1,18 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
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.cli.cmdoptions import make_target_python
|
||||
from pipenv.patched.notpip._internal.cli.req_command import RequirementCommand
|
||||
from pipenv.patched.notpip._internal.req import RequirementSet
|
||||
from pipenv.patched.notpip._internal.req.req_tracker import RequirementTracker
|
||||
from pipenv.patched.notpip._internal.resolve import Resolver
|
||||
from pipenv.patched.notpip._internal.utils.filesystem import check_path_owner
|
||||
from pipenv.patched.notpip._internal.utils.misc import ensure_dir, normalize_path
|
||||
from pipenv.patched.notpip._internal.utils.misc import ensure_dir, normalize_path, write_output
|
||||
from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -28,7 +30,6 @@ class DownloadCommand(RequirementCommand):
|
||||
pip also supports downloading from "requirements files", which provide
|
||||
an easy way to specify a whole environment to be downloaded.
|
||||
"""
|
||||
name = 'download'
|
||||
|
||||
usage = """
|
||||
%prog [options] <requirement specifier> [package-index-options] ...
|
||||
@@ -37,8 +38,6 @@ class DownloadCommand(RequirementCommand):
|
||||
%prog [options] <local project path> ...
|
||||
%prog [options] <archive url/path> ..."""
|
||||
|
||||
summary = 'Download packages.'
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(DownloadCommand, self).__init__(*args, **kw)
|
||||
|
||||
@@ -69,10 +68,7 @@ class DownloadCommand(RequirementCommand):
|
||||
help=("Download packages into <dir>."),
|
||||
)
|
||||
|
||||
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())
|
||||
cmdoptions.add_target_python_options(cmd_opts)
|
||||
|
||||
index_opts = cmdoptions.make_option_group(
|
||||
cmdoptions.index_group,
|
||||
@@ -88,11 +84,6 @@ class DownloadCommand(RequirementCommand):
|
||||
# of the RequirementSet code require that property.
|
||||
options.editables = []
|
||||
|
||||
if options.python_version:
|
||||
python_versions = [options.python_version]
|
||||
else:
|
||||
python_versions = None
|
||||
|
||||
cmdoptions.check_dist_restriction(options)
|
||||
|
||||
options.src_dir = os.path.abspath(options.src_dir)
|
||||
@@ -100,77 +91,66 @@ class DownloadCommand(RequirementCommand):
|
||||
|
||||
ensure_dir(options.download_dir)
|
||||
|
||||
with self._build_session(options) as session:
|
||||
finder = self._build_package_finder(
|
||||
options=options,
|
||||
session=session,
|
||||
platform=options.platform,
|
||||
python_versions=python_versions,
|
||||
abi=options.abi,
|
||||
implementation=options.implementation,
|
||||
session = self.get_default_session(options)
|
||||
|
||||
target_python = make_target_python(options)
|
||||
finder = self._build_package_finder(
|
||||
options=options,
|
||||
session=session,
|
||||
target_python=target_python,
|
||||
)
|
||||
build_delete = (not (options.no_clean or options.build_dir))
|
||||
if options.cache_dir and not check_path_owner(options.cache_dir):
|
||||
logger.warning(
|
||||
"The directory '%s' or its parent directory is not owned "
|
||||
"by the current user and caching wheels has been "
|
||||
"disabled. check the permissions and owner of that "
|
||||
"directory. If executing pip with sudo, you may want "
|
||||
"sudo's -H flag.",
|
||||
options.cache_dir,
|
||||
)
|
||||
build_delete = (not (options.no_clean or options.build_dir))
|
||||
if options.cache_dir and not check_path_owner(options.cache_dir):
|
||||
logger.warning(
|
||||
"The directory '%s' or its parent directory is not owned "
|
||||
"by the current user and caching wheels has been "
|
||||
"disabled. check the permissions and owner of that "
|
||||
"directory. If executing pip with sudo, you may want "
|
||||
"sudo's -H flag.",
|
||||
options.cache_dir,
|
||||
)
|
||||
options.cache_dir = None
|
||||
options.cache_dir = None
|
||||
|
||||
with RequirementTracker() as req_tracker, TempDirectory(
|
||||
options.build_dir, delete=build_delete, kind="download"
|
||||
) as directory:
|
||||
with RequirementTracker() as req_tracker, TempDirectory(
|
||||
options.build_dir, delete=build_delete, kind="download"
|
||||
) as directory:
|
||||
|
||||
requirement_set = RequirementSet(
|
||||
require_hashes=options.require_hashes,
|
||||
)
|
||||
self.populate_requirement_set(
|
||||
requirement_set,
|
||||
args,
|
||||
options,
|
||||
finder,
|
||||
session,
|
||||
self.name,
|
||||
None
|
||||
)
|
||||
requirement_set = RequirementSet(
|
||||
require_hashes=options.require_hashes,
|
||||
)
|
||||
self.populate_requirement_set(
|
||||
requirement_set,
|
||||
args,
|
||||
options,
|
||||
finder,
|
||||
session,
|
||||
None
|
||||
)
|
||||
|
||||
preparer = RequirementPreparer(
|
||||
build_dir=directory.path,
|
||||
src_dir=options.src_dir,
|
||||
download_dir=options.download_dir,
|
||||
wheel_download_dir=None,
|
||||
progress_bar=options.progress_bar,
|
||||
build_isolation=options.build_isolation,
|
||||
req_tracker=req_tracker,
|
||||
)
|
||||
preparer = self.make_requirement_preparer(
|
||||
temp_build_dir=directory,
|
||||
options=options,
|
||||
req_tracker=req_tracker,
|
||||
download_dir=options.download_dir,
|
||||
)
|
||||
|
||||
resolver = Resolver(
|
||||
preparer=preparer,
|
||||
finder=finder,
|
||||
session=session,
|
||||
wheel_cache=None,
|
||||
use_user_site=False,
|
||||
upgrade_strategy="to-satisfy-only",
|
||||
force_reinstall=False,
|
||||
ignore_dependencies=options.ignore_dependencies,
|
||||
ignore_requires_python=False,
|
||||
ignore_installed=True,
|
||||
isolated=options.isolated_mode,
|
||||
)
|
||||
resolver.resolve(requirement_set)
|
||||
resolver = self.make_resolver(
|
||||
preparer=preparer,
|
||||
finder=finder,
|
||||
session=session,
|
||||
options=options,
|
||||
py_version_info=options.python_version,
|
||||
)
|
||||
resolver.resolve(requirement_set)
|
||||
|
||||
downloaded = ' '.join([
|
||||
req.name for req in requirement_set.successfully_downloaded
|
||||
])
|
||||
if downloaded:
|
||||
logger.info('Successfully downloaded %s', downloaded)
|
||||
downloaded = ' '.join([
|
||||
req.name for req in requirement_set.successfully_downloaded
|
||||
])
|
||||
if downloaded:
|
||||
write_output('Successfully downloaded %s', downloaded)
|
||||
|
||||
# Clean up
|
||||
if not options.no_clean:
|
||||
requirement_set.cleanup_files()
|
||||
# Clean up
|
||||
if not options.no_clean:
|
||||
requirement_set.cleanup_files()
|
||||
|
||||
return requirement_set
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
|
||||
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 Command
|
||||
from pipenv.patched.notpip._internal.models.format_control import FormatControl
|
||||
from pipenv.patched.notpip._internal.operations.freeze import freeze
|
||||
@@ -17,10 +21,9 @@ class FreezeCommand(Command):
|
||||
|
||||
packages are listed in a case-insensitive sorted order.
|
||||
"""
|
||||
name = 'freeze'
|
||||
|
||||
usage = """
|
||||
%prog [options]"""
|
||||
summary = 'Output installed packages in requirements format.'
|
||||
log_streams = ("ext://sys.stderr", "ext://sys.stderr")
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
@@ -56,6 +59,7 @@ class FreezeCommand(Command):
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Only output packages installed in user-site.')
|
||||
self.cmd_opts.add_option(cmdoptions.list_path())
|
||||
self.cmd_opts.add_option(
|
||||
'--all',
|
||||
dest='freeze_all',
|
||||
@@ -77,11 +81,14 @@ class FreezeCommand(Command):
|
||||
if not options.freeze_all:
|
||||
skip.update(DEV_PKGS)
|
||||
|
||||
cmdoptions.check_list_path_option(options)
|
||||
|
||||
freeze_kwargs = dict(
|
||||
requirement=options.requirements,
|
||||
find_links=options.find_links,
|
||||
local_only=options.local,
|
||||
user_only=options.user,
|
||||
paths=options.path,
|
||||
skip_regex=options.skip_requirements_regex,
|
||||
isolated=options.isolated_mode,
|
||||
wheel_cache=wheel_cache,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import hashlib
|
||||
@@ -7,7 +10,7 @@ import sys
|
||||
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
|
||||
from pipenv.patched.notpip._internal.utils.misc import read_chunks, write_output
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -18,11 +21,9 @@ class HashCommand(Command):
|
||||
|
||||
These can be used with --hash in a requirements file to do repeatable
|
||||
installs.
|
||||
|
||||
"""
|
||||
name = 'hash'
|
||||
|
||||
usage = '%prog [options] <file> ...'
|
||||
summary = 'Compute hashes of package archives.'
|
||||
ignore_require_venv = True
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
@@ -44,8 +45,8 @@ class HashCommand(Command):
|
||||
|
||||
algorithm = options.algorithm
|
||||
for path in args:
|
||||
logger.info('%s:\n--hash=%s:%s',
|
||||
path, algorithm, _hash_of_file(path, algorithm))
|
||||
write_output('%s:\n--hash=%s:%s',
|
||||
path, algorithm, _hash_of_file(path, algorithm))
|
||||
|
||||
|
||||
def _hash_of_file(path, algorithm):
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from pipenv.patched.notpip._internal.cli.base_command import Command
|
||||
@@ -7,14 +10,15 @@ from pipenv.patched.notpip._internal.exceptions import CommandError
|
||||
|
||||
class HelpCommand(Command):
|
||||
"""Show help for commands"""
|
||||
name = 'help'
|
||||
|
||||
usage = """
|
||||
%prog <command>"""
|
||||
summary = 'Show help for commands.'
|
||||
ignore_require_venv = True
|
||||
|
||||
def run(self, options, args):
|
||||
from pipenv.patched.notpip._internal.commands import commands_dict, get_similar_commands
|
||||
from pipenv.patched.notpip._internal.commands import (
|
||||
commands_dict, create_command, get_similar_commands,
|
||||
)
|
||||
|
||||
try:
|
||||
# 'pip help' with no args is handled by pip.__init__.parseopt()
|
||||
@@ -31,7 +35,7 @@ class HelpCommand(Command):
|
||||
|
||||
raise CommandError(' - '.join(msg))
|
||||
|
||||
command = commands_dict[cmd_name]()
|
||||
command = create_command(cmd_name)
|
||||
command.parser.print_help()
|
||||
|
||||
return SUCCESS
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# It's included for now because without it InstallCommand.run() has a
|
||||
# couple errors where we have to know req.name is str rather than
|
||||
# Optional[str] for the InstallRequirement req.
|
||||
# mypy: strict-optional=False
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import errno
|
||||
@@ -8,31 +15,101 @@ import shutil
|
||||
from optparse import SUPPRESS_HELP
|
||||
|
||||
from pipenv.patched.notpip._vendor import pkg_resources
|
||||
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
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.cli.cmdoptions import make_target_python
|
||||
from pipenv.patched.notpip._internal.cli.req_command import RequirementCommand
|
||||
from pipenv.patched.notpip._internal.cli.status_codes import ERROR, SUCCESS
|
||||
from pipenv.patched.notpip._internal.exceptions import (
|
||||
CommandError, InstallationError, PreviousBuildDirError,
|
||||
CommandError,
|
||||
InstallationError,
|
||||
PreviousBuildDirError,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.locations import distutils_scheme, virtualenv_no_global
|
||||
from pipenv.patched.notpip._internal.locations import distutils_scheme
|
||||
from pipenv.patched.notpip._internal.operations.check import check_install_conflicts
|
||||
from pipenv.patched.notpip._internal.operations.prepare import RequirementPreparer
|
||||
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.utils.filesystem import check_path_owner
|
||||
from pipenv.patched.notpip._internal.utils.misc import (
|
||||
ensure_dir, get_installed_version,
|
||||
ensure_dir,
|
||||
get_installed_version,
|
||||
protect_pip_from_modification_on_windows,
|
||||
write_output,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
from pipenv.patched.notpip._internal.utils.virtualenv import virtualenv_no_global
|
||||
from pipenv.patched.notpip._internal.wheel import WheelBuilder
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from optparse import Values
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from pipenv.patched.notpip._internal.models.format_control import FormatControl
|
||||
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
|
||||
from pipenv.patched.notpip._internal.wheel import BinaryAllowedPredicate
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def is_wheel_installed():
|
||||
"""
|
||||
Return whether the wheel package is installed.
|
||||
"""
|
||||
try:
|
||||
import wheel # noqa: F401
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def build_wheels(
|
||||
builder, # type: WheelBuilder
|
||||
pep517_requirements, # type: List[InstallRequirement]
|
||||
legacy_requirements, # type: List[InstallRequirement]
|
||||
):
|
||||
# type: (...) -> List[InstallRequirement]
|
||||
"""
|
||||
Build wheels for requirements, depending on whether wheel is installed.
|
||||
"""
|
||||
# We don't build wheels for legacy requirements if wheel is not installed.
|
||||
should_build_legacy = is_wheel_installed()
|
||||
|
||||
# Always build PEP 517 requirements
|
||||
build_failures = builder.build(
|
||||
pep517_requirements,
|
||||
should_unpack=True,
|
||||
)
|
||||
|
||||
if should_build_legacy:
|
||||
# We don't care about failures building legacy
|
||||
# requirements, as we'll fall through to a direct
|
||||
# install for those.
|
||||
builder.build(
|
||||
legacy_requirements,
|
||||
should_unpack=True,
|
||||
)
|
||||
|
||||
return build_failures
|
||||
|
||||
|
||||
def get_check_binary_allowed(format_control):
|
||||
# type: (FormatControl) -> BinaryAllowedPredicate
|
||||
def check_binary_allowed(req):
|
||||
# type: (InstallRequirement) -> bool
|
||||
if req.use_pep517:
|
||||
return True
|
||||
canonical_name = canonicalize_name(req.name)
|
||||
allowed_formats = format_control.get_allowed_formats(canonical_name)
|
||||
return "binary" in allowed_formats
|
||||
|
||||
return check_binary_allowed
|
||||
|
||||
|
||||
class InstallCommand(RequirementCommand):
|
||||
"""
|
||||
Install packages from:
|
||||
@@ -45,7 +122,6 @@ class InstallCommand(RequirementCommand):
|
||||
pip also supports installing from "requirements files", which provide
|
||||
an easy way to specify a whole environment to be installed.
|
||||
"""
|
||||
name = 'install'
|
||||
|
||||
usage = """
|
||||
%prog [options] <requirement specifier> [package-index-options] ...
|
||||
@@ -54,8 +130,6 @@ class InstallCommand(RequirementCommand):
|
||||
%prog [options] [-e] <local project path> ...
|
||||
%prog [options] <archive url/path> ..."""
|
||||
|
||||
summary = 'Install packages.'
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(InstallCommand, self).__init__(*args, **kw)
|
||||
|
||||
@@ -77,10 +151,7 @@ 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())
|
||||
cmdoptions.add_target_python_options(cmd_opts)
|
||||
|
||||
cmd_opts.add_option(
|
||||
'--user',
|
||||
@@ -148,7 +219,11 @@ class InstallCommand(RequirementCommand):
|
||||
'-I', '--ignore-installed',
|
||||
dest='ignore_installed',
|
||||
action='store_true',
|
||||
help='Ignore the installed packages (reinstalling instead).')
|
||||
help='Ignore the installed packages, overwriting them. '
|
||||
'This can break your system if the existing package '
|
||||
'is of a different version or was installed '
|
||||
'with a different package manager!'
|
||||
)
|
||||
|
||||
cmd_opts.add_option(cmdoptions.ignore_requires_python())
|
||||
cmd_opts.add_option(cmdoptions.no_build_isolation())
|
||||
@@ -204,6 +279,7 @@ class InstallCommand(RequirementCommand):
|
||||
self.parser.insert_option_group(0, cmd_opts)
|
||||
|
||||
def run(self, options, args):
|
||||
# type: (Values, List[Any]) -> int
|
||||
cmdoptions.check_install_build_global(options)
|
||||
upgrade_strategy = "to-satisfy-only"
|
||||
if options.upgrade:
|
||||
@@ -214,11 +290,6 @@ class InstallCommand(RequirementCommand):
|
||||
|
||||
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:
|
||||
@@ -235,7 +306,8 @@ class InstallCommand(RequirementCommand):
|
||||
install_options.append('--user')
|
||||
install_options.append('--prefix=')
|
||||
|
||||
target_temp_dir = TempDirectory(kind="target")
|
||||
target_temp_dir = None # type: Optional[TempDirectory]
|
||||
target_temp_dir_path = None # type: Optional[str]
|
||||
if options.target_dir:
|
||||
options.ignore_installed = True
|
||||
options.target_dir = os.path.abspath(options.target_dir)
|
||||
@@ -247,200 +319,193 @@ class InstallCommand(RequirementCommand):
|
||||
)
|
||||
|
||||
# Create a target directory for using with the target option
|
||||
target_temp_dir.create()
|
||||
install_options.append('--home=' + target_temp_dir.path)
|
||||
target_temp_dir = TempDirectory(kind="target")
|
||||
target_temp_dir_path = target_temp_dir.path
|
||||
install_options.append('--home=' + target_temp_dir_path)
|
||||
|
||||
global_options = options.global_options or []
|
||||
|
||||
with self._build_session(options) as session:
|
||||
finder = self._build_package_finder(
|
||||
options=options,
|
||||
session=session,
|
||||
platform=options.platform,
|
||||
python_versions=python_versions,
|
||||
abi=options.abi,
|
||||
implementation=options.implementation,
|
||||
session = self.get_default_session(options)
|
||||
|
||||
target_python = make_target_python(options)
|
||||
finder = self._build_package_finder(
|
||||
options=options,
|
||||
session=session,
|
||||
target_python=target_python,
|
||||
ignore_requires_python=options.ignore_requires_python,
|
||||
)
|
||||
build_delete = (not (options.no_clean or options.build_dir))
|
||||
wheel_cache = WheelCache(options.cache_dir, options.format_control)
|
||||
|
||||
if options.cache_dir and not check_path_owner(options.cache_dir):
|
||||
logger.warning(
|
||||
"The directory '%s' or its parent directory is not owned "
|
||||
"by the current user and caching wheels has been "
|
||||
"disabled. check the permissions and owner of that "
|
||||
"directory. If executing pip with sudo, you may want "
|
||||
"sudo's -H flag.",
|
||||
options.cache_dir,
|
||||
)
|
||||
build_delete = (not (options.no_clean or options.build_dir))
|
||||
wheel_cache = WheelCache(options.cache_dir, options.format_control)
|
||||
options.cache_dir = None
|
||||
|
||||
if options.cache_dir and not check_path_owner(options.cache_dir):
|
||||
logger.warning(
|
||||
"The directory '%s' or its parent directory is not owned "
|
||||
"by the current user and caching wheels has been "
|
||||
"disabled. check the permissions and owner of that "
|
||||
"directory. If executing pip with sudo, you may want "
|
||||
"sudo's -H flag.",
|
||||
options.cache_dir,
|
||||
)
|
||||
options.cache_dir = None
|
||||
with RequirementTracker() as req_tracker, TempDirectory(
|
||||
options.build_dir, delete=build_delete, kind="install"
|
||||
) as directory:
|
||||
requirement_set = RequirementSet(
|
||||
require_hashes=options.require_hashes,
|
||||
check_supported_wheels=not options.target_dir,
|
||||
)
|
||||
|
||||
with RequirementTracker() as req_tracker, TempDirectory(
|
||||
options.build_dir, delete=build_delete, kind="install"
|
||||
) as directory:
|
||||
requirement_set = RequirementSet(
|
||||
require_hashes=options.require_hashes,
|
||||
check_supported_wheels=not options.target_dir,
|
||||
try:
|
||||
self.populate_requirement_set(
|
||||
requirement_set, args, options, finder, session,
|
||||
wheel_cache
|
||||
)
|
||||
preparer = self.make_requirement_preparer(
|
||||
temp_build_dir=directory,
|
||||
options=options,
|
||||
req_tracker=req_tracker,
|
||||
)
|
||||
resolver = self.make_resolver(
|
||||
preparer=preparer,
|
||||
finder=finder,
|
||||
session=session,
|
||||
options=options,
|
||||
wheel_cache=wheel_cache,
|
||||
use_user_site=options.use_user_site,
|
||||
ignore_installed=options.ignore_installed,
|
||||
ignore_requires_python=options.ignore_requires_python,
|
||||
force_reinstall=options.force_reinstall,
|
||||
upgrade_strategy=upgrade_strategy,
|
||||
use_pep517=options.use_pep517,
|
||||
)
|
||||
resolver.resolve(requirement_set)
|
||||
|
||||
try:
|
||||
self.populate_requirement_set(
|
||||
requirement_set, args, options, finder, session,
|
||||
self.name, wheel_cache
|
||||
)
|
||||
preparer = RequirementPreparer(
|
||||
build_dir=directory.path,
|
||||
src_dir=options.src_dir,
|
||||
download_dir=None,
|
||||
wheel_download_dir=None,
|
||||
progress_bar=options.progress_bar,
|
||||
build_isolation=options.build_isolation,
|
||||
req_tracker=req_tracker,
|
||||
)
|
||||
pip_req = requirement_set.get_requirement("pip")
|
||||
except KeyError:
|
||||
modifying_pip = None
|
||||
else:
|
||||
# If we're not replacing an already installed pip,
|
||||
# we're not modifying it.
|
||||
modifying_pip = getattr(pip_req, "satisfied_by", None) is None
|
||||
protect_pip_from_modification_on_windows(
|
||||
modifying_pip=modifying_pip
|
||||
)
|
||||
|
||||
resolver = Resolver(
|
||||
preparer=preparer,
|
||||
finder=finder,
|
||||
session=session,
|
||||
wheel_cache=wheel_cache,
|
||||
use_user_site=options.use_user_site,
|
||||
upgrade_strategy=upgrade_strategy,
|
||||
force_reinstall=options.force_reinstall,
|
||||
ignore_dependencies=options.ignore_dependencies,
|
||||
ignore_requires_python=options.ignore_requires_python,
|
||||
ignore_installed=options.ignore_installed,
|
||||
isolated=options.isolated_mode,
|
||||
use_pep517=options.use_pep517
|
||||
)
|
||||
resolver.resolve(requirement_set)
|
||||
check_binary_allowed = get_check_binary_allowed(
|
||||
finder.format_control
|
||||
)
|
||||
# Consider legacy and PEP517-using requirements separately
|
||||
legacy_requirements = []
|
||||
pep517_requirements = []
|
||||
for req in requirement_set.requirements.values():
|
||||
if req.use_pep517:
|
||||
pep517_requirements.append(req)
|
||||
else:
|
||||
legacy_requirements.append(req)
|
||||
|
||||
protect_pip_from_modification_on_windows(
|
||||
modifying_pip=requirement_set.has_requirement("pip")
|
||||
)
|
||||
wheel_builder = WheelBuilder(
|
||||
preparer, wheel_cache,
|
||||
build_options=[], global_options=[],
|
||||
check_binary_allowed=check_binary_allowed,
|
||||
)
|
||||
|
||||
# Consider legacy and PEP517-using requirements separately
|
||||
legacy_requirements = []
|
||||
pep517_requirements = []
|
||||
for req in requirement_set.requirements.values():
|
||||
if req.use_pep517:
|
||||
pep517_requirements.append(req)
|
||||
else:
|
||||
legacy_requirements.append(req)
|
||||
build_failures = build_wheels(
|
||||
builder=wheel_builder,
|
||||
pep517_requirements=pep517_requirements,
|
||||
legacy_requirements=legacy_requirements,
|
||||
)
|
||||
|
||||
# We don't build wheels for legacy requirements if we
|
||||
# don't have wheel installed or we don't have a cache dir
|
||||
# If we're using PEP 517, we cannot do a direct install
|
||||
# so we fail here.
|
||||
if build_failures:
|
||||
raise InstallationError(
|
||||
"Could not build wheels for {} which use"
|
||||
" PEP 517 and cannot be installed directly".format(
|
||||
", ".join(r.name for r in build_failures)))
|
||||
|
||||
to_install = resolver.get_installation_order(
|
||||
requirement_set
|
||||
)
|
||||
|
||||
# Consistency Checking of the package set we're installing.
|
||||
should_warn_about_conflicts = (
|
||||
not options.ignore_dependencies and
|
||||
options.warn_about_conflicts
|
||||
)
|
||||
if should_warn_about_conflicts:
|
||||
self._warn_about_conflicts(to_install)
|
||||
|
||||
# Don't warn about script install locations if
|
||||
# --target has been specified
|
||||
warn_script_location = options.warn_script_location
|
||||
if options.target_dir:
|
||||
warn_script_location = False
|
||||
|
||||
installed = install_given_reqs(
|
||||
to_install,
|
||||
install_options,
|
||||
global_options,
|
||||
root=options.root_path,
|
||||
home=target_temp_dir_path,
|
||||
prefix=options.prefix_path,
|
||||
pycompile=options.compile,
|
||||
warn_script_location=warn_script_location,
|
||||
use_user_site=options.use_user_site,
|
||||
)
|
||||
|
||||
lib_locations = get_lib_location_guesses(
|
||||
user=options.use_user_site,
|
||||
home=target_temp_dir_path,
|
||||
root=options.root_path,
|
||||
prefix=options.prefix_path,
|
||||
isolated=options.isolated_mode,
|
||||
)
|
||||
working_set = pkg_resources.WorkingSet(lib_locations)
|
||||
|
||||
reqs = sorted(installed, key=operator.attrgetter('name'))
|
||||
items = []
|
||||
for req in reqs:
|
||||
item = req.name
|
||||
try:
|
||||
import wheel # noqa: F401
|
||||
build_legacy = bool(options.cache_dir)
|
||||
except ImportError:
|
||||
build_legacy = False
|
||||
|
||||
wb = WheelBuilder(
|
||||
finder, preparer, wheel_cache,
|
||||
build_options=[], global_options=[],
|
||||
)
|
||||
|
||||
# Always build PEP 517 requirements
|
||||
build_failures = wb.build(
|
||||
pep517_requirements,
|
||||
session=session, autobuilding=True
|
||||
)
|
||||
|
||||
if build_legacy:
|
||||
# We don't care about failures building legacy
|
||||
# requirements, as we'll fall through to a direct
|
||||
# install for those.
|
||||
wb.build(
|
||||
legacy_requirements,
|
||||
session=session, autobuilding=True
|
||||
installed_version = get_installed_version(
|
||||
req.name, working_set=working_set
|
||||
)
|
||||
|
||||
# If we're using PEP 517, we cannot do a direct install
|
||||
# so we fail here.
|
||||
if build_failures:
|
||||
raise InstallationError(
|
||||
"Could not build wheels for {} which use"
|
||||
" PEP 517 and cannot be installed directly".format(
|
||||
", ".join(r.name for r in build_failures)))
|
||||
|
||||
to_install = resolver.get_installation_order(
|
||||
requirement_set
|
||||
if installed_version:
|
||||
item += '-' + installed_version
|
||||
except Exception:
|
||||
pass
|
||||
items.append(item)
|
||||
installed_desc = ' '.join(items)
|
||||
if installed_desc:
|
||||
write_output(
|
||||
'Successfully installed %s', installed_desc,
|
||||
)
|
||||
except EnvironmentError as error:
|
||||
show_traceback = (self.verbosity >= 1)
|
||||
|
||||
# Consistency Checking of the package set we're installing.
|
||||
should_warn_about_conflicts = (
|
||||
not options.ignore_dependencies and
|
||||
options.warn_about_conflicts
|
||||
)
|
||||
if should_warn_about_conflicts:
|
||||
self._warn_about_conflicts(to_install)
|
||||
message = create_env_error_message(
|
||||
error, show_traceback, options.use_user_site,
|
||||
)
|
||||
logger.error(message, exc_info=show_traceback)
|
||||
|
||||
# Don't warn about script install locations if
|
||||
# --target has been specified
|
||||
warn_script_location = options.warn_script_location
|
||||
if options.target_dir:
|
||||
warn_script_location = False
|
||||
|
||||
installed = install_given_reqs(
|
||||
to_install,
|
||||
install_options,
|
||||
global_options,
|
||||
root=options.root_path,
|
||||
home=target_temp_dir.path,
|
||||
prefix=options.prefix_path,
|
||||
pycompile=options.compile,
|
||||
warn_script_location=warn_script_location,
|
||||
use_user_site=options.use_user_site,
|
||||
)
|
||||
|
||||
lib_locations = get_lib_location_guesses(
|
||||
user=options.use_user_site,
|
||||
home=target_temp_dir.path,
|
||||
root=options.root_path,
|
||||
prefix=options.prefix_path,
|
||||
isolated=options.isolated_mode,
|
||||
)
|
||||
working_set = pkg_resources.WorkingSet(lib_locations)
|
||||
|
||||
reqs = sorted(installed, key=operator.attrgetter('name'))
|
||||
items = []
|
||||
for req in reqs:
|
||||
item = req.name
|
||||
try:
|
||||
installed_version = get_installed_version(
|
||||
req.name, working_set=working_set
|
||||
)
|
||||
if installed_version:
|
||||
item += '-' + installed_version
|
||||
except Exception:
|
||||
pass
|
||||
items.append(item)
|
||||
installed = ' '.join(items)
|
||||
if installed:
|
||||
logger.info('Successfully installed %s', installed)
|
||||
except EnvironmentError as error:
|
||||
show_traceback = (self.verbosity >= 1)
|
||||
|
||||
message = create_env_error_message(
|
||||
error, show_traceback, options.use_user_site,
|
||||
)
|
||||
logger.error(message, exc_info=show_traceback)
|
||||
|
||||
return ERROR
|
||||
except PreviousBuildDirError:
|
||||
options.no_clean = True
|
||||
raise
|
||||
finally:
|
||||
# Clean up
|
||||
if not options.no_clean:
|
||||
requirement_set.cleanup_files()
|
||||
wheel_cache.cleanup()
|
||||
return ERROR
|
||||
except PreviousBuildDirError:
|
||||
options.no_clean = True
|
||||
raise
|
||||
finally:
|
||||
# Clean up
|
||||
if not options.no_clean:
|
||||
requirement_set.cleanup_files()
|
||||
wheel_cache.cleanup()
|
||||
|
||||
if options.target_dir:
|
||||
self._handle_target_dir(
|
||||
options.target_dir, target_temp_dir, options.upgrade
|
||||
)
|
||||
return requirement_set
|
||||
|
||||
return SUCCESS
|
||||
|
||||
def _handle_target_dir(self, target_dir, target_temp_dir, upgrade):
|
||||
ensure_dir(target_dir)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import json
|
||||
@@ -7,27 +10,30 @@ from pipenv.patched.notpip._vendor import six
|
||||
from pipenv.patched.notpip._vendor.six.moves import zip_longest
|
||||
|
||||
from pipenv.patched.notpip._internal.cli import cmdoptions
|
||||
from pipenv.patched.notpip._internal.cli.base_command import Command
|
||||
from pipenv.patched.notpip._internal.cli.req_command import IndexGroupCommand
|
||||
from pipenv.patched.notpip._internal.exceptions import CommandError
|
||||
from pipenv.patched.notpip._internal.index import PackageFinder
|
||||
from pipenv.patched.notpip._internal.models.selection_prefs import SelectionPreferences
|
||||
from pipenv.patched.notpip._internal.self_outdated_check import make_link_collector
|
||||
from pipenv.patched.notpip._internal.utils.misc import (
|
||||
dist_is_editable, get_installed_distributions,
|
||||
dist_is_editable,
|
||||
get_installed_distributions,
|
||||
write_output,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.packaging import get_installer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ListCommand(Command):
|
||||
class ListCommand(IndexGroupCommand):
|
||||
"""
|
||||
List installed packages, including editables.
|
||||
|
||||
Packages are listed in a case-insensitive sorted order.
|
||||
"""
|
||||
name = 'list'
|
||||
|
||||
usage = """
|
||||
%prog [options]"""
|
||||
summary = 'List installed packages.'
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(ListCommand, self).__init__(*args, **kw)
|
||||
@@ -62,7 +68,7 @@ class ListCommand(Command):
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Only output packages installed in user-site.')
|
||||
|
||||
cmd_opts.add_option(cmdoptions.list_path())
|
||||
cmd_opts.add_option(
|
||||
'--pre',
|
||||
action='store_true',
|
||||
@@ -109,16 +115,21 @@ class ListCommand(Command):
|
||||
self.parser.insert_option_group(0, index_opts)
|
||||
self.parser.insert_option_group(0, cmd_opts)
|
||||
|
||||
def _build_package_finder(self, options, index_urls, session):
|
||||
def _build_package_finder(self, options, session):
|
||||
"""
|
||||
Create a package finder appropriate to this list command.
|
||||
"""
|
||||
return PackageFinder(
|
||||
find_links=options.find_links,
|
||||
index_urls=index_urls,
|
||||
link_collector = make_link_collector(session, options=options)
|
||||
|
||||
# Pass allow_yanked=False to ignore yanked versions.
|
||||
selection_prefs = SelectionPreferences(
|
||||
allow_yanked=False,
|
||||
allow_all_prereleases=options.pre,
|
||||
trusted_hosts=options.trusted_hosts,
|
||||
session=session,
|
||||
)
|
||||
|
||||
return PackageFinder.create(
|
||||
link_collector=link_collector,
|
||||
selection_prefs=selection_prefs,
|
||||
)
|
||||
|
||||
def run(self, options, args):
|
||||
@@ -126,11 +137,14 @@ class ListCommand(Command):
|
||||
raise CommandError(
|
||||
"Options --outdated and --uptodate cannot be combined.")
|
||||
|
||||
cmdoptions.check_list_path_option(options)
|
||||
|
||||
packages = get_installed_distributions(
|
||||
local_only=options.local,
|
||||
user_only=options.user,
|
||||
editables_only=options.editable,
|
||||
include_editables=options.include_editable,
|
||||
paths=options.path,
|
||||
)
|
||||
|
||||
# get_not_required must be called firstly in order to find and
|
||||
@@ -166,13 +180,8 @@ class ListCommand(Command):
|
||||
return {pkg for pkg in packages if pkg.key not in dep_keys}
|
||||
|
||||
def iter_packages_latest_infos(self, packages, options):
|
||||
index_urls = [options.index_url] + options.extra_index_urls
|
||||
if options.no_index:
|
||||
logger.debug('Ignoring indexes: %s', ','.join(index_urls))
|
||||
index_urls = []
|
||||
|
||||
with self._build_session(options) as session:
|
||||
finder = self._build_package_finder(options, index_urls, session)
|
||||
finder = self._build_package_finder(options, session)
|
||||
|
||||
for dist in packages:
|
||||
typ = 'unknown'
|
||||
@@ -182,12 +191,15 @@ class ListCommand(Command):
|
||||
all_candidates = [candidate for candidate in all_candidates
|
||||
if not candidate.version.is_prerelease]
|
||||
|
||||
if not all_candidates:
|
||||
evaluator = finder.make_candidate_evaluator(
|
||||
project_name=dist.project_name,
|
||||
)
|
||||
best_candidate = evaluator.sort_best_candidate(all_candidates)
|
||||
if best_candidate is None:
|
||||
continue
|
||||
best_candidate = max(all_candidates,
|
||||
key=finder._candidate_sort_key)
|
||||
|
||||
remote_version = best_candidate.version
|
||||
if best_candidate.location.is_wheel:
|
||||
if best_candidate.link.is_wheel:
|
||||
typ = 'wheel'
|
||||
else:
|
||||
typ = 'sdist'
|
||||
@@ -207,12 +219,12 @@ class ListCommand(Command):
|
||||
elif options.list_format == 'freeze':
|
||||
for dist in packages:
|
||||
if options.verbose >= 1:
|
||||
logger.info("%s==%s (%s)", dist.project_name,
|
||||
dist.version, dist.location)
|
||||
write_output("%s==%s (%s)", dist.project_name,
|
||||
dist.version, dist.location)
|
||||
else:
|
||||
logger.info("%s==%s", dist.project_name, dist.version)
|
||||
write_output("%s==%s", dist.project_name, dist.version)
|
||||
elif options.list_format == 'json':
|
||||
logger.info(format_for_json(packages, options))
|
||||
write_output(format_for_json(packages, options))
|
||||
|
||||
def output_package_listing_columns(self, data, header):
|
||||
# insert the header first: we need to know the size of column names
|
||||
@@ -226,7 +238,7 @@ class ListCommand(Command):
|
||||
pkg_strings.insert(1, " ".join(map(lambda x: '-' * x, sizes)))
|
||||
|
||||
for val in pkg_strings:
|
||||
logger.info(val)
|
||||
write_output(val)
|
||||
|
||||
|
||||
def tabulate(vals):
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
@@ -12,22 +15,23 @@ from pipenv.patched.notpip._vendor.packaging.version import parse as parse_versi
|
||||
from pipenv.patched.notpip._vendor.six.moves import xmlrpc_client # type: ignore
|
||||
|
||||
from pipenv.patched.notpip._internal.cli.base_command import Command
|
||||
from pipenv.patched.notpip._internal.cli.req_command import SessionCommandMixin
|
||||
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.network.xmlrpc import PipXmlrpcTransport
|
||||
from pipenv.patched.notpip._internal.utils.compat import get_terminal_size
|
||||
from pipenv.patched.notpip._internal.utils.logging import indent_log
|
||||
from pipenv.patched.notpip._internal.utils.misc import write_output
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SearchCommand(Command):
|
||||
class SearchCommand(Command, SessionCommandMixin):
|
||||
"""Search for PyPI packages whose name or summary contains <query>."""
|
||||
name = 'search'
|
||||
|
||||
usage = """
|
||||
%prog [options] <query>"""
|
||||
summary = 'Search PyPI for packages.'
|
||||
ignore_require_venv = True
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
@@ -59,11 +63,13 @@ class SearchCommand(Command):
|
||||
|
||||
def search(self, query, options):
|
||||
index_url = options.index
|
||||
with self._build_session(options) as session:
|
||||
transport = PipXmlrpcTransport(index_url, session)
|
||||
pypi = xmlrpc_client.ServerProxy(index_url, transport)
|
||||
hits = pypi.search({'name': query, 'summary': query}, 'or')
|
||||
return hits
|
||||
|
||||
session = self.get_default_session(options)
|
||||
|
||||
transport = PipXmlrpcTransport(index_url, session)
|
||||
pypi = xmlrpc_client.ServerProxy(index_url, transport)
|
||||
hits = pypi.search({'name': query, 'summary': query}, 'or')
|
||||
return hits
|
||||
|
||||
|
||||
def transform_hits(hits):
|
||||
@@ -118,15 +124,19 @@ def print_results(hits, name_column_width=None, terminal_width=None):
|
||||
line = '%-*s - %s' % (name_column_width,
|
||||
'%s (%s)' % (name, latest), summary)
|
||||
try:
|
||||
logger.info(line)
|
||||
write_output(line)
|
||||
if name in installed_packages:
|
||||
dist = pkg_resources.get_distribution(name)
|
||||
with indent_log():
|
||||
if dist.version == latest:
|
||||
logger.info('INSTALLED: %s (latest)', dist.version)
|
||||
write_output('INSTALLED: %s (latest)', dist.version)
|
||||
else:
|
||||
logger.info('INSTALLED: %s', dist.version)
|
||||
logger.info('LATEST: %s', latest)
|
||||
write_output('INSTALLED: %s', dist.version)
|
||||
if parse_version(latest).pre:
|
||||
write_output('LATEST: %s (pre-release; install'
|
||||
' with "pip install --pre")', latest)
|
||||
else:
|
||||
write_output('LATEST: %s', latest)
|
||||
except UnicodeEncodeError:
|
||||
pass
|
||||
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import os
|
||||
from email.parser import FeedParser # type: ignore
|
||||
from email.parser import FeedParser
|
||||
|
||||
from pipenv.patched.notpip._vendor import pkg_resources
|
||||
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
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.utils.misc import write_output
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -19,10 +23,9 @@ class ShowCommand(Command):
|
||||
|
||||
The output is in RFC-compliant mail header format.
|
||||
"""
|
||||
name = 'show'
|
||||
|
||||
usage = """
|
||||
%prog [options] <package> ..."""
|
||||
summary = 'Show information about installed packages.'
|
||||
ignore_require_venv = True
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
@@ -61,6 +64,20 @@ def search_packages_info(query):
|
||||
installed[canonicalize_name(p.project_name)] = p
|
||||
|
||||
query_names = [canonicalize_name(name) for name in query]
|
||||
missing = sorted(
|
||||
[name for name, pkg in zip(query, query_names) if pkg not in installed]
|
||||
)
|
||||
if missing:
|
||||
logger.warning('Package(s) not found: %s', ', '.join(missing))
|
||||
|
||||
def get_requiring_packages(package_name):
|
||||
canonical_name = canonicalize_name(package_name)
|
||||
return [
|
||||
pkg.project_name for pkg in pkg_resources.working_set
|
||||
if canonical_name in
|
||||
[canonicalize_name(required.name) for required in
|
||||
pkg.requires()]
|
||||
]
|
||||
|
||||
for dist in [installed[pkg] for pkg in query_names if pkg in installed]:
|
||||
package = {
|
||||
@@ -68,6 +85,7 @@ def search_packages_info(query):
|
||||
'version': dist.version,
|
||||
'location': dist.location,
|
||||
'requires': [dep.project_name for dep in dist.requires()],
|
||||
'required_by': get_requiring_packages(dist.project_name)
|
||||
}
|
||||
file_list = None
|
||||
metadata = None
|
||||
@@ -130,39 +148,33 @@ def print_results(distributions, list_files=False, verbose=False):
|
||||
for i, dist in enumerate(distributions):
|
||||
results_printed = True
|
||||
if i > 0:
|
||||
logger.info("---")
|
||||
write_output("---")
|
||||
|
||||
name = dist.get('name', '')
|
||||
required_by = [
|
||||
pkg.project_name for pkg in pkg_resources.working_set
|
||||
if name in [required.name for required in pkg.requires()]
|
||||
]
|
||||
|
||||
logger.info("Name: %s", name)
|
||||
logger.info("Version: %s", dist.get('version', ''))
|
||||
logger.info("Summary: %s", dist.get('summary', ''))
|
||||
logger.info("Home-page: %s", dist.get('home-page', ''))
|
||||
logger.info("Author: %s", dist.get('author', ''))
|
||||
logger.info("Author-email: %s", dist.get('author-email', ''))
|
||||
logger.info("License: %s", dist.get('license', ''))
|
||||
logger.info("Location: %s", dist.get('location', ''))
|
||||
logger.info("Requires: %s", ', '.join(dist.get('requires', [])))
|
||||
logger.info("Required-by: %s", ', '.join(required_by))
|
||||
write_output("Name: %s", dist.get('name', ''))
|
||||
write_output("Version: %s", dist.get('version', ''))
|
||||
write_output("Summary: %s", dist.get('summary', ''))
|
||||
write_output("Home-page: %s", dist.get('home-page', ''))
|
||||
write_output("Author: %s", dist.get('author', ''))
|
||||
write_output("Author-email: %s", dist.get('author-email', ''))
|
||||
write_output("License: %s", dist.get('license', ''))
|
||||
write_output("Location: %s", dist.get('location', ''))
|
||||
write_output("Requires: %s", ', '.join(dist.get('requires', [])))
|
||||
write_output("Required-by: %s", ', '.join(dist.get('required_by', [])))
|
||||
|
||||
if verbose:
|
||||
logger.info("Metadata-Version: %s",
|
||||
dist.get('metadata-version', ''))
|
||||
logger.info("Installer: %s", dist.get('installer', ''))
|
||||
logger.info("Classifiers:")
|
||||
write_output("Metadata-Version: %s",
|
||||
dist.get('metadata-version', ''))
|
||||
write_output("Installer: %s", dist.get('installer', ''))
|
||||
write_output("Classifiers:")
|
||||
for classifier in dist.get('classifiers', []):
|
||||
logger.info(" %s", classifier)
|
||||
logger.info("Entry-points:")
|
||||
write_output(" %s", classifier)
|
||||
write_output("Entry-points:")
|
||||
for entry in dist.get('entry_points', []):
|
||||
logger.info(" %s", entry.strip())
|
||||
write_output(" %s", entry.strip())
|
||||
if list_files:
|
||||
logger.info("Files:")
|
||||
write_output("Files:")
|
||||
for line in dist.get('files', []):
|
||||
logger.info(" %s", line.strip())
|
||||
write_output(" %s", line.strip())
|
||||
if "files" not in dist:
|
||||
logger.info("Cannot locate installed-files.txt")
|
||||
write_output("Cannot locate installed-files.txt")
|
||||
return results_printed
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
from pipenv.patched.notpip._internal.cli.base_command import Command
|
||||
from pipenv.patched.notpip._internal.cli.req_command import SessionCommandMixin
|
||||
from pipenv.patched.notpip._internal.exceptions import InstallationError
|
||||
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
|
||||
|
||||
|
||||
class UninstallCommand(Command):
|
||||
class UninstallCommand(Command, SessionCommandMixin):
|
||||
"""
|
||||
Uninstall packages.
|
||||
|
||||
@@ -19,11 +23,10 @@ class UninstallCommand(Command):
|
||||
leave behind no metadata to determine what files were installed.
|
||||
- Script wrappers installed by ``python setup.py develop``.
|
||||
"""
|
||||
name = 'uninstall'
|
||||
|
||||
usage = """
|
||||
%prog [options] <package> ...
|
||||
%prog [options] -r <requirements file> ..."""
|
||||
summary = 'Uninstall packages.'
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(UninstallCommand, self).__init__(*args, **kw)
|
||||
@@ -45,34 +48,35 @@ class UninstallCommand(Command):
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def run(self, options, args):
|
||||
with self._build_session(options) as session:
|
||||
reqs_to_uninstall = {}
|
||||
for name in args:
|
||||
req = install_req_from_line(
|
||||
name, isolated=options.isolated_mode,
|
||||
)
|
||||
session = self.get_default_session(options)
|
||||
|
||||
reqs_to_uninstall = {}
|
||||
for name in args:
|
||||
req = install_req_from_line(
|
||||
name, isolated=options.isolated_mode,
|
||||
)
|
||||
if req.name:
|
||||
reqs_to_uninstall[canonicalize_name(req.name)] = req
|
||||
for filename in options.requirements:
|
||||
for req in parse_requirements(
|
||||
filename,
|
||||
options=options,
|
||||
session=session):
|
||||
if req.name:
|
||||
reqs_to_uninstall[canonicalize_name(req.name)] = req
|
||||
for filename in options.requirements:
|
||||
for req in parse_requirements(
|
||||
filename,
|
||||
options=options,
|
||||
session=session):
|
||||
if req.name:
|
||||
reqs_to_uninstall[canonicalize_name(req.name)] = req
|
||||
if not reqs_to_uninstall:
|
||||
raise InstallationError(
|
||||
'You must give at least one requirement to %(name)s (see '
|
||||
'"pip help %(name)s")' % dict(name=self.name)
|
||||
)
|
||||
|
||||
protect_pip_from_modification_on_windows(
|
||||
modifying_pip="pip" in reqs_to_uninstall
|
||||
if not reqs_to_uninstall:
|
||||
raise InstallationError(
|
||||
'You must give at least one requirement to %(name)s (see '
|
||||
'"pip help %(name)s")' % dict(name=self.name)
|
||||
)
|
||||
|
||||
for req in reqs_to_uninstall.values():
|
||||
uninstall_pathset = req.uninstall(
|
||||
auto_confirm=options.yes, verbose=self.verbosity > 0,
|
||||
)
|
||||
if uninstall_pathset:
|
||||
uninstall_pathset.commit()
|
||||
protect_pip_from_modification_on_windows(
|
||||
modifying_pip="pip" in reqs_to_uninstall
|
||||
)
|
||||
|
||||
for req in reqs_to_uninstall.values():
|
||||
uninstall_pathset = req.uninstall(
|
||||
auto_confirm=options.yes, verbose=self.verbosity > 0,
|
||||
)
|
||||
if uninstall_pathset:
|
||||
uninstall_pathset.commit()
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
@@ -6,15 +10,19 @@ import os
|
||||
|
||||
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.req_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
|
||||
from pipenv.patched.notpip._internal.req.req_tracker import RequirementTracker
|
||||
from pipenv.patched.notpip._internal.resolve import Resolver
|
||||
from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
from pipenv.patched.notpip._internal.wheel import WheelBuilder
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from optparse import Values
|
||||
from typing import Any, List
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -33,7 +41,6 @@ class WheelCommand(RequirementCommand):
|
||||
|
||||
"""
|
||||
|
||||
name = 'wheel'
|
||||
usage = """
|
||||
%prog [options] <requirement specifier> ...
|
||||
%prog [options] -r <requirements file> ...
|
||||
@@ -41,8 +48,6 @@ class WheelCommand(RequirementCommand):
|
||||
%prog [options] [-e] <local project path> ...
|
||||
%prog [options] <archive url/path> ..."""
|
||||
|
||||
summary = 'Build wheels from your requirements.'
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(WheelCommand, self).__init__(*args, **kw)
|
||||
|
||||
@@ -106,81 +111,70 @@ class WheelCommand(RequirementCommand):
|
||||
self.parser.insert_option_group(0, cmd_opts)
|
||||
|
||||
def run(self, options, args):
|
||||
# type: (Values, List[Any]) -> None
|
||||
cmdoptions.check_install_build_global(options)
|
||||
|
||||
index_urls = [options.index_url] + options.extra_index_urls
|
||||
if options.no_index:
|
||||
logger.debug('Ignoring indexes: %s', ','.join(index_urls))
|
||||
index_urls = []
|
||||
|
||||
if options.build_dir:
|
||||
options.build_dir = os.path.abspath(options.build_dir)
|
||||
|
||||
options.src_dir = os.path.abspath(options.src_dir)
|
||||
|
||||
with self._build_session(options) as session:
|
||||
finder = self._build_package_finder(options, session)
|
||||
build_delete = (not (options.no_clean or options.build_dir))
|
||||
wheel_cache = WheelCache(options.cache_dir, options.format_control)
|
||||
session = self.get_default_session(options)
|
||||
|
||||
with RequirementTracker() as req_tracker, TempDirectory(
|
||||
options.build_dir, delete=build_delete, kind="wheel"
|
||||
) as directory:
|
||||
finder = self._build_package_finder(options, session)
|
||||
build_delete = (not (options.no_clean or options.build_dir))
|
||||
wheel_cache = WheelCache(options.cache_dir, options.format_control)
|
||||
|
||||
requirement_set = RequirementSet(
|
||||
require_hashes=options.require_hashes,
|
||||
with RequirementTracker() as req_tracker, TempDirectory(
|
||||
options.build_dir, delete=build_delete, kind="wheel"
|
||||
) as directory:
|
||||
|
||||
requirement_set = RequirementSet(
|
||||
require_hashes=options.require_hashes,
|
||||
)
|
||||
|
||||
try:
|
||||
self.populate_requirement_set(
|
||||
requirement_set, args, options, finder, session,
|
||||
wheel_cache
|
||||
)
|
||||
|
||||
try:
|
||||
self.populate_requirement_set(
|
||||
requirement_set, args, options, finder, session,
|
||||
self.name, wheel_cache
|
||||
)
|
||||
preparer = self.make_requirement_preparer(
|
||||
temp_build_dir=directory,
|
||||
options=options,
|
||||
req_tracker=req_tracker,
|
||||
wheel_download_dir=options.wheel_dir,
|
||||
)
|
||||
|
||||
preparer = RequirementPreparer(
|
||||
build_dir=directory.path,
|
||||
src_dir=options.src_dir,
|
||||
download_dir=None,
|
||||
wheel_download_dir=options.wheel_dir,
|
||||
progress_bar=options.progress_bar,
|
||||
build_isolation=options.build_isolation,
|
||||
req_tracker=req_tracker,
|
||||
)
|
||||
resolver = self.make_resolver(
|
||||
preparer=preparer,
|
||||
finder=finder,
|
||||
session=session,
|
||||
options=options,
|
||||
wheel_cache=wheel_cache,
|
||||
ignore_requires_python=options.ignore_requires_python,
|
||||
use_pep517=options.use_pep517,
|
||||
)
|
||||
resolver.resolve(requirement_set)
|
||||
|
||||
resolver = Resolver(
|
||||
preparer=preparer,
|
||||
finder=finder,
|
||||
session=session,
|
||||
wheel_cache=wheel_cache,
|
||||
use_user_site=False,
|
||||
upgrade_strategy="to-satisfy-only",
|
||||
force_reinstall=False,
|
||||
ignore_dependencies=options.ignore_dependencies,
|
||||
ignore_requires_python=options.ignore_requires_python,
|
||||
ignore_installed=True,
|
||||
isolated=options.isolated_mode,
|
||||
use_pep517=options.use_pep517
|
||||
# build wheels
|
||||
wb = WheelBuilder(
|
||||
preparer, wheel_cache,
|
||||
build_options=options.build_options or [],
|
||||
global_options=options.global_options or [],
|
||||
no_clean=options.no_clean,
|
||||
)
|
||||
build_failures = wb.build(
|
||||
requirement_set.requirements.values(),
|
||||
)
|
||||
if len(build_failures) != 0:
|
||||
raise CommandError(
|
||||
"Failed to build one or more wheels"
|
||||
)
|
||||
resolver.resolve(requirement_set)
|
||||
|
||||
# build wheels
|
||||
wb = WheelBuilder(
|
||||
finder, preparer, wheel_cache,
|
||||
build_options=options.build_options or [],
|
||||
global_options=options.global_options or [],
|
||||
no_clean=options.no_clean,
|
||||
)
|
||||
build_failures = wb.build(
|
||||
requirement_set.requirements.values(), session=session,
|
||||
)
|
||||
if len(build_failures) != 0:
|
||||
raise CommandError(
|
||||
"Failed to build one or more wheels"
|
||||
)
|
||||
except PreviousBuildDirError:
|
||||
options.no_clean = True
|
||||
raise
|
||||
finally:
|
||||
if not options.no_clean:
|
||||
requirement_set.cleanup_files()
|
||||
wheel_cache.cleanup()
|
||||
except PreviousBuildDirError:
|
||||
options.no_clean = True
|
||||
raise
|
||||
finally:
|
||||
if not options.no_clean:
|
||||
requirement_set.cleanup_files()
|
||||
wheel_cache.cleanup()
|
||||
|
||||
@@ -11,25 +11,28 @@ Some terminology:
|
||||
A single word describing where the configuration key-value pair came from
|
||||
"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
import locale
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pipenv.patched.notpip._vendor import six
|
||||
from pipenv.patched.notpip._vendor.six.moves import configparser
|
||||
|
||||
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,
|
||||
ConfigurationError,
|
||||
ConfigurationFileCouldNotBeLoaded,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils import appdirs
|
||||
from pipenv.patched.notpip._internal.utils.compat import WINDOWS, expanduser
|
||||
from pipenv.patched.notpip._internal.utils.misc import ensure_dir, enum
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import ( # noqa: F401
|
||||
from typing import (
|
||||
Any, Dict, Iterable, List, NewType, Optional, Tuple
|
||||
)
|
||||
|
||||
@@ -52,6 +55,12 @@ def _normalize_name(name):
|
||||
|
||||
def _disassemble_key(name):
|
||||
# type: (str) -> List[str]
|
||||
if "." not in name:
|
||||
error_message = (
|
||||
"Key does not contain dot separated section and key. "
|
||||
"Perhaps you wanted to use 'global.{}' instead?"
|
||||
).format(name)
|
||||
raise ConfigurationError(error_message)
|
||||
return name.split(".", 1)
|
||||
|
||||
|
||||
@@ -59,12 +68,37 @@ def _disassemble_key(name):
|
||||
kinds = enum(
|
||||
USER="user", # User Specific
|
||||
GLOBAL="global", # System Wide
|
||||
VENV="venv", # Virtual Environment Specific
|
||||
SITE="site", # [Virtual] Environment Specific
|
||||
ENV="env", # from PIP_CONFIG_FILE
|
||||
ENV_VAR="env-var", # from Environment Variables
|
||||
)
|
||||
|
||||
|
||||
CONFIG_BASENAME = 'pip.ini' if WINDOWS else 'pip.conf'
|
||||
|
||||
|
||||
def get_configuration_files():
|
||||
global_config_files = [
|
||||
os.path.join(path, CONFIG_BASENAME)
|
||||
for path in appdirs.site_config_dirs('pip')
|
||||
]
|
||||
|
||||
site_config_file = os.path.join(sys.prefix, CONFIG_BASENAME)
|
||||
legacy_config_file = os.path.join(
|
||||
expanduser('~'),
|
||||
'pip' if WINDOWS else '.pip',
|
||||
CONFIG_BASENAME,
|
||||
)
|
||||
new_config_file = os.path.join(
|
||||
appdirs.user_config_dir("pip"), CONFIG_BASENAME
|
||||
)
|
||||
return {
|
||||
kinds.GLOBAL: global_config_files,
|
||||
kinds.SITE: [site_config_file],
|
||||
kinds.USER: [legacy_config_file, new_config_file],
|
||||
}
|
||||
|
||||
|
||||
class Configuration(object):
|
||||
"""Handles management of configuration.
|
||||
|
||||
@@ -83,7 +117,7 @@ class Configuration(object):
|
||||
# type: (bool, Kind) -> None
|
||||
super(Configuration, self).__init__()
|
||||
|
||||
_valid_load_only = [kinds.USER, kinds.GLOBAL, kinds.VENV, None]
|
||||
_valid_load_only = [kinds.USER, kinds.GLOBAL, kinds.SITE, None]
|
||||
if load_only not in _valid_load_only:
|
||||
raise ConfigurationError(
|
||||
"Got invalid value for load_only - should be one of {}".format(
|
||||
@@ -95,7 +129,7 @@ class Configuration(object):
|
||||
|
||||
# The order here determines the override order.
|
||||
self._override_order = [
|
||||
kinds.GLOBAL, kinds.USER, kinds.VENV, kinds.ENV, kinds.ENV_VAR
|
||||
kinds.GLOBAL, kinds.USER, kinds.SITE, kinds.ENV, kinds.ENV_VAR
|
||||
]
|
||||
|
||||
self._ignore_env_names = ["version", "help"]
|
||||
@@ -188,7 +222,7 @@ class Configuration(object):
|
||||
# name removed from parser, section may now be empty
|
||||
section_iter = iter(parser.items(section))
|
||||
try:
|
||||
val = six.next(section_iter)
|
||||
val = next(section_iter)
|
||||
except StopIteration:
|
||||
val = None
|
||||
|
||||
@@ -205,7 +239,7 @@ class Configuration(object):
|
||||
|
||||
def save(self):
|
||||
# type: () -> None
|
||||
"""Save the currentin-memory state.
|
||||
"""Save the current in-memory state.
|
||||
"""
|
||||
self._ensure_have_load_only()
|
||||
|
||||
@@ -216,7 +250,7 @@ class Configuration(object):
|
||||
ensure_dir(os.path.dirname(fname))
|
||||
|
||||
with open(fname, "w") as f:
|
||||
parser.write(f) # type: ignore
|
||||
parser.write(f)
|
||||
|
||||
#
|
||||
# Private routines
|
||||
@@ -351,8 +385,10 @@ class Configuration(object):
|
||||
else:
|
||||
yield kinds.ENV, []
|
||||
|
||||
config_files = get_configuration_files()
|
||||
|
||||
# at the base we have any global configuration
|
||||
yield kinds.GLOBAL, list(site_config_files)
|
||||
yield kinds.GLOBAL, config_files[kinds.GLOBAL]
|
||||
|
||||
# per-user configuration next
|
||||
should_load_user_config = not self.isolated and not (
|
||||
@@ -360,11 +396,10 @@ class Configuration(object):
|
||||
)
|
||||
if should_load_user_config:
|
||||
# The legacy config file is overridden by the new config file
|
||||
yield kinds.USER, [legacy_config_file, new_config_file]
|
||||
yield kinds.USER, config_files[kinds.USER]
|
||||
|
||||
# finally virtualenv configuration first trumping others
|
||||
if running_under_virtualenv():
|
||||
yield kinds.VENV, [venv_config_file]
|
||||
yield kinds.SITE, config_files[kinds.SITE]
|
||||
|
||||
def _get_parser_to_modify(self):
|
||||
# type: () -> Tuple[str, RawConfigParser]
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
from pipenv.patched.notpip._internal.distributions.source.legacy import SourceDistribution
|
||||
from pipenv.patched.notpip._internal.distributions.wheel import WheelDistribution
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from pipenv.patched.notpip._internal.distributions.base import AbstractDistribution
|
||||
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
|
||||
|
||||
|
||||
def make_distribution_for_install_requirement(install_req):
|
||||
# type: (InstallRequirement) -> AbstractDistribution
|
||||
"""Returns a Distribution for the given InstallRequirement
|
||||
"""
|
||||
# Editable requirements will always be source distributions. They use the
|
||||
# legacy logic until we create a modern standard for them.
|
||||
if install_req.editable:
|
||||
return SourceDistribution(install_req)
|
||||
|
||||
# If it's a wheel, it's a WheelDistribution
|
||||
if install_req.is_wheel:
|
||||
return WheelDistribution(install_req)
|
||||
|
||||
# Otherwise, a SourceDistribution
|
||||
return SourceDistribution(install_req)
|
||||
@@ -0,0 +1,36 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
import abc
|
||||
|
||||
from pipenv.patched.notpip._vendor.six import add_metaclass
|
||||
|
||||
|
||||
@add_metaclass(abc.ABCMeta)
|
||||
class AbstractDistribution(object):
|
||||
"""A base class for handling installable artifacts.
|
||||
|
||||
The requirements for anything installable are as follows:
|
||||
|
||||
- we must be able to determine the requirement name
|
||||
(or we can't correctly handle the non-upgrade case).
|
||||
|
||||
- for packages with setup requirements, we must also be able
|
||||
to determine their requirements without installing additional
|
||||
packages (for the same reason as run-time dependencies)
|
||||
|
||||
- we must be able to create a Distribution object exposing the
|
||||
above metadata.
|
||||
"""
|
||||
|
||||
def __init__(self, req):
|
||||
super(AbstractDistribution, self).__init__()
|
||||
self.req = req
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_pkg_resources_distribution(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def prepare_distribution_metadata(self, finder, build_isolation):
|
||||
raise NotImplementedError()
|
||||
@@ -0,0 +1,18 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from pipenv.patched.notpip._internal.distributions.base import AbstractDistribution
|
||||
|
||||
|
||||
class InstalledDistribution(AbstractDistribution):
|
||||
"""Represents an installed package.
|
||||
|
||||
This does not need any preparation as the required information has already
|
||||
been computed.
|
||||
"""
|
||||
|
||||
def get_pkg_resources_distribution(self):
|
||||
return self.req.satisfied_by
|
||||
|
||||
def prepare_distribution_metadata(self, finder, build_isolation):
|
||||
pass
|
||||
@@ -0,0 +1,98 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
import logging
|
||||
|
||||
from pipenv.patched.notpip._internal.build_env import BuildEnvironment
|
||||
from pipenv.patched.notpip._internal.distributions.base import AbstractDistribution
|
||||
from pipenv.patched.notpip._internal.exceptions import InstallationError
|
||||
from pipenv.patched.notpip._internal.utils.subprocess import runner_with_spinner_message
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SourceDistribution(AbstractDistribution):
|
||||
"""Represents a source distribution.
|
||||
|
||||
The preparation step for these needs metadata for the packages to be
|
||||
generated, either using PEP 517 or using the legacy `setup.py egg_info`.
|
||||
|
||||
NOTE from @pradyunsg (14 June 2019)
|
||||
I expect SourceDistribution class will need to be split into
|
||||
`legacy_source` (setup.py based) and `source` (PEP 517 based) when we start
|
||||
bringing logic for preparation out of InstallRequirement into this class.
|
||||
"""
|
||||
|
||||
def get_pkg_resources_distribution(self):
|
||||
return self.req.get_dist()
|
||||
|
||||
def prepare_distribution_metadata(self, finder, build_isolation):
|
||||
# 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:
|
||||
self._setup_isolation(finder)
|
||||
|
||||
self.req.prepare_metadata()
|
||||
self.req.assert_source_matches_version()
|
||||
|
||||
def _setup_isolation(self, finder):
|
||||
def _raise_conflicts(conflicting_with, conflicting_reqs):
|
||||
format_string = (
|
||||
"Some build dependencies for {requirement} "
|
||||
"conflict with {conflicting_with}: {description}."
|
||||
)
|
||||
error_message = format_string.format(
|
||||
requirement=self.req,
|
||||
conflicting_with=conflicting_with,
|
||||
description=', '.join(
|
||||
'%s is incompatible with %s' % (installed, wanted)
|
||||
for installed, wanted in sorted(conflicting)
|
||||
)
|
||||
)
|
||||
raise InstallationError(error_message)
|
||||
|
||||
# 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, 'overlay',
|
||||
"Installing build dependencies"
|
||||
)
|
||||
conflicting, missing = self.req.build_env.check_requirements(
|
||||
self.req.requirements_to_check
|
||||
)
|
||||
if conflicting:
|
||||
_raise_conflicts("PEP 517/518 supported requirements",
|
||||
conflicting)
|
||||
if missing:
|
||||
logger.warning(
|
||||
"Missing build requirements in pyproject.toml for %s.",
|
||||
self.req,
|
||||
)
|
||||
logger.warning(
|
||||
"The project does not specify a build backend, and "
|
||||
"pip cannot fall back to setuptools without %s.",
|
||||
" and ".join(map(repr, sorted(missing)))
|
||||
)
|
||||
# Install any extra build dependencies that the backend requests.
|
||||
# This must be done in a second pass, as the pyproject.toml
|
||||
# dependencies must be installed before we can call the backend.
|
||||
with self.req.build_env:
|
||||
runner = runner_with_spinner_message(
|
||||
"Getting requirements to build wheel"
|
||||
)
|
||||
backend = self.req.pep517_backend
|
||||
with backend.subprocess_runner(runner):
|
||||
reqs = backend.get_requires_for_build_wheel()
|
||||
|
||||
conflicting, missing = self.req.build_env.check_requirements(reqs)
|
||||
if conflicting:
|
||||
_raise_conflicts("the backend dependencies", conflicting)
|
||||
self.req.build_env.install_requirements(
|
||||
finder, missing, 'normal',
|
||||
"Installing backend dependencies"
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from pipenv.patched.notpip._vendor import pkg_resources
|
||||
|
||||
from pipenv.patched.notpip._internal.distributions.base import AbstractDistribution
|
||||
|
||||
|
||||
class WheelDistribution(AbstractDistribution):
|
||||
"""Represents a wheel distribution.
|
||||
|
||||
This does not need any preparation as wheels can be directly unpacked.
|
||||
"""
|
||||
|
||||
def get_pkg_resources_distribution(self):
|
||||
return list(pkg_resources.find_distributions(
|
||||
self.req.source_dir))[0]
|
||||
|
||||
def prepare_distribution_metadata(self, finder, build_isolation):
|
||||
pass
|
||||
@@ -1,408 +1,87 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import cgi
|
||||
import email.utils
|
||||
import getpass
|
||||
import json
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
from pipenv.patched.notpip._vendor import requests, six, urllib3
|
||||
from pipenv.patched.notpip._vendor.cachecontrol import CacheControlAdapter
|
||||
from pipenv.patched.notpip._vendor.cachecontrol.caches import FileCache
|
||||
from pipenv.patched.notpip._vendor.lockfile import LockError
|
||||
from pipenv.patched.notpip._vendor.requests.adapters import BaseAdapter, HTTPAdapter
|
||||
from pipenv.patched.notpip._vendor.requests.auth import AuthBase, HTTPBasicAuth
|
||||
from pipenv.patched.notpip._vendor import requests
|
||||
from pipenv.patched.notpip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
|
||||
from pipenv.patched.notpip._vendor.requests.structures import CaseInsensitiveDict
|
||||
from pipenv.patched.notpip._vendor.requests.utils import get_netrc_auth
|
||||
# NOTE: XMLRPC Client is not annotated in typeshed as on 2017-07-17, which is
|
||||
# why we ignore the type on this import
|
||||
from pipenv.patched.notpip._vendor.six.moves import xmlrpc_client # type: ignore
|
||||
from pipenv.patched.notpip._vendor.six import PY2
|
||||
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._vendor.urllib3.util import IS_PYOPENSSL
|
||||
|
||||
import pipenv.patched.notpip
|
||||
from pipenv.patched.notpip._internal.exceptions import HashMismatch, InstallationError
|
||||
from pipenv.patched.notpip._internal.locations import write_delete_marker_file
|
||||
from pipenv.patched.notpip._internal.models.index import PyPI
|
||||
from pipenv.patched.notpip._internal.network.session import PipSession
|
||||
from pipenv.patched.notpip._internal.utils.encoding import auto_decode
|
||||
from pipenv.patched.notpip._internal.utils.filesystem import check_path_owner
|
||||
from pipenv.patched.notpip._internal.utils.glibc import libc_ver
|
||||
from pipenv.patched.notpip._internal.utils.logging import indent_log
|
||||
from pipenv.patched.notpip._internal.utils.filesystem import copy2_fixed
|
||||
from pipenv.patched.notpip._internal.utils.misc import (
|
||||
ARCHIVE_EXTENSIONS, ask_path_exists, backup_dir, call_subprocess, consume,
|
||||
display_path, format_size, get_installed_version, rmtree,
|
||||
split_auth_from_netloc, splitext, unpack_file,
|
||||
ask_path_exists,
|
||||
backup_dir,
|
||||
consume,
|
||||
display_path,
|
||||
format_size,
|
||||
hide_url,
|
||||
path_to_display,
|
||||
rmtree,
|
||||
splitext,
|
||||
)
|
||||
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.typing import MYPY_CHECK_RUNNING
|
||||
from pipenv.patched.notpip._internal.utils.ui import DownloadProgressProvider
|
||||
from pipenv.patched.notpip._internal.utils.unpacking import unpack_file
|
||||
from pipenv.patched.notpip._internal.utils.urls import get_url_scheme
|
||||
from pipenv.patched.notpip._internal.vcs import vcs
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import ( # noqa: F401
|
||||
Optional, Tuple, Dict, IO, Text, Union
|
||||
from typing import (
|
||||
IO, Callable, List, Optional, Text, Tuple,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.models.link import Link # noqa: F401
|
||||
from pipenv.patched.notpip._internal.utils.hashes import Hashes # noqa: F401
|
||||
from pipenv.patched.notpip._internal.vcs import AuthInfo # noqa: F401
|
||||
|
||||
try:
|
||||
import ssl # noqa
|
||||
except ImportError:
|
||||
ssl = None
|
||||
from mypy_extensions import TypedDict
|
||||
|
||||
from pipenv.patched.notpip._internal.models.link import Link
|
||||
from pipenv.patched.notpip._internal.utils.hashes import Hashes
|
||||
from pipenv.patched.notpip._internal.vcs.versioncontrol import VersionControl
|
||||
|
||||
if PY2:
|
||||
CopytreeKwargs = TypedDict(
|
||||
'CopytreeKwargs',
|
||||
{
|
||||
'ignore': Callable[[str, List[str]], List[str]],
|
||||
'symlinks': bool,
|
||||
},
|
||||
total=False,
|
||||
)
|
||||
else:
|
||||
CopytreeKwargs = TypedDict(
|
||||
'CopytreeKwargs',
|
||||
{
|
||||
'copy_function': Callable[[str, str], None],
|
||||
'ignore': Callable[[str, List[str]], List[str]],
|
||||
'ignore_dangling_symlinks': bool,
|
||||
'symlinks': bool,
|
||||
},
|
||||
total=False,
|
||||
)
|
||||
|
||||
HAS_TLS = (ssl is not None) or IS_PYOPENSSL
|
||||
|
||||
__all__ = ['get_file_content',
|
||||
'is_url', 'url_to_path', 'path_to_url',
|
||||
'is_archive_file', 'unpack_vcs_link',
|
||||
'unpack_file_url', 'is_vcs_url', 'is_file_url',
|
||||
'unpack_http_url', 'unpack_url']
|
||||
'unpack_vcs_link',
|
||||
'unpack_file_url',
|
||||
'unpack_http_url', 'unpack_url',
|
||||
'parse_content_disposition', 'sanitize_content_filename']
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def user_agent():
|
||||
"""
|
||||
Return a string representing the user agent.
|
||||
"""
|
||||
data = {
|
||||
"installer": {"name": "pip", "version": pipenv.patched.notpip.__version__},
|
||||
"python": platform.python_version(),
|
||||
"implementation": {
|
||||
"name": platform.python_implementation(),
|
||||
},
|
||||
}
|
||||
|
||||
if data["implementation"]["name"] == 'CPython':
|
||||
data["implementation"]["version"] = platform.python_version()
|
||||
elif data["implementation"]["name"] == 'PyPy':
|
||||
if sys.pypy_version_info.releaselevel == 'final':
|
||||
pypy_version_info = sys.pypy_version_info[:3]
|
||||
else:
|
||||
pypy_version_info = sys.pypy_version_info
|
||||
data["implementation"]["version"] = ".".join(
|
||||
[str(x) for x in pypy_version_info]
|
||||
)
|
||||
elif data["implementation"]["name"] == 'Jython':
|
||||
# Complete Guess
|
||||
data["implementation"]["version"] = platform.python_version()
|
||||
elif data["implementation"]["name"] == 'IronPython':
|
||||
# Complete Guess
|
||||
data["implementation"]["version"] = platform.python_version()
|
||||
|
||||
if sys.platform.startswith("linux"):
|
||||
from pipenv.patched.notpip._vendor import distro
|
||||
distro_infos = dict(filter(
|
||||
lambda x: x[1],
|
||||
zip(["name", "version", "id"], distro.linux_distribution()),
|
||||
))
|
||||
libc = dict(filter(
|
||||
lambda x: x[1],
|
||||
zip(["lib", "version"], libc_ver()),
|
||||
))
|
||||
if libc:
|
||||
distro_infos["libc"] = libc
|
||||
if distro_infos:
|
||||
data["distro"] = distro_infos
|
||||
|
||||
if sys.platform.startswith("darwin") and platform.mac_ver()[0]:
|
||||
data["distro"] = {"name": "macOS", "version": platform.mac_ver()[0]}
|
||||
|
||||
if platform.system():
|
||||
data.setdefault("system", {})["name"] = platform.system()
|
||||
|
||||
if platform.release():
|
||||
data.setdefault("system", {})["release"] = platform.release()
|
||||
|
||||
if platform.machine():
|
||||
data["cpu"] = platform.machine()
|
||||
|
||||
if HAS_TLS:
|
||||
data["openssl_version"] = ssl.OPENSSL_VERSION
|
||||
|
||||
setuptools_version = get_installed_version("setuptools")
|
||||
if setuptools_version is not None:
|
||||
data["setuptools_version"] = setuptools_version
|
||||
|
||||
return "{data[installer][name]}/{data[installer][version]} {json}".format(
|
||||
data=data,
|
||||
json=json.dumps(data, separators=(",", ":"), sort_keys=True),
|
||||
)
|
||||
|
||||
|
||||
class MultiDomainBasicAuth(AuthBase):
|
||||
|
||||
def __init__(self, prompting=True):
|
||||
# type: (bool) -> None
|
||||
self.prompting = prompting
|
||||
self.passwords = {} # type: Dict[str, AuthInfo]
|
||||
|
||||
def __call__(self, req):
|
||||
parsed = urllib_parse.urlparse(req.url)
|
||||
|
||||
# Split the credentials from the netloc.
|
||||
netloc, url_user_password = split_auth_from_netloc(parsed.netloc)
|
||||
|
||||
# Set the url of the request to the url without any credentials
|
||||
req.url = urllib_parse.urlunparse(parsed[:1] + (netloc,) + parsed[2:])
|
||||
|
||||
# Use any stored credentials that we have for this netloc
|
||||
username, password = self.passwords.get(netloc, (None, None))
|
||||
|
||||
# Use the credentials embedded in the url if we have none stored
|
||||
if username is None:
|
||||
username, password = url_user_password
|
||||
|
||||
# Get creds from netrc if we still don't have them
|
||||
if username is None and password is None:
|
||||
netrc_auth = get_netrc_auth(req.url)
|
||||
username, password = netrc_auth if netrc_auth else (None, None)
|
||||
|
||||
if username or password:
|
||||
# Store the username and password
|
||||
self.passwords[netloc] = (username, password)
|
||||
|
||||
# Send the basic auth with this request
|
||||
req = HTTPBasicAuth(username or "", password or "")(req)
|
||||
|
||||
# Attach a hook to handle 401 responses
|
||||
req.register_hook("response", self.handle_401)
|
||||
|
||||
return req
|
||||
|
||||
def handle_401(self, resp, **kwargs):
|
||||
# We only care about 401 responses, anything else we want to just
|
||||
# pass through the actual response
|
||||
if resp.status_code != 401:
|
||||
return resp
|
||||
|
||||
# We are not able to prompt the user so simply return the response
|
||||
if not self.prompting:
|
||||
return resp
|
||||
|
||||
parsed = urllib_parse.urlparse(resp.url)
|
||||
|
||||
# Prompt the user for a new username and password
|
||||
username = six.moves.input("User for %s: " % parsed.netloc)
|
||||
password = getpass.getpass("Password: ")
|
||||
|
||||
# Store the new username and password to use for future requests
|
||||
if username or password:
|
||||
self.passwords[parsed.netloc] = (username, password)
|
||||
|
||||
# Consume content and release the original connection to allow our new
|
||||
# request to reuse the same one.
|
||||
resp.content
|
||||
resp.raw.release_conn()
|
||||
|
||||
# Add our new username and password to the request
|
||||
req = HTTPBasicAuth(username or "", password or "")(resp.request)
|
||||
req.register_hook("response", self.warn_on_401)
|
||||
|
||||
# Send our new request
|
||||
new_resp = resp.connection.send(req, **kwargs)
|
||||
new_resp.history.append(resp)
|
||||
|
||||
return new_resp
|
||||
|
||||
def warn_on_401(self, resp, **kwargs):
|
||||
# warn user that they provided incorrect credentials
|
||||
if resp.status_code == 401:
|
||||
logger.warning('401 Error, Credentials not correct for %s',
|
||||
resp.request.url)
|
||||
|
||||
|
||||
class LocalFSAdapter(BaseAdapter):
|
||||
|
||||
def send(self, request, stream=None, timeout=None, verify=None, cert=None,
|
||||
proxies=None):
|
||||
pathname = url_to_path(request.url)
|
||||
|
||||
resp = Response()
|
||||
resp.status_code = 200
|
||||
resp.url = request.url
|
||||
|
||||
try:
|
||||
stats = os.stat(pathname)
|
||||
except OSError as exc:
|
||||
resp.status_code = 404
|
||||
resp.raw = exc
|
||||
else:
|
||||
modified = email.utils.formatdate(stats.st_mtime, usegmt=True)
|
||||
content_type = mimetypes.guess_type(pathname)[0] or "text/plain"
|
||||
resp.headers = CaseInsensitiveDict({
|
||||
"Content-Type": content_type,
|
||||
"Content-Length": stats.st_size,
|
||||
"Last-Modified": modified,
|
||||
})
|
||||
|
||||
resp.raw = open(pathname, "rb")
|
||||
resp.close = resp.raw.close
|
||||
|
||||
return resp
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
|
||||
class SafeFileCache(FileCache):
|
||||
"""
|
||||
A file based cache which is safe to use even when the target directory may
|
||||
not be accessible or writable.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SafeFileCache, self).__init__(*args, **kwargs)
|
||||
|
||||
# Check to ensure that the directory containing our cache directory
|
||||
# is owned by the user current executing pip. If it does not exist
|
||||
# we will check the parent directory until we find one that does exist.
|
||||
# If it is not owned by the user executing pip then we will disable
|
||||
# the cache and log a warning.
|
||||
if not check_path_owner(self.directory):
|
||||
logger.warning(
|
||||
"The directory '%s' or its parent directory is not owned by "
|
||||
"the current user and the cache has been disabled. Please "
|
||||
"check the permissions and owner of that directory. If "
|
||||
"executing pip with sudo, you may want sudo's -H flag.",
|
||||
self.directory,
|
||||
)
|
||||
|
||||
# Set our directory to None to disable the Cache
|
||||
self.directory = None
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
# If we don't have a directory, then the cache should be a no-op.
|
||||
if self.directory is None:
|
||||
return
|
||||
|
||||
try:
|
||||
return super(SafeFileCache, self).get(*args, **kwargs)
|
||||
except (LockError, OSError, IOError):
|
||||
# We intentionally silence this error, if we can't access the cache
|
||||
# then we can just skip caching and process the request as if
|
||||
# caching wasn't enabled.
|
||||
pass
|
||||
|
||||
def set(self, *args, **kwargs):
|
||||
# If we don't have a directory, then the cache should be a no-op.
|
||||
if self.directory is None:
|
||||
return
|
||||
|
||||
try:
|
||||
return super(SafeFileCache, self).set(*args, **kwargs)
|
||||
except (LockError, OSError, IOError):
|
||||
# We intentionally silence this error, if we can't access the cache
|
||||
# then we can just skip caching and process the request as if
|
||||
# caching wasn't enabled.
|
||||
pass
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
# If we don't have a directory, then the cache should be a no-op.
|
||||
if self.directory is None:
|
||||
return
|
||||
|
||||
try:
|
||||
return super(SafeFileCache, self).delete(*args, **kwargs)
|
||||
except (LockError, OSError, IOError):
|
||||
# We intentionally silence this error, if we can't access the cache
|
||||
# then we can just skip caching and process the request as if
|
||||
# caching wasn't enabled.
|
||||
pass
|
||||
|
||||
|
||||
class InsecureHTTPAdapter(HTTPAdapter):
|
||||
|
||||
def cert_verify(self, conn, url, verify, cert):
|
||||
conn.cert_reqs = 'CERT_NONE'
|
||||
conn.ca_certs = None
|
||||
|
||||
|
||||
class PipSession(requests.Session):
|
||||
|
||||
timeout = None # type: Optional[int]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
retries = kwargs.pop("retries", 0)
|
||||
cache = kwargs.pop("cache", None)
|
||||
insecure_hosts = kwargs.pop("insecure_hosts", [])
|
||||
|
||||
super(PipSession, self).__init__(*args, **kwargs)
|
||||
|
||||
# Attach our User Agent to the request
|
||||
self.headers["User-Agent"] = user_agent()
|
||||
|
||||
# Attach our Authentication handler to the session
|
||||
self.auth = MultiDomainBasicAuth()
|
||||
|
||||
# Create our urllib3.Retry instance which will allow us to customize
|
||||
# how we handle retries.
|
||||
retries = urllib3.Retry(
|
||||
# Set the total number of retries that a particular request can
|
||||
# have.
|
||||
total=retries,
|
||||
|
||||
# A 503 error from PyPI typically means that the Fastly -> Origin
|
||||
# connection got interrupted in some way. A 503 error in general
|
||||
# is typically considered a transient error so we'll go ahead and
|
||||
# retry it.
|
||||
# A 500 may indicate transient error in Amazon S3
|
||||
# A 520 or 527 - may indicate transient error in CloudFlare
|
||||
status_forcelist=[500, 503, 520, 527],
|
||||
|
||||
# Add a small amount of back off between failed requests in
|
||||
# order to prevent hammering the service.
|
||||
backoff_factor=0.25,
|
||||
)
|
||||
|
||||
# We want to _only_ cache responses on securely fetched origins. We do
|
||||
# this because we can't validate the response of an insecurely fetched
|
||||
# origin, and we don't want someone to be able to poison the cache and
|
||||
# require manual eviction from the cache to fix it.
|
||||
if cache:
|
||||
secure_adapter = CacheControlAdapter(
|
||||
cache=SafeFileCache(cache, use_dir_lock=True),
|
||||
max_retries=retries,
|
||||
)
|
||||
else:
|
||||
secure_adapter = HTTPAdapter(max_retries=retries)
|
||||
|
||||
# Our Insecure HTTPAdapter disables HTTPS validation. It does not
|
||||
# support caching (see above) so we'll use it for all http:// URLs as
|
||||
# well as any https:// host that we've marked as ignoring TLS errors
|
||||
# for.
|
||||
insecure_adapter = InsecureHTTPAdapter(max_retries=retries)
|
||||
|
||||
self.mount("https://", secure_adapter)
|
||||
self.mount("http://", insecure_adapter)
|
||||
|
||||
# Enable file:// urls
|
||||
self.mount("file://", LocalFSAdapter())
|
||||
|
||||
# We want to use a non-validating adapter for any requests which are
|
||||
# deemed insecure.
|
||||
for host in insecure_hosts:
|
||||
self.mount("https://{}/".format(host), insecure_adapter)
|
||||
|
||||
def request(self, method, url, *args, **kwargs):
|
||||
# Allow setting a default timeout on a session
|
||||
kwargs.setdefault("timeout", self.timeout)
|
||||
|
||||
# Dispatch the actual request
|
||||
return super(PipSession, self).request(method, url, *args, **kwargs)
|
||||
|
||||
|
||||
def get_file_content(url, comes_from=None, session=None):
|
||||
# type: (str, Optional[str], Optional[PipSession]) -> Tuple[str, Text]
|
||||
"""Gets the content of a file; it may be a filename, file: URL, or
|
||||
@@ -417,29 +96,30 @@ def get_file_content(url, comes_from=None, session=None):
|
||||
"get_file_content() missing 1 required keyword argument: 'session'"
|
||||
)
|
||||
|
||||
match = _scheme_re.search(url)
|
||||
if match:
|
||||
scheme = match.group(1).lower()
|
||||
if (scheme == 'file' and comes_from and
|
||||
comes_from.startswith('http')):
|
||||
scheme = get_url_scheme(url)
|
||||
|
||||
if scheme in ['http', 'https']:
|
||||
# FIXME: catch some errors
|
||||
resp = session.get(url)
|
||||
resp.raise_for_status()
|
||||
return resp.url, resp.text
|
||||
|
||||
elif scheme == 'file':
|
||||
if comes_from and comes_from.startswith('http'):
|
||||
raise InstallationError(
|
||||
'Requirements file %s references URL %s, which is local'
|
||||
% (comes_from, url))
|
||||
if scheme == 'file':
|
||||
path = url.split(':', 1)[1]
|
||||
path = path.replace('\\', '/')
|
||||
match = _url_slash_drive_re.match(path)
|
||||
if match:
|
||||
path = match.group(1) + ':' + path.split('|', 1)[1]
|
||||
path = urllib_parse.unquote(path)
|
||||
if path.startswith('/'):
|
||||
path = '/' + path.lstrip('/')
|
||||
url = path
|
||||
else:
|
||||
# FIXME: catch some errors
|
||||
resp = session.get(url)
|
||||
resp.raise_for_status()
|
||||
return resp.url, resp.text
|
||||
|
||||
path = url.split(':', 1)[1]
|
||||
path = path.replace('\\', '/')
|
||||
match = _url_slash_drive_re.match(path)
|
||||
if match:
|
||||
path = match.group(1) + ':' + path.split('|', 1)[1]
|
||||
path = urllib_parse.unquote(path)
|
||||
if path.startswith('/'):
|
||||
path = '/' + path.lstrip('/')
|
||||
url = path
|
||||
|
||||
try:
|
||||
with open(url, 'rb') as f:
|
||||
content = auto_decode(f.read())
|
||||
@@ -450,89 +130,25 @@ def get_file_content(url, comes_from=None, session=None):
|
||||
return url, content
|
||||
|
||||
|
||||
_scheme_re = re.compile(r'^(http|https|file):', re.I)
|
||||
_url_slash_drive_re = re.compile(r'/*([a-z])\|', re.I)
|
||||
|
||||
|
||||
def is_url(name):
|
||||
# type: (Union[str, Text]) -> bool
|
||||
"""Returns true if the name looks like a URL"""
|
||||
if ':' not in name:
|
||||
return False
|
||||
scheme = name.split(':', 1)[0].lower()
|
||||
return scheme in ['http', 'https', 'file', 'ftp'] + vcs.all_schemes
|
||||
|
||||
|
||||
def url_to_path(url):
|
||||
# type: (str) -> str
|
||||
"""
|
||||
Convert a file: URL to a path.
|
||||
"""
|
||||
assert url.startswith('file:'), (
|
||||
"You can only turn file: urls into filenames (not %r)" % url)
|
||||
|
||||
_, netloc, path, _, _ = urllib_parse.urlsplit(url)
|
||||
|
||||
# if we have a UNC path, prepend UNC share notation
|
||||
if netloc:
|
||||
netloc = '\\\\' + netloc
|
||||
|
||||
path = urllib_request.url2pathname(netloc + path)
|
||||
return path
|
||||
|
||||
|
||||
def path_to_url(path):
|
||||
# type: (Union[str, Text]) -> str
|
||||
"""
|
||||
Convert a path to a file: URL. The path will be made absolute and have
|
||||
quoted path parts.
|
||||
"""
|
||||
path = os.path.normpath(os.path.abspath(path))
|
||||
url = urllib_parse.urljoin('file:', urllib_request.pathname2url(path))
|
||||
return url
|
||||
|
||||
|
||||
def is_archive_file(name):
|
||||
# type: (str) -> bool
|
||||
"""Return True if `name` is a considered as an archive file."""
|
||||
ext = splitext(name)[1].lower()
|
||||
if ext in ARCHIVE_EXTENSIONS:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def unpack_vcs_link(link, location):
|
||||
# type: (Link, str) -> None
|
||||
vcs_backend = _get_used_vcs_backend(link)
|
||||
vcs_backend.unpack(location)
|
||||
assert vcs_backend is not None
|
||||
vcs_backend.unpack(location, url=hide_url(link.url))
|
||||
|
||||
|
||||
def _get_used_vcs_backend(link):
|
||||
for backend in vcs.backends:
|
||||
if link.scheme in backend.schemes:
|
||||
vcs_backend = backend(link.url)
|
||||
return vcs_backend
|
||||
|
||||
|
||||
def is_vcs_url(link):
|
||||
# type: (Link) -> bool
|
||||
return bool(_get_used_vcs_backend(link))
|
||||
|
||||
|
||||
def is_file_url(link):
|
||||
# type: (Link) -> bool
|
||||
return link.url.lower().startswith('file:')
|
||||
|
||||
|
||||
def is_dir_url(link):
|
||||
# type: (Link) -> bool
|
||||
"""Return whether a file:// Link points to a directory.
|
||||
|
||||
``link`` must not have any other scheme but file://. Call is_file_url()
|
||||
first.
|
||||
|
||||
# type: (Link) -> Optional[VersionControl]
|
||||
"""
|
||||
link_path = url_to_path(link.url_without_fragment)
|
||||
return os.path.isdir(link_path)
|
||||
Return a VersionControl object or None.
|
||||
"""
|
||||
for vcs_backend in vcs.backends:
|
||||
if link.scheme in vcs_backend.schemes:
|
||||
return vcs_backend
|
||||
return None
|
||||
|
||||
|
||||
def _progress_indicator(iterable, *args, **kwargs):
|
||||
@@ -543,7 +159,7 @@ def _download_url(
|
||||
resp, # type: Response
|
||||
link, # type: Link
|
||||
content_file, # type: IO
|
||||
hashes, # type: Hashes
|
||||
hashes, # type: Optional[Hashes]
|
||||
progress_bar # type: str
|
||||
):
|
||||
# type: (...) -> None
|
||||
@@ -627,8 +243,6 @@ def _download_url(
|
||||
else:
|
||||
logger.info("Downloading %s", url)
|
||||
|
||||
logger.debug('Downloading from URL %s', link)
|
||||
|
||||
downloaded_chunks = written_chunks(
|
||||
progress_indicator(
|
||||
resp_read(CONTENT_CHUNK_SIZE),
|
||||
@@ -703,7 +317,7 @@ def unpack_http_url(
|
||||
|
||||
# unpack the archive to the build dir location. even when only
|
||||
# downloading archives, they have to be unpacked to parse dependencies
|
||||
unpack_file(from_path, location, content_type, link)
|
||||
unpack_file(from_path, location, content_type)
|
||||
|
||||
# a download dir is specified; let's copy the archive there
|
||||
if download_dir and not already_downloaded_path:
|
||||
@@ -713,6 +327,46 @@ def unpack_http_url(
|
||||
os.unlink(from_path)
|
||||
|
||||
|
||||
def _copy2_ignoring_special_files(src, dest):
|
||||
# type: (str, str) -> None
|
||||
"""Copying special files is not supported, but as a convenience to users
|
||||
we skip errors copying them. This supports tools that may create e.g.
|
||||
socket files in the project source directory.
|
||||
"""
|
||||
try:
|
||||
copy2_fixed(src, dest)
|
||||
except shutil.SpecialFileError as e:
|
||||
# SpecialFileError may be raised due to either the source or
|
||||
# destination. If the destination was the cause then we would actually
|
||||
# care, but since the destination directory is deleted prior to
|
||||
# copy we ignore all of them assuming it is caused by the source.
|
||||
logger.warning(
|
||||
"Ignoring special file error '%s' encountered copying %s to %s.",
|
||||
str(e),
|
||||
path_to_display(src),
|
||||
path_to_display(dest),
|
||||
)
|
||||
|
||||
|
||||
def _copy_source_tree(source, target):
|
||||
# type: (str, str) -> None
|
||||
def ignore(d, names):
|
||||
# Pulling in those directories can potentially be very slow,
|
||||
# exclude the following directories if they appear in the top
|
||||
# level dir (and only it).
|
||||
# See discussion at https://github.com/pypa/pip/pull/6770
|
||||
return ['.tox', '.nox'] if d == source else []
|
||||
|
||||
kwargs = dict(ignore=ignore, symlinks=True) # type: CopytreeKwargs
|
||||
|
||||
if not PY2:
|
||||
# Python 2 does not support copy_function, so we only ignore
|
||||
# errors on special file copy in Python 3.
|
||||
kwargs['copy_function'] = _copy2_ignoring_special_files
|
||||
|
||||
shutil.copytree(source, target, **kwargs)
|
||||
|
||||
|
||||
def unpack_file_url(
|
||||
link, # type: Link
|
||||
location, # type: str
|
||||
@@ -725,13 +379,12 @@ def unpack_file_url(
|
||||
If download_dir is provided and link points to a file, make a copy
|
||||
of the link file inside download_dir.
|
||||
"""
|
||||
link_path = url_to_path(link.url_without_fragment)
|
||||
|
||||
link_path = link.file_path
|
||||
# If it's a url to a local directory
|
||||
if is_dir_url(link):
|
||||
if link.is_existing_dir():
|
||||
if os.path.isdir(location):
|
||||
rmtree(location)
|
||||
shutil.copytree(link_path, location, symlinks=True)
|
||||
_copy_source_tree(link_path, location)
|
||||
if download_dir:
|
||||
logger.info('Link is a directory, ignoring download_dir')
|
||||
return
|
||||
@@ -760,83 +413,17 @@ def unpack_file_url(
|
||||
|
||||
# unpack the archive to the build dir location. even when only downloading
|
||||
# archives, they have to be unpacked to parse dependencies
|
||||
unpack_file(from_path, location, content_type, link)
|
||||
unpack_file(from_path, location, content_type)
|
||||
|
||||
# a download dir is specified and not already downloaded
|
||||
if download_dir and not already_downloaded_path:
|
||||
_copy_file(from_path, download_dir, link)
|
||||
|
||||
|
||||
def _copy_dist_from_dir(link_path, location):
|
||||
"""Copy distribution files in `link_path` to `location`.
|
||||
|
||||
Invoked when user requests to install a local directory. E.g.:
|
||||
|
||||
pip install .
|
||||
pip install ~/dev/git-repos/python-prompt-toolkit
|
||||
|
||||
"""
|
||||
|
||||
# Note: This is currently VERY SLOW if you have a lot of data in the
|
||||
# directory, because it copies everything with `shutil.copytree`.
|
||||
# What it should really do is build an sdist and install that.
|
||||
# See https://github.com/pypa/pip/issues/2195
|
||||
|
||||
if os.path.isdir(location):
|
||||
rmtree(location)
|
||||
|
||||
# build an sdist
|
||||
setup_py = 'setup.py'
|
||||
sdist_args = [sys.executable]
|
||||
sdist_args.append('-c')
|
||||
sdist_args.append(SETUPTOOLS_SHIM % setup_py)
|
||||
sdist_args.append('sdist')
|
||||
sdist_args += ['--dist-dir', location]
|
||||
logger.info('Running setup.py sdist for %s', link_path)
|
||||
|
||||
with indent_log():
|
||||
call_subprocess(sdist_args, cwd=link_path, show_stdout=False)
|
||||
|
||||
# unpack sdist into `location`
|
||||
sdist = os.path.join(location, os.listdir(location)[0])
|
||||
logger.info('Unpacking sdist %s into %s', sdist, location)
|
||||
unpack_file(sdist, location, content_type=None, link=None)
|
||||
|
||||
|
||||
class PipXmlrpcTransport(xmlrpc_client.Transport):
|
||||
"""Provide a `xmlrpclib.Transport` implementation via a `PipSession`
|
||||
object.
|
||||
"""
|
||||
|
||||
def __init__(self, index_url, session, use_datetime=False):
|
||||
xmlrpc_client.Transport.__init__(self, use_datetime)
|
||||
index_parts = urllib_parse.urlparse(index_url)
|
||||
self._scheme = index_parts.scheme
|
||||
self._session = session
|
||||
|
||||
def request(self, host, handler, request_body, verbose=False):
|
||||
parts = (self._scheme, host, handler, None, None, None)
|
||||
url = urllib_parse.urlunparse(parts)
|
||||
try:
|
||||
headers = {'Content-Type': 'text/xml'}
|
||||
response = self._session.post(url, data=request_body,
|
||||
headers=headers, stream=True)
|
||||
response.raise_for_status()
|
||||
self.verbose = verbose
|
||||
return self.parse_response(response.raw)
|
||||
except requests.HTTPError as exc:
|
||||
logger.critical(
|
||||
"HTTP error %s while getting %s",
|
||||
exc.response.status_code, url,
|
||||
)
|
||||
raise
|
||||
|
||||
|
||||
def unpack_url(
|
||||
link, # type: Optional[Link]
|
||||
location, # type: Optional[str]
|
||||
link, # type: Link
|
||||
location, # type: str
|
||||
download_dir=None, # type: Optional[str]
|
||||
only_download=False, # type: bool
|
||||
session=None, # type: Optional[PipSession]
|
||||
hashes=None, # type: Optional[Hashes]
|
||||
progress_bar="on" # type: str
|
||||
@@ -857,11 +444,11 @@ def unpack_url(
|
||||
would ordinarily raise HashUnsupported) are allowed.
|
||||
"""
|
||||
# non-editable vcs urls
|
||||
if is_vcs_url(link):
|
||||
if link.is_vcs:
|
||||
unpack_vcs_link(link, location)
|
||||
|
||||
# file urls
|
||||
elif is_file_url(link):
|
||||
elif link.is_file:
|
||||
unpack_file_url(link, location, download_dir, hashes=hashes)
|
||||
|
||||
# http urls
|
||||
@@ -877,15 +464,36 @@ def unpack_url(
|
||||
hashes=hashes,
|
||||
progress_bar=progress_bar
|
||||
)
|
||||
if only_download:
|
||||
write_delete_marker_file(location)
|
||||
|
||||
|
||||
def sanitize_content_filename(filename):
|
||||
# type: (str) -> str
|
||||
"""
|
||||
Sanitize the "filename" value from a Content-Disposition header.
|
||||
"""
|
||||
return os.path.basename(filename)
|
||||
|
||||
|
||||
def parse_content_disposition(content_disposition, default_filename):
|
||||
# type: (str, str) -> str
|
||||
"""
|
||||
Parse the "filename" value from a Content-Disposition header, and
|
||||
return the default filename if the result is empty.
|
||||
"""
|
||||
_type, params = cgi.parse_header(content_disposition)
|
||||
filename = params.get('filename')
|
||||
if filename:
|
||||
# We need to sanitize the filename to prevent directory traversal
|
||||
# in case the filename contains ".." path parts.
|
||||
filename = sanitize_content_filename(filename)
|
||||
return filename or default_filename
|
||||
|
||||
|
||||
def _download_http_url(
|
||||
link, # type: Link
|
||||
session, # type: PipSession
|
||||
temp_dir, # type: str
|
||||
hashes, # type: Hashes
|
||||
hashes, # type: Optional[Hashes]
|
||||
progress_bar # type: str
|
||||
):
|
||||
# type: (...) -> Tuple[str, str]
|
||||
@@ -928,11 +536,8 @@ def _download_http_url(
|
||||
# Have a look at the Content-Disposition header for a better guess
|
||||
content_disposition = resp.headers.get('content-disposition')
|
||||
if content_disposition:
|
||||
type, params = cgi.parse_header(content_disposition)
|
||||
# We use ``or`` here because we don't want to use an "empty" value
|
||||
# from the filename param.
|
||||
filename = params.get('filename') or filename
|
||||
ext = splitext(filename)[1]
|
||||
filename = parse_content_disposition(content_disposition, filename)
|
||||
ext = splitext(filename)[1] # type: Optional[str]
|
||||
if not ext:
|
||||
ext = mimetypes.guess_extension(content_type)
|
||||
if ext:
|
||||
@@ -948,24 +553,26 @@ def _download_http_url(
|
||||
|
||||
|
||||
def _check_download_dir(link, download_dir, hashes):
|
||||
# type: (Link, str, Hashes) -> Optional[str]
|
||||
# type: (Link, str, Optional[Hashes]) -> Optional[str]
|
||||
""" Check download_dir for previously downloaded file with correct hash
|
||||
If a correct file is found return its path else None
|
||||
"""
|
||||
download_path = os.path.join(download_dir, link.filename)
|
||||
if os.path.exists(download_path):
|
||||
# If already downloaded, does its hash match?
|
||||
logger.info('File was already downloaded %s', download_path)
|
||||
if hashes:
|
||||
try:
|
||||
hashes.check_against_path(download_path)
|
||||
except HashMismatch:
|
||||
logger.warning(
|
||||
'Previously-downloaded file %s has bad hash. '
|
||||
'Re-downloading.',
|
||||
download_path
|
||||
)
|
||||
os.unlink(download_path)
|
||||
return None
|
||||
return download_path
|
||||
return None
|
||||
|
||||
if not os.path.exists(download_path):
|
||||
return None
|
||||
|
||||
# If already downloaded, does its hash match?
|
||||
logger.info('File was already downloaded %s', download_path)
|
||||
if hashes:
|
||||
try:
|
||||
hashes.check_against_path(download_path)
|
||||
except HashMismatch:
|
||||
logger.warning(
|
||||
'Previously-downloaded file %s has bad hash. '
|
||||
'Re-downloading.',
|
||||
download_path
|
||||
)
|
||||
os.unlink(download_path)
|
||||
return None
|
||||
return download_path
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
"""Exceptions used throughout package"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from itertools import chain, groupby, repeat
|
||||
@@ -8,8 +12,9 @@ from pipenv.patched.notpip._vendor.six import iteritems
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Optional # noqa: F401
|
||||
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement # noqa: F401
|
||||
from typing import Optional
|
||||
from pipenv.patched.notpip._vendor.pkg_resources import Distribution
|
||||
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
|
||||
|
||||
|
||||
class PipError(Exception):
|
||||
@@ -28,6 +33,36 @@ class UninstallationError(PipError):
|
||||
"""General exception during uninstallation"""
|
||||
|
||||
|
||||
class NoneMetadataError(PipError):
|
||||
"""
|
||||
Raised when accessing "METADATA" or "PKG-INFO" metadata for a
|
||||
pip._vendor.pkg_resources.Distribution object and
|
||||
`dist.has_metadata('METADATA')` returns True but
|
||||
`dist.get_metadata('METADATA')` returns None (and similarly for
|
||||
"PKG-INFO").
|
||||
"""
|
||||
|
||||
def __init__(self, dist, metadata_name):
|
||||
# type: (Distribution, str) -> None
|
||||
"""
|
||||
:param dist: A Distribution object.
|
||||
:param metadata_name: The name of the metadata being accessed
|
||||
(can be "METADATA" or "PKG-INFO").
|
||||
"""
|
||||
self.dist = dist
|
||||
self.metadata_name = metadata_name
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
# Use `dist` in the error message because its stringification
|
||||
# includes more information, like the version and location.
|
||||
return (
|
||||
'None {} metadata found for distribution: {}'.format(
|
||||
self.metadata_name, self.dist,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class DistributionNotFound(InstallationError):
|
||||
"""Raised when a distribution cannot be found to satisfy a requirement"""
|
||||
|
||||
@@ -246,7 +281,6 @@ class HashMismatch(HashError):
|
||||
for e in expecteds)
|
||||
lines.append(' Got %s\n' %
|
||||
self.gots[hash_name].hexdigest())
|
||||
prefix = ' or'
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+117
-62
@@ -10,35 +10,102 @@ for sub-dependencies
|
||||
a. "first found, wins" (where the order is breadth first)
|
||||
"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from itertools import chain
|
||||
|
||||
from pipenv.patched.notpip._vendor.packaging import specifiers
|
||||
|
||||
from pipenv.patched.notpip._internal.exceptions import (
|
||||
BestVersionAlreadyInstalled, DistributionNotFound, HashError, HashErrors,
|
||||
BestVersionAlreadyInstalled,
|
||||
DistributionNotFound,
|
||||
HashError,
|
||||
HashErrors,
|
||||
UnsupportedPythonVersion,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.req.constructors import install_req_from_req_string
|
||||
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
|
||||
from pipenv.patched.notpip._internal.utils.logging import indent_log
|
||||
from pipenv.patched.notpip._internal.utils.misc import dist_in_usersite, ensure_dir
|
||||
from pipenv.patched.notpip._internal.utils.packaging import check_dist_requires_python
|
||||
from pipenv.patched.notpip._internal.utils.misc import (
|
||||
dist_in_usersite,
|
||||
ensure_dir,
|
||||
normalize_version_info,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.packaging import (
|
||||
check_requires_python,
|
||||
get_requires_python,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Optional, DefaultDict, List, Set # noqa: F401
|
||||
from pipenv.patched.notpip._internal.download import PipSession # noqa: F401
|
||||
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement # noqa: F401
|
||||
from pipenv.patched.notpip._internal.index import PackageFinder # noqa: F401
|
||||
from pipenv.patched.notpip._internal.req.req_set import RequirementSet # noqa: F401
|
||||
from pipenv.patched.notpip._internal.operations.prepare import ( # noqa: F401
|
||||
DistAbstraction, RequirementPreparer
|
||||
)
|
||||
from pipenv.patched.notpip._internal.cache import WheelCache # noqa: F401
|
||||
from typing import Callable, DefaultDict, List, Optional, Set, Tuple
|
||||
from pipenv.patched.notpip._vendor import pkg_resources
|
||||
|
||||
from pipenv.patched.notpip._internal.distributions import AbstractDistribution
|
||||
from pipenv.patched.notpip._internal.network.session import PipSession
|
||||
from pipenv.patched.notpip._internal.index import PackageFinder
|
||||
from pipenv.patched.notpip._internal.operations.prepare import RequirementPreparer
|
||||
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
|
||||
from pipenv.patched.notpip._internal.req.req_set import RequirementSet
|
||||
|
||||
InstallRequirementProvider = Callable[
|
||||
[str, InstallRequirement], InstallRequirement
|
||||
]
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _check_dist_requires_python(
|
||||
dist, # type: pkg_resources.Distribution
|
||||
version_info, # type: Tuple[int, int, int]
|
||||
ignore_requires_python=False, # type: bool
|
||||
):
|
||||
# type: (...) -> None
|
||||
"""
|
||||
Check whether the given Python version is compatible with a distribution's
|
||||
"Requires-Python" value.
|
||||
|
||||
:param version_info: A 3-tuple of ints representing the Python
|
||||
major-minor-micro version to check.
|
||||
:param ignore_requires_python: Whether to ignore the "Requires-Python"
|
||||
value if the given Python version isn't compatible.
|
||||
|
||||
:raises UnsupportedPythonVersion: When the given Python version isn't
|
||||
compatible.
|
||||
"""
|
||||
requires_python = get_requires_python(dist)
|
||||
try:
|
||||
is_compatible = check_requires_python(
|
||||
requires_python, version_info=version_info,
|
||||
)
|
||||
except specifiers.InvalidSpecifier as exc:
|
||||
logger.warning(
|
||||
"Package %r has an invalid Requires-Python: %s",
|
||||
dist.project_name, exc,
|
||||
)
|
||||
return
|
||||
|
||||
if is_compatible:
|
||||
return
|
||||
|
||||
version = '.'.join(map(str, version_info))
|
||||
if ignore_requires_python:
|
||||
logger.debug(
|
||||
'Ignoring failed Requires-Python check for package %r: '
|
||||
'%s not in %r',
|
||||
dist.project_name, version, requires_python,
|
||||
)
|
||||
return
|
||||
|
||||
raise UnsupportedPythonVersion(
|
||||
'Package {!r} requires a different Python: {} not in {!r}'.format(
|
||||
dist.project_name, version, requires_python,
|
||||
))
|
||||
|
||||
|
||||
class Resolver(object):
|
||||
"""Resolves which packages need to be installed/uninstalled to perform \
|
||||
the requested operation without breaking the requirements of any package.
|
||||
@@ -51,41 +118,42 @@ class Resolver(object):
|
||||
preparer, # type: RequirementPreparer
|
||||
session, # type: PipSession
|
||||
finder, # type: PackageFinder
|
||||
wheel_cache, # type: Optional[WheelCache]
|
||||
make_install_req, # type: InstallRequirementProvider
|
||||
use_user_site, # type: bool
|
||||
ignore_dependencies, # type: bool
|
||||
ignore_installed, # type: bool
|
||||
ignore_requires_python, # type: bool
|
||||
force_reinstall, # type: bool
|
||||
isolated, # type: bool
|
||||
upgrade_strategy, # type: str
|
||||
use_pep517=None, # type: Optional[bool]
|
||||
py_version_info=None, # type: Optional[Tuple[int, ...]]
|
||||
ignore_compatibility=False, # type: bool
|
||||
):
|
||||
# type: (...) -> None
|
||||
super(Resolver, self).__init__()
|
||||
assert upgrade_strategy in self._allowed_strategies
|
||||
|
||||
if py_version_info is None:
|
||||
py_version_info = sys.version_info[:3]
|
||||
else:
|
||||
py_version_info = normalize_version_info(py_version_info)
|
||||
|
||||
self._py_version_info = py_version_info
|
||||
|
||||
self.preparer = preparer
|
||||
self.finder = finder
|
||||
self.session = session
|
||||
|
||||
# NOTE: This would eventually be replaced with a cache that can give
|
||||
# information about both sdist and wheels transparently.
|
||||
self.wheel_cache = wheel_cache
|
||||
|
||||
# This is set in resolve
|
||||
self.require_hashes = None # type: Optional[bool]
|
||||
|
||||
self.upgrade_strategy = upgrade_strategy
|
||||
self.force_reinstall = force_reinstall
|
||||
self.isolated = isolated
|
||||
self.ignore_dependencies = ignore_dependencies
|
||||
self.ignore_installed = ignore_installed
|
||||
self.ignore_requires_python = ignore_requires_python
|
||||
self.ignore_compatibility = ignore_compatibility
|
||||
self.use_user_site = use_user_site
|
||||
self.use_pep517 = use_pep517
|
||||
self._make_install_req = make_install_req
|
||||
self.ignore_compatibility = ignore_compatibility
|
||||
self.requires_python = None
|
||||
if self.ignore_compatibility:
|
||||
self.ignore_requires_python = True
|
||||
@@ -121,7 +189,8 @@ class Resolver(object):
|
||||
)
|
||||
|
||||
# Display where finder is looking for packages
|
||||
locations = self.finder.get_formatted_locations()
|
||||
search_scope = self.finder.search_scope
|
||||
locations = search_scope.get_formatted_locations()
|
||||
if locations:
|
||||
logger.info(locations)
|
||||
|
||||
@@ -164,7 +233,6 @@ class Resolver(object):
|
||||
req.conflicts_with = req.satisfied_by
|
||||
req.satisfied_by = None
|
||||
|
||||
# XXX: Stop passing requirement_set for options
|
||||
def _check_skip_installed(self, req_to_install):
|
||||
# type: (InstallRequirement) -> Optional[str]
|
||||
"""Check if req_to_install should be skipped.
|
||||
@@ -219,7 +287,7 @@ class Resolver(object):
|
||||
return None
|
||||
|
||||
def _get_abstract_dist_for(self, req):
|
||||
# type: (InstallRequirement) -> DistAbstraction
|
||||
# type: (InstallRequirement) -> AbstractDistribution
|
||||
"""Takes a InstallRequirement and returns a single AbstractDist \
|
||||
representing a prepared variant of the same.
|
||||
"""
|
||||
@@ -243,9 +311,11 @@ class Resolver(object):
|
||||
)
|
||||
|
||||
upgrade_allowed = self._is_upgrade_allowed(req)
|
||||
|
||||
# We eagerly populate the link, since that's our "legacy" behavior.
|
||||
req.populate_link(self.finder, upgrade_allowed, self.require_hashes)
|
||||
abstract_dist = self.preparer.prepare_linked_requirement(
|
||||
req, self.session, self.finder, upgrade_allowed,
|
||||
self.require_hashes
|
||||
req, self.session, self.finder, self.require_hashes
|
||||
)
|
||||
|
||||
# NOTE
|
||||
@@ -280,7 +350,7 @@ class Resolver(object):
|
||||
self,
|
||||
requirement_set, # type: RequirementSet
|
||||
req_to_install, # type: InstallRequirement
|
||||
ignore_requires_python=False # type: bool
|
||||
ignore_requires_python=False, # type: bool
|
||||
):
|
||||
# type: (...) -> List[InstallRequirement]
|
||||
"""Prepare a single requirements file.
|
||||
@@ -301,31 +371,30 @@ class Resolver(object):
|
||||
abstract_dist = self._get_abstract_dist_for(req_to_install)
|
||||
|
||||
# Parse and return dependencies
|
||||
dist = abstract_dist.dist()
|
||||
dist = abstract_dist.get_pkg_resources_distribution()
|
||||
# This will raise UnsupportedPythonVersion if the given Python
|
||||
# version isn't compatible with the distribution's Requires-Python.
|
||||
ignore_requires_python = (
|
||||
ignore_requires_python or self.ignore_requires_python or
|
||||
self.ignore_compatibility
|
||||
)
|
||||
_check_dist_requires_python(
|
||||
dist, version_info=self._py_version_info,
|
||||
ignore_requires_python=ignore_requires_python,
|
||||
)
|
||||
# Patched in - lets get the python version on here then
|
||||
# FIXME: Does this patch even work? it puts the python version
|
||||
# on the resolver... why?
|
||||
try:
|
||||
check_dist_requires_python(dist)
|
||||
except UnsupportedPythonVersion as err:
|
||||
if self.ignore_requires_python or ignore_requires_python or self.ignore_compatibility:
|
||||
logger.warning(err.args[0])
|
||||
else:
|
||||
raise
|
||||
|
||||
# A huge hack, by Kenneth Reitz.
|
||||
try:
|
||||
self.requires_python = check_dist_requires_python(dist, absorb=False)
|
||||
self.requires_python = get_requires_python(dist)
|
||||
except TypeError:
|
||||
self.requires_python = None
|
||||
|
||||
|
||||
more_reqs = [] # type: List[InstallRequirement]
|
||||
|
||||
def add_req(subreq, extras_requested):
|
||||
sub_install_req = install_req_from_req_string(
|
||||
sub_install_req = self._make_install_req(
|
||||
str(subreq),
|
||||
req_to_install,
|
||||
isolated=self.isolated,
|
||||
wheel_cache=self.wheel_cache,
|
||||
use_pep517=self.use_pep517
|
||||
)
|
||||
parent_req_name = req_to_install.name
|
||||
to_scan_again, add_to_parent = requirement_set.add_requirement(
|
||||
@@ -343,10 +412,10 @@ class Resolver(object):
|
||||
# We add req_to_install before its dependencies, so that we
|
||||
# can refer to it when adding dependencies.
|
||||
if not requirement_set.has_requirement(req_to_install.name):
|
||||
# 'unnamed' requirements will get added here
|
||||
available_requested = sorted(
|
||||
set(dist.extras) & set(req_to_install.extras)
|
||||
)
|
||||
# 'unnamed' requirements will get added here
|
||||
req_to_install.is_direct = True
|
||||
requirement_set.add_requirement(
|
||||
req_to_install, parent_req_name=None,
|
||||
@@ -374,20 +443,6 @@ class Resolver(object):
|
||||
for subreq in dist.requires(available_requested):
|
||||
add_req(subreq, extras_requested=available_requested)
|
||||
|
||||
# Hack for deep-resolving extras.
|
||||
for available in available_requested:
|
||||
if hasattr(dist, '_DistInfoDistribution__dep_map'):
|
||||
for req in dist._DistInfoDistribution__dep_map[available]:
|
||||
req = InstallRequirement(
|
||||
req,
|
||||
req_to_install,
|
||||
isolated=self.isolated,
|
||||
wheel_cache=self.wheel_cache,
|
||||
use_pep517=None
|
||||
)
|
||||
|
||||
more_reqs.append(req)
|
||||
|
||||
if not req_to_install.editable and not req_to_install.satisfied_by:
|
||||
# XXX: --no-install leads this to report 'Successfully
|
||||
# downloaded' for only non-editable reqs, even though we took
|
||||
@@ -1,4 +1,9 @@
|
||||
"""Locations where we look for configs, install stuff, etc"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
@@ -11,82 +16,44 @@ from distutils import sysconfig as distutils_sysconfig
|
||||
from distutils.command.install import SCHEME_KEYS # type: ignore
|
||||
|
||||
from pipenv.patched.notpip._internal.utils import appdirs
|
||||
from pipenv.patched.notpip._internal.utils.compat import WINDOWS, expanduser
|
||||
from pipenv.patched.notpip._internal.utils.compat import WINDOWS
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
from pipenv.patched.notpip._internal.utils.virtualenv import running_under_virtualenv
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Any, Union, Dict, List, Optional # noqa: F401
|
||||
from typing import Any, Union, Dict, List, Optional
|
||||
|
||||
|
||||
# Application Directories
|
||||
USER_CACHE_DIR = appdirs.user_cache_dir("pip")
|
||||
|
||||
|
||||
DELETE_MARKER_MESSAGE = '''\
|
||||
This file is placed here by pip to indicate the source was put
|
||||
here by pip.
|
||||
|
||||
Once this package is successfully installed this source code will be
|
||||
deleted (unless you remove this file).
|
||||
'''
|
||||
PIP_DELETE_MARKER_FILENAME = 'pip-delete-this-directory.txt'
|
||||
|
||||
|
||||
def write_delete_marker_file(directory):
|
||||
# type: (str) -> None
|
||||
def get_major_minor_version():
|
||||
# type: () -> str
|
||||
"""
|
||||
Write the pip delete marker file into this directory.
|
||||
Return the major-minor version of the current Python as a string, e.g.
|
||||
"3.7" or "3.10".
|
||||
"""
|
||||
filepath = os.path.join(directory, PIP_DELETE_MARKER_FILENAME)
|
||||
with open(filepath, 'w') as marker_fp:
|
||||
marker_fp.write(DELETE_MARKER_MESSAGE)
|
||||
return '{}.{}'.format(*sys.version_info)
|
||||
|
||||
|
||||
def running_under_virtualenv():
|
||||
# type: () -> bool
|
||||
"""
|
||||
Return True if we're running inside a virtualenv, False otherwise.
|
||||
|
||||
"""
|
||||
if hasattr(sys, 'real_prefix'):
|
||||
return True
|
||||
elif sys.prefix != getattr(sys, "base_prefix", sys.prefix):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def virtualenv_no_global():
|
||||
# type: () -> bool
|
||||
"""
|
||||
Return True if in a venv and no system site packages.
|
||||
"""
|
||||
# this mirrors the logic in virtualenv.py for locating the
|
||||
# no-global-site-packages.txt file
|
||||
site_mod_dir = os.path.dirname(os.path.abspath(site.__file__))
|
||||
no_global_file = os.path.join(site_mod_dir, 'no-global-site-packages.txt')
|
||||
if running_under_virtualenv() and os.path.isfile(no_global_file):
|
||||
return True
|
||||
def get_src_prefix():
|
||||
if running_under_virtualenv():
|
||||
src_prefix = os.path.join(sys.prefix, 'src')
|
||||
else:
|
||||
return False
|
||||
# FIXME: keep src in cwd for now (it is not a temporary folder)
|
||||
try:
|
||||
src_prefix = os.path.join(os.getcwd(), 'src')
|
||||
except OSError:
|
||||
# In case the current working directory has been renamed or deleted
|
||||
sys.exit(
|
||||
"The folder you are executing pip from can no longer be found."
|
||||
)
|
||||
|
||||
# under macOS + virtualenv sys.prefix is not properly resolved
|
||||
# it is something like /path/to/python/bin/..
|
||||
return os.path.abspath(src_prefix)
|
||||
|
||||
if running_under_virtualenv():
|
||||
src_prefix = os.path.join(sys.prefix, 'src')
|
||||
else:
|
||||
# FIXME: keep src in cwd for now (it is not a temporary folder)
|
||||
try:
|
||||
src_prefix = os.path.join(os.getcwd(), 'src')
|
||||
except OSError:
|
||||
# In case the current working directory has been renamed or deleted
|
||||
sys.exit(
|
||||
"The folder you are executing pip from can no longer be found."
|
||||
)
|
||||
|
||||
# under macOS + virtualenv sys.prefix is not properly resolved
|
||||
# it is something like /path/to/python/bin/..
|
||||
# Note: using realpath due to tmp dirs on OSX being symlinks
|
||||
src_prefix = os.path.abspath(src_prefix)
|
||||
|
||||
# FIXME doesn't account for venv linked to global site-packages
|
||||
|
||||
@@ -103,7 +70,7 @@ try:
|
||||
user_site = site.getusersitepackages()
|
||||
except AttributeError:
|
||||
user_site = site.USER_SITE
|
||||
user_dir = expanduser('~')
|
||||
|
||||
if WINDOWS:
|
||||
bin_py = os.path.join(sys.prefix, 'Scripts')
|
||||
bin_user = os.path.join(user_site, 'Scripts')
|
||||
@@ -111,38 +78,15 @@ if WINDOWS:
|
||||
if not os.path.exists(bin_py):
|
||||
bin_py = os.path.join(sys.prefix, 'bin')
|
||||
bin_user = os.path.join(user_site, 'bin')
|
||||
|
||||
config_basename = 'pip.ini'
|
||||
|
||||
legacy_storage_dir = os.path.join(user_dir, 'pip')
|
||||
legacy_config_file = os.path.join(
|
||||
legacy_storage_dir,
|
||||
config_basename,
|
||||
)
|
||||
else:
|
||||
bin_py = os.path.join(sys.prefix, 'bin')
|
||||
bin_user = os.path.join(user_site, 'bin')
|
||||
|
||||
config_basename = 'pip.conf'
|
||||
|
||||
legacy_storage_dir = os.path.join(user_dir, '.pip')
|
||||
legacy_config_file = os.path.join(
|
||||
legacy_storage_dir,
|
||||
config_basename,
|
||||
)
|
||||
# Forcing to use /usr/local/bin for standard macOS framework installs
|
||||
# Also log to ~/Library/Logs/ for use with the Console.app log viewer
|
||||
if sys.platform[:6] == 'darwin' and sys.prefix[:16] == '/System/Library/':
|
||||
bin_py = '/usr/local/bin'
|
||||
|
||||
site_config_files = [
|
||||
os.path.join(path, config_basename)
|
||||
for path in appdirs.site_config_dirs('pip')
|
||||
]
|
||||
|
||||
venv_config_file = os.path.join(sys.prefix, config_basename)
|
||||
new_config_file = os.path.join(appdirs.user_config_dir("pip"), config_basename)
|
||||
|
||||
|
||||
def distutils_scheme(dist_name, user=False, home=None, root=None,
|
||||
isolated=False, prefix=None):
|
||||
@@ -171,8 +115,9 @@ def distutils_scheme(dist_name, user=False, home=None, root=None,
|
||||
# or user base for installations during finalize_options()
|
||||
# ideally, we'd prefer a scheme class that has no side-effects.
|
||||
assert not (user and prefix), "user={} prefix={}".format(user, prefix)
|
||||
assert not (home and prefix), "home={} prefix={}".format(home, prefix)
|
||||
i.user = user or i.user
|
||||
if user:
|
||||
if user or home:
|
||||
i.prefix = ""
|
||||
i.prefix = prefix or i.prefix
|
||||
i.home = home or i.home
|
||||
@@ -196,7 +141,7 @@ def distutils_scheme(dist_name, user=False, home=None, root=None,
|
||||
sys.prefix,
|
||||
'include',
|
||||
'site',
|
||||
'python' + sys.version[:3],
|
||||
'python{}'.format(get_major_minor_version()),
|
||||
dist_name,
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
"""Primary application entrypoint.
|
||||
"""
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import locale
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
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 create_command
|
||||
from pipenv.patched.notpip._internal.exceptions import PipError
|
||||
from pipenv.patched.notpip._internal.utils import deprecation
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def main(args=None):
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
|
||||
# Configure our deprecation warnings to be sent through loggers
|
||||
deprecation.install_warning_logger()
|
||||
|
||||
autocomplete()
|
||||
|
||||
try:
|
||||
cmd_name, cmd_args = parse_command(args)
|
||||
except PipError as exc:
|
||||
sys.stderr.write("ERROR: %s" % exc)
|
||||
sys.stderr.write(os.linesep)
|
||||
sys.exit(1)
|
||||
|
||||
# Needed for locale.getpreferredencoding(False) to work
|
||||
# in pip._internal.utils.encoding.auto_decode
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
except locale.Error as e:
|
||||
# setlocale can apparently crash if locale are uninitialized
|
||||
logger.debug("Ignoring error %s when setting locale", e)
|
||||
command = create_command(cmd_name, isolated=("--isolated" in cmd_args))
|
||||
|
||||
return command.main(cmd_args)
|
||||
@@ -1,32 +1,40 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from pipenv.patched.notpip._vendor.packaging.version import parse as parse_version
|
||||
|
||||
from pipenv.patched.notpip._internal.utils.models import KeyBasedCompareMixin
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from pipenv.patched.notpip._vendor.packaging.version import _BaseVersion # noqa: F401
|
||||
from pipenv.patched.notpip._internal.models.link import Link # noqa: F401
|
||||
from typing import Any, Union # noqa: F401
|
||||
from pipenv.patched.notpip._vendor.packaging.version import _BaseVersion
|
||||
from pipenv.patched.notpip._internal.models.link import Link
|
||||
from typing import Any
|
||||
|
||||
|
||||
class InstallationCandidate(KeyBasedCompareMixin):
|
||||
"""Represents a potential "candidate" for installation.
|
||||
"""
|
||||
|
||||
def __init__(self, project, version, location, requires_python=None):
|
||||
def __init__(self, project, version, link, requires_python=None):
|
||||
# type: (Any, str, Link, Any) -> None
|
||||
self.project = project
|
||||
self.version = parse_version(version) # type: _BaseVersion
|
||||
self.location = location
|
||||
self.link = link
|
||||
self.requires_python = requires_python
|
||||
|
||||
super(InstallationCandidate, self).__init__(
|
||||
key=(self.project, self.version, self.location),
|
||||
key=(self.project, self.version, self.link),
|
||||
defining_class=InstallationCandidate
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return "<InstallationCandidate({!r}, {!r}, {!r})>".format(
|
||||
self.project, self.version, self.location,
|
||||
self.project, self.version, self.link,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return '{!r} candidate (version {} at {})'.format(
|
||||
self.project, self.version, self.link,
|
||||
)
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
from pipenv.patched.notpip._internal.exceptions import CommandError
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Optional, Set, FrozenSet # noqa: F401
|
||||
from typing import Optional, Set, FrozenSet
|
||||
|
||||
|
||||
class FormatControl(object):
|
||||
@@ -36,6 +41,10 @@ class FormatControl(object):
|
||||
@staticmethod
|
||||
def handle_mutual_excludes(value, target, other):
|
||||
# type: (str, Optional[Set], Optional[Set]) -> None
|
||||
if value.startswith('-'):
|
||||
raise CommandError(
|
||||
"--no-binary / --only-binary option requires 1 argument."
|
||||
)
|
||||
new = value.split(',')
|
||||
while ':all:' in new:
|
||||
other.clear()
|
||||
|
||||
@@ -1,49 +1,70 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
import os
|
||||
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.filetypes import WHEEL_EXTENSION
|
||||
from pipenv.patched.notpip._internal.utils.misc import (
|
||||
WHEEL_EXTENSION, redact_password_from_url, splitext,
|
||||
redact_auth_from_url,
|
||||
split_auth_from_netloc,
|
||||
splitext,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.models import KeyBasedCompareMixin
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
from pipenv.patched.notpip._internal.utils.urls import path_to_url, url_to_path
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Optional, Tuple, Union, Text # noqa: F401
|
||||
from pipenv.patched.notpip._internal.index import HTMLPage # noqa: F401
|
||||
from typing import Optional, Text, Tuple, Union
|
||||
from pipenv.patched.notpip._internal.collector import HTMLPage
|
||||
from pipenv.patched.notpip._internal.utils.hashes import Hashes
|
||||
|
||||
|
||||
class Link(KeyBasedCompareMixin):
|
||||
"""Represents a parsed link from a Package Index's simple URL
|
||||
"""
|
||||
|
||||
def __init__(self, url, comes_from=None, requires_python=None):
|
||||
# type: (str, Optional[Union[str, HTMLPage]], Optional[str]) -> None
|
||||
def __init__(
|
||||
self,
|
||||
url, # type: str
|
||||
comes_from=None, # type: Optional[Union[str, HTMLPage]]
|
||||
requires_python=None, # type: Optional[str]
|
||||
yanked_reason=None, # type: Optional[Text]
|
||||
):
|
||||
# type: (...) -> 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.
|
||||
:param url: url of the resource pointed to (href of the link)
|
||||
:param comes_from: instance of HTMLPage where the link was found,
|
||||
or string.
|
||||
:param 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.
|
||||
:param yanked_reason: the reason the file has been yanked, if the
|
||||
file has been yanked, or None if the file hasn't been yanked.
|
||||
This is the value of the "data-yanked" attribute, if present, in
|
||||
a simple repository HTML link. If the file has been yanked but
|
||||
no reason was provided, this should be the empty string. See
|
||||
PEP 592 for more information and the specification.
|
||||
"""
|
||||
|
||||
# url can be a UNC windows share
|
||||
if url.startswith('\\\\'):
|
||||
url = path_to_url(url)
|
||||
|
||||
self.url = url
|
||||
self._parsed_url = urllib_parse.urlsplit(url)
|
||||
# Store the url as a private attribute to prevent accidentally
|
||||
# trying to set a new value.
|
||||
self._url = url
|
||||
|
||||
self.comes_from = comes_from
|
||||
self.requires_python = requires_python if requires_python else None
|
||||
self.yanked_reason = yanked_reason
|
||||
|
||||
super(Link, self).__init__(
|
||||
key=(self.url),
|
||||
defining_class=Link
|
||||
)
|
||||
super(Link, self).__init__(key=url, defining_class=Link)
|
||||
|
||||
def __str__(self):
|
||||
if self.requires_python:
|
||||
@@ -51,37 +72,56 @@ class Link(KeyBasedCompareMixin):
|
||||
else:
|
||||
rp = ''
|
||||
if self.comes_from:
|
||||
return '%s (from %s)%s' % (redact_password_from_url(self.url),
|
||||
return '%s (from %s)%s' % (redact_auth_from_url(self._url),
|
||||
self.comes_from, rp)
|
||||
else:
|
||||
return redact_password_from_url(str(self.url))
|
||||
return redact_auth_from_url(str(self._url))
|
||||
|
||||
def __repr__(self):
|
||||
return '<Link %s>' % self
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
# type: () -> str
|
||||
return self._url
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
# type: () -> str
|
||||
_, netloc, path, _, _ = urllib_parse.urlsplit(self.url)
|
||||
name = posixpath.basename(path.rstrip('/')) or netloc
|
||||
path = self.path.rstrip('/')
|
||||
name = posixpath.basename(path)
|
||||
if not name:
|
||||
# Make sure we don't leak auth information if the netloc
|
||||
# includes a username and password.
|
||||
netloc, user_pass = split_auth_from_netloc(self.netloc)
|
||||
return netloc
|
||||
|
||||
name = urllib_parse.unquote(name)
|
||||
assert name, ('URL %r produced no filename' % self.url)
|
||||
assert name, ('URL %r produced no filename' % self._url)
|
||||
return name
|
||||
|
||||
@property
|
||||
def file_path(self):
|
||||
# type: () -> str
|
||||
return url_to_path(self.url)
|
||||
|
||||
@property
|
||||
def scheme(self):
|
||||
# type: () -> str
|
||||
return urllib_parse.urlsplit(self.url)[0]
|
||||
return self._parsed_url.scheme
|
||||
|
||||
@property
|
||||
def netloc(self):
|
||||
# type: () -> str
|
||||
return urllib_parse.urlsplit(self.url)[1]
|
||||
"""
|
||||
This can contain auth information.
|
||||
"""
|
||||
return self._parsed_url.netloc
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
# type: () -> str
|
||||
return urllib_parse.unquote(urllib_parse.urlsplit(self.url)[2])
|
||||
return urllib_parse.unquote(self._parsed_url.path)
|
||||
|
||||
def splitext(self):
|
||||
# type: () -> Tuple[str, str]
|
||||
@@ -95,7 +135,7 @@ class Link(KeyBasedCompareMixin):
|
||||
@property
|
||||
def url_without_fragment(self):
|
||||
# type: () -> str
|
||||
scheme, netloc, path, query, fragment = urllib_parse.urlsplit(self.url)
|
||||
scheme, netloc, path, query, fragment = self._parsed_url
|
||||
return urllib_parse.urlunsplit((scheme, netloc, path, query, None))
|
||||
|
||||
_egg_fragment_re = re.compile(r'[#&]egg=([^&]*)')
|
||||
@@ -103,7 +143,7 @@ class Link(KeyBasedCompareMixin):
|
||||
@property
|
||||
def egg_fragment(self):
|
||||
# type: () -> Optional[str]
|
||||
match = self._egg_fragment_re.search(self.url)
|
||||
match = self._egg_fragment_re.search(self._url)
|
||||
if not match:
|
||||
return None
|
||||
return match.group(1)
|
||||
@@ -113,7 +153,7 @@ class Link(KeyBasedCompareMixin):
|
||||
@property
|
||||
def subdirectory_fragment(self):
|
||||
# type: () -> Optional[str]
|
||||
match = self._subdirectory_fragment_re.search(self.url)
|
||||
match = self._subdirectory_fragment_re.search(self._url)
|
||||
if not match:
|
||||
return None
|
||||
return match.group(1)
|
||||
@@ -125,7 +165,7 @@ class Link(KeyBasedCompareMixin):
|
||||
@property
|
||||
def hash(self):
|
||||
# type: () -> Optional[str]
|
||||
match = self._hash_re.search(self.url)
|
||||
match = self._hash_re.search(self._url)
|
||||
if match:
|
||||
return match.group(2)
|
||||
return None
|
||||
@@ -133,7 +173,7 @@ class Link(KeyBasedCompareMixin):
|
||||
@property
|
||||
def hash_name(self):
|
||||
# type: () -> Optional[str]
|
||||
match = self._hash_re.search(self.url)
|
||||
match = self._hash_re.search(self._url)
|
||||
if match:
|
||||
return match.group(1)
|
||||
return None
|
||||
@@ -141,7 +181,16 @@ class Link(KeyBasedCompareMixin):
|
||||
@property
|
||||
def show_url(self):
|
||||
# type: () -> Optional[str]
|
||||
return posixpath.basename(self.url.split('#', 1)[0].split('?', 1)[0])
|
||||
return posixpath.basename(self._url.split('#', 1)[0].split('?', 1)[0])
|
||||
|
||||
@property
|
||||
def is_file(self):
|
||||
# type: () -> bool
|
||||
return self.scheme == 'file'
|
||||
|
||||
def is_existing_dir(self):
|
||||
# type: () -> bool
|
||||
return self.is_file and os.path.isdir(self.file_path)
|
||||
|
||||
@property
|
||||
def is_wheel(self):
|
||||
@@ -149,15 +198,30 @@ class Link(KeyBasedCompareMixin):
|
||||
return self.ext == WHEEL_EXTENSION
|
||||
|
||||
@property
|
||||
def is_artifact(self):
|
||||
def is_vcs(self):
|
||||
# type: () -> bool
|
||||
"""
|
||||
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 self.scheme in vcs.all_schemes
|
||||
|
||||
return True
|
||||
@property
|
||||
def is_yanked(self):
|
||||
# type: () -> bool
|
||||
return self.yanked_reason is not None
|
||||
|
||||
@property
|
||||
def has_hash(self):
|
||||
return self.hash_name is not None
|
||||
|
||||
def is_hash_allowed(self, hashes):
|
||||
# type: (Optional[Hashes]) -> bool
|
||||
"""
|
||||
Return True if the link has a hash and it is allowed.
|
||||
"""
|
||||
if hashes is None or not self.has_hash:
|
||||
return False
|
||||
# Assert non-None so mypy knows self.hash_name and self.hash are str.
|
||||
assert self.hash_name is not None
|
||||
assert self.hash is not None
|
||||
|
||||
return hashes.is_hash_allowed(self.hash_name, hex_digest=self.hash)
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
import itertools
|
||||
import logging
|
||||
import os
|
||||
import posixpath
|
||||
|
||||
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
|
||||
from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse
|
||||
|
||||
from pipenv.patched.notpip._internal.models.index import PyPI
|
||||
from pipenv.patched.notpip._internal.utils.compat import HAS_TLS
|
||||
from pipenv.patched.notpip._internal.utils.misc import normalize_path, redact_auth_from_url
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import List
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SearchScope(object):
|
||||
|
||||
"""
|
||||
Encapsulates the locations that pip is configured to search.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
find_links, # type: List[str]
|
||||
index_urls, # type: List[str]
|
||||
):
|
||||
# type: (...) -> SearchScope
|
||||
"""
|
||||
Create a SearchScope object after normalizing the `find_links`.
|
||||
"""
|
||||
# Build find_links. If an argument starts with ~, it may be
|
||||
# a local file relative to a home directory. So try normalizing
|
||||
# it and if it exists, use the normalized version.
|
||||
# This is deliberately conservative - it might be fine just to
|
||||
# blindly normalize anything starting with a ~...
|
||||
built_find_links = [] # type: List[str]
|
||||
for link in find_links:
|
||||
if link.startswith('~'):
|
||||
new_link = normalize_path(link)
|
||||
if os.path.exists(new_link):
|
||||
link = new_link
|
||||
built_find_links.append(link)
|
||||
|
||||
# If we don't have TLS enabled, then WARN if anyplace we're looking
|
||||
# relies on TLS.
|
||||
if not HAS_TLS:
|
||||
for link in itertools.chain(index_urls, built_find_links):
|
||||
parsed = urllib_parse.urlparse(link)
|
||||
if parsed.scheme == 'https':
|
||||
logger.warning(
|
||||
'pip is configured with locations that require '
|
||||
'TLS/SSL, however the ssl module in Python is not '
|
||||
'available.'
|
||||
)
|
||||
break
|
||||
|
||||
return cls(
|
||||
find_links=built_find_links,
|
||||
index_urls=index_urls,
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
find_links, # type: List[str]
|
||||
index_urls, # type: List[str]
|
||||
):
|
||||
# type: (...) -> None
|
||||
self.find_links = find_links
|
||||
self.index_urls = index_urls
|
||||
|
||||
def get_formatted_locations(self):
|
||||
# type: () -> str
|
||||
lines = []
|
||||
if self.index_urls and self.index_urls != [PyPI.simple_url]:
|
||||
lines.append(
|
||||
'Looking in indexes: {}'.format(', '.join(
|
||||
redact_auth_from_url(url) for url in self.index_urls))
|
||||
)
|
||||
if self.find_links:
|
||||
lines.append(
|
||||
'Looking in links: {}'.format(', '.join(
|
||||
redact_auth_from_url(url) for url in self.find_links))
|
||||
)
|
||||
return '\n'.join(lines)
|
||||
|
||||
def get_index_urls_locations(self, project_name):
|
||||
# type: (str) -> List[str]
|
||||
"""Returns the locations found via self.index_urls
|
||||
|
||||
Checks the url_name on the main (first in the list) index and
|
||||
use this url_name to produce all locations
|
||||
"""
|
||||
|
||||
def mkurl_pypi_url(url):
|
||||
loc = posixpath.join(
|
||||
url,
|
||||
urllib_parse.quote(canonicalize_name(project_name)))
|
||||
# For maximum compatibility with easy_install, ensure the path
|
||||
# ends in a trailing slash. Although this isn't in the spec
|
||||
# (and PyPI can handle it without the slash) some other index
|
||||
# implementations might break if they relied on easy_install's
|
||||
# behavior.
|
||||
if not loc.endswith('/'):
|
||||
loc = loc + '/'
|
||||
return loc
|
||||
|
||||
return [mkurl_pypi_url(url) for url in self.index_urls]
|
||||
@@ -0,0 +1,47 @@
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Optional
|
||||
from pipenv.patched.notpip._internal.models.format_control import FormatControl
|
||||
|
||||
|
||||
class SelectionPreferences(object):
|
||||
|
||||
"""
|
||||
Encapsulates the candidate selection preferences for downloading
|
||||
and installing files.
|
||||
"""
|
||||
|
||||
# Don't include an allow_yanked default value to make sure each call
|
||||
# site considers whether yanked releases are allowed. This also causes
|
||||
# that decision to be made explicit in the calling code, which helps
|
||||
# people when reading the code.
|
||||
def __init__(
|
||||
self,
|
||||
allow_yanked, # type: bool
|
||||
allow_all_prereleases=False, # type: bool
|
||||
format_control=None, # type: Optional[FormatControl]
|
||||
prefer_binary=False, # type: bool
|
||||
ignore_requires_python=None, # type: Optional[bool]
|
||||
):
|
||||
# type: (...) -> None
|
||||
"""Create a SelectionPreferences object.
|
||||
|
||||
:param allow_yanked: Whether files marked as yanked (in the sense
|
||||
of PEP 592) are permitted to be candidates for install.
|
||||
:param format_control: A FormatControl object or None. Used to control
|
||||
the selection of source packages / binary packages when consulting
|
||||
the index and links.
|
||||
:param prefer_binary: Whether to prefer an old, but valid, binary
|
||||
dist over a new source dist.
|
||||
:param ignore_requires_python: Whether to ignore incompatible
|
||||
"Requires-Python" values in links. Defaults to False.
|
||||
"""
|
||||
if ignore_requires_python is None:
|
||||
ignore_requires_python = False
|
||||
|
||||
self.allow_yanked = allow_yanked
|
||||
self.allow_all_prereleases = allow_all_prereleases
|
||||
self.format_control = format_control
|
||||
self.prefer_binary = prefer_binary
|
||||
self.ignore_requires_python = ignore_requires_python
|
||||
@@ -0,0 +1,106 @@
|
||||
import sys
|
||||
|
||||
from pipenv.patched.notpip._internal.pep425tags import get_supported, version_info_to_nodot
|
||||
from pipenv.patched.notpip._internal.utils.misc import normalize_version_info
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import List, Optional, Tuple
|
||||
from pipenv.patched.notpip._internal.pep425tags import Pep425Tag
|
||||
|
||||
|
||||
class TargetPython(object):
|
||||
|
||||
"""
|
||||
Encapsulates the properties of a Python interpreter one is targeting
|
||||
for a package install, download, etc.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
platform=None, # type: Optional[str]
|
||||
py_version_info=None, # type: Optional[Tuple[int, ...]]
|
||||
abi=None, # type: Optional[str]
|
||||
implementation=None, # type: Optional[str]
|
||||
):
|
||||
# type: (...) -> None
|
||||
"""
|
||||
:param platform: A string or None. If None, searches for packages
|
||||
that are supported by the current system. Otherwise, will find
|
||||
packages that can be built on the platform passed in. These
|
||||
packages will only be downloaded for distribution: they will
|
||||
not be built locally.
|
||||
:param py_version_info: An optional tuple of ints representing the
|
||||
Python version information to use (e.g. `sys.version_info[:3]`).
|
||||
This can have length 1, 2, or 3 when provided.
|
||||
:param abi: A string or None. This is passed to pep425tags.py's
|
||||
get_supported() function as is.
|
||||
:param implementation: A string or None. This is passed to
|
||||
pep425tags.py's get_supported() function as is.
|
||||
"""
|
||||
# Store the given py_version_info for when we call get_supported().
|
||||
self._given_py_version_info = py_version_info
|
||||
|
||||
if py_version_info is None:
|
||||
py_version_info = sys.version_info[:3]
|
||||
else:
|
||||
py_version_info = normalize_version_info(py_version_info)
|
||||
|
||||
py_version = '.'.join(map(str, py_version_info[:2]))
|
||||
|
||||
self.abi = abi
|
||||
self.implementation = implementation
|
||||
self.platform = platform
|
||||
self.py_version = py_version
|
||||
self.py_version_info = py_version_info
|
||||
|
||||
# This is used to cache the return value of get_tags().
|
||||
self._valid_tags = None # type: Optional[List[Pep425Tag]]
|
||||
|
||||
def format_given(self):
|
||||
# type: () -> str
|
||||
"""
|
||||
Format the given, non-None attributes for display.
|
||||
"""
|
||||
display_version = None
|
||||
if self._given_py_version_info is not None:
|
||||
display_version = '.'.join(
|
||||
str(part) for part in self._given_py_version_info
|
||||
)
|
||||
|
||||
key_values = [
|
||||
('platform', self.platform),
|
||||
('version_info', display_version),
|
||||
('abi', self.abi),
|
||||
('implementation', self.implementation),
|
||||
]
|
||||
return ' '.join(
|
||||
'{}={!r}'.format(key, value) for key, value in key_values
|
||||
if value is not None
|
||||
)
|
||||
|
||||
def get_tags(self):
|
||||
# type: () -> List[Pep425Tag]
|
||||
"""
|
||||
Return the supported PEP 425 tags to check wheel candidates against.
|
||||
|
||||
The tags are returned in order of preference (most preferred first).
|
||||
"""
|
||||
if self._valid_tags is None:
|
||||
# Pass versions=None if no py_version_info was given since
|
||||
# versions=None uses special default logic.
|
||||
py_version_info = self._given_py_version_info
|
||||
if py_version_info is None:
|
||||
versions = None
|
||||
else:
|
||||
versions = [version_info_to_nodot(py_version_info)]
|
||||
|
||||
tags = get_supported(
|
||||
versions=versions,
|
||||
platform=self.platform,
|
||||
abi=self.abi,
|
||||
impl=self.implementation,
|
||||
)
|
||||
self._valid_tags = tags
|
||||
|
||||
return self._valid_tags
|
||||
@@ -0,0 +1,2 @@
|
||||
"""Contains purely network-related utilities.
|
||||
"""
|
||||
@@ -0,0 +1,298 @@
|
||||
"""Network Authentication Helpers
|
||||
|
||||
Contains interface (MultiDomainBasicAuth) and associated glue code for
|
||||
providing credentials in the context of network requests.
|
||||
"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
import logging
|
||||
|
||||
from pipenv.patched.notpip._vendor.requests.auth import AuthBase, HTTPBasicAuth
|
||||
from pipenv.patched.notpip._vendor.requests.utils import get_netrc_auth
|
||||
from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse
|
||||
|
||||
from pipenv.patched.notpip._internal.utils.misc import (
|
||||
ask,
|
||||
ask_input,
|
||||
ask_password,
|
||||
remove_auth_from_url,
|
||||
split_auth_netloc_from_url,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from optparse import Values
|
||||
from typing import Dict, Optional, Tuple
|
||||
|
||||
from pipenv.patched.notpip._internal.vcs.versioncontrol import AuthInfo
|
||||
|
||||
Credentials = Tuple[str, str, str]
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import keyring # noqa
|
||||
except ImportError:
|
||||
keyring = None
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"Keyring is skipped due to an exception: %s", str(exc),
|
||||
)
|
||||
keyring = None
|
||||
|
||||
|
||||
def get_keyring_auth(url, username):
|
||||
"""Return the tuple auth for a given url from keyring."""
|
||||
if not url or not keyring:
|
||||
return None
|
||||
|
||||
try:
|
||||
try:
|
||||
get_credential = keyring.get_credential
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
logger.debug("Getting credentials from keyring for %s", url)
|
||||
cred = get_credential(url, username)
|
||||
if cred is not None:
|
||||
return cred.username, cred.password
|
||||
return None
|
||||
|
||||
if username:
|
||||
logger.debug("Getting password from keyring for %s", url)
|
||||
password = keyring.get_password(url, username)
|
||||
if password:
|
||||
return username, password
|
||||
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"Keyring is skipped due to an exception: %s", str(exc),
|
||||
)
|
||||
|
||||
|
||||
class MultiDomainBasicAuth(AuthBase):
|
||||
|
||||
def __init__(self, prompting=True, index_urls=None):
|
||||
# type: (bool, Optional[Values]) -> None
|
||||
self.prompting = prompting
|
||||
self.index_urls = index_urls
|
||||
self.passwords = {} # type: Dict[str, AuthInfo]
|
||||
# When the user is prompted to enter credentials and keyring is
|
||||
# available, we will offer to save them. If the user accepts,
|
||||
# this value is set to the credentials they entered. After the
|
||||
# request authenticates, the caller should call
|
||||
# ``save_credentials`` to save these.
|
||||
self._credentials_to_save = None # type: Optional[Credentials]
|
||||
|
||||
def _get_index_url(self, url):
|
||||
"""Return the original index URL matching the requested URL.
|
||||
|
||||
Cached or dynamically generated credentials may work against
|
||||
the original index URL rather than just the netloc.
|
||||
|
||||
The provided url should have had its username and password
|
||||
removed already. If the original index url had credentials then
|
||||
they will be included in the return value.
|
||||
|
||||
Returns None if no matching index was found, or if --no-index
|
||||
was specified by the user.
|
||||
"""
|
||||
if not url or not self.index_urls:
|
||||
return None
|
||||
|
||||
for u in self.index_urls:
|
||||
prefix = remove_auth_from_url(u).rstrip("/") + "/"
|
||||
if url.startswith(prefix):
|
||||
return u
|
||||
|
||||
def _get_new_credentials(self, original_url, allow_netrc=True,
|
||||
allow_keyring=True):
|
||||
"""Find and return credentials for the specified URL."""
|
||||
# Split the credentials and netloc from the url.
|
||||
url, netloc, url_user_password = split_auth_netloc_from_url(
|
||||
original_url,
|
||||
)
|
||||
|
||||
# Start with the credentials embedded in the url
|
||||
username, password = url_user_password
|
||||
if username is not None and password is not None:
|
||||
logger.debug("Found credentials in url for %s", netloc)
|
||||
return url_user_password
|
||||
|
||||
# Find a matching index url for this request
|
||||
index_url = self._get_index_url(url)
|
||||
if index_url:
|
||||
# Split the credentials from the url.
|
||||
index_info = split_auth_netloc_from_url(index_url)
|
||||
if index_info:
|
||||
index_url, _, index_url_user_password = index_info
|
||||
logger.debug("Found index url %s", index_url)
|
||||
|
||||
# If an index URL was found, try its embedded credentials
|
||||
if index_url and index_url_user_password[0] is not None:
|
||||
username, password = index_url_user_password
|
||||
if username is not None and password is not None:
|
||||
logger.debug("Found credentials in index url for %s", netloc)
|
||||
return index_url_user_password
|
||||
|
||||
# Get creds from netrc if we still don't have them
|
||||
if allow_netrc:
|
||||
netrc_auth = get_netrc_auth(original_url)
|
||||
if netrc_auth:
|
||||
logger.debug("Found credentials in netrc for %s", netloc)
|
||||
return netrc_auth
|
||||
|
||||
# If we don't have a password and keyring is available, use it.
|
||||
if allow_keyring:
|
||||
# The index url is more specific than the netloc, so try it first
|
||||
kr_auth = (
|
||||
get_keyring_auth(index_url, username) or
|
||||
get_keyring_auth(netloc, username)
|
||||
)
|
||||
if kr_auth:
|
||||
logger.debug("Found credentials in keyring for %s", netloc)
|
||||
return kr_auth
|
||||
|
||||
return username, password
|
||||
|
||||
def _get_url_and_credentials(self, original_url):
|
||||
"""Return the credentials to use for the provided URL.
|
||||
|
||||
If allowed, netrc and keyring may be used to obtain the
|
||||
correct credentials.
|
||||
|
||||
Returns (url_without_credentials, username, password). Note
|
||||
that even if the original URL contains credentials, this
|
||||
function may return a different username and password.
|
||||
"""
|
||||
url, netloc, _ = split_auth_netloc_from_url(original_url)
|
||||
|
||||
# Use any stored credentials that we have for this netloc
|
||||
username, password = self.passwords.get(netloc, (None, None))
|
||||
|
||||
if username is None and password is None:
|
||||
# No stored credentials. Acquire new credentials without prompting
|
||||
# the user. (e.g. from netrc, keyring, or the URL itself)
|
||||
username, password = self._get_new_credentials(original_url)
|
||||
|
||||
if username is not None or password is not None:
|
||||
# Convert the username and password if they're None, so that
|
||||
# this netloc will show up as "cached" in the conditional above.
|
||||
# Further, HTTPBasicAuth doesn't accept None, so it makes sense to
|
||||
# cache the value that is going to be used.
|
||||
username = username or ""
|
||||
password = password or ""
|
||||
|
||||
# Store any acquired credentials.
|
||||
self.passwords[netloc] = (username, password)
|
||||
|
||||
assert (
|
||||
# Credentials were found
|
||||
(username is not None and password is not None) or
|
||||
# Credentials were not found
|
||||
(username is None and password is None)
|
||||
), "Could not load credentials from url: {}".format(original_url)
|
||||
|
||||
return url, username, password
|
||||
|
||||
def __call__(self, req):
|
||||
# Get credentials for this request
|
||||
url, username, password = self._get_url_and_credentials(req.url)
|
||||
|
||||
# Set the url of the request to the url without any credentials
|
||||
req.url = url
|
||||
|
||||
if username is not None and password is not None:
|
||||
# Send the basic auth with this request
|
||||
req = HTTPBasicAuth(username, password)(req)
|
||||
|
||||
# Attach a hook to handle 401 responses
|
||||
req.register_hook("response", self.handle_401)
|
||||
|
||||
return req
|
||||
|
||||
# Factored out to allow for easy patching in tests
|
||||
def _prompt_for_password(self, netloc):
|
||||
username = ask_input("User for %s: " % netloc)
|
||||
if not username:
|
||||
return None, None
|
||||
auth = get_keyring_auth(netloc, username)
|
||||
if auth:
|
||||
return auth[0], auth[1], False
|
||||
password = ask_password("Password: ")
|
||||
return username, password, True
|
||||
|
||||
# Factored out to allow for easy patching in tests
|
||||
def _should_save_password_to_keyring(self):
|
||||
if not keyring:
|
||||
return False
|
||||
return ask("Save credentials to keyring [y/N]: ", ["y", "n"]) == "y"
|
||||
|
||||
def handle_401(self, resp, **kwargs):
|
||||
# We only care about 401 responses, anything else we want to just
|
||||
# pass through the actual response
|
||||
if resp.status_code != 401:
|
||||
return resp
|
||||
|
||||
# We are not able to prompt the user so simply return the response
|
||||
if not self.prompting:
|
||||
return resp
|
||||
|
||||
parsed = urllib_parse.urlparse(resp.url)
|
||||
|
||||
# Prompt the user for a new username and password
|
||||
username, password, save = self._prompt_for_password(parsed.netloc)
|
||||
|
||||
# Store the new username and password to use for future requests
|
||||
self._credentials_to_save = None
|
||||
if username is not None and password is not None:
|
||||
self.passwords[parsed.netloc] = (username, password)
|
||||
|
||||
# Prompt to save the password to keyring
|
||||
if save and self._should_save_password_to_keyring():
|
||||
self._credentials_to_save = (parsed.netloc, username, password)
|
||||
|
||||
# Consume content and release the original connection to allow our new
|
||||
# request to reuse the same one.
|
||||
resp.content
|
||||
resp.raw.release_conn()
|
||||
|
||||
# Add our new username and password to the request
|
||||
req = HTTPBasicAuth(username or "", password or "")(resp.request)
|
||||
req.register_hook("response", self.warn_on_401)
|
||||
|
||||
# On successful request, save the credentials that were used to
|
||||
# keyring. (Note that if the user responded "no" above, this member
|
||||
# is not set and nothing will be saved.)
|
||||
if self._credentials_to_save:
|
||||
req.register_hook("response", self.save_credentials)
|
||||
|
||||
# Send our new request
|
||||
new_resp = resp.connection.send(req, **kwargs)
|
||||
new_resp.history.append(resp)
|
||||
|
||||
return new_resp
|
||||
|
||||
def warn_on_401(self, resp, **kwargs):
|
||||
"""Response callback to warn about incorrect credentials."""
|
||||
if resp.status_code == 401:
|
||||
logger.warning(
|
||||
'401 Error, Credentials not correct for %s', resp.request.url,
|
||||
)
|
||||
|
||||
def save_credentials(self, resp, **kwargs):
|
||||
"""Response callback to save credentials on success."""
|
||||
assert keyring is not None, "should never reach here without keyring"
|
||||
if not keyring:
|
||||
return
|
||||
|
||||
creds = self._credentials_to_save
|
||||
self._credentials_to_save = None
|
||||
if creds and resp.status_code < 400:
|
||||
try:
|
||||
logger.info('Saving credentials to keyring')
|
||||
keyring.set_password(*creds)
|
||||
except Exception:
|
||||
logger.exception('Failed to save credentials')
|
||||
@@ -0,0 +1,75 @@
|
||||
"""HTTP cache implementation.
|
||||
"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
import os
|
||||
from contextlib import contextmanager
|
||||
|
||||
from pipenv.patched.notpip._vendor.cachecontrol.cache import BaseCache
|
||||
from pipenv.patched.notpip._vendor.cachecontrol.caches import FileCache
|
||||
|
||||
from pipenv.patched.notpip._internal.utils.filesystem import adjacent_tmp_file, replace
|
||||
from pipenv.patched.notpip._internal.utils.misc import ensure_dir
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@contextmanager
|
||||
def suppressed_cache_errors():
|
||||
"""If we can't access the cache then we can just skip caching and process
|
||||
requests as if caching wasn't enabled.
|
||||
"""
|
||||
try:
|
||||
yield
|
||||
except (OSError, IOError):
|
||||
pass
|
||||
|
||||
|
||||
class SafeFileCache(BaseCache):
|
||||
"""
|
||||
A file based cache which is safe to use even when the target directory may
|
||||
not be accessible or writable.
|
||||
"""
|
||||
|
||||
def __init__(self, directory):
|
||||
# type: (str) -> None
|
||||
assert directory is not None, "Cache directory must not be None."
|
||||
super(SafeFileCache, self).__init__()
|
||||
self.directory = directory
|
||||
|
||||
def _get_cache_path(self, name):
|
||||
# type: (str) -> str
|
||||
# From cachecontrol.caches.file_cache.FileCache._fn, brought into our
|
||||
# class for backwards-compatibility and to avoid using a non-public
|
||||
# method.
|
||||
hashed = FileCache.encode(name)
|
||||
parts = list(hashed[:5]) + [hashed]
|
||||
return os.path.join(self.directory, *parts)
|
||||
|
||||
def get(self, key):
|
||||
# type: (str) -> Optional[bytes]
|
||||
path = self._get_cache_path(key)
|
||||
with suppressed_cache_errors():
|
||||
with open(path, 'rb') as f:
|
||||
return f.read()
|
||||
|
||||
def set(self, key, value):
|
||||
# type: (str, bytes) -> None
|
||||
path = self._get_cache_path(key)
|
||||
with suppressed_cache_errors():
|
||||
ensure_dir(os.path.dirname(path))
|
||||
|
||||
with adjacent_tmp_file(path) as f:
|
||||
f.write(value)
|
||||
|
||||
replace(f.name, path)
|
||||
|
||||
def delete(self, key):
|
||||
# type: (str) -> None
|
||||
path = self._get_cache_path(key)
|
||||
with suppressed_cache_errors():
|
||||
os.remove(path)
|
||||
@@ -0,0 +1,426 @@
|
||||
"""PipSession and supporting code, containing all pip-specific
|
||||
network request configuration and behavior.
|
||||
"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
import email.utils
|
||||
import json
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from pipenv.patched.notpip._vendor import requests, six, urllib3
|
||||
from pipenv.patched.notpip._vendor.cachecontrol import CacheControlAdapter
|
||||
from pipenv.patched.notpip._vendor.requests.adapters import BaseAdapter, HTTPAdapter
|
||||
from pipenv.patched.notpip._vendor.requests.models import Response
|
||||
from pipenv.patched.notpip._vendor.requests.structures import CaseInsensitiveDict
|
||||
from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse
|
||||
from pipenv.patched.notpip._vendor.urllib3.exceptions import InsecureRequestWarning
|
||||
|
||||
from pipenv.patched.notpip import __version__
|
||||
from pipenv.patched.notpip._internal.network.auth import MultiDomainBasicAuth
|
||||
from pipenv.patched.notpip._internal.network.cache import SafeFileCache
|
||||
# Import ssl from compat so the initial import occurs in only one place.
|
||||
from pipenv.patched.notpip._internal.utils.compat import HAS_TLS, ipaddress, ssl
|
||||
from pipenv.patched.notpip._internal.utils.filesystem import check_path_owner
|
||||
from pipenv.patched.notpip._internal.utils.glibc import libc_ver
|
||||
from pipenv.patched.notpip._internal.utils.misc import (
|
||||
build_url_from_netloc,
|
||||
get_installed_version,
|
||||
parse_netloc,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
from pipenv.patched.notpip._internal.utils.urls import url_to_path
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import (
|
||||
Iterator, List, Optional, Tuple, Union,
|
||||
)
|
||||
|
||||
from pipenv.patched.notpip._internal.models.link import Link
|
||||
|
||||
SecureOrigin = Tuple[str, str, Optional[Union[int, str]]]
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Ignore warning raised when using --trusted-host.
|
||||
warnings.filterwarnings("ignore", category=InsecureRequestWarning)
|
||||
|
||||
|
||||
SECURE_ORIGINS = [
|
||||
# protocol, hostname, port
|
||||
# Taken from Chrome's list of secure origins (See: http://bit.ly/1qrySKC)
|
||||
("https", "*", "*"),
|
||||
("*", "localhost", "*"),
|
||||
("*", "127.0.0.0/8", "*"),
|
||||
("*", "::1/128", "*"),
|
||||
("file", "*", None),
|
||||
# ssh is always secure.
|
||||
("ssh", "*", "*"),
|
||||
] # type: List[SecureOrigin]
|
||||
|
||||
|
||||
# These are environment variables present when running under various
|
||||
# CI systems. For each variable, some CI systems that use the variable
|
||||
# are indicated. The collection was chosen so that for each of a number
|
||||
# of popular systems, at least one of the environment variables is used.
|
||||
# This list is used to provide some indication of and lower bound for
|
||||
# CI traffic to PyPI. Thus, it is okay if the list is not comprehensive.
|
||||
# For more background, see: https://github.com/pypa/pip/issues/5499
|
||||
CI_ENVIRONMENT_VARIABLES = (
|
||||
# Azure Pipelines
|
||||
'BUILD_BUILDID',
|
||||
# Jenkins
|
||||
'BUILD_ID',
|
||||
# AppVeyor, CircleCI, Codeship, Gitlab CI, Shippable, Travis CI
|
||||
'CI',
|
||||
# Explicit environment variable.
|
||||
'PIP_IS_CI',
|
||||
)
|
||||
|
||||
|
||||
def looks_like_ci():
|
||||
# type: () -> bool
|
||||
"""
|
||||
Return whether it looks like pip is running under CI.
|
||||
"""
|
||||
# We don't use the method of checking for a tty (e.g. using isatty())
|
||||
# because some CI systems mimic a tty (e.g. Travis CI). Thus that
|
||||
# method doesn't provide definitive information in either direction.
|
||||
return any(name in os.environ for name in CI_ENVIRONMENT_VARIABLES)
|
||||
|
||||
|
||||
def user_agent():
|
||||
"""
|
||||
Return a string representing the user agent.
|
||||
"""
|
||||
data = {
|
||||
"installer": {"name": "pip", "version": __version__},
|
||||
"python": platform.python_version(),
|
||||
"implementation": {
|
||||
"name": platform.python_implementation(),
|
||||
},
|
||||
}
|
||||
|
||||
if data["implementation"]["name"] == 'CPython':
|
||||
data["implementation"]["version"] = platform.python_version()
|
||||
elif data["implementation"]["name"] == 'PyPy':
|
||||
if sys.pypy_version_info.releaselevel == 'final':
|
||||
pypy_version_info = sys.pypy_version_info[:3]
|
||||
else:
|
||||
pypy_version_info = sys.pypy_version_info
|
||||
data["implementation"]["version"] = ".".join(
|
||||
[str(x) for x in pypy_version_info]
|
||||
)
|
||||
elif data["implementation"]["name"] == 'Jython':
|
||||
# Complete Guess
|
||||
data["implementation"]["version"] = platform.python_version()
|
||||
elif data["implementation"]["name"] == 'IronPython':
|
||||
# Complete Guess
|
||||
data["implementation"]["version"] = platform.python_version()
|
||||
|
||||
if sys.platform.startswith("linux"):
|
||||
from pipenv.patched.notpip._vendor import distro
|
||||
distro_infos = dict(filter(
|
||||
lambda x: x[1],
|
||||
zip(["name", "version", "id"], distro.linux_distribution()),
|
||||
))
|
||||
libc = dict(filter(
|
||||
lambda x: x[1],
|
||||
zip(["lib", "version"], libc_ver()),
|
||||
))
|
||||
if libc:
|
||||
distro_infos["libc"] = libc
|
||||
if distro_infos:
|
||||
data["distro"] = distro_infos
|
||||
|
||||
if sys.platform.startswith("darwin") and platform.mac_ver()[0]:
|
||||
data["distro"] = {"name": "macOS", "version": platform.mac_ver()[0]}
|
||||
|
||||
if platform.system():
|
||||
data.setdefault("system", {})["name"] = platform.system()
|
||||
|
||||
if platform.release():
|
||||
data.setdefault("system", {})["release"] = platform.release()
|
||||
|
||||
if platform.machine():
|
||||
data["cpu"] = platform.machine()
|
||||
|
||||
if HAS_TLS:
|
||||
data["openssl_version"] = ssl.OPENSSL_VERSION
|
||||
|
||||
setuptools_version = get_installed_version("setuptools")
|
||||
if setuptools_version is not None:
|
||||
data["setuptools_version"] = setuptools_version
|
||||
|
||||
# Use None rather than False so as not to give the impression that
|
||||
# pip knows it is not being run under CI. Rather, it is a null or
|
||||
# inconclusive result. Also, we include some value rather than no
|
||||
# value to make it easier to know that the check has been run.
|
||||
data["ci"] = True if looks_like_ci() else None
|
||||
|
||||
user_data = os.environ.get("PIP_USER_AGENT_USER_DATA")
|
||||
if user_data is not None:
|
||||
data["user_data"] = user_data
|
||||
|
||||
return "{data[installer][name]}/{data[installer][version]} {json}".format(
|
||||
data=data,
|
||||
json=json.dumps(data, separators=(",", ":"), sort_keys=True),
|
||||
)
|
||||
|
||||
|
||||
class LocalFSAdapter(BaseAdapter):
|
||||
|
||||
def send(self, request, stream=None, timeout=None, verify=None, cert=None,
|
||||
proxies=None):
|
||||
pathname = url_to_path(request.url)
|
||||
|
||||
resp = Response()
|
||||
resp.status_code = 200
|
||||
resp.url = request.url
|
||||
|
||||
try:
|
||||
stats = os.stat(pathname)
|
||||
except OSError as exc:
|
||||
resp.status_code = 404
|
||||
resp.raw = exc
|
||||
else:
|
||||
modified = email.utils.formatdate(stats.st_mtime, usegmt=True)
|
||||
content_type = mimetypes.guess_type(pathname)[0] or "text/plain"
|
||||
resp.headers = CaseInsensitiveDict({
|
||||
"Content-Type": content_type,
|
||||
"Content-Length": stats.st_size,
|
||||
"Last-Modified": modified,
|
||||
})
|
||||
|
||||
resp.raw = open(pathname, "rb")
|
||||
resp.close = resp.raw.close
|
||||
|
||||
return resp
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
|
||||
class InsecureHTTPAdapter(HTTPAdapter):
|
||||
|
||||
def cert_verify(self, conn, url, verify, cert):
|
||||
conn.cert_reqs = 'CERT_NONE'
|
||||
conn.ca_certs = None
|
||||
|
||||
|
||||
class PipSession(requests.Session):
|
||||
|
||||
timeout = None # type: Optional[int]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
:param trusted_hosts: Domains not to emit warnings for when not using
|
||||
HTTPS.
|
||||
"""
|
||||
retries = kwargs.pop("retries", 0)
|
||||
cache = kwargs.pop("cache", None)
|
||||
trusted_hosts = kwargs.pop("trusted_hosts", []) # type: List[str]
|
||||
index_urls = kwargs.pop("index_urls", None)
|
||||
|
||||
super(PipSession, self).__init__(*args, **kwargs)
|
||||
|
||||
# Namespace the attribute with "pip_" just in case to prevent
|
||||
# possible conflicts with the base class.
|
||||
self.pip_trusted_origins = [] # type: List[Tuple[str, Optional[int]]]
|
||||
|
||||
# Attach our User Agent to the request
|
||||
self.headers["User-Agent"] = user_agent()
|
||||
|
||||
# Attach our Authentication handler to the session
|
||||
self.auth = MultiDomainBasicAuth(index_urls=index_urls)
|
||||
|
||||
# Create our urllib3.Retry instance which will allow us to customize
|
||||
# how we handle retries.
|
||||
retries = urllib3.Retry(
|
||||
# Set the total number of retries that a particular request can
|
||||
# have.
|
||||
total=retries,
|
||||
|
||||
# A 503 error from PyPI typically means that the Fastly -> Origin
|
||||
# connection got interrupted in some way. A 503 error in general
|
||||
# is typically considered a transient error so we'll go ahead and
|
||||
# retry it.
|
||||
# A 500 may indicate transient error in Amazon S3
|
||||
# A 520 or 527 - may indicate transient error in CloudFlare
|
||||
status_forcelist=[500, 503, 520, 527],
|
||||
|
||||
# Add a small amount of back off between failed requests in
|
||||
# order to prevent hammering the service.
|
||||
backoff_factor=0.25,
|
||||
)
|
||||
|
||||
# Check to ensure that the directory containing our cache directory
|
||||
# is owned by the user current executing pip. If it does not exist
|
||||
# we will check the parent directory until we find one that does exist.
|
||||
if cache and not check_path_owner(cache):
|
||||
logger.warning(
|
||||
"The directory '%s' or its parent directory is not owned by "
|
||||
"the current user and the cache has been disabled. Please "
|
||||
"check the permissions and owner of that directory. If "
|
||||
"executing pip with sudo, you may want sudo's -H flag.",
|
||||
cache,
|
||||
)
|
||||
cache = None
|
||||
|
||||
# We want to _only_ cache responses on securely fetched origins. We do
|
||||
# this because we can't validate the response of an insecurely fetched
|
||||
# origin, and we don't want someone to be able to poison the cache and
|
||||
# require manual eviction from the cache to fix it.
|
||||
if cache:
|
||||
secure_adapter = CacheControlAdapter(
|
||||
cache=SafeFileCache(cache),
|
||||
max_retries=retries,
|
||||
)
|
||||
else:
|
||||
secure_adapter = HTTPAdapter(max_retries=retries)
|
||||
|
||||
# Our Insecure HTTPAdapter disables HTTPS validation. It does not
|
||||
# support caching (see above) so we'll use it for all http:// URLs as
|
||||
# well as any https:// host that we've marked as ignoring TLS errors
|
||||
# for.
|
||||
insecure_adapter = InsecureHTTPAdapter(max_retries=retries)
|
||||
# Save this for later use in add_insecure_host().
|
||||
self._insecure_adapter = insecure_adapter
|
||||
|
||||
self.mount("https://", secure_adapter)
|
||||
self.mount("http://", insecure_adapter)
|
||||
|
||||
# Enable file:// urls
|
||||
self.mount("file://", LocalFSAdapter())
|
||||
|
||||
for host in trusted_hosts:
|
||||
self.add_trusted_host(host, suppress_logging=True)
|
||||
|
||||
def add_trusted_host(self, host, source=None, suppress_logging=False):
|
||||
# type: (str, Optional[str], bool) -> None
|
||||
"""
|
||||
:param host: It is okay to provide a host that has previously been
|
||||
added.
|
||||
:param source: An optional source string, for logging where the host
|
||||
string came from.
|
||||
"""
|
||||
if not suppress_logging:
|
||||
msg = 'adding trusted host: {!r}'.format(host)
|
||||
if source is not None:
|
||||
msg += ' (from {})'.format(source)
|
||||
logger.info(msg)
|
||||
|
||||
host_port = parse_netloc(host)
|
||||
if host_port not in self.pip_trusted_origins:
|
||||
self.pip_trusted_origins.append(host_port)
|
||||
|
||||
self.mount(build_url_from_netloc(host) + '/', self._insecure_adapter)
|
||||
if not host_port[1]:
|
||||
# Mount wildcard ports for the same host.
|
||||
self.mount(
|
||||
build_url_from_netloc(host) + ':',
|
||||
self._insecure_adapter
|
||||
)
|
||||
|
||||
def iter_secure_origins(self):
|
||||
# type: () -> Iterator[SecureOrigin]
|
||||
for secure_origin in SECURE_ORIGINS:
|
||||
yield secure_origin
|
||||
for host, port in self.pip_trusted_origins:
|
||||
yield ('*', host, '*' if port is None else port)
|
||||
|
||||
def is_secure_origin(self, location):
|
||||
# type: (Link) -> bool
|
||||
# Determine if this url used a secure transport mechanism
|
||||
parsed = urllib_parse.urlparse(str(location))
|
||||
origin_protocol, origin_host, origin_port = (
|
||||
parsed.scheme, parsed.hostname, parsed.port,
|
||||
)
|
||||
|
||||
# The protocol to use to see if the protocol matches.
|
||||
# Don't count the repository type as part of the protocol: in
|
||||
# cases such as "git+ssh", only use "ssh". (I.e., Only verify against
|
||||
# the last scheme.)
|
||||
origin_protocol = origin_protocol.rsplit('+', 1)[-1]
|
||||
|
||||
# Determine if our origin is a secure origin by looking through our
|
||||
# hardcoded list of secure origins, as well as any additional ones
|
||||
# configured on this PackageFinder instance.
|
||||
for secure_origin in self.iter_secure_origins():
|
||||
secure_protocol, secure_host, secure_port = secure_origin
|
||||
if origin_protocol != secure_protocol and secure_protocol != "*":
|
||||
continue
|
||||
|
||||
try:
|
||||
# We need to do this decode dance to ensure that we have a
|
||||
# unicode object, even on Python 2.x.
|
||||
addr = ipaddress.ip_address(
|
||||
origin_host
|
||||
if (
|
||||
isinstance(origin_host, six.text_type) or
|
||||
origin_host is None
|
||||
)
|
||||
else origin_host.decode("utf8")
|
||||
)
|
||||
network = ipaddress.ip_network(
|
||||
secure_host
|
||||
if isinstance(secure_host, six.text_type)
|
||||
# setting secure_host to proper Union[bytes, str]
|
||||
# creates problems in other places
|
||||
else secure_host.decode("utf8") # type: ignore
|
||||
)
|
||||
except ValueError:
|
||||
# We don't have both a valid address or a valid network, so
|
||||
# we'll check this origin against hostnames.
|
||||
if (
|
||||
origin_host and
|
||||
origin_host.lower() != secure_host.lower() and
|
||||
secure_host != "*"
|
||||
):
|
||||
continue
|
||||
else:
|
||||
# We have a valid address and network, so see if the address
|
||||
# is contained within the network.
|
||||
if addr not in network:
|
||||
continue
|
||||
|
||||
# Check to see if the port matches.
|
||||
if (
|
||||
origin_port != secure_port and
|
||||
secure_port != "*" and
|
||||
secure_port is not None
|
||||
):
|
||||
continue
|
||||
|
||||
# If we've gotten here, then this origin matches the current
|
||||
# secure origin and we should return True
|
||||
return True
|
||||
|
||||
# If we've gotten to this point, then the origin isn't secure and we
|
||||
# will not accept it as a valid location to search. We will however
|
||||
# log a warning that we are ignoring it.
|
||||
logger.warning(
|
||||
"The repository located at %s is not a trusted or secure host and "
|
||||
"is being ignored. If this repository is available via HTTPS we "
|
||||
"recommend you use HTTPS instead, otherwise you may silence "
|
||||
"this warning and allow it anyway with '--trusted-host %s'.",
|
||||
origin_host,
|
||||
origin_host,
|
||||
)
|
||||
|
||||
return False
|
||||
|
||||
def request(self, method, url, *args, **kwargs):
|
||||
# Allow setting a default timeout on a session
|
||||
kwargs.setdefault("timeout", self.timeout)
|
||||
|
||||
# Dispatch the actual request
|
||||
return super(PipSession, self).request(method, url, *args, **kwargs)
|
||||
@@ -0,0 +1,44 @@
|
||||
"""xmlrpclib.Transport implementation
|
||||
"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
import logging
|
||||
|
||||
from pipenv.patched.notpip._vendor import requests
|
||||
# NOTE: XMLRPC Client is not annotated in typeshed as on 2017-07-17, which is
|
||||
# why we ignore the type on this import
|
||||
from pipenv.patched.notpip._vendor.six.moves import xmlrpc_client # type: ignore
|
||||
from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PipXmlrpcTransport(xmlrpc_client.Transport):
|
||||
"""Provide a `xmlrpclib.Transport` implementation via a `PipSession`
|
||||
object.
|
||||
"""
|
||||
|
||||
def __init__(self, index_url, session, use_datetime=False):
|
||||
xmlrpc_client.Transport.__init__(self, use_datetime)
|
||||
index_parts = urllib_parse.urlparse(index_url)
|
||||
self._scheme = index_parts.scheme
|
||||
self._session = session
|
||||
|
||||
def request(self, host, handler, request_body, verbose=False):
|
||||
parts = (self._scheme, host, handler, None, None, None)
|
||||
url = urllib_parse.urlunparse(parts)
|
||||
try:
|
||||
headers = {'Content-Type': 'text/xml'}
|
||||
response = self._session.post(url, data=request_body,
|
||||
headers=headers, stream=True)
|
||||
response.raise_for_status()
|
||||
self.verbose = verbose
|
||||
return self.parse_response(response.raw)
|
||||
except requests.HTTPError as exc:
|
||||
logger.critical(
|
||||
"HTTP error %s while getting %s",
|
||||
exc.response.status_code, url,
|
||||
)
|
||||
raise
|
||||
@@ -1,21 +1,27 @@
|
||||
"""Validation of dependencies of packages
|
||||
"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
import logging
|
||||
from collections import namedtuple
|
||||
|
||||
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
|
||||
from pipenv.patched.notpip._vendor.pkg_resources import RequirementParseError
|
||||
|
||||
from pipenv.patched.notpip._internal.operations.prepare import make_abstract_dist
|
||||
from pipenv.patched.notpip._internal.distributions import (
|
||||
make_distribution_for_install_requirement,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.misc import get_installed_distributions
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement # noqa: F401
|
||||
from typing import ( # noqa: F401
|
||||
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
|
||||
from typing import (
|
||||
Any, Callable, Dict, Optional, Set, Tuple, List
|
||||
)
|
||||
|
||||
@@ -63,8 +69,8 @@ def check_package_set(package_set, should_ignore=None):
|
||||
def should_ignore(name):
|
||||
return False
|
||||
|
||||
missing = dict()
|
||||
conflicting = dict()
|
||||
missing = {}
|
||||
conflicting = {}
|
||||
|
||||
for package_name in package_set:
|
||||
# Info about dependencies of package_name
|
||||
@@ -130,7 +136,9 @@ def _simulate_installation_of(to_install, package_set):
|
||||
|
||||
# Modify it as installing requirement_set would (assuming no errors)
|
||||
for inst_req in to_install:
|
||||
dist = make_abstract_dist(inst_req).dist()
|
||||
abstract_dist = make_distribution_for_install_requirement(inst_req)
|
||||
dist = abstract_dist.get_pkg_resources_distribution()
|
||||
|
||||
name = canonicalize_name(dist.key)
|
||||
package_set[name] = PackageDetails(dist.version, dist.requires())
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import collections
|
||||
@@ -11,20 +15,22 @@ from pipenv.patched.notpip._vendor.pkg_resources import RequirementParseError
|
||||
|
||||
from pipenv.patched.notpip._internal.exceptions import BadCommand, InstallationError
|
||||
from pipenv.patched.notpip._internal.req.constructors import (
|
||||
install_req_from_editable, install_req_from_line,
|
||||
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.misc import (
|
||||
dist_is_editable, get_installed_distributions,
|
||||
dist_is_editable,
|
||||
get_installed_distributions,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import ( # noqa: F401
|
||||
from typing import (
|
||||
Iterator, Optional, List, Container, Set, Dict, Tuple, Iterable, Union
|
||||
)
|
||||
from pipenv.patched.notpip._internal.cache import WheelCache # noqa: F401
|
||||
from pipenv.patched.notpip._vendor.pkg_resources import ( # noqa: F401
|
||||
from pipenv.patched.notpip._internal.cache import WheelCache
|
||||
from pipenv.patched.notpip._vendor.pkg_resources import (
|
||||
Distribution, Requirement
|
||||
)
|
||||
|
||||
@@ -39,6 +45,7 @@ def freeze(
|
||||
find_links=None, # type: Optional[List[str]]
|
||||
local_only=None, # type: Optional[bool]
|
||||
user_only=None, # type: Optional[bool]
|
||||
paths=None, # type: Optional[List[str]]
|
||||
skip_regex=None, # type: Optional[str]
|
||||
isolated=False, # type: bool
|
||||
wheel_cache=None, # type: Optional[WheelCache]
|
||||
@@ -57,13 +64,18 @@ def freeze(
|
||||
installations = {} # type: Dict[str, FrozenRequirement]
|
||||
for dist in get_installed_distributions(local_only=local_only,
|
||||
skip=(),
|
||||
user_only=user_only):
|
||||
user_only=user_only,
|
||||
paths=paths):
|
||||
try:
|
||||
req = FrozenRequirement.from_dist(dist)
|
||||
except RequirementParseError:
|
||||
except RequirementParseError as exc:
|
||||
# We include dist rather than dist.project_name because the
|
||||
# dist string includes more information, like the version and
|
||||
# location. We also include the exception message to aid
|
||||
# troubleshooting.
|
||||
logger.warning(
|
||||
"Could not parse requirement: %s",
|
||||
dist.project_name
|
||||
'Could not generate requirement for distribution %r: %s',
|
||||
dist, exc
|
||||
)
|
||||
continue
|
||||
if exclude_editable and req.editable:
|
||||
@@ -173,12 +185,12 @@ def get_requirement_info(dist):
|
||||
location = os.path.normcase(os.path.abspath(dist.location))
|
||||
|
||||
from pipenv.patched.notpip._internal.vcs import vcs, RemoteNotFoundError
|
||||
vc_type = vcs.get_backend_type(location)
|
||||
vcs_backend = vcs.get_backend_for_dir(location)
|
||||
|
||||
if not vc_type:
|
||||
if vcs_backend is None:
|
||||
req = dist.as_requirement()
|
||||
logger.debug(
|
||||
'No VCS found for editable requirement {!r} in: {!r}', req,
|
||||
'No VCS found for editable requirement "%s" in: %r', req,
|
||||
location,
|
||||
)
|
||||
comments = [
|
||||
@@ -187,12 +199,12 @@ def get_requirement_info(dist):
|
||||
return (location, True, comments)
|
||||
|
||||
try:
|
||||
req = vc_type.get_src_requirement(location, dist.project_name)
|
||||
req = vcs_backend.get_src_requirement(location, dist.project_name)
|
||||
except RemoteNotFoundError:
|
||||
req = dist.as_requirement()
|
||||
comments = [
|
||||
'# Editable {} install with no remote ({})'.format(
|
||||
vc_type.__name__, req,
|
||||
type(vcs_backend).__name__, req,
|
||||
)
|
||||
]
|
||||
return (location, True, comments)
|
||||
@@ -202,7 +214,7 @@ def get_requirement_info(dist):
|
||||
'cannot determine version of editable source in %s '
|
||||
'(%s command not found in path)',
|
||||
location,
|
||||
vc_type.name,
|
||||
vcs_backend.name,
|
||||
)
|
||||
return (None, True, [])
|
||||
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
"""Metadata generation logic for source distributions.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from pipenv.patched.notpip._internal.exceptions import InstallationError
|
||||
from pipenv.patched.notpip._internal.utils.misc import ensure_dir
|
||||
from pipenv.patched.notpip._internal.utils.setuptools_build import make_setuptools_shim_args
|
||||
from pipenv.patched.notpip._internal.utils.subprocess import call_subprocess
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
from pipenv.patched.notpip._internal.vcs import vcs
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Callable, List
|
||||
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_metadata_generator(install_req):
|
||||
# type: (InstallRequirement) -> Callable[[InstallRequirement], str]
|
||||
"""Return a callable metadata generator for this InstallRequirement.
|
||||
|
||||
A metadata generator takes an InstallRequirement (install_req) as an input,
|
||||
generates metadata via the appropriate process for that install_req and
|
||||
returns the generated metadata directory.
|
||||
"""
|
||||
if not install_req.use_pep517:
|
||||
return _generate_metadata_legacy
|
||||
|
||||
return _generate_metadata
|
||||
|
||||
|
||||
def _find_egg_info(source_directory, is_editable):
|
||||
# type: (str, bool) -> str
|
||||
"""Find an .egg-info in `source_directory`, based on `is_editable`.
|
||||
"""
|
||||
|
||||
def looks_like_virtual_env(path):
|
||||
# type: (str) -> bool
|
||||
return (
|
||||
os.path.lexists(os.path.join(path, 'bin', 'python')) or
|
||||
os.path.exists(os.path.join(path, 'Scripts', 'Python.exe'))
|
||||
)
|
||||
|
||||
def locate_editable_egg_info(base):
|
||||
# type: (str) -> List[str]
|
||||
candidates = [] # type: List[str]
|
||||
for root, dirs, files in os.walk(base):
|
||||
for dir_ in vcs.dirnames:
|
||||
if dir_ in dirs:
|
||||
dirs.remove(dir_)
|
||||
# Iterate over a copy of ``dirs``, since mutating
|
||||
# a list while iterating over it can cause trouble.
|
||||
# (See https://github.com/pypa/pip/pull/462.)
|
||||
for dir_ in list(dirs):
|
||||
if looks_like_virtual_env(os.path.join(root, dir_)):
|
||||
dirs.remove(dir_)
|
||||
# Also don't search through tests
|
||||
elif dir_ == 'test' or dir_ == 'tests':
|
||||
dirs.remove(dir_)
|
||||
candidates.extend(os.path.join(root, dir_) for dir_ in dirs)
|
||||
return [f for f in candidates if f.endswith('.egg-info')]
|
||||
|
||||
def depth_of_directory(dir_):
|
||||
# type: (str) -> int
|
||||
return (
|
||||
dir_.count(os.path.sep) +
|
||||
(os.path.altsep and dir_.count(os.path.altsep) or 0)
|
||||
)
|
||||
|
||||
base = source_directory
|
||||
if is_editable:
|
||||
filenames = locate_editable_egg_info(base)
|
||||
else:
|
||||
base = os.path.join(base, 'pip-egg-info')
|
||||
filenames = os.listdir(base)
|
||||
|
||||
if not filenames:
|
||||
raise InstallationError(
|
||||
"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
|
||||
# an extracted tarball for testing purposes.
|
||||
if len(filenames) > 1:
|
||||
filenames.sort(key=depth_of_directory)
|
||||
|
||||
return os.path.join(base, filenames[0])
|
||||
|
||||
|
||||
def _generate_metadata_legacy(install_req):
|
||||
# type: (InstallRequirement) -> str
|
||||
req_details_str = install_req.name or "from {}".format(install_req.link)
|
||||
logger.debug(
|
||||
'Running setup.py (path:%s) egg_info for package %s',
|
||||
install_req.setup_py_path, req_details_str,
|
||||
)
|
||||
|
||||
# Compose arguments for subprocess call
|
||||
base_cmd = make_setuptools_shim_args(install_req.setup_py_path)
|
||||
if install_req.isolated:
|
||||
base_cmd += ["--no-user-cfg"]
|
||||
|
||||
# For non-editable installs, don't put the .egg-info files at the root,
|
||||
# to avoid confusion due to the source code being considered an installed
|
||||
# egg.
|
||||
egg_base_option = [] # type: List[str]
|
||||
if not install_req.editable:
|
||||
egg_info_dir = os.path.join(
|
||||
install_req.unpacked_source_directory, 'pip-egg-info',
|
||||
)
|
||||
egg_base_option = ['--egg-base', egg_info_dir]
|
||||
|
||||
# setuptools complains if the target directory does not exist.
|
||||
ensure_dir(egg_info_dir)
|
||||
|
||||
with install_req.build_env:
|
||||
call_subprocess(
|
||||
base_cmd + ["egg_info"] + egg_base_option,
|
||||
cwd=install_req.unpacked_source_directory,
|
||||
command_desc='python setup.py egg_info',
|
||||
)
|
||||
|
||||
# Return the .egg-info directory.
|
||||
return _find_egg_info(
|
||||
install_req.unpacked_source_directory,
|
||||
install_req.editable,
|
||||
)
|
||||
|
||||
|
||||
def _generate_metadata(install_req):
|
||||
# type: (InstallRequirement) -> str
|
||||
return install_req.prepare_pep517_metadata()
|
||||
@@ -1,173 +1,53 @@
|
||||
"""Prepares a distribution for installation
|
||||
"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from pipenv.patched.notpip._vendor import pkg_resources, requests
|
||||
from pipenv.patched.notpip._vendor import requests
|
||||
|
||||
from pipenv.patched.notpip._internal.build_env import BuildEnvironment
|
||||
from pipenv.patched.notpip._internal.download import (
|
||||
is_dir_url, is_file_url, is_vcs_url, unpack_url, url_to_path,
|
||||
from pipenv.patched.notpip._internal.distributions import (
|
||||
make_distribution_for_install_requirement,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.distributions.installed import InstalledDistribution
|
||||
from pipenv.patched.notpip._internal.download import unpack_url
|
||||
from pipenv.patched.notpip._internal.exceptions import (
|
||||
DirectoryUrlHashUnsupported, HashUnpinned, InstallationError,
|
||||
PreviousBuildDirError, VcsHashUnsupported,
|
||||
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
|
||||
from pipenv.patched.notpip._internal.utils.marker_files import write_delete_marker_file
|
||||
from pipenv.patched.notpip._internal.utils.misc import display_path, normalize_path
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
from pipenv.patched.notpip._internal.vcs import vcs
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Any, Optional # noqa: F401
|
||||
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement # noqa: F401
|
||||
from pipenv.patched.notpip._internal.index import PackageFinder # noqa: F401
|
||||
from pipenv.patched.notpip._internal.download import PipSession # noqa: F401
|
||||
from pipenv.patched.notpip._internal.req.req_tracker import RequirementTracker # noqa: F401
|
||||
from typing import Optional
|
||||
|
||||
from pipenv.patched.notpip._internal.distributions import AbstractDistribution
|
||||
from pipenv.patched.notpip._internal.index import PackageFinder
|
||||
from pipenv.patched.notpip._internal.network.session import PipSession
|
||||
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
|
||||
from pipenv.patched.notpip._internal.req.req_tracker import RequirementTracker
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def make_abstract_dist(req):
|
||||
# type: (InstallRequirement) -> DistAbstraction
|
||||
"""Factory to make an abstract dist object.
|
||||
|
||||
Preconditions: Either an editable req with a source_dir, or satisfied_by or
|
||||
a wheel link, or a non-editable req with a source_dir.
|
||||
|
||||
:return: A concrete DistAbstraction.
|
||||
def _get_prepared_distribution(req, req_tracker, finder, build_isolation):
|
||||
"""Prepare a distribution for installation.
|
||||
"""
|
||||
if req.editable:
|
||||
return IsSDist(req)
|
||||
elif req.link and req.link.is_wheel:
|
||||
return IsWheel(req)
|
||||
else:
|
||||
return IsSDist(req)
|
||||
|
||||
|
||||
class DistAbstraction(object):
|
||||
"""Abstracts out the wheel vs non-wheel Resolver.resolve() logic.
|
||||
|
||||
The requirements for anything installable are as follows:
|
||||
- we must be able to determine the requirement name
|
||||
(or we can't correctly handle the non-upgrade case).
|
||||
- we must be able to generate a list of run-time dependencies
|
||||
without installing any additional packages (or we would
|
||||
have to either burn time by doing temporary isolated installs
|
||||
or alternatively violate pips 'don't start installing unless
|
||||
all requirements are available' rule - neither of which are
|
||||
desirable).
|
||||
- for packages with setup requirements, we must also be able
|
||||
to determine their requirements without installing additional
|
||||
packages (for the same reason as run-time dependencies)
|
||||
- we must be able to create a Distribution object exposing the
|
||||
above metadata.
|
||||
"""
|
||||
|
||||
def __init__(self, req):
|
||||
# type: (InstallRequirement) -> None
|
||||
self.req = req # type: InstallRequirement
|
||||
|
||||
def dist(self):
|
||||
# type: () -> Any
|
||||
"""Return a setuptools Dist object."""
|
||||
raise NotImplementedError
|
||||
|
||||
def prep_for_dist(self, finder, build_isolation):
|
||||
# type: (PackageFinder, bool) -> Any
|
||||
"""Ensure that we can get a Dist for this requirement."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class IsWheel(DistAbstraction):
|
||||
|
||||
def dist(self):
|
||||
# type: () -> pkg_resources.Distribution
|
||||
return list(pkg_resources.find_distributions(
|
||||
self.req.source_dir))[0]
|
||||
|
||||
def prep_for_dist(self, finder, build_isolation):
|
||||
# type: (PackageFinder, bool) -> Any
|
||||
# FIXME:https://github.com/pypa/pip/issues/1112
|
||||
pass
|
||||
|
||||
|
||||
class IsSDist(DistAbstraction):
|
||||
|
||||
def dist(self):
|
||||
return self.req.get_dist()
|
||||
|
||||
def prep_for_dist(self, finder, build_isolation):
|
||||
# type: (PackageFinder, bool) -> 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
|
||||
|
||||
def _raise_conflicts(conflicting_with, conflicting_reqs):
|
||||
raise InstallationError(
|
||||
"Some build dependencies for %s conflict with %s: %s." % (
|
||||
self.req, conflicting_with, ', '.join(
|
||||
'%s is incompatible with %s' % (installed, wanted)
|
||||
for installed, wanted in sorted(conflicting))))
|
||||
|
||||
if should_isolate:
|
||||
# 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, 'overlay',
|
||||
"Installing build dependencies"
|
||||
)
|
||||
conflicting, missing = self.req.build_env.check_requirements(
|
||||
self.req.requirements_to_check
|
||||
)
|
||||
if conflicting:
|
||||
_raise_conflicts("PEP 517/518 supported requirements",
|
||||
conflicting)
|
||||
if missing:
|
||||
logger.warning(
|
||||
"Missing build requirements in pyproject.toml for %s.",
|
||||
self.req,
|
||||
)
|
||||
logger.warning(
|
||||
"The project does not specify a build backend, and "
|
||||
"pip cannot fall back to setuptools without %s.",
|
||||
" and ".join(map(repr, sorted(missing)))
|
||||
)
|
||||
# Install any extra build dependencies that the backend requests.
|
||||
# This must be done in a second pass, as the pyproject.toml
|
||||
# dependencies must be installed before we can call the backend.
|
||||
with self.req.build_env:
|
||||
# We need to have the env active when calling the hook.
|
||||
self.req.spin_message = "Getting requirements to build wheel"
|
||||
reqs = self.req.pep517_backend.get_requires_for_build_wheel()
|
||||
conflicting, missing = self.req.build_env.check_requirements(reqs)
|
||||
if conflicting:
|
||||
_raise_conflicts("the backend dependencies", conflicting)
|
||||
self.req.build_env.install_requirements(
|
||||
finder, missing, 'normal',
|
||||
"Installing backend dependencies"
|
||||
)
|
||||
|
||||
self.req.prepare_metadata()
|
||||
self.req.assert_source_matches_version()
|
||||
|
||||
|
||||
class Installed(DistAbstraction):
|
||||
|
||||
def dist(self):
|
||||
# type: () -> pkg_resources.Distribution
|
||||
return self.req.satisfied_by
|
||||
|
||||
def prep_for_dist(self, finder, build_isolation):
|
||||
# type: (PackageFinder, bool) -> Any
|
||||
pass
|
||||
abstract_dist = make_distribution_for_install_requirement(req)
|
||||
with req_tracker.track(req):
|
||||
abstract_dist.prepare_distribution_metadata(finder, build_isolation)
|
||||
return abstract_dist
|
||||
|
||||
|
||||
class RequirementPreparer(object):
|
||||
@@ -191,8 +71,10 @@ class RequirementPreparer(object):
|
||||
self.build_dir = build_dir
|
||||
self.req_tracker = req_tracker
|
||||
|
||||
# Where still packed archives should be written to. If None, they are
|
||||
# Where still-packed archives should be written to. If None, they are
|
||||
# not saved, and are deleted immediately after unpacking.
|
||||
if download_dir:
|
||||
download_dir = expanduser(download_dir)
|
||||
self.download_dir = download_dir
|
||||
|
||||
# Where still-packed .whl files should be written to. If None, they are
|
||||
@@ -215,35 +97,36 @@ class RequirementPreparer(object):
|
||||
@property
|
||||
def _download_should_save(self):
|
||||
# type: () -> bool
|
||||
# TODO: Modify to reduce indentation needed
|
||||
if self.download_dir:
|
||||
self.download_dir = expanduser(self.download_dir)
|
||||
if os.path.exists(self.download_dir):
|
||||
return True
|
||||
else:
|
||||
logger.critical('Could not find download directory')
|
||||
raise InstallationError(
|
||||
"Could not find or access download directory '%s'"
|
||||
% display_path(self.download_dir))
|
||||
return False
|
||||
if not self.download_dir:
|
||||
return False
|
||||
|
||||
if os.path.exists(self.download_dir):
|
||||
return True
|
||||
|
||||
logger.critical('Could not find download directory')
|
||||
raise InstallationError(
|
||||
"Could not find or access download directory '%s'"
|
||||
% display_path(self.download_dir))
|
||||
|
||||
def prepare_linked_requirement(
|
||||
self,
|
||||
req, # type: InstallRequirement
|
||||
session, # type: PipSession
|
||||
finder, # type: PackageFinder
|
||||
upgrade_allowed, # type: bool
|
||||
require_hashes # type: bool
|
||||
require_hashes, # type: bool
|
||||
):
|
||||
# type: (...) -> DistAbstraction
|
||||
# type: (...) -> AbstractDistribution
|
||||
"""Prepare a requirement that would be obtained from req.link
|
||||
"""
|
||||
assert req.link
|
||||
link = req.link
|
||||
|
||||
# TODO: Breakup into smaller functions
|
||||
if req.link and req.link.scheme == 'file':
|
||||
path = url_to_path(req.link.url)
|
||||
if link.scheme == 'file':
|
||||
path = link.file_path
|
||||
logger.info('Processing %s', display_path(path))
|
||||
else:
|
||||
logger.info('Collecting %s', req)
|
||||
logger.info('Collecting %s', req.req or req)
|
||||
|
||||
with indent_log():
|
||||
# @@ if filesystem packages are not marked
|
||||
@@ -256,20 +139,8 @@ class RequirementPreparer(object):
|
||||
# installation.
|
||||
# FIXME: this won't upgrade when there's an existing
|
||||
# package unpacked in `req.source_dir`
|
||||
# package unpacked in `req.source_dir`
|
||||
if os.path.exists(os.path.join(req.source_dir, 'setup.py')):
|
||||
rmtree(req.source_dir)
|
||||
req.populate_link(finder, upgrade_allowed, require_hashes)
|
||||
|
||||
# We can't hit this spot and have populate_link return None.
|
||||
# req.satisfied_by is None here (because we're
|
||||
# guarded) and upgrade has no impact except when satisfied_by
|
||||
# is not None.
|
||||
# Then inside find_requirement existing_applicable -> False
|
||||
# If no new versions are found, DistributionNotFound is raised,
|
||||
# otherwise a result is guaranteed.
|
||||
assert req.link
|
||||
link = req.link
|
||||
|
||||
# Now that we have the real link, we can tell what kind of
|
||||
# requirements we have and raise some more informative errors
|
||||
@@ -281,9 +152,9 @@ class RequirementPreparer(object):
|
||||
# we would report less-useful error messages for
|
||||
# unhashable requirements, complaining that there's no
|
||||
# hash provided.
|
||||
if is_vcs_url(link):
|
||||
if link.is_vcs:
|
||||
raise VcsHashUnsupported()
|
||||
elif is_file_url(link) and is_dir_url(link):
|
||||
elif link.is_existing_dir():
|
||||
raise DirectoryUrlHashUnsupported()
|
||||
if not req.original_link and not req.is_pinned:
|
||||
# Unpinned packages are asking for trouble when a new
|
||||
@@ -303,26 +174,15 @@ class RequirementPreparer(object):
|
||||
# showing the user what the hash should be.
|
||||
hashes = MissingHashes()
|
||||
|
||||
download_dir = self.download_dir
|
||||
if link.is_wheel and self.wheel_download_dir:
|
||||
# when doing 'pip wheel` we download wheels to a
|
||||
# dedicated dir.
|
||||
download_dir = self.wheel_download_dir
|
||||
|
||||
try:
|
||||
download_dir = self.download_dir
|
||||
# We always delete unpacked sdists after pip ran.
|
||||
autodelete_unpacked = True
|
||||
if req.link.is_wheel and self.wheel_download_dir:
|
||||
# when doing 'pip wheel` we download wheels to a
|
||||
# dedicated dir.
|
||||
download_dir = self.wheel_download_dir
|
||||
if req.link.is_wheel:
|
||||
if download_dir:
|
||||
# When downloading, we only unpack wheels to get
|
||||
# metadata.
|
||||
autodelete_unpacked = True
|
||||
else:
|
||||
# When installing a wheel, we use the unpacked
|
||||
# wheel.
|
||||
autodelete_unpacked = False
|
||||
unpack_url(
|
||||
req.link, req.source_dir,
|
||||
download_dir, autodelete_unpacked,
|
||||
link, req.source_dir, download_dir,
|
||||
session=session, hashes=hashes,
|
||||
progress_bar=self.progress_bar
|
||||
)
|
||||
@@ -335,14 +195,31 @@ class RequirementPreparer(object):
|
||||
raise InstallationError(
|
||||
'Could not install requirement %s because of HTTP '
|
||||
'error %s for URL %s' %
|
||||
(req, exc, req.link)
|
||||
(req, exc, link)
|
||||
)
|
||||
abstract_dist = make_abstract_dist(req)
|
||||
with self.req_tracker.track(req):
|
||||
abstract_dist.prep_for_dist(finder, self.build_isolation)
|
||||
|
||||
if link.is_wheel:
|
||||
if download_dir:
|
||||
# When downloading, we only unpack wheels to get
|
||||
# metadata.
|
||||
autodelete_unpacked = True
|
||||
else:
|
||||
# When installing a wheel, we use the unpacked
|
||||
# wheel.
|
||||
autodelete_unpacked = False
|
||||
else:
|
||||
# We always delete unpacked sdists after pip runs.
|
||||
autodelete_unpacked = True
|
||||
if autodelete_unpacked:
|
||||
write_delete_marker_file(req.source_dir)
|
||||
|
||||
abstract_dist = _get_prepared_distribution(
|
||||
req, self.req_tracker, finder, self.build_isolation,
|
||||
)
|
||||
|
||||
if self._download_should_save:
|
||||
# Make a .zip of the source_dir we already created.
|
||||
if req.link.scheme in vcs.all_schemes:
|
||||
if link.is_vcs:
|
||||
req.archive(self.download_dir)
|
||||
return abstract_dist
|
||||
|
||||
@@ -353,7 +230,7 @@ class RequirementPreparer(object):
|
||||
use_user_site, # type: bool
|
||||
finder # type: PackageFinder
|
||||
):
|
||||
# type: (...) -> DistAbstraction
|
||||
# type: (...) -> AbstractDistribution
|
||||
"""Prepare an editable requirement
|
||||
"""
|
||||
assert req.editable, "cannot prepare a non-editable req as editable"
|
||||
@@ -370,9 +247,9 @@ class RequirementPreparer(object):
|
||||
req.ensure_has_source_dir(self.src_dir)
|
||||
req.update_editable(not self._download_should_save)
|
||||
|
||||
abstract_dist = make_abstract_dist(req)
|
||||
with self.req_tracker.track(req):
|
||||
abstract_dist.prep_for_dist(finder, self.build_isolation)
|
||||
abstract_dist = _get_prepared_distribution(
|
||||
req, self.req_tracker, finder, self.build_isolation,
|
||||
)
|
||||
|
||||
if self._download_should_save:
|
||||
req.archive(self.download_dir)
|
||||
@@ -380,8 +257,13 @@ class RequirementPreparer(object):
|
||||
|
||||
return abstract_dist
|
||||
|
||||
def prepare_installed_requirement(self, req, require_hashes, skip_reason):
|
||||
# type: (InstallRequirement, bool, Optional[str]) -> DistAbstraction
|
||||
def prepare_installed_requirement(
|
||||
self,
|
||||
req, # type: InstallRequirement
|
||||
require_hashes, # type: bool
|
||||
skip_reason # type: str
|
||||
):
|
||||
# type: (...) -> AbstractDistribution
|
||||
"""Prepare an already-installed requirement
|
||||
"""
|
||||
assert req.satisfied_by, "req should have been satisfied but isn't"
|
||||
@@ -401,6 +283,6 @@ class RequirementPreparer(object):
|
||||
'completely repeatable environment, install into an '
|
||||
'empty virtualenv.'
|
||||
)
|
||||
abstract_dist = Installed(req)
|
||||
abstract_dist = InstalledDistribution(req)
|
||||
|
||||
return abstract_dist
|
||||
|
||||
@@ -10,16 +10,13 @@ import sysconfig
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
try:
|
||||
import pipenv.patched.notpip._internal.utils.glibc
|
||||
except ImportError:
|
||||
import pipenv.patched.notpip.utils.glibc
|
||||
import pipenv.patched.notpip._internal.utils.glibc
|
||||
from pipenv.patched.notpip._internal.utils.compat import get_extension_suffixes
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import ( # noqa: F401
|
||||
Tuple, Callable, List, Optional, Union, Dict
|
||||
from typing import (
|
||||
Tuple, Callable, List, Optional, Union, Dict, Set
|
||||
)
|
||||
|
||||
Pep425Tag = Tuple[str, str, str]
|
||||
@@ -52,6 +49,12 @@ def get_abbr_impl():
|
||||
return pyimpl
|
||||
|
||||
|
||||
def version_info_to_nodot(version_info):
|
||||
# type: (Tuple[int, ...]) -> str
|
||||
# Only use up to the first two numbers.
|
||||
return ''.join(map(str, version_info[:2]))
|
||||
|
||||
|
||||
def get_impl_ver():
|
||||
# type: () -> str
|
||||
"""Return implementation version."""
|
||||
@@ -102,32 +105,30 @@ def get_abi_tag():
|
||||
(CPython 2, PyPy)."""
|
||||
soabi = get_config_var('SOABI')
|
||||
impl = get_abbr_impl()
|
||||
abi = None # type: Optional[str]
|
||||
|
||||
if not soabi and impl in {'cp', 'pp'} and hasattr(sys, 'maxunicode'):
|
||||
d = ''
|
||||
m = ''
|
||||
u = ''
|
||||
if get_flag('Py_DEBUG',
|
||||
lambda: hasattr(sys, 'gettotalrefcount'),
|
||||
warn=(impl == 'cp')):
|
||||
is_cpython = (impl == 'cp')
|
||||
if get_flag(
|
||||
'Py_DEBUG', lambda: hasattr(sys, 'gettotalrefcount'),
|
||||
warn=is_cpython):
|
||||
d = 'd'
|
||||
if get_flag('WITH_PYMALLOC',
|
||||
lambda: impl == 'cp',
|
||||
warn=(impl == 'cp')):
|
||||
if sys.version_info < (3, 8) and get_flag(
|
||||
'WITH_PYMALLOC', lambda: is_cpython, warn=is_cpython):
|
||||
m = 'm'
|
||||
if get_flag('Py_UNICODE_SIZE',
|
||||
lambda: sys.maxunicode == 0x10ffff,
|
||||
expected=4,
|
||||
warn=(impl == 'cp' and
|
||||
sys.version_info < (3, 3))) \
|
||||
and sys.version_info < (3, 3):
|
||||
if sys.version_info < (3, 3) and get_flag(
|
||||
'Py_UNICODE_SIZE', lambda: sys.maxunicode == 0x10ffff,
|
||||
expected=4, warn=is_cpython):
|
||||
u = 'u'
|
||||
abi = '%s%s%s%s%s' % (impl, get_impl_ver(), d, m, u)
|
||||
elif soabi and soabi.startswith('cpython-'):
|
||||
abi = 'cp' + soabi.split('-')[1]
|
||||
elif soabi:
|
||||
abi = soabi.replace('.', '_').replace('-', '_')
|
||||
else:
|
||||
abi = None
|
||||
|
||||
return abi
|
||||
|
||||
|
||||
@@ -163,6 +164,33 @@ def get_platform():
|
||||
return result
|
||||
|
||||
|
||||
def is_linux_armhf():
|
||||
# type: () -> bool
|
||||
if get_platform() != "linux_armv7l":
|
||||
return False
|
||||
# hard-float ABI can be detected from the ELF header of the running
|
||||
# process
|
||||
sys_executable = os.environ.get('PIP_PYTHON_PATH', sys.executable)
|
||||
try:
|
||||
with open(sys_executable, 'rb') as f:
|
||||
elf_header_raw = f.read(40) # read 40 first bytes of ELF header
|
||||
except (IOError, OSError, TypeError):
|
||||
return False
|
||||
if elf_header_raw is None or len(elf_header_raw) < 40:
|
||||
return False
|
||||
if isinstance(elf_header_raw, str):
|
||||
elf_header = [ord(c) for c in elf_header_raw]
|
||||
else:
|
||||
elf_header = [b for b in elf_header_raw]
|
||||
result = elf_header[0:4] == [0x7f, 0x45, 0x4c, 0x46] # ELF magic number
|
||||
result &= elf_header[4:5] == [1] # 32-bit ELF
|
||||
result &= elf_header[5:6] == [1] # little-endian
|
||||
result &= elf_header[18:20] == [0x28, 0] # ARM machine
|
||||
result &= elf_header[39:40] == [5] # ARM EABIv5
|
||||
result &= (elf_header[37:38][0] & 4) == 4 # EF_ARM_ABI_FLOAT_HARD
|
||||
return result
|
||||
|
||||
|
||||
def is_manylinux1_compatible():
|
||||
# type: () -> bool
|
||||
# Only Linux, and only x86-64 / i686
|
||||
@@ -199,6 +227,32 @@ def is_manylinux2010_compatible():
|
||||
return pipenv.patched.notpip._internal.utils.glibc.have_compatible_glibc(2, 12)
|
||||
|
||||
|
||||
def is_manylinux2014_compatible():
|
||||
# type: () -> bool
|
||||
# Only Linux, and only supported architectures
|
||||
platform = get_platform()
|
||||
if platform not in {"linux_x86_64", "linux_i686", "linux_aarch64",
|
||||
"linux_armv7l", "linux_ppc64", "linux_ppc64le",
|
||||
"linux_s390x"}:
|
||||
return False
|
||||
|
||||
# check for hard-float ABI in case we're running linux_armv7l not to
|
||||
# install hard-float ABI wheel in a soft-float ABI environment
|
||||
if platform == "linux_armv7l" and not is_linux_armhf():
|
||||
return False
|
||||
|
||||
# Check for presence of _manylinux module
|
||||
try:
|
||||
import _manylinux
|
||||
return bool(_manylinux.manylinux2014_compatible)
|
||||
except (ImportError, AttributeError):
|
||||
# Fall through to heuristic check below
|
||||
pass
|
||||
|
||||
# Check glibc version. CentOS 7 uses glibc 2.17.
|
||||
return pipenv.patched.notpip._internal.utils.glibc.have_compatible_glibc(2, 17)
|
||||
|
||||
|
||||
def get_darwin_arches(major, minor, machine):
|
||||
# type: (int, int, str) -> List[str]
|
||||
"""Return a list of supported arches (including group arches) for
|
||||
@@ -307,7 +361,7 @@ def get_supported(
|
||||
if abi:
|
||||
abis[0:0] = [abi]
|
||||
|
||||
abi3s = set()
|
||||
abi3s = set() # type: Set[str]
|
||||
for suffix in get_extension_suffixes():
|
||||
if suffix.startswith('.abi'):
|
||||
abi3s.add(suffix.split('.', 2)[1])
|
||||
@@ -332,6 +386,16 @@ def get_supported(
|
||||
else:
|
||||
# arch pattern didn't match (?!)
|
||||
arches = [arch]
|
||||
elif arch_prefix == 'manylinux2014':
|
||||
arches = [arch]
|
||||
# manylinux1/manylinux2010 wheels run on most manylinux2014 systems
|
||||
# with the exception of wheels depending on ncurses. PEP 599 states
|
||||
# manylinux1/manylinux2010 wheels should be considered
|
||||
# manylinux2014 wheels:
|
||||
# https://www.python.org/dev/peps/pep-0599/#backwards-compatibility-with-manylinux2010-wheels
|
||||
if arch_suffix in {'i686', 'x86_64'}:
|
||||
arches.append('manylinux2010' + arch_sep + arch_suffix)
|
||||
arches.append('manylinux1' + arch_sep + arch_suffix)
|
||||
elif arch_prefix == 'manylinux2010':
|
||||
# manylinux1 wheels run on most manylinux2010 systems with the
|
||||
# exception of wheels depending on ncurses. PEP 571 states
|
||||
@@ -340,6 +404,8 @@ def get_supported(
|
||||
arches = [arch, 'manylinux1' + arch_sep + arch_suffix]
|
||||
elif platform is None:
|
||||
arches = []
|
||||
if is_manylinux2014_compatible():
|
||||
arches.append('manylinux2014' + arch_sep + arch_suffix)
|
||||
if is_manylinux2010_compatible():
|
||||
arches.append('manylinux2010' + arch_sep + arch_suffix)
|
||||
if is_manylinux1_compatible():
|
||||
|
||||
@@ -10,7 +10,7 @@ from pipenv.patched.notpip._internal.exceptions import InstallationError
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Any, Tuple, Optional, List # noqa: F401
|
||||
from typing import Any, Tuple, Optional, List
|
||||
|
||||
|
||||
def _is_list_of_str(obj):
|
||||
@@ -21,9 +21,9 @@ def _is_list_of_str(obj):
|
||||
)
|
||||
|
||||
|
||||
def make_pyproject_path(setup_py_dir):
|
||||
def make_pyproject_path(unpacked_source_directory):
|
||||
# type: (str) -> str
|
||||
path = os.path.join(setup_py_dir, 'pyproject.toml')
|
||||
path = os.path.join(unpacked_source_directory, 'pyproject.toml')
|
||||
|
||||
# Python2 __file__ should not be unicode
|
||||
if six.PY2 and isinstance(path, six.text_type):
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
from .req_install import InstallRequirement
|
||||
from .req_set import RequirementSet
|
||||
from .req_file import parse_requirements
|
||||
from pipenv.patched.notpip._internal.utils.logging import indent_log
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
from .req_file import parse_requirements
|
||||
from .req_install import InstallRequirement
|
||||
from .req_set import RequirementSet
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import List, Sequence # noqa: F401
|
||||
from typing import Any, List, Sequence
|
||||
|
||||
__all__ = [
|
||||
"RequirementSet", "InstallRequirement",
|
||||
@@ -23,7 +27,8 @@ def install_given_reqs(
|
||||
to_install, # type: List[InstallRequirement]
|
||||
install_options, # type: List[str]
|
||||
global_options=(), # type: Sequence[str]
|
||||
*args, **kwargs
|
||||
*args, # type: Any
|
||||
**kwargs # type: Any
|
||||
):
|
||||
# type: (...) -> List[InstallRequirement]
|
||||
"""
|
||||
|
||||
@@ -8,6 +8,10 @@ These are meant to be used elsewhere within pip to create instances of
|
||||
InstallRequirement.
|
||||
"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
@@ -17,24 +21,23 @@ from pipenv.patched.notpip._vendor.packaging.requirements import InvalidRequirem
|
||||
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.pyproject import make_pyproject_path
|
||||
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.utils.filetypes import ARCHIVE_EXTENSIONS
|
||||
from pipenv.patched.notpip._internal.utils.misc import is_installable_dir, splitext
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
from pipenv.patched.notpip._internal.vcs import vcs
|
||||
from pipenv.patched.notpip._internal.utils.urls import path_to_url
|
||||
from pipenv.patched.notpip._internal.vcs import is_url, vcs
|
||||
from pipenv.patched.notpip._internal.wheel import Wheel
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import ( # noqa: F401
|
||||
Optional, Tuple, Set, Any, Union, Text, Dict,
|
||||
from typing import (
|
||||
Any, Dict, Optional, Set, Tuple, Union,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.cache import WheelCache # noqa: F401
|
||||
from pipenv.patched.notpip._internal.cache import WheelCache
|
||||
|
||||
|
||||
__all__ = [
|
||||
@@ -46,6 +49,15 @@ logger = logging.getLogger(__name__)
|
||||
operators = Specifier._operators.keys()
|
||||
|
||||
|
||||
def is_archive_file(name):
|
||||
# type: (str) -> bool
|
||||
"""Return True if `name` is a considered as an archive file."""
|
||||
ext = splitext(name)[1].lower()
|
||||
if ext in ARCHIVE_EXTENSIONS:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _strip_extras(path):
|
||||
# type: (str) -> Tuple[str, Optional[str]]
|
||||
m = re.match(r'^(.+)(\[[^\]]+\])$', path)
|
||||
@@ -59,6 +71,13 @@ def _strip_extras(path):
|
||||
return path_no_extras, extras
|
||||
|
||||
|
||||
def convert_extras(extras):
|
||||
# type: (Optional[str]) -> Set[str]
|
||||
if not extras:
|
||||
return set()
|
||||
return Requirement("placeholder" + extras.lower()).extras
|
||||
|
||||
|
||||
def parse_editable(editable_req):
|
||||
# type: (str) -> Tuple[Optional[str], str, Optional[Set[str]]]
|
||||
"""Parses an editable requirement into:
|
||||
@@ -111,9 +130,9 @@ def parse_editable(editable_req):
|
||||
|
||||
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
|
||||
'{} is not a valid editable requirement. '
|
||||
'It should either be a path to a local project or a VCS URL '
|
||||
'(beginning with svn+, git+, hg+, or bzr+).'.format(editable_req)
|
||||
)
|
||||
|
||||
vc_type = url.split('+', 1)[0].lower()
|
||||
@@ -161,6 +180,37 @@ def deduce_helpful_msg(req):
|
||||
return msg
|
||||
|
||||
|
||||
class RequirementParts(object):
|
||||
def __init__(
|
||||
self,
|
||||
requirement, # type: Optional[Requirement]
|
||||
link, # type: Optional[Link]
|
||||
markers, # type: Optional[Marker]
|
||||
extras, # type: Set[str]
|
||||
):
|
||||
self.requirement = requirement
|
||||
self.link = link
|
||||
self.markers = markers
|
||||
self.extras = extras
|
||||
|
||||
|
||||
def parse_req_from_editable(editable_req):
|
||||
# type: (str) -> RequirementParts
|
||||
name, url, extras_override = parse_editable(editable_req)
|
||||
|
||||
if name is not None:
|
||||
try:
|
||||
req = Requirement(name)
|
||||
except InvalidRequirement:
|
||||
raise InstallationError("Invalid requirement: '%s'" % name)
|
||||
else:
|
||||
req = None
|
||||
|
||||
link = Link(url)
|
||||
|
||||
return RequirementParts(req, link, None, extras_override)
|
||||
|
||||
|
||||
# ---- The actual constructors follow ----
|
||||
|
||||
|
||||
@@ -174,45 +224,80 @@ def install_req_from_editable(
|
||||
constraint=False # type: bool
|
||||
):
|
||||
# type: (...) -> InstallRequirement
|
||||
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
|
||||
parts = parse_req_from_editable(editable_req)
|
||||
|
||||
source_dir = parts.link.file_path if parts.link.scheme == 'file' else None
|
||||
|
||||
return InstallRequirement(
|
||||
req, comes_from, source_dir=source_dir,
|
||||
parts.requirement, comes_from, source_dir=source_dir,
|
||||
editable=True,
|
||||
link=Link(url),
|
||||
link=parts.link,
|
||||
constraint=constraint,
|
||||
use_pep517=use_pep517,
|
||||
isolated=isolated,
|
||||
options=options if options else {},
|
||||
wheel_cache=wheel_cache,
|
||||
extras=extras_override or (),
|
||||
extras=parts.extras,
|
||||
)
|
||||
|
||||
|
||||
def install_req_from_line(
|
||||
name, # type: str
|
||||
comes_from=None, # type: Optional[Union[str, InstallRequirement]]
|
||||
use_pep517=None, # type: Optional[bool]
|
||||
isolated=False, # type: bool
|
||||
options=None, # type: Optional[Dict[str, Any]]
|
||||
wheel_cache=None, # type: Optional[WheelCache]
|
||||
constraint=False # type: bool
|
||||
):
|
||||
# type: (...) -> InstallRequirement
|
||||
"""Creates an InstallRequirement from a name, which might be a
|
||||
requirement, directory containing 'setup.py', filename, or URL.
|
||||
def _looks_like_path(name):
|
||||
# type: (str) -> bool
|
||||
"""Checks whether the string "looks like" a path on the filesystem.
|
||||
|
||||
This does not check whether the target actually exists, only judge from the
|
||||
appearance.
|
||||
|
||||
Returns true if any of the following conditions is true:
|
||||
* a path separator is found (either os.path.sep or os.path.altsep);
|
||||
* a dot is found (which represents the current directory).
|
||||
"""
|
||||
if os.path.sep in name:
|
||||
return True
|
||||
if os.path.altsep is not None and os.path.altsep in name:
|
||||
return True
|
||||
if name.startswith("."):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _get_url_from_path(path, name):
|
||||
# type: (str, str) -> str
|
||||
"""
|
||||
First, it checks whether a provided path is an installable directory
|
||||
(e.g. it has a setup.py). If it is, returns the path.
|
||||
|
||||
If false, check if the path is an archive file (such as a .whl).
|
||||
The function checks if the path is a file. If false, if the path has
|
||||
an @, it will treat it as a PEP 440 URL requirement and return the path.
|
||||
"""
|
||||
if _looks_like_path(name) and os.path.isdir(path):
|
||||
if is_installable_dir(path):
|
||||
return path_to_url(path)
|
||||
raise InstallationError(
|
||||
"Directory %r is not installable. Neither 'setup.py' "
|
||||
"nor 'pyproject.toml' found." % name
|
||||
)
|
||||
if not is_archive_file(path):
|
||||
return None
|
||||
if os.path.isfile(path):
|
||||
return path_to_url(path)
|
||||
urlreq_parts = name.split('@', 1)
|
||||
if len(urlreq_parts) >= 2 and not _looks_like_path(urlreq_parts[0]):
|
||||
# If the path contains '@' and the part before it does not look
|
||||
# like a path, try to treat it as a PEP 440 URL req instead.
|
||||
return None
|
||||
logger.warning(
|
||||
'Requirement %r looks like a filename, but the '
|
||||
'file does not exist',
|
||||
name
|
||||
)
|
||||
return path_to_url(path)
|
||||
|
||||
|
||||
def parse_req_from_line(name, line_source):
|
||||
# type: (str, Optional[str]) -> RequirementParts
|
||||
if is_url(name):
|
||||
marker_sep = '; '
|
||||
else:
|
||||
@@ -236,26 +321,9 @@ def install_req_from_line(
|
||||
link = Link(name)
|
||||
else:
|
||||
p, extras_as_string = _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))
|
||||
url = _get_url_from_path(p, name)
|
||||
if url is not None:
|
||||
link = Link(url)
|
||||
|
||||
# it's a local file, dir, or url
|
||||
if link:
|
||||
@@ -276,10 +344,13 @@ def install_req_from_line(
|
||||
else:
|
||||
req_as_string = name
|
||||
|
||||
if extras_as_string:
|
||||
extras = Requirement("placeholder" + extras_as_string.lower()).extras
|
||||
else:
|
||||
extras = ()
|
||||
extras = convert_extras(extras_as_string)
|
||||
|
||||
def with_source(text):
|
||||
if not line_source:
|
||||
return text
|
||||
return '{} (from {})'.format(text, line_source)
|
||||
|
||||
if req_as_string is not None:
|
||||
try:
|
||||
req = Requirement(req_as_string)
|
||||
@@ -291,20 +362,45 @@ def install_req_from_line(
|
||||
not any(op in req_as_string for op in operators)):
|
||||
add_msg = "= is not a valid operator. Did you mean == ?"
|
||||
else:
|
||||
add_msg = ""
|
||||
raise InstallationError(
|
||||
"Invalid requirement: '%s'\n%s" % (req_as_string, add_msg)
|
||||
add_msg = ''
|
||||
msg = with_source(
|
||||
'Invalid requirement: {!r}'.format(req_as_string)
|
||||
)
|
||||
if add_msg:
|
||||
msg += '\nHint: {}'.format(add_msg)
|
||||
raise InstallationError(msg)
|
||||
else:
|
||||
req = None
|
||||
|
||||
return RequirementParts(req, link, markers, extras)
|
||||
|
||||
|
||||
def install_req_from_line(
|
||||
name, # type: str
|
||||
comes_from=None, # type: Optional[Union[str, InstallRequirement]]
|
||||
use_pep517=None, # type: Optional[bool]
|
||||
isolated=False, # type: bool
|
||||
options=None, # type: Optional[Dict[str, Any]]
|
||||
wheel_cache=None, # type: Optional[WheelCache]
|
||||
constraint=False, # type: bool
|
||||
line_source=None, # type: Optional[str]
|
||||
):
|
||||
# type: (...) -> InstallRequirement
|
||||
"""Creates an InstallRequirement from a name, which might be a
|
||||
requirement, directory containing 'setup.py', filename, or URL.
|
||||
|
||||
:param line_source: An optional string describing where the line is from,
|
||||
for logging purposes in case of an error.
|
||||
"""
|
||||
parts = parse_req_from_line(name, line_source)
|
||||
|
||||
return InstallRequirement(
|
||||
req, comes_from, link=link, markers=markers,
|
||||
parts.requirement, comes_from, link=parts.link, markers=parts.markers,
|
||||
use_pep517=use_pep517, isolated=isolated,
|
||||
options=options if options else {},
|
||||
wheel_cache=wheel_cache,
|
||||
constraint=constraint,
|
||||
extras=extras,
|
||||
extras=parts.extras,
|
||||
)
|
||||
|
||||
|
||||
@@ -319,13 +415,14 @@ def install_req_from_req_string(
|
||||
try:
|
||||
req = Requirement(req_string)
|
||||
except InvalidRequirement:
|
||||
raise InstallationError("Invalid requirement: '%s'" % req)
|
||||
raise InstallationError("Invalid requirement: '%s'" % req_string)
|
||||
|
||||
domains_not_allowed = [
|
||||
PyPI.file_storage_domain,
|
||||
TestPyPI.file_storage_domain,
|
||||
]
|
||||
if req.url and comes_from.link.netloc in domains_not_allowed:
|
||||
if (req.url and comes_from and comes_from.link 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 "
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
Requirements file parsing
|
||||
"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import optparse
|
||||
@@ -16,26 +19,28 @@ from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse
|
||||
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.models.search_scope import SearchScope
|
||||
from pipenv.patched.notpip._internal.req.constructors import (
|
||||
install_req_from_editable, install_req_from_line,
|
||||
install_req_from_editable,
|
||||
install_req_from_line,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import ( # noqa: F401
|
||||
Iterator, Tuple, Optional, List, Callable, Text
|
||||
from typing import (
|
||||
Any, Callable, Iterator, List, NoReturn, Optional, Text, Tuple,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.req import InstallRequirement # noqa: F401
|
||||
from pipenv.patched.notpip._internal.cache import WheelCache # noqa: F401
|
||||
from pipenv.patched.notpip._internal.index import PackageFinder # noqa: F401
|
||||
from pipenv.patched.notpip._internal.download import PipSession # noqa: F401
|
||||
from pipenv.patched.notpip._internal.req import InstallRequirement
|
||||
from pipenv.patched.notpip._internal.cache import WheelCache
|
||||
from pipenv.patched.notpip._internal.index import PackageFinder
|
||||
from pipenv.patched.notpip._internal.network.session import PipSession
|
||||
|
||||
ReqFileLines = Iterator[Tuple[int, Text]]
|
||||
|
||||
__all__ = ['parse_requirements']
|
||||
|
||||
SCHEME_RE = re.compile(r'^(http|https|file):', re.I)
|
||||
COMMENT_RE = re.compile(r'(^|\s)+#.*$')
|
||||
COMMENT_RE = re.compile(r'(^|\s+)#.*$')
|
||||
|
||||
# Matches environment variable-style values in '${MY_VARIABLE_1}' with the
|
||||
# variable name consisting of only uppercase letters, digits or the '_'
|
||||
@@ -138,7 +143,7 @@ def process_line(
|
||||
session=None, # type: Optional[PipSession]
|
||||
wheel_cache=None, # type: Optional[WheelCache]
|
||||
use_pep517=None, # type: Optional[bool]
|
||||
constraint=False # type: bool
|
||||
constraint=False, # type: bool
|
||||
):
|
||||
# type: (...) -> Iterator[InstallRequirement]
|
||||
"""Process a single requirements line; This can result in creating/yielding
|
||||
@@ -187,10 +192,16 @@ def process_line(
|
||||
for dest in SUPPORTED_OPTIONS_REQ_DEST:
|
||||
if dest in opts.__dict__ and opts.__dict__[dest]:
|
||||
req_options[dest] = opts.__dict__[dest]
|
||||
line_source = 'line {} of {}'.format(line_number, filename)
|
||||
yield install_req_from_line(
|
||||
args_str, line_comes_from, constraint=constraint,
|
||||
args_str,
|
||||
comes_from=line_comes_from,
|
||||
use_pep517=use_pep517,
|
||||
isolated=isolated, options=req_options, wheel_cache=wheel_cache
|
||||
isolated=isolated,
|
||||
options=req_options,
|
||||
wheel_cache=wheel_cache,
|
||||
constraint=constraint,
|
||||
line_source=line_source,
|
||||
)
|
||||
|
||||
# yield an editable requirement
|
||||
@@ -232,12 +243,14 @@ def process_line(
|
||||
|
||||
# set finder options
|
||||
elif finder:
|
||||
find_links = finder.find_links
|
||||
index_urls = finder.index_urls
|
||||
if opts.index_url:
|
||||
finder.index_urls = [opts.index_url]
|
||||
index_urls = [opts.index_url]
|
||||
if opts.no_index is True:
|
||||
finder.index_urls = []
|
||||
index_urls = []
|
||||
if opts.extra_index_urls:
|
||||
finder.index_urls.extend(opts.extra_index_urls)
|
||||
index_urls.extend(opts.extra_index_urls)
|
||||
if opts.find_links:
|
||||
# FIXME: it would be nice to keep track of the source
|
||||
# of the find_links: support a find-links local path
|
||||
@@ -247,12 +260,19 @@ def process_line(
|
||||
relative_to_reqs_file = os.path.join(req_dir, value)
|
||||
if os.path.exists(relative_to_reqs_file):
|
||||
value = relative_to_reqs_file
|
||||
finder.find_links.append(value)
|
||||
find_links.append(value)
|
||||
|
||||
search_scope = SearchScope(
|
||||
find_links=find_links,
|
||||
index_urls=index_urls,
|
||||
)
|
||||
finder.search_scope = search_scope
|
||||
|
||||
if opts.pre:
|
||||
finder.allow_all_prereleases = True
|
||||
if opts.trusted_hosts:
|
||||
finder.secure_origins.extend(
|
||||
("*", host, "*") for host in opts.trusted_hosts)
|
||||
finder.set_allow_all_prereleases()
|
||||
for host in opts.trusted_hosts or []:
|
||||
source = 'line {} of {}'.format(line_number, filename)
|
||||
session.add_trusted_host(host, source=source)
|
||||
|
||||
|
||||
def break_args_options(line):
|
||||
@@ -288,6 +308,7 @@ def build_parser(line):
|
||||
# By default optparse sys.exits on parsing errors. We want to wrap
|
||||
# that in our own exception.
|
||||
def parser_exit(self, msg):
|
||||
# type: (Any, str) -> NoReturn
|
||||
# add offending line
|
||||
msg = 'Invalid requirement: %s\n%s' % (line, msg)
|
||||
raise RequirementsFileParseError(msg)
|
||||
@@ -364,7 +385,7 @@ def expand_env_variables(lines_enum):
|
||||
1. Strings that contain a `$` aren't accidentally (partially) expanded.
|
||||
2. Ensure consistency across platforms for requirement files.
|
||||
|
||||
These points are the result of a discusssion on the `github pull
|
||||
These points are the result of a discussion on the `github pull
|
||||
request #3514 <https://github.com/pypa/pip/pull/3514>`_.
|
||||
|
||||
Valid characters in variable names follow the `POSIX standard
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import atexit
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
@@ -15,41 +20,54 @@ 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.pep517.wrappers import Pep517HookCaller
|
||||
|
||||
from pipenv.patched.notpip._internal import wheel
|
||||
from pipenv.patched.notpip._internal import pep425tags, wheel
|
||||
from pipenv.patched.notpip._internal.build_env import NoOpBuildEnvironment
|
||||
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.operations.generate_metadata import get_metadata_generator
|
||||
from pipenv.patched.notpip._internal.pyproject import load_pyproject_toml, make_pyproject_path
|
||||
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.marker_files import (
|
||||
PIP_DELETE_MARKER_FILENAME,
|
||||
has_delete_marker_file,
|
||||
)
|
||||
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, redact_password_from_url, rmtree,
|
||||
_make_build_dir,
|
||||
ask_path_exists,
|
||||
backup_dir,
|
||||
display_path,
|
||||
dist_in_site_packages,
|
||||
dist_in_usersite,
|
||||
ensure_dir,
|
||||
get_installed_version,
|
||||
hide_url,
|
||||
redact_auth_from_url,
|
||||
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.setuptools_build import make_setuptools_shim_args
|
||||
from pipenv.patched.notpip._internal.utils.subprocess import (
|
||||
call_subprocess,
|
||||
runner_with_spinner_message,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
from pipenv.patched.notpip._internal.utils.ui import open_spinner
|
||||
from pipenv.patched.notpip._internal.utils.virtualenv import running_under_virtualenv
|
||||
from pipenv.patched.notpip._internal.vcs import vcs
|
||||
from pipenv.patched.notpip._internal.wheel import move_wheel_files
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import ( # noqa: F401
|
||||
Optional, Iterable, List, Union, Any, Text, Sequence, Dict
|
||||
from typing import (
|
||||
Any, Dict, Iterable, List, Optional, Sequence, Union,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.build_env import BuildEnvironment # noqa: F401
|
||||
from pipenv.patched.notpip._internal.cache import WheelCache # noqa: F401
|
||||
from pipenv.patched.notpip._internal.index import PackageFinder # noqa: F401
|
||||
from pipenv.patched.notpip._vendor.pkg_resources import Distribution # noqa: F401
|
||||
from pipenv.patched.notpip._vendor.packaging.specifiers import SpecifierSet # noqa: F401
|
||||
from pipenv.patched.notpip._vendor.packaging.markers import Marker # noqa: F401
|
||||
from pipenv.patched.notpip._internal.build_env import BuildEnvironment
|
||||
from pipenv.patched.notpip._internal.cache import WheelCache
|
||||
from pipenv.patched.notpip._internal.index import PackageFinder
|
||||
from pipenv.patched.notpip._vendor.pkg_resources import Distribution
|
||||
from pipenv.patched.notpip._vendor.packaging.specifiers import SpecifierSet
|
||||
from pipenv.patched.notpip._vendor.packaging.markers import Marker
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -58,7 +76,7 @@ logger = logging.getLogger(__name__)
|
||||
class InstallRequirement(object):
|
||||
"""
|
||||
Represents something that may be installed later on, may have information
|
||||
about where to fetch the relavant requirement and also contains logic for
|
||||
about where to fetch the relevant requirement and also contains logic for
|
||||
installing the said requirement.
|
||||
"""
|
||||
|
||||
@@ -69,7 +87,6 @@ class InstallRequirement(object):
|
||||
source_dir=None, # type: Optional[str]
|
||||
editable=False, # type: bool
|
||||
link=None, # type: Optional[Link]
|
||||
update=True, # type: bool
|
||||
markers=None, # type: Optional[Marker]
|
||||
use_pep517=None, # type: Optional[bool]
|
||||
isolated=False, # type: bool
|
||||
@@ -83,10 +100,10 @@ class InstallRequirement(object):
|
||||
self.req = req
|
||||
self.comes_from = comes_from
|
||||
self.constraint = constraint
|
||||
if source_dir is not None:
|
||||
self.source_dir = os.path.normpath(os.path.abspath(source_dir))
|
||||
if source_dir is None:
|
||||
self.source_dir = None # type: Optional[str]
|
||||
else:
|
||||
self.source_dir = None
|
||||
self.source_dir = os.path.normpath(os.path.abspath(source_dir))
|
||||
self.editable = editable
|
||||
|
||||
self._wheel_cache = wheel_cache
|
||||
@@ -107,7 +124,6 @@ class InstallRequirement(object):
|
||||
markers = req.marker
|
||||
self.markers = markers
|
||||
|
||||
self._egg_info_path = None # type: Optional[str]
|
||||
# This holds the pkg_resources.Distribution object if this requirement
|
||||
# is already available:
|
||||
self.satisfied_by = None
|
||||
@@ -115,16 +131,12 @@ class InstallRequirement(object):
|
||||
# conflicts with another installed distribution:
|
||||
self.conflicts_with = None
|
||||
# Temporary build location
|
||||
self._temp_build_dir = TempDirectory(kind="req-build")
|
||||
self._temp_build_dir = None # type: Optional[TempDirectory]
|
||||
# Used to store the global directory where the _temp_build_dir should
|
||||
# have been created. Cf _correct_build_location method.
|
||||
# have been created. Cf move_to_correct_build_directory method.
|
||||
self._ideal_build_dir = None # type: Optional[str]
|
||||
# True if the editable should be updated:
|
||||
self.update = update
|
||||
# Set to True after successful installation
|
||||
self.install_succeeded = None # type: Optional[bool]
|
||||
# UninstallPathSet of uninstalled distribution (for possible rollback)
|
||||
self.uninstalled_pathset = None
|
||||
self.options = options if options else {}
|
||||
# Set to True after successful preparation of this requirement
|
||||
self.prepared = False
|
||||
@@ -156,19 +168,20 @@ class InstallRequirement(object):
|
||||
self.use_pep517 = use_pep517
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
if self.req:
|
||||
s = str(self.req)
|
||||
if self.link:
|
||||
s += ' from %s' % redact_password_from_url(self.link.url)
|
||||
s += ' from %s' % redact_auth_from_url(self.link.url)
|
||||
elif self.link:
|
||||
s = redact_password_from_url(self.link.url)
|
||||
s = redact_auth_from_url(self.link.url)
|
||||
else:
|
||||
s = '<InstallRequirement>'
|
||||
if self.satisfied_by is not None:
|
||||
s += ' in %s' % display_path(self.satisfied_by.location)
|
||||
if self.comes_from:
|
||||
if isinstance(self.comes_from, six.string_types):
|
||||
comes_from = self.comes_from
|
||||
comes_from = self.comes_from # type: Optional[str]
|
||||
else:
|
||||
comes_from = self.comes_from.from_path()
|
||||
if comes_from:
|
||||
@@ -176,9 +189,25 @@ class InstallRequirement(object):
|
||||
return s
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return '<%s object: %s editable=%r>' % (
|
||||
self.__class__.__name__, str(self), self.editable)
|
||||
|
||||
def format_debug(self):
|
||||
# type: () -> str
|
||||
"""An un-tested helper for getting state, for debugging.
|
||||
"""
|
||||
attributes = vars(self)
|
||||
names = sorted(attributes)
|
||||
|
||||
state = (
|
||||
"{}={!r}".format(attr, attributes[attr]) for attr in sorted(names)
|
||||
)
|
||||
return '<{name} object: {{{state}}}>'.format(
|
||||
name=self.__class__.__name__,
|
||||
state=", ".join(state),
|
||||
)
|
||||
|
||||
def populate_link(self, finder, upgrade, require_hashes):
|
||||
# type: (PackageFinder, bool, bool) -> None
|
||||
"""Ensure that if a link can be found for this, that it is found.
|
||||
@@ -196,7 +225,12 @@ class InstallRequirement(object):
|
||||
self.link = finder.find_requirement(self, upgrade)
|
||||
if self._wheel_cache is not None and not require_hashes:
|
||||
old_link = self.link
|
||||
self.link = self._wheel_cache.get(self.link, self.name)
|
||||
supported_tags = pep425tags.get_supported()
|
||||
self.link = self._wheel_cache.get(
|
||||
link=self.link,
|
||||
package_name=self.name,
|
||||
supported_tags=supported_tags,
|
||||
)
|
||||
if old_link != self.link:
|
||||
logger.debug('Using cached wheel link: %s', self.link)
|
||||
|
||||
@@ -226,6 +260,7 @@ class InstallRequirement(object):
|
||||
|
||||
@property
|
||||
def installed_version(self):
|
||||
# type: () -> Optional[str]
|
||||
return get_installed_version(self.name)
|
||||
|
||||
def match_markers(self, extras_requested=None):
|
||||
@@ -290,20 +325,21 @@ class InstallRequirement(object):
|
||||
s += '->' + comes_from
|
||||
return s
|
||||
|
||||
def build_location(self, build_dir):
|
||||
# type: (str) -> Optional[str]
|
||||
def ensure_build_location(self, build_dir):
|
||||
# type: (str) -> str
|
||||
assert build_dir is not None
|
||||
if self._temp_build_dir.path is not None:
|
||||
if self._temp_build_dir is not None:
|
||||
assert self._temp_build_dir.path
|
||||
return self._temp_build_dir.path
|
||||
if self.req is None:
|
||||
# for requirement via a path to a directory: the name of the
|
||||
# package is not available yet so we create a temp directory
|
||||
# Once run_egg_info will have run, we'll be able
|
||||
# to fix it via _correct_build_location
|
||||
# Once run_egg_info will have run, we'll be able to fix it via
|
||||
# move_to_correct_build_directory().
|
||||
# Some systems have /tmp as a symlink which confuses custom
|
||||
# builds (such as numpy). Thus, we ensure that the real path
|
||||
# is returned.
|
||||
self._temp_build_dir.create()
|
||||
self._temp_build_dir = TempDirectory(kind="req-build")
|
||||
self._ideal_build_dir = build_dir
|
||||
|
||||
return self._temp_build_dir.path
|
||||
@@ -318,59 +354,73 @@ class InstallRequirement(object):
|
||||
_make_build_dir(build_dir)
|
||||
return os.path.join(build_dir, name)
|
||||
|
||||
def _correct_build_location(self):
|
||||
def move_to_correct_build_directory(self):
|
||||
# type: () -> None
|
||||
"""Move self._temp_build_dir to self._ideal_build_dir/self.req.name
|
||||
"""Move self._temp_build_dir to "self._ideal_build_dir/self.req.name"
|
||||
|
||||
For some requirements (e.g. a path to a directory), the name of the
|
||||
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.run_egg_info to fix the temporary build
|
||||
directory.
|
||||
This is only called to "fix" the build directory after generating
|
||||
metadata.
|
||||
"""
|
||||
if self.source_dir is not None:
|
||||
return
|
||||
assert self.req is not None
|
||||
assert self._temp_build_dir.path
|
||||
assert (self._ideal_build_dir is not None and
|
||||
self._ideal_build_dir.path) # type: ignore
|
||||
old_location = self._temp_build_dir.path
|
||||
self._temp_build_dir.path = None
|
||||
assert self._temp_build_dir
|
||||
assert (
|
||||
self._ideal_build_dir is not None and
|
||||
self._ideal_build_dir.path # type: ignore
|
||||
)
|
||||
old_location = self._temp_build_dir
|
||||
self._temp_build_dir = None # checked inside ensure_build_location
|
||||
|
||||
new_location = self.build_location(self._ideal_build_dir)
|
||||
# Figure out the correct place to put the files.
|
||||
new_location = self.ensure_build_location(self._ideal_build_dir)
|
||||
if os.path.exists(new_location):
|
||||
raise InstallationError(
|
||||
'A package already exists in %s; please remove it to continue'
|
||||
% display_path(new_location))
|
||||
% display_path(new_location)
|
||||
)
|
||||
|
||||
# Move the files to the correct location.
|
||||
logger.debug(
|
||||
'Moving package %s from %s to new location %s',
|
||||
self, display_path(old_location), display_path(new_location),
|
||||
self, display_path(old_location.path), display_path(new_location),
|
||||
)
|
||||
shutil.move(old_location, new_location)
|
||||
self._temp_build_dir.path = new_location
|
||||
self._ideal_build_dir = None
|
||||
shutil.move(old_location.path, new_location)
|
||||
|
||||
# Update directory-tracking variables, to be in line with new_location
|
||||
self.source_dir = os.path.normpath(os.path.abspath(new_location))
|
||||
self._egg_info_path = None
|
||||
self._temp_build_dir = TempDirectory(
|
||||
path=new_location, kind="req-install",
|
||||
)
|
||||
|
||||
# Correct the metadata directory, if it exists
|
||||
if self.metadata_directory:
|
||||
old_meta = self.metadata_directory
|
||||
rel = os.path.relpath(old_meta, start=old_location)
|
||||
rel = os.path.relpath(old_meta, start=old_location.path)
|
||||
new_meta = os.path.join(new_location, rel)
|
||||
new_meta = os.path.normpath(os.path.abspath(new_meta))
|
||||
self.metadata_directory = new_meta
|
||||
|
||||
# Done with any "move built files" work, since have moved files to the
|
||||
# "ideal" build location. Setting to None allows to clearly flag that
|
||||
# no more moves are needed.
|
||||
self._ideal_build_dir = None
|
||||
|
||||
def remove_temporary_source(self):
|
||||
# type: () -> None
|
||||
"""Remove the source files from this requirement, if they are marked
|
||||
for deletion"""
|
||||
if self.source_dir and os.path.exists(
|
||||
os.path.join(self.source_dir, PIP_DELETE_MARKER_FILENAME)):
|
||||
if self.source_dir and has_delete_marker_file(self.source_dir):
|
||||
logger.debug('Removing source in %s', self.source_dir)
|
||||
rmtree(self.source_dir)
|
||||
self.source_dir = None
|
||||
self._temp_build_dir.cleanup()
|
||||
if self._temp_build_dir:
|
||||
self._temp_build_dir.cleanup()
|
||||
self._temp_build_dir = None
|
||||
self.build_env.cleanup()
|
||||
|
||||
def check_if_exists(self, use_user_site):
|
||||
@@ -434,7 +484,7 @@ class InstallRequirement(object):
|
||||
pycompile=True # type: bool
|
||||
):
|
||||
# type: (...) -> None
|
||||
move_wheel_files(
|
||||
wheel.move_wheel_files(
|
||||
self.name, self.req, wheeldir,
|
||||
user=use_user_site,
|
||||
home=home,
|
||||
@@ -447,18 +497,17 @@ class InstallRequirement(object):
|
||||
|
||||
# Things valid for sdists
|
||||
@property
|
||||
def setup_py_dir(self):
|
||||
def unpacked_source_directory(self):
|
||||
# type: () -> str
|
||||
return os.path.join(
|
||||
self.source_dir,
|
||||
self.link and self.link.subdirectory_fragment or '')
|
||||
|
||||
@property
|
||||
def setup_py(self):
|
||||
def setup_py_path(self):
|
||||
# type: () -> str
|
||||
assert self.source_dir, "No source dir for %s" % self
|
||||
|
||||
setup_py = os.path.join(self.setup_py_dir, 'setup.py')
|
||||
setup_py = os.path.join(self.unpacked_source_directory, 'setup.py')
|
||||
|
||||
# Python2 __file__ should not be unicode
|
||||
if six.PY2 and isinstance(setup_py, six.text_type):
|
||||
@@ -467,11 +516,10 @@ class InstallRequirement(object):
|
||||
return setup_py
|
||||
|
||||
@property
|
||||
def pyproject_toml(self):
|
||||
def pyproject_toml_path(self):
|
||||
# type: () -> str
|
||||
assert self.source_dir, "No source dir for %s" % self
|
||||
|
||||
return make_pyproject_path(self.setup_py_dir)
|
||||
return make_pyproject_path(self.unpacked_source_directory)
|
||||
|
||||
def load_pyproject_toml(self):
|
||||
# type: () -> None
|
||||
@@ -482,37 +530,24 @@ class InstallRequirement(object):
|
||||
use_pep517 attribute can be used to determine whether we should
|
||||
follow the PEP 517 or legacy (setup.py) code path.
|
||||
"""
|
||||
pep517_data = load_pyproject_toml(
|
||||
pyproject_toml_data = load_pyproject_toml(
|
||||
self.use_pep517,
|
||||
self.pyproject_toml,
|
||||
self.setup_py,
|
||||
self.pyproject_toml_path,
|
||||
self.setup_py_path,
|
||||
str(self)
|
||||
)
|
||||
|
||||
if pep517_data is None:
|
||||
if pyproject_toml_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)
|
||||
return
|
||||
|
||||
# Use a custom function to call subprocesses
|
||||
self.spin_message = ""
|
||||
|
||||
def runner(cmd, cwd=None, extra_environ=None):
|
||||
with open_spinner(self.spin_message) as spinner:
|
||||
call_subprocess(
|
||||
cmd,
|
||||
cwd=cwd,
|
||||
extra_environ=extra_environ,
|
||||
show_stdout=False,
|
||||
spinner=spinner
|
||||
)
|
||||
self.spin_message = ""
|
||||
|
||||
self.pep517_backend._subprocess_runner = runner
|
||||
self.use_pep517 = True
|
||||
requires, backend, check = pyproject_toml_data
|
||||
self.requirements_to_check = check
|
||||
self.pyproject_requires = requires
|
||||
self.pep517_backend = Pep517HookCaller(
|
||||
self.unpacked_source_directory, backend
|
||||
)
|
||||
|
||||
def prepare_metadata(self):
|
||||
# type: () -> None
|
||||
@@ -523,11 +558,9 @@ class InstallRequirement(object):
|
||||
"""
|
||||
assert self.source_dir
|
||||
|
||||
metadata_generator = get_metadata_generator(self)
|
||||
with indent_log():
|
||||
if self.use_pep517:
|
||||
self.prepare_pep517_metadata()
|
||||
else:
|
||||
self.run_egg_info()
|
||||
self.metadata_directory = metadata_generator(self)
|
||||
|
||||
if not self.req:
|
||||
if isinstance(parse_version(self.metadata["Version"]), Version):
|
||||
@@ -541,7 +574,7 @@ class InstallRequirement(object):
|
||||
self.metadata["Version"],
|
||||
])
|
||||
)
|
||||
self._correct_build_location()
|
||||
self.move_to_correct_build_directory()
|
||||
else:
|
||||
metadata_name = canonicalize_name(self.metadata["Name"])
|
||||
if canonicalize_name(self.req.name) != metadata_name:
|
||||
@@ -554,116 +587,31 @@ class InstallRequirement(object):
|
||||
self.req = Requirement(metadata_name)
|
||||
|
||||
def prepare_pep517_metadata(self):
|
||||
# type: () -> None
|
||||
# type: () -> str
|
||||
assert self.pep517_backend is not None
|
||||
|
||||
metadata_dir = os.path.join(
|
||||
self.setup_py_dir,
|
||||
'pip-wheel-metadata'
|
||||
)
|
||||
ensure_dir(metadata_dir)
|
||||
# NOTE: This needs to be refactored to stop using atexit
|
||||
metadata_tmpdir = TempDirectory(kind="modern-metadata")
|
||||
atexit.register(metadata_tmpdir.cleanup)
|
||||
|
||||
metadata_dir = metadata_tmpdir.path
|
||||
|
||||
with self.build_env:
|
||||
# Note that Pep517HookCaller implements a fallback for
|
||||
# prepare_metadata_for_build_wheel, so we don't have to
|
||||
# consider the possibility that this hook doesn't exist.
|
||||
runner = runner_with_spinner_message("Preparing wheel metadata")
|
||||
backend = self.pep517_backend
|
||||
self.spin_message = "Preparing wheel metadata"
|
||||
distinfo_dir = backend.prepare_metadata_for_build_wheel(
|
||||
metadata_dir
|
||||
)
|
||||
|
||||
self.metadata_directory = os.path.join(metadata_dir, distinfo_dir)
|
||||
|
||||
def run_egg_info(self):
|
||||
# type: () -> None
|
||||
if self.name:
|
||||
logger.debug(
|
||||
'Running setup.py (path:%s) egg_info for package %s',
|
||||
self.setup_py, self.name,
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
'Running setup.py (path:%s) egg_info for package from %s',
|
||||
self.setup_py, self.link,
|
||||
)
|
||||
script = SETUPTOOLS_SHIM % self.setup_py
|
||||
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']
|
||||
# We can't put the .egg-info files at the root, because then the
|
||||
# source code will be mistaken for an installed egg, causing
|
||||
# problems
|
||||
if self.editable:
|
||||
egg_base_option = [] # type: List[str]
|
||||
else:
|
||||
egg_info_dir = os.path.join(self.setup_py_dir, 'pip-egg-info')
|
||||
ensure_dir(egg_info_dir)
|
||||
egg_base_option = ['--egg-base', 'pip-egg-info']
|
||||
with self.build_env:
|
||||
call_subprocess(
|
||||
egg_info_cmd + egg_base_option,
|
||||
cwd=self.setup_py_dir,
|
||||
show_stdout=False,
|
||||
command_desc='python setup.py egg_info')
|
||||
|
||||
@property
|
||||
def egg_info_path(self):
|
||||
# type: () -> str
|
||||
if self._egg_info_path is None:
|
||||
if self.editable:
|
||||
base = self.source_dir
|
||||
else:
|
||||
base = os.path.join(self.setup_py_dir, 'pip-egg-info')
|
||||
filenames = os.listdir(base)
|
||||
if self.editable:
|
||||
filenames = []
|
||||
for root, dirs, files in os.walk(base):
|
||||
for dir in vcs.dirnames:
|
||||
if dir in dirs:
|
||||
dirs.remove(dir)
|
||||
# Iterate over a copy of ``dirs``, since mutating
|
||||
# a list while iterating over it can cause trouble.
|
||||
# (See https://github.com/pypa/pip/pull/462.)
|
||||
for dir in list(dirs):
|
||||
# Don't search in anything that looks like a virtualenv
|
||||
# environment
|
||||
if (
|
||||
os.path.lexists(
|
||||
os.path.join(root, dir, 'bin', 'python')
|
||||
) or
|
||||
os.path.exists(
|
||||
os.path.join(
|
||||
root, dir, 'Scripts', 'Python.exe'
|
||||
)
|
||||
)):
|
||||
dirs.remove(dir)
|
||||
# Also don't search through tests
|
||||
elif dir == 'test' or dir == 'tests':
|
||||
dirs.remove(dir)
|
||||
filenames.extend([os.path.join(root, dir)
|
||||
for dir in dirs])
|
||||
filenames = [f for f in filenames if f.endswith('.egg-info')]
|
||||
|
||||
if not filenames:
|
||||
raise InstallationError(
|
||||
"Files/directories not found in %s" % base
|
||||
with backend.subprocess_runner(runner):
|
||||
distinfo_dir = backend.prepare_metadata_for_build_wheel(
|
||||
metadata_dir
|
||||
)
|
||||
# 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
|
||||
# an extracted tarball for testing purposes.
|
||||
if len(filenames) > 1:
|
||||
filenames.sort(
|
||||
key=lambda x: x.count(os.path.sep) +
|
||||
(os.path.altsep and x.count(os.path.altsep) or 0)
|
||||
)
|
||||
self._egg_info_path = os.path.join(base, filenames[0])
|
||||
return self._egg_info_path
|
||||
|
||||
return os.path.join(metadata_dir, distinfo_dir)
|
||||
|
||||
@property
|
||||
def metadata(self):
|
||||
# type: () -> Any
|
||||
if not hasattr(self, '_metadata'):
|
||||
self._metadata = get_metadata(self.get_dist())
|
||||
|
||||
@@ -672,22 +620,21 @@ class InstallRequirement(object):
|
||||
def get_dist(self):
|
||||
# type: () -> Distribution
|
||||
"""Return a pkg_resources.Distribution for this requirement"""
|
||||
if self.metadata_directory:
|
||||
base_dir, distinfo = os.path.split(self.metadata_directory)
|
||||
metadata = pkg_resources.PathMetadata(
|
||||
base_dir, self.metadata_directory
|
||||
)
|
||||
dist_name = os.path.splitext(distinfo)[0]
|
||||
typ = pkg_resources.DistInfoDistribution
|
||||
else:
|
||||
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]
|
||||
# https://github.com/python/mypy/issues/1174
|
||||
typ = pkg_resources.Distribution # type: ignore
|
||||
dist_dir = self.metadata_directory.rstrip(os.sep)
|
||||
|
||||
return typ(
|
||||
# Determine the correct Distribution object type.
|
||||
if dist_dir.endswith(".egg-info"):
|
||||
dist_cls = pkg_resources.Distribution
|
||||
else:
|
||||
assert dist_dir.endswith(".dist-info")
|
||||
dist_cls = pkg_resources.DistInfoDistribution
|
||||
|
||||
# Build a PathMetadata object, from path to metadata. :wink:
|
||||
base_dir, dist_dir_name = os.path.split(dist_dir)
|
||||
dist_name = os.path.splitext(dist_dir_name)[0]
|
||||
metadata = pkg_resources.PathMetadata(base_dir, dist_dir)
|
||||
|
||||
return dist_cls(
|
||||
base_dir,
|
||||
project_name=dist_name,
|
||||
metadata=metadata,
|
||||
@@ -713,7 +660,7 @@ class InstallRequirement(object):
|
||||
|
||||
# For both source distributions and editables
|
||||
def ensure_has_source_dir(self, parent_dir):
|
||||
# type: (str) -> str
|
||||
# type: (str) -> None
|
||||
"""Ensure that a source_dir is set.
|
||||
|
||||
This will create a temporary build dir if the name of the requirement
|
||||
@@ -724,8 +671,7 @@ class InstallRequirement(object):
|
||||
:return: self.source_dir
|
||||
"""
|
||||
if self.source_dir is None:
|
||||
self.source_dir = self.build_location(parent_dir)
|
||||
return self.source_dir
|
||||
self.source_dir = self.ensure_build_location(parent_dir)
|
||||
|
||||
# For editable installations
|
||||
def install_editable(
|
||||
@@ -737,29 +683,21 @@ class InstallRequirement(object):
|
||||
# type: (...) -> None
|
||||
logger.info('Running setup.py develop for %s', self.name)
|
||||
|
||||
if self.isolated:
|
||||
global_options = list(global_options) + ["--no-user-cfg"]
|
||||
|
||||
if prefix:
|
||||
prefix_param = ['--prefix={}'.format(prefix)]
|
||||
install_options = list(install_options) + prefix_param
|
||||
|
||||
base_cmd = make_setuptools_shim_args(
|
||||
self.setup_py_path,
|
||||
global_options=global_options,
|
||||
no_user_config=self.isolated
|
||||
)
|
||||
with indent_log():
|
||||
# FIXME: should we do --install-headers here too?
|
||||
with self.build_env:
|
||||
sys_executable = os.environ.get('PIP_PYTHON_PATH', sys.executable)
|
||||
call_subprocess(
|
||||
[
|
||||
sys_executable,
|
||||
'-c',
|
||||
SETUPTOOLS_SHIM % self.setup_py
|
||||
] +
|
||||
list(global_options) +
|
||||
base_cmd +
|
||||
['develop', '--no-deps'] +
|
||||
list(install_options),
|
||||
|
||||
cwd=self.setup_py_dir,
|
||||
show_stdout=False,
|
||||
cwd=self.unpacked_source_directory,
|
||||
)
|
||||
|
||||
self.install_succeeded = True
|
||||
@@ -779,16 +717,14 @@ class InstallRequirement(object):
|
||||
# Static paths don't get updated
|
||||
return
|
||||
assert '+' in self.link.url, "bad url: %r" % self.link.url
|
||||
if not self.update:
|
||||
return
|
||||
vc_type, url = self.link.url.split('+', 1)
|
||||
backend = vcs.get_backend(vc_type)
|
||||
if backend:
|
||||
vcs_backend = backend(self.link.url)
|
||||
vcs_backend = vcs.get_backend(vc_type)
|
||||
if vcs_backend:
|
||||
hidden_url = hide_url(self.link.url)
|
||||
if obtain:
|
||||
vcs_backend.obtain(self.source_dir)
|
||||
vcs_backend.obtain(self.source_dir, url=hidden_url)
|
||||
else:
|
||||
vcs_backend.export(self.source_dir)
|
||||
vcs_backend.export(self.source_dir, url=hidden_url)
|
||||
else:
|
||||
assert 0, (
|
||||
'Unexpected version control type (in %s): %s'
|
||||
@@ -820,6 +756,7 @@ class InstallRequirement(object):
|
||||
return uninstalled_pathset
|
||||
|
||||
def _clean_zip_name(self, name, prefix): # only used by archive.
|
||||
# type: (str, str) -> str
|
||||
assert name.startswith(prefix + os.path.sep), (
|
||||
"name %r doesn't start with prefix %r" % (name, prefix)
|
||||
)
|
||||
@@ -833,14 +770,18 @@ class InstallRequirement(object):
|
||||
name = self._clean_zip_name(path, rootdir)
|
||||
return self.name + '/' + name
|
||||
|
||||
# TODO: Investigate if this should be kept in InstallRequirement
|
||||
# Seems to be used only when VCS + downloads
|
||||
def archive(self, build_dir):
|
||||
# type: (str) -> None
|
||||
"""Saves archive to provided build_dir.
|
||||
|
||||
Used for saving downloaded VCS requirements as part of `pip download`.
|
||||
"""
|
||||
assert self.source_dir
|
||||
|
||||
create_archive = True
|
||||
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(
|
||||
'The file %s exists. (i)gnore, (w)ipe, (b)ackup, (a)bort ' %
|
||||
@@ -860,32 +801,37 @@ class InstallRequirement(object):
|
||||
shutil.move(archive_path, dest_file)
|
||||
elif response == 'a':
|
||||
sys.exit(-1)
|
||||
if create_archive:
|
||||
zip = zipfile.ZipFile(
|
||||
archive_path, 'w', zipfile.ZIP_DEFLATED,
|
||||
allowZip64=True
|
||||
|
||||
if not create_archive:
|
||||
return
|
||||
|
||||
zip_output = zipfile.ZipFile(
|
||||
archive_path, 'w', zipfile.ZIP_DEFLATED, allowZip64=True,
|
||||
)
|
||||
with zip_output:
|
||||
dir = os.path.normcase(
|
||||
os.path.abspath(self.unpacked_source_directory)
|
||||
)
|
||||
dir = os.path.normcase(os.path.abspath(self.setup_py_dir))
|
||||
for dirpath, dirnames, filenames in os.walk(dir):
|
||||
if 'pip-egg-info' in dirnames:
|
||||
dirnames.remove('pip-egg-info')
|
||||
for dirname in dirnames:
|
||||
dir_arcname = self._get_archive_name(dirname,
|
||||
parentdir=dirpath,
|
||||
rootdir=dir)
|
||||
dir_arcname = self._get_archive_name(
|
||||
dirname, parentdir=dirpath, rootdir=dir,
|
||||
)
|
||||
zipdir = zipfile.ZipInfo(dir_arcname + '/')
|
||||
zipdir.external_attr = 0x1ED << 16 # 0o755
|
||||
zip.writestr(zipdir, '')
|
||||
zip_output.writestr(zipdir, '')
|
||||
for filename in filenames:
|
||||
if filename == PIP_DELETE_MARKER_FILENAME:
|
||||
continue
|
||||
file_arcname = self._get_archive_name(filename,
|
||||
parentdir=dirpath,
|
||||
rootdir=dir)
|
||||
file_arcname = self._get_archive_name(
|
||||
filename, parentdir=dirpath, rootdir=dir,
|
||||
)
|
||||
filename = os.path.join(dirpath, filename)
|
||||
zip.write(filename, file_arcname)
|
||||
zip.close()
|
||||
logger.info('Saved %s', display_path(archive_path))
|
||||
zip_output.write(filename, file_arcname)
|
||||
|
||||
logger.info('Saved %s', display_path(archive_path))
|
||||
|
||||
def install(
|
||||
self,
|
||||
@@ -927,25 +873,20 @@ class InstallRequirement(object):
|
||||
install_options = list(install_options) + \
|
||||
self.options.get('install_options', [])
|
||||
|
||||
if self.isolated:
|
||||
# https://github.com/python/mypy/issues/1174
|
||||
global_options = global_options + ["--no-user-cfg"] # type: ignore
|
||||
|
||||
with TempDirectory(kind="record") as temp_dir:
|
||||
record_filename = os.path.join(temp_dir.path, 'install-record.txt')
|
||||
install_args = self.get_install_args(
|
||||
global_options, record_filename, root, prefix, pycompile,
|
||||
)
|
||||
msg = 'Running setup.py install for %s' % (self.name,)
|
||||
with open_spinner(msg) as spinner:
|
||||
with indent_log():
|
||||
with self.build_env:
|
||||
call_subprocess(
|
||||
install_args + install_options,
|
||||
cwd=self.setup_py_dir,
|
||||
show_stdout=False,
|
||||
spinner=spinner,
|
||||
)
|
||||
|
||||
runner = runner_with_spinner_message(
|
||||
"Running setup.py install for {}".format(self.name)
|
||||
)
|
||||
with indent_log(), self.build_env:
|
||||
runner(
|
||||
cmd=install_args + install_options,
|
||||
cwd=self.unpacked_source_directory,
|
||||
)
|
||||
|
||||
if not os.path.exists(record_filename):
|
||||
logger.debug('Record file %s not found', record_filename)
|
||||
@@ -953,6 +894,7 @@ class InstallRequirement(object):
|
||||
self.install_succeeded = True
|
||||
|
||||
def prepend_root(path):
|
||||
# type: (str) -> str
|
||||
if root is None or not os.path.isabs(path):
|
||||
return path
|
||||
else:
|
||||
@@ -971,7 +913,6 @@ class InstallRequirement(object):
|
||||
self,
|
||||
)
|
||||
# FIXME: put the record somewhere
|
||||
# FIXME: should this be an error?
|
||||
return
|
||||
new_lines = []
|
||||
with open(record_filename) as f:
|
||||
@@ -997,12 +938,13 @@ class InstallRequirement(object):
|
||||
pycompile # type: bool
|
||||
):
|
||||
# type: (...) -> List[str]
|
||||
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) + \
|
||||
['install', '--record', record_filename]
|
||||
install_args = make_setuptools_shim_args(
|
||||
self.setup_py_path,
|
||||
global_options=global_options,
|
||||
no_user_config=self.isolated,
|
||||
unbuffered_output=True
|
||||
)
|
||||
install_args += ['install', '--record', record_filename]
|
||||
install_args += ['--single-version-externally-managed']
|
||||
|
||||
if root is not None:
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
|
||||
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
from pipenv.patched.notpip._internal import pep425tags
|
||||
from pipenv.patched.notpip._internal.exceptions import InstallationError
|
||||
from pipenv.patched.notpip._internal.utils.logging import indent_log
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
from pipenv.patched.notpip._internal.wheel import Wheel
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Optional, List, Tuple, Dict, Iterable # noqa: F401
|
||||
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement # noqa: F401
|
||||
from typing import Dict, Iterable, List, Optional, Tuple
|
||||
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -19,35 +25,54 @@ logger = logging.getLogger(__name__)
|
||||
class RequirementSet(object):
|
||||
|
||||
def __init__(self, require_hashes=False, check_supported_wheels=True, ignore_compatibility=True):
|
||||
# type: (bool, bool) -> None
|
||||
# type: (bool) -> None
|
||||
"""Create a RequirementSet.
|
||||
"""
|
||||
|
||||
self.requirements = OrderedDict() # type: Dict[str, InstallRequirement] # noqa: E501
|
||||
self.require_hashes = require_hashes
|
||||
self.check_supported_wheels = check_supported_wheels
|
||||
|
||||
self.unnamed_requirements = [] # type: List[InstallRequirement]
|
||||
self.successfully_downloaded = [] # type: List[InstallRequirement]
|
||||
self.reqs_to_cleanup = [] # type: List[InstallRequirement]
|
||||
if ignore_compatibility:
|
||||
self.check_supported_wheels = False
|
||||
self.ignore_compatibility = (check_supported_wheels is False or ignore_compatibility is True)
|
||||
|
||||
# Mapping of alias: real_name
|
||||
self.requirement_aliases = {} # type: Dict[str, str]
|
||||
self.unnamed_requirements = [] # type: List[InstallRequirement]
|
||||
self.successfully_downloaded = [] # type: List[InstallRequirement]
|
||||
self.reqs_to_cleanup = [] # type: List[InstallRequirement]
|
||||
|
||||
def __str__(self):
|
||||
reqs = [req for req in self.requirements.values()
|
||||
if not req.comes_from]
|
||||
reqs.sort(key=lambda req: req.name.lower())
|
||||
return ' '.join([str(req.req) for req in reqs])
|
||||
# type: () -> str
|
||||
requirements = sorted(
|
||||
(req for req in self.requirements.values() if not req.comes_from),
|
||||
key=lambda req: canonicalize_name(req.name),
|
||||
)
|
||||
return ' '.join(str(req.req) for req in requirements)
|
||||
|
||||
def __repr__(self):
|
||||
reqs = [req for req in self.requirements.values()]
|
||||
reqs.sort(key=lambda req: req.name.lower())
|
||||
reqs_str = ', '.join([str(req.req) for req in reqs])
|
||||
return ('<%s object; %d requirement(s): %s>'
|
||||
% (self.__class__.__name__, len(reqs), reqs_str))
|
||||
# type: () -> str
|
||||
requirements = sorted(
|
||||
self.requirements.values(),
|
||||
key=lambda req: canonicalize_name(req.name),
|
||||
)
|
||||
|
||||
format_string = '<{classname} object; {count} requirement(s): {reqs}>'
|
||||
return format_string.format(
|
||||
classname=self.__class__.__name__,
|
||||
count=len(requirements),
|
||||
reqs=', '.join(str(req.req) for req in requirements),
|
||||
)
|
||||
|
||||
def add_unnamed_requirement(self, install_req):
|
||||
# type: (InstallRequirement) -> None
|
||||
assert not install_req.name
|
||||
self.unnamed_requirements.append(install_req)
|
||||
|
||||
def add_named_requirement(self, install_req):
|
||||
# type: (InstallRequirement) -> None
|
||||
assert install_req.name
|
||||
|
||||
project_name = canonicalize_name(install_req.name)
|
||||
self.requirements[project_name] = install_req
|
||||
|
||||
def add_requirement(
|
||||
self,
|
||||
@@ -70,13 +95,11 @@ class RequirementSet(object):
|
||||
the requirement is not applicable, or [install_req] if the
|
||||
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",
|
||||
name, install_req.markers,
|
||||
install_req.name, install_req.markers,
|
||||
)
|
||||
return [], None
|
||||
|
||||
@@ -86,7 +109,8 @@ class RequirementSet(object):
|
||||
# single requirements file.
|
||||
if install_req.link and install_req.link.is_wheel:
|
||||
wheel = Wheel(install_req.link.filename)
|
||||
if self.check_supported_wheels and not wheel.supported():
|
||||
tags = pep425tags.get_supported()
|
||||
if (self.check_supported_wheels and not wheel.supported(tags)):
|
||||
raise InstallationError(
|
||||
"%s is not a supported wheel on this platform." %
|
||||
wheel.filename
|
||||
@@ -100,13 +124,12 @@ class RequirementSet(object):
|
||||
|
||||
# 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)
|
||||
if not install_req.name:
|
||||
self.add_unnamed_requirement(install_req)
|
||||
return [install_req], None
|
||||
|
||||
try:
|
||||
existing_req = self.get_requirement(name)
|
||||
existing_req = self.get_requirement(install_req.name)
|
||||
except KeyError:
|
||||
existing_req = None
|
||||
|
||||
@@ -120,17 +143,14 @@ class RequirementSet(object):
|
||||
if has_conflicting_requirement:
|
||||
raise InstallationError(
|
||||
"Double requirement given: %s (already in %s, name=%r)"
|
||||
% (install_req, existing_req, name)
|
||||
% (install_req, existing_req, install_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
|
||||
self.add_named_requirement(install_req)
|
||||
# We'd want to rescan this requirement later
|
||||
return [install_req], install_req
|
||||
|
||||
# Assume there's no need to scan, and that we've already
|
||||
@@ -150,7 +170,7 @@ class RequirementSet(object):
|
||||
raise InstallationError(
|
||||
"Could not satisfy constraints for '%s': "
|
||||
"installation from path or url cannot be "
|
||||
"constrained to a version" % name,
|
||||
"constrained to a version" % install_req.name,
|
||||
)
|
||||
# If we're now installing a constraint, mark the existing
|
||||
# object for real installation.
|
||||
@@ -166,29 +186,22 @@ class RequirementSet(object):
|
||||
# scanning again.
|
||||
return [existing_req], existing_req
|
||||
|
||||
def has_requirement(self, project_name):
|
||||
def has_requirement(self, name):
|
||||
# type: (str) -> bool
|
||||
name = project_name.lower()
|
||||
if (name in self.requirements and
|
||||
not self.requirements[name].constraint or
|
||||
name in self.requirement_aliases and
|
||||
not self.requirements[self.requirement_aliases[name]].constraint):
|
||||
return True
|
||||
return False
|
||||
project_name = canonicalize_name(name)
|
||||
|
||||
@property
|
||||
def has_requirements(self):
|
||||
# type: () -> List[InstallRequirement]
|
||||
return list(req for req in self.requirements.values() if not
|
||||
req.constraint) or self.unnamed_requirements
|
||||
return (
|
||||
project_name in self.requirements and
|
||||
not self.requirements[project_name].constraint
|
||||
)
|
||||
|
||||
def get_requirement(self, project_name):
|
||||
def get_requirement(self, name):
|
||||
# type: (str) -> InstallRequirement
|
||||
for name in project_name, project_name.lower():
|
||||
if name in self.requirements:
|
||||
return self.requirements[name]
|
||||
if name in self.requirement_aliases:
|
||||
return self.requirements[self.requirement_aliases[name]]
|
||||
project_name = canonicalize_name(name)
|
||||
|
||||
if project_name in self.requirements:
|
||||
return self.requirements[project_name]
|
||||
|
||||
pass
|
||||
|
||||
def cleanup_files(self):
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import contextlib
|
||||
@@ -10,9 +13,10 @@ from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Set, Iterator # noqa: F401
|
||||
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement # noqa: F401
|
||||
from pipenv.patched.notpip._internal.models.link import Link # noqa: F401
|
||||
from types import TracebackType
|
||||
from typing import Iterator, Optional, Set, Type
|
||||
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
|
||||
from pipenv.patched.notpip._internal.models.link import Link
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -24,7 +28,6 @@ class RequirementTracker(object):
|
||||
self._root = os.environ.get('PIP_REQ_TRACKER')
|
||||
if self._root is None:
|
||||
self._temp_dir = TempDirectory(delete=False, kind='req-tracker')
|
||||
self._temp_dir.create()
|
||||
self._root = os.environ['PIP_REQ_TRACKER'] = self._temp_dir.path
|
||||
logger.debug('Created requirements tracker %r', self._root)
|
||||
else:
|
||||
@@ -33,9 +36,16 @@ class RequirementTracker(object):
|
||||
self._entries = set() # type: Set[InstallRequirement]
|
||||
|
||||
def __enter__(self):
|
||||
# type: () -> RequirementTracker
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type, # type: Optional[Type[BaseException]]
|
||||
exc_val, # type: Optional[BaseException]
|
||||
exc_tb # type: Optional[TracebackType]
|
||||
):
|
||||
# type: (...) -> None
|
||||
self.cleanup()
|
||||
|
||||
def _entry_path(self, link):
|
||||
|
||||
@@ -14,15 +14,30 @@ 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,
|
||||
normalize_path, renames, rmtree,
|
||||
FakeFile,
|
||||
ask,
|
||||
dist_in_usersite,
|
||||
dist_is_local,
|
||||
egg_link_path,
|
||||
is_local,
|
||||
normalize_path,
|
||||
renames,
|
||||
rmtree,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.temp_dir import AdjacentTempDirectory, TempDirectory
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import (
|
||||
Any, Callable, Dict, Iterable, Iterator, List, Optional, Set, Tuple,
|
||||
)
|
||||
from pipenv.patched.notpip._vendor.pkg_resources import Distribution
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _script_names(dist, script_name, is_gui):
|
||||
# type: (Distribution, str, bool) -> List[str]
|
||||
"""Create the fully qualified name of the files created by
|
||||
{console,gui}_scripts for the given ``dist``.
|
||||
Returns the list of file names
|
||||
@@ -44,9 +59,11 @@ def _script_names(dist, script_name, is_gui):
|
||||
|
||||
|
||||
def _unique(fn):
|
||||
# type: (Callable) -> Callable[..., Iterator[Any]]
|
||||
@functools.wraps(fn)
|
||||
def unique(*args, **kw):
|
||||
seen = set()
|
||||
# type: (Any, Any) -> Iterator[Any]
|
||||
seen = set() # type: Set[Any]
|
||||
for item in fn(*args, **kw):
|
||||
if item not in seen:
|
||||
seen.add(item)
|
||||
@@ -56,6 +73,7 @@ def _unique(fn):
|
||||
|
||||
@_unique
|
||||
def uninstallation_paths(dist):
|
||||
# type: (Distribution) -> Iterator[str]
|
||||
"""
|
||||
Yield all the uninstallation paths for dist based on RECORD-without-.py[co]
|
||||
|
||||
@@ -78,13 +96,14 @@ def uninstallation_paths(dist):
|
||||
|
||||
|
||||
def compact(paths):
|
||||
# type: (Iterable[str]) -> Set[str]
|
||||
"""Compact a path set to contain the minimal number of paths
|
||||
necessary to contain all paths in the set. If /a/path/ and
|
||||
/a/path/to/a/file.txt are both in the set, leave only the
|
||||
shorter path."""
|
||||
|
||||
sep = os.path.sep
|
||||
short_paths = set()
|
||||
short_paths = set() # type: Set[str]
|
||||
for path in sorted(paths, key=len):
|
||||
should_skip = any(
|
||||
path.startswith(shortpath.rstrip("*")) and
|
||||
@@ -97,6 +116,7 @@ def compact(paths):
|
||||
|
||||
|
||||
def compress_for_rename(paths):
|
||||
# type: (Iterable[str]) -> Set[str]
|
||||
"""Returns a set containing the paths that need to be renamed.
|
||||
|
||||
This set may include directories when the original sequence of paths
|
||||
@@ -106,9 +126,10 @@ def compress_for_rename(paths):
|
||||
remaining = set(case_map)
|
||||
unchecked = sorted(set(os.path.split(p)[0]
|
||||
for p in case_map.values()), key=len)
|
||||
wildcards = set()
|
||||
wildcards = set() # type: Set[str]
|
||||
|
||||
def norm_join(*a):
|
||||
# type: (str) -> str
|
||||
return os.path.normcase(os.path.join(*a))
|
||||
|
||||
for root in unchecked:
|
||||
@@ -117,8 +138,8 @@ def compress_for_rename(paths):
|
||||
# This directory has already been handled.
|
||||
continue
|
||||
|
||||
all_files = set()
|
||||
all_subdirs = set()
|
||||
all_files = set() # type: Set[str]
|
||||
all_subdirs = set() # type: Set[str]
|
||||
for dirname, subdirs, files in os.walk(root):
|
||||
all_subdirs.update(norm_join(root, dirname, d)
|
||||
for d in subdirs)
|
||||
@@ -135,6 +156,7 @@ def compress_for_rename(paths):
|
||||
|
||||
|
||||
def compress_for_output_listing(paths):
|
||||
# type: (Iterable[str]) -> Tuple[Set[str], Set[str]]
|
||||
"""Returns a tuple of 2 sets of which paths to display to user
|
||||
|
||||
The first set contains paths that would be deleted. Files of a package
|
||||
@@ -145,7 +167,7 @@ def compress_for_output_listing(paths):
|
||||
folders.
|
||||
"""
|
||||
|
||||
will_remove = list(paths)
|
||||
will_remove = set(paths)
|
||||
will_skip = set()
|
||||
|
||||
# Determine folders and files
|
||||
@@ -158,7 +180,8 @@ def compress_for_output_listing(paths):
|
||||
folders.add(os.path.dirname(path))
|
||||
files.add(path)
|
||||
|
||||
_normcased_files = set(map(os.path.normcase, files))
|
||||
# probably this one https://github.com/python/mypy/issues/390
|
||||
_normcased_files = set(map(os.path.normcase, files)) # type: ignore
|
||||
|
||||
folders = compact(folders)
|
||||
|
||||
@@ -187,30 +210,31 @@ class StashedUninstallPathSet(object):
|
||||
"""A set of file rename operations to stash files while
|
||||
tentatively uninstalling them."""
|
||||
def __init__(self):
|
||||
# type: () -> None
|
||||
# Mapping from source file root to [Adjacent]TempDirectory
|
||||
# for files under that directory.
|
||||
self._save_dirs = {}
|
||||
self._save_dirs = {} # type: Dict[str, TempDirectory]
|
||||
# (old path, new path) tuples for each move that may need
|
||||
# to be undone.
|
||||
self._moves = []
|
||||
self._moves = [] # type: List[Tuple[str, str]]
|
||||
|
||||
def _get_directory_stash(self, path):
|
||||
# type: (str) -> str
|
||||
"""Stashes a directory.
|
||||
|
||||
Directories are stashed adjacent to their original location if
|
||||
possible, or else moved/copied into the user's temp dir."""
|
||||
|
||||
try:
|
||||
save_dir = AdjacentTempDirectory(path)
|
||||
save_dir.create()
|
||||
save_dir = AdjacentTempDirectory(path) # type: TempDirectory
|
||||
except OSError:
|
||||
save_dir = TempDirectory(kind="uninstall")
|
||||
save_dir.create()
|
||||
self._save_dirs[os.path.normcase(path)] = save_dir
|
||||
|
||||
return save_dir.path
|
||||
|
||||
def _get_file_stash(self, path):
|
||||
# type: (str) -> str
|
||||
"""Stashes a file.
|
||||
|
||||
If no root has been provided, one will be created for the directory
|
||||
@@ -230,7 +254,6 @@ class StashedUninstallPathSet(object):
|
||||
# Did not find any suitable root
|
||||
head = os.path.dirname(path)
|
||||
save_dir = TempDirectory(kind='uninstall')
|
||||
save_dir.create()
|
||||
self._save_dirs[head] = save_dir
|
||||
|
||||
relpath = os.path.relpath(path, head)
|
||||
@@ -239,15 +262,18 @@ class StashedUninstallPathSet(object):
|
||||
return save_dir.path
|
||||
|
||||
def stash(self, path):
|
||||
# type: (str) -> str
|
||||
"""Stashes the directory or file and returns its new location.
|
||||
Handle symlinks as files to avoid modifying the symlink targets.
|
||||
"""
|
||||
if os.path.isdir(path):
|
||||
path_is_dir = os.path.isdir(path) and not os.path.islink(path)
|
||||
if path_is_dir:
|
||||
new_path = self._get_directory_stash(path)
|
||||
else:
|
||||
new_path = self._get_file_stash(path)
|
||||
|
||||
self._moves.append((path, new_path))
|
||||
if os.path.isdir(path) and os.path.isdir(new_path):
|
||||
if (path_is_dir and os.path.isdir(new_path)):
|
||||
# If we're moving a directory, we need to
|
||||
# remove the destination first or else it will be
|
||||
# moved to inside the existing directory.
|
||||
@@ -258,6 +284,7 @@ class StashedUninstallPathSet(object):
|
||||
return new_path
|
||||
|
||||
def commit(self):
|
||||
# type: () -> None
|
||||
"""Commits the uninstall by removing stashed files."""
|
||||
for _, save_dir in self._save_dirs.items():
|
||||
save_dir.cleanup()
|
||||
@@ -265,6 +292,7 @@ class StashedUninstallPathSet(object):
|
||||
self._save_dirs = {}
|
||||
|
||||
def rollback(self):
|
||||
# type: () -> None
|
||||
"""Undoes the uninstall by moving stashed files back."""
|
||||
for p in self._moves:
|
||||
logging.info("Moving to %s\n from %s", *p)
|
||||
@@ -272,7 +300,7 @@ class StashedUninstallPathSet(object):
|
||||
for new_path, path in self._moves:
|
||||
try:
|
||||
logger.debug('Replacing %s from %s', new_path, path)
|
||||
if os.path.isfile(new_path):
|
||||
if os.path.isfile(new_path) or os.path.islink(new_path):
|
||||
os.unlink(new_path)
|
||||
elif os.path.isdir(new_path):
|
||||
rmtree(new_path)
|
||||
@@ -285,6 +313,7 @@ class StashedUninstallPathSet(object):
|
||||
|
||||
@property
|
||||
def can_rollback(self):
|
||||
# type: () -> bool
|
||||
return bool(self._moves)
|
||||
|
||||
|
||||
@@ -292,13 +321,15 @@ class UninstallPathSet(object):
|
||||
"""A set of file paths to be removed in the uninstallation of a
|
||||
requirement."""
|
||||
def __init__(self, dist):
|
||||
self.paths = set()
|
||||
self._refuse = set()
|
||||
self.pth = {}
|
||||
# type: (Distribution) -> None
|
||||
self.paths = set() # type: Set[str]
|
||||
self._refuse = set() # type: Set[str]
|
||||
self.pth = {} # type: Dict[str, UninstallPthEntries]
|
||||
self.dist = dist
|
||||
self._moved_paths = StashedUninstallPathSet()
|
||||
|
||||
def _permitted(self, path):
|
||||
# type: (str) -> bool
|
||||
"""
|
||||
Return True if the given path is one we are permitted to
|
||||
remove/modify, False otherwise.
|
||||
@@ -307,6 +338,7 @@ class UninstallPathSet(object):
|
||||
return is_local(path)
|
||||
|
||||
def add(self, path):
|
||||
# type: (str) -> None
|
||||
head, tail = os.path.split(path)
|
||||
|
||||
# we normalize the head to resolve parent directory symlinks, but not
|
||||
@@ -326,6 +358,7 @@ class UninstallPathSet(object):
|
||||
self.add(cache_from_source(path))
|
||||
|
||||
def add_pth(self, pth_file, entry):
|
||||
# type: (str, str) -> None
|
||||
pth_file = normalize_path(pth_file)
|
||||
if self._permitted(pth_file):
|
||||
if pth_file not in self.pth:
|
||||
@@ -335,6 +368,7 @@ class UninstallPathSet(object):
|
||||
self._refuse.add(pth_file)
|
||||
|
||||
def remove(self, auto_confirm=False, verbose=False):
|
||||
# type: (bool, bool) -> None
|
||||
"""Remove paths in ``self.paths`` with confirmation (unless
|
||||
``auto_confirm`` is True)."""
|
||||
|
||||
@@ -366,10 +400,12 @@ class UninstallPathSet(object):
|
||||
logger.info('Successfully uninstalled %s', dist_name_version)
|
||||
|
||||
def _allowed_to_proceed(self, verbose):
|
||||
# type: (bool) -> bool
|
||||
"""Display which files would be deleted and prompt for confirmation
|
||||
"""
|
||||
|
||||
def _display(msg, paths):
|
||||
# type: (str, Iterable[str]) -> None
|
||||
if not paths:
|
||||
return
|
||||
|
||||
@@ -383,7 +419,7 @@ class UninstallPathSet(object):
|
||||
else:
|
||||
# In verbose mode, display all the files that are going to be
|
||||
# deleted.
|
||||
will_remove = list(self.paths)
|
||||
will_remove = set(self.paths)
|
||||
will_skip = set()
|
||||
|
||||
_display('Would remove:', will_remove)
|
||||
@@ -395,24 +431,27 @@ class UninstallPathSet(object):
|
||||
return ask('Proceed (y/n)? ', ('y', 'n')) == 'y'
|
||||
|
||||
def rollback(self):
|
||||
# type: () -> None
|
||||
"""Rollback the changes previously made by remove()."""
|
||||
if not self._moved_paths.can_rollback:
|
||||
logger.error(
|
||||
"Can't roll back %s; was not uninstalled",
|
||||
self.dist.project_name,
|
||||
)
|
||||
return False
|
||||
return
|
||||
logger.info('Rolling back uninstall of %s', self.dist.project_name)
|
||||
self._moved_paths.rollback()
|
||||
for pth in self.pth.values():
|
||||
pth.rollback()
|
||||
|
||||
def commit(self):
|
||||
# type: () -> None
|
||||
"""Remove temporary save dir: rollback will no longer be possible."""
|
||||
self._moved_paths.commit()
|
||||
|
||||
@classmethod
|
||||
def from_dist(cls, dist):
|
||||
# type: (Distribution) -> UninstallPathSet
|
||||
dist_path = normalize_path(dist.location)
|
||||
if not dist_is_local(dist):
|
||||
logger.info(
|
||||
@@ -544,25 +583,33 @@ class UninstallPathSet(object):
|
||||
|
||||
class UninstallPthEntries(object):
|
||||
def __init__(self, pth_file):
|
||||
# type: (str) -> None
|
||||
if not os.path.isfile(pth_file):
|
||||
raise UninstallationError(
|
||||
"Cannot remove entries from nonexistent file %s" % pth_file
|
||||
)
|
||||
self.file = pth_file
|
||||
self.entries = set()
|
||||
self._saved_lines = None
|
||||
self.entries = set() # type: Set[str]
|
||||
self._saved_lines = None # type: Optional[List[bytes]]
|
||||
|
||||
def add(self, entry):
|
||||
# type: (str) -> None
|
||||
entry = os.path.normcase(entry)
|
||||
# On Windows, os.path.normcase converts the entry to use
|
||||
# backslashes. This is correct for entries that describe absolute
|
||||
# paths outside of site-packages, but all the others use forward
|
||||
# slashes.
|
||||
# os.path.splitdrive is used instead of os.path.isabs because isabs
|
||||
# treats non-absolute paths with drive letter markings like c:foo\bar
|
||||
# as absolute paths. It also does not recognize UNC paths if they don't
|
||||
# have more than "\\sever\share". Valid examples: "\\server\share\" or
|
||||
# "\\server\share\folder". Python 2.7.8+ support UNC in splitdrive.
|
||||
if WINDOWS and not os.path.splitdrive(entry)[0]:
|
||||
entry = entry.replace('\\', '/')
|
||||
self.entries.add(entry)
|
||||
|
||||
def remove(self):
|
||||
# type: () -> None
|
||||
logger.debug('Removing pth entries from %s:', self.file)
|
||||
with open(self.file, 'rb') as fh:
|
||||
# windows uses '\r\n' with py3k, but uses '\n' with py2.x
|
||||
@@ -585,6 +632,7 @@ class UninstallPthEntries(object):
|
||||
fh.writelines(lines)
|
||||
|
||||
def rollback(self):
|
||||
# type: () -> bool
|
||||
if self._saved_lines is None:
|
||||
logger.error(
|
||||
'Cannot roll back changes to %s, none were made', self.file
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
from pipenv.patched.notpip._vendor import pkg_resources
|
||||
from pipenv.patched.notpip._vendor.packaging import version as packaging_version
|
||||
from pipenv.patched.notpip._vendor.six import ensure_binary
|
||||
|
||||
from pipenv.patched.notpip._internal.collector import LinkCollector
|
||||
from pipenv.patched.notpip._internal.index import PackageFinder
|
||||
from pipenv.patched.notpip._internal.models.search_scope import SearchScope
|
||||
from pipenv.patched.notpip._internal.models.selection_prefs import SelectionPreferences
|
||||
from pipenv.patched.notpip._internal.utils.compat import WINDOWS
|
||||
from pipenv.patched.notpip._internal.utils.filesystem import (
|
||||
adjacent_tmp_file,
|
||||
check_path_owner,
|
||||
replace,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.misc import (
|
||||
ensure_dir,
|
||||
get_installed_version,
|
||||
redact_auth_from_url,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.packaging import get_installer
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
import optparse
|
||||
from optparse import Values
|
||||
from typing import Any, Dict, Text, Union
|
||||
|
||||
from pipenv.patched.notpip._internal.network.session import PipSession
|
||||
|
||||
|
||||
SELFCHECK_DATE_FMT = "%Y-%m-%dT%H:%M:%SZ"
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def make_link_collector(
|
||||
session, # type: PipSession
|
||||
options, # type: Values
|
||||
suppress_no_index=False, # type: bool
|
||||
):
|
||||
# type: (...) -> LinkCollector
|
||||
"""
|
||||
:param session: The Session to use to make requests.
|
||||
:param suppress_no_index: Whether to ignore the --no-index option
|
||||
when constructing the SearchScope object.
|
||||
"""
|
||||
index_urls = [options.index_url] + options.extra_index_urls
|
||||
if options.no_index and not suppress_no_index:
|
||||
logger.debug(
|
||||
'Ignoring indexes: %s',
|
||||
','.join(redact_auth_from_url(url) for url in index_urls),
|
||||
)
|
||||
index_urls = []
|
||||
|
||||
# Make sure find_links is a list before passing to create().
|
||||
find_links = options.find_links or []
|
||||
|
||||
search_scope = SearchScope.create(
|
||||
find_links=find_links, index_urls=index_urls,
|
||||
)
|
||||
|
||||
link_collector = LinkCollector(session=session, search_scope=search_scope)
|
||||
|
||||
return link_collector
|
||||
|
||||
|
||||
def _get_statefile_name(key):
|
||||
# type: (Union[str, Text]) -> str
|
||||
key_bytes = ensure_binary(key)
|
||||
name = hashlib.sha224(key_bytes).hexdigest()
|
||||
return name
|
||||
|
||||
|
||||
class SelfCheckState(object):
|
||||
def __init__(self, cache_dir):
|
||||
# type: (str) -> None
|
||||
self.state = {} # type: Dict[str, Any]
|
||||
self.statefile_path = None
|
||||
|
||||
# Try to load the existing state
|
||||
if cache_dir:
|
||||
self.statefile_path = os.path.join(
|
||||
cache_dir, "selfcheck", _get_statefile_name(self.key)
|
||||
)
|
||||
try:
|
||||
with open(self.statefile_path) as statefile:
|
||||
self.state = json.load(statefile)
|
||||
except (IOError, ValueError, KeyError):
|
||||
# Explicitly suppressing exceptions, since we don't want to
|
||||
# error out if the cache file is invalid.
|
||||
pass
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
return sys.prefix
|
||||
|
||||
def save(self, pypi_version, current_time):
|
||||
# type: (str, datetime.datetime) -> None
|
||||
# If we do not have a path to cache in, don't bother saving.
|
||||
if not self.statefile_path:
|
||||
return
|
||||
|
||||
# Check to make sure that we own the directory
|
||||
if not check_path_owner(os.path.dirname(self.statefile_path)):
|
||||
return
|
||||
|
||||
# Now that we've ensured the directory is owned by this user, we'll go
|
||||
# ahead and make sure that all our directories are created.
|
||||
ensure_dir(os.path.dirname(self.statefile_path))
|
||||
|
||||
state = {
|
||||
# Include the key so it's easy to tell which pip wrote the
|
||||
# file.
|
||||
"key": self.key,
|
||||
"last_check": current_time.strftime(SELFCHECK_DATE_FMT),
|
||||
"pypi_version": pypi_version,
|
||||
}
|
||||
|
||||
text = json.dumps(state, sort_keys=True, separators=(",", ":"))
|
||||
|
||||
with adjacent_tmp_file(self.statefile_path) as f:
|
||||
f.write(ensure_binary(text))
|
||||
|
||||
try:
|
||||
# Since we have a prefix-specific state file, we can just
|
||||
# overwrite whatever is there, no need to check.
|
||||
replace(f.name, self.statefile_path)
|
||||
except OSError:
|
||||
# Best effort.
|
||||
pass
|
||||
|
||||
|
||||
def was_installed_by_pip(pkg):
|
||||
# type: (str) -> bool
|
||||
"""Checks whether pkg was installed by pip
|
||||
|
||||
This is used not to display the upgrade message when pip is in fact
|
||||
installed by system package manager, such as dnf on Fedora.
|
||||
"""
|
||||
try:
|
||||
dist = pkg_resources.get_distribution(pkg)
|
||||
return "pip" == get_installer(dist)
|
||||
except pkg_resources.DistributionNotFound:
|
||||
return False
|
||||
|
||||
|
||||
def pip_self_version_check(session, options):
|
||||
# type: (PipSession, optparse.Values) -> None
|
||||
"""Check for an update for pip.
|
||||
|
||||
Limit the frequency of checks to once per week. State is stored either in
|
||||
the active virtualenv or in the user's USER_CACHE_DIR keyed off the prefix
|
||||
of the pip script path.
|
||||
"""
|
||||
installed_version = get_installed_version("pip")
|
||||
if not installed_version:
|
||||
return
|
||||
|
||||
pip_version = packaging_version.parse(installed_version)
|
||||
pypi_version = None
|
||||
|
||||
try:
|
||||
state = SelfCheckState(cache_dir=options.cache_dir)
|
||||
|
||||
current_time = datetime.datetime.utcnow()
|
||||
# Determine if we need to refresh the state
|
||||
if "last_check" in state.state and "pypi_version" in state.state:
|
||||
last_check = datetime.datetime.strptime(
|
||||
state.state["last_check"],
|
||||
SELFCHECK_DATE_FMT
|
||||
)
|
||||
if (current_time - last_check).total_seconds() < 7 * 24 * 60 * 60:
|
||||
pypi_version = state.state["pypi_version"]
|
||||
|
||||
# Refresh the version if we need to or just see if we need to warn
|
||||
if pypi_version is None:
|
||||
# Lets use PackageFinder to see what the latest pip version is
|
||||
link_collector = make_link_collector(
|
||||
session,
|
||||
options=options,
|
||||
suppress_no_index=True,
|
||||
)
|
||||
|
||||
# Pass allow_yanked=False so we don't suggest upgrading to a
|
||||
# yanked version.
|
||||
selection_prefs = SelectionPreferences(
|
||||
allow_yanked=False,
|
||||
allow_all_prereleases=False, # Explicitly set to False
|
||||
)
|
||||
|
||||
finder = PackageFinder.create(
|
||||
link_collector=link_collector,
|
||||
selection_prefs=selection_prefs,
|
||||
)
|
||||
best_candidate = finder.find_best_candidate("pip").best_candidate
|
||||
if best_candidate is None:
|
||||
return
|
||||
pypi_version = str(best_candidate.version)
|
||||
|
||||
# save that we've performed a check
|
||||
state.save(pypi_version, current_time)
|
||||
|
||||
remote_version = packaging_version.parse(pypi_version)
|
||||
|
||||
local_version_is_older = (
|
||||
pip_version < remote_version and
|
||||
pip_version.base_version != remote_version.base_version and
|
||||
was_installed_by_pip('pip')
|
||||
)
|
||||
|
||||
# Determine if our pypi_version is older
|
||||
if not local_version_is_older:
|
||||
return
|
||||
|
||||
# Advise "python -m pip" on Windows to avoid issues
|
||||
# with overwriting pip.exe.
|
||||
if WINDOWS:
|
||||
pip_cmd = "python -m pip"
|
||||
else:
|
||||
pip_cmd = "pip"
|
||||
logger.warning(
|
||||
"You are using pip version %s; however, version %s is "
|
||||
"available.\nYou should consider upgrading via the "
|
||||
"'%s install --upgrade pip' command.",
|
||||
pip_version, pypi_version, pip_cmd
|
||||
)
|
||||
except Exception:
|
||||
logger.debug(
|
||||
"There was an error checking the latest version of pip",
|
||||
exc_info=True,
|
||||
)
|
||||
@@ -2,6 +2,10 @@
|
||||
This code was taken from https://github.com/ActiveState/appdirs and modified
|
||||
to suit our purposes.
|
||||
"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
@@ -13,9 +17,7 @@ from pipenv.patched.notpip._internal.utils.compat import WINDOWS, expanduser
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import ( # noqa: F401
|
||||
List, Union
|
||||
)
|
||||
from typing import List
|
||||
|
||||
|
||||
def user_cache_dir(appname):
|
||||
@@ -220,6 +222,8 @@ def _get_win_folder_from_registry(csidl_name):
|
||||
|
||||
def _get_win_folder_with_ctypes(csidl_name):
|
||||
# type: (str) -> str
|
||||
# On Python 2, ctypes.create_unicode_buffer().value returns "unicode",
|
||||
# which isn't the same as str in the annotation above.
|
||||
csidl_const = {
|
||||
"CSIDL_APPDATA": 26,
|
||||
"CSIDL_COMMON_APPDATA": 35,
|
||||
@@ -227,7 +231,8 @@ def _get_win_folder_with_ctypes(csidl_name):
|
||||
}[csidl_name]
|
||||
|
||||
buf = ctypes.create_unicode_buffer(1024)
|
||||
ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
|
||||
windll = ctypes.windll # type: ignore
|
||||
windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
|
||||
|
||||
# Downgrade to short path name if have highbit chars. See
|
||||
# <http://bugs.activestate.com/show_bug.cgi?id=85099>.
|
||||
@@ -238,10 +243,11 @@ def _get_win_folder_with_ctypes(csidl_name):
|
||||
break
|
||||
if has_high_char:
|
||||
buf2 = ctypes.create_unicode_buffer(1024)
|
||||
if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
|
||||
if windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
|
||||
buf = buf2
|
||||
|
||||
return buf.value
|
||||
# The type: ignore is explained under the type annotation for this function
|
||||
return buf.value # type: ignore
|
||||
|
||||
|
||||
if WINDOWS:
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
"""Stuff that differs in different Python versions and platform
|
||||
distributions."""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import, division
|
||||
|
||||
import codecs
|
||||
@@ -9,12 +13,21 @@ import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
from pipenv.patched.notpip._vendor.six import text_type
|
||||
from pipenv.patched.notpip._vendor.six import PY2, text_type
|
||||
from pipenv.patched.notpip._vendor.urllib3.util import IS_PYOPENSSL
|
||||
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Tuple, Text # noqa: F401
|
||||
from typing import Optional, Text, Tuple, Union
|
||||
|
||||
try:
|
||||
import _ssl # noqa
|
||||
except ImportError:
|
||||
ssl = None
|
||||
else:
|
||||
# This additional assignment was needed to prevent a mypy error.
|
||||
ssl = _ssl
|
||||
|
||||
try:
|
||||
import ipaddress
|
||||
@@ -36,10 +49,9 @@ __all__ = [
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if sys.version_info >= (3, 4):
|
||||
uses_pycache = True
|
||||
from importlib.util import cache_from_source
|
||||
else:
|
||||
HAS_TLS = (ssl is not None) or IS_PYOPENSSL
|
||||
|
||||
if PY2:
|
||||
import imp
|
||||
|
||||
try:
|
||||
@@ -49,41 +61,54 @@ else:
|
||||
cache_from_source = None
|
||||
|
||||
uses_pycache = cache_from_source is not None
|
||||
|
||||
|
||||
if sys.version_info >= (3, 5):
|
||||
backslashreplace_decode = "backslashreplace"
|
||||
else:
|
||||
# In version 3.4 and older, backslashreplace exists
|
||||
uses_pycache = True
|
||||
from importlib.util import cache_from_source
|
||||
|
||||
|
||||
if PY2:
|
||||
# In Python 2.7, backslashreplace exists
|
||||
# but does not support use for decoding.
|
||||
# We implement our own replace handler for this
|
||||
# situation, so that we can consistently use
|
||||
# backslash replacement for all versions.
|
||||
def backslashreplace_decode_fn(err):
|
||||
raw_bytes = (err.object[i] for i in range(err.start, err.end))
|
||||
if sys.version_info[0] == 2:
|
||||
# Python 2 gave us characters - convert to numeric bytes
|
||||
raw_bytes = (ord(b) for b in raw_bytes)
|
||||
# Python 2 gave us characters - convert to numeric bytes
|
||||
raw_bytes = (ord(b) for b in raw_bytes)
|
||||
return u"".join(u"\\x%x" % c for c in raw_bytes), err.end
|
||||
codecs.register_error(
|
||||
"backslashreplace_decode",
|
||||
backslashreplace_decode_fn,
|
||||
)
|
||||
backslashreplace_decode = "backslashreplace_decode"
|
||||
else:
|
||||
backslashreplace_decode = "backslashreplace"
|
||||
|
||||
|
||||
def console_to_str(data):
|
||||
# type: (bytes) -> Text
|
||||
"""Return a string, safe for output, of subprocess output.
|
||||
|
||||
We assume the data is in the locale preferred encoding.
|
||||
If it won't decode properly, we warn the user but decode as
|
||||
best we can.
|
||||
|
||||
We also ensure that the output can be safely written to
|
||||
standard output without encoding errors.
|
||||
def str_to_display(data, desc=None):
|
||||
# type: (Union[bytes, Text], Optional[str]) -> Text
|
||||
"""
|
||||
For display or logging purposes, convert a bytes object (or text) to
|
||||
text (e.g. unicode in Python 2) safe for output.
|
||||
|
||||
:param desc: An optional phrase describing the input data, for use in
|
||||
the log message if a warning is logged. Defaults to "Bytes object".
|
||||
|
||||
This function should never error out and so can take a best effort
|
||||
approach. It is okay to be lossy if needed since the return value is
|
||||
just for display.
|
||||
|
||||
We assume the data is in the locale preferred encoding. If it won't
|
||||
decode properly, we warn the user but decode as best we can.
|
||||
|
||||
We also ensure that the output can be safely written to standard output
|
||||
without encoding errors.
|
||||
"""
|
||||
if isinstance(data, text_type):
|
||||
return data
|
||||
|
||||
# Otherwise, data is a bytes object (str in Python 2).
|
||||
# First, get the encoding we assume. This is the preferred
|
||||
# encoding for the locale, unless that is not found, or
|
||||
# it is ASCII, in which case assume UTF-8
|
||||
@@ -96,10 +121,10 @@ def console_to_str(data):
|
||||
try:
|
||||
decoded_data = data.decode(encoding)
|
||||
except UnicodeDecodeError:
|
||||
logger.warning(
|
||||
"Subprocess output does not appear to be encoded as %s",
|
||||
encoding,
|
||||
)
|
||||
if desc is None:
|
||||
desc = 'Bytes object'
|
||||
msg_format = '{} does not appear to be encoded as %s'.format(desc)
|
||||
logger.warning(msg_format, encoding)
|
||||
decoded_data = data.decode(encoding, errors=backslashreplace_decode)
|
||||
|
||||
# Make sure we can print the output, by encoding it to the output
|
||||
@@ -127,14 +152,14 @@ def console_to_str(data):
|
||||
return decoded_data
|
||||
|
||||
|
||||
if sys.version_info >= (3,):
|
||||
def native_str(s, replace=False):
|
||||
# type: (str, bool) -> str
|
||||
if isinstance(s, bytes):
|
||||
return s.decode('utf-8', 'replace' if replace else 'strict')
|
||||
return s
|
||||
def console_to_str(data):
|
||||
# type: (bytes) -> Text
|
||||
"""Return a string, safe for output, of subprocess output.
|
||||
"""
|
||||
return str_to_display(data, desc='Subprocess output')
|
||||
|
||||
else:
|
||||
|
||||
if PY2:
|
||||
def native_str(s, replace=False):
|
||||
# type: (str, bool) -> str
|
||||
# Replace is ignored -- unicode to UTF-8 can't fail
|
||||
@@ -142,6 +167,13 @@ else:
|
||||
return s.encode('utf-8')
|
||||
return s
|
||||
|
||||
else:
|
||||
def native_str(s, replace=False):
|
||||
# type: (str, bool) -> str
|
||||
if isinstance(s, bytes):
|
||||
return s.decode('utf-8', 'replace' if replace else 'strict')
|
||||
return s
|
||||
|
||||
|
||||
def get_path_uid(path):
|
||||
# type: (str) -> int
|
||||
@@ -173,17 +205,18 @@ def get_path_uid(path):
|
||||
return file_uid
|
||||
|
||||
|
||||
if sys.version_info >= (3, 4):
|
||||
from importlib.machinery import EXTENSION_SUFFIXES
|
||||
|
||||
def get_extension_suffixes():
|
||||
return EXTENSION_SUFFIXES
|
||||
else:
|
||||
if PY2:
|
||||
from imp import get_suffixes
|
||||
|
||||
def get_extension_suffixes():
|
||||
return [suffix[0] for suffix in get_suffixes()]
|
||||
|
||||
else:
|
||||
from importlib.machinery import EXTENSION_SUFFIXES
|
||||
|
||||
def get_extension_suffixes():
|
||||
return EXTENSION_SUFFIXES
|
||||
|
||||
|
||||
def expanduser(path):
|
||||
# type: (str) -> str
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
"""
|
||||
A module that implements tooling to enable easy warnings about deprecations.
|
||||
"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
@@ -12,7 +16,10 @@ from pipenv.patched.notpip import __version__ as current_version
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Any, Optional # noqa: F401
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
DEPRECATION_MSG_PREFIX = "DEPRECATION: "
|
||||
|
||||
|
||||
class PipDeprecationWarning(Warning):
|
||||
@@ -75,16 +82,23 @@ def deprecated(reason, replacement, gone_in, issue=None):
|
||||
"""
|
||||
|
||||
# Construct a nice message.
|
||||
# This is purposely eagerly formatted as we want it to appear as if someone
|
||||
# typed this entire message out.
|
||||
message = "DEPRECATION: " + reason
|
||||
if replacement is not None:
|
||||
message += " A possible replacement is {}.".format(replacement)
|
||||
if issue is not None:
|
||||
url = "https://github.com/pypa/pip/issues/" + str(issue)
|
||||
message += " You can find discussion regarding this at {}.".format(url)
|
||||
# This is eagerly formatted as we want it to get logged as if someone
|
||||
# typed this entire message out.
|
||||
sentences = [
|
||||
(reason, DEPRECATION_MSG_PREFIX + "{}"),
|
||||
(gone_in, "pip {} will remove support for this functionality."),
|
||||
(replacement, "A possible replacement is {}."),
|
||||
(issue, (
|
||||
"You can find discussion regarding this at "
|
||||
"https://github.com/pypa/pip/issues/{}."
|
||||
)),
|
||||
]
|
||||
message = " ".join(
|
||||
template.format(val) for val, template in sentences if val is not None
|
||||
)
|
||||
|
||||
# Raise as an error if it has to be removed.
|
||||
if gone_in is not None and parse(current_version) >= parse(gone_in):
|
||||
raise PipDeprecationWarning(message)
|
||||
|
||||
warnings.warn(message, category=PipDeprecationWarning, stacklevel=2)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
|
||||
import codecs
|
||||
import locale
|
||||
import re
|
||||
@@ -6,16 +9,16 @@ import sys
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import List, Tuple, Text # noqa: F401
|
||||
from typing import List, Tuple, Text
|
||||
|
||||
BOMS = [
|
||||
(codecs.BOM_UTF8, 'utf8'),
|
||||
(codecs.BOM_UTF16, 'utf16'),
|
||||
(codecs.BOM_UTF16_BE, 'utf16-be'),
|
||||
(codecs.BOM_UTF16_LE, 'utf16-le'),
|
||||
(codecs.BOM_UTF32, 'utf32'),
|
||||
(codecs.BOM_UTF32_BE, 'utf32-be'),
|
||||
(codecs.BOM_UTF32_LE, 'utf32-le'),
|
||||
(codecs.BOM_UTF8, 'utf-8'),
|
||||
(codecs.BOM_UTF16, 'utf-16'),
|
||||
(codecs.BOM_UTF16_BE, 'utf-16-be'),
|
||||
(codecs.BOM_UTF16_LE, 'utf-16-le'),
|
||||
(codecs.BOM_UTF32, 'utf-32'),
|
||||
(codecs.BOM_UTF32_BE, 'utf-32-be'),
|
||||
(codecs.BOM_UTF32_LE, 'utf-32-le'),
|
||||
] # type: List[Tuple[bytes, Text]]
|
||||
|
||||
ENCODING_RE = re.compile(br'coding[:=]\s*([-\w.]+)')
|
||||
|
||||
@@ -1,7 +1,27 @@
|
||||
import os
|
||||
import os.path
|
||||
import shutil
|
||||
import stat
|
||||
from contextlib import contextmanager
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
# NOTE: retrying is not annotated in typeshed as on 2017-07-17, which is
|
||||
# why we ignore the type on this import.
|
||||
from pipenv.patched.notpip._vendor.retrying import retry # type: ignore
|
||||
from pipenv.patched.notpip._vendor.six import PY2
|
||||
|
||||
from pipenv.patched.notpip._internal.utils.compat import get_path_uid
|
||||
from pipenv.patched.notpip._internal.utils.misc import cast
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import BinaryIO, Iterator
|
||||
|
||||
class NamedTemporaryFileResult(BinaryIO):
|
||||
@property
|
||||
def file(self):
|
||||
# type: () -> BinaryIO
|
||||
pass
|
||||
|
||||
|
||||
def check_path_owner(path):
|
||||
@@ -28,3 +48,68 @@ def check_path_owner(path):
|
||||
else:
|
||||
previous, path = path, os.path.dirname(path)
|
||||
return False # assume we don't own the path
|
||||
|
||||
|
||||
def copy2_fixed(src, dest):
|
||||
# type: (str, str) -> None
|
||||
"""Wrap shutil.copy2() but map errors copying socket files to
|
||||
SpecialFileError as expected.
|
||||
|
||||
See also https://bugs.python.org/issue37700.
|
||||
"""
|
||||
try:
|
||||
shutil.copy2(src, dest)
|
||||
except (OSError, IOError):
|
||||
for f in [src, dest]:
|
||||
try:
|
||||
is_socket_file = is_socket(f)
|
||||
except OSError:
|
||||
# An error has already occurred. Another error here is not
|
||||
# a problem and we can ignore it.
|
||||
pass
|
||||
else:
|
||||
if is_socket_file:
|
||||
raise shutil.SpecialFileError("`%s` is a socket" % f)
|
||||
|
||||
raise
|
||||
|
||||
|
||||
def is_socket(path):
|
||||
# type: (str) -> bool
|
||||
return stat.S_ISSOCK(os.lstat(path).st_mode)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def adjacent_tmp_file(path):
|
||||
# type: (str) -> Iterator[NamedTemporaryFileResult]
|
||||
"""Given a path to a file, open a temp file next to it securely and ensure
|
||||
it is written to disk after the context reaches its end.
|
||||
"""
|
||||
with NamedTemporaryFile(
|
||||
delete=False,
|
||||
dir=os.path.dirname(path),
|
||||
prefix=os.path.basename(path),
|
||||
suffix='.tmp',
|
||||
) as f:
|
||||
result = cast('NamedTemporaryFileResult', f)
|
||||
try:
|
||||
yield result
|
||||
finally:
|
||||
result.file.flush()
|
||||
os.fsync(result.file.fileno())
|
||||
|
||||
|
||||
_replace_retry = retry(stop_max_delay=1000, wait_fixed=250)
|
||||
|
||||
if PY2:
|
||||
@_replace_retry
|
||||
def replace(src, dest):
|
||||
# type: (str, str) -> None
|
||||
try:
|
||||
os.rename(src, dest)
|
||||
except OSError:
|
||||
os.remove(dest)
|
||||
os.rename(src, dest)
|
||||
|
||||
else:
|
||||
replace = _replace_retry(os.replace)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
"""Filetype information.
|
||||
"""
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Tuple
|
||||
|
||||
WHEEL_EXTENSION = '.whl'
|
||||
BZ2_EXTENSIONS = ('.tar.bz2', '.tbz') # type: Tuple[str, ...]
|
||||
XZ_EXTENSIONS = ('.tar.xz', '.txz', '.tlz',
|
||||
'.tar.lz', '.tar.lzma') # type: Tuple[str, ...]
|
||||
ZIP_EXTENSIONS = ('.zip', WHEEL_EXTENSION) # type: Tuple[str, ...]
|
||||
TAR_EXTENSIONS = ('.tar.gz', '.tgz', '.tar') # type: Tuple[str, ...]
|
||||
ARCHIVE_EXTENSIONS = (
|
||||
ZIP_EXTENSIONS + BZ2_EXTENSIONS + TAR_EXTENSIONS + XZ_EXTENSIONS
|
||||
)
|
||||
@@ -1,18 +1,48 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import ctypes
|
||||
import os
|
||||
import re
|
||||
import warnings
|
||||
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Optional, Tuple # noqa: F401
|
||||
from typing import Optional, Tuple
|
||||
|
||||
|
||||
def glibc_version_string():
|
||||
# type: () -> Optional[str]
|
||||
"Returns glibc version string, or None if not using glibc."
|
||||
return glibc_version_string_confstr() or glibc_version_string_ctypes()
|
||||
|
||||
|
||||
def glibc_version_string_confstr():
|
||||
# type: () -> Optional[str]
|
||||
"Primary implementation of glibc_version_string using os.confstr."
|
||||
# os.confstr is quite a bit faster than ctypes.DLL. It's also less likely
|
||||
# to be broken or missing. This strategy is used in the standard library
|
||||
# platform module:
|
||||
# https://github.com/python/cpython/blob/fcf1d003bf4f0100c9d0921ff3d70e1127ca1b71/Lib/platform.py#L175-L183
|
||||
try:
|
||||
# os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17":
|
||||
_, version = os.confstr("CS_GNU_LIBC_VERSION").split()
|
||||
except (AttributeError, OSError, ValueError):
|
||||
# os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)...
|
||||
return None
|
||||
return version
|
||||
|
||||
|
||||
def glibc_version_string_ctypes():
|
||||
# type: () -> Optional[str]
|
||||
"Fallback implementation of glibc_version_string using ctypes."
|
||||
|
||||
try:
|
||||
import ctypes
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
# ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen
|
||||
# manpage says, "If filename is NULL, then the returned handle is for the
|
||||
@@ -56,7 +86,7 @@ def check_glibc_version(version_str, required_major, minimum_minor):
|
||||
|
||||
def have_compatible_glibc(required_major, minimum_minor):
|
||||
# type: (int, int) -> bool
|
||||
version_str = glibc_version_string() # type: Optional[str]
|
||||
version_str = glibc_version_string()
|
||||
if version_str is None:
|
||||
return False
|
||||
return check_glibc_version(version_str, required_major, minimum_minor)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import hashlib
|
||||
@@ -5,20 +8,22 @@ import hashlib
|
||||
from pipenv.patched.notpip._vendor.six import iteritems, iterkeys, itervalues
|
||||
|
||||
from pipenv.patched.notpip._internal.exceptions import (
|
||||
HashMismatch, HashMissing, InstallationError,
|
||||
HashMismatch,
|
||||
HashMissing,
|
||||
InstallationError,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.misc import read_chunks
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import ( # noqa: F401
|
||||
from typing import (
|
||||
Dict, List, BinaryIO, NoReturn, Iterator
|
||||
)
|
||||
from pipenv.patched.notpip._vendor.six import PY3
|
||||
if PY3:
|
||||
from hashlib import _Hash # noqa: F401
|
||||
from hashlib import _Hash
|
||||
else:
|
||||
from hashlib import _hash as _Hash # noqa: F401
|
||||
from hashlib import _hash as _Hash
|
||||
|
||||
|
||||
# The recommended hash algo of the moment. Change this whenever the state of
|
||||
@@ -44,6 +49,19 @@ class Hashes(object):
|
||||
"""
|
||||
self._allowed = {} if hashes is None else hashes
|
||||
|
||||
@property
|
||||
def digest_count(self):
|
||||
# type: () -> int
|
||||
return sum(len(digests) for digests in self._allowed.values())
|
||||
|
||||
def is_hash_allowed(
|
||||
self,
|
||||
hash_name, # type: str
|
||||
hex_digest, # type: str
|
||||
):
|
||||
"""Return whether the given hex digest is allowed."""
|
||||
return hex_digest in self._allowed.get(hash_name, [])
|
||||
|
||||
def check_against_chunks(self, chunks):
|
||||
# type: (Iterator[bytes]) -> None
|
||||
"""Check good hashes against ones built from iterable of chunks of
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
"""A helper module that injects SecureTransport, on import.
|
||||
|
||||
The import should be done as early as possible, to ensure all requests and
|
||||
sessions (or whatever) are created after injecting SecureTransport.
|
||||
|
||||
Note that we only do the injection on macOS, when the linked OpenSSL is too
|
||||
old to handle TLSv1.2.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
def inject_securetransport():
|
||||
# type: () -> None
|
||||
# Only relevant on macOS
|
||||
if sys.platform != "darwin":
|
||||
return
|
||||
|
||||
try:
|
||||
import ssl
|
||||
except ImportError:
|
||||
return
|
||||
|
||||
# Checks for OpenSSL 1.0.1
|
||||
if ssl.OPENSSL_VERSION_NUMBER >= 0x1000100f:
|
||||
return
|
||||
|
||||
try:
|
||||
from pipenv.patched.notpip._vendor.urllib3.contrib import securetransport
|
||||
except (ImportError, OSError):
|
||||
return
|
||||
|
||||
securetransport.inject_into_urllib3()
|
||||
|
||||
|
||||
inject_securetransport()
|
||||
@@ -1,3 +1,6 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import contextlib
|
||||
@@ -6,10 +9,12 @@ import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
import sys
|
||||
from logging import Filter, getLogger
|
||||
|
||||
from pipenv.patched.notpip._vendor.six import PY2
|
||||
|
||||
from pipenv.patched.notpip._internal.utils.compat import WINDOWS
|
||||
from pipenv.patched.notpip._internal.utils.deprecation import DEPRECATION_MSG_PREFIX
|
||||
from pipenv.patched.notpip._internal.utils.misc import ensure_dir
|
||||
|
||||
try:
|
||||
@@ -19,15 +24,36 @@ except ImportError:
|
||||
|
||||
|
||||
try:
|
||||
from pipenv.patched.notpip._vendor import colorama
|
||||
# Use "import as" and set colorama in the else clause to avoid mypy
|
||||
# errors and get the following correct revealed type for colorama:
|
||||
# `Union[_importlib_modulespec.ModuleType, None]`
|
||||
# Otherwise, we get an error like the following in the except block:
|
||||
# > Incompatible types in assignment (expression has type "None",
|
||||
# variable has type Module)
|
||||
# TODO: eliminate the need to use "import as" once mypy addresses some
|
||||
# of its issues with conditional imports. Here is an umbrella issue:
|
||||
# https://github.com/python/mypy/issues/1297
|
||||
from pipenv.patched.notpip._vendor import colorama as _colorama
|
||||
# Lots of different errors can come from this, including SystemError and
|
||||
# ImportError.
|
||||
except Exception:
|
||||
colorama = None
|
||||
else:
|
||||
# Import Fore explicitly rather than accessing below as colorama.Fore
|
||||
# to avoid the following error running mypy:
|
||||
# > Module has no attribute "Fore"
|
||||
# TODO: eliminate the need to import Fore once mypy addresses some of its
|
||||
# issues with conditional imports. This particular case could be an
|
||||
# instance of the following issue (but also see the umbrella issue above):
|
||||
# https://github.com/python/mypy/issues/3500
|
||||
from pipenv.patched.notpip._vendor.colorama import Fore
|
||||
|
||||
colorama = _colorama
|
||||
|
||||
|
||||
_log_state = threading.local()
|
||||
_log_state.indentation = 0
|
||||
subprocess_logger = getLogger('pip.subprocessor')
|
||||
|
||||
|
||||
class BrokenStdoutLoggingError(Exception):
|
||||
@@ -90,9 +116,10 @@ def get_indentation():
|
||||
|
||||
|
||||
class IndentingFormatter(logging.Formatter):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
A logging.Formatter obeying containing indent_log contexts.
|
||||
A logging.Formatter that obeys the indent_log() context manager.
|
||||
|
||||
:param add_timestamp: A bool indicating output lines should be prefixed
|
||||
with their record's timestamp.
|
||||
@@ -100,15 +127,36 @@ class IndentingFormatter(logging.Formatter):
|
||||
self.add_timestamp = kwargs.pop("add_timestamp", False)
|
||||
super(IndentingFormatter, self).__init__(*args, **kwargs)
|
||||
|
||||
def get_message_start(self, formatted, levelno):
|
||||
"""
|
||||
Return the start of the formatted log message (not counting the
|
||||
prefix to add to each line).
|
||||
"""
|
||||
if levelno < logging.WARNING:
|
||||
return ''
|
||||
if formatted.startswith(DEPRECATION_MSG_PREFIX):
|
||||
# Then the message already has a prefix. We don't want it to
|
||||
# look like "WARNING: DEPRECATION: ...."
|
||||
return ''
|
||||
if levelno < logging.ERROR:
|
||||
return 'WARNING: '
|
||||
|
||||
return 'ERROR: '
|
||||
|
||||
def format(self, record):
|
||||
"""
|
||||
Calls the standard formatter, but will indent all of the log messages
|
||||
by our current indentation level.
|
||||
Calls the standard formatter, but will indent all of the log message
|
||||
lines by our current indentation level.
|
||||
"""
|
||||
formatted = super(IndentingFormatter, self).format(record)
|
||||
message_start = self.get_message_start(formatted, record.levelno)
|
||||
formatted = message_start + formatted
|
||||
|
||||
prefix = ''
|
||||
if self.add_timestamp:
|
||||
prefix = self.formatTime(record, "%Y-%m-%dT%H:%M:%S ")
|
||||
# TODO: Use Formatter.default_time_format after dropping PY2.
|
||||
t = self.formatTime(record, "%Y-%m-%dT%H:%M:%S")
|
||||
prefix = '%s,%03d ' % (t, record.msecs)
|
||||
prefix += " " * get_indentation()
|
||||
formatted = "".join([
|
||||
prefix + line
|
||||
@@ -129,8 +177,8 @@ class ColorizedStreamHandler(logging.StreamHandler):
|
||||
if colorama:
|
||||
COLORS = [
|
||||
# This needs to be in order from highest logging level to lowest.
|
||||
(logging.ERROR, _color_wrap(colorama.Fore.RED)),
|
||||
(logging.WARNING, _color_wrap(colorama.Fore.YELLOW)),
|
||||
(logging.ERROR, _color_wrap(Fore.RED)),
|
||||
(logging.WARNING, _color_wrap(Fore.YELLOW)),
|
||||
]
|
||||
else:
|
||||
COLORS = []
|
||||
@@ -205,7 +253,7 @@ class BetterRotatingFileHandler(logging.handlers.RotatingFileHandler):
|
||||
return logging.handlers.RotatingFileHandler._open(self)
|
||||
|
||||
|
||||
class MaxLevelFilter(logging.Filter):
|
||||
class MaxLevelFilter(Filter):
|
||||
|
||||
def __init__(self, level):
|
||||
self.level = level
|
||||
@@ -214,6 +262,18 @@ class MaxLevelFilter(logging.Filter):
|
||||
return record.levelno < self.level
|
||||
|
||||
|
||||
class ExcludeLoggerFilter(Filter):
|
||||
|
||||
"""
|
||||
A logging Filter that excludes records from a logger (or its children).
|
||||
"""
|
||||
|
||||
def filter(self, record):
|
||||
# The base Filter class allows only records from a logger (or its
|
||||
# children).
|
||||
return not super(ExcludeLoggerFilter, self).filter(record)
|
||||
|
||||
|
||||
def setup_logging(verbosity, no_color, user_log_file):
|
||||
"""Configures and sets up all of the logging
|
||||
|
||||
@@ -254,18 +314,29 @@ def setup_logging(verbosity, no_color, user_log_file):
|
||||
"stderr": "ext://sys.stderr",
|
||||
}
|
||||
handler_classes = {
|
||||
"stream": "pip._internal.utils.logging.ColorizedStreamHandler",
|
||||
"file": "pip._internal.utils.logging.BetterRotatingFileHandler",
|
||||
"stream": "pipenv.patched.notpip._internal.utils.logging.ColorizedStreamHandler",
|
||||
"file": "pipenv.patched.notpip._internal.utils.logging.BetterRotatingFileHandler",
|
||||
}
|
||||
handlers = ["console", "console_errors", "console_subprocess"] + (
|
||||
["user_log"] if include_user_log else []
|
||||
)
|
||||
|
||||
logging.config.dictConfig({
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"filters": {
|
||||
"exclude_warnings": {
|
||||
"()": "pip._internal.utils.logging.MaxLevelFilter",
|
||||
"()": "pipenv.patched.notpip._internal.utils.logging.MaxLevelFilter",
|
||||
"level": logging.WARNING,
|
||||
},
|
||||
"restrict_to_subprocess": {
|
||||
"()": "logging.Filter",
|
||||
"name": subprocess_logger.name,
|
||||
},
|
||||
"exclude_subprocess": {
|
||||
"()": "pipenv.patched.notpip._internal.utils.logging.ExcludeLoggerFilter",
|
||||
"name": subprocess_logger.name,
|
||||
},
|
||||
},
|
||||
"formatters": {
|
||||
"indent": {
|
||||
@@ -284,7 +355,7 @@ def setup_logging(verbosity, no_color, user_log_file):
|
||||
"class": handler_classes["stream"],
|
||||
"no_color": no_color,
|
||||
"stream": log_streams["stdout"],
|
||||
"filters": ["exclude_warnings"],
|
||||
"filters": ["exclude_subprocess", "exclude_warnings"],
|
||||
"formatter": "indent",
|
||||
},
|
||||
"console_errors": {
|
||||
@@ -292,6 +363,17 @@ def setup_logging(verbosity, no_color, user_log_file):
|
||||
"class": handler_classes["stream"],
|
||||
"no_color": no_color,
|
||||
"stream": log_streams["stderr"],
|
||||
"filters": ["exclude_subprocess"],
|
||||
"formatter": "indent",
|
||||
},
|
||||
# A handler responsible for logging to the console messages
|
||||
# from the "subprocessor" logger.
|
||||
"console_subprocess": {
|
||||
"level": level,
|
||||
"class": handler_classes["stream"],
|
||||
"no_color": no_color,
|
||||
"stream": log_streams["stderr"],
|
||||
"filters": ["restrict_to_subprocess"],
|
||||
"formatter": "indent",
|
||||
},
|
||||
"user_log": {
|
||||
@@ -304,9 +386,7 @@ def setup_logging(verbosity, no_color, user_log_file):
|
||||
},
|
||||
"root": {
|
||||
"level": root_level,
|
||||
"handlers": ["console", "console_errors"] + (
|
||||
["user_log"] if include_user_log else []
|
||||
),
|
||||
"handlers": handlers,
|
||||
},
|
||||
"loggers": {
|
||||
"pip._vendor": {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
import os.path
|
||||
|
||||
DELETE_MARKER_MESSAGE = '''\
|
||||
This file is placed here by pip to indicate the source was put
|
||||
here by pip.
|
||||
|
||||
Once this package is successfully installed this source code will be
|
||||
deleted (unless you remove this file).
|
||||
'''
|
||||
PIP_DELETE_MARKER_FILENAME = 'pip-delete-this-directory.txt'
|
||||
|
||||
|
||||
def has_delete_marker_file(directory):
|
||||
return os.path.exists(os.path.join(directory, PIP_DELETE_MARKER_FILENAME))
|
||||
|
||||
|
||||
def write_delete_marker_file(directory):
|
||||
# type: (str) -> None
|
||||
"""
|
||||
Write the pip delete marker file into this directory.
|
||||
"""
|
||||
filepath = os.path.join(directory, PIP_DELETE_MARKER_FILENAME)
|
||||
with open(filepath, 'w') as marker_fp:
|
||||
marker_fp.write(DELETE_MARKER_MESSAGE)
|
||||
@@ -1,41 +1,49 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import contextlib
|
||||
import errno
|
||||
import getpass
|
||||
import io
|
||||
import locale
|
||||
# we have a submodule named 'logging' which would shadow this if we used the
|
||||
# regular name:
|
||||
import logging as std_logging
|
||||
import logging
|
||||
import os
|
||||
import posixpath
|
||||
import re
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import zipfile
|
||||
from collections import deque
|
||||
|
||||
from pipenv.patched.notpip._vendor import pkg_resources
|
||||
# NOTE: retrying is not annotated in typeshed as on 2017-07-17, which is
|
||||
# why we ignore the type on this import.
|
||||
from pipenv.patched.notpip._vendor.retrying import retry # type: ignore
|
||||
from pipenv.patched.notpip._vendor.six import PY2
|
||||
from pipenv.patched.notpip._vendor.six import PY2, text_type
|
||||
from pipenv.patched.notpip._vendor.six.moves import input
|
||||
from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse
|
||||
from pipenv.patched.notpip._vendor.six.moves.urllib.parse import unquote as urllib_unquote
|
||||
|
||||
from pipenv.patched.notpip._internal.exceptions import CommandError, InstallationError
|
||||
from pipenv.patched.notpip import __version__
|
||||
from pipenv.patched.notpip._internal.exceptions import CommandError
|
||||
from pipenv.patched.notpip._internal.locations import (
|
||||
running_under_virtualenv, site_packages, user_site, virtualenv_no_global,
|
||||
write_delete_marker_file,
|
||||
get_major_minor_version,
|
||||
site_packages,
|
||||
user_site,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.compat import (
|
||||
WINDOWS, console_to_str, expanduser, stdlib_pkgs,
|
||||
WINDOWS,
|
||||
expanduser,
|
||||
stdlib_pkgs,
|
||||
str_to_display,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.marker_files import write_delete_marker_file
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
from pipenv.patched.notpip._internal.utils.virtualenv import (
|
||||
running_under_virtualenv,
|
||||
virtualenv_no_global,
|
||||
)
|
||||
|
||||
if PY2:
|
||||
from io import BytesIO as StringIO
|
||||
@@ -43,51 +51,62 @@ else:
|
||||
from io import StringIO
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import ( # noqa: F401
|
||||
Optional, Tuple, Iterable, List, Match, Union, Any, Mapping, Text,
|
||||
AnyStr, Container
|
||||
from typing import (
|
||||
Any, AnyStr, Container, Iterable, List, Optional, Text,
|
||||
Tuple, Union, cast,
|
||||
)
|
||||
from pipenv.patched.notpip._vendor.pkg_resources import Distribution # noqa: F401
|
||||
from pipenv.patched.notpip._internal.models.link import Link # noqa: F401
|
||||
from pipenv.patched.notpip._internal.utils.ui import SpinnerInterface # noqa: F401
|
||||
from pipenv.patched.notpip._vendor.pkg_resources import Distribution
|
||||
|
||||
VersionInfo = Tuple[int, int, int]
|
||||
else:
|
||||
# typing's cast() is needed at runtime, but we don't want to import typing.
|
||||
# Thus, we use a dummy no-op version, which we tell mypy to ignore.
|
||||
def cast(type_, value): # type: ignore
|
||||
return value
|
||||
|
||||
|
||||
__all__ = ['rmtree', 'display_path', 'backup_dir',
|
||||
'ask', 'splitext',
|
||||
'format_size', 'is_installable_dir',
|
||||
'is_svn_page', 'file_contents',
|
||||
'split_leading_dir', 'has_leading_dir',
|
||||
'normalize_path',
|
||||
'renames', 'get_prog',
|
||||
'unzip_file', 'untar_file', 'unpack_file', 'call_subprocess',
|
||||
'captured_stdout', 'ensure_dir',
|
||||
'ARCHIVE_EXTENSIONS', 'SUPPORTED_EXTENSIONS', 'WHEEL_EXTENSION',
|
||||
'get_installed_version', 'remove_auth_from_url']
|
||||
|
||||
|
||||
logger = std_logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
WHEEL_EXTENSION = '.whl'
|
||||
BZ2_EXTENSIONS = ('.tar.bz2', '.tbz')
|
||||
XZ_EXTENSIONS = ('.tar.xz', '.txz', '.tlz', '.tar.lz', '.tar.lzma')
|
||||
ZIP_EXTENSIONS = ('.zip', WHEEL_EXTENSION)
|
||||
TAR_EXTENSIONS = ('.tar.gz', '.tgz', '.tar')
|
||||
ARCHIVE_EXTENSIONS = (
|
||||
ZIP_EXTENSIONS + BZ2_EXTENSIONS + TAR_EXTENSIONS + XZ_EXTENSIONS)
|
||||
SUPPORTED_EXTENSIONS = ZIP_EXTENSIONS + TAR_EXTENSIONS
|
||||
|
||||
try:
|
||||
import bz2 # noqa
|
||||
SUPPORTED_EXTENSIONS += BZ2_EXTENSIONS
|
||||
except ImportError:
|
||||
logger.debug('bz2 module is not available')
|
||||
def get_pip_version():
|
||||
# type: () -> str
|
||||
pip_pkg_dir = os.path.join(os.path.dirname(__file__), "..", "..")
|
||||
pip_pkg_dir = os.path.abspath(pip_pkg_dir)
|
||||
|
||||
try:
|
||||
# Only for Python 3.3+
|
||||
import lzma # noqa
|
||||
SUPPORTED_EXTENSIONS += XZ_EXTENSIONS
|
||||
except ImportError:
|
||||
logger.debug('lzma module is not available')
|
||||
return (
|
||||
'pip {} from {} (python {})'.format(
|
||||
__version__, pip_pkg_dir, get_major_minor_version(),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def normalize_version_info(py_version_info):
|
||||
# type: (Tuple[int, ...]) -> Tuple[int, int, int]
|
||||
"""
|
||||
Convert a tuple of ints representing a Python version to one of length
|
||||
three.
|
||||
|
||||
:param py_version_info: a tuple of ints representing a Python version,
|
||||
or None to specify no version. The tuple can have any length.
|
||||
|
||||
:return: a tuple of length three if `py_version_info` is non-None.
|
||||
Otherwise, return `py_version_info` unchanged (i.e. None).
|
||||
"""
|
||||
if len(py_version_info) < 3:
|
||||
py_version_info += (3 - len(py_version_info)) * (0,)
|
||||
elif len(py_version_info) > 3:
|
||||
py_version_info = py_version_info[:3]
|
||||
|
||||
return cast('VersionInfo', py_version_info)
|
||||
|
||||
|
||||
def ensure_dir(path):
|
||||
@@ -125,8 +144,13 @@ def rmtree_errorhandler(func, path, exc_info):
|
||||
"""On Windows, the files in .svn are read-only, so when rmtree() tries to
|
||||
remove them, an exception is thrown. We catch that here, remove the
|
||||
read-only attribute, and hopefully continue without problems."""
|
||||
# if file type currently read only
|
||||
if os.stat(path).st_mode & stat.S_IREAD:
|
||||
try:
|
||||
has_attr_readonly = not (os.stat(path).st_mode & stat.S_IWRITE)
|
||||
except (IOError, OSError):
|
||||
# it's equivalent to os.path.exists
|
||||
return
|
||||
|
||||
if has_attr_readonly:
|
||||
# convert to read/write
|
||||
os.chmod(path, stat.S_IWRITE)
|
||||
# use the original function to repeat the operation
|
||||
@@ -136,6 +160,40 @@ def rmtree_errorhandler(func, path, exc_info):
|
||||
raise
|
||||
|
||||
|
||||
def path_to_display(path):
|
||||
# type: (Optional[Union[str, Text]]) -> Optional[Text]
|
||||
"""
|
||||
Convert a bytes (or text) path to text (unicode in Python 2) for display
|
||||
and logging purposes.
|
||||
|
||||
This function should never error out. Also, this function is mainly needed
|
||||
for Python 2 since in Python 3 str paths are already text.
|
||||
"""
|
||||
if path is None:
|
||||
return None
|
||||
if isinstance(path, text_type):
|
||||
return path
|
||||
# Otherwise, path is a bytes object (str in Python 2).
|
||||
try:
|
||||
display_path = path.decode(sys.getfilesystemencoding(), 'strict')
|
||||
except UnicodeDecodeError:
|
||||
# Include the full bytes to make troubleshooting easier, even though
|
||||
# it may not be very human readable.
|
||||
if PY2:
|
||||
# Convert the bytes to a readable str representation using
|
||||
# repr(), and then convert the str to unicode.
|
||||
# Also, we add the prefix "b" to the repr() return value both
|
||||
# to make the Python 2 output look like the Python 3 output, and
|
||||
# to signal to the user that this is a bytes representation.
|
||||
display_path = str_to_display('b{!r}'.format(path))
|
||||
else:
|
||||
# Silence the "F821 undefined name 'ascii'" flake8 error since
|
||||
# in Python 3 ascii() is a built-in.
|
||||
display_path = ascii(path) # noqa: F821
|
||||
|
||||
return display_path
|
||||
|
||||
|
||||
def display_path(path):
|
||||
# type: (Union[str, Text]) -> str
|
||||
"""Gives the display value for a given path, making it relative to cwd
|
||||
@@ -169,15 +227,21 @@ def ask_path_exists(message, options):
|
||||
return ask(message, options)
|
||||
|
||||
|
||||
def _check_no_input(message):
|
||||
# type: (str) -> None
|
||||
"""Raise an error if no input is allowed."""
|
||||
if os.environ.get('PIP_NO_INPUT'):
|
||||
raise Exception(
|
||||
'No input was expected ($PIP_NO_INPUT set); question: %s' %
|
||||
message
|
||||
)
|
||||
|
||||
|
||||
def ask(message, options):
|
||||
# type: (str, Iterable[str]) -> str
|
||||
"""Ask the message interactively, with the given possible responses"""
|
||||
while 1:
|
||||
if os.environ.get('PIP_NO_INPUT'):
|
||||
raise Exception(
|
||||
'No input was expected ($PIP_NO_INPUT set); question: %s' %
|
||||
message
|
||||
)
|
||||
_check_no_input(message)
|
||||
response = input(message)
|
||||
response = response.strip().lower()
|
||||
if response not in options:
|
||||
@@ -189,6 +253,20 @@ def ask(message, options):
|
||||
return response
|
||||
|
||||
|
||||
def ask_input(message):
|
||||
# type: (str) -> str
|
||||
"""Ask for input interactively."""
|
||||
_check_no_input(message)
|
||||
return input(message)
|
||||
|
||||
|
||||
def ask_password(message):
|
||||
# type: (str) -> str
|
||||
"""Ask for a password interactively."""
|
||||
_check_no_input(message)
|
||||
return getpass.getpass(message)
|
||||
|
||||
|
||||
def format_size(bytes):
|
||||
# type: (float) -> str
|
||||
if bytes > 1000 * 1000:
|
||||
@@ -216,21 +294,6 @@ def is_installable_dir(path):
|
||||
return False
|
||||
|
||||
|
||||
def is_svn_page(html):
|
||||
# type: (Union[str, Text]) -> Optional[Match[Union[str, Text]]]
|
||||
"""
|
||||
Returns true if the page appears to be the index page of an svn repository
|
||||
"""
|
||||
return (re.search(r'<title>[^<]*Revision \d+:', html) and
|
||||
re.search(r'Powered by (?:<a[^>]*?>)?Subversion', html, re.I))
|
||||
|
||||
|
||||
def file_contents(filename):
|
||||
# type: (str) -> Text
|
||||
with open(filename, 'rb') as fp:
|
||||
return fp.read().decode('utf-8')
|
||||
|
||||
|
||||
def read_chunks(file, size=io.DEFAULT_BUFFER_SIZE):
|
||||
"""Yield pieces of data from a file-like object until EOF."""
|
||||
while True:
|
||||
@@ -240,34 +303,6 @@ def read_chunks(file, size=io.DEFAULT_BUFFER_SIZE):
|
||||
yield chunk
|
||||
|
||||
|
||||
def split_leading_dir(path):
|
||||
# type: (Union[str, Text]) -> List[Union[str, Text]]
|
||||
path = path.lstrip('/').lstrip('\\')
|
||||
if '/' in path and (('\\' in path and path.find('/') < path.find('\\')) or
|
||||
'\\' not in path):
|
||||
return path.split('/', 1)
|
||||
elif '\\' in path:
|
||||
return path.split('\\', 1)
|
||||
else:
|
||||
return [path, '']
|
||||
|
||||
|
||||
def has_leading_dir(paths):
|
||||
# type: (Iterable[Union[str, Text]]) -> bool
|
||||
"""Returns true if all the paths have the same leading path name
|
||||
(i.e., everything is in one subdirectory in an archive)"""
|
||||
common_prefix = None
|
||||
for path in paths:
|
||||
prefix, rest = split_leading_dir(path)
|
||||
if not prefix:
|
||||
return False
|
||||
elif common_prefix is None:
|
||||
common_prefix = prefix
|
||||
elif prefix != common_prefix:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def normalize_path(path, resolve_symlinks=True):
|
||||
# type: (str, bool) -> str
|
||||
"""
|
||||
@@ -317,10 +352,12 @@ def is_local(path):
|
||||
|
||||
If we're not in a virtualenv, all paths are considered "local."
|
||||
|
||||
Caution: this function assumes the head of path has been normalized
|
||||
with normalize_path.
|
||||
"""
|
||||
if not running_under_virtualenv():
|
||||
return True
|
||||
return normalize_path(path).startswith(normalize_path(sys.prefix))
|
||||
return path.startswith(normalize_path(sys.prefix))
|
||||
|
||||
|
||||
def dist_is_local(dist):
|
||||
@@ -340,8 +377,7 @@ def dist_in_usersite(dist):
|
||||
"""
|
||||
Return True if given Distribution is installed in user site.
|
||||
"""
|
||||
norm_path = normalize_path(dist_location(dist))
|
||||
return norm_path.startswith(normalize_path(user_site))
|
||||
return dist_location(dist).startswith(normalize_path(user_site))
|
||||
|
||||
|
||||
def dist_in_site_packages(dist):
|
||||
@@ -350,9 +386,7 @@ def dist_in_site_packages(dist):
|
||||
Return True if given Distribution is installed in
|
||||
sysconfig.get_python_lib().
|
||||
"""
|
||||
return normalize_path(
|
||||
dist_location(dist)
|
||||
).startswith(normalize_path(site_packages))
|
||||
return dist_location(dist).startswith(normalize_path(site_packages))
|
||||
|
||||
|
||||
def dist_is_editable(dist):
|
||||
@@ -367,12 +401,15 @@ def dist_is_editable(dist):
|
||||
return False
|
||||
|
||||
|
||||
def get_installed_distributions(local_only=True,
|
||||
skip=stdlib_pkgs,
|
||||
include_editables=True,
|
||||
editables_only=False,
|
||||
user_only=False):
|
||||
# type: (bool, Container[str], bool, bool, bool) -> List[Distribution]
|
||||
def get_installed_distributions(
|
||||
local_only=True, # type: bool
|
||||
skip=stdlib_pkgs, # type: Container[str]
|
||||
include_editables=True, # type: bool
|
||||
editables_only=False, # type: bool
|
||||
user_only=False, # type: bool
|
||||
paths=None # type: Optional[List[str]]
|
||||
):
|
||||
# type: (...) -> List[Distribution]
|
||||
"""
|
||||
Return a list of installed Distribution objects.
|
||||
|
||||
@@ -389,7 +426,14 @@ def get_installed_distributions(local_only=True,
|
||||
If ``user_only`` is True , only report installations in the user
|
||||
site directory.
|
||||
|
||||
If ``paths`` is set, only report the distributions present at the
|
||||
specified list of locations.
|
||||
"""
|
||||
if paths:
|
||||
working_set = pkg_resources.WorkingSet(paths)
|
||||
else:
|
||||
working_set = pkg_resources.working_set
|
||||
|
||||
if local_only:
|
||||
local_test = dist_is_local
|
||||
else:
|
||||
@@ -417,7 +461,7 @@ def get_installed_distributions(local_only=True,
|
||||
return True
|
||||
|
||||
# because of pkg_resources vendoring, mypy cannot find stub in typeshed
|
||||
return [d for d in pkg_resources.working_set # type: ignore
|
||||
return [d for d in working_set # type: ignore
|
||||
if local_test(d) and
|
||||
d.key not in skip and
|
||||
editable_test(d) and
|
||||
@@ -447,12 +491,9 @@ def egg_link_path(dist):
|
||||
"""
|
||||
sites = []
|
||||
if running_under_virtualenv():
|
||||
if virtualenv_no_global():
|
||||
sites.append(site_packages)
|
||||
else:
|
||||
sites.append(site_packages)
|
||||
if user_site:
|
||||
sites.append(user_site)
|
||||
sites.append(site_packages)
|
||||
if not virtualenv_no_global() and user_site:
|
||||
sites.append(user_site)
|
||||
else:
|
||||
if user_site:
|
||||
sites.append(user_site)
|
||||
@@ -473,331 +514,17 @@ def dist_location(dist):
|
||||
packages, where dist.location is the source code location, and we
|
||||
want to know where the egg-link file is.
|
||||
|
||||
The returned location is normalized (in particular, with symlinks removed).
|
||||
"""
|
||||
egg_link = egg_link_path(dist)
|
||||
if egg_link:
|
||||
return egg_link
|
||||
return dist.location
|
||||
return normalize_path(egg_link)
|
||||
return normalize_path(dist.location)
|
||||
|
||||
|
||||
def current_umask():
|
||||
"""Get the current umask which involves having to set it temporarily."""
|
||||
mask = os.umask(0)
|
||||
os.umask(mask)
|
||||
return mask
|
||||
|
||||
|
||||
def unzip_file(filename, location, flatten=True):
|
||||
# type: (str, str, bool) -> None
|
||||
"""
|
||||
Unzip the file (with path `filename`) to the destination `location`. All
|
||||
files are written based on system defaults and umask (i.e. permissions are
|
||||
not preserved), except that regular file members with any execute
|
||||
permissions (user, group, or world) have "chmod +x" applied after being
|
||||
written. Note that for windows, any execute changes using os.chmod are
|
||||
no-ops per the python docs.
|
||||
"""
|
||||
ensure_dir(location)
|
||||
zipfp = open(filename, 'rb')
|
||||
try:
|
||||
zip = zipfile.ZipFile(zipfp, allowZip64=True)
|
||||
leading = has_leading_dir(zip.namelist()) and flatten
|
||||
for info in zip.infolist():
|
||||
name = info.filename
|
||||
fn = name
|
||||
if leading:
|
||||
fn = split_leading_dir(name)[1]
|
||||
fn = os.path.join(location, fn)
|
||||
dir = os.path.dirname(fn)
|
||||
if fn.endswith('/') or fn.endswith('\\'):
|
||||
# A directory
|
||||
ensure_dir(fn)
|
||||
else:
|
||||
ensure_dir(dir)
|
||||
# Don't use read() to avoid allocating an arbitrarily large
|
||||
# chunk of memory for the file's content
|
||||
fp = zip.open(name)
|
||||
try:
|
||||
with open(fn, 'wb') as destfp:
|
||||
shutil.copyfileobj(fp, destfp)
|
||||
finally:
|
||||
fp.close()
|
||||
mode = info.external_attr >> 16
|
||||
# if mode and regular file and any execute permissions for
|
||||
# user/group/world?
|
||||
if mode and stat.S_ISREG(mode) and mode & 0o111:
|
||||
# make dest file have execute for user/group/world
|
||||
# (chmod +x) no-op on windows per python docs
|
||||
os.chmod(fn, (0o777 - current_umask() | 0o111))
|
||||
finally:
|
||||
zipfp.close()
|
||||
|
||||
|
||||
def untar_file(filename, location):
|
||||
def write_output(msg, *args):
|
||||
# type: (str, str) -> None
|
||||
"""
|
||||
Untar the file (with path `filename`) to the destination `location`.
|
||||
All files are written based on system defaults and umask (i.e. permissions
|
||||
are not preserved), except that regular file members with any execute
|
||||
permissions (user, group, or world) have "chmod +x" applied after being
|
||||
written. Note that for windows, any execute changes using os.chmod are
|
||||
no-ops per the python docs.
|
||||
"""
|
||||
ensure_dir(location)
|
||||
if filename.lower().endswith('.gz') or filename.lower().endswith('.tgz'):
|
||||
mode = 'r:gz'
|
||||
elif filename.lower().endswith(BZ2_EXTENSIONS):
|
||||
mode = 'r:bz2'
|
||||
elif filename.lower().endswith(XZ_EXTENSIONS):
|
||||
mode = 'r:xz'
|
||||
elif filename.lower().endswith('.tar'):
|
||||
mode = 'r'
|
||||
else:
|
||||
logger.warning(
|
||||
'Cannot determine compression type for file %s', filename,
|
||||
)
|
||||
mode = 'r:*'
|
||||
tar = tarfile.open(filename, mode)
|
||||
try:
|
||||
leading = has_leading_dir([
|
||||
member.name for member in tar.getmembers()
|
||||
])
|
||||
for member in tar.getmembers():
|
||||
fn = member.name
|
||||
if leading:
|
||||
# https://github.com/python/mypy/issues/1174
|
||||
fn = split_leading_dir(fn)[1] # type: ignore
|
||||
path = os.path.join(location, fn)
|
||||
if member.isdir():
|
||||
ensure_dir(path)
|
||||
elif member.issym():
|
||||
try:
|
||||
# https://github.com/python/typeshed/issues/2673
|
||||
tar._extract_member(member, path) # type: ignore
|
||||
except Exception as exc:
|
||||
# Some corrupt tar files seem to produce this
|
||||
# (specifically bad symlinks)
|
||||
logger.warning(
|
||||
'In the tar file %s the member %s is invalid: %s',
|
||||
filename, member.name, exc,
|
||||
)
|
||||
continue
|
||||
else:
|
||||
try:
|
||||
fp = tar.extractfile(member)
|
||||
except (KeyError, AttributeError) as exc:
|
||||
# Some corrupt tar files seem to produce this
|
||||
# (specifically bad symlinks)
|
||||
logger.warning(
|
||||
'In the tar file %s the member %s is invalid: %s',
|
||||
filename, member.name, exc,
|
||||
)
|
||||
continue
|
||||
ensure_dir(os.path.dirname(path))
|
||||
with open(path, 'wb') as destfp:
|
||||
shutil.copyfileobj(fp, destfp)
|
||||
fp.close()
|
||||
# Update the timestamp (useful for cython compiled files)
|
||||
# https://github.com/python/typeshed/issues/2673
|
||||
tar.utime(member, path) # type: ignore
|
||||
# member have any execute permissions for user/group/world?
|
||||
if member.mode & 0o111:
|
||||
# make dest file have execute for user/group/world
|
||||
# no-op on windows per python docs
|
||||
os.chmod(path, (0o777 - current_umask() | 0o111))
|
||||
finally:
|
||||
tar.close()
|
||||
|
||||
|
||||
def unpack_file(
|
||||
filename, # type: str
|
||||
location, # type: str
|
||||
content_type, # type: Optional[str]
|
||||
link # type: Optional[Link]
|
||||
):
|
||||
# type: (...) -> None
|
||||
filename = os.path.realpath(filename)
|
||||
if (content_type == 'application/zip' or
|
||||
filename.lower().endswith(ZIP_EXTENSIONS) or
|
||||
zipfile.is_zipfile(filename)):
|
||||
unzip_file(
|
||||
filename,
|
||||
location,
|
||||
flatten=not filename.endswith('.whl')
|
||||
)
|
||||
elif (content_type == 'application/x-gzip' or
|
||||
tarfile.is_tarfile(filename) or
|
||||
filename.lower().endswith(
|
||||
TAR_EXTENSIONS + BZ2_EXTENSIONS + XZ_EXTENSIONS)):
|
||||
untar_file(filename, location)
|
||||
elif (content_type and content_type.startswith('text/html') and
|
||||
is_svn_page(file_contents(filename))):
|
||||
# We don't really care about this
|
||||
from pipenv.patched.notpip._internal.vcs.subversion import Subversion
|
||||
Subversion('svn+' + link.url).unpack(location)
|
||||
else:
|
||||
# FIXME: handle?
|
||||
# FIXME: magic signatures?
|
||||
logger.critical(
|
||||
'Cannot unpack file %s (downloaded from %s, content-type: %s); '
|
||||
'cannot detect archive format',
|
||||
filename, location, content_type,
|
||||
)
|
||||
raise InstallationError(
|
||||
'Cannot determine archive format of %s' % location
|
||||
)
|
||||
|
||||
|
||||
def call_subprocess(
|
||||
cmd, # type: List[str]
|
||||
show_stdout=True, # type: bool
|
||||
cwd=None, # type: Optional[str]
|
||||
on_returncode='raise', # type: str
|
||||
extra_ok_returncodes=None, # type: Optional[Iterable[int]]
|
||||
command_desc=None, # type: Optional[str]
|
||||
extra_environ=None, # type: Optional[Mapping[str, Any]]
|
||||
unset_environ=None, # type: Optional[Iterable[str]]
|
||||
spinner=None # type: Optional[SpinnerInterface]
|
||||
):
|
||||
# type: (...) -> Optional[Text]
|
||||
"""
|
||||
Args:
|
||||
extra_ok_returncodes: an iterable of integer return codes that are
|
||||
acceptable, in addition to 0. Defaults to None, which means [].
|
||||
unset_environ: an iterable of environment variable names to unset
|
||||
prior to calling subprocess.Popen().
|
||||
"""
|
||||
if extra_ok_returncodes is None:
|
||||
extra_ok_returncodes = []
|
||||
if unset_environ is None:
|
||||
unset_environ = []
|
||||
# This function's handling of subprocess output is confusing and I
|
||||
# previously broke it terribly, so as penance I will write a long comment
|
||||
# explaining things.
|
||||
#
|
||||
# The obvious thing that affects output is the show_stdout=
|
||||
# kwarg. show_stdout=True means, let the subprocess write directly to our
|
||||
# stdout. Even though it is nominally the default, it is almost never used
|
||||
# inside pip (and should not be used in new code without a very good
|
||||
# reason); as of 2016-02-22 it is only used in a few places inside the VCS
|
||||
# wrapper code. Ideally we should get rid of it entirely, because it
|
||||
# creates a lot of complexity here for a rarely used feature.
|
||||
#
|
||||
# Most places in pip set show_stdout=False. What this means is:
|
||||
# - We connect the child stdout to a pipe, which we read.
|
||||
# - By default, we hide the output but show a spinner -- unless the
|
||||
# subprocess exits with an error, in which case we show the output.
|
||||
# - If the --verbose option was passed (= loglevel is DEBUG), then we show
|
||||
# the output unconditionally. (But in this case we don't want to show
|
||||
# the output a second time if it turns out that there was an error.)
|
||||
#
|
||||
# stderr is always merged with stdout (even if show_stdout=True).
|
||||
if show_stdout:
|
||||
stdout = None
|
||||
else:
|
||||
stdout = subprocess.PIPE
|
||||
if command_desc is None:
|
||||
cmd_parts = []
|
||||
for part in cmd:
|
||||
if ' ' in part or '\n' in part or '"' in part or "'" in part:
|
||||
part = '"%s"' % part.replace('"', '\\"')
|
||||
cmd_parts.append(part)
|
||||
command_desc = ' '.join(cmd_parts)
|
||||
logger.debug("Running command %s", command_desc)
|
||||
env = os.environ.copy()
|
||||
if extra_environ:
|
||||
env.update(extra_environ)
|
||||
for name in unset_environ:
|
||||
env.pop(name, None)
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
cmd, stderr=subprocess.STDOUT, stdin=subprocess.PIPE,
|
||||
stdout=stdout, cwd=cwd, env=env,
|
||||
)
|
||||
proc.stdin.close()
|
||||
except Exception as exc:
|
||||
logger.critical(
|
||||
"Error %s while executing command %s", exc, command_desc,
|
||||
)
|
||||
raise
|
||||
all_output = []
|
||||
if stdout is not None:
|
||||
while True:
|
||||
line = console_to_str(proc.stdout.readline())
|
||||
if not line:
|
||||
break
|
||||
line = line.rstrip()
|
||||
all_output.append(line + '\n')
|
||||
if logger.getEffectiveLevel() <= std_logging.DEBUG:
|
||||
# Show the line immediately
|
||||
logger.debug(line)
|
||||
else:
|
||||
# Update the spinner
|
||||
if spinner is not None:
|
||||
spinner.spin()
|
||||
try:
|
||||
proc.wait()
|
||||
finally:
|
||||
if proc.stdout:
|
||||
proc.stdout.close()
|
||||
if spinner is not None:
|
||||
if proc.returncode:
|
||||
spinner.finish("error")
|
||||
else:
|
||||
spinner.finish("done")
|
||||
if proc.returncode and proc.returncode not in extra_ok_returncodes:
|
||||
if on_returncode == 'raise':
|
||||
if (logger.getEffectiveLevel() > std_logging.DEBUG and
|
||||
not show_stdout):
|
||||
logger.info(
|
||||
'Complete output from command %s:', command_desc,
|
||||
)
|
||||
logger.info(
|
||||
''.join(all_output) +
|
||||
'\n----------------------------------------'
|
||||
)
|
||||
raise InstallationError(
|
||||
'Command "%s" failed with error code %s in %s'
|
||||
% (command_desc, proc.returncode, cwd))
|
||||
elif on_returncode == 'warn':
|
||||
logger.warning(
|
||||
'Command "%s" had error code %s in %s',
|
||||
command_desc, proc.returncode, cwd,
|
||||
)
|
||||
elif on_returncode == 'ignore':
|
||||
pass
|
||||
else:
|
||||
raise ValueError('Invalid value: on_returncode=%s' %
|
||||
repr(on_returncode))
|
||||
if not show_stdout:
|
||||
return ''.join(all_output)
|
||||
return None
|
||||
|
||||
|
||||
def read_text_file(filename):
|
||||
# type: (str) -> str
|
||||
"""Return the contents of *filename*.
|
||||
|
||||
Try to decode the file contents with utf-8, the preferred system encoding
|
||||
(e.g., cp1252 on some Windows machines), and latin1, in that order.
|
||||
Decoding a byte string with latin1 will never raise an error. In the worst
|
||||
case, the returned string will contain some garbage characters.
|
||||
|
||||
"""
|
||||
with open(filename, 'rb') as fp:
|
||||
data = fp.read()
|
||||
|
||||
encodings = ['utf-8', locale.getpreferredencoding(False), 'latin1']
|
||||
for enc in encodings:
|
||||
try:
|
||||
# https://github.com/python/mypy/issues/1174
|
||||
data = data.decode(enc) # type: ignore
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
break
|
||||
|
||||
assert not isinstance(data, bytes) # Latin1 should have worked.
|
||||
return data
|
||||
logger.info(msg, *args)
|
||||
|
||||
|
||||
def _make_build_dir(build_dir):
|
||||
@@ -922,20 +649,38 @@ def enum(*sequential, **named):
|
||||
return type('Enum', (), enums)
|
||||
|
||||
|
||||
def make_vcs_requirement_url(repo_url, rev, project_name, subdir=None):
|
||||
def build_netloc(host, port):
|
||||
# type: (str, Optional[int]) -> str
|
||||
"""
|
||||
Return the URL for a VCS requirement.
|
||||
|
||||
Args:
|
||||
repo_url: the remote VCS url, with any needed VCS prefix (e.g. "git+").
|
||||
project_name: the (unescaped) project name.
|
||||
Build a netloc from a host-port pair
|
||||
"""
|
||||
egg_project_name = pkg_resources.to_filename(project_name)
|
||||
req = '{}@{}#egg={}'.format(repo_url, rev, egg_project_name)
|
||||
if subdir:
|
||||
req += '&subdirectory={}'.format(subdir)
|
||||
if port is None:
|
||||
return host
|
||||
if ':' in host:
|
||||
# Only wrap host with square brackets when it is IPv6
|
||||
host = '[{}]'.format(host)
|
||||
return '{}:{}'.format(host, port)
|
||||
|
||||
return req
|
||||
|
||||
def build_url_from_netloc(netloc, scheme='https'):
|
||||
# type: (str, str) -> str
|
||||
"""
|
||||
Build a full URL from a netloc.
|
||||
"""
|
||||
if netloc.count(':') >= 2 and '@' not in netloc and '[' not in netloc:
|
||||
# It must be a bare IPv6 address, so wrap it with brackets.
|
||||
netloc = '[{}]'.format(netloc)
|
||||
return '{}://{}'.format(scheme, netloc)
|
||||
|
||||
|
||||
def parse_netloc(netloc):
|
||||
# type: (str) -> Tuple[str, Optional[int]]
|
||||
"""
|
||||
Return the host-port pair from a netloc.
|
||||
"""
|
||||
url = build_url_from_netloc(netloc)
|
||||
parsed = urllib_parse.urlparse(url)
|
||||
return parsed.hostname, parsed.port
|
||||
|
||||
|
||||
def split_auth_from_netloc(netloc):
|
||||
@@ -969,59 +714,137 @@ def split_auth_from_netloc(netloc):
|
||||
def redact_netloc(netloc):
|
||||
# type: (str) -> str
|
||||
"""
|
||||
Replace the password in a netloc with "****", if it exists.
|
||||
Replace the sensitive data in a netloc with "****", if it exists.
|
||||
|
||||
For example, "user:pass@example.com" returns "user:****@example.com".
|
||||
For example:
|
||||
- "user:pass@example.com" returns "user:****@example.com"
|
||||
- "accesstoken@example.com" returns "****@example.com"
|
||||
"""
|
||||
netloc, (user, password) = split_auth_from_netloc(netloc)
|
||||
if user is None:
|
||||
return netloc
|
||||
password = '' if password is None else ':****'
|
||||
return '{user}{password}@{netloc}'.format(user=urllib_parse.quote(user),
|
||||
if password is None:
|
||||
user = '****'
|
||||
password = ''
|
||||
else:
|
||||
user = urllib_parse.quote(user)
|
||||
password = ':****'
|
||||
return '{user}{password}@{netloc}'.format(user=user,
|
||||
password=password,
|
||||
netloc=netloc)
|
||||
|
||||
|
||||
def _transform_url(url, transform_netloc):
|
||||
"""Transform and replace netloc in a url.
|
||||
|
||||
transform_netloc is a function taking the netloc and returning a
|
||||
tuple. The first element of this tuple is the new netloc. The
|
||||
entire tuple is returned.
|
||||
|
||||
Returns a tuple containing the transformed url as item 0 and the
|
||||
original tuple returned by transform_netloc as item 1.
|
||||
"""
|
||||
purl = urllib_parse.urlsplit(url)
|
||||
netloc = transform_netloc(purl.netloc)
|
||||
netloc_tuple = transform_netloc(purl.netloc)
|
||||
# stripped url
|
||||
url_pieces = (
|
||||
purl.scheme, netloc, purl.path, purl.query, purl.fragment
|
||||
purl.scheme, netloc_tuple[0], purl.path, purl.query, purl.fragment
|
||||
)
|
||||
surl = urllib_parse.urlunsplit(url_pieces)
|
||||
return surl
|
||||
return surl, netloc_tuple
|
||||
|
||||
|
||||
def _get_netloc(netloc):
|
||||
return split_auth_from_netloc(netloc)[0]
|
||||
return split_auth_from_netloc(netloc)
|
||||
|
||||
|
||||
def _redact_netloc(netloc):
|
||||
return (redact_netloc(netloc),)
|
||||
|
||||
|
||||
def split_auth_netloc_from_url(url):
|
||||
# type: (str) -> Tuple[str, str, Tuple[str, str]]
|
||||
"""
|
||||
Parse a url into separate netloc, auth, and url with no auth.
|
||||
|
||||
Returns: (url_without_auth, netloc, (username, password))
|
||||
"""
|
||||
url_without_auth, (netloc, auth) = _transform_url(url, _get_netloc)
|
||||
return url_without_auth, netloc, auth
|
||||
|
||||
|
||||
def remove_auth_from_url(url):
|
||||
# type: (str) -> str
|
||||
# Return a copy of url with 'username:password@' removed.
|
||||
"""Return a copy of url with 'username:password@' removed."""
|
||||
# username/pass params are passed to subversion through flags
|
||||
# and are not recognized in the url.
|
||||
return _transform_url(url, _get_netloc)
|
||||
return _transform_url(url, _get_netloc)[0]
|
||||
|
||||
|
||||
def redact_password_from_url(url):
|
||||
def redact_auth_from_url(url):
|
||||
# type: (str) -> str
|
||||
"""Replace the password in a given url with ****."""
|
||||
return _transform_url(url, redact_netloc)
|
||||
return _transform_url(url, _redact_netloc)[0]
|
||||
|
||||
|
||||
class HiddenText(object):
|
||||
def __init__(
|
||||
self,
|
||||
secret, # type: str
|
||||
redacted, # type: str
|
||||
):
|
||||
# type: (...) -> None
|
||||
self.secret = secret
|
||||
self.redacted = redacted
|
||||
|
||||
def __repr__(self):
|
||||
# type: (...) -> str
|
||||
return '<HiddenText {!r}>'.format(str(self))
|
||||
|
||||
def __str__(self):
|
||||
# type: (...) -> str
|
||||
return self.redacted
|
||||
|
||||
# This is useful for testing.
|
||||
def __eq__(self, other):
|
||||
# type: (Any) -> bool
|
||||
if type(self) != type(other):
|
||||
return False
|
||||
|
||||
# The string being used for redaction doesn't also have to match,
|
||||
# just the raw, original string.
|
||||
return (self.secret == other.secret)
|
||||
|
||||
# We need to provide an explicit __ne__ implementation for Python 2.
|
||||
# TODO: remove this when we drop PY2 support.
|
||||
def __ne__(self, other):
|
||||
# type: (Any) -> bool
|
||||
return not self == other
|
||||
|
||||
|
||||
def hide_value(value):
|
||||
# type: (str) -> HiddenText
|
||||
return HiddenText(value, redacted='****')
|
||||
|
||||
|
||||
def hide_url(url):
|
||||
# type: (str) -> HiddenText
|
||||
redacted = redact_auth_from_url(url)
|
||||
return HiddenText(url, redacted=redacted)
|
||||
|
||||
|
||||
def protect_pip_from_modification_on_windows(modifying_pip):
|
||||
# type: (bool) -> None
|
||||
"""Protection of pip.exe from modification on Windows
|
||||
|
||||
On Windows, any operation modifying pip should be run as:
|
||||
python -m pip ...
|
||||
"""
|
||||
pip_names = [
|
||||
"pip.exe",
|
||||
"pip{}.exe".format(sys.version_info[0]),
|
||||
"pip{}.{}.exe".format(*sys.version_info[:2])
|
||||
]
|
||||
pip_names = set()
|
||||
for ext in ('', '.exe'):
|
||||
pip_names.add('pip{ext}'.format(ext=ext))
|
||||
pip_names.add('pip{}{ext}'.format(sys.version_info[0], ext=ext))
|
||||
pip_names.add('pip{}.{}{ext}'.format(*sys.version_info[:2], ext=ext))
|
||||
|
||||
# See https://github.com/pypa/pip/issues/1299 for more discussion
|
||||
should_show_use_python_msg = (
|
||||
@@ -1038,3 +861,10 @@ def protect_pip_from_modification_on_windows(modifying_pip):
|
||||
'To modify pip, please run the following command:\n{}'
|
||||
.format(" ".join(new_command))
|
||||
)
|
||||
|
||||
|
||||
def is_console_interactive():
|
||||
# type: () -> bool
|
||||
"""Is this console interactive?
|
||||
"""
|
||||
return sys.stdin is not None and sys.stdin.isatty()
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
"""Utilities for defining models
|
||||
"""
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: disallow-untyped-defs=False
|
||||
|
||||
import operator
|
||||
|
||||
|
||||
class KeyBasedCompareMixin(object):
|
||||
"""Provides comparision capabilities that is based on a key
|
||||
"""Provides comparison capabilities that is based on a key
|
||||
"""
|
||||
|
||||
def __init__(self, key, defining_class):
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
from pipenv.patched.notpip._vendor import lockfile, pkg_resources
|
||||
from pipenv.patched.notpip._vendor.packaging import version as packaging_version
|
||||
|
||||
from pipenv.patched.notpip._internal.index import PackageFinder
|
||||
from pipenv.patched.notpip._internal.utils.compat import WINDOWS
|
||||
from pipenv.patched.notpip._internal.utils.filesystem import check_path_owner
|
||||
from pipenv.patched.notpip._internal.utils.misc import ensure_dir, get_installed_version
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
import optparse # noqa: F401
|
||||
from typing import Any, Dict # noqa: F401
|
||||
from pipenv.patched.notpip._internal.download import PipSession # noqa: F401
|
||||
|
||||
|
||||
SELFCHECK_DATE_FMT = "%Y-%m-%dT%H:%M:%SZ"
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SelfCheckState(object):
|
||||
def __init__(self, cache_dir):
|
||||
# type: (str) -> None
|
||||
self.state = {} # type: Dict[str, Any]
|
||||
self.statefile_path = None
|
||||
|
||||
# Try to load the existing state
|
||||
if cache_dir:
|
||||
self.statefile_path = os.path.join(cache_dir, "selfcheck.json")
|
||||
try:
|
||||
with open(self.statefile_path) as statefile:
|
||||
self.state = json.load(statefile)[sys.prefix]
|
||||
except (IOError, ValueError, KeyError):
|
||||
# Explicitly suppressing exceptions, since we don't want to
|
||||
# error out if the cache file is invalid.
|
||||
pass
|
||||
|
||||
def save(self, pypi_version, current_time):
|
||||
# type: (str, datetime.datetime) -> None
|
||||
# If we do not have a path to cache in, don't bother saving.
|
||||
if not self.statefile_path:
|
||||
return
|
||||
|
||||
# Check to make sure that we own the directory
|
||||
if not check_path_owner(os.path.dirname(self.statefile_path)):
|
||||
return
|
||||
|
||||
# Now that we've ensured the directory is owned by this user, we'll go
|
||||
# ahead and make sure that all our directories are created.
|
||||
ensure_dir(os.path.dirname(self.statefile_path))
|
||||
|
||||
# Attempt to write out our version check file
|
||||
with lockfile.LockFile(self.statefile_path):
|
||||
if os.path.exists(self.statefile_path):
|
||||
with open(self.statefile_path) as statefile:
|
||||
state = json.load(statefile)
|
||||
else:
|
||||
state = {}
|
||||
|
||||
state[sys.prefix] = {
|
||||
"last_check": current_time.strftime(SELFCHECK_DATE_FMT),
|
||||
"pypi_version": pypi_version,
|
||||
}
|
||||
|
||||
with open(self.statefile_path, "w") as statefile:
|
||||
json.dump(state, statefile, sort_keys=True,
|
||||
separators=(",", ":"))
|
||||
|
||||
|
||||
def was_installed_by_pip(pkg):
|
||||
# type: (str) -> bool
|
||||
"""Checks whether pkg was installed by pip
|
||||
|
||||
This is used not to display the upgrade message when pip is in fact
|
||||
installed by system package manager, such as dnf on Fedora.
|
||||
"""
|
||||
try:
|
||||
dist = pkg_resources.get_distribution(pkg)
|
||||
return (dist.has_metadata('INSTALLER') and
|
||||
'pip' in dist.get_metadata_lines('INSTALLER'))
|
||||
except pkg_resources.DistributionNotFound:
|
||||
return False
|
||||
|
||||
|
||||
def pip_version_check(session, options):
|
||||
# type: (PipSession, optparse.Values) -> None
|
||||
"""Check for an update for pip.
|
||||
|
||||
Limit the frequency of checks to once per week. State is stored either in
|
||||
the active virtualenv or in the user's USER_CACHE_DIR keyed off the prefix
|
||||
of the pip script path.
|
||||
"""
|
||||
installed_version = get_installed_version("pip")
|
||||
if not installed_version:
|
||||
return
|
||||
|
||||
pip_version = packaging_version.parse(installed_version)
|
||||
pypi_version = None
|
||||
|
||||
try:
|
||||
state = SelfCheckState(cache_dir=options.cache_dir)
|
||||
|
||||
current_time = datetime.datetime.utcnow()
|
||||
# Determine if we need to refresh the state
|
||||
if "last_check" in state.state and "pypi_version" in state.state:
|
||||
last_check = datetime.datetime.strptime(
|
||||
state.state["last_check"],
|
||||
SELFCHECK_DATE_FMT
|
||||
)
|
||||
if (current_time - last_check).total_seconds() < 7 * 24 * 60 * 60:
|
||||
pypi_version = state.state["pypi_version"]
|
||||
|
||||
# Refresh the version if we need to or just see if we need to warn
|
||||
if pypi_version is None:
|
||||
# Lets use PackageFinder to see what the latest pip version is
|
||||
finder = PackageFinder(
|
||||
find_links=options.find_links,
|
||||
index_urls=[options.index_url] + options.extra_index_urls,
|
||||
allow_all_prereleases=False, # Explicitly set to False
|
||||
trusted_hosts=options.trusted_hosts,
|
||||
session=session,
|
||||
)
|
||||
all_candidates = finder.find_all_candidates("pip")
|
||||
if not all_candidates:
|
||||
return
|
||||
pypi_version = str(
|
||||
max(all_candidates, key=lambda c: c.version).version
|
||||
)
|
||||
|
||||
# save that we've performed a check
|
||||
state.save(pypi_version, current_time)
|
||||
|
||||
remote_version = packaging_version.parse(pypi_version)
|
||||
|
||||
# Determine if our pypi_version is older
|
||||
if (pip_version < remote_version and
|
||||
pip_version.base_version != remote_version.base_version and
|
||||
was_installed_by_pip('pip')):
|
||||
# Advise "python -m pip" on Windows to avoid issues
|
||||
# with overwriting pip.exe.
|
||||
if WINDOWS:
|
||||
pip_cmd = "python -m pip"
|
||||
else:
|
||||
pip_cmd = "pip"
|
||||
logger.warning(
|
||||
"You are using pip version %s, however version %s is "
|
||||
"available.\nYou should consider upgrading via the "
|
||||
"'%s install --upgrade pip' command.",
|
||||
pip_version, pypi_version, pip_cmd
|
||||
)
|
||||
except Exception:
|
||||
logger.debug(
|
||||
"There was an error checking the latest version of pip",
|
||||
exc_info=True,
|
||||
)
|
||||
@@ -7,75 +7,83 @@ from email.parser import FeedParser
|
||||
from pipenv.patched.notpip._vendor import pkg_resources
|
||||
from pipenv.patched.notpip._vendor.packaging import specifiers, version
|
||||
|
||||
from pipenv.patched.notpip._internal import exceptions
|
||||
from pipenv.patched.notpip._internal.exceptions import NoneMetadataError
|
||||
from pipenv.patched.notpip._internal.utils.misc import display_path
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Optional # noqa: F401
|
||||
from email.message import Message # noqa: F401
|
||||
from pipenv.patched.notpip._vendor.pkg_resources import Distribution # noqa: F401
|
||||
from typing import Optional, Tuple
|
||||
from email.message import Message
|
||||
from pipenv.patched.notpip._vendor.pkg_resources import Distribution
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def check_requires_python(requires_python):
|
||||
# type: (Optional[str]) -> bool
|
||||
def check_requires_python(requires_python, version_info):
|
||||
# type: (Optional[str], Tuple[int, ...]) -> bool
|
||||
"""
|
||||
Check if the python version in use match the `requires_python` specifier.
|
||||
Check if the given Python version matches a "Requires-Python" specifier.
|
||||
|
||||
Returns `True` if the version of python in use matches the requirement.
|
||||
Returns `False` if the version of python in use does not matches the
|
||||
requirement.
|
||||
:param version_info: A 3-tuple of ints representing a Python
|
||||
major-minor-micro version to check (e.g. `sys.version_info[:3]`).
|
||||
|
||||
Raises an InvalidSpecifier if `requires_python` have an invalid format.
|
||||
:return: `True` if the given Python version satisfies the requirement.
|
||||
Otherwise, return `False`.
|
||||
|
||||
:raises InvalidSpecifier: If `requires_python` has an invalid format.
|
||||
"""
|
||||
if requires_python is None:
|
||||
# The package provides no information
|
||||
return True
|
||||
requires_python_specifier = specifiers.SpecifierSet(requires_python)
|
||||
|
||||
# We only use major.minor.micro
|
||||
python_version = version.parse('{0}.{1}.{2}'.format(*sys.version_info[:3]))
|
||||
return python_version in requires_python_specifier
|
||||
|
||||
|
||||
def get_metadata(dist):
|
||||
# type: (Distribution) -> Message
|
||||
"""
|
||||
:raises NoneMetadataError: if the distribution reports `has_metadata()`
|
||||
True but `get_metadata()` returns None.
|
||||
"""
|
||||
metadata_name = 'METADATA'
|
||||
if (isinstance(dist, pkg_resources.DistInfoDistribution) and
|
||||
dist.has_metadata('METADATA')):
|
||||
metadata = dist.get_metadata('METADATA')
|
||||
dist.has_metadata(metadata_name)):
|
||||
metadata = dist.get_metadata(metadata_name)
|
||||
elif dist.has_metadata('PKG-INFO'):
|
||||
metadata = dist.get_metadata('PKG-INFO')
|
||||
metadata_name = 'PKG-INFO'
|
||||
metadata = dist.get_metadata(metadata_name)
|
||||
else:
|
||||
logger.warning("No metadata found in %s", display_path(dist.location))
|
||||
metadata = ''
|
||||
|
||||
if metadata is None:
|
||||
raise NoneMetadataError(dist, metadata_name)
|
||||
|
||||
feed_parser = FeedParser()
|
||||
# The following line errors out if with a "NoneType" TypeError if
|
||||
# passed metadata=None.
|
||||
feed_parser.feed(metadata)
|
||||
return feed_parser.close()
|
||||
|
||||
|
||||
def check_dist_requires_python(dist, absorb=False):
|
||||
def get_requires_python(dist):
|
||||
# type: (pkg_resources.Distribution) -> Optional[str]
|
||||
"""
|
||||
Return the "Requires-Python" metadata for a distribution, or None
|
||||
if not present.
|
||||
"""
|
||||
pkg_info_dict = get_metadata(dist)
|
||||
requires_python = pkg_info_dict.get('Requires-Python')
|
||||
if absorb:
|
||||
return requires_python
|
||||
try:
|
||||
if not check_requires_python(requires_python):
|
||||
raise exceptions.UnsupportedPythonVersion(
|
||||
"%s requires Python '%s' but the running Python is %s" % (
|
||||
dist.project_name,
|
||||
requires_python,
|
||||
'.'.join(map(str, sys.version_info[:3])),)
|
||||
)
|
||||
except specifiers.InvalidSpecifier as e:
|
||||
logger.warning(
|
||||
"Package %s has an invalid Requires-Python entry %s - %s",
|
||||
dist.project_name, requires_python, e,
|
||||
)
|
||||
return
|
||||
|
||||
if requires_python is not None:
|
||||
# Convert to a str to satisfy the type checker, since requires_python
|
||||
# can be a Header object.
|
||||
requires_python = str(requires_python)
|
||||
|
||||
return requires_python
|
||||
|
||||
|
||||
def get_installer(dist):
|
||||
|
||||
@@ -1,8 +1,49 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import List, Sequence
|
||||
|
||||
# Shim to wrap setup.py invocation with setuptools
|
||||
SETUPTOOLS_SHIM = (
|
||||
"import setuptools, tokenize;__file__=%r;"
|
||||
#
|
||||
# We set sys.argv[0] to the path to the underlying setup.py file so
|
||||
# setuptools / distutils don't take the path to the setup.py to be "-c" when
|
||||
# invoking via the shim. This avoids e.g. the following manifest_maker
|
||||
# warning: "warning: manifest_maker: standard file '-c' not found".
|
||||
_SETUPTOOLS_SHIM = (
|
||||
"import sys, setuptools, tokenize; sys.argv[0] = {0!r}; __file__={0!r};"
|
||||
"f=getattr(tokenize, 'open', open)(__file__);"
|
||||
"code=f.read().replace('\\r\\n', '\\n');"
|
||||
"f.close();"
|
||||
"exec(compile(code, __file__, 'exec'))"
|
||||
)
|
||||
|
||||
|
||||
def make_setuptools_shim_args(
|
||||
setup_py_path, # type: str
|
||||
global_options=None, # type: Sequence[str]
|
||||
no_user_config=False, # type: bool
|
||||
unbuffered_output=False # type: bool
|
||||
):
|
||||
# type: (...) -> List[str]
|
||||
"""
|
||||
Get setuptools command arguments with shim wrapped setup file invocation.
|
||||
|
||||
:param setup_py_path: The path to setup.py to be wrapped.
|
||||
:param global_options: Additional global options.
|
||||
:param no_user_config: If True, disables personal user configuration.
|
||||
:param unbuffered_output: If True, adds the unbuffered switch to the
|
||||
argument list.
|
||||
"""
|
||||
sys_executable = os.environ.get('PIP_PYTHON_PATH', sys.executable)
|
||||
args = [sys_executable]
|
||||
if unbuffered_output:
|
||||
args.append('-u')
|
||||
args.extend(['-c', _SETUPTOOLS_SHIM.format(setup_py_path)])
|
||||
if global_options:
|
||||
args.extend(global_options)
|
||||
if no_user_config:
|
||||
args.append('--no-user-cfg')
|
||||
return args
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user