Finish updating vendored deps

Signed-off-by: Dan Ryan <dan@danryan.co>

Update delegator patch

Signed-off-by: Dan Ryan <dan@danryan.co>

Update patches for pip

Signed-off-by: Dan Ryan <dan@danryan.co>

Update vendored deps

Signed-off-by: Dan Ryan <dan@danryan.co>

Update patches

Signed-off-by: Dan Ryan <dan@danryan.co>

Fix imports to use pip shims instead of direct imports

Signed-off-by: Dan Ryan <dan@danryan.co>

Update vendoring scripts and pip shims

Signed-off-by: Dan Ryan <dan@danryan.co>

Log to stdout in real time during verbose logging

Signed-off-by: Dan Ryan <dan@danryan.co>

Don’t log environment

Fix unicode decoding issues

Signed-off-by: Dan Ryan <dan@danryan.co>

Only set buffers on ttys

Signed-off-by: Dan Ryan <dan@danryan.co>

Fix typo

Signed-off-by: Dan Ryan <dan@danryan.co>

Use default encodings

Signed-off-by: Dan Ryan <dan@danryan.co>

Fix encodings and run only failing tests

Signed-off-by: Dan Ryan <dan@danryan.co>
This commit is contained in:
Dan Ryan
2018-10-26 20:47:06 -04:00
parent b8db36ebdf
commit 0cefb5ed9b
114 changed files with 3363 additions and 2259 deletions
+2 -2
View File
@@ -4,6 +4,7 @@ steps:
pip install certifi
python -m certifi > cacert.txt
Write-Host "##vso[task.setvariable variable=GIT_SSL_CAINFO]$(Get-Content cacert.txt)"
$env:GIT_SSL_CAINFO="$(Get-Content cacert.txt)"
# Shorten paths to get under MAX_PATH or else integration tests will fail
# https://bugs.python.org/issue18199
subst T: "$env:TEMP"
@@ -11,8 +12,7 @@ steps:
$env:TEMP='T:\'
Write-Host "##vso[task.setvariable variable=TMP]T:\"
$env:TEMP='T:\'
Get-ChildItem Env:
D:\.venv\Scripts\pipenv run pytest -n 4 -ra --ignore=pipenv\patched --ignore=pipenv\vendor --junitxml=test-results.xml tests
D:\.venv\Scripts\pipenv run pytest -ra --ignore=pipenv\patched --ignore=pipenv\vendor -k 'test_get_vcs_refs or test_install_editable_git_tag' --junitxml=test-results.xml tests
displayName: Run integration tests
- task: PublishTestResults@2
+19 -1
View File
@@ -1,9 +1,13 @@
# -*- coding=utf-8 -*-
# |~~\' |~~
# |__/||~~\|--|/~\\ /
# | ||__/|__| |\/
# |
import os
import sys
import warnings
from .__version__ import __version__
PIPENV_ROOT = os.path.dirname(os.path.realpath(__file__))
@@ -13,14 +17,28 @@ PIPENV_PATCHED = os.sep.join([PIPENV_ROOT, "patched"])
sys.path.insert(0, PIPENV_VENDOR)
# Inject patched directory into system path.
sys.path.insert(0, PIPENV_PATCHED)
from vistir.compat import fs_str
from pipenv.vendor.urllib3.exceptions import DependencyWarning
from pipenv.vendor.vistir.compat import ResourceWarning, fs_str
warnings.filterwarnings("ignore", category=DependencyWarning)
warnings.filterwarnings("ignore", category=ResourceWarning)
if sys.version_info >= (3, 1) and sys.version_info <= (3, 6):
if sys.stdout.isatty() and sys.stderr.isatty():
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf8')
os.environ["PIP_DISABLE_PIP_VERSION_CHECK"] = fs_str("1")
os.environ["PIP_SHIMS_BASE_MODULE"] = fs_str("pipenv.patched.notpip")
# Hack to make things work better.
try:
if "concurrency" in sys.modules:
del sys.modules["concurrency"]
except Exception:
pass
from .cli import cli
from . import resolver
+76
View File
@@ -300,3 +300,79 @@ def NamedTemporaryFile(
os.unlink(name)
os.close(fd)
raise
def getpreferredencoding():
import locale
# Borrowed from Invoke
# (see https://github.com/pyinvoke/invoke/blob/93af29d/invoke/runners.py#L881)
_encoding = locale.getpreferredencoding(False)
if six.PY2 and not sys.platform == "win32":
_default_encoding = locale.getdefaultlocale()[1]
if _default_encoding is not None:
_encoding = _default_encoding
return _encoding
DEFAULT_ENCODING = getpreferredencoding()
# From https://github.com/CarlFK/veyepar/blob/5c5de47/dj/scripts/fixunicode.py
# MIT LIcensed, thanks Carl!
def force_encoding():
try:
stdout_isatty = sys.stdout.isatty
stderr_isatty = sys.stderr.isatty
except AttributeError:
return DEFAULT_ENCODING, DEFAULT_ENCODING
else:
if not (stdout_isatty() and stderr_isatty()):
return DEFAULT_ENCODING, DEFAULT_ENCODING
stdout_encoding = sys.stdout.encoding
stderr_encoding = sys.stderr.encoding
if sys.platform == "win32" and sys.version_info >= (3, 1):
return DEFAULT_ENCODING, DEFAULT_ENCODING
if stdout_encoding.lower() != "utf-8" or stderr_encoding.lower() != "utf-8":
from ctypes import pythonapi, py_object, c_char_p
try:
PyFile_SetEncoding = pythonapi.PyFile_SetEncoding
except AttributeError:
return DEFAULT_ENCODING, DEFAULT_ENCODING
else:
PyFile_SetEncoding.argtypes = (py_object, c_char_p)
if stdout_encoding.lower() != "utf-8":
try:
was_set = PyFile_SetEncoding(sys.stdout, "utf-8")
except OSError:
was_set = False
if not was_set:
stdout_encoding = DEFAULT_ENCODING
else:
stdout_encoding = "utf-8"
if stderr_encoding.lower() != "utf-8":
try:
was_set = PyFile_SetEncoding(sys.stderr, "utf-8")
except OSError:
was_set = False
if not was_set:
stderr_encoding = DEFAULT_ENCODING
else:
stderr_encoding = "utf-8"
return stdout_encoding, stderr_encoding
OUT_ENCODING, ERR_ENCODING = force_encoding()
def decode_output(output):
if not isinstance(output, six.string_types):
return output
try:
output = output.encode(DEFAULT_ENCODING)
except AttributeError:
pass
output = output.decode(DEFAULT_ENCODING)
return output
+10 -9
View File
@@ -1,4 +1,5 @@
# -*- coding=utf-8 -*-
import contextlib
import logging
import os
@@ -105,9 +106,13 @@ UNICODE_TO_ASCII_TRANSLATION_MAP = {
def fix_utf8(text):
if not isinstance(text, six.string_types):
return text
if six.PY2:
text = unicode.translate(vistir.misc.to_text(text), UNICODE_TO_ASCII_TRANSLATION_MAP)
return u"{0}".format(text)
from ._compat import decode_output
try:
text = decode_output(text)
except UnicodeDecodeError:
if six.PY2:
text = unicode.translate(vistir.misc.to_text(text), UNICODE_TO_ASCII_TRANSLATION_MAP)
return text
@contextlib.contextmanager
@@ -230,7 +235,7 @@ def cleanup_virtualenv(bare=True):
def import_requirements(r=None, dev=False):
from .patched.notpip._vendor import requests as pip_requests
from .patched.notpip._internal.req.req_file import parse_requirements
from .vendor.pip_shims.shims import parse_requirements
# Parse requirements.txt file with Pip's parser.
# Pip requires a `PipSession` which is a subclass of requests.Session.
@@ -1754,7 +1759,7 @@ def do_install(
selective_upgrade=False,
):
from .environments import PIPENV_VIRTUALENV, PIPENV_USE_SYSTEM
from notpip._internal.exceptions import PipError
from .vendor.pip_shims.shims import PipError
requirements_directory = vistir.path.create_tracked_tempdir(
suffix="-requirements", prefix="pipenv-"
@@ -2212,10 +2217,6 @@ def _launch_windows_subprocess(script):
command = system_which(script.command)
options = {"universal_newlines": True}
env_strings = [
vistir.compat.to_native_string("{0}: {1}".format(k, v)) for k, v in os.environ.items()
]
click.echo(vistir.compat.to_native_string("\n".join(env_strings)), err=True)
# Command not found, maybe this is a shell built-in?
if not command:
+1 -1
View File
@@ -1 +1 @@
__version__ = "18.0"
__version__ = "18.1"
+6 -238
View File
@@ -4,7 +4,6 @@ from __future__ import absolute_import
import locale
import logging
import os
import optparse
import warnings
import sys
@@ -38,17 +37,12 @@ else:
else:
securetransport.inject_into_urllib3()
from pipenv.patched.notpip import __version__
from pipenv.patched.notpip._internal import cmdoptions
from pipenv.patched.notpip._internal.exceptions import CommandError, PipError
from pipenv.patched.notpip._internal.utils.misc import get_installed_distributions, get_prog
from pipenv.patched.notpip._internal.cli.autocompletion import autocomplete
from pipenv.patched.notpip._internal.cli.main_parser import parse_command
from pipenv.patched.notpip._internal.commands import commands_dict
from pipenv.patched.notpip._internal.exceptions import PipError
from pipenv.patched.notpip._internal.utils import deprecation
from pipenv.patched.notpip._internal.vcs import git, mercurial, subversion, bazaar # noqa
from pipenv.patched.notpip._internal.baseparser import (
ConfigOptionParser, UpdatingDefaultsHelpFormatter,
)
from pipenv.patched.notpip._internal.commands import get_summaries, get_similar_commands
from pipenv.patched.notpip._internal.commands import commands_dict
from pipenv.patched.notpip._vendor.urllib3.exceptions import InsecureRequestWarning
logger = logging.getLogger(__name__)
@@ -57,232 +51,6 @@ logger = logging.getLogger(__name__)
warnings.filterwarnings("ignore", category=InsecureRequestWarning)
def autocomplete():
"""Command and option completion for the main option parser (and options)
and its subcommands (and options).
Enable by sourcing one of the completion shell scripts (bash, zsh or fish).
"""
# Don't complete if user hasn't sourced bash_completion file.
if 'PIP_AUTO_COMPLETE' not in os.environ:
return
cwords = os.environ['COMP_WORDS'].split()[1:]
cword = int(os.environ['COMP_CWORD'])
try:
current = cwords[cword - 1]
except IndexError:
current = ''
subcommands = [cmd for cmd, summary in get_summaries()]
options = []
# subcommand
try:
subcommand_name = [w for w in cwords if w in subcommands][0]
except IndexError:
subcommand_name = None
parser = create_main_parser()
# subcommand options
if subcommand_name:
# special case: 'help' subcommand has no options
if subcommand_name == 'help':
sys.exit(1)
# special case: list locally installed dists for show and uninstall
should_list_installed = (
subcommand_name in ['show', 'uninstall'] and
not current.startswith('-')
)
if should_list_installed:
installed = []
lc = current.lower()
for dist in get_installed_distributions(local_only=True):
if dist.key.startswith(lc) and dist.key not in cwords[1:]:
installed.append(dist.key)
# if there are no dists installed, fall back to option completion
if installed:
for dist in installed:
print(dist)
sys.exit(1)
subcommand = commands_dict[subcommand_name]()
for opt in subcommand.parser.option_list_all:
if opt.help != optparse.SUPPRESS_HELP:
for opt_str in opt._long_opts + opt._short_opts:
options.append((opt_str, opt.nargs))
# filter out previously specified options from available options
prev_opts = [x.split('=')[0] for x in cwords[1:cword - 1]]
options = [(x, v) for (x, v) in options if x not in prev_opts]
# filter options by current input
options = [(k, v) for k, v in options if k.startswith(current)]
# get completion type given cwords and available subcommand options
completion_type = get_path_completion_type(
cwords, cword, subcommand.parser.option_list_all,
)
# get completion files and directories if ``completion_type`` is
# ``<file>``, ``<dir>`` or ``<path>``
if completion_type:
options = auto_complete_paths(current, completion_type)
options = ((opt, 0) for opt in options)
for option in options:
opt_label = option[0]
# append '=' to options which require args
if option[1] and option[0][:2] == "--":
opt_label += '='
print(opt_label)
else:
# show main parser options only when necessary
opts = [i.option_list for i in parser.option_groups]
opts.append(parser.option_list)
opts = (o for it in opts for o in it)
if current.startswith('-'):
for opt in opts:
if opt.help != optparse.SUPPRESS_HELP:
subcommands += opt._long_opts + opt._short_opts
else:
# get completion type given cwords and all available options
completion_type = get_path_completion_type(cwords, cword, opts)
if completion_type:
subcommands = auto_complete_paths(current, completion_type)
print(' '.join([x for x in subcommands if x.startswith(current)]))
sys.exit(1)
def get_path_completion_type(cwords, cword, opts):
"""Get the type of path completion (``file``, ``dir``, ``path`` or None)
:param cwords: same as the environmental variable ``COMP_WORDS``
:param cword: same as the environmental variable ``COMP_CWORD``
:param opts: The available options to check
:return: path completion type (``file``, ``dir``, ``path`` or None)
"""
if cword < 2 or not cwords[cword - 2].startswith('-'):
return
for opt in opts:
if opt.help == optparse.SUPPRESS_HELP:
continue
for o in str(opt).split('/'):
if cwords[cword - 2].split('=')[0] == o:
if any(x in ('path', 'file', 'dir')
for x in opt.metavar.split('/')):
return opt.metavar
def auto_complete_paths(current, completion_type):
"""If ``completion_type`` is ``file`` or ``path``, list all regular files
and directories starting with ``current``; otherwise only list directories
starting with ``current``.
:param current: The word to be completed
:param completion_type: path completion type(`file`, `path` or `dir`)i
:return: A generator of regular files and/or directories
"""
directory, filename = os.path.split(current)
current_path = os.path.abspath(directory)
# Don't complete paths if they can't be accessed
if not os.access(current_path, os.R_OK):
return
filename = os.path.normcase(filename)
# list all files that start with ``filename``
file_list = (x for x in os.listdir(current_path)
if os.path.normcase(x).startswith(filename))
for f in file_list:
opt = os.path.join(current_path, f)
comp_file = os.path.normcase(os.path.join(directory, f))
# complete regular files when there is not ``<dir>`` after option
# complete directories when there is ``<file>``, ``<path>`` or
# ``<dir>``after option
if completion_type != 'dir' and os.path.isfile(opt):
yield comp_file
elif os.path.isdir(opt):
yield os.path.join(comp_file, '')
def create_main_parser():
parser_kw = {
'usage': '\n%prog <command> [options]',
'add_help_option': False,
'formatter': UpdatingDefaultsHelpFormatter(),
'name': 'global',
'prog': get_prog(),
}
parser = ConfigOptionParser(**parser_kw)
parser.disable_interspersed_args()
pip_pkg_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
parser.version = 'pip %s from %s (python %s)' % (
__version__, pip_pkg_dir, sys.version[:3],
)
# add the general options
gen_opts = cmdoptions.make_option_group(cmdoptions.general_group, parser)
parser.add_option_group(gen_opts)
parser.main = True # so the help formatter knows
# create command listing for description
command_summaries = get_summaries()
description = [''] + ['%-27s %s' % (i, j) for i, j in command_summaries]
parser.description = '\n'.join(description)
return parser
def parseopts(args):
parser = create_main_parser()
# Note: parser calls disable_interspersed_args(), so the result of this
# call is to split the initial args into the general options before the
# subcommand and everything else.
# For example:
# args: ['--timeout=5', 'install', '--user', 'INITools']
# general_options: ['--timeout==5']
# args_else: ['install', '--user', 'INITools']
general_options, args_else = parser.parse_args(args)
# --version
if general_options.version:
sys.stdout.write(parser.version)
sys.stdout.write(os.linesep)
sys.exit()
# pip || pip help -> print_help()
if not args_else or (args_else[0] == 'help' and len(args_else) == 1):
parser.print_help()
sys.exit()
# the subcommand name
cmd_name = args_else[0]
if cmd_name not in commands_dict:
guess = get_similar_commands(cmd_name)
msg = ['unknown command "%s"' % cmd_name]
if guess:
msg.append('maybe you meant "%s"' % guess)
raise CommandError(' - '.join(msg))
# all the args without the subcommand
cmd_args = args[:]
cmd_args.remove(cmd_name)
return cmd_name, cmd_args
def check_isolated(args):
isolated = False
if "--isolated" in args:
isolated = True
return isolated
def main(args=None):
if args is None:
args = sys.argv[1:]
@@ -293,7 +61,7 @@ def main(args=None):
autocomplete()
try:
cmd_name, cmd_args = parseopts(args)
cmd_name, cmd_args = parse_command(args)
except PipError as exc:
sys.stderr.write("ERROR: %s" % exc)
sys.stderr.write(os.linesep)
@@ -306,5 +74,5 @@ def main(args=None):
except locale.Error as e:
# setlocale can apparently crash if locale are uninitialized
logger.debug("Ignoring error %s when setting locale", e)
command = commands_dict[cmd_name](isolated=check_isolated(cmd_args))
command = commands_dict[cmd_name](isolated=("--isolated" in cmd_args))
return command.main(cmd_args)
@@ -7,6 +7,8 @@ import sys
from distutils.sysconfig import get_python_lib
from sysconfig import get_paths
from pipenv.patched.notpip._vendor.pkg_resources import Requirement, VersionConflict, WorkingSet
from pipenv.patched.notpip._internal.utils.misc import call_subprocess
from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory
from pipenv.patched.notpip._internal.utils.ui import open_spinner
@@ -75,6 +77,20 @@ class BuildEnvironment(object):
def cleanup(self):
self._temp_dir.cleanup()
def missing_requirements(self, reqs):
"""Return a list of the requirements from reqs that are not present
"""
missing = []
with self:
ws = WorkingSet(os.environ["PYTHONPATH"].split(os.pathsep))
for req in reqs:
try:
if ws.find(Requirement.parse(req)) is None:
missing.append(req)
except VersionConflict:
missing.append(req)
return missing
def install_requirements(self, finder, requirements, message):
args = [
sys.executable, '-m', 'pip', 'install', '--ignore-installed',
+6 -6
View File
@@ -8,9 +8,9 @@ import os
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
from pipenv.patched.notpip._internal import index
from pipenv.patched.notpip._internal.compat import expanduser
from pipenv.patched.notpip._internal.download import path_to_url
from pipenv.patched.notpip._internal.models.link import Link
from pipenv.patched.notpip._internal.utils.compat import expanduser
from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory
from pipenv.patched.notpip._internal.wheel import InvalidWheelFilename, Wheel
@@ -22,7 +22,7 @@ class Cache(object):
:param cache_dir: The root of the cache.
:param format_control: A pip.index.FormatControl object to limit
:param format_control: An object of FormatControl class to limit
binaries being read from the cache.
:param allowed_formats: which formats of files the cache should store.
('binary' and 'source' are the only allowed values)
@@ -72,8 +72,8 @@ class Cache(object):
return []
canonical_name = canonicalize_name(package_name)
formats = index.fmt_ctl_formats(
self.format_control, canonical_name
formats = self.format_control.get_allowed_formats(
canonical_name
)
if not self.allowed_formats.intersection(formats):
return []
@@ -101,7 +101,7 @@ class Cache(object):
root = self.get_path_for_link(link)
path = os.path.join(root, candidate)
return index.Link(path_to_url(path))
return Link(path_to_url(path))
def cleanup(self):
pass
@@ -0,0 +1,4 @@
"""Subpackage containing all of pip's command line interface related code
"""
# This file intentionally does not import submodules
@@ -0,0 +1,152 @@
"""Logic that powers autocompletion installed by ``pip completion``.
"""
import optparse
import os
import sys
from pipenv.patched.notpip._internal.cli.main_parser import create_main_parser
from pipenv.patched.notpip._internal.commands import commands_dict, get_summaries
from pipenv.patched.notpip._internal.utils.misc import get_installed_distributions
def autocomplete():
"""Entry Point for completion of main and subcommand options.
"""
# Don't complete if user hasn't sourced bash_completion file.
if 'PIP_AUTO_COMPLETE' not in os.environ:
return
cwords = os.environ['COMP_WORDS'].split()[1:]
cword = int(os.environ['COMP_CWORD'])
try:
current = cwords[cword - 1]
except IndexError:
current = ''
subcommands = [cmd for cmd, summary in get_summaries()]
options = []
# subcommand
try:
subcommand_name = [w for w in cwords if w in subcommands][0]
except IndexError:
subcommand_name = None
parser = create_main_parser()
# subcommand options
if subcommand_name:
# special case: 'help' subcommand has no options
if subcommand_name == 'help':
sys.exit(1)
# special case: list locally installed dists for show and uninstall
should_list_installed = (
subcommand_name in ['show', 'uninstall'] and
not current.startswith('-')
)
if should_list_installed:
installed = []
lc = current.lower()
for dist in get_installed_distributions(local_only=True):
if dist.key.startswith(lc) and dist.key not in cwords[1:]:
installed.append(dist.key)
# if there are no dists installed, fall back to option completion
if installed:
for dist in installed:
print(dist)
sys.exit(1)
subcommand = commands_dict[subcommand_name]()
for opt in subcommand.parser.option_list_all:
if opt.help != optparse.SUPPRESS_HELP:
for opt_str in opt._long_opts + opt._short_opts:
options.append((opt_str, opt.nargs))
# filter out previously specified options from available options
prev_opts = [x.split('=')[0] for x in cwords[1:cword - 1]]
options = [(x, v) for (x, v) in options if x not in prev_opts]
# filter options by current input
options = [(k, v) for k, v in options if k.startswith(current)]
# get completion type given cwords and available subcommand options
completion_type = get_path_completion_type(
cwords, cword, subcommand.parser.option_list_all,
)
# get completion files and directories if ``completion_type`` is
# ``<file>``, ``<dir>`` or ``<path>``
if completion_type:
options = auto_complete_paths(current, completion_type)
options = ((opt, 0) for opt in options)
for option in options:
opt_label = option[0]
# append '=' to options which require args
if option[1] and option[0][:2] == "--":
opt_label += '='
print(opt_label)
else:
# show main parser options only when necessary
opts = [i.option_list for i in parser.option_groups]
opts.append(parser.option_list)
opts = (o for it in opts for o in it)
if current.startswith('-'):
for opt in opts:
if opt.help != optparse.SUPPRESS_HELP:
subcommands += opt._long_opts + opt._short_opts
else:
# get completion type given cwords and all available options
completion_type = get_path_completion_type(cwords, cword, opts)
if completion_type:
subcommands = auto_complete_paths(current, completion_type)
print(' '.join([x for x in subcommands if x.startswith(current)]))
sys.exit(1)
def get_path_completion_type(cwords, cword, opts):
"""Get the type of path completion (``file``, ``dir``, ``path`` or None)
:param cwords: same as the environmental variable ``COMP_WORDS``
:param cword: same as the environmental variable ``COMP_CWORD``
:param opts: The available options to check
:return: path completion type (``file``, ``dir``, ``path`` or None)
"""
if cword < 2 or not cwords[cword - 2].startswith('-'):
return
for opt in opts:
if opt.help == optparse.SUPPRESS_HELP:
continue
for o in str(opt).split('/'):
if cwords[cword - 2].split('=')[0] == o:
if not opt.metavar or any(
x in ('path', 'file', 'dir')
for x in opt.metavar.split('/')):
return opt.metavar
def auto_complete_paths(current, completion_type):
"""If ``completion_type`` is ``file`` or ``path``, list all regular files
and directories starting with ``current``; otherwise only list directories
starting with ``current``.
:param current: The word to be completed
:param completion_type: path completion type(`file`, `path` or `dir`)i
:return: A generator of regular files and/or directories
"""
directory, filename = os.path.split(current)
current_path = os.path.abspath(directory)
# Don't complete paths if they can't be accessed
if not os.access(current_path, os.R_OK):
return
filename = os.path.normcase(filename)
# list all files that start with ``filename``
file_list = (x for x in os.listdir(current_path)
if os.path.normcase(x).startswith(filename))
for f in file_list:
opt = os.path.join(current_path, f)
comp_file = os.path.normcase(os.path.join(directory, f))
# complete regular files when there is not ``<dir>`` after option
# complete directories when there is ``<file>``, ``<path>`` or
# ``<dir>``after option
if completion_type != 'dir' and os.path.isfile(opt):
yield comp_file
elif os.path.isdir(opt):
yield os.path.join(comp_file, '')
@@ -7,10 +7,14 @@ import optparse
import os
import sys
from pipenv.patched.notpip._internal import cmdoptions
from pipenv.patched.notpip._internal.baseparser import (
from pipenv.patched.notpip._internal.cli import cmdoptions
from pipenv.patched.notpip._internal.cli.parser import (
ConfigOptionParser, UpdatingDefaultsHelpFormatter,
)
from pipenv.patched.notpip._internal.cli.status_codes import (
ERROR, PREVIOUS_BUILD_DIR_ERROR, SUCCESS, UNKNOWN_ERROR,
VIRTUALENV_NOT_FOUND,
)
from pipenv.patched.notpip._internal.download import PipSession
from pipenv.patched.notpip._internal.exceptions import (
BadCommand, CommandError, InstallationError, PreviousBuildDirError,
@@ -18,12 +22,10 @@ from pipenv.patched.notpip._internal.exceptions import (
)
from pipenv.patched.notpip._internal.index import PackageFinder
from pipenv.patched.notpip._internal.locations import running_under_virtualenv
from pipenv.patched.notpip._internal.req.req_file import parse_requirements
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
from pipenv.patched.notpip._internal.status_codes import (
ERROR, PREVIOUS_BUILD_DIR_ERROR, SUCCESS, UNKNOWN_ERROR,
VIRTUALENV_NOT_FOUND,
from pipenv.patched.notpip._internal.req.constructors import (
install_req_from_editable, install_req_from_line,
)
from pipenv.patched.notpip._internal.req.req_file import parse_requirements
from pipenv.patched.notpip._internal.utils.logging import setup_logging
from pipenv.patched.notpip._internal.utils.misc import get_prog, normalize_path
from pipenv.patched.notpip._internal.utils.outdated import pip_version_check
@@ -168,12 +170,14 @@ class Command(object):
return UNKNOWN_ERROR
finally:
# Check if we're using the latest version of pip available
skip_version_check = (
options.disable_pip_version_check or
getattr(options, "no_index", False)
allow_version_check = (
# Does this command have the index_group options?
hasattr(options, "no_index") and
# Is this command allowed to perform this check?
not (options.disable_pip_version_check or options.no_index)
)
if not skip_version_check:
# Check if we're using the latest version of pip available
if allow_version_check:
session = self._build_session(
options,
retries=0,
@@ -208,7 +212,7 @@ class RequirementCommand(Command):
requirement_set.add_requirement(req_to_add)
for req in args:
req_to_add = InstallRequirement.from_line(
req_to_add = install_req_from_line(
req, None, isolated=options.isolated_mode,
wheel_cache=wheel_cache
)
@@ -216,7 +220,7 @@ class RequirementCommand(Command):
requirement_set.add_requirement(req_to_add)
for req in options.editables:
req_to_add = InstallRequirement.from_editable(
req_to_add = install_req_from_editable(
req,
isolated=options.isolated_mode,
wheel_cache=wheel_cache
@@ -13,10 +13,9 @@ import warnings
from functools import partial
from optparse import SUPPRESS_HELP, Option, OptionGroup
from pipenv.patched.notpip._internal.index import (
FormatControl, fmt_ctl_handle_mutual_exclude, fmt_ctl_no_binary,
)
from pipenv.patched.notpip._internal.exceptions import CommandError
from pipenv.patched.notpip._internal.locations import USER_CACHE_DIR, src_prefix
from pipenv.patched.notpip._internal.models.format_control import FormatControl
from pipenv.patched.notpip._internal.models.index import PyPI
from pipenv.patched.notpip._internal.utils.hashes import STRONG_HASHES
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
@@ -53,13 +52,52 @@ def check_install_build_global(options, check_options=None):
names = ["build_options", "global_options", "install_options"]
if any(map(getname, names)):
control = options.format_control
fmt_ctl_no_binary(control)
control.disallow_binaries()
warnings.warn(
'Disabling all use of wheels due to the use of --build-options '
'/ --global-options / --install-options.', stacklevel=2,
)
def check_dist_restriction(options, check_target=False):
"""Function for determining if custom platform options are allowed.
:param options: The OptionParser options.
:param check_target: Whether or not to check if --target is being used.
"""
dist_restriction_set = any([
options.python_version,
options.platform,
options.abi,
options.implementation,
])
binary_only = FormatControl(set(), {':all:'})
sdist_dependencies_allowed = (
options.format_control != binary_only and
not options.ignore_dependencies
)
# Installations or downloads using dist restrictions must not combine
# source distributions and dist-specific wheels, as they are not
# gauranteed to be locally compatible.
if dist_restriction_set and sdist_dependencies_allowed:
raise CommandError(
"When restricting platform and interpreter constraints using "
"--python-version, --platform, --abi, or --implementation, "
"either --no-deps must be set, or --only-binary=:all: must be "
"set and --no-binary must not be set (or must be set to "
":none:)."
)
if check_target:
if dist_restriction_set and not options.target_dir:
raise CommandError(
"Can not use any platform or abi specific options unless "
"installing via '--target'"
)
###########
# options #
###########
@@ -365,24 +403,25 @@ def _get_format_control(values, option):
def _handle_no_binary(option, opt_str, value, parser):
existing = getattr(parser.values, option.dest)
fmt_ctl_handle_mutual_exclude(
existing = _get_format_control(parser.values, option)
FormatControl.handle_mutual_excludes(
value, existing.no_binary, existing.only_binary,
)
def _handle_only_binary(option, opt_str, value, parser):
existing = getattr(parser.values, option.dest)
fmt_ctl_handle_mutual_exclude(
existing = _get_format_control(parser.values, option)
FormatControl.handle_mutual_excludes(
value, existing.only_binary, existing.no_binary,
)
def no_binary():
format_control = FormatControl(set(), set())
return Option(
"--no-binary", dest="format_control", action="callback",
callback=_handle_no_binary, type="str",
default=FormatControl(set(), set()),
default=format_control,
help="Do not use binary packages. Can be supplied multiple times, and "
"each time adds to the existing value. Accepts either :all: to "
"disable all binary packages, :none: to empty the set, or one or "
@@ -393,10 +432,11 @@ def no_binary():
def only_binary():
format_control = FormatControl(set(), set())
return Option(
"--only-binary", dest="format_control", action="callback",
callback=_handle_only_binary, type="str",
default=FormatControl(set(), set()),
default=format_control,
help="Do not use source packages. Can be supplied multiple times, and "
"each time adds to the existing value. Accepts either :all: to "
"disable all source packages, :none: to empty the set, or one or "
@@ -406,6 +446,61 @@ def only_binary():
)
platform = partial(
Option,
'--platform',
dest='platform',
metavar='platform',
default=None,
help=("Only use wheels compatible with <platform>. "
"Defaults to the platform of the running system."),
)
python_version = partial(
Option,
'--python-version',
dest='python_version',
metavar='python_version',
default=None,
help=("Only use wheels compatible with Python "
"interpreter version <version>. If not specified, then the "
"current system interpreter minor version is used. A major "
"version (e.g. '2') can be specified to match all "
"minor revs of that major version. A minor version "
"(e.g. '34') can also be specified."),
)
implementation = partial(
Option,
'--implementation',
dest='implementation',
metavar='implementation',
default=None,
help=("Only use wheels compatible with Python "
"implementation <implementation>, e.g. 'pp', 'jy', 'cp', "
" or 'ip'. If not specified, then the current "
"interpreter implementation is used. Use 'py' to force "
"implementation-agnostic wheels."),
)
abi = partial(
Option,
'--abi',
dest='abi',
metavar='abi',
default=None,
help=("Only use wheels compatible with Python "
"abi <abi>, e.g. 'pypy_41'. If not specified, then the "
"current interpreter abi tag is used. Generally "
"you will need to specify --implementation, "
"--platform, and --python-version when using "
"this option."),
)
def prefer_binary():
return Option(
"--prefer-binary",
@@ -501,7 +596,7 @@ no_clean = partial(
'--no-clean',
action='store_true',
default=False,
help="Don't clean up build directories)."
help="Don't clean up build directories."
) # type: Any
pre = partial(
@@ -0,0 +1,96 @@
"""A single place for constructing and exposing the main parser
"""
import os
import sys
from pipenv.patched.notpip import __version__
from pipenv.patched.notpip._internal.cli import cmdoptions
from pipenv.patched.notpip._internal.cli.parser import (
ConfigOptionParser, UpdatingDefaultsHelpFormatter,
)
from pipenv.patched.notpip._internal.commands import (
commands_dict, get_similar_commands, get_summaries,
)
from pipenv.patched.notpip._internal.exceptions import CommandError
from pipenv.patched.notpip._internal.utils.misc import get_prog
__all__ = ["create_main_parser", "parse_command"]
def create_main_parser():
"""Creates and returns the main parser for pip's CLI
"""
parser_kw = {
'usage': '\n%prog <command> [options]',
'add_help_option': False,
'formatter': UpdatingDefaultsHelpFormatter(),
'name': 'global',
'prog': get_prog(),
}
parser = ConfigOptionParser(**parser_kw)
parser.disable_interspersed_args()
pip_pkg_dir = os.path.abspath(os.path.join(
os.path.dirname(__file__), "..", "..",
))
parser.version = 'pip %s from %s (python %s)' % (
__version__, pip_pkg_dir, sys.version[:3],
)
# add the general options
gen_opts = cmdoptions.make_option_group(cmdoptions.general_group, parser)
parser.add_option_group(gen_opts)
parser.main = True # so the help formatter knows
# create command listing for description
command_summaries = get_summaries()
description = [''] + ['%-27s %s' % (i, j) for i, j in command_summaries]
parser.description = '\n'.join(description)
return parser
def parse_command(args):
parser = create_main_parser()
# Note: parser calls disable_interspersed_args(), so the result of this
# call is to split the initial args into the general options before the
# subcommand and everything else.
# For example:
# args: ['--timeout=5', 'install', '--user', 'INITools']
# general_options: ['--timeout==5']
# args_else: ['install', '--user', 'INITools']
general_options, args_else = parser.parse_args(args)
# --version
if general_options.version:
sys.stdout.write(parser.version)
sys.stdout.write(os.linesep)
sys.exit()
# pip || pip help -> print_help()
if not args_else or (args_else[0] == 'help' and len(args_else) == 1):
parser.print_help()
sys.exit()
# the subcommand name
cmd_name = args_else[0]
if cmd_name not in commands_dict:
guess = get_similar_commands(cmd_name)
msg = ['unknown command "%s"' % cmd_name]
if guess:
msg.append('maybe you meant "%s"' % guess)
raise CommandError(' - '.join(msg))
# all the args without the subcommand
cmd_args = args[:]
cmd_args.remove(cmd_name)
return cmd_name, cmd_args
@@ -9,8 +9,9 @@ from distutils.util import strtobool
from pipenv.patched.notpip._vendor.six import string_types
from pipenv.patched.notpip._internal.compat import get_terminal_size
from pipenv.patched.notpip._internal.cli.status_codes import UNKNOWN_ERROR
from pipenv.patched.notpip._internal.configuration import Configuration, ConfigurationError
from pipenv.patched.notpip._internal.utils.compat import get_terminal_size
logger = logging.getLogger(__name__)
@@ -192,7 +193,14 @@ class ConfigOptionParser(CustomOptionParser):
continue
if option.action in ('store_true', 'store_false', 'count'):
val = strtobool(val)
try:
val = strtobool(val)
except ValueError:
error_msg = invalid_config_error_message(
option.action, key, val
)
self.error(error_msg)
elif option.action == 'append':
val = val.split()
val = [self.check_default(option, key, v) for v in val]
@@ -225,7 +233,7 @@ class ConfigOptionParser(CustomOptionParser):
try:
self.config.load()
except ConfigurationError as err:
self.exit(2, err.args[0])
self.exit(UNKNOWN_ERROR, str(err))
defaults = self._update_defaults(self.defaults.copy()) # ours
for option in self._get_all_options():
@@ -237,4 +245,17 @@ class ConfigOptionParser(CustomOptionParser):
def error(self, msg):
self.print_usage(sys.stderr)
self.exit(2, "%s\n" % msg)
self.exit(UNKNOWN_ERROR, "%s\n" % msg)
def invalid_config_error_message(action, key, val):
"""Returns a better error message when invalid configuration option
is provided."""
if action in ('store_true', 'store_false'):
return ("{0} is not a valid value for {1} option, "
"please specify a boolean value like yes/no, "
"true/false or 1/0 instead.").format(val, key)
return ("{0} is not a valid value for {1} option, "
"please specify a numerical value like 1/0 "
"instead.").format(val, key)
@@ -21,7 +21,7 @@ from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import List, Type # noqa: F401
from pipenv.patched.notpip._internal.basecommand import Command # noqa: F401
from pipenv.patched.notpip._internal.cli.base_command import Command # noqa: F401
commands_order = [
InstallCommand,
@@ -1,6 +1,6 @@
import logging
from pipenv.patched.notpip._internal.basecommand import Command
from pipenv.patched.notpip._internal.cli.base_command import Command
from pipenv.patched.notpip._internal.operations.check import (
check_package_set, create_package_set_from_installed,
)
@@ -3,7 +3,7 @@ from __future__ import absolute_import
import sys
import textwrap
from pipenv.patched.notpip._internal.basecommand import Command
from pipenv.patched.notpip._internal.cli.base_command import Command
from pipenv.patched.notpip._internal.utils.misc import get_prog
BASE_COMPLETION = """
@@ -2,11 +2,11 @@ import logging
import os
import subprocess
from pipenv.patched.notpip._internal.basecommand import Command
from pipenv.patched.notpip._internal.cli.base_command import Command
from pipenv.patched.notpip._internal.cli.status_codes import ERROR, SUCCESS
from pipenv.patched.notpip._internal.configuration import Configuration, kinds
from pipenv.patched.notpip._internal.exceptions import PipError
from pipenv.patched.notpip._internal.locations import venv_config_file
from pipenv.patched.notpip._internal.status_codes import ERROR, SUCCESS
from pipenv.patched.notpip._internal.utils.misc import get_prog
logger = logging.getLogger(__name__)
@@ -3,10 +3,8 @@ from __future__ import absolute_import
import logging
import os
from pipenv.patched.notpip._internal import cmdoptions
from pipenv.patched.notpip._internal.basecommand import RequirementCommand
from pipenv.patched.notpip._internal.exceptions import CommandError
from pipenv.patched.notpip._internal.index import FormatControl
from pipenv.patched.notpip._internal.cli import cmdoptions
from pipenv.patched.notpip._internal.cli.base_command import RequirementCommand
from pipenv.patched.notpip._internal.operations.prepare import RequirementPreparer
from pipenv.patched.notpip._internal.req import RequirementSet
from pipenv.patched.notpip._internal.req.req_tracker import RequirementTracker
@@ -69,52 +67,10 @@ class DownloadCommand(RequirementCommand):
help=("Download packages into <dir>."),
)
cmd_opts.add_option(
'--platform',
dest='platform',
metavar='platform',
default=None,
help=("Only download wheels compatible with <platform>. "
"Defaults to the platform of the running system."),
)
cmd_opts.add_option(
'--python-version',
dest='python_version',
metavar='python_version',
default=None,
help=("Only download wheels compatible with Python "
"interpreter version <version>. If not specified, then the "
"current system interpreter minor version is used. A major "
"version (e.g. '2') can be specified to match all "
"minor revs of that major version. A minor version "
"(e.g. '34') can also be specified."),
)
cmd_opts.add_option(
'--implementation',
dest='implementation',
metavar='implementation',
default=None,
help=("Only download wheels compatible with Python "
"implementation <implementation>, e.g. 'pp', 'jy', 'cp', "
" or 'ip'. If not specified, then the current "
"interpreter implementation is used. Use 'py' to force "
"implementation-agnostic wheels."),
)
cmd_opts.add_option(
'--abi',
dest='abi',
metavar='abi',
default=None,
help=("Only download wheels compatible with Python "
"abi <abi>, e.g. 'pypy_41'. If not specified, then the "
"current interpreter abi tag is used. Generally "
"you will need to specify --implementation, "
"--platform, and --python-version when using "
"this option."),
)
cmd_opts.add_option(cmdoptions.platform())
cmd_opts.add_option(cmdoptions.python_version())
cmd_opts.add_option(cmdoptions.implementation())
cmd_opts.add_option(cmdoptions.abi())
index_opts = cmdoptions.make_option_group(
cmdoptions.index_group,
@@ -135,25 +91,7 @@ class DownloadCommand(RequirementCommand):
else:
python_versions = None
dist_restriction_set = any([
options.python_version,
options.platform,
options.abi,
options.implementation,
])
binary_only = FormatControl(set(), {':all:'})
no_sdist_dependencies = (
options.format_control != binary_only and
not options.ignore_dependencies
)
if dist_restriction_set and no_sdist_dependencies:
raise CommandError(
"When restricting platform and interpreter constraints using "
"--python-version, --platform, --abi, or --implementation, "
"either --no-deps must be set, or --only-binary=:all: must be "
"set and --no-binary must not be set (or must be set to "
":none:)."
)
cmdoptions.check_dist_restriction(options)
options.src_dir = os.path.abspath(options.src_dir)
options.download_dir = normalize_path(options.download_dir)
@@ -2,11 +2,11 @@ from __future__ import absolute_import
import sys
from pipenv.patched.notpip._internal import index
from pipenv.patched.notpip._internal.basecommand import Command
from pipenv.patched.notpip._internal.cache import WheelCache
from pipenv.patched.notpip._internal.compat import stdlib_pkgs
from pipenv.patched.notpip._internal.cli.base_command import Command
from pipenv.patched.notpip._internal.models.format_control import FormatControl
from pipenv.patched.notpip._internal.operations.freeze import freeze
from pipenv.patched.notpip._internal.utils.compat import stdlib_pkgs
DEV_PKGS = {'pip', 'setuptools', 'distribute', 'wheel'}
@@ -71,7 +71,7 @@ class FreezeCommand(Command):
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options, args):
format_control = index.FormatControl(set(), set())
format_control = FormatControl(set(), set())
wheel_cache = WheelCache(options.cache_dir, format_control)
skip = set(stdlib_pkgs)
if not options.freeze_all:
@@ -4,8 +4,8 @@ import hashlib
import logging
import sys
from pipenv.patched.notpip._internal.basecommand import Command
from pipenv.patched.notpip._internal.status_codes import ERROR
from pipenv.patched.notpip._internal.cli.base_command import Command
from pipenv.patched.notpip._internal.cli.status_codes import ERROR
from pipenv.patched.notpip._internal.utils.hashes import FAVORITE_HASH, STRONG_HASHES
from pipenv.patched.notpip._internal.utils.misc import read_chunks
@@ -1,6 +1,7 @@
from __future__ import absolute_import
from pipenv.patched.notpip._internal.basecommand import SUCCESS, Command
from pipenv.patched.notpip._internal.cli.base_command import Command
from pipenv.patched.notpip._internal.cli.status_codes import SUCCESS
from pipenv.patched.notpip._internal.exceptions import CommandError
@@ -9,9 +9,10 @@ from optparse import SUPPRESS_HELP
from pipenv.patched.notpip._vendor import pkg_resources
from pipenv.patched.notpip._internal import cmdoptions
from pipenv.patched.notpip._internal.basecommand import RequirementCommand
from pipenv.patched.notpip._internal.cache import WheelCache
from pipenv.patched.notpip._internal.cli import cmdoptions
from pipenv.patched.notpip._internal.cli.base_command import RequirementCommand
from pipenv.patched.notpip._internal.cli.status_codes import ERROR
from pipenv.patched.notpip._internal.exceptions import (
CommandError, InstallationError, PreviousBuildDirError,
)
@@ -21,7 +22,6 @@ from pipenv.patched.notpip._internal.operations.prepare import RequirementPrepar
from pipenv.patched.notpip._internal.req import RequirementSet, install_given_reqs
from pipenv.patched.notpip._internal.req.req_tracker import RequirementTracker
from pipenv.patched.notpip._internal.resolve import Resolver
from pipenv.patched.notpip._internal.status_codes import ERROR
from pipenv.patched.notpip._internal.utils.filesystem import check_path_owner
from pipenv.patched.notpip._internal.utils.misc import (
ensure_dir, get_installed_version,
@@ -83,6 +83,11 @@ class InstallCommand(RequirementCommand):
'<dir>. Use --upgrade to replace existing packages in <dir> '
'with new versions.'
)
cmd_opts.add_option(cmdoptions.platform())
cmd_opts.add_option(cmdoptions.python_version())
cmd_opts.add_option(cmdoptions.implementation())
cmd_opts.add_option(cmdoptions.abi())
cmd_opts.add_option(
'--user',
dest='use_user_site',
@@ -204,7 +209,6 @@ class InstallCommand(RequirementCommand):
def run(self, options, args):
cmdoptions.check_install_build_global(options)
upgrade_strategy = "to-satisfy-only"
if options.upgrade:
upgrade_strategy = options.upgrade_strategy
@@ -212,6 +216,13 @@ class InstallCommand(RequirementCommand):
if options.build_dir:
options.build_dir = os.path.abspath(options.build_dir)
cmdoptions.check_dist_restriction(options, check_target=True)
if options.python_version:
python_versions = [options.python_version]
else:
python_versions = None
options.src_dir = os.path.abspath(options.src_dir)
install_options = options.install_options or []
if options.use_user_site:
@@ -246,7 +257,14 @@ class InstallCommand(RequirementCommand):
global_options = options.global_options or []
with self._build_session(options) as session:
finder = self._build_package_finder(options, session)
finder = self._build_package_finder(
options=options,
session=session,
platform=options.platform,
python_versions=python_versions,
abi=options.abi,
implementation=options.implementation,
)
build_delete = (not (options.no_clean or options.build_dir))
wheel_cache = WheelCache(options.cache_dir, options.format_control)
@@ -266,6 +284,7 @@ class InstallCommand(RequirementCommand):
) as directory:
requirement_set = RequirementSet(
require_hashes=options.require_hashes,
check_supported_wheels=not options.target_dir,
)
try:
@@ -6,8 +6,8 @@ import logging
from pipenv.patched.notpip._vendor import six
from pipenv.patched.notpip._vendor.six.moves import zip_longest
from pipenv.patched.notpip._internal.basecommand import Command
from pipenv.patched.notpip._internal.cmdoptions import index_group, make_option_group
from pipenv.patched.notpip._internal.cli import cmdoptions
from pipenv.patched.notpip._internal.cli.base_command import Command
from pipenv.patched.notpip._internal.exceptions import CommandError
from pipenv.patched.notpip._internal.index import PackageFinder
from pipenv.patched.notpip._internal.utils.misc import (
@@ -102,7 +102,9 @@ class ListCommand(Command):
help='Include editable package from output.',
default=True,
)
index_opts = make_option_group(index_group, self.parser)
index_opts = cmdoptions.make_option_group(
cmdoptions.index_group, self.parser
)
self.parser.insert_option_group(0, index_opts)
self.parser.insert_option_group(0, cmd_opts)
@@ -11,12 +11,12 @@ from pipenv.patched.notpip._vendor.packaging.version import parse as parse_versi
# why we ignore the type on this import
from pipenv.patched.notpip._vendor.six.moves import xmlrpc_client # type: ignore
from pipenv.patched.notpip._internal.basecommand import SUCCESS, Command
from pipenv.patched.notpip._internal.compat import get_terminal_size
from pipenv.patched.notpip._internal.cli.base_command import Command
from pipenv.patched.notpip._internal.cli.status_codes import NO_MATCHES_FOUND, SUCCESS
from pipenv.patched.notpip._internal.download import PipXmlrpcTransport
from pipenv.patched.notpip._internal.exceptions import CommandError
from pipenv.patched.notpip._internal.models.index import PyPI
from pipenv.patched.notpip._internal.status_codes import NO_MATCHES_FOUND
from pipenv.patched.notpip._internal.utils.compat import get_terminal_size
from pipenv.patched.notpip._internal.utils.logging import indent_log
logger = logging.getLogger(__name__)
@@ -7,8 +7,8 @@ from email.parser import FeedParser # type: ignore
from pipenv.patched.notpip._vendor import pkg_resources
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
from pipenv.patched.notpip._internal.basecommand import Command
from pipenv.patched.notpip._internal.status_codes import ERROR, SUCCESS
from pipenv.patched.notpip._internal.cli.base_command import Command
from pipenv.patched.notpip._internal.cli.status_codes import ERROR, SUCCESS
logger = logging.getLogger(__name__)
@@ -2,9 +2,10 @@ from __future__ import absolute_import
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
from pipenv.patched.notpip._internal.basecommand import Command
from pipenv.patched.notpip._internal.cli.base_command import Command
from pipenv.patched.notpip._internal.exceptions import InstallationError
from pipenv.patched.notpip._internal.req import InstallRequirement, parse_requirements
from pipenv.patched.notpip._internal.req import parse_requirements
from pipenv.patched.notpip._internal.req.constructors import install_req_from_line
from pipenv.patched.notpip._internal.utils.misc import protect_pip_from_modification_on_windows
@@ -47,7 +48,7 @@ class UninstallCommand(Command):
with self._build_session(options) as session:
reqs_to_uninstall = {}
for name in args:
req = InstallRequirement.from_line(
req = install_req_from_line(
name, isolated=options.isolated_mode,
)
if req.name:
@@ -4,9 +4,9 @@ from __future__ import absolute_import
import logging
import os
from pipenv.patched.notpip._internal import cmdoptions
from pipenv.patched.notpip._internal.basecommand import RequirementCommand
from pipenv.patched.notpip._internal.cache import WheelCache
from pipenv.patched.notpip._internal.cli import cmdoptions
from pipenv.patched.notpip._internal.cli.base_command import RequirementCommand
from pipenv.patched.notpip._internal.exceptions import CommandError, PreviousBuildDirError
from pipenv.patched.notpip._internal.operations.prepare import RequirementPreparer
from pipenv.patched.notpip._internal.req import RequirementSet
@@ -18,7 +18,9 @@ import os
from pipenv.patched.notpip._vendor import six
from pipenv.patched.notpip._vendor.six.moves import configparser
from pipenv.patched.notpip._internal.exceptions import ConfigurationError
from pipenv.patched.notpip._internal.exceptions import (
ConfigurationError, ConfigurationFileCouldNotBeLoaded,
)
from pipenv.patched.notpip._internal.locations import (
legacy_config_file, new_config_file, running_under_virtualenv,
site_config_files, venv_config_file,
@@ -289,11 +291,16 @@ class Configuration(object):
try:
parser.read(fname)
except UnicodeDecodeError:
raise ConfigurationError((
"ERROR: "
"Configuration file contains invalid %s characters.\n"
"Please fix your configuration, located at %s\n"
) % (locale.getpreferredencoding(False), fname))
# See https://github.com/pypa/pip/issues/4963
raise ConfigurationFileCouldNotBeLoaded(
reason="contains invalid {} characters".format(
locale.getpreferredencoding(False)
),
fname=fname,
)
except configparser.Error as error:
# See https://github.com/pypa/pip/issues/4893
raise ConfigurationFileCouldNotBeLoaded(error=error)
return parser
def _load_environment_vars(self):
+2 -2
View File
@@ -323,7 +323,7 @@ class InsecureHTTPAdapter(HTTPAdapter):
conn.ca_certs = None
class PipSession(Session):
class PipSession(requests.Session):
timeout = None
@@ -753,7 +753,7 @@ def _copy_dist_from_dir(link_path, location):
# build an sdist
setup_py = 'setup.py'
sdist_args = [os.environ.get('PIP_PYTHON_PATH', sys.executable)]
sdist_args = [sys.executable]
sdist_args.append('-c')
sdist_args.append(SETUPTOOLS_SHIM % setup_py)
sdist_args.append('sdist')
@@ -247,3 +247,22 @@ class HashMismatch(HashError):
class UnsupportedPythonVersion(InstallationError):
"""Unsupported python version according to Requires-Python package
metadata."""
class ConfigurationFileCouldNotBeLoaded(ConfigurationError):
"""When there are errors while loading a configuration file
"""
def __init__(self, reason="could not be loaded", fname=None, error=None):
super(ConfigurationFileCouldNotBeLoaded, self).__init__(error)
self.reason = reason
self.fname = fname
self.error = error
def __str__(self):
if self.fname is not None:
message_part = " in {}.".format(self.fname)
else:
assert self.error is not None
message_part = ".\n{}\n".format(self.error.message)
return "Configuration file {}{}".format(self.reason, message_part)
+172 -398
View File
@@ -20,24 +20,27 @@ from pipenv.patched.notpip._vendor.requests.exceptions import SSLError
from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse
from pipenv.patched.notpip._vendor.six.moves.urllib import request as urllib_request
from pipenv.patched.notpip._internal.compat import ipaddress
from pipenv.patched.notpip._internal.download import HAS_TLS, is_url, path_to_url, url_to_path
from pipenv.patched.notpip._internal.exceptions import (
BestVersionAlreadyInstalled, DistributionNotFound, InvalidWheelFilename,
UnsupportedWheel,
)
from pipenv.patched.notpip._internal.models.candidate import InstallationCandidate
from pipenv.patched.notpip._internal.models.format_control import FormatControl
from pipenv.patched.notpip._internal.models.index import PyPI
from pipenv.patched.notpip._internal.models.link import Link
from pipenv.patched.notpip._internal.pep425tags import get_supported
from pipenv.patched.notpip._internal.utils.compat import ipaddress
from pipenv.patched.notpip._internal.utils.deprecation import deprecated
from pipenv.patched.notpip._internal.utils.logging import indent_log
from pipenv.patched.notpip._internal.utils.misc import (
ARCHIVE_EXTENSIONS, SUPPORTED_EXTENSIONS, cached_property, normalize_path,
remove_auth_from_url, splitext,
ARCHIVE_EXTENSIONS, SUPPORTED_EXTENSIONS, normalize_path,
remove_auth_from_url,
)
from pipenv.patched.notpip._internal.utils.packaging import check_requires_python
from pipenv.patched.notpip._internal.wheel import Wheel, wheel_ext
__all__ = ['FormatControl', 'fmt_ctl_handle_mutual_exclude', 'PackageFinder']
__all__ = ['FormatControl', 'PackageFinder']
SECURE_ORIGINS = [
@@ -56,46 +59,120 @@ SECURE_ORIGINS = [
logger = logging.getLogger(__name__)
class InstallationCandidate(object):
def _get_content_type(url, session):
"""Get the Content-Type of the given url, using a HEAD request"""
scheme, netloc, path, query, fragment = urllib_parse.urlsplit(url)
if scheme not in {'http', 'https'}:
# FIXME: some warning or something?
# assertion error?
return ''
def __init__(self, project, version, location, requires_python=None):
self.project = project
self.version = parse_version(version)
self.location = location
self._key = (self.project, self.version, self.location)
self.requires_python = requires_python
resp = session.head(url, allow_redirects=True)
resp.raise_for_status()
def __repr__(self):
return "<InstallationCandidate({!r}, {!r}, {!r})>".format(
self.project, self.version, self.location,
return resp.headers.get("Content-Type", "")
def _handle_get_page_fail(link, reason, url, meth=None):
if meth is None:
meth = logger.debug
meth("Could not fetch URL %s: %s - skipping", link, reason)
def _get_html_page(link, session=None):
if session is None:
raise TypeError(
"_get_html_page() missing 1 required keyword argument: 'session'"
)
def __hash__(self):
return hash(self._key)
url = link.url
url = url.split('#', 1)[0]
def __lt__(self, other):
return self._compare(other, lambda s, o: s < o)
# Check for VCS schemes that do not support lookup as web pages.
from pipenv.patched.notpip._internal.vcs import VcsSupport
for scheme in VcsSupport.schemes:
if url.lower().startswith(scheme) and url[len(scheme)] in '+:':
logger.debug('Cannot look at %s URL %s', scheme, link)
return None
def __le__(self, other):
return self._compare(other, lambda s, o: s <= o)
try:
filename = link.filename
for bad_ext in ARCHIVE_EXTENSIONS:
if filename.endswith(bad_ext):
content_type = _get_content_type(url, session=session)
if content_type.lower().startswith('text/html'):
break
else:
logger.debug(
'Skipping page %s because of Content-Type: %s',
link,
content_type,
)
return
def __eq__(self, other):
return self._compare(other, lambda s, o: s == o)
logger.debug('Getting page %s', url)
def __ge__(self, other):
return self._compare(other, lambda s, o: s >= o)
# Tack index.html onto file:// URLs that point to directories
(scheme, netloc, path, params, query, fragment) = \
urllib_parse.urlparse(url)
if (scheme == 'file' and
os.path.isdir(urllib_request.url2pathname(path))):
# add trailing slash if not present so urljoin doesn't trim
# final segment
if not url.endswith('/'):
url += '/'
url = urllib_parse.urljoin(url, 'index.html')
logger.debug(' file: URL is directory, getting %s', url)
def __gt__(self, other):
return self._compare(other, lambda s, o: s > o)
resp = session.get(
url,
headers={
"Accept": "text/html",
# We don't want to blindly returned cached data for
# /simple/, because authors generally expecting that
# twine upload && pip install will function, but if
# they've done a pip install in the last ~10 minutes
# it won't. Thus by setting this to zero we will not
# blindly use any cached data, however the benefit of
# using max-age=0 instead of no-cache, is that we will
# still support conditional requests, so we will still
# minimize traffic sent in cases where the page hasn't
# changed at all, we will just always incur the round
# trip for the conditional GET now instead of only
# once per 10 minutes.
# For more information, please see pypa/pip#5670.
"Cache-Control": "max-age=0",
},
)
resp.raise_for_status()
def __ne__(self, other):
return self._compare(other, lambda s, o: s != o)
# The check for archives above only works if the url ends with
# something that looks like an archive. However that is not a
# requirement of an url. Unless we issue a HEAD request on every
# url we cannot know ahead of time for sure if something is HTML
# or not. However we can check after we've downloaded it.
content_type = resp.headers.get('Content-Type', 'unknown')
if not content_type.lower().startswith("text/html"):
logger.debug(
'Skipping page %s because of Content-Type: %s',
link,
content_type,
)
return
def _compare(self, other, method):
if not isinstance(other, InstallationCandidate):
return NotImplemented
return method(self._key, other._key)
inst = HTMLPage(resp.content, resp.url, resp.headers)
except requests.HTTPError as exc:
_handle_get_page_fail(link, exc, url)
except SSLError as exc:
reason = "There was a problem confirming the ssl certificate: "
reason += str(exc)
_handle_get_page_fail(link, reason, url, meth=logger.info)
except requests.ConnectionError as exc:
_handle_get_page_fail(link, "connection error: %s" % exc, url)
except requests.Timeout:
_handle_get_page_fail(link, "timed out", url)
else:
return inst
class PackageFinder(object):
@@ -210,15 +287,15 @@ class PackageFinder(object):
return "\n".join(lines)
def add_dependency_links(self, links):
# # FIXME: this shouldn't be global list this, it should only
# # apply to requirements of the package that specifies the
# # dependency_links value
# # FIXME: also, we should track comes_from (i.e., use Link)
# FIXME: this shouldn't be global list this, it should only
# apply to requirements of the package that specifies the
# dependency_links value
# FIXME: also, we should track comes_from (i.e., use Link)
if self.process_dependency_links:
deprecated(
"Dependency Links processing has been deprecated and will be "
"removed in a future release.",
replacement=None,
replacement="PEP 508 URL dependencies",
gone_in="18.2",
issue=4187,
)
@@ -242,6 +319,7 @@ class PackageFinder(object):
return extras
@staticmethod
def _sort_locations(locations, expand_dir=False):
"""
@@ -467,7 +545,7 @@ class PackageFinder(object):
logger.debug('* %s', location)
canonical_name = canonicalize_name(project_name)
formats = fmt_ctl_formats(self.format_control, canonical_name)
formats = self.format_control.get_allowed_formats(canonical_name)
search = Search(project_name, canonical_name, formats)
find_links_versions = self._package_versions(
# We trust every directly linked archive in find_links
@@ -483,7 +561,7 @@ class PackageFinder(object):
continue
with indent_log():
page_versions.extend(
self._package_versions(page.links, search)
self._package_versions(page.iter_links(), search)
)
dependency_versions = self._package_versions(
@@ -626,7 +704,9 @@ class PackageFinder(object):
try:
page = self._get_page(location)
except requests.HTTPError as e:
except requests.HTTPError:
continue
if page is None:
continue
yield page
@@ -743,7 +823,7 @@ class PackageFinder(object):
return InstallationCandidate(search.supplied, version, link, link.requires_python)
def _get_page(self, link):
return HTMLPage.get_page(link, session=self.session)
return _get_html_page(link, session=self.session)
def egg_info_matches(
@@ -763,7 +843,7 @@ def egg_info_matches(
return None
if search_name is None:
full_match = match.group(0)
return full_match[full_match.index('-'):]
return full_match.split('-', 1)[-1]
name = match.group(0).lower()
# To match the "safe" name that pkg_resources creates:
name = name.replace('_', '-')
@@ -775,377 +855,71 @@ def egg_info_matches(
return None
def _determine_base_url(document, page_url):
"""Determine the HTML document's base URL.
This looks for a ``<base>`` tag in the HTML document. If present, its href
attribute denotes the base URL of anchor tags in the document. If there is
no such tag (or if it does not have a valid href attribute), the HTML
file's URL is used as the base URL.
:param document: An HTML document representation. The current
implementation expects the result of ``html5lib.parse()``.
:param page_url: The URL of the HTML document.
"""
for base in document.findall(".//base"):
href = base.get("href")
if href is not None:
return href
return page_url
def _get_encoding_from_headers(headers):
"""Determine if we have any encoding information in our headers.
"""
if headers and "Content-Type" in headers:
content_type, params = cgi.parse_header(headers["Content-Type"])
if "charset" in params:
return params['charset']
return None
_CLEAN_LINK_RE = re.compile(r'[^a-z0-9$&+,/:;=?@.#%_\\|-]', re.I)
def _clean_link(url):
"""Makes sure a link is fully encoded. That is, if a ' ' shows up in
the link, it will be rewritten to %20 (while not over-quoting
% or other characters)."""
return _CLEAN_LINK_RE.sub(lambda match: '%%%2x' % ord(match.group(0)), url)
class HTMLPage(object):
"""Represents one page, along with its URL"""
def __init__(self, content, url, headers=None):
# Determine if we have any encoding information in our headers
encoding = None
if headers and "Content-Type" in headers:
content_type, params = cgi.parse_header(headers["Content-Type"])
if "charset" in params:
encoding = params['charset']
self.content = content
self.parsed = html5lib.parse(
self.content,
transport_encoding=encoding,
namespaceHTMLElements=False,
)
self.url = url
self.headers = headers
def __str__(self):
return self.url
@classmethod
def get_page(cls, link, skip_archives=True, session=None):
if session is None:
raise TypeError(
"get_page() missing 1 required keyword argument: 'session'"
)
url = link.url
url = url.split('#', 1)[0]
# Check for VCS schemes that do not support lookup as web pages.
from pipenv.patched.notpip._internal.vcs import VcsSupport
for scheme in VcsSupport.schemes:
if url.lower().startswith(scheme) and url[len(scheme)] in '+:':
logger.debug('Cannot look at %s URL %s', scheme, link)
return None
try:
if skip_archives:
filename = link.filename
for bad_ext in ARCHIVE_EXTENSIONS:
if filename.endswith(bad_ext):
content_type = cls._get_content_type(
url, session=session,
)
if content_type.lower().startswith('text/html'):
break
else:
logger.debug(
'Skipping page %s because of Content-Type: %s',
link,
content_type,
)
return
logger.debug('Getting page %s', url)
# Tack index.html onto file:// URLs that point to directories
(scheme, netloc, path, params, query, fragment) = \
urllib_parse.urlparse(url)
if (scheme == 'file' and
os.path.isdir(urllib_request.url2pathname(path))):
# add trailing slash if not present so urljoin doesn't trim
# final segment
if not url.endswith('/'):
url += '/'
url = urllib_parse.urljoin(url, 'index.html')
logger.debug(' file: URL is directory, getting %s', url)
resp = session.get(
url,
headers={
"Accept": "text/html",
"Cache-Control": "max-age=600",
},
)
resp.raise_for_status()
# The check for archives above only works if the url ends with
# something that looks like an archive. However that is not a
# requirement of an url. Unless we issue a HEAD request on every
# url we cannot know ahead of time for sure if something is HTML
# or not. However we can check after we've downloaded it.
content_type = resp.headers.get('Content-Type', 'unknown')
if not content_type.lower().startswith("text/html"):
logger.debug(
'Skipping page %s because of Content-Type: %s',
link,
content_type,
)
return
inst = cls(resp.content, resp.url, resp.headers)
except requests.HTTPError as exc:
cls._handle_fail(link, exc, url)
except SSLError as exc:
reason = "There was a problem confirming the ssl certificate: "
reason += str(exc)
cls._handle_fail(link, reason, url, meth=logger.info)
except requests.ConnectionError as exc:
cls._handle_fail(link, "connection error: %s" % exc, url)
except requests.Timeout:
cls._handle_fail(link, "timed out", url)
else:
return inst
@staticmethod
def _handle_fail(link, reason, url, meth=None):
if meth is None:
meth = logger.debug
meth("Could not fetch URL %s: %s - skipping", link, reason)
@staticmethod
def _get_content_type(url, session):
"""Get the Content-Type of the given url, using a HEAD request"""
scheme, netloc, path, query, fragment = urllib_parse.urlsplit(url)
if scheme not in {'http', 'https'}:
# FIXME: some warning or something?
# assertion error?
return ''
resp = session.head(url, allow_redirects=True)
resp.raise_for_status()
return resp.headers.get("Content-Type", "")
@cached_property
def base_url(self):
bases = [
x for x in self.parsed.findall(".//base")
if x.get("href") is not None
]
if bases and bases[0].get("href"):
return bases[0].get("href")
else:
return self.url
@property
def links(self):
def iter_links(self):
"""Yields all links in the page"""
for anchor in self.parsed.findall(".//a"):
document = html5lib.parse(
self.content,
transport_encoding=_get_encoding_from_headers(self.headers),
namespaceHTMLElements=False,
)
base_url = _determine_base_url(document, self.url)
for anchor in document.findall(".//a"):
if anchor.get("href"):
href = anchor.get("href")
url = self.clean_link(
urllib_parse.urljoin(self.base_url, href)
)
url = _clean_link(urllib_parse.urljoin(base_url, href))
pyrequire = anchor.get('data-requires-python')
pyrequire = unescape(pyrequire) if pyrequire else None
yield Link(url, self, requires_python=pyrequire)
_clean_re = re.compile(r'[^a-z0-9$&+,/:;=?@.#%_\\|-]', re.I)
def clean_link(self, url):
"""Makes sure a link is fully encoded. That is, if a ' ' shows up in
the link, it will be rewritten to %20 (while not over-quoting
% or other characters)."""
return self._clean_re.sub(
lambda match: '%%%2x' % ord(match.group(0)), url)
class Link(object):
def __init__(self, url, comes_from=None, requires_python=None):
"""
Object representing a parsed link from https://pypi.org/simple/*
url:
url of the resource pointed to (href of the link)
comes_from:
instance of HTMLPage where the link was found, or string.
requires_python:
String containing the `Requires-Python` metadata field, specified
in PEP 345. This may be specified by a data-requires-python
attribute in the HTML link tag, as described in PEP 503.
"""
# url can be a UNC windows share
if url.startswith('\\\\'):
url = path_to_url(url)
self.url = url
self.comes_from = comes_from
self.requires_python = requires_python if requires_python else None
def __str__(self):
if self.requires_python:
rp = ' (requires-python:%s)' % self.requires_python
else:
rp = ''
if self.comes_from:
return '%s (from %s)%s' % (self.url, self.comes_from, rp)
else:
return str(self.url)
def __repr__(self):
return '<Link %s>' % self
def __eq__(self, other):
if not isinstance(other, Link):
return NotImplemented
return self.url == other.url
def __ne__(self, other):
if not isinstance(other, Link):
return NotImplemented
return self.url != other.url
def __lt__(self, other):
if not isinstance(other, Link):
return NotImplemented
return self.url < other.url
def __le__(self, other):
if not isinstance(other, Link):
return NotImplemented
return self.url <= other.url
def __gt__(self, other):
if not isinstance(other, Link):
return NotImplemented
return self.url > other.url
def __ge__(self, other):
if not isinstance(other, Link):
return NotImplemented
return self.url >= other.url
def __hash__(self):
return hash(self.url)
@property
def filename(self):
_, netloc, path, _, _ = urllib_parse.urlsplit(self.url)
name = posixpath.basename(path.rstrip('/')) or netloc
name = urllib_parse.unquote(name)
assert name, ('URL %r produced no filename' % self.url)
return name
@property
def scheme(self):
return urllib_parse.urlsplit(self.url)[0]
@property
def netloc(self):
return urllib_parse.urlsplit(self.url)[1]
@property
def path(self):
return urllib_parse.unquote(urllib_parse.urlsplit(self.url)[2])
def splitext(self):
return splitext(posixpath.basename(self.path.rstrip('/')))
@property
def ext(self):
return self.splitext()[1]
@property
def url_without_fragment(self):
scheme, netloc, path, query, fragment = urllib_parse.urlsplit(self.url)
return urllib_parse.urlunsplit((scheme, netloc, path, query, None))
_egg_fragment_re = re.compile(r'[#&]egg=([^&]*)')
@property
def egg_fragment(self):
match = self._egg_fragment_re.search(self.url)
if not match:
return None
return match.group(1)
_subdirectory_fragment_re = re.compile(r'[#&]subdirectory=([^&]*)')
@property
def subdirectory_fragment(self):
match = self._subdirectory_fragment_re.search(self.url)
if not match:
return None
return match.group(1)
_hash_re = re.compile(
r'(sha1|sha224|sha384|sha256|sha512|md5)=([a-f0-9]+)'
)
@property
def hash(self):
match = self._hash_re.search(self.url)
if match:
return match.group(2)
return None
@property
def hash_name(self):
match = self._hash_re.search(self.url)
if match:
return match.group(1)
return None
@property
def show_url(self):
return posixpath.basename(self.url.split('#', 1)[0].split('?', 1)[0])
@property
def is_wheel(self):
return self.ext == wheel_ext
@property
def is_artifact(self):
"""
Determines if this points to an actual artifact (e.g. a tarball) or if
it points to an "abstract" thing like a path or a VCS location.
"""
from pipenv.patched.notpip._internal.vcs import vcs
if self.scheme in vcs.all_schemes:
return False
return True
FormatControl = namedtuple('FormatControl', 'no_binary only_binary')
"""This object has two fields, no_binary and only_binary.
If a field is falsy, it isn't set. If it is {':all:'}, it should match all
packages except those listed in the other field. Only one field can be set
to {':all:'} at a time. The rest of the time exact package name matches
are listed, with any given package only showing up in one field at a time.
"""
def fmt_ctl_handle_mutual_exclude(value, target, other):
new = value.split(',')
while ':all:' in new:
other.clear()
target.clear()
target.add(':all:')
del new[:new.index(':all:') + 1]
if ':none:' not in new:
# Without a none, we want to discard everything as :all: covers it
return
for name in new:
if name == ':none:':
target.clear()
continue
name = canonicalize_name(name)
other.discard(name)
target.add(name)
def fmt_ctl_formats(fmt_ctl, canonical_name):
result = {"binary", "source"}
if canonical_name in fmt_ctl.only_binary:
result.discard('source')
elif canonical_name in fmt_ctl.no_binary:
result.discard('binary')
elif ':all:' in fmt_ctl.only_binary:
result.discard('source')
elif ':all:' in fmt_ctl.no_binary:
result.discard('binary')
return frozenset(result)
def fmt_ctl_no_binary(fmt_ctl):
fmt_ctl_handle_mutual_exclude(
':all:', fmt_ctl.no_binary, fmt_ctl.only_binary,
)
yield Link(url, self.url, requires_python=pyrequire)
Search = namedtuple('Search', 'supplied canonical formats')
+1 -1
View File
@@ -10,8 +10,8 @@ import sysconfig
from distutils import sysconfig as distutils_sysconfig
from distutils.command.install import SCHEME_KEYS # type: ignore
from pipenv.patched.notpip._internal.compat import WINDOWS, expanduser
from pipenv.patched.notpip._internal.utils import appdirs
from pipenv.patched.notpip._internal.utils.compat import WINDOWS, expanduser
# Application Directories
USER_CACHE_DIR = appdirs.user_cache_dir("pip")
@@ -0,0 +1,24 @@
from pipenv.patched.notpip._vendor.packaging.version import parse as parse_version
from pipenv.patched.notpip._internal.utils.models import KeyBasedCompareMixin
class InstallationCandidate(KeyBasedCompareMixin):
"""Represents a potential "candidate" for installation.
"""
def __init__(self, project, version, location, requires_python=None):
self.project = project
self.version = parse_version(version)
self.location = location
self.requires_python = requires_python
super(InstallationCandidate, self).__init__(
key=(self.project, self.version, self.location),
defining_class=InstallationCandidate
)
def __repr__(self):
return "<InstallationCandidate({!r}, {!r}, {!r})>".format(
self.project, self.version, self.location,
)
@@ -0,0 +1,62 @@
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
class FormatControl(object):
"""A helper class for controlling formats from which packages are installed.
If a field is falsy, it isn't set. If it is {':all:'}, it should match all
packages except those listed in the other field. Only one field can be set
to {':all:'} at a time. The rest of the time exact package name matches
are listed, with any given package only showing up in one field at a time.
"""
def __init__(self, no_binary=None, only_binary=None):
self.no_binary = set() if no_binary is None else no_binary
self.only_binary = set() if only_binary is None else only_binary
def __eq__(self, other):
return self.__dict__ == other.__dict__
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
return "{}({}, {})".format(
self.__class__.__name__,
self.no_binary,
self.only_binary
)
@staticmethod
def handle_mutual_excludes(value, target, other):
new = value.split(',')
while ':all:' in new:
other.clear()
target.clear()
target.add(':all:')
del new[:new.index(':all:') + 1]
# Without a none, we want to discard everything as :all: covers it
if ':none:' not in new:
return
for name in new:
if name == ':none:':
target.clear()
continue
name = canonicalize_name(name)
other.discard(name)
target.add(name)
def get_allowed_formats(self, canonical_name):
result = {"binary", "source"}
if canonical_name in self.only_binary:
result.discard('source')
elif canonical_name in self.no_binary:
result.discard('binary')
elif ':all:' in self.only_binary:
result.discard('source')
elif ':all:' in self.no_binary:
result.discard('binary')
return frozenset(result)
def disallow_binaries(self):
self.handle_mutual_excludes(
':all:', self.no_binary, self.only_binary,
)
@@ -1,15 +1,29 @@
from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse
class Index(object):
def __init__(self, url):
class PackageIndex(object):
"""Represents a Package Index and provides easier access to endpoints
"""
def __init__(self, url, file_storage_domain):
super(PackageIndex, self).__init__()
self.url = url
self.netloc = urllib_parse.urlsplit(url).netloc
self.simple_url = self.url_to_path('simple')
self.pypi_url = self.url_to_path('pypi')
self.simple_url = self._url_for_path('simple')
self.pypi_url = self._url_for_path('pypi')
def url_to_path(self, path):
# This is part of a temporary hack used to block installs of PyPI
# packages which depend on external urls only necessary until PyPI can
# block such packages themselves
self.file_storage_domain = file_storage_domain
def _url_for_path(self, path):
return urllib_parse.urljoin(self.url, path)
PyPI = Index('https://pypi.org/')
PyPI = PackageIndex(
'https://pypi.org/', file_storage_domain='files.pythonhosted.org'
)
TestPyPI = PackageIndex(
'https://test.pypi.org/', file_storage_domain='test-files.pythonhosted.org'
)
@@ -0,0 +1,141 @@
import posixpath
import re
from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse
from pipenv.patched.notpip._internal.download import path_to_url
from pipenv.patched.notpip._internal.utils.misc import splitext
from pipenv.patched.notpip._internal.utils.models import KeyBasedCompareMixin
from pipenv.patched.notpip._internal.wheel import wheel_ext
class Link(KeyBasedCompareMixin):
"""Represents a parsed link from a Package Index's simple URL
"""
def __init__(self, url, comes_from=None, requires_python=None):
"""
url:
url of the resource pointed to (href of the link)
comes_from:
instance of HTMLPage where the link was found, or string.
requires_python:
String containing the `Requires-Python` metadata field, specified
in PEP 345. This may be specified by a data-requires-python
attribute in the HTML link tag, as described in PEP 503.
"""
# url can be a UNC windows share
if url.startswith('\\\\'):
url = path_to_url(url)
self.url = url
self.comes_from = comes_from
self.requires_python = requires_python if requires_python else None
super(Link, self).__init__(
key=(self.url),
defining_class=Link
)
def __str__(self):
if self.requires_python:
rp = ' (requires-python:%s)' % self.requires_python
else:
rp = ''
if self.comes_from:
return '%s (from %s)%s' % (self.url, self.comes_from, rp)
else:
return str(self.url)
def __repr__(self):
return '<Link %s>' % self
@property
def filename(self):
_, netloc, path, _, _ = urllib_parse.urlsplit(self.url)
name = posixpath.basename(path.rstrip('/')) or netloc
name = urllib_parse.unquote(name)
assert name, ('URL %r produced no filename' % self.url)
return name
@property
def scheme(self):
return urllib_parse.urlsplit(self.url)[0]
@property
def netloc(self):
return urllib_parse.urlsplit(self.url)[1]
@property
def path(self):
return urllib_parse.unquote(urllib_parse.urlsplit(self.url)[2])
def splitext(self):
return splitext(posixpath.basename(self.path.rstrip('/')))
@property
def ext(self):
return self.splitext()[1]
@property
def url_without_fragment(self):
scheme, netloc, path, query, fragment = urllib_parse.urlsplit(self.url)
return urllib_parse.urlunsplit((scheme, netloc, path, query, None))
_egg_fragment_re = re.compile(r'[#&]egg=([^&]*)')
@property
def egg_fragment(self):
match = self._egg_fragment_re.search(self.url)
if not match:
return None
return match.group(1)
_subdirectory_fragment_re = re.compile(r'[#&]subdirectory=([^&]*)')
@property
def subdirectory_fragment(self):
match = self._subdirectory_fragment_re.search(self.url)
if not match:
return None
return match.group(1)
_hash_re = re.compile(
r'(sha1|sha224|sha384|sha256|sha512|md5)=([a-f0-9]+)'
)
@property
def hash(self):
match = self._hash_re.search(self.url)
if match:
return match.group(2)
return None
@property
def hash_name(self):
match = self._hash_re.search(self.url)
if match:
return match.group(1)
return None
@property
def show_url(self):
return posixpath.basename(self.url.split('#', 1)[0].split('?', 1)[0])
@property
def is_wheel(self):
return self.ext == wheel_ext
@property
def is_artifact(self):
"""
Determines if this points to an actual artifact (e.g. a tarball) or if
it points to an "abstract" thing like a path or a VCS location.
"""
from pipenv.patched.notpip._internal.vcs import vcs
if self.scheme in vcs.all_schemes:
return False
return True
@@ -10,11 +10,13 @@ from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
from pipenv.patched.notpip._vendor.pkg_resources import RequirementParseError
from pipenv.patched.notpip._internal.exceptions import InstallationError
from pipenv.patched.notpip._internal.req import InstallRequirement
from pipenv.patched.notpip._internal.req.constructors import (
install_req_from_editable, install_req_from_line,
)
from pipenv.patched.notpip._internal.req.req_file import COMMENT_RE
from pipenv.patched.notpip._internal.utils.deprecation import deprecated
from pipenv.patched.notpip._internal.utils.misc import (
dist_is_editable, get_installed_distributions,
dist_is_editable, get_installed_distributions, make_vcs_requirement_url,
)
logger = logging.getLogger(__name__)
@@ -99,13 +101,13 @@ def freeze(
line = line[2:].strip()
else:
line = line[len('--editable'):].strip().lstrip('=')
line_req = InstallRequirement.from_editable(
line_req = install_req_from_editable(
line,
isolated=isolated,
wheel_cache=wheel_cache,
)
else:
line_req = InstallRequirement.from_line(
line_req = install_req_from_line(
COMMENT_RE.sub('', line).strip(),
isolated=isolated,
wheel_cache=wheel_cache,
@@ -166,7 +168,13 @@ class FrozenRequirement(object):
_date_re = re.compile(r'-(20\d\d\d\d\d\d)$')
@classmethod
def from_dist(cls, dist, dependency_links):
def _init_args_from_dist(cls, dist, dependency_links):
"""
Compute and return arguments (req, editable, comments) to pass to
FrozenRequirement.__init__().
This method is for use in FrozenRequirement.from_dist().
"""
location = os.path.normcase(os.path.abspath(dist.location))
comments = []
from pipenv.patched.notpip._internal.vcs import vcs, get_src_requirement
@@ -231,12 +239,15 @@ class FrozenRequirement(object):
else:
rev = '{%s}' % date_match.group(1)
editable = True
req = '%s@%s#egg=%s' % (
svn_location,
rev,
cls.egg_name(dist)
)
return cls(dist.project_name, req, editable, comments)
egg_name = cls.egg_name(dist)
req = make_vcs_requirement_url(svn_location, rev, egg_name)
return (req, editable, comments)
@classmethod
def from_dist(cls, dist, dependency_links):
args = cls._init_args_from_dist(dist, dependency_links)
return cls(dist.project_name, *args)
@staticmethod
def egg_name(dist):
@@ -7,7 +7,6 @@ import os
from pipenv.patched.notpip._vendor import pkg_resources, requests
from pipenv.patched.notpip._internal.build_env import BuildEnvironment
from pipenv.patched.notpip._internal.compat import expanduser
from pipenv.patched.notpip._internal.download import (
is_dir_url, is_file_url, is_vcs_url, unpack_url, url_to_path,
)
@@ -15,6 +14,7 @@ from pipenv.patched.notpip._internal.exceptions import (
DirectoryUrlHashUnsupported, HashUnpinned, InstallationError,
PreviousBuildDirError, VcsHashUnsupported,
)
from pipenv.patched.notpip._internal.utils.compat import expanduser
from pipenv.patched.notpip._internal.utils.hashes import MissingHashes
from pipenv.patched.notpip._internal.utils.logging import indent_log
from pipenv.patched.notpip._internal.utils.misc import display_path, normalize_path, rmtree
@@ -65,7 +65,7 @@ class DistAbstraction(object):
"""Return a setuptools Dist object."""
raise NotImplementedError(self.dist)
def prep_for_dist(self, finder):
def prep_for_dist(self, finder, build_isolation):
"""Ensure that we can get a Dist for this requirement."""
raise NotImplementedError(self.dist)
@@ -93,36 +93,36 @@ class IsSDist(DistAbstraction):
return dist
def prep_for_dist(self, finder, build_isolation):
# Before calling "setup.py egg_info", we need to set-up the build
# environment.
build_requirements = self.req.get_pep_518_info()
should_isolate = build_isolation and build_requirements is not None
# Prepare for building. We need to:
# 1. Load pyproject.toml (if it exists)
# 2. Set up the build environment
self.req.load_pyproject_toml()
should_isolate = self.req.use_pep517 and build_isolation
if should_isolate:
# Haven't implemented PEP 517 yet, so spew a warning about it if
# build-requirements don't include setuptools and wheel.
missing_requirements = {'setuptools', 'wheel'} - {
pkg_resources.Requirement(r).key for r in build_requirements
}
if missing_requirements:
# Isolate in a BuildEnvironment and install the build-time
# requirements.
self.req.build_env = BuildEnvironment()
self.req.build_env.install_requirements(
finder, self.req.pyproject_requires,
"Installing build dependencies"
)
missing = []
if self.req.requirements_to_check:
check = self.req.requirements_to_check
missing = self.req.build_env.missing_requirements(check)
if missing:
logger.warning(
"Missing build requirements in pyproject.toml for %s.",
self.req,
)
logger.warning(
"This version of pip does not implement PEP 517 so it "
"cannot build a wheel without %s.",
" and ".join(map(repr, sorted(missing_requirements)))
"The project does not specify a build backend, and pip "
"cannot fall back to setuptools without %s.",
" and ".join(map(repr, sorted(missing)))
)
# Isolate in a BuildEnvironment and install the build-time
# requirements.
self.req.build_env = BuildEnvironment()
self.req.build_env.install_requirements(
finder, build_requirements,
"Installing build dependencies"
)
try:
self.req.run_egg_info()
except (OSError, TypeError):
@@ -136,7 +136,7 @@ class Installed(DistAbstraction):
def dist(self, finder):
return self.req.satisfied_by
def prep_for_dist(self, finder):
def prep_for_dist(self, finder, build_isolation):
pass
@@ -10,7 +10,12 @@ import sysconfig
import warnings
from collections import OrderedDict
import pipenv.patched.notpip._internal.utils.glibc
try:
import pipenv.patched.notpip._internal.utils.glibc
except ImportError:
import pipenv.patched.notpip.utils.glibc
from pipenv.patched.notpip._internal.utils.compat import get_extension_suffixes
logger = logging.getLogger(__name__)
@@ -252,10 +257,9 @@ def get_supported(versions=None, noarch=False, platform=None,
abis[0:0] = [abi]
abi3s = set()
import imp
for suffix in imp.get_suffixes():
if suffix[0].startswith('.abi'):
abi3s.add(suffix[0].split('.', 2)[1])
for suffix in get_extension_suffixes():
if suffix.startswith('.abi'):
abi3s.add(suffix.split('.', 2)[1])
abis.extend(sorted(list(abi3s)))
@@ -0,0 +1,144 @@
from __future__ import absolute_import
import io
import os
from pipenv.patched.notpip._vendor import pytoml, six
from pipenv.patched.notpip._internal.exceptions import InstallationError
def _is_list_of_str(obj):
return (
isinstance(obj, list) and
all(isinstance(item, six.string_types) for item in obj)
)
def load_pyproject_toml(use_pep517, pyproject_toml, setup_py, req_name):
"""Load the pyproject.toml file.
Parameters:
use_pep517 - Has the user requested PEP 517 processing? None
means the user hasn't explicitly specified.
pyproject_toml - Location of the project's pyproject.toml file
setup_py - Location of the project's setup.py file
req_name - The name of the requirement we're processing (for
error reporting)
Returns:
None if we should use the legacy code path, otherwise a tuple
(
requirements from pyproject.toml,
name of PEP 517 backend,
requirements we should check are installed after setting
up the build environment
)
"""
has_pyproject = os.path.isfile(pyproject_toml)
has_setup = os.path.isfile(setup_py)
if has_pyproject:
with io.open(pyproject_toml, encoding="utf-8") as f:
pp_toml = pytoml.load(f)
build_system = pp_toml.get("build-system")
else:
build_system = None
# The following cases must use PEP 517
# We check for use_pep517 equalling False because that
# means the user explicitly requested --no-use-pep517
if has_pyproject and not has_setup:
if use_pep517 is False:
raise InstallationError(
"Disabling PEP 517 processing is invalid: "
"project does not have a setup.py"
)
use_pep517 = True
elif build_system and "build-backend" in build_system:
if use_pep517 is False:
raise InstallationError(
"Disabling PEP 517 processing is invalid: "
"project specifies a build backend of {} "
"in pyproject.toml".format(
build_system["build-backend"]
)
)
use_pep517 = True
# If we haven't worked out whether to use PEP 517 yet,
# and the user hasn't explicitly stated a preference,
# we do so if the project has a pyproject.toml file.
elif use_pep517 is None:
use_pep517 = has_pyproject
# At this point, we know whether we're going to use PEP 517.
assert use_pep517 is not None
# If we're using the legacy code path, there is nothing further
# for us to do here.
if not use_pep517:
return None
if build_system is None:
# Either the user has a pyproject.toml with no build-system
# section, or the user has no pyproject.toml, but has opted in
# explicitly via --use-pep517.
# In the absence of any explicit backend specification, we
# assume the setuptools backend, and require wheel and a version
# of setuptools that supports that backend.
build_system = {
"requires": ["setuptools>=38.2.5", "wheel"],
"build-backend": "setuptools.build_meta",
}
# If we're using PEP 517, we have build system information (either
# from pyproject.toml, or defaulted by the code above).
# Note that at this point, we do not know if the user has actually
# specified a backend, though.
assert build_system is not None
# Ensure that the build-system section in pyproject.toml conforms
# to PEP 518.
error_template = (
"{package} has a pyproject.toml file that does not comply "
"with PEP 518: {reason}"
)
# Specifying the build-system table but not the requires key is invalid
if "requires" not in build_system:
raise InstallationError(
error_template.format(package=req_name, reason=(
"it has a 'build-system' table but not "
"'build-system.requires' which is mandatory in the table"
))
)
# Error out if requires is not a list of strings
requires = build_system["requires"]
if not _is_list_of_str(requires):
raise InstallationError(error_template.format(
package=req_name,
reason="'build-system.requires' is not a list of strings.",
))
backend = build_system.get("build-backend")
check = []
if backend is None:
# If the user didn't specify a backend, we assume they want to use
# the setuptools backend. But we can't be sure they have included
# a version of setuptools which supplies the backend, or wheel
# (which is neede by the backend) in their requirements. So we
# make a note to check that those requirements are present once
# we have set up the environment.
# TODO: Review this - it's quite a lot of work to check for a very
# specific case. The problem is, that case is potentially quite
# common - projects that adopted PEP 518 early for the ability to
# specify requirements to execute setup.py, but never considered
# needing to mention the build tools themselves. The original PEP
# 518 code had a similar check (but implemented in a different
# way).
backend = "setuptools.build_meta"
check = ["setuptools>=38.2.5", "wheel"]
return (requires, backend, check)
@@ -0,0 +1,298 @@
"""Backing implementation for InstallRequirement's various constructors
The idea here is that these formed a major chunk of InstallRequirement's size
so, moving them and support code dedicated to them outside of that class
helps creates for better understandability for the rest of the code.
These are meant to be used elsewhere within pip to create instances of
InstallRequirement.
"""
import logging
import os
import re
import traceback
from pipenv.patched.notpip._vendor.packaging.markers import Marker
from pipenv.patched.notpip._vendor.packaging.requirements import InvalidRequirement, Requirement
from pipenv.patched.notpip._vendor.packaging.specifiers import Specifier
from pipenv.patched.notpip._vendor.pkg_resources import RequirementParseError, parse_requirements
from pipenv.patched.notpip._internal.download import (
is_archive_file, is_url, path_to_url, url_to_path,
)
from pipenv.patched.notpip._internal.exceptions import InstallationError
from pipenv.patched.notpip._internal.models.index import PyPI, TestPyPI
from pipenv.patched.notpip._internal.models.link import Link
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
from pipenv.patched.notpip._internal.utils.misc import is_installable_dir
from pipenv.patched.notpip._internal.vcs import vcs
from pipenv.patched.notpip._internal.wheel import Wheel
__all__ = [
"install_req_from_editable", "install_req_from_line",
"parse_editable"
]
logger = logging.getLogger(__name__)
operators = Specifier._operators.keys()
def _strip_extras(path):
m = re.match(r'^(.+)(\[[^\]]+\])$', path)
extras = None
if m:
path_no_extras = m.group(1)
extras = m.group(2)
else:
path_no_extras = path
return path_no_extras, extras
def parse_editable(editable_req):
"""Parses an editable requirement into:
- a requirement name
- an URL
- extras
- editable options
Accepted requirements:
svn+http://blahblah@rev#egg=Foobar[baz]&subdirectory=version_subdir
.[some_extra]
"""
url = editable_req
# If a file path is specified with extras, strip off the extras.
url_no_extras, extras = _strip_extras(url)
if os.path.isdir(url_no_extras):
if not os.path.exists(os.path.join(url_no_extras, 'setup.py')):
raise InstallationError(
"Directory %r is not installable. File 'setup.py' not found." %
url_no_extras
)
# Treating it as code that has already been checked out
url_no_extras = path_to_url(url_no_extras)
if url_no_extras.lower().startswith('file:'):
package_name = Link(url_no_extras).egg_fragment
if extras:
return (
package_name,
url_no_extras,
Requirement("placeholder" + extras.lower()).extras,
)
else:
return package_name, url_no_extras, None
for version_control in vcs:
if url.lower().startswith('%s:' % version_control):
url = '%s+%s' % (version_control, url)
break
if '+' not in url:
raise InstallationError(
'%s should either be a path to a local project or a VCS url '
'beginning with svn+, git+, hg+, or bzr+' %
editable_req
)
vc_type = url.split('+', 1)[0].lower()
if not vcs.get_backend(vc_type):
error_message = 'For --editable=%s only ' % editable_req + \
', '.join([backend.name + '+URL' for backend in vcs.backends]) + \
' is currently supported'
raise InstallationError(error_message)
package_name = Link(url).egg_fragment
if not package_name:
raise InstallationError(
"Could not detect requirement name for '%s', please specify one "
"with #egg=your_package_name" % editable_req
)
return package_name, url, None
def deduce_helpful_msg(req):
"""Returns helpful msg in case requirements file does not exist,
or cannot be parsed.
:params req: Requirements file path
"""
msg = ""
if os.path.exists(req):
msg = " It does exist."
# Try to parse and check if it is a requirements file.
try:
with open(req, 'r') as fp:
# parse first line only
next(parse_requirements(fp.read()))
msg += " The argument you provided " + \
"(%s) appears to be a" % (req) + \
" requirements file. If that is the" + \
" case, use the '-r' flag to install" + \
" the packages specified within it."
except RequirementParseError:
logger.debug("Cannot parse '%s' as requirements \
file" % (req), exc_info=1)
else:
msg += " File '%s' does not exist." % (req)
return msg
# ---- The actual constructors follow ----
def install_req_from_editable(
editable_req, comes_from=None, isolated=False, options=None,
wheel_cache=None, constraint=False
):
name, url, extras_override = parse_editable(editable_req)
if url.startswith('file:'):
source_dir = url_to_path(url)
else:
source_dir = None
if name is not None:
try:
req = Requirement(name)
except InvalidRequirement:
raise InstallationError("Invalid requirement: '%s'" % name)
else:
req = None
return InstallRequirement(
req, comes_from, source_dir=source_dir,
editable=True,
link=Link(url),
constraint=constraint,
isolated=isolated,
options=options if options else {},
wheel_cache=wheel_cache,
extras=extras_override or (),
)
def install_req_from_line(
name, comes_from=None, isolated=False, options=None, wheel_cache=None,
constraint=False
):
"""Creates an InstallRequirement from a name, which might be a
requirement, directory containing 'setup.py', filename, or URL.
"""
if is_url(name):
marker_sep = '; '
else:
marker_sep = ';'
if marker_sep in name:
name, markers = name.split(marker_sep, 1)
markers = markers.strip()
if not markers:
markers = None
else:
markers = Marker(markers)
else:
markers = None
name = name.strip()
req = None
path = os.path.normpath(os.path.abspath(name))
link = None
extras = None
if is_url(name):
link = Link(name)
else:
p, extras = _strip_extras(path)
looks_like_dir = os.path.isdir(p) and (
os.path.sep in name or
(os.path.altsep is not None and os.path.altsep in name) or
name.startswith('.')
)
if looks_like_dir:
if not is_installable_dir(p):
raise InstallationError(
"Directory %r is not installable. Neither 'setup.py' "
"nor 'pyproject.toml' found." % name
)
link = Link(path_to_url(p))
elif is_archive_file(p):
if not os.path.isfile(p):
logger.warning(
'Requirement %r looks like a filename, but the '
'file does not exist',
name
)
link = Link(path_to_url(p))
# it's a local file, dir, or url
if link:
# Handle relative file URLs
if link.scheme == 'file' and re.search(r'\.\./', link.url):
link = Link(
path_to_url(os.path.normpath(os.path.abspath(link.path))))
# wheel file
if link.is_wheel:
wheel = Wheel(link.filename) # can raise InvalidWheelFilename
req = "%s==%s" % (wheel.name, wheel.version)
else:
# set the req to the egg fragment. when it's not there, this
# will become an 'unnamed' requirement
req = link.egg_fragment
# a requirement specifier
else:
req = name
if extras:
extras = Requirement("placeholder" + extras.lower()).extras
else:
extras = ()
if req is not None:
try:
req = Requirement(req)
except InvalidRequirement:
if os.path.sep in req:
add_msg = "It looks like a path."
add_msg += deduce_helpful_msg(req)
elif '=' in req and not any(op in req for op in operators):
add_msg = "= is not a valid operator. Did you mean == ?"
else:
add_msg = traceback.format_exc()
raise InstallationError(
"Invalid requirement: '%s'\n%s" % (req, add_msg)
)
return InstallRequirement(
req, comes_from, link=link, markers=markers,
isolated=isolated,
options=options if options else {},
wheel_cache=wheel_cache,
constraint=constraint,
extras=extras,
)
def install_req_from_req(
req, comes_from=None, isolated=False, wheel_cache=None
):
try:
req = Requirement(req)
except InvalidRequirement:
raise InstallationError("Invalid requirement: '%s'" % req)
domains_not_allowed = [
PyPI.file_storage_domain,
TestPyPI.file_storage_domain,
]
if req.url and comes_from.link.netloc in domains_not_allowed:
# Explicitly disallow pypi packages that depend on external urls
raise InstallationError(
"Packages installed from PyPI cannot depend on packages "
"which are not also hosted on PyPI.\n"
"%s depends on %s " % (comes_from.name, req)
)
return InstallRequirement(
req, comes_from, isolated=isolated, wheel_cache=wheel_cache
)
@@ -13,10 +13,12 @@ import sys
from pipenv.patched.notpip._vendor.six.moves import filterfalse
from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse
from pipenv.patched.notpip._internal import cmdoptions
from pipenv.patched.notpip._internal.cli import cmdoptions
from pipenv.patched.notpip._internal.download import get_file_content
from pipenv.patched.notpip._internal.exceptions import RequirementsFileParseError
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
from pipenv.patched.notpip._internal.req.constructors import (
install_req_from_editable, install_req_from_line,
)
__all__ = ['parse_requirements']
@@ -151,7 +153,7 @@ def process_line(line, filename, line_number, finder=None, comes_from=None,
for dest in SUPPORTED_OPTIONS_REQ_DEST:
if dest in opts.__dict__ and opts.__dict__[dest]:
req_options[dest] = opts.__dict__[dest]
yield InstallRequirement.from_line(
yield install_req_from_line(
args_str, line_comes_from, constraint=constraint,
isolated=isolated, options=req_options, wheel_cache=wheel_cache
)
@@ -159,7 +161,7 @@ def process_line(line, filename, line_number, finder=None, comes_from=None,
# yield an editable requirement
elif opts.editables:
isolated = options.isolated_mode if options else False
yield InstallRequirement.from_editable(
yield install_req_from_editable(
opts.editables[0], comes_from=line_comes_from,
constraint=constraint, isolated=isolated, wheel_cache=wheel_cache
)
@@ -1,66 +1,46 @@
from __future__ import absolute_import
import io
import logging
import os
import re
import shutil
import sys
import sysconfig
import traceback
import zipfile
from distutils.util import change_root
from email.parser import FeedParser # type: ignore
from pipenv.patched.notpip._vendor import pkg_resources, pytoml, six
from pipenv.patched.notpip._vendor.packaging import specifiers
from pipenv.patched.notpip._vendor.packaging.markers import Marker
from pipenv.patched.notpip._vendor.packaging.requirements import InvalidRequirement, Requirement
from pipenv.patched.notpip._vendor import pkg_resources, six
from pipenv.patched.notpip._vendor.packaging.requirements import Requirement
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
from pipenv.patched.notpip._vendor.packaging.version import Version
from pipenv.patched.notpip._vendor.packaging.version import parse as parse_version
from pipenv.patched.notpip._vendor.pkg_resources import RequirementParseError, parse_requirements
from pipenv.patched.notpip._vendor.pep517.wrappers import Pep517HookCaller
from pipenv.patched.notpip._internal import wheel
from pipenv.patched.notpip._internal.build_env import NoOpBuildEnvironment
from pipenv.patched.notpip._internal.compat import native_str
from pipenv.patched.notpip._internal.download import (
is_archive_file, is_url, path_to_url, url_to_path,
)
from pipenv.patched.notpip._internal.exceptions import InstallationError
from pipenv.patched.notpip._internal.locations import (
PIP_DELETE_MARKER_FILENAME, running_under_virtualenv,
)
from pipenv.patched.notpip._internal.models.link import Link
from pipenv.patched.notpip._internal.pyproject import load_pyproject_toml
from pipenv.patched.notpip._internal.req.req_uninstall import UninstallPathSet
from pipenv.patched.notpip._internal.utils.compat import native_str
from pipenv.patched.notpip._internal.utils.hashes import Hashes
from pipenv.patched.notpip._internal.utils.logging import indent_log
from pipenv.patched.notpip._internal.utils.misc import (
_make_build_dir, ask_path_exists, backup_dir, call_subprocess,
display_path, dist_in_site_packages, dist_in_usersite, ensure_dir,
get_installed_version, is_installable_dir, read_text_file, rmtree,
get_installed_version, rmtree,
)
from pipenv.patched.notpip._internal.utils.packaging import get_metadata
from pipenv.patched.notpip._internal.utils.setuptools_build import SETUPTOOLS_SHIM
from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory
from pipenv.patched.notpip._internal.utils.ui import open_spinner
from pipenv.patched.notpip._internal.vcs import vcs
from pipenv.patched.notpip._internal.wheel import Wheel, move_wheel_files
from pipenv.patched.notpip._internal.wheel import move_wheel_files
logger = logging.getLogger(__name__)
operators = specifiers.Specifier._operators.keys()
def _strip_extras(path):
m = re.match(r'^(.+)(\[[^\]]+\])$', path)
extras = None
if m:
path_no_extras = m.group(1)
extras = m.group(2)
else:
path_no_extras = path
return path_no_extras, extras
class InstallRequirement(object):
"""
@@ -87,7 +67,6 @@ class InstallRequirement(object):
if link is not None:
self.link = self.original_link = link
else:
from pipenv.patched.notpip._internal.index import Link
self.link = self.original_link = req and req.url and Link(req.url)
if extras:
@@ -128,155 +107,32 @@ class InstallRequirement(object):
self.isolated = isolated
self.build_env = NoOpBuildEnvironment()
# Constructors
# TODO: Move these out of this class into custom methods.
@classmethod
def from_editable(cls, editable_req, comes_from=None, isolated=False,
options=None, wheel_cache=None, constraint=False):
from pipenv.patched.notpip._internal.index import Link
# The static build requirements (from pyproject.toml)
self.pyproject_requires = None
name, url, extras_override = parse_editable(editable_req)
if url.startswith('file:'):
source_dir = url_to_path(url)
else:
source_dir = None
# Build requirements that we will check are available
# TODO: We don't do this for --no-build-isolation. Should we?
self.requirements_to_check = []
if name is not None:
try:
req = Requirement(name)
except InvalidRequirement:
raise InstallationError("Invalid requirement: '%s'" % name)
else:
req = None
return cls(
req, comes_from, source_dir=source_dir,
editable=True,
link=Link(url),
constraint=constraint,
isolated=isolated,
options=options if options else {},
wheel_cache=wheel_cache,
extras=extras_override or (),
)
# The PEP 517 backend we should use to build the project
self.pep517_backend = None
@classmethod
def from_req(cls, req, comes_from=None, isolated=False, wheel_cache=None):
try:
req = Requirement(req)
except InvalidRequirement:
raise InstallationError("Invalid requirement: '%s'" % req)
if req.url:
raise InstallationError(
"Direct url requirement (like %s) are not allowed for "
"dependencies" % req
)
return cls(req, comes_from, isolated=isolated, wheel_cache=wheel_cache)
@classmethod
def from_line(
cls, name, comes_from=None, isolated=False, options=None,
wheel_cache=None, constraint=False):
"""Creates an InstallRequirement from a name, which might be a
requirement, directory containing 'setup.py', filename, or URL.
"""
from pipenv.patched.notpip._internal.index import Link
if is_url(name):
marker_sep = '; '
else:
marker_sep = ';'
if marker_sep in name:
name, markers = name.split(marker_sep, 1)
markers = markers.strip()
if not markers:
markers = None
else:
markers = Marker(markers)
else:
markers = None
name = name.strip()
req = None
path = os.path.normpath(os.path.abspath(name))
link = None
extras = None
if is_url(name):
link = Link(name)
else:
p, extras = _strip_extras(path)
looks_like_dir = os.path.isdir(p) and (
os.path.sep in name or
(os.path.altsep is not None and os.path.altsep in name) or
name.startswith('.')
)
if looks_like_dir:
if not is_installable_dir(p):
raise InstallationError(
"Directory %r is not installable. File 'setup.py' "
"not found." % name
)
link = Link(path_to_url(p))
elif is_archive_file(p):
if not os.path.isfile(p):
logger.warning(
'Requirement %r looks like a filename, but the '
'file does not exist',
name
)
link = Link(path_to_url(p))
# it's a local file, dir, or url
if link:
# Handle relative file URLs
if link.scheme == 'file' and re.search(r'\.\./', link.url):
link = Link(
path_to_url(os.path.normpath(os.path.abspath(link.path))))
# wheel file
if link.is_wheel:
wheel = Wheel(link.filename) # can raise InvalidWheelFilename
req = "%s==%s" % (wheel.name, wheel.version)
else:
# set the req to the egg fragment. when it's not there, this
# will become an 'unnamed' requirement
req = link.egg_fragment
# a requirement specifier
else:
req = name
if extras:
extras = Requirement("placeholder" + extras.lower()).extras
else:
extras = ()
if req is not None:
try:
req = Requirement(req)
except InvalidRequirement:
if os.path.sep in req:
add_msg = "It looks like a path."
add_msg += deduce_helpful_msg(req)
elif '=' in req and not any(op in req for op in operators):
add_msg = "= is not a valid operator. Did you mean == ?"
else:
add_msg = traceback.format_exc()
raise InstallationError(
"Invalid requirement: '%s'\n%s" % (req, add_msg))
return cls(
req, comes_from, link=link, markers=markers,
isolated=isolated,
options=options if options else {},
wheel_cache=wheel_cache,
constraint=constraint,
extras=extras,
)
# Are we using PEP 517 for this requirement?
# After pyproject.toml has been loaded, the only valid values are True
# and False. Before loading, None is valid (meaning "use the default").
# Setting an explicit value before loading pyproject.toml is supported,
# but after loading this flag should be treated as read only.
self.use_pep517 = None
def __str__(self):
if self.req:
s = str(self.req)
if self.link:
s += ' from %s' % self.link.url
elif self.link:
s = self.link.url
else:
s = self.link.url if self.link else None
s = '<InstallRequirement>'
if self.satisfied_by is not None:
s += ' in %s' % display_path(self.satisfied_by.location)
if self.comes_from:
@@ -429,7 +285,7 @@ class InstallRequirement(object):
package is not available until we run egg_info, so the build_location
will return a temporary directory and store the _ideal_build_dir.
This is only called by self.egg_info_path to fix the temporary build
This is only called by self.run_egg_info to fix the temporary build
directory.
"""
if self.source_dir is not None:
@@ -557,48 +413,29 @@ class InstallRequirement(object):
return pp_toml
def get_pep_518_info(self):
"""Get PEP 518 build-time requirements.
def load_pyproject_toml(self):
"""Load the pyproject.toml file.
Returns the list of the packages required to build the project,
specified as per PEP 518 within the package. If `pyproject.toml` is not
present, returns None to signify not using the same.
After calling this routine, all of the attributes related to PEP 517
processing for this requirement have been set. In particular, the
use_pep517 attribute can be used to determine whether we should
follow the PEP 517 or legacy (setup.py) code path.
"""
# If pyproject.toml does not exist, don't do anything.
if not os.path.isfile(self.pyproject_toml):
return None
error_template = (
"{package} has a pyproject.toml file that does not comply "
"with PEP 518: {reason}"
pep517_data = load_pyproject_toml(
self.use_pep517,
self.pyproject_toml,
self.setup_py,
str(self)
)
with io.open(self.pyproject_toml, encoding="utf-8") as f:
pp_toml = pytoml.load(f)
# If there is no build-system table, just use setuptools and wheel.
if "build-system" not in pp_toml:
return ["setuptools", "wheel"]
# Specifying the build-system table but not the requires key is invalid
build_system = pp_toml["build-system"]
if "requires" not in build_system:
raise InstallationError(
error_template.format(package=self, reason=(
"it has a 'build-system' table but not "
"'build-system.requires' which is mandatory in the table"
))
)
# Error out if it's not a list of strings
requires = build_system["requires"]
if not _is_list_of_str(requires):
raise InstallationError(error_template.format(
package=self,
reason="'build-system.requires' is not a list of strings.",
))
return requires
if pep517_data is None:
self.use_pep517 = False
else:
self.use_pep517 = True
requires, backend, check = pep517_data
self.requirements_to_check = check
self.pyproject_requires = requires
self.pep517_backend = Pep517HookCaller(self.setup_py_dir, backend)
def run_egg_info(self):
assert self.source_dir
@@ -615,7 +452,8 @@ class InstallRequirement(object):
with indent_log():
script = SETUPTOOLS_SHIM % self.setup_py
base_cmd = [os.environ.get('PIP_PYTHON_PATH', sys.executable), '-c', script]
sys_executable = os.environ.get('PIP_PYTHON_PATH', sys.executable)
base_cmd = [sys_executable, '-c', script]
if self.isolated:
base_cmd += ["--no-user-cfg"]
egg_info_cmd = base_cmd + ['egg_info']
@@ -636,20 +474,20 @@ class InstallRequirement(object):
command_desc='python setup.py egg_info')
if not self.req:
if isinstance(parse_version(self.pkg_info()["Version"]), Version):
if isinstance(parse_version(self.metadata["Version"]), Version):
op = "=="
else:
op = "==="
self.req = Requirement(
"".join([
self.pkg_info()["Name"],
self.metadata["Name"],
op,
self.pkg_info()["Version"],
self.metadata["Version"],
])
)
self._correct_build_location()
else:
metadata_name = canonicalize_name(self.pkg_info()["Name"])
metadata_name = canonicalize_name(self.metadata["Name"])
if canonicalize_name(self.req.name) != metadata_name:
logger.warning(
'Running setup.py (path:%s) egg_info for package %s '
@@ -659,19 +497,8 @@ class InstallRequirement(object):
)
self.req = Requirement(metadata_name)
def egg_info_data(self, filename):
if self.satisfied_by is not None:
if not self.satisfied_by.has_metadata(filename):
return None
return self.satisfied_by.get_metadata(filename)
assert self.source_dir
filename = self.egg_info_path(filename)
if not os.path.exists(filename):
return None
data = read_text_file(filename)
return data
def egg_info_path(self, filename):
@property
def egg_info_path(self):
if self._egg_info_path is None:
if self.editable:
base = self.source_dir
@@ -709,8 +536,7 @@ class InstallRequirement(object):
if not filenames:
raise InstallationError(
"Files/directories (from %s) not found in %s"
% (filename, base)
"Files/directories not found in %s" % base
)
# if we have more than one match, we pick the toplevel one. This
# can easily be the case if there is a dist folder which contains
@@ -721,24 +547,18 @@ class InstallRequirement(object):
(os.path.altsep and x.count(os.path.altsep) or 0)
)
self._egg_info_path = os.path.join(base, filenames[0])
return os.path.join(self._egg_info_path, filename)
return self._egg_info_path
def pkg_info(self):
p = FeedParser()
data = self.egg_info_data('PKG-INFO')
if not data:
logger.warning(
'No PKG-INFO file found in %s',
display_path(self.egg_info_path('PKG-INFO')),
)
p.feed(data or '')
return p.close()
@property
def metadata(self):
if not hasattr(self, '_metadata'):
self._metadata = get_metadata(self.get_dist())
_requirements_section_re = re.compile(r'\[(.*?)\]')
return self._metadata
def get_dist(self):
"""Return a pkg_resources.Distribution built from self.egg_info_path"""
egg_info = self.egg_info_path('').rstrip(os.path.sep)
egg_info = self.egg_info_path.rstrip(os.path.sep)
base_dir = os.path.dirname(egg_info)
metadata = pkg_resources.PathMetadata(base_dir, egg_info)
dist_name = os.path.splitext(os.path.basename(egg_info))[0]
@@ -750,7 +570,7 @@ class InstallRequirement(object):
def assert_source_matches_version(self):
assert self.source_dir
version = self.pkg_info()['version']
version = self.metadata['version']
if self.req.specifier and version not in self.req.specifier:
logger.warning(
'Requested %s, but installing version %s',
@@ -794,10 +614,11 @@ class InstallRequirement(object):
with indent_log():
# FIXME: should we do --install-headers here too?
sys_executable = os.environ.get('PIP_PYTHON_PATH', sys.executable)
with self.build_env:
call_subprocess(
[
os.environ.get('PIP_PYTHON_PATH', sys.executable),
sys_executable,
'-c',
SETUPTOOLS_SHIM % self.setup_py
] +
@@ -877,7 +698,7 @@ class InstallRequirement(object):
def archive(self, build_dir):
assert self.source_dir
create_archive = True
archive_name = '%s-%s.zip' % (self.name, self.pkg_info()["version"])
archive_name = '%s-%s.zip' % (self.name, self.metadata["version"])
archive_path = os.path.join(build_dir, archive_name)
if os.path.exists(archive_path):
response = ask_path_exists(
@@ -1015,7 +836,8 @@ class InstallRequirement(object):
def get_install_args(self, global_options, record_filename, root, prefix,
pycompile):
install_args = [os.environ.get('PIP_PYTHON_PATH', sys.executable), "-u"]
sys_executable = os.environ.get('PIP_PYTHON_PATH', sys.executable)
install_args = [sys_executable, "-u"]
install_args.append('-c')
install_args.append(SETUPTOOLS_SHIM % self.setup_py)
install_args += list(global_options) + \
@@ -1039,104 +861,3 @@ class InstallRequirement(object):
py_ver_str, self.name)]
return install_args
def parse_editable(editable_req):
"""Parses an editable requirement into:
- a requirement name
- an URL
- extras
- editable options
Accepted requirements:
svn+http://blahblah@rev#egg=Foobar[baz]&subdirectory=version_subdir
.[some_extra]
"""
from pipenv.patched.notpip._internal.index import Link
url = editable_req
# If a file path is specified with extras, strip off the extras.
url_no_extras, extras = _strip_extras(url)
if os.path.isdir(url_no_extras):
if not os.path.exists(os.path.join(url_no_extras, 'setup.py')):
raise InstallationError(
"Directory %r is not installable. File 'setup.py' not found." %
url_no_extras
)
# Treating it as code that has already been checked out
url_no_extras = path_to_url(url_no_extras)
if url_no_extras.lower().startswith('file:'):
package_name = Link(url_no_extras).egg_fragment
if extras:
return (
package_name,
url_no_extras,
Requirement("placeholder" + extras.lower()).extras,
)
else:
return package_name, url_no_extras, None
for version_control in vcs:
if url.lower().startswith('%s:' % version_control):
url = '%s+%s' % (version_control, url)
break
if '+' not in url:
raise InstallationError(
'%s should either be a path to a local project or a VCS url '
'beginning with svn+, git+, hg+, or bzr+' %
editable_req
)
vc_type = url.split('+', 1)[0].lower()
if not vcs.get_backend(vc_type):
error_message = 'For --editable=%s only ' % editable_req + \
', '.join([backend.name + '+URL' for backend in vcs.backends]) + \
' is currently supported'
raise InstallationError(error_message)
package_name = Link(url).egg_fragment
if not package_name:
raise InstallationError(
"Could not detect requirement name for '%s', please specify one "
"with #egg=your_package_name" % editable_req
)
return package_name, url, None
def deduce_helpful_msg(req):
"""Returns helpful msg in case requirements file does not exist,
or cannot be parsed.
:params req: Requirements file path
"""
msg = ""
if os.path.exists(req):
msg = " It does exist."
# Try to parse and check if it is a requirements file.
try:
with open(req, 'r') as fp:
# parse first line only
next(parse_requirements(fp.read()))
msg += " The argument you provided " + \
"(%s) appears to be a" % (req) + \
" requirements file. If that is the" + \
" case, use the '-r' flag to install" + \
" the packages specified within it."
except RequirementParseError:
logger.debug("Cannot parse '%s' as requirements \
file" % (req), exc_info=1)
else:
msg += " File '%s' does not exist." % (req)
return msg
def _is_list_of_str(obj):
return (
isinstance(obj, list) and
all(isinstance(item, six.string_types) for item in obj)
)
+79 -57
View File
@@ -12,19 +12,22 @@ logger = logging.getLogger(__name__)
class RequirementSet(object):
def __init__(self, require_hashes=False, ignore_compatibility=True):
def __init__(self, require_hashes=False, check_supported_wheels=True, ignore_compatibility=True):
"""Create a RequirementSet.
"""
self.requirements = OrderedDict()
self.require_hashes = require_hashes
self.check_supported_wheels = check_supported_wheels
if ignore_compatibility:
self.check_supported_wheels = False
self.ignore_compatibility = True if (check_supported_wheels is False or ignore_compatibility is True) else False
# Mapping of alias: real_name
self.requirement_aliases = {}
self.unnamed_requirements = []
self.successfully_downloaded = []
self.reqs_to_cleanup = []
self.ignore_compatibility = ignore_compatibility
def __str__(self):
reqs = [req for req in self.requirements.values()
@@ -56,17 +59,22 @@ class RequirementSet(object):
requirement is applicable and has just been added.
"""
name = install_req.name
# If the markers do not match, ignore this requirement.
if not install_req.match_markers(extras_requested):
logger.info("Ignoring %s: markers '%s' don't match your "
"environment", install_req.name,
install_req.markers)
logger.info(
"Ignoring %s: markers '%s' don't match your environment",
name, install_req.markers,
)
return [], None
# This check has to come after we filter requirements with the
# environment markers.
# If the wheel is not supported, raise an error.
# Should check this after filtering out based on environment markers to
# allow specifying different wheels based on the environment/OS, in a
# single requirements file.
if install_req.link and install_req.link.is_wheel:
wheel = Wheel(install_req.link.filename)
if not wheel.supported() and not self.ignore_compatibility:
if self.check_supported_wheels and not wheel.supported():
raise InstallationError(
"%s is not a supported wheel on this platform." %
wheel.filename
@@ -78,59 +86,73 @@ class RequirementSet(object):
"a non direct req should have a parent"
)
# Unnamed requirements are scanned again and the requirement won't be
# added as a dependency until after scanning.
if not name:
# url or path requirement w/o an egg fragment
self.unnamed_requirements.append(install_req)
return [install_req], None
else:
try:
existing_req = self.get_requirement(name)
except KeyError:
existing_req = None
if (parent_req_name is None and existing_req and not
existing_req.constraint and
existing_req.extras == install_req.extras and not
existing_req.req.specifier == install_req.req.specifier):
raise InstallationError(
'Double requirement given: %s (already in %s, name=%r)'
% (install_req, existing_req, name))
if not existing_req:
# Add requirement
self.requirements[name] = install_req
# FIXME: what about other normalizations? E.g., _ vs. -?
if name.lower() != name:
self.requirement_aliases[name.lower()] = name
result = [install_req]
else:
# Assume there's no need to scan, and that we've already
# encountered this for scanning.
result = []
if not install_req.constraint and existing_req.constraint:
if (install_req.link and not (existing_req.link and
install_req.link.path == existing_req.link.path)):
self.reqs_to_cleanup.append(install_req)
raise InstallationError(
"Could not satisfy constraints for '%s': "
"installation from path or url cannot be "
"constrained to a version" % name,
)
# If we're now installing a constraint, mark the existing
# object for real installation.
existing_req.constraint = False
existing_req.extras = tuple(
sorted(set(existing_req.extras).union(
set(install_req.extras))))
logger.debug("Setting %s extras to: %s",
existing_req, existing_req.extras)
# And now we need to scan this.
result = [existing_req]
# Canonicalise to the already-added object for the backref
# check below.
install_req = existing_req
# We return install_req here to allow for the caller to add it to
# the dependency information for the parent package.
return result, install_req
try:
existing_req = self.get_requirement(name)
except KeyError:
existing_req = None
has_conflicting_requirement = (
parent_req_name is None and
existing_req and
not existing_req.constraint and
existing_req.extras == install_req.extras and
existing_req.req.specifier != install_req.req.specifier
)
if has_conflicting_requirement:
raise InstallationError(
"Double requirement given: %s (already in %s, name=%r)"
% (install_req, existing_req, name)
)
# When no existing requirement exists, add the requirement as a
# dependency and it will be scanned again after.
if not existing_req:
self.requirements[name] = install_req
# FIXME: what about other normalizations? E.g., _ vs. -?
if name.lower() != name:
self.requirement_aliases[name.lower()] = name
# We'd want to rescan this requirements later
return [install_req], install_req
# Assume there's no need to scan, and that we've already
# encountered this for scanning.
if install_req.constraint or not existing_req.constraint:
return [], existing_req
does_not_satisfy_constraint = (
install_req.link and
not (
existing_req.link and
install_req.link.path == existing_req.link.path
)
)
if does_not_satisfy_constraint:
self.reqs_to_cleanup.append(install_req)
raise InstallationError(
"Could not satisfy constraints for '%s': "
"installation from path or url cannot be "
"constrained to a version" % name,
)
# If we're now installing a constraint, mark the existing
# object for real installation.
existing_req.constraint = False
existing_req.extras = tuple(sorted(
set(existing_req.extras) | set(install_req.extras)
))
logger.debug(
"Setting %s extras to: %s",
existing_req, existing_req.extras,
)
# Return the existing requirement for addition to the parent and
# scanning again.
return [existing_req], existing_req
def has_requirement(self, project_name):
name = project_name.lower()
@@ -152,7 +174,7 @@ class RequirementSet(object):
return self.requirements[name]
if name in self.requirement_aliases:
return self.requirements[self.requirement_aliases[name]]
# raise KeyError("No project with the name %r" % project_name)
pass
def cleanup_files(self):
"""Clean up files, remove builds."""
@@ -9,9 +9,9 @@ import sysconfig
from pipenv.patched.notpip._vendor import pkg_resources
from pipenv.patched.notpip._internal.compat import WINDOWS, cache_from_source, uses_pycache
from pipenv.patched.notpip._internal.exceptions import UninstallationError
from pipenv.patched.notpip._internal.locations import bin_py, bin_user
from pipenv.patched.notpip._internal.utils.compat import WINDOWS, cache_from_source, uses_pycache
from pipenv.patched.notpip._internal.utils.logging import indent_log
from pipenv.patched.notpip._internal.utils.misc import (
FakeFile, ask, dist_in_usersite, dist_is_local, egg_link_path, is_local,
@@ -120,6 +120,8 @@ def compress_for_output_listing(paths):
folders.add(os.path.dirname(path))
files.add(path)
_normcased_files = set(map(os.path.normcase, files))
folders = compact(folders)
# This walks the tree using os.walk to not miss extra folders
@@ -130,8 +132,9 @@ def compress_for_output_listing(paths):
if fname.endswith(".pyc"):
continue
file_ = os.path.normcase(os.path.join(dirpath, fname))
if os.path.isfile(file_) and file_ not in files:
file_ = os.path.join(dirpath, fname)
if (os.path.isfile(file_) and
os.path.normcase(file_) not in _normcased_files):
# We are skipping this file. Add it to the set.
will_skip.add(file_)
+5 -8
View File
@@ -18,7 +18,7 @@ from pipenv.patched.notpip._internal.exceptions import (
BestVersionAlreadyInstalled, DistributionNotFound, HashError, HashErrors,
UnsupportedPythonVersion,
)
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
from pipenv.patched.notpip._internal.req.constructors import install_req_from_req
from pipenv.patched.notpip._internal.utils.logging import indent_log
from pipenv.patched.notpip._internal.utils.misc import dist_in_usersite, ensure_dir
from pipenv.patched.notpip._internal.utils.packaging import check_dist_requires_python
@@ -249,9 +249,6 @@ class Resolver(object):
# Tell user what we are doing for this requirement:
# obtain (editable), skipping, processing (local url), collecting
# (remote url or package name)
if ignore_requires_python or self.ignore_requires_python:
self.ignore_compatibility = True
if req_to_install.constraint or req_to_install.prepared:
return []
@@ -267,7 +264,7 @@ class Resolver(object):
try:
check_dist_requires_python(dist)
except UnsupportedPythonVersion as err:
if self.ignore_compatibility:
if self.ignore_requires_python or self.ignore_compatibility:
logger.warning(err.args[0])
else:
raise
@@ -281,7 +278,7 @@ class Resolver(object):
more_reqs = []
def add_req(subreq, extras_requested):
sub_install_req = InstallRequirement.from_req(
sub_install_req = install_req_from_req(
str(subreq),
req_to_install,
isolated=self.isolated,
@@ -303,10 +300,10 @@ class Resolver(object):
# We add req_to_install before its dependencies, so that we
# can refer to it when adding dependencies.
if not requirement_set.has_requirement(req_to_install.name):
# 'unnamed' requirements will get added here
available_requested = sorted(
set(dist.extras) & set(req_to_install.extras)
)
# 'unnamed' requirements will get added here
req_to_install.is_direct = True
requirement_set.add_requirement(
req_to_install, parent_req_name=None,
@@ -338,7 +335,7 @@ class Resolver(object):
for available in available_requested:
if hasattr(dist, '_DistInfoDistribution__dep_map'):
for req in dist._DistInfoDistribution__dep_map[available]:
req = InstallRequirement.from_req(
req = install_req_from_req(
str(req),
req_to_install,
isolated=self.isolated,
@@ -9,7 +9,7 @@ import sys
from pipenv.patched.notpip._vendor.six import PY2, text_type
from pipenv.patched.notpip._internal.compat import WINDOWS, expanduser
from pipenv.patched.notpip._internal.utils.compat import WINDOWS, expanduser
def user_cache_dir(appname):
@@ -25,6 +25,7 @@ except ImportError:
__all__ = [
"ipaddress", "uses_pycache", "console_to_str", "native_str",
"get_path_uid", "stdlib_pkgs", "WINDOWS", "samefile", "get_terminal_size",
"get_extension_suffixes",
]
@@ -160,6 +161,18 @@ def get_path_uid(path):
return file_uid
if sys.version_info >= (3, 4):
from importlib.machinery import EXTENSION_SUFFIXES
def get_extension_suffixes():
return EXTENSION_SUFFIXES
else:
from imp import get_suffixes
def get_extension_suffixes():
return [suffix[0] for suffix in get_suffixes()]
def expanduser(path):
"""
Expand ~ and ~user constructions.
@@ -1,7 +1,7 @@
import os
import os.path
from pipenv.patched.notpip._internal.compat import get_path_uid
from pipenv.patched.notpip._internal.utils.compat import get_path_uid
def check_path_owner(path):
@@ -5,7 +5,7 @@ import logging
import logging.handlers
import os
from pipenv.patched.notpip._internal.compat import WINDOWS
from pipenv.patched.notpip._internal.utils.compat import WINDOWS
from pipenv.patched.notpip._internal.utils.misc import ensure_dir
try:
+49 -8
View File
@@ -26,14 +26,14 @@ from pipenv.patched.notpip._vendor.six import PY2
from pipenv.patched.notpip._vendor.six.moves import input
from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse
from pipenv.patched.notpip._internal.compat import (
WINDOWS, console_to_str, expanduser, stdlib_pkgs,
)
from pipenv.patched.notpip._internal.exceptions import CommandError, InstallationError
from pipenv.patched.notpip._internal.locations import (
running_under_virtualenv, site_packages, user_site, virtualenv_no_global,
write_delete_marker_file,
)
from pipenv.patched.notpip._internal.utils.compat import (
WINDOWS, console_to_str, expanduser, stdlib_pkgs,
)
if PY2:
from io import BytesIO as StringIO
@@ -96,7 +96,7 @@ def get_prog():
try:
prog = os.path.basename(sys.argv[0])
if prog in ('__main__.py', '-c'):
return "%s -m pip" % os.environ.get('PIP_PYTHON_PATH', sys.executable)
return "%s -m pip" % sys.executable
else:
return prog
except (AttributeError, TypeError, IndexError):
@@ -187,12 +187,16 @@ def format_size(bytes):
def is_installable_dir(path):
"""Return True if `path` is a directory containing a setup.py file."""
"""Is path is a directory containing setup.py or pyproject.toml?
"""
if not os.path.isdir(path):
return False
setup_py = os.path.join(path, 'setup.py')
if os.path.isfile(setup_py):
return True
pyproject_toml = os.path.join(path, 'pyproject.toml')
if os.path.isfile(pyproject_toml):
return True
return False
@@ -852,6 +856,44 @@ def enum(*sequential, **named):
return type('Enum', (), enums)
def make_vcs_requirement_url(repo_url, rev, egg_project_name, subdir=None):
"""
Return the URL for a VCS requirement.
Args:
repo_url: the remote VCS url, with any needed VCS prefix (e.g. "git+").
"""
req = '{}@{}#egg={}'.format(repo_url, rev, egg_project_name)
if subdir:
req += '&subdirectory={}'.format(subdir)
return req
def split_auth_from_netloc(netloc):
"""
Parse out and remove the auth information from a netloc.
Returns: (netloc, (username, password)).
"""
if '@' not in netloc:
return netloc, (None, None)
# Split from the right because that's how urllib.parse.urlsplit()
# behaves if more than one @ is present (which can be checked using
# the password attribute of urlsplit()'s return value).
auth, netloc = netloc.rsplit('@', 1)
if ':' in auth:
# Split from the left because that's how urllib.parse.urlsplit()
# behaves if more than one : is present (which again can be checked
# using the password attribute of the return value)
user_pass = tuple(auth.split(':', 1))
else:
user_pass = auth, None
return netloc, user_pass
def remove_auth_from_url(url):
# Return a copy of url with 'username:password@' removed.
# username/pass params are passed to subversion through flags
@@ -859,12 +901,11 @@ def remove_auth_from_url(url):
# parsed url
purl = urllib_parse.urlsplit(url)
stripped_netloc = \
purl.netloc.split('@')[-1]
netloc, user_pass = split_auth_from_netloc(purl.netloc)
# stripped url
url_pieces = (
purl.scheme, stripped_netloc, purl.path, purl.query, purl.fragment
purl.scheme, netloc, purl.path, purl.query, purl.fragment
)
surl = urllib_parse.urlunsplit(url_pieces)
return surl
@@ -0,0 +1,40 @@
"""Utilities for defining models
"""
import operator
class KeyBasedCompareMixin(object):
"""Provides comparision capabilities that is based on a key
"""
def __init__(self, key, defining_class):
self._compare_key = key
self._defining_class = defining_class
def __hash__(self):
return hash(self._compare_key)
def __lt__(self, other):
return self._compare(other, operator.__lt__)
def __le__(self, other):
return self._compare(other, operator.__le__)
def __gt__(self, other):
return self._compare(other, operator.__gt__)
def __ge__(self, other):
return self._compare(other, operator.__ge__)
def __eq__(self, other):
return self._compare(other, operator.__eq__)
def __ne__(self, other):
return self._compare(other, operator.__ne__)
def _compare(self, other, method):
if not isinstance(other, self._defining_class):
return NotImplemented
return method(self._compare_key, other._compare_key)
@@ -9,8 +9,8 @@ import sys
from pipenv.patched.notpip._vendor import lockfile, pkg_resources
from pipenv.patched.notpip._vendor.packaging import version as packaging_version
from pipenv.patched.notpip._internal.compat import WINDOWS
from pipenv.patched.notpip._internal.index import PackageFinder
from pipenv.patched.notpip._internal.utils.compat import WINDOWS
from pipenv.patched.notpip._internal.utils.filesystem import check_path_owner
from pipenv.patched.notpip._internal.utils.misc import ensure_dir, get_installed_version
@@ -22,16 +22,25 @@ logger = logging.getLogger(__name__)
class SelfCheckState(object):
def __init__(self, cache_dir):
self.statefile_path = os.path.join(cache_dir, "selfcheck.json")
self.state = {}
self.statefile_path = None
# Load the existing state
try:
with open(self.statefile_path) as statefile:
self.state = json.load(statefile)[sys.prefix]
except (IOError, ValueError, KeyError):
self.state = {}
# Try to load the existing state
if cache_dir:
self.statefile_path = os.path.join(cache_dir, "selfcheck.json")
try:
with open(self.statefile_path) as statefile:
self.state = json.load(statefile)[sys.prefix]
except (IOError, ValueError, KeyError):
# Explicitly suppressing exceptions, since we don't want to
# error out if the cache file is invalid.
pass
def save(self, pypi_version, current_time):
# If we do not have a path to cache in, don't bother saving.
if not self.statefile_path:
return
# Check to make sure that we own the directory
if not check_path_owner(os.path.dirname(self.statefile_path)):
return
@@ -8,6 +8,7 @@ from pipenv.patched.notpip._vendor import pkg_resources
from pipenv.patched.notpip._vendor.packaging import specifiers, version
from pipenv.patched.notpip._internal import exceptions
from pipenv.patched.notpip._internal.utils.misc import display_path
logger = logging.getLogger(__name__)
@@ -35,22 +36,31 @@ def check_requires_python(requires_python):
def get_metadata(dist):
if (isinstance(dist, pkg_resources.DistInfoDistribution) and
dist.has_metadata('METADATA')):
return dist.get_metadata('METADATA')
metadata = dist.get_metadata('METADATA')
elif dist.has_metadata('PKG-INFO'):
return dist.get_metadata('PKG-INFO')
metadata = dist.get_metadata('PKG-INFO')
else:
logger.warning("No metadata found in %s", display_path(dist.location))
metadata = ''
feed_parser = FeedParser()
feed_parser.feed(metadata)
return feed_parser.close()
def check_dist_requires_python(dist, absorb=True):
metadata = get_metadata(dist)
feed_parser = FeedParser()
feed_parser.feed(metadata)
pkg_info_dict = feed_parser.close()
pkg_info_dict = get_metadata(dist)
requires_python = pkg_info_dict.get('Requires-Python')
if not absorb:
if absorb:
return requires_python
try:
if not check_requires_python(requires_python):
return requires_python
raise exceptions.UnsupportedPythonVersion(
"%s requires Python '%s' but the running Python is %s" % (
dist.project_name,
requires_python,
'.'.join(map(str, sys.version_info[:3])),)
)
except specifiers.InvalidSpecifier as e:
logger.warning(
"Package %s has an invalid Requires-Python entry %s - %s",
+1 -1
View File
@@ -15,7 +15,7 @@ from pipenv.patched.notpip._vendor.progress.bar import (
from pipenv.patched.notpip._vendor.progress.helpers import HIDE_CURSOR, SHOW_CURSOR, WritelnMixin
from pipenv.patched.notpip._vendor.progress.spinner import Spinner
from pipenv.patched.notpip._internal.compat import WINDOWS
from pipenv.patched.notpip._internal.utils.compat import WINDOWS
from pipenv.patched.notpip._internal.utils.logging import get_indentation
from pipenv.patched.notpip._internal.utils.misc import format_size
from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
+42 -36
View File
@@ -17,7 +17,7 @@ from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import Dict, Optional, Tuple # noqa: F401
from pipenv.patched.notpip._internal.basecommand import Command # noqa: F401
from pipenv.patched.notpip._internal.cli.base_command import Command # noqa: F401
__all__ = ['vcs', 'get_src_requirement']
@@ -200,12 +200,6 @@ class VersionControl(object):
drive, tail = os.path.splitdrive(repo)
return repo.startswith(os.path.sep) or drive
# See issue #1083 for why this method was introduced:
# https://github.com/pypa/pip/issues/1083
def translate_egg_surname(self, surname):
# For example, Django has branches of the form "stable/1.7.x".
return surname.replace('/', '_')
def export(self, location):
"""
Export the repository at the url to the destination location
@@ -213,51 +207,65 @@ class VersionControl(object):
"""
raise NotImplementedError
def get_url_rev(self, url):
def get_netloc_and_auth(self, netloc, scheme):
"""
Returns the correct repository URL and revision by parsing the given
repository URL
Parse the repository URL's netloc, and return the new netloc to use
along with auth information.
Args:
netloc: the original repository URL netloc.
scheme: the repository URL's scheme without the vcs prefix.
This is mainly for the Subversion class to override, so that auth
information can be provided via the --username and --password options
instead of through the URL. For other subclasses like Git without
such an option, auth information must stay in the URL.
Returns: (netloc, (username, password)).
"""
return netloc, (None, None)
def get_url_rev_and_auth(self, url):
"""
Parse the repository URL to use, and return the URL, revision,
and auth info to use.
Returns: (url, rev, (username, password)).
"""
error_message = (
"Sorry, '%s' is a malformed VCS url. "
"The format is <vcs>+<protocol>://<url>, "
"e.g. svn+http://myrepo/svn/MyApp#egg=MyApp"
)
assert '+' in url, error_message % url
url = url.split('+', 1)[1]
scheme, netloc, path, query, frag = urllib_parse.urlsplit(url)
if '+' not in scheme:
raise ValueError(
"Sorry, {!r} is a malformed VCS url. "
"The format is <vcs>+<protocol>://<url>, "
"e.g. svn+http://myrepo/svn/MyApp#egg=MyApp".format(url)
)
# Remove the vcs prefix.
scheme = scheme.split('+', 1)[1]
netloc, user_pass = self.get_netloc_and_auth(netloc, scheme)
rev = None
if '@' in path:
path, rev = path.rsplit('@', 1)
url = urllib_parse.urlunsplit((scheme, netloc, path, query, ''))
return url, rev
return url, rev, user_pass
def get_url_rev_args(self, url):
def make_rev_args(self, username, password):
"""
Return the URL and RevOptions "extra arguments" to use in obtain(),
as a tuple (url, extra_args).
Return the RevOptions "extra arguments" to use in obtain().
"""
return url, []
return []
def get_url_rev_options(self, url):
"""
Return the URL and RevOptions object to use in obtain() and in
some cases export(), as a tuple (url, rev_options).
"""
url, rev = self.get_url_rev(url)
url, extra_args = self.get_url_rev_args(url)
url, rev, user_pass = self.get_url_rev_and_auth(url)
username, password = user_pass
extra_args = self.make_rev_args(username, password)
rev_options = self.make_rev_options(rev, extra_args=extra_args)
return url, rev_options
def get_info(self, location):
"""
Returns (url, revision), where both are strings
"""
assert not location.rstrip('/').endswith(self.dirname), \
'Bad directory: %s' % location
return self.get_url(location), self.get_revision(location)
def normalize_url(self, url):
"""
Normalize a URL for comparison by unquoting it and removing any
@@ -291,7 +299,7 @@ class VersionControl(object):
"""
raise NotImplementedError
def update(self, dest, rev_options):
def update(self, dest, url, rev_options):
"""
Update an already-existing repo to the given ``rev_options``.
@@ -341,7 +349,7 @@ class VersionControl(object):
self.repo_name,
rev_display,
)
self.update(dest, rev_options)
self.update(dest, url, rev_options)
else:
logger.info('Skipping because already up-to-date.')
return
@@ -421,8 +429,6 @@ class VersionControl(object):
def get_url(self, location):
"""
Return the url used at location
This is used in get_info() and obtain().
"""
raise NotImplementedError
@@ -6,7 +6,9 @@ import os
from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse
from pipenv.patched.notpip._internal.download import path_to_url
from pipenv.patched.notpip._internal.utils.misc import display_path, rmtree
from pipenv.patched.notpip._internal.utils.misc import (
display_path, make_vcs_requirement_url, rmtree,
)
from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory
from pipenv.patched.notpip._internal.vcs import VersionControl, vcs
@@ -62,16 +64,16 @@ class Bazaar(VersionControl):
def switch(self, dest, url, rev_options):
self.run_command(['switch', url], cwd=dest)
def update(self, dest, rev_options):
def update(self, dest, url, rev_options):
cmd_args = ['pull', '-q'] + rev_options.to_args()
self.run_command(cmd_args, cwd=dest)
def get_url_rev(self, url):
def get_url_rev_and_auth(self, url):
# hotfix the URL scheme after removing bzr+ from bzr+ssh:// readd it
url, rev = super(Bazaar, self).get_url_rev(url)
url, rev, user_pass = super(Bazaar, self).get_url_rev_and_auth(url)
if url.startswith('ssh://'):
url = 'bzr+' + url
return url, rev
return url, rev, user_pass
def get_url(self, location):
urls = self.run_command(['info'], show_stdout=False, cwd=location)
@@ -98,9 +100,9 @@ class Bazaar(VersionControl):
return None
if not repo.lower().startswith('bzr:'):
repo = 'bzr+' + repo
egg_project_name = dist.egg_name().split('-', 1)[0]
current_rev = self.get_revision(location)
return '%s@%s#egg=%s' % (repo, current_rev, egg_project_name)
egg_project_name = dist.egg_name().split('-', 1)[0]
return make_vcs_requirement_url(repo, current_rev, egg_project_name)
def is_commit_id_equal(self, dest, name):
"""Always assume the versions don't match"""
+77 -40
View File
@@ -8,9 +8,9 @@ from pipenv.patched.notpip._vendor.packaging.version import parse as parse_versi
from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse
from pipenv.patched.notpip._vendor.six.moves.urllib import request as urllib_request
from pipenv.patched.notpip._internal.compat import samefile
from pipenv.patched.notpip._internal.exceptions import BadCommand
from pipenv.patched.notpip._internal.utils.misc import display_path
from pipenv.patched.notpip._internal.utils.compat import samefile
from pipenv.patched.notpip._internal.utils.misc import display_path, make_vcs_requirement_url
from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory
from pipenv.patched.notpip._internal.vcs import VersionControl, vcs
@@ -77,6 +77,20 @@ class Git(VersionControl):
version = '.'.join(version.split('.')[:3])
return parse_version(version)
def get_branch(self, location):
"""
Return the current branch, or None if HEAD isn't at a branch
(e.g. detached HEAD).
"""
args = ['rev-parse', '--abbrev-ref', 'HEAD']
output = self.run_command(args, show_stdout=False, cwd=location)
branch = output.strip()
if branch == 'HEAD':
return None
return branch
def export(self, location):
"""Export the Git repository at the url to the destination location"""
if not location.endswith('/'):
@@ -91,8 +105,8 @@ class Git(VersionControl):
def get_revision_sha(self, dest, rev):
"""
Return a commit hash for the given revision if it names a remote
branch or tag. Otherwise, return None.
Return (sha_or_none, is_branch), where sha_or_none is a commit hash
if the revision names a remote branch or tag, otherwise None.
Args:
dest: the repository directory.
@@ -115,22 +129,30 @@ class Git(VersionControl):
branch_ref = 'refs/remotes/origin/{}'.format(rev)
tag_ref = 'refs/tags/{}'.format(rev)
return refs.get(branch_ref) or refs.get(tag_ref)
sha = refs.get(branch_ref)
if sha is not None:
return (sha, True)
def check_rev_options(self, dest, rev_options):
"""Check the revision options before checkout.
sha = refs.get(tag_ref)
Returns a new RevOptions object for the SHA1 of the branch or tag
if found.
return (sha, False)
def resolve_revision(self, dest, url, rev_options):
"""
Resolve a revision to a new RevOptions object with the SHA1 of the
branch, tag, or ref if found.
Args:
rev_options: a RevOptions object.
"""
rev = rev_options.arg_rev
sha = self.get_revision_sha(dest, rev)
sha, is_branch = self.get_revision_sha(dest, rev)
if sha is not None:
return rev_options.make_new(sha)
rev_options = rev_options.make_new(sha)
rev_options.branch_name = rev if is_branch else None
return rev_options
# Do not show a warning for the common case of something that has
# the form of a Git commit hash.
@@ -139,6 +161,19 @@ class Git(VersionControl):
"Did not find branch or tag '%s', assuming revision or ref.",
rev,
)
if not rev.startswith('refs/'):
return rev_options
# If it looks like a ref, we have to fetch it explicitly.
self.run_command(
['fetch', '-q', url] + rev_options.to_args(),
cwd=dest,
)
# Change the revision to the SHA of the ref we fetched
sha = self.get_revision(dest, rev='FETCH_HEAD')
rev_options = rev_options.make_new(sha)
return rev_options
def is_commit_id_equal(self, dest, name):
@@ -164,20 +199,22 @@ class Git(VersionControl):
if rev_options.rev:
# Then a specific revision was requested.
rev_options = self.check_rev_options(dest, rev_options)
# Only do a checkout if the current commit id doesn't match
# the requested revision.
if not self.is_commit_id_equal(dest, rev_options.rev):
rev = rev_options.rev
# Only fetch the revision if it's a ref
if rev.startswith('refs/'):
self.run_command(
['fetch', '-q', url] + rev_options.to_args(),
cwd=dest,
)
# Change the revision to the SHA of the ref we fetched
rev = 'FETCH_HEAD'
self.run_command(['checkout', '-q', rev], cwd=dest)
rev_options = self.resolve_revision(dest, url, rev_options)
branch_name = getattr(rev_options, 'branch_name', None)
if branch_name is None:
# Only do a checkout if the current commit id doesn't match
# the requested revision.
if not self.is_commit_id_equal(dest, rev_options.rev):
cmd_args = ['checkout', '-q'] + rev_options.to_args()
self.run_command(cmd_args, cwd=dest)
elif self.get_branch(dest) != branch_name:
# Then a specific branch was requested, and that branch
# is not yet checked out.
track_branch = 'origin/{}'.format(branch_name)
cmd_args = [
'checkout', '-b', branch_name, '--track', track_branch,
]
self.run_command(cmd_args, cwd=dest)
#: repo may contain submodules
self.update_submodules(dest)
@@ -189,7 +226,7 @@ class Git(VersionControl):
self.update_submodules(dest)
def update(self, dest, rev_options):
def update(self, dest, url, rev_options):
# First fetch changes from the default remote
if self.get_git_version() >= parse_version('1.9.0'):
# fetch tags in addition to everything else
@@ -197,7 +234,7 @@ class Git(VersionControl):
else:
self.run_command(['fetch', '-q'], cwd=dest)
# Then reset to wanted revision (maybe even origin/master)
rev_options = self.check_rev_options(dest, rev_options)
rev_options = self.resolve_revision(dest, url, rev_options)
cmd_args = ['reset', '--hard', '-q'] + rev_options.to_args()
self.run_command(cmd_args, cwd=dest)
#: update submodules
@@ -218,9 +255,11 @@ class Git(VersionControl):
url = found_remote.split(' ')[1]
return url.strip()
def get_revision(self, location):
def get_revision(self, location, rev=None):
if rev is None:
rev = 'HEAD'
current_rev = self.run_command(
['rev-parse', 'HEAD'], show_stdout=False, cwd=location,
['rev-parse', rev], show_stdout=False, cwd=location,
)
return current_rev.strip()
@@ -255,17 +294,15 @@ class Git(VersionControl):
repo = self.get_url(location)
if not repo.lower().startswith('git:'):
repo = 'git+' + repo
egg_project_name = dist.egg_name().split('-', 1)[0]
if not repo:
return None
current_rev = self.get_revision(location)
req = '%s@%s#egg=%s' % (repo, current_rev, egg_project_name)
subdirectory = self._get_subdirectory(location)
if subdirectory:
req += '&subdirectory=' + subdirectory
egg_project_name = dist.egg_name().split('-', 1)[0]
subdir = self._get_subdirectory(location)
req = make_vcs_requirement_url(repo, current_rev, egg_project_name,
subdir=subdir)
return req
def get_url_rev(self, url):
def get_url_rev_and_auth(self, url):
"""
Prefixes stub URLs like 'user@hostname:user/repo.git' with 'ssh://'.
That's required because although they use SSH they sometimes don't
@@ -275,12 +312,12 @@ class Git(VersionControl):
if '://' not in url:
assert 'file:' not in url
url = url.replace('git+', 'git+ssh://')
url, rev = super(Git, self).get_url_rev(url)
url, rev, user_pass = super(Git, self).get_url_rev_and_auth(url)
url = url.replace('ssh://', '')
else:
url, rev = super(Git, self).get_url_rev(url)
url, rev, user_pass = super(Git, self).get_url_rev_and_auth(url)
return url, rev
return url, rev, user_pass
def update_submodules(self, location):
if not os.path.exists(os.path.join(location, '.gitmodules')):
@@ -6,7 +6,7 @@ import os
from pipenv.patched.notpip._vendor.six.moves import configparser
from pipenv.patched.notpip._internal.download import path_to_url
from pipenv.patched.notpip._internal.utils.misc import display_path
from pipenv.patched.notpip._internal.utils.misc import display_path, make_vcs_requirement_url
from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory
from pipenv.patched.notpip._internal.vcs import VersionControl, vcs
@@ -59,7 +59,7 @@ class Mercurial(VersionControl):
cmd_args = ['update', '-q'] + rev_options.to_args()
self.run_command(cmd_args, cwd=dest)
def update(self, dest, rev_options):
def update(self, dest, url, rev_options):
self.run_command(['pull', '-q'], cwd=dest)
cmd_args = ['update', '-q'] + rev_options.to_args()
self.run_command(cmd_args, cwd=dest)
@@ -88,11 +88,10 @@ class Mercurial(VersionControl):
repo = self.get_url(location)
if not repo.lower().startswith('hg:'):
repo = 'hg+' + repo
egg_project_name = dist.egg_name().split('-', 1)[0]
if not repo:
return None
current_rev_hash = self.get_revision_hash(location)
return '%s@%s#egg=%s' % (repo, current_rev_hash, egg_project_name)
egg_project_name = dist.egg_name().split('-', 1)[0]
return make_vcs_requirement_url(repo, current_rev_hash,
egg_project_name)
def is_commit_id_equal(self, dest, name):
"""Always assume the versions don't match"""
@@ -4,17 +4,15 @@ import logging
import os
import re
from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse
from pipenv.patched.notpip._internal.index import Link
from pipenv.patched.notpip._internal.models.link import Link
from pipenv.patched.notpip._internal.utils.logging import indent_log
from pipenv.patched.notpip._internal.utils.misc import display_path, remove_auth_from_url, rmtree
from pipenv.patched.notpip._internal.utils.misc import (
display_path, make_vcs_requirement_url, rmtree, split_auth_from_netloc,
)
from pipenv.patched.notpip._internal.vcs import VersionControl, vcs
_svn_xml_url_re = re.compile('url="([^"]+)"')
_svn_rev_re = re.compile(r'committed-rev="(\d+)"')
_svn_url_re = re.compile(r'URL: (.+)')
_svn_revision_re = re.compile(r'Revision: (.+)')
_svn_info_xml_rev_re = re.compile(r'\s*revision="(\d+)"')
_svn_info_xml_url_re = re.compile(r'<url>(.*)</url>')
@@ -31,34 +29,6 @@ class Subversion(VersionControl):
def get_base_rev_args(self, rev):
return ['-r', rev]
def get_info(self, location):
"""Returns (url, revision), where both are strings"""
assert not location.rstrip('/').endswith(self.dirname), \
'Bad directory: %s' % location
output = self.run_command(
['info', location],
show_stdout=False,
extra_environ={'LANG': 'C'},
)
match = _svn_url_re.search(output)
if not match:
logger.warning(
'Cannot determine URL of svn checkout %s',
display_path(location),
)
logger.debug('Output that cannot be parsed: \n%s', output)
return None, None
url = match.group(1).strip()
match = _svn_revision_re.search(output)
if not match:
logger.warning(
'Cannot determine revision of svn checkout %s',
display_path(location),
)
logger.debug('Output that cannot be parsed: \n%s', output)
return url, None
return url, match.group(1)
def export(self, location):
"""Export the svn repository at the url to the destination location"""
url, rev_options = self.get_url_rev_options(self.url)
@@ -87,7 +57,7 @@ class Subversion(VersionControl):
cmd_args = ['switch'] + rev_options.to_args() + [url, dest]
self.run_command(cmd_args)
def update(self, dest, rev_options):
def update(self, dest, url, rev_options):
cmd_args = ['update'] + rev_options.to_args() + [dest]
self.run_command(cmd_args)
@@ -132,18 +102,34 @@ class Subversion(VersionControl):
revision = max(revision, localrev)
return revision
def get_url_rev(self, url):
def get_netloc_and_auth(self, netloc, scheme):
"""
This override allows the auth information to be passed to svn via the
--username and --password options instead of via the URL.
"""
if scheme == 'ssh':
# The --username and --password options can't be used for
# svn+ssh URLs, so keep the auth information in the URL.
return super(Subversion, self).get_netloc_and_auth(
netloc, scheme)
return split_auth_from_netloc(netloc)
def get_url_rev_and_auth(self, url):
# hotfix the URL scheme after removing svn+ from svn+ssh:// readd it
url, rev = super(Subversion, self).get_url_rev(url)
url, rev, user_pass = super(Subversion, self).get_url_rev_and_auth(url)
if url.startswith('ssh://'):
url = 'svn+' + url
return url, rev
return url, rev, user_pass
def get_url_rev_args(self, url):
extra_args = get_rev_options_args(url)
url = remove_auth_from_url(url)
def make_rev_args(self, username, password):
extra_args = []
if username:
extra_args += ['--username', username]
if password:
extra_args += ['--password', password]
return url, extra_args
return extra_args
def get_url(self, location):
# In cases where the source is in a subdirectory, not alongside
@@ -213,42 +199,15 @@ class Subversion(VersionControl):
repo = self.get_url(location)
if repo is None:
return None
repo = 'svn+' + repo
rev = self.get_revision(location)
# FIXME: why not project name?
egg_project_name = dist.egg_name().split('-', 1)[0]
rev = self.get_revision(location)
return 'svn+%s@%s#egg=%s' % (repo, rev, egg_project_name)
return make_vcs_requirement_url(repo, rev, egg_project_name)
def is_commit_id_equal(self, dest, name):
"""Always assume the versions don't match"""
return False
def get_rev_options_args(url):
"""
Return the extra arguments to pass to RevOptions.
"""
r = urllib_parse.urlsplit(url)
if hasattr(r, 'username'):
# >= Python-2.5
username, password = r.username, r.password
else:
netloc = r[1]
if '@' in netloc:
auth = netloc.split('@')[0]
if ':' in auth:
username, password = auth.split(':', 1)
else:
username, password = auth, None
else:
username, password = None, None
extra_args = []
if username:
extra_args += ['--username', username]
if password:
extra_args += ['--password', password]
return extra_args
vcs.register(Subversion)
+15 -9
View File
@@ -167,7 +167,8 @@ def message_about_scripts_not_on_PATH(scripts):
]
# If an executable sits with sys.executable, we don't warn for it.
# This covers the case of venv invocations without activating the venv.
not_warn_dirs.append(os.path.normcase(os.path.dirname(sys.executable)))
executable_loc = os.environ.get("PIP_PYTHON_PATH", sys.executable)
not_warn_dirs.append(os.path.normcase(os.path.dirname(executable_loc)))
warn_for = {
parent_dir: scripts for parent_dir, scripts in grouped_by_dir.items()
if os.path.normcase(parent_dir) not in not_warn_dirs
@@ -475,7 +476,7 @@ if __name__ == '__main__':
if warn_script_location:
msg = message_about_scripts_not_on_PATH(generated_console_scripts)
if msg is not None:
logger.warn(msg)
logger.warning(msg)
if len(gui) > 0:
generated.extend(
@@ -500,16 +501,19 @@ if __name__ == '__main__':
with open_for_csv(temp_record, 'w+') as record_out:
reader = csv.reader(record_in)
writer = csv.writer(record_out)
outrows = []
for row in reader:
row[0] = installed.pop(row[0], row[0])
if row[0] in changed:
row[1], row[2] = rehash(row[0])
writer.writerow(row)
outrows.append(tuple(row))
for f in generated:
digest, length = rehash(f)
writer.writerow((normpath(f, lib_dir), digest, length))
outrows.append((normpath(f, lib_dir), digest, length))
for f in installed:
writer.writerow((installed[f], '', ''))
outrows.append((installed[f], '', ''))
for row in sorted(outrows):
writer.writerow(row)
shutil.move(temp_record, record)
@@ -664,8 +668,9 @@ class WheelBuilder(object):
# isolating. Currently, it breaks Python in virtualenvs, because it
# relies on site.py to find parts of the standard library outside the
# virtualenv.
executable_loc = os.environ.get('PIP_PYTHON_PATH', sys.executable)
return [
os.environ.get('PIP_PYTHON_PATH', sys.executable), '-u', '-c',
executable_loc, '-u', '-c',
SETUPTOOLS_SHIM % req.setup_py
] + list(self.global_options)
@@ -710,6 +715,7 @@ class WheelBuilder(object):
:return: True if all the wheels built correctly.
"""
from pipenv.patched.notpip._internal import index
from pipenv.patched.notpip._internal.models.link import Link
building_is_possible = self._wheel_dir or (
autobuilding and self.wheel_cache.cache_dir
@@ -717,6 +723,7 @@ class WheelBuilder(object):
assert building_is_possible
buildset = []
format_control = self.finder.format_control
for req in requirements:
if req.constraint:
continue
@@ -740,8 +747,7 @@ class WheelBuilder(object):
if index.egg_info_matches(base, None, link) is None:
# E.g. local directory. Build wheel just for this run.
ephem_cache = True
if "binary" not in index.fmt_ctl_formats(
self.finder.format_control,
if "binary" not in format_control.get_allowed_formats(
canonicalize_name(req.name)):
logger.info(
"Skipping bdist_wheel for %s, due to binaries "
@@ -802,7 +808,7 @@ class WheelBuilder(object):
self.preparer.build_dir
)
# Update the link for this.
req.link = index.Link(path_to_url(wheel_file))
req.link = Link(path_to_url(wheel_file))
assert req.link.is_wheel
# extract the wheel into the dir
unpack_url(
@@ -1,3 +1,3 @@
from .core import where, old_where
__version__ = "2018.04.16"
__version__ = "2018.08.24"
@@ -1,2 +1,2 @@
from certifi import where
from pipenv.patched.notpip._vendor.certifi import where
print(where())
+63 -163
View File
@@ -3692,169 +3692,6 @@ lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof
TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR
-----END CERTIFICATE-----
# Issuer: CN=Certplus Root CA G1 O=Certplus
# Subject: CN=Certplus Root CA G1 O=Certplus
# Label: "Certplus Root CA G1"
# Serial: 1491911565779898356709731176965615564637713
# MD5 Fingerprint: 7f:09:9c:f7:d9:b9:5c:69:69:56:d5:37:3e:14:0d:42
# SHA1 Fingerprint: 22:fd:d0:b7:fd:a2:4e:0d:ac:49:2c:a0:ac:a6:7b:6a:1f:e3:f7:66
# SHA256 Fingerprint: 15:2a:40:2b:fc:df:2c:d5:48:05:4d:22:75:b3:9c:7f:ca:3e:c0:97:80:78:b0:f0:ea:76:e5:61:a6:c7:43:3e
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgISESBVg+QtPlRWhS2DN7cs3EYRMA0GCSqGSIb3DQEBDQUA
MD4xCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2Vy
dHBsdXMgUm9vdCBDQSBHMTAeFw0xNDA1MjYwMDAwMDBaFw0zODAxMTUwMDAwMDBa
MD4xCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2Vy
dHBsdXMgUm9vdCBDQSBHMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
ANpQh7bauKk+nWT6VjOaVj0W5QOVsjQcmm1iBdTYj+eJZJ+622SLZOZ5KmHNr49a
iZFluVj8tANfkT8tEBXgfs+8/H9DZ6itXjYj2JizTfNDnjl8KvzsiNWI7nC9hRYt
6kuJPKNxQv4c/dMcLRC4hlTqQ7jbxofaqK6AJc96Jh2qkbBIb6613p7Y1/oA/caP
0FG7Yn2ksYyy/yARujVjBYZHYEMzkPZHogNPlk2dT8Hq6pyi/jQu3rfKG3akt62f
6ajUeD94/vI4CTYd0hYCyOwqaK/1jpTvLRN6HkJKHRUxrgwEV/xhc/MxVoYxgKDE
EW4wduOU8F8ExKyHcomYxZ3MVwia9Az8fXoFOvpHgDm2z4QTd28n6v+WZxcIbekN
1iNQMLAVdBM+5S//Ds3EC0pd8NgAM0lm66EYfFkuPSi5YXHLtaW6uOrc4nBvCGrc
h2c0798wct3zyT8j/zXhviEpIDCB5BmlIOklynMxdCm+4kLV87ImZsdo/Rmz5yCT
mehd4F6H50boJZwKKSTUzViGUkAksnsPmBIgJPaQbEfIDbsYIC7Z/fyL8inqh3SV
4EJQeIQEQWGw9CEjjy3LKCHyamz0GqbFFLQ3ZU+V/YDI+HLlJWvEYLF7bY5KinPO
WftwenMGE9nTdDckQQoRb5fc5+R+ob0V8rqHDz1oihYHAgMBAAGjYzBhMA4GA1Ud
DwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSowcCbkahDFXxd
Bie0KlHYlwuBsTAfBgNVHSMEGDAWgBSowcCbkahDFXxdBie0KlHYlwuBsTANBgkq
hkiG9w0BAQ0FAAOCAgEAnFZvAX7RvUz1isbwJh/k4DgYzDLDKTudQSk0YcbX8ACh
66Ryj5QXvBMsdbRX7gp8CXrc1cqh0DQT+Hern+X+2B50ioUHj3/MeXrKls3N/U/7
/SMNkPX0XtPGYX2eEeAC7gkE2Qfdpoq3DIMku4NQkv5gdRE+2J2winq14J2by5BS
S7CTKtQ+FjPlnsZlFT5kOwQ/2wyPX1wdaR+v8+khjPPvl/aatxm2hHSco1S1cE5j
2FddUyGbQJJD+tZ3VTNPZNX70Cxqjm0lpu+F6ALEUz65noe8zDUa3qHpimOHZR4R
Kttjd5cUvpoUmRGywO6wT/gUITJDT5+rosuoD6o7BlXGEilXCNQ314cnrUlZp5Gr
RHpejXDbl85IULFzk/bwg2D5zfHhMf1bfHEhYxQUqq/F3pN+aLHsIqKqkHWetUNy
6mSjhEv9DKgma3GX7lZjZuhCVPnHHd/Qj1vfyDBviP4NxDMcU6ij/UgQ8uQKTuEV
V/xuZDDCVRHc6qnNSlSsKWNEz0pAoNZoWRsz+e86i9sgktxChL8Bq4fA1SCC28a5
g4VCXA9DO2pJNdWY9BW/+mGBDAkgGNLQFwzLSABQ6XaCjGTXOqAHVcweMcDvOrRl
++O/QmueD6i9a5jc2NvLi6Td11n0bt3+qsOR0C5CB8AMTVPNJLFMWx5R9N/pkvo=
-----END CERTIFICATE-----
# Issuer: CN=Certplus Root CA G2 O=Certplus
# Subject: CN=Certplus Root CA G2 O=Certplus
# Label: "Certplus Root CA G2"
# Serial: 1492087096131536844209563509228951875861589
# MD5 Fingerprint: a7:ee:c4:78:2d:1b:ee:2d:b9:29:ce:d6:a7:96:32:31
# SHA1 Fingerprint: 4f:65:8e:1f:e9:06:d8:28:02:e9:54:47:41:c9:54:25:5d:69:cc:1a
# SHA256 Fingerprint: 6c:c0:50:41:e6:44:5e:74:69:6c:4c:fb:c9:f8:0f:54:3b:7e:ab:bb:44:b4:ce:6f:78:7c:6a:99:71:c4:2f:17
-----BEGIN CERTIFICATE-----
MIICHDCCAaKgAwIBAgISESDZkc6uo+jF5//pAq/Pc7xVMAoGCCqGSM49BAMDMD4x
CzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2VydHBs
dXMgUm9vdCBDQSBHMjAeFw0xNDA1MjYwMDAwMDBaFw0zODAxMTUwMDAwMDBaMD4x
CzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2VydHBs
dXMgUm9vdCBDQSBHMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABM0PW1aC3/BFGtat
93nwHcmsltaeTpwftEIRyoa/bfuFo8XlGVzX7qY/aWfYeOKmycTbLXku54uNAm8x
Ik0G42ByRZ0OQneezs/lf4WbGOT8zC5y0xaTTsqZY1yhBSpsBqNjMGEwDgYDVR0P
AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNqDYwJ5jtpMxjwj
FNiPwyCrKGBZMB8GA1UdIwQYMBaAFNqDYwJ5jtpMxjwjFNiPwyCrKGBZMAoGCCqG
SM49BAMDA2gAMGUCMHD+sAvZ94OX7PNVHdTcswYO/jOYnYs5kGuUIe22113WTNch
p+e/IQ8rzfcq3IUHnQIxAIYUFuXcsGXCwI4Un78kFmjlvPl5adytRSv3tjFzzAal
U5ORGpOucGpnutee5WEaXw==
-----END CERTIFICATE-----
# Issuer: CN=OpenTrust Root CA G1 O=OpenTrust
# Subject: CN=OpenTrust Root CA G1 O=OpenTrust
# Label: "OpenTrust Root CA G1"
# Serial: 1492036577811947013770400127034825178844775
# MD5 Fingerprint: 76:00:cc:81:29:cd:55:5e:88:6a:7a:2e:f7:4d:39:da
# SHA1 Fingerprint: 79:91:e8:34:f7:e2:ee:dd:08:95:01:52:e9:55:2d:14:e9:58:d5:7e
# SHA256 Fingerprint: 56:c7:71:28:d9:8c:18:d9:1b:4c:fd:ff:bc:25:ee:91:03:d4:75:8e:a2:ab:ad:82:6a:90:f3:45:7d:46:0e:b4
-----BEGIN CERTIFICATE-----
MIIFbzCCA1egAwIBAgISESCzkFU5fX82bWTCp59rY45nMA0GCSqGSIb3DQEBCwUA
MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9w
ZW5UcnVzdCBSb290IENBIEcxMB4XDTE0MDUyNjA4NDU1MFoXDTM4MDExNTAwMDAw
MFowQDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwU
T3BlblRydXN0IFJvb3QgQ0EgRzEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
AoICAQD4eUbalsUwXopxAy1wpLuwxQjczeY1wICkES3d5oeuXT2R0odsN7faYp6b
wiTXj/HbpqbfRm9RpnHLPhsxZ2L3EVs0J9V5ToybWL0iEA1cJwzdMOWo010hOHQX
/uMftk87ay3bfWAfjH1MBcLrARYVmBSO0ZB3Ij/swjm4eTrwSSTilZHcYTSSjFR0
77F9jAHiOH3BX2pfJLKOYheteSCtqx234LSWSE9mQxAGFiQD4eCcjsZGT44ameGP
uY4zbGneWK2gDqdkVBFpRGZPTBKnjix9xNRbxQA0MMHZmf4yzgeEtE7NCv82TWLx
p2NX5Ntqp66/K7nJ5rInieV+mhxNaMbBGN4zK1FGSxyO9z0M+Yo0FMT7MzUj8czx
Kselu7Cizv5Ta01BG2Yospb6p64KTrk5M0ScdMGTHPjgniQlQ/GbI4Kq3ywgsNw2
TgOzfALU5nsaqocTvz6hdLubDuHAk5/XpGbKuxs74zD0M1mKB3IDVedzagMxbm+W
G+Oin6+Sx+31QrclTDsTBM8clq8cIqPQqwWyTBIjUtz9GVsnnB47ev1CI9sjgBPw
vFEVVJSmdz7QdFG9URQIOTfLHzSpMJ1ShC5VkLG631UAC9hWLbFJSXKAqWLXwPYY
EQRVzXR7z2FwefR7LFxckvzluFqrTJOVoSfupb7PcSNCupt2LQIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUl0YhVyE1
2jZVx/PxN3DlCPaTKbYwHwYDVR0jBBgwFoAUl0YhVyE12jZVx/PxN3DlCPaTKbYw
DQYJKoZIhvcNAQELBQADggIBAB3dAmB84DWn5ph76kTOZ0BP8pNuZtQ5iSas000E
PLuHIT839HEl2ku6q5aCgZG27dmxpGWX4m9kWaSW7mDKHyP7Rbr/jyTwyqkxf3kf
gLMtMrpkZ2CvuVnN35pJ06iCsfmYlIrM4LvgBBuZYLFGZdwIorJGnkSI6pN+VxbS
FXJfLkur1J1juONI5f6ELlgKn0Md/rcYkoZDSw6cMoYsYPXpSOqV7XAp8dUv/TW0
V8/bhUiZucJvbI/NeJWsZCj9VrDDb8O+WVLhX4SPgPL0DTatdrOjteFkdjpY3H1P
XlZs5VVZV6Xf8YpmMIzUUmI4d7S+KNfKNsSbBfD4Fdvb8e80nR14SohWZ25g/4/I
i+GOvUKpMwpZQhISKvqxnUOOBZuZ2mKtVzazHbYNeS2WuOvyDEsMpZTGMKcmGS3t
TAZQMPH9WD25SxdfGbRqhFS0OE85og2WaMMolP3tLR9Ka0OWLpABEPs4poEL0L91
09S5zvE/bw4cHjdx5RiHdRk/ULlepEU0rbDK5uUTdg8xFKmOLZTW1YVNcxVPS/Ky
Pu1svf0OnWZzsD2097+o4BGkxK51CUpjAEggpsadCwmKtODmzj7HPiY46SvepghJ
AwSQiumPv+i2tCqjI40cHLI5kqiPAlxAOXXUc0ECd97N4EOH1uS6SsNsEn/+KuYj
1oxx
-----END CERTIFICATE-----
# Issuer: CN=OpenTrust Root CA G2 O=OpenTrust
# Subject: CN=OpenTrust Root CA G2 O=OpenTrust
# Label: "OpenTrust Root CA G2"
# Serial: 1492012448042702096986875987676935573415441
# MD5 Fingerprint: 57:24:b6:59:24:6b:ae:c8:fe:1c:0c:20:f2:c0:4e:eb
# SHA1 Fingerprint: 79:5f:88:60:c5:ab:7c:3d:92:e6:cb:f4:8d:e1:45:cd:11:ef:60:0b
# SHA256 Fingerprint: 27:99:58:29:fe:6a:75:15:c1:bf:e8:48:f9:c4:76:1d:b1:6c:22:59:29:25:7b:f4:0d:08:94:f2:9e:a8:ba:f2
-----BEGIN CERTIFICATE-----
MIIFbzCCA1egAwIBAgISESChaRu/vbm9UpaPI+hIvyYRMA0GCSqGSIb3DQEBDQUA
MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9w
ZW5UcnVzdCBSb290IENBIEcyMB4XDTE0MDUyNjAwMDAwMFoXDTM4MDExNTAwMDAw
MFowQDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwU
T3BlblRydXN0IFJvb3QgQ0EgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
AoICAQDMtlelM5QQgTJT32F+D3Y5z1zCU3UdSXqWON2ic2rxb95eolq5cSG+Ntmh
/LzubKh8NBpxGuga2F8ORAbtp+Dz0mEL4DKiltE48MLaARf85KxP6O6JHnSrT78e
CbY2albz4e6WiWYkBuTNQjpK3eCasMSCRbP+yatcfD7J6xcvDH1urqWPyKwlCm/6
1UWY0jUJ9gNDlP7ZvyCVeYCYitmJNbtRG6Q3ffyZO6v/v6wNj0OxmXsWEH4db0fE
FY8ElggGQgT4hNYdvJGmQr5J1WqIP7wtUdGejeBSzFfdNTVY27SPJIjki9/ca1TS
gSuyzpJLHB9G+h3Ykst2Z7UJmQnlrBcUVXDGPKBWCgOz3GIZ38i1MH/1PCZ1Eb3X
G7OHngevZXHloM8apwkQHZOJZlvoPGIytbU6bumFAYueQ4xncyhZW+vj3CzMpSZy
YhK05pyDRPZRpOLAeiRXyg6lPzq1O4vldu5w5pLeFlwoW5cZJ5L+epJUzpM5ChaH
vGOz9bGTXOBut9Dq+WIyiET7vycotjCVXRIouZW+j1MY5aIYFuJWpLIsEPUdN6b4
t/bQWVyJ98LVtZR00dX+G7bw5tYee9I8y6jj9RjzIR9u701oBnstXW5DiabA+aC/
gh7PU3+06yzbXfZqfUAkBXKJOAGTy3HCOV0GEfZvePg3DTmEJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUajn6QiL3
5okATV59M4PLuG53hq8wHwYDVR0jBBgwFoAUajn6QiL35okATV59M4PLuG53hq8w
DQYJKoZIhvcNAQENBQADggIBAJjLq0A85TMCl38th6aP1F5Kr7ge57tx+4BkJamz
Gj5oXScmp7oq4fBXgwpkTx4idBvpkF/wrM//T2h6OKQQbA2xx6R3gBi2oihEdqc0
nXGEL8pZ0keImUEiyTCYYW49qKgFbdEfwFFEVn8nNQLdXpgKQuswv42hm1GqO+qT
RmTFAHneIWv2V6CG1wZy7HBGS4tz3aAhdT7cHcCP009zHIXZ/n9iyJVvttN7jLpT
wm+bREx50B1ws9efAvSyB7DH5fitIw6mVskpEndI2S9G/Tvw/HRwkqWOOAgfZDC2
t0v7NqwQjqBSM2OdAzVWxWm9xiNaJ5T2pBL4LTM8oValX9YZ6e18CL13zSdkzJTa
TkZQh+D5wVOAHrut+0dSixv9ovneDiK3PTNZbNTe9ZUGMg1RGUFcPk8G97krgCf2
o6p6fAbhQ8MTOWIaNr3gKC6UAuQpLmBVrkA9sHSSXvAgZJY/X0VdiLWK2gKgW0VU
3jg9CcCoSmVGFvyqv1ROTVu+OEO3KMqLM6oaJbolXCkvW0pujOotnCr2BXbgd5eA
iN1nE28daCSLT7d0geX0YJ96Vdc+N9oWaz53rK4YcJUIeSkDiv7BO7M/Gg+kO14f
WKGVyasvc0rQLW6aWQ9VGHgtPFGml4vmu7JwqkwR3v98KzfUetF3NI/n+UL3PIEM
S1IK
-----END CERTIFICATE-----
# Issuer: CN=OpenTrust Root CA G3 O=OpenTrust
# Subject: CN=OpenTrust Root CA G3 O=OpenTrust
# Label: "OpenTrust Root CA G3"
# Serial: 1492104908271485653071219941864171170455615
# MD5 Fingerprint: 21:37:b4:17:16:92:7b:67:46:70:a9:96:d7:a8:13:24
# SHA1 Fingerprint: 6e:26:64:f3:56:bf:34:55:bf:d1:93:3f:7c:01:de:d8:13:da:8a:a6
# SHA256 Fingerprint: b7:c3:62:31:70:6e:81:07:8c:36:7c:b8:96:19:8f:1e:32:08:dd:92:69:49:dd:8f:57:09:a4:10:f7:5b:62:92
-----BEGIN CERTIFICATE-----
MIICITCCAaagAwIBAgISESDm+Ez8JLC+BUCs2oMbNGA/MAoGCCqGSM49BAMDMEAx
CzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9wZW5U
cnVzdCBSb290IENBIEczMB4XDTE0MDUyNjAwMDAwMFoXDTM4MDExNTAwMDAwMFow
QDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwUT3Bl
blRydXN0IFJvb3QgQ0EgRzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARK7liuTcpm
3gY6oxH84Bjwbhy6LTAMidnW7ptzg6kjFYwvWYpa3RTqnVkrQ7cG7DK2uu5Bta1d
oYXM6h0UZqNnfkbilPPntlahFVmhTzeXuSIevRHr9LIfXsMUmuXZl5mjYzBhMA4G
A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRHd8MUi2I5
DMlv4VBN0BBY3JWIbTAfBgNVHSMEGDAWgBRHd8MUi2I5DMlv4VBN0BBY3JWIbTAK
BggqhkjOPQQDAwNpADBmAjEAj6jcnboMBBf6Fek9LykBl7+BFjNAk2z8+e2AcG+q
j9uEwov1NcoG3GRvaBbhj5G5AjEA2Euly8LQCGzpGPta3U1fJAuwACEl74+nBCZx
4nxp5V2a+EEfOzmTk51V6s2N8fvB
-----END CERTIFICATE-----
# Issuer: CN=ISRG Root X1 O=Internet Security Research Group
# Subject: CN=ISRG Root X1 O=Internet Security Research Group
# Label: "ISRG Root X1"
@@ -4398,3 +4235,66 @@ MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX
ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg
h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg==
-----END CERTIFICATE-----
# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6
# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6
# Label: "GlobalSign Root CA - R6"
# Serial: 1417766617973444989252670301619537
# MD5 Fingerprint: 4f:dd:07:e4:d4:22:64:39:1e:0c:37:42:ea:d1:c6:ae
# SHA1 Fingerprint: 80:94:64:0e:b5:a7:a1:ca:11:9c:1f:dd:d5:9f:81:02:63:a7:fb:d1
# SHA256 Fingerprint: 2c:ab:ea:fe:37:d0:6c:a2:2a:ba:73:91:c0:03:3d:25:98:29:52:c4:53:64:73:49:76:3a:3a:b5:ad:6c:cf:69
-----BEGIN CERTIFICATE-----
MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg
MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh
bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx
MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET
MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ
KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI
xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k
ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD
aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw
LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw
1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX
k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2
SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h
bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n
WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY
rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce
MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD
AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu
bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN
nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt
Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61
55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj
vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf
cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz
oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp
nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs
pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v
JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R
8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4
5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA=
-----END CERTIFICATE-----
# Issuer: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed
# Subject: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed
# Label: "OISTE WISeKey Global Root GC CA"
# Serial: 44084345621038548146064804565436152554
# MD5 Fingerprint: a9:d6:b9:2d:2f:93:64:f8:a5:69:ca:91:e9:68:07:23
# SHA1 Fingerprint: e0:11:84:5e:34:de:be:88:81:b9:9c:f6:16:26:d1:96:1f:c3:b9:31
# SHA256 Fingerprint: 85:60:f9:1c:36:24:da:ba:95:70:b5:fe:a0:db:e3:6f:f1:1a:83:23:be:94:86:85:4f:b3:f3:4a:55:71:19:8d
-----BEGIN CERTIFICATE-----
MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw
CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91
bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg
Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ
BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu
ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS
b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni
eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W
p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T
rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV
57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg
Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9
-----END CERTIFICATE-----
@@ -12,10 +12,10 @@ __title__ = "packaging"
__summary__ = "Core utilities for Python packages"
__uri__ = "https://github.com/pypa/packaging"
__version__ = "17.1"
__version__ = "18.0"
__author__ = "Donald Stufft and individual contributors"
__email__ = "donald@stufft.io"
__license__ = "BSD or Apache License, Version 2.0"
__copyright__ = "Copyright 2014-2016 %s" % __author__
__copyright__ = "Copyright 2014-2018 %s" % __author__
@@ -92,16 +92,16 @@ class Requirement(object):
try:
req = REQUIREMENT.parseString(requirement_string)
except ParseException as e:
raise InvalidRequirement(
"Invalid requirement, parse error at \"{0!r}\"".format(
requirement_string[e.loc:e.loc + 8]))
raise InvalidRequirement("Parse error at \"{0!r}\": {1}".format(
requirement_string[e.loc:e.loc + 8], e.msg
))
self.name = req.name
if req.url:
parsed_url = urlparse.urlparse(req.url)
if not (parsed_url.scheme and parsed_url.netloc) or (
not parsed_url.scheme and not parsed_url.netloc):
raise InvalidRequirement("Invalid URL given")
raise InvalidRequirement("Invalid URL: {0}".format(req.url))
self.url = req.url
else:
self.url = None
@@ -503,7 +503,7 @@ class Specifier(_IndividualSpecifier):
return False
# Ensure that we do not allow a local version of the version mentioned
# in the specifier, which is techincally greater than, to match.
# in the specifier, which is technically greater than, to match.
if prospective.local is not None:
if Version(prospective.base_version) == Version(spec.base_version):
return False
@@ -1,7 +1,6 @@
The MIT License (MIT)
Copyright (c) 2014 Matthias C. M. Troffaes
Copyright (c) 2012-2014 Antoine Pitrou and contributors
Copyright (c) 2017 Thomas Kluyver
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -10,14 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -0,0 +1,4 @@
"""Wrappers to build Python packages using PEP 517 hooks
"""
__version__ = '0.2'
@@ -0,0 +1,182 @@
"""This is invoked in a subprocess to call the build backend hooks.
It expects:
- Command line args: hook_name, control_dir
- Environment variable: PEP517_BUILD_BACKEND=entry.point:spec
- control_dir/input.json:
- {"kwargs": {...}}
Results:
- control_dir/output.json
- {"return_val": ...}
"""
from glob import glob
from importlib import import_module
import os
from os.path import join as pjoin
import re
import shutil
import sys
# This is run as a script, not a module, so it can't do a relative import
import compat
def _build_backend():
"""Find and load the build backend"""
ep = os.environ['PEP517_BUILD_BACKEND']
mod_path, _, obj_path = ep.partition(':')
obj = import_module(mod_path)
if obj_path:
for path_part in obj_path.split('.'):
obj = getattr(obj, path_part)
return obj
def get_requires_for_build_wheel(config_settings):
"""Invoke the optional get_requires_for_build_wheel hook
Returns [] if the hook is not defined.
"""
backend = _build_backend()
try:
hook = backend.get_requires_for_build_wheel
except AttributeError:
return []
else:
return hook(config_settings)
def prepare_metadata_for_build_wheel(metadata_directory, config_settings):
"""Invoke optional prepare_metadata_for_build_wheel
Implements a fallback by building a wheel if the hook isn't defined.
"""
backend = _build_backend()
try:
hook = backend.prepare_metadata_for_build_wheel
except AttributeError:
return _get_wheel_metadata_from_wheel(backend, metadata_directory,
config_settings)
else:
return hook(metadata_directory, config_settings)
WHEEL_BUILT_MARKER = 'PEP517_ALREADY_BUILT_WHEEL'
def _dist_info_files(whl_zip):
"""Identify the .dist-info folder inside a wheel ZipFile."""
res = []
for path in whl_zip.namelist():
m = re.match(r'[^/\\]+-[^/\\]+\.dist-info/', path)
if m:
res.append(path)
if res:
return res
raise Exception("No .dist-info folder found in wheel")
def _get_wheel_metadata_from_wheel(backend, metadata_directory, config_settings):
"""Build a wheel and extract the metadata from it.
Fallback for when the build backend does not define the 'get_wheel_metadata'
hook.
"""
from zipfile import ZipFile
whl_basename = backend.build_wheel(metadata_directory, config_settings)
with open(os.path.join(metadata_directory, WHEEL_BUILT_MARKER), 'wb'):
pass # Touch marker file
whl_file = os.path.join(metadata_directory, whl_basename)
with ZipFile(whl_file) as zipf:
dist_info = _dist_info_files(zipf)
zipf.extractall(path=metadata_directory, members=dist_info)
return dist_info[0].split('/')[0]
def _find_already_built_wheel(metadata_directory):
"""Check for a wheel already built during the get_wheel_metadata hook.
"""
if not metadata_directory:
return None
metadata_parent = os.path.dirname(metadata_directory)
if not os.path.isfile(pjoin(metadata_parent, WHEEL_BUILT_MARKER)):
return None
whl_files = glob(os.path.join(metadata_parent, '*.whl'))
if not whl_files:
print('Found wheel built marker, but no .whl files')
return None
if len(whl_files) > 1:
print('Found multiple .whl files; unspecified behaviour. '
'Will call build_wheel.')
return None
# Exactly one .whl file
return whl_files[0]
def build_wheel(wheel_directory, config_settings, metadata_directory=None):
"""Invoke the mandatory build_wheel hook.
If a wheel was already built in the prepare_metadata_for_build_wheel fallback, this
will copy it rather than rebuilding the wheel.
"""
prebuilt_whl = _find_already_built_wheel(metadata_directory)
if prebuilt_whl:
shutil.copy2(prebuilt_whl, wheel_directory)
return os.path.basename(prebuilt_whl)
return _build_backend().build_wheel(wheel_directory, config_settings,
metadata_directory)
def get_requires_for_build_sdist(config_settings):
"""Invoke the optional get_requires_for_build_wheel hook
Returns [] if the hook is not defined.
"""
backend = _build_backend()
try:
hook = backend.get_requires_for_build_sdist
except AttributeError:
return []
else:
return hook(config_settings)
class _DummyException(Exception):
"""Nothing should ever raise this exception"""
class GotUnsupportedOperation(Exception):
"""For internal use when backend raises UnsupportedOperation"""
def build_sdist(sdist_directory, config_settings):
"""Invoke the mandatory build_sdist hook."""
backend = _build_backend()
try:
return backend.build_sdist(sdist_directory, config_settings)
except getattr(backend, 'UnsupportedOperation', _DummyException):
raise GotUnsupportedOperation
HOOK_NAMES = {
'get_requires_for_build_wheel',
'prepare_metadata_for_build_wheel',
'build_wheel',
'get_requires_for_build_sdist',
'build_sdist',
}
def main():
if len(sys.argv) < 3:
sys.exit("Needs args: hook_name, control_dir")
hook_name = sys.argv[1]
control_dir = sys.argv[2]
if hook_name not in HOOK_NAMES:
sys.exit("Unknown hook: %s" % hook_name)
hook = globals()[hook_name]
hook_input = compat.read_json(pjoin(control_dir, 'input.json'))
json_out = {'unsupported': False, 'return_val': None}
try:
json_out['return_val'] = hook(**hook_input['kwargs'])
except GotUnsupportedOperation:
json_out['unsupported'] = True
compat.write_json(json_out, pjoin(control_dir, 'output.json'), indent=2)
if __name__ == '__main__':
main()
@@ -0,0 +1,194 @@
"""Check a project and backend by attempting to build using PEP 517 hooks.
"""
import argparse
import logging
import os
from os.path import isfile, join as pjoin
from pipenv.patched.notpip._vendor.pytoml import TomlError, load as toml_load
import shutil
from subprocess import CalledProcessError
import sys
import tarfile
from tempfile import mkdtemp
import zipfile
from .colorlog import enable_colourful_output
from .envbuild import BuildEnvironment
from .wrappers import Pep517HookCaller
log = logging.getLogger(__name__)
def check_build_sdist(hooks):
with BuildEnvironment() as env:
try:
env.pip_install(hooks.build_sys_requires)
log.info('Installed static build dependencies')
except CalledProcessError:
log.error('Failed to install static build dependencies')
return False
try:
reqs = hooks.get_requires_for_build_sdist({})
log.info('Got build requires: %s', reqs)
except:
log.error('Failure in get_requires_for_build_sdist', exc_info=True)
return False
try:
env.pip_install(reqs)
log.info('Installed dynamic build dependencies')
except CalledProcessError:
log.error('Failed to install dynamic build dependencies')
return False
td = mkdtemp()
log.info('Trying to build sdist in %s', td)
try:
try:
filename = hooks.build_sdist(td, {})
log.info('build_sdist returned %r', filename)
except:
log.info('Failure in build_sdist', exc_info=True)
return False
if not filename.endswith('.tar.gz'):
log.error("Filename %s doesn't have .tar.gz extension", filename)
return False
path = pjoin(td, filename)
if isfile(path):
log.info("Output file %s exists", path)
else:
log.error("Output file %s does not exist", path)
return False
if tarfile.is_tarfile(path):
log.info("Output file is a tar file")
else:
log.error("Output file is not a tar file")
return False
finally:
shutil.rmtree(td)
return True
def check_build_wheel(hooks):
with BuildEnvironment() as env:
try:
env.pip_install(hooks.build_sys_requires)
log.info('Installed static build dependencies')
except CalledProcessError:
log.error('Failed to install static build dependencies')
return False
try:
reqs = hooks.get_requires_for_build_wheel({})
log.info('Got build requires: %s', reqs)
except:
log.error('Failure in get_requires_for_build_sdist', exc_info=True)
return False
try:
env.pip_install(reqs)
log.info('Installed dynamic build dependencies')
except CalledProcessError:
log.error('Failed to install dynamic build dependencies')
return False
td = mkdtemp()
log.info('Trying to build wheel in %s', td)
try:
try:
filename = hooks.build_wheel(td, {})
log.info('build_wheel returned %r', filename)
except:
log.info('Failure in build_wheel', exc_info=True)
return False
if not filename.endswith('.whl'):
log.error("Filename %s doesn't have .whl extension", filename)
return False
path = pjoin(td, filename)
if isfile(path):
log.info("Output file %s exists", path)
else:
log.error("Output file %s does not exist", path)
return False
if zipfile.is_zipfile(path):
log.info("Output file is a zip file")
else:
log.error("Output file is not a zip file")
return False
finally:
shutil.rmtree(td)
return True
def check(source_dir):
pyproject = pjoin(source_dir, 'pyproject.toml')
if isfile(pyproject):
log.info('Found pyproject.toml')
else:
log.error('Missing pyproject.toml')
return False
try:
with open(pyproject) as f:
pyproject_data = toml_load(f)
# Ensure the mandatory data can be loaded
buildsys = pyproject_data['build-system']
requires = buildsys['requires']
backend = buildsys['build-backend']
log.info('Loaded pyproject.toml')
except (TomlError, KeyError):
log.error("Invalid pyproject.toml", exc_info=True)
return False
hooks = Pep517HookCaller(source_dir, backend)
sdist_ok = check_build_sdist(hooks)
wheel_ok = check_build_wheel(hooks)
if not sdist_ok:
log.warning('Sdist checks failed; scroll up to see')
if not wheel_ok:
log.warning('Wheel checks failed')
return sdist_ok
def main(argv=None):
ap = argparse.ArgumentParser()
ap.add_argument('source_dir',
help="A directory containing pyproject.toml")
args = ap.parse_args(argv)
enable_colourful_output()
ok = check(args.source_dir)
if ok:
print(ansi('Checks passed', 'green'))
else:
print(ansi('Checks failed', 'red'))
sys.exit(1)
ansi_codes = {
'reset': '\x1b[0m',
'bold': '\x1b[1m',
'red': '\x1b[31m',
'green': '\x1b[32m',
}
def ansi(s, attr):
if os.name != 'nt' and sys.stdout.isatty():
return ansi_codes[attr] + str(s) + ansi_codes['reset']
else:
return str(s)
if __name__ == '__main__':
main()
@@ -0,0 +1,110 @@
"""Nicer log formatting with colours.
Code copied from Tornado, Apache licensed.
"""
# Copyright 2012 Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
import sys
try:
import curses
except ImportError:
curses = None
def _stderr_supports_color():
color = False
if curses and hasattr(sys.stderr, 'isatty') and sys.stderr.isatty():
try:
curses.setupterm()
if curses.tigetnum("colors") > 0:
color = True
except Exception:
pass
return color
class LogFormatter(logging.Formatter):
"""Log formatter with colour support
"""
DEFAULT_COLORS = {
logging.INFO: 2, # Green
logging.WARNING: 3, # Yellow
logging.ERROR: 1, # Red
logging.CRITICAL: 1,
}
def __init__(self, color=True, datefmt=None):
r"""
:arg bool color: Enables color support.
:arg string fmt: Log message format.
It will be applied to the attributes dict of log records. The
text between ``%(color)s`` and ``%(end_color)s`` will be colored
depending on the level if color support is on.
:arg dict colors: color mappings from logging level to terminal color
code
:arg string datefmt: Datetime format.
Used for formatting ``(asctime)`` placeholder in ``prefix_fmt``.
.. versionchanged:: 3.2
Added ``fmt`` and ``datefmt`` arguments.
"""
logging.Formatter.__init__(self, datefmt=datefmt)
self._colors = {}
if color and _stderr_supports_color():
# The curses module has some str/bytes confusion in
# python3. Until version 3.2.3, most methods return
# bytes, but only accept strings. In addition, we want to
# output these strings with the logging module, which
# works with unicode strings. The explicit calls to
# unicode() below are harmless in python2 but will do the
# right conversion in python 3.
fg_color = (curses.tigetstr("setaf") or
curses.tigetstr("setf") or "")
if (3, 0) < sys.version_info < (3, 2, 3):
fg_color = str(fg_color, "ascii")
for levelno, code in self.DEFAULT_COLORS.items():
self._colors[levelno] = str(curses.tparm(fg_color, code), "ascii")
self._normal = str(curses.tigetstr("sgr0"), "ascii")
scr = curses.initscr()
self.termwidth = scr.getmaxyx()[1]
curses.endwin()
else:
self._normal = ''
# Default width is usually 80, but too wide is worse than too narrow
self.termwidth = 70
def formatMessage(self, record):
l = len(record.message)
right_text = '{initial}-{name}'.format(initial=record.levelname[0],
name=record.name)
if l + len(right_text) < self.termwidth:
space = ' ' * (self.termwidth - (l + len(right_text)))
else:
space = ' '
if record.levelno in self._colors:
start_color = self._colors[record.levelno]
end_color = self._normal
else:
start_color = end_color = ''
return record.message + space + start_color + right_text + end_color
def enable_colourful_output(level=logging.INFO):
handler = logging.StreamHandler()
handler.setFormatter(LogFormatter())
logging.root.addHandler(handler)
logging.root.setLevel(level)
@@ -0,0 +1,23 @@
"""Handle reading and writing JSON in UTF-8, on Python 3 and 2."""
import json
import sys
if sys.version_info[0] >= 3:
# Python 3
def write_json(obj, path, **kwargs):
with open(path, 'w', encoding='utf-8') as f:
json.dump(obj, f, **kwargs)
def read_json(path):
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
else:
# Python 2
def write_json(obj, path, **kwargs):
with open(path, 'wb') as f:
json.dump(obj, f, encoding='utf-8', **kwargs)
def read_json(path):
with open(path, 'rb') as f:
return json.load(f)
@@ -0,0 +1,150 @@
"""Build wheels/sdists by installing build deps to a temporary environment.
"""
import os
import logging
from pipenv.patched.notpip._vendor import pytoml
import shutil
from subprocess import check_call
import sys
from sysconfig import get_paths
from tempfile import mkdtemp
from .wrappers import Pep517HookCaller
log = logging.getLogger(__name__)
def _load_pyproject(source_dir):
with open(os.path.join(source_dir, 'pyproject.toml')) as f:
pyproject_data = pytoml.load(f)
buildsys = pyproject_data['build-system']
return buildsys['requires'], buildsys['build-backend']
class BuildEnvironment(object):
"""Context manager to install build deps in a simple temporary environment
Based on code I wrote for pip, which is MIT licensed.
"""
# Copyright (c) 2008-2016 The pip developers (see AUTHORS.txt file)
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
path = None
def __init__(self, cleanup=True):
self._cleanup = cleanup
def __enter__(self):
self.path = mkdtemp(prefix='pep517-build-env-')
log.info('Temporary build environment: %s', self.path)
self.save_path = os.environ.get('PATH', None)
self.save_pythonpath = os.environ.get('PYTHONPATH', None)
install_scheme = 'nt' if (os.name == 'nt') else 'posix_prefix'
install_dirs = get_paths(install_scheme, vars={
'base': self.path,
'platbase': self.path,
})
scripts = install_dirs['scripts']
if self.save_path:
os.environ['PATH'] = scripts + os.pathsep + self.save_path
else:
os.environ['PATH'] = scripts + os.pathsep + os.defpath
if install_dirs['purelib'] == install_dirs['platlib']:
lib_dirs = install_dirs['purelib']
else:
lib_dirs = install_dirs['purelib'] + os.pathsep + \
install_dirs['platlib']
if self.save_pythonpath:
os.environ['PYTHONPATH'] = lib_dirs + os.pathsep + \
self.save_pythonpath
else:
os.environ['PYTHONPATH'] = lib_dirs
return self
def pip_install(self, reqs):
"""Install dependencies into this env by calling pip in a subprocess"""
if not reqs:
return
log.info('Calling pip to install %s', reqs)
check_call([sys.executable, '-m', 'pip', 'install', '--ignore-installed',
'--prefix', self.path] + list(reqs))
def __exit__(self, exc_type, exc_val, exc_tb):
if self._cleanup and (self.path is not None) and os.path.isdir(self.path):
shutil.rmtree(self.path)
if self.save_path is None:
os.environ.pop('PATH', None)
else:
os.environ['PATH'] = self.save_path
if self.save_pythonpath is None:
os.environ.pop('PYTHONPATH', None)
else:
os.environ['PYTHONPATH'] = self.save_pythonpath
def build_wheel(source_dir, wheel_dir, config_settings=None):
"""Build a wheel from a source directory using PEP 517 hooks.
:param str source_dir: Source directory containing pyproject.toml
:param str wheel_dir: Target directory to create wheel in
:param dict config_settings: Options to pass to build backend
This is a blocking function which will run pip in a subprocess to install
build requirements.
"""
if config_settings is None:
config_settings = {}
requires, backend = _load_pyproject(source_dir)
hooks = Pep517HookCaller(source_dir, backend)
with BuildEnvironment() as env:
env.pip_install(requires)
reqs = hooks.get_requires_for_build_wheel(config_settings)
env.pip_install(reqs)
return hooks.build_wheel(wheel_dir, config_settings)
def build_sdist(source_dir, sdist_dir, config_settings=None):
"""Build an sdist from a source directory using PEP 517 hooks.
:param str source_dir: Source directory containing pyproject.toml
:param str sdist_dir: Target directory to place sdist in
:param dict config_settings: Options to pass to build backend
This is a blocking function which will run pip in a subprocess to install
build requirements.
"""
if config_settings is None:
config_settings = {}
requires, backend = _load_pyproject(source_dir)
hooks = Pep517HookCaller(source_dir, backend)
with BuildEnvironment() as env:
env.pip_install(requires)
reqs = hooks.get_requires_for_build_sdist(config_settings)
env.pip_install(reqs)
return hooks.build_sdist(sdist_dir, config_settings)
@@ -0,0 +1,134 @@
from contextlib import contextmanager
import os
from os.path import dirname, abspath, join as pjoin
import shutil
from subprocess import check_call
import sys
from tempfile import mkdtemp
from . import compat
_in_proc_script = pjoin(dirname(abspath(__file__)), '_in_process.py')
@contextmanager
def tempdir():
td = mkdtemp()
try:
yield td
finally:
shutil.rmtree(td)
class UnsupportedOperation(Exception):
"""May be raised by build_sdist if the backend indicates that it can't."""
class Pep517HookCaller(object):
"""A wrapper around a source directory to be built with a PEP 517 backend.
source_dir : The path to the source directory, containing pyproject.toml.
backend : The build backend spec, as per PEP 517, from pyproject.toml.
"""
def __init__(self, source_dir, build_backend):
self.source_dir = abspath(source_dir)
self.build_backend = build_backend
def get_requires_for_build_wheel(self, config_settings=None):
"""Identify packages required for building a wheel
Returns a list of dependency specifications, e.g.:
["wheel >= 0.25", "setuptools"]
This does not include requirements specified in pyproject.toml.
It returns the result of calling the equivalently named hook in a
subprocess.
"""
return self._call_hook('get_requires_for_build_wheel', {
'config_settings': config_settings
})
def prepare_metadata_for_build_wheel(self, metadata_directory, config_settings=None):
"""Prepare a *.dist-info folder with metadata for this project.
Returns the name of the newly created folder.
If the build backend defines a hook with this name, it will be called
in a subprocess. If not, the backend will be asked to build a wheel,
and the dist-info extracted from that.
"""
return self._call_hook('prepare_metadata_for_build_wheel', {
'metadata_directory': abspath(metadata_directory),
'config_settings': config_settings,
})
def build_wheel(self, wheel_directory, config_settings=None, metadata_directory=None):
"""Build a wheel from this project.
Returns the name of the newly created file.
In general, this will call the 'build_wheel' hook in the backend.
However, if that was previously called by
'prepare_metadata_for_build_wheel', and the same metadata_directory is
used, the previously built wheel will be copied to wheel_directory.
"""
if metadata_directory is not None:
metadata_directory = abspath(metadata_directory)
return self._call_hook('build_wheel', {
'wheel_directory': abspath(wheel_directory),
'config_settings': config_settings,
'metadata_directory': metadata_directory,
})
def get_requires_for_build_sdist(self, config_settings=None):
"""Identify packages required for building a wheel
Returns a list of dependency specifications, e.g.:
["setuptools >= 26"]
This does not include requirements specified in pyproject.toml.
It returns the result of calling the equivalently named hook in a
subprocess.
"""
return self._call_hook('get_requires_for_build_sdist', {
'config_settings': config_settings
})
def build_sdist(self, sdist_directory, config_settings=None):
"""Build an sdist from this project.
Returns the name of the newly created file.
This calls the 'build_sdist' backend hook in a subprocess.
"""
return self._call_hook('build_sdist', {
'sdist_directory': abspath(sdist_directory),
'config_settings': config_settings,
})
def _call_hook(self, hook_name, kwargs):
env = os.environ.copy()
# On Python 2, pytoml returns Unicode values (which is correct) but the
# environment passed to check_call needs to contain string values. We
# convert here by encoding using ASCII (the backend can only contain
# letters, digits and _, . and : characters, and will be used as a
# Python identifier, so non-ASCII content is wrong on Python 2 in
# any case).
if sys.version_info[0] == 2:
build_backend = self.build_backend.encode('ASCII')
else:
build_backend = self.build_backend
env['PEP517_BUILD_BACKEND'] = build_backend
with tempdir() as td:
compat.write_json({'kwargs': kwargs}, pjoin(td, 'input.json'),
indent=2)
# Run the hook in a subprocess
check_call([sys.executable, _in_proc_script, hook_name, td],
cwd=self.source_dir, env=env)
data = compat.read_json(pjoin(td, 'output.json'))
if data.get('unsupported'):
raise UnsupportedOperation
return data['return_val']
@@ -47,6 +47,11 @@ except ImportError:
# Python 3.2 compatibility
import imp as _imp
try:
FileExistsError
except NameError:
FileExistsError = OSError
from pipenv.patched.notpip._vendor import six
from pipenv.patched.notpip._vendor.six.moves import urllib, map, filter
@@ -78,8 +83,11 @@ __import__('pipenv.patched.notpip._vendor.packaging.requirements')
__import__('pipenv.patched.notpip._vendor.packaging.markers')
if (3, 0) < sys.version_info < (3, 3):
raise RuntimeError("Python 3.3 or later is required")
__metaclass__ = type
if (3, 0) < sys.version_info < (3, 4):
raise RuntimeError("Python 3.4 or later is required")
if six.PY2:
# Those builtin exceptions are only defined in Python 3
@@ -537,7 +545,7 @@ class IResourceProvider(IMetadataProvider):
"""List of resource names in the directory (like ``os.listdir()``)"""
class WorkingSet(object):
class WorkingSet:
"""A collection of active distributions on sys.path (or a similar list)"""
def __init__(self, entries=None):
@@ -637,13 +645,12 @@ class WorkingSet(object):
distributions in the working set, otherwise only ones matching
both `group` and `name` are yielded (in distribution order).
"""
for dist in self:
entries = dist.get_entry_map(group)
if name is None:
for ep in entries.values():
yield ep
elif name in entries:
yield entries[name]
return (
entry
for dist in self
for entry in dist.get_entry_map(group).values()
if name is None or name == entry.name
)
def run_script(self, requires, script_name):
"""Locate distribution for `requires` and run `script_name` script"""
@@ -944,7 +951,7 @@ class _ReqExtras(dict):
return not req.marker or any(extra_evals)
class Environment(object):
class Environment:
"""Searchable snapshot of distributions on a search path"""
def __init__(
@@ -959,7 +966,7 @@ class Environment(object):
`platform` is an optional string specifying the name of the platform
that platform-specific distributions must be compatible with. If
unspecified, it defaults to the current platform. `python` is an
optional string naming the desired version of Python (e.g. ``'3.3'``);
optional string naming the desired version of Python (e.g. ``'3.6'``);
it defaults to the current version.
You may explicitly set `platform` (and/or `python`) to ``None`` if you
@@ -2087,7 +2094,12 @@ def _handle_ns(packageName, path_item):
importer = get_importer(path_item)
if importer is None:
return None
loader = importer.find_module(packageName)
# capture warnings due to #1111
with warnings.catch_warnings():
warnings.simplefilter("ignore")
loader = importer.find_module(packageName)
if loader is None:
return None
module = sys.modules.get(packageName)
@@ -2132,12 +2144,13 @@ def _rebuild_mod_path(orig_path, package_name, module):
parts = path_parts[:-module_parts]
return safe_sys_path_index(_normalize_cached(os.sep.join(parts)))
if not isinstance(orig_path, list):
# Is this behavior useful when module.__path__ is not a list?
return
new_path = sorted(orig_path, key=position_in_sys_path)
new_path = [_normalize_cached(p) for p in new_path]
orig_path.sort(key=position_in_sys_path)
module.__path__[:] = [_normalize_cached(p) for p in orig_path]
if isinstance(module.__path__, list):
module.__path__[:] = new_path
else:
module.__path__ = new_path
def declare_namespace(packageName):
@@ -2148,9 +2161,10 @@ def declare_namespace(packageName):
if packageName in _namespace_packages:
return
path, parent = sys.path, None
if '.' in packageName:
parent = '.'.join(packageName.split('.')[:-1])
path = sys.path
parent, _, _ = packageName.rpartition('.')
if parent:
declare_namespace(parent)
if parent not in _namespace_packages:
__import__(parent)
@@ -2161,7 +2175,7 @@ def declare_namespace(packageName):
# Track what packages are namespaces, so when new path items are added,
# they can be updated
_namespace_packages.setdefault(parent, []).append(packageName)
_namespace_packages.setdefault(parent or None, []).append(packageName)
_namespace_packages.setdefault(packageName, [])
for path_item in path:
@@ -2279,7 +2293,7 @@ EGG_NAME = re.compile(
).match
class EntryPoint(object):
class EntryPoint:
"""Object representing an advertised importable object"""
def __init__(self, name, module_name, attrs=(), extras=(), dist=None):
@@ -2433,7 +2447,7 @@ def _version_from_file(lines):
return safe_version(value.strip()) or None
class Distribution(object):
class Distribution:
"""Wrap an actual or potential sys.path entry w/metadata"""
PKG_INFO = 'PKG-INFO'
@@ -3027,7 +3041,10 @@ def _bypass_ensure_directory(path):
dirname, filename = split(path)
if dirname and filename and not isdir(dirname):
_bypass_ensure_directory(dirname)
mkdir(dirname, 0o755)
try:
mkdir(dirname, 0o755)
except FileExistsError:
pass
def split_sections(s):
@@ -2,6 +2,8 @@ import os
import errno
import sys
from pipenv.patched.notpip._vendor import six
def _makedirs_31(path, exist_ok=False):
try:
@@ -15,8 +17,7 @@ def _makedirs_31(path, exist_ok=False):
# and exists_ok considerations are disentangled.
# See https://github.com/pypa/setuptools/pull/1083#issuecomment-315168663
needs_makedirs = (
sys.version_info < (3, 2, 5) or
(3, 3) <= sys.version_info < (3, 3, 6) or
six.PY2 or
(3, 4) <= sys.version_info < (3, 4, 1)
)
makedirs = _makedirs_31 if needs_makedirs else os.makedirs
+34 -12
View File
@@ -1,6 +1,6 @@
# module pyparsing.py
#
# Copyright (c) 2003-2016 Paul T. McGuire
# Copyright (c) 2003-2018 Paul T. McGuire
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -25,6 +25,7 @@
__doc__ = \
"""
pyparsing module - Classes and methods to define and execute parsing grammars
=============================================================================
The pyparsing module is an alternative approach to creating and executing simple grammars,
vs. the traditional lex/yacc approach, or the use of regular expressions. With pyparsing, you
@@ -58,10 +59,23 @@ The pyparsing module handles some of the problems that are typically vexing when
- extra or missing whitespace (the above program will also handle "Hello,World!", "Hello , World !", etc.)
- quoted strings
- embedded comments
Getting Started -
-----------------
Visit the classes L{ParserElement} and L{ParseResults} to see the base classes that most other pyparsing
classes inherit from. Use the docstrings for examples of how to:
- construct literal match expressions from L{Literal} and L{CaselessLiteral} classes
- construct character word-group expressions using the L{Word} class
- see how to create repetitive expressions using L{ZeroOrMore} and L{OneOrMore} classes
- use L{'+'<And>}, L{'|'<MatchFirst>}, L{'^'<Or>}, and L{'&'<Each>} operators to combine simple expressions into more complex ones
- associate names with your parsed results using L{ParserElement.setResultsName}
- find some helpful expression short-cuts like L{delimitedList} and L{oneOf}
- find more useful common expressions in the L{pyparsing_common} namespace class
"""
__version__ = "2.2.0"
__versionTime__ = "06 Mar 2017 02:06 UTC"
__version__ = "2.2.1"
__versionTime__ = "18 Sep 2018 00:49 UTC"
__author__ = "Paul McGuire <ptmcg@users.sourceforge.net>"
import string
@@ -82,6 +96,15 @@ try:
except ImportError:
from threading import RLock
try:
# Python 3
from collections.abc import Iterable
from collections.abc import MutableMapping
except ImportError:
# Python 2.7
from collections import Iterable
from collections import MutableMapping
try:
from collections import OrderedDict as _OrderedDict
except ImportError:
@@ -940,7 +963,7 @@ class ParseResults(object):
def __dir__(self):
return (dir(type(self)) + list(self.keys()))
collections.MutableMapping.register(ParseResults)
MutableMapping.register(ParseResults)
def col (loc,strg):
"""Returns current column within a string, counting newlines as line separators.
@@ -1025,11 +1048,11 @@ def _trim_arity(func, maxargs=2):
# special handling for Python 3.5.0 - extra deep call stack by 1
offset = -3 if system_version == (3,5,0) else -2
frame_summary = traceback.extract_stack(limit=-offset+limit-1)[offset]
return [(frame_summary.filename, frame_summary.lineno)]
return [frame_summary[:2]]
def extract_tb(tb, limit=0):
frames = traceback.extract_tb(tb, limit=limit)
frame_summary = frames[-1]
return [(frame_summary.filename, frame_summary.lineno)]
return [frame_summary[:2]]
else:
extract_stack = traceback.extract_stack
extract_tb = traceback.extract_tb
@@ -1374,7 +1397,7 @@ class ParserElement(object):
else:
preloc = loc
tokensStart = preloc
if self.mayIndexError or loc >= len(instring):
if self.mayIndexError or preloc >= len(instring):
try:
loc,tokens = self.parseImpl( instring, preloc, doActions )
except IndexError:
@@ -1408,7 +1431,6 @@ class ParserElement(object):
self.resultsName,
asList=self.saveAsList and isinstance(tokens,(ParseResults,list)),
modal=self.modalResults )
if debugging:
#~ print ("Matched",self,"->",retTokens.asList())
if (self.debugActions[1] ):
@@ -3242,7 +3264,7 @@ class ParseExpression(ParserElement):
if isinstance( exprs, basestring ):
self.exprs = [ ParserElement._literalStringClass( exprs ) ]
elif isinstance( exprs, collections.Iterable ):
elif isinstance( exprs, Iterable ):
exprs = list(exprs)
# if sequence of strings provided, wrap with Literal
if all(isinstance(expr, basestring) for expr in exprs):
@@ -4393,7 +4415,7 @@ def traceParseAction(f):
@traceParseAction
def remove_duplicate_chars(tokens):
return ''.join(sorted(set(''.join(tokens)))
return ''.join(sorted(set(''.join(tokens))))
wds = OneOrMore(wd).setParseAction(remove_duplicate_chars)
print(wds.parseString("slkdjs sld sldd sdlf sdljf"))
@@ -4583,7 +4605,7 @@ def oneOf( strs, caseless=False, useRegex=True ):
symbols = []
if isinstance(strs,basestring):
symbols = strs.split()
elif isinstance(strs, collections.Iterable):
elif isinstance(strs, Iterable):
symbols = list(strs)
else:
warnings.warn("Invalid argument to oneOf, expected string or iterable",
@@ -4734,7 +4756,7 @@ stringEnd = StringEnd().setName("stringEnd")
_escapedPunc = Word( _bslash, r"\[]-*.$+^?()~ ", exact=2 ).setParseAction(lambda s,l,t:t[0][1])
_escapedHexChar = Regex(r"\\0?[xX][0-9a-fA-F]+").setParseAction(lambda s,l,t:unichr(int(t[0].lstrip(r'\0x'),16)))
_escapedOctChar = Regex(r"\\0[0-7]+").setParseAction(lambda s,l,t:unichr(int(t[0][1:],8)))
_singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | Word(printables, excludeChars=r'\]', exact=1) | Regex(r"\w", re.UNICODE)
_singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | CharsNotIn(r'\]', exact=1)
_charRange = Group(_singleChar + Suppress("-") + _singleChar)
_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group( OneOrMore( _charRange | _singleChar ) ).setResultsName("body") + "]"
@@ -223,8 +223,8 @@ _float_re = re.compile(r'[+-]?(?:0|[1-9](?:_?\d)*)(?:\.\d(?:_?\d)*)?(?:[eE][+-]?
_datetime_re = re.compile(r'(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d+)?(?:Z|([+-]\d{2}):(\d{2}))')
_basicstr_ml_re = re.compile(r'(?:(?:|"|"")[^"\\\000-\011\013-\037])*')
_litstr_re = re.compile(r"[^'\000-\037]*")
_litstr_ml_re = re.compile(r"(?:(?:|'|'')(?:[^'\000-\011\013-\037]))*")
_litstr_re = re.compile(r"[^'\000\010\012-\037]*")
_litstr_ml_re = re.compile(r"(?:(?:|'|'')(?:[^'\000-\010\013-\037]))*")
def _p_value(s, object_pairs_hook):
pos = s.pos()
@@ -91,7 +91,7 @@ except (AssertionError, ValueError):
RequestsDependencyWarning)
# Attempt to enable urllib3's SNI support, if possible
from pipenv.patched.notpip._internal.compat import WINDOWS
from pipenv.patched.notpip._internal.utils.compat import WINDOWS
if not WINDOWS:
try:
from pipenv.patched.notpip._vendor.urllib3.contrib import pyopenssl
+6 -5
View File
@@ -9,14 +9,15 @@ msgpack-python==0.5.6
lockfile==0.12.2
progress==1.4
ipaddress==1.0.22 # Only needed on 2.6 and 2.7
packaging==17.1
pyparsing==2.2.0
pytoml==0.1.16
packaging==18.0
pep517==0.2
pyparsing==2.2.1
pytoml==0.1.19
retrying==1.3.3
requests==2.19.1
chardet==3.0.4
idna==2.7
urllib3==1.23
certifi==2018.4.16
setuptools==39.2.0
certifi==2018.8.24
setuptools==40.4.3
webencodings==0.5.1
+1 -1
View File
@@ -4,4 +4,4 @@ crayons==0.1.2
pipfile==0.0.2
pip-tools==3.1.0
prettytoml==0.3
pip==18.0
pip==18.1
+47 -41
View File
@@ -1,49 +1,55 @@
# -*- coding=utf-8 -*-
import importlib
from pip_shims import pip_version
import pkg_resources
__all__ = [
"InstallRequirement",
"parse_requirements",
"RequirementSet",
"user_cache_dir",
"FAVORITE_HASH",
"is_file_url",
"url_to_path",
"PackageFinder",
"FormatControl",
"Wheel",
"Command",
"cmdoptions",
"get_installed_distributions",
"PyPI",
"SafeFileCache",
"InstallationError",
"parse_version",
"pip_version",
"install_req_from_editable",
"install_req_from_line",
"user_cache_dir"
]
def do_import(module_path, subimport=None, old_path=None, vendored_name=None):
old_path = old_path or module_path
prefix = vendored_name if vendored_name else "pip"
prefixes = ["{0}._internal".format(prefix), "{0}".format(prefix)]
paths = [module_path, old_path]
search_order = ["{0}.{1}".format(p, pth) for p in prefixes for pth in paths if pth is not None]
package = subimport if subimport else None
for to_import in search_order:
if not subimport:
to_import, _, package = to_import.rpartition(".")
try:
imported = importlib.import_module(to_import)
except ImportError:
continue
else:
return getattr(imported, package)
InstallRequirement = do_import('req.req_install', 'InstallRequirement', vendored_name="notpip")
parse_requirements = do_import('req.req_file', 'parse_requirements', vendored_name="notpip")
RequirementSet = do_import('req.req_set', 'RequirementSet', vendored_name="notpip")
user_cache_dir = do_import('utils.appdirs', 'user_cache_dir', vendored_name="notpip")
FAVORITE_HASH = do_import('utils.hashes', 'FAVORITE_HASH', vendored_name="notpip")
is_file_url = do_import('download', 'is_file_url', vendored_name="notpip")
url_to_path = do_import('download', 'url_to_path', vendored_name="notpip")
PackageFinder = do_import('index', 'PackageFinder', vendored_name="notpip")
FormatControl = do_import('index', 'FormatControl', vendored_name="notpip")
Wheel = do_import('wheel', 'Wheel', vendored_name="notpip")
Command = do_import('cli.base_command', 'Command', old_path='basecommand', vendored_name="notpip")
cmdoptions = do_import('cli.cmdoptions', old_path='cmdoptions', vendored_name="notpip")
get_installed_distributions = do_import('utils.misc', 'get_installed_distributions', old_path='utils', vendored_name="notpip")
PyPI = do_import('models.index', 'PyPI', vendored_name='notpip')
SafeFileCache = do_import('download', 'SafeFileCache', vendored_name='notpip')
InstallationError = do_import('exceptions', 'InstallationError', vendored_name='notpip')
from pipenv.vendor.appdirs import user_cache_dir
from pip_shims.shims import (
InstallRequirement,
parse_requirements,
RequirementSet,
FAVORITE_HASH,
is_file_url,
url_to_path,
PackageFinder,
FormatControl,
Wheel,
Command,
cmdoptions,
get_installed_distributions,
PyPI,
SafeFileCache,
InstallationError,
parse_version,
pip_version,
)
# pip 18.1 has refactored InstallRequirement constructors use by pip-tools.
if pkg_resources.parse_version(pip_version) < pkg_resources.parse_version('18.1'):
if parse_version(pip_version) < parse_version('18.1'):
install_req_from_line = InstallRequirement.from_line
install_req_from_editable = InstallRequirement.from_editable
else:
install_req_from_line = do_import('req.constructors', 'install_req_from_line', vendored_name="notpip")
install_req_from_editable = do_import('req.constructors', 'install_req_from_editable', vendored_name="notpip")
from pip_shims.shims import (
install_req_from_editable, install_req_from_line
)
+3 -2
View File
@@ -639,8 +639,9 @@ class Project(object):
def create_pipfile(self, python=None):
"""Creates the Pipfile, filled with juicy defaults."""
from .patched.notpip._internal import ConfigOptionParser
from .patched.notpip._internal.cmdoptions import make_option_group, index_group
from .vendor.pip_shims.shims import (
ConfigOptionParser, make_option_group, index_group
)
config_parser = ConfigOptionParser(name=self.name)
config_parser.add_option_group(make_option_group(index_group, config_parser))
+11 -2
View File
@@ -97,9 +97,16 @@ def main():
import warnings
from pipenv.vendor.vistir.compat import ResourceWarning
warnings.simplefilter("ignore", category=ResourceWarning)
from pipenv.vendor import colorama
colorama.init()
import io
import six
if six.PY3:
sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='utf8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer,encoding='utf8')
else:
from pipenv._compat import force_encoding
force_encoding()
os.environ["PIP_DISABLE_PIP_VERSION_CHECK"] = str("1")
os.environ["PYTHONIOENCODING"] = str("utf-8")
parser = get_parser()
parsed, remaining = parser.parse_known_args()
# sys.argv = remaining
@@ -110,4 +117,6 @@ def main():
if __name__ == "__main__":
_patch_path()
from pipenv.vendor import colorama
colorama.init()
main()
+17 -17
View File
@@ -211,10 +211,10 @@ def actually_resolve_deps(
pre,
req_dir=None,
):
from .patched.notpip._internal import basecommand
from .patched.notpip._internal.req import parse_requirements
from .patched.notpip._internal.exceptions import DistributionNotFound
from .patched.notpip._vendor.requests.exceptions import HTTPError
from .vendor.pip_shims.shims import (
Command, parse_requirements, DistributionNotFound
)
from .vendor.requests.exceptions import HTTPError
from pipenv.patched.piptools.resolver import Resolver
from pipenv.patched.piptools.repositories.pypi import PyPIRepository
from pipenv.patched.piptools.scripts.compile import get_pip_command
@@ -223,7 +223,7 @@ def actually_resolve_deps(
from .vendor.requirementslib.models.requirements import Requirement
from .vendor.vistir.path import create_tracked_tempdir, create_tracked_tempfile
class PipCommand(basecommand.Command):
class PipCommand(Command):
"""Needed for pip-tools."""
name = "PipCommand"
@@ -338,6 +338,7 @@ def venv_resolve_deps(
from .vendor.pexpect.exceptions import EOF, TIMEOUT
from .vendor import delegator
from . import resolver
from ._compat import decode_output
import json
if not deps:
@@ -380,14 +381,14 @@ def venv_resolve_deps(
break
_out = c.subprocess.before
if _out is not None:
_out = to_native_string("{0}".format(_out))
_out = decode_output("{0}".format(_out))
out += _out
sp.text = to_native_string("Locking... {0}".format(_out[:100]))
if environments.is_verbose():
if _out is not None:
sp._hide_cursor()
sp.write(_out.rstrip())
sp._show_cursor()
sp.text = to_native_string("{0}".format(_out[:100]))
if environments.is_verbose():
if _out is not None:
sp._hide_cursor()
sp.write(_out.rstrip())
sp._show_cursor()
c.block()
if c.return_code != 0:
sp.red.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format(
@@ -423,7 +424,7 @@ def resolve_deps(
"""Given a list of dependencies, return a resolved list of dependencies,
using pip-tools -- and their hashes, using the warehouse API / pip.
"""
from .patched.notpip._vendor.requests.exceptions import ConnectionError
from .vendor.requests.exceptions import ConnectionError
from .vendor.requirementslib.models.requirements import Requirement
index_lookup = {}
@@ -637,9 +638,8 @@ def is_editable(pipfile_entry):
def is_installable_file(path):
"""Determine if a path can potentially be installed"""
from .patched.notpip._internal.utils.misc import is_installable_dir
from .vendor.pip_shims.shims import is_installable_dir, is_archive_file
from .patched.notpip._internal.utils.packaging import specifiers
from .patched.notpip._internal.download import is_archive_file
from ._compat import Path
if hasattr(path, "keys") and any(
@@ -691,7 +691,7 @@ def is_file(package):
def pep440_version(version):
"""Normalize version to PEP 440 standards"""
from .patched.notpip._internal.index import parse_version
from .vendor.pip_shims.shims import parse_version
# Use pip built-in version parser.
return str(parse_version(version))
@@ -1149,7 +1149,7 @@ def translate_markers(pipfile_entry):
"""
if not isinstance(pipfile_entry, Mapping):
raise TypeError("Entry is not a pipfile formatted mapping.")
from notpip._vendor.distlib.markers import DEFAULT_CONTEXT as marker_context
from .vendor.distlib.markers import DEFAULT_CONTEXT as marker_context
from .vendor.packaging.markers import Marker
from .vendor.vistir.misc import dedup
+2 -4
View File
@@ -1,7 +1,5 @@
# See https://pypi.python.org/pypi/backports
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
from . import weakref
from . import enum
from . import shutil_get_terminal_size
from . import functools_lru_cache
+7
View File
@@ -95,6 +95,13 @@ class DotEnv():
for k, v in self.dict().items():
if k in os.environ and not override:
continue
# With Python 2 on Windows, ensuree environment variables are
# system strings to avoid "TypeError: environment can only contain
# strings" in Python's subprocess module.
if sys.version_info.major < 3 and sys.platform == 'win32':
from pipenv.utils import fs_str
k = fs_str(k)
v = fs_str(v)
os.environ[k] = v
return True
-29
View File
@@ -1,29 +0,0 @@
BSD 3-Clause License
Copyright (c) 2018, Brett Cannon
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+1 -1
View File
@@ -216,7 +216,7 @@ def _read_requires_python(metadata):
def _get_dependencies_from_pip(ireq, sources):
"""Retrieves dependencies for the requirement from pip internals.
"""Retrieves dependencies for the requirement from pipenv.patched.notpip internals.
The current strategy is to try the followings in order, returning the
first successful result.
+1 -1
View File
@@ -3,7 +3,7 @@ from __future__ import absolute_import
import sys
__version__ = '0.3.1'
__version__ = '0.3.2'
from . import shims
+4
View File
@@ -43,6 +43,9 @@ class _shims(object):
"pip_shims.utils": utils
}
self.pip_version = getattr(self._modules["pip"], "__version__")
version_types = ["post", "pre", "dev", "rc"]
if any(post in self.pip_version.rsplit(".")[-1] for post in version_types):
self.pip_version, _, _ = self.pip_version.rpartition(".")
self.parsed_pip_version = self._parse(self.pip_version)
self._contextmanagers = ("RequirementTracker",)
self._moves = {
@@ -140,6 +143,7 @@ class _shims(object):
("wheel.WheelCache", "7", "9.0.3")
),
"WheelBuilder": ("wheel.WheelBuilder", "7.0.0", "9999"),
"PyPI": ("models.index.PyPI", "7.0.0", "9999"),
}
def _ensure_methods(self, cls, classname, *methods):
+1 -1
View File
@@ -1,6 +1,6 @@
from __future__ import print_function, absolute_import
__version__ = '1.1.5'
__version__ = '1.1.6'
# Add NullHandler to "pythonfinder" logger, because Python2's default root
# logger has no handler and warnings like this would be reported:
+1
View File
@@ -27,6 +27,7 @@ from ..utils import (
path_is_known_executable,
unnest,
)
from .python import PythonVersion
@attr.s
+2
View File
@@ -14,6 +14,8 @@ from vistir.compat import Path
from ..utils import (
ensure_path,
optional_instance_of,
get_python_version,
filter_pythons,
unnest,
)
from .mixins import BaseFinder, BasePath
+1 -1
View File
@@ -4,7 +4,7 @@ Copyright 2018 Kenneth Reitz
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
+1 -1
View File
@@ -132,7 +132,7 @@ def strip_ssh_from_git_uri(uri):
def add_ssh_scheme_to_git_uri(uri):
"""Cleans VCS uris from pip format"""
"""Cleans VCS uris from pipenv.patched.notpip format"""
if isinstance(uri, six.string_types):
# Add scheme for parsing purposes, this is also what pip does
if uri.startswith("git+") and "://" not in uri:
-9
View File
@@ -1,9 +0,0 @@
__title__ = "shutil_backports"
__version__ = "0.1.0"
__license__ = "MIT"
__author__ = "Christopher Rosell"
__copyright__ = "Copyright 2014 Christopher Rosell"
__all__ = ["get_terminal_size"]
from .get_terminal_size import *

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