Update requirementslib for PEP517 builder

Signed-off-by: Dan Ryan <dan@danryan.co>
This commit is contained in:
Dan Ryan
2019-02-14 20:38:16 -05:00
parent 19f2ee61e9
commit 01845e5f51
6 changed files with 687 additions and 620 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- coding=utf-8 -*-
from __future__ import absolute_import, print_function
__version__ = '1.4.0'
__version__ = '1.4.1.dev0'
import logging
import warnings
+21 -21
View File
@@ -22,12 +22,12 @@ from .utils import optional_instance_of, get_url_name
from ..environment import MYPY_RUNNING
if MYPY_RUNNING:
from typing import Union, Any, Dict, Iterable, Sequence, Mapping, List, NoReturn
package_type = Dict[str, Dict[str, Union[List[str], str]]]
source_type = Dict[str, Union[str, bool]]
from typing import Union, Any, Dict, Iterable, Sequence, Mapping, List, NoReturn, Text
package_type = Dict[Text, Dict[Text, Union[List[Text], Text]]]
source_type = Dict[Text, Union[Text, bool]]
sources_type = Iterable[source_type]
meta_type = Dict[str, Union[int, Dict[str, str], sources_type]]
lockfile_type = Dict[str, Union[package_type, meta_type]]
meta_type = Dict[Text, Union[int, Dict[Text, Text], sources_type]]
lockfile_type = Dict[Text, Union[package_type, meta_type]]
# Let's start by patching plette to make sure we can validate data without being broken
@@ -45,7 +45,7 @@ def patch_plette():
global VALIDATORS
def validate(cls, data):
# type: (Any, Dict[str, Any]) -> None
# type: (Any, Dict[Text, Any]) -> None
if not cerberus: # Skip validation if Cerberus is not available.
return
schema = cls.__SCHEMA__
@@ -98,7 +98,7 @@ def reorder_source_keys(data):
class PipfileLoader(plette.pipfiles.Pipfile):
@classmethod
def validate(cls, data):
# type: (Dict[str, Any]) -> None
# type: (Dict[Text, Any]) -> None
for key, klass in plette.pipfiles.PIPFILE_SECTIONS.items():
if key not in data or key == "source":
continue
@@ -121,7 +121,7 @@ class PipfileLoader(plette.pipfiles.Pipfile):
@classmethod
def load(cls, f, encoding=None):
# type: (Any, str) -> PipfileLoader
# type: (Any, Text) -> PipfileLoader
content = f.read()
if encoding is not None:
content = content.decode(encoding)
@@ -145,7 +145,7 @@ class PipfileLoader(plette.pipfiles.Pipfile):
return instance
def __getattribute__(self, key):
# type: (str) -> Any
# type: (Text) -> Any
if key == "source":
return self._data[key]
return super(PipfileLoader, self).__getattribute__(key)
@@ -182,8 +182,8 @@ class Pipfile(object):
return self._pipfile
def get_deps(self, dev=False, only=True):
# type: (bool, bool) -> Dict[str, Dict[str, Union[List[str], str]]]
deps = {} # type: Dict[str, Dict[str, Union[List[str], str]]]
# type: (bool, bool) -> Dict[Text, Dict[Text, Union[List[Text], Text]]]
deps = {} # type: Dict[Text, Dict[Text, Union[List[Text], Text]]]
if dev:
deps.update(self.pipfile._data["dev-packages"])
if only:
@@ -191,11 +191,11 @@ class Pipfile(object):
return merge_items([deps, self.pipfile._data["packages"]])
def get(self, k):
# type: (str) -> Any
# type: (Text) -> Any
return self.__getitem__(k)
def __contains__(self, k):
# type: (str) -> bool
# type: (Text) -> bool
check_pipfile = k in self.extended_keys or self.pipfile.__contains__(k)
if check_pipfile:
return True
@@ -247,10 +247,10 @@ class Pipfile(object):
@classmethod
def read_projectfile(cls, path):
# type: (str) -> ProjectFile
# type: (Text) -> ProjectFile
"""Read the specified project file and provide an interface for writing/updating.
:param str path: Path to the target file.
:param Text path: Path to the target file.
:return: A project file with the model and location for interaction
:rtype: :class:`~requirementslib.models.project.ProjectFile`
"""
@@ -263,10 +263,10 @@ class Pipfile(object):
@classmethod
def load_projectfile(cls, path, create=False):
# type: (str, bool) -> ProjectFile
# type: (Text, bool) -> ProjectFile
"""Given a path, load or create the necessary pipfile.
:param str path: Path to the project root or pipfile
:param Text path: Path to the project root or pipfile
:param bool create: Whether to create the pipfile if not found, defaults to True
:raises OSError: Thrown if the project root directory doesn't exist
:raises FileNotFoundError: Thrown if the pipfile doesn't exist and ``create=False``
@@ -288,10 +288,10 @@ class Pipfile(object):
@classmethod
def load(cls, path, create=False):
# type: (str, bool) -> Pipfile
# type: (Text, bool) -> Pipfile
"""Given a path, load or create the necessary pipfile.
:param str path: Path to the project root or pipfile
:param Text path: Path to the project root or pipfile
:param bool create: Whether to create the pipfile if not found, defaults to True
:raises OSError: Thrown if the project root directory doesn't exist
:raises FileNotFoundError: Thrown if the pipfile doesn't exist and ``create=False``
@@ -347,10 +347,10 @@ class Pipfile(object):
@property
def build_requires(self):
# type: () -> List[str]
# type: () -> List[Text]
return self.build_system.get("requires", [])
@property
def build_backend(self):
# type: () -> str
# type: () -> Text
return self.build_system.get("build-backend", None)
File diff suppressed because it is too large Load Diff
+283 -150
View File
@@ -15,11 +15,13 @@ import pep517.envbuild
import pep517.wrappers
import six
from appdirs import user_cache_dir
from distlib.wheel import Wheel
from packaging.markers import Marker
from six.moves import configparser
from six.moves.urllib.parse import unquote, urlparse, urlunparse
from vistir.compat import Iterable, Path
from vistir.contextmanagers import cd, temp_path, replaced_streams
from vistir.contextmanagers import cd, temp_path
from vistir.misc import run
from vistir.path import create_tracked_tempdir, ensure_mkdir_p, mkdir_p, rmtree
@@ -46,9 +48,15 @@ except ImportError:
if MYPY_RUNNING:
from typing import Any, Dict, List, Generator, Optional, Union, Tuple
from pip_shims.shims import InstallRequirement
from pkg_resources import Requirement as PkgResourcesRequirement
from typing import Any, Dict, List, Generator, Optional, Union, Tuple, TypeVar, Text, Set
from pip_shims.shims import InstallRequirement, PackageFinder
from pkg_resources import (
PathMetadata, DistInfoDistribution, Requirement as PkgResourcesRequirement
)
from packaging.requirements import Requirement as PackagingRequirement
TRequirement = TypeVar("TRequirement")
RequirementType = TypeVar('RequirementType', covariant=True, bound=PackagingRequirement)
MarkerType = TypeVar('MarkerType', covariant=True, bound=Marker)
CACHE_DIR = os.environ.get("PIPENV_CACHE_DIR", user_cache_dir("pipenv"))
@@ -59,9 +67,17 @@ _setup_stop_after = None
_setup_distribution = None
class BuildEnv(pep517.envbuild.BuildEnvironment):
def pip_install(self, reqs):
cmd = [sys.executable, '-m', 'pip', 'install', '--ignore-installed', '--prefix',
self.path] + list(reqs)
run(cmd, block=True, combine_stderr=True, return_object=False,
write_to_stdout=False, nospin=True)
@contextlib.contextmanager
def _suppress_distutils_logs():
# type: () -> None
# type: () -> Generator[None, None, None]
"""Hack to hide noise generated by `setup.py develop`.
There isn't a good way to suppress them now, so let's monky-patch.
@@ -81,7 +97,7 @@ def _suppress_distutils_logs():
@ensure_mkdir_p(mode=0o775)
def _get_src_dir(root):
# type: (str) -> str
# type: (Text) -> Text
src = os.environ.get("PIP_SRC")
if src:
return src
@@ -97,7 +113,7 @@ def _get_src_dir(root):
def ensure_reqs(reqs):
# type: (List[Union[str, PkgResourcesRequirement]]) -> List[PkgResourcesRequirement]
# type: (List[Union[Text, PkgResourcesRequirement]]) -> List[PkgResourcesRequirement]
import pkg_resources
if not isinstance(reqs, Iterable):
raise TypeError("Expecting an Iterable, got %r" % reqs)
@@ -107,40 +123,43 @@ def ensure_reqs(reqs):
continue
if isinstance(req, six.string_types):
req = pkg_resources.Requirement.parse("{0}".format(str(req)))
req = strip_extras_markers_from_requirement(req)
# req = strip_extras_markers_from_requirement(req)
new_reqs.append(req)
return new_reqs
def pep517_subprocess_runner(cmd, cwd=None, extra_environ=None):
# type: (List[Text], Optional[Text], Optional[Dict[Text, Text]]) -> None
"""The default method of calling the wrapper subprocess."""
env = os.environ.copy()
if extra_environ:
env.update(extra_environ)
run(cmd, cwd=cwd, env=env, block=True, combine_stderr=True, return_object=False,
write_to_stdout=False, nospin=True)
def _prepare_wheel_building_kwargs(ireq=None, src_root=None, src_dir=None, editable=False):
# type: (Optional[InstallRequirement], Optional[str], Optional[str], bool) -> Dict[str, str]
download_dir = os.path.join(CACHE_DIR, "pkgs") # type: str
# type: (Optional[InstallRequirement], Optional[Text], Optional[Text], bool) -> Dict[Text, Text]
download_dir = os.path.join(CACHE_DIR, "pkgs") # type: Text
mkdir_p(download_dir)
wheel_download_dir = os.path.join(CACHE_DIR, "wheels") # type: str
wheel_download_dir = os.path.join(CACHE_DIR, "wheels") # type: Text
mkdir_p(wheel_download_dir)
if src_dir is None:
if editable and src_root is not None:
src_dir = src_root
elif ireq is None and src_root is not None:
src_dir = _get_src_dir(root=src_root) # type: str
# # elif ireq is not None and ireq.editable is not None and ireq.source_dir is not None:
# # src_dir = ireq.source_dir
src_dir = _get_src_dir(root=src_root) # type: Text
elif ireq is not None and ireq.editable and src_root is not None:
src_dir = _get_src_dir(root=src_root)
else:
src_dir = create_tracked_tempdir(prefix="reqlib-src")
# This logic matches pip's behavior, although I don't fully understand the
# intention. I guess the idea is to build editables in-place, otherwise out
# of the source tree?
# if (ireq is not None and ireq.editable) or editable:
# build_dir = src_dir
# else:
# Let's always resolve in isolation
# src_dir = create_tracked_tempdir(prefix="reqlib-src")
if src_dir is None:
src_dir = create_tracked_tempdir(prefix="reqlib-src")
build_dir = create_tracked_tempdir(prefix="reqlib-build")
return {
@@ -152,7 +171,7 @@ def _prepare_wheel_building_kwargs(ireq=None, src_root=None, src_dir=None, edita
def iter_metadata(path, pkg_name=None, metadata_type="egg-info"):
# type: (str, Optional[str], str) -> Generator
# type: (Text, Optional[Text], Text) -> Generator
if pkg_name is not None:
pkg_variants = get_name_variants(pkg_name)
non_matching_dirs = []
@@ -170,7 +189,7 @@ def iter_metadata(path, pkg_name=None, metadata_type="egg-info"):
def find_egginfo(target, pkg_name=None):
# type: (str, Optional[str]) -> Generator
# type: (Text, Optional[Text]) -> Generator
egg_dirs = (
egg_dir for egg_dir in iter_metadata(target, pkg_name=pkg_name)
if egg_dir is not None
@@ -183,7 +202,7 @@ def find_egginfo(target, pkg_name=None):
def find_distinfo(target, pkg_name=None):
# type: (str, Optional[str]) -> Generator
# type: (Text, Optional[Text]) -> Generator
dist_dirs = (
dist_dir for dist_dir in iter_metadata(target, pkg_name=pkg_name, metadata_type="dist-info")
if dist_dir is not None
@@ -195,10 +214,18 @@ def find_distinfo(target, pkg_name=None):
yield dist_dir
def get_metadata(path, pkg_name=None):
def get_metadata(path, pkg_name=None, metadata_type=None):
# type: (Text, Optional[Text], Optional[Text]) -> Dict[Text, Union[Text, List[RequirementType], Dict[Text, RequirementType]]]
metadata_dirs = []
wheel_allowed = metadata_type == "wheel" or metadata_type is None
egg_allowed = metadata_type == "egg" or metadata_type is None
egg_dir = next(iter(find_egginfo(path, pkg_name=pkg_name)), None)
dist_dir = next(iter(find_distinfo(path, pkg_name=pkg_name)), None)
matched_dir = next(iter(d for d in (dist_dir, egg_dir) if d is not None), None)
if dist_dir and wheel_allowed:
metadata_dirs.append(dist_dir)
if egg_dir and egg_allowed:
metadata_dirs.append(egg_dir)
matched_dir = next(iter(d for d in metadata_dirs if d is not None), None)
metadata_dir = None
base_dir = None
if matched_dir is not None:
@@ -208,72 +235,126 @@ def get_metadata(path, pkg_name=None):
dist = None
distinfo_dist = None
egg_dist = None
if dist_dir is not None:
if wheel_allowed and dist_dir is not None:
distinfo_dist = next(iter(pkg_resources.find_distributions(base_dir)), None)
if egg_dir is not None:
if egg_allowed and egg_dir is not None:
path_metadata = pkg_resources.PathMetadata(base_dir, metadata_dir)
egg_dist = next(
iter(pkg_resources.distributions_from_metadata(path_metadata.egg_info)),
None,
)
dist = next(iter(d for d in (distinfo_dist, egg_dist) if d is not None), None)
if dist:
try:
requires = dist.requires()
except Exception:
requires = []
try:
dep_map = dist._build_dep_map()
except Exception:
dep_map = {}
deps = []
extras = {}
for k in dep_map.keys():
if k is None:
deps.extend(dep_map.get(k))
continue
else:
extra = None
_deps = dep_map.get(k)
if k.startswith(":python_version"):
marker = k.replace(":", "; ")
else:
marker = ""
extra = "{0}".format(k)
_deps = ["{0}{1}".format(str(req), marker) for req in _deps]
_deps = ensure_reqs(_deps)
if extra:
extras[extra] = _deps
else:
deps.extend(_deps)
return {
"name": dist.project_name,
"version": dist.version,
"requires": requires,
"extras": extras
}
if dist is not None:
return get_metadata_from_dist(dist)
return {}
def get_extra_name_from_marker(marker):
# type: (MarkerType) -> Optional[Text]
if not marker:
raise ValueError("Invalid value for marker: {0!r}".format(marker))
if not getattr(marker, "_markers", None):
raise TypeError("Expecting a marker instance, received {0!r}".format(marker))
for elem in marker._markers:
if isinstance(elem, tuple) and elem[0].value == "extra":
return elem[2].value
return None
def get_metadata_from_wheel(wheel_path):
# type: (Text) -> Dict[Any, Any]
if not isinstance(wheel_path, six.string_types):
raise TypeError("Expected string instance, received {0!r}".format(wheel_path))
try:
dist = Wheel(wheel_path)
except Exception:
pass
metadata = dist.metadata
name = metadata.name
version = metadata.version
requires = []
extras_keys = getattr(metadata, "extras", None)
extras = {}
for req in getattr(metadata, "run_requires", []):
parsed_req = init_requirement(req)
parsed_marker = parsed_req.marker
if parsed_marker:
extra = get_extra_name_from_marker(parsed_marker)
if extra is None:
requires.append(parsed_req)
continue
if extra not in extras:
extras[extra] = []
parsed_req = strip_extras_markers_from_requirement(parsed_req)
extras[extra].append(parsed_req)
else:
requires.append(parsed_req)
return {
"name": name,
"version": version,
"requires": requires,
"extras": extras
}
def get_metadata_from_dist(dist):
# type: (Union[PathMetadata, DistInfoDistribution]) -> Dict[Text, Union[Text, List[RequirementType], Dict[Text, RequirementType]]]
try:
requires = dist.requires()
except Exception:
requires = []
try:
dep_map = dist._build_dep_map()
except Exception:
dep_map = {}
deps = []
extras = {}
for k in dep_map.keys():
if k is None:
deps.extend(dep_map.get(k))
continue
else:
extra = None
_deps = dep_map.get(k)
if k.startswith(":python_version"):
marker = k.replace(":", "; ")
else:
marker = ""
extra = "{0}".format(k)
_deps = ["{0}{1}".format(str(req), marker) for req in _deps]
_deps = ensure_reqs(_deps)
if extra:
extras[extra] = _deps
else:
deps.extend(_deps)
return {
"name": dist.project_name,
"version": dist.version,
"requires": requires,
"extras": extras
}
@attr.s(slots=True, frozen=True)
class BaseRequirement(object):
name = attr.ib(type=str, default="", cmp=True)
name = attr.ib(default="", cmp=True) # type: Text
requirement = attr.ib(default=None, cmp=True) # type: Optional[PkgResourcesRequirement]
def __str__(self):
# type: () -> str
# type: () -> Text
return "{0}".format(str(self.requirement))
def as_dict(self):
# type: () -> Dict[str, Optional[PkgResourcesRequirement]]
# type: () -> Dict[Text, Optional[PkgResourcesRequirement]]
return {self.name: self.requirement}
def as_tuple(self):
# type: () -> Tuple[str, Optional[PkgResourcesRequirement]]
# type: () -> Tuple[Text, Optional[PkgResourcesRequirement]]
return (self.name, self.requirement)
@classmethod
def from_string(cls, line):
# type: (str) -> BaseRequirement
# type: (Text) -> BaseRequirement
line = line.strip()
req = init_requirement(line)
return cls.from_req(req)
@@ -294,11 +375,11 @@ class BaseRequirement(object):
@attr.s(slots=True, frozen=True)
class Extra(object):
name = attr.ib(type=str, default=None, cmp=True)
name = attr.ib(default=None, cmp=True) # type: Text
requirements = attr.ib(factory=frozenset, cmp=True, type=frozenset)
def __str__(self):
# type: () -> str
# type: () -> Text
return "{0}: {{{1}}}".format(self.section, ", ".join([r.name for r in self.requirements]))
def add(self, req):
@@ -308,34 +389,36 @@ class Extra(object):
return self
def as_dict(self):
# type: () -> Dict[str, Tuple[PkgResourcesRequirement]]
# type: () -> Dict[Text, Tuple[RequirementType, ...]]
return {self.name: tuple([r.requirement for r in self.requirements])}
@attr.s(slots=True, cmp=True, hash=True)
class SetupInfo(object):
name = attr.ib(type=str, default=None, cmp=True)
base_dir = attr.ib(type=str, default=None, cmp=True, hash=False)
version = attr.ib(type=str, default=None, cmp=True)
name = attr.ib(default=None, cmp=True) # type: Text
base_dir = attr.ib(default=None, cmp=True, hash=False) # type: Text
version = attr.ib(default=None, cmp=True) # type: Text
_requirements = attr.ib(type=frozenset, factory=frozenset, cmp=True, hash=True)
build_requires = attr.ib(type=tuple, default=attr.Factory(tuple), cmp=True)
build_backend = attr.ib(type=str, default="setuptools.build_meta:__legacy__", cmp=True)
build_backend = attr.ib(default="setuptools.build_meta:__legacy__", cmp=True) # type: Text
setup_requires = attr.ib(type=tuple, default=attr.Factory(tuple), cmp=True)
python_requires = attr.ib(type=packaging.specifiers.SpecifierSet, default=None, cmp=True)
_extras_requirements = attr.ib(type=tuple, default=attr.Factory(tuple), cmp=True)
setup_cfg = attr.ib(type=Path, default=None, cmp=True, hash=False)
setup_py = attr.ib(type=Path, default=None, cmp=True, hash=False)
pyproject = attr.ib(type=Path, default=None, cmp=True, hash=False)
ireq = attr.ib(default=None, cmp=True, hash=False)
ireq = attr.ib(default=None, cmp=True, hash=False) # type: Optional[InstallRequirement]
extra_kwargs = attr.ib(default=attr.Factory(dict), type=dict, cmp=False, hash=False)
metadata = attr.ib(default=None, type=tuple)
metadata = attr.ib(default=None) # type: Optional[Tuple[Text]]
@property
def requires(self):
# type: () -> Dict[Text, RequirementType]
return {req.name: req.requirement for req in self._requirements}
@property
def extras(self):
# type: () -> Dict[Text, Optional[Any]]
extras_dict = {}
extras = set(self._extras_requirements)
for section, deps in extras:
@@ -347,6 +430,7 @@ class SetupInfo(object):
@classmethod
def get_setup_cfg(cls, setup_cfg_path):
# type: (Text) -> Dict[Text, Union[Text, None, Set[BaseRequirement], List[Text], Tuple[Text, Tuple[BaseRequirement]]]]
if os.path.exists(setup_cfg_path):
default_opts = {
"metadata": {"name": "", "version": ""},
@@ -365,7 +449,7 @@ class SetupInfo(object):
results["name"] = parser.get("metadata", "name")
if parser.has_option("metadata", "version"):
results["version"] = parser.get("metadata", "version")
install_requires = set()
install_requires = set() # type: Set[BaseRequirement]
if parser.has_option("options", "install_requires"):
install_requires = set([
BaseRequirement.from_string(dep)
@@ -395,7 +479,7 @@ class SetupInfo(object):
@property
def egg_base(self):
base = None # type: Optional[Path]
base = None # type: Optional[Text]
if self.setup_py.exists():
base = self.setup_py.parent
elif self.pyproject.exists():
@@ -405,7 +489,7 @@ class SetupInfo(object):
if base is None:
base = Path(self.base_dir)
if base is None:
base = Path(self.extra_kwargs["build_dir"])
base = Path(self.extra_kwargs["src_dir"])
egg_base = base.joinpath("reqlib-metadata")
if not egg_base.exists():
atexit.register(rmtree, egg_base.as_posix())
@@ -413,6 +497,7 @@ class SetupInfo(object):
return egg_base.as_posix()
def parse_setup_cfg(self):
# type: () -> None
if self.setup_cfg is not None and self.setup_cfg.exists():
parsed = self.get_setup_cfg(self.setup_cfg.as_posix())
if self.name is None:
@@ -423,7 +508,7 @@ class SetupInfo(object):
if self.build_requires:
self.build_requires = tuple(set(self.build_requires) | set(build_requires))
self._requirements = frozenset(
set(self._requirements) | parsed["install_requires"]
set(self._requirements) | set(parsed["install_requires"])
)
if self.python_requires is None:
self.python_requires = parsed.get("python_requires")
@@ -438,6 +523,7 @@ class SetupInfo(object):
self._extras_requirements += ((extra, extras_tuple),)
def run_setup(self):
# type: () -> None
if self.setup_py is not None and self.setup_py.exists():
target_cwd = self.setup_py.parent.as_posix()
with temp_path(), cd(target_cwd), _suppress_distutils_logs():
@@ -508,36 +594,65 @@ class SetupInfo(object):
if not self.version:
self.version = dist.get_version()
def run_pep517(self, build=False):
# type: () -> str
with pep517.envbuild.BuildEnvironment():
hookcaller = pep517.wrappers.Pep517HookCaller(
self.base_dir, self.build_backend
)
build_deps = hookcaller.get_requires_for_build_wheel()
if self.ireq.editable:
build_deps += hookcaller.get_requires_for_build_sdist()
metadata_dirname = hookcaller.prepare_metadata_for_build_wheel(self.egg_base)
@contextlib.contextmanager
def run_pep517(self):
# type: (bool) -> Generator[pep517.wrappers.Pep517HookCaller, None, None]
config = {}
config.setdefault("--global-option", [])
builder = pep517.wrappers.Pep517HookCaller(
self.base_dir, self.build_backend
)
builder._subprocess_runner = pep517_subprocess_runner
with BuildEnv() as env:
env.pip_install(self.build_requires)
try:
reqs = builder.get_requires_for_build_wheel(config_settings=config)
env.pip_install(reqs)
metadata_dirname = builder.prepare_metadata_for_build_wheel(
self.egg_base, config_settings=config
)
except Exception:
reqs = builder.get_requires_for_build_sdist(config_settings=config)
env.pip_install(reqs)
metadata_dir = os.path.join(self.egg_base, metadata_dirname)
yield builder
if build:
return self.build_pep517()
return metadata_dir
def build(self):
# type: () -> Optional[Text]
dist_path = None
with self.run_pep517() as hookcaller:
dist_path = self.build_pep517(hookcaller)
if os.path.exists(os.path.join(self.extra_kwargs["build_dir"], dist_path)):
self.get_metadata_from_wheel(
os.path.join(self.extra_kwargs["build_dir"], dist_path)
)
if not self.metadata or not self.name:
self.get_egg_metadata()
else:
return dist_path
if not self.metadata or not self.name:
hookcaller._subprocess_runner(
["setup.py", "egg_info", "--egg-base", self.egg_base]
)
self.get_egg_metadata()
return dist_path
def build_pep517(self, hookcaller):
# type: (pep517.wrappers.Pep517HookCaller) -> Optional[str]
# type: (pep517.wrappers.Pep517HookCaller) -> Optional[Text]
dist_path = None
try:
dist_path = hookcaller.build_wheel(
self.extra_kwargs["build_dir"],
metadata_directory=self.egg_base
)
return dist_path
except Exception:
dist_path = hookcaller.build_sdist(self.extra_kwargs["build_dir"])
self.get_egg_metadata(metadata_type="egg")
return dist_path
def reload(self):
# type: () -> None
# type: () -> Dict[Text, Any]
"""
Wipe existing distribution info metadata for rebuilding.
"""
@@ -548,38 +663,58 @@ class SetupInfo(object):
self._extras_requirements = ()
self.get_info()
def get_egg_metadata(self):
def get_metadata_from_wheel(self, wheel_path):
# type: (Text) -> Dict[Any, Any]
metadata_dict = get_metadata_from_wheel(wheel_path)
if metadata_dict:
self.populate_metadata(metadata_dict)
def get_egg_metadata(self, metadata_dir=None, metadata_type=None):
# type: (Optional[Text], Optional[Text]) -> None
package_indicators = [self.pyproject, self.setup_py, self.setup_cfg]
# if self.setup_py is not None and self.setup_py.exists():
metadata_dirs = []
if any([fn is not None and fn.exists() for fn in package_indicators]):
metadata = get_metadata(self.egg_base, pkg_name=self.name)
if metadata:
self.metadata = tuple([(k, v) for k, v in metadata.items()])
if self.name is None:
self.name = metadata.get("name", self.name)
if not self.version:
self.version = metadata.get("version", self.version)
self._requirements = frozenset(
set(self._requirements) | set([
metadata_dirs = [self.extra_kwargs["build_dir"], self.egg_base, self.extra_kwargs["src_dir"]]
if metadata_dir is not None:
metadata_dirs = [metadata_dir] + metadata_dirs
metadata = [
get_metadata(d, pkg_name=self.name, metadata_type=metadata_type)
for d in metadata_dirs if os.path.exists(d)
]
metadata = next(iter(d for d in metadata if d is not None), None)
if metadata is not None:
self.populate_metadata(metadata)
def populate_metadata(self, metadata):
# type: (Dict[Any, Any]) -> None
self.metadata = tuple([(k, v) for k, v in metadata.items()])
if self.name is None:
self.name = metadata.get("name", self.name)
if not self.version:
self.version = metadata.get("version", self.version)
self._requirements = frozenset(
set(self._requirements) | set([
BaseRequirement.from_req(req)
for req in metadata.get("requires", [])
])
)
if getattr(self.ireq, "extras", None):
for extra in self.ireq.extras:
extras = metadata.get("extras", {}).get(extra, [])
if extras:
extras_tuple = tuple([
BaseRequirement.from_req(req)
for req in metadata.get("requires", [])
for req in ensure_reqs(extras)
if req is not None
])
)
if getattr(self.ireq, "extras", None):
for extra in self.ireq.extras:
extras = metadata.get("extras", {}).get(extra, [])
if extras:
extras_tuple = tuple([
BaseRequirement.from_req(req)
for req in ensure_reqs(extras)
if req is not None
])
self._extras_requirements += ((extra, extras_tuple),)
self._requirements = frozenset(
set(self._requirements) | set(extras_tuple)
)
self._extras_requirements += ((extra, extras_tuple),)
self._requirements = frozenset(
set(self._requirements) | set(extras_tuple)
)
def run_pyproject(self):
# type: () -> None
if self.pyproject and self.pyproject.exists():
result = get_pyproject(self.pyproject.parent)
if result is not None:
@@ -588,27 +723,25 @@ class SetupInfo(object):
self.build_backend = backend
else:
self.build_backend = "setuptools.build_meta:__legacy__"
if requires:
self.build_requires = tuple(set(requires) | set(self.build_requires))
else:
self.build_requires = ("setuptools", "wheel")
if requires and not self.build_requires:
self.build_requires = tuple(requires)
def get_info(self):
# type: () -> Dict[Text, Any]
if self.setup_cfg and self.setup_cfg.exists():
with cd(self.base_dir):
self.parse_setup_cfg()
if self.pyproject and self.pyproject.exists():
with cd(self.base_dir), replaced_streams():
self.run_pyproject()
self.run_pep517()
self.get_egg_metadata()
with cd(self.base_dir):
self.run_pyproject()
self.build()
if self.setup_py and self.setup_py.exists() and self.metadata is None:
if not self.requires or not self.name:
try:
with cd(self.base_dir):
for metadata_dir in os.listdir(self.egg_base):
shutil.rmtree(metadata_dir, ignore_errors=True)
self.run_setup()
except Exception:
with cd(self.base_dir):
@@ -620,7 +753,7 @@ class SetupInfo(object):
return self.as_dict()
def as_dict(self):
# type: () -> Dict[str, Any]
# type: () -> Dict[Text, Any]
prop_dict = {
"name": self.name,
"version": self.version,
@@ -641,12 +774,14 @@ class SetupInfo(object):
@classmethod
def from_requirement(cls, requirement, finder=None):
# type: (TRequirement, Optional[PackageFinder]) -> Optional[SetupInfo]
ireq = requirement.as_ireq()
subdir = getattr(requirement.req, "subdirectory", None)
return cls.from_ireq(ireq, subdir=subdir, finder=finder)
@classmethod
def from_ireq(cls, ireq, subdir=None, finder=None):
# type: (InstallRequirement, Optional[Text], Optional[PackageFinder]) -> Optional[SetupInfo]
import pip_shims.shims
if not ireq.link:
return
@@ -658,22 +793,19 @@ class SetupInfo(object):
finder = get_finder()
vcs_method, uri = split_vcs_method_from_uri(unquote(ireq.link.url_without_fragment))
parsed = urlparse(uri)
url_path = parsed.path
if "@" in url_path:
url_path, _, _ = url_path.rpartition("@")
parsed = parsed._replace(path=url_path)
uri = urlunparse(parsed)
if "file" in parsed.scheme:
url_path = parsed.path
if "@" in url_path:
url_path, _, _ = url_path.rpartition("@")
parsed = parsed._replace(path=url_path)
uri = urlunparse(parsed)
path = None
if ireq.link.scheme == "file" or uri.startswith("file://"):
if "file:/" in uri and "file:///" not in uri:
uri = uri.replace("file:/", "file:///")
path = pip_shims.shims.url_to_path(uri)
# if pip_shims.shims.is_installable_dir(path) and ireq.editable:
# ireq.source_dir = path
kwargs = _prepare_wheel_building_kwargs(ireq)
ireq.source_dir = kwargs["src_dir"]
# os.environ["PIP_BUILD_DIR"] = kwargs["build_dir"]
ireq.ensure_has_source_dir(kwargs["build_dir"])
if not (
ireq.editable
and pip_shims.shims.is_file_url(ireq.link)
@@ -690,17 +822,18 @@ class SetupInfo(object):
"The file URL points to a directory not installable: {}"
.format(ireq.link)
)
if not ireq.editable:
build_dir = ireq.build_location(kwargs["build_dir"])
ireq._temp_build_dir.path = kwargs["build_dir"]
else:
build_dir = ireq.build_location(kwargs["src_dir"])
ireq._temp_build_dir.path = kwargs["build_dir"]
# if not ireq.editable:
build_dir = ireq.build_location(kwargs["build_dir"])
src_dir = ireq.ensure_has_source_dir(kwargs["src_dir"])
ireq._temp_build_dir.path = kwargs["build_dir"]
# else:
# build_dir = ireq.build_location(kwargs["src_dir"])
# ireq._temp_build_dir.path = kwargs["build_dir"]
ireq.populate_link(finder, False, False)
pip_shims.shims.unpack_url(
ireq.link,
build_dir,
src_dir,
download_dir,
only_download=only_download,
session=finder.session,
@@ -708,17 +841,17 @@ class SetupInfo(object):
progress_bar="off",
)
created = cls.create(
build_dir, subdirectory=subdir, ireq=ireq, kwargs=kwargs
src_dir, subdirectory=subdir, ireq=ireq, kwargs=kwargs
)
return created
@classmethod
def create(cls, base_dir, subdirectory=None, ireq=None, kwargs=None):
# type: (Text, Optional[Text], Optional[InstallRequirement], Optional[Dict[Text, Text]]) -> Optional[SetupInfo]
if not base_dir or base_dir is None:
return
creation_kwargs = {"extra_kwargs": kwargs}
if not isinstance(base_dir, Path):
base_dir = Path(base_dir)
creation_kwargs["base_dir"] = base_dir.as_posix()
+73 -59
View File
@@ -29,14 +29,22 @@ from ..utils import SCHEME_LIST, VCS_LIST, is_star, add_ssh_scheme_to_git_uri
from ..environment import MYPY_RUNNING
if MYPY_RUNNING:
from typing import Union, Optional, List, Set, Any, TypeVar, Tuple, Sequence, Dict
from typing import Union, Optional, List, Set, Any, TypeVar, Tuple, Sequence, Dict, Text
from attr import _ValidatorType
from packaging.requirements import Requirement as PackagingRequirement
from pkg_resources import Requirement as PkgResourcesRequirement
from pkg_resources.extern.packaging.markers import Marker as PkgResourcesMarker
from pkg_resources.extern.packaging.markers import (
Op as PkgResourcesOp, Variable as PkgResourcesVariable,
Value as PkgResourcesValue, Marker as PkgResourcesMarker
)
from pip_shims.shims import Link
from vistir.compat import Path
_T = TypeVar("_T")
TMarker = Union[Marker, PkgResourcesMarker]
TVariable = TypeVar("TVariable", PkgResourcesVariable, Variable)
TValue = TypeVar("TValue", PkgResourcesValue, Value)
TOp = TypeVar("TOp", PkgResourcesOp, Op)
MarkerTuple = Tuple[TVariable, TOp, TValue]
TRequirement = Union[PackagingRequirement, PkgResourcesRequirement]
@@ -59,7 +67,7 @@ DIRECT_URL_RE = re.compile(r"{0}\s?@\s?{1}".format(NAME_WITH_EXTRAS, URL))
def filter_none(k, v):
# type: (str, Any) -> bool
# type: (Text, Any) -> bool
if v:
return True
return False
@@ -71,7 +79,7 @@ def optional_instance_of(cls):
def create_link(link):
# type: (str) -> Link
# type: (Text) -> Link
if not isinstance(link, six.string_types):
raise TypeError("must provide a string to instantiate a new link")
@@ -80,13 +88,13 @@ def create_link(link):
def get_url_name(url):
# type: (str) -> str
# type: (Text) -> Text
"""
Given a url, derive an appropriate name to use in a pipfile.
:param str url: A url to derive a string from
:returns: The name of the corresponding pipfile entry
:rtype: str
:rtype: Text
"""
if not isinstance(url, six.string_types):
raise TypeError("Expected a string, got {0!r}".format(url))
@@ -94,7 +102,7 @@ def get_url_name(url):
def init_requirement(name):
# type: (str) -> TRequirement
# type: (Text) -> TRequirement
if not isinstance(name, six.string_types):
raise TypeError("must supply a name to generate a requirement")
@@ -108,12 +116,11 @@ def init_requirement(name):
def extras_to_string(extras):
# type: (Sequence) -> str
# type: (Sequence) -> Text
"""Turn a list of extras into a string"""
if isinstance(extras, six.string_types):
if extras.startswith("["):
return extras
else:
extras = [extras]
if not extras:
@@ -122,7 +129,7 @@ def extras_to_string(extras):
def parse_extras(extras_str):
# type: (str) -> List
# type: (Text) -> List
"""
Turn a string of extras into a parsed extras list
"""
@@ -133,7 +140,7 @@ def parse_extras(extras_str):
def specs_to_string(specs):
# type: (List[str, Specifier]) -> str
# type: (List[Union[Text, Specifier]]) -> Text
"""
Turn a list of specifier tuples into a string
"""
@@ -150,14 +157,14 @@ def specs_to_string(specs):
def build_vcs_uri(
vcs, # type: Optional[str]
uri, # type: str
name=None, # type: Optional[str]
ref=None, # type: Optional[str]
subdirectory=None, # type: Optional[str]
extras=None # type: Optional[List[str]]
vcs, # type: Optional[Text]
uri, # type: Text
name=None, # type: Optional[Text]
ref=None, # type: Optional[Text]
subdirectory=None, # type: Optional[Text]
extras=None # type: Optional[List[Text]]
):
# type: (...) -> str
# type: (...) -> Text
if extras is None:
extras = []
vcs_start = ""
@@ -170,52 +177,54 @@ def build_vcs_uri(
if name:
uri = "{0}#egg={1}".format(uri, name)
if extras:
extras = extras_to_string(extras)
uri = "{0}{1}".format(uri, extras)
extras_string = extras_to_string(extras)
uri = "{0}{1}".format(uri, extras_string)
if subdirectory:
uri = "{0}&subdirectory={1}".format(uri, subdirectory)
return uri
def convert_direct_url_to_url(direct_url):
# type: (str) -> str
# type: (Text) -> Text
"""
Given a direct url as defined by *PEP 508*, convert to a :class:`~pip_shims.shims.Link`
compatible URL by moving the name and extras into an **egg_fragment**.
:param str direct_url: A pep-508 compliant direct url.
:return: A reformatted URL for use with Link objects and :class:`~pip_shims.shims.InstallRequirement` objects.
:rtype: str
:rtype: Text
"""
direct_match = DIRECT_URL_RE.match(direct_url)
if direct_match is None:
url_match = URL_RE.match(direct_url)
if url_match or is_valid_url(direct_url):
return url_match
return direct_url
match_dict = direct_match.groupdict()
url = [match_dict.get(segment) for segment in ("scheme", "host", "path", "pathsep")]
url = "".join([s for s in url if s is not None])
if not match_dict:
raise ValueError("Failed converting value to normal URL, is it a direct URL? {0!r}".format(direct_url))
url_segments = [match_dict.get(s) for s in ("scheme", "host", "path", "pathsep")]
url = "".join([s for s in url_segments if s is not None])
new_url = build_vcs_uri(
None,
url,
ref=match_dict.get("ref"),
name=match_dict.get("name"),
extras=match_dict.get("extras"),
subdir=match_dict.get("subdirectory")
subdirectory=match_dict.get("subdirectory")
)
return new_url
def convert_url_to_direct_url(url, name=None):
# type: (str, Optional[str]) -> str
# type: (Text, Optional[Text]) -> Text
"""
Given a :class:`~pip_shims.shims.Link` compatible URL, convert to a direct url as
defined by *PEP 508* by extracting the name and extras from the **egg_fragment**.
:param str url: A :class:`~pip_shims.shims.InstallRequirement` compliant URL.
:param Optiona[str] name: A name to use in case the supplied URL doesn't provide one.
:param Text url: A :class:`~pip_shims.shims.InstallRequirement` compliant URL.
:param Optiona[Text] name: A name to use in case the supplied URL doesn't provide one.
:return: A pep-508 compliant direct url.
:rtype: str
:rtype: Text
:raises ValueError: Raised when the URL can't be parsed or a name can't be found.
:raises TypeError: When a non-string input is provided.
@@ -228,15 +237,15 @@ def convert_url_to_direct_url(url, name=None):
if direct_match:
return url
url_match = URL_RE.match(url)
if url_match is None:
if url_match is None or not url_match.groupdict():
raise ValueError("Failed parse a valid URL from {0!r}".format(url))
match_dict = url_match.groupdict()
url = [match_dict.get(segment) for segment in ("scheme", "host", "path", "pathsep")]
url_segments = [match_dict.get(s) for s in ("scheme", "host", "path", "pathsep")]
name = match_dict.get("name", name)
extras = match_dict.get("extras")
new_url = ""
if extras and not name:
url.append(extras)
url_segments.append(extras)
elif extras and name:
new_url = "{0}{1}@ ".format(name, extras)
else:
@@ -248,14 +257,14 @@ def convert_url_to_direct_url(url, name=None):
"No name could be parsed from {0!r}".format(url)
)
if match_dict.get("ref"):
url.append("@{0}".format(match_dict.get("ref")))
url_segments.append("@{0}".format(match_dict.get("ref")))
url = "".join([s for s in url if s is not None])
new_url = "{0}{1}".format(new_url, url)
return new_url
url = "{0}{1}".format(new_url, url)
return url
def get_version(pipfile_entry):
# type: (Union[str, Dict[str, bool, List[str]]]) -> str
# type: (Union[Text, Dict[Text, bool, List[Text]]]) -> Text
if str(pipfile_entry) == "{}" or is_star(pipfile_entry):
return ""
@@ -282,13 +291,16 @@ def strip_extras_markers_from_requirement(req):
"""
if req is None:
raise TypeError("Must pass in a valid requirement, received {0!r}".format(req))
if req.marker is not None:
if getattr(req, "marker", None) is not None:
marker = req.marker # type: TMarker
req.marker._markers = _strip_extras_markers(req.marker._markers)
if not req.marker._markers:
req.marker = None
return req
def _strip_extras_markers(marker):
# type: (TMarker) -> TMarker
# type: (Union[MarkerTuple, List[Union[MarkerTuple, str]]]) -> List[Union[MarkerTuple, str]]
if marker is None or not isinstance(marker, (list, tuple)):
raise TypeError("Expecting a marker type, received {0!r}".format(marker))
markers_to_remove = []
@@ -310,14 +322,14 @@ def _strip_extras_markers(marker):
def get_pyproject(path):
# type: (Union[str, Path]) -> Tuple[List[str], str]
# type: (Union[Text, Path]) -> Tuple[List[Text], Text]
"""
Given a base path, look for the corresponding ``pyproject.toml`` file and return its
build_requires and build_backend.
:param str path: The root path of the project, should be a directory (will be truncated)
:param Text path: The root path of the project, should be a directory (will be truncated)
:return: A 2 tuple of build requirements and the build backend
:rtype: Tuple[List[str], str]
:rtype: Tuple[List[Text], Text]
"""
from vistir.compat import Path
@@ -330,8 +342,10 @@ def get_pyproject(path):
pp_toml = path.joinpath("pyproject.toml")
setup_py = path.joinpath("setup.py")
if not pp_toml.exists():
if setup_py.exists():
if not setup_py.exists():
return None
requires = ["setuptools>=40.6", "wheel"]
backend = "setuptools.build_meta:__legacy__"
else:
pyproject_data = {}
with io.open(pp_toml.as_posix(), encoding="utf-8") as fh:
@@ -339,10 +353,10 @@ def get_pyproject(path):
build_system = pyproject_data.get("build-system", None)
if build_system is None:
if setup_py.exists():
requires = ["setuptools", "wheel"]
backend = "setuptools.build_meta"
requires = ["setuptools>=40.6", "wheel"]
backend = "setuptools.build_meta:__legacy__"
else:
requires = ["setuptools>=38.2.5", "wheel"]
requires = ["setuptools>=40.6", "wheel"]
backend = "setuptools.build_meta"
build_system = {
"requires": requires,
@@ -350,13 +364,13 @@ def get_pyproject(path):
}
pyproject_data["build_system"] = build_system
else:
requires = build_system.get("requires")
backend = build_system.get("build-backend", "setuptools.build_meta")
return (requires, backend)
requires = build_system.get("requires", ["setuptools>=40.6", "wheel"])
backend = build_system.get("build-backend", "setuptools.build_meta:__legacy__")
return (requires, backend)
def split_markers_from_line(line):
# type: (str) -> Tuple[str, Optional[str]]
# type: (Text) -> Tuple[Text, Optional[Text]]
"""Split markers from a dependency"""
if not any(line.startswith(uri_prefix) for uri_prefix in SCHEME_LIST):
marker_sep = ";"
@@ -370,7 +384,7 @@ def split_markers_from_line(line):
def split_vcs_method_from_uri(uri):
# type: (str) -> Tuple[Optional[str], str]
# type: (Text) -> Tuple[Optional[Text], Text]
"""Split a vcs+uri formatted uri into (vcs, uri)"""
vcs_start = "{0}+"
vcs = first([vcs for vcs in VCS_LIST if uri.startswith(vcs_start.format(vcs))])
@@ -380,14 +394,14 @@ def split_vcs_method_from_uri(uri):
def split_ref_from_uri(uri):
# type: (str) -> Tuple[str, Optional[str]]
# type: (Text) -> Tuple[Text, Optional[Text]]
"""
Given a path or URI, check for a ref and split it from the path if it is present,
returning a tuple of the original input and the ref or None.
:param str uri: The path or URI to split
:param Text uri: The path or URI to split
:returns: A 2-tuple of the path or URI and the ref
:rtype: Tuple[str, Optional[str]]
:rtype: Tuple[Text, Optional[Text]]
"""
if not isinstance(uri, six.string_types):
raise TypeError("Expected a string, received {0!r}".format(uri))
@@ -780,12 +794,12 @@ def fix_requires_python_marker(requires_python):
def normalize_name(pkg):
# type: (str) -> str
# type: (Text) -> Text
"""Given a package name, return its normalized, non-canonicalized form.
:param str pkg: The name of a package
:param Text pkg: The name of a package
:return: A normalized package name
:rtype: str
:rtype: Text
"""
assert isinstance(pkg, six.string_types)
@@ -793,12 +807,12 @@ def normalize_name(pkg):
def get_name_variants(pkg):
# type: (str) -> Set[str]
# type: (Text) -> Set[Text]
"""
Given a packager name, get the variants of its name for both the canonicalized
and "safe" forms.
:param str pkg: The package to lookup
:param Text pkg: The package to lookup
:returns: A list of names.
:rtype: Set
"""
+35 -22
View File
@@ -10,17 +10,22 @@ import sys
import tomlkit
import vistir
six.add_move(six.MovedAttribute("Mapping", "collections", "collections.abc"))
six.add_move(six.MovedAttribute("Sequence", "collections", "collections.abc"))
six.add_move(six.MovedAttribute("Set", "collections", "collections.abc"))
six.add_move(six.MovedAttribute("ItemsView", "collections", "collections.abc"))
from six.moves import Mapping, Sequence, Set, ItemsView
six.add_move(six.MovedAttribute("Mapping", "collections", "collections.abc")) # type: ignore # noqa
six.add_move(six.MovedAttribute("Sequence", "collections", "collections.abc")) # type: ignore # noqa
six.add_move(six.MovedAttribute("Set", "collections", "collections.abc")) # type: ignore # noqa
six.add_move(six.MovedAttribute("ItemsView", "collections", "collections.abc")) # type: ignore # noqa
from six.moves import Mapping, Sequence, Set, ItemsView # type: ignore # noqa
from six.moves.urllib.parse import urlparse, urlsplit, urlunparse
import pip_shims.shims
from vistir.compat import Path
from vistir.path import is_valid_url, ensure_mkdir_p, create_tracked_tempdir
from .environment import MYPY_RUNNING
if MYPY_RUNNING:
from typing import Dict, Any, Optional, Union, Tuple, List, Iterable, Generator, Text
VCS_LIST = ("git", "svn", "hg", "bzr")
@@ -69,11 +74,12 @@ VCS_SCHEMES = [
def is_installable_dir(path):
# type: (Text) -> bool
if pip_shims.shims.is_installable_dir(path):
return True
path = Path(path)
pyproject = path.joinpath("pyproject.toml")
if pyproject.exists():
pyproject_path = os.path.join(path, "pyproject.toml")
if os.path.exists(pyproject_path):
pyproject = Path(pyproject_path)
pyproject_toml = tomlkit.loads(pyproject.read_text())
build_system = pyproject_toml.get("build-system", {}).get("build-backend", "")
if build_system:
@@ -82,7 +88,7 @@ def is_installable_dir(path):
def strip_ssh_from_git_uri(uri):
# type: (str) -> str
# type: (Text) -> Text
"""Return git+ssh:// formatted URI to git+git@ format"""
if isinstance(uri, six.string_types):
if "git+ssh://" in uri:
@@ -99,8 +105,8 @@ def strip_ssh_from_git_uri(uri):
def add_ssh_scheme_to_git_uri(uri):
# type: (str) -> str
"""Cleans VCS uris from pipenv.patched.notpip format"""
# type: (Text) -> Text
"""Cleans VCS uris from pip 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:
@@ -114,6 +120,7 @@ def add_ssh_scheme_to_git_uri(uri):
def is_vcs(pipfile_entry):
# type: (Union[Text, Dict[Text, Union[Text, bool, Tuple[Text], List[Text]]]]) -> bool
"""Determine if dictionary entry from Pipfile is for a vcs dependency."""
if isinstance(pipfile_entry, Mapping):
return any(key for key in pipfile_entry.keys() if key in VCS_LIST)
@@ -128,6 +135,7 @@ def is_vcs(pipfile_entry):
def is_editable(pipfile_entry):
# type: (Union[Text, Dict[Text, Union[Text, bool, Tuple[Text], List[Text]]]]) -> bool
if isinstance(pipfile_entry, Mapping):
return pipfile_entry.get("editable", False) is True
if isinstance(pipfile_entry, six.string_types):
@@ -136,6 +144,7 @@ def is_editable(pipfile_entry):
def multi_split(s, split):
# type: (Text, Iterable[Text]) -> List[Text]
"""Splits on multiple given separators."""
for r in split:
s = s.replace(r, "|")
@@ -143,13 +152,14 @@ def multi_split(s, split):
def is_star(val):
# type: (Union[Text, Dict[Text, Union[Text, bool, Tuple[Text], List[Text]]]]) -> bool
return (isinstance(val, six.string_types) and val == "*") or (
isinstance(val, Mapping) and val.get("version", "") == "*"
)
def convert_entry_to_path(path):
# type: (Dict[str, Any]) -> str
# type: (Dict[Text, Union[Text, bool, Tuple[Text], List[Text]]]) -> Text
"""Convert a pipfile entry to a string"""
if not isinstance(path, Mapping):
@@ -167,6 +177,7 @@ def convert_entry_to_path(path):
def is_installable_file(path):
# type: (Union[Text, Dict[Text, Union[Text, bool, Tuple[Text], List[Text]]]]) -> bool
"""Determine if a path can potentially be installed"""
from packaging import specifiers
@@ -187,7 +198,7 @@ def is_installable_file(path):
parsed = urlparse(path)
is_local = (not parsed.scheme or parsed.scheme == "file" or (len(parsed.scheme) == 1 and os.name == "nt"))
if parsed.scheme and parsed.scheme == "file":
path = vistir.path.url_to_path(path)
path = vistir.compat.fs_decode(vistir.path.url_to_path(path))
normalized_path = vistir.path.normalize_path(path)
if is_local and not os.path.exists(normalized_path):
return False
@@ -220,7 +231,7 @@ def get_dist_metadata(dist):
def get_setup_paths(base_path, subdirectory=None):
# type: (str, Optional[str]) -> Dict[str, Optional[str]]
# type: (Text, Optional[Text]) -> Dict[Text, Optional[Text]]
if base_path is None:
raise TypeError("must provide a path to derive setup paths from")
setup_py = os.path.join(base_path, "setup.py")
@@ -245,23 +256,24 @@ def get_setup_paths(base_path, subdirectory=None):
def prepare_pip_source_args(sources, pip_args=None):
# type: (List[Dict[Text, Union[Text, bool]]], Optional[List[Text]]) -> List[Text]
if pip_args is None:
pip_args = []
if sources:
# Add the source to pip9.
pip_args.extend(["-i", sources[0]["url"]])
pip_args.extend(["-i", sources[0]["url"]]) # type: ignore
# Trust the host if it's not verified.
if not sources[0].get("verify_ssl", True):
pip_args.extend(["--trusted-host", urlparse(sources[0]["url"]).hostname])
pip_args.extend(["--trusted-host", urlparse(sources[0]["url"]).hostname]) # type: ignore
# Add additional sources as extra indexes.
if len(sources) > 1:
for source in sources[1:]:
pip_args.extend(["--extra-index-url", source["url"]])
pip_args.extend(["--extra-index-url", source["url"]]) # type: ignore
# Trust the host if it's not verified.
if not source.get("verify_ssl", True):
pip_args.extend(
["--trusted-host", urlparse(source["url"]).hostname]
)
) # type: ignore
return pip_args
@@ -271,10 +283,11 @@ def _ensure_dir(path):
@contextlib.contextmanager
def ensure_setup_py(base_dir):
if not base_dir:
base_dir = create_tracked_tempdir(prefix="requirementslib-setup")
base_dir = Path(base_dir)
def ensure_setup_py(base):
# type: (Text) -> Generator[None, None, None]
if not base:
base = create_tracked_tempdir(prefix="requirementslib-setup")
base_dir = Path(base)
if base_dir.exists() and base_dir.name == "setup.py":
base_dir = base_dir.parent
elif not (base_dir.exists() and base_dir.is_dir()):