first group of challenges
This commit is contained in:
29
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
29
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="PyPep8Inspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoredErrors">
|
||||||
|
<list>
|
||||||
|
<option value="W29" />
|
||||||
|
<option value="E501" />
|
||||||
|
<option value="W29" />
|
||||||
|
<option value="E501" />
|
||||||
|
<option value="W29" />
|
||||||
|
<option value="E501" />
|
||||||
|
<option value="W29" />
|
||||||
|
<option value="E501" />
|
||||||
|
<option value="W29" />
|
||||||
|
<option value="E501" />
|
||||||
|
<option value="W29" />
|
||||||
|
<option value="E501" />
|
||||||
|
<option value="W29" />
|
||||||
|
<option value="E501" />
|
||||||
|
<option value="W29" />
|
||||||
|
<option value="E501" />
|
||||||
|
<option value="W29" />
|
||||||
|
<option value="E501" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
4
.idea/misc.xml
generated
Normal file
4
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6 (paython-by-learn)" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/paython-by-learn.iml" filepath="$PROJECT_DIR$/.idea/paython-by-learn.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/other.xml
generated
Normal file
6
.idea/other.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="PySciProjectComponent">
|
||||||
|
<option name="PY_SCI_VIEW" value="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
16
.idea/paython-by-learn.iml
generated
Normal file
16
.idea/paython-by-learn.iml
generated
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
<component name="PyDocumentationSettings">
|
||||||
|
<option name="renderExternalDocumentation" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="TestRunnerService">
|
||||||
|
<option name="PROJECT_TEST_RUNNER" value="Unittests" />
|
||||||
|
</component>
|
||||||
|
</module>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
323
.idea/workspace.xml
generated
Normal file
323
.idea/workspace.xml
generated
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="7106b959-52c5-4d26-9f5a-8703a78c8c29" name="Default Changelist" comment="" />
|
||||||
|
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="FileEditorManager">
|
||||||
|
<leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
|
||||||
|
<file pinned="false" current-in-tab="false">
|
||||||
|
<entry file="file://$PROJECT_DIR$/challenges35-44/challenge-041.py">
|
||||||
|
<provider selected="true" editor-type-id="text-editor" />
|
||||||
|
</entry>
|
||||||
|
</file>
|
||||||
|
<file pinned="false" current-in-tab="false">
|
||||||
|
<entry file="file://$PROJECT_DIR$/challenges35-44/challenge-040.py">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="15">
|
||||||
|
<caret line="1" column="31" selection-start-line="1" selection-start-column="31" selection-end-line="1" selection-end-column="31" />
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
</file>
|
||||||
|
<file pinned="false" current-in-tab="true">
|
||||||
|
<entry file="file://$PROJECT_DIR$/challenges35-44/challenge-042.py">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="150">
|
||||||
|
<caret line="10" column="15" selection-start-line="10" selection-start-column="15" selection-end-line="10" selection-end-column="15" />
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
</file>
|
||||||
|
</leaf>
|
||||||
|
</component>
|
||||||
|
<component name="FileTemplateManagerImpl">
|
||||||
|
<option name="RECENT_TEMPLATES">
|
||||||
|
<list>
|
||||||
|
<option value="Python Script" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="IdeDocumentHistory">
|
||||||
|
<option name="CHANGED_PATHS">
|
||||||
|
<list>
|
||||||
|
<option value="$PROJECT_DIR$/challenges35-44/challenge-040.py" />
|
||||||
|
<option value="$PROJECT_DIR$/challenges35-44/challenge-042.py" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectFrameBounds" extendedState="6">
|
||||||
|
<option name="x" value="2335" />
|
||||||
|
<option name="y" value="-4" />
|
||||||
|
<option name="width" value="934" />
|
||||||
|
<option name="height" value="1084" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectId" id="1O5wfQ7DfSfe4iwhiOQ9ZxAmGlm" />
|
||||||
|
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
|
||||||
|
<component name="ProjectView">
|
||||||
|
<navigator proportions="" version="1">
|
||||||
|
<foldersAlwaysOnTop value="true" />
|
||||||
|
</navigator>
|
||||||
|
<panes>
|
||||||
|
<pane id="Course" />
|
||||||
|
<pane id="ProjectPane">
|
||||||
|
<subPane>
|
||||||
|
<expand>
|
||||||
|
<path>
|
||||||
|
<item name="paython-by-learn" type="b2602c69:ProjectViewProjectNode" />
|
||||||
|
<item name="paython-by-learn" type="462c0819:PsiDirectoryNode" />
|
||||||
|
</path>
|
||||||
|
<path>
|
||||||
|
<item name="paython-by-learn" type="b2602c69:ProjectViewProjectNode" />
|
||||||
|
<item name="paython-by-learn" type="462c0819:PsiDirectoryNode" />
|
||||||
|
<item name="challenges35-44" type="462c0819:PsiDirectoryNode" />
|
||||||
|
</path>
|
||||||
|
</expand>
|
||||||
|
<select />
|
||||||
|
</subPane>
|
||||||
|
</pane>
|
||||||
|
</panes>
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent">
|
||||||
|
<property name="SHARE_PROJECT_CONFIGURATION_FILES" value="true" />
|
||||||
|
<property name="WebServerToolWindowFactoryState" value="false" />
|
||||||
|
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
|
||||||
|
<property name="node.js.detected.package.eslint" value="true" />
|
||||||
|
<property name="node.js.detected.package.tslint" value="true" />
|
||||||
|
<property name="node.js.path.for.package.eslint" value="project" />
|
||||||
|
<property name="node.js.path.for.package.tslint" value="project" />
|
||||||
|
<property name="node.js.selected.package.eslint" value="(autodetect)" />
|
||||||
|
<property name="node.js.selected.package.tslint" value="(autodetect)" />
|
||||||
|
</component>
|
||||||
|
<component name="RecentsManager">
|
||||||
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
|
<recent name="$PROJECT_DIR$/challenges1-11" />
|
||||||
|
</key>
|
||||||
|
<key name="CopyFile.RECENT_KEYS">
|
||||||
|
<recent name="$PROJECT_DIR$" />
|
||||||
|
</key>
|
||||||
|
</component>
|
||||||
|
<component name="RunDashboard">
|
||||||
|
<option name="ruleStates">
|
||||||
|
<list>
|
||||||
|
<RuleState>
|
||||||
|
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
|
||||||
|
</RuleState>
|
||||||
|
<RuleState>
|
||||||
|
<option name="name" value="StatusDashboardGroupingRule" />
|
||||||
|
</RuleState>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="RunManager" selected="Python.challenge-042">
|
||||||
|
<configuration default="true" type="PythonConfigurationType" factoryName="Python">
|
||||||
|
<module name="paython-by-learn" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
|
<option name="IS_MODULE_SDK" value="false" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||||
|
<option name="SCRIPT_NAME" value="" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="SHOW_COMMAND_LINE" value="true" />
|
||||||
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
<option name="MODULE_MODE" value="false" />
|
||||||
|
<option name="REDIRECT_INPUT" value="false" />
|
||||||
|
<option name="INPUT_FILE" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<configuration name="challenge-038" type="PythonConfigurationType" factoryName="Python" temporary="true">
|
||||||
|
<module name="paython-by-learn" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/challenges35-44" />
|
||||||
|
<option name="IS_MODULE_SDK" value="true" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||||
|
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/challenges35-44/challenge-038.py" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||||
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
<option name="MODULE_MODE" value="false" />
|
||||||
|
<option name="REDIRECT_INPUT" value="false" />
|
||||||
|
<option name="INPUT_FILE" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<configuration name="challenge-039" type="PythonConfigurationType" factoryName="Python" temporary="true">
|
||||||
|
<module name="paython-by-learn" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/challenges35-44" />
|
||||||
|
<option name="IS_MODULE_SDK" value="true" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||||
|
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/challenges35-44/challenge-039.py" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||||
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
<option name="MODULE_MODE" value="false" />
|
||||||
|
<option name="REDIRECT_INPUT" value="false" />
|
||||||
|
<option name="INPUT_FILE" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<configuration name="challenge-040 (1)" type="PythonConfigurationType" factoryName="Python" temporary="true">
|
||||||
|
<module name="paython-by-learn" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/challenges35-44" />
|
||||||
|
<option name="IS_MODULE_SDK" value="true" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||||
|
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/challenges35-44/challenge-040.py" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="SHOW_COMMAND_LINE" value="true" />
|
||||||
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
<option name="MODULE_MODE" value="false" />
|
||||||
|
<option name="REDIRECT_INPUT" value="false" />
|
||||||
|
<option name="INPUT_FILE" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<configuration name="challenge-040" type="PythonConfigurationType" factoryName="Python" temporary="true">
|
||||||
|
<module name="paython-by-learn" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/challenges35-44" />
|
||||||
|
<option name="IS_MODULE_SDK" value="true" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||||
|
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/challenges35-44/challenge-041.py" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="SHOW_COMMAND_LINE" value="true" />
|
||||||
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
<option name="MODULE_MODE" value="false" />
|
||||||
|
<option name="REDIRECT_INPUT" value="false" />
|
||||||
|
<option name="INPUT_FILE" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<configuration name="challenge-042" type="PythonConfigurationType" factoryName="Python" temporary="true">
|
||||||
|
<module name="paython-by-learn" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/challenges35-44" />
|
||||||
|
<option name="IS_MODULE_SDK" value="true" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||||
|
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/challenges35-44/challenge-042.py" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="SHOW_COMMAND_LINE" value="true" />
|
||||||
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
<option name="MODULE_MODE" value="false" />
|
||||||
|
<option name="REDIRECT_INPUT" value="false" />
|
||||||
|
<option name="INPUT_FILE" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<recent_temporary>
|
||||||
|
<list>
|
||||||
|
<item itemvalue="Python.challenge-042" />
|
||||||
|
<item itemvalue="Python.challenge-040 (1)" />
|
||||||
|
<item itemvalue="Python.challenge-040" />
|
||||||
|
<item itemvalue="Python.challenge-039" />
|
||||||
|
<item itemvalue="Python.challenge-038" />
|
||||||
|
</list>
|
||||||
|
</recent_temporary>
|
||||||
|
</component>
|
||||||
|
<component name="SvnConfiguration">
|
||||||
|
<configuration />
|
||||||
|
</component>
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="7106b959-52c5-4d26-9f5a-8703a78c8c29" name="Default Changelist" comment="" />
|
||||||
|
<created>1563280868040</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1563280868040</updated>
|
||||||
|
<workItem from="1563280871705" duration="840000" />
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="ToolWindowManager">
|
||||||
|
<frame x="2906" y="24" width="934" height="1056" extended-state="4" />
|
||||||
|
<layout>
|
||||||
|
<window_info content_ui="combo" id="Project" order="0" visible="true" weight="0.52030736" />
|
||||||
|
<window_info id="Structure" order="1" weight="0.25" />
|
||||||
|
<window_info anchor="bottom" id="Message" order="0" />
|
||||||
|
<window_info anchor="bottom" id="Find" order="1" />
|
||||||
|
<window_info anchor="bottom" id="Run" order="2" />
|
||||||
|
<window_info anchor="bottom" id="Debug" order="3" weight="0.4" />
|
||||||
|
<window_info anchor="bottom" id="Cvs" order="4" weight="0.25" />
|
||||||
|
<window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
|
||||||
|
<window_info anchor="bottom" id="TODO" order="6" />
|
||||||
|
<window_info anchor="bottom" id="Version Control" order="7" />
|
||||||
|
<window_info anchor="bottom" id="Educational.CheckDetails" order="8" />
|
||||||
|
<window_info active="true" anchor="bottom" id="Terminal" order="9" visible="true" weight="0.33158448" />
|
||||||
|
<window_info anchor="bottom" id="Event Log" order="10" side_tool="true" />
|
||||||
|
<window_info anchor="bottom" id="Python Console" order="11" weight="0.3295099" />
|
||||||
|
<window_info anchor="right" id="Commander" internal_type="SLIDING" order="0" type="SLIDING" weight="0.4" />
|
||||||
|
<window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
|
||||||
|
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
|
||||||
|
</layout>
|
||||||
|
</component>
|
||||||
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
<option name="version" value="1" />
|
||||||
|
</component>
|
||||||
|
<component name="com.intellij.coverage.CoverageDataManagerImpl">
|
||||||
|
<SUITE FILE_PATH="coverage/paython_by_learn$challenge_039.coverage" NAME="challenge-039 Coverage Results" MODIFIED="1563282890417" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/challenges35-44" />
|
||||||
|
</component>
|
||||||
|
<component name="editorHistoryManager">
|
||||||
|
<entry file="file://$PROJECT_DIR$/challenges35-44/challenge-041.py">
|
||||||
|
<provider selected="true" editor-type-id="text-editor" />
|
||||||
|
</entry>
|
||||||
|
<entry file="file://$PROJECT_DIR$/challenges35-44/challenge-040.py">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="15">
|
||||||
|
<caret line="1" column="31" selection-start-line="1" selection-start-column="31" selection-end-line="1" selection-end-column="31" />
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
<entry file="file://$PROJECT_DIR$/challenges35-44/challenge-042.py">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="150">
|
||||||
|
<caret line="10" column="15" selection-start-line="10" selection-start-column="15" selection-end-line="10" selection-end-column="15" />
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
</component>
|
||||||
|
</project>
|
3
challenges1-11/challenage-001.py
Normal file
3
challenges1-11/challenage-001.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
print('enter your name')
|
||||||
|
name = input("name : ")
|
||||||
|
print('your name is {0}'.format(name))
|
5
challenges1-11/challenage-002.py
Normal file
5
challenges1-11/challenage-002.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
print('enter your name')
|
||||||
|
name = input("name : ")
|
||||||
|
print('enter your surname')
|
||||||
|
surname = input("surname : ")
|
||||||
|
print('your name is {0} and surname is {1}'.format(name, surname))
|
1
challenges1-11/challenage-003.py
Normal file
1
challenges1-11/challenage-003.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
print('What do you call a bear with no teeth? \nA gummy bear!')
|
3
challenges1-11/challenage-004.py
Normal file
3
challenges1-11/challenage-004.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
number_1 = int(input('Calculate two numbers \n please, enter 1st. number : '))
|
||||||
|
number_2 = int(input('please, enter 2st. number : '))
|
||||||
|
print('the result is {0}'.format(number_1+number_2))
|
4
challenges1-11/challenage-005.py
Normal file
4
challenges1-11/challenage-005.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
number_1 = int(input('enter 1st number to calculate : '))
|
||||||
|
number_2 = int(input('enter 2nd number to calculate : '))
|
||||||
|
number_3 = int(input('enter 3rd number to multiply result by : '))
|
||||||
|
print('the answer is {0}'.format((number_2+number_1)*number_3))
|
0
challenges1-11/challenage-006.py
Normal file
0
challenges1-11/challenage-006.py
Normal file
3
challenges1-11/challenage-007.py
Normal file
3
challenges1-11/challenage-007.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
name = input("enter your name : ")
|
||||||
|
age = int(input("enter your age : "))
|
||||||
|
print('{0} next birthday you will be {1}'.format(name, age+1))
|
3
challenges1-11/challenage-008.py
Normal file
3
challenges1-11/challenage-008.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
bill_price = int(input('Please enter bill total price : '))
|
||||||
|
number_of_diners = int(input('Please enter diners number : '))
|
||||||
|
print('the price for each person is {0}'.format(round(bill_price/number_of_diners,2)))
|
5
challenges1-11/challenage-009.py
Normal file
5
challenges1-11/challenage-009.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
days = int(input('Enter the number of days : '))
|
||||||
|
hours = days*24
|
||||||
|
minutes = hours*60
|
||||||
|
seconds = minutes * 60
|
||||||
|
print('In {0} days there are {1} hours , {2} minutes and {3} seconds'.format(days, hours, minutes, seconds))
|
2
challenges1-11/challenage-010.py
Normal file
2
challenges1-11/challenage-010.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
weight = float(input('Enter weight to convert it to pounds : '))
|
||||||
|
print('{0} kg = {1} pounds'.format(weight, (weight*2.204)))
|
4
challenges1-11/challenage-011.py
Normal file
4
challenges1-11/challenage-011.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
large = int(input('enter number over than 100 : '))
|
||||||
|
small = int(input('enter number smaller than 10 : '))
|
||||||
|
answer = large//small
|
||||||
|
print('{0} goes into {1} for {2} times'.format(small, large, answer))
|
6
challenges12-19/challenge-012.py
Normal file
6
challenges12-19/challenge-012.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
number_1 = int(input('Enter 1st number : '))
|
||||||
|
number_2 = int(input('Enter 2nd number : '))
|
||||||
|
if number_1 > number_2:
|
||||||
|
print('{0} smaller than {1}'.format(number_2, number_1))
|
||||||
|
else:
|
||||||
|
print('{0} smaller than {1}'.format(number_1, number_2))
|
5
challenges12-19/challenge-013.py
Normal file
5
challenges12-19/challenge-013.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
number = int(input('Enter number under 20 : '))
|
||||||
|
if number < 20:
|
||||||
|
print('Thank you')
|
||||||
|
else:
|
||||||
|
print('Too high')
|
5
challenges12-19/challenge-014.py
Normal file
5
challenges12-19/challenge-014.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
number = int(input('Enter number between 10 to 20 : '))
|
||||||
|
if 10 < number < 20:
|
||||||
|
print('Thank you')
|
||||||
|
else:
|
||||||
|
print('Incorrect answer')
|
9
challenges12-19/challenge-015.py
Normal file
9
challenges12-19/challenge-015.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
color = str(input('Enter you favourite colour : '))
|
||||||
|
if color == 'red':
|
||||||
|
print('I like red too')
|
||||||
|
elif color == 'RED':
|
||||||
|
print('I like red too')
|
||||||
|
elif color == 'Red':
|
||||||
|
print('I like red too')
|
||||||
|
else:
|
||||||
|
print('I don\'t like {0}, I prefer red'.format(color))
|
11
challenges12-19/challenge-016.py
Normal file
11
challenges12-19/challenge-016.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
answer = str(input('is it raining? yes|no : '))
|
||||||
|
answer = answer.lower()
|
||||||
|
if answer == 'yes':
|
||||||
|
windy = str(input('is it windy! yes|no : '))
|
||||||
|
windy = windy.lower()
|
||||||
|
if windy == 'yes':
|
||||||
|
print('It is too windy for umbrella')
|
||||||
|
else:
|
||||||
|
print('Take an umbrella')
|
||||||
|
else:
|
||||||
|
print('Enjoy your day')
|
9
challenges12-19/challenge-017.py
Normal file
9
challenges12-19/challenge-017.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
age = int(input('Please enter your age : '))
|
||||||
|
if age >= 18:
|
||||||
|
print('You can vote')
|
||||||
|
elif age == 17:
|
||||||
|
print('You can learn to drive')
|
||||||
|
elif age == 16:
|
||||||
|
print('You can buy a lottery ticket')
|
||||||
|
else:
|
||||||
|
print('You can go Trick-or-Treating')
|
7
challenges12-19/challenge-018.py
Normal file
7
challenges12-19/challenge-018.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
number = int(input('Enter number : '))
|
||||||
|
if number < 10:
|
||||||
|
print('too low')
|
||||||
|
elif 10 <= number < 20:
|
||||||
|
print('Correct')
|
||||||
|
else:
|
||||||
|
print('Too high')
|
9
challenges12-19/challenge-019.py
Normal file
9
challenges12-19/challenge-019.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
number = int(input('Enter one of these numbers 1,2,3 : '))
|
||||||
|
if number == 1:
|
||||||
|
print('Thank you')
|
||||||
|
elif number == 2:
|
||||||
|
print('Well done')
|
||||||
|
elif number == 3:
|
||||||
|
print('Correct')
|
||||||
|
else:
|
||||||
|
print('Undefiend number')
|
2
challenges20-26/challenge-020.py
Normal file
2
challenges20-26/challenge-020.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
name = str(input('enter your name to check length : '))
|
||||||
|
print('{0} name length is {1}'.format(name, len(name)))
|
4
challenges20-26/challenge-021.py
Normal file
4
challenges20-26/challenge-021.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
first_name = str(input('Enter your first name : '))
|
||||||
|
surname = str(input('Enter your surname : '))
|
||||||
|
compact = first_name + ' ' + surname
|
||||||
|
print('{0} your name length is {1}'.format(compact, len(compact)))
|
4
challenges20-26/challenge-022.py
Normal file
4
challenges20-26/challenge-022.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
first_name = str(input('Enter your first name in lower-case : '))
|
||||||
|
surname = str(input('Enter your surname in lower-case : '))
|
||||||
|
compact = first_name.title() + ' ' + surname.title()
|
||||||
|
print(compact)
|
4
challenges20-26/challenge-023.py
Normal file
4
challenges20-26/challenge-023.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
rhyme = str(input('Enter the 1st. line of nursery rhyme : '))
|
||||||
|
start = int(input('Enter start number for display : '))
|
||||||
|
end = int(input('Enter end number for display : '))
|
||||||
|
print('this is the 1st. line \n {0} \n and this is truncated {1}'.format(rhyme, rhyme[start:end]))
|
2
challenges20-26/challenge-024.py
Normal file
2
challenges20-26/challenge-024.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
any = str(input('Type any thing to display in UPPER case : '))
|
||||||
|
print(any.upper())
|
7
challenges20-26/challenge-025.py
Normal file
7
challenges20-26/challenge-025.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
first_name = str(input('Enter your first name : '))
|
||||||
|
if len(first_name) < 5:
|
||||||
|
surname = str(input('Enter your surname : '))
|
||||||
|
compact = first_name.upper() + surname.upper()
|
||||||
|
print(compact)
|
||||||
|
else:
|
||||||
|
print(first_name.lower())
|
9
challenges20-26/challenge-026.py
Normal file
9
challenges20-26/challenge-026.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
word = str(input('Please enter a word : '))
|
||||||
|
first = word[0]
|
||||||
|
length = len(word)
|
||||||
|
rest = word[1:length]
|
||||||
|
if first != 'a' and first != 'e' and first != 'i' and first != 'o' and first != 'u':
|
||||||
|
newword = rest + first + 'ay'
|
||||||
|
else:
|
||||||
|
newword = word+'ay'
|
||||||
|
print(newword)
|
2
challenges27-34/challenge-027.py
Normal file
2
challenges27-34/challenge-027.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
number = float(input('Enter number with lots of decimal number : '))
|
||||||
|
print(number*2)
|
3
challenges27-34/challenge-028.py
Normal file
3
challenges27-34/challenge-028.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
number = float(input('Enter number with lots of decimal number : '))
|
||||||
|
print(round(number*2, 2))
|
||||||
|
|
4
challenges27-34/challenge-029.py
Normal file
4
challenges27-34/challenge-029.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import math
|
||||||
|
|
||||||
|
number = int(input('Enter number over 500 : '))
|
||||||
|
print(round(math.sqrt(number)))
|
3
challenges27-34/challenge-030.py
Normal file
3
challenges27-34/challenge-030.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import math
|
||||||
|
|
||||||
|
print(round(math.pi, 5))
|
5
challenges27-34/challenge-031.py
Normal file
5
challenges27-34/challenge-031.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import math
|
||||||
|
|
||||||
|
radius = float(input('Enter circle radius : '))
|
||||||
|
area = math.pi * (radius**2)
|
||||||
|
print(area)
|
7
challenges27-34/challenge-032.py
Normal file
7
challenges27-34/challenge-032.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import math
|
||||||
|
|
||||||
|
radius = float(input('Enter cylinder radius : '))
|
||||||
|
depth = float(input('Enter cylinder depth : '))
|
||||||
|
area = math.pi * (radius**2)
|
||||||
|
volume = area * depth
|
||||||
|
print(round(volume, 3))
|
3
challenges27-34/challenge-033.py
Normal file
3
challenges27-34/challenge-033.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
number_1 = int(input('Enter 1st number : '))
|
||||||
|
number_2 = int(input('Enter 2nd number : '))
|
||||||
|
print('{0} divided by {1} is {2} with reminder {3}'.format(number_1, number_2, number_1//number_2, number_1%number_2))
|
15
challenges27-34/challenge-034.py
Normal file
15
challenges27-34/challenge-034.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import math
|
||||||
|
|
||||||
|
print('1) Square\n2) Triangle')
|
||||||
|
choose = int(input('Enter a number : '))
|
||||||
|
if choose == 1:
|
||||||
|
square = int(input('Enter square length side : '))
|
||||||
|
area = square*square
|
||||||
|
print('Square area is {0}'.format(area))
|
||||||
|
elif choose == 2:
|
||||||
|
base = int(input('Enter triangle base : '))
|
||||||
|
height = int(input('Enter triangle height : '))
|
||||||
|
area = (base/2)*height
|
||||||
|
print('Triangle area is {0}'.format(area))
|
||||||
|
else:
|
||||||
|
print('Wrong choose')
|
3
challenges35-44/challenge-035.py
Normal file
3
challenges35-44/challenge-035.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
name = str(input('Enter your name : '))
|
||||||
|
for i in name:
|
||||||
|
print(i)
|
4
challenges35-44/challenge-036.py
Normal file
4
challenges35-44/challenge-036.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
name = str(input('Enter your name : '))
|
||||||
|
number = int(input('Enter repeat number : '))
|
||||||
|
for i in range(0, number):
|
||||||
|
print(name)
|
3
challenges35-44/challenge-037.py
Normal file
3
challenges35-44/challenge-037.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
name = str(input('Enter your name : '))
|
||||||
|
for i in name:
|
||||||
|
print(i+'\n')
|
6
challenges35-44/challenge-038.py
Normal file
6
challenges35-44/challenge-038.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
name = str(input('Enter your name : '))
|
||||||
|
number = int(input('Enter repeat number : '))
|
||||||
|
for i in range(0, number):
|
||||||
|
for j in name:
|
||||||
|
print(j+'\n')
|
||||||
|
print(name)
|
4
challenges35-44/challenge-039.py
Normal file
4
challenges35-44/challenge-039.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
number = int(input('Enter number between 1 and 12 : '))
|
||||||
|
for i in range(1, 13):
|
||||||
|
result = i * number
|
||||||
|
print('{0} x {1} is {2}'.format(i, number, result))
|
3
challenges35-44/challenge-040.py
Normal file
3
challenges35-44/challenge-040.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
number = int(input('Enter a number below 50 : '))
|
||||||
|
for i in range(50, number-1, -1):
|
||||||
|
print(i)
|
7
challenges35-44/challenge-041.py
Normal file
7
challenges35-44/challenge-041.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
name = str(input('Enter your name : '))
|
||||||
|
number = int(input('Enter number : '))
|
||||||
|
if number < 10:
|
||||||
|
for i in range(0, number):
|
||||||
|
print(name)
|
||||||
|
else:
|
||||||
|
print('Too high')
|
11
challenges35-44/challenge-042.py
Normal file
11
challenges35-44/challenge-042.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
total = 0
|
||||||
|
sub_total = 0
|
||||||
|
for i in range(0, 5):
|
||||||
|
number = int(input('Enter a number : '))
|
||||||
|
sub_total = sub_total + number
|
||||||
|
choose = str(input('Would you like to add numbers to total? yes|no : '))
|
||||||
|
if choose.lower() == 'yes':
|
||||||
|
total = total + sub_total
|
||||||
|
print(total)
|
||||||
|
else:
|
||||||
|
print(total)
|
76
venv/bin/activate
Normal file
76
venv/bin/activate
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# This file must be used with "source bin/activate" *from bash*
|
||||||
|
# you cannot run it directly
|
||||||
|
|
||||||
|
deactivate () {
|
||||||
|
# reset old environment variables
|
||||||
|
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
||||||
|
PATH="${_OLD_VIRTUAL_PATH:-}"
|
||||||
|
export PATH
|
||||||
|
unset _OLD_VIRTUAL_PATH
|
||||||
|
fi
|
||||||
|
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
||||||
|
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
||||||
|
export PYTHONHOME
|
||||||
|
unset _OLD_VIRTUAL_PYTHONHOME
|
||||||
|
fi
|
||||||
|
|
||||||
|
# This should detect bash and zsh, which have a hash command that must
|
||||||
|
# be called to get it to forget past commands. Without forgetting
|
||||||
|
# past commands the $PATH changes we made may not be respected
|
||||||
|
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||||
|
hash -r
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
||||||
|
PS1="${_OLD_VIRTUAL_PS1:-}"
|
||||||
|
export PS1
|
||||||
|
unset _OLD_VIRTUAL_PS1
|
||||||
|
fi
|
||||||
|
|
||||||
|
unset VIRTUAL_ENV
|
||||||
|
if [ ! "$1" = "nondestructive" ] ; then
|
||||||
|
# Self destruct!
|
||||||
|
unset -f deactivate
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# unset irrelevant variables
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
VIRTUAL_ENV="/home/abd/PycharmProjects/paython-by-learn/venv"
|
||||||
|
export VIRTUAL_ENV
|
||||||
|
|
||||||
|
_OLD_VIRTUAL_PATH="$PATH"
|
||||||
|
PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||||
|
export PATH
|
||||||
|
|
||||||
|
# unset PYTHONHOME if set
|
||||||
|
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
||||||
|
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
||||||
|
if [ -n "${PYTHONHOME:-}" ] ; then
|
||||||
|
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
||||||
|
unset PYTHONHOME
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||||
|
_OLD_VIRTUAL_PS1="${PS1:-}"
|
||||||
|
if [ "x(venv) " != x ] ; then
|
||||||
|
PS1="(venv) ${PS1:-}"
|
||||||
|
else
|
||||||
|
if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
|
||||||
|
# special case for Aspen magic directories
|
||||||
|
# see http://www.zetadev.com/software/aspen/
|
||||||
|
PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1"
|
||||||
|
else
|
||||||
|
PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
export PS1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# This should detect bash and zsh, which have a hash command that must
|
||||||
|
# be called to get it to forget past commands. Without forgetting
|
||||||
|
# past commands the $PATH changes we made may not be respected
|
||||||
|
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||||
|
hash -r
|
||||||
|
fi
|
37
venv/bin/activate.csh
Normal file
37
venv/bin/activate.csh
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# This file must be used with "source bin/activate.csh" *from csh*.
|
||||||
|
# You cannot run it directly.
|
||||||
|
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
||||||
|
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
|
||||||
|
|
||||||
|
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate'
|
||||||
|
|
||||||
|
# Unset irrelevant variables.
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
setenv VIRTUAL_ENV "/home/abd/PycharmProjects/paython-by-learn/venv"
|
||||||
|
|
||||||
|
set _OLD_VIRTUAL_PATH="$PATH"
|
||||||
|
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
|
||||||
|
|
||||||
|
|
||||||
|
set _OLD_VIRTUAL_PROMPT="$prompt"
|
||||||
|
|
||||||
|
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
||||||
|
if ("venv" != "") then
|
||||||
|
set env_name = "venv"
|
||||||
|
else
|
||||||
|
if (`basename "VIRTUAL_ENV"` == "__") then
|
||||||
|
# special case for Aspen magic directories
|
||||||
|
# see http://www.zetadev.com/software/aspen/
|
||||||
|
set env_name = `basename \`dirname "$VIRTUAL_ENV"\``
|
||||||
|
else
|
||||||
|
set env_name = `basename "$VIRTUAL_ENV"`
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
set prompt = "[$env_name] $prompt"
|
||||||
|
unset env_name
|
||||||
|
endif
|
||||||
|
|
||||||
|
alias pydoc python -m pydoc
|
||||||
|
|
||||||
|
rehash
|
75
venv/bin/activate.fish
Normal file
75
venv/bin/activate.fish
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# This file must be used with ". bin/activate.fish" *from fish* (http://fishshell.org)
|
||||||
|
# you cannot run it directly
|
||||||
|
|
||||||
|
function deactivate -d "Exit virtualenv and return to normal shell environment"
|
||||||
|
# reset old environment variables
|
||||||
|
if test -n "$_OLD_VIRTUAL_PATH"
|
||||||
|
set -gx PATH $_OLD_VIRTUAL_PATH
|
||||||
|
set -e _OLD_VIRTUAL_PATH
|
||||||
|
end
|
||||||
|
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
||||||
|
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
|
||||||
|
set -e _OLD_VIRTUAL_PYTHONHOME
|
||||||
|
end
|
||||||
|
|
||||||
|
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
||||||
|
functions -e fish_prompt
|
||||||
|
set -e _OLD_FISH_PROMPT_OVERRIDE
|
||||||
|
functions -c _old_fish_prompt fish_prompt
|
||||||
|
functions -e _old_fish_prompt
|
||||||
|
end
|
||||||
|
|
||||||
|
set -e VIRTUAL_ENV
|
||||||
|
if test "$argv[1]" != "nondestructive"
|
||||||
|
# Self destruct!
|
||||||
|
functions -e deactivate
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# unset irrelevant variables
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
set -gx VIRTUAL_ENV "/home/abd/PycharmProjects/paython-by-learn/venv"
|
||||||
|
|
||||||
|
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||||
|
set -gx PATH "$VIRTUAL_ENV/bin" $PATH
|
||||||
|
|
||||||
|
# unset PYTHONHOME if set
|
||||||
|
if set -q PYTHONHOME
|
||||||
|
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
||||||
|
set -e PYTHONHOME
|
||||||
|
end
|
||||||
|
|
||||||
|
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||||
|
# fish uses a function instead of an env var to generate the prompt.
|
||||||
|
|
||||||
|
# save the current fish_prompt function as the function _old_fish_prompt
|
||||||
|
functions -c fish_prompt _old_fish_prompt
|
||||||
|
|
||||||
|
# with the original prompt function renamed, we can override with our own.
|
||||||
|
function fish_prompt
|
||||||
|
# Save the return status of the last command
|
||||||
|
set -l old_status $status
|
||||||
|
|
||||||
|
# Prompt override?
|
||||||
|
if test -n "(venv) "
|
||||||
|
printf "%s%s" "(venv) " (set_color normal)
|
||||||
|
else
|
||||||
|
# ...Otherwise, prepend env
|
||||||
|
set -l _checkbase (basename "$VIRTUAL_ENV")
|
||||||
|
if test $_checkbase = "__"
|
||||||
|
# special case for Aspen magic directories
|
||||||
|
# see http://www.zetadev.com/software/aspen/
|
||||||
|
printf "%s[%s]%s " (set_color -b blue white) (basename (dirname "$VIRTUAL_ENV")) (set_color normal)
|
||||||
|
else
|
||||||
|
printf "%s(%s)%s" (set_color -b blue white) (basename "$VIRTUAL_ENV") (set_color normal)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Restore the return status of the previous command.
|
||||||
|
echo "exit $old_status" | .
|
||||||
|
_old_fish_prompt
|
||||||
|
end
|
||||||
|
|
||||||
|
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
||||||
|
end
|
12
venv/bin/easy_install
Executable file
12
venv/bin/easy_install
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/home/abd/PycharmProjects/paython-by-learn/venv/bin/python
|
||||||
|
# EASY-INSTALL-ENTRY-SCRIPT: 'setuptools==40.8.0','console_scripts','easy_install'
|
||||||
|
__requires__ = 'setuptools==40.8.0'
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pkg_resources import load_entry_point
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(
|
||||||
|
load_entry_point('setuptools==40.8.0', 'console_scripts', 'easy_install')()
|
||||||
|
)
|
12
venv/bin/easy_install-3.6
Executable file
12
venv/bin/easy_install-3.6
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/home/abd/PycharmProjects/paython-by-learn/venv/bin/python
|
||||||
|
# EASY-INSTALL-ENTRY-SCRIPT: 'setuptools==40.8.0','console_scripts','easy_install-3.6'
|
||||||
|
__requires__ = 'setuptools==40.8.0'
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pkg_resources import load_entry_point
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(
|
||||||
|
load_entry_point('setuptools==40.8.0', 'console_scripts', 'easy_install-3.6')()
|
||||||
|
)
|
12
venv/bin/pip
Executable file
12
venv/bin/pip
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/home/abd/PycharmProjects/paython-by-learn/venv/bin/python
|
||||||
|
# EASY-INSTALL-ENTRY-SCRIPT: 'pip==19.0.3','console_scripts','pip'
|
||||||
|
__requires__ = 'pip==19.0.3'
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pkg_resources import load_entry_point
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(
|
||||||
|
load_entry_point('pip==19.0.3', 'console_scripts', 'pip')()
|
||||||
|
)
|
12
venv/bin/pip3
Executable file
12
venv/bin/pip3
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/home/abd/PycharmProjects/paython-by-learn/venv/bin/python
|
||||||
|
# EASY-INSTALL-ENTRY-SCRIPT: 'pip==19.0.3','console_scripts','pip3'
|
||||||
|
__requires__ = 'pip==19.0.3'
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pkg_resources import load_entry_point
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(
|
||||||
|
load_entry_point('pip==19.0.3', 'console_scripts', 'pip3')()
|
||||||
|
)
|
12
venv/bin/pip3.6
Executable file
12
venv/bin/pip3.6
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/home/abd/PycharmProjects/paython-by-learn/venv/bin/python
|
||||||
|
# EASY-INSTALL-ENTRY-SCRIPT: 'pip==19.0.3','console_scripts','pip3.6'
|
||||||
|
__requires__ = 'pip==19.0.3'
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pkg_resources import load_entry_point
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(
|
||||||
|
load_entry_point('pip==19.0.3', 'console_scripts', 'pip3.6')()
|
||||||
|
)
|
BIN
venv/bin/python
Executable file
BIN
venv/bin/python
Executable file
Binary file not shown.
BIN
venv/bin/python3
Executable file
BIN
venv/bin/python3
Executable file
Binary file not shown.
BIN
venv/bin/python3.6
Executable file
BIN
venv/bin/python3.6
Executable file
Binary file not shown.
2
venv/lib/python3.6/site-packages/easy-install.pth
Normal file
2
venv/lib/python3.6/site-packages/easy-install.pth
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
./setuptools-40.8.0-py3.6.egg
|
||||||
|
./pip-19.0.3-py3.6.egg
|
@@ -0,0 +1,73 @@
|
|||||||
|
Metadata-Version: 1.2
|
||||||
|
Name: pip
|
||||||
|
Version: 19.0.3
|
||||||
|
Summary: The PyPA recommended tool for installing Python packages.
|
||||||
|
Home-page: https://pip.pypa.io/
|
||||||
|
Author: The pip developers
|
||||||
|
Author-email: pypa-dev@groups.google.com
|
||||||
|
License: MIT
|
||||||
|
Description: pip - The Python Package Installer
|
||||||
|
==================================
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/v/pip.svg
|
||||||
|
:target: https://pypi.org/project/pip/
|
||||||
|
|
||||||
|
.. image:: https://readthedocs.org/projects/pip/badge/?version=latest
|
||||||
|
:target: https://pip.pypa.io/en/latest
|
||||||
|
|
||||||
|
pip is the `package installer`_ for Python. You can use pip to install packages from the `Python Package Index`_ and other indexes.
|
||||||
|
|
||||||
|
Please take a look at our documentation for how to install and use pip:
|
||||||
|
|
||||||
|
* `Installation`_
|
||||||
|
* `Usage`_
|
||||||
|
* `Release notes`_
|
||||||
|
|
||||||
|
If you find bugs, need help, or want to talk to the developers please use our mailing lists or chat rooms:
|
||||||
|
|
||||||
|
* `Issue tracking`_
|
||||||
|
* `Discourse channel`_
|
||||||
|
* `User IRC`_
|
||||||
|
|
||||||
|
If you want to get involved head over to GitHub to get the source code and feel free to jump on the developer mailing lists and chat rooms:
|
||||||
|
|
||||||
|
* `GitHub page`_
|
||||||
|
* `Dev mailing list`_
|
||||||
|
* `Dev IRC`_
|
||||||
|
|
||||||
|
Code of Conduct
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Everyone interacting in the pip project's codebases, issue trackers, chat
|
||||||
|
rooms, and mailing lists is expected to follow the `PyPA Code of Conduct`_.
|
||||||
|
|
||||||
|
.. _package installer: https://packaging.python.org/en/latest/current/
|
||||||
|
.. _Python Package Index: https://pypi.org
|
||||||
|
.. _Installation: https://pip.pypa.io/en/stable/installing.html
|
||||||
|
.. _Usage: https://pip.pypa.io/en/stable/
|
||||||
|
.. _Release notes: https://pip.pypa.io/en/stable/news.html
|
||||||
|
.. _GitHub page: https://github.com/pypa/pip
|
||||||
|
.. _Issue tracking: https://github.com/pypa/pip/issues
|
||||||
|
.. _Discourse channel: https://discuss.python.org/c/packaging
|
||||||
|
.. _Dev mailing list: https://groups.google.com/forum/#!forum/pypa-dev
|
||||||
|
.. _User IRC: https://webchat.freenode.net/?channels=%23pypa
|
||||||
|
.. _Dev IRC: https://webchat.freenode.net/?channels=%23pypa-dev
|
||||||
|
.. _PyPA Code of Conduct: https://www.pypa.io/en/latest/code-of-conduct/
|
||||||
|
|
||||||
|
Keywords: distutils easy_install egg setuptools wheel virtualenv
|
||||||
|
Platform: UNKNOWN
|
||||||
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: License :: OSI Approved :: MIT License
|
||||||
|
Classifier: Topic :: Software Development :: Build Tools
|
||||||
|
Classifier: Programming Language :: Python
|
||||||
|
Classifier: Programming Language :: Python :: 2
|
||||||
|
Classifier: Programming Language :: Python :: 2.7
|
||||||
|
Classifier: Programming Language :: Python :: 3
|
||||||
|
Classifier: Programming Language :: Python :: 3.4
|
||||||
|
Classifier: Programming Language :: Python :: 3.5
|
||||||
|
Classifier: Programming Language :: Python :: 3.6
|
||||||
|
Classifier: Programming Language :: Python :: 3.7
|
||||||
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||||
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||||
|
Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*
|
@@ -0,0 +1,391 @@
|
|||||||
|
AUTHORS.txt
|
||||||
|
LICENSE.txt
|
||||||
|
MANIFEST.in
|
||||||
|
NEWS.rst
|
||||||
|
README.rst
|
||||||
|
pyproject.toml
|
||||||
|
setup.cfg
|
||||||
|
setup.py
|
||||||
|
docs/pip_sphinxext.py
|
||||||
|
docs/html/conf.py
|
||||||
|
docs/html/cookbook.rst
|
||||||
|
docs/html/index.rst
|
||||||
|
docs/html/installing.rst
|
||||||
|
docs/html/logic.rst
|
||||||
|
docs/html/news.rst
|
||||||
|
docs/html/quickstart.rst
|
||||||
|
docs/html/usage.rst
|
||||||
|
docs/html/user_guide.rst
|
||||||
|
docs/html/development/configuration.rst
|
||||||
|
docs/html/development/contributing.rst
|
||||||
|
docs/html/development/getting-started.rst
|
||||||
|
docs/html/development/index.rst
|
||||||
|
docs/html/development/release-process.rst
|
||||||
|
docs/html/development/vendoring-policy.rst
|
||||||
|
docs/html/reference/index.rst
|
||||||
|
docs/html/reference/pip.rst
|
||||||
|
docs/html/reference/pip_check.rst
|
||||||
|
docs/html/reference/pip_config.rst
|
||||||
|
docs/html/reference/pip_download.rst
|
||||||
|
docs/html/reference/pip_freeze.rst
|
||||||
|
docs/html/reference/pip_hash.rst
|
||||||
|
docs/html/reference/pip_install.rst
|
||||||
|
docs/html/reference/pip_list.rst
|
||||||
|
docs/html/reference/pip_search.rst
|
||||||
|
docs/html/reference/pip_show.rst
|
||||||
|
docs/html/reference/pip_uninstall.rst
|
||||||
|
docs/html/reference/pip_wheel.rst
|
||||||
|
docs/man/index.rst
|
||||||
|
docs/man/commands/check.rst
|
||||||
|
docs/man/commands/config.rst
|
||||||
|
docs/man/commands/download.rst
|
||||||
|
docs/man/commands/freeze.rst
|
||||||
|
docs/man/commands/hash.rst
|
||||||
|
docs/man/commands/help.rst
|
||||||
|
docs/man/commands/install.rst
|
||||||
|
docs/man/commands/list.rst
|
||||||
|
docs/man/commands/search.rst
|
||||||
|
docs/man/commands/show.rst
|
||||||
|
docs/man/commands/uninstall.rst
|
||||||
|
docs/man/commands/wheel.rst
|
||||||
|
src/pip/__init__.py
|
||||||
|
src/pip/__main__.py
|
||||||
|
src/pip.egg-info/PKG-INFO
|
||||||
|
src/pip.egg-info/SOURCES.txt
|
||||||
|
src/pip.egg-info/dependency_links.txt
|
||||||
|
src/pip.egg-info/entry_points.txt
|
||||||
|
src/pip.egg-info/not-zip-safe
|
||||||
|
src/pip.egg-info/top_level.txt
|
||||||
|
src/pip/_internal/__init__.py
|
||||||
|
src/pip/_internal/build_env.py
|
||||||
|
src/pip/_internal/cache.py
|
||||||
|
src/pip/_internal/configuration.py
|
||||||
|
src/pip/_internal/download.py
|
||||||
|
src/pip/_internal/exceptions.py
|
||||||
|
src/pip/_internal/index.py
|
||||||
|
src/pip/_internal/locations.py
|
||||||
|
src/pip/_internal/pep425tags.py
|
||||||
|
src/pip/_internal/pyproject.py
|
||||||
|
src/pip/_internal/resolve.py
|
||||||
|
src/pip/_internal/wheel.py
|
||||||
|
src/pip/_internal/cli/__init__.py
|
||||||
|
src/pip/_internal/cli/autocompletion.py
|
||||||
|
src/pip/_internal/cli/base_command.py
|
||||||
|
src/pip/_internal/cli/cmdoptions.py
|
||||||
|
src/pip/_internal/cli/main_parser.py
|
||||||
|
src/pip/_internal/cli/parser.py
|
||||||
|
src/pip/_internal/cli/status_codes.py
|
||||||
|
src/pip/_internal/commands/__init__.py
|
||||||
|
src/pip/_internal/commands/check.py
|
||||||
|
src/pip/_internal/commands/completion.py
|
||||||
|
src/pip/_internal/commands/configuration.py
|
||||||
|
src/pip/_internal/commands/download.py
|
||||||
|
src/pip/_internal/commands/freeze.py
|
||||||
|
src/pip/_internal/commands/hash.py
|
||||||
|
src/pip/_internal/commands/help.py
|
||||||
|
src/pip/_internal/commands/install.py
|
||||||
|
src/pip/_internal/commands/list.py
|
||||||
|
src/pip/_internal/commands/search.py
|
||||||
|
src/pip/_internal/commands/show.py
|
||||||
|
src/pip/_internal/commands/uninstall.py
|
||||||
|
src/pip/_internal/commands/wheel.py
|
||||||
|
src/pip/_internal/models/__init__.py
|
||||||
|
src/pip/_internal/models/candidate.py
|
||||||
|
src/pip/_internal/models/format_control.py
|
||||||
|
src/pip/_internal/models/index.py
|
||||||
|
src/pip/_internal/models/link.py
|
||||||
|
src/pip/_internal/operations/__init__.py
|
||||||
|
src/pip/_internal/operations/check.py
|
||||||
|
src/pip/_internal/operations/freeze.py
|
||||||
|
src/pip/_internal/operations/prepare.py
|
||||||
|
src/pip/_internal/req/__init__.py
|
||||||
|
src/pip/_internal/req/constructors.py
|
||||||
|
src/pip/_internal/req/req_file.py
|
||||||
|
src/pip/_internal/req/req_install.py
|
||||||
|
src/pip/_internal/req/req_set.py
|
||||||
|
src/pip/_internal/req/req_tracker.py
|
||||||
|
src/pip/_internal/req/req_uninstall.py
|
||||||
|
src/pip/_internal/utils/__init__.py
|
||||||
|
src/pip/_internal/utils/appdirs.py
|
||||||
|
src/pip/_internal/utils/compat.py
|
||||||
|
src/pip/_internal/utils/deprecation.py
|
||||||
|
src/pip/_internal/utils/encoding.py
|
||||||
|
src/pip/_internal/utils/filesystem.py
|
||||||
|
src/pip/_internal/utils/glibc.py
|
||||||
|
src/pip/_internal/utils/hashes.py
|
||||||
|
src/pip/_internal/utils/logging.py
|
||||||
|
src/pip/_internal/utils/misc.py
|
||||||
|
src/pip/_internal/utils/models.py
|
||||||
|
src/pip/_internal/utils/outdated.py
|
||||||
|
src/pip/_internal/utils/packaging.py
|
||||||
|
src/pip/_internal/utils/setuptools_build.py
|
||||||
|
src/pip/_internal/utils/temp_dir.py
|
||||||
|
src/pip/_internal/utils/typing.py
|
||||||
|
src/pip/_internal/utils/ui.py
|
||||||
|
src/pip/_internal/vcs/__init__.py
|
||||||
|
src/pip/_internal/vcs/bazaar.py
|
||||||
|
src/pip/_internal/vcs/git.py
|
||||||
|
src/pip/_internal/vcs/mercurial.py
|
||||||
|
src/pip/_internal/vcs/subversion.py
|
||||||
|
src/pip/_vendor/README.rst
|
||||||
|
src/pip/_vendor/__init__.py
|
||||||
|
src/pip/_vendor/appdirs.LICENSE.txt
|
||||||
|
src/pip/_vendor/appdirs.py
|
||||||
|
src/pip/_vendor/distro.LICENSE
|
||||||
|
src/pip/_vendor/distro.py
|
||||||
|
src/pip/_vendor/ipaddress.LICENSE
|
||||||
|
src/pip/_vendor/ipaddress.py
|
||||||
|
src/pip/_vendor/pyparsing.LICENSE
|
||||||
|
src/pip/_vendor/pyparsing.py
|
||||||
|
src/pip/_vendor/retrying.LICENSE
|
||||||
|
src/pip/_vendor/retrying.py
|
||||||
|
src/pip/_vendor/six.LICENSE
|
||||||
|
src/pip/_vendor/six.py
|
||||||
|
src/pip/_vendor/vendor.txt
|
||||||
|
src/pip/_vendor/cachecontrol/LICENSE.txt
|
||||||
|
src/pip/_vendor/cachecontrol/__init__.py
|
||||||
|
src/pip/_vendor/cachecontrol/_cmd.py
|
||||||
|
src/pip/_vendor/cachecontrol/adapter.py
|
||||||
|
src/pip/_vendor/cachecontrol/cache.py
|
||||||
|
src/pip/_vendor/cachecontrol/compat.py
|
||||||
|
src/pip/_vendor/cachecontrol/controller.py
|
||||||
|
src/pip/_vendor/cachecontrol/filewrapper.py
|
||||||
|
src/pip/_vendor/cachecontrol/heuristics.py
|
||||||
|
src/pip/_vendor/cachecontrol/serialize.py
|
||||||
|
src/pip/_vendor/cachecontrol/wrapper.py
|
||||||
|
src/pip/_vendor/cachecontrol/caches/__init__.py
|
||||||
|
src/pip/_vendor/cachecontrol/caches/file_cache.py
|
||||||
|
src/pip/_vendor/cachecontrol/caches/redis_cache.py
|
||||||
|
src/pip/_vendor/certifi/LICENSE
|
||||||
|
src/pip/_vendor/certifi/__init__.py
|
||||||
|
src/pip/_vendor/certifi/__main__.py
|
||||||
|
src/pip/_vendor/certifi/cacert.pem
|
||||||
|
src/pip/_vendor/certifi/core.py
|
||||||
|
src/pip/_vendor/chardet/LICENSE
|
||||||
|
src/pip/_vendor/chardet/__init__.py
|
||||||
|
src/pip/_vendor/chardet/big5freq.py
|
||||||
|
src/pip/_vendor/chardet/big5prober.py
|
||||||
|
src/pip/_vendor/chardet/chardistribution.py
|
||||||
|
src/pip/_vendor/chardet/charsetgroupprober.py
|
||||||
|
src/pip/_vendor/chardet/charsetprober.py
|
||||||
|
src/pip/_vendor/chardet/codingstatemachine.py
|
||||||
|
src/pip/_vendor/chardet/compat.py
|
||||||
|
src/pip/_vendor/chardet/cp949prober.py
|
||||||
|
src/pip/_vendor/chardet/enums.py
|
||||||
|
src/pip/_vendor/chardet/escprober.py
|
||||||
|
src/pip/_vendor/chardet/escsm.py
|
||||||
|
src/pip/_vendor/chardet/eucjpprober.py
|
||||||
|
src/pip/_vendor/chardet/euckrfreq.py
|
||||||
|
src/pip/_vendor/chardet/euckrprober.py
|
||||||
|
src/pip/_vendor/chardet/euctwfreq.py
|
||||||
|
src/pip/_vendor/chardet/euctwprober.py
|
||||||
|
src/pip/_vendor/chardet/gb2312freq.py
|
||||||
|
src/pip/_vendor/chardet/gb2312prober.py
|
||||||
|
src/pip/_vendor/chardet/hebrewprober.py
|
||||||
|
src/pip/_vendor/chardet/jisfreq.py
|
||||||
|
src/pip/_vendor/chardet/jpcntx.py
|
||||||
|
src/pip/_vendor/chardet/langbulgarianmodel.py
|
||||||
|
src/pip/_vendor/chardet/langcyrillicmodel.py
|
||||||
|
src/pip/_vendor/chardet/langgreekmodel.py
|
||||||
|
src/pip/_vendor/chardet/langhebrewmodel.py
|
||||||
|
src/pip/_vendor/chardet/langhungarianmodel.py
|
||||||
|
src/pip/_vendor/chardet/langthaimodel.py
|
||||||
|
src/pip/_vendor/chardet/langturkishmodel.py
|
||||||
|
src/pip/_vendor/chardet/latin1prober.py
|
||||||
|
src/pip/_vendor/chardet/mbcharsetprober.py
|
||||||
|
src/pip/_vendor/chardet/mbcsgroupprober.py
|
||||||
|
src/pip/_vendor/chardet/mbcssm.py
|
||||||
|
src/pip/_vendor/chardet/sbcharsetprober.py
|
||||||
|
src/pip/_vendor/chardet/sbcsgroupprober.py
|
||||||
|
src/pip/_vendor/chardet/sjisprober.py
|
||||||
|
src/pip/_vendor/chardet/universaldetector.py
|
||||||
|
src/pip/_vendor/chardet/utf8prober.py
|
||||||
|
src/pip/_vendor/chardet/version.py
|
||||||
|
src/pip/_vendor/chardet/cli/__init__.py
|
||||||
|
src/pip/_vendor/chardet/cli/chardetect.py
|
||||||
|
src/pip/_vendor/colorama/LICENSE.txt
|
||||||
|
src/pip/_vendor/colorama/__init__.py
|
||||||
|
src/pip/_vendor/colorama/ansi.py
|
||||||
|
src/pip/_vendor/colorama/ansitowin32.py
|
||||||
|
src/pip/_vendor/colorama/initialise.py
|
||||||
|
src/pip/_vendor/colorama/win32.py
|
||||||
|
src/pip/_vendor/colorama/winterm.py
|
||||||
|
src/pip/_vendor/distlib/LICENSE.txt
|
||||||
|
src/pip/_vendor/distlib/__init__.py
|
||||||
|
src/pip/_vendor/distlib/compat.py
|
||||||
|
src/pip/_vendor/distlib/database.py
|
||||||
|
src/pip/_vendor/distlib/index.py
|
||||||
|
src/pip/_vendor/distlib/locators.py
|
||||||
|
src/pip/_vendor/distlib/manifest.py
|
||||||
|
src/pip/_vendor/distlib/markers.py
|
||||||
|
src/pip/_vendor/distlib/metadata.py
|
||||||
|
src/pip/_vendor/distlib/resources.py
|
||||||
|
src/pip/_vendor/distlib/scripts.py
|
||||||
|
src/pip/_vendor/distlib/t32.exe
|
||||||
|
src/pip/_vendor/distlib/t64.exe
|
||||||
|
src/pip/_vendor/distlib/util.py
|
||||||
|
src/pip/_vendor/distlib/version.py
|
||||||
|
src/pip/_vendor/distlib/w32.exe
|
||||||
|
src/pip/_vendor/distlib/w64.exe
|
||||||
|
src/pip/_vendor/distlib/wheel.py
|
||||||
|
src/pip/_vendor/distlib/_backport/__init__.py
|
||||||
|
src/pip/_vendor/distlib/_backport/misc.py
|
||||||
|
src/pip/_vendor/distlib/_backport/shutil.py
|
||||||
|
src/pip/_vendor/distlib/_backport/sysconfig.cfg
|
||||||
|
src/pip/_vendor/distlib/_backport/sysconfig.py
|
||||||
|
src/pip/_vendor/distlib/_backport/tarfile.py
|
||||||
|
src/pip/_vendor/html5lib/LICENSE
|
||||||
|
src/pip/_vendor/html5lib/__init__.py
|
||||||
|
src/pip/_vendor/html5lib/_ihatexml.py
|
||||||
|
src/pip/_vendor/html5lib/_inputstream.py
|
||||||
|
src/pip/_vendor/html5lib/_tokenizer.py
|
||||||
|
src/pip/_vendor/html5lib/_utils.py
|
||||||
|
src/pip/_vendor/html5lib/constants.py
|
||||||
|
src/pip/_vendor/html5lib/html5parser.py
|
||||||
|
src/pip/_vendor/html5lib/serializer.py
|
||||||
|
src/pip/_vendor/html5lib/_trie/__init__.py
|
||||||
|
src/pip/_vendor/html5lib/_trie/_base.py
|
||||||
|
src/pip/_vendor/html5lib/_trie/datrie.py
|
||||||
|
src/pip/_vendor/html5lib/_trie/py.py
|
||||||
|
src/pip/_vendor/html5lib/filters/__init__.py
|
||||||
|
src/pip/_vendor/html5lib/filters/alphabeticalattributes.py
|
||||||
|
src/pip/_vendor/html5lib/filters/base.py
|
||||||
|
src/pip/_vendor/html5lib/filters/inject_meta_charset.py
|
||||||
|
src/pip/_vendor/html5lib/filters/lint.py
|
||||||
|
src/pip/_vendor/html5lib/filters/optionaltags.py
|
||||||
|
src/pip/_vendor/html5lib/filters/sanitizer.py
|
||||||
|
src/pip/_vendor/html5lib/filters/whitespace.py
|
||||||
|
src/pip/_vendor/html5lib/treeadapters/__init__.py
|
||||||
|
src/pip/_vendor/html5lib/treeadapters/genshi.py
|
||||||
|
src/pip/_vendor/html5lib/treeadapters/sax.py
|
||||||
|
src/pip/_vendor/html5lib/treebuilders/__init__.py
|
||||||
|
src/pip/_vendor/html5lib/treebuilders/base.py
|
||||||
|
src/pip/_vendor/html5lib/treebuilders/dom.py
|
||||||
|
src/pip/_vendor/html5lib/treebuilders/etree.py
|
||||||
|
src/pip/_vendor/html5lib/treebuilders/etree_lxml.py
|
||||||
|
src/pip/_vendor/html5lib/treewalkers/__init__.py
|
||||||
|
src/pip/_vendor/html5lib/treewalkers/base.py
|
||||||
|
src/pip/_vendor/html5lib/treewalkers/dom.py
|
||||||
|
src/pip/_vendor/html5lib/treewalkers/etree.py
|
||||||
|
src/pip/_vendor/html5lib/treewalkers/etree_lxml.py
|
||||||
|
src/pip/_vendor/html5lib/treewalkers/genshi.py
|
||||||
|
src/pip/_vendor/idna/LICENSE.rst
|
||||||
|
src/pip/_vendor/idna/__init__.py
|
||||||
|
src/pip/_vendor/idna/codec.py
|
||||||
|
src/pip/_vendor/idna/compat.py
|
||||||
|
src/pip/_vendor/idna/core.py
|
||||||
|
src/pip/_vendor/idna/idnadata.py
|
||||||
|
src/pip/_vendor/idna/intranges.py
|
||||||
|
src/pip/_vendor/idna/package_data.py
|
||||||
|
src/pip/_vendor/idna/uts46data.py
|
||||||
|
src/pip/_vendor/lockfile/LICENSE
|
||||||
|
src/pip/_vendor/lockfile/__init__.py
|
||||||
|
src/pip/_vendor/lockfile/linklockfile.py
|
||||||
|
src/pip/_vendor/lockfile/mkdirlockfile.py
|
||||||
|
src/pip/_vendor/lockfile/pidlockfile.py
|
||||||
|
src/pip/_vendor/lockfile/sqlitelockfile.py
|
||||||
|
src/pip/_vendor/lockfile/symlinklockfile.py
|
||||||
|
src/pip/_vendor/msgpack/COPYING
|
||||||
|
src/pip/_vendor/msgpack/__init__.py
|
||||||
|
src/pip/_vendor/msgpack/_version.py
|
||||||
|
src/pip/_vendor/msgpack/exceptions.py
|
||||||
|
src/pip/_vendor/msgpack/fallback.py
|
||||||
|
src/pip/_vendor/packaging/LICENSE
|
||||||
|
src/pip/_vendor/packaging/LICENSE.APACHE
|
||||||
|
src/pip/_vendor/packaging/LICENSE.BSD
|
||||||
|
src/pip/_vendor/packaging/__about__.py
|
||||||
|
src/pip/_vendor/packaging/__init__.py
|
||||||
|
src/pip/_vendor/packaging/_compat.py
|
||||||
|
src/pip/_vendor/packaging/_structures.py
|
||||||
|
src/pip/_vendor/packaging/markers.py
|
||||||
|
src/pip/_vendor/packaging/requirements.py
|
||||||
|
src/pip/_vendor/packaging/specifiers.py
|
||||||
|
src/pip/_vendor/packaging/utils.py
|
||||||
|
src/pip/_vendor/packaging/version.py
|
||||||
|
src/pip/_vendor/pep517/LICENSE
|
||||||
|
src/pip/_vendor/pep517/__init__.py
|
||||||
|
src/pip/_vendor/pep517/_in_process.py
|
||||||
|
src/pip/_vendor/pep517/build.py
|
||||||
|
src/pip/_vendor/pep517/check.py
|
||||||
|
src/pip/_vendor/pep517/colorlog.py
|
||||||
|
src/pip/_vendor/pep517/compat.py
|
||||||
|
src/pip/_vendor/pep517/envbuild.py
|
||||||
|
src/pip/_vendor/pep517/wrappers.py
|
||||||
|
src/pip/_vendor/pkg_resources/LICENSE
|
||||||
|
src/pip/_vendor/pkg_resources/__init__.py
|
||||||
|
src/pip/_vendor/pkg_resources/py31compat.py
|
||||||
|
src/pip/_vendor/progress/LICENSE
|
||||||
|
src/pip/_vendor/progress/__init__.py
|
||||||
|
src/pip/_vendor/progress/bar.py
|
||||||
|
src/pip/_vendor/progress/counter.py
|
||||||
|
src/pip/_vendor/progress/helpers.py
|
||||||
|
src/pip/_vendor/progress/spinner.py
|
||||||
|
src/pip/_vendor/pytoml/LICENSE
|
||||||
|
src/pip/_vendor/pytoml/__init__.py
|
||||||
|
src/pip/_vendor/pytoml/core.py
|
||||||
|
src/pip/_vendor/pytoml/parser.py
|
||||||
|
src/pip/_vendor/pytoml/test.py
|
||||||
|
src/pip/_vendor/pytoml/utils.py
|
||||||
|
src/pip/_vendor/pytoml/writer.py
|
||||||
|
src/pip/_vendor/requests/LICENSE
|
||||||
|
src/pip/_vendor/requests/__init__.py
|
||||||
|
src/pip/_vendor/requests/__version__.py
|
||||||
|
src/pip/_vendor/requests/_internal_utils.py
|
||||||
|
src/pip/_vendor/requests/adapters.py
|
||||||
|
src/pip/_vendor/requests/api.py
|
||||||
|
src/pip/_vendor/requests/auth.py
|
||||||
|
src/pip/_vendor/requests/certs.py
|
||||||
|
src/pip/_vendor/requests/compat.py
|
||||||
|
src/pip/_vendor/requests/cookies.py
|
||||||
|
src/pip/_vendor/requests/exceptions.py
|
||||||
|
src/pip/_vendor/requests/help.py
|
||||||
|
src/pip/_vendor/requests/hooks.py
|
||||||
|
src/pip/_vendor/requests/models.py
|
||||||
|
src/pip/_vendor/requests/packages.py
|
||||||
|
src/pip/_vendor/requests/sessions.py
|
||||||
|
src/pip/_vendor/requests/status_codes.py
|
||||||
|
src/pip/_vendor/requests/structures.py
|
||||||
|
src/pip/_vendor/requests/utils.py
|
||||||
|
src/pip/_vendor/urllib3/LICENSE.txt
|
||||||
|
src/pip/_vendor/urllib3/__init__.py
|
||||||
|
src/pip/_vendor/urllib3/_collections.py
|
||||||
|
src/pip/_vendor/urllib3/connection.py
|
||||||
|
src/pip/_vendor/urllib3/connectionpool.py
|
||||||
|
src/pip/_vendor/urllib3/exceptions.py
|
||||||
|
src/pip/_vendor/urllib3/fields.py
|
||||||
|
src/pip/_vendor/urllib3/filepost.py
|
||||||
|
src/pip/_vendor/urllib3/poolmanager.py
|
||||||
|
src/pip/_vendor/urllib3/request.py
|
||||||
|
src/pip/_vendor/urllib3/response.py
|
||||||
|
src/pip/_vendor/urllib3/contrib/__init__.py
|
||||||
|
src/pip/_vendor/urllib3/contrib/_appengine_environ.py
|
||||||
|
src/pip/_vendor/urllib3/contrib/appengine.py
|
||||||
|
src/pip/_vendor/urllib3/contrib/ntlmpool.py
|
||||||
|
src/pip/_vendor/urllib3/contrib/pyopenssl.py
|
||||||
|
src/pip/_vendor/urllib3/contrib/securetransport.py
|
||||||
|
src/pip/_vendor/urllib3/contrib/socks.py
|
||||||
|
src/pip/_vendor/urllib3/contrib/_securetransport/__init__.py
|
||||||
|
src/pip/_vendor/urllib3/contrib/_securetransport/bindings.py
|
||||||
|
src/pip/_vendor/urllib3/contrib/_securetransport/low_level.py
|
||||||
|
src/pip/_vendor/urllib3/packages/__init__.py
|
||||||
|
src/pip/_vendor/urllib3/packages/six.py
|
||||||
|
src/pip/_vendor/urllib3/packages/backports/__init__.py
|
||||||
|
src/pip/_vendor/urllib3/packages/backports/makefile.py
|
||||||
|
src/pip/_vendor/urllib3/packages/ssl_match_hostname/__init__.py
|
||||||
|
src/pip/_vendor/urllib3/packages/ssl_match_hostname/_implementation.py
|
||||||
|
src/pip/_vendor/urllib3/util/__init__.py
|
||||||
|
src/pip/_vendor/urllib3/util/connection.py
|
||||||
|
src/pip/_vendor/urllib3/util/queue.py
|
||||||
|
src/pip/_vendor/urllib3/util/request.py
|
||||||
|
src/pip/_vendor/urllib3/util/response.py
|
||||||
|
src/pip/_vendor/urllib3/util/retry.py
|
||||||
|
src/pip/_vendor/urllib3/util/ssl_.py
|
||||||
|
src/pip/_vendor/urllib3/util/timeout.py
|
||||||
|
src/pip/_vendor/urllib3/util/url.py
|
||||||
|
src/pip/_vendor/urllib3/util/wait.py
|
||||||
|
src/pip/_vendor/webencodings/LICENSE
|
||||||
|
src/pip/_vendor/webencodings/__init__.py
|
||||||
|
src/pip/_vendor/webencodings/labels.py
|
||||||
|
src/pip/_vendor/webencodings/mklabels.py
|
||||||
|
src/pip/_vendor/webencodings/tests.py
|
||||||
|
src/pip/_vendor/webencodings/x_user_defined.py
|
@@ -0,0 +1 @@
|
|||||||
|
|
@@ -0,0 +1,5 @@
|
|||||||
|
[console_scripts]
|
||||||
|
pip = pip._internal:main
|
||||||
|
pip3 = pip._internal:main
|
||||||
|
pip3.6 = pip._internal:main
|
||||||
|
|
@@ -0,0 +1 @@
|
|||||||
|
|
@@ -0,0 +1 @@
|
|||||||
|
pip
|
@@ -0,0 +1 @@
|
|||||||
|
__version__ = "19.0.3"
|
@@ -0,0 +1,19 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# If we are running from a wheel, add the wheel to sys.path
|
||||||
|
# This allows the usage python pip-*.whl/pip install pip-*.whl
|
||||||
|
if __package__ == '':
|
||||||
|
# __file__ is pip-*.whl/pip/__main__.py
|
||||||
|
# first dirname call strips of '/__main__.py', second strips off '/pip'
|
||||||
|
# Resulting path is the name of the wheel itself
|
||||||
|
# Add that to sys.path so we can import pip
|
||||||
|
path = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
sys.path.insert(0, path)
|
||||||
|
|
||||||
|
from pip._internal import main as _main # isort:skip # noqa
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(_main())
|
@@ -0,0 +1,78 @@
|
|||||||
|
#!/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 pip._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 pip._vendor.urllib3.contrib import securetransport
|
||||||
|
except (ImportError, OSError):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
securetransport.inject_into_urllib3()
|
||||||
|
|
||||||
|
from pip._internal.cli.autocompletion import autocomplete
|
||||||
|
from pip._internal.cli.main_parser import parse_command
|
||||||
|
from pip._internal.commands import commands_dict
|
||||||
|
from pip._internal.exceptions import PipError
|
||||||
|
from pip._internal.utils import deprecation
|
||||||
|
from pip._internal.vcs import git, mercurial, subversion, bazaar # noqa
|
||||||
|
from pip._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)
|
@@ -0,0 +1,215 @@
|
|||||||
|
"""Build Environment used for isolation during sdist building
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import textwrap
|
||||||
|
from collections import OrderedDict
|
||||||
|
from distutils.sysconfig import get_python_lib
|
||||||
|
from sysconfig import get_paths
|
||||||
|
|
||||||
|
from pip._vendor.pkg_resources import Requirement, VersionConflict, WorkingSet
|
||||||
|
|
||||||
|
from pip import __file__ as pip_location
|
||||||
|
from pip._internal.utils.misc import call_subprocess
|
||||||
|
from pip._internal.utils.temp_dir import TempDirectory
|
||||||
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
from pip._internal.utils.ui import open_spinner
|
||||||
|
|
||||||
|
if MYPY_CHECK_RUNNING:
|
||||||
|
from typing import Tuple, Set, Iterable, Optional, List # noqa: F401
|
||||||
|
from pip._internal.index import PackageFinder # noqa: F401
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class _Prefix:
|
||||||
|
|
||||||
|
def __init__(self, path):
|
||||||
|
# type: (str) -> None
|
||||||
|
self.path = path
|
||||||
|
self.setup = False
|
||||||
|
self.bin_dir = get_paths(
|
||||||
|
'nt' if os.name == 'nt' else 'posix_prefix',
|
||||||
|
vars={'base': path, 'platbase': path}
|
||||||
|
)['scripts']
|
||||||
|
# Note: prefer distutils' sysconfig to get the
|
||||||
|
# library paths so PyPy is correctly supported.
|
||||||
|
purelib = get_python_lib(plat_specific=False, prefix=path)
|
||||||
|
platlib = get_python_lib(plat_specific=True, prefix=path)
|
||||||
|
if purelib == platlib:
|
||||||
|
self.lib_dirs = [purelib]
|
||||||
|
else:
|
||||||
|
self.lib_dirs = [purelib, platlib]
|
||||||
|
|
||||||
|
|
||||||
|
class BuildEnvironment(object):
|
||||||
|
"""Creates and manages an isolated environment to install build deps
|
||||||
|
"""
|
||||||
|
|
||||||
|
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)))
|
||||||
|
for name in ('normal', 'overlay')
|
||||||
|
))
|
||||||
|
|
||||||
|
self._bin_dirs = [] # type: List[str]
|
||||||
|
self._lib_dirs = [] # type: List[str]
|
||||||
|
for prefix in reversed(list(self._prefixes.values())):
|
||||||
|
self._bin_dirs.append(prefix.bin_dir)
|
||||||
|
self._lib_dirs.extend(prefix.lib_dirs)
|
||||||
|
|
||||||
|
# Customize site to:
|
||||||
|
# - ensure .pth files are honored
|
||||||
|
# - prevent access to system site packages
|
||||||
|
system_sites = {
|
||||||
|
os.path.normcase(site) for site in (
|
||||||
|
get_python_lib(plat_specific=False),
|
||||||
|
get_python_lib(plat_specific=True),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
self._site_dir = os.path.join(self._temp_dir.path, 'site')
|
||||||
|
if not os.path.exists(self._site_dir):
|
||||||
|
os.mkdir(self._site_dir)
|
||||||
|
with open(os.path.join(self._site_dir, 'sitecustomize.py'), 'w') as fp:
|
||||||
|
fp.write(textwrap.dedent(
|
||||||
|
'''
|
||||||
|
import os, site, sys
|
||||||
|
|
||||||
|
# First, drop system-sites related paths.
|
||||||
|
original_sys_path = sys.path[:]
|
||||||
|
known_paths = set()
|
||||||
|
for path in {system_sites!r}:
|
||||||
|
site.addsitedir(path, known_paths=known_paths)
|
||||||
|
system_paths = set(
|
||||||
|
os.path.normcase(path)
|
||||||
|
for path in sys.path[len(original_sys_path):]
|
||||||
|
)
|
||||||
|
original_sys_path = [
|
||||||
|
path for path in original_sys_path
|
||||||
|
if os.path.normcase(path) not in system_paths
|
||||||
|
]
|
||||||
|
sys.path = original_sys_path
|
||||||
|
|
||||||
|
# Second, add lib directories.
|
||||||
|
# ensuring .pth file are processed.
|
||||||
|
for path in {lib_dirs!r}:
|
||||||
|
assert not path in sys.path
|
||||||
|
site.addsitedir(path)
|
||||||
|
'''
|
||||||
|
).format(system_sites=system_sites, lib_dirs=self._lib_dirs))
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self._save_env = {
|
||||||
|
name: os.environ.get(name, None)
|
||||||
|
for name in ('PATH', 'PYTHONNOUSERSITE', 'PYTHONPATH')
|
||||||
|
}
|
||||||
|
|
||||||
|
path = self._bin_dirs[:]
|
||||||
|
old_path = self._save_env['PATH']
|
||||||
|
if old_path:
|
||||||
|
path.extend(old_path.split(os.pathsep))
|
||||||
|
|
||||||
|
pythonpath = [self._site_dir]
|
||||||
|
|
||||||
|
os.environ.update({
|
||||||
|
'PATH': os.pathsep.join(path),
|
||||||
|
'PYTHONNOUSERSITE': '1',
|
||||||
|
'PYTHONPATH': os.pathsep.join(pythonpath),
|
||||||
|
})
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
for varname, old_value in self._save_env.items():
|
||||||
|
if old_value is None:
|
||||||
|
os.environ.pop(varname, None)
|
||||||
|
else:
|
||||||
|
os.environ[varname] = old_value
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
# type: () -> None
|
||||||
|
self._temp_dir.cleanup()
|
||||||
|
|
||||||
|
def check_requirements(self, reqs):
|
||||||
|
# type: (Iterable[str]) -> Tuple[Set[Tuple[str, str]], Set[str]]
|
||||||
|
"""Return 2 sets:
|
||||||
|
- conflicting requirements: set of (installed, wanted) reqs tuples
|
||||||
|
- missing requirements: set of reqs
|
||||||
|
"""
|
||||||
|
missing = set()
|
||||||
|
conflicting = set()
|
||||||
|
if reqs:
|
||||||
|
ws = WorkingSet(self._lib_dirs)
|
||||||
|
for req in reqs:
|
||||||
|
try:
|
||||||
|
if ws.find(Requirement.parse(req)) is None:
|
||||||
|
missing.add(req)
|
||||||
|
except VersionConflict as e:
|
||||||
|
conflicting.add((str(e.args[0].as_requirement()),
|
||||||
|
str(e.args[1])))
|
||||||
|
return conflicting, missing
|
||||||
|
|
||||||
|
def install_requirements(
|
||||||
|
self,
|
||||||
|
finder, # type: PackageFinder
|
||||||
|
requirements, # type: Iterable[str]
|
||||||
|
prefix_as_string, # type: str
|
||||||
|
message # type: Optional[str]
|
||||||
|
):
|
||||||
|
# type: (...) -> None
|
||||||
|
prefix = self._prefixes[prefix_as_string]
|
||||||
|
assert not prefix.setup
|
||||||
|
prefix.setup = True
|
||||||
|
if not requirements:
|
||||||
|
return
|
||||||
|
args = [
|
||||||
|
sys.executable, os.path.dirname(pip_location), 'install',
|
||||||
|
'--ignore-installed', '--no-user', '--prefix', prefix.path,
|
||||||
|
'--no-warn-script-location',
|
||||||
|
] # type: List[str]
|
||||||
|
if logger.getEffectiveLevel() <= logging.DEBUG:
|
||||||
|
args.append('-v')
|
||||||
|
for format_control in ('no_binary', 'only_binary'):
|
||||||
|
formats = getattr(finder.format_control, format_control)
|
||||||
|
args.extend(('--' + format_control.replace('_', '-'),
|
||||||
|
','.join(sorted(formats or {':none:'}))))
|
||||||
|
if finder.index_urls:
|
||||||
|
args.extend(['-i', finder.index_urls[0]])
|
||||||
|
for extra_index in finder.index_urls[1:]:
|
||||||
|
args.extend(['--extra-index-url', extra_index])
|
||||||
|
else:
|
||||||
|
args.append('--no-index')
|
||||||
|
for link in finder.find_links:
|
||||||
|
args.extend(['--find-links', link])
|
||||||
|
for _, host, _ in finder.secure_origins:
|
||||||
|
args.extend(['--trusted-host', host])
|
||||||
|
if finder.allow_all_prereleases:
|
||||||
|
args.append('--pre')
|
||||||
|
args.append('--')
|
||||||
|
args.extend(requirements)
|
||||||
|
with open_spinner(message) as spinner:
|
||||||
|
call_subprocess(args, show_stdout=False, spinner=spinner)
|
||||||
|
|
||||||
|
|
||||||
|
class NoOpBuildEnvironment(BuildEnvironment):
|
||||||
|
"""A no-op drop-in replacement for BuildEnvironment
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def install_requirements(self, finder, requirements, prefix, message):
|
||||||
|
raise NotImplementedError()
|
@@ -0,0 +1,224 @@
|
|||||||
|
"""Cache Management
|
||||||
|
"""
|
||||||
|
|
||||||
|
import errno
|
||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from pip._vendor.packaging.utils import canonicalize_name
|
||||||
|
|
||||||
|
from pip._internal.download import path_to_url
|
||||||
|
from pip._internal.models.link import Link
|
||||||
|
from pip._internal.utils.compat import expanduser
|
||||||
|
from pip._internal.utils.temp_dir import TempDirectory
|
||||||
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
from pip._internal.wheel import InvalidWheelFilename, Wheel
|
||||||
|
|
||||||
|
if MYPY_CHECK_RUNNING:
|
||||||
|
from typing import Optional, Set, List, Any # noqa: F401
|
||||||
|
from pip._internal.index import FormatControl # noqa: F401
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Cache(object):
|
||||||
|
"""An abstract class - provides cache directories for data from links
|
||||||
|
|
||||||
|
|
||||||
|
:param cache_dir: The root of the cache.
|
||||||
|
:param format_control: An object of FormatControl class to limit
|
||||||
|
binaries being read from the cache.
|
||||||
|
:param allowed_formats: which formats of files the cache should store.
|
||||||
|
('binary' and 'source' are the only allowed values)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, cache_dir, format_control, allowed_formats):
|
||||||
|
# type: (str, FormatControl, Set[str]) -> None
|
||||||
|
super(Cache, self).__init__()
|
||||||
|
self.cache_dir = expanduser(cache_dir) if cache_dir else None
|
||||||
|
self.format_control = format_control
|
||||||
|
self.allowed_formats = allowed_formats
|
||||||
|
|
||||||
|
_valid_formats = {"source", "binary"}
|
||||||
|
assert self.allowed_formats.union(_valid_formats) == _valid_formats
|
||||||
|
|
||||||
|
def _get_cache_path_parts(self, link):
|
||||||
|
# type: (Link) -> List[str]
|
||||||
|
"""Get parts of part that must be os.path.joined with cache_dir
|
||||||
|
"""
|
||||||
|
|
||||||
|
# We want to generate an url to use as our cache key, we don't want to
|
||||||
|
# just re-use the URL because it might have other items in the fragment
|
||||||
|
# and we don't care about those.
|
||||||
|
key_parts = [link.url_without_fragment]
|
||||||
|
if link.hash_name is not None and link.hash is not None:
|
||||||
|
key_parts.append("=".join([link.hash_name, link.hash]))
|
||||||
|
key_url = "#".join(key_parts)
|
||||||
|
|
||||||
|
# Encode our key url with sha224, we'll use this because it has similar
|
||||||
|
# security properties to sha256, but with a shorter total output (and
|
||||||
|
# thus less secure). However the differences don't make a lot of
|
||||||
|
# difference for our use case here.
|
||||||
|
hashed = hashlib.sha224(key_url.encode()).hexdigest()
|
||||||
|
|
||||||
|
# We want to nest the directories some to prevent having a ton of top
|
||||||
|
# level directories where we might run out of sub directories on some
|
||||||
|
# FS.
|
||||||
|
parts = [hashed[:2], hashed[2:4], hashed[4:6], hashed[6:]]
|
||||||
|
|
||||||
|
return parts
|
||||||
|
|
||||||
|
def _get_candidates(self, link, package_name):
|
||||||
|
# type: (Link, Optional[str]) -> List[Any]
|
||||||
|
can_not_cache = (
|
||||||
|
not self.cache_dir or
|
||||||
|
not package_name or
|
||||||
|
not link
|
||||||
|
)
|
||||||
|
if can_not_cache:
|
||||||
|
return []
|
||||||
|
|
||||||
|
canonical_name = canonicalize_name(package_name)
|
||||||
|
formats = self.format_control.get_allowed_formats(
|
||||||
|
canonical_name
|
||||||
|
)
|
||||||
|
if not self.allowed_formats.intersection(formats):
|
||||||
|
return []
|
||||||
|
|
||||||
|
root = self.get_path_for_link(link)
|
||||||
|
try:
|
||||||
|
return os.listdir(root)
|
||||||
|
except OSError as err:
|
||||||
|
if err.errno in {errno.ENOENT, errno.ENOTDIR}:
|
||||||
|
return []
|
||||||
|
raise
|
||||||
|
|
||||||
|
def get_path_for_link(self, link):
|
||||||
|
# type: (Link) -> str
|
||||||
|
"""Return a directory to store cached items in for link.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def get(self, link, package_name):
|
||||||
|
# type: (Link, Optional[str]) -> Link
|
||||||
|
"""Returns a link to a cached item if it exists, otherwise returns the
|
||||||
|
passed link.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def _link_for_candidate(self, link, candidate):
|
||||||
|
# type: (Link, str) -> Link
|
||||||
|
root = self.get_path_for_link(link)
|
||||||
|
path = os.path.join(root, candidate)
|
||||||
|
|
||||||
|
return Link(path_to_url(path))
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
# type: () -> None
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleWheelCache(Cache):
|
||||||
|
"""A cache of wheels for future installs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, cache_dir, format_control):
|
||||||
|
# type: (str, FormatControl) -> None
|
||||||
|
super(SimpleWheelCache, self).__init__(
|
||||||
|
cache_dir, format_control, {"binary"}
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_path_for_link(self, link):
|
||||||
|
# type: (Link) -> str
|
||||||
|
"""Return a directory to store cached wheels for link
|
||||||
|
|
||||||
|
Because there are M wheels for any one sdist, we provide a directory
|
||||||
|
to cache them in, and then consult that directory when looking up
|
||||||
|
cache hits.
|
||||||
|
|
||||||
|
We only insert things into the cache if they have plausible version
|
||||||
|
numbers, so that we don't contaminate the cache with things that were
|
||||||
|
not unique. E.g. ./package might have dozens of installs done for it
|
||||||
|
and build a version of 0.0...and if we built and cached a wheel, we'd
|
||||||
|
end up using the same wheel even if the source has been edited.
|
||||||
|
|
||||||
|
:param link: The link of the sdist for which this will cache wheels.
|
||||||
|
"""
|
||||||
|
parts = self._get_cache_path_parts(link)
|
||||||
|
|
||||||
|
# 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
|
||||||
|
candidates = []
|
||||||
|
|
||||||
|
for wheel_name in self._get_candidates(link, package_name):
|
||||||
|
try:
|
||||||
|
wheel = Wheel(wheel_name)
|
||||||
|
except InvalidWheelFilename:
|
||||||
|
continue
|
||||||
|
if not wheel.supported():
|
||||||
|
# Built for a different python/arch/etc
|
||||||
|
continue
|
||||||
|
candidates.append((wheel.support_index_min(), wheel_name))
|
||||||
|
|
||||||
|
if not candidates:
|
||||||
|
return link
|
||||||
|
|
||||||
|
return self._link_for_candidate(link, min(candidates)[1])
|
||||||
|
|
||||||
|
|
||||||
|
class EphemWheelCache(SimpleWheelCache):
|
||||||
|
"""A SimpleWheelCache that creates it's own temporary cache directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
# type: () -> None
|
||||||
|
self._temp_dir.cleanup()
|
||||||
|
|
||||||
|
|
||||||
|
class WheelCache(Cache):
|
||||||
|
"""Wraps EphemWheelCache and SimpleWheelCache into a single Cache
|
||||||
|
|
||||||
|
This Cache allows for gracefully degradation, using the ephem wheel cache
|
||||||
|
when a certain link is not found in the simple wheel cache first.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, cache_dir, format_control):
|
||||||
|
# type: (str, FormatControl) -> None
|
||||||
|
super(WheelCache, self).__init__(
|
||||||
|
cache_dir, format_control, {'binary'}
|
||||||
|
)
|
||||||
|
self._wheel_cache = SimpleWheelCache(cache_dir, format_control)
|
||||||
|
self._ephem_cache = EphemWheelCache(format_control)
|
||||||
|
|
||||||
|
def get_path_for_link(self, link):
|
||||||
|
# type: (Link) -> str
|
||||||
|
return self._wheel_cache.get_path_for_link(link)
|
||||||
|
|
||||||
|
def get_ephem_path_for_link(self, link):
|
||||||
|
# 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 cleanup(self):
|
||||||
|
# type: () -> None
|
||||||
|
self._wheel_cache.cleanup()
|
||||||
|
self._ephem_cache.cleanup()
|
@@ -0,0 +1,4 @@
|
|||||||
|
"""Subpackage containing all of pip's command line interface related code
|
||||||
|
"""
|
||||||
|
|
||||||
|
# This file intentionally does not import submodules
|
@@ -0,0 +1,152 @@
|
|||||||
|
"""Logic that powers autocompletion installed by ``pip completion``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import optparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from pip._internal.cli.main_parser import create_main_parser
|
||||||
|
from pip._internal.commands import commands_dict, get_summaries
|
||||||
|
from pip._internal.utils.misc import get_installed_distributions
|
||||||
|
|
||||||
|
|
||||||
|
def autocomplete():
|
||||||
|
"""Entry Point for completion of main and subcommand options.
|
||||||
|
"""
|
||||||
|
# Don't complete if user hasn't sourced bash_completion file.
|
||||||
|
if 'PIP_AUTO_COMPLETE' not in os.environ:
|
||||||
|
return
|
||||||
|
cwords = os.environ['COMP_WORDS'].split()[1:]
|
||||||
|
cword = int(os.environ['COMP_CWORD'])
|
||||||
|
try:
|
||||||
|
current = cwords[cword - 1]
|
||||||
|
except IndexError:
|
||||||
|
current = ''
|
||||||
|
|
||||||
|
subcommands = [cmd for cmd, summary in get_summaries()]
|
||||||
|
options = []
|
||||||
|
# subcommand
|
||||||
|
try:
|
||||||
|
subcommand_name = [w for w in cwords if w in subcommands][0]
|
||||||
|
except IndexError:
|
||||||
|
subcommand_name = None
|
||||||
|
|
||||||
|
parser = create_main_parser()
|
||||||
|
# subcommand options
|
||||||
|
if subcommand_name:
|
||||||
|
# special case: 'help' subcommand has no options
|
||||||
|
if subcommand_name == 'help':
|
||||||
|
sys.exit(1)
|
||||||
|
# special case: list locally installed dists for show and uninstall
|
||||||
|
should_list_installed = (
|
||||||
|
subcommand_name in ['show', 'uninstall'] and
|
||||||
|
not current.startswith('-')
|
||||||
|
)
|
||||||
|
if should_list_installed:
|
||||||
|
installed = []
|
||||||
|
lc = current.lower()
|
||||||
|
for dist in get_installed_distributions(local_only=True):
|
||||||
|
if dist.key.startswith(lc) and dist.key not in cwords[1:]:
|
||||||
|
installed.append(dist.key)
|
||||||
|
# if there are no dists installed, fall back to option completion
|
||||||
|
if installed:
|
||||||
|
for dist in installed:
|
||||||
|
print(dist)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
subcommand = commands_dict[subcommand_name]()
|
||||||
|
|
||||||
|
for opt in subcommand.parser.option_list_all:
|
||||||
|
if opt.help != optparse.SUPPRESS_HELP:
|
||||||
|
for opt_str in opt._long_opts + opt._short_opts:
|
||||||
|
options.append((opt_str, opt.nargs))
|
||||||
|
|
||||||
|
# filter out previously specified options from available options
|
||||||
|
prev_opts = [x.split('=')[0] for x in cwords[1:cword - 1]]
|
||||||
|
options = [(x, v) for (x, v) in options if x not in prev_opts]
|
||||||
|
# filter options by current input
|
||||||
|
options = [(k, v) for k, v in options if k.startswith(current)]
|
||||||
|
# get completion type given cwords and available subcommand options
|
||||||
|
completion_type = get_path_completion_type(
|
||||||
|
cwords, cword, subcommand.parser.option_list_all,
|
||||||
|
)
|
||||||
|
# get completion files and directories if ``completion_type`` is
|
||||||
|
# ``<file>``, ``<dir>`` or ``<path>``
|
||||||
|
if completion_type:
|
||||||
|
options = auto_complete_paths(current, completion_type)
|
||||||
|
options = ((opt, 0) for opt in options)
|
||||||
|
for option in options:
|
||||||
|
opt_label = option[0]
|
||||||
|
# append '=' to options which require args
|
||||||
|
if option[1] and option[0][:2] == "--":
|
||||||
|
opt_label += '='
|
||||||
|
print(opt_label)
|
||||||
|
else:
|
||||||
|
# show main parser options only when necessary
|
||||||
|
|
||||||
|
opts = [i.option_list for i in parser.option_groups]
|
||||||
|
opts.append(parser.option_list)
|
||||||
|
opts = (o for it in opts for o in it)
|
||||||
|
if current.startswith('-'):
|
||||||
|
for opt in opts:
|
||||||
|
if opt.help != optparse.SUPPRESS_HELP:
|
||||||
|
subcommands += opt._long_opts + opt._short_opts
|
||||||
|
else:
|
||||||
|
# get completion type given cwords and all available options
|
||||||
|
completion_type = get_path_completion_type(cwords, cword, opts)
|
||||||
|
if completion_type:
|
||||||
|
subcommands = auto_complete_paths(current, completion_type)
|
||||||
|
|
||||||
|
print(' '.join([x for x in subcommands if x.startswith(current)]))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_path_completion_type(cwords, cword, opts):
|
||||||
|
"""Get the type of path completion (``file``, ``dir``, ``path`` or None)
|
||||||
|
|
||||||
|
:param cwords: same as the environmental variable ``COMP_WORDS``
|
||||||
|
:param cword: same as the environmental variable ``COMP_CWORD``
|
||||||
|
:param opts: The available options to check
|
||||||
|
:return: path completion type (``file``, ``dir``, ``path`` or None)
|
||||||
|
"""
|
||||||
|
if cword < 2 or not cwords[cword - 2].startswith('-'):
|
||||||
|
return
|
||||||
|
for opt in opts:
|
||||||
|
if opt.help == optparse.SUPPRESS_HELP:
|
||||||
|
continue
|
||||||
|
for o in str(opt).split('/'):
|
||||||
|
if cwords[cword - 2].split('=')[0] == o:
|
||||||
|
if not opt.metavar or any(
|
||||||
|
x in ('path', 'file', 'dir')
|
||||||
|
for x in opt.metavar.split('/')):
|
||||||
|
return opt.metavar
|
||||||
|
|
||||||
|
|
||||||
|
def auto_complete_paths(current, completion_type):
|
||||||
|
"""If ``completion_type`` is ``file`` or ``path``, list all regular files
|
||||||
|
and directories starting with ``current``; otherwise only list directories
|
||||||
|
starting with ``current``.
|
||||||
|
|
||||||
|
:param current: The word to be completed
|
||||||
|
:param completion_type: path completion type(`file`, `path` or `dir`)i
|
||||||
|
:return: A generator of regular files and/or directories
|
||||||
|
"""
|
||||||
|
directory, filename = os.path.split(current)
|
||||||
|
current_path = os.path.abspath(directory)
|
||||||
|
# Don't complete paths if they can't be accessed
|
||||||
|
if not os.access(current_path, os.R_OK):
|
||||||
|
return
|
||||||
|
filename = os.path.normcase(filename)
|
||||||
|
# list all files that start with ``filename``
|
||||||
|
file_list = (x for x in os.listdir(current_path)
|
||||||
|
if os.path.normcase(x).startswith(filename))
|
||||||
|
for f in file_list:
|
||||||
|
opt = os.path.join(current_path, f)
|
||||||
|
comp_file = os.path.normcase(os.path.join(directory, f))
|
||||||
|
# complete regular files when there is not ``<dir>`` after option
|
||||||
|
# complete directories when there is ``<file>``, ``<path>`` or
|
||||||
|
# ``<dir>``after option
|
||||||
|
if completion_type != 'dir' and os.path.isfile(opt):
|
||||||
|
yield comp_file
|
||||||
|
elif os.path.isdir(opt):
|
||||||
|
yield os.path.join(comp_file, '')
|
@@ -0,0 +1,341 @@
|
|||||||
|
"""Base Command class, and related routines"""
|
||||||
|
from __future__ import absolute_import, print_function
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import logging.config
|
||||||
|
import optparse
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from pip._internal.cli import cmdoptions
|
||||||
|
from pip._internal.cli.parser import (
|
||||||
|
ConfigOptionParser, UpdatingDefaultsHelpFormatter,
|
||||||
|
)
|
||||||
|
from pip._internal.cli.status_codes import (
|
||||||
|
ERROR, PREVIOUS_BUILD_DIR_ERROR, SUCCESS, UNKNOWN_ERROR,
|
||||||
|
VIRTUALENV_NOT_FOUND,
|
||||||
|
)
|
||||||
|
from pip._internal.download import PipSession
|
||||||
|
from pip._internal.exceptions import (
|
||||||
|
BadCommand, CommandError, InstallationError, PreviousBuildDirError,
|
||||||
|
UninstallationError,
|
||||||
|
)
|
||||||
|
from pip._internal.index import PackageFinder
|
||||||
|
from pip._internal.locations import running_under_virtualenv
|
||||||
|
from pip._internal.req.constructors import (
|
||||||
|
install_req_from_editable, install_req_from_line,
|
||||||
|
)
|
||||||
|
from pip._internal.req.req_file import parse_requirements
|
||||||
|
from pip._internal.utils.deprecation import deprecated
|
||||||
|
from pip._internal.utils.logging import BrokenStdoutLoggingError, setup_logging
|
||||||
|
from pip._internal.utils.misc import (
|
||||||
|
get_prog, normalize_path, redact_password_from_url,
|
||||||
|
)
|
||||||
|
from pip._internal.utils.outdated import pip_version_check
|
||||||
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
|
if MYPY_CHECK_RUNNING:
|
||||||
|
from typing import Optional, List, Tuple, Any # noqa: F401
|
||||||
|
from optparse import Values # noqa: F401
|
||||||
|
from pip._internal.cache import WheelCache # noqa: F401
|
||||||
|
from pip._internal.req.req_set import RequirementSet # noqa: F401
|
||||||
|
|
||||||
|
__all__ = ['Command']
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(object):
|
||||||
|
name = None # type: Optional[str]
|
||||||
|
usage = None # type: Optional[str]
|
||||||
|
hidden = False # type: bool
|
||||||
|
ignore_require_venv = False # type: bool
|
||||||
|
|
||||||
|
def __init__(self, isolated=False):
|
||||||
|
# type: (bool) -> None
|
||||||
|
parser_kw = {
|
||||||
|
'usage': self.usage,
|
||||||
|
'prog': '%s %s' % (get_prog(), self.name),
|
||||||
|
'formatter': UpdatingDefaultsHelpFormatter(),
|
||||||
|
'add_help_option': False,
|
||||||
|
'name': self.name,
|
||||||
|
'description': self.__doc__,
|
||||||
|
'isolated': isolated,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.parser = ConfigOptionParser(**parser_kw)
|
||||||
|
|
||||||
|
# Commands should add options to this option group
|
||||||
|
optgroup_name = '%s Options' % self.name.capitalize()
|
||||||
|
self.cmd_opts = optparse.OptionGroup(self.parser, optgroup_name)
|
||||||
|
|
||||||
|
# Add the general options
|
||||||
|
gen_opts = cmdoptions.make_option_group(
|
||||||
|
cmdoptions.general_group,
|
||||||
|
self.parser,
|
||||||
|
)
|
||||||
|
self.parser.add_option_group(gen_opts)
|
||||||
|
|
||||||
|
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
|
||||||
|
options, args = self.parse_args(args)
|
||||||
|
|
||||||
|
# Set verbosity so that it can be used elsewhere.
|
||||||
|
self.verbosity = options.verbose - options.quiet
|
||||||
|
|
||||||
|
level_number = setup_logging(
|
||||||
|
verbosity=self.verbosity,
|
||||||
|
no_color=options.no_color,
|
||||||
|
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):
|
||||||
|
message = (
|
||||||
|
"A future version of pip will drop support for Python 2.7."
|
||||||
|
)
|
||||||
|
if platform.python_implementation() == "CPython":
|
||||||
|
message = (
|
||||||
|
"Python 2.7 will reach the end of its life on January "
|
||||||
|
"1st, 2020. Please upgrade your Python as Python 2.7 "
|
||||||
|
"won't be maintained after that date. "
|
||||||
|
) + message
|
||||||
|
deprecated(message, replacement=None, gone_in=None)
|
||||||
|
|
||||||
|
# TODO: Try to get these passing down from the command?
|
||||||
|
# without resorting to os.environ to hold these.
|
||||||
|
# This also affects isolated builds and it should.
|
||||||
|
|
||||||
|
if options.no_input:
|
||||||
|
os.environ['PIP_NO_INPUT'] = '1'
|
||||||
|
|
||||||
|
if options.exists_action:
|
||||||
|
os.environ['PIP_EXISTS_ACTION'] = ' '.join(options.exists_action)
|
||||||
|
|
||||||
|
if options.require_venv and not self.ignore_require_venv:
|
||||||
|
# If a venv is required check if it can really be found
|
||||||
|
if not running_under_virtualenv():
|
||||||
|
logger.critical(
|
||||||
|
'Could not find an activated virtualenv (required).'
|
||||||
|
)
|
||||||
|
sys.exit(VIRTUALENV_NOT_FOUND)
|
||||||
|
|
||||||
|
try:
|
||||||
|
status = self.run(options, args)
|
||||||
|
# FIXME: all commands should return an exit status
|
||||||
|
# and when it is done, isinstance is not needed anymore
|
||||||
|
if isinstance(status, int):
|
||||||
|
return status
|
||||||
|
except PreviousBuildDirError as exc:
|
||||||
|
logger.critical(str(exc))
|
||||||
|
logger.debug('Exception information:', exc_info=True)
|
||||||
|
|
||||||
|
return PREVIOUS_BUILD_DIR_ERROR
|
||||||
|
except (InstallationError, UninstallationError, BadCommand) as exc:
|
||||||
|
logger.critical(str(exc))
|
||||||
|
logger.debug('Exception information:', exc_info=True)
|
||||||
|
|
||||||
|
return ERROR
|
||||||
|
except CommandError as exc:
|
||||||
|
logger.critical('ERROR: %s', exc)
|
||||||
|
logger.debug('Exception information:', exc_info=True)
|
||||||
|
|
||||||
|
return ERROR
|
||||||
|
except BrokenStdoutLoggingError:
|
||||||
|
# Bypass our logger and write any remaining messages to stderr
|
||||||
|
# because stdout no longer works.
|
||||||
|
print('ERROR: Pipe to stdout was broken', file=sys.stderr)
|
||||||
|
if level_number <= logging.DEBUG:
|
||||||
|
traceback.print_exc(file=sys.stderr)
|
||||||
|
|
||||||
|
return ERROR
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.critical('Operation cancelled by user')
|
||||||
|
logger.debug('Exception information:', exc_info=True)
|
||||||
|
|
||||||
|
return ERROR
|
||||||
|
except BaseException:
|
||||||
|
logger.critical('Exception:', exc_info=True)
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
@@ -0,0 +1,809 @@
|
|||||||
|
"""
|
||||||
|
shared options and groups
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import textwrap
|
||||||
|
import warnings
|
||||||
|
from distutils.util import strtobool
|
||||||
|
from functools import partial
|
||||||
|
from optparse import SUPPRESS_HELP, Option, OptionGroup
|
||||||
|
|
||||||
|
from pip._internal.exceptions import CommandError
|
||||||
|
from pip._internal.locations import USER_CACHE_DIR, src_prefix
|
||||||
|
from pip._internal.models.format_control import FormatControl
|
||||||
|
from pip._internal.models.index import PyPI
|
||||||
|
from pip._internal.utils.hashes import STRONG_HASHES
|
||||||
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
from pip._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 pip._internal.cli.parser import ConfigOptionParser # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
|
def raise_option_error(parser, option, msg):
|
||||||
|
"""
|
||||||
|
Raise an option parsing error using parser.error().
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parser: an OptionParser instance.
|
||||||
|
option: an Option instance.
|
||||||
|
msg: the error text.
|
||||||
|
"""
|
||||||
|
msg = '{} error: {}'.format(option, msg)
|
||||||
|
msg = textwrap.fill(' '.join(msg.split()))
|
||||||
|
parser.error(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def make_option_group(group, parser):
|
||||||
|
# type: (Dict[str, Any], ConfigOptionParser) -> OptionGroup
|
||||||
|
"""
|
||||||
|
Return an OptionGroup object
|
||||||
|
group -- assumed to be dict with 'name' and 'options' keys
|
||||||
|
parser -- an optparse Parser
|
||||||
|
"""
|
||||||
|
option_group = OptionGroup(parser, group['name'])
|
||||||
|
for option in group['options']:
|
||||||
|
option_group.add_option(option())
|
||||||
|
return option_group
|
||||||
|
|
||||||
|
|
||||||
|
def check_install_build_global(options, check_options=None):
|
||||||
|
# type: (Values, Optional[Values]) -> None
|
||||||
|
"""Disable wheels if per-setup.py call options are set.
|
||||||
|
|
||||||
|
:param options: The OptionParser options to update.
|
||||||
|
:param check_options: The options to check, if not supplied defaults to
|
||||||
|
options.
|
||||||
|
"""
|
||||||
|
if check_options is None:
|
||||||
|
check_options = options
|
||||||
|
|
||||||
|
def getname(n):
|
||||||
|
return getattr(check_options, n, None)
|
||||||
|
names = ["build_options", "global_options", "install_options"]
|
||||||
|
if any(map(getname, names)):
|
||||||
|
control = options.format_control
|
||||||
|
control.disallow_binaries()
|
||||||
|
warnings.warn(
|
||||||
|
'Disabling all use of wheels due to the use of --build-options '
|
||||||
|
'/ --global-options / --install-options.', stacklevel=2,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def check_dist_restriction(options, check_target=False):
|
||||||
|
# type: (Values, bool) -> None
|
||||||
|
"""Function for determining if custom platform options are allowed.
|
||||||
|
|
||||||
|
:param options: The OptionParser options.
|
||||||
|
:param check_target: Whether or not to check if --target is being used.
|
||||||
|
"""
|
||||||
|
dist_restriction_set = any([
|
||||||
|
options.python_version,
|
||||||
|
options.platform,
|
||||||
|
options.abi,
|
||||||
|
options.implementation,
|
||||||
|
])
|
||||||
|
|
||||||
|
binary_only = FormatControl(set(), {':all:'})
|
||||||
|
sdist_dependencies_allowed = (
|
||||||
|
options.format_control != binary_only and
|
||||||
|
not options.ignore_dependencies
|
||||||
|
)
|
||||||
|
|
||||||
|
# Installations or downloads using dist restrictions must not combine
|
||||||
|
# source distributions and dist-specific wheels, as they are not
|
||||||
|
# gauranteed to be locally compatible.
|
||||||
|
if dist_restriction_set and sdist_dependencies_allowed:
|
||||||
|
raise CommandError(
|
||||||
|
"When restricting platform and interpreter constraints using "
|
||||||
|
"--python-version, --platform, --abi, or --implementation, "
|
||||||
|
"either --no-deps must be set, or --only-binary=:all: must be "
|
||||||
|
"set and --no-binary must not be set (or must be set to "
|
||||||
|
":none:)."
|
||||||
|
)
|
||||||
|
|
||||||
|
if check_target:
|
||||||
|
if dist_restriction_set and not options.target_dir:
|
||||||
|
raise CommandError(
|
||||||
|
"Can not use any platform or abi specific options unless "
|
||||||
|
"installing via '--target'"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
###########
|
||||||
|
# options #
|
||||||
|
###########
|
||||||
|
|
||||||
|
help_ = partial(
|
||||||
|
Option,
|
||||||
|
'-h', '--help',
|
||||||
|
dest='help',
|
||||||
|
action='help',
|
||||||
|
help='Show help.',
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
isolated_mode = partial(
|
||||||
|
Option,
|
||||||
|
"--isolated",
|
||||||
|
dest="isolated_mode",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help=(
|
||||||
|
"Run pip in an isolated mode, ignoring environment variables and user "
|
||||||
|
"configuration."
|
||||||
|
),
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
require_virtualenv = partial(
|
||||||
|
Option,
|
||||||
|
# Run only if inside a virtualenv, bail if not.
|
||||||
|
'--require-virtualenv', '--require-venv',
|
||||||
|
dest='require_venv',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help=SUPPRESS_HELP
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
verbose = partial(
|
||||||
|
Option,
|
||||||
|
'-v', '--verbose',
|
||||||
|
dest='verbose',
|
||||||
|
action='count',
|
||||||
|
default=0,
|
||||||
|
help='Give more output. Option is additive, and can be used up to 3 times.'
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
no_color = partial(
|
||||||
|
Option,
|
||||||
|
'--no-color',
|
||||||
|
dest='no_color',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help="Suppress colored output",
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
version = partial(
|
||||||
|
Option,
|
||||||
|
'-V', '--version',
|
||||||
|
dest='version',
|
||||||
|
action='store_true',
|
||||||
|
help='Show version and exit.',
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
quiet = partial(
|
||||||
|
Option,
|
||||||
|
'-q', '--quiet',
|
||||||
|
dest='quiet',
|
||||||
|
action='count',
|
||||||
|
default=0,
|
||||||
|
help=(
|
||||||
|
'Give less output. Option is additive, and can be used up to 3'
|
||||||
|
' times (corresponding to WARNING, ERROR, and CRITICAL logging'
|
||||||
|
' levels).'
|
||||||
|
),
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
progress_bar = partial(
|
||||||
|
Option,
|
||||||
|
'--progress-bar',
|
||||||
|
dest='progress_bar',
|
||||||
|
type='choice',
|
||||||
|
choices=list(BAR_TYPES.keys()),
|
||||||
|
default='on',
|
||||||
|
help=(
|
||||||
|
'Specify type of progress to be displayed [' +
|
||||||
|
'|'.join(BAR_TYPES.keys()) + '] (default: %default)'
|
||||||
|
),
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
log = partial(
|
||||||
|
Option,
|
||||||
|
"--log", "--log-file", "--local-log",
|
||||||
|
dest="log",
|
||||||
|
metavar="path",
|
||||||
|
help="Path to a verbose appending log."
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
no_input = partial(
|
||||||
|
Option,
|
||||||
|
# Don't ask for input
|
||||||
|
'--no-input',
|
||||||
|
dest='no_input',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help=SUPPRESS_HELP
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
proxy = partial(
|
||||||
|
Option,
|
||||||
|
'--proxy',
|
||||||
|
dest='proxy',
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
help="Specify a proxy in the form [user:passwd@]proxy.server:port."
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
retries = partial(
|
||||||
|
Option,
|
||||||
|
'--retries',
|
||||||
|
dest='retries',
|
||||||
|
type='int',
|
||||||
|
default=5,
|
||||||
|
help="Maximum number of retries each connection should attempt "
|
||||||
|
"(default %default times).",
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
timeout = partial(
|
||||||
|
Option,
|
||||||
|
'--timeout', '--default-timeout',
|
||||||
|
metavar='sec',
|
||||||
|
dest='timeout',
|
||||||
|
type='float',
|
||||||
|
default=15,
|
||||||
|
help='Set the socket timeout (default %default seconds).',
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
skip_requirements_regex = partial(
|
||||||
|
Option,
|
||||||
|
# A regex to be used to skip requirements
|
||||||
|
'--skip-requirements-regex',
|
||||||
|
dest='skip_requirements_regex',
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
help=SUPPRESS_HELP,
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
|
||||||
|
def exists_action():
|
||||||
|
# type: () -> Option
|
||||||
|
return Option(
|
||||||
|
# Option when path already exist
|
||||||
|
'--exists-action',
|
||||||
|
dest='exists_action',
|
||||||
|
type='choice',
|
||||||
|
choices=['s', 'i', 'w', 'b', 'a'],
|
||||||
|
default=[],
|
||||||
|
action='append',
|
||||||
|
metavar='action',
|
||||||
|
help="Default action when a path already exists: "
|
||||||
|
"(s)witch, (i)gnore, (w)ipe, (b)ackup, (a)bort).",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
cert = partial(
|
||||||
|
Option,
|
||||||
|
'--cert',
|
||||||
|
dest='cert',
|
||||||
|
type='str',
|
||||||
|
metavar='path',
|
||||||
|
help="Path to alternate CA bundle.",
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
client_cert = partial(
|
||||||
|
Option,
|
||||||
|
'--client-cert',
|
||||||
|
dest='client_cert',
|
||||||
|
type='str',
|
||||||
|
default=None,
|
||||||
|
metavar='path',
|
||||||
|
help="Path to SSL client certificate, a single file containing the "
|
||||||
|
"private key and the certificate in PEM format.",
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
index_url = partial(
|
||||||
|
Option,
|
||||||
|
'-i', '--index-url', '--pypi-url',
|
||||||
|
dest='index_url',
|
||||||
|
metavar='URL',
|
||||||
|
default=PyPI.simple_url,
|
||||||
|
help="Base URL of 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.",
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
|
||||||
|
def extra_index_url():
|
||||||
|
return Option(
|
||||||
|
'--extra-index-url',
|
||||||
|
dest='extra_index_urls',
|
||||||
|
metavar='URL',
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
help="Extra URLs of package indexes to use in addition to "
|
||||||
|
"--index-url. Should follow the same rules as "
|
||||||
|
"--index-url.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
no_index = partial(
|
||||||
|
Option,
|
||||||
|
'--no-index',
|
||||||
|
dest='no_index',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Ignore package index (only looking at --find-links URLs instead).',
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
|
||||||
|
def find_links():
|
||||||
|
# type: () -> Option
|
||||||
|
return Option(
|
||||||
|
'-f', '--find-links',
|
||||||
|
dest='find_links',
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
metavar='url',
|
||||||
|
help="If a url or path to an html file, then parse for links to "
|
||||||
|
"archives. If a local path or file:// url that's a directory, "
|
||||||
|
"then look for archives in the directory listing.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def trusted_host():
|
||||||
|
# type: () -> Option
|
||||||
|
return Option(
|
||||||
|
"--trusted-host",
|
||||||
|
dest="trusted_hosts",
|
||||||
|
action="append",
|
||||||
|
metavar="HOSTNAME",
|
||||||
|
default=[],
|
||||||
|
help="Mark this host as trusted, even though it does not have valid "
|
||||||
|
"or any HTTPS.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def constraints():
|
||||||
|
# type: () -> Option
|
||||||
|
return Option(
|
||||||
|
'-c', '--constraint',
|
||||||
|
dest='constraints',
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
metavar='file',
|
||||||
|
help='Constrain versions using the given constraints file. '
|
||||||
|
'This option can be used multiple times.'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def requirements():
|
||||||
|
# type: () -> Option
|
||||||
|
return Option(
|
||||||
|
'-r', '--requirement',
|
||||||
|
dest='requirements',
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
metavar='file',
|
||||||
|
help='Install from the given requirements file. '
|
||||||
|
'This option can be used multiple times.'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def editable():
|
||||||
|
# type: () -> Option
|
||||||
|
return Option(
|
||||||
|
'-e', '--editable',
|
||||||
|
dest='editables',
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
metavar='path/url',
|
||||||
|
help=('Install a project in editable mode (i.e. setuptools '
|
||||||
|
'"develop mode") from a local project path or a VCS url.'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
src = partial(
|
||||||
|
Option,
|
||||||
|
'--src', '--source', '--source-dir', '--source-directory',
|
||||||
|
dest='src_dir',
|
||||||
|
metavar='dir',
|
||||||
|
default=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".'
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
|
||||||
|
def _get_format_control(values, option):
|
||||||
|
# type: (Values, Option) -> Any
|
||||||
|
"""Get a format_control object."""
|
||||||
|
return getattr(values, option.dest)
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_no_binary(option, opt_str, value, parser):
|
||||||
|
# type: (Option, str, str, OptionParser) -> None
|
||||||
|
existing = _get_format_control(parser.values, option)
|
||||||
|
FormatControl.handle_mutual_excludes(
|
||||||
|
value, existing.no_binary, existing.only_binary,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_only_binary(option, opt_str, value, parser):
|
||||||
|
# type: (Option, str, str, OptionParser) -> None
|
||||||
|
existing = _get_format_control(parser.values, option)
|
||||||
|
FormatControl.handle_mutual_excludes(
|
||||||
|
value, existing.only_binary, existing.no_binary,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def no_binary():
|
||||||
|
# type: () -> Option
|
||||||
|
format_control = FormatControl(set(), set())
|
||||||
|
return Option(
|
||||||
|
"--no-binary", dest="format_control", action="callback",
|
||||||
|
callback=_handle_no_binary, type="str",
|
||||||
|
default=format_control,
|
||||||
|
help="Do not use binary packages. Can be supplied multiple times, and "
|
||||||
|
"each time adds to the existing value. Accepts either :all: to "
|
||||||
|
"disable all binary packages, :none: to empty the set, or one or "
|
||||||
|
"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.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def only_binary():
|
||||||
|
# type: () -> Option
|
||||||
|
format_control = FormatControl(set(), set())
|
||||||
|
return Option(
|
||||||
|
"--only-binary", dest="format_control", action="callback",
|
||||||
|
callback=_handle_only_binary, type="str",
|
||||||
|
default=format_control,
|
||||||
|
help="Do not use source packages. Can be supplied multiple times, and "
|
||||||
|
"each time adds to the existing value. Accepts either :all: to "
|
||||||
|
"disable all source packages, :none: to empty the set, or one or "
|
||||||
|
"more package names with commas between them. Packages without "
|
||||||
|
"binary distributions will fail to install when this option is "
|
||||||
|
"used on them.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
platform = partial(
|
||||||
|
Option,
|
||||||
|
'--platform',
|
||||||
|
dest='platform',
|
||||||
|
metavar='platform',
|
||||||
|
default=None,
|
||||||
|
help=("Only use wheels compatible with <platform>. "
|
||||||
|
"Defaults to the platform of the running system."),
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
|
||||||
|
python_version = partial(
|
||||||
|
Option,
|
||||||
|
'--python-version',
|
||||||
|
dest='python_version',
|
||||||
|
metavar='python_version',
|
||||||
|
default=None,
|
||||||
|
help=("Only use wheels compatible with Python "
|
||||||
|
"interpreter version <version>. If not specified, then the "
|
||||||
|
"current system interpreter minor version is used. A major "
|
||||||
|
"version (e.g. '2') can be specified to match all "
|
||||||
|
"minor revs of that major version. A minor version "
|
||||||
|
"(e.g. '34') can also be specified."),
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
|
||||||
|
implementation = partial(
|
||||||
|
Option,
|
||||||
|
'--implementation',
|
||||||
|
dest='implementation',
|
||||||
|
metavar='implementation',
|
||||||
|
default=None,
|
||||||
|
help=("Only use wheels compatible with Python "
|
||||||
|
"implementation <implementation>, e.g. 'pp', 'jy', 'cp', "
|
||||||
|
" or 'ip'. If not specified, then the current "
|
||||||
|
"interpreter implementation is used. Use 'py' to force "
|
||||||
|
"implementation-agnostic wheels."),
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
|
||||||
|
abi = partial(
|
||||||
|
Option,
|
||||||
|
'--abi',
|
||||||
|
dest='abi',
|
||||||
|
metavar='abi',
|
||||||
|
default=None,
|
||||||
|
help=("Only use wheels compatible with Python "
|
||||||
|
"abi <abi>, e.g. 'pypy_41'. If not specified, then the "
|
||||||
|
"current interpreter abi tag is used. Generally "
|
||||||
|
"you will need to specify --implementation, "
|
||||||
|
"--platform, and --python-version when using "
|
||||||
|
"this option."),
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
|
||||||
|
def prefer_binary():
|
||||||
|
# type: () -> Option
|
||||||
|
return Option(
|
||||||
|
"--prefer-binary",
|
||||||
|
dest="prefer_binary",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="Prefer older binary packages over newer source packages."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
cache_dir = partial(
|
||||||
|
Option,
|
||||||
|
"--cache-dir",
|
||||||
|
dest="cache_dir",
|
||||||
|
default=USER_CACHE_DIR,
|
||||||
|
metavar="dir",
|
||||||
|
help="Store the cache data in <dir>."
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
|
||||||
|
def no_cache_dir_callback(option, opt, value, parser):
|
||||||
|
"""
|
||||||
|
Process a value provided for the --no-cache-dir option.
|
||||||
|
|
||||||
|
This is an optparse.Option callback for the --no-cache-dir option.
|
||||||
|
"""
|
||||||
|
# The value argument will be None if --no-cache-dir is passed via the
|
||||||
|
# command-line, since the option doesn't accept arguments. However,
|
||||||
|
# the value can be non-None if the option is triggered e.g. by an
|
||||||
|
# environment variable, like PIP_NO_CACHE_DIR=true.
|
||||||
|
if value is not None:
|
||||||
|
# Then parse the string value to get argument error-checking.
|
||||||
|
try:
|
||||||
|
strtobool(value)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise_option_error(parser, option=option, msg=str(exc))
|
||||||
|
|
||||||
|
# Originally, setting PIP_NO_CACHE_DIR to a value that strtobool()
|
||||||
|
# converted to 0 (like "false" or "no") caused cache_dir to be disabled
|
||||||
|
# rather than enabled (logic would say the latter). Thus, we disable
|
||||||
|
# the cache directory not just on values that parse to True, but (for
|
||||||
|
# backwards compatibility reasons) also on values that parse to False.
|
||||||
|
# In other words, always set it to False if the option is provided in
|
||||||
|
# some (valid) form.
|
||||||
|
parser.values.cache_dir = False
|
||||||
|
|
||||||
|
|
||||||
|
no_cache = partial(
|
||||||
|
Option,
|
||||||
|
"--no-cache-dir",
|
||||||
|
dest="cache_dir",
|
||||||
|
action="callback",
|
||||||
|
callback=no_cache_dir_callback,
|
||||||
|
help="Disable the cache.",
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
no_deps = partial(
|
||||||
|
Option,
|
||||||
|
'--no-deps', '--no-dependencies',
|
||||||
|
dest='ignore_dependencies',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help="Don't install package dependencies.",
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
build_dir = partial(
|
||||||
|
Option,
|
||||||
|
'-b', '--build', '--build-dir', '--build-directory',
|
||||||
|
dest='build_dir',
|
||||||
|
metavar='dir',
|
||||||
|
help='Directory to unpack packages into and build in. Note that '
|
||||||
|
'an initial build still takes place in a temporary directory. '
|
||||||
|
'The location of temporary directories can be controlled by setting '
|
||||||
|
'the TMPDIR environment variable (TEMP on Windows) appropriately. '
|
||||||
|
'When passed, build directories are not cleaned in case of failures.'
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
ignore_requires_python = partial(
|
||||||
|
Option,
|
||||||
|
'--ignore-requires-python',
|
||||||
|
dest='ignore_requires_python',
|
||||||
|
action='store_true',
|
||||||
|
help='Ignore the Requires-Python information.'
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
no_build_isolation = partial(
|
||||||
|
Option,
|
||||||
|
'--no-build-isolation',
|
||||||
|
dest='build_isolation',
|
||||||
|
action='store_false',
|
||||||
|
default=True,
|
||||||
|
help='Disable isolation when building a modern source distribution. '
|
||||||
|
'Build dependencies specified by PEP 518 must be already installed '
|
||||||
|
'if this option is used.'
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
|
||||||
|
def no_use_pep517_callback(option, opt, value, parser):
|
||||||
|
"""
|
||||||
|
Process a value provided for the --no-use-pep517 option.
|
||||||
|
|
||||||
|
This is an optparse.Option callback for the no_use_pep517 option.
|
||||||
|
"""
|
||||||
|
# Since --no-use-pep517 doesn't accept arguments, the value argument
|
||||||
|
# will be None if --no-use-pep517 is passed via the command-line.
|
||||||
|
# However, the value can be non-None if the option is triggered e.g.
|
||||||
|
# by an environment variable, for example "PIP_NO_USE_PEP517=true".
|
||||||
|
if value is not None:
|
||||||
|
msg = """A value was passed for --no-use-pep517,
|
||||||
|
probably using either the PIP_NO_USE_PEP517 environment variable
|
||||||
|
or the "no-use-pep517" config file option. Use an appropriate value
|
||||||
|
of the PIP_USE_PEP517 environment variable or the "use-pep517"
|
||||||
|
config file option instead.
|
||||||
|
"""
|
||||||
|
raise_option_error(parser, option=option, msg=msg)
|
||||||
|
|
||||||
|
# Otherwise, --no-use-pep517 was passed via the command-line.
|
||||||
|
parser.values.use_pep517 = False
|
||||||
|
|
||||||
|
|
||||||
|
use_pep517 = partial(
|
||||||
|
Option,
|
||||||
|
'--use-pep517',
|
||||||
|
dest='use_pep517',
|
||||||
|
action='store_true',
|
||||||
|
default=None,
|
||||||
|
help='Use PEP 517 for building source distributions '
|
||||||
|
'(use --no-use-pep517 to force legacy behaviour).'
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
no_use_pep517 = partial(
|
||||||
|
Option,
|
||||||
|
'--no-use-pep517',
|
||||||
|
dest='use_pep517',
|
||||||
|
action='callback',
|
||||||
|
callback=no_use_pep517_callback,
|
||||||
|
default=None,
|
||||||
|
help=SUPPRESS_HELP
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
install_options = partial(
|
||||||
|
Option,
|
||||||
|
'--install-option',
|
||||||
|
dest='install_options',
|
||||||
|
action='append',
|
||||||
|
metavar='options',
|
||||||
|
help="Extra arguments to be supplied to the setup.py install "
|
||||||
|
"command (use like --install-option=\"--install-scripts=/usr/local/"
|
||||||
|
"bin\"). Use multiple --install-option options to pass multiple "
|
||||||
|
"options to setup.py install. If you are using an option with a "
|
||||||
|
"directory path, be sure to use absolute path.",
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
global_options = partial(
|
||||||
|
Option,
|
||||||
|
'--global-option',
|
||||||
|
dest='global_options',
|
||||||
|
action='append',
|
||||||
|
metavar='options',
|
||||||
|
help="Extra global options to be supplied to the setup.py "
|
||||||
|
"call before the install command.",
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
no_clean = partial(
|
||||||
|
Option,
|
||||||
|
'--no-clean',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help="Don't clean up build directories."
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
pre = partial(
|
||||||
|
Option,
|
||||||
|
'--pre',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help="Include pre-release and development versions. By default, "
|
||||||
|
"pip only finds stable versions.",
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
disable_pip_version_check = partial(
|
||||||
|
Option,
|
||||||
|
"--disable-pip-version-check",
|
||||||
|
dest="disable_pip_version_check",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="Don't periodically check PyPI to determine whether a new version "
|
||||||
|
"of pip is available for download. Implied with --no-index.",
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
|
||||||
|
# Deprecated, Remove later
|
||||||
|
always_unzip = partial(
|
||||||
|
Option,
|
||||||
|
'-Z', '--always-unzip',
|
||||||
|
dest='always_unzip',
|
||||||
|
action='store_true',
|
||||||
|
help=SUPPRESS_HELP,
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
|
||||||
|
def _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
|
||||||
|
try:
|
||||||
|
algo, digest = value.split(':', 1)
|
||||||
|
except ValueError:
|
||||||
|
parser.error('Arguments to %s must be a hash name '
|
||||||
|
'followed by a value, like --hash=sha256:abcde...' %
|
||||||
|
opt_str)
|
||||||
|
if algo not in STRONG_HASHES:
|
||||||
|
parser.error('Allowed hash algorithms for %s are %s.' %
|
||||||
|
(opt_str, ', '.join(STRONG_HASHES)))
|
||||||
|
parser.values.hashes.setdefault(algo, []).append(digest)
|
||||||
|
|
||||||
|
|
||||||
|
hash = partial(
|
||||||
|
Option,
|
||||||
|
'--hash',
|
||||||
|
# Hash values eventually end up in InstallRequirement.hashes due to
|
||||||
|
# __dict__ copying in process_line().
|
||||||
|
dest='hashes',
|
||||||
|
action='callback',
|
||||||
|
callback=_merge_hash,
|
||||||
|
type='string',
|
||||||
|
help="Verify that the package's archive matches this "
|
||||||
|
'hash before installing. Example: --hash=sha256:abcdef...',
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
|
||||||
|
require_hashes = partial(
|
||||||
|
Option,
|
||||||
|
'--require-hashes',
|
||||||
|
dest='require_hashes',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Require a hash to check each requirement against, for '
|
||||||
|
'repeatable installs. This option is implied when any package in a '
|
||||||
|
'requirements file has a --hash option.',
|
||||||
|
) # type: Callable[..., Option]
|
||||||
|
|
||||||
|
|
||||||
|
##########
|
||||||
|
# groups #
|
||||||
|
##########
|
||||||
|
|
||||||
|
general_group = {
|
||||||
|
'name': 'General Options',
|
||||||
|
'options': [
|
||||||
|
help_,
|
||||||
|
isolated_mode,
|
||||||
|
require_virtualenv,
|
||||||
|
verbose,
|
||||||
|
version,
|
||||||
|
quiet,
|
||||||
|
log,
|
||||||
|
no_input,
|
||||||
|
proxy,
|
||||||
|
retries,
|
||||||
|
timeout,
|
||||||
|
skip_requirements_regex,
|
||||||
|
exists_action,
|
||||||
|
trusted_host,
|
||||||
|
cert,
|
||||||
|
client_cert,
|
||||||
|
cache_dir,
|
||||||
|
no_cache,
|
||||||
|
disable_pip_version_check,
|
||||||
|
no_color,
|
||||||
|
]
|
||||||
|
} # type: Dict[str, Any]
|
||||||
|
|
||||||
|
index_group = {
|
||||||
|
'name': 'Package Index Options',
|
||||||
|
'options': [
|
||||||
|
index_url,
|
||||||
|
extra_index_url,
|
||||||
|
no_index,
|
||||||
|
find_links,
|
||||||
|
]
|
||||||
|
} # type: Dict[str, Any]
|
@@ -0,0 +1,104 @@
|
|||||||
|
"""A single place for constructing and exposing the main parser
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from pip import __version__
|
||||||
|
from pip._internal.cli import cmdoptions
|
||||||
|
from pip._internal.cli.parser import (
|
||||||
|
ConfigOptionParser, UpdatingDefaultsHelpFormatter,
|
||||||
|
)
|
||||||
|
from pip._internal.commands import (
|
||||||
|
commands_dict, get_similar_commands, get_summaries,
|
||||||
|
)
|
||||||
|
from pip._internal.exceptions import CommandError
|
||||||
|
from pip._internal.utils.misc import get_prog
|
||||||
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
|
if MYPY_CHECK_RUNNING:
|
||||||
|
from typing import Tuple, List # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["create_main_parser", "parse_command"]
|
||||||
|
|
||||||
|
|
||||||
|
def create_main_parser():
|
||||||
|
# type: () -> ConfigOptionParser
|
||||||
|
"""Creates and returns the main parser for pip's CLI
|
||||||
|
"""
|
||||||
|
|
||||||
|
parser_kw = {
|
||||||
|
'usage': '\n%prog <command> [options]',
|
||||||
|
'add_help_option': False,
|
||||||
|
'formatter': UpdatingDefaultsHelpFormatter(),
|
||||||
|
'name': 'global',
|
||||||
|
'prog': get_prog(),
|
||||||
|
}
|
||||||
|
|
||||||
|
parser = ConfigOptionParser(**parser_kw)
|
||||||
|
parser.disable_interspersed_args()
|
||||||
|
|
||||||
|
pip_pkg_dir = os.path.abspath(os.path.join(
|
||||||
|
os.path.dirname(__file__), "..", "..",
|
||||||
|
))
|
||||||
|
parser.version = 'pip %s from %s (python %s)' % (
|
||||||
|
__version__, pip_pkg_dir, sys.version[:3],
|
||||||
|
)
|
||||||
|
|
||||||
|
# add the general options
|
||||||
|
gen_opts = cmdoptions.make_option_group(cmdoptions.general_group, parser)
|
||||||
|
parser.add_option_group(gen_opts)
|
||||||
|
|
||||||
|
# so the help formatter knows
|
||||||
|
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]
|
||||||
|
parser.description = '\n'.join(description)
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def parse_command(args):
|
||||||
|
# type: (List[str]) -> Tuple[str, List[str]]
|
||||||
|
parser = create_main_parser()
|
||||||
|
|
||||||
|
# Note: parser calls disable_interspersed_args(), so the result of this
|
||||||
|
# call is to split the initial args into the general options before the
|
||||||
|
# subcommand and everything else.
|
||||||
|
# For example:
|
||||||
|
# args: ['--timeout=5', 'install', '--user', 'INITools']
|
||||||
|
# general_options: ['--timeout==5']
|
||||||
|
# args_else: ['install', '--user', 'INITools']
|
||||||
|
general_options, args_else = parser.parse_args(args)
|
||||||
|
|
||||||
|
# --version
|
||||||
|
if general_options.version:
|
||||||
|
sys.stdout.write(parser.version) # type: ignore
|
||||||
|
sys.stdout.write(os.linesep)
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
# pip || pip help -> print_help()
|
||||||
|
if not args_else or (args_else[0] == 'help' and len(args_else) == 1):
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
# the subcommand name
|
||||||
|
cmd_name = args_else[0]
|
||||||
|
|
||||||
|
if cmd_name not in commands_dict:
|
||||||
|
guess = get_similar_commands(cmd_name)
|
||||||
|
|
||||||
|
msg = ['unknown command "%s"' % cmd_name]
|
||||||
|
if guess:
|
||||||
|
msg.append('maybe you meant "%s"' % guess)
|
||||||
|
|
||||||
|
raise CommandError(' - '.join(msg))
|
||||||
|
|
||||||
|
# all the args without the subcommand
|
||||||
|
cmd_args = args[:]
|
||||||
|
cmd_args.remove(cmd_name)
|
||||||
|
|
||||||
|
return cmd_name, cmd_args
|
@@ -0,0 +1,261 @@
|
|||||||
|
"""Base option parser setup"""
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import optparse
|
||||||
|
import sys
|
||||||
|
import textwrap
|
||||||
|
from distutils.util import strtobool
|
||||||
|
|
||||||
|
from pip._vendor.six import string_types
|
||||||
|
|
||||||
|
from pip._internal.cli.status_codes import UNKNOWN_ERROR
|
||||||
|
from pip._internal.configuration import Configuration, ConfigurationError
|
||||||
|
from pip._internal.utils.compat import get_terminal_size
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
|
||||||
|
"""A prettier/less verbose help formatter for optparse."""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
# help position must be aligned with __init__.parseopts.description
|
||||||
|
kwargs['max_help_position'] = 30
|
||||||
|
kwargs['indent_increment'] = 1
|
||||||
|
kwargs['width'] = get_terminal_size()[0] - 2
|
||||||
|
optparse.IndentedHelpFormatter.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
def format_option_strings(self, option):
|
||||||
|
return self._format_option_strings(option, ' <%s>', ', ')
|
||||||
|
|
||||||
|
def _format_option_strings(self, option, mvarfmt=' <%s>', optsep=', '):
|
||||||
|
"""
|
||||||
|
Return a comma-separated list of option strings and metavars.
|
||||||
|
|
||||||
|
:param option: tuple of (short opt, long opt), e.g: ('-f', '--format')
|
||||||
|
:param mvarfmt: metavar format string - evaluated as mvarfmt % metavar
|
||||||
|
:param optsep: separator
|
||||||
|
"""
|
||||||
|
opts = []
|
||||||
|
|
||||||
|
if option._short_opts:
|
||||||
|
opts.append(option._short_opts[0])
|
||||||
|
if option._long_opts:
|
||||||
|
opts.append(option._long_opts[0])
|
||||||
|
if len(opts) > 1:
|
||||||
|
opts.insert(1, optsep)
|
||||||
|
|
||||||
|
if option.takes_value():
|
||||||
|
metavar = option.metavar or option.dest.lower()
|
||||||
|
opts.append(mvarfmt % metavar.lower())
|
||||||
|
|
||||||
|
return ''.join(opts)
|
||||||
|
|
||||||
|
def format_heading(self, heading):
|
||||||
|
if heading == 'Options':
|
||||||
|
return ''
|
||||||
|
return heading + ':\n'
|
||||||
|
|
||||||
|
def format_usage(self, usage):
|
||||||
|
"""
|
||||||
|
Ensure there is only one newline between usage and the first heading
|
||||||
|
if there is no description.
|
||||||
|
"""
|
||||||
|
msg = '\nUsage: %s\n' % self.indent_lines(textwrap.dedent(usage), " ")
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def format_description(self, description):
|
||||||
|
# leave full control over description to us
|
||||||
|
if description:
|
||||||
|
if hasattr(self.parser, 'main'):
|
||||||
|
label = 'Commands'
|
||||||
|
else:
|
||||||
|
label = 'Description'
|
||||||
|
# some doc strings have initial newlines, some don't
|
||||||
|
description = description.lstrip('\n')
|
||||||
|
# some doc strings have final newlines and spaces, some don't
|
||||||
|
description = description.rstrip()
|
||||||
|
# dedent, then reindent
|
||||||
|
description = self.indent_lines(textwrap.dedent(description), " ")
|
||||||
|
description = '%s:\n%s\n' % (label, description)
|
||||||
|
return description
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def format_epilog(self, epilog):
|
||||||
|
# leave full control over epilog to us
|
||||||
|
if epilog:
|
||||||
|
return epilog
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def indent_lines(self, text, indent):
|
||||||
|
new_lines = [indent + line for line in text.split('\n')]
|
||||||
|
return "\n".join(new_lines)
|
||||||
|
|
||||||
|
|
||||||
|
class UpdatingDefaultsHelpFormatter(PrettyHelpFormatter):
|
||||||
|
"""Custom help formatter for use in ConfigOptionParser.
|
||||||
|
|
||||||
|
This is updates the defaults before expanding them, allowing
|
||||||
|
them to show up correctly in the help listing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def expand_default(self, option):
|
||||||
|
if self.parser is not None:
|
||||||
|
self.parser._update_defaults(self.parser.defaults)
|
||||||
|
return optparse.IndentedHelpFormatter.expand_default(self, option)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomOptionParser(optparse.OptionParser):
|
||||||
|
|
||||||
|
def insert_option_group(self, idx, *args, **kwargs):
|
||||||
|
"""Insert an OptionGroup at a given position."""
|
||||||
|
group = self.add_option_group(*args, **kwargs)
|
||||||
|
|
||||||
|
self.option_groups.pop()
|
||||||
|
self.option_groups.insert(idx, group)
|
||||||
|
|
||||||
|
return group
|
||||||
|
|
||||||
|
@property
|
||||||
|
def option_list_all(self):
|
||||||
|
"""Get a list of all options, including those in option groups."""
|
||||||
|
res = self.option_list[:]
|
||||||
|
for i in self.option_groups:
|
||||||
|
res.extend(i.option_list)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigOptionParser(CustomOptionParser):
|
||||||
|
"""Custom option parser which updates its defaults by checking the
|
||||||
|
configuration files and environmental variables"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.name = kwargs.pop('name')
|
||||||
|
|
||||||
|
isolated = kwargs.pop("isolated", False)
|
||||||
|
self.config = Configuration(isolated)
|
||||||
|
|
||||||
|
assert self.name
|
||||||
|
optparse.OptionParser.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
def check_default(self, option, key, val):
|
||||||
|
try:
|
||||||
|
return option.check_value(key, val)
|
||||||
|
except optparse.OptionValueError as exc:
|
||||||
|
print("An error occurred during configuration: %s" % exc)
|
||||||
|
sys.exit(3)
|
||||||
|
|
||||||
|
def _get_ordered_configuration_items(self):
|
||||||
|
# Configuration gives keys in an unordered manner. Order them.
|
||||||
|
override_order = ["global", self.name, ":env:"]
|
||||||
|
|
||||||
|
# Pool the options into different groups
|
||||||
|
section_items = {name: [] for name in override_order}
|
||||||
|
for section_key, val in self.config.items():
|
||||||
|
# ignore empty values
|
||||||
|
if not val:
|
||||||
|
logger.debug(
|
||||||
|
"Ignoring configuration key '%s' as it's value is empty.",
|
||||||
|
section_key
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
section, key = section_key.split(".", 1)
|
||||||
|
if section in override_order:
|
||||||
|
section_items[section].append((key, val))
|
||||||
|
|
||||||
|
# Yield each group in their override order
|
||||||
|
for section in override_order:
|
||||||
|
for key, val in section_items[section]:
|
||||||
|
yield key, val
|
||||||
|
|
||||||
|
def _update_defaults(self, defaults):
|
||||||
|
"""Updates the given defaults with values from the config files and
|
||||||
|
the environ. Does a little special handling for certain types of
|
||||||
|
options (lists)."""
|
||||||
|
|
||||||
|
# Accumulate complex default state.
|
||||||
|
self.values = optparse.Values(self.defaults)
|
||||||
|
late_eval = set()
|
||||||
|
# Then set the options with those values
|
||||||
|
for key, val in self._get_ordered_configuration_items():
|
||||||
|
# '--' because configuration supports only long names
|
||||||
|
option = self.get_option('--' + key)
|
||||||
|
|
||||||
|
# Ignore options not present in this parser. E.g. non-globals put
|
||||||
|
# in [global] by users that want them to apply to all applicable
|
||||||
|
# commands.
|
||||||
|
if option is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if option.action in ('store_true', 'store_false', 'count'):
|
||||||
|
try:
|
||||||
|
val = strtobool(val)
|
||||||
|
except ValueError:
|
||||||
|
error_msg = invalid_config_error_message(
|
||||||
|
option.action, key, val
|
||||||
|
)
|
||||||
|
self.error(error_msg)
|
||||||
|
|
||||||
|
elif option.action == 'append':
|
||||||
|
val = val.split()
|
||||||
|
val = [self.check_default(option, key, v) for v in val]
|
||||||
|
elif option.action == 'callback':
|
||||||
|
late_eval.add(option.dest)
|
||||||
|
opt_str = option.get_opt_string()
|
||||||
|
val = option.convert_value(opt_str, val)
|
||||||
|
# From take_action
|
||||||
|
args = option.callback_args or ()
|
||||||
|
kwargs = option.callback_kwargs or {}
|
||||||
|
option.callback(option, opt_str, val, self, *args, **kwargs)
|
||||||
|
else:
|
||||||
|
val = self.check_default(option, key, val)
|
||||||
|
|
||||||
|
defaults[option.dest] = val
|
||||||
|
|
||||||
|
for key in late_eval:
|
||||||
|
defaults[key] = getattr(self.values, key)
|
||||||
|
self.values = None
|
||||||
|
return defaults
|
||||||
|
|
||||||
|
def get_default_values(self):
|
||||||
|
"""Overriding to make updating the defaults after instantiation of
|
||||||
|
the option parser possible, _update_defaults() does the dirty work."""
|
||||||
|
if not self.process_default_values:
|
||||||
|
# Old, pre-Optik 1.5 behaviour.
|
||||||
|
return optparse.Values(self.defaults)
|
||||||
|
|
||||||
|
# Load the configuration, or error out in case of an error
|
||||||
|
try:
|
||||||
|
self.config.load()
|
||||||
|
except ConfigurationError as err:
|
||||||
|
self.exit(UNKNOWN_ERROR, str(err))
|
||||||
|
|
||||||
|
defaults = self._update_defaults(self.defaults.copy()) # ours
|
||||||
|
for option in self._get_all_options():
|
||||||
|
default = defaults.get(option.dest)
|
||||||
|
if isinstance(default, string_types):
|
||||||
|
opt_str = option.get_opt_string()
|
||||||
|
defaults[option.dest] = option.check_value(opt_str, default)
|
||||||
|
return optparse.Values(defaults)
|
||||||
|
|
||||||
|
def error(self, msg):
|
||||||
|
self.print_usage(sys.stderr)
|
||||||
|
self.exit(UNKNOWN_ERROR, "%s\n" % msg)
|
||||||
|
|
||||||
|
|
||||||
|
def invalid_config_error_message(action, key, val):
|
||||||
|
"""Returns a better error message when invalid configuration option
|
||||||
|
is provided."""
|
||||||
|
if action in ('store_true', 'store_false'):
|
||||||
|
return ("{0} is not a valid value for {1} option, "
|
||||||
|
"please specify a boolean value like yes/no, "
|
||||||
|
"true/false or 1/0 instead.").format(val, key)
|
||||||
|
|
||||||
|
return ("{0} is not a valid value for {1} option, "
|
||||||
|
"please specify a numerical value like 1/0 "
|
||||||
|
"instead.").format(val, key)
|
@@ -0,0 +1,8 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
SUCCESS = 0
|
||||||
|
ERROR = 1
|
||||||
|
UNKNOWN_ERROR = 2
|
||||||
|
VIRTUALENV_NOT_FOUND = 3
|
||||||
|
PREVIOUS_BUILD_DIR_ERROR = 4
|
||||||
|
NO_MATCHES_FOUND = 23
|
@@ -0,0 +1,79 @@
|
|||||||
|
"""
|
||||||
|
Package containing all pip commands
|
||||||
|
"""
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from pip._internal.commands.completion import CompletionCommand
|
||||||
|
from pip._internal.commands.configuration import ConfigurationCommand
|
||||||
|
from pip._internal.commands.download import DownloadCommand
|
||||||
|
from pip._internal.commands.freeze import FreezeCommand
|
||||||
|
from pip._internal.commands.hash import HashCommand
|
||||||
|
from pip._internal.commands.help import HelpCommand
|
||||||
|
from pip._internal.commands.list import ListCommand
|
||||||
|
from pip._internal.commands.check import CheckCommand
|
||||||
|
from pip._internal.commands.search import SearchCommand
|
||||||
|
from pip._internal.commands.show import ShowCommand
|
||||||
|
from pip._internal.commands.install import InstallCommand
|
||||||
|
from pip._internal.commands.uninstall import UninstallCommand
|
||||||
|
from pip._internal.commands.wheel import WheelCommand
|
||||||
|
|
||||||
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
|
if MYPY_CHECK_RUNNING:
|
||||||
|
from typing import List, Type # noqa: F401
|
||||||
|
from pip._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}
|
||||||
|
|
||||||
|
|
||||||
|
def get_summaries(ordered=True):
|
||||||
|
"""Yields sorted (command name, command summary) tuples."""
|
||||||
|
|
||||||
|
if ordered:
|
||||||
|
cmditems = _sort_commands(commands_dict, commands_order)
|
||||||
|
else:
|
||||||
|
cmditems = commands_dict.items()
|
||||||
|
|
||||||
|
for name, command_class in cmditems:
|
||||||
|
yield (name, command_class.summary)
|
||||||
|
|
||||||
|
|
||||||
|
def get_similar_commands(name):
|
||||||
|
"""Command name auto-correct."""
|
||||||
|
from difflib import get_close_matches
|
||||||
|
|
||||||
|
name = name.lower()
|
||||||
|
|
||||||
|
close_commands = get_close_matches(name, commands_dict.keys())
|
||||||
|
|
||||||
|
if close_commands:
|
||||||
|
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)
|
@@ -0,0 +1,41 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from pip._internal.cli.base_command import Command
|
||||||
|
from pip._internal.operations.check import (
|
||||||
|
check_package_set, create_package_set_from_installed,
|
||||||
|
)
|
||||||
|
|
||||||
|
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()
|
||||||
|
missing, conflicting = check_package_set(package_set)
|
||||||
|
|
||||||
|
for project_name in missing:
|
||||||
|
version = package_set[project_name].version
|
||||||
|
for dependency in missing[project_name]:
|
||||||
|
logger.info(
|
||||||
|
"%s %s requires %s, which is not installed.",
|
||||||
|
project_name, version, dependency[0],
|
||||||
|
)
|
||||||
|
|
||||||
|
for project_name in conflicting:
|
||||||
|
version = package_set[project_name].version
|
||||||
|
for dep_name, dep_version, req in conflicting[project_name]:
|
||||||
|
logger.info(
|
||||||
|
"%s %s has requirement %s, but you have %s %s.",
|
||||||
|
project_name, version, req, dep_name, dep_version,
|
||||||
|
)
|
||||||
|
|
||||||
|
if missing or conflicting or parsing_probs:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
logger.info("No broken requirements found.")
|
@@ -0,0 +1,94 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
from pip._internal.cli.base_command import Command
|
||||||
|
from pip._internal.utils.misc import get_prog
|
||||||
|
|
||||||
|
BASE_COMPLETION = """
|
||||||
|
# pip %(shell)s completion start%(script)s# pip %(shell)s completion end
|
||||||
|
"""
|
||||||
|
|
||||||
|
COMPLETION_SCRIPTS = {
|
||||||
|
'bash': """
|
||||||
|
_pip_completion()
|
||||||
|
{
|
||||||
|
COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \\
|
||||||
|
COMP_CWORD=$COMP_CWORD \\
|
||||||
|
PIP_AUTO_COMPLETE=1 $1 ) )
|
||||||
|
}
|
||||||
|
complete -o default -F _pip_completion %(prog)s
|
||||||
|
""",
|
||||||
|
'zsh': """
|
||||||
|
function _pip_completion {
|
||||||
|
local words cword
|
||||||
|
read -Ac words
|
||||||
|
read -cn cword
|
||||||
|
reply=( $( COMP_WORDS="$words[*]" \\
|
||||||
|
COMP_CWORD=$(( cword-1 )) \\
|
||||||
|
PIP_AUTO_COMPLETE=1 $words[1] ) )
|
||||||
|
}
|
||||||
|
compctl -K _pip_completion %(prog)s
|
||||||
|
""",
|
||||||
|
'fish': """
|
||||||
|
function __fish_complete_pip
|
||||||
|
set -lx COMP_WORDS (commandline -o) ""
|
||||||
|
set -lx COMP_CWORD ( \\
|
||||||
|
math (contains -i -- (commandline -t) $COMP_WORDS)-1 \\
|
||||||
|
)
|
||||||
|
set -lx PIP_AUTO_COMPLETE 1
|
||||||
|
string split \\ -- (eval $COMP_WORDS[1])
|
||||||
|
end
|
||||||
|
complete -fa "(__fish_complete_pip)" -c %(prog)s
|
||||||
|
""",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
super(CompletionCommand, self).__init__(*args, **kw)
|
||||||
|
|
||||||
|
cmd_opts = self.cmd_opts
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--bash', '-b',
|
||||||
|
action='store_const',
|
||||||
|
const='bash',
|
||||||
|
dest='shell',
|
||||||
|
help='Emit completion code for bash')
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--zsh', '-z',
|
||||||
|
action='store_const',
|
||||||
|
const='zsh',
|
||||||
|
dest='shell',
|
||||||
|
help='Emit completion code for zsh')
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--fish', '-f',
|
||||||
|
action='store_const',
|
||||||
|
const='fish',
|
||||||
|
dest='shell',
|
||||||
|
help='Emit completion code for fish')
|
||||||
|
|
||||||
|
self.parser.insert_option_group(0, cmd_opts)
|
||||||
|
|
||||||
|
def run(self, options, args):
|
||||||
|
"""Prints the completion code of the given shell"""
|
||||||
|
shells = COMPLETION_SCRIPTS.keys()
|
||||||
|
shell_options = ['--' + shell for shell in sorted(shells)]
|
||||||
|
if options.shell in shells:
|
||||||
|
script = textwrap.dedent(
|
||||||
|
COMPLETION_SCRIPTS.get(options.shell, '') % {
|
||||||
|
'prog': get_prog(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
print(BASE_COMPLETION % {'script': script, 'shell': options.shell})
|
||||||
|
else:
|
||||||
|
sys.stderr.write(
|
||||||
|
'ERROR: You must pass %s\n' % ' or '.join(shell_options)
|
||||||
|
)
|
@@ -0,0 +1,227 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from pip._internal.cli.base_command import Command
|
||||||
|
from pip._internal.cli.status_codes import ERROR, SUCCESS
|
||||||
|
from pip._internal.configuration import Configuration, kinds
|
||||||
|
from pip._internal.exceptions import PipError
|
||||||
|
from pip._internal.locations import venv_config_file
|
||||||
|
from pip._internal.utils.misc import get_prog
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigurationCommand(Command):
|
||||||
|
"""Manage local and global configuration.
|
||||||
|
|
||||||
|
Subcommands:
|
||||||
|
|
||||||
|
list: List the active configuration (or from the file specified)
|
||||||
|
edit: Edit the configuration file in an editor
|
||||||
|
get: Get the value associated with name
|
||||||
|
set: Set the name=value
|
||||||
|
unset: Unset the value associated with name
|
||||||
|
|
||||||
|
If none of --user, --global and --venv 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'
|
||||||
|
usage = """
|
||||||
|
%prog [<file-option>] list
|
||||||
|
%prog [<file-option>] [--editor <editor-path>] edit
|
||||||
|
|
||||||
|
%prog [<file-option>] get name
|
||||||
|
%prog [<file-option>] set name value
|
||||||
|
%prog [<file-option>] unset name
|
||||||
|
"""
|
||||||
|
|
||||||
|
summary = "Manage local and global configuration."
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(ConfigurationCommand, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.configuration = None
|
||||||
|
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'--editor',
|
||||||
|
dest='editor',
|
||||||
|
action='store',
|
||||||
|
default=None,
|
||||||
|
help=(
|
||||||
|
'Editor to use to edit the file. Uses VISUAL or EDITOR '
|
||||||
|
'environment variables if not provided.'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'--global',
|
||||||
|
dest='global_file',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Use the system-wide configuration file only'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'--user',
|
||||||
|
dest='user_file',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Use the user configuration file only'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'--venv',
|
||||||
|
dest='venv_file',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Use the virtualenv configuration file only'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.parser.insert_option_group(0, self.cmd_opts)
|
||||||
|
|
||||||
|
def run(self, options, args):
|
||||||
|
handlers = {
|
||||||
|
"list": self.list_values,
|
||||||
|
"edit": self.open_in_editor,
|
||||||
|
"get": self.get_name,
|
||||||
|
"set": self.set_name_value,
|
||||||
|
"unset": self.unset_name
|
||||||
|
}
|
||||||
|
|
||||||
|
# Determine action
|
||||||
|
if not args or args[0] not in handlers:
|
||||||
|
logger.error("Need an action ({}) to perform.".format(
|
||||||
|
", ".join(sorted(handlers)))
|
||||||
|
)
|
||||||
|
return ERROR
|
||||||
|
|
||||||
|
action = args[0]
|
||||||
|
|
||||||
|
# Determine which configuration files are to be loaded
|
||||||
|
# Depends on whether the command is modifying.
|
||||||
|
try:
|
||||||
|
load_only = self._determine_file(
|
||||||
|
options, need_value=(action in ["get", "set", "unset", "edit"])
|
||||||
|
)
|
||||||
|
except PipError as e:
|
||||||
|
logger.error(e.args[0])
|
||||||
|
return ERROR
|
||||||
|
|
||||||
|
# Load a new configuration
|
||||||
|
self.configuration = Configuration(
|
||||||
|
isolated=options.isolated_mode, load_only=load_only
|
||||||
|
)
|
||||||
|
self.configuration.load()
|
||||||
|
|
||||||
|
# Error handling happens here, not in the action-handlers.
|
||||||
|
try:
|
||||||
|
handlers[action](options, args[1:])
|
||||||
|
except PipError as e:
|
||||||
|
logger.error(e.args[0])
|
||||||
|
return ERROR
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
if sum(file_options.values()) == 0:
|
||||||
|
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
|
||||||
|
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]
|
||||||
|
|
||||||
|
raise PipError(
|
||||||
|
"Need exactly one file to operate upon "
|
||||||
|
"(--user, --venv, --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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
def set_name_value(self, options, args):
|
||||||
|
key, value = self._get_n_args(args, "set [name] [value]", n=2)
|
||||||
|
self.configuration.set_value(key, value)
|
||||||
|
|
||||||
|
self._save_configuration()
|
||||||
|
|
||||||
|
def unset_name(self, options, args):
|
||||||
|
key = self._get_n_args(args, "unset [name]", n=1)
|
||||||
|
self.configuration.unset_value(key)
|
||||||
|
|
||||||
|
self._save_configuration()
|
||||||
|
|
||||||
|
def open_in_editor(self, options, args):
|
||||||
|
editor = self._determine_editor(options)
|
||||||
|
|
||||||
|
fname = self.configuration.get_file_to_edit()
|
||||||
|
if fname is None:
|
||||||
|
raise PipError("Could not determine appropriate file.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
subprocess.check_call([editor, fname])
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
raise PipError(
|
||||||
|
"Editor Subprocess exited with exit code {}"
|
||||||
|
.format(e.returncode)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_n_args(self, args, example, n):
|
||||||
|
"""Helper to make sure the command got the right number of arguments
|
||||||
|
"""
|
||||||
|
if len(args) != n:
|
||||||
|
msg = (
|
||||||
|
'Got unexpected number of arguments, expected {}. '
|
||||||
|
'(example: "{} config {}")'
|
||||||
|
).format(n, get_prog(), example)
|
||||||
|
raise PipError(msg)
|
||||||
|
|
||||||
|
if n == 1:
|
||||||
|
return args[0]
|
||||||
|
else:
|
||||||
|
return args
|
||||||
|
|
||||||
|
def _save_configuration(self):
|
||||||
|
# We successfully ran a modifying command. Need to save the
|
||||||
|
# configuration.
|
||||||
|
try:
|
||||||
|
self.configuration.save()
|
||||||
|
except Exception:
|
||||||
|
logger.error(
|
||||||
|
"Unable to save configuration. Please report this as a bug.",
|
||||||
|
exc_info=1
|
||||||
|
)
|
||||||
|
raise PipError("Internal Error.")
|
||||||
|
|
||||||
|
def _determine_editor(self, options):
|
||||||
|
if options.editor is not None:
|
||||||
|
return options.editor
|
||||||
|
elif "VISUAL" in os.environ:
|
||||||
|
return os.environ["VISUAL"]
|
||||||
|
elif "EDITOR" in os.environ:
|
||||||
|
return os.environ["EDITOR"]
|
||||||
|
else:
|
||||||
|
raise PipError("Could not determine editor to use.")
|
@@ -0,0 +1,176 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from pip._internal.cli import cmdoptions
|
||||||
|
from pip._internal.cli.base_command import RequirementCommand
|
||||||
|
from pip._internal.operations.prepare import RequirementPreparer
|
||||||
|
from pip._internal.req import RequirementSet
|
||||||
|
from pip._internal.req.req_tracker import RequirementTracker
|
||||||
|
from pip._internal.resolve import Resolver
|
||||||
|
from pip._internal.utils.filesystem import check_path_owner
|
||||||
|
from pip._internal.utils.misc import ensure_dir, normalize_path
|
||||||
|
from pip._internal.utils.temp_dir import TempDirectory
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadCommand(RequirementCommand):
|
||||||
|
"""
|
||||||
|
Download packages from:
|
||||||
|
|
||||||
|
- PyPI (and other indexes) using requirement specifiers.
|
||||||
|
- VCS project urls.
|
||||||
|
- Local project directories.
|
||||||
|
- Local or remote source archives.
|
||||||
|
|
||||||
|
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] ...
|
||||||
|
%prog [options] -r <requirements file> [package-index-options] ...
|
||||||
|
%prog [options] <vcs project url> ...
|
||||||
|
%prog [options] <local project path> ...
|
||||||
|
%prog [options] <archive url/path> ..."""
|
||||||
|
|
||||||
|
summary = 'Download packages.'
|
||||||
|
|
||||||
|
def __init__(self, *args, **kw):
|
||||||
|
super(DownloadCommand, self).__init__(*args, **kw)
|
||||||
|
|
||||||
|
cmd_opts = self.cmd_opts
|
||||||
|
|
||||||
|
cmd_opts.add_option(cmdoptions.constraints())
|
||||||
|
cmd_opts.add_option(cmdoptions.requirements())
|
||||||
|
cmd_opts.add_option(cmdoptions.build_dir())
|
||||||
|
cmd_opts.add_option(cmdoptions.no_deps())
|
||||||
|
cmd_opts.add_option(cmdoptions.global_options())
|
||||||
|
cmd_opts.add_option(cmdoptions.no_binary())
|
||||||
|
cmd_opts.add_option(cmdoptions.only_binary())
|
||||||
|
cmd_opts.add_option(cmdoptions.prefer_binary())
|
||||||
|
cmd_opts.add_option(cmdoptions.src())
|
||||||
|
cmd_opts.add_option(cmdoptions.pre())
|
||||||
|
cmd_opts.add_option(cmdoptions.no_clean())
|
||||||
|
cmd_opts.add_option(cmdoptions.require_hashes())
|
||||||
|
cmd_opts.add_option(cmdoptions.progress_bar())
|
||||||
|
cmd_opts.add_option(cmdoptions.no_build_isolation())
|
||||||
|
cmd_opts.add_option(cmdoptions.use_pep517())
|
||||||
|
cmd_opts.add_option(cmdoptions.no_use_pep517())
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'-d', '--dest', '--destination-dir', '--destination-directory',
|
||||||
|
dest='download_dir',
|
||||||
|
metavar='dir',
|
||||||
|
default=os.curdir,
|
||||||
|
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())
|
||||||
|
|
||||||
|
index_opts = cmdoptions.make_option_group(
|
||||||
|
cmdoptions.index_group,
|
||||||
|
self.parser,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.parser.insert_option_group(0, index_opts)
|
||||||
|
self.parser.insert_option_group(0, cmd_opts)
|
||||||
|
|
||||||
|
def run(self, options, args):
|
||||||
|
options.ignore_installed = True
|
||||||
|
# editable doesn't really make sense for `pip download`, but the bowels
|
||||||
|
# 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)
|
||||||
|
options.download_dir = normalize_path(options.download_dir)
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
downloaded = ' '.join([
|
||||||
|
req.name for req in requirement_set.successfully_downloaded
|
||||||
|
])
|
||||||
|
if downloaded:
|
||||||
|
logger.info('Successfully downloaded %s', downloaded)
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
if not options.no_clean:
|
||||||
|
requirement_set.cleanup_files()
|
||||||
|
|
||||||
|
return requirement_set
|
@@ -0,0 +1,96 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from pip._internal.cache import WheelCache
|
||||||
|
from pip._internal.cli.base_command import Command
|
||||||
|
from pip._internal.models.format_control import FormatControl
|
||||||
|
from pip._internal.operations.freeze import freeze
|
||||||
|
from pip._internal.utils.compat import stdlib_pkgs
|
||||||
|
|
||||||
|
DEV_PKGS = {'pip', 'setuptools', 'distribute', 'wheel'}
|
||||||
|
|
||||||
|
|
||||||
|
class FreezeCommand(Command):
|
||||||
|
"""
|
||||||
|
Output installed packages in requirements format.
|
||||||
|
|
||||||
|
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):
|
||||||
|
super(FreezeCommand, self).__init__(*args, **kw)
|
||||||
|
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'-r', '--requirement',
|
||||||
|
dest='requirements',
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
metavar='file',
|
||||||
|
help="Use the order in the given requirements file and its "
|
||||||
|
"comments when generating output. This option can be "
|
||||||
|
"used multiple times.")
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'-f', '--find-links',
|
||||||
|
dest='find_links',
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
metavar='URL',
|
||||||
|
help='URL for finding packages, which will be added to the '
|
||||||
|
'output.')
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'-l', '--local',
|
||||||
|
dest='local',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='If in a virtualenv that has global access, do not output '
|
||||||
|
'globally-installed packages.')
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'--user',
|
||||||
|
dest='user',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Only output packages installed in user-site.')
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'--all',
|
||||||
|
dest='freeze_all',
|
||||||
|
action='store_true',
|
||||||
|
help='Do not skip these packages in the output:'
|
||||||
|
' %s' % ', '.join(DEV_PKGS))
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'--exclude-editable',
|
||||||
|
dest='exclude_editable',
|
||||||
|
action='store_true',
|
||||||
|
help='Exclude editable package from output.')
|
||||||
|
|
||||||
|
self.parser.insert_option_group(0, self.cmd_opts)
|
||||||
|
|
||||||
|
def run(self, options, args):
|
||||||
|
format_control = FormatControl(set(), set())
|
||||||
|
wheel_cache = WheelCache(options.cache_dir, format_control)
|
||||||
|
skip = set(stdlib_pkgs)
|
||||||
|
if not options.freeze_all:
|
||||||
|
skip.update(DEV_PKGS)
|
||||||
|
|
||||||
|
freeze_kwargs = dict(
|
||||||
|
requirement=options.requirements,
|
||||||
|
find_links=options.find_links,
|
||||||
|
local_only=options.local,
|
||||||
|
user_only=options.user,
|
||||||
|
skip_regex=options.skip_requirements_regex,
|
||||||
|
isolated=options.isolated_mode,
|
||||||
|
wheel_cache=wheel_cache,
|
||||||
|
skip=skip,
|
||||||
|
exclude_editable=options.exclude_editable,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
for line in freeze(**freeze_kwargs):
|
||||||
|
sys.stdout.write(line + '\n')
|
||||||
|
finally:
|
||||||
|
wheel_cache.cleanup()
|
@@ -0,0 +1,57 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from pip._internal.cli.base_command import Command
|
||||||
|
from pip._internal.cli.status_codes import ERROR
|
||||||
|
from pip._internal.utils.hashes import FAVORITE_HASH, STRONG_HASHES
|
||||||
|
from pip._internal.utils.misc import read_chunks
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class HashCommand(Command):
|
||||||
|
"""
|
||||||
|
Compute a hash of a local package archive.
|
||||||
|
|
||||||
|
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):
|
||||||
|
super(HashCommand, self).__init__(*args, **kw)
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'-a', '--algorithm',
|
||||||
|
dest='algorithm',
|
||||||
|
choices=STRONG_HASHES,
|
||||||
|
action='store',
|
||||||
|
default=FAVORITE_HASH,
|
||||||
|
help='The hash algorithm to use: one of %s' %
|
||||||
|
', '.join(STRONG_HASHES))
|
||||||
|
self.parser.insert_option_group(0, self.cmd_opts)
|
||||||
|
|
||||||
|
def run(self, options, args):
|
||||||
|
if not args:
|
||||||
|
self.parser.print_usage(sys.stderr)
|
||||||
|
return ERROR
|
||||||
|
|
||||||
|
algorithm = options.algorithm
|
||||||
|
for path in args:
|
||||||
|
logger.info('%s:\n--hash=%s:%s',
|
||||||
|
path, algorithm, _hash_of_file(path, algorithm))
|
||||||
|
|
||||||
|
|
||||||
|
def _hash_of_file(path, algorithm):
|
||||||
|
"""Return the hash digest of a file."""
|
||||||
|
with open(path, 'rb') as archive:
|
||||||
|
hash = hashlib.new(algorithm)
|
||||||
|
for chunk in read_chunks(archive):
|
||||||
|
hash.update(chunk)
|
||||||
|
return hash.hexdigest()
|
@@ -0,0 +1,37 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from pip._internal.cli.base_command import Command
|
||||||
|
from pip._internal.cli.status_codes import SUCCESS
|
||||||
|
from pip._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 pip._internal.commands import commands_dict, get_similar_commands
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 'pip help' with no args is handled by pip.__init__.parseopt()
|
||||||
|
cmd_name = args[0] # the command we need help for
|
||||||
|
except IndexError:
|
||||||
|
return SUCCESS
|
||||||
|
|
||||||
|
if cmd_name not in commands_dict:
|
||||||
|
guess = get_similar_commands(cmd_name)
|
||||||
|
|
||||||
|
msg = ['unknown command "%s"' % cmd_name]
|
||||||
|
if guess:
|
||||||
|
msg.append('maybe you meant "%s"' % guess)
|
||||||
|
|
||||||
|
raise CommandError(' - '.join(msg))
|
||||||
|
|
||||||
|
command = commands_dict[cmd_name]()
|
||||||
|
command.parser.print_help()
|
||||||
|
|
||||||
|
return SUCCESS
|
@@ -0,0 +1,566 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import errno
|
||||||
|
import logging
|
||||||
|
import operator
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from optparse import SUPPRESS_HELP
|
||||||
|
|
||||||
|
from pip._vendor import pkg_resources
|
||||||
|
|
||||||
|
from pip._internal.cache import WheelCache
|
||||||
|
from pip._internal.cli import cmdoptions
|
||||||
|
from pip._internal.cli.base_command import RequirementCommand
|
||||||
|
from pip._internal.cli.status_codes import ERROR
|
||||||
|
from pip._internal.exceptions import (
|
||||||
|
CommandError, InstallationError, PreviousBuildDirError,
|
||||||
|
)
|
||||||
|
from pip._internal.locations import distutils_scheme, virtualenv_no_global
|
||||||
|
from pip._internal.operations.check import check_install_conflicts
|
||||||
|
from pip._internal.operations.prepare import RequirementPreparer
|
||||||
|
from pip._internal.req import RequirementSet, install_given_reqs
|
||||||
|
from pip._internal.req.req_tracker import RequirementTracker
|
||||||
|
from pip._internal.resolve import Resolver
|
||||||
|
from pip._internal.utils.filesystem import check_path_owner
|
||||||
|
from pip._internal.utils.misc import (
|
||||||
|
ensure_dir, get_installed_version,
|
||||||
|
protect_pip_from_modification_on_windows,
|
||||||
|
)
|
||||||
|
from pip._internal.utils.temp_dir import TempDirectory
|
||||||
|
from pip._internal.wheel import WheelBuilder
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class InstallCommand(RequirementCommand):
|
||||||
|
"""
|
||||||
|
Install packages from:
|
||||||
|
|
||||||
|
- PyPI (and other indexes) using requirement specifiers.
|
||||||
|
- VCS project urls.
|
||||||
|
- Local project directories.
|
||||||
|
- Local or remote source archives.
|
||||||
|
|
||||||
|
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] ...
|
||||||
|
%prog [options] -r <requirements file> [package-index-options] ...
|
||||||
|
%prog [options] [-e] <vcs project url> ...
|
||||||
|
%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)
|
||||||
|
|
||||||
|
cmd_opts = self.cmd_opts
|
||||||
|
|
||||||
|
cmd_opts.add_option(cmdoptions.requirements())
|
||||||
|
cmd_opts.add_option(cmdoptions.constraints())
|
||||||
|
cmd_opts.add_option(cmdoptions.no_deps())
|
||||||
|
cmd_opts.add_option(cmdoptions.pre())
|
||||||
|
|
||||||
|
cmd_opts.add_option(cmdoptions.editable())
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'-t', '--target',
|
||||||
|
dest='target_dir',
|
||||||
|
metavar='dir',
|
||||||
|
default=None,
|
||||||
|
help='Install packages into <dir>. '
|
||||||
|
'By default this will not replace existing files/folders in '
|
||||||
|
'<dir>. Use --upgrade to replace existing packages in <dir> '
|
||||||
|
'with new versions.'
|
||||||
|
)
|
||||||
|
cmd_opts.add_option(cmdoptions.platform())
|
||||||
|
cmd_opts.add_option(cmdoptions.python_version())
|
||||||
|
cmd_opts.add_option(cmdoptions.implementation())
|
||||||
|
cmd_opts.add_option(cmdoptions.abi())
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--user',
|
||||||
|
dest='use_user_site',
|
||||||
|
action='store_true',
|
||||||
|
help="Install to the Python user install directory for your "
|
||||||
|
"platform. Typically ~/.local/, or %APPDATA%\\Python on "
|
||||||
|
"Windows. (See the Python documentation for site.USER_BASE "
|
||||||
|
"for full details.)")
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--no-user',
|
||||||
|
dest='use_user_site',
|
||||||
|
action='store_false',
|
||||||
|
help=SUPPRESS_HELP)
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--root',
|
||||||
|
dest='root_path',
|
||||||
|
metavar='dir',
|
||||||
|
default=None,
|
||||||
|
help="Install everything relative to this alternate root "
|
||||||
|
"directory.")
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--prefix',
|
||||||
|
dest='prefix_path',
|
||||||
|
metavar='dir',
|
||||||
|
default=None,
|
||||||
|
help="Installation prefix where lib, bin and other top-level "
|
||||||
|
"folders are placed")
|
||||||
|
|
||||||
|
cmd_opts.add_option(cmdoptions.build_dir())
|
||||||
|
|
||||||
|
cmd_opts.add_option(cmdoptions.src())
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'-U', '--upgrade',
|
||||||
|
dest='upgrade',
|
||||||
|
action='store_true',
|
||||||
|
help='Upgrade all specified packages to the newest available '
|
||||||
|
'version. The handling of dependencies depends on the '
|
||||||
|
'upgrade-strategy used.'
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--upgrade-strategy',
|
||||||
|
dest='upgrade_strategy',
|
||||||
|
default='only-if-needed',
|
||||||
|
choices=['only-if-needed', 'eager'],
|
||||||
|
help='Determines how dependency upgrading should be handled '
|
||||||
|
'[default: %default]. '
|
||||||
|
'"eager" - dependencies are upgraded regardless of '
|
||||||
|
'whether the currently installed version satisfies the '
|
||||||
|
'requirements of the upgraded package(s). '
|
||||||
|
'"only-if-needed" - are upgraded only when they do not '
|
||||||
|
'satisfy the requirements of the upgraded package(s).'
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--force-reinstall',
|
||||||
|
dest='force_reinstall',
|
||||||
|
action='store_true',
|
||||||
|
help='Reinstall all packages even if they are already '
|
||||||
|
'up-to-date.')
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'-I', '--ignore-installed',
|
||||||
|
dest='ignore_installed',
|
||||||
|
action='store_true',
|
||||||
|
help='Ignore the installed packages (reinstalling instead).')
|
||||||
|
|
||||||
|
cmd_opts.add_option(cmdoptions.ignore_requires_python())
|
||||||
|
cmd_opts.add_option(cmdoptions.no_build_isolation())
|
||||||
|
cmd_opts.add_option(cmdoptions.use_pep517())
|
||||||
|
cmd_opts.add_option(cmdoptions.no_use_pep517())
|
||||||
|
|
||||||
|
cmd_opts.add_option(cmdoptions.install_options())
|
||||||
|
cmd_opts.add_option(cmdoptions.global_options())
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
"--compile",
|
||||||
|
action="store_true",
|
||||||
|
dest="compile",
|
||||||
|
default=True,
|
||||||
|
help="Compile Python source files to bytecode",
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
"--no-compile",
|
||||||
|
action="store_false",
|
||||||
|
dest="compile",
|
||||||
|
help="Do not compile Python source files to bytecode",
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
"--no-warn-script-location",
|
||||||
|
action="store_false",
|
||||||
|
dest="warn_script_location",
|
||||||
|
default=True,
|
||||||
|
help="Do not warn when installing scripts outside PATH",
|
||||||
|
)
|
||||||
|
cmd_opts.add_option(
|
||||||
|
"--no-warn-conflicts",
|
||||||
|
action="store_false",
|
||||||
|
dest="warn_about_conflicts",
|
||||||
|
default=True,
|
||||||
|
help="Do not warn about broken dependencies",
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd_opts.add_option(cmdoptions.no_binary())
|
||||||
|
cmd_opts.add_option(cmdoptions.only_binary())
|
||||||
|
cmd_opts.add_option(cmdoptions.prefer_binary())
|
||||||
|
cmd_opts.add_option(cmdoptions.no_clean())
|
||||||
|
cmd_opts.add_option(cmdoptions.require_hashes())
|
||||||
|
cmd_opts.add_option(cmdoptions.progress_bar())
|
||||||
|
|
||||||
|
index_opts = cmdoptions.make_option_group(
|
||||||
|
cmdoptions.index_group,
|
||||||
|
self.parser,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.parser.insert_option_group(0, index_opts)
|
||||||
|
self.parser.insert_option_group(0, cmd_opts)
|
||||||
|
|
||||||
|
def run(self, options, args):
|
||||||
|
cmdoptions.check_install_build_global(options)
|
||||||
|
upgrade_strategy = "to-satisfy-only"
|
||||||
|
if options.upgrade:
|
||||||
|
upgrade_strategy = options.upgrade_strategy
|
||||||
|
|
||||||
|
if options.build_dir:
|
||||||
|
options.build_dir = os.path.abspath(options.build_dir)
|
||||||
|
|
||||||
|
cmdoptions.check_dist_restriction(options, check_target=True)
|
||||||
|
|
||||||
|
if options.python_version:
|
||||||
|
python_versions = [options.python_version]
|
||||||
|
else:
|
||||||
|
python_versions = None
|
||||||
|
|
||||||
|
options.src_dir = os.path.abspath(options.src_dir)
|
||||||
|
install_options = options.install_options or []
|
||||||
|
if options.use_user_site:
|
||||||
|
if options.prefix_path:
|
||||||
|
raise CommandError(
|
||||||
|
"Can not combine '--user' and '--prefix' as they imply "
|
||||||
|
"different installation locations"
|
||||||
|
)
|
||||||
|
if virtualenv_no_global():
|
||||||
|
raise InstallationError(
|
||||||
|
"Can not perform a '--user' install. User site-packages "
|
||||||
|
"are not visible in this virtualenv."
|
||||||
|
)
|
||||||
|
install_options.append('--user')
|
||||||
|
install_options.append('--prefix=')
|
||||||
|
|
||||||
|
target_temp_dir = TempDirectory(kind="target")
|
||||||
|
if options.target_dir:
|
||||||
|
options.ignore_installed = True
|
||||||
|
options.target_dir = os.path.abspath(options.target_dir)
|
||||||
|
if (os.path.exists(options.target_dir) and not
|
||||||
|
os.path.isdir(options.target_dir)):
|
||||||
|
raise CommandError(
|
||||||
|
"Target path exists but is not a directory, will not "
|
||||||
|
"continue."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a target directory for using with the target option
|
||||||
|
target_temp_dir.create()
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
protect_pip_from_modification_on_windows(
|
||||||
|
modifying_pip=requirement_set.has_requirement("pip")
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
# We don't build wheels for legacy requirements if we
|
||||||
|
# don't have wheel installed or we don't have a cache dir
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
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()
|
||||||
|
|
||||||
|
if options.target_dir:
|
||||||
|
self._handle_target_dir(
|
||||||
|
options.target_dir, target_temp_dir, options.upgrade
|
||||||
|
)
|
||||||
|
return requirement_set
|
||||||
|
|
||||||
|
def _handle_target_dir(self, target_dir, target_temp_dir, upgrade):
|
||||||
|
ensure_dir(target_dir)
|
||||||
|
|
||||||
|
# Checking both purelib and platlib directories for installed
|
||||||
|
# packages to be moved to target directory
|
||||||
|
lib_dir_list = []
|
||||||
|
|
||||||
|
with target_temp_dir:
|
||||||
|
# Checking both purelib and platlib directories for installed
|
||||||
|
# packages to be moved to target directory
|
||||||
|
scheme = distutils_scheme('', home=target_temp_dir.path)
|
||||||
|
purelib_dir = scheme['purelib']
|
||||||
|
platlib_dir = scheme['platlib']
|
||||||
|
data_dir = scheme['data']
|
||||||
|
|
||||||
|
if os.path.exists(purelib_dir):
|
||||||
|
lib_dir_list.append(purelib_dir)
|
||||||
|
if os.path.exists(platlib_dir) and platlib_dir != purelib_dir:
|
||||||
|
lib_dir_list.append(platlib_dir)
|
||||||
|
if os.path.exists(data_dir):
|
||||||
|
lib_dir_list.append(data_dir)
|
||||||
|
|
||||||
|
for lib_dir in lib_dir_list:
|
||||||
|
for item in os.listdir(lib_dir):
|
||||||
|
if lib_dir == data_dir:
|
||||||
|
ddir = os.path.join(data_dir, item)
|
||||||
|
if any(s.startswith(ddir) for s in lib_dir_list[:-1]):
|
||||||
|
continue
|
||||||
|
target_item_dir = os.path.join(target_dir, item)
|
||||||
|
if os.path.exists(target_item_dir):
|
||||||
|
if not upgrade:
|
||||||
|
logger.warning(
|
||||||
|
'Target directory %s already exists. Specify '
|
||||||
|
'--upgrade to force replacement.',
|
||||||
|
target_item_dir
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
if os.path.islink(target_item_dir):
|
||||||
|
logger.warning(
|
||||||
|
'Target directory %s already exists and is '
|
||||||
|
'a link. Pip will not automatically replace '
|
||||||
|
'links, please remove if replacement is '
|
||||||
|
'desired.',
|
||||||
|
target_item_dir
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
if os.path.isdir(target_item_dir):
|
||||||
|
shutil.rmtree(target_item_dir)
|
||||||
|
else:
|
||||||
|
os.remove(target_item_dir)
|
||||||
|
|
||||||
|
shutil.move(
|
||||||
|
os.path.join(lib_dir, item),
|
||||||
|
target_item_dir
|
||||||
|
)
|
||||||
|
|
||||||
|
def _warn_about_conflicts(self, to_install):
|
||||||
|
try:
|
||||||
|
package_set, _dep_info = check_install_conflicts(to_install)
|
||||||
|
except Exception:
|
||||||
|
logger.error("Error checking for conflicts.", exc_info=True)
|
||||||
|
return
|
||||||
|
missing, conflicting = _dep_info
|
||||||
|
|
||||||
|
# NOTE: There is some duplication here from pip check
|
||||||
|
for project_name in missing:
|
||||||
|
version = package_set[project_name][0]
|
||||||
|
for dependency in missing[project_name]:
|
||||||
|
logger.critical(
|
||||||
|
"%s %s requires %s, which is not installed.",
|
||||||
|
project_name, version, dependency[1],
|
||||||
|
)
|
||||||
|
|
||||||
|
for project_name in conflicting:
|
||||||
|
version = package_set[project_name][0]
|
||||||
|
for dep_name, dep_version, req in conflicting[project_name]:
|
||||||
|
logger.critical(
|
||||||
|
"%s %s has requirement %s, but you'll have %s %s which is "
|
||||||
|
"incompatible.",
|
||||||
|
project_name, version, req, dep_name, dep_version,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_lib_location_guesses(*args, **kwargs):
|
||||||
|
scheme = distutils_scheme('', *args, **kwargs)
|
||||||
|
return [scheme['purelib'], scheme['platlib']]
|
||||||
|
|
||||||
|
|
||||||
|
def create_env_error_message(error, show_traceback, using_user_site):
|
||||||
|
"""Format an error message for an EnvironmentError
|
||||||
|
|
||||||
|
It may occur anytime during the execution of the install command.
|
||||||
|
"""
|
||||||
|
parts = []
|
||||||
|
|
||||||
|
# Mention the error if we are not going to show a traceback
|
||||||
|
parts.append("Could not install packages due to an EnvironmentError")
|
||||||
|
if not show_traceback:
|
||||||
|
parts.append(": ")
|
||||||
|
parts.append(str(error))
|
||||||
|
else:
|
||||||
|
parts.append(".")
|
||||||
|
|
||||||
|
# Spilt the error indication from a helper message (if any)
|
||||||
|
parts[-1] += "\n"
|
||||||
|
|
||||||
|
# Suggest useful actions to the user:
|
||||||
|
# (1) using user site-packages or (2) verifying the permissions
|
||||||
|
if error.errno == errno.EACCES:
|
||||||
|
user_option_part = "Consider using the `--user` option"
|
||||||
|
permissions_part = "Check the permissions"
|
||||||
|
|
||||||
|
if not using_user_site:
|
||||||
|
parts.extend([
|
||||||
|
user_option_part, " or ",
|
||||||
|
permissions_part.lower(),
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
parts.append(permissions_part)
|
||||||
|
parts.append(".\n")
|
||||||
|
|
||||||
|
return "".join(parts).strip() + "\n"
|
@@ -0,0 +1,301 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from pip._vendor import six
|
||||||
|
from pip._vendor.six.moves import zip_longest
|
||||||
|
|
||||||
|
from pip._internal.cli import cmdoptions
|
||||||
|
from pip._internal.cli.base_command import Command
|
||||||
|
from pip._internal.exceptions import CommandError
|
||||||
|
from pip._internal.index import PackageFinder
|
||||||
|
from pip._internal.utils.misc import (
|
||||||
|
dist_is_editable, get_installed_distributions,
|
||||||
|
)
|
||||||
|
from pip._internal.utils.packaging import get_installer
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ListCommand(Command):
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
|
||||||
|
cmd_opts = self.cmd_opts
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'-o', '--outdated',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='List outdated packages')
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'-u', '--uptodate',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='List uptodate packages')
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'-e', '--editable',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='List editable projects.')
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'-l', '--local',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help=('If in a virtualenv that has global access, do not list '
|
||||||
|
'globally-installed packages.'),
|
||||||
|
)
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'--user',
|
||||||
|
dest='user',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Only output packages installed in user-site.')
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--pre',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help=("Include pre-release and development versions. By default, "
|
||||||
|
"pip only finds stable versions."),
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--format',
|
||||||
|
action='store',
|
||||||
|
dest='list_format',
|
||||||
|
default="columns",
|
||||||
|
choices=('columns', 'freeze', 'json'),
|
||||||
|
help="Select the output format among: columns (default), freeze, "
|
||||||
|
"or json",
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--not-required',
|
||||||
|
action='store_true',
|
||||||
|
dest='not_required',
|
||||||
|
help="List packages that are not dependencies of "
|
||||||
|
"installed packages.",
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--exclude-editable',
|
||||||
|
action='store_false',
|
||||||
|
dest='include_editable',
|
||||||
|
help='Exclude editable package from output.',
|
||||||
|
)
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--include-editable',
|
||||||
|
action='store_true',
|
||||||
|
dest='include_editable',
|
||||||
|
help='Include editable package from output.',
|
||||||
|
default=True,
|
||||||
|
)
|
||||||
|
index_opts = cmdoptions.make_option_group(
|
||||||
|
cmdoptions.index_group, self.parser
|
||||||
|
)
|
||||||
|
|
||||||
|
self.parser.insert_option_group(0, index_opts)
|
||||||
|
self.parser.insert_option_group(0, cmd_opts)
|
||||||
|
|
||||||
|
def _build_package_finder(self, options, index_urls, session):
|
||||||
|
"""
|
||||||
|
Create a package finder appropriate to this list command.
|
||||||
|
"""
|
||||||
|
return PackageFinder(
|
||||||
|
find_links=options.find_links,
|
||||||
|
index_urls=index_urls,
|
||||||
|
allow_all_prereleases=options.pre,
|
||||||
|
trusted_hosts=options.trusted_hosts,
|
||||||
|
session=session,
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self, options, args):
|
||||||
|
if options.outdated and options.uptodate:
|
||||||
|
raise CommandError(
|
||||||
|
"Options --outdated and --uptodate cannot be combined.")
|
||||||
|
|
||||||
|
packages = get_installed_distributions(
|
||||||
|
local_only=options.local,
|
||||||
|
user_only=options.user,
|
||||||
|
editables_only=options.editable,
|
||||||
|
include_editables=options.include_editable,
|
||||||
|
)
|
||||||
|
|
||||||
|
# get_not_required must be called firstly in order to find and
|
||||||
|
# filter out all dependencies correctly. Otherwise a package
|
||||||
|
# can't be identified as requirement because some parent packages
|
||||||
|
# could be filtered out before.
|
||||||
|
if options.not_required:
|
||||||
|
packages = self.get_not_required(packages, options)
|
||||||
|
|
||||||
|
if options.outdated:
|
||||||
|
packages = self.get_outdated(packages, options)
|
||||||
|
elif options.uptodate:
|
||||||
|
packages = self.get_uptodate(packages, options)
|
||||||
|
|
||||||
|
self.output_package_listing(packages, options)
|
||||||
|
|
||||||
|
def get_outdated(self, packages, options):
|
||||||
|
return [
|
||||||
|
dist for dist in self.iter_packages_latest_infos(packages, options)
|
||||||
|
if dist.latest_version > dist.parsed_version
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_uptodate(self, packages, options):
|
||||||
|
return [
|
||||||
|
dist for dist in self.iter_packages_latest_infos(packages, options)
|
||||||
|
if dist.latest_version == dist.parsed_version
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_not_required(self, packages, options):
|
||||||
|
dep_keys = set()
|
||||||
|
for dist in packages:
|
||||||
|
dep_keys.update(requirement.key for requirement in dist.requires())
|
||||||
|
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)
|
||||||
|
|
||||||
|
for dist in packages:
|
||||||
|
typ = 'unknown'
|
||||||
|
all_candidates = finder.find_all_candidates(dist.key)
|
||||||
|
if not options.pre:
|
||||||
|
# Remove prereleases
|
||||||
|
all_candidates = [candidate for candidate in all_candidates
|
||||||
|
if not candidate.version.is_prerelease]
|
||||||
|
|
||||||
|
if not all_candidates:
|
||||||
|
continue
|
||||||
|
best_candidate = max(all_candidates,
|
||||||
|
key=finder._candidate_sort_key)
|
||||||
|
remote_version = best_candidate.version
|
||||||
|
if best_candidate.location.is_wheel:
|
||||||
|
typ = 'wheel'
|
||||||
|
else:
|
||||||
|
typ = 'sdist'
|
||||||
|
# This is dirty but makes the rest of the code much cleaner
|
||||||
|
dist.latest_version = remote_version
|
||||||
|
dist.latest_filetype = typ
|
||||||
|
yield dist
|
||||||
|
|
||||||
|
def output_package_listing(self, packages, options):
|
||||||
|
packages = sorted(
|
||||||
|
packages,
|
||||||
|
key=lambda dist: dist.project_name.lower(),
|
||||||
|
)
|
||||||
|
if options.list_format == 'columns' and packages:
|
||||||
|
data, header = format_for_columns(packages, options)
|
||||||
|
self.output_package_listing_columns(data, header)
|
||||||
|
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)
|
||||||
|
else:
|
||||||
|
logger.info("%s==%s", dist.project_name, dist.version)
|
||||||
|
elif options.list_format == 'json':
|
||||||
|
logger.info(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
|
||||||
|
if len(data) > 0:
|
||||||
|
data.insert(0, header)
|
||||||
|
|
||||||
|
pkg_strings, sizes = tabulate(data)
|
||||||
|
|
||||||
|
# Create and add a separator.
|
||||||
|
if len(data) > 0:
|
||||||
|
pkg_strings.insert(1, " ".join(map(lambda x: '-' * x, sizes)))
|
||||||
|
|
||||||
|
for val in pkg_strings:
|
||||||
|
logger.info(val)
|
||||||
|
|
||||||
|
|
||||||
|
def tabulate(vals):
|
||||||
|
# From pfmoore on GitHub:
|
||||||
|
# https://github.com/pypa/pip/issues/3651#issuecomment-216932564
|
||||||
|
assert len(vals) > 0
|
||||||
|
|
||||||
|
sizes = [0] * max(len(x) for x in vals)
|
||||||
|
for row in vals:
|
||||||
|
sizes = [max(s, len(str(c))) for s, c in zip_longest(sizes, row)]
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for row in vals:
|
||||||
|
display = " ".join([str(c).ljust(s) if c is not None else ''
|
||||||
|
for s, c in zip_longest(sizes, row)])
|
||||||
|
result.append(display)
|
||||||
|
|
||||||
|
return result, sizes
|
||||||
|
|
||||||
|
|
||||||
|
def format_for_columns(pkgs, options):
|
||||||
|
"""
|
||||||
|
Convert the package data into something usable
|
||||||
|
by output_package_listing_columns.
|
||||||
|
"""
|
||||||
|
running_outdated = options.outdated
|
||||||
|
# Adjust the header for the `pip list --outdated` case.
|
||||||
|
if running_outdated:
|
||||||
|
header = ["Package", "Version", "Latest", "Type"]
|
||||||
|
else:
|
||||||
|
header = ["Package", "Version"]
|
||||||
|
|
||||||
|
data = []
|
||||||
|
if options.verbose >= 1 or any(dist_is_editable(x) for x in pkgs):
|
||||||
|
header.append("Location")
|
||||||
|
if options.verbose >= 1:
|
||||||
|
header.append("Installer")
|
||||||
|
|
||||||
|
for proj in pkgs:
|
||||||
|
# if we're working on the 'outdated' list, separate out the
|
||||||
|
# latest_version and type
|
||||||
|
row = [proj.project_name, proj.version]
|
||||||
|
|
||||||
|
if running_outdated:
|
||||||
|
row.append(proj.latest_version)
|
||||||
|
row.append(proj.latest_filetype)
|
||||||
|
|
||||||
|
if options.verbose >= 1 or dist_is_editable(proj):
|
||||||
|
row.append(proj.location)
|
||||||
|
if options.verbose >= 1:
|
||||||
|
row.append(get_installer(proj))
|
||||||
|
|
||||||
|
data.append(row)
|
||||||
|
|
||||||
|
return data, header
|
||||||
|
|
||||||
|
|
||||||
|
def format_for_json(packages, options):
|
||||||
|
data = []
|
||||||
|
for dist in packages:
|
||||||
|
info = {
|
||||||
|
'name': dist.project_name,
|
||||||
|
'version': six.text_type(dist.version),
|
||||||
|
}
|
||||||
|
if options.verbose >= 1:
|
||||||
|
info['location'] = dist.location
|
||||||
|
info['installer'] = get_installer(dist)
|
||||||
|
if options.outdated:
|
||||||
|
info['latest_version'] = six.text_type(dist.latest_version)
|
||||||
|
info['latest_filetype'] = dist.latest_filetype
|
||||||
|
data.append(info)
|
||||||
|
return json.dumps(data)
|
@@ -0,0 +1,135 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import textwrap
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from pip._vendor import pkg_resources
|
||||||
|
from pip._vendor.packaging.version import parse as parse_version
|
||||||
|
# NOTE: XMLRPC Client is not annotated in typeshed as on 2017-07-17, which is
|
||||||
|
# why we ignore the type on this import
|
||||||
|
from pip._vendor.six.moves import xmlrpc_client # type: ignore
|
||||||
|
|
||||||
|
from pip._internal.cli.base_command import Command
|
||||||
|
from pip._internal.cli.status_codes import NO_MATCHES_FOUND, SUCCESS
|
||||||
|
from pip._internal.download import PipXmlrpcTransport
|
||||||
|
from pip._internal.exceptions import CommandError
|
||||||
|
from pip._internal.models.index import PyPI
|
||||||
|
from pip._internal.utils.compat import get_terminal_size
|
||||||
|
from pip._internal.utils.logging import indent_log
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SearchCommand(Command):
|
||||||
|
"""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):
|
||||||
|
super(SearchCommand, self).__init__(*args, **kw)
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'-i', '--index',
|
||||||
|
dest='index',
|
||||||
|
metavar='URL',
|
||||||
|
default=PyPI.pypi_url,
|
||||||
|
help='Base URL of Python Package Index (default %default)')
|
||||||
|
|
||||||
|
self.parser.insert_option_group(0, self.cmd_opts)
|
||||||
|
|
||||||
|
def run(self, options, args):
|
||||||
|
if not args:
|
||||||
|
raise CommandError('Missing required argument (search query).')
|
||||||
|
query = args
|
||||||
|
pypi_hits = self.search(query, options)
|
||||||
|
hits = transform_hits(pypi_hits)
|
||||||
|
|
||||||
|
terminal_width = None
|
||||||
|
if sys.stdout.isatty():
|
||||||
|
terminal_width = get_terminal_size()[0]
|
||||||
|
|
||||||
|
print_results(hits, terminal_width=terminal_width)
|
||||||
|
if pypi_hits:
|
||||||
|
return SUCCESS
|
||||||
|
return NO_MATCHES_FOUND
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def transform_hits(hits):
|
||||||
|
"""
|
||||||
|
The list from pypi is really a list of versions. We want a list of
|
||||||
|
packages with the list of versions stored inline. This converts the
|
||||||
|
list from pypi into one we can use.
|
||||||
|
"""
|
||||||
|
packages = OrderedDict()
|
||||||
|
for hit in hits:
|
||||||
|
name = hit['name']
|
||||||
|
summary = hit['summary']
|
||||||
|
version = hit['version']
|
||||||
|
|
||||||
|
if name not in packages.keys():
|
||||||
|
packages[name] = {
|
||||||
|
'name': name,
|
||||||
|
'summary': summary,
|
||||||
|
'versions': [version],
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
packages[name]['versions'].append(version)
|
||||||
|
|
||||||
|
# if this is the highest version, replace summary and score
|
||||||
|
if version == highest_version(packages[name]['versions']):
|
||||||
|
packages[name]['summary'] = summary
|
||||||
|
|
||||||
|
return list(packages.values())
|
||||||
|
|
||||||
|
|
||||||
|
def print_results(hits, name_column_width=None, terminal_width=None):
|
||||||
|
if not hits:
|
||||||
|
return
|
||||||
|
if name_column_width is None:
|
||||||
|
name_column_width = max([
|
||||||
|
len(hit['name']) + len(highest_version(hit.get('versions', ['-'])))
|
||||||
|
for hit in hits
|
||||||
|
]) + 4
|
||||||
|
|
||||||
|
installed_packages = [p.project_name for p in pkg_resources.working_set]
|
||||||
|
for hit in hits:
|
||||||
|
name = hit['name']
|
||||||
|
summary = hit['summary'] or ''
|
||||||
|
latest = highest_version(hit.get('versions', ['-']))
|
||||||
|
if terminal_width is not None:
|
||||||
|
target_width = terminal_width - name_column_width - 5
|
||||||
|
if target_width > 10:
|
||||||
|
# wrap and indent summary to fit terminal
|
||||||
|
summary = textwrap.wrap(summary, target_width)
|
||||||
|
summary = ('\n' + ' ' * (name_column_width + 3)).join(summary)
|
||||||
|
|
||||||
|
line = '%-*s - %s' % (name_column_width,
|
||||||
|
'%s (%s)' % (name, latest), summary)
|
||||||
|
try:
|
||||||
|
logger.info(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)
|
||||||
|
else:
|
||||||
|
logger.info('INSTALLED: %s', dist.version)
|
||||||
|
logger.info('LATEST: %s', latest)
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def highest_version(versions):
|
||||||
|
return max(versions, key=parse_version)
|
@@ -0,0 +1,168 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from email.parser import FeedParser # type: ignore
|
||||||
|
|
||||||
|
from pip._vendor import pkg_resources
|
||||||
|
from pip._vendor.packaging.utils import canonicalize_name
|
||||||
|
|
||||||
|
from pip._internal.cli.base_command import Command
|
||||||
|
from pip._internal.cli.status_codes import ERROR, SUCCESS
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ShowCommand(Command):
|
||||||
|
"""
|
||||||
|
Show information about one or more installed packages.
|
||||||
|
|
||||||
|
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):
|
||||||
|
super(ShowCommand, self).__init__(*args, **kw)
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'-f', '--files',
|
||||||
|
dest='files',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Show the full list of installed files for each package.')
|
||||||
|
|
||||||
|
self.parser.insert_option_group(0, self.cmd_opts)
|
||||||
|
|
||||||
|
def run(self, options, args):
|
||||||
|
if not args:
|
||||||
|
logger.warning('ERROR: Please provide a package name or names.')
|
||||||
|
return ERROR
|
||||||
|
query = args
|
||||||
|
|
||||||
|
results = search_packages_info(query)
|
||||||
|
if not print_results(
|
||||||
|
results, list_files=options.files, verbose=options.verbose):
|
||||||
|
return ERROR
|
||||||
|
return SUCCESS
|
||||||
|
|
||||||
|
|
||||||
|
def search_packages_info(query):
|
||||||
|
"""
|
||||||
|
Gather details from installed distributions. Print distribution name,
|
||||||
|
version, location, and installed files. Installed files requires a
|
||||||
|
pip generated 'installed-files.txt' in the distributions '.egg-info'
|
||||||
|
directory.
|
||||||
|
"""
|
||||||
|
installed = {}
|
||||||
|
for p in pkg_resources.working_set:
|
||||||
|
installed[canonicalize_name(p.project_name)] = p
|
||||||
|
|
||||||
|
query_names = [canonicalize_name(name) for name in query]
|
||||||
|
|
||||||
|
for dist in [installed[pkg] for pkg in query_names if pkg in installed]:
|
||||||
|
package = {
|
||||||
|
'name': dist.project_name,
|
||||||
|
'version': dist.version,
|
||||||
|
'location': dist.location,
|
||||||
|
'requires': [dep.project_name for dep in dist.requires()],
|
||||||
|
}
|
||||||
|
file_list = None
|
||||||
|
metadata = None
|
||||||
|
if isinstance(dist, pkg_resources.DistInfoDistribution):
|
||||||
|
# RECORDs should be part of .dist-info metadatas
|
||||||
|
if dist.has_metadata('RECORD'):
|
||||||
|
lines = dist.get_metadata_lines('RECORD')
|
||||||
|
paths = [l.split(',')[0] for l in lines]
|
||||||
|
paths = [os.path.join(dist.location, p) for p in paths]
|
||||||
|
file_list = [os.path.relpath(p, dist.location) for p in paths]
|
||||||
|
|
||||||
|
if dist.has_metadata('METADATA'):
|
||||||
|
metadata = dist.get_metadata('METADATA')
|
||||||
|
else:
|
||||||
|
# Otherwise use pip's log for .egg-info's
|
||||||
|
if dist.has_metadata('installed-files.txt'):
|
||||||
|
paths = dist.get_metadata_lines('installed-files.txt')
|
||||||
|
paths = [os.path.join(dist.egg_info, p) for p in paths]
|
||||||
|
file_list = [os.path.relpath(p, dist.location) for p in paths]
|
||||||
|
|
||||||
|
if dist.has_metadata('PKG-INFO'):
|
||||||
|
metadata = dist.get_metadata('PKG-INFO')
|
||||||
|
|
||||||
|
if dist.has_metadata('entry_points.txt'):
|
||||||
|
entry_points = dist.get_metadata_lines('entry_points.txt')
|
||||||
|
package['entry_points'] = entry_points
|
||||||
|
|
||||||
|
if dist.has_metadata('INSTALLER'):
|
||||||
|
for line in dist.get_metadata_lines('INSTALLER'):
|
||||||
|
if line.strip():
|
||||||
|
package['installer'] = line.strip()
|
||||||
|
break
|
||||||
|
|
||||||
|
# @todo: Should pkg_resources.Distribution have a
|
||||||
|
# `get_pkg_info` method?
|
||||||
|
feed_parser = FeedParser()
|
||||||
|
feed_parser.feed(metadata)
|
||||||
|
pkg_info_dict = feed_parser.close()
|
||||||
|
for key in ('metadata-version', 'summary',
|
||||||
|
'home-page', 'author', 'author-email', 'license'):
|
||||||
|
package[key] = pkg_info_dict.get(key)
|
||||||
|
|
||||||
|
# It looks like FeedParser cannot deal with repeated headers
|
||||||
|
classifiers = []
|
||||||
|
for line in metadata.splitlines():
|
||||||
|
if line.startswith('Classifier: '):
|
||||||
|
classifiers.append(line[len('Classifier: '):])
|
||||||
|
package['classifiers'] = classifiers
|
||||||
|
|
||||||
|
if file_list:
|
||||||
|
package['files'] = sorted(file_list)
|
||||||
|
yield package
|
||||||
|
|
||||||
|
|
||||||
|
def print_results(distributions, list_files=False, verbose=False):
|
||||||
|
"""
|
||||||
|
Print the informations from installed distributions found.
|
||||||
|
"""
|
||||||
|
results_printed = False
|
||||||
|
for i, dist in enumerate(distributions):
|
||||||
|
results_printed = True
|
||||||
|
if i > 0:
|
||||||
|
logger.info("---")
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
logger.info("Metadata-Version: %s",
|
||||||
|
dist.get('metadata-version', ''))
|
||||||
|
logger.info("Installer: %s", dist.get('installer', ''))
|
||||||
|
logger.info("Classifiers:")
|
||||||
|
for classifier in dist.get('classifiers', []):
|
||||||
|
logger.info(" %s", classifier)
|
||||||
|
logger.info("Entry-points:")
|
||||||
|
for entry in dist.get('entry_points', []):
|
||||||
|
logger.info(" %s", entry.strip())
|
||||||
|
if list_files:
|
||||||
|
logger.info("Files:")
|
||||||
|
for line in dist.get('files', []):
|
||||||
|
logger.info(" %s", line.strip())
|
||||||
|
if "files" not in dist:
|
||||||
|
logger.info("Cannot locate installed-files.txt")
|
||||||
|
return results_printed
|
@@ -0,0 +1,78 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from pip._vendor.packaging.utils import canonicalize_name
|
||||||
|
|
||||||
|
from pip._internal.cli.base_command import Command
|
||||||
|
from pip._internal.exceptions import InstallationError
|
||||||
|
from pip._internal.req import parse_requirements
|
||||||
|
from pip._internal.req.constructors import install_req_from_line
|
||||||
|
from pip._internal.utils.misc import protect_pip_from_modification_on_windows
|
||||||
|
|
||||||
|
|
||||||
|
class UninstallCommand(Command):
|
||||||
|
"""
|
||||||
|
Uninstall packages.
|
||||||
|
|
||||||
|
pip is able to uninstall most installed packages. Known exceptions are:
|
||||||
|
|
||||||
|
- Pure distutils packages installed with ``python setup.py install``, which
|
||||||
|
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)
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'-r', '--requirement',
|
||||||
|
dest='requirements',
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
metavar='file',
|
||||||
|
help='Uninstall all the packages listed in the given requirements '
|
||||||
|
'file. This option can be used multiple times.',
|
||||||
|
)
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'-y', '--yes',
|
||||||
|
dest='yes',
|
||||||
|
action='store_true',
|
||||||
|
help="Don't ask for confirmation of uninstall deletions.")
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
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()
|
@@ -0,0 +1,186 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from pip._internal.cache import WheelCache
|
||||||
|
from pip._internal.cli import cmdoptions
|
||||||
|
from pip._internal.cli.base_command import RequirementCommand
|
||||||
|
from pip._internal.exceptions import CommandError, PreviousBuildDirError
|
||||||
|
from pip._internal.operations.prepare import RequirementPreparer
|
||||||
|
from pip._internal.req import RequirementSet
|
||||||
|
from pip._internal.req.req_tracker import RequirementTracker
|
||||||
|
from pip._internal.resolve import Resolver
|
||||||
|
from pip._internal.utils.temp_dir import TempDirectory
|
||||||
|
from pip._internal.wheel import WheelBuilder
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class WheelCommand(RequirementCommand):
|
||||||
|
"""
|
||||||
|
Build Wheel archives for your requirements and dependencies.
|
||||||
|
|
||||||
|
Wheel is a built-package format, and offers the advantage of not
|
||||||
|
recompiling your software during every install. For more details, see the
|
||||||
|
wheel docs: https://wheel.readthedocs.io/en/latest/
|
||||||
|
|
||||||
|
Requirements: setuptools>=0.8, and wheel.
|
||||||
|
|
||||||
|
'pip wheel' uses the bdist_wheel setuptools extension from the wheel
|
||||||
|
package to build individual wheels.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = 'wheel'
|
||||||
|
usage = """
|
||||||
|
%prog [options] <requirement specifier> ...
|
||||||
|
%prog [options] -r <requirements file> ...
|
||||||
|
%prog [options] [-e] <vcs project url> ...
|
||||||
|
%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)
|
||||||
|
|
||||||
|
cmd_opts = self.cmd_opts
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'-w', '--wheel-dir',
|
||||||
|
dest='wheel_dir',
|
||||||
|
metavar='dir',
|
||||||
|
default=os.curdir,
|
||||||
|
help=("Build wheels into <dir>, where the default is the "
|
||||||
|
"current working directory."),
|
||||||
|
)
|
||||||
|
cmd_opts.add_option(cmdoptions.no_binary())
|
||||||
|
cmd_opts.add_option(cmdoptions.only_binary())
|
||||||
|
cmd_opts.add_option(cmdoptions.prefer_binary())
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--build-option',
|
||||||
|
dest='build_options',
|
||||||
|
metavar='options',
|
||||||
|
action='append',
|
||||||
|
help="Extra arguments to be supplied to 'setup.py bdist_wheel'.",
|
||||||
|
)
|
||||||
|
cmd_opts.add_option(cmdoptions.no_build_isolation())
|
||||||
|
cmd_opts.add_option(cmdoptions.use_pep517())
|
||||||
|
cmd_opts.add_option(cmdoptions.no_use_pep517())
|
||||||
|
cmd_opts.add_option(cmdoptions.constraints())
|
||||||
|
cmd_opts.add_option(cmdoptions.editable())
|
||||||
|
cmd_opts.add_option(cmdoptions.requirements())
|
||||||
|
cmd_opts.add_option(cmdoptions.src())
|
||||||
|
cmd_opts.add_option(cmdoptions.ignore_requires_python())
|
||||||
|
cmd_opts.add_option(cmdoptions.no_deps())
|
||||||
|
cmd_opts.add_option(cmdoptions.build_dir())
|
||||||
|
cmd_opts.add_option(cmdoptions.progress_bar())
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--global-option',
|
||||||
|
dest='global_options',
|
||||||
|
action='append',
|
||||||
|
metavar='options',
|
||||||
|
help="Extra global options to be supplied to the setup.py "
|
||||||
|
"call before the 'bdist_wheel' command.")
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--pre',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help=("Include pre-release and development versions. By default, "
|
||||||
|
"pip only finds stable versions."),
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd_opts.add_option(cmdoptions.no_clean())
|
||||||
|
cmd_opts.add_option(cmdoptions.require_hashes())
|
||||||
|
|
||||||
|
index_opts = cmdoptions.make_option_group(
|
||||||
|
cmdoptions.index_group,
|
||||||
|
self.parser,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.parser.insert_option_group(0, index_opts)
|
||||||
|
self.parser.insert_option_group(0, cmd_opts)
|
||||||
|
|
||||||
|
def run(self, options, args):
|
||||||
|
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)
|
||||||
|
|
||||||
|
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,
|
||||||
|
self.name, wheel_cache
|
||||||
|
)
|
||||||
|
|
||||||
|
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 = 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
|
||||||
|
)
|
||||||
|
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()
|
@@ -0,0 +1,387 @@
|
|||||||
|
"""Configuration management setup
|
||||||
|
|
||||||
|
Some terminology:
|
||||||
|
- name
|
||||||
|
As written in config files.
|
||||||
|
- value
|
||||||
|
Value associated with a name
|
||||||
|
- key
|
||||||
|
Name combined with it's section (section.name)
|
||||||
|
- variant
|
||||||
|
A single word describing where the configuration key-value pair came from
|
||||||
|
"""
|
||||||
|
|
||||||
|
import locale
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from pip._vendor import six
|
||||||
|
from pip._vendor.six.moves import configparser
|
||||||
|
|
||||||
|
from pip._internal.exceptions import (
|
||||||
|
ConfigurationError, ConfigurationFileCouldNotBeLoaded,
|
||||||
|
)
|
||||||
|
from pip._internal.locations import (
|
||||||
|
legacy_config_file, new_config_file, running_under_virtualenv,
|
||||||
|
site_config_files, venv_config_file,
|
||||||
|
)
|
||||||
|
from pip._internal.utils.misc import ensure_dir, enum
|
||||||
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
|
if MYPY_CHECK_RUNNING:
|
||||||
|
from typing import ( # noqa: F401
|
||||||
|
Any, Dict, Iterable, List, NewType, Optional, Tuple
|
||||||
|
)
|
||||||
|
|
||||||
|
RawConfigParser = configparser.RawConfigParser # Shorthand
|
||||||
|
Kind = NewType("Kind", str)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE: Maybe use the optionx attribute to normalize keynames.
|
||||||
|
def _normalize_name(name):
|
||||||
|
# type: (str) -> str
|
||||||
|
"""Make a name consistent regardless of source (environment or file)
|
||||||
|
"""
|
||||||
|
name = name.lower().replace('_', '-')
|
||||||
|
if name.startswith('--'):
|
||||||
|
name = name[2:] # only prefer long opts
|
||||||
|
return name
|
||||||
|
|
||||||
|
|
||||||
|
def _disassemble_key(name):
|
||||||
|
# type: (str) -> List[str]
|
||||||
|
return name.split(".", 1)
|
||||||
|
|
||||||
|
|
||||||
|
# The kinds of configurations there are.
|
||||||
|
kinds = enum(
|
||||||
|
USER="user", # User Specific
|
||||||
|
GLOBAL="global", # System Wide
|
||||||
|
VENV="venv", # Virtual Environment Specific
|
||||||
|
ENV="env", # from PIP_CONFIG_FILE
|
||||||
|
ENV_VAR="env-var", # from Environment Variables
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Configuration(object):
|
||||||
|
"""Handles management of configuration.
|
||||||
|
|
||||||
|
Provides an interface to accessing and managing configuration files.
|
||||||
|
|
||||||
|
This class converts provides an API that takes "section.key-name" style
|
||||||
|
keys and stores the value associated with it as "key-name" under the
|
||||||
|
section "section".
|
||||||
|
|
||||||
|
This allows for a clean interface wherein the both the section and the
|
||||||
|
key-name are preserved in an easy to manage form in the configuration files
|
||||||
|
and the data stored is also nice.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, isolated, load_only=None):
|
||||||
|
# type: (bool, Kind) -> None
|
||||||
|
super(Configuration, self).__init__()
|
||||||
|
|
||||||
|
_valid_load_only = [kinds.USER, kinds.GLOBAL, kinds.VENV, None]
|
||||||
|
if load_only not in _valid_load_only:
|
||||||
|
raise ConfigurationError(
|
||||||
|
"Got invalid value for load_only - should be one of {}".format(
|
||||||
|
", ".join(map(repr, _valid_load_only[:-1]))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.isolated = isolated # type: bool
|
||||||
|
self.load_only = load_only # type: Optional[Kind]
|
||||||
|
|
||||||
|
# The order here determines the override order.
|
||||||
|
self._override_order = [
|
||||||
|
kinds.GLOBAL, kinds.USER, kinds.VENV, kinds.ENV, kinds.ENV_VAR
|
||||||
|
]
|
||||||
|
|
||||||
|
self._ignore_env_names = ["version", "help"]
|
||||||
|
|
||||||
|
# Because we keep track of where we got the data from
|
||||||
|
self._parsers = {
|
||||||
|
variant: [] for variant in self._override_order
|
||||||
|
} # type: Dict[Kind, List[Tuple[str, RawConfigParser]]]
|
||||||
|
self._config = {
|
||||||
|
variant: {} for variant in self._override_order
|
||||||
|
} # type: Dict[Kind, Dict[str, Any]]
|
||||||
|
self._modified_parsers = [] # type: List[Tuple[str, RawConfigParser]]
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
# type: () -> None
|
||||||
|
"""Loads configuration from configuration files and environment
|
||||||
|
"""
|
||||||
|
self._load_config_files()
|
||||||
|
if not self.isolated:
|
||||||
|
self._load_environment_vars()
|
||||||
|
|
||||||
|
def get_file_to_edit(self):
|
||||||
|
# type: () -> Optional[str]
|
||||||
|
"""Returns the file with highest priority in configuration
|
||||||
|
"""
|
||||||
|
assert self.load_only is not None, \
|
||||||
|
"Need to be specified a file to be editing"
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self._get_parser_to_modify()[0]
|
||||||
|
except IndexError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
# type: () -> Iterable[Tuple[str, Any]]
|
||||||
|
"""Returns key-value pairs like dict.items() representing the loaded
|
||||||
|
configuration
|
||||||
|
"""
|
||||||
|
return self._dictionary.items()
|
||||||
|
|
||||||
|
def get_value(self, key):
|
||||||
|
# type: (str) -> Any
|
||||||
|
"""Get a value from the configuration.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self._dictionary[key]
|
||||||
|
except KeyError:
|
||||||
|
raise ConfigurationError("No such key - {}".format(key))
|
||||||
|
|
||||||
|
def set_value(self, key, value):
|
||||||
|
# type: (str, Any) -> None
|
||||||
|
"""Modify a value in the configuration.
|
||||||
|
"""
|
||||||
|
self._ensure_have_load_only()
|
||||||
|
|
||||||
|
fname, parser = self._get_parser_to_modify()
|
||||||
|
|
||||||
|
if parser is not None:
|
||||||
|
section, name = _disassemble_key(key)
|
||||||
|
|
||||||
|
# Modify the parser and the configuration
|
||||||
|
if not parser.has_section(section):
|
||||||
|
parser.add_section(section)
|
||||||
|
parser.set(section, name, value)
|
||||||
|
|
||||||
|
self._config[self.load_only][key] = value
|
||||||
|
self._mark_as_modified(fname, parser)
|
||||||
|
|
||||||
|
def unset_value(self, key):
|
||||||
|
# type: (str) -> None
|
||||||
|
"""Unset a value in the configuration.
|
||||||
|
"""
|
||||||
|
self._ensure_have_load_only()
|
||||||
|
|
||||||
|
if key not in self._config[self.load_only]:
|
||||||
|
raise ConfigurationError("No such key - {}".format(key))
|
||||||
|
|
||||||
|
fname, parser = self._get_parser_to_modify()
|
||||||
|
|
||||||
|
if parser is not None:
|
||||||
|
section, name = _disassemble_key(key)
|
||||||
|
|
||||||
|
# Remove the key in the parser
|
||||||
|
modified_something = False
|
||||||
|
if parser.has_section(section):
|
||||||
|
# Returns whether the option was removed or not
|
||||||
|
modified_something = parser.remove_option(section, name)
|
||||||
|
|
||||||
|
if modified_something:
|
||||||
|
# name removed from parser, section may now be empty
|
||||||
|
section_iter = iter(parser.items(section))
|
||||||
|
try:
|
||||||
|
val = six.next(section_iter)
|
||||||
|
except StopIteration:
|
||||||
|
val = None
|
||||||
|
|
||||||
|
if val is None:
|
||||||
|
parser.remove_section(section)
|
||||||
|
|
||||||
|
self._mark_as_modified(fname, parser)
|
||||||
|
else:
|
||||||
|
raise ConfigurationError(
|
||||||
|
"Fatal Internal error [id=1]. Please report as a bug."
|
||||||
|
)
|
||||||
|
|
||||||
|
del self._config[self.load_only][key]
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
# type: () -> None
|
||||||
|
"""Save the currentin-memory state.
|
||||||
|
"""
|
||||||
|
self._ensure_have_load_only()
|
||||||
|
|
||||||
|
for fname, parser in self._modified_parsers:
|
||||||
|
logger.info("Writing to %s", fname)
|
||||||
|
|
||||||
|
# Ensure directory exists.
|
||||||
|
ensure_dir(os.path.dirname(fname))
|
||||||
|
|
||||||
|
with open(fname, "w") as f:
|
||||||
|
parser.write(f) # type: ignore
|
||||||
|
|
||||||
|
#
|
||||||
|
# Private routines
|
||||||
|
#
|
||||||
|
|
||||||
|
def _ensure_have_load_only(self):
|
||||||
|
# type: () -> None
|
||||||
|
if self.load_only is None:
|
||||||
|
raise ConfigurationError("Needed a specific file to be modifying.")
|
||||||
|
logger.debug("Will be working with %s variant only", self.load_only)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _dictionary(self):
|
||||||
|
# type: () -> Dict[str, Any]
|
||||||
|
"""A dictionary representing the loaded configuration.
|
||||||
|
"""
|
||||||
|
# NOTE: Dictionaries are not populated if not loaded. So, conditionals
|
||||||
|
# are not needed here.
|
||||||
|
retval = {}
|
||||||
|
|
||||||
|
for variant in self._override_order:
|
||||||
|
retval.update(self._config[variant])
|
||||||
|
|
||||||
|
return retval
|
||||||
|
|
||||||
|
def _load_config_files(self):
|
||||||
|
# type: () -> None
|
||||||
|
"""Loads configuration from configuration files
|
||||||
|
"""
|
||||||
|
config_files = dict(self._iter_config_files())
|
||||||
|
if config_files[kinds.ENV][0:1] == [os.devnull]:
|
||||||
|
logger.debug(
|
||||||
|
"Skipping loading configuration files due to "
|
||||||
|
"environment's PIP_CONFIG_FILE being os.devnull"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
for variant, files in config_files.items():
|
||||||
|
for fname in files:
|
||||||
|
# If there's specific variant set in `load_only`, load only
|
||||||
|
# that variant, not the others.
|
||||||
|
if self.load_only is not None and variant != self.load_only:
|
||||||
|
logger.debug(
|
||||||
|
"Skipping file '%s' (variant: %s)", fname, variant
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
parser = self._load_file(variant, fname)
|
||||||
|
|
||||||
|
# Keeping track of the parsers used
|
||||||
|
self._parsers[variant].append((fname, parser))
|
||||||
|
|
||||||
|
def _load_file(self, variant, fname):
|
||||||
|
# type: (Kind, str) -> RawConfigParser
|
||||||
|
logger.debug("For variant '%s', will try loading '%s'", variant, fname)
|
||||||
|
parser = self._construct_parser(fname)
|
||||||
|
|
||||||
|
for section in parser.sections():
|
||||||
|
items = parser.items(section)
|
||||||
|
self._config[variant].update(self._normalized_keys(section, items))
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def _construct_parser(self, fname):
|
||||||
|
# type: (str) -> RawConfigParser
|
||||||
|
parser = configparser.RawConfigParser()
|
||||||
|
# If there is no such file, don't bother reading it but create the
|
||||||
|
# parser anyway, to hold the data.
|
||||||
|
# Doing this is useful when modifying and saving files, where we don't
|
||||||
|
# need to construct a parser.
|
||||||
|
if os.path.exists(fname):
|
||||||
|
try:
|
||||||
|
parser.read(fname)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
# See https://github.com/pypa/pip/issues/4963
|
||||||
|
raise ConfigurationFileCouldNotBeLoaded(
|
||||||
|
reason="contains invalid {} characters".format(
|
||||||
|
locale.getpreferredencoding(False)
|
||||||
|
),
|
||||||
|
fname=fname,
|
||||||
|
)
|
||||||
|
except configparser.Error as error:
|
||||||
|
# See https://github.com/pypa/pip/issues/4893
|
||||||
|
raise ConfigurationFileCouldNotBeLoaded(error=error)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def _load_environment_vars(self):
|
||||||
|
# type: () -> None
|
||||||
|
"""Loads configuration from environment variables
|
||||||
|
"""
|
||||||
|
self._config[kinds.ENV_VAR].update(
|
||||||
|
self._normalized_keys(":env:", self._get_environ_vars())
|
||||||
|
)
|
||||||
|
|
||||||
|
def _normalized_keys(self, section, items):
|
||||||
|
# type: (str, Iterable[Tuple[str, Any]]) -> Dict[str, Any]
|
||||||
|
"""Normalizes items to construct a dictionary with normalized keys.
|
||||||
|
|
||||||
|
This routine is where the names become keys and are made the same
|
||||||
|
regardless of source - configuration files or environment.
|
||||||
|
"""
|
||||||
|
normalized = {}
|
||||||
|
for name, val in items:
|
||||||
|
key = section + "." + _normalize_name(name)
|
||||||
|
normalized[key] = val
|
||||||
|
return normalized
|
||||||
|
|
||||||
|
def _get_environ_vars(self):
|
||||||
|
# type: () -> Iterable[Tuple[str, str]]
|
||||||
|
"""Returns a generator with all environmental vars with prefix PIP_"""
|
||||||
|
for key, val in os.environ.items():
|
||||||
|
should_be_yielded = (
|
||||||
|
key.startswith("PIP_") and
|
||||||
|
key[4:].lower() not in self._ignore_env_names
|
||||||
|
)
|
||||||
|
if should_be_yielded:
|
||||||
|
yield key[4:].lower(), val
|
||||||
|
|
||||||
|
# XXX: This is patched in the tests.
|
||||||
|
def _iter_config_files(self):
|
||||||
|
# type: () -> Iterable[Tuple[Kind, List[str]]]
|
||||||
|
"""Yields variant and configuration files associated with it.
|
||||||
|
|
||||||
|
This should be treated like items of a dictionary.
|
||||||
|
"""
|
||||||
|
# SMELL: Move the conditions out of this function
|
||||||
|
|
||||||
|
# environment variables have the lowest priority
|
||||||
|
config_file = os.environ.get('PIP_CONFIG_FILE', None)
|
||||||
|
if config_file is not None:
|
||||||
|
yield kinds.ENV, [config_file]
|
||||||
|
else:
|
||||||
|
yield kinds.ENV, []
|
||||||
|
|
||||||
|
# at the base we have any global configuration
|
||||||
|
yield kinds.GLOBAL, list(site_config_files)
|
||||||
|
|
||||||
|
# per-user configuration next
|
||||||
|
should_load_user_config = not self.isolated and not (
|
||||||
|
config_file and os.path.exists(config_file)
|
||||||
|
)
|
||||||
|
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]
|
||||||
|
|
||||||
|
# finally virtualenv configuration first trumping others
|
||||||
|
if running_under_virtualenv():
|
||||||
|
yield kinds.VENV, [venv_config_file]
|
||||||
|
|
||||||
|
def _get_parser_to_modify(self):
|
||||||
|
# type: () -> Tuple[str, RawConfigParser]
|
||||||
|
# Determine which parser to modify
|
||||||
|
parsers = self._parsers[self.load_only]
|
||||||
|
if not parsers:
|
||||||
|
# This should not happen if everything works correctly.
|
||||||
|
raise ConfigurationError(
|
||||||
|
"Fatal Internal error [id=2]. Please report as a bug."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Use the highest priority parser.
|
||||||
|
return parsers[-1]
|
||||||
|
|
||||||
|
# XXX: This is patched in the tests.
|
||||||
|
def _mark_as_modified(self, fname, parser):
|
||||||
|
# type: (str, RawConfigParser) -> None
|
||||||
|
file_parser_tuple = (fname, parser)
|
||||||
|
if file_parser_tuple not in self._modified_parsers:
|
||||||
|
self._modified_parsers.append(file_parser_tuple)
|
@@ -0,0 +1,971 @@
|
|||||||
|
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 pip._vendor import requests, six, urllib3
|
||||||
|
from pip._vendor.cachecontrol import CacheControlAdapter
|
||||||
|
from pip._vendor.cachecontrol.caches import FileCache
|
||||||
|
from pip._vendor.lockfile import LockError
|
||||||
|
from pip._vendor.requests.adapters import BaseAdapter, HTTPAdapter
|
||||||
|
from pip._vendor.requests.auth import AuthBase, HTTPBasicAuth
|
||||||
|
from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
|
||||||
|
from pip._vendor.requests.structures import CaseInsensitiveDict
|
||||||
|
from pip._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 pip._vendor.six.moves import xmlrpc_client # type: ignore
|
||||||
|
from pip._vendor.six.moves.urllib import parse as urllib_parse
|
||||||
|
from pip._vendor.six.moves.urllib import request as urllib_request
|
||||||
|
from pip._vendor.urllib3.util import IS_PYOPENSSL
|
||||||
|
|
||||||
|
import pip
|
||||||
|
from pip._internal.exceptions import HashMismatch, InstallationError
|
||||||
|
from pip._internal.locations import write_delete_marker_file
|
||||||
|
from pip._internal.models.index import PyPI
|
||||||
|
from pip._internal.utils.encoding import auto_decode
|
||||||
|
from pip._internal.utils.filesystem import check_path_owner
|
||||||
|
from pip._internal.utils.glibc import libc_ver
|
||||||
|
from pip._internal.utils.logging import indent_log
|
||||||
|
from pip._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,
|
||||||
|
)
|
||||||
|
from pip._internal.utils.setuptools_build import SETUPTOOLS_SHIM
|
||||||
|
from pip._internal.utils.temp_dir import TempDirectory
|
||||||
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
from pip._internal.utils.ui import DownloadProgressProvider
|
||||||
|
from pip._internal.vcs import vcs
|
||||||
|
|
||||||
|
if MYPY_CHECK_RUNNING:
|
||||||
|
from typing import ( # noqa: F401
|
||||||
|
Optional, Tuple, Dict, IO, Text, Union
|
||||||
|
)
|
||||||
|
from pip._internal.models.link import Link # noqa: F401
|
||||||
|
from pip._internal.utils.hashes import Hashes # noqa: F401
|
||||||
|
from pip._internal.vcs import AuthInfo # noqa: F401
|
||||||
|
|
||||||
|
try:
|
||||||
|
import ssl # noqa
|
||||||
|
except ImportError:
|
||||||
|
ssl = None
|
||||||
|
|
||||||
|
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']
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def user_agent():
|
||||||
|
"""
|
||||||
|
Return a string representing the user agent.
|
||||||
|
"""
|
||||||
|
data = {
|
||||||
|
"installer": {"name": "pip", "version": pip.__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 pip._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
|
||||||
|
http: URL. Returns (location, content). Content is unicode.
|
||||||
|
|
||||||
|
:param url: File path or url.
|
||||||
|
:param comes_from: Origin description of requirements.
|
||||||
|
:param session: Instance of pip.download.PipSession.
|
||||||
|
"""
|
||||||
|
if session is None:
|
||||||
|
raise TypeError(
|
||||||
|
"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')):
|
||||||
|
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
|
||||||
|
try:
|
||||||
|
with open(url, 'rb') as f:
|
||||||
|
content = auto_decode(f.read())
|
||||||
|
except IOError as exc:
|
||||||
|
raise InstallationError(
|
||||||
|
'Could not open requirements file: %s' % str(exc)
|
||||||
|
)
|
||||||
|
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):
|
||||||
|
vcs_backend = _get_used_vcs_backend(link)
|
||||||
|
vcs_backend.unpack(location)
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
link_path = url_to_path(link.url_without_fragment)
|
||||||
|
return os.path.isdir(link_path)
|
||||||
|
|
||||||
|
|
||||||
|
def _progress_indicator(iterable, *args, **kwargs):
|
||||||
|
return iterable
|
||||||
|
|
||||||
|
|
||||||
|
def _download_url(
|
||||||
|
resp, # type: Response
|
||||||
|
link, # type: Link
|
||||||
|
content_file, # type: IO
|
||||||
|
hashes, # type: Hashes
|
||||||
|
progress_bar # type: str
|
||||||
|
):
|
||||||
|
# type: (...) -> None
|
||||||
|
try:
|
||||||
|
total_length = int(resp.headers['content-length'])
|
||||||
|
except (ValueError, KeyError, TypeError):
|
||||||
|
total_length = 0
|
||||||
|
|
||||||
|
cached_resp = getattr(resp, "from_cache", False)
|
||||||
|
if logger.getEffectiveLevel() > logging.INFO:
|
||||||
|
show_progress = False
|
||||||
|
elif cached_resp:
|
||||||
|
show_progress = False
|
||||||
|
elif total_length > (40 * 1000):
|
||||||
|
show_progress = True
|
||||||
|
elif not total_length:
|
||||||
|
show_progress = True
|
||||||
|
else:
|
||||||
|
show_progress = False
|
||||||
|
|
||||||
|
show_url = link.show_url
|
||||||
|
|
||||||
|
def resp_read(chunk_size):
|
||||||
|
try:
|
||||||
|
# Special case for urllib3.
|
||||||
|
for chunk in resp.raw.stream(
|
||||||
|
chunk_size,
|
||||||
|
# We use decode_content=False here because we don't
|
||||||
|
# want urllib3 to mess with the raw bytes we get
|
||||||
|
# from the server. If we decompress inside of
|
||||||
|
# urllib3 then we cannot verify the checksum
|
||||||
|
# because the checksum will be of the compressed
|
||||||
|
# file. This breakage will only occur if the
|
||||||
|
# server adds a Content-Encoding header, which
|
||||||
|
# depends on how the server was configured:
|
||||||
|
# - Some servers will notice that the file isn't a
|
||||||
|
# compressible file and will leave the file alone
|
||||||
|
# and with an empty Content-Encoding
|
||||||
|
# - Some servers will notice that the file is
|
||||||
|
# already compressed and will leave the file
|
||||||
|
# alone and will add a Content-Encoding: gzip
|
||||||
|
# header
|
||||||
|
# - Some servers won't notice anything at all and
|
||||||
|
# will take a file that's already been compressed
|
||||||
|
# and compress it again and set the
|
||||||
|
# Content-Encoding: gzip header
|
||||||
|
#
|
||||||
|
# By setting this not to decode automatically we
|
||||||
|
# hope to eliminate problems with the second case.
|
||||||
|
decode_content=False):
|
||||||
|
yield chunk
|
||||||
|
except AttributeError:
|
||||||
|
# Standard file-like object.
|
||||||
|
while True:
|
||||||
|
chunk = resp.raw.read(chunk_size)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
yield chunk
|
||||||
|
|
||||||
|
def written_chunks(chunks):
|
||||||
|
for chunk in chunks:
|
||||||
|
content_file.write(chunk)
|
||||||
|
yield chunk
|
||||||
|
|
||||||
|
progress_indicator = _progress_indicator
|
||||||
|
|
||||||
|
if link.netloc == PyPI.netloc:
|
||||||
|
url = show_url
|
||||||
|
else:
|
||||||
|
url = link.url_without_fragment
|
||||||
|
|
||||||
|
if show_progress: # We don't show progress on cached responses
|
||||||
|
progress_indicator = DownloadProgressProvider(progress_bar,
|
||||||
|
max=total_length)
|
||||||
|
if total_length:
|
||||||
|
logger.info("Downloading %s (%s)", url, format_size(total_length))
|
||||||
|
else:
|
||||||
|
logger.info("Downloading %s", url)
|
||||||
|
elif cached_resp:
|
||||||
|
logger.info("Using cached %s", 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),
|
||||||
|
CONTENT_CHUNK_SIZE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if hashes:
|
||||||
|
hashes.check_against_chunks(downloaded_chunks)
|
||||||
|
else:
|
||||||
|
consume(downloaded_chunks)
|
||||||
|
|
||||||
|
|
||||||
|
def _copy_file(filename, location, link):
|
||||||
|
copy = True
|
||||||
|
download_location = os.path.join(location, link.filename)
|
||||||
|
if os.path.exists(download_location):
|
||||||
|
response = ask_path_exists(
|
||||||
|
'The file %s exists. (i)gnore, (w)ipe, (b)ackup, (a)abort' %
|
||||||
|
display_path(download_location), ('i', 'w', 'b', 'a'))
|
||||||
|
if response == 'i':
|
||||||
|
copy = False
|
||||||
|
elif response == 'w':
|
||||||
|
logger.warning('Deleting %s', display_path(download_location))
|
||||||
|
os.remove(download_location)
|
||||||
|
elif response == 'b':
|
||||||
|
dest_file = backup_dir(download_location)
|
||||||
|
logger.warning(
|
||||||
|
'Backing up %s to %s',
|
||||||
|
display_path(download_location),
|
||||||
|
display_path(dest_file),
|
||||||
|
)
|
||||||
|
shutil.move(download_location, dest_file)
|
||||||
|
elif response == 'a':
|
||||||
|
sys.exit(-1)
|
||||||
|
if copy:
|
||||||
|
shutil.copy(filename, download_location)
|
||||||
|
logger.info('Saved %s', display_path(download_location))
|
||||||
|
|
||||||
|
|
||||||
|
def unpack_http_url(
|
||||||
|
link, # type: Link
|
||||||
|
location, # type: str
|
||||||
|
download_dir=None, # type: Optional[str]
|
||||||
|
session=None, # type: Optional[PipSession]
|
||||||
|
hashes=None, # type: Optional[Hashes]
|
||||||
|
progress_bar="on" # type: str
|
||||||
|
):
|
||||||
|
# type: (...) -> None
|
||||||
|
if session is None:
|
||||||
|
raise TypeError(
|
||||||
|
"unpack_http_url() missing 1 required keyword argument: 'session'"
|
||||||
|
)
|
||||||
|
|
||||||
|
with TempDirectory(kind="unpack") as temp_dir:
|
||||||
|
# If a download dir is specified, is the file already downloaded there?
|
||||||
|
already_downloaded_path = None
|
||||||
|
if download_dir:
|
||||||
|
already_downloaded_path = _check_download_dir(link,
|
||||||
|
download_dir,
|
||||||
|
hashes)
|
||||||
|
|
||||||
|
if already_downloaded_path:
|
||||||
|
from_path = already_downloaded_path
|
||||||
|
content_type = mimetypes.guess_type(from_path)[0]
|
||||||
|
else:
|
||||||
|
# let's download to a tmp dir
|
||||||
|
from_path, content_type = _download_http_url(link,
|
||||||
|
session,
|
||||||
|
temp_dir.path,
|
||||||
|
hashes,
|
||||||
|
progress_bar)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
# a download dir is specified; let's copy the archive there
|
||||||
|
if download_dir and not already_downloaded_path:
|
||||||
|
_copy_file(from_path, download_dir, link)
|
||||||
|
|
||||||
|
if not already_downloaded_path:
|
||||||
|
os.unlink(from_path)
|
||||||
|
|
||||||
|
|
||||||
|
def unpack_file_url(
|
||||||
|
link, # type: Link
|
||||||
|
location, # type: str
|
||||||
|
download_dir=None, # type: Optional[str]
|
||||||
|
hashes=None # type: Optional[Hashes]
|
||||||
|
):
|
||||||
|
# type: (...) -> None
|
||||||
|
"""Unpack link into location.
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# If it's a url to a local directory
|
||||||
|
if is_dir_url(link):
|
||||||
|
if os.path.isdir(location):
|
||||||
|
rmtree(location)
|
||||||
|
shutil.copytree(link_path, location, symlinks=True)
|
||||||
|
if download_dir:
|
||||||
|
logger.info('Link is a directory, ignoring download_dir')
|
||||||
|
return
|
||||||
|
|
||||||
|
# If --require-hashes is off, `hashes` is either empty, the
|
||||||
|
# link's embedded hash, or MissingHashes; it is required to
|
||||||
|
# match. If --require-hashes is on, we are satisfied by any
|
||||||
|
# hash in `hashes` matching: a URL-based or an option-based
|
||||||
|
# one; no internet-sourced hash will be in `hashes`.
|
||||||
|
if hashes:
|
||||||
|
hashes.check_against_path(link_path)
|
||||||
|
|
||||||
|
# If a download dir is specified, is the file already there and valid?
|
||||||
|
already_downloaded_path = None
|
||||||
|
if download_dir:
|
||||||
|
already_downloaded_path = _check_download_dir(link,
|
||||||
|
download_dir,
|
||||||
|
hashes)
|
||||||
|
|
||||||
|
if already_downloaded_path:
|
||||||
|
from_path = already_downloaded_path
|
||||||
|
else:
|
||||||
|
from_path = link_path
|
||||||
|
|
||||||
|
content_type = mimetypes.guess_type(from_path)[0]
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
# 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]
|
||||||
|
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
|
||||||
|
):
|
||||||
|
# type: (...) -> None
|
||||||
|
"""Unpack link.
|
||||||
|
If link is a VCS link:
|
||||||
|
if only_download, export into download_dir and ignore location
|
||||||
|
else unpack into location
|
||||||
|
for other types of link:
|
||||||
|
- unpack into location
|
||||||
|
- if download_dir, copy the file into download_dir
|
||||||
|
- if only_download, mark location for deletion
|
||||||
|
|
||||||
|
:param hashes: A Hashes object, one of whose embedded hashes must match,
|
||||||
|
or HashMismatch will be raised. If the Hashes is empty, no matches are
|
||||||
|
required, and unhashable types of requirements (like VCS ones, which
|
||||||
|
would ordinarily raise HashUnsupported) are allowed.
|
||||||
|
"""
|
||||||
|
# non-editable vcs urls
|
||||||
|
if is_vcs_url(link):
|
||||||
|
unpack_vcs_link(link, location)
|
||||||
|
|
||||||
|
# file urls
|
||||||
|
elif is_file_url(link):
|
||||||
|
unpack_file_url(link, location, download_dir, hashes=hashes)
|
||||||
|
|
||||||
|
# http urls
|
||||||
|
else:
|
||||||
|
if session is None:
|
||||||
|
session = PipSession()
|
||||||
|
|
||||||
|
unpack_http_url(
|
||||||
|
link,
|
||||||
|
location,
|
||||||
|
download_dir,
|
||||||
|
session,
|
||||||
|
hashes=hashes,
|
||||||
|
progress_bar=progress_bar
|
||||||
|
)
|
||||||
|
if only_download:
|
||||||
|
write_delete_marker_file(location)
|
||||||
|
|
||||||
|
|
||||||
|
def _download_http_url(
|
||||||
|
link, # type: Link
|
||||||
|
session, # type: PipSession
|
||||||
|
temp_dir, # type: str
|
||||||
|
hashes, # type: Hashes
|
||||||
|
progress_bar # type: str
|
||||||
|
):
|
||||||
|
# type: (...) -> Tuple[str, str]
|
||||||
|
"""Download link url into temp_dir using provided session"""
|
||||||
|
target_url = link.url.split('#', 1)[0]
|
||||||
|
try:
|
||||||
|
resp = session.get(
|
||||||
|
target_url,
|
||||||
|
# We use Accept-Encoding: identity here because requests
|
||||||
|
# defaults to accepting compressed responses. This breaks in
|
||||||
|
# a variety of ways depending on how the server is configured.
|
||||||
|
# - Some servers will notice that the file isn't a compressible
|
||||||
|
# file and will leave the file alone and with an empty
|
||||||
|
# Content-Encoding
|
||||||
|
# - Some servers will notice that the file is already
|
||||||
|
# compressed and will leave the file alone and will add a
|
||||||
|
# Content-Encoding: gzip header
|
||||||
|
# - Some servers won't notice anything at all and will take
|
||||||
|
# a file that's already been compressed and compress it again
|
||||||
|
# and set the Content-Encoding: gzip header
|
||||||
|
# By setting this to request only the identity encoding We're
|
||||||
|
# hoping to eliminate the third case. Hopefully there does not
|
||||||
|
# exist a server which when given a file will notice it is
|
||||||
|
# already compressed and that you're not asking for a
|
||||||
|
# compressed file and will then decompress it before sending
|
||||||
|
# because if that's the case I don't think it'll ever be
|
||||||
|
# possible to make this work.
|
||||||
|
headers={"Accept-Encoding": "identity"},
|
||||||
|
stream=True,
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
except requests.HTTPError as exc:
|
||||||
|
logger.critical(
|
||||||
|
"HTTP error %s while getting %s", exc.response.status_code, link,
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
|
content_type = resp.headers.get('content-type', '')
|
||||||
|
filename = link.filename # fallback
|
||||||
|
# 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]
|
||||||
|
if not ext:
|
||||||
|
ext = mimetypes.guess_extension(content_type)
|
||||||
|
if ext:
|
||||||
|
filename += ext
|
||||||
|
if not ext and link.url != resp.url:
|
||||||
|
ext = os.path.splitext(resp.url)[1]
|
||||||
|
if ext:
|
||||||
|
filename += ext
|
||||||
|
file_path = os.path.join(temp_dir, filename)
|
||||||
|
with open(file_path, 'wb') as content_file:
|
||||||
|
_download_url(resp, link, content_file, hashes, progress_bar)
|
||||||
|
return file_path, content_type
|
||||||
|
|
||||||
|
|
||||||
|
def _check_download_dir(link, download_dir, hashes):
|
||||||
|
# type: (Link, str, 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
|
@@ -0,0 +1,274 @@
|
|||||||
|
"""Exceptions used throughout package"""
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from itertools import chain, groupby, repeat
|
||||||
|
|
||||||
|
from pip._vendor.six import iteritems
|
||||||
|
|
||||||
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
|
if MYPY_CHECK_RUNNING:
|
||||||
|
from typing import Optional # noqa: F401
|
||||||
|
from pip._internal.req.req_install import InstallRequirement # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
|
class PipError(Exception):
|
||||||
|
"""Base pip exception"""
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigurationError(PipError):
|
||||||
|
"""General exception in configuration"""
|
||||||
|
|
||||||
|
|
||||||
|
class InstallationError(PipError):
|
||||||
|
"""General exception during installation"""
|
||||||
|
|
||||||
|
|
||||||
|
class UninstallationError(PipError):
|
||||||
|
"""General exception during uninstallation"""
|
||||||
|
|
||||||
|
|
||||||
|
class DistributionNotFound(InstallationError):
|
||||||
|
"""Raised when a distribution cannot be found to satisfy a requirement"""
|
||||||
|
|
||||||
|
|
||||||
|
class RequirementsFileParseError(InstallationError):
|
||||||
|
"""Raised when a general error occurs parsing a requirements file line."""
|
||||||
|
|
||||||
|
|
||||||
|
class BestVersionAlreadyInstalled(PipError):
|
||||||
|
"""Raised when the most up-to-date version of a package is already
|
||||||
|
installed."""
|
||||||
|
|
||||||
|
|
||||||
|
class BadCommand(PipError):
|
||||||
|
"""Raised when virtualenv or a command is not found"""
|
||||||
|
|
||||||
|
|
||||||
|
class CommandError(PipError):
|
||||||
|
"""Raised when there is an error in command-line arguments"""
|
||||||
|
|
||||||
|
|
||||||
|
class PreviousBuildDirError(PipError):
|
||||||
|
"""Raised when there's a previous conflicting build directory"""
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidWheelFilename(InstallationError):
|
||||||
|
"""Invalid wheel filename."""
|
||||||
|
|
||||||
|
|
||||||
|
class UnsupportedWheel(InstallationError):
|
||||||
|
"""Unsupported wheel."""
|
||||||
|
|
||||||
|
|
||||||
|
class HashErrors(InstallationError):
|
||||||
|
"""Multiple HashError instances rolled into one for reporting"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.errors = []
|
||||||
|
|
||||||
|
def append(self, error):
|
||||||
|
self.errors.append(error)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
lines = []
|
||||||
|
self.errors.sort(key=lambda e: e.order)
|
||||||
|
for cls, errors_of_cls in groupby(self.errors, lambda e: e.__class__):
|
||||||
|
lines.append(cls.head)
|
||||||
|
lines.extend(e.body() for e in errors_of_cls)
|
||||||
|
if lines:
|
||||||
|
return '\n'.join(lines)
|
||||||
|
|
||||||
|
def __nonzero__(self):
|
||||||
|
return bool(self.errors)
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
return self.__nonzero__()
|
||||||
|
|
||||||
|
|
||||||
|
class HashError(InstallationError):
|
||||||
|
"""
|
||||||
|
A failure to verify a package against known-good hashes
|
||||||
|
|
||||||
|
:cvar order: An int sorting hash exception classes by difficulty of
|
||||||
|
recovery (lower being harder), so the user doesn't bother fretting
|
||||||
|
about unpinned packages when he has deeper issues, like VCS
|
||||||
|
dependencies, to deal with. Also keeps error reports in a
|
||||||
|
deterministic order.
|
||||||
|
:cvar head: A section heading for display above potentially many
|
||||||
|
exceptions of this kind
|
||||||
|
:ivar req: The InstallRequirement that triggered this error. This is
|
||||||
|
pasted on after the exception is instantiated, because it's not
|
||||||
|
typically available earlier.
|
||||||
|
|
||||||
|
"""
|
||||||
|
req = None # type: Optional[InstallRequirement]
|
||||||
|
head = ''
|
||||||
|
|
||||||
|
def body(self):
|
||||||
|
"""Return a summary of me for display under the heading.
|
||||||
|
|
||||||
|
This default implementation simply prints a description of the
|
||||||
|
triggering requirement.
|
||||||
|
|
||||||
|
:param req: The InstallRequirement that provoked this error, with
|
||||||
|
populate_link() having already been called
|
||||||
|
|
||||||
|
"""
|
||||||
|
return ' %s' % self._requirement_name()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '%s\n%s' % (self.head, self.body())
|
||||||
|
|
||||||
|
def _requirement_name(self):
|
||||||
|
"""Return a description of the requirement that triggered me.
|
||||||
|
|
||||||
|
This default implementation returns long description of the req, with
|
||||||
|
line numbers
|
||||||
|
|
||||||
|
"""
|
||||||
|
return str(self.req) if self.req else 'unknown package'
|
||||||
|
|
||||||
|
|
||||||
|
class VcsHashUnsupported(HashError):
|
||||||
|
"""A hash was provided for a version-control-system-based requirement, but
|
||||||
|
we don't have a method for hashing those."""
|
||||||
|
|
||||||
|
order = 0
|
||||||
|
head = ("Can't verify hashes for these requirements because we don't "
|
||||||
|
"have a way to hash version control repositories:")
|
||||||
|
|
||||||
|
|
||||||
|
class DirectoryUrlHashUnsupported(HashError):
|
||||||
|
"""A hash was provided for a version-control-system-based requirement, but
|
||||||
|
we don't have a method for hashing those."""
|
||||||
|
|
||||||
|
order = 1
|
||||||
|
head = ("Can't verify hashes for these file:// requirements because they "
|
||||||
|
"point to directories:")
|
||||||
|
|
||||||
|
|
||||||
|
class HashMissing(HashError):
|
||||||
|
"""A hash was needed for a requirement but is absent."""
|
||||||
|
|
||||||
|
order = 2
|
||||||
|
head = ('Hashes are required in --require-hashes mode, but they are '
|
||||||
|
'missing from some requirements. Here is a list of those '
|
||||||
|
'requirements along with the hashes their downloaded archives '
|
||||||
|
'actually had. Add lines like these to your requirements files to '
|
||||||
|
'prevent tampering. (If you did not enable --require-hashes '
|
||||||
|
'manually, note that it turns on automatically when any package '
|
||||||
|
'has a hash.)')
|
||||||
|
|
||||||
|
def __init__(self, gotten_hash):
|
||||||
|
"""
|
||||||
|
:param gotten_hash: The hash of the (possibly malicious) archive we
|
||||||
|
just downloaded
|
||||||
|
"""
|
||||||
|
self.gotten_hash = gotten_hash
|
||||||
|
|
||||||
|
def body(self):
|
||||||
|
# Dodge circular import.
|
||||||
|
from pip._internal.utils.hashes import FAVORITE_HASH
|
||||||
|
|
||||||
|
package = None
|
||||||
|
if self.req:
|
||||||
|
# In the case of URL-based requirements, display the original URL
|
||||||
|
# seen in the requirements file rather than the package name,
|
||||||
|
# so the output can be directly copied into the requirements file.
|
||||||
|
package = (self.req.original_link if self.req.original_link
|
||||||
|
# In case someone feeds something downright stupid
|
||||||
|
# to InstallRequirement's constructor.
|
||||||
|
else getattr(self.req, 'req', None))
|
||||||
|
return ' %s --hash=%s:%s' % (package or 'unknown package',
|
||||||
|
FAVORITE_HASH,
|
||||||
|
self.gotten_hash)
|
||||||
|
|
||||||
|
|
||||||
|
class HashUnpinned(HashError):
|
||||||
|
"""A requirement had a hash specified but was not pinned to a specific
|
||||||
|
version."""
|
||||||
|
|
||||||
|
order = 3
|
||||||
|
head = ('In --require-hashes mode, all requirements must have their '
|
||||||
|
'versions pinned with ==. These do not:')
|
||||||
|
|
||||||
|
|
||||||
|
class HashMismatch(HashError):
|
||||||
|
"""
|
||||||
|
Distribution file hash values don't match.
|
||||||
|
|
||||||
|
:ivar package_name: The name of the package that triggered the hash
|
||||||
|
mismatch. Feel free to write to this after the exception is raise to
|
||||||
|
improve its error message.
|
||||||
|
|
||||||
|
"""
|
||||||
|
order = 4
|
||||||
|
head = ('THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS '
|
||||||
|
'FILE. If you have updated the package versions, please update '
|
||||||
|
'the hashes. Otherwise, examine the package contents carefully; '
|
||||||
|
'someone may have tampered with them.')
|
||||||
|
|
||||||
|
def __init__(self, allowed, gots):
|
||||||
|
"""
|
||||||
|
:param allowed: A dict of algorithm names pointing to lists of allowed
|
||||||
|
hex digests
|
||||||
|
:param gots: A dict of algorithm names pointing to hashes we
|
||||||
|
actually got from the files under suspicion
|
||||||
|
"""
|
||||||
|
self.allowed = allowed
|
||||||
|
self.gots = gots
|
||||||
|
|
||||||
|
def body(self):
|
||||||
|
return ' %s:\n%s' % (self._requirement_name(),
|
||||||
|
self._hash_comparison())
|
||||||
|
|
||||||
|
def _hash_comparison(self):
|
||||||
|
"""
|
||||||
|
Return a comparison of actual and expected hash values.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
Expected sha256 abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde
|
||||||
|
or 123451234512345123451234512345123451234512345
|
||||||
|
Got bcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdef
|
||||||
|
|
||||||
|
"""
|
||||||
|
def hash_then_or(hash_name):
|
||||||
|
# For now, all the decent hashes have 6-char names, so we can get
|
||||||
|
# away with hard-coding space literals.
|
||||||
|
return chain([hash_name], repeat(' or'))
|
||||||
|
|
||||||
|
lines = []
|
||||||
|
for hash_name, expecteds in iteritems(self.allowed):
|
||||||
|
prefix = hash_then_or(hash_name)
|
||||||
|
lines.extend((' Expected %s %s' % (next(prefix), e))
|
||||||
|
for e in expecteds)
|
||||||
|
lines.append(' Got %s\n' %
|
||||||
|
self.gots[hash_name].hexdigest())
|
||||||
|
prefix = ' or'
|
||||||
|
return '\n'.join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
class UnsupportedPythonVersion(InstallationError):
|
||||||
|
"""Unsupported python version according to Requires-Python package
|
||||||
|
metadata."""
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigurationFileCouldNotBeLoaded(ConfigurationError):
|
||||||
|
"""When there are errors while loading a configuration file
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, reason="could not be loaded", fname=None, error=None):
|
||||||
|
super(ConfigurationFileCouldNotBeLoaded, self).__init__(error)
|
||||||
|
self.reason = reason
|
||||||
|
self.fname = fname
|
||||||
|
self.error = error
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.fname is not None:
|
||||||
|
message_part = " in {}.".format(self.fname)
|
||||||
|
else:
|
||||||
|
assert self.error is not None
|
||||||
|
message_part = ".\n{}\n".format(self.error.message)
|
||||||
|
return "Configuration file {}{}".format(self.reason, message_part)
|
@@ -0,0 +1,990 @@
|
|||||||
|
"""Routines related to PyPI, indexes"""
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import cgi
|
||||||
|
import itertools
|
||||||
|
import logging
|
||||||
|
import mimetypes
|
||||||
|
import os
|
||||||
|
import posixpath
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
from pip._vendor import html5lib, requests, six
|
||||||
|
from pip._vendor.distlib.compat import unescape
|
||||||
|
from pip._vendor.packaging import specifiers
|
||||||
|
from pip._vendor.packaging.utils import canonicalize_name
|
||||||
|
from pip._vendor.packaging.version import parse as parse_version
|
||||||
|
from pip._vendor.requests.exceptions import RetryError, SSLError
|
||||||
|
from pip._vendor.six.moves.urllib import parse as urllib_parse
|
||||||
|
from pip._vendor.six.moves.urllib import request as urllib_request
|
||||||
|
|
||||||
|
from pip._internal.download import HAS_TLS, is_url, path_to_url, url_to_path
|
||||||
|
from pip._internal.exceptions import (
|
||||||
|
BestVersionAlreadyInstalled, DistributionNotFound, InvalidWheelFilename,
|
||||||
|
UnsupportedWheel,
|
||||||
|
)
|
||||||
|
from pip._internal.models.candidate import InstallationCandidate
|
||||||
|
from pip._internal.models.format_control import FormatControl
|
||||||
|
from pip._internal.models.index import PyPI
|
||||||
|
from pip._internal.models.link import Link
|
||||||
|
from pip._internal.pep425tags import get_supported
|
||||||
|
from pip._internal.utils.compat import ipaddress
|
||||||
|
from pip._internal.utils.logging import indent_log
|
||||||
|
from pip._internal.utils.misc import (
|
||||||
|
ARCHIVE_EXTENSIONS, SUPPORTED_EXTENSIONS, WHEEL_EXTENSION, normalize_path,
|
||||||
|
redact_password_from_url,
|
||||||
|
)
|
||||||
|
from pip._internal.utils.packaging import check_requires_python
|
||||||
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
from pip._internal.wheel import Wheel
|
||||||
|
|
||||||
|
if MYPY_CHECK_RUNNING:
|
||||||
|
from logging import Logger # noqa: F401
|
||||||
|
from typing import ( # noqa: F401
|
||||||
|
Tuple, Optional, Any, List, Union, Callable, Set, Sequence,
|
||||||
|
Iterable, MutableMapping
|
||||||
|
)
|
||||||
|
from pip._vendor.packaging.version import _BaseVersion # noqa: F401
|
||||||
|
from pip._vendor.requests import Response # noqa: F401
|
||||||
|
from pip._internal.req import InstallRequirement # noqa: F401
|
||||||
|
from pip._internal.download import PipSession # noqa: F401
|
||||||
|
|
||||||
|
SecureOrigin = Tuple[str, str, Optional[str]]
|
||||||
|
BuildTag = Tuple[Any, ...] # either emply tuple or Tuple[int, str]
|
||||||
|
CandidateSortingKey = Tuple[int, _BaseVersion, BuildTag, Optional[int]]
|
||||||
|
|
||||||
|
__all__ = ['FormatControl', 'PackageFinder']
|
||||||
|
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
from pip._internal.vcs import VcsSupport
|
||||||
|
for scheme in VcsSupport.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', 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 _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 _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 as exc:
|
||||||
|
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 requests.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 HTMLPage(resp.content, resp.url, resp.headers)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class PackageFinder(object):
|
||||||
|
"""This finds packages.
|
||||||
|
|
||||||
|
This is meant to match easy_install's technique for looking for
|
||||||
|
packages, by reading pages and looking for appropriate links.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
find_links, # type: List[str]
|
||||||
|
index_urls, # type: List[str]
|
||||||
|
allow_all_prereleases=False, # type: bool
|
||||||
|
trusted_hosts=None, # type: Optional[Iterable[str]]
|
||||||
|
session=None, # type: Optional[PipSession]
|
||||||
|
format_control=None, # type: Optional[FormatControl]
|
||||||
|
platform=None, # type: Optional[str]
|
||||||
|
versions=None, # type: Optional[List[str]]
|
||||||
|
abi=None, # type: Optional[str]
|
||||||
|
implementation=None, # type: Optional[str]
|
||||||
|
prefer_binary=False # type: bool
|
||||||
|
):
|
||||||
|
# type: (...) -> None
|
||||||
|
"""Create a PackageFinder.
|
||||||
|
|
||||||
|
: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 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 versions: A list of strings or None. This is passed directly
|
||||||
|
to pep425tags.py in the get_supported() method.
|
||||||
|
:param abi: A string or None. This is passed directly
|
||||||
|
to pep425tags.py in the get_supported() method.
|
||||||
|
:param implementation: A string or None. This is passed directly
|
||||||
|
to pep425tags.py in the get_supported() method.
|
||||||
|
"""
|
||||||
|
if session is None:
|
||||||
|
raise TypeError(
|
||||||
|
"PackageFinder() missing 1 required keyword argument: "
|
||||||
|
"'session'"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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 ~...
|
||||||
|
self.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
|
||||||
|
self.find_links.append(link)
|
||||||
|
|
||||||
|
self.index_urls = index_urls
|
||||||
|
|
||||||
|
# These are boring links that have already been logged somehow:
|
||||||
|
self.logged_links = set() # type: Set[Link]
|
||||||
|
|
||||||
|
self.format_control = format_control or FormatControl(set(), set())
|
||||||
|
|
||||||
|
# Domains that we won't emit warnings for when not using HTTPS
|
||||||
|
self.secure_origins = [
|
||||||
|
("*", host, "*")
|
||||||
|
for host in (trusted_hosts if trusted_hosts else [])
|
||||||
|
] # type: List[SecureOrigin]
|
||||||
|
|
||||||
|
# Do we want to allow _all_ pre-releases?
|
||||||
|
self.allow_all_prereleases = allow_all_prereleases
|
||||||
|
|
||||||
|
# The Session we'll use to make requests
|
||||||
|
self.session = session
|
||||||
|
|
||||||
|
# The valid tags to check potential found wheel candidates against
|
||||||
|
self.valid_tags = get_supported(
|
||||||
|
versions=versions,
|
||||||
|
platform=platform,
|
||||||
|
abi=abi,
|
||||||
|
impl=implementation,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Do we prefer old, but valid, binary dist over new source dist
|
||||||
|
self.prefer_binary = prefer_binary
|
||||||
|
|
||||||
|
# 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(self.index_urls, self.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
|
||||||
|
|
||||||
|
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_password_from_url(url) for url in self.index_urls))
|
||||||
|
)
|
||||||
|
if self.find_links:
|
||||||
|
lines.append(
|
||||||
|
"Looking in links: {}".format(", ".join(self.find_links))
|
||||||
|
)
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _sort_locations(locations, expand_dir=False):
|
||||||
|
# type: (Sequence[str], bool) -> Tuple[List[str], List[str]]
|
||||||
|
"""
|
||||||
|
Sort locations into "files" (archives) and "urls", and 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
|
||||||
|
|
||||||
|
def _candidate_sort_key(self, candidate):
|
||||||
|
# type: (InstallationCandidate) -> CandidateSortingKey
|
||||||
|
"""
|
||||||
|
Function used to generate link sort key for link tuples.
|
||||||
|
The greater the return value, the more preferred it is.
|
||||||
|
If not finding wheels, then sorted by version only.
|
||||||
|
If finding wheels, then the sort order is by version, then:
|
||||||
|
1. existing installs
|
||||||
|
2. wheels ordered via Wheel.support_index_min(self.valid_tags)
|
||||||
|
3. source archives
|
||||||
|
If prefer_binary was set, then all wheels are sorted above sources.
|
||||||
|
Note: it was considered to embed this logic into the Link
|
||||||
|
comparison operators, but then different sdist links
|
||||||
|
with the same version, would have to be considered equal
|
||||||
|
"""
|
||||||
|
support_num = len(self.valid_tags)
|
||||||
|
build_tag = tuple() # type: BuildTag
|
||||||
|
binary_preference = 0
|
||||||
|
if candidate.location.is_wheel:
|
||||||
|
# can raise InvalidWheelFilename
|
||||||
|
wheel = Wheel(candidate.location.filename)
|
||||||
|
if not wheel.supported(self.valid_tags):
|
||||||
|
raise UnsupportedWheel(
|
||||||
|
"%s is not a supported wheel for this platform. It "
|
||||||
|
"can't be sorted." % wheel.filename
|
||||||
|
)
|
||||||
|
if self.prefer_binary:
|
||||||
|
binary_preference = 1
|
||||||
|
pri = -(wheel.support_index_min(self.valid_tags))
|
||||||
|
if wheel.build_tag is not None:
|
||||||
|
match = re.match(r'^(\d+)(.*)$', wheel.build_tag)
|
||||||
|
build_tag_groups = match.groups()
|
||||||
|
build_tag = (int(build_tag_groups[0]), build_tag_groups[1])
|
||||||
|
else: # sdist
|
||||||
|
pri = -(support_num)
|
||||||
|
return (binary_preference, candidate.version, build_tag, pri)
|
||||||
|
|
||||||
|
def _validate_secure_origin(self, logger, location):
|
||||||
|
# type: (Logger, Link) -> bool
|
||||||
|
# Determine if this url used a secure transport mechanism
|
||||||
|
parsed = urllib_parse.urlparse(str(location))
|
||||||
|
origin = (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.)
|
||||||
|
protocol = origin[0].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 (SECURE_ORIGINS + self.secure_origins):
|
||||||
|
if protocol != secure_origin[0] and secure_origin[0] != "*":
|
||||||
|
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[1]
|
||||||
|
if (
|
||||||
|
isinstance(origin[1], six.text_type) or
|
||||||
|
origin[1] is None
|
||||||
|
)
|
||||||
|
else origin[1].decode("utf8")
|
||||||
|
)
|
||||||
|
network = ipaddress.ip_network(
|
||||||
|
secure_origin[1]
|
||||||
|
if isinstance(secure_origin[1], six.text_type)
|
||||||
|
# setting secure_origin[1] to proper Union[bytes, str]
|
||||||
|
# creates problems in other places
|
||||||
|
else secure_origin[1].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[1] and
|
||||||
|
origin[1].lower() != secure_origin[1].lower() and
|
||||||
|
secure_origin[1] != "*"):
|
||||||
|
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 patches
|
||||||
|
if (origin[2] != secure_origin[2] and
|
||||||
|
secure_origin[2] != "*" and
|
||||||
|
secure_origin[2] 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'.",
|
||||||
|
parsed.hostname,
|
||||||
|
parsed.hostname,
|
||||||
|
)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
def find_all_candidates(self, project_name):
|
||||||
|
# type: (str) -> List[Optional[InstallationCandidate]]
|
||||||
|
"""Find all available InstallationCandidate for project_name
|
||||||
|
|
||||||
|
This checks index_urls and find_links.
|
||||||
|
All versions found are returned as an InstallationCandidate list.
|
||||||
|
|
||||||
|
See _link_package_versions for details on which files are accepted
|
||||||
|
"""
|
||||||
|
index_locations = self._get_index_urls_locations(project_name)
|
||||||
|
index_file_loc, index_url_loc = self._sort_locations(index_locations)
|
||||||
|
fl_file_loc, fl_url_loc = self._sort_locations(
|
||||||
|
self.find_links, expand_dir=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
file_locations = (Link(url) for url in itertools.chain(
|
||||||
|
index_file_loc, fl_file_loc,
|
||||||
|
))
|
||||||
|
|
||||||
|
# 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 any thing which 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._validate_secure_origin(logger, link)
|
||||||
|
]
|
||||||
|
|
||||||
|
logger.debug('%d location(s) to search for versions of %s:',
|
||||||
|
len(url_locations), project_name)
|
||||||
|
|
||||||
|
for location in url_locations:
|
||||||
|
logger.debug('* %s', location)
|
||||||
|
|
||||||
|
canonical_name = canonicalize_name(project_name)
|
||||||
|
formats = self.format_control.get_allowed_formats(canonical_name)
|
||||||
|
search = Search(project_name, canonical_name, formats)
|
||||||
|
find_links_versions = self._package_versions(
|
||||||
|
# We trust every directly linked archive in find_links
|
||||||
|
(Link(url, '-f') for url in self.find_links),
|
||||||
|
search
|
||||||
|
)
|
||||||
|
|
||||||
|
page_versions = []
|
||||||
|
for page in self._get_pages(url_locations, project_name):
|
||||||
|
logger.debug('Analyzing links from page %s', page.url)
|
||||||
|
with indent_log():
|
||||||
|
page_versions.extend(
|
||||||
|
self._package_versions(page.iter_links(), search)
|
||||||
|
)
|
||||||
|
|
||||||
|
file_versions = self._package_versions(file_locations, search)
|
||||||
|
if file_versions:
|
||||||
|
file_versions.sort(reverse=True)
|
||||||
|
logger.debug(
|
||||||
|
'Local files found: %s',
|
||||||
|
', '.join([
|
||||||
|
url_to_path(candidate.location.url)
|
||||||
|
for candidate in file_versions
|
||||||
|
])
|
||||||
|
)
|
||||||
|
|
||||||
|
# This is an intentional priority ordering
|
||||||
|
return file_versions + find_links_versions + page_versions
|
||||||
|
|
||||||
|
def find_requirement(self, req, upgrade):
|
||||||
|
# type: (InstallRequirement, bool) -> Optional[Link]
|
||||||
|
"""Try to find a Link matching req
|
||||||
|
|
||||||
|
Expects req, an InstallRequirement and upgrade, a boolean
|
||||||
|
Returns a Link if found,
|
||||||
|
Raises DistributionNotFound or BestVersionAlreadyInstalled otherwise
|
||||||
|
"""
|
||||||
|
all_candidates = self.find_all_candidates(req.name)
|
||||||
|
|
||||||
|
# Filter out anything which doesn't match our specifier
|
||||||
|
compatible_versions = set(
|
||||||
|
req.specifier.filter(
|
||||||
|
# We turn the version object into a str here because otherwise
|
||||||
|
# when we're debundled but setuptools isn't, Python will see
|
||||||
|
# packaging.version.Version and
|
||||||
|
# pkg_resources._vendor.packaging.version.Version as different
|
||||||
|
# types. This way we'll use a str as a common data interchange
|
||||||
|
# format. If we stop using the pkg_resources provided specifier
|
||||||
|
# and start using our own, we can drop the cast to str().
|
||||||
|
[str(c.version) for c in all_candidates],
|
||||||
|
prereleases=(
|
||||||
|
self.allow_all_prereleases
|
||||||
|
if self.allow_all_prereleases else None
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
applicable_candidates = [
|
||||||
|
# Again, converting to str to deal with debundling.
|
||||||
|
c for c in all_candidates if str(c.version) in compatible_versions
|
||||||
|
]
|
||||||
|
|
||||||
|
if applicable_candidates:
|
||||||
|
best_candidate = max(applicable_candidates,
|
||||||
|
key=self._candidate_sort_key)
|
||||||
|
else:
|
||||||
|
best_candidate = None
|
||||||
|
|
||||||
|
if req.satisfied_by is not None:
|
||||||
|
installed_version = parse_version(req.satisfied_by.version)
|
||||||
|
else:
|
||||||
|
installed_version = None
|
||||||
|
|
||||||
|
if installed_version is None and best_candidate is None:
|
||||||
|
logger.critical(
|
||||||
|
'Could not find a version that satisfies the requirement %s '
|
||||||
|
'(from versions: %s)',
|
||||||
|
req,
|
||||||
|
', '.join(
|
||||||
|
sorted(
|
||||||
|
{str(c.version) for c in all_candidates},
|
||||||
|
key=parse_version,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
raise DistributionNotFound(
|
||||||
|
'No matching distribution found for %s' % req
|
||||||
|
)
|
||||||
|
|
||||||
|
best_installed = False
|
||||||
|
if installed_version and (
|
||||||
|
best_candidate is None or
|
||||||
|
best_candidate.version <= installed_version):
|
||||||
|
best_installed = True
|
||||||
|
|
||||||
|
if not upgrade and installed_version is not None:
|
||||||
|
if best_installed:
|
||||||
|
logger.debug(
|
||||||
|
'Existing installed version (%s) is most up-to-date and '
|
||||||
|
'satisfies requirement',
|
||||||
|
installed_version,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.debug(
|
||||||
|
'Existing installed version (%s) satisfies requirement '
|
||||||
|
'(most up-to-date version is %s)',
|
||||||
|
installed_version,
|
||||||
|
best_candidate.version,
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if best_installed:
|
||||||
|
# We have an existing version, and its the best version
|
||||||
|
logger.debug(
|
||||||
|
'Installed version (%s) is most up-to-date (past versions: '
|
||||||
|
'%s)',
|
||||||
|
installed_version,
|
||||||
|
', '.join(sorted(compatible_versions, key=parse_version)) or
|
||||||
|
"none",
|
||||||
|
)
|
||||||
|
raise BestVersionAlreadyInstalled
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
'Using version %s (newest of versions: %s)',
|
||||||
|
best_candidate.version,
|
||||||
|
', '.join(sorted(compatible_versions, key=parse_version))
|
||||||
|
)
|
||||||
|
return best_candidate.location
|
||||||
|
|
||||||
|
def _get_pages(self, locations, project_name):
|
||||||
|
# type: (Iterable[Link], str) -> Iterable[HTMLPage]
|
||||||
|
"""
|
||||||
|
Yields (page, page_url) from the given locations, skipping
|
||||||
|
locations that have errors.
|
||||||
|
"""
|
||||||
|
seen = set() # type: Set[Link]
|
||||||
|
for location in locations:
|
||||||
|
if location in seen:
|
||||||
|
continue
|
||||||
|
seen.add(location)
|
||||||
|
|
||||||
|
page = _get_html_page(location, session=self.session)
|
||||||
|
if page is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
yield page
|
||||||
|
|
||||||
|
_py_version_re = re.compile(r'-py([123]\.?[0-9]?)$')
|
||||||
|
|
||||||
|
def _sort_links(self, links):
|
||||||
|
# type: (Iterable[Link]) -> List[Link]
|
||||||
|
"""
|
||||||
|
Returns elements of links in order, non-egg links first, egg links
|
||||||
|
second, while eliminating duplicates
|
||||||
|
"""
|
||||||
|
eggs, no_eggs = [], []
|
||||||
|
seen = set() # type: Set[Link]
|
||||||
|
for link in links:
|
||||||
|
if link not in seen:
|
||||||
|
seen.add(link)
|
||||||
|
if link.egg_fragment:
|
||||||
|
eggs.append(link)
|
||||||
|
else:
|
||||||
|
no_eggs.append(link)
|
||||||
|
return no_eggs + eggs
|
||||||
|
|
||||||
|
def _package_versions(
|
||||||
|
self,
|
||||||
|
links, # type: Iterable[Link]
|
||||||
|
search # type: Search
|
||||||
|
):
|
||||||
|
# type: (...) -> List[Optional[InstallationCandidate]]
|
||||||
|
result = []
|
||||||
|
for link in self._sort_links(links):
|
||||||
|
v = self._link_package_versions(link, search)
|
||||||
|
if v is not None:
|
||||||
|
result.append(v)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _log_skipped_link(self, link, reason):
|
||||||
|
# type: (Link, str) -> None
|
||||||
|
if link not in self.logged_links:
|
||||||
|
logger.debug('Skipping link %s; %s', link, reason)
|
||||||
|
self.logged_links.add(link)
|
||||||
|
|
||||||
|
def _link_package_versions(self, link, search):
|
||||||
|
# type: (Link, Search) -> Optional[InstallationCandidate]
|
||||||
|
"""Return an InstallationCandidate or None"""
|
||||||
|
version = None
|
||||||
|
if link.egg_fragment:
|
||||||
|
egg_info = link.egg_fragment
|
||||||
|
ext = link.ext
|
||||||
|
else:
|
||||||
|
egg_info, ext = link.splitext()
|
||||||
|
if not ext:
|
||||||
|
self._log_skipped_link(link, 'not a file')
|
||||||
|
return None
|
||||||
|
if ext not in SUPPORTED_EXTENSIONS:
|
||||||
|
self._log_skipped_link(
|
||||||
|
link, 'unsupported archive format: %s' % ext,
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
if "binary" not in search.formats and ext == WHEEL_EXTENSION:
|
||||||
|
self._log_skipped_link(
|
||||||
|
link, 'No binaries permitted for %s' % search.supplied,
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
if "macosx10" in link.path and ext == '.zip':
|
||||||
|
self._log_skipped_link(link, 'macosx10 one')
|
||||||
|
return None
|
||||||
|
if ext == WHEEL_EXTENSION:
|
||||||
|
try:
|
||||||
|
wheel = Wheel(link.filename)
|
||||||
|
except InvalidWheelFilename:
|
||||||
|
self._log_skipped_link(link, 'invalid wheel filename')
|
||||||
|
return None
|
||||||
|
if canonicalize_name(wheel.name) != search.canonical:
|
||||||
|
self._log_skipped_link(
|
||||||
|
link, 'wrong project name (not %s)' % search.supplied)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not wheel.supported(self.valid_tags):
|
||||||
|
self._log_skipped_link(
|
||||||
|
link, 'it is not compatible with this Python')
|
||||||
|
return None
|
||||||
|
|
||||||
|
version = wheel.version
|
||||||
|
|
||||||
|
# This should be up by the search.ok_binary check, but see issue 2700.
|
||||||
|
if "source" not in search.formats and ext != WHEEL_EXTENSION:
|
||||||
|
self._log_skipped_link(
|
||||||
|
link, 'No sources permitted for %s' % search.supplied,
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not version:
|
||||||
|
version = _egg_info_matches(egg_info, search.canonical)
|
||||||
|
if not version:
|
||||||
|
self._log_skipped_link(
|
||||||
|
link, 'Missing project version for %s' % search.supplied)
|
||||||
|
return None
|
||||||
|
|
||||||
|
match = self._py_version_re.search(version)
|
||||||
|
if match:
|
||||||
|
version = version[:match.start()]
|
||||||
|
py_version = match.group(1)
|
||||||
|
if py_version != sys.version[:3]:
|
||||||
|
self._log_skipped_link(
|
||||||
|
link, 'Python version is incorrect')
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
support_this_python = check_requires_python(link.requires_python)
|
||||||
|
except specifiers.InvalidSpecifier:
|
||||||
|
logger.debug("Package %s has an invalid Requires-Python entry: %s",
|
||||||
|
link.filename, link.requires_python)
|
||||||
|
support_this_python = True
|
||||||
|
|
||||||
|
if not support_this_python:
|
||||||
|
logger.debug("The package %s is incompatible with the python "
|
||||||
|
"version in use. Acceptable python versions are: %s",
|
||||||
|
link, link.requires_python)
|
||||||
|
return None
|
||||||
|
logger.debug('Found link %s, version: %s', link, version)
|
||||||
|
|
||||||
|
return InstallationCandidate(search.supplied, version, link)
|
||||||
|
|
||||||
|
|
||||||
|
def _find_name_version_sep(egg_info, canonical_name):
|
||||||
|
# type: (str, str) -> int
|
||||||
|
"""Find the separator's index based on the package's canonical name.
|
||||||
|
|
||||||
|
`egg_info` must be an egg info string for the given package, and
|
||||||
|
`canonical_name` must be the package's canonical name.
|
||||||
|
|
||||||
|
This function is needed since the canonicalized name does not necessarily
|
||||||
|
have the same length as the egg info's name part. An example::
|
||||||
|
|
||||||
|
>>> egg_info = 'foo__bar-1.0'
|
||||||
|
>>> canonical_name = 'foo-bar'
|
||||||
|
>>> _find_name_version_sep(egg_info, canonical_name)
|
||||||
|
8
|
||||||
|
"""
|
||||||
|
# Project name and version must be separated by one single dash. Find all
|
||||||
|
# occurrences of dashes; if the string in front of it matches the canonical
|
||||||
|
# name, this is the one separating the name and version parts.
|
||||||
|
for i, c in enumerate(egg_info):
|
||||||
|
if c != "-":
|
||||||
|
continue
|
||||||
|
if canonicalize_name(egg_info[:i]) == canonical_name:
|
||||||
|
return i
|
||||||
|
raise ValueError("{} does not match {}".format(egg_info, canonical_name))
|
||||||
|
|
||||||
|
|
||||||
|
def _egg_info_matches(egg_info, canonical_name):
|
||||||
|
# type: (str, str) -> Optional[str]
|
||||||
|
"""Pull the version part out of a string.
|
||||||
|
|
||||||
|
:param egg_info: The string to parse. E.g. foo-2.1
|
||||||
|
:param canonical_name: The canonicalized name of the package this
|
||||||
|
belongs to.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
version_start = _find_name_version_sep(egg_info, canonical_name) + 1
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
version = egg_info[version_start:]
|
||||||
|
if not version:
|
||||||
|
return None
|
||||||
|
return version
|
||||||
|
|
||||||
|
|
||||||
|
def _determine_base_url(document, page_url):
|
||||||
|
"""Determine the HTML document's base URL.
|
||||||
|
|
||||||
|
This looks for a ``<base>`` tag in the HTML document. If present, its href
|
||||||
|
attribute denotes the base URL of anchor tags in the document. If there is
|
||||||
|
no such tag (or if it does not have a valid href attribute), the HTML
|
||||||
|
file's URL is used as the base URL.
|
||||||
|
|
||||||
|
:param document: An HTML document representation. The current
|
||||||
|
implementation expects the result of ``html5lib.parse()``.
|
||||||
|
:param page_url: The URL of the HTML document.
|
||||||
|
"""
|
||||||
|
for base in document.findall(".//base"):
|
||||||
|
href = base.get("href")
|
||||||
|
if href is not None:
|
||||||
|
return href
|
||||||
|
return page_url
|
||||||
|
|
||||||
|
|
||||||
|
def _get_encoding_from_headers(headers):
|
||||||
|
"""Determine if we have any encoding information in our headers.
|
||||||
|
"""
|
||||||
|
if headers and "Content-Type" in headers:
|
||||||
|
content_type, params = cgi.parse_header(headers["Content-Type"])
|
||||||
|
if "charset" in params:
|
||||||
|
return params['charset']
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
_CLEAN_LINK_RE = re.compile(r'[^a-z0-9$&+,/:;=?@.#%_\\|-]', re.I)
|
||||||
|
|
||||||
|
|
||||||
|
def _clean_link(url):
|
||||||
|
# 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)."""
|
||||||
|
return _CLEAN_LINK_RE.sub(lambda match: '%%%2x' % ord(match.group(0)), url)
|
||||||
|
|
||||||
|
|
||||||
|
class HTMLPage(object):
|
||||||
|
"""Represents one page, along with its URL"""
|
||||||
|
|
||||||
|
def __init__(self, content, url, headers=None):
|
||||||
|
# type: (bytes, str, MutableMapping[str, str]) -> None
|
||||||
|
self.content = content
|
||||||
|
self.url = url
|
||||||
|
self.headers = headers
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return redact_password_from_url(self.url)
|
||||||
|
|
||||||
|
def iter_links(self):
|
||||||
|
# type: () -> Iterable[Link]
|
||||||
|
"""Yields all links in the page"""
|
||||||
|
document = html5lib.parse(
|
||||||
|
self.content,
|
||||||
|
transport_encoding=_get_encoding_from_headers(self.headers),
|
||||||
|
namespaceHTMLElements=False,
|
||||||
|
)
|
||||||
|
base_url = _determine_base_url(document, self.url)
|
||||||
|
for anchor in document.findall(".//a"):
|
||||||
|
if anchor.get("href"):
|
||||||
|
href = anchor.get("href")
|
||||||
|
url = _clean_link(urllib_parse.urljoin(base_url, href))
|
||||||
|
pyrequire = anchor.get('data-requires-python')
|
||||||
|
pyrequire = unescape(pyrequire) if pyrequire else None
|
||||||
|
yield Link(url, self.url, requires_python=pyrequire)
|
||||||
|
|
||||||
|
|
||||||
|
Search = namedtuple('Search', 'supplied canonical formats')
|
||||||
|
"""Capture key aspects of a search.
|
||||||
|
|
||||||
|
:attribute supplied: The user supplied package.
|
||||||
|
:attribute canonical: The canonical package name.
|
||||||
|
:attribute formats: The formats allowed for this package. Should be a set
|
||||||
|
with 'binary' or 'source' or both in it.
|
||||||
|
"""
|
@@ -0,0 +1,211 @@
|
|||||||
|
"""Locations where we look for configs, install stuff, etc"""
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import platform
|
||||||
|
import site
|
||||||
|
import sys
|
||||||
|
import sysconfig
|
||||||
|
from distutils import sysconfig as distutils_sysconfig
|
||||||
|
from distutils.command.install import SCHEME_KEYS # type: ignore
|
||||||
|
|
||||||
|
from pip._internal.utils import appdirs
|
||||||
|
from pip._internal.utils.compat import WINDOWS, expanduser
|
||||||
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
|
if MYPY_CHECK_RUNNING:
|
||||||
|
from typing import Any, Union, Dict, List, Optional # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
|
# 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
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
site_packages = sysconfig.get_path("purelib") # type: Optional[str]
|
||||||
|
|
||||||
|
# This is because of a bug in PyPy's sysconfig module, see
|
||||||
|
# https://bitbucket.org/pypy/pypy/issues/2506/sysconfig-returns-incorrect-paths
|
||||||
|
# for more information.
|
||||||
|
if platform.python_implementation().lower() == "pypy":
|
||||||
|
site_packages = distutils_sysconfig.get_python_lib()
|
||||||
|
try:
|
||||||
|
# Use getusersitepackages if this is present, as it ensures that the
|
||||||
|
# value is initialised properly.
|
||||||
|
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')
|
||||||
|
# buildout uses 'bin' on Windows too?
|
||||||
|
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):
|
||||||
|
# type:(str, bool, str, str, bool, str) -> dict
|
||||||
|
"""
|
||||||
|
Return a distutils install scheme
|
||||||
|
"""
|
||||||
|
from distutils.dist import Distribution
|
||||||
|
|
||||||
|
scheme = {}
|
||||||
|
|
||||||
|
if isolated:
|
||||||
|
extra_dist_args = {"script_args": ["--no-user-cfg"]}
|
||||||
|
else:
|
||||||
|
extra_dist_args = {}
|
||||||
|
dist_args = {'name': dist_name} # type: Dict[str, Union[str, List[str]]]
|
||||||
|
dist_args.update(extra_dist_args)
|
||||||
|
|
||||||
|
d = Distribution(dist_args)
|
||||||
|
# Ignoring, typeshed issue reported python/typeshed/issues/2567
|
||||||
|
d.parse_config_files()
|
||||||
|
# NOTE: Ignoring type since mypy can't find attributes on 'Command'
|
||||||
|
i = d.get_command_obj('install', create=True) # type: Any
|
||||||
|
assert i is not None
|
||||||
|
# NOTE: setting user or home has the side-effect of creating the home dir
|
||||||
|
# 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)
|
||||||
|
i.user = user or i.user
|
||||||
|
if user:
|
||||||
|
i.prefix = ""
|
||||||
|
i.prefix = prefix or i.prefix
|
||||||
|
i.home = home or i.home
|
||||||
|
i.root = root or i.root
|
||||||
|
i.finalize_options()
|
||||||
|
for key in SCHEME_KEYS:
|
||||||
|
scheme[key] = getattr(i, 'install_' + key)
|
||||||
|
|
||||||
|
# install_lib specified in setup.cfg should install *everything*
|
||||||
|
# into there (i.e. it takes precedence over both purelib and
|
||||||
|
# platlib). Note, i.install_lib is *always* set after
|
||||||
|
# finalize_options(); we only want to override here if the user
|
||||||
|
# has explicitly requested it hence going back to the config
|
||||||
|
|
||||||
|
# Ignoring, typeshed issue reported python/typeshed/issues/2567
|
||||||
|
if 'install_lib' in d.get_option_dict('install'): # type: ignore
|
||||||
|
scheme.update(dict(purelib=i.install_lib, platlib=i.install_lib))
|
||||||
|
|
||||||
|
if running_under_virtualenv():
|
||||||
|
scheme['headers'] = os.path.join(
|
||||||
|
sys.prefix,
|
||||||
|
'include',
|
||||||
|
'site',
|
||||||
|
'python' + sys.version[:3],
|
||||||
|
dist_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
if root is not None:
|
||||||
|
path_no_drive = os.path.splitdrive(
|
||||||
|
os.path.abspath(scheme["headers"]))[1]
|
||||||
|
scheme["headers"] = os.path.join(
|
||||||
|
root,
|
||||||
|
path_no_drive[1:],
|
||||||
|
)
|
||||||
|
|
||||||
|
return scheme
|
@@ -0,0 +1,2 @@
|
|||||||
|
"""A package that contains models that represent entities.
|
||||||
|
"""
|
@@ -0,0 +1,31 @@
|
|||||||
|
from pip._vendor.packaging.version import parse as parse_version
|
||||||
|
|
||||||
|
from pip._internal.utils.models import KeyBasedCompareMixin
|
||||||
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
|
if MYPY_CHECK_RUNNING:
|
||||||
|
from pip._vendor.packaging.version import _BaseVersion # noqa: F401
|
||||||
|
from pip._internal.models.link import Link # noqa: F401
|
||||||
|
from typing import Any, Union # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
|
class InstallationCandidate(KeyBasedCompareMixin):
|
||||||
|
"""Represents a potential "candidate" for installation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, project, version, location):
|
||||||
|
# type: (Any, str, Link) -> None
|
||||||
|
self.project = project
|
||||||
|
self.version = parse_version(version) # type: _BaseVersion
|
||||||
|
self.location = location
|
||||||
|
|
||||||
|
super(InstallationCandidate, self).__init__(
|
||||||
|
key=(self.project, self.version, self.location),
|
||||||
|
defining_class=InstallationCandidate
|
||||||
|
)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
# type: () -> str
|
||||||
|
return "<InstallationCandidate({!r}, {!r}, {!r})>".format(
|
||||||
|
self.project, self.version, self.location,
|
||||||
|
)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user