mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 22:50:18 +00:00
Update requirementslib for PEP517 builder
Signed-off-by: Dan Ryan <dan@danryan.co>
This commit is contained in:
+1
-1
@@ -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
@@ -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)
|
||||
|
||||
+274
-367
File diff suppressed because it is too large
Load Diff
+283
-150
@@ -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
@@ -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
@@ -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()):
|
||||
|
||||
Reference in New Issue
Block a user