mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 22:50:18 +00:00
stop using requirementslib models (#5793)
* Move away from requirementslib models * Revise test since PEP-440 does not support wildcard versions but does support equivalent compatible release specifiers. * simplify and remove dead code * Ensure the os_name marker is AND with the other markers. * Move what we still need from requirementslib into the pipenv utils and stop vendoring it. * Remove requirementslib. * force upgrade of virtualenv for python 3.12 * remove virtualenv-clone * Update vcs specifiers documentation; infer name from specific pip line formats where possible. * Provide helpful text and error for recently removed commands * Set the right log levels and verbosity to show users the errors generated by pip resolver when supplying -v flag * Fix the collection of all matching package hashes for non-pypi indexes. Plus lesson from testing torch which contains local identifiers.
This commit is contained in:
@@ -75,7 +75,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: [3.7, 3.8, 3.9, "3.10", "3.11"]
|
||||
python-version: [3.7, 3.8, 3.9, "3.10", "3.11"] # "3.12-dev" Windows CI hangs indefinitely
|
||||
os: [MacOS, Ubuntu, Windows]
|
||||
|
||||
steps:
|
||||
|
||||
@@ -25,6 +25,7 @@ myst-parser = {extras = ["linkify"], version = "*"}
|
||||
invoke = "==2.0.0"
|
||||
exceptiongroup = "==1.1.0"
|
||||
tomli = "*"
|
||||
pyyaml = "==6.0.1"
|
||||
|
||||
[packages]
|
||||
pytz = "*"
|
||||
|
||||
Generated
+383
-374
File diff suppressed because it is too large
Load Diff
+34
-10
@@ -103,30 +103,54 @@ All sub-dependencies will get added to the `Pipfile.lock` as well. Sub-dependenc
|
||||
|
||||
## VCS Dependencies
|
||||
|
||||
VCS dependencies from git and other version control systems using URLs formatted according to the following rule:
|
||||
VCS dependencies from git and other version control systems using URLs formatted using preferred pip line formats:
|
||||
|
||||
<vcs_type>+<scheme>://<location>/<user_or_organization>/<repository>@<branch_or_tag>#egg=<package_name>
|
||||
<vcs_type>+<scheme>://<location>/<user_or_organization>/<repository>@<branch_or_tag>
|
||||
|
||||
The only optional section is the `@<branch_or_tag>` section. When using git over SSH, you may use the shorthand vcs and scheme alias `git+git@<location>:<user_or_organization>/<repository>@<branch_or_tag>#egg=<package_name>`. Note that this is translated to `git+ssh://git@<location>` when parsed.
|
||||
Extras may be specified using the following format when issuing install command:
|
||||
|
||||
<package_name><possible_extras>@ <vcs_type>+<scheme>://<location>/<user_or_organization>/<repository>@<branch_or_tag>
|
||||
|
||||
Note: that the #egg fragments should only be used for legacy pip lines which are still required in editable requirements.
|
||||
|
||||
$ pipenv install -e git+https://github.com/requests/requests.git@v2.31.0#egg=requests
|
||||
|
||||
Note that it is **strongly recommended** that you install any version-controlled dependencies in editable mode, using `pipenv install -e`, in order to ensure that dependency resolution can be performed with an up-to-date copy of the repository each time it is performed, and that it includes all known dependencies.
|
||||
|
||||
Below is an example usage which installs the git repository located at `https://github.com/requests/requests.git` from tag `v2.20.1` as package name `requests`:
|
||||
|
||||
$ pipenv install -e git+https://github.com/requests/requests.git@v2.20.1#egg=requests
|
||||
Creating a Pipfile for this project...
|
||||
Installing -e git+https://github.com/requests/requests.git@v2.20.1#egg=requests...
|
||||
[...snipped...]
|
||||
Adding -e git+https://github.com/requests/requests.git@v2.20.1#egg=requests to Pipfile's [packages]...
|
||||
[...]
|
||||
Resolving -e git+https://github.com/requests/requests.git@v2.20.1#egg=requests...
|
||||
Added requests to Pipfile's [packages] ...
|
||||
Installation Succeeded
|
||||
Pipfile.lock not found, creating...
|
||||
Locking [packages] dependencies...
|
||||
Building requirements...
|
||||
Resolving dependencies...
|
||||
Success!
|
||||
Locking [dev-packages] dependencies...
|
||||
Updated Pipfile.lock (389441cc656bb774aaa28c7e53a35137aace7499ca01668765d528fa79f8acc8)!
|
||||
Installing dependencies from Pipfile.lock (f8acc8)...
|
||||
To activate this project's virtualenv, run pipenv shell.
|
||||
Alternatively, run a command inside the virtualenv with pipenv run.
|
||||
|
||||
$ cat Pipfile
|
||||
[packages]
|
||||
requests = {git = "https://github.com/requests/requests.git", editable = true, ref = "v2.20.1"}
|
||||
requests = {editable = true, ref = "v2.20.1", git = "git+https://github.com/requests/requests.git"}
|
||||
|
||||
$ cat Pipfile.lock
|
||||
...
|
||||
"requests": {
|
||||
"editable": true,
|
||||
"git": "git+https://github.com/requests/requests.git",
|
||||
"markers": "python_version >= '3.7'",
|
||||
"ref": "6cfbe1aedd56f8c2f9ff8b968efe65b22669795b"
|
||||
},
|
||||
...
|
||||
|
||||
Valid values for `<vcs_type>` include `git`, `bzr`, `svn`, and `hg`. Valid values for `<scheme>` include `http`, `https`, `ssh`, and `file`. In specific cases you also have access to other schemes: `svn` may be combined with `svn` as a scheme, and `bzr` can be combined with `sftp` and `lp`.
|
||||
|
||||
You can read more about pip's implementation of VCS support `here <https://pip.pypa.io/en/stable/reference/pip_install/#vcs-support>`__. For more information about other options available when specifying VCS dependencies, please check the `Pipfile spec <https://github.com/pypa/pipfile>`_.
|
||||
You can read more about pip's implementation of VCS support `here <https://pip.pypa.io/en/stable/reference/pip_install/#vcs-support>`__.
|
||||
|
||||
|
||||
## Specifying Package Categories
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
Drop requirementslib for managing pip lines and InstallRequirements, bring remaining requirementslib functionality into pipenv.
|
||||
Fixes numerous reports about extras installs with vcs and file installs; format pip lines correctly to not generate deprecation warnings.
|
||||
+18
-2
@@ -3,8 +3,18 @@ import os
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
# This has to come before imports of pipenv
|
||||
PIPENV_ROOT = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
|
||||
PIP_ROOT = os.sep.join([PIPENV_ROOT, "patched", "pip"])
|
||||
sys.path.insert(0, PIPENV_ROOT)
|
||||
sys.path.insert(0, PIP_ROOT)
|
||||
|
||||
# Load patched pip instead of system pip
|
||||
os.environ["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
|
||||
|
||||
|
||||
def _ensure_modules():
|
||||
# Can be removed when we drop pydantic
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"typing_extensions",
|
||||
location=os.path.join(
|
||||
@@ -14,6 +24,14 @@ def _ensure_modules():
|
||||
typing_extensions = importlib.util.module_from_spec(spec)
|
||||
sys.modules["typing_extensions"] = typing_extensions
|
||||
spec.loader.exec_module(typing_extensions)
|
||||
# Ensure when pip gets invoked it uses our patched version
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"pip",
|
||||
location=os.path.join(os.path.dirname(__file__), "patched", "pip", "__init__.py"),
|
||||
)
|
||||
pip = importlib.util.module_from_spec(spec)
|
||||
sys.modules["pip"] = pip
|
||||
spec.loader.exec_module(pip)
|
||||
|
||||
|
||||
_ensure_modules()
|
||||
@@ -26,8 +44,6 @@ warnings.filterwarnings("ignore", category=DependencyWarning)
|
||||
warnings.filterwarnings("ignore", category=ResourceWarning)
|
||||
warnings.filterwarnings("ignore", category=UserWarning)
|
||||
|
||||
# Load patched pip instead of system pip
|
||||
os.environ["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
|
||||
|
||||
if os.name == "nt":
|
||||
from pipenv.vendor import colorama
|
||||
|
||||
@@ -216,7 +216,7 @@ def install(state, **kwargs):
|
||||
requirementstxt=state.installstate.requirementstxt,
|
||||
pre=state.installstate.pre,
|
||||
deploy=state.installstate.deploy,
|
||||
index_url=state.index,
|
||||
index=state.index,
|
||||
packages=state.installstate.packages,
|
||||
editable_packages=state.installstate.editables,
|
||||
site_packages=state.site_packages,
|
||||
@@ -610,7 +610,7 @@ def run_open(state, module, *args, **kwargs):
|
||||
[
|
||||
state.project._which("python"),
|
||||
"-c",
|
||||
"import {0}; print({0}.__file__)".format(module),
|
||||
f"import {module}; print({module}.__file__)",
|
||||
]
|
||||
)
|
||||
if c.returncode:
|
||||
@@ -693,11 +693,10 @@ def scripts(state):
|
||||
scripts = state.project.parsed_pipfile.get("scripts", {})
|
||||
first_column_width = max(len(word) for word in ["Command"] + list(scripts))
|
||||
second_column_width = max(len(word) for word in ["Script"] + list(scripts.values()))
|
||||
lines = ["{0:<{width}} Script".format("Command", width=first_column_width)]
|
||||
lines.append("{} {}".format("-" * first_column_width, "-" * second_column_width))
|
||||
lines = [f"{command:<{first_column_width}} Script" for command in ["Command"]]
|
||||
lines.append(f"{'-' * first_column_width} {'-' * second_column_width}")
|
||||
lines.extend(
|
||||
"{0:<{width}} {1}".format(name, script, width=first_column_width)
|
||||
for name, script in scripts.items()
|
||||
f"{name:<{first_column_width}} {script}" for name, script in scripts.items()
|
||||
)
|
||||
console.print("\n".join(line for line in lines))
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import os
|
||||
import re
|
||||
|
||||
from pipenv.project import Project
|
||||
from pipenv.utils import err
|
||||
from pipenv.utils.internet import is_valid_url
|
||||
from pipenv.vendor.click import (
|
||||
BadArgumentUsage,
|
||||
@@ -483,6 +484,83 @@ def validate_pypi_mirror(ctx, param, value):
|
||||
return value
|
||||
|
||||
|
||||
# OLD REMOVED COMMANDS THAT WE STILL DISPLAY HELP TEXT FOR #
|
||||
def skip_lock_option(f):
|
||||
def callback(ctx, param, value):
|
||||
if value:
|
||||
err.print(
|
||||
"The flag --skip-lock has been functionally removed. "
|
||||
"Without running the lock resolver it is not possible to manage multiple package indexes. "
|
||||
"Additionally it bypassed the build consistency guarantees provided by maintaining a lock file.",
|
||||
style="yellow bold",
|
||||
)
|
||||
raise ValueError("The flag --skip-lock flag has been removed.")
|
||||
return value
|
||||
|
||||
return option(
|
||||
"--skip-lock",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
expose_value=False,
|
||||
envvar="PIPENV_SKIP_LOCK",
|
||||
callback=callback,
|
||||
type=click_types.BOOL,
|
||||
show_envvar=True,
|
||||
hidden=True, # This hides the option from the help text.
|
||||
)(f)
|
||||
|
||||
|
||||
def keep_outdated_option(f):
|
||||
def callback(ctx, param, value):
|
||||
state = ctx.ensure_object(State)
|
||||
state.installstate.keep_outdated = value
|
||||
if value:
|
||||
err.print(
|
||||
"The flag --keep-outdated has been removed. "
|
||||
"The flag did not respect package resolver results and lead to inconsistent lock files. "
|
||||
"Consider using the `pipenv upgrade` command to selectively upgrade packages.",
|
||||
style="yellow bold",
|
||||
)
|
||||
raise ValueError("The flag --keep-outdated flag has been removed.")
|
||||
return value
|
||||
|
||||
return option(
|
||||
"--keep-outdated",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
expose_value=False,
|
||||
callback=callback,
|
||||
type=click_types.BOOL,
|
||||
show_envvar=True,
|
||||
hidden=True, # This hides the option from the help text.
|
||||
)(f)
|
||||
|
||||
|
||||
def selective_upgrade_option(f):
|
||||
def callback(ctx, param, value):
|
||||
state = ctx.ensure_object(State)
|
||||
state.installstate.selective_upgrade = value
|
||||
if value:
|
||||
err.print(
|
||||
"The flag --selective-upgrade has been removed. "
|
||||
"The flag was buggy and lead to inconsistent lock files. "
|
||||
"Consider using the `pipenv upgrade` command to selectively upgrade packages.",
|
||||
style="yellow bold",
|
||||
)
|
||||
raise ValueError("The flag --selective-upgrade flag has been removed.")
|
||||
return value
|
||||
|
||||
return option(
|
||||
"--selective-upgrade",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
type=click_types.BOOL,
|
||||
help="Update specified packages.",
|
||||
callback=callback,
|
||||
expose_value=False,
|
||||
)(f)
|
||||
|
||||
|
||||
def common_options(f):
|
||||
f = pypi_mirror_option(f)
|
||||
f = verbose_option(f)
|
||||
@@ -496,6 +574,7 @@ def install_base_options(f):
|
||||
f = common_options(f)
|
||||
f = pre_option(f)
|
||||
f = extra_pip_args(f)
|
||||
f = keep_outdated_option(f) # Removed, but still displayed in help text.
|
||||
return f
|
||||
|
||||
|
||||
@@ -505,6 +584,7 @@ def uninstall_options(f):
|
||||
f = uninstall_dev_option(f)
|
||||
f = editable_option(f)
|
||||
f = package_arg(f)
|
||||
f = skip_lock_option(f) # Removed, but still displayed in help text.
|
||||
return f
|
||||
|
||||
|
||||
@@ -530,6 +610,8 @@ def install_options(f):
|
||||
f = ignore_pipfile_option(f)
|
||||
f = editable_option(f)
|
||||
f = package_arg(f)
|
||||
f = skip_lock_option(f) # Removed, but still display help text.
|
||||
f = selective_upgrade_option(f) # Removed, but still display help text.
|
||||
return f
|
||||
|
||||
|
||||
|
||||
+29
-24
@@ -12,21 +12,23 @@ import typing
|
||||
from pathlib import Path
|
||||
from sysconfig import get_paths, get_python_version, get_scheme_names
|
||||
from urllib.parse import urlparse
|
||||
from urllib.request import url2pathname
|
||||
|
||||
import pipenv
|
||||
from pipenv.patched.pip._internal.commands.install import InstallCommand
|
||||
from pipenv.patched.pip._internal.index.package_finder import PackageFinder
|
||||
from pipenv.patched.pip._internal.req.req_install import InstallRequirement
|
||||
from pipenv.patched.pip._vendor import pkg_resources
|
||||
from pipenv.patched.pip._vendor.packaging.specifiers import SpecifierSet
|
||||
from pipenv.patched.pip._vendor.packaging.utils import canonicalize_name
|
||||
from pipenv.utils import console
|
||||
from pipenv.utils.constants import VCS_LIST
|
||||
from pipenv.utils.dependencies import as_pipfile
|
||||
from pipenv.utils.fileutils import normalize_path, temp_path
|
||||
from pipenv.utils.funktools import chunked, unnest
|
||||
from pipenv.utils.indexes import prepare_pip_source_args
|
||||
from pipenv.utils.processes import subprocess_run
|
||||
from pipenv.utils.shell import make_posix
|
||||
from pipenv.utils.shell import make_posix, temp_environ
|
||||
from pipenv.vendor.pythonfinder.utils import is_in_path
|
||||
from pipenv.vendor.requirementslib.fileutils import normalize_path, temp_path
|
||||
from pipenv.vendor.requirementslib.utils import temp_environ
|
||||
|
||||
try:
|
||||
# this is only in Python3.8 and later
|
||||
@@ -200,11 +202,6 @@ class Environment:
|
||||
.. note:: The implementation of this is borrowed from a combination of pip and
|
||||
virtualenv and is likely to change at some point in the future.
|
||||
|
||||
>>> from pipenv.core import project
|
||||
>>> from pipenv.environment import Environment
|
||||
>>> env = Environment(prefix=project.virtualenv_location, is_venv=True, sources=project.sources)
|
||||
>>> import pprint
|
||||
>>> pprint.pprint(env.base_paths)
|
||||
{'PATH': '/home/hawk/.virtualenvs/pipenv-MfOPs1lW/bin::/bin:/usr/bin',
|
||||
'PYTHONPATH': '/home/hawk/.virtualenvs/pipenv-MfOPs1lW/lib/python3.7/site-packages',
|
||||
'data': '/home/hawk/.virtualenvs/pipenv-MfOPs1lW',
|
||||
@@ -760,38 +757,46 @@ class Environment:
|
||||
|
||||
return any(d for d in self.get_distributions() if d.project_name == pkgname)
|
||||
|
||||
def is_satisfied(self, req):
|
||||
def is_satisfied(self, req: InstallRequirement):
|
||||
match = next(
|
||||
iter(
|
||||
d
|
||||
for d in self.get_distributions()
|
||||
if canonicalize_name(d.project_name) == req.normalized_name
|
||||
if req.name
|
||||
and canonicalize_name(d.project_name) == canonicalize_name(req.name)
|
||||
),
|
||||
None,
|
||||
)
|
||||
if match is not None:
|
||||
if req.editable and req.line_instance.is_local and self.find_egg(match):
|
||||
requested_path = req.line_instance.path
|
||||
if req.editable and req.link and req.link.is_file:
|
||||
requested_path = req.link.file_path
|
||||
if os.path.exists(requested_path):
|
||||
local_path = requested_path
|
||||
else:
|
||||
parsed_url = urlparse(requested_path)
|
||||
local_path = url2pathname(parsed_url.path)
|
||||
local_path = parsed_url.path
|
||||
return requested_path and os.path.samefile(local_path, match.location)
|
||||
elif match.has_metadata("direct_url.json"):
|
||||
direct_url_metadata = json.loads(match.get_metadata("direct_url.json"))
|
||||
commit_id = direct_url_metadata.get("vcs_info", {}).get("commit_id", "")
|
||||
vcs_type = direct_url_metadata.get("vcs_info", {}).get("vcs", "")
|
||||
_, pipfile_part = req.as_pipfile().popitem()
|
||||
return (
|
||||
vcs_type == req.vcs
|
||||
and commit_id == req.commit_hash
|
||||
and direct_url_metadata["url"] == pipfile_part[req.vcs]
|
||||
requested_revision = direct_url_metadata.get("vcs_info", {}).get(
|
||||
"requested_revision", ""
|
||||
)
|
||||
elif req.is_vcs or req.is_file_or_url:
|
||||
vcs_type = direct_url_metadata.get("vcs_info", {}).get("vcs", "")
|
||||
_, pipfile_part = as_pipfile(req).popitem()
|
||||
vcs_ref = ""
|
||||
for vcs in VCS_LIST:
|
||||
if pipfile_part.get(vcs):
|
||||
vcs_ref = pipfile_part[vcs].rsplit("@", 1)[-1]
|
||||
break
|
||||
return (
|
||||
vcs_type == req.link.scheme
|
||||
and vcs_ref == requested_revision
|
||||
and direct_url_metadata["url"] == pipfile_part[req.link.scheme]
|
||||
)
|
||||
elif req.link and req.link.is_vcs:
|
||||
return False
|
||||
elif req.line_instance.specifiers is not None:
|
||||
return req.line_instance.specifiers.contains(
|
||||
elif req.specifier is not None:
|
||||
return SpecifierSet(str(req.specifier)).contains(
|
||||
match.version, prereleases=True
|
||||
)
|
||||
return True
|
||||
|
||||
@@ -5,8 +5,8 @@ import re
|
||||
import sys
|
||||
|
||||
from pipenv.patched.pip._vendor.platformdirs import user_cache_dir
|
||||
from pipenv.utils.fileutils import normalize_drive
|
||||
from pipenv.utils.shell import env_to_bool, is_env_truthy, isatty
|
||||
from pipenv.vendor.requirementslib.fileutils import normalize_drive
|
||||
|
||||
# HACK: avoid resolver.py uses the wrong byte code files.
|
||||
# I hope I can remove this one day.
|
||||
|
||||
@@ -5,7 +5,7 @@ import sys
|
||||
|
||||
|
||||
def format_full_version(info):
|
||||
version = "{0.major}.{0.minor}.{0.micro}".format(info)
|
||||
version = f"{info.major}.{info.minor}.{info.micro}"
|
||||
kind = info.releaselevel
|
||||
if kind != "final":
|
||||
version += kind[0] + str(info.serial)
|
||||
|
||||
+213
-20
@@ -11,6 +11,10 @@ import sys
|
||||
import urllib.parse
|
||||
from json.decoder import JSONDecodeError
|
||||
from pathlib import Path
|
||||
from urllib import parse
|
||||
from urllib.parse import unquote
|
||||
|
||||
from pipenv.utils.constants import VCS_LIST
|
||||
|
||||
try:
|
||||
import tomllib as toml
|
||||
@@ -23,16 +27,35 @@ from pipenv.environments import Setting, is_in_virtualenv, normalize_pipfile_pat
|
||||
from pipenv.patched.pip._internal.commands.install import InstallCommand
|
||||
from pipenv.patched.pip._internal.configuration import Configuration
|
||||
from pipenv.patched.pip._internal.exceptions import ConfigurationError
|
||||
from pipenv.patched.pip._internal.models.link import Link
|
||||
from pipenv.patched.pip._internal.req.req_install import InstallRequirement
|
||||
from pipenv.patched.pip._internal.utils.hashes import FAVORITE_HASH
|
||||
from pipenv.patched.pip._vendor import pkg_resources
|
||||
from pipenv.utils import err
|
||||
from pipenv.utils.constants import is_type_checking
|
||||
from pipenv.utils.dependencies import (
|
||||
clean_pkg_version,
|
||||
determine_package_name,
|
||||
determine_path_specifier,
|
||||
determine_vcs_specifier,
|
||||
expansive_install_req_from_line,
|
||||
get_canonical_names,
|
||||
is_editable,
|
||||
pep423_name,
|
||||
python_version,
|
||||
)
|
||||
from pipenv.utils.internet import get_url_name, is_pypi_url, is_valid_url, proper_case
|
||||
from pipenv.utils.fileutils import open_file
|
||||
from pipenv.utils.internet import (
|
||||
PackageIndexHTMLParser,
|
||||
get_requests_session,
|
||||
get_url_name,
|
||||
is_pypi_url,
|
||||
is_valid_url,
|
||||
proper_case,
|
||||
)
|
||||
from pipenv.utils.locking import atomic_open_for_write
|
||||
from pipenv.utils.project import get_default_pyproject_backend
|
||||
from pipenv.utils.requirements import normalize_name
|
||||
from pipenv.utils.shell import (
|
||||
find_requirements,
|
||||
find_windows_executable,
|
||||
@@ -45,7 +68,6 @@ from pipenv.utils.shell import (
|
||||
)
|
||||
from pipenv.utils.toml import cleanup_toml, convert_toml_outline_tables
|
||||
from pipenv.vendor import click, plette, tomlkit
|
||||
from pipenv.vendor.requirementslib.models.utils import get_default_pyproject_backend
|
||||
|
||||
try:
|
||||
# this is only in Python3.8 and later
|
||||
@@ -132,6 +154,7 @@ class Project:
|
||||
self._environment = None
|
||||
self._build_system = {"requires": ["setuptools", "wheel"]}
|
||||
self.python_version = python_version
|
||||
self.sessions = {} # pip requests sessions
|
||||
self.s = Setting()
|
||||
# Load Pip configuration and get items
|
||||
self.configuration = Configuration(isolated=False, load_only=None)
|
||||
@@ -212,6 +235,109 @@ class Project:
|
||||
else:
|
||||
return ["packages", "dev-packages"] + list(package_categories)
|
||||
|
||||
def get_requests_session_for_source(self, source):
|
||||
if self.sessions.get(source["name"]):
|
||||
session = self.sessions[source["name"]]
|
||||
else:
|
||||
session = get_requests_session(
|
||||
self.s.PIPENV_MAX_RETRIES, source.get("verify_ssl", True)
|
||||
)
|
||||
self.sessions[source["name"]] = session
|
||||
return session
|
||||
|
||||
@classmethod
|
||||
def prepend_hash_types(cls, checksums, hash_type):
|
||||
cleaned_checksums = set()
|
||||
for checksum in checksums:
|
||||
if not checksum:
|
||||
continue
|
||||
if not checksum.startswith(f"{hash_type}:"):
|
||||
checksum = f"{hash_type}:{checksum}"
|
||||
cleaned_checksums.add(checksum)
|
||||
return sorted(cleaned_checksums)
|
||||
|
||||
def get_hash_from_link(self, hash_cache, link):
|
||||
if link.hash and link.hash_name == FAVORITE_HASH:
|
||||
return f"{link.hash_name}:{link.hash}"
|
||||
|
||||
return hash_cache.get_hash(link)
|
||||
|
||||
def get_hashes_from_pypi(self, ireq, source):
|
||||
pkg_url = f"https://pypi.org/pypi/{ireq.name}/json"
|
||||
session = self.get_requests_session_for_source(source)
|
||||
try:
|
||||
collected_hashes = set()
|
||||
# Grab the hashes from the new warehouse API.
|
||||
r = session.get(pkg_url, timeout=10)
|
||||
api_releases = r.json()["releases"]
|
||||
cleaned_releases = {}
|
||||
for api_version, api_info in api_releases.items():
|
||||
api_version = clean_pkg_version(api_version)
|
||||
cleaned_releases[api_version] = api_info
|
||||
version = ""
|
||||
if ireq.specifier:
|
||||
spec = next(iter(s for s in ireq.specifier), None)
|
||||
if spec:
|
||||
version = spec.version
|
||||
for release in cleaned_releases[version]:
|
||||
collected_hashes.add(release["digests"][FAVORITE_HASH])
|
||||
return self.prepend_hash_types(collected_hashes, FAVORITE_HASH)
|
||||
except (ValueError, KeyError, ConnectionError):
|
||||
if self.s.is_verbose():
|
||||
err.print(
|
||||
f"[bold][red]Warning[/red][/bold]: Error generating hash for {ireq.name}."
|
||||
)
|
||||
return None
|
||||
|
||||
def get_hashes_from_remote_index_urls(self, ireq, source):
|
||||
pkg_url = f"{source['url']}/{ireq.name}/"
|
||||
session = self.get_requests_session_for_source(source)
|
||||
try:
|
||||
collected_hashes = set()
|
||||
# Grab the hashes from the new warehouse API.
|
||||
response = session.get(pkg_url, timeout=10)
|
||||
# Create an instance of the parser
|
||||
parser = PackageIndexHTMLParser()
|
||||
# Feed the HTML to the parser
|
||||
parser.feed(response.text)
|
||||
# Extract hrefs
|
||||
hrefs = parser.urls
|
||||
|
||||
version = ""
|
||||
if ireq.specifier:
|
||||
spec = next(iter(s for s in ireq.specifier), None)
|
||||
if spec:
|
||||
version = spec.version
|
||||
for package_url in hrefs:
|
||||
if version in parse.unquote(package_url):
|
||||
url_params = parse.urlparse(package_url).fragment
|
||||
params_dict = parse.parse_qs(url_params)
|
||||
if params_dict.get(FAVORITE_HASH):
|
||||
collected_hashes.add(params_dict[FAVORITE_HASH][0])
|
||||
else: # Fallback to downloading the file to obtain hash
|
||||
if source["url"] not in package_url:
|
||||
package_url = f"{source['url']}{package_url}"
|
||||
link = Link(package_url)
|
||||
collected_hashes.add(self.get_file_hash(session, link))
|
||||
return self.prepend_hash_types(collected_hashes, FAVORITE_HASH)
|
||||
except (ValueError, KeyError, ConnectionError):
|
||||
if self.s.is_verbose():
|
||||
click.echo(
|
||||
"{}: Error generating hash for {}".format(
|
||||
click.style("Warning", bold=True, fg="red"), ireq.name
|
||||
),
|
||||
err=True,
|
||||
)
|
||||
return None
|
||||
|
||||
def get_file_hash(self, session, link):
|
||||
h = hashlib.new(FAVORITE_HASH)
|
||||
err.print(f"Downloading file {link.filename} to obtain hash...")
|
||||
with open_file(link.url, session) as fp:
|
||||
for chunk in iter(lambda: fp.read(8096), b""):
|
||||
h.update(chunk)
|
||||
return f"{h.name}:{h.hexdigest()}"
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
if self._name is None:
|
||||
@@ -634,7 +760,7 @@ class Project:
|
||||
|
||||
@property
|
||||
def _pipfile(self):
|
||||
from .vendor.requirementslib.models.pipfile import Pipfile as ReqLibPipfile
|
||||
from pipenv.utils.pipfile import Pipfile as ReqLibPipfile
|
||||
|
||||
pf = ReqLibPipfile.load(self.pipfile_location)
|
||||
return pf
|
||||
@@ -660,7 +786,7 @@ class Project:
|
||||
return packages
|
||||
|
||||
def _get_vcs_packages(self, dev=False):
|
||||
from pipenv.vendor.requirementslib.utils import is_vcs
|
||||
from pipenv.utils.requirementslib import is_vcs
|
||||
|
||||
section = "dev-packages" if dev else "packages"
|
||||
packages = {
|
||||
@@ -745,9 +871,7 @@ class Project:
|
||||
return source
|
||||
|
||||
def get_or_create_lockfile(self, categories, from_pipfile=False):
|
||||
from pipenv.vendor.requirementslib.models.lockfile import (
|
||||
Lockfile as Req_Lockfile,
|
||||
)
|
||||
from pipenv.utils.locking import Lockfile as Req_Lockfile
|
||||
|
||||
if from_pipfile and self.pipfile_exists:
|
||||
lockfile_dict = {}
|
||||
@@ -868,6 +992,14 @@ class Project:
|
||||
source["url"] = os.environ["PIPENV_PYPI_MIRROR"]
|
||||
return sources
|
||||
|
||||
def get_default_index(self):
|
||||
return self.pipfile_sources()[0]
|
||||
|
||||
def get_index_by_name(self, index_name):
|
||||
for source in self.pipfile_sources():
|
||||
if source.get("name") == index_name:
|
||||
return source
|
||||
|
||||
@property
|
||||
def sources(self):
|
||||
if self.lockfile_exists and hasattr(self.lockfile_content, "keys"):
|
||||
@@ -937,7 +1069,9 @@ class Project:
|
||||
|
||||
def get_package_name_in_pipfile(self, package_name, category):
|
||||
"""Get the equivalent package name in pipfile"""
|
||||
section = self.parsed_pipfile.get(category, {})
|
||||
section = self.parsed_pipfile.get(category)
|
||||
if section is None:
|
||||
section = {}
|
||||
package_name = pep423_name(package_name)
|
||||
for name in section.keys():
|
||||
if pep423_name(name) == package_name:
|
||||
@@ -968,31 +1102,90 @@ class Project:
|
||||
del parsed[category][pkg_name]
|
||||
self.write_toml(parsed)
|
||||
|
||||
def add_package_to_pipfile(self, package, dev=False, category=None):
|
||||
from .vendor.requirementslib import Requirement
|
||||
def generate_package_pipfile_entry(self, package, pip_line, category=None):
|
||||
# Don't re-capitalize file URLs or VCSs.
|
||||
if not isinstance(package, InstallRequirement):
|
||||
package = expansive_install_req_from_line(package.strip())
|
||||
|
||||
req_name = determine_package_name(package)
|
||||
path_specifier = determine_path_specifier(package)
|
||||
vcs_specifier = determine_vcs_specifier(package)
|
||||
name = self.get_package_name_in_pipfile(req_name, category=category)
|
||||
normalized_name = normalize_name(req_name)
|
||||
|
||||
extras = package.extras
|
||||
specifier = "*"
|
||||
if package.req and package.specifier:
|
||||
specifier = str(package.specifier)
|
||||
|
||||
# Construct package requirement
|
||||
entry = {}
|
||||
if extras:
|
||||
entry["extras"] = list(extras)
|
||||
if path_specifier:
|
||||
entry["file"] = unquote(path_specifier)
|
||||
elif vcs_specifier:
|
||||
for vcs in VCS_LIST:
|
||||
if vcs in package.link.scheme:
|
||||
if pip_line.startswith("-e"):
|
||||
entry["editable"] = True
|
||||
pip_line = pip_line.replace("-e ", "")
|
||||
if "[" in pip_line and "]" in pip_line:
|
||||
extras_section = pip_line.split("[")[1].split("]")[0]
|
||||
entry["extras"] = sorted(
|
||||
[extra.strip() for extra in extras_section.split(",")]
|
||||
)
|
||||
if "@ " in pip_line:
|
||||
vcs_part = pip_line.split("@ ", 1)[1]
|
||||
else:
|
||||
vcs_part = pip_line
|
||||
vcs_parts = vcs_part.rsplit("@", 1)
|
||||
entry["ref"] = vcs_parts[1].split("#", 1)[0].strip()
|
||||
entry[vcs] = vcs_parts[0].strip()
|
||||
break
|
||||
else:
|
||||
entry["version"] = specifier
|
||||
if hasattr(package, "index"):
|
||||
entry["index"] = package.index
|
||||
|
||||
if len(entry) == 1 and "version" in entry:
|
||||
return name, normalized_name, specifier
|
||||
else:
|
||||
return name, normalized_name, entry
|
||||
|
||||
def add_package_to_pipfile(self, package, pip_line, dev=False, category=None):
|
||||
category = category if category else "dev-packages" if dev else "packages"
|
||||
|
||||
name, normalized_name, entry = self.generate_package_pipfile_entry(
|
||||
package, pip_line, category=category
|
||||
)
|
||||
|
||||
return self.add_pipfile_entry_to_pipfile(
|
||||
name, normalized_name, entry, category=category
|
||||
)
|
||||
|
||||
def add_pipfile_entry_to_pipfile(self, name, normalized_name, entry, category=None):
|
||||
newly_added = False
|
||||
|
||||
# Read and append Pipfile.
|
||||
p = self.parsed_pipfile
|
||||
# Don't re-capitalize file URLs or VCSs.
|
||||
if not isinstance(package, Requirement):
|
||||
package = Requirement.from_line(package.strip(), parse_setup_info=False)
|
||||
req_name, converted = package.pipfile_entry
|
||||
category = category if category else "dev-packages" if dev else "packages"
|
||||
|
||||
# Set empty group if it doesn't exist yet.
|
||||
if category not in p:
|
||||
p[category] = {}
|
||||
# Add the package to the group.
|
||||
name = self.get_package_name_in_pipfile(req_name, category=category)
|
||||
normalized_name = pep423_name(req_name)
|
||||
|
||||
if name and name != normalized_name:
|
||||
self.remove_package_from_pipfile(name, category=category)
|
||||
|
||||
# Add the package to the group.
|
||||
if normalized_name not in p[category]:
|
||||
newly_added = True
|
||||
p[category][normalized_name] = converted
|
||||
|
||||
p[category][normalized_name] = entry
|
||||
|
||||
# Write Pipfile.
|
||||
self.write_toml(p)
|
||||
return newly_added, category
|
||||
return newly_added, category, normalized_name
|
||||
|
||||
def src_name_from_url(self, index_url):
|
||||
name, _, tld_guess = urllib.parse.urlsplit(index_url).netloc.rpartition(".")
|
||||
|
||||
+38
-98
@@ -1,11 +1,8 @@
|
||||
import importlib.util
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
os.environ["PIP_PYTHON_PATH"] = str(sys.executable)
|
||||
|
||||
|
||||
def _ensure_modules():
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
@@ -74,21 +71,9 @@ def which(*args, **kwargs):
|
||||
|
||||
|
||||
def handle_parsed_args(parsed):
|
||||
if "PIPENV_VERBOSITY" in os.environ:
|
||||
parsed.verbose = int(os.getenv("PIPENV_VERBOSITY"))
|
||||
if parsed.debug:
|
||||
parsed.verbose = max(parsed.verbose, 2)
|
||||
if parsed.verbose > 1:
|
||||
logging.getLogger("pip").setLevel(logging.DEBUG)
|
||||
elif parsed.verbose > 0:
|
||||
logging.getLogger("pip").setLevel(logging.INFO)
|
||||
logger = logging.getLogger(
|
||||
"pipenv.patched.pip._internal.resolution.resolvelib.reporter"
|
||||
)
|
||||
logger.addHandler(logging.StreamHandler())
|
||||
logger.setLevel(logging.INFO)
|
||||
os.environ["PIP_RESOLVER_DEBUG"] = ""
|
||||
os.environ["PIPENV_VERBOSITY"] = str(parsed.verbose)
|
||||
if parsed.verbose:
|
||||
os.environ["PIPENV_VERBOSITY"] = "1"
|
||||
os.environ["PIP_RESOLVER_DEBUG"] = "1"
|
||||
if parsed.constraints_file:
|
||||
with open(parsed.constraints_file) as constraints:
|
||||
file_constraints = constraints.read().strip().split("\n")
|
||||
@@ -107,7 +92,7 @@ class Entry:
|
||||
from pipenv.utils.dependencies import (
|
||||
get_lockfile_section_using_pipfile_category,
|
||||
)
|
||||
from pipenv.vendor.requirementslib.models.utils import tomlkit_value_to_python
|
||||
from pipenv.utils.toml import tomlkit_value_to_python
|
||||
|
||||
self.name = name
|
||||
if isinstance(entry_dict, dict):
|
||||
@@ -141,14 +126,19 @@ class Entry:
|
||||
|
||||
@staticmethod
|
||||
def make_requirement(name=None, entry=None):
|
||||
from pipenv.vendor.requirementslib.models.requirements import Requirement
|
||||
from pipenv.utils.dependencies import from_pipfile
|
||||
|
||||
return Requirement.from_pipfile(name, entry)
|
||||
return from_pipfile(name, entry)
|
||||
|
||||
@classmethod
|
||||
def clean_initial_dict(cls, entry_dict):
|
||||
if not entry_dict.get("version", "").startswith("=="):
|
||||
entry_dict["version"] = cls.clean_specifier(entry_dict.get("version", ""))
|
||||
from pipenv.patched.pip._vendor.packaging.requirements import Requirement
|
||||
|
||||
entry_dict.get("version", "")
|
||||
version = entry_dict.get("version", "")
|
||||
if isinstance(version, Requirement):
|
||||
version = str(version.specifier)
|
||||
entry_dict["version"] = cls.clean_specifier(version)
|
||||
if "name" in entry_dict:
|
||||
del entry_dict["name"]
|
||||
return entry_dict
|
||||
@@ -176,7 +166,7 @@ class Entry:
|
||||
@classmethod
|
||||
def get_markers_from_dict(cls, entry_dict):
|
||||
from pipenv.patched.pip._vendor.packaging import markers as packaging_markers
|
||||
from pipenv.vendor.requirementslib.models.markers import normalize_marker_str
|
||||
from pipenv.utils.markers import normalize_marker_str
|
||||
|
||||
marker_keys = cls.parse_pyparsing_exprs(packaging_markers.VARIABLE)
|
||||
markers = set()
|
||||
@@ -217,7 +207,7 @@ class Entry:
|
||||
|
||||
@staticmethod
|
||||
def marker_to_str(marker):
|
||||
from pipenv.vendor.requirementslib.models.markers import normalize_marker_str
|
||||
from pipenv.utils.markers import normalize_marker_str
|
||||
|
||||
if not marker:
|
||||
return None
|
||||
@@ -242,20 +232,19 @@ class Entry:
|
||||
entry_extras = list(self.entry.extras)
|
||||
if self.lockfile_entry.extras:
|
||||
entry_extras.extend(list(self.lockfile_entry.extras))
|
||||
self._entry.req.extras = entry_extras
|
||||
self.entry_dict["extras"] = self.entry.extras
|
||||
self.entry_dict["extras"] = entry_extras
|
||||
if self.original_markers and not self.markers:
|
||||
original_markers = self.marker_to_str(self.original_markers)
|
||||
self.markers = original_markers
|
||||
self.entry_dict["markers"] = self.marker_to_str(original_markers)
|
||||
entry_hashes = set(self.entry.hashes)
|
||||
locked_hashes = set(self.lockfile_entry.hashes)
|
||||
if entry_hashes != locked_hashes and not self.is_updated:
|
||||
self.entry_dict["hashes"] = sorted(entry_hashes | locked_hashes)
|
||||
entry_hashes = set(self.entry_dict.get("hashes", []))
|
||||
self.entry_dict["hashes"] = sorted(entry_hashes)
|
||||
self.entry_dict["name"] = self.name
|
||||
if "version" in self.entry_dict:
|
||||
self.entry_dict["version"] = self.strip_version(self.entry_dict["version"])
|
||||
_, self.entry_dict = self.get_markers_from_dict(self.entry_dict)
|
||||
if self.resolver.index_lookup.get(self.name):
|
||||
self.entry_dict["index"] = self.resolver.index_lookup[self.name]
|
||||
return self.entry_dict
|
||||
|
||||
@property
|
||||
@@ -383,12 +372,12 @@ class Entry:
|
||||
|
||||
@property
|
||||
def updated_version(self):
|
||||
version = self.entry.specifiers
|
||||
version = str(self.entry.specifier)
|
||||
return self.strip_version(version)
|
||||
|
||||
@property
|
||||
def updated_specifier(self) -> str:
|
||||
return self.entry.specifiers
|
||||
return str(self.entry.specifier)
|
||||
|
||||
@property
|
||||
def original_specifier(self) -> str:
|
||||
@@ -452,15 +441,7 @@ class Entry:
|
||||
:return: A set of **InstallRequirement** instances representing constraints
|
||||
:rtype: Set
|
||||
"""
|
||||
constraints = {
|
||||
c for c in self.resolver.parsed_constraints if c and c.name == self.entry.name
|
||||
}
|
||||
pipfile_constraint = self.get_pipfile_constraint()
|
||||
if pipfile_constraint and not (
|
||||
self.pipfile_entry.editable or pipfile_constraint.editable
|
||||
):
|
||||
constraints.add(pipfile_constraint)
|
||||
return constraints
|
||||
return self.resolver.parsed_constraints
|
||||
|
||||
def get_pipfile_constraint(self):
|
||||
"""
|
||||
@@ -470,7 +451,7 @@ class Entry:
|
||||
:return: An **InstallRequirement** instance representing a version constraint
|
||||
"""
|
||||
if self.is_in_pipfile:
|
||||
return self.pipfile_entry.ireq
|
||||
return self.pipfile_entry
|
||||
|
||||
def validate_constraints(self):
|
||||
"""
|
||||
@@ -481,22 +462,24 @@ class Entry:
|
||||
:raises: :exc:`pipenv.exceptions.DependencyConflict` if the constraints dont exist
|
||||
"""
|
||||
from pipenv.exceptions import DependencyConflict
|
||||
from pipenv.patched.pip._vendor.packaging.requirements import Requirement
|
||||
from pipenv.utils import err
|
||||
|
||||
constraints = self.get_constraints()
|
||||
pinned_version = self.updated_version
|
||||
for constraint in constraints:
|
||||
if not constraint.req:
|
||||
if not isinstance(constraint, Requirement):
|
||||
continue
|
||||
if pinned_version and not constraint.req.specifier.contains(
|
||||
if pinned_version and not constraint.specifier.contains(
|
||||
str(pinned_version), prereleases=True
|
||||
):
|
||||
if self.project.s.is_verbose():
|
||||
print(f"Tried constraint: {constraint!r}", file=sys.stderr)
|
||||
err.print(f"Tried constraint: {constraint!r}")
|
||||
msg = (
|
||||
"Cannot resolve conflicting version {}{} while {}{} is "
|
||||
"locked.".format(
|
||||
self.name,
|
||||
constraint.req.specifier,
|
||||
constraint.specifier,
|
||||
self.name,
|
||||
self.updated_specifier,
|
||||
)
|
||||
@@ -582,37 +565,6 @@ def clean_results(results, resolver, project, category):
|
||||
return new_results
|
||||
|
||||
|
||||
def parse_packages(packages, pre, clear, system, requirements_dir=None):
|
||||
from pipenv.utils.indexes import parse_indexes
|
||||
from pipenv.vendor.requirementslib.fileutils import cd, temp_path
|
||||
from pipenv.vendor.requirementslib.models.requirements import Requirement
|
||||
|
||||
parsed_packages = []
|
||||
for package in packages:
|
||||
*_, line = parse_indexes(package)
|
||||
line = " ".join(line)
|
||||
pf = {}
|
||||
req = Requirement.from_line(line)
|
||||
if not req.name:
|
||||
with temp_path(), cd(req.req.setup_info.base_dir):
|
||||
sys.path.insert(0, req.req.setup_info.base_dir)
|
||||
req.req.setup_info.get_info()
|
||||
req.update_name_from_path(req.req.setup_info.base_dir)
|
||||
try:
|
||||
name, entry = req.pipfile_entry
|
||||
except Exception:
|
||||
continue
|
||||
else:
|
||||
if name is not None and entry is not None:
|
||||
pf[name] = entry
|
||||
parsed_packages.append(pf)
|
||||
print("RESULTS:")
|
||||
if parsed_packages:
|
||||
print(json.dumps(parsed_packages))
|
||||
else:
|
||||
print(json.dumps([]))
|
||||
|
||||
|
||||
def resolve_packages(
|
||||
pre,
|
||||
clear,
|
||||
@@ -633,8 +585,12 @@ def resolve_packages(
|
||||
else None
|
||||
)
|
||||
|
||||
if not isinstance(packages, set):
|
||||
packages = set(packages)
|
||||
if not isinstance(constraints, set):
|
||||
constraints = set(constraints) if constraints else set()
|
||||
if constraints:
|
||||
packages += constraints
|
||||
packages |= constraints
|
||||
|
||||
def resolve(
|
||||
packages, pre, project, sources, clear, system, category, requirements_dir=None
|
||||
@@ -692,31 +648,15 @@ def _main(
|
||||
parse_only=False,
|
||||
category=None,
|
||||
):
|
||||
if parse_only:
|
||||
parse_packages(
|
||||
packages,
|
||||
pre=pre,
|
||||
clear=clear,
|
||||
system=system,
|
||||
requirements_dir=requirements_dir,
|
||||
)
|
||||
else:
|
||||
resolve_packages(
|
||||
pre, clear, verbose, system, write, requirements_dir, packages, category
|
||||
)
|
||||
resolve_packages(
|
||||
pre, clear, verbose, system, write, requirements_dir, packages, category
|
||||
)
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
parser = get_parser()
|
||||
parsed, remaining = parser.parse_known_args(argv)
|
||||
_ensure_modules()
|
||||
import warnings
|
||||
|
||||
from pipenv.vendor.click.utils import get_text_stream
|
||||
|
||||
warnings.simplefilter("ignore", category=ResourceWarning)
|
||||
sys.stdout = get_text_stream("stdout")
|
||||
sys.stderr = get_text_stream("stderr")
|
||||
os.environ["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
|
||||
os.environ["PYTHONIOENCODING"] = "utf-8"
|
||||
os.environ["PYTHONUNBUFFERED"] = "1"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import shutil
|
||||
|
||||
from pipenv import environments
|
||||
from pipenv.utils.funktools import handle_remove_readonly
|
||||
from pipenv.vendor import click
|
||||
from pipenv.vendor.requirementslib.models.setup_info import handle_remove_readonly
|
||||
|
||||
|
||||
def do_clear(project):
|
||||
|
||||
+76
-158
@@ -9,6 +9,11 @@ from pipenv import environments, exceptions
|
||||
from pipenv.patched.pip._internal.exceptions import PipError
|
||||
from pipenv.patched.pip._vendor import rich
|
||||
from pipenv.routines.lock import do_lock
|
||||
from pipenv.utils import fileutils
|
||||
from pipenv.utils.dependencies import (
|
||||
expansive_install_req_from_line,
|
||||
get_lockfile_section_using_pipfile_category,
|
||||
)
|
||||
from pipenv.utils.indexes import get_source_list
|
||||
from pipenv.utils.internet import download_file, is_valid_url
|
||||
from pipenv.utils.pip import (
|
||||
@@ -18,11 +23,9 @@ from pipenv.utils.pip import (
|
||||
from pipenv.utils.pipfile import ensure_pipfile
|
||||
from pipenv.utils.project import ensure_project
|
||||
from pipenv.utils.requirements import add_index_to_pipfile, import_requirements
|
||||
from pipenv.utils.shell import temp_environ
|
||||
from pipenv.utils.virtualenv import cleanup_virtualenv, do_create_virtualenv
|
||||
from pipenv.vendor import click
|
||||
from pipenv.vendor.requirementslib import fileutils
|
||||
from pipenv.vendor.requirementslib.models.requirements import Requirement
|
||||
from pipenv.vendor.requirementslib.utils import temp_environ
|
||||
|
||||
console = rich.console.Console()
|
||||
err = rich.console.Console(stderr=True)
|
||||
@@ -32,7 +35,7 @@ def do_install(
|
||||
project,
|
||||
packages=False,
|
||||
editable_packages=False,
|
||||
index_url=False,
|
||||
index=False,
|
||||
dev=False,
|
||||
python=False,
|
||||
pypi_mirror=None,
|
||||
@@ -123,7 +126,6 @@ def do_install(
|
||||
fd.close()
|
||||
# Replace the url with the temporary requirements file
|
||||
requirementstxt = temp_reqs
|
||||
remote = True
|
||||
if requirementstxt:
|
||||
error, traceback = None, None
|
||||
click.secho(
|
||||
@@ -140,7 +142,7 @@ def do_install(
|
||||
)
|
||||
except (UnicodeDecodeError, PipError) as e:
|
||||
# Don't print the temp file path if remote since it will be deleted.
|
||||
req_path = requirements_url if remote else project.path_to(requirementstxt)
|
||||
req_path = project.path_to(requirementstxt)
|
||||
error = (
|
||||
"Unexpected syntax in {}. Are you sure this is a "
|
||||
"requirements.txt style file?".format(req_path)
|
||||
@@ -153,10 +155,6 @@ def do_install(
|
||||
)
|
||||
traceback = e
|
||||
finally:
|
||||
# If requirements file was provided by remote url delete the temporary file
|
||||
if remote:
|
||||
fd.close() # Close for windows to allow file cleanup.
|
||||
os.remove(temp_reqs)
|
||||
if error and traceback:
|
||||
click.secho(error, fg="red")
|
||||
click.secho(str(traceback), fg="yellow", err=True)
|
||||
@@ -188,8 +186,6 @@ def do_install(
|
||||
|
||||
# This is for if the user passed in dependencies, then we want to make sure we
|
||||
else:
|
||||
from pipenv.vendor.requirementslib.models.requirements import Requirement
|
||||
|
||||
# make a tuple of (display_name, entry)
|
||||
pkg_list = packages + [f"-e {pkg}" for pkg in editable_packages]
|
||||
if not system and not project.virtualenv_exists:
|
||||
@@ -219,9 +215,11 @@ def do_install(
|
||||
os.environ["PIP_USER"] = "0"
|
||||
if "PYTHONHOME" in os.environ:
|
||||
del os.environ["PYTHONHOME"]
|
||||
st.console.print(f"Resolving {pkg_line}...")
|
||||
st.console.print(f"Resolving {pkg_line}...", markup=False)
|
||||
try:
|
||||
pkg_requirement = Requirement.from_line(pkg_line)
|
||||
pkg_requirement = expansive_install_req_from_line(
|
||||
pkg_line, expand_env=True
|
||||
)
|
||||
except ValueError as e:
|
||||
err.print("{}: {}".format(click.style("WARNING", fg="red"), e))
|
||||
err.print(
|
||||
@@ -233,7 +231,8 @@ def do_install(
|
||||
st.update(f"Installing {pkg_requirement.name}...")
|
||||
# Warn if --editable wasn't passed.
|
||||
if (
|
||||
pkg_requirement.is_vcs
|
||||
pkg_requirement.link
|
||||
and pkg_requirement.link.is_vcs
|
||||
and not pkg_requirement.editable
|
||||
and not project.s.PIPENV_RESOLVE_VCS
|
||||
):
|
||||
@@ -254,25 +253,38 @@ def do_install(
|
||||
pipfile_sections = "[dev-packages]"
|
||||
else:
|
||||
pipfile_sections = "[packages]"
|
||||
st.console.print(
|
||||
f"[bold]Adding [green]{pkg_requirement.name}[/green][/bold] to Pipfile's [yellow]\\{pipfile_sections}[/yellow] ..."
|
||||
)
|
||||
# Add the package to the Pipfile.
|
||||
if index_url:
|
||||
index_name = add_index_to_pipfile(project, index_url)
|
||||
pkg_requirement.index = index_name
|
||||
if index:
|
||||
source = project.get_index_by_name(index)
|
||||
default_index = project.get_default_index()["name"]
|
||||
if not source:
|
||||
index_name = add_index_to_pipfile(project, index)
|
||||
if index_name != default_index:
|
||||
pkg_requirement.index = index_name
|
||||
elif source["name"] != default_index:
|
||||
pkg_requirement.index = source["name"]
|
||||
try:
|
||||
if categories:
|
||||
for category in categories:
|
||||
added, cat = project.add_package_to_pipfile(
|
||||
pkg_requirement, dev, category
|
||||
added, cat, normalized_name = project.add_package_to_pipfile(
|
||||
pkg_requirement, pkg_line, dev, category
|
||||
)
|
||||
if added:
|
||||
new_packages.append((pkg_requirement.name, cat))
|
||||
new_packages.append((normalized_name, cat))
|
||||
st.console.print(
|
||||
f"[bold]Added [green]{normalized_name}[/green][/bold] to Pipfile's "
|
||||
f"[yellow]\\{pipfile_sections}[/yellow] ..."
|
||||
)
|
||||
else:
|
||||
added, cat = project.add_package_to_pipfile(pkg_requirement, dev)
|
||||
added, cat, normalized_name = project.add_package_to_pipfile(
|
||||
pkg_requirement, pkg_line, dev
|
||||
)
|
||||
if added:
|
||||
new_packages.append((pkg_requirement.name, cat))
|
||||
new_packages.append((normalized_name, cat))
|
||||
st.console.print(
|
||||
f"[bold]Added [green]{normalized_name}[/green][/bold] to Pipfile's "
|
||||
f"[yellow]\\{pipfile_sections}[/yellow] ..."
|
||||
)
|
||||
except ValueError:
|
||||
import traceback
|
||||
|
||||
@@ -306,11 +318,11 @@ def do_install(
|
||||
extra_pip_args=extra_pip_args,
|
||||
categories=categories,
|
||||
)
|
||||
except RuntimeError:
|
||||
except Exception as e:
|
||||
# If we fail to install, remove the package from the Pipfile.
|
||||
for pkg_name, category in new_packages:
|
||||
project.remove_package_from_pipfile(pkg_name, category)
|
||||
sys.exit(1)
|
||||
raise e
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
@@ -393,8 +405,6 @@ def do_install_dependencies(
|
||||
else:
|
||||
categories = ["packages"]
|
||||
|
||||
lockfile = None
|
||||
pipfile = None
|
||||
for category in categories:
|
||||
lockfile = project.get_or_create_lockfile(categories=categories)
|
||||
if not bare:
|
||||
@@ -405,18 +415,18 @@ def do_install_dependencies(
|
||||
bold=True,
|
||||
)
|
||||
dev = dev or dev_only
|
||||
if lockfile:
|
||||
deps_list = list(
|
||||
lockfile.get_requirements(dev=dev, only=dev_only, categories=[category])
|
||||
)
|
||||
else:
|
||||
deps_list = []
|
||||
for req_name, specifier in pipfile.items():
|
||||
deps_list.append(Requirement.from_pipfile(req_name, specifier))
|
||||
failed_deps_queue = queue.Queue()
|
||||
deps_list = list(
|
||||
lockfile.get_requirements(dev=dev, only=dev_only, categories=[category])
|
||||
)
|
||||
editable_or_vcs_deps = [
|
||||
(dep, pip_line) for dep, pip_line in deps_list if (dep.link and dep.editable)
|
||||
]
|
||||
normal_deps = [
|
||||
(dep, pip_line)
|
||||
for dep, pip_line in deps_list
|
||||
if not (dep.link and dep.editable)
|
||||
]
|
||||
|
||||
editable_or_vcs_deps = [dep for dep in deps_list if (dep.editable or dep.vcs)]
|
||||
normal_deps = [dep for dep in deps_list if not (dep.editable or dep.vcs)]
|
||||
install_kwargs = {
|
||||
"no_deps": True,
|
||||
"ignore_hashes": ignore_hashes,
|
||||
@@ -425,51 +435,19 @@ def do_install_dependencies(
|
||||
"sequential_deps": editable_or_vcs_deps,
|
||||
"extra_pip_args": extra_pip_args,
|
||||
}
|
||||
|
||||
lockfile_category = get_lockfile_section_using_pipfile_category(category)
|
||||
lockfile_section = lockfile[lockfile_category]
|
||||
batch_install(
|
||||
project,
|
||||
normal_deps,
|
||||
lockfile_section,
|
||||
procs,
|
||||
failed_deps_queue,
|
||||
requirements_dir,
|
||||
**install_kwargs,
|
||||
)
|
||||
|
||||
if not procs.empty():
|
||||
_cleanup_procs(project, procs, failed_deps_queue)
|
||||
|
||||
# Iterate over the hopefully-poorly-packaged dependencies...
|
||||
if not failed_deps_queue.empty():
|
||||
click.secho("Installing initially failed dependencies...", bold=True)
|
||||
retry_list = []
|
||||
while not failed_deps_queue.empty():
|
||||
failed_dep = failed_deps_queue.get()
|
||||
retry_list.append(failed_dep)
|
||||
install_kwargs.update({"retry": False})
|
||||
batch_install(
|
||||
project,
|
||||
retry_list,
|
||||
procs,
|
||||
failed_deps_queue,
|
||||
requirements_dir,
|
||||
**install_kwargs,
|
||||
)
|
||||
if not procs.empty():
|
||||
_cleanup_procs(project, procs, failed_deps_queue, retry=False)
|
||||
if not failed_deps_queue.empty():
|
||||
failed_list = []
|
||||
while not failed_deps_queue.empty():
|
||||
failed_dep = failed_deps_queue.get()
|
||||
failed_list.append(failed_dep)
|
||||
click.echo(
|
||||
click.style(
|
||||
f"Failed to install some dependency or packages. "
|
||||
f"The following have failed installation and attempted retry: {failed_list}",
|
||||
fg="red",
|
||||
),
|
||||
err=True,
|
||||
)
|
||||
sys.exit(1)
|
||||
_cleanup_procs(project, procs)
|
||||
|
||||
|
||||
def batch_install_iteration(
|
||||
@@ -477,33 +455,12 @@ def batch_install_iteration(
|
||||
deps_to_install,
|
||||
sources,
|
||||
procs,
|
||||
failed_deps_queue,
|
||||
requirements_dir,
|
||||
no_deps=True,
|
||||
ignore_hashes=False,
|
||||
allow_global=False,
|
||||
retry=True,
|
||||
extra_pip_args=None,
|
||||
):
|
||||
from pipenv.vendor.requirementslib.models.utils import (
|
||||
strip_extras_markers_from_requirement,
|
||||
)
|
||||
|
||||
is_artifact = False
|
||||
for dep in deps_to_install:
|
||||
if dep.req.req:
|
||||
dep.req.req = strip_extras_markers_from_requirement(dep.req.req)
|
||||
if dep.markers:
|
||||
dep.markers = str(strip_extras_markers_from_requirement(dep.get_markers))
|
||||
# Install the module.
|
||||
if dep.is_file_or_url and (
|
||||
dep.is_direct_url
|
||||
or any(dep.req.uri.endswith(ext) for ext in ["zip", "tar.gz"])
|
||||
):
|
||||
is_artifact = True
|
||||
elif dep.is_vcs:
|
||||
is_artifact = True
|
||||
|
||||
with temp_environ():
|
||||
if not allow_global:
|
||||
os.environ["PIP_USER"] = "0"
|
||||
@@ -511,10 +468,6 @@ def batch_install_iteration(
|
||||
del os.environ["PYTHONHOME"]
|
||||
if "GIT_CONFIG" in os.environ:
|
||||
del os.environ["GIT_CONFIG"]
|
||||
use_pep517 = True
|
||||
if not retry and not is_artifact:
|
||||
use_pep517 = False
|
||||
|
||||
cmds = pip_install_deps(
|
||||
project,
|
||||
deps=deps_to_install,
|
||||
@@ -523,26 +476,25 @@ def batch_install_iteration(
|
||||
ignore_hashes=ignore_hashes,
|
||||
no_deps=no_deps,
|
||||
requirements_dir=requirements_dir,
|
||||
use_pep517=use_pep517,
|
||||
use_pep517=True,
|
||||
extra_pip_args=extra_pip_args,
|
||||
)
|
||||
|
||||
for c in cmds:
|
||||
procs.put(c)
|
||||
_cleanup_procs(project, procs, failed_deps_queue, retry=retry)
|
||||
_cleanup_procs(project, procs)
|
||||
|
||||
|
||||
def batch_install(
|
||||
project,
|
||||
deps_list,
|
||||
lockfile_section,
|
||||
procs,
|
||||
failed_deps_queue,
|
||||
requirements_dir,
|
||||
no_deps=True,
|
||||
ignore_hashes=False,
|
||||
allow_global=False,
|
||||
pypi_mirror=None,
|
||||
retry=True,
|
||||
sequential_deps=None,
|
||||
extra_pip_args=None,
|
||||
):
|
||||
@@ -551,7 +503,9 @@ def batch_install(
|
||||
deps_to_install = deps_list[:]
|
||||
deps_to_install.extend(sequential_deps)
|
||||
deps_to_install = [
|
||||
dep for dep in deps_to_install if not project.environment.is_satisfied(dep)
|
||||
(dep, pip_line)
|
||||
for dep, pip_line in deps_to_install
|
||||
if not project.environment.is_satisfied(dep)
|
||||
]
|
||||
search_all_sources = project.settings.get("install_search_all_sources", False)
|
||||
sources = get_source_list(
|
||||
@@ -562,27 +516,26 @@ def batch_install(
|
||||
pypi_mirror=pypi_mirror,
|
||||
)
|
||||
if search_all_sources:
|
||||
dependencies = [pip_line for _, pip_line in deps_to_install]
|
||||
batch_install_iteration(
|
||||
project,
|
||||
deps_to_install,
|
||||
dependencies,
|
||||
sources,
|
||||
procs,
|
||||
failed_deps_queue,
|
||||
requirements_dir,
|
||||
no_deps=no_deps,
|
||||
ignore_hashes=ignore_hashes,
|
||||
allow_global=allow_global,
|
||||
retry=retry,
|
||||
extra_pip_args=extra_pip_args,
|
||||
)
|
||||
else:
|
||||
# Sort the dependencies out by index -- include editable/vcs in the default group
|
||||
deps_by_index = defaultdict(list)
|
||||
for dependency in deps_to_install:
|
||||
if dependency.index:
|
||||
deps_by_index[dependency.index].append(dependency)
|
||||
else:
|
||||
deps_by_index[project.sources_default["name"]].append(dependency)
|
||||
for dependency, pip_line in deps_to_install:
|
||||
index = project.sources_default["name"]
|
||||
if dependency.name and lockfile_section[dependency.name].get("index"):
|
||||
index = lockfile_section[dependency.name]["index"]
|
||||
deps_by_index[index].append(pip_line)
|
||||
# Treat each index as its own pip install phase
|
||||
for index_name, dependencies in deps_by_index.items():
|
||||
try:
|
||||
@@ -592,12 +545,10 @@ def batch_install(
|
||||
dependencies,
|
||||
[install_source],
|
||||
procs,
|
||||
failed_deps_queue,
|
||||
requirements_dir,
|
||||
no_deps=no_deps,
|
||||
ignore_hashes=ignore_hashes,
|
||||
allow_global=allow_global,
|
||||
retry=retry,
|
||||
extra_pip_args=extra_pip_args,
|
||||
)
|
||||
except StopIteration:
|
||||
@@ -609,7 +560,7 @@ def batch_install(
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _cleanup_procs(project, procs, failed_deps_queue, retry=True):
|
||||
def _cleanup_procs(project, procs):
|
||||
while not procs.empty():
|
||||
c = procs.get()
|
||||
try:
|
||||
@@ -621,47 +572,14 @@ def _cleanup_procs(project, procs, failed_deps_queue, retry=True):
|
||||
click.secho(out.strip() or err.strip(), fg="yellow")
|
||||
# The Installation failed...
|
||||
if failed:
|
||||
# The Installation failed...
|
||||
# We echo both c.stdout and c.stderr because pip returns error details on out.
|
||||
err = err.strip().splitlines() if err else []
|
||||
out = out.strip().splitlines() if out else []
|
||||
err_lines = [line for message in [out, err] for line in message]
|
||||
deps = getattr(c, "deps", {}).copy()
|
||||
for dep in deps:
|
||||
# If there is a mismatch in installed locations or the install fails
|
||||
# due to wrongful disabling of pep517, we should allow for
|
||||
# additional passes at installation
|
||||
if "does not match installed location" in err:
|
||||
project.environment.expand_egg_links()
|
||||
click.echo(
|
||||
"{}".format(
|
||||
click.style(
|
||||
"Failed initial installation: Failed to overwrite existing "
|
||||
"package, likely due to path aliasing. Expanding and trying "
|
||||
"again!",
|
||||
fg="yellow",
|
||||
)
|
||||
)
|
||||
)
|
||||
if dep:
|
||||
dep.use_pep517 = True
|
||||
elif "Disabling PEP 517 processing is invalid" in err:
|
||||
if dep:
|
||||
dep.use_pep517 = True
|
||||
elif not retry:
|
||||
# The Installation failed...
|
||||
# We echo both c.stdout and c.stderr because pip returns error details on out.
|
||||
err = err.strip().splitlines() if err else []
|
||||
out = out.strip().splitlines() if out else []
|
||||
err_lines = [line for message in [out, err] for line in message]
|
||||
# Return the subprocess' return code.
|
||||
raise exceptions.InstallError(deps, extra=err_lines)
|
||||
else:
|
||||
# Alert the user.
|
||||
click.echo(
|
||||
"{} {}! Will try again.".format(
|
||||
click.style("An error occurred while installing", fg="red"),
|
||||
click.style(dep.as_line() if dep else "", fg="green"),
|
||||
),
|
||||
err=True,
|
||||
)
|
||||
# Save the Failed Dependency for later.
|
||||
failed_deps_queue.put(dep)
|
||||
# Return the subprocess' return code.
|
||||
raise exceptions.InstallError(deps, extra=err_lines)
|
||||
|
||||
|
||||
def do_init(
|
||||
|
||||
@@ -29,10 +29,10 @@ def do_lock(
|
||||
lockfile_categories.insert(0, "default")
|
||||
# Create the lockfile.
|
||||
lockfile = project.lockfile(categories=lockfile_categories)
|
||||
for category in lockfile_categories:
|
||||
for k, v in lockfile.get(category, {}).copy().items():
|
||||
if not hasattr(v, "keys"):
|
||||
del lockfile[category][k]
|
||||
# for category in lockfile_categories:
|
||||
# for k, v in lockfile.get(category, {}).copy().items():
|
||||
# if not hasattr(v, "keys"):
|
||||
# del lockfile[category][k]
|
||||
|
||||
# Resolve package to generate constraints before resolving other categories
|
||||
for category in lockfile_categories:
|
||||
@@ -55,7 +55,7 @@ def do_lock(
|
||||
|
||||
# Prune old lockfile category as new one will be created.
|
||||
try:
|
||||
del lockfile[category]
|
||||
old_lock_data = lockfile.pop(category)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
@@ -73,6 +73,7 @@ def do_lock(
|
||||
pypi_mirror=pypi_mirror,
|
||||
pipfile=packages,
|
||||
lockfile=lockfile,
|
||||
old_lock_data=old_lock_data,
|
||||
)
|
||||
|
||||
# Overwrite any category packages with default packages.
|
||||
|
||||
@@ -4,10 +4,13 @@ from collections.abc import Mapping
|
||||
|
||||
from pipenv.patched.pip._vendor.packaging.utils import canonicalize_name
|
||||
from pipenv.routines.lock import do_lock
|
||||
from pipenv.utils.dependencies import pep423_name
|
||||
from pipenv.utils.dependencies import (
|
||||
as_pipfile,
|
||||
expansive_install_req_from_line,
|
||||
get_version,
|
||||
pep423_name,
|
||||
)
|
||||
from pipenv.vendor import click
|
||||
from pipenv.vendor.requirementslib.models.requirements import Requirement
|
||||
from pipenv.vendor.requirementslib.models.utils import get_version
|
||||
|
||||
|
||||
def do_outdated(project, pypi_mirror=None, pre=False, clear=False):
|
||||
@@ -26,8 +29,11 @@ def do_outdated(project, pypi_mirror=None, pre=False, clear=False):
|
||||
for name, deps in project.environment.reverse_dependencies().items()
|
||||
}
|
||||
for result in installed_packages:
|
||||
dep = Requirement.from_line(str(result.as_requirement()))
|
||||
packages.update(dep.as_pipfile())
|
||||
dep = expansive_install_req_from_line(
|
||||
str(result.as_requirement()), expand_env=True
|
||||
)
|
||||
packages.update(as_pipfile(dep))
|
||||
|
||||
updated_packages = {}
|
||||
lockfile = do_lock(
|
||||
project, clear=clear, pre=pre, write=False, pypi_mirror=pypi_mirror
|
||||
|
||||
@@ -2,61 +2,10 @@ import re
|
||||
import sys
|
||||
|
||||
from pipenv.utils.dependencies import get_lockfile_section_using_pipfile_category
|
||||
from pipenv.utils.requirements import requirements_from_lockfile
|
||||
from pipenv.vendor import click
|
||||
|
||||
|
||||
def requirements_from_deps(deps, include_hashes=True, include_markers=True):
|
||||
pip_packages = []
|
||||
|
||||
for package_name, package_info in deps.items():
|
||||
# Handling git repositories
|
||||
if "git" in package_info:
|
||||
git = package_info["git"]
|
||||
ref = package_info.get("ref", "")
|
||||
extras = (
|
||||
"[{}]".format(",".join(package_info.get("extras", [])))
|
||||
if "extras" in package_info
|
||||
else ""
|
||||
)
|
||||
pip_package = f"{package_name}{extras} @ git+{git}@{ref}"
|
||||
# Handling file-sourced packages
|
||||
elif "file" in package_info or "path" in package_info:
|
||||
file = package_info.get("file") or package_info.get("path")
|
||||
extras = (
|
||||
"[{}]".format(",".join(package_info.get("extras", [])))
|
||||
if "extras" in package_info
|
||||
else ""
|
||||
)
|
||||
pip_package = f"{file}{extras}"
|
||||
else:
|
||||
# Handling packages from standard pypi like indexes
|
||||
version = package_info.get("version", "").replace("==", "")
|
||||
hashes = (
|
||||
" --hash={}".format(" --hash=".join(package_info["hashes"]))
|
||||
if include_hashes and "hashes" in package_info
|
||||
else ""
|
||||
)
|
||||
markers = (
|
||||
"; {}".format(package_info["markers"])
|
||||
if include_markers
|
||||
and "markers" in package_info
|
||||
and package_info["markers"]
|
||||
else ""
|
||||
)
|
||||
extras = (
|
||||
"[{}]".format(",".join(package_info.get("extras", [])))
|
||||
if "extras" in package_info
|
||||
else ""
|
||||
)
|
||||
pip_package = f"{package_name}{extras}=={version}{markers}{hashes}"
|
||||
|
||||
# Append to the list
|
||||
pip_packages.append(pip_package)
|
||||
|
||||
# pip_packages contains the pip-installable lines
|
||||
return pip_packages
|
||||
|
||||
|
||||
def generate_requirements(
|
||||
project,
|
||||
dev=False,
|
||||
@@ -84,7 +33,7 @@ def generate_requirements(
|
||||
if not dev_only:
|
||||
deps.update(lockfile["default"])
|
||||
|
||||
pip_installable_lines = requirements_from_deps(
|
||||
pip_installable_lines = requirements_from_lockfile(
|
||||
deps, include_hashes=include_hashes, include_markers=include_markers
|
||||
)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from pipenv.patched.pip._internal.build_env import get_runnable_pip
|
||||
from pipenv.patched.pip._vendor.packaging.utils import canonicalize_name
|
||||
from pipenv.routines.lock import do_lock
|
||||
from pipenv.utils.dependencies import (
|
||||
expansive_install_req_from_line,
|
||||
get_canonical_names,
|
||||
get_lockfile_section_using_pipfile_category,
|
||||
get_pipfile_category_using_lockfile_section,
|
||||
@@ -16,7 +17,6 @@ from pipenv.utils.project import ensure_project
|
||||
from pipenv.utils.requirements import BAD_PACKAGES
|
||||
from pipenv.utils.shell import cmd_list_to_shell, project_python
|
||||
from pipenv.vendor import click
|
||||
from pipenv.vendor.requirementslib import Requirement
|
||||
|
||||
|
||||
def do_uninstall(
|
||||
@@ -43,7 +43,7 @@ def do_uninstall(
|
||||
if not categories:
|
||||
categories = project.get_package_categories(for_lockfile=True)
|
||||
editable_pkgs = [
|
||||
Requirement.from_line(f"-e {p}").name for p in editable_packages if p
|
||||
expansive_install_req_from_line(f"-e {p}").name for p in editable_packages if p
|
||||
]
|
||||
packages += editable_pkgs
|
||||
package_names = {p for p in packages if p}
|
||||
|
||||
+52
-61
@@ -1,19 +1,17 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
|
||||
from pipenv.routines.install import do_sync
|
||||
from pipenv.routines.lock import do_lock
|
||||
from pipenv.routines.outdated import do_outdated
|
||||
from pipenv.utils.dependencies import (
|
||||
convert_deps_to_pip,
|
||||
expansive_install_req_from_line,
|
||||
get_pipfile_category_using_lockfile_section,
|
||||
is_star,
|
||||
pep423_name,
|
||||
)
|
||||
from pipenv.utils.project import ensure_project
|
||||
from pipenv.utils.requirements import add_index_to_pipfile
|
||||
from pipenv.utils.resolver import venv_resolve_deps
|
||||
from pipenv.vendor import click
|
||||
from pipenv.vendor.requirementslib.models.requirements import Requirement
|
||||
|
||||
|
||||
def do_update(
|
||||
@@ -44,15 +42,6 @@ def do_update(
|
||||
site_packages=site_packages,
|
||||
clear=clear,
|
||||
)
|
||||
if not outdated:
|
||||
outdated = bool(dry_run)
|
||||
if outdated:
|
||||
do_outdated(
|
||||
project,
|
||||
clear=clear,
|
||||
pre=pre,
|
||||
pypi_mirror=pypi_mirror,
|
||||
)
|
||||
packages = [p for p in packages if p]
|
||||
editable = [p for p in editable_packages if p]
|
||||
if not packages:
|
||||
@@ -86,6 +75,16 @@ def do_update(
|
||||
lock_only=lock_only,
|
||||
)
|
||||
|
||||
if not outdated:
|
||||
outdated = bool(dry_run)
|
||||
if outdated:
|
||||
do_outdated(
|
||||
project,
|
||||
clear=clear,
|
||||
pre=pre,
|
||||
pypi_mirror=pypi_mirror,
|
||||
)
|
||||
|
||||
do_sync(
|
||||
project,
|
||||
dev=dev,
|
||||
@@ -120,67 +119,59 @@ def upgrade(
|
||||
elif not categories:
|
||||
categories = ["default"]
|
||||
|
||||
package_args = [p for p in packages] + [f"-e {pkg}" for pkg in editable_packages]
|
||||
|
||||
index_name = None
|
||||
if index_url:
|
||||
index_name = add_index_to_pipfile(project, index_url)
|
||||
|
||||
reqs = {}
|
||||
requested_packages = {}
|
||||
for package in package_args[:]:
|
||||
# section = project.packages if not dev else project.dev_packages
|
||||
section = {}
|
||||
package = Requirement.from_line(package)
|
||||
if index_name:
|
||||
package.index = index_name
|
||||
package_name, package_val = package.pipfile_entry
|
||||
package_name = pep423_name(package_name)
|
||||
requested_packages[package_name] = package
|
||||
try:
|
||||
if not is_star(section[package_name]) and is_star(package_val):
|
||||
# Support for VCS dependencies.
|
||||
package_val = convert_deps_to_pip(
|
||||
{package_name: section[package_name]}, project=project
|
||||
)[0]
|
||||
except KeyError:
|
||||
pass
|
||||
reqs[package_name] = package_val
|
||||
package_args = [p for p in packages] + [f"-e {pkg}" for pkg in editable_packages]
|
||||
|
||||
if not reqs:
|
||||
click.echo("Nothing to upgrade!")
|
||||
sys.exit(0)
|
||||
|
||||
# Resolve package to generate constraints of new package data
|
||||
upgrade_lock_data = venv_resolve_deps(
|
||||
reqs,
|
||||
which=project._which,
|
||||
project=project,
|
||||
lockfile={},
|
||||
category="default",
|
||||
pre=pre,
|
||||
allow_global=system,
|
||||
pypi_mirror=pypi_mirror,
|
||||
)
|
||||
if not upgrade_lock_data:
|
||||
click.echo("Nothing to upgrade!")
|
||||
sys.exit(0)
|
||||
|
||||
# Upgrade the relevant packages in the various categories specified
|
||||
requested_install_reqs = defaultdict(dict)
|
||||
requested_packages = defaultdict(dict)
|
||||
for category in categories:
|
||||
pipfile_category = get_pipfile_category_using_lockfile_section(category)
|
||||
|
||||
for package in package_args[:]:
|
||||
install_req = expansive_install_req_from_line(package, expand_env=True)
|
||||
if index_name:
|
||||
install_req.index = index_name
|
||||
name, normalized_name, pipfile_entry = project.generate_package_pipfile_entry(
|
||||
install_req, package, category=pipfile_category
|
||||
)
|
||||
project.add_pipfile_entry_to_pipfile(
|
||||
name, normalized_name, pipfile_entry, category=pipfile_category
|
||||
)
|
||||
requested_packages[pipfile_category][normalized_name] = pipfile_entry
|
||||
requested_install_reqs[pipfile_category][normalized_name] = install_req
|
||||
|
||||
if project.pipfile_exists:
|
||||
packages = project.parsed_pipfile.get(pipfile_category, {})
|
||||
else:
|
||||
packages = project.get_pipfile_section(pipfile_category)
|
||||
for package_name, requirement in requested_packages.items():
|
||||
requested_package = reqs[package_name]
|
||||
|
||||
if not package_args:
|
||||
click.echo("Nothing to upgrade!")
|
||||
sys.exit(0)
|
||||
|
||||
# Resolve package to generate constraints of new package data
|
||||
upgrade_lock_data = venv_resolve_deps(
|
||||
requested_packages[pipfile_category],
|
||||
which=project._which,
|
||||
project=project,
|
||||
lockfile={},
|
||||
category="default",
|
||||
pre=pre,
|
||||
allow_global=system,
|
||||
pypi_mirror=pypi_mirror,
|
||||
)
|
||||
if not upgrade_lock_data:
|
||||
click.echo("Nothing to upgrade!")
|
||||
sys.exit(0)
|
||||
|
||||
for package_name, pipfile_entry in requested_packages[pipfile_category].items():
|
||||
if package_name not in packages:
|
||||
packages.append(package_name, requested_package)
|
||||
packages.append(package_name, pipfile_entry)
|
||||
else:
|
||||
packages[package_name] = requested_package
|
||||
if lock_only is False:
|
||||
project.add_package_to_pipfile(requirement, category=pipfile_category)
|
||||
packages[package_name] = pipfile_entry
|
||||
|
||||
full_lock_resolution = venv_resolve_deps(
|
||||
packages,
|
||||
|
||||
+1
-1
@@ -8,8 +8,8 @@ import sys
|
||||
from pathlib import Path
|
||||
from shutil import get_terminal_size
|
||||
|
||||
from pipenv.utils.shell import temp_environ
|
||||
from pipenv.vendor import shellingham
|
||||
from pipenv.vendor.requirementslib.utils import temp_environ
|
||||
|
||||
ShellDetectionFailure = shellingham.ShellDetectionFailure
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import logging
|
||||
|
||||
from pipenv.patched.pip._vendor.rich.console import Console
|
||||
|
||||
logging.basicConfig(level=logging.ERROR)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
console = Console()
|
||||
err = Console(stderr=True)
|
||||
|
||||
@@ -3,6 +3,38 @@ VCS_LIST = ("git", "svn", "hg", "bzr")
|
||||
SCHEME_LIST = ("http://", "https://", "ftp://", "ftps://", "file://")
|
||||
FALSE_VALUES = ("0", "false", "no", "off")
|
||||
TRUE_VALUES = ("1", "true", "yes", "on")
|
||||
REMOTE_FILE_SCHEMES = [
|
||||
"http",
|
||||
"https",
|
||||
"ftp",
|
||||
]
|
||||
VCS_SCHEMES = [
|
||||
"git+http",
|
||||
"git+https",
|
||||
"git+ssh",
|
||||
"git+git",
|
||||
"hg+http",
|
||||
"hg+https",
|
||||
"hg+ssh",
|
||||
"svn+http",
|
||||
"svn+https",
|
||||
"svn+svn",
|
||||
"bzr+http",
|
||||
"bzr+https",
|
||||
"bzr+ssh",
|
||||
"bzr+sftp",
|
||||
"bzr+ftp",
|
||||
"bzr+lp",
|
||||
]
|
||||
REMOTE_SCHEMES = REMOTE_FILE_SCHEMES + VCS_SCHEMES
|
||||
|
||||
RELEVANT_PROJECT_FILES = (
|
||||
"METADATA",
|
||||
"PKG-INFO",
|
||||
"setup.py",
|
||||
"setup.cfg",
|
||||
"pyproject.toml",
|
||||
)
|
||||
|
||||
|
||||
def is_type_checking():
|
||||
|
||||
+894
-136
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
import errno
|
||||
import os
|
||||
import sys
|
||||
@@ -13,7 +10,7 @@ class RequirementError(Exception):
|
||||
class MissingParameter(Exception):
|
||||
def __init__(self, param):
|
||||
self.message = self.get_message(param)
|
||||
super(MissingParameter, self).__init__(self.message)
|
||||
super().__init__(self.message)
|
||||
|
||||
@classmethod
|
||||
def get_message(cls, param):
|
||||
@@ -25,7 +22,6 @@ class MissingParameter(Exception):
|
||||
|
||||
class FileCorruptException(OSError):
|
||||
def __init__(self, path, *args, **kwargs):
|
||||
path = path
|
||||
backup_path = kwargs.pop("backup_path", None)
|
||||
if not backup_path and args:
|
||||
args = reversed(args)
|
||||
@@ -38,7 +34,7 @@ class FileCorruptException(OSError):
|
||||
if args:
|
||||
args = reversed(args)
|
||||
self.message = self.get_message(path, backup_path=backup_path)
|
||||
super(FileCorruptException, self).__init__(self.message)
|
||||
super().__init__(self.message)
|
||||
|
||||
def get_message(self, path, backup_path=None):
|
||||
message = "ERROR: Failed to load file at %s" % path
|
||||
@@ -46,7 +42,7 @@ class FileCorruptException(OSError):
|
||||
msg = "it will be backed up to %s and removed" % backup_path
|
||||
else:
|
||||
msg = "it will be removed and replaced on the next lock."
|
||||
message = "{0}\nYour lockfile is corrupt, {1}".format(message, msg)
|
||||
message = f"{message}\nYour lockfile is corrupt, {msg}"
|
||||
return message
|
||||
|
||||
def show(self):
|
||||
@@ -56,7 +52,7 @@ class FileCorruptException(OSError):
|
||||
class LockfileCorruptException(FileCorruptException):
|
||||
def __init__(self, path, backup_path=None):
|
||||
self.message = self.get_message(path, backup_path=backup_path)
|
||||
super(LockfileCorruptException, self).__init__(self.message)
|
||||
super().__init__(self.message)
|
||||
|
||||
def get_message(self, path, backup_path=None):
|
||||
message = "ERROR: Failed to load lockfile at %s" % path
|
||||
@@ -64,7 +60,7 @@ class LockfileCorruptException(FileCorruptException):
|
||||
msg = "it will be backed up to %s and removed" % backup_path
|
||||
else:
|
||||
msg = "it will be removed and replaced on the next lock."
|
||||
message = "{0}\nYour lockfile is corrupt, {1}".format(message, msg)
|
||||
message = f"{message}\nYour lockfile is corrupt, {msg}"
|
||||
return message
|
||||
|
||||
def show(self, path, backup_path=None):
|
||||
@@ -74,7 +70,7 @@ class LockfileCorruptException(FileCorruptException):
|
||||
class PipfileCorruptException(FileCorruptException):
|
||||
def __init__(self, path, backup_path=None):
|
||||
self.message = self.get_message(path, backup_path=backup_path)
|
||||
super(PipfileCorruptException, self).__init__(self.message)
|
||||
super().__init__(self.message)
|
||||
|
||||
def get_message(self, path, backup_path=None):
|
||||
message = "ERROR: Failed to load Pipfile at %s" % path
|
||||
@@ -82,7 +78,7 @@ class PipfileCorruptException(FileCorruptException):
|
||||
msg = "it will be backed up to %s and removed" % backup_path
|
||||
else:
|
||||
msg = "it will be removed and replaced on the next lock."
|
||||
message = "{0}\nYour Pipfile is corrupt, {1}".format(message, msg)
|
||||
message = f"{message}\nYour Pipfile is corrupt, {msg}"
|
||||
return message
|
||||
|
||||
def show(self, path, backup_path=None):
|
||||
@@ -93,4 +89,4 @@ class PipfileNotFound(FileNotFoundError):
|
||||
def __init__(self, path, *args, **kwargs):
|
||||
self.errno = errno.ENOENT
|
||||
self.filename = path
|
||||
super(PipfileNotFound, self).__init__(self.filename)
|
||||
super().__init__(self.filename)
|
||||
@@ -1,51 +1,25 @@
|
||||
"""A collection for utilities for working with files and paths."""
|
||||
import atexit
|
||||
import io
|
||||
import os
|
||||
import posixpath
|
||||
import sys
|
||||
import warnings
|
||||
from contextlib import closing, contextmanager
|
||||
from http.client import HTTPResponse as Urllib_HTTPResponse
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
from typing import IO, Any, ContextManager, Iterator, Optional, Text, TypeVar, Union
|
||||
from typing import IO, Any, ContextManager, Optional, TypeVar, Union
|
||||
from urllib import parse as urllib_parse
|
||||
from urllib import request as urllib_request
|
||||
from urllib.parse import quote, urlparse
|
||||
|
||||
from pipenv.patched.pip._vendor.requests import Session
|
||||
from pipenv.patched.pip._vendor.urllib3.response import HTTPResponse as Urllib3_HTTPResponse
|
||||
from pipenv.patched.pip._vendor.urllib3.response import (
|
||||
HTTPResponse as Urllib3_HTTPResponse,
|
||||
)
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
@contextmanager
|
||||
def cd(path):
|
||||
# type: () -> Iterator[None]
|
||||
"""Context manager to temporarily change working directories.
|
||||
|
||||
:param str path: The directory to move into
|
||||
>>> print(os.path.abspath(os.curdir))
|
||||
'/home/user/code/myrepo'
|
||||
>>> with cd("/home/user/code/otherdir/subdir"):
|
||||
... print("Changed directory: %s" % os.path.abspath(os.curdir))
|
||||
Changed directory: /home/user/code/otherdir/subdir
|
||||
>>> print(os.path.abspath(os.curdir))
|
||||
'/home/user/code/myrepo'
|
||||
"""
|
||||
if not path:
|
||||
return
|
||||
prev_cwd = Path.cwd().as_posix()
|
||||
if isinstance(path, Path):
|
||||
path = path.as_posix()
|
||||
os.chdir(str(path))
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
os.chdir(prev_cwd)
|
||||
|
||||
|
||||
def is_file_url(url: Any) -> bool:
|
||||
"""Returns true if the given url is a file url."""
|
||||
if not url:
|
||||
@@ -54,7 +28,7 @@ def is_file_url(url: Any) -> bool:
|
||||
try:
|
||||
url = url.url
|
||||
except AttributeError:
|
||||
raise ValueError("Cannot parse url from unknown type: {!r}".format(url))
|
||||
raise ValueError(f"Cannot parse url from unknown type: {url!r}")
|
||||
return urllib_parse.urlparse(url.lower()).scheme == "file"
|
||||
|
||||
|
||||
@@ -83,7 +57,7 @@ if os.name == "nt":
|
||||
# from click _winconsole.py
|
||||
from ctypes import create_unicode_buffer, windll
|
||||
|
||||
def get_long_path(short_path: Text) -> Text:
|
||||
def get_long_path(short_path: str) -> str:
|
||||
BUFFER_SIZE = 500
|
||||
buffer = create_unicode_buffer(BUFFER_SIZE)
|
||||
get_long_path_name = windll.kernel32.GetLongPathNameW
|
||||
@@ -135,7 +109,7 @@ def path_to_url(path):
|
||||
# XXX: actually part of a surrogate pair, but were just incidentally
|
||||
# XXX: passed in as a piece of a filename
|
||||
quoted_path = quote(path, errors="backslashreplace")
|
||||
return "file:///{}:{}".format(drive, quoted_path)
|
||||
return f"file:///{drive}:{quoted_path}"
|
||||
# XXX: This is also here to help deal with incidental dangling surrogates
|
||||
# XXX: on linux, by making sure they are preserved during encoding so that
|
||||
# XXX: we can urlencode the backslash correctly
|
||||
@@ -160,7 +134,7 @@ def open_file(
|
||||
try:
|
||||
link = link.url_without_fragment
|
||||
except AttributeError:
|
||||
raise ValueError("Cannot parse url from unknown type: {0!r}".format(link))
|
||||
raise ValueError(f"Cannot parse url from unknown type: {link!r}")
|
||||
|
||||
if not is_valid_url(link) and os.path.exists(link):
|
||||
link = path_to_url(link)
|
||||
@@ -169,9 +143,9 @@ def open_file(
|
||||
# Local URL
|
||||
local_path = url_to_path(link)
|
||||
if os.path.isdir(local_path):
|
||||
raise ValueError("Cannot open directory for read: {}".format(link))
|
||||
raise ValueError(f"Cannot open directory for read: {link}")
|
||||
else:
|
||||
with io.open(local_path, "rb") as local_file:
|
||||
with open(local_path, "rb") as local_file:
|
||||
yield local_file
|
||||
else:
|
||||
# Remote URL
|
||||
@@ -264,52 +238,3 @@ def check_for_unc_path(path):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def get_converted_relative_path(path, relative_to=None):
|
||||
"""Convert `path` to be relative.
|
||||
|
||||
Given a vague relative path, return the path relative to the given
|
||||
location.
|
||||
|
||||
:param str path: The location of a target path
|
||||
:param str relative_to: The starting path to build against, optional
|
||||
:returns: A relative posix-style path with a leading `./`
|
||||
|
||||
This performs additional conversion to ensure the result is of POSIX form,
|
||||
and starts with `./`, or is precisely `.`.
|
||||
|
||||
>>> os.chdir('/home/user/code/myrepo/myfolder')
|
||||
>>> vistir.path.get_converted_relative_path('/home/user/code/file.zip')
|
||||
'./../../file.zip'
|
||||
>>> vistir.path.get_converted_relative_path('/home/user/code/myrepo/myfolder/mysubfolder')
|
||||
'./mysubfolder'
|
||||
>>> vistir.path.get_converted_relative_path('/home/user/code/myrepo/myfolder')
|
||||
'.'
|
||||
"""
|
||||
if not relative_to:
|
||||
relative_to = os.getcwd()
|
||||
|
||||
start_path = Path(str(relative_to))
|
||||
try:
|
||||
start = start_path.resolve()
|
||||
except OSError:
|
||||
start = start_path.absolute()
|
||||
|
||||
# check if there is a drive letter or mount point
|
||||
# if it is a mountpoint use the original absolute path
|
||||
# instead of the unc path
|
||||
if check_for_unc_path(start):
|
||||
start = start_path.absolute()
|
||||
|
||||
path = start.joinpath(str(path)).relative_to(start)
|
||||
|
||||
# check and see if the path that was passed into the function is a UNC path
|
||||
# and raise value error if it is not.
|
||||
if check_for_unc_path(path):
|
||||
raise ValueError("The path argument does not currently accept UNC paths")
|
||||
|
||||
relpath_s = posixpath.normpath(path.as_posix())
|
||||
if not (relpath_s == "." or relpath_s.startswith("./")):
|
||||
relpath_s = posixpath.join(".", relpath_s)
|
||||
return relpath_s
|
||||
+291
-6
@@ -1,13 +1,19 @@
|
||||
"""
|
||||
A small collection of useful functional tools for working with iterables.
|
||||
|
||||
This module should be in requirementslib. Once we release a new version of requirementslib
|
||||
we can remove this file and use the one in requirementslib.
|
||||
"""
|
||||
import errno
|
||||
import locale
|
||||
import os
|
||||
import stat
|
||||
import subprocess
|
||||
import time
|
||||
import warnings
|
||||
from functools import partial
|
||||
from itertools import islice, tee
|
||||
from itertools import count, islice, tee
|
||||
from typing import Any, Iterable
|
||||
|
||||
DIRECTORY_CLEANUP_TIMEOUT = 1.0
|
||||
|
||||
|
||||
def _is_iterable(elem: Any) -> bool:
|
||||
if getattr(elem, "__iter__", False) or isinstance(elem, Iterable):
|
||||
@@ -35,7 +41,6 @@ def chunked(n: int, iterable: Iterable) -> Iterable:
|
||||
|
||||
|
||||
def unnest(elem: Iterable) -> Any:
|
||||
# type: (Iterable) -> Any
|
||||
"""Flatten an arbitrarily nested iterable.
|
||||
|
||||
:param elem: An iterable to flatten
|
||||
@@ -73,8 +78,288 @@ def unnest(elem: Iterable) -> Any:
|
||||
|
||||
|
||||
def dedup(iterable: Iterable) -> Iterable:
|
||||
# type: (Iterable) -> Iterable
|
||||
"""Deduplicate an iterable object like iter(set(iterable)) but order-
|
||||
preserved."""
|
||||
|
||||
return iter(dict.fromkeys(iterable))
|
||||
|
||||
|
||||
def is_readonly_path(fn: os.PathLike) -> bool:
|
||||
"""check if a provided path exists and is readonly.
|
||||
|
||||
permissions check is `bool(path.stat & stat.s_iread)` or `not
|
||||
os.access(path, os.w_ok)`
|
||||
"""
|
||||
if os.path.exists(fn):
|
||||
file_stat = os.stat(fn).st_mode
|
||||
return not bool(file_stat & stat.s_iwrite) or not os.access(fn, os.w_ok)
|
||||
return False
|
||||
|
||||
|
||||
def _wait_for_files(path): # pragma: no cover
|
||||
"""Retry with backoff up to 1 second to delete files from a directory.
|
||||
|
||||
:param str path: The path to crawl to delete files from
|
||||
:return: A list of remaining paths or None
|
||||
:rtype: Optional[List[str]]
|
||||
"""
|
||||
timeout = 0.001 # noqa:S101
|
||||
remaining = []
|
||||
while timeout < DIRECTORY_CLEANUP_TIMEOUT:
|
||||
remaining = []
|
||||
if os.path.isdir(path):
|
||||
L = os.listdir(path)
|
||||
for target in L:
|
||||
_remaining = _wait_for_files(target)
|
||||
if _remaining:
|
||||
remaining.extend(_remaining)
|
||||
continue
|
||||
try:
|
||||
os.unlink(path)
|
||||
except FileNotFoundError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
return
|
||||
except (OSError, PermissionError): # noqa:B014
|
||||
time.sleep(timeout)
|
||||
timeout *= 2
|
||||
remaining.append(path)
|
||||
else:
|
||||
return
|
||||
return remaining
|
||||
|
||||
|
||||
def _walk_for_powershell(directory):
|
||||
for _, dirs, files in os.walk(directory):
|
||||
powershell = next(
|
||||
iter(fn for fn in files if fn.lower() == "powershell.exe"), None
|
||||
)
|
||||
if powershell is not None:
|
||||
return os.path.join(directory, powershell)
|
||||
for subdir in dirs:
|
||||
powershell = _walk_for_powershell(os.path.join(directory, subdir))
|
||||
if powershell:
|
||||
return powershell
|
||||
return None
|
||||
|
||||
|
||||
def _get_powershell_path():
|
||||
paths = [
|
||||
os.path.expandvars(r"%windir%\{0}\WindowsPowerShell").format(subdir)
|
||||
for subdir in ("SysWOW64", "system32")
|
||||
]
|
||||
powershell_path = next(iter(_walk_for_powershell(pth) for pth in paths), None)
|
||||
if not powershell_path:
|
||||
powershell_path = subprocess.run(["where", "powershell"])
|
||||
if powershell_path.stdout:
|
||||
return powershell_path.stdout.strip()
|
||||
|
||||
|
||||
def _get_sid_with_powershell():
|
||||
powershell_path = _get_powershell_path()
|
||||
if not powershell_path:
|
||||
return None
|
||||
args = [
|
||||
powershell_path,
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-Command",
|
||||
"Invoke-Expression '[System.Security.Principal.WindowsIdentity]::GetCurrent().user | Write-Host'",
|
||||
]
|
||||
sid = subprocess.run(args, capture_output=True)
|
||||
return sid.stdout.strip()
|
||||
|
||||
|
||||
def get_value_from_tuple(value, value_type):
|
||||
try:
|
||||
import winreg
|
||||
except ImportError:
|
||||
import _winreg as winreg
|
||||
if value_type in (winreg.REG_SZ, winreg.REG_EXPAND_SZ):
|
||||
if "\0" in value:
|
||||
return value[: value.index("\0")]
|
||||
return value
|
||||
return None
|
||||
|
||||
|
||||
def query_registry_value(root, key_name, value):
|
||||
try:
|
||||
import winreg
|
||||
except ImportError:
|
||||
import _winreg as winreg
|
||||
try:
|
||||
with winreg.OpenKeyEx(root, key_name, 0, winreg.KEY_READ) as key:
|
||||
return get_value_from_tuple(*winreg.QueryValueEx(key, value))
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
|
||||
def _get_sid_from_registry():
|
||||
try:
|
||||
import winreg
|
||||
except ImportError:
|
||||
import _winreg as winreg
|
||||
var_names = ("%USERPROFILE%", "%HOME%")
|
||||
current_user_home = next(iter(os.path.expandvars(v) for v in var_names if v), None)
|
||||
root, subkey = (
|
||||
winreg.HKEY_LOCAL_MACHINE,
|
||||
r"Software\Microsoft\Windows NT\CurrentVersion\ProfileList",
|
||||
)
|
||||
subkey_names = []
|
||||
value = None
|
||||
matching_key = None
|
||||
try:
|
||||
with winreg.OpenKeyEx(root, subkey, 0, winreg.KEY_READ) as key:
|
||||
for i in count():
|
||||
key_name = winreg.EnumKey(key, i)
|
||||
subkey_names.append(key_name)
|
||||
value = query_registry_value(
|
||||
root, rf"{subkey}\{key_name}", "ProfileImagePath"
|
||||
)
|
||||
if value and value.lower() == current_user_home.lower():
|
||||
matching_key = key_name
|
||||
break
|
||||
except OSError:
|
||||
pass
|
||||
if matching_key is not None:
|
||||
return matching_key
|
||||
|
||||
|
||||
def _get_current_user():
|
||||
fns = (_get_sid_from_registry, _get_sid_with_powershell)
|
||||
for fn in fns:
|
||||
result = fn()
|
||||
if result:
|
||||
return result
|
||||
return None
|
||||
|
||||
|
||||
def _find_icacls_exe():
|
||||
if os.name == "nt":
|
||||
paths = [
|
||||
os.path.expandvars(r"%windir%\{0}").format(subdir)
|
||||
for subdir in ("system32", "SysWOW64")
|
||||
]
|
||||
for path in paths:
|
||||
icacls_path = next(
|
||||
iter(fn for fn in os.listdir(path) if fn.lower() == "icacls.exe"), None
|
||||
)
|
||||
if icacls_path is not None:
|
||||
icacls_path = os.path.join(path, icacls_path)
|
||||
return icacls_path
|
||||
return None
|
||||
|
||||
|
||||
def set_write_bit(fn: str) -> None:
|
||||
"""Set read-write permissions for the current user on the target path. Fail
|
||||
silently if the path doesn't exist.
|
||||
|
||||
:param str fn: The target filename or path
|
||||
:return: None
|
||||
"""
|
||||
if not os.path.exists(fn):
|
||||
return
|
||||
file_stat = os.stat(fn).st_mode
|
||||
os.chmod(fn, file_stat | stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
|
||||
if os.name == "nt":
|
||||
user_sid = _get_current_user()
|
||||
icacls_exe = _find_icacls_exe() or "icacls"
|
||||
|
||||
if user_sid:
|
||||
c = subprocess.run(
|
||||
[
|
||||
icacls_exe,
|
||||
f"''{fn}''",
|
||||
"/grant",
|
||||
f"{user_sid}:WD",
|
||||
"/T",
|
||||
"/C",
|
||||
"/Q",
|
||||
],
|
||||
capture_output=True,
|
||||
# 2020-06-12 Yukihiko Shinoda
|
||||
# There are 3 way to get system default encoding in Stack Overflow.
|
||||
# see: https://stackoverflow.com/questions/37506535/how-to-get-the-system-default-encoding-in-python-2-x
|
||||
# I investigated these way by using Shift-JIS Windows.
|
||||
# >>> import locale
|
||||
# >>> locale.getpreferredencoding()
|
||||
# "cp932" (Shift-JIS)
|
||||
# >>> import sys
|
||||
# >>> sys.getdefaultencoding()
|
||||
# "utf-8"
|
||||
# >>> sys.stdout.encoding
|
||||
# "UTF8"
|
||||
encoding=locale.getpreferredencoding(),
|
||||
)
|
||||
if not c.err and c.returncode == 0:
|
||||
return
|
||||
|
||||
if not os.path.isdir(fn):
|
||||
for path in [fn, os.path.dirname(fn)]:
|
||||
try:
|
||||
os.chflags(path, 0)
|
||||
except AttributeError:
|
||||
pass
|
||||
return None
|
||||
for root, dirs, files in os.walk(fn, topdown=False):
|
||||
for dir_ in [os.path.join(root, d) for d in dirs]:
|
||||
set_write_bit(dir_)
|
||||
for file_ in [os.path.join(root, f) for f in files]:
|
||||
set_write_bit(file_)
|
||||
|
||||
|
||||
def handle_remove_readonly(func, path, exc):
|
||||
"""Error handler for shutil.rmtree.
|
||||
|
||||
Windows source repo folders are read-only by default, so this error handler
|
||||
attempts to set them as writeable and then proceed with deletion.
|
||||
|
||||
:param function func: The caller function
|
||||
:param str path: The target path for removal
|
||||
:param Exception exc: The raised exception
|
||||
|
||||
This function will call check :func:`is_readonly_path` before attempting to call
|
||||
:func:`set_write_bit` on the target path and try again.
|
||||
"""
|
||||
|
||||
PERM_ERRORS = (errno.EACCES, errno.EPERM, errno.ENOENT)
|
||||
default_warning_message = "Unable to remove file due to permissions restriction: {!r}"
|
||||
# split the initial exception out into its type, exception, and traceback
|
||||
exc_type, exc_exception, exc_tb = exc
|
||||
if is_readonly_path(path):
|
||||
# Apply write permission and call original function
|
||||
set_write_bit(path)
|
||||
try:
|
||||
func(path)
|
||||
except (OSError, FileNotFoundError, PermissionError) as e: # pragma: no cover
|
||||
if e.errno in PERM_ERRORS:
|
||||
if e.errno == errno.ENOENT:
|
||||
return
|
||||
remaining = None
|
||||
if os.path.isdir(path):
|
||||
remaining = _wait_for_files(path)
|
||||
if remaining:
|
||||
warnings.warn(
|
||||
default_warning_message.format(path),
|
||||
ResourceWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
func(path, ignore_errors=True)
|
||||
return
|
||||
|
||||
if exc_exception.errno in PERM_ERRORS:
|
||||
set_write_bit(path)
|
||||
remaining = _wait_for_files(path)
|
||||
try:
|
||||
func(path)
|
||||
except (OSError, FileNotFoundError, PermissionError) as e: # noqa:B014
|
||||
if e.errno in PERM_ERRORS:
|
||||
if e.errno != errno.ENOENT: # File still exists
|
||||
warnings.warn(
|
||||
default_warning_message.format(path),
|
||||
ResourceWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return
|
||||
else:
|
||||
raise exc_exception
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
import re
|
||||
from html.parser import HTMLParser
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from pipenv.patched.pip._vendor import requests
|
||||
@@ -7,7 +8,7 @@ from pipenv.patched.pip._vendor.requests.adapters import HTTPAdapter
|
||||
from pipenv.patched.pip._vendor.urllib3 import util as urllib3_util
|
||||
|
||||
|
||||
def _get_requests_session(max_retries=1, verify_ssl=True):
|
||||
def get_requests_session(max_retries=1, verify_ssl=True):
|
||||
"""Load requests lazily."""
|
||||
pip_client_cert = os.environ.get("PIP_CLIENT_CERT")
|
||||
requests_session = requests.Session()
|
||||
@@ -46,7 +47,7 @@ def create_mirror_source(url, name):
|
||||
|
||||
def download_file(url, filename, max_retries=1):
|
||||
"""Downloads file from url to a path with filename"""
|
||||
r = _get_requests_session(max_retries).get(url, stream=True)
|
||||
r = get_requests_session(max_retries).get(url, stream=True)
|
||||
r.close()
|
||||
if not r.ok:
|
||||
raise OSError("Unable to download file")
|
||||
@@ -116,7 +117,7 @@ def is_url_equal(url: str, other_url: str) -> bool:
|
||||
def proper_case(package_name):
|
||||
"""Properly case project name from pypi.org."""
|
||||
# Hit the simple API.
|
||||
r = _get_requests_session().get(
|
||||
r = get_requests_session().get(
|
||||
f"https://pypi.org/pypi/{package_name}/json", timeout=0.3, stream=True
|
||||
)
|
||||
r.close()
|
||||
@@ -128,3 +129,17 @@ def proper_case(package_name):
|
||||
good_name = match.group(1)
|
||||
|
||||
return good_name
|
||||
|
||||
|
||||
class PackageIndexHTMLParser(HTMLParser):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.urls = []
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
# If tag is an anchor
|
||||
if tag == "a":
|
||||
# find href attribute
|
||||
for attr in attrs:
|
||||
if attr[0] == "href":
|
||||
self.urls.append(attr[1])
|
||||
|
||||
+354
-57
@@ -1,93 +1,157 @@
|
||||
import copy
|
||||
import itertools
|
||||
import os
|
||||
import stat
|
||||
from contextlib import contextmanager
|
||||
from json import JSONDecodeError
|
||||
from pathlib import Path
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import Mapping
|
||||
from typing import Dict, Iterator, List, Optional
|
||||
|
||||
from .dependencies import clean_resolved_dep, pep423_name, translate_markers
|
||||
from pipenv.patched.pip._internal.req.req_install import InstallRequirement
|
||||
from pipenv.utils.dependencies import (
|
||||
clean_resolved_dep,
|
||||
determine_vcs_revision_hash,
|
||||
expansive_install_req_from_line,
|
||||
pep423_name,
|
||||
translate_markers,
|
||||
)
|
||||
from pipenv.utils.exceptions import (
|
||||
LockfileCorruptException,
|
||||
MissingParameter,
|
||||
PipfileNotFound,
|
||||
)
|
||||
from pipenv.utils.pipfile import DEFAULT_NEWLINES, ProjectFile
|
||||
from pipenv.utils.requirements import normalize_name
|
||||
from pipenv.utils.requirementslib import is_editable, is_vcs, merge_items
|
||||
from pipenv.vendor.plette import lockfiles
|
||||
from pipenv.vendor.pydantic import BaseModel, Field
|
||||
|
||||
|
||||
def format_requirement_for_lockfile(req, markers_lookup, index_lookup, hashes=None):
|
||||
if req.specifiers:
|
||||
version = str(req.get_version)
|
||||
def merge_markers(entry, markers):
|
||||
if not isinstance(markers, list):
|
||||
markers = [markers]
|
||||
for marker in markers:
|
||||
if not isinstance(marker, str):
|
||||
marker = str(marker)
|
||||
if "markers" not in entry:
|
||||
entry["markers"] = marker
|
||||
elif marker not in entry["markers"]:
|
||||
entry["markers"] = f"({entry['markers']}) and ({marker})"
|
||||
|
||||
|
||||
def format_requirement_for_lockfile(
|
||||
req: InstallRequirement,
|
||||
markers_lookup,
|
||||
index_lookup,
|
||||
original_deps,
|
||||
pipfile_entries,
|
||||
hashes=None,
|
||||
):
|
||||
if req.specifier:
|
||||
version = str(req.specifier)
|
||||
else:
|
||||
version = None
|
||||
index = index_lookup.get(req.normalized_name)
|
||||
markers = markers_lookup.get(req.normalized_name)
|
||||
name = normalize_name(req.name)
|
||||
index = index_lookup.get(name)
|
||||
markers = req.markers
|
||||
req.index = index
|
||||
name, pf_entry = req.pipfile_entry
|
||||
name = pep423_name(req.name)
|
||||
pipfile_entry = pipfile_entries[name] if name in pipfile_entries else {}
|
||||
entry = {}
|
||||
if isinstance(pf_entry, str):
|
||||
entry["version"] = pf_entry.lstrip("=")
|
||||
else:
|
||||
entry.update(pf_entry)
|
||||
if version is not None and not req.is_vcs:
|
||||
entry["version"] = version
|
||||
if req.line_instance.is_direct_url and not req.is_vcs:
|
||||
entry["file"] = req.req.uri
|
||||
if req.link and req.link.is_vcs:
|
||||
vcs = req.link.scheme.split("+", 1)[0]
|
||||
entry["ref"] = determine_vcs_revision_hash(req, vcs, pipfile_entry.get("ref"))
|
||||
if name in original_deps:
|
||||
entry[vcs] = original_deps[name]
|
||||
else:
|
||||
entry[vcs] = req.link.url
|
||||
if req.req:
|
||||
entry["version"] = str(req.specifier)
|
||||
elif version:
|
||||
entry["version"] = version
|
||||
elif req.link and req.link.is_file:
|
||||
entry["file"] = req.link.url
|
||||
if hashes:
|
||||
entry["hashes"] = sorted(set(hashes))
|
||||
entry["name"] = name
|
||||
if index:
|
||||
entry.update({"index": index})
|
||||
if markers:
|
||||
entry.update({"markers": markers})
|
||||
entry.update({"markers": str(markers)})
|
||||
if name in markers_lookup:
|
||||
merge_markers(entry, markers_lookup[name])
|
||||
if isinstance(pipfile_entry, dict) and "markers" in pipfile_entry:
|
||||
merge_markers(entry, pipfile_entry["markers"])
|
||||
if isinstance(pipfile_entry, dict) and "os_name" in pipfile_entry:
|
||||
merge_markers(entry, f"os_name {pipfile_entry['os_name']}")
|
||||
entry = translate_markers(entry)
|
||||
if req.vcs or req.editable:
|
||||
for key in ("index", "version", "file"):
|
||||
try:
|
||||
del entry[key]
|
||||
except KeyError:
|
||||
pass
|
||||
if req.extras:
|
||||
entry["extras"] = sorted(req.extras)
|
||||
if isinstance(pipfile_entry, dict) and pipfile_entry.get("file"):
|
||||
entry["file"] = pipfile_entry["file"]
|
||||
entry["editable"] = True
|
||||
entry.pop("version", None)
|
||||
entry.pop("index", None)
|
||||
elif isinstance(pipfile_entry, dict) and pipfile_entry.get("path"):
|
||||
entry["path"] = pipfile_entry["path"]
|
||||
entry["editable"] = True
|
||||
entry.pop("version", None)
|
||||
entry.pop("index", None)
|
||||
return name, entry
|
||||
|
||||
|
||||
def get_locked_dep(dep, pipfile_section):
|
||||
entry = None
|
||||
cleaner_kwargs = {"is_top_level": False, "pipfile_entry": None}
|
||||
if isinstance(dep, Mapping) and dep.get("name"):
|
||||
def get_locked_dep(project, dep, pipfile_section, current_entry=None):
|
||||
# initialize default values
|
||||
is_top_level = False
|
||||
|
||||
# if the dependency has a name, find corresponding entry in pipfile
|
||||
if isinstance(dep, dict) and dep.get("name"):
|
||||
dep_name = pep423_name(dep["name"])
|
||||
for pipfile_key, pipfile_entry in pipfile_section.items():
|
||||
if pep423_name(pipfile_key) == dep_name:
|
||||
entry = pipfile_entry
|
||||
if pep423_name(pipfile_key) == dep_name or pipfile_key == dep_name:
|
||||
is_top_level = True
|
||||
if isinstance(pipfile_entry, dict):
|
||||
if pipfile_entry.get("version"):
|
||||
pipfile_entry.pop("version")
|
||||
if pipfile_entry.get("ref"):
|
||||
pipfile_entry.pop("ref")
|
||||
dep.update(pipfile_entry)
|
||||
break
|
||||
|
||||
if entry:
|
||||
cleaner_kwargs.update({"is_top_level": True, "pipfile_entry": entry})
|
||||
lockfile_entry = clean_resolved_dep(dep, **cleaner_kwargs)
|
||||
if entry and isinstance(entry, Mapping):
|
||||
version = entry.get("version", "") if entry else ""
|
||||
else:
|
||||
version = entry if entry else ""
|
||||
# clean the dependency
|
||||
lockfile_entry = clean_resolved_dep(project, dep, is_top_level, current_entry)
|
||||
|
||||
# get the lockfile version and compare with pipfile version
|
||||
lockfile_name, lockfile_dict = lockfile_entry.copy().popitem()
|
||||
lockfile_version = lockfile_dict.get("version", "")
|
||||
# Keep pins from the lockfile
|
||||
if lockfile_version != version and version.startswith("==") and "*" not in version:
|
||||
lockfile_dict["version"] = version
|
||||
lockfile_entry[lockfile_name] = lockfile_dict
|
||||
|
||||
return lockfile_entry
|
||||
|
||||
|
||||
def prepare_lockfile(results, pipfile, lockfile):
|
||||
# from .vendor.requirementslib.utils import is_vcs
|
||||
def prepare_lockfile(project, results, pipfile, lockfile_section, old_lock_data=None):
|
||||
for dep in results:
|
||||
if not dep:
|
||||
continue
|
||||
# Merge in any relevant information from the pipfile entry, including
|
||||
# markers, normalized names, URL info, etc that we may have dropped during lock
|
||||
# if not is_vcs(dep):
|
||||
lockfile_entry = get_locked_dep(dep, pipfile)
|
||||
name = next(iter(k for k in lockfile_entry.keys()))
|
||||
current_entry = lockfile.get(name)
|
||||
if current_entry:
|
||||
if not isinstance(current_entry, Mapping):
|
||||
lockfile[name] = lockfile_entry[name]
|
||||
else:
|
||||
lockfile[name].update(lockfile_entry[name])
|
||||
lockfile[name] = translate_markers(lockfile[name])
|
||||
dep_name = dep["name"]
|
||||
current_entry = None
|
||||
if dep_name in old_lock_data:
|
||||
current_entry = old_lock_data[dep_name]
|
||||
lockfile_entry = get_locked_dep(project, dep, pipfile, current_entry)
|
||||
|
||||
# If the current dependency doesn't exist in the lockfile, add it
|
||||
if dep_name not in lockfile_section:
|
||||
lockfile_section[dep_name] = lockfile_entry[dep_name]
|
||||
else:
|
||||
lockfile[name] = lockfile_entry[name]
|
||||
return lockfile
|
||||
# If the dependency exists, update the details
|
||||
current_entry = lockfile_section[dep_name]
|
||||
if not isinstance(current_entry, dict):
|
||||
lockfile_section[dep_name] = lockfile_entry[dep_name]
|
||||
else:
|
||||
# If the current entry is a dict, merge the new details
|
||||
lockfile_section[dep_name].update(lockfile_entry[dep_name])
|
||||
lockfile_section[dep_name] = translate_markers(lockfile_section[dep_name])
|
||||
|
||||
return lockfile_section
|
||||
|
||||
|
||||
@contextmanager
|
||||
@@ -170,3 +234,236 @@ def atomic_open_for_write(target, binary=False, newline=None, encoding=None) ->
|
||||
except OSError:
|
||||
pass
|
||||
os.rename(f.name, target) # No os.replace() on Python 2.
|
||||
|
||||
|
||||
class Lockfile(BaseModel):
|
||||
path: Path = Field(
|
||||
default_factory=lambda: Path(os.curdir).joinpath("Pipfile.lock").absolute()
|
||||
)
|
||||
_requirements: Optional[list] = Field(default_factory=list)
|
||||
_dev_requirements: Optional[list] = Field(default_factory=list)
|
||||
projectfile: ProjectFile = None
|
||||
lockfile: lockfiles.Lockfile
|
||||
newlines: str = DEFAULT_NEWLINES
|
||||
|
||||
class Config:
|
||||
validate_assignment = True
|
||||
arbitrary_types_allowed = True
|
||||
allow_mutation = True
|
||||
include_private_attributes = True
|
||||
# keep_untouched = (cached_property,)
|
||||
|
||||
@property
|
||||
def section_keys(self):
|
||||
return set(self.lockfile.keys()) - {"_meta"}
|
||||
|
||||
@property
|
||||
def extended_keys(self):
|
||||
return [k for k in itertools.product(self.section_keys, ["", "vcs", "editable"])]
|
||||
|
||||
def get(self, k):
|
||||
return self.__getitem__(k)
|
||||
|
||||
def __contains__(self, k):
|
||||
check_lockfile = k in self.extended_keys or self.lockfile.__contains__(k)
|
||||
if check_lockfile:
|
||||
return True
|
||||
return super().__contains__(k)
|
||||
|
||||
def __setitem__(self, k, v):
|
||||
lockfile = self.lockfile
|
||||
lockfile.__setitem__(k, v)
|
||||
|
||||
def __getitem__(self, k, *args, **kwargs):
|
||||
retval = None
|
||||
lockfile = self.lockfile
|
||||
try:
|
||||
retval = lockfile[k]
|
||||
except KeyError:
|
||||
if "-" in k:
|
||||
section, _, pkg_type = k.rpartition("-")
|
||||
vals = getattr(lockfile.get(section, {}), "_data", {})
|
||||
if pkg_type == "vcs":
|
||||
retval = {k: v for k, v in vals.items() if is_vcs(v)}
|
||||
elif pkg_type == "editable":
|
||||
retval = {k: v for k, v in vals.items() if is_editable(v)}
|
||||
if retval is None:
|
||||
raise
|
||||
else:
|
||||
retval = getattr(retval, "_data", retval)
|
||||
return retval
|
||||
|
||||
def __getattr__(self, k, *args, **kwargs):
|
||||
lockfile = self.lockfile
|
||||
try:
|
||||
return super().__getattribute__(k)
|
||||
except AttributeError:
|
||||
retval = getattr(lockfile, k, None)
|
||||
if retval is not None:
|
||||
return retval
|
||||
return super().__getattribute__(k, *args, **kwargs)
|
||||
|
||||
def get_deps(self, dev=False, only=True):
|
||||
deps = {}
|
||||
if dev:
|
||||
deps.update(self.develop._data)
|
||||
if only:
|
||||
return deps
|
||||
deps = merge_items([deps, self.default._data])
|
||||
return deps
|
||||
|
||||
@classmethod
|
||||
def read_projectfile(cls, path):
|
||||
pf = ProjectFile.read(path, lockfiles.Lockfile, invalid_ok=True)
|
||||
return pf
|
||||
|
||||
@classmethod
|
||||
def lockfile_from_pipfile(cls, pipfile_path):
|
||||
from pipenv.utils.pipfile import Pipfile
|
||||
|
||||
if os.path.isfile(pipfile_path):
|
||||
if not os.path.isabs(pipfile_path):
|
||||
pipfile_path = os.path.abspath(pipfile_path)
|
||||
pipfile = Pipfile.load(os.path.dirname(pipfile_path))
|
||||
return lockfiles.Lockfile.with_meta_from(pipfile.pipfile)
|
||||
raise PipfileNotFound(pipfile_path)
|
||||
|
||||
@classmethod
|
||||
def load_projectfile(
|
||||
cls, path: Optional[str] = None, create: bool = True, data: Optional[Dict] = None
|
||||
) -> "ProjectFile":
|
||||
if not path:
|
||||
path = os.curdir
|
||||
path = Path(path).absolute()
|
||||
project_path = path if path.is_dir() else path.parent
|
||||
lockfile_path = path if path.is_file() else project_path / "Pipfile.lock"
|
||||
if not project_path.exists():
|
||||
raise OSError(f"Project does not exist: {project_path.as_posix()}")
|
||||
elif not lockfile_path.exists() and not create:
|
||||
raise FileNotFoundError(
|
||||
f"Lockfile does not exist: {lockfile_path.as_posix()}"
|
||||
)
|
||||
projectfile = cls.read_projectfile(lockfile_path.as_posix())
|
||||
if not lockfile_path.exists():
|
||||
if not data:
|
||||
pipfile = project_path.joinpath("Pipfile")
|
||||
lf = cls.lockfile_from_pipfile(pipfile)
|
||||
else:
|
||||
lf = lockfiles.Lockfile(data)
|
||||
projectfile.model = lf
|
||||
else:
|
||||
if data:
|
||||
raise ValueError("Cannot pass data when loading existing lockfile")
|
||||
with open(lockfile_path.as_posix()) as f:
|
||||
projectfile.model = lockfiles.Lockfile.load(f)
|
||||
return projectfile
|
||||
|
||||
@classmethod
|
||||
def from_data(
|
||||
cls, path: Optional[str], data: Optional[Dict], meta_from_project: bool = True
|
||||
) -> "Lockfile":
|
||||
if path is None:
|
||||
raise MissingParameter("path")
|
||||
if data is None:
|
||||
raise MissingParameter("data")
|
||||
if not isinstance(data, dict):
|
||||
raise TypeError("Expecting a dictionary for parameter 'data'")
|
||||
path = os.path.abspath(str(path))
|
||||
if os.path.isdir(path):
|
||||
project_path = path
|
||||
elif not os.path.isdir(path) and os.path.isdir(os.path.dirname(path)):
|
||||
project_path = os.path.dirname(path)
|
||||
pipfile_path = os.path.join(project_path, "Pipfile")
|
||||
lockfile_path = os.path.join(project_path, "Pipfile.lock")
|
||||
if meta_from_project:
|
||||
lockfile = cls.lockfile_from_pipfile(pipfile_path)
|
||||
lockfile.update(data)
|
||||
else:
|
||||
lockfile = lockfiles.Lockfile(data)
|
||||
projectfile = ProjectFile(
|
||||
line_ending=DEFAULT_NEWLINES, location=lockfile_path, model=lockfile
|
||||
)
|
||||
return cls(
|
||||
projectfile=projectfile,
|
||||
lockfile=lockfile,
|
||||
newlines=projectfile.line_ending,
|
||||
path=Path(projectfile.location),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def load(cls, path: Optional[str], create: bool = True) -> "Lockfile":
|
||||
try:
|
||||
projectfile = cls.load_projectfile(path, create=create)
|
||||
except JSONDecodeError:
|
||||
path = os.path.abspath(path)
|
||||
path = Path(
|
||||
os.path.join(path, "Pipfile.lock") if os.path.isdir(path) else path
|
||||
)
|
||||
formatted_path = path.as_posix()
|
||||
backup_path = f"{formatted_path}.bak"
|
||||
LockfileCorruptException.show(formatted_path, backup_path=backup_path)
|
||||
path.rename(backup_path)
|
||||
cls.load(formatted_path, create=True)
|
||||
lockfile_path = Path(projectfile.location)
|
||||
creation_args = {
|
||||
"projectfile": projectfile,
|
||||
"lockfile": projectfile.model,
|
||||
"newlines": projectfile.line_ending,
|
||||
"path": lockfile_path,
|
||||
}
|
||||
return cls(**creation_args)
|
||||
|
||||
@classmethod
|
||||
def create(cls, path: Optional[str], create: bool = True) -> "Lockfile":
|
||||
return cls.load(path, create=create)
|
||||
|
||||
def get_section(self, name: str) -> Optional[Dict]:
|
||||
return self.lockfile.get(name)
|
||||
|
||||
@property
|
||||
def develop(self) -> Dict:
|
||||
return self.lockfile.develop
|
||||
|
||||
@property
|
||||
def default(self) -> Dict:
|
||||
return self.lockfile.default
|
||||
|
||||
def get_requirements(
|
||||
self, dev: bool = True, only: bool = False, categories: Optional[List[str]] = None
|
||||
) -> Iterator[InstallRequirement]:
|
||||
from pipenv.utils.requirements import requirement_from_lockfile
|
||||
|
||||
if categories:
|
||||
deps = {}
|
||||
for category in categories:
|
||||
if category == "packages":
|
||||
category = "default"
|
||||
elif category == "dev-packages":
|
||||
category = "develop"
|
||||
try:
|
||||
category_deps = self[category]
|
||||
except KeyError:
|
||||
category_deps = {}
|
||||
self.lockfile[category] = category_deps
|
||||
deps = merge_items([deps, category_deps])
|
||||
else:
|
||||
deps = self.get_deps(dev=dev, only=only)
|
||||
for package_name, package_info in deps.items():
|
||||
pip_line = requirement_from_lockfile(
|
||||
package_name, package_info, include_hashes=False, include_markers=False
|
||||
)
|
||||
pip_line_specified = requirement_from_lockfile(
|
||||
package_name, package_info, include_hashes=True, include_markers=True
|
||||
)
|
||||
yield expansive_install_req_from_line(pip_line), pip_line_specified
|
||||
|
||||
def requirements_list(self, category: str) -> List[Dict]:
|
||||
if self.lockfile.get(category):
|
||||
return [
|
||||
{name: entry._data} for name, entry in self.lockfile[category].items()
|
||||
]
|
||||
return []
|
||||
|
||||
def write(self) -> None:
|
||||
self.projectfile.model = copy.deepcopy(self.lockfile)
|
||||
self.projectfile.write()
|
||||
|
||||
@@ -3,24 +3,23 @@ import operator
|
||||
import re
|
||||
from collections.abc import Mapping, Set
|
||||
from functools import reduce
|
||||
from typing import Any, AnyStr, Iterator, List, Optional, Tuple, Type, Union
|
||||
from typing import Optional
|
||||
|
||||
from pipenv.patched.pip._vendor.distlib import markers
|
||||
from pipenv.patched.pip._vendor.packaging.markers import InvalidMarker, Marker
|
||||
from pipenv.patched.pip._vendor.packaging.specifiers import LegacySpecifier, Specifier, SpecifierSet
|
||||
from pipenv.patched.pip._vendor.packaging.specifiers import (
|
||||
LegacySpecifier,
|
||||
Specifier,
|
||||
SpecifierSet,
|
||||
)
|
||||
from pipenv.vendor.pydantic import BaseModel
|
||||
|
||||
from ..exceptions import RequirementError
|
||||
|
||||
MAX_VERSIONS = {1: 7, 2: 7, 3: 11, 4: 0}
|
||||
DEPRECATED_VERSIONS = ["3.0", "3.1", "3.2", "3.3"]
|
||||
|
||||
|
||||
def is_instance(item, cls):
|
||||
# type: (Any, Type) -> bool
|
||||
if isinstance(item, cls) or item.__class__.__name__ == cls.__name__:
|
||||
return True
|
||||
return False
|
||||
class RequirementError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class PipenvMarkers(BaseModel):
|
||||
@@ -38,9 +37,7 @@ class PipenvMarkers(BaseModel):
|
||||
|
||||
@property
|
||||
def line_part(self):
|
||||
return " and ".join(
|
||||
["{0} {1}".format(k, v) for k, v in self.dict().items() if v is not None]
|
||||
)
|
||||
return " and ".join([f"{k} {v}" for k, v in self.dict().items() if v is not None])
|
||||
|
||||
@property
|
||||
def pipfile_part(self):
|
||||
@@ -67,7 +64,7 @@ class PipenvMarkers(BaseModel):
|
||||
def from_pipfile(cls, name, pipfile):
|
||||
attr_fields = [field_name for field_name in cls.__fields__]
|
||||
found_keys = [k for k in pipfile.keys() if k in attr_fields]
|
||||
marker_strings = ["{0} {1}".format(k, pipfile[k]) for k in found_keys]
|
||||
marker_strings = [f"{k} {pipfile[k]}" for k in found_keys]
|
||||
if pipfile.get("markers"):
|
||||
marker_strings.append(pipfile.get("markers"))
|
||||
markers = set()
|
||||
@@ -82,6 +79,13 @@ class PipenvMarkers(BaseModel):
|
||||
return combined_marker
|
||||
|
||||
|
||||
def is_instance(item, cls):
|
||||
# type: (Any, Type) -> bool
|
||||
if isinstance(item, cls) or item.__class__.__name__ == cls.__name__:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _tuplize_version(version):
|
||||
# type: (str) -> Union[Tuple[()], Tuple[int, ...], Tuple[int, int, str]]
|
||||
output = []
|
||||
@@ -110,7 +114,7 @@ def _format_pyspec(specifier):
|
||||
# type: (Union[str, Specifier]) -> Specifier
|
||||
if isinstance(specifier, str):
|
||||
if not specifier.startswith(tuple(Specifier._operators.keys())):
|
||||
specifier = "=={0}".format(specifier)
|
||||
specifier = f"=={specifier}"
|
||||
specifier = Specifier(specifier)
|
||||
version = getattr(specifier, "version", specifier).rstrip()
|
||||
if version:
|
||||
@@ -121,7 +125,7 @@ def _format_pyspec(specifier):
|
||||
if version.endswith(".*"):
|
||||
version = version[:-2]
|
||||
version = version.rstrip("*")
|
||||
specifier = Specifier("{0}{1}".format(specifier.operator, version))
|
||||
specifier = Specifier(f"{specifier.operator}{version}")
|
||||
try:
|
||||
op = REPLACE_RANGES[specifier.operator]
|
||||
except KeyError:
|
||||
@@ -137,7 +141,7 @@ def _format_pyspec(specifier):
|
||||
next_tuple = (next_tuple[0], curr_tuple[1])
|
||||
else:
|
||||
return specifier
|
||||
specifier = Specifier("{0}{1}".format(op, _format_version(next_tuple)))
|
||||
specifier = Specifier(f"{op}{_format_version(next_tuple)}")
|
||||
return specifier
|
||||
|
||||
|
||||
@@ -214,9 +218,7 @@ def normalize_specifier_set(specs):
|
||||
# And rename it to something meaningful
|
||||
def get_sorted_version_string(version_set):
|
||||
# type: (Set[AnyStr]) -> AnyStr
|
||||
version_list = sorted(
|
||||
"{0}".format(_format_version(version)) for version in version_set
|
||||
)
|
||||
version_list = sorted(f"{_format_version(version)}" for version in version_set)
|
||||
version = ", ".join(version_list)
|
||||
return version
|
||||
|
||||
@@ -495,7 +497,7 @@ def _split_specifierset_str(specset_str, prefix="=="):
|
||||
if prefix == "!=" and any(v in values for v in DEPRECATED_VERSIONS):
|
||||
values += DEPRECATED_VERSIONS[:]
|
||||
for value in sorted(values):
|
||||
specifiers.add(Specifier("{0}{1}".format(prefix, value)))
|
||||
specifiers.add(Specifier(f"{prefix}{value}"))
|
||||
return specifiers
|
||||
|
||||
|
||||
@@ -516,7 +518,7 @@ def _get_specifiers_from_markers(marker_item):
|
||||
elif op.value == "not in":
|
||||
specifiers.update(_split_specifierset_str(value.value, prefix="!="))
|
||||
else:
|
||||
specifiers.add(Specifier("{0}{1}".format(op.value, value.value)))
|
||||
specifiers.add(Specifier(f"{op.value}{value.value}"))
|
||||
elif isinstance(marker_item, list):
|
||||
parts = get_specset(marker_item)
|
||||
if parts:
|
||||
@@ -611,12 +613,17 @@ def _contains_micro_version(version_string):
|
||||
return re.search(r"\d+\.\d+\.\d+", version_string) is not None
|
||||
|
||||
|
||||
def format_pyversion(parts):
|
||||
op, val = parts
|
||||
version_marker = (
|
||||
"python_full_version" if _contains_micro_version(val) else "python_version"
|
||||
)
|
||||
return "{0} {1} '{2}'".format(version_marker, op, val)
|
||||
def merge_markers(m1, m2):
|
||||
# type: (Marker, Marker) -> Optional[Marker]
|
||||
if not all((m1, m2)):
|
||||
return next(iter(v for v in (m1, m2) if v), None)
|
||||
m1 = _ensure_marker(m1)
|
||||
m2 = _ensure_marker(m2)
|
||||
_markers = [] # type: List[Marker]
|
||||
for marker in (m1, m2):
|
||||
_markers.append(str(marker))
|
||||
marker_str = " and ".join([normalize_marker_str(m) for m in _markers if m])
|
||||
return _ensure_marker(normalize_marker_str(marker_str))
|
||||
|
||||
|
||||
def normalize_marker_str(marker) -> str:
|
||||
@@ -632,9 +639,9 @@ def normalize_marker_str(marker) -> str:
|
||||
marker_str = " and ".join([format_pyversion(pv) for pv in parts])
|
||||
if marker:
|
||||
if marker_str:
|
||||
marker_str = "{0!s} and {1!s}".format(marker_str, marker)
|
||||
marker_str = f"{marker_str!s} and {marker!s}"
|
||||
else:
|
||||
marker_str = "{0!s}".format(marker)
|
||||
marker_str = f"{marker!s}"
|
||||
return marker_str.replace('"', "'")
|
||||
|
||||
|
||||
@@ -642,9 +649,9 @@ def marker_from_specifier(spec) -> Marker:
|
||||
if not any(spec.startswith(k) for k in Specifier._operators.keys()):
|
||||
if spec.strip().lower() in ["any", "<any>", "*"]:
|
||||
return None
|
||||
spec = "=={0}".format(spec)
|
||||
spec = f"=={spec}"
|
||||
elif spec.startswith("==") and spec.count("=") > 3:
|
||||
spec = "=={0}".format(spec.lstrip("="))
|
||||
spec = "=={}".format(spec.lstrip("="))
|
||||
if not spec:
|
||||
return None
|
||||
marker_segments = []
|
||||
@@ -654,14 +661,9 @@ def marker_from_specifier(spec) -> Marker:
|
||||
return Marker(marker_str)
|
||||
|
||||
|
||||
def merge_markers(m1, m2):
|
||||
# type: (Marker, Marker) -> Optional[Marker]
|
||||
if not all((m1, m2)):
|
||||
return next(iter(v for v in (m1, m2) if v), None)
|
||||
m1 = _ensure_marker(m1)
|
||||
m2 = _ensure_marker(m2)
|
||||
_markers = [] # type: List[Marker]
|
||||
for marker in (m1, m2):
|
||||
_markers.append(str(marker))
|
||||
marker_str = " and ".join([normalize_marker_str(m) for m in _markers if m])
|
||||
return _ensure_marker(normalize_marker_str(marker_str))
|
||||
def format_pyversion(parts):
|
||||
op, val = parts
|
||||
version_marker = (
|
||||
"python_full_version" if _contains_micro_version(val) else "python_version"
|
||||
)
|
||||
return f"{version_marker} {op} '{val}'"
|
||||
+19
-259
@@ -1,72 +1,14 @@
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
from pipenv.patched.pip._internal.build_env import get_runnable_pip
|
||||
from pipenv.project import Project
|
||||
from pipenv.utils import err
|
||||
from pipenv.utils.dependencies import get_constraints_from_deps, prepare_constraint_file
|
||||
from pipenv.utils.indexes import get_source_list, prepare_pip_source_args
|
||||
from pipenv.utils.fileutils import create_tracked_tempdir, normalize_path
|
||||
from pipenv.utils.indexes import prepare_pip_source_args
|
||||
from pipenv.utils.processes import subprocess_run
|
||||
from pipenv.utils.shell import cmd_list_to_shell, project_python
|
||||
from pipenv.vendor.requirementslib import Requirement
|
||||
from pipenv.vendor.requirementslib.fileutils import create_tracked_tempdir, normalize_path
|
||||
|
||||
|
||||
def format_pip_output(out, r=None):
|
||||
def gen(out):
|
||||
for line in out.split("\n"):
|
||||
# Remove requirements file information from pip9 output.
|
||||
if "(from -r" in line:
|
||||
yield line[: line.index("(from -r")]
|
||||
|
||||
else:
|
||||
yield line
|
||||
|
||||
out = "\n".join([line for line in gen(out)])
|
||||
return out
|
||||
|
||||
|
||||
def format_pip_error(error):
|
||||
error = error.replace("Expected", "[bold green]Expected[/bold green]")
|
||||
error = error.replace("Got", "[bold red]Got[/red bold]")
|
||||
error = error.replace(
|
||||
"THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS FILE",
|
||||
"[bold red]THESE PACKAGES DO NOT MATCH THE HASHES FROM Pipfile.lock![/bold red]",
|
||||
)
|
||||
error = error.replace(
|
||||
"someone may have tampered with them",
|
||||
"[red]someone may have tampered with them[/red]",
|
||||
)
|
||||
error = error.replace("option to pip install", "option to 'pipenv install'")
|
||||
return error
|
||||
|
||||
|
||||
def pip_download(project, package_name):
|
||||
cache_dir = Path(project.s.PIPENV_CACHE_DIR)
|
||||
pip_config = {
|
||||
"PIP_CACHE_DIR": cache_dir.as_posix(),
|
||||
"PIP_WHEEL_DIR": cache_dir.joinpath("wheels").as_posix(),
|
||||
"PIP_DESTINATION_DIR": cache_dir.joinpath("pkgs").as_posix(),
|
||||
}
|
||||
for source in project.sources:
|
||||
cmd = [
|
||||
project_python(project),
|
||||
get_runnable_pip(),
|
||||
"download",
|
||||
package_name,
|
||||
"-i",
|
||||
source["url"],
|
||||
"-d",
|
||||
project.download_location,
|
||||
]
|
||||
c = subprocess_run(cmd, env=pip_config)
|
||||
if c.returncode == 0:
|
||||
break
|
||||
|
||||
return c
|
||||
|
||||
|
||||
def pip_install_deps(
|
||||
@@ -95,56 +37,29 @@ def pip_install_deps(
|
||||
editable_requirements = tempfile.NamedTemporaryFile(
|
||||
prefix="pipenv-", suffix="-reqs.txt", dir=requirements_dir, delete=False
|
||||
)
|
||||
for requirement in deps:
|
||||
ignore_hash = ignore_hashes
|
||||
vcs_or_editable = (
|
||||
requirement.is_vcs
|
||||
or requirement.vcs
|
||||
or requirement.editable
|
||||
or (requirement.is_file_or_url and not requirement.hashes)
|
||||
)
|
||||
if vcs_or_editable:
|
||||
ignore_hash = True
|
||||
if requirement and vcs_or_editable:
|
||||
requirement.index = None
|
||||
|
||||
line = requirement.line_instance.get_line(
|
||||
with_prefix=True,
|
||||
with_hashes=not ignore_hash,
|
||||
with_markers=True,
|
||||
as_list=False,
|
||||
)
|
||||
for pip_line in deps:
|
||||
ignore_hash = ignore_hashes or "--hash" not in pip_line
|
||||
|
||||
if project.s.is_verbose():
|
||||
err.print(f"Writing supplied requirement line to temporary file: {line!r}")
|
||||
target = editable_requirements if vcs_or_editable else standard_requirements
|
||||
target.write(line.encode())
|
||||
err.print(
|
||||
f"Writing supplied requirement line to temporary file: {pip_line!r}"
|
||||
)
|
||||
target = editable_requirements if ignore_hash else standard_requirements
|
||||
target.write(pip_line.encode())
|
||||
target.write(b"\n")
|
||||
|
||||
standard_requirements.close()
|
||||
editable_requirements.close()
|
||||
|
||||
cmds = []
|
||||
files = []
|
||||
standard_deps = list(
|
||||
filter(
|
||||
lambda d: not (
|
||||
d.is_vcs or d.vcs or d.editable or (d.is_file_or_url and not d.hashes)
|
||||
),
|
||||
deps,
|
||||
)
|
||||
)
|
||||
if standard_deps:
|
||||
files.append(standard_requirements)
|
||||
editable_deps = list(
|
||||
filter(
|
||||
lambda d: d.is_vcs
|
||||
or d.vcs
|
||||
or d.editable
|
||||
or (d.is_file_or_url and not d.hashes),
|
||||
deps,
|
||||
)
|
||||
)
|
||||
if editable_deps:
|
||||
files.append(editable_requirements)
|
||||
for pip_line in deps:
|
||||
if "--hash" in pip_line and standard_requirements not in files:
|
||||
files.append(standard_requirements)
|
||||
elif editable_requirements not in files:
|
||||
files.append(editable_requirements)
|
||||
|
||||
for file in files:
|
||||
pip_command = [
|
||||
project_python(project, system=allow_global),
|
||||
@@ -166,10 +81,8 @@ def pip_install_deps(
|
||||
if project.s.is_verbose():
|
||||
msg = f"Install Phase: {'Standard Requirements' if file == standard_requirements else 'Editable Requirements'}"
|
||||
err.print(msg, style="bold")
|
||||
for requirement in (
|
||||
standard_deps if file == standard_requirements else editable_deps
|
||||
):
|
||||
err.print(f"Preparing Installation of {requirement.name!r}", style="bold")
|
||||
for pip_line in deps:
|
||||
err.print(f"Preparing Installation of {pip_line!r}", style="bold")
|
||||
err.print(f"$ {cmd_list_to_shell(pip_command)}", style="cyan")
|
||||
cache_dir = Path(project.s.PIPENV_CACHE_DIR)
|
||||
default_exists_action = "w"
|
||||
@@ -186,10 +99,6 @@ def pip_install_deps(
|
||||
err.print(f"Using source directory: {src_dir!r}")
|
||||
pip_config.update({"PIP_SRC": src_dir})
|
||||
c = subprocess_run(pip_command, block=False, capture_output=True, env=pip_config)
|
||||
if file == standard_requirements:
|
||||
c.deps = standard_deps
|
||||
else:
|
||||
c.deps = editable_deps
|
||||
c.env = pip_config
|
||||
cmds.append(c)
|
||||
if project.s.is_verbose():
|
||||
@@ -204,132 +113,6 @@ def pip_install_deps(
|
||||
return cmds
|
||||
|
||||
|
||||
def pip_install(
|
||||
project,
|
||||
requirement=None,
|
||||
r=None,
|
||||
allow_global=False,
|
||||
ignore_hashes=False,
|
||||
no_deps=False,
|
||||
block=True,
|
||||
index=None,
|
||||
pre=False,
|
||||
dev=False,
|
||||
requirements_dir=None,
|
||||
extra_indexes=None,
|
||||
pypi_mirror=None,
|
||||
use_pep517=True,
|
||||
use_constraint=False,
|
||||
extra_pip_args: Optional[List] = None,
|
||||
):
|
||||
piplogger = logging.getLogger("pipenv.patched.pip._internal.commands.install")
|
||||
trusted_hosts = get_trusted_hosts()
|
||||
if not allow_global:
|
||||
src_dir = os.getenv(
|
||||
"PIP_SRC", os.getenv("PIP_SRC_DIR", project.virtualenv_src_location)
|
||||
)
|
||||
else:
|
||||
src_dir = os.getenv("PIP_SRC", os.getenv("PIP_SRC_DIR"))
|
||||
if requirement:
|
||||
if requirement.editable or not requirement.hashes:
|
||||
ignore_hashes = True
|
||||
elif not (requirement.is_vcs or requirement.editable or requirement.vcs):
|
||||
ignore_hashes = False
|
||||
line = None
|
||||
# Try installing for each source in project.sources.
|
||||
search_all_sources = project.settings.get("install_search_all_sources", False)
|
||||
if not index and requirement.index:
|
||||
index = requirement.index
|
||||
if index and not extra_indexes:
|
||||
if search_all_sources:
|
||||
extra_indexes = list(project.sources)
|
||||
else: # Default: index restrictions apply during installation
|
||||
extra_indexes = []
|
||||
if requirement.index:
|
||||
extra_indexes = list(
|
||||
filter(lambda d: d.get("name") == requirement.index, project.sources)
|
||||
)
|
||||
if not extra_indexes:
|
||||
extra_indexes = list(project.sources)
|
||||
if requirement and requirement.vcs or requirement.editable:
|
||||
requirement.index = None
|
||||
|
||||
r = write_requirement_to_file(
|
||||
project,
|
||||
requirement,
|
||||
requirements_dir=requirements_dir,
|
||||
include_hashes=not ignore_hashes,
|
||||
)
|
||||
sources = get_source_list(
|
||||
project,
|
||||
index,
|
||||
extra_indexes=extra_indexes,
|
||||
trusted_hosts=trusted_hosts,
|
||||
pypi_mirror=pypi_mirror,
|
||||
)
|
||||
source_names = {src.get("name") for src in sources}
|
||||
if not search_all_sources and requirement.index in source_names:
|
||||
sources = list(filter(lambda d: d.get("name") == requirement.index, sources))
|
||||
if r:
|
||||
with open(r) as fh:
|
||||
if "--hash" not in fh.read():
|
||||
ignore_hashes = True
|
||||
if project.s.is_verbose():
|
||||
piplogger.setLevel(logging.WARN)
|
||||
if requirement:
|
||||
err.print(f"Installing {requirement.name!r}", style="bold")
|
||||
|
||||
pip_command = [
|
||||
project_python(project, system=allow_global),
|
||||
get_runnable_pip(),
|
||||
"install",
|
||||
]
|
||||
pip_args = get_pip_args(
|
||||
project,
|
||||
pre=pre,
|
||||
verbose=project.s.is_verbose(),
|
||||
upgrade=True,
|
||||
no_use_pep517=not use_pep517,
|
||||
no_deps=no_deps,
|
||||
require_hashes=not ignore_hashes,
|
||||
extra_pip_args=extra_pip_args,
|
||||
)
|
||||
pip_command.extend(pip_args)
|
||||
if r:
|
||||
pip_command.extend(["-r", normalize_path(r)])
|
||||
elif line:
|
||||
pip_command.extend(line)
|
||||
if dev and use_constraint:
|
||||
default_constraints = get_constraints_from_deps(project.packages)
|
||||
constraint_filename = prepare_constraint_file(
|
||||
default_constraints,
|
||||
directory=requirements_dir,
|
||||
sources=None,
|
||||
pip_args=None,
|
||||
)
|
||||
pip_command.extend(["-c", normalize_path(constraint_filename)])
|
||||
pip_command.extend(prepare_pip_source_args(sources))
|
||||
if project.s.is_verbose():
|
||||
err.print(f"$ {cmd_list_to_shell(pip_command)}")
|
||||
cache_dir = Path(project.s.PIPENV_CACHE_DIR)
|
||||
default_exists_action = "w"
|
||||
exists_action = project.s.PIP_EXISTS_ACTION or default_exists_action
|
||||
pip_config = {
|
||||
"PIP_CACHE_DIR": cache_dir.as_posix(),
|
||||
"PIP_WHEEL_DIR": cache_dir.joinpath("wheels").as_posix(),
|
||||
"PIP_DESTINATION_DIR": cache_dir.joinpath("pkgs").as_posix(),
|
||||
"PIP_EXISTS_ACTION": exists_action,
|
||||
"PATH": os.environ.get("PATH"),
|
||||
}
|
||||
if src_dir:
|
||||
if project.s.is_verbose():
|
||||
err(f"Using source directory: {src_dir!r}")
|
||||
pip_config.update({"PIP_SRC": src_dir})
|
||||
c = subprocess_run(pip_command, block=block, env=pip_config)
|
||||
c.env = pip_config
|
||||
return c
|
||||
|
||||
|
||||
def get_pip_args(
|
||||
project,
|
||||
pre: bool = False,
|
||||
@@ -366,26 +149,3 @@ def get_trusted_hosts():
|
||||
return os.environ.get("PIP_TRUSTED_HOSTS", []).split(" ")
|
||||
except AttributeError:
|
||||
return []
|
||||
|
||||
|
||||
def write_requirement_to_file(
|
||||
project: Project,
|
||||
requirement: Requirement,
|
||||
requirements_dir: Optional[str] = None,
|
||||
include_hashes: bool = True,
|
||||
) -> str:
|
||||
if not requirements_dir:
|
||||
requirements_dir = create_tracked_tempdir(prefix="pipenv", suffix="requirements")
|
||||
line = requirement.line_instance.get_line(
|
||||
with_prefix=True, with_hashes=include_hashes, with_markers=True, as_list=False
|
||||
)
|
||||
|
||||
f = tempfile.NamedTemporaryFile(
|
||||
prefix="pipenv-", suffix="-requirement.txt", dir=requirements_dir, delete=False
|
||||
)
|
||||
if project.s.is_verbose():
|
||||
err.print(f"Writing supplied requirement line to temporary file: {line!r}")
|
||||
f.write(line.encode())
|
||||
r = f.name
|
||||
f.close()
|
||||
return r
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
import io
|
||||
import itertools
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pipenv import environments, exceptions
|
||||
from pipenv.utils import console, err
|
||||
from pipenv.utils.internet import get_url_name
|
||||
from pipenv.utils.markers import RequirementError
|
||||
from pipenv.utils.requirements import import_requirements
|
||||
from pipenv.utils.requirementslib import is_editable, is_vcs, merge_items
|
||||
from pipenv.utils.toml import tomlkit_value_to_python
|
||||
from pipenv.vendor import tomlkit
|
||||
from pipenv.vendor.plette import pipfiles
|
||||
from pipenv.vendor.pydantic import BaseModel, Field, validator
|
||||
|
||||
DEFAULT_NEWLINES = "\n"
|
||||
|
||||
|
||||
def walk_up(bottom):
|
||||
@@ -110,3 +123,310 @@ def ensure_pipfile(
|
||||
if changed:
|
||||
err.print("Fixing package names in Pipfile...", style="bold")
|
||||
project.write_toml(p)
|
||||
|
||||
|
||||
def reorder_source_keys(data):
|
||||
# type: (tomlkit.toml_document.TOMLDocument) -> tomlkit.toml_document.TOMLDocument
|
||||
sources = [] # type: sources_type
|
||||
for source_key in ["source", "sources"]:
|
||||
sources.extend(data.get(source_key, tomlkit.aot()).value)
|
||||
new_source_aot = tomlkit.aot()
|
||||
for entry in sources:
|
||||
table = tomlkit.table() # type: tomlkit.items.Table
|
||||
source_entry = PipfileLoader.populate_source(entry.copy())
|
||||
for key in ["name", "url", "verify_ssl"]:
|
||||
table.update({key: source_entry[key]})
|
||||
new_source_aot.append(table)
|
||||
data["source"] = new_source_aot
|
||||
if data.get("sources", None):
|
||||
del data["sources"]
|
||||
return data
|
||||
|
||||
|
||||
def preferred_newlines(f):
|
||||
if isinstance(f.newlines, str):
|
||||
return f.newlines
|
||||
return DEFAULT_NEWLINES
|
||||
|
||||
|
||||
class ProjectFile(BaseModel):
|
||||
location: str
|
||||
line_ending: str
|
||||
model: Optional[Any] = Field(default_factory=lambda: {})
|
||||
|
||||
@classmethod
|
||||
def read(cls, location: str, model_cls, invalid_ok: bool = False) -> "ProjectFile":
|
||||
if not os.path.exists(location) and not invalid_ok:
|
||||
raise FileNotFoundError(location)
|
||||
try:
|
||||
with open(location, encoding="utf-8") as f:
|
||||
model = model_cls.load(f)
|
||||
line_ending = preferred_newlines(f)
|
||||
except Exception:
|
||||
if not invalid_ok:
|
||||
raise
|
||||
model = {}
|
||||
line_ending = DEFAULT_NEWLINES
|
||||
return cls(location=location, line_ending=line_ending, model=model)
|
||||
|
||||
def write(self) -> None:
|
||||
kwargs = {"encoding": "utf-8", "newline": self.line_ending}
|
||||
with open(self.location, "w", **kwargs) as f:
|
||||
if self.model:
|
||||
self.model.dump(f)
|
||||
|
||||
def dumps(self) -> str:
|
||||
if self.model:
|
||||
strio = io.StringIO()
|
||||
self.model.dump(strio)
|
||||
return strio.getvalue()
|
||||
return ""
|
||||
|
||||
|
||||
class PipfileLoader(pipfiles.Pipfile):
|
||||
@classmethod
|
||||
def validate(cls, data):
|
||||
# type: (tomlkit.toml_document.TOMLDocument) -> None
|
||||
for key, klass in pipfiles.PIPFILE_SECTIONS.items():
|
||||
if key not in data or key == "sources":
|
||||
continue
|
||||
try:
|
||||
klass.validate(data[key])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def ensure_package_sections(cls, data):
|
||||
# type: (tomlkit.toml_document.TOMLDocument[Text, Any]) -> tomlkit.toml_document.TOMLDocument[Text, Any]
|
||||
"""Ensure that all pipfile package sections are present in the given
|
||||
toml document.
|
||||
|
||||
:param :class:`~tomlkit.toml_document.TOMLDocument` data: The toml document to
|
||||
ensure package sections are present on
|
||||
:return: The updated toml document, ensuring ``packages`` and ``dev-packages``
|
||||
sections are present
|
||||
:rtype: :class:`~tomlkit.toml_document.TOMLDocument`
|
||||
"""
|
||||
package_keys = (
|
||||
k for k in pipfiles.PIPFILE_SECTIONS.keys() if k.endswith("packages")
|
||||
)
|
||||
for key in package_keys:
|
||||
if key not in data:
|
||||
data.update({key: tomlkit.table()})
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def populate_source(cls, source):
|
||||
"""Derive missing values of source from the existing fields."""
|
||||
# Only URL pararemter is mandatory, let the KeyError be thrown.
|
||||
if "name" not in source:
|
||||
source["name"] = get_url_name(source["url"])
|
||||
if "verify_ssl" not in source:
|
||||
source["verify_ssl"] = "https://" in source["url"]
|
||||
if not isinstance(source["verify_ssl"], bool):
|
||||
source["verify_ssl"] = str(source["verify_ssl"]).lower() == "true"
|
||||
return source
|
||||
|
||||
@classmethod
|
||||
def load(cls, f, encoding=None):
|
||||
# type: (Any, Text) -> PipfileLoader
|
||||
content = f.read()
|
||||
if encoding is not None:
|
||||
content = content.decode(encoding)
|
||||
_data = tomlkit.loads(content)
|
||||
should_reload = "source" not in _data
|
||||
_data = reorder_source_keys(_data)
|
||||
if should_reload:
|
||||
if "sources" in _data:
|
||||
content = tomlkit.dumps(_data)
|
||||
else:
|
||||
# HACK: There is no good way to prepend a section to an existing
|
||||
# TOML document, but there's no good way to copy non-structural
|
||||
# content from one TOML document to another either. Modify the
|
||||
# TOML content directly, and load the new in-memory document.
|
||||
sep = "" if content.startswith("\n") else "\n"
|
||||
content = pipfiles.DEFAULT_SOURCE_TOML + sep + content
|
||||
data = tomlkit.loads(content)
|
||||
data = cls.ensure_package_sections(data)
|
||||
instance = cls(data)
|
||||
instance._data = dict(instance._data)
|
||||
return instance
|
||||
|
||||
def __contains__(self, key):
|
||||
# type: (Text) -> bool
|
||||
if key not in self._data:
|
||||
package_keys = self._data.get("packages", {}).keys()
|
||||
dev_package_keys = self._data.get("dev-packages", {}).keys()
|
||||
return any(key in pkg_list for pkg_list in (package_keys, dev_package_keys))
|
||||
return True
|
||||
|
||||
def __getattribute__(self, key):
|
||||
# type: (Text) -> Any
|
||||
if key == "source":
|
||||
return self._data[key]
|
||||
return super().__getattribute__(key)
|
||||
|
||||
|
||||
class Pipfile(BaseModel):
|
||||
path: Path
|
||||
projectfile: ProjectFile
|
||||
pipfile: Optional[PipfileLoader]
|
||||
_pyproject: Optional[tomlkit.TOMLDocument] = tomlkit.document()
|
||||
build_system: Optional[Dict] = {}
|
||||
_requirements: Optional[List] = []
|
||||
_dev_requirements: Optional[List] = []
|
||||
|
||||
class Config:
|
||||
validate_assignment = True
|
||||
arbitrary_types_allowed = True
|
||||
allow_mutation = True
|
||||
include_private_attributes = True
|
||||
# keep_untouched = (cached_property,)
|
||||
|
||||
@validator("path", pre=True, always=True)
|
||||
def _get_path(cls, v):
|
||||
return v or Path(os.curdir).absolute()
|
||||
|
||||
@validator("projectfile", pre=True, always=True)
|
||||
def _get_projectfile(cls, v, values):
|
||||
return v or cls.load_projectfile(os.curdir, create=False)
|
||||
|
||||
@validator("pipfile", pre=True, always=True)
|
||||
def _get_pipfile(cls, v, values):
|
||||
return v or values["projectfile"].model
|
||||
|
||||
@property
|
||||
def root(self):
|
||||
return self.path.parent
|
||||
|
||||
@property
|
||||
def extended_keys(self):
|
||||
return [
|
||||
k
|
||||
for k in itertools.product(
|
||||
("packages", "dev-packages"), ("", "vcs", "editable")
|
||||
)
|
||||
]
|
||||
|
||||
def get_deps(self, dev=False, only=True):
|
||||
deps = {} # type: Dict[Text, Dict[Text, Union[List[Text], Text]]]
|
||||
if dev:
|
||||
deps.update(dict(self.pipfile._data.get("dev-packages", {})))
|
||||
if only:
|
||||
return deps
|
||||
return tomlkit_value_to_python(
|
||||
merge_items([deps, dict(self.pipfile._data.get("packages", {}))])
|
||||
)
|
||||
|
||||
def get(self, k):
|
||||
return self.__getitem__(k)
|
||||
|
||||
def __contains__(self, k):
|
||||
check_pipfile = k in self.extended_keys or self.pipfile.__contains__(k)
|
||||
if check_pipfile:
|
||||
return True
|
||||
return False
|
||||
|
||||
def __getitem__(self, k, *args, **kwargs):
|
||||
retval = None
|
||||
pipfile = self.pipfile
|
||||
section = None
|
||||
pkg_type = None
|
||||
try:
|
||||
retval = pipfile[k]
|
||||
except KeyError:
|
||||
if "-" in k:
|
||||
section, _, pkg_type = k.rpartition("-")
|
||||
vals = getattr(pipfile.get(section, {}), "_data", {})
|
||||
vals = tomlkit_value_to_python(vals)
|
||||
if pkg_type == "vcs":
|
||||
retval = {k: v for k, v in vals.items() if is_vcs(v)}
|
||||
elif pkg_type == "editable":
|
||||
retval = {k: v for k, v in vals.items() if is_editable(v)}
|
||||
if retval is None:
|
||||
raise
|
||||
else:
|
||||
retval = getattr(retval, "_data", retval)
|
||||
return retval
|
||||
|
||||
def __getattr__(self, k, *args, **kwargs):
|
||||
pipfile = self.pipfile
|
||||
try:
|
||||
retval = super(Pipfile).__getattribute__(k)
|
||||
except AttributeError:
|
||||
retval = getattr(pipfile, k, None)
|
||||
return retval
|
||||
|
||||
@property
|
||||
def requires_python(self):
|
||||
# type: () -> bool
|
||||
return getattr(
|
||||
self.pipfile.requires,
|
||||
"python_version",
|
||||
getattr(self.pipfile.requires, "python_full_version", None),
|
||||
)
|
||||
|
||||
@property
|
||||
def allow_prereleases(self):
|
||||
# type: () -> bool
|
||||
return self.pipfile.get("pipenv", {}).get("allow_prereleases", False)
|
||||
|
||||
@classmethod
|
||||
def read_projectfile(cls, path):
|
||||
# type: (Text) -> ProjectFile
|
||||
"""Read the specified project file and provide an interface for
|
||||
writing/updating.
|
||||
|
||||
:param Text path: Path to the target file.
|
||||
:return: A project file with the model and location for interaction
|
||||
:rtype: :class:`~project.ProjectFile`
|
||||
"""
|
||||
pf = ProjectFile.read(path, PipfileLoader, invalid_ok=True)
|
||||
return pf
|
||||
|
||||
@classmethod
|
||||
def load_projectfile(cls, path, create=False):
|
||||
# type: (Text, bool) -> ProjectFile
|
||||
"""Given a path, load or create the necessary 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``
|
||||
:return: A project file instance for the supplied project
|
||||
:rtype: :class:`~project.ProjectFile`
|
||||
"""
|
||||
if not path:
|
||||
raise RuntimeError("Must pass a path to classmethod 'Pipfile.load'")
|
||||
if not isinstance(path, Path):
|
||||
path = Path(path).absolute()
|
||||
pipfile_path = path if path.is_file() else path.joinpath("Pipfile")
|
||||
project_path = pipfile_path.parent
|
||||
if not project_path.exists():
|
||||
raise FileNotFoundError("%s is not a valid project path!" % path)
|
||||
elif not pipfile_path.exists() or not pipfile_path.is_file():
|
||||
if not create:
|
||||
raise RequirementError("%s is not a valid Pipfile" % pipfile_path)
|
||||
return cls.read_projectfile(pipfile_path.as_posix())
|
||||
|
||||
@classmethod
|
||||
def load(cls, path, create=False):
|
||||
# type: (Text, bool) -> Pipfile
|
||||
"""Given a path, load or create the necessary 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``
|
||||
:return: A pipfile instance pointing at the supplied project
|
||||
:rtype:: class:`~pipfile.Pipfile`
|
||||
"""
|
||||
|
||||
projectfile = cls.load_projectfile(path, create=create)
|
||||
pipfile = projectfile.model
|
||||
creation_args = {
|
||||
"projectfile": projectfile,
|
||||
"pipfile": pipfile,
|
||||
"path": Path(projectfile.location),
|
||||
}
|
||||
return cls(**creation_args)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import os
|
||||
from functools import lru_cache
|
||||
|
||||
from pipenv import exceptions
|
||||
from pipenv.patched.pip._vendor.packaging.version import parse as parse_version
|
||||
from pipenv.patched.pip._vendor.pkg_resources import Requirement, get_distribution
|
||||
from pipenv.utils.dependencies import python_version
|
||||
from pipenv.utils.pipfile import ensure_pipfile
|
||||
from pipenv.utils.shell import shorten_path
|
||||
@@ -82,3 +85,21 @@ def ensure_project(
|
||||
categories=categories,
|
||||
)
|
||||
os.environ["PIP_PYTHON_PATH"] = project.python(system=system)
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def get_setuptools_version():
|
||||
# type: () -> Optional[STRING_TYPE]
|
||||
|
||||
setuptools_dist = get_distribution(Requirement("setuptools"))
|
||||
return getattr(setuptools_dist, "version", None)
|
||||
|
||||
|
||||
def get_default_pyproject_backend():
|
||||
# type: () -> STRING_TYPE
|
||||
st_version = get_setuptools_version()
|
||||
if st_version is not None:
|
||||
parsed_st_version = parse_version(st_version)
|
||||
if parsed_st_version >= parse_version("40.8.0"):
|
||||
return "setuptools.build_meta:__legacy__"
|
||||
return "setuptools.build_meta"
|
||||
|
||||
+137
-23
@@ -1,17 +1,60 @@
|
||||
import os
|
||||
import re
|
||||
import urllib.parse
|
||||
from typing import Tuple
|
||||
|
||||
from pipenv.patched.pip._internal.network.session import PipSession
|
||||
from pipenv.patched.pip._internal.req import parse_requirements
|
||||
from pipenv.patched.pip._internal.req.constructors import (
|
||||
install_req_from_parsed_requirement,
|
||||
)
|
||||
from pipenv.patched.pip._internal.utils.misc import split_auth_from_netloc
|
||||
from pipenv.patched.pip._internal.utils.misc import _transform_url, split_auth_from_netloc
|
||||
from pipenv.utils.constants import VCS_LIST
|
||||
from pipenv.utils.indexes import parse_indexes
|
||||
from pipenv.utils.internet import get_host_and_port
|
||||
from pipenv.utils.pip import get_trusted_hosts
|
||||
|
||||
|
||||
def redact_netloc(netloc: str) -> Tuple[str]:
|
||||
"""
|
||||
Replace the sensitive data in a netloc with "****", if it exists, unless it's an environment variable.
|
||||
|
||||
For example:
|
||||
- "user:pass@example.com" returns "user:****@example.com"
|
||||
- "accesstoken@example.com" returns "****@example.com"
|
||||
- "${ENV_VAR}:pass@example.com" returns "${ENV_VAR}:****@example.com" if ${ENV_VAR} is an environment variable
|
||||
"""
|
||||
netloc, (user, password) = split_auth_from_netloc(netloc)
|
||||
if user is None:
|
||||
return (netloc,)
|
||||
if password is None:
|
||||
# Check if user is an environment variable
|
||||
if not re.match(r"\$\{\w+\}", user):
|
||||
# If not, redact the user
|
||||
user = "****"
|
||||
password = ""
|
||||
else:
|
||||
# Check if password is an environment variable
|
||||
if not re.match(r"\$\{\w+\}", password):
|
||||
# If not, redact the password
|
||||
password = ":****"
|
||||
else:
|
||||
# If it is, leave it as is
|
||||
password = ":" + password
|
||||
user = urllib.parse.quote(user)
|
||||
return (f"{user}{password}@{netloc}",)
|
||||
|
||||
|
||||
def redact_auth_from_url(url: str) -> str:
|
||||
"""Replace the password in a given url with ****."""
|
||||
return _transform_url(url, redact_netloc)[0]
|
||||
|
||||
|
||||
def normalize_name(pkg) -> str:
|
||||
"""Given a package name, return its normalized, non-canonicalized form."""
|
||||
return pkg.replace("_", "-").lower()
|
||||
|
||||
|
||||
def import_requirements(project, r=None, dev=False, categories=None):
|
||||
# Parse requirements.txt file with Pip's parser.
|
||||
# Pip requires a `PipSession` which is a subclass of requests.Session.
|
||||
@@ -36,44 +79,34 @@ def import_requirements(project, r=None, dev=False, categories=None):
|
||||
indexes.append(extra_index)
|
||||
if trusted_host:
|
||||
trusted_hosts.append(get_host_and_port(trusted_host))
|
||||
indexes = sorted(set(indexes))
|
||||
trusted_hosts = sorted(set(trusted_hosts))
|
||||
reqs = [
|
||||
install_req_from_parsed_requirement(f)
|
||||
for f in parse_requirements(r, session=PipSession())
|
||||
]
|
||||
for package in reqs:
|
||||
for f in parse_requirements(r, session=PipSession()):
|
||||
package = install_req_from_parsed_requirement(f)
|
||||
if package.name not in BAD_PACKAGES:
|
||||
if package.link is not None:
|
||||
if package.editable:
|
||||
package_string = f"-e {package.link}"
|
||||
else:
|
||||
netloc, (user, pw) = split_auth_from_netloc(package.link.netloc)
|
||||
safe = True
|
||||
if user and not re.match(r"\${[\W\w]+}", user):
|
||||
safe = False
|
||||
if pw and not re.match(r"\${[\W\w]+}", pw):
|
||||
safe = False
|
||||
if safe:
|
||||
package_string = str(package.link._url)
|
||||
else:
|
||||
package_string = str(package.link)
|
||||
package_string = urllib.parse.unquote(
|
||||
redact_auth_from_url(package.original_link.url)
|
||||
)
|
||||
|
||||
if categories:
|
||||
for category in categories:
|
||||
project.add_package_to_pipfile(
|
||||
package_string, dev=dev, category=category
|
||||
package, package_string, dev=dev, category=category
|
||||
)
|
||||
else:
|
||||
project.add_package_to_pipfile(package_string, dev=dev)
|
||||
project.add_package_to_pipfile(package, package_string, dev=dev)
|
||||
else:
|
||||
if categories:
|
||||
for category in categories:
|
||||
project.add_package_to_pipfile(
|
||||
str(package.req), dev=dev, category=category
|
||||
package, str(package.req), dev=dev, category=category
|
||||
)
|
||||
else:
|
||||
project.add_package_to_pipfile(str(package.req), dev=dev)
|
||||
|
||||
project.add_package_to_pipfile(package, str(package.req), dev=dev)
|
||||
indexes = sorted(set(indexes))
|
||||
trusted_hosts = sorted(set(trusted_hosts))
|
||||
for index in indexes:
|
||||
add_index_to_pipfile(project, index, trusted_hosts)
|
||||
project.recase_pipfile()
|
||||
@@ -105,3 +138,84 @@ BAD_PACKAGES = (
|
||||
"setuptools",
|
||||
"wheel",
|
||||
)
|
||||
|
||||
|
||||
def requirement_from_lockfile(
|
||||
package_name, package_info, include_hashes=True, include_markers=True
|
||||
):
|
||||
from pipenv.utils.dependencies import is_editable_path, is_star
|
||||
|
||||
# Handle string requirements
|
||||
if isinstance(package_info, str):
|
||||
if package_info and not is_star(package_info):
|
||||
return f"{package_name}=={package_info}"
|
||||
else:
|
||||
return package_name
|
||||
# Handling vcs repositories
|
||||
for vcs in VCS_LIST:
|
||||
if vcs in package_info:
|
||||
url = package_info[vcs]
|
||||
ref = package_info.get("ref", "")
|
||||
extras = (
|
||||
"[{}]".format(",".join(package_info.get("extras", [])))
|
||||
if "extras" in package_info
|
||||
else ""
|
||||
)
|
||||
include_vcs = "" if f"{vcs}+" in url else f"{vcs}+"
|
||||
egg_fragment = "" if "#egg=" in url else f"#egg={package_name}"
|
||||
ref_str = "" if f"@{ref}" in url else f"@{ref}"
|
||||
if is_editable_path(url) or "file://" in url:
|
||||
pip_line = f"-e {include_vcs}{url}{ref_str}{egg_fragment}{extras}"
|
||||
else:
|
||||
pip_line = f"{package_name}{extras}@ {include_vcs}{url}{ref_str}"
|
||||
return pip_line
|
||||
# Handling file-sourced packages
|
||||
for k in ["file", "path"]:
|
||||
line = []
|
||||
if k in package_info:
|
||||
path = package_info[k]
|
||||
if is_editable_path(path):
|
||||
line.append("-e")
|
||||
line.append(f"{package_info[k]}")
|
||||
pip_line = " ".join(line)
|
||||
return pip_line
|
||||
|
||||
# Handling packages from standard pypi like indexes
|
||||
version = package_info.get("version", "").replace("==", "")
|
||||
hashes = (
|
||||
f" --hash={' --hash='.join(package_info['hashes'])}"
|
||||
if include_hashes and "hashes" in package_info
|
||||
else ""
|
||||
)
|
||||
markers = (
|
||||
"; {}".format(package_info["markers"])
|
||||
if include_markers and "markers" in package_info and package_info["markers"]
|
||||
else ""
|
||||
)
|
||||
os_markers = (
|
||||
"; {}".format(package_info["os_markers"])
|
||||
if include_markers and "os_markers" in package_info and package_info["os_markers"]
|
||||
else ""
|
||||
)
|
||||
extras = (
|
||||
"[{}]".format(",".join(package_info.get("extras", [])))
|
||||
if "extras" in package_info
|
||||
else ""
|
||||
)
|
||||
pip_line = f"{package_name}{extras}=={version}{os_markers}{markers}{hashes}"
|
||||
return pip_line
|
||||
|
||||
|
||||
def requirements_from_lockfile(deps, include_hashes=True, include_markers=True):
|
||||
pip_packages = []
|
||||
|
||||
for package_name, package_info in deps.items():
|
||||
pip_package = requirement_from_lockfile(
|
||||
package_name, package_info, include_hashes, include_markers
|
||||
)
|
||||
|
||||
# Append to the list
|
||||
pip_packages.append(pip_package)
|
||||
|
||||
# pip_packages contains the pip-installable lines
|
||||
return pip_packages
|
||||
|
||||
@@ -1,77 +1,35 @@
|
||||
# This Module is taken in part from the click project and expanded
|
||||
# see https://github.com/pallets/click/blob/6cafd32/click/_winconsole.py
|
||||
# Copyright © 2014 by the Pallets team.
|
||||
|
||||
# Some rights reserved.
|
||||
|
||||
# Redistribution and use in source and binary forms of the software as well as
|
||||
# documentation, with or without modification, are permitted provided that the
|
||||
# following conditions are met:
|
||||
# Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# Neither the name of the copyright holder nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this
|
||||
# software without specific prior written permission.
|
||||
|
||||
# THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
|
||||
# NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE AND
|
||||
# DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from collections.abc import ItemsView, Mapping, Sequence, Set
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple, TypeVar, Union
|
||||
from urllib.parse import urlparse, urlsplit, urlunparse
|
||||
|
||||
import pipenv.vendor.tomlkit as tomlkit
|
||||
from pipenv.patched.pip._internal.commands.install import InstallCommand
|
||||
from pipenv.patched.pip._internal.models.link import Link
|
||||
from pipenv.patched.pip._internal.models.target_python import TargetPython
|
||||
from pipenv.patched.pip._internal.network.download import Downloader
|
||||
from pipenv.patched.pip._internal.operations.prepare import (
|
||||
File,
|
||||
_check_download_dir,
|
||||
get_file_url,
|
||||
unpack_vcs_link,
|
||||
)
|
||||
from pipenv.patched.pip._internal.utils.filetypes import is_archive_file
|
||||
from pipenv.patched.pip._internal.utils.hashes import Hashes
|
||||
from pipenv.patched.pip._internal.utils.misc import is_installable_dir
|
||||
from pipenv.patched.pip._internal.utils.temp_dir import TempDirectory
|
||||
from pipenv.patched.pip._internal.utils.unpacking import unpack_file
|
||||
from pipenv.patched.pip._vendor.packaging import specifiers
|
||||
from pipenv.utils.fileutils import is_valid_url, normalize_path, url_to_path
|
||||
from pipenv.vendor import tomlkit
|
||||
|
||||
from .environment import MYPY_RUNNING
|
||||
from .fileutils import is_valid_url, normalize_path, url_to_path
|
||||
|
||||
if MYPY_RUNNING:
|
||||
from typing import Dict, Iterator, List, Optional, Text, Tuple, TypeVar, Union
|
||||
|
||||
STRING_TYPE = Union[bytes, str, Text]
|
||||
S = TypeVar("S", bytes, str, Text)
|
||||
PipfileEntryType = Union[STRING_TYPE, bool, Tuple[STRING_TYPE], List[STRING_TYPE]]
|
||||
PipfileType = Union[STRING_TYPE, Dict[STRING_TYPE, PipfileEntryType]]
|
||||
STRING_TYPE = Union[bytes, str, str]
|
||||
S = TypeVar("S", bytes, str, str)
|
||||
PipfileEntryType = Union[STRING_TYPE, bool, Tuple[STRING_TYPE], List[STRING_TYPE]]
|
||||
PipfileType = Union[STRING_TYPE, Dict[STRING_TYPE, PipfileEntryType]]
|
||||
|
||||
|
||||
VCS_LIST = ("git", "svn", "hg", "bzr")
|
||||
|
||||
|
||||
def setup_logger():
|
||||
logger = logging.getLogger("requirementslib")
|
||||
loglevel = logging.DEBUG
|
||||
handler = logging.StreamHandler(stream=sys.stderr)
|
||||
handler.setLevel(loglevel)
|
||||
logger.addHandler(handler)
|
||||
logger.setLevel(loglevel)
|
||||
return logger
|
||||
|
||||
|
||||
log = setup_logger()
|
||||
|
||||
|
||||
SCHEME_LIST = ("http://", "https://", "ftp://", "ftps://", "file://")
|
||||
|
||||
|
||||
@@ -111,10 +69,8 @@ def strip_ssh_from_git_uri(uri):
|
||||
# split the path on the first separating / so we can put the first segment
|
||||
# into the 'netloc' section with a : separator
|
||||
path_part, _, path = parsed.path.lstrip("/").partition("/")
|
||||
path = "/{0}".format(path)
|
||||
parsed = parsed._replace(
|
||||
netloc="{0}:{1}".format(parsed.netloc, path_part), path=path
|
||||
)
|
||||
path = f"/{path}"
|
||||
parsed = parsed._replace(netloc=f"{parsed.netloc}:{path_part}", path=path)
|
||||
uri = urlunparse(parsed).replace("git+ssh://", "git+", 1)
|
||||
return uri
|
||||
|
||||
@@ -129,7 +85,7 @@ def add_ssh_scheme_to_git_uri(uri):
|
||||
parsed = urlparse(uri)
|
||||
if ":" in parsed.netloc:
|
||||
netloc, _, path_start = parsed.netloc.rpartition(":")
|
||||
path = "/{0}{1}".format(path_start, parsed.path)
|
||||
path = f"/{path_start}{parsed.path}"
|
||||
uri = urlunparse(parsed._replace(netloc=netloc, path=path))
|
||||
return uri
|
||||
|
||||
@@ -170,10 +126,10 @@ def convert_entry_to_path(path):
|
||||
"""Convert a pipfile entry to a string."""
|
||||
|
||||
if not isinstance(path, Mapping):
|
||||
raise TypeError("expecting a mapping, received {0!r}".format(path))
|
||||
raise TypeError(f"expecting a mapping, received {path!r}")
|
||||
|
||||
if not any(key in path for key in ["file", "path"]):
|
||||
raise ValueError("missing path-like entry in supplied mapping {0!r}".format(path))
|
||||
raise ValueError(f"missing path-like entry in supplied mapping {path!r}")
|
||||
|
||||
if "file" in path:
|
||||
path = url_to_path(path["file"])
|
||||
@@ -276,7 +232,7 @@ def prepare_pip_source_args(sources, pip_args=None):
|
||||
pip_args = []
|
||||
if sources:
|
||||
# Add the source to pip9.
|
||||
pip_args.extend(["-i", sources[0]["url"]]) # type: ignore
|
||||
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(
|
||||
@@ -375,14 +331,10 @@ class PathAccessError(KeyError, IndexError, TypeError):
|
||||
|
||||
def __repr__(self):
|
||||
cn = self.__class__.__name__
|
||||
return "%s(%r, %r, %r)" % (cn, self.exc, self.seg, self.path)
|
||||
return f"{cn}({self.exc!r}, {self.seg!r}, {self.path!r})"
|
||||
|
||||
def __str__(self):
|
||||
return "could not access %r from path %r, got error: %r" % (
|
||||
self.seg,
|
||||
self.path,
|
||||
self.exc,
|
||||
)
|
||||
return f"could not access {self.seg} from path {self.path}, got error: {self.exc}"
|
||||
|
||||
|
||||
def get_path(root, path, default=_UNSET):
|
||||
@@ -421,7 +373,7 @@ def get_path(root, path, default=_UNSET):
|
||||
cur = cur[seg]
|
||||
except (KeyError, IndexError) as exc:
|
||||
raise PathAccessError(exc, seg, path)
|
||||
except TypeError as exc:
|
||||
except TypeError:
|
||||
# either string index in a list, or a parent that
|
||||
# doesn't support indexing
|
||||
try:
|
||||
@@ -701,20 +653,90 @@ def get_pip_command() -> InstallCommand:
|
||||
# General options (find_links, index_url, extra_index_url, trusted_host,
|
||||
# and pre) are deferred to pip.
|
||||
pip_command = InstallCommand(
|
||||
name="InstallCommand", summary="requirementslib pip Install command."
|
||||
name="InstallCommand", summary="pipenv pip Install command."
|
||||
)
|
||||
return pip_command
|
||||
|
||||
|
||||
# Borrowed from Pew.
|
||||
# See https://github.com/berdario/pew/blob/master/pew/_utils.py#L82
|
||||
@contextmanager
|
||||
def temp_environ():
|
||||
# type: () -> Iterator[None]
|
||||
"""Allow the ability to set os.environ temporarily."""
|
||||
environ = dict(os.environ)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
os.environ.clear()
|
||||
os.environ.update(environ)
|
||||
def unpack_url(
|
||||
link: Link,
|
||||
location: str,
|
||||
download: Downloader,
|
||||
verbosity: int,
|
||||
download_dir: Optional[str] = None,
|
||||
hashes: Optional[Hashes] = None,
|
||||
) -> Optional[File]:
|
||||
"""Unpack link into location, downloading if required.
|
||||
|
||||
:param hashes: A Hashes object, one of whose embedded hashes must match,
|
||||
or HashMismatch will be raised. If the Hashes is empty, no matches are
|
||||
required, and unhashable types of requirements (like VCS ones, which
|
||||
would ordinarily raise HashUnsupported) are allowed.
|
||||
"""
|
||||
# non-editable vcs urls
|
||||
if link.scheme in [
|
||||
"git+http",
|
||||
"git+https",
|
||||
"git+ssh",
|
||||
"git+git",
|
||||
"hg+http",
|
||||
"hg+https",
|
||||
"hg+ssh",
|
||||
"svn+http",
|
||||
"svn+https",
|
||||
"svn+svn",
|
||||
"bzr+http",
|
||||
"bzr+https",
|
||||
"bzr+ssh",
|
||||
"bzr+sftp",
|
||||
"bzr+ftp",
|
||||
"bzr+lp",
|
||||
]:
|
||||
unpack_vcs_link(link, location, verbosity=verbosity)
|
||||
return File(location, content_type=None)
|
||||
|
||||
assert not link.is_existing_dir()
|
||||
|
||||
# file urls
|
||||
if link.is_file:
|
||||
file = get_file_url(link, download_dir, hashes=hashes)
|
||||
|
||||
# http urls
|
||||
else:
|
||||
file = get_http_url(
|
||||
link,
|
||||
download,
|
||||
download_dir,
|
||||
hashes=hashes,
|
||||
)
|
||||
|
||||
# unpack the archive to the build dir location. even when only downloading
|
||||
# archives, they have to be unpacked to parse dependencies, except wheels
|
||||
if not link.is_wheel:
|
||||
unpack_file(file.path, location, file.content_type)
|
||||
|
||||
return file
|
||||
|
||||
|
||||
def get_http_url(
|
||||
link: Link,
|
||||
download: Downloader,
|
||||
download_dir: Optional[str] = None,
|
||||
hashes: Optional[Hashes] = None,
|
||||
) -> File:
|
||||
temp_dir = TempDirectory(kind="unpack", globally_managed=False)
|
||||
# If a download dir is specified, is the file already downloaded there?
|
||||
already_downloaded_path = None
|
||||
if download_dir:
|
||||
already_downloaded_path = _check_download_dir(link, download_dir, hashes)
|
||||
|
||||
if already_downloaded_path:
|
||||
from_path = already_downloaded_path
|
||||
content_type = None
|
||||
else:
|
||||
# let's download to a tmp dir
|
||||
from_path, content_type = download(link, temp_dir.path)
|
||||
if hashes:
|
||||
hashes.check_against_path(from_path)
|
||||
|
||||
return File(from_path, content_type)
|
||||
+180
-508
@@ -1,5 +1,4 @@
|
||||
import contextlib
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
@@ -7,18 +6,15 @@ import sys
|
||||
import tempfile
|
||||
import warnings
|
||||
from functools import lru_cache
|
||||
from html.parser import HTMLParser
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Set, Tuple, Union
|
||||
from urllib import parse
|
||||
from typing import Dict, List, Optional, Set
|
||||
|
||||
from pipenv import environments, resolver
|
||||
from pipenv.exceptions import RequirementError, ResolutionFailure
|
||||
from pipenv.exceptions import ResolutionFailure
|
||||
from pipenv.patched.pip._internal.cache import WheelCache
|
||||
from pipenv.patched.pip._internal.commands.install import InstallCommand
|
||||
from pipenv.patched.pip._internal.exceptions import InstallationError
|
||||
from pipenv.patched.pip._internal.models.target_python import TargetPython
|
||||
from pipenv.patched.pip._internal.network.cache import SafeFileCache
|
||||
from pipenv.patched.pip._internal.operations.build.build_tracker import (
|
||||
get_build_tracker,
|
||||
)
|
||||
@@ -26,14 +22,13 @@ from pipenv.patched.pip._internal.req.constructors import (
|
||||
install_req_from_parsed_requirement,
|
||||
)
|
||||
from pipenv.patched.pip._internal.req.req_file import parse_requirements
|
||||
from pipenv.patched.pip._internal.utils.hashes import FAVORITE_HASH
|
||||
from pipenv.patched.pip._internal.req.req_install import InstallRequirement
|
||||
from pipenv.patched.pip._internal.utils.temp_dir import global_tempdir_manager
|
||||
from pipenv.patched.pip._vendor import pkg_resources, rich
|
||||
from pipenv.project import Project
|
||||
from pipenv.utils.fileutils import create_tracked_tempdir
|
||||
from pipenv.utils.requirements import normalize_name
|
||||
from pipenv.vendor import click
|
||||
from pipenv.vendor.requirementslib.fileutils import create_tracked_tempdir, open_file
|
||||
from pipenv.vendor.requirementslib.models.requirements import Line, Requirement
|
||||
from pipenv.vendor.requirementslib.models.utils import DIRECT_URL_RE
|
||||
|
||||
try:
|
||||
# this is only in Python3.8 and later
|
||||
@@ -44,18 +39,17 @@ except ImportError:
|
||||
|
||||
from .dependencies import (
|
||||
HackedPythonVersion,
|
||||
clean_pkg_version,
|
||||
convert_deps_to_pip,
|
||||
determine_package_name,
|
||||
expansive_install_req_from_line,
|
||||
get_constraints_from_deps,
|
||||
get_lockfile_section_using_pipfile_category,
|
||||
get_vcs_deps,
|
||||
is_pinned_requirement,
|
||||
pep423_name,
|
||||
prepare_constraint_file,
|
||||
translate_markers,
|
||||
)
|
||||
from .indexes import parse_indexes, prepare_pip_source_args
|
||||
from .internet import _get_requests_session, is_pypi_url
|
||||
from .internet import is_pypi_url
|
||||
from .locking import format_requirement_for_lockfile, prepare_lockfile
|
||||
from .shell import make_posix, subprocess_run, temp_environ
|
||||
|
||||
@@ -101,42 +95,14 @@ class HashCacheMixin:
|
||||
avoid issues where the location on the server changes.
|
||||
"""
|
||||
|
||||
def __init__(self, directory, session):
|
||||
def __init__(self, project, session):
|
||||
self.project = project
|
||||
self.session = session
|
||||
if not os.path.isdir(directory):
|
||||
os.makedirs(directory, exist_ok=True)
|
||||
super().__init__(directory=directory)
|
||||
|
||||
def get_hash(self, link):
|
||||
# If there is no link hash (i.e., md5, sha256, etc.), we don't want
|
||||
# to store it.
|
||||
hash_value = self.get(link.url)
|
||||
if not hash_value:
|
||||
hash_value = self._get_file_hash(link).encode()
|
||||
self.set(link.url, hash_value)
|
||||
hash_value = self.project.get_file_hash(self.session, link).encode()
|
||||
return hash_value.decode("utf8")
|
||||
|
||||
def _get_file_hash(self, link):
|
||||
h = hashlib.new(FAVORITE_HASH)
|
||||
with open_file(link.url, self.session) as fp:
|
||||
for chunk in iter(lambda: fp.read(8096), b""):
|
||||
h.update(chunk)
|
||||
return f"{h.name}:{h.hexdigest()}"
|
||||
|
||||
|
||||
class PackageIndexHTMLParser(HTMLParser):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.urls = []
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
# If tag is an anchor
|
||||
if tag == "a":
|
||||
# find href attribute
|
||||
for attr in attrs:
|
||||
if attr[0] == "href":
|
||||
self.urls.append(attr[1])
|
||||
|
||||
|
||||
class Resolver:
|
||||
def __init__(
|
||||
@@ -151,6 +117,9 @@ class Resolver:
|
||||
clear=False,
|
||||
pre=False,
|
||||
category=None,
|
||||
original_deps=None,
|
||||
install_reqs=None,
|
||||
pipfile_entries=None,
|
||||
):
|
||||
self.initial_constraints = constraints
|
||||
self.req_dir = req_dir
|
||||
@@ -167,18 +136,11 @@ class Resolver:
|
||||
self.skipped = skipped if skipped is not None else {}
|
||||
self.markers = {}
|
||||
self.requires_python_markers = {}
|
||||
self._pip_args = None
|
||||
self._constraints = None
|
||||
self._parsed_constraints = None
|
||||
self._resolver = None
|
||||
self._finder = None
|
||||
self._session = None
|
||||
self._constraint_file = None
|
||||
self._pip_options = None
|
||||
self._pip_command = None
|
||||
self.original_deps = original_deps if original_deps is not None else {}
|
||||
self.install_reqs = install_reqs if install_reqs is not None else {}
|
||||
self.pipfile_entries = pipfile_entries if pipfile_entries is not None else {}
|
||||
self._retry_attempts = 0
|
||||
self._hash_cache = None
|
||||
self._sessions = {}
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
@@ -194,260 +156,25 @@ class Resolver:
|
||||
@property
|
||||
def hash_cache(self):
|
||||
if not self._hash_cache:
|
||||
self._hash_cache = type("HashCache", (HashCacheMixin, SafeFileCache), {})(
|
||||
os.path.join(self.project.s.PIPENV_CACHE_DIR, "hashes"), self.session
|
||||
)
|
||||
self._hash_cache = HashCacheMixin(self.project, self.session)
|
||||
return self._hash_cache
|
||||
|
||||
def get_metadata(
|
||||
def check_if_package_req_skipped(
|
||||
self,
|
||||
deps: List[str],
|
||||
index_lookup: Dict[str, str],
|
||||
markers_lookup: Dict[str, str],
|
||||
project: Project,
|
||||
sources: Dict[str, str],
|
||||
req_dir: Optional[str] = None,
|
||||
pre: bool = False,
|
||||
clear: bool = False,
|
||||
category: str = None,
|
||||
) -> Tuple[
|
||||
Set[str],
|
||||
Dict[str, Dict[str, Union[str, bool, List[str]]]],
|
||||
Dict[str, str],
|
||||
Dict[str, str],
|
||||
]:
|
||||
constraints: Set[str] = set()
|
||||
skipped: Dict[str, Dict[str, Union[str, bool, List[str]]]] = {}
|
||||
if index_lookup is None:
|
||||
index_lookup = {}
|
||||
if markers_lookup is None:
|
||||
markers_lookup = {}
|
||||
if not req_dir:
|
||||
req_dir = create_tracked_tempdir(prefix="pipenv-", suffix="-reqdir")
|
||||
for dep in deps:
|
||||
if not dep:
|
||||
continue
|
||||
req, req_idx, markers_idx = self.parse_line(
|
||||
dep,
|
||||
index_lookup=index_lookup,
|
||||
markers_lookup=markers_lookup,
|
||||
project=project,
|
||||
req: InstallRequirement,
|
||||
) -> bool:
|
||||
if req.markers and not req.markers.evaluate():
|
||||
err.print(
|
||||
f"Could not find a matching version of {req}; {req.markers} for your environment, "
|
||||
"its dependencies will be skipped.",
|
||||
)
|
||||
index_lookup.update(req_idx)
|
||||
markers_lookup.update(markers_idx)
|
||||
# Add dependencies of any file (e.g. wheels/tarballs), source, or local
|
||||
# directories into the initial constraint pool to be resolved with the
|
||||
# rest of the dependencies, while adding the files/vcs deps/paths themselves
|
||||
# to the lockfile directly
|
||||
use_sources = None
|
||||
if req.name in index_lookup:
|
||||
use_sources = list(
|
||||
filter(lambda s: s.get("name") == index_lookup[req.name], sources)
|
||||
)
|
||||
if not use_sources:
|
||||
use_sources = sources
|
||||
transient_resolver = Resolver(
|
||||
[],
|
||||
req_dir,
|
||||
project,
|
||||
use_sources,
|
||||
index_lookup=index_lookup,
|
||||
markers_lookup=markers_lookup,
|
||||
clear=clear,
|
||||
pre=pre,
|
||||
category=category,
|
||||
)
|
||||
constraint_update, lockfile_update = self.get_deps_from_req(
|
||||
req, resolver=transient_resolver, resolve_vcs=project.s.PIPENV_RESOLVE_VCS
|
||||
)
|
||||
constraints |= constraint_update
|
||||
skipped.update(lockfile_update)
|
||||
return constraints, skipped, index_lookup, markers_lookup
|
||||
|
||||
def parse_line(
|
||||
self,
|
||||
line: str,
|
||||
index_lookup: Dict[str, str] = None,
|
||||
markers_lookup: Dict[str, str] = None,
|
||||
project: Optional[Project] = None,
|
||||
) -> Tuple[Requirement, Dict[str, str], Dict[str, str]]:
|
||||
if index_lookup is None:
|
||||
index_lookup = {}
|
||||
if markers_lookup is None:
|
||||
markers_lookup = {}
|
||||
if project is None:
|
||||
from pipenv.project import Project
|
||||
|
||||
project = Project()
|
||||
index, extra_index, trust_host, remainder = parse_indexes(line)
|
||||
line = " ".join(remainder)
|
||||
try:
|
||||
req = Requirement.from_line(line)
|
||||
except ValueError:
|
||||
direct_url = DIRECT_URL_RE.match(line)
|
||||
if direct_url:
|
||||
name = direct_url.groupdict()["name"]
|
||||
line = f"{name}@ {line}"
|
||||
try:
|
||||
req = Requirement.from_line(line)
|
||||
except ValueError:
|
||||
raise ResolutionFailure(
|
||||
f"Failed to resolve requirement from line: {line!s}"
|
||||
)
|
||||
else:
|
||||
raise ResolutionFailure(
|
||||
f"Failed to resolve requirement from line: {line!s}"
|
||||
)
|
||||
if index:
|
||||
try:
|
||||
index_lookup[req.normalized_name] = project.get_source(
|
||||
url=index, refresh=True
|
||||
).get("name")
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
req.normalized_name
|
||||
except TypeError:
|
||||
raise RequirementError(req=req)
|
||||
# strip the marker and re-add it later after resolution
|
||||
# but we will need a fallback in case resolution fails
|
||||
# eg pypiwin32
|
||||
if req.markers:
|
||||
markers_lookup[req.normalized_name] = req.markers.replace('"', "'")
|
||||
return req, index_lookup, markers_lookup
|
||||
|
||||
def get_deps_from_req(
|
||||
self,
|
||||
req: Requirement,
|
||||
resolver: Optional["Resolver"] = None,
|
||||
resolve_vcs: bool = True,
|
||||
) -> Tuple[Set[str], Dict[str, Dict[str, Union[str, bool, List[str]]]]]:
|
||||
from pipenv.vendor.requirementslib.models.requirements import Requirement
|
||||
from pipenv.vendor.requirementslib.models.utils import (
|
||||
_requirement_to_str_lowercase_name,
|
||||
)
|
||||
from pipenv.vendor.requirementslib.utils import is_installable_dir
|
||||
|
||||
# TODO: this is way too complex, refactor this
|
||||
constraints: Set[str] = set()
|
||||
locked_deps: Dict[str, Dict[str, Union[str, bool, List[str]]]] = {}
|
||||
editable_packages = self.project.get_editable_packages(category=self.category)
|
||||
if (req.is_file_or_url or req.is_vcs) and not req.is_wheel:
|
||||
# for local packages with setup.py files and potential direct url deps:
|
||||
if req.is_vcs:
|
||||
req_list, lockfile = get_vcs_deps(reqs=[req])
|
||||
req = next(iter(req for req in req_list if req is not None), req_list)
|
||||
entry = lockfile[pep423_name(req.normalized_name)]
|
||||
else:
|
||||
_, entry = req.pipfile_entry
|
||||
parsed_line: Line = req.req.parsed_line
|
||||
try:
|
||||
name = req.normalized_name
|
||||
except TypeError:
|
||||
raise RequirementError(req=req)
|
||||
if parsed_line.setup_info:
|
||||
setup_info = parsed_line.setup_info
|
||||
else:
|
||||
setup_info = req.req.parse_setup_info()
|
||||
locked_deps[pep423_name(name)] = entry
|
||||
requirements = []
|
||||
# Allow users to toggle resolution off for non-editable VCS packages
|
||||
# but leave it on for local, installable folders on the filesystem
|
||||
if resolve_vcs or (
|
||||
req.editable
|
||||
or parsed_line.is_wheel
|
||||
or (
|
||||
req.is_file_or_url
|
||||
and parsed_line.is_local
|
||||
and is_installable_dir(parsed_line.path)
|
||||
)
|
||||
):
|
||||
setup_info.run_pyproject()
|
||||
setup_info.run_setup()
|
||||
requirements = [v for v in getattr(setup_info, "requires", {}).values()]
|
||||
if req.extras:
|
||||
for extra in req.extras:
|
||||
requirements.extend(
|
||||
v
|
||||
for v in getattr(setup_info, "extras", {}).get(extra, [])
|
||||
if v not in requirements
|
||||
)
|
||||
for r in requirements:
|
||||
if getattr(r, "url", None) and not getattr(r, "editable", False):
|
||||
if r is not None:
|
||||
if not r.url:
|
||||
continue
|
||||
line = _requirement_to_str_lowercase_name(r)
|
||||
new_req, _, _ = self.parse_line(line)
|
||||
if r.marker and not r.marker.evaluate():
|
||||
new_constraints = {}
|
||||
_, new_entry = req.pipfile_entry
|
||||
new_lock = {pep423_name(new_req.normalized_name): new_entry}
|
||||
else:
|
||||
new_constraints, new_lock = self.get_deps_from_req(
|
||||
new_req, resolver
|
||||
)
|
||||
locked_deps.update(new_lock)
|
||||
constraints |= new_constraints
|
||||
# if there is no marker or there is a valid marker, add the constraint line
|
||||
elif r and (not r.marker or (r.marker and r.marker.evaluate())):
|
||||
if r.name not in editable_packages:
|
||||
line = _requirement_to_str_lowercase_name(r)
|
||||
constraints.add(line)
|
||||
# ensure the top level entry remains as provided
|
||||
# note that we shouldn't pin versions for editable vcs deps
|
||||
if not req.is_vcs:
|
||||
if req.specifiers:
|
||||
locked_deps[name]["version"] = req.specifiers
|
||||
elif parsed_line.setup_info and parsed_line.setup_info.version:
|
||||
locked_deps[name]["version"] = f"=={parsed_line.setup_info.version}"
|
||||
# if not req.is_vcs:
|
||||
locked_deps.update({name: entry})
|
||||
else:
|
||||
# if the dependency isn't installable, don't add it to constraints
|
||||
# and instead add it directly to the lock
|
||||
if (
|
||||
req
|
||||
and req.requirement
|
||||
and (req.requirement.marker and not req.requirement.marker.evaluate())
|
||||
):
|
||||
pypi = resolver.finder if resolver else None
|
||||
ireq = req.ireq
|
||||
best_match = (
|
||||
pypi.find_best_candidate(ireq.name, ireq.specifier).best_candidate
|
||||
if pypi
|
||||
else None
|
||||
)
|
||||
if best_match:
|
||||
ireq.req.specifier = ireq.specifier.__class__(
|
||||
f"=={best_match.version}"
|
||||
)
|
||||
hashes = resolver.collect_hashes(ireq) if resolver else []
|
||||
new_req = Requirement.from_ireq(ireq)
|
||||
new_req.add_hashes(hashes)
|
||||
new_req.merge_markers(req.markers)
|
||||
name, entry = new_req.pipfile_entry
|
||||
locked_deps[pep423_name(name)] = translate_markers(entry)
|
||||
click.echo(
|
||||
"{} doesn't match your environment, "
|
||||
"its dependencies won't be resolved.".format(req.as_line()),
|
||||
err=True,
|
||||
)
|
||||
else:
|
||||
click.echo(
|
||||
"Could not find a version of {} that matches your environment, "
|
||||
"it will be skipped.".format(req.as_line()),
|
||||
err=True,
|
||||
)
|
||||
return constraints, locked_deps
|
||||
constraints.add(req.constraint_line)
|
||||
return constraints, locked_deps
|
||||
return constraints, locked_deps
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
deps: List[str],
|
||||
deps: Set[str],
|
||||
project: Project,
|
||||
index_lookup: Dict[str, str] = None,
|
||||
markers_lookup: Dict[str, str] = None,
|
||||
@@ -463,41 +190,68 @@ class Resolver:
|
||||
index_lookup = {}
|
||||
if markers_lookup is None:
|
||||
markers_lookup = {}
|
||||
original_deps = {}
|
||||
install_reqs = {}
|
||||
pipfile_entries = {}
|
||||
skipped = {}
|
||||
if sources is None:
|
||||
sources = project.sources
|
||||
packages = project.get_pipfile_section(category)
|
||||
constraints = set()
|
||||
for dep in deps: # Build up the index and markers lookups
|
||||
if not dep:
|
||||
continue
|
||||
is_constraint = True
|
||||
install_req = expansive_install_req_from_line(dep, expand_env=True)
|
||||
package_name = determine_package_name(install_req)
|
||||
original_deps[package_name] = dep
|
||||
install_reqs[package_name] = install_req
|
||||
index, extra_index, trust_host, remainder = parse_indexes(dep)
|
||||
if package_name in packages:
|
||||
pipfile_entry = packages[package_name]
|
||||
pipfile_entries[package_name] = pipfile_entry
|
||||
if isinstance(pipfile_entry, dict):
|
||||
if packages[package_name].get("index"):
|
||||
index_lookup[package_name] = packages[package_name].get("index")
|
||||
if packages[package_name].get("skip_resolver"):
|
||||
is_constraint = False
|
||||
skipped[package_name] = dep
|
||||
elif index:
|
||||
index_lookup[package_name] = index
|
||||
else:
|
||||
index_lookup[package_name] = project.get_default_index()["name"]
|
||||
if install_req.markers:
|
||||
markers_lookup[package_name] = install_req.markers
|
||||
if is_constraint:
|
||||
constraints.add(dep)
|
||||
resolver = Resolver(
|
||||
[],
|
||||
set(),
|
||||
req_dir,
|
||||
project,
|
||||
sources,
|
||||
index_lookup=index_lookup,
|
||||
markers_lookup=markers_lookup,
|
||||
skipped=skipped,
|
||||
clear=clear,
|
||||
pre=pre,
|
||||
category=category,
|
||||
original_deps=original_deps,
|
||||
install_reqs=install_reqs,
|
||||
pipfile_entries=pipfile_entries,
|
||||
)
|
||||
constraints, skipped, index_lookup, markers_lookup = resolver.get_metadata(
|
||||
deps,
|
||||
index_lookup,
|
||||
markers_lookup,
|
||||
project,
|
||||
sources,
|
||||
req_dir=req_dir,
|
||||
pre=pre,
|
||||
clear=clear,
|
||||
category=category,
|
||||
) # Workaround to the fact `get_metadata` instantiates a transient Resolver
|
||||
for package_name, dep in original_deps.items():
|
||||
install_req = install_reqs[package_name]
|
||||
if resolver.check_if_package_req_skipped(install_req):
|
||||
resolver.skipped[package_name] = dep
|
||||
resolver.initial_constraints = constraints
|
||||
resolver.skipped = skipped
|
||||
resolver.index_lookup = index_lookup
|
||||
resolver.finder.index_lookup = index_lookup
|
||||
resolver.markers_lookup = markers_lookup
|
||||
return resolver
|
||||
|
||||
@property
|
||||
def pip_command(self):
|
||||
if self._pip_command is None:
|
||||
self._pip_command = self._get_pip_command()
|
||||
return self._pip_command
|
||||
return self._get_pip_command()
|
||||
|
||||
def prepare_pip_args(self, use_pep517=None, build_isolation=True):
|
||||
pip_args = []
|
||||
@@ -516,11 +270,9 @@ class Resolver:
|
||||
def pip_args(self):
|
||||
use_pep517 = environments.get_from_env("USE_PEP517", prefix="PIP")
|
||||
build_isolation = environments.get_from_env("BUILD_ISOLATION", prefix="PIP")
|
||||
if self._pip_args is None:
|
||||
self._pip_args = self.prepare_pip_args(
|
||||
use_pep517=use_pep517, build_isolation=build_isolation
|
||||
)
|
||||
return self._pip_args
|
||||
return self.prepare_pip_args(
|
||||
use_pep517=use_pep517, build_isolation=build_isolation
|
||||
)
|
||||
|
||||
def prepare_constraint_file(self):
|
||||
constraint_filename = prepare_constraint_file(
|
||||
@@ -533,9 +285,7 @@ class Resolver:
|
||||
|
||||
@property
|
||||
def constraint_file(self):
|
||||
if self._constraint_file is None:
|
||||
self._constraint_file = self.prepare_constraint_file()
|
||||
return self._constraint_file
|
||||
return self.prepare_constraint_file()
|
||||
|
||||
@cached_property
|
||||
def default_constraint_file(self):
|
||||
@@ -550,24 +300,20 @@ class Resolver:
|
||||
|
||||
@property
|
||||
def pip_options(self):
|
||||
if self._pip_options is None:
|
||||
pip_options, _ = self.pip_command.parser.parse_args(self.pip_args)
|
||||
pip_options.cache_dir = self.project.s.PIPENV_CACHE_DIR
|
||||
pip_options.no_python_version_warning = True
|
||||
pip_options.no_input = self.project.settings.get("disable_pip_input", True)
|
||||
pip_options.progress_bar = "off"
|
||||
pip_options.ignore_requires_python = True
|
||||
pip_options.pre = self.pre or self.project.settings.get(
|
||||
"allow_prereleases", False
|
||||
)
|
||||
self._pip_options = pip_options
|
||||
return self._pip_options
|
||||
pip_options, _ = self.pip_command.parser.parse_args(self.pip_args)
|
||||
pip_options.cache_dir = self.project.s.PIPENV_CACHE_DIR
|
||||
pip_options.no_python_version_warning = True
|
||||
pip_options.no_input = self.project.settings.get("disable_pip_input", True)
|
||||
pip_options.progress_bar = "off"
|
||||
pip_options.ignore_requires_python = True
|
||||
pip_options.pre = self.pre or self.project.settings.get(
|
||||
"allow_prereleases", False
|
||||
)
|
||||
return pip_options
|
||||
|
||||
@property
|
||||
def session(self):
|
||||
if self._session is None:
|
||||
self._session = self.pip_command._build_session(self.pip_options)
|
||||
return self._session
|
||||
return self.pip_command._build_session(self.pip_options)
|
||||
|
||||
def prepare_index_lookup(self):
|
||||
index_mapping = {}
|
||||
@@ -582,29 +328,26 @@ class Resolver:
|
||||
|
||||
@property
|
||||
def finder(self):
|
||||
if self._finder is None:
|
||||
self._finder = get_package_finder(
|
||||
install_cmd=self.pip_command,
|
||||
options=self.pip_options,
|
||||
session=self.session,
|
||||
)
|
||||
finder = get_package_finder(
|
||||
install_cmd=self.pip_command,
|
||||
options=self.pip_options,
|
||||
session=self.session,
|
||||
)
|
||||
index_lookup = self.prepare_index_lookup()
|
||||
self._finder._link_collector.index_lookup = index_lookup
|
||||
self._finder._link_collector.search_scope.index_lookup = index_lookup
|
||||
return self._finder
|
||||
finder._link_collector.index_lookup = index_lookup
|
||||
finder._link_collector.search_scope.index_lookup = index_lookup
|
||||
return finder
|
||||
|
||||
@property
|
||||
def parsed_constraints(self):
|
||||
pip_options = self.pip_options
|
||||
pip_options.extra_index_urls = []
|
||||
if self._parsed_constraints is None:
|
||||
self._parsed_constraints = parse_requirements(
|
||||
self.constraint_file,
|
||||
finder=self.finder,
|
||||
session=self.session,
|
||||
options=pip_options,
|
||||
)
|
||||
return self._parsed_constraints
|
||||
return parse_requirements(
|
||||
self.constraint_file,
|
||||
finder=self.finder,
|
||||
session=self.session,
|
||||
options=pip_options,
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def parsed_default_constraints(self):
|
||||
@@ -617,11 +360,11 @@ class Resolver:
|
||||
session=self.session,
|
||||
options=pip_options,
|
||||
)
|
||||
return parsed_default_constraints
|
||||
return set(parsed_default_constraints)
|
||||
|
||||
@cached_property
|
||||
def default_constraints(self):
|
||||
default_constraints = [
|
||||
possible_default_constraints = [
|
||||
install_req_from_parsed_requirement(
|
||||
c,
|
||||
isolated=self.pip_options.build_isolation,
|
||||
@@ -629,25 +372,34 @@ class Resolver:
|
||||
)
|
||||
for c in self.parsed_default_constraints
|
||||
]
|
||||
return default_constraints
|
||||
default_constraints = []
|
||||
for c in possible_default_constraints:
|
||||
default_constraints.append(c)
|
||||
return set(default_constraints)
|
||||
|
||||
@property
|
||||
def possible_constraints(self):
|
||||
possible_constraints_list = [
|
||||
install_req_from_parsed_requirement(
|
||||
c,
|
||||
isolated=self.pip_options.build_isolation,
|
||||
use_pep517=self.pip_options.use_pep517,
|
||||
user_supplied=True,
|
||||
)
|
||||
for c in self.parsed_constraints
|
||||
]
|
||||
return possible_constraints_list
|
||||
|
||||
@property
|
||||
def constraints(self):
|
||||
if self._constraints is None:
|
||||
self._constraints = [
|
||||
install_req_from_parsed_requirement(
|
||||
c,
|
||||
isolated=self.pip_options.build_isolation,
|
||||
use_pep517=self.pip_options.use_pep517,
|
||||
user_supplied=True,
|
||||
)
|
||||
for c in self.parsed_constraints
|
||||
]
|
||||
# Only use default_constraints when installing dev-packages
|
||||
if self.category != "packages":
|
||||
self._constraints += self.default_constraints
|
||||
self._constraints.sort(key=lambda ireq: ireq.name)
|
||||
return self._constraints
|
||||
possible_constraints_list = self.possible_constraints
|
||||
constraints_list = set()
|
||||
for c in possible_constraints_list:
|
||||
constraints_list.add(c)
|
||||
# Only use default_constraints when installing dev-packages
|
||||
if self.category != "packages":
|
||||
constraints_list |= self.default_constraints
|
||||
return set(constraints_list)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def get_resolver(self, clear=False):
|
||||
@@ -682,7 +434,6 @@ class Resolver:
|
||||
yield resolver
|
||||
|
||||
def resolve(self):
|
||||
self.constraints # For some reason it is important to evaluate constraints before resolver context
|
||||
with temp_environ(), self.get_resolver() as resolver:
|
||||
try:
|
||||
results = resolver.resolve(self.constraints, check_supported_wheels=False)
|
||||
@@ -694,7 +445,7 @@ class Resolver:
|
||||
return self.resolved_tree
|
||||
|
||||
def resolve_constraints(self):
|
||||
from pipenv.vendor.requirementslib.models.markers import marker_from_specifier
|
||||
from .markers import marker_from_specifier
|
||||
|
||||
new_tree = set()
|
||||
for result in self.resolved_tree:
|
||||
@@ -723,100 +474,10 @@ class Resolver:
|
||||
new_tree.add(result)
|
||||
self.resolved_tree = new_tree
|
||||
|
||||
@classmethod
|
||||
def prepend_hash_types(cls, checksums, hash_type):
|
||||
cleaned_checksums = set()
|
||||
for checksum in checksums:
|
||||
if not checksum:
|
||||
continue
|
||||
if not checksum.startswith(f"{hash_type}:"):
|
||||
checksum = f"{hash_type}:{checksum}"
|
||||
cleaned_checksums.add(checksum)
|
||||
return cleaned_checksums
|
||||
|
||||
def _get_requests_session_for_source(self, source):
|
||||
if self._sessions.get(source["name"]):
|
||||
session = self._sessions[source["name"]]
|
||||
else:
|
||||
session = _get_requests_session(
|
||||
self.project.s.PIPENV_MAX_RETRIES, source.get("verify_ssl", True)
|
||||
)
|
||||
self._sessions[source["name"]] = session
|
||||
return session
|
||||
|
||||
def _get_hashes_from_pypi(self, ireq, source):
|
||||
pkg_url = f"https://pypi.org/pypi/{ireq.name}/json"
|
||||
session = self._get_requests_session_for_source(source)
|
||||
try:
|
||||
collected_hashes = set()
|
||||
# Grab the hashes from the new warehouse API.
|
||||
r = session.get(pkg_url, timeout=10)
|
||||
api_releases = r.json()["releases"]
|
||||
cleaned_releases = {}
|
||||
for api_version, api_info in api_releases.items():
|
||||
api_version = clean_pkg_version(api_version)
|
||||
cleaned_releases[api_version] = api_info
|
||||
version = ""
|
||||
if ireq.specifier:
|
||||
spec = next(iter(s for s in ireq.specifier), None)
|
||||
if spec:
|
||||
version = spec.version
|
||||
for release in cleaned_releases[version]:
|
||||
collected_hashes.add(release["digests"][FAVORITE_HASH])
|
||||
return self.prepend_hash_types(collected_hashes, FAVORITE_HASH)
|
||||
except (ValueError, KeyError, ConnectionError):
|
||||
if self.project.s.is_verbose():
|
||||
click.echo(
|
||||
"{}: Error generating hash for {}".format(
|
||||
click.style("Warning", bold=True, fg="red"), ireq.name
|
||||
),
|
||||
err=True,
|
||||
)
|
||||
return None
|
||||
|
||||
def _get_hashes_from_remote_index_urls(self, ireq, source):
|
||||
pkg_url = f"{source['url']}/{ireq.name}/"
|
||||
session = self._get_requests_session_for_source(source)
|
||||
try:
|
||||
collected_hashes = set()
|
||||
# Grab the hashes from the new warehouse API.
|
||||
response = session.get(pkg_url, timeout=10)
|
||||
# Create an instance of the parser
|
||||
parser = PackageIndexHTMLParser()
|
||||
# Feed the HTML to the parser
|
||||
parser.feed(response.text)
|
||||
# Extract hrefs
|
||||
hrefs = parser.urls
|
||||
|
||||
version = ""
|
||||
if ireq.specifier:
|
||||
spec = next(iter(s for s in ireq.specifier), None)
|
||||
if spec:
|
||||
version = spec.version
|
||||
for package_url in hrefs:
|
||||
if version in package_url:
|
||||
url_params = parse.urlparse(package_url).fragment
|
||||
params_dict = parse.parse_qs(url_params)
|
||||
if params_dict.get(FAVORITE_HASH):
|
||||
collected_hashes.add(params_dict[FAVORITE_HASH][0])
|
||||
return self.prepend_hash_types(collected_hashes, FAVORITE_HASH)
|
||||
except (ValueError, KeyError, ConnectionError):
|
||||
if self.project.s.is_verbose():
|
||||
click.echo(
|
||||
"{}: Error generating hash for {}".format(
|
||||
click.style("Warning", bold=True, fg="red"), ireq.name
|
||||
),
|
||||
err=True,
|
||||
)
|
||||
return None
|
||||
|
||||
def collect_hashes(self, ireq):
|
||||
link = ireq.link # Handle VCS and file links first
|
||||
if link and (link.is_vcs or (link.is_file and link.is_existing_dir())):
|
||||
return set()
|
||||
if link and ireq.original_link:
|
||||
return {self._get_hash_from_link(ireq.original_link)}
|
||||
|
||||
if not is_pinned_requirement(ireq):
|
||||
return set()
|
||||
|
||||
@@ -828,11 +489,11 @@ class Resolver:
|
||||
source = sources[0] if len(sources) else None
|
||||
if source:
|
||||
if is_pypi_url(source["url"]):
|
||||
hashes = self._get_hashes_from_pypi(ireq, source)
|
||||
hashes = self.project.get_hashes_from_pypi(ireq, source)
|
||||
if hashes:
|
||||
return hashes
|
||||
else:
|
||||
hashes = self._get_hashes_from_remote_index_urls(ireq, source)
|
||||
hashes = self.project.get_hashes_from_remote_index_urls(ireq, source)
|
||||
if hashes:
|
||||
return hashes
|
||||
|
||||
@@ -841,12 +502,14 @@ class Resolver:
|
||||
).iter_applicable()
|
||||
applicable_candidates = list(applicable_candidates)
|
||||
if applicable_candidates:
|
||||
return {
|
||||
self._get_hash_from_link(candidate.link)
|
||||
for candidate in applicable_candidates
|
||||
}
|
||||
return sorted(
|
||||
{
|
||||
self.project.get_hash_from_link(self.hash_cache, candidate.link)
|
||||
for candidate in applicable_candidates
|
||||
}
|
||||
)
|
||||
if link:
|
||||
return {self._get_hash_from_link(link)}
|
||||
return {self.project.get_hash_from_link(self.hash_cache, link)}
|
||||
return set()
|
||||
|
||||
def resolve_hashes(self):
|
||||
@@ -855,19 +518,18 @@ class Resolver:
|
||||
self.hashes[ireq] = self.collect_hashes(ireq)
|
||||
return self.hashes
|
||||
|
||||
def _get_hash_from_link(self, link):
|
||||
if link.hash and link.hash_name == FAVORITE_HASH:
|
||||
return f"{link.hash_name}:{link.hash}"
|
||||
|
||||
return self.hash_cache.get_hash(link)
|
||||
|
||||
def _clean_skipped_result(self, req, value):
|
||||
def clean_skipped_result(
|
||||
self, req_name: str, ireq: InstallRequirement, pipfile_entry
|
||||
):
|
||||
ref = None
|
||||
if req.is_vcs:
|
||||
ref = req.commit_hash
|
||||
ireq = req.ireq
|
||||
entry = value.copy()
|
||||
entry["name"] = req.name
|
||||
if ireq.link and ireq.link.is_vcs:
|
||||
ref = ireq.link.egg_fragment
|
||||
|
||||
if isinstance(pipfile_entry, dict):
|
||||
entry = pipfile_entry.copy()
|
||||
else:
|
||||
entry = {}
|
||||
entry["name"] = req_name
|
||||
if entry.get("editable", False) and entry.get("version"):
|
||||
del entry["version"]
|
||||
ref = ref if ref is not None else entry.get("ref")
|
||||
@@ -876,31 +538,35 @@ class Resolver:
|
||||
collected_hashes = self.collect_hashes(ireq)
|
||||
if collected_hashes:
|
||||
entry["hashes"] = sorted(set(collected_hashes))
|
||||
return req.name, entry
|
||||
return req_name, entry
|
||||
|
||||
def clean_results(self):
|
||||
reqs = [(Requirement.from_ireq(ireq), ireq) for ireq in self.resolved_tree]
|
||||
reqs = [(ireq,) for ireq in self.resolved_tree]
|
||||
results = {}
|
||||
for req, ireq in reqs:
|
||||
if req.vcs and req.editable and not req.is_direct_url:
|
||||
continue
|
||||
elif req.normalized_name in self.skipped.keys():
|
||||
for (ireq,) in reqs:
|
||||
if normalize_name(ireq.name) in self.skipped.keys():
|
||||
continue
|
||||
collected_hashes = self.hashes.get(ireq, set())
|
||||
req.add_hashes(collected_hashes)
|
||||
if collected_hashes:
|
||||
collected_hashes = sorted(collected_hashes)
|
||||
name, entry = format_requirement_for_lockfile(
|
||||
req, self.markers_lookup, self.index_lookup, collected_hashes
|
||||
ireq,
|
||||
self.markers_lookup,
|
||||
self.index_lookup,
|
||||
self.original_deps,
|
||||
self.pipfile_entries,
|
||||
collected_hashes,
|
||||
)
|
||||
entry = translate_markers(entry)
|
||||
if name in results:
|
||||
results[name].update(entry)
|
||||
else:
|
||||
results[name] = entry
|
||||
for k in list(self.skipped.keys()):
|
||||
req = Requirement.from_pipfile(k, self.skipped[k])
|
||||
name, entry = self._clean_skipped_result(req, self.skipped[k])
|
||||
for req_name in self.skipped:
|
||||
install_req = self.install_reqs[req_name]
|
||||
name, entry = self.clean_skipped_result(
|
||||
req_name, install_req, self.pipfile_entries[req_name]
|
||||
)
|
||||
entry = translate_markers(entry)
|
||||
if name in results:
|
||||
results[name].update(entry)
|
||||
@@ -957,7 +623,7 @@ def actually_resolve_deps(
|
||||
warning.lineno,
|
||||
warning.line,
|
||||
)
|
||||
return (results, hashes, resolver.markers_lookup, resolver, resolver.skipped)
|
||||
return (results, hashes, resolver)
|
||||
|
||||
|
||||
def resolve(cmd, st, project):
|
||||
@@ -999,6 +665,7 @@ def venv_resolve_deps(
|
||||
pypi_mirror=None,
|
||||
pipfile=None,
|
||||
lockfile=None,
|
||||
old_lock_data=None,
|
||||
):
|
||||
"""
|
||||
Resolve dependencies for a pipenv project, acts as a portal to the target environment.
|
||||
@@ -1008,7 +675,7 @@ def venv_resolve_deps(
|
||||
dependency resolution. This function reads the output of that call and mutates
|
||||
the provided lockfile accordingly, returning nothing.
|
||||
|
||||
:param List[:class:`~requirementslib.Requirement`] deps: A list of dependencies to resolve.
|
||||
:param List[:class:`~pip.InstallRequirement`] deps: A list of dependencies to resolve.
|
||||
:param Callable which: [description]
|
||||
:param project: The pipenv Project instance to use during resolution
|
||||
:param Optional[bool] pre: Whether to resolve pre-release candidates, defaults to False
|
||||
@@ -1036,6 +703,8 @@ def venv_resolve_deps(
|
||||
pipfile = getattr(project, category, {})
|
||||
if lockfile is None:
|
||||
lockfile = project.lockfile(categories=[category])
|
||||
if old_lock_data is None:
|
||||
old_lock_data = lockfile.get(lockfile_section, {})
|
||||
req_dir = create_tracked_tempdir(prefix="pipenv", suffix="requirements")
|
||||
results = []
|
||||
with temp_environ():
|
||||
@@ -1054,9 +723,10 @@ def venv_resolve_deps(
|
||||
# dependency resolution on them, so we are including this step inside the
|
||||
# spinner context manager for the UX improvement
|
||||
st.console.print("Building requirements...")
|
||||
deps = convert_deps_to_pip(deps, project, include_index=True)
|
||||
deps = convert_deps_to_pip(
|
||||
deps, project.pipfile_sources(), include_index=True
|
||||
)
|
||||
constraints = set(deps)
|
||||
st.console.print("Resolving dependencies...")
|
||||
# Useful for debugging and hitting breakpoints in the resolver
|
||||
if project.s.PIPENV_RESOLVER_PARENT_PYTHON:
|
||||
try:
|
||||
@@ -1079,7 +749,7 @@ def venv_resolve_deps(
|
||||
st.console.print(
|
||||
environments.PIPENV_SPINNER_FAIL_TEXT.format("Locking Failed!")
|
||||
)
|
||||
raise
|
||||
raise # maybe sys.exit(1) here?
|
||||
else: # Default/Production behavior is to use project python's resolver
|
||||
cmd = [
|
||||
which("python", allow_global=allow_global),
|
||||
@@ -1094,6 +764,8 @@ def venv_resolve_deps(
|
||||
if category:
|
||||
cmd.append("--category")
|
||||
cmd.append(category)
|
||||
if project.s.is_verbose():
|
||||
cmd.append("--verbose")
|
||||
target_file = tempfile.NamedTemporaryFile(
|
||||
prefix="resolver", suffix=".json", delete=False
|
||||
)
|
||||
@@ -1133,7 +805,9 @@ def venv_resolve_deps(
|
||||
click.echo(f"Error: {c.stderr.strip()}", err=True)
|
||||
if lockfile_section not in lockfile:
|
||||
lockfile[lockfile_section] = {}
|
||||
return prepare_lockfile(results, pipfile, lockfile[lockfile_section])
|
||||
return prepare_lockfile(
|
||||
project, results, pipfile, lockfile[lockfile_section], old_lock_data
|
||||
)
|
||||
|
||||
|
||||
def resolve_deps(
|
||||
@@ -1164,7 +838,7 @@ def resolve_deps(
|
||||
req_dir = create_tracked_tempdir(prefix="pipenv-", suffix="-requirements")
|
||||
with HackedPythonVersion(python_path=project.python(system=allow_global)):
|
||||
try:
|
||||
results, hashes, markers_lookup, resolver, skipped = actually_resolve_deps(
|
||||
results, hashes, internal_resolver = actually_resolve_deps(
|
||||
deps,
|
||||
index_lookup,
|
||||
markers_lookup,
|
||||
@@ -1189,9 +863,7 @@ def resolve_deps(
|
||||
(
|
||||
results,
|
||||
hashes,
|
||||
markers_lookup,
|
||||
resolver,
|
||||
skipped,
|
||||
internal_resolver,
|
||||
) = actually_resolve_deps(
|
||||
deps,
|
||||
index_lookup,
|
||||
@@ -1205,7 +877,7 @@ def resolve_deps(
|
||||
)
|
||||
except RuntimeError:
|
||||
sys.exit(1)
|
||||
return results, resolver
|
||||
return results, internal_resolver
|
||||
|
||||
|
||||
@lru_cache()
|
||||
|
||||
@@ -11,9 +11,9 @@ from contextlib import contextmanager
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
from pipenv.utils.fileutils import normalize_drive, normalize_path
|
||||
from pipenv.vendor import click
|
||||
from pipenv.vendor.pythonfinder.utils import ensure_path
|
||||
from pipenv.vendor.requirementslib.fileutils import normalize_drive, normalize_path
|
||||
|
||||
from .constants import FALSE_VALUES, SCHEME_LIST, TRUE_VALUES
|
||||
from .processes import subprocess_run
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
from typing import Union
|
||||
|
||||
from pipenv.vendor.plette.models import Package, PackageCollection
|
||||
from pipenv.vendor.tomlkit.container import Container
|
||||
from pipenv.vendor.tomlkit.items import AoT, Array, Bool, InlineTable, Item, String, Table
|
||||
|
||||
try:
|
||||
import tomllib as toml
|
||||
except ImportError:
|
||||
@@ -5,6 +11,10 @@ except ImportError:
|
||||
|
||||
from pipenv.vendor import tomlkit
|
||||
|
||||
TOML_DICT_TYPES = Union[Container, Package, PackageCollection, Table, InlineTable]
|
||||
TOML_DICT_OBJECTS = (Container, Package, Table, InlineTable, PackageCollection)
|
||||
TOML_DICT_NAMES = [o.__class__.__name__ for o in TOML_DICT_OBJECTS]
|
||||
|
||||
|
||||
def cleanup_toml(tml):
|
||||
toml = tml.split("\n")
|
||||
@@ -74,3 +84,57 @@ def convert_toml_outline_tables(parsed, project):
|
||||
|
||||
parsed[section] = result
|
||||
return parsed
|
||||
|
||||
|
||||
def tomlkit_value_to_python(toml_value):
|
||||
# type: (Union[Array, AoT, TOML_DICT_TYPES, Item]) -> Union[List, Dict]
|
||||
value_type = type(toml_value).__name__
|
||||
if (
|
||||
isinstance(toml_value, TOML_DICT_OBJECTS + (dict,))
|
||||
or value_type in TOML_DICT_NAMES
|
||||
):
|
||||
return tomlkit_dict_to_python(toml_value)
|
||||
elif isinstance(toml_value, AoT) or value_type == "AoT":
|
||||
return [tomlkit_value_to_python(val) for val in toml_value._body]
|
||||
elif isinstance(toml_value, Array) or value_type == "Array":
|
||||
return [tomlkit_value_to_python(val) for val in list(toml_value)]
|
||||
elif isinstance(toml_value, String) or value_type == "String":
|
||||
return f"{toml_value!s}"
|
||||
elif isinstance(toml_value, Bool) or value_type == "Bool":
|
||||
return toml_value.value
|
||||
elif isinstance(toml_value, Item):
|
||||
return toml_value.value
|
||||
return toml_value
|
||||
|
||||
|
||||
def tomlkit_dict_to_python(toml_dict):
|
||||
# type: (TOML_DICT_TYPES) -> Dict
|
||||
value_type = type(toml_dict).__name__
|
||||
if toml_dict is None:
|
||||
raise TypeError("Invalid type NoneType when converting toml dict to python")
|
||||
converted = None # type: Optional[Dict]
|
||||
if isinstance(toml_dict, (InlineTable, Table)) or value_type in (
|
||||
"InlineTable",
|
||||
"Table",
|
||||
):
|
||||
converted = toml_dict.value
|
||||
elif isinstance(toml_dict, (Package, PackageCollection)) or value_type in (
|
||||
"Package, PackageCollection"
|
||||
):
|
||||
converted = toml_dict._data
|
||||
if isinstance(converted, Container) or type(converted).__name__ == "Container":
|
||||
converted = converted.value
|
||||
elif isinstance(toml_dict, Container) or value_type == "Container":
|
||||
converted = toml_dict.value
|
||||
elif isinstance(toml_dict, dict):
|
||||
converted = toml_dict.copy()
|
||||
else:
|
||||
raise TypeError(
|
||||
"Invalid type for conversion: expected Container, Dict, or Table, "
|
||||
"got {!r}".format(toml_dict)
|
||||
)
|
||||
if isinstance(converted, dict):
|
||||
return {k: tomlkit_value_to_python(v) for k, v in converted.items()}
|
||||
elif isinstance(converted, (TOML_DICT_OBJECTS)) or value_type in TOML_DICT_NAMES:
|
||||
return tomlkit_dict_to_python(converted)
|
||||
return converted
|
||||
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2019-2021 Dan Ryan and Frost Ming.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
import logging
|
||||
import warnings
|
||||
|
||||
from .models.lockfile import Lockfile
|
||||
from .models.pipfile import Pipfile
|
||||
from .models.requirements import Requirement
|
||||
|
||||
__version__ = "3.0.0"
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.addHandler(logging.NullHandler())
|
||||
warnings.filterwarnings("ignore", category=ResourceWarning)
|
||||
|
||||
|
||||
__all__ = ["Lockfile", "Pipfile", "Requirement"]
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
import os
|
||||
|
||||
from pipenv.patched.pip._vendor.platformdirs import user_cache_dir
|
||||
|
||||
|
||||
def is_type_checking():
|
||||
try:
|
||||
from typing import TYPE_CHECKING
|
||||
except ImportError:
|
||||
return False
|
||||
return TYPE_CHECKING
|
||||
|
||||
|
||||
REQUIREMENTSLIB_CACHE_DIR = os.getenv(
|
||||
"REQUIREMENTSLIB_CACHE_DIR", user_cache_dir("pipenv")
|
||||
)
|
||||
MYPY_RUNNING = os.environ.get("MYPY_RUNNING", is_type_checking())
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
"""A small collection of useful functional tools for working with iterables.
|
||||
|
||||
unnest, chunked and take are used by pipenv. However, since pipenv
|
||||
relies on requirementslib, we can them here.
|
||||
"""
|
||||
from functools import partial
|
||||
from itertools import islice, tee
|
||||
from typing import Any, Iterable
|
||||
|
||||
|
||||
def _is_iterable(elem: Any) -> bool:
|
||||
if getattr(elem, "__iter__", False) or isinstance(elem, Iterable):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def take(n: int, iterable: Iterable) -> Iterable:
|
||||
"""Take n elements from the supplied iterable without consuming it.
|
||||
|
||||
:param int n: Number of unique groups
|
||||
:param iter iterable: An iterable to split up
|
||||
"""
|
||||
return list(islice(iterable, n))
|
||||
|
||||
|
||||
def chunked(n: int, iterable: Iterable) -> Iterable:
|
||||
"""Split an iterable into lists of length *n*.
|
||||
|
||||
:param int n: Number of unique groups
|
||||
:param iter iterable: An iterable to split up
|
||||
"""
|
||||
return iter(partial(take, n, iter(iterable)), [])
|
||||
|
||||
|
||||
def unnest(elem: Iterable) -> Any:
|
||||
# type: (Iterable) -> Any
|
||||
"""Flatten an arbitrarily nested iterable.
|
||||
|
||||
:param elem: An iterable to flatten
|
||||
:type elem: :class:`~collections.Iterable`
|
||||
>>> nested_iterable = (
|
||||
1234, (3456, 4398345, (234234)), (
|
||||
2396, (
|
||||
928379, 29384, (
|
||||
293759, 2347, (
|
||||
2098, 7987, 27599
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
>>> list(unnest(nested_iterable))
|
||||
[1234, 3456, 4398345, 234234, 2396, 928379, 29384, 293759,
|
||||
2347, 2098, 7987, 27599]
|
||||
"""
|
||||
|
||||
if isinstance(elem, Iterable) and not isinstance(elem, str):
|
||||
elem, target = tee(elem, 2)
|
||||
else:
|
||||
target = elem
|
||||
if not target or not _is_iterable(target):
|
||||
yield target
|
||||
else:
|
||||
for el in target:
|
||||
if isinstance(el, Iterable) and not isinstance(el, str):
|
||||
el, el_copy = tee(el, 2)
|
||||
for sub in unnest(el_copy):
|
||||
yield sub
|
||||
else:
|
||||
yield el
|
||||
|
||||
|
||||
def dedup(iterable: Iterable) -> Iterable:
|
||||
# type: (Iterable) -> Iterable
|
||||
"""Deduplicate an iterable object like iter(set(iterable)) but order-
|
||||
preserved."""
|
||||
return iter(dict.fromkeys(iterable))
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
from typing import Any, Dict
|
||||
|
||||
from pipenv.vendor.pydantic import BaseModel, Extra
|
||||
|
||||
|
||||
class ReqLibBaseModel(BaseModel):
|
||||
def __setattr__(self, name, value): # noqa: C901 (ignore complexity)
|
||||
private_attributes = {
|
||||
field_name
|
||||
for field_name in self.__annotations__
|
||||
if field_name.startswith("_")
|
||||
}
|
||||
|
||||
if name in private_attributes or name in self.__fields__:
|
||||
return object.__setattr__(self, name, value)
|
||||
|
||||
if self.__config__.extra is not Extra.allow and name not in self.__fields__:
|
||||
raise ValueError(f'"{self.__class__.__name__}" object has no field "{name}"')
|
||||
|
||||
object.__setattr__(self, name, value)
|
||||
|
||||
def dict(self, *args, **kwargs) -> Dict[str, Any]:
|
||||
"""The requirementslib classes make use of a lot of private attributes
|
||||
which do not get serialized out to the dict by default in pydantic."""
|
||||
model_dict = super().dict(*args, **kwargs)
|
||||
private_attrs = {k: v for k, v in self.__dict__.items() if k.startswith("_")}
|
||||
model_dict.update(private_attrs)
|
||||
return model_dict
|
||||
@@ -1,62 +0,0 @@
|
||||
import atexit
|
||||
import os
|
||||
|
||||
from pipenv.patched.pip._internal.index.package_finder import PackageFinder
|
||||
from pipenv.patched.pip._vendor.platformdirs import user_cache_dir
|
||||
|
||||
from ..utils import get_package_finder, get_pip_command, prepare_pip_source_args
|
||||
|
||||
CACHE_DIR = os.environ.get("PIPENV_CACHE_DIR", user_cache_dir("pipenv"))
|
||||
|
||||
|
||||
def is_python(section):
|
||||
return section.startswith("[") and ":" in section
|
||||
|
||||
|
||||
def get_pip_options(args=None, sources=None, pip_command=None):
|
||||
"""Build a pip command from a list of sources.
|
||||
|
||||
:param args: positional arguments passed through to the pip parser
|
||||
:param sources: A list of pipfile-formatted sources, defaults to None
|
||||
:param sources: list[dict], optional
|
||||
:param pip_command: A pre-built pip command instance
|
||||
:type pip_command: :class:`~pipenv.patched.pip._internal.cli.base_command.Command`
|
||||
:return: An instance of pip_options using the supplied arguments plus sane defaults
|
||||
:rtype: :class:`~pipenv.patched.pip._internal.cli.cmdoptions`
|
||||
"""
|
||||
|
||||
if not pip_command:
|
||||
pip_command = get_pip_command()
|
||||
if not sources:
|
||||
sources = [{"url": "https://pypi.org/simple", "name": "pypi", "verify_ssl": True}]
|
||||
os.makedirs(CACHE_DIR, mode=0o777, exist_ok=True)
|
||||
pip_args = args or []
|
||||
pip_args = prepare_pip_source_args(sources, pip_args)
|
||||
pip_options, _ = pip_command.parser.parse_args(pip_args)
|
||||
pip_options.cache_dir = CACHE_DIR
|
||||
return pip_options
|
||||
|
||||
|
||||
def get_finder(sources=None, pip_command=None, pip_options=None) -> PackageFinder:
|
||||
"""Get a package finder for looking up candidates to install.
|
||||
|
||||
:param sources: A list of pipfile-formatted sources, defaults to None
|
||||
:param sources: list[dict], optional
|
||||
:param pip_command: A pip command instance, defaults to None
|
||||
:type pip_command: :class:`~pipenv.patched.pip._internal.cli.base_command.Command`
|
||||
:param pip_options: A pip options, defaults to None
|
||||
:type pip_options: :class:`~pipenv.patched.pip._internal.cli.cmdoptions`
|
||||
:return: A package finder
|
||||
:rtype: :class:`~pipenv.patched.pip._internal.index.PackageFinder`
|
||||
"""
|
||||
|
||||
if not pip_command:
|
||||
pip_command = get_pip_command()
|
||||
if not sources:
|
||||
sources = [{"url": "https://pypi.org/simple", "name": "pypi", "verify_ssl": True}]
|
||||
if not pip_options:
|
||||
pip_options = get_pip_options(sources=sources, pip_command=pip_command)
|
||||
session = pip_command._build_session(pip_options)
|
||||
atexit.register(session.close)
|
||||
finder = get_package_finder(get_pip_command(), options=pip_options, session=session)
|
||||
return session, finder
|
||||
-248
@@ -1,248 +0,0 @@
|
||||
import copy
|
||||
import itertools
|
||||
import os
|
||||
from json import JSONDecodeError
|
||||
from pathlib import Path
|
||||
from typing import Dict, Iterator, List, Optional
|
||||
|
||||
from pipenv.vendor.plette import lockfiles
|
||||
from pipenv.vendor.pydantic import Field
|
||||
|
||||
from ..exceptions import LockfileCorruptException, MissingParameter, PipfileNotFound
|
||||
from ..utils import is_editable, is_vcs, merge_items
|
||||
from .common import ReqLibBaseModel
|
||||
from .project import ProjectFile
|
||||
from .requirements import Requirement
|
||||
|
||||
DEFAULT_NEWLINES = "\n"
|
||||
|
||||
|
||||
def preferred_newlines(f):
|
||||
if isinstance(f.newlines, str):
|
||||
return f.newlines
|
||||
return DEFAULT_NEWLINES
|
||||
|
||||
|
||||
class Lockfile(ReqLibBaseModel):
|
||||
path: Path = Field(
|
||||
default_factory=lambda: Path(os.curdir).joinpath("Pipfile.lock").absolute()
|
||||
)
|
||||
_requirements: Optional[list] = Field(default_factory=list)
|
||||
_dev_requirements: Optional[list] = Field(default_factory=list)
|
||||
projectfile: ProjectFile = None
|
||||
lockfile: lockfiles.Lockfile
|
||||
newlines: str = DEFAULT_NEWLINES
|
||||
|
||||
class Config:
|
||||
validate_assignment = True
|
||||
arbitrary_types_allowed = True
|
||||
allow_mutation = True
|
||||
include_private_attributes = True
|
||||
# keep_untouched = (cached_property,)
|
||||
|
||||
@property
|
||||
def section_keys(self):
|
||||
return set(self.lockfile.keys()) - {"_meta"}
|
||||
|
||||
@property
|
||||
def extended_keys(self):
|
||||
return [k for k in itertools.product(self.section_keys, ["", "vcs", "editable"])]
|
||||
|
||||
def get(self, k):
|
||||
return self.__getitem__(k)
|
||||
|
||||
def __contains__(self, k):
|
||||
check_lockfile = k in self.extended_keys or self.lockfile.__contains__(k)
|
||||
if check_lockfile:
|
||||
return True
|
||||
return super(Lockfile, self).__contains__(k)
|
||||
|
||||
def __setitem__(self, k, v):
|
||||
lockfile = self.lockfile
|
||||
lockfile.__setitem__(k, v)
|
||||
|
||||
def __getitem__(self, k, *args, **kwargs):
|
||||
retval = None
|
||||
lockfile = self.lockfile
|
||||
try:
|
||||
retval = lockfile[k]
|
||||
except KeyError:
|
||||
if "-" in k:
|
||||
section, _, pkg_type = k.rpartition("-")
|
||||
vals = getattr(lockfile.get(section, {}), "_data", {})
|
||||
if pkg_type == "vcs":
|
||||
retval = {k: v for k, v in vals.items() if is_vcs(v)}
|
||||
elif pkg_type == "editable":
|
||||
retval = {k: v for k, v in vals.items() if is_editable(v)}
|
||||
if retval is None:
|
||||
raise
|
||||
else:
|
||||
retval = getattr(retval, "_data", retval)
|
||||
return retval
|
||||
|
||||
def __getattr__(self, k, *args, **kwargs):
|
||||
lockfile = self.lockfile
|
||||
try:
|
||||
return super(Lockfile, self).__getattribute__(k)
|
||||
except AttributeError:
|
||||
retval = getattr(lockfile, k, None)
|
||||
if retval is not None:
|
||||
return retval
|
||||
return super(Lockfile, self).__getattribute__(k, *args, **kwargs)
|
||||
|
||||
def get_deps(self, dev=False, only=True):
|
||||
deps = {}
|
||||
if dev:
|
||||
deps.update(self.develop._data)
|
||||
if only:
|
||||
return deps
|
||||
deps = merge_items([deps, self.default._data])
|
||||
return deps
|
||||
|
||||
@classmethod
|
||||
def read_projectfile(cls, path):
|
||||
pf = ProjectFile.read(path, lockfiles.Lockfile, invalid_ok=True)
|
||||
return pf
|
||||
|
||||
@classmethod
|
||||
def lockfile_from_pipfile(cls, pipfile_path):
|
||||
from .pipfile import Pipfile
|
||||
|
||||
if os.path.isfile(pipfile_path):
|
||||
if not os.path.isabs(pipfile_path):
|
||||
pipfile_path = os.path.abspath(pipfile_path)
|
||||
pipfile = Pipfile.load(os.path.dirname(pipfile_path))
|
||||
return lockfiles.Lockfile.with_meta_from(pipfile.pipfile)
|
||||
raise PipfileNotFound(pipfile_path)
|
||||
|
||||
@classmethod
|
||||
def load_projectfile(
|
||||
cls, path: Optional[str] = None, create: bool = True, data: Optional[Dict] = None
|
||||
) -> "ProjectFile":
|
||||
if not path:
|
||||
path = os.curdir
|
||||
path = Path(path).absolute()
|
||||
project_path = path if path.is_dir() else path.parent
|
||||
lockfile_path = path if path.is_file() else project_path / "Pipfile.lock"
|
||||
if not project_path.exists():
|
||||
raise OSError(f"Project does not exist: {project_path.as_posix()}")
|
||||
elif not lockfile_path.exists() and not create:
|
||||
raise FileNotFoundError(
|
||||
f"Lockfile does not exist: {lockfile_path.as_posix()}"
|
||||
)
|
||||
projectfile = cls.read_projectfile(lockfile_path.as_posix())
|
||||
if not lockfile_path.exists():
|
||||
if not data:
|
||||
pipfile = project_path.joinpath("Pipfile")
|
||||
lf = cls.lockfile_from_pipfile(pipfile)
|
||||
else:
|
||||
lf = lockfiles.Lockfile(data)
|
||||
projectfile.model = lf
|
||||
else:
|
||||
if data:
|
||||
raise ValueError("Cannot pass data when loading existing lockfile")
|
||||
with open(lockfile_path.as_posix(), "r") as f:
|
||||
projectfile.model = lockfiles.Lockfile.load(f)
|
||||
return projectfile
|
||||
|
||||
@classmethod
|
||||
def from_data(
|
||||
cls, path: Optional[str], data: Optional[Dict], meta_from_project: bool = True
|
||||
) -> "Lockfile":
|
||||
if path is None:
|
||||
raise MissingParameter("path")
|
||||
if data is None:
|
||||
raise MissingParameter("data")
|
||||
if not isinstance(data, dict):
|
||||
raise TypeError("Expecting a dictionary for parameter 'data'")
|
||||
path = os.path.abspath(str(path))
|
||||
if os.path.isdir(path):
|
||||
project_path = path
|
||||
elif not os.path.isdir(path) and os.path.isdir(os.path.dirname(path)):
|
||||
project_path = os.path.dirname(path)
|
||||
pipfile_path = os.path.join(project_path, "Pipfile")
|
||||
lockfile_path = os.path.join(project_path, "Pipfile.lock")
|
||||
if meta_from_project:
|
||||
lockfile = cls.lockfile_from_pipfile(pipfile_path)
|
||||
lockfile.update(data)
|
||||
else:
|
||||
lockfile = lockfiles.Lockfile(data)
|
||||
projectfile = ProjectFile(
|
||||
line_ending=DEFAULT_NEWLINES, location=lockfile_path, model=lockfile
|
||||
)
|
||||
return cls(
|
||||
projectfile=projectfile,
|
||||
lockfile=lockfile,
|
||||
newlines=projectfile.line_ending,
|
||||
path=Path(projectfile.location),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def load(cls, path: Optional[str], create: bool = True) -> "Lockfile":
|
||||
try:
|
||||
projectfile = cls.load_projectfile(path, create=create)
|
||||
except JSONDecodeError:
|
||||
path = os.path.abspath(path)
|
||||
path = Path(
|
||||
os.path.join(path, "Pipfile.lock") if os.path.isdir(path) else path
|
||||
)
|
||||
formatted_path = path.as_posix()
|
||||
backup_path = f"{formatted_path}.bak"
|
||||
LockfileCorruptException.show(formatted_path, backup_path=backup_path)
|
||||
path.rename(backup_path)
|
||||
cls.load(formatted_path, create=True)
|
||||
lockfile_path = Path(projectfile.location)
|
||||
creation_args = {
|
||||
"projectfile": projectfile,
|
||||
"lockfile": projectfile.model,
|
||||
"newlines": projectfile.line_ending,
|
||||
"path": lockfile_path,
|
||||
}
|
||||
return cls(**creation_args)
|
||||
|
||||
@classmethod
|
||||
def create(cls, path: Optional[str], create: bool = True) -> "Lockfile":
|
||||
return cls.load(path, create=create)
|
||||
|
||||
def get_section(self, name: str) -> Optional[Dict]:
|
||||
return self.lockfile.get(name)
|
||||
|
||||
@property
|
||||
def develop(self) -> Dict:
|
||||
return self.lockfile.develop
|
||||
|
||||
@property
|
||||
def default(self) -> Dict:
|
||||
return self.lockfile.default
|
||||
|
||||
def get_requirements(
|
||||
self, dev: bool = True, only: bool = False, categories: Optional[List[str]] = None
|
||||
) -> Iterator[Requirement]:
|
||||
if categories:
|
||||
deps = {}
|
||||
for category in categories:
|
||||
if category == "packages":
|
||||
category = "default"
|
||||
elif category == "dev-packages":
|
||||
category = "develop"
|
||||
try:
|
||||
category_deps = self[category]
|
||||
except KeyError:
|
||||
category_deps = {}
|
||||
self.lockfile[category] = category_deps
|
||||
deps = merge_items([deps, category_deps])
|
||||
else:
|
||||
deps = self.get_deps(dev=dev, only=only)
|
||||
for k, v in deps.items():
|
||||
yield Requirement.from_pipfile(k, v)
|
||||
|
||||
def requirements_list(self, category: str) -> List[Dict]:
|
||||
if self.lockfile.get(category):
|
||||
return [
|
||||
{name: entry._data} for name, entry in self.lockfile[category].items()
|
||||
]
|
||||
return []
|
||||
|
||||
def write(self) -> None:
|
||||
self.projectfile.model = copy.deepcopy(self.lockfile)
|
||||
self.projectfile.write()
|
||||
-1083
File diff suppressed because it is too large
Load Diff
@@ -1,97 +0,0 @@
|
||||
"""These were old pip utils that were dropped starting in pip 22.1 but
|
||||
`requirementslib` still deeply depends upon.
|
||||
|
||||
In the interest of getting the build working with the latest version of
|
||||
pip again, this workaround to copy these dependent utils in was
|
||||
provided. Ideally the code that depends on this behavior would be
|
||||
modernized and refactored to not require it.
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
from typing import Dict, Iterable, List
|
||||
|
||||
|
||||
# This can be removed once this pr is merged
|
||||
# https://github.com/python/cpython/pull/16575
|
||||
def is_socket(path: str) -> bool:
|
||||
return stat.S_ISSOCK(os.lstat(path).st_mode)
|
||||
|
||||
|
||||
def copy2_fixed(src: str, dest: str) -> None:
|
||||
"""Wrap shutil.copy2() but map errors copying socket files to
|
||||
SpecialFileError as expected.
|
||||
|
||||
See also https://bugs.python.org/issue37700.
|
||||
"""
|
||||
try:
|
||||
shutil.copy2(src, dest)
|
||||
except OSError:
|
||||
for f in [src, dest]:
|
||||
try:
|
||||
is_socket_file = is_socket(f)
|
||||
except OSError:
|
||||
# An error has already occurred. Another error here is not
|
||||
# a problem and we can ignore it.
|
||||
pass
|
||||
else:
|
||||
if is_socket_file:
|
||||
raise shutil.SpecialFileError("`{f}` is a socket".format(**locals()))
|
||||
|
||||
raise
|
||||
|
||||
|
||||
def _copy2_ignoring_special_files(src: str, dest: str) -> None:
|
||||
"""Copying special files is not supported, but as a convenience to users we
|
||||
skip errors copying them.
|
||||
|
||||
This supports tools that may create e.g. socket files in the project
|
||||
source directory.
|
||||
"""
|
||||
try:
|
||||
copy2_fixed(src, dest)
|
||||
except shutil.SpecialFileError as e:
|
||||
# SpecialFileError may be raised due to either the source or
|
||||
# destination. If the destination was the cause then we would actually
|
||||
# care, but since the destination directory is deleted prior to
|
||||
# copy we ignore all of them assuming it is caused by the source.
|
||||
logger.warning(
|
||||
"Ignoring special file error '%s' encountered copying %s to %s.",
|
||||
str(e),
|
||||
src,
|
||||
dest,
|
||||
)
|
||||
|
||||
|
||||
def _copy_source_tree(source: str, target: str) -> None:
|
||||
target_abspath = os.path.abspath(target)
|
||||
target_basename = os.path.basename(target_abspath)
|
||||
target_dirname = os.path.dirname(target_abspath)
|
||||
|
||||
def ignore(d: str, names: List[str]) -> List[str]:
|
||||
skipped: List[str] = []
|
||||
if d == source:
|
||||
# Pulling in those directories can potentially be very slow,
|
||||
# exclude the following directories if they appear in the top
|
||||
# level dir (and only it).
|
||||
# See discussion at https://github.com/pypa/pip/pull/6770
|
||||
skipped += [".tox", ".nox"]
|
||||
if os.path.abspath(d) == target_dirname:
|
||||
# Prevent an infinite recursion if the target is in source.
|
||||
# This can happen when TMPDIR is set to ${PWD}/...
|
||||
# and we copy PWD to TMPDIR.
|
||||
skipped += [target_basename]
|
||||
return skipped
|
||||
|
||||
shutil.copytree(
|
||||
source,
|
||||
target,
|
||||
ignore=ignore,
|
||||
symlinks=True,
|
||||
copy_function=_copy2_ignoring_special_files,
|
||||
)
|
||||
-324
@@ -1,324 +0,0 @@
|
||||
import itertools
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Iterable, List, Optional, Text, Union
|
||||
|
||||
import pipenv.vendor.tomlkit as tomlkit
|
||||
from pipenv.vendor.plette import pipfiles
|
||||
from pipenv.vendor.pydantic import BaseModel, validator
|
||||
|
||||
from ..environment import MYPY_RUNNING
|
||||
from ..exceptions import RequirementError
|
||||
from ..utils import is_editable, is_vcs, merge_items
|
||||
from .common import ReqLibBaseModel
|
||||
from .project import ProjectFile
|
||||
from .requirements import Requirement
|
||||
from .utils import get_url_name, tomlkit_value_to_python
|
||||
|
||||
if MYPY_RUNNING:
|
||||
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[Text, Union[int, Dict[Text, Text], sources_type]]
|
||||
lockfile_type = Dict[Text, Union[package_type, meta_type]]
|
||||
|
||||
|
||||
def reorder_source_keys(data):
|
||||
# type: (tomlkit.toml_document.TOMLDocument) -> tomlkit.toml_document.TOMLDocument
|
||||
sources = [] # type: sources_type
|
||||
for source_key in ["source", "sources"]:
|
||||
sources.extend(data.get(source_key, tomlkit.aot()).value)
|
||||
new_source_aot = tomlkit.aot()
|
||||
for entry in sources:
|
||||
table = tomlkit.table() # type: tomlkit.items.Table
|
||||
source_entry = PipfileLoader.populate_source(entry.copy())
|
||||
for key in ["name", "url", "verify_ssl"]:
|
||||
table.update({key: source_entry[key]})
|
||||
new_source_aot.append(table)
|
||||
data["source"] = new_source_aot
|
||||
if data.get("sources", None):
|
||||
del data["sources"]
|
||||
return data
|
||||
|
||||
|
||||
class PipfileLoader(pipfiles.Pipfile):
|
||||
@classmethod
|
||||
def validate(cls, data):
|
||||
# type: (tomlkit.toml_document.TOMLDocument) -> None
|
||||
for key, klass in pipfiles.PIPFILE_SECTIONS.items():
|
||||
if key not in data or key == "sources":
|
||||
continue
|
||||
try:
|
||||
klass.validate(data[key])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def ensure_package_sections(cls, data):
|
||||
# type: (tomlkit.toml_document.TOMLDocument[Text, Any]) -> tomlkit.toml_document.TOMLDocument[Text, Any]
|
||||
"""Ensure that all pipfile package sections are present in the given
|
||||
toml document.
|
||||
|
||||
:param :class:`~tomlkit.toml_document.TOMLDocument` data: The toml document to
|
||||
ensure package sections are present on
|
||||
:return: The updated toml document, ensuring ``packages`` and ``dev-packages``
|
||||
sections are present
|
||||
:rtype: :class:`~tomlkit.toml_document.TOMLDocument`
|
||||
"""
|
||||
package_keys = (
|
||||
k for k in pipfiles.PIPFILE_SECTIONS.keys() if k.endswith("packages")
|
||||
)
|
||||
for key in package_keys:
|
||||
if key not in data:
|
||||
data.update({key: tomlkit.table()})
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def populate_source(cls, source):
|
||||
"""Derive missing values of source from the existing fields."""
|
||||
# Only URL pararemter is mandatory, let the KeyError be thrown.
|
||||
if "name" not in source:
|
||||
source["name"] = get_url_name(source["url"])
|
||||
if "verify_ssl" not in source:
|
||||
source["verify_ssl"] = "https://" in source["url"]
|
||||
if not isinstance(source["verify_ssl"], bool):
|
||||
source["verify_ssl"] = str(source["verify_ssl"]).lower() == "true"
|
||||
return source
|
||||
|
||||
@classmethod
|
||||
def load(cls, f, encoding=None):
|
||||
# type: (Any, Text) -> PipfileLoader
|
||||
content = f.read()
|
||||
if encoding is not None:
|
||||
content = content.decode(encoding)
|
||||
_data = tomlkit.loads(content)
|
||||
should_reload = "source" not in _data
|
||||
_data = reorder_source_keys(_data)
|
||||
if should_reload:
|
||||
if "sources" in _data:
|
||||
content = tomlkit.dumps(_data)
|
||||
else:
|
||||
# HACK: There is no good way to prepend a section to an existing
|
||||
# TOML document, but there's no good way to copy non-structural
|
||||
# content from one TOML document to another either. Modify the
|
||||
# TOML content directly, and load the new in-memory document.
|
||||
sep = "" if content.startswith("\n") else "\n"
|
||||
content = pipfiles.DEFAULT_SOURCE_TOML + sep + content
|
||||
data = tomlkit.loads(content)
|
||||
data = cls.ensure_package_sections(data)
|
||||
instance = cls(data)
|
||||
instance._data = dict(instance._data)
|
||||
return instance
|
||||
|
||||
def __contains__(self, key):
|
||||
# type: (Text) -> bool
|
||||
if key not in self._data:
|
||||
package_keys = self._data.get("packages", {}).keys()
|
||||
dev_package_keys = self._data.get("dev-packages", {}).keys()
|
||||
return any(key in pkg_list for pkg_list in (package_keys, dev_package_keys))
|
||||
return True
|
||||
|
||||
def __getattribute__(self, key):
|
||||
# type: (Text) -> Any
|
||||
if key == "source":
|
||||
return self._data[key]
|
||||
return super(PipfileLoader, self).__getattribute__(key)
|
||||
|
||||
|
||||
class Pipfile(ReqLibBaseModel):
|
||||
path: Path
|
||||
projectfile: ProjectFile
|
||||
pipfile: Optional[PipfileLoader]
|
||||
_pyproject: Optional[tomlkit.TOMLDocument] = tomlkit.document()
|
||||
build_system: Optional[Dict] = dict()
|
||||
_requirements: Optional[List] = list()
|
||||
_dev_requirements: Optional[List] = list()
|
||||
|
||||
class Config:
|
||||
validate_assignment = True
|
||||
arbitrary_types_allowed = True
|
||||
allow_mutation = True
|
||||
include_private_attributes = True
|
||||
# keep_untouched = (cached_property,)
|
||||
|
||||
@validator("path", pre=True, always=True)
|
||||
def _get_path(cls, v):
|
||||
return v or Path(os.curdir).absolute()
|
||||
|
||||
@validator("projectfile", pre=True, always=True)
|
||||
def _get_projectfile(cls, v, values):
|
||||
return v or cls.load_projectfile(os.curdir, create=False)
|
||||
|
||||
@validator("pipfile", pre=True, always=True)
|
||||
def _get_pipfile(cls, v, values):
|
||||
return v or values["projectfile"].model
|
||||
|
||||
@property
|
||||
def root(self):
|
||||
return self.path.parent
|
||||
|
||||
@property
|
||||
def extended_keys(self):
|
||||
return [
|
||||
k
|
||||
for k in itertools.product(
|
||||
("packages", "dev-packages"), ("", "vcs", "editable")
|
||||
)
|
||||
]
|
||||
|
||||
def get_deps(self, dev=False, only=True):
|
||||
deps = {} # type: Dict[Text, Dict[Text, Union[List[Text], Text]]]
|
||||
if dev:
|
||||
deps.update(dict(self.pipfile._data.get("dev-packages", {})))
|
||||
if only:
|
||||
return deps
|
||||
return tomlkit_value_to_python(
|
||||
merge_items([deps, dict(self.pipfile._data.get("packages", {}))])
|
||||
)
|
||||
|
||||
def get(self, k):
|
||||
return self.__getitem__(k)
|
||||
|
||||
def __contains__(self, k):
|
||||
check_pipfile = k in self.extended_keys or self.pipfile.__contains__(k)
|
||||
if check_pipfile:
|
||||
return True
|
||||
return False
|
||||
|
||||
def __getitem__(self, k, *args, **kwargs):
|
||||
retval = None
|
||||
pipfile = self.pipfile
|
||||
section = None
|
||||
pkg_type = None
|
||||
try:
|
||||
retval = pipfile[k]
|
||||
except KeyError:
|
||||
if "-" in k:
|
||||
section, _, pkg_type = k.rpartition("-")
|
||||
vals = getattr(pipfile.get(section, {}), "_data", {})
|
||||
vals = tomlkit_value_to_python(vals)
|
||||
if pkg_type == "vcs":
|
||||
retval = {k: v for k, v in vals.items() if is_vcs(v)}
|
||||
elif pkg_type == "editable":
|
||||
retval = {k: v for k, v in vals.items() if is_editable(v)}
|
||||
if retval is None:
|
||||
raise
|
||||
else:
|
||||
retval = getattr(retval, "_data", retval)
|
||||
return retval
|
||||
|
||||
def __getattr__(self, k, *args, **kwargs):
|
||||
pipfile = self.pipfile
|
||||
try:
|
||||
retval = super(Pipfile).__getattribute__(k)
|
||||
except AttributeError:
|
||||
retval = getattr(pipfile, k, None)
|
||||
return retval
|
||||
|
||||
@property
|
||||
def requires_python(self):
|
||||
# type: () -> bool
|
||||
return getattr(
|
||||
self.pipfile.requires,
|
||||
"python_version",
|
||||
getattr(self.pipfile.requires, "python_full_version", None),
|
||||
)
|
||||
|
||||
@property
|
||||
def allow_prereleases(self):
|
||||
# type: () -> bool
|
||||
return self.pipfile.get("pipenv", {}).get("allow_prereleases", False)
|
||||
|
||||
@classmethod
|
||||
def read_projectfile(cls, path):
|
||||
# type: (Text) -> ProjectFile
|
||||
"""Read the specified project file and provide an interface for
|
||||
writing/updating.
|
||||
|
||||
: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`
|
||||
"""
|
||||
pf = ProjectFile.read(path, PipfileLoader, invalid_ok=True)
|
||||
return pf
|
||||
|
||||
@classmethod
|
||||
def load_projectfile(cls, path, create=False):
|
||||
# type: (Text, bool) -> ProjectFile
|
||||
"""Given a path, load or create the necessary 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``
|
||||
:return: A project file instance for the supplied project
|
||||
:rtype: :class:`~requirementslib.models.project.ProjectFile`
|
||||
"""
|
||||
if not path:
|
||||
raise RuntimeError("Must pass a path to classmethod 'Pipfile.load'")
|
||||
if not isinstance(path, Path):
|
||||
path = Path(path).absolute()
|
||||
pipfile_path = path if path.is_file() else path.joinpath("Pipfile")
|
||||
project_path = pipfile_path.parent
|
||||
if not project_path.exists():
|
||||
raise FileNotFoundError("%s is not a valid project path!" % path)
|
||||
elif not pipfile_path.exists() or not pipfile_path.is_file():
|
||||
if not create:
|
||||
raise RequirementError("%s is not a valid Pipfile" % pipfile_path)
|
||||
return cls.read_projectfile(pipfile_path.as_posix())
|
||||
|
||||
@classmethod
|
||||
def load(cls, path, create=False):
|
||||
# type: (Text, bool) -> Pipfile
|
||||
"""Given a path, load or create the necessary 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``
|
||||
:return: A pipfile instance pointing at the supplied project
|
||||
:rtype:: class:`~requirementslib.models.pipfile.Pipfile`
|
||||
"""
|
||||
|
||||
projectfile = cls.load_projectfile(path, create=create)
|
||||
pipfile = projectfile.model
|
||||
creation_args = {
|
||||
"projectfile": projectfile,
|
||||
"pipfile": pipfile,
|
||||
"path": Path(projectfile.location),
|
||||
}
|
||||
return cls(**creation_args)
|
||||
|
||||
@property
|
||||
def dev_packages(self):
|
||||
# type: () -> List[Requirement]
|
||||
return self.dev_requirements
|
||||
|
||||
@property
|
||||
def packages(self):
|
||||
# type: () -> List[Requirement]
|
||||
return self.requirements
|
||||
|
||||
@property
|
||||
def dev_requirements(self):
|
||||
# type: () -> List[Requirement]
|
||||
if not self._dev_requirements:
|
||||
packages = tomlkit_value_to_python(self.pipfile.get("dev-packages", {}))
|
||||
self._dev_requirements = [
|
||||
Requirement.from_pipfile(k, v)
|
||||
for k, v in packages.items()
|
||||
if v is not None
|
||||
]
|
||||
return self._dev_requirements
|
||||
|
||||
@property
|
||||
def requirements(self):
|
||||
# type: () -> List[Requirement]
|
||||
if not self._requirements:
|
||||
packages = tomlkit_value_to_python(self.pipfile.get("packages", {}))
|
||||
self._requirements = [
|
||||
Requirement.from_pipfile(k, v)
|
||||
for k, v in packages.items()
|
||||
if v is not None
|
||||
]
|
||||
return self._requirements
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
import collections
|
||||
import io
|
||||
import os
|
||||
from typing import Any, Optional
|
||||
|
||||
from pipenv.patched.pip._vendor.packaging.markers import Marker
|
||||
from pipenv.vendor.pydantic import BaseModel, Field
|
||||
|
||||
SectionDifference = collections.namedtuple("SectionDifference", ["inthis", "inthat"])
|
||||
FileDifference = collections.namedtuple("FileDifference", ["default", "develop"])
|
||||
|
||||
|
||||
def _are_pipfile_entries_equal(a, b):
|
||||
a = {k: v for k, v in a.items() if k not in ("markers", "hashes", "hash")}
|
||||
b = {k: v for k, v in b.items() if k not in ("markers", "hashes", "hash")}
|
||||
if a != b:
|
||||
return False
|
||||
try:
|
||||
marker_eval_a = Marker(a["markers"]).evaluate()
|
||||
except (AttributeError, KeyError, TypeError, ValueError):
|
||||
marker_eval_a = True
|
||||
try:
|
||||
marker_eval_b = Marker(b["markers"]).evaluate()
|
||||
except (AttributeError, KeyError, TypeError, ValueError):
|
||||
marker_eval_b = True
|
||||
return marker_eval_a == marker_eval_b
|
||||
|
||||
|
||||
DEFAULT_NEWLINES = "\n"
|
||||
|
||||
|
||||
def preferred_newlines(f):
|
||||
if isinstance(f.newlines, str):
|
||||
return f.newlines
|
||||
return DEFAULT_NEWLINES
|
||||
|
||||
|
||||
class ProjectFile(BaseModel):
|
||||
location: str
|
||||
line_ending: str
|
||||
model: Optional[Any] = Field(default_factory=lambda: dict())
|
||||
|
||||
@classmethod
|
||||
def read(cls, location: str, model_cls, invalid_ok: bool = False) -> "ProjectFile":
|
||||
if not os.path.exists(location) and not invalid_ok:
|
||||
raise FileNotFoundError(location)
|
||||
try:
|
||||
with io.open(location, encoding="utf-8") as f:
|
||||
model = model_cls.load(f)
|
||||
line_ending = preferred_newlines(f)
|
||||
except Exception:
|
||||
if not invalid_ok:
|
||||
raise
|
||||
model = {}
|
||||
line_ending = DEFAULT_NEWLINES
|
||||
return cls(location=location, line_ending=line_ending, model=model)
|
||||
|
||||
def write(self) -> None:
|
||||
kwargs = {"encoding": "utf-8", "newline": self.line_ending}
|
||||
with io.open(self.location, "w", **kwargs) as f:
|
||||
if self.model:
|
||||
self.model.dump(f)
|
||||
|
||||
def dumps(self) -> str:
|
||||
if self.model:
|
||||
strio = io.StringIO()
|
||||
self.model.dump(strio)
|
||||
return strio.getvalue()
|
||||
return ""
|
||||
-2885
File diff suppressed because it is too large
Load Diff
-1849
File diff suppressed because it is too large
Load Diff
-484
@@ -1,484 +0,0 @@
|
||||
from typing import Dict, Optional, Text, Tuple, TypeVar, Union
|
||||
from urllib.parse import quote
|
||||
from urllib.parse import unquote as url_unquote
|
||||
from urllib.parse import unquote_plus
|
||||
|
||||
from pipenv.patched.pip._internal.models.link import Link
|
||||
from pipenv.patched.pip._internal.req.constructors import _strip_extras
|
||||
from pipenv.patched.pip._vendor.urllib3.util import parse_url as urllib3_parse
|
||||
from pipenv.patched.pip._vendor.urllib3.util.url import Url
|
||||
from pipenv.vendor.pydantic import Field
|
||||
|
||||
from ..environment import MYPY_RUNNING
|
||||
from ..utils import is_installable_file
|
||||
from .common import ReqLibBaseModel
|
||||
from .utils import DIRECT_URL_RE, extras_to_string, parse_extras_str, split_ref_from_uri
|
||||
|
||||
if MYPY_RUNNING:
|
||||
|
||||
_T = TypeVar("_T")
|
||||
STRING_TYPE = Union[bytes, str, Text]
|
||||
S = TypeVar("S", bytes, str, Text)
|
||||
|
||||
|
||||
def _get_parsed_url(url) -> Url:
|
||||
"""This is a stand-in function for `urllib3.util.parse_url`
|
||||
|
||||
The original function doesn't handle special characters very well, this simply splits
|
||||
out the authentication section, creates the parsed url, then puts the authentication
|
||||
section back in, bypassing validation.
|
||||
|
||||
:return: The new, parsed URL object
|
||||
:rtype: :class:`~urllib3.util.url.Url`
|
||||
"""
|
||||
|
||||
try:
|
||||
parsed = urllib3_parse(url)
|
||||
except ValueError:
|
||||
scheme, _, url = url.partition("://")
|
||||
auth, _, url = url.rpartition("@")
|
||||
url = "{scheme}://{url}".format(scheme=scheme, url=url)
|
||||
parsed = urllib3_parse(url)._replace(auth=auth)
|
||||
if parsed.auth:
|
||||
return parsed._replace(auth=url_unquote(parsed.auth))
|
||||
return parsed
|
||||
|
||||
|
||||
class URI(ReqLibBaseModel):
|
||||
host: Optional[str] = Field(...)
|
||||
scheme: Optional[str] = Field(
|
||||
"https", description="The URI Scheme, e.g. `salesforce`"
|
||||
)
|
||||
port: Optional[int] = Field(
|
||||
None, description="The numeric port of the url if specified"
|
||||
)
|
||||
path: Optional[str] = Field("", description="The url path, e.g. `/path/to/endpoint`")
|
||||
query: Optional[str] = Field(
|
||||
"", description="Query parameters, e.g. `?variable=value...`"
|
||||
)
|
||||
fragment: Optional[str] = Field(
|
||||
"", description="URL Fragments, e.g. `#fragment=value`"
|
||||
)
|
||||
subdirectory: Optional[str] = Field(
|
||||
"", description="Subdirectory fragment, e.g. `&subdirectory=blah...`"
|
||||
)
|
||||
ref: Optional[str] = Field("", description="VCS ref this URI points at, if available")
|
||||
username: Optional[str] = Field(
|
||||
"", description="The username if provided, parsed from `user:password@hostname`"
|
||||
)
|
||||
password: Optional[str] = Field(
|
||||
"", description="Password parsed from `user:password@hostname`", repr=False
|
||||
)
|
||||
query_dict: Optional[Dict] = Field(default_factory=dict)
|
||||
name: Optional[str] = Field(
|
||||
"",
|
||||
description="The name of the specified package in case it is a VCS URI with an egg fragment",
|
||||
)
|
||||
extras: Optional[Tuple] = Field(default_factory=tuple)
|
||||
is_direct_url: Optional[bool] = Field(False)
|
||||
is_implicit_ssh: Optional[bool] = Field(False)
|
||||
auth: Optional[str] = None
|
||||
_fragment_dict: Optional[Dict] = Field(default_factory=dict)
|
||||
_username_is_quoted: Optional[bool] = False
|
||||
_password_is_quoted: Optional[bool] = False
|
||||
|
||||
class Config:
|
||||
validate_assignment = True
|
||||
arbitrary_types_allowed = True
|
||||
allow_mutation = True
|
||||
include_private_attributes = True
|
||||
# keep_untouched = (cached_property,)
|
||||
|
||||
def __init__(self, **data):
|
||||
super().__init__(**data)
|
||||
self._parse_auth()
|
||||
self._parse_query()
|
||||
self._parse_fragment()
|
||||
|
||||
def _parse_query(self) -> None:
|
||||
query = self.query if self.query is not None else ""
|
||||
query_dict = dict()
|
||||
queries = query.split("&")
|
||||
query_items = []
|
||||
subdirectory = self.subdirectory if self.subdirectory else None
|
||||
for q in queries:
|
||||
key, _, val = q.partition("=")
|
||||
val = unquote_plus(val)
|
||||
if key == "subdirectory" and not subdirectory:
|
||||
subdirectory = val
|
||||
else:
|
||||
query_items.append((key, val))
|
||||
query_dict.update(query_items)
|
||||
self.query_dict = query_dict
|
||||
self.subdirectory = subdirectory
|
||||
self.query = query
|
||||
|
||||
def _parse_fragment(self) -> None:
|
||||
subdirectory = self.subdirectory if self.subdirectory else ""
|
||||
fragment = self.fragment if self.fragment else ""
|
||||
if self.fragment is None:
|
||||
return self
|
||||
fragments = self.fragment.split("&")
|
||||
fragment_items = {}
|
||||
name = self.name if self.name else ""
|
||||
extras = self.extras
|
||||
for q in fragments:
|
||||
key, _, val = q.partition("=")
|
||||
val = unquote_plus(val)
|
||||
fragment_items[key] = val
|
||||
if key == "egg":
|
||||
from .utils import parse_extras_str
|
||||
|
||||
name, stripped_extras = _strip_extras(val)
|
||||
if stripped_extras:
|
||||
extras = tuple(parse_extras_str(stripped_extras))
|
||||
elif key == "subdirectory":
|
||||
subdirectory = val
|
||||
self.name = name
|
||||
self.extras = extras
|
||||
self.subdirectory = subdirectory
|
||||
self.fragment = fragment
|
||||
self._fragment_dict = fragment_items
|
||||
|
||||
def _parse_auth(self) -> None:
|
||||
if self.auth:
|
||||
username, _, password = self.auth.partition(":")
|
||||
username_is_quoted, password_is_quoted = False, False
|
||||
quoted_username, quoted_password = "", ""
|
||||
if password:
|
||||
quoted_password = quote(password)
|
||||
password_is_quoted = quoted_password != password
|
||||
if username:
|
||||
quoted_username = quote(username)
|
||||
username_is_quoted = quoted_username != username
|
||||
self.username = quoted_username
|
||||
self.password = quoted_password
|
||||
self._username_is_quoted = username_is_quoted
|
||||
self._password_is_quoted = password_is_quoted
|
||||
|
||||
def get_password(self, unquote=False, include_token=True) -> str:
|
||||
password = self.password if self.password else ""
|
||||
if password and unquote and self._password_is_quoted:
|
||||
password = url_unquote(password)
|
||||
return password
|
||||
|
||||
def get_username(self, unquote=False) -> str:
|
||||
username = self.username if self.username else ""
|
||||
if username and unquote and self._username_is_quoted:
|
||||
username = url_unquote(username)
|
||||
return username
|
||||
|
||||
@staticmethod
|
||||
def parse_subdirectory(url_part):
|
||||
# type: (str) -> Tuple[str, Optional[str]]
|
||||
subdir = None
|
||||
if "&subdirectory" in url_part:
|
||||
url_part, _, subdir = url_part.rpartition("&")
|
||||
if "#egg=" not in url_part:
|
||||
subdir = "#{0}".format(subdir.strip())
|
||||
else:
|
||||
subdir = "&{0}".format(subdir.strip())
|
||||
return url_part.strip(), subdir
|
||||
|
||||
@classmethod
|
||||
def get_parsed_url(cls, url):
|
||||
# if there is a "#" in the auth section, this could break url parsing
|
||||
maybe_auth = None
|
||||
parsed_url = _get_parsed_url(url)
|
||||
if "@" in url and "#" in url:
|
||||
scheme = "{0}://".format(parsed_url.scheme)
|
||||
if parsed_url.scheme == "file":
|
||||
scheme = "{0}/".format(scheme)
|
||||
url_without_scheme = url.replace(scheme, "")
|
||||
maybe_auth, _, maybe_url = url_without_scheme.partition("@")
|
||||
if "#" in maybe_auth and (not parsed_url.host or "." not in parsed_url.host):
|
||||
new_parsed_url = _get_parsed_url("{0}{1}".format(scheme, maybe_url))
|
||||
new_parsed_url = new_parsed_url._replace(auth=maybe_auth)
|
||||
return new_parsed_url
|
||||
return parsed_url
|
||||
|
||||
@classmethod
|
||||
def parse(cls, url) -> "URI":
|
||||
is_direct_url = False
|
||||
name_with_extras = None
|
||||
is_implicit_ssh = url.strip().startswith("git+git@")
|
||||
if is_implicit_ssh:
|
||||
from ..utils import add_ssh_scheme_to_git_uri
|
||||
|
||||
url = add_ssh_scheme_to_git_uri(url)
|
||||
direct_match = DIRECT_URL_RE.match(url)
|
||||
if direct_match is not None:
|
||||
is_direct_url = True
|
||||
name_with_extras, _, url = url.partition("@")
|
||||
name_with_extras = name_with_extras.strip()
|
||||
url, ref = split_ref_from_uri(url.strip())
|
||||
if "file:/" in url and "file:///" not in url:
|
||||
url = url.replace("file:/", "file:///")
|
||||
parsed = cls.get_parsed_url(url)
|
||||
# if there is a "#" in the auth section, this could break url parsing
|
||||
if not (parsed.scheme and parsed.host):
|
||||
# check if this is a file uri
|
||||
if not (
|
||||
parsed.scheme
|
||||
and parsed.path
|
||||
and (parsed.scheme == "file" or parsed.scheme.endswith("+file"))
|
||||
):
|
||||
raise ValueError("Failed parsing URL {0!r} - Not a valid url".format(url))
|
||||
parsed_dict = dict(parsed._asdict()).copy()
|
||||
parsed_dict["is_direct_url"] = is_direct_url
|
||||
parsed_dict["is_implicit_ssh"] = is_implicit_ssh
|
||||
parsed_dict.update(
|
||||
**update_url_name_and_fragment(name_with_extras, ref, parsed_dict)
|
||||
) # type: ignore
|
||||
return cls(**parsed_dict)
|
||||
|
||||
def to_string(
|
||||
self,
|
||||
escape_password=True, # type: bool
|
||||
unquote=True, # type: bool
|
||||
direct=None, # type: Optional[bool]
|
||||
strip_ssh=False, # type: bool
|
||||
strip_ref=False, # type: bool
|
||||
strip_name=False, # type: bool
|
||||
strip_subdir=False, # type: bool
|
||||
):
|
||||
# type: (...) -> str
|
||||
"""Converts the current URI to a string, unquoting or escaping the
|
||||
password as needed.
|
||||
|
||||
:param escape_password: Whether to replace password with ``----``, default True
|
||||
:param escape_password: bool, optional
|
||||
:param unquote: Whether to unquote url-escapes in the password, default False
|
||||
:param unquote: bool, optional
|
||||
:param bool direct: Whether to format as a direct URL
|
||||
:param bool strip_ssh: Whether to strip the SSH scheme from the url (git only)
|
||||
:param bool strip_ref: Whether to drop the VCS ref (if present)
|
||||
:param bool strip_name: Whether to drop the name and extras (if present)
|
||||
:param bool strip_subdir: Whether to drop the subdirectory (if present)
|
||||
:return: The reconstructed string representing the URI
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
if direct is None:
|
||||
direct = self.is_direct_url
|
||||
if escape_password:
|
||||
password = "----" if self.password else ""
|
||||
if password:
|
||||
username = self.get_username(unquote=unquote)
|
||||
elif self.username:
|
||||
username = "----"
|
||||
else:
|
||||
username = ""
|
||||
else:
|
||||
password = self.get_password(unquote=unquote)
|
||||
username = self.get_username(unquote=unquote)
|
||||
auth = ""
|
||||
if username:
|
||||
if password:
|
||||
auth = "{username}:{password}@".format(
|
||||
password=password, username=username
|
||||
)
|
||||
else:
|
||||
auth = "{username}@".format(username=username)
|
||||
query = ""
|
||||
if self.query:
|
||||
query = "{query}?{self.query}".format(query=query, self=self)
|
||||
subdir_prefix = "#"
|
||||
if not direct:
|
||||
if self.name and not strip_name:
|
||||
fragment = "#egg={self.name_with_extras}".format(self=self)
|
||||
subdir_prefix = "&"
|
||||
elif not strip_name and (
|
||||
self.extras and self.scheme and self.scheme.startswith("file")
|
||||
):
|
||||
from .utils import extras_to_string
|
||||
|
||||
fragment = extras_to_string(self.extras)
|
||||
else:
|
||||
fragment = ""
|
||||
query = "{query}{fragment}".format(query=query, fragment=fragment)
|
||||
if self.subdirectory and not strip_subdir:
|
||||
query = "{query}{subdir_prefix}subdirectory={self.subdirectory}".format(
|
||||
query=query, subdir_prefix=subdir_prefix, self=self
|
||||
)
|
||||
host_port_path = self.get_host_port_path(strip_ref=strip_ref)
|
||||
url = "{self.scheme}://{auth}{host_port_path}{query}".format(
|
||||
self=self, auth=auth, host_port_path=host_port_path, query=query
|
||||
)
|
||||
if strip_ssh:
|
||||
from ..utils import strip_ssh_from_git_uri
|
||||
|
||||
url = strip_ssh_from_git_uri(url)
|
||||
if self.name and direct and not strip_name:
|
||||
return "{self.name_with_extras}@ {url}".format(self=self, url=url)
|
||||
return url
|
||||
|
||||
def get_host_port_path(self, strip_ref=False):
|
||||
# type: (bool) -> str
|
||||
host = self.host if self.host else ""
|
||||
if self.port is not None:
|
||||
host = "{host}:{self.port!s}".format(host=host, self=self)
|
||||
path = "{self.path}".format(self=self) if self.path else ""
|
||||
if self.ref and not strip_ref:
|
||||
path = "{path}@{self.ref}".format(path=path, self=self)
|
||||
return "{host}{path}".format(host=host, path=path)
|
||||
|
||||
@property
|
||||
def hidden_auth(self):
|
||||
# type: () -> str
|
||||
auth = ""
|
||||
if self.username and self.password:
|
||||
password = "****"
|
||||
username = self.get_username(unquote=True)
|
||||
auth = "{username}:{password}".format(username=username, password=password)
|
||||
elif self.username and not self.password:
|
||||
auth = "****"
|
||||
return auth
|
||||
|
||||
@property
|
||||
def name_with_extras(self):
|
||||
# type: () -> str
|
||||
from .utils import extras_to_string
|
||||
|
||||
if not self.name:
|
||||
return ""
|
||||
extras = extras_to_string(self.extras)
|
||||
return "{self.name}{extras}".format(self=self, extras=extras)
|
||||
|
||||
@property
|
||||
def as_link(self):
|
||||
# type: () -> Link
|
||||
link = Link(self.to_string(escape_password=False, strip_ssh=False, direct=False))
|
||||
return link
|
||||
|
||||
@property
|
||||
def bare_url(self):
|
||||
# type: () -> str
|
||||
return self.to_string(
|
||||
escape_password=False,
|
||||
strip_ssh=self.is_implicit_ssh,
|
||||
direct=False,
|
||||
strip_name=True,
|
||||
strip_ref=True,
|
||||
strip_subdir=True,
|
||||
)
|
||||
|
||||
@property
|
||||
def url_without_fragment_or_ref(self):
|
||||
# type: () -> str
|
||||
return self.to_string(
|
||||
escape_password=False,
|
||||
strip_ssh=self.is_implicit_ssh,
|
||||
direct=False,
|
||||
strip_name=True,
|
||||
strip_ref=True,
|
||||
)
|
||||
|
||||
@property
|
||||
def url_without_fragment(self):
|
||||
# type: () -> str
|
||||
return self.to_string(
|
||||
escape_password=False,
|
||||
strip_ssh=self.is_implicit_ssh,
|
||||
direct=False,
|
||||
strip_name=True,
|
||||
)
|
||||
|
||||
@property
|
||||
def url_without_ref(self):
|
||||
# type: () -> str
|
||||
return self.to_string(
|
||||
escape_password=False,
|
||||
strip_ssh=self.is_implicit_ssh,
|
||||
direct=False,
|
||||
strip_ref=True,
|
||||
)
|
||||
|
||||
@property
|
||||
def base_url(self):
|
||||
# type: () -> str
|
||||
return self.to_string(
|
||||
escape_password=False,
|
||||
strip_ssh=self.is_implicit_ssh,
|
||||
direct=False,
|
||||
unquote=False,
|
||||
)
|
||||
|
||||
@property
|
||||
def full_url(self):
|
||||
# type: () -> str
|
||||
return self.to_string(escape_password=False, strip_ssh=False, direct=False)
|
||||
|
||||
@property
|
||||
def secret(self):
|
||||
# type: () -> str
|
||||
return self.full_url
|
||||
|
||||
@property
|
||||
def safe_string(self):
|
||||
# type: () -> str
|
||||
return self.to_string(escape_password=True, unquote=True)
|
||||
|
||||
@property
|
||||
def unsafe_string(self):
|
||||
# type: () -> str
|
||||
return self.to_string(escape_password=False, unquote=True)
|
||||
|
||||
@property
|
||||
def uri_escape(self):
|
||||
# type: () -> str
|
||||
return self.to_string(escape_password=False, unquote=False)
|
||||
|
||||
@property
|
||||
def is_installable(self):
|
||||
# type: () -> bool
|
||||
return self.is_file_url and is_installable_file(self.bare_url)
|
||||
|
||||
@property
|
||||
def is_vcs(self):
|
||||
# type: () -> bool
|
||||
from ..utils import VCS_SCHEMES
|
||||
|
||||
return self.scheme in VCS_SCHEMES
|
||||
|
||||
@property
|
||||
def is_file_url(self):
|
||||
# type: () -> bool
|
||||
return all([self.scheme, self.scheme == "file"])
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return self.to_string(escape_password=True, unquote=True)
|
||||
|
||||
|
||||
def update_url_name_and_fragment(name_with_extras, ref, parsed_dict):
|
||||
# type: (Optional[str], Optional[str], Dict[str, Optional[str]]) -> Dict[str, Optional[str]]
|
||||
if name_with_extras:
|
||||
fragment = "" # type: Optional[str]
|
||||
parsed_extras = ()
|
||||
name, extras = _strip_extras(name_with_extras)
|
||||
if extras:
|
||||
parsed_extras = parsed_extras + tuple(parse_extras_str(extras))
|
||||
if parsed_dict["fragment"] is not None:
|
||||
fragment = "{0}".format(parsed_dict["fragment"])
|
||||
if fragment.startswith("egg="):
|
||||
_, _, fragment_part = fragment.partition("=")
|
||||
fragment_name, fragment_extras = _strip_extras(fragment_part)
|
||||
name = name if name else fragment_name
|
||||
if fragment_extras:
|
||||
parsed_extras = parsed_extras + tuple(
|
||||
parse_extras_str(fragment_extras)
|
||||
)
|
||||
name_with_extras = "{0}{1}".format(name, extras_to_string(parsed_extras))
|
||||
elif (
|
||||
parsed_dict.get("path") is not None and "&subdirectory" in parsed_dict["path"]
|
||||
):
|
||||
path, fragment = URI.parse_subdirectory(parsed_dict["path"]) # type: ignore
|
||||
parsed_dict["path"] = path
|
||||
elif ref is not None and "&subdirectory" in ref:
|
||||
ref, fragment = URI.parse_subdirectory(ref)
|
||||
parsed_dict["name"] = name
|
||||
parsed_dict["extras"] = parsed_extras
|
||||
if ref:
|
||||
parsed_dict["ref"] = ref.strip()
|
||||
return parsed_dict
|
||||
-787
@@ -1,787 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
import string
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from typing import (
|
||||
Any,
|
||||
AnyStr,
|
||||
Dict,
|
||||
List,
|
||||
Match,
|
||||
Optional,
|
||||
Set,
|
||||
Text,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
import pipenv.vendor.tomlkit as tomlkit
|
||||
from pipenv.patched.pip._internal.models.link import Link
|
||||
from pipenv.patched.pip._internal.req.constructors import install_req_from_line
|
||||
from pipenv.patched.pip._internal.utils._jaraco_text import drop_comment, join_continuation, yield_lines
|
||||
from pipenv.patched.pip._vendor.packaging.markers import InvalidMarker, Marker, Op, Value, Variable
|
||||
from pipenv.patched.pip._vendor.packaging.requirements import Requirement as PackagingRequirement
|
||||
from pipenv.patched.pip._vendor.packaging.specifiers import InvalidSpecifier, Specifier, SpecifierSet
|
||||
from pipenv.patched.pip._vendor.packaging.utils import canonicalize_name
|
||||
from pipenv.patched.pip._vendor.packaging.version import parse as parse_version
|
||||
from pipenv.patched.pip._vendor.pkg_resources import Requirement, get_distribution, safe_name
|
||||
from pipenv.patched.pip._vendor.urllib3 import util as urllib3_util
|
||||
from pipenv.patched.pip._vendor.urllib3.util import parse_url as urllib3_parse
|
||||
from pipenv.vendor.plette.models import Package, PackageCollection
|
||||
from pipenv.vendor.tomlkit.container import Container
|
||||
from pipenv.vendor.tomlkit.items import AoT, Array, Bool, InlineTable, Item, String, Table
|
||||
|
||||
from ..environment import MYPY_RUNNING
|
||||
from ..fileutils import is_valid_url
|
||||
from ..utils import VCS_LIST, is_star
|
||||
|
||||
if MYPY_RUNNING:
|
||||
from pipenv.patched.pip._vendor.packaging.markers import Marker as PkgResourcesMarker
|
||||
from pipenv.patched.pip._vendor.packaging.markers import Op as PkgResourcesOp
|
||||
from pipenv.patched.pip._vendor.packaging.markers import Value as PkgResourcesValue
|
||||
from pipenv.patched.pip._vendor.packaging.markers import Variable as PkgResourcesVariable
|
||||
from pipenv.patched.pip._vendor.urllib3.util.url import Url
|
||||
|
||||
_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, Requirement]
|
||||
STRING_TYPE = Union[bytes, str, Text]
|
||||
TOML_DICT_TYPES = Union[Container, Package, PackageCollection, Table, InlineTable]
|
||||
S = TypeVar("S", bytes, str, Text)
|
||||
|
||||
|
||||
TOML_DICT_OBJECTS = (Container, Package, Table, InlineTable, PackageCollection)
|
||||
TOML_DICT_NAMES = [o.__class__.__name__ for o in TOML_DICT_OBJECTS]
|
||||
|
||||
HASH_STRING = " --hash={0}"
|
||||
|
||||
ALPHA_NUMERIC = r"[{0}{1}]".format(string.ascii_letters, string.digits)
|
||||
PUNCTUATION = r"[\-_\.]"
|
||||
ALPHANUM_PUNCTUATION = r"[{0}{1}\-_\.]".format(string.ascii_letters, string.digits)
|
||||
NAME = r"{0}+{1}*{2}".format(ALPHANUM_PUNCTUATION, PUNCTUATION, ALPHA_NUMERIC)
|
||||
REF = r"[{0}{1}\-\_\./]".format(string.ascii_letters, string.digits)
|
||||
EXTRAS = r"(?P<extras>\[{0}(?:,{0})*\])".format(NAME)
|
||||
NAME_WITH_EXTRAS = r"(?P<name>{0}){1}?".format(NAME, EXTRAS)
|
||||
NAME_RE = re.compile(NAME_WITH_EXTRAS)
|
||||
SUBDIR_RE = r"(?:[&#]subdirectory=(?P<subdirectory>.*))"
|
||||
URL_NAME = r"(?:#egg={0})".format(NAME_WITH_EXTRAS)
|
||||
REF_RE = r"(?:@(?P<ref>{0}+)?)".format(REF)
|
||||
PATH_RE = r"(?P<pathsep>[:/])(?P<path>[^ @]+){0}?".format(REF_RE)
|
||||
PASS_RE = r"(?:(?<=:)(?P<password>[^ ]+))"
|
||||
AUTH_RE = r"(?:(?P<username>[^ ]+)[:@]{0}?@)".format(PASS_RE)
|
||||
HOST_RE = r"(?:{0}?(?P<host>[^ ]+?\.?{1}+(?P<port>:\d+)?))?".format(
|
||||
AUTH_RE, ALPHA_NUMERIC
|
||||
)
|
||||
URL = r"(?P<scheme>[^ ]+://){0}{1}".format(HOST_RE, PATH_RE)
|
||||
URL_RE = re.compile(r"{0}(?:{1}?{2}?)?".format(URL, URL_NAME, SUBDIR_RE))
|
||||
DIRECT_URL_RE = re.compile(r"{0}\s?@\s?{1}".format(NAME_WITH_EXTRAS, URL))
|
||||
|
||||
|
||||
def filter_none(k, v) -> bool:
|
||||
if v:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def filter_dict(dict_) -> Dict[AnyStr, Any]:
|
||||
return {k: v for k, v in dict_.items() if filter_none(k, v)}
|
||||
|
||||
|
||||
def create_link(link):
|
||||
# type: (AnyStr) -> Link
|
||||
|
||||
if not isinstance(link, str):
|
||||
raise TypeError("must provide a string to instantiate a new link")
|
||||
|
||||
return Link(link)
|
||||
|
||||
|
||||
def tomlkit_value_to_python(toml_value):
|
||||
# type: (Union[Array, AoT, TOML_DICT_TYPES, Item]) -> Union[List, Dict]
|
||||
value_type = type(toml_value).__name__
|
||||
if (
|
||||
isinstance(toml_value, TOML_DICT_OBJECTS + (dict,))
|
||||
or value_type in TOML_DICT_NAMES
|
||||
):
|
||||
return tomlkit_dict_to_python(toml_value)
|
||||
elif isinstance(toml_value, AoT) or value_type == "AoT":
|
||||
return [tomlkit_value_to_python(val) for val in toml_value._body]
|
||||
elif isinstance(toml_value, Array) or value_type == "Array":
|
||||
return [tomlkit_value_to_python(val) for val in list(toml_value)]
|
||||
elif isinstance(toml_value, String) or value_type == "String":
|
||||
return "{0!s}".format(toml_value)
|
||||
elif isinstance(toml_value, Bool) or value_type == "Bool":
|
||||
return toml_value.value
|
||||
elif isinstance(toml_value, Item):
|
||||
return toml_value.value
|
||||
return toml_value
|
||||
|
||||
|
||||
def tomlkit_dict_to_python(toml_dict):
|
||||
# type: (TOML_DICT_TYPES) -> Dict
|
||||
value_type = type(toml_dict).__name__
|
||||
if toml_dict is None:
|
||||
raise TypeError("Invalid type NoneType when converting toml dict to python")
|
||||
converted = None # type: Optional[Dict]
|
||||
if isinstance(toml_dict, (InlineTable, Table)) or value_type in (
|
||||
"InlineTable",
|
||||
"Table",
|
||||
):
|
||||
converted = toml_dict.value
|
||||
elif isinstance(toml_dict, (Package, PackageCollection)) or value_type in (
|
||||
"Package, PackageCollection"
|
||||
):
|
||||
converted = toml_dict._data
|
||||
if isinstance(converted, Container) or type(converted).__name__ == "Container":
|
||||
converted = converted.value
|
||||
elif isinstance(toml_dict, Container) or value_type == "Container":
|
||||
converted = toml_dict.value
|
||||
elif isinstance(toml_dict, dict):
|
||||
converted = toml_dict.copy()
|
||||
else:
|
||||
raise TypeError(
|
||||
"Invalid type for conversion: expected Container, Dict, or Table, "
|
||||
"got {0!r}".format(toml_dict)
|
||||
)
|
||||
if isinstance(converted, dict):
|
||||
return {k: tomlkit_value_to_python(v) for k, v in converted.items()}
|
||||
elif isinstance(converted, (TOML_DICT_OBJECTS)) or value_type in TOML_DICT_NAMES:
|
||||
return tomlkit_dict_to_python(converted)
|
||||
return converted
|
||||
|
||||
|
||||
def get_url_name(url):
|
||||
# type: (AnyStr) -> AnyStr
|
||||
"""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: Text
|
||||
"""
|
||||
if not isinstance(url, str):
|
||||
raise TypeError("Expected a string, got {0!r}".format(url))
|
||||
return urllib3_util.parse_url(url).host
|
||||
|
||||
|
||||
class HashableRequirement(Requirement):
|
||||
def __hash__(self):
|
||||
specifier_hash = hash(tuple((str(s),) for s in self.specifier))
|
||||
return hash(
|
||||
(
|
||||
self.url,
|
||||
specifier_hash,
|
||||
frozenset(self.extras),
|
||||
str(self.marker) if self.marker else None,
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def parse(s):
|
||||
(req,) = map(
|
||||
HashableRequirement, join_continuation(map(drop_comment, yield_lines(s)))
|
||||
)
|
||||
return req
|
||||
|
||||
|
||||
def init_requirement(name):
|
||||
if not isinstance(name, str):
|
||||
raise TypeError("must supply a name to generate a requirement")
|
||||
|
||||
req = HashableRequirement.parse(name)
|
||||
req.vcs = None
|
||||
req.local_file = None
|
||||
req.revision = None
|
||||
req.path = None
|
||||
return req
|
||||
|
||||
|
||||
def convert_to_hashable_requirement(req: Requirement) -> Optional[HashableRequirement]:
|
||||
if req is None:
|
||||
return None
|
||||
|
||||
hashable_req = HashableRequirement(str(req))
|
||||
hashable_req.extras = req.extras
|
||||
hashable_req.marker = req.marker
|
||||
hashable_req.url = req.url
|
||||
return hashable_req
|
||||
|
||||
|
||||
def extras_to_string(extras) -> str:
|
||||
"""Turn a list of extras into a string."""
|
||||
if isinstance(extras, str):
|
||||
if extras.startswith("["):
|
||||
return extras
|
||||
else:
|
||||
extras = [extras]
|
||||
if not extras:
|
||||
return ""
|
||||
return "[{0}]".format(",".join(sorted(set(extras))))
|
||||
|
||||
|
||||
def parse_extras_str(extras_str):
|
||||
"""Turn a string of extras into a parsed extras list.
|
||||
|
||||
:param str extras_str: An extras string
|
||||
:return: A sorted list of extras
|
||||
:rtype: List[str]
|
||||
"""
|
||||
extras = Requirement.parse("fakepkg{0}".format(extras_to_string(extras_str))).extras
|
||||
return sorted(dict.fromkeys([extra.lower() for extra in extras]))
|
||||
|
||||
|
||||
def parse_extras_from_line(installable_line):
|
||||
extras = []
|
||||
|
||||
# Extract the part within square brackets, if any
|
||||
start = installable_line.find("[")
|
||||
end = installable_line.find("]")
|
||||
|
||||
if start != -1 and end != -1 and start < end:
|
||||
extras_str = installable_line[start + 1 : end]
|
||||
extras = extras_str.split(",")
|
||||
|
||||
return extras
|
||||
|
||||
|
||||
def specs_to_string(specs):
|
||||
# type: (List[Union[STRING_TYPE, Specifier]]) -> AnyStr
|
||||
"""Turn a list of specifier tuples into a string.
|
||||
|
||||
:param List[Union[Specifier, str]] specs: a list of specifiers to format
|
||||
:return: A string of specifiers
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
if specs:
|
||||
if isinstance(specs, str):
|
||||
return specs
|
||||
try:
|
||||
extras = ",".join(["".join(spec) for spec in specs])
|
||||
except TypeError:
|
||||
extras = ",".join(["".join(spec._spec) for spec in specs]) # type: ignore
|
||||
return extras
|
||||
return ""
|
||||
|
||||
|
||||
def build_vcs_uri(
|
||||
vcs,
|
||||
uri,
|
||||
name=None,
|
||||
ref=None,
|
||||
subdirectory=None,
|
||||
extras=None,
|
||||
):
|
||||
# type: (...) -> STRING_TYPE
|
||||
if extras is None:
|
||||
extras = []
|
||||
vcs_start = ""
|
||||
if vcs is not None:
|
||||
vcs_start = "{0}+".format(vcs)
|
||||
if not uri.startswith(vcs_start):
|
||||
uri = "{0}{1}".format(vcs_start, uri)
|
||||
if ref:
|
||||
uri = "{0}@{1}".format(uri, ref)
|
||||
if name:
|
||||
uri = "{0}#egg={1}".format(uri, name)
|
||||
if 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 _get_parsed_url(url):
|
||||
# type: (S) -> Url
|
||||
"""This is a stand-in function for `urllib3.util.parse_url`
|
||||
|
||||
The original function doesn't handle special characters very well, this simply splits
|
||||
out the authentication section, creates the parsed url, then puts the authentication
|
||||
section back in, bypassing validation.
|
||||
|
||||
:return: The new, parsed URL object
|
||||
:rtype: :class:`~urllib3.util.url.Url`
|
||||
"""
|
||||
|
||||
try:
|
||||
parsed = urllib3_parse(url)
|
||||
except ValueError:
|
||||
scheme, _, url = url.partition("://")
|
||||
auth, _, url = url.rpartition("@")
|
||||
url = "{scheme}://{url}".format(scheme=scheme, url=url)
|
||||
parsed = urllib3_parse(url)._replace(auth=auth)
|
||||
return parsed
|
||||
|
||||
|
||||
def convert_direct_url_to_url(direct_url):
|
||||
# type: (AnyStr) -> AnyStr
|
||||
"""Converts direct URLs to standard, link-style URLs.
|
||||
|
||||
Given a direct url as defined by *PEP 508*, convert to a :class:`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:`InstallRequirement` objects.
|
||||
:rtype: AnyStr
|
||||
"""
|
||||
direct_match = DIRECT_URL_RE.match(direct_url) # type: Optional[Match]
|
||||
if direct_match is None:
|
||||
url_match = URL_RE.match(direct_url)
|
||||
if url_match or is_valid_url(direct_url):
|
||||
return direct_url
|
||||
match_dict = (
|
||||
{}
|
||||
) # type: Dict[STRING_TYPE, Union[Tuple[STRING_TYPE, ...], STRING_TYPE]]
|
||||
if direct_match is not None:
|
||||
match_dict = direct_match.groupdict() # type: ignore
|
||||
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 = "" # type: STRING_TYPE
|
||||
url = "".join([s for s in url_segments if s is not None]) # type: ignore
|
||||
new_url = build_vcs_uri(
|
||||
None,
|
||||
url,
|
||||
ref=match_dict.get("ref"),
|
||||
name=match_dict.get("name"),
|
||||
extras=match_dict.get("extras"),
|
||||
subdirectory=match_dict.get("subdirectory"),
|
||||
)
|
||||
return new_url
|
||||
|
||||
|
||||
def get_version(pipfile_entry):
|
||||
if str(pipfile_entry) == "{}" or is_star(pipfile_entry):
|
||||
return ""
|
||||
|
||||
if hasattr(pipfile_entry, "keys") and "version" in pipfile_entry:
|
||||
if is_star(pipfile_entry.get("version")):
|
||||
return ""
|
||||
version = pipfile_entry.get("version")
|
||||
if version is None:
|
||||
version = ""
|
||||
return version.strip().lstrip("(").rstrip(")")
|
||||
|
||||
if isinstance(pipfile_entry, str):
|
||||
return pipfile_entry.strip().lstrip("(").rstrip(")")
|
||||
return ""
|
||||
|
||||
|
||||
def strip_extras_markers_from_requirement(req):
|
||||
# type: (TRequirement) -> TRequirement
|
||||
"""Strips extras markers from requirement instances.
|
||||
|
||||
Given a :class:`~packaging.requirements.Requirement` instance with markers defining
|
||||
*extra == 'name'*, strip out the extras from the markers and return the cleaned
|
||||
requirement
|
||||
|
||||
:param PackagingRequirement req: A packaging requirement to clean
|
||||
:return: A cleaned requirement
|
||||
:rtype: PackagingRequirement
|
||||
"""
|
||||
if req is None:
|
||||
raise TypeError("Must pass in a valid requirement, received {0!r}".format(req))
|
||||
if getattr(req, "marker", None) is not None:
|
||||
marker = req.marker # type: TMarker
|
||||
marker._markers = _strip_extras_markers(marker._markers)
|
||||
if not marker._markers:
|
||||
req.marker = None
|
||||
else:
|
||||
req.marker = marker
|
||||
return req
|
||||
|
||||
|
||||
def _strip_extras_markers(marker):
|
||||
# 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 = []
|
||||
# iterate forwards and generate a list of indexes to remove first, then reverse the
|
||||
# list so we can remove the text that normally occurs after (but we will already
|
||||
# be past it in the loop)
|
||||
for i, marker_list in enumerate(marker):
|
||||
if isinstance(marker_list, list):
|
||||
cleaned = _strip_extras_markers(marker_list)
|
||||
if not cleaned:
|
||||
markers_to_remove.append(i)
|
||||
elif isinstance(marker_list, tuple) and marker_list[0].value == "extra":
|
||||
markers_to_remove.append(i)
|
||||
for i in reversed(markers_to_remove):
|
||||
del marker[i]
|
||||
if i > 0 and marker[i - 1] == "and":
|
||||
del marker[i - 1]
|
||||
return marker
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def get_setuptools_version():
|
||||
# type: () -> Optional[STRING_TYPE]
|
||||
|
||||
setuptools_dist = get_distribution(Requirement("setuptools"))
|
||||
return getattr(setuptools_dist, "version", None)
|
||||
|
||||
|
||||
def get_default_pyproject_backend():
|
||||
# type: () -> STRING_TYPE
|
||||
st_version = get_setuptools_version()
|
||||
if st_version is not None:
|
||||
parsed_st_version = parse_version(st_version)
|
||||
if parsed_st_version >= parse_version("40.8.0"):
|
||||
return "setuptools.build_meta:__legacy__"
|
||||
return "setuptools.build_meta"
|
||||
|
||||
|
||||
def get_pyproject(path: Union[AnyStr, Path]) -> Optional[Dict[str, Union[List[AnyStr], AnyStr]]]:
|
||||
"""
|
||||
Given a base path, look for the corresponding ``pyproject.toml`` file
|
||||
and return its build_requires and build_backend.
|
||||
|
||||
:param path: The root path of the project, should be a directory (will be truncated)
|
||||
:return: A dictionary with build requirements, build backend, and dependencies
|
||||
"""
|
||||
if not path:
|
||||
return
|
||||
|
||||
if not isinstance(path, Path):
|
||||
path = Path(path)
|
||||
|
||||
if not path.is_dir():
|
||||
path = path.parent
|
||||
|
||||
pp_toml = path / "pyproject.toml"
|
||||
|
||||
# Default values
|
||||
requires = ["setuptools>=40.8", "wheel"]
|
||||
backend = get_default_pyproject_backend()
|
||||
dependencies = []
|
||||
|
||||
if pp_toml.exists():
|
||||
with open(pp_toml, encoding="utf-8") as fh:
|
||||
pyproject_data = tomlkit.loads(fh.read())
|
||||
|
||||
# Extracting build system information
|
||||
build_system = pyproject_data.get("build-system", None)
|
||||
if build_system is not None:
|
||||
requires = build_system.get("requires", requires)
|
||||
backend = build_system.get("build-backend", backend)
|
||||
|
||||
# Extracting project dependencies
|
||||
project_data = pyproject_data.get("project", None)
|
||||
if project_data is not None:
|
||||
dependencies = project_data.get("dependencies", [])
|
||||
|
||||
return {"build_requires": requires, "build_backend": backend, "dependencies": dependencies}
|
||||
|
||||
|
||||
def split_markers_from_line(line):
|
||||
# type: (AnyStr) -> Tuple[AnyStr, Optional[AnyStr]]
|
||||
"""Split markers from a dependency."""
|
||||
quote_chars = ["'", '"']
|
||||
line_quote = next(
|
||||
iter(quote for quote in quote_chars if line.startswith(quote)), None
|
||||
)
|
||||
if line_quote and line.endswith(line_quote):
|
||||
line = line.strip(line_quote)
|
||||
marker_sep = " ; "
|
||||
markers = None
|
||||
if marker_sep in line:
|
||||
line, markers = line.split(marker_sep, 1)
|
||||
markers = markers.strip() if markers else None
|
||||
return line, markers
|
||||
|
||||
|
||||
def split_vcs_method_from_uri(uri):
|
||||
# type: (AnyStr) -> Tuple[Optional[STRING_TYPE], STRING_TYPE]
|
||||
"""Split a vcs+uri formatted uri into (vcs, uri)"""
|
||||
vcs_start = "{0}+"
|
||||
vcs = next(
|
||||
iter([vcs for vcs in VCS_LIST if uri.startswith(vcs_start.format(vcs))]), None
|
||||
)
|
||||
if vcs:
|
||||
vcs, uri = uri.split("+", 1)
|
||||
return vcs, uri
|
||||
|
||||
|
||||
def split_ref_from_uri(uri):
|
||||
# type: (AnyStr) -> Tuple[AnyStr, Optional[AnyStr]]
|
||||
"""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 AnyStr uri: The path or URI to split
|
||||
:returns: A 2-tuple of the path or URI and the ref
|
||||
:rtype: Tuple[AnyStr, Optional[AnyStr]]
|
||||
"""
|
||||
if not isinstance(uri, str):
|
||||
raise TypeError("Expected a string, received {0!r}".format(uri))
|
||||
parsed = _get_parsed_url(uri)
|
||||
path = parsed.path if parsed.path else ""
|
||||
scheme = parsed.scheme if parsed.scheme else ""
|
||||
ref = None
|
||||
schema_is_filelike = scheme in ("", "file")
|
||||
if (not schema_is_filelike and "@" in path) or (
|
||||
schema_is_filelike and (re.match("^.*@[^/@]*$", path) or path.count("@") >= 2)
|
||||
):
|
||||
path, _, ref = path.rpartition("@")
|
||||
parsed = parsed._replace(path=path)
|
||||
return (parsed.url, ref)
|
||||
|
||||
|
||||
def validate_vcs(instance, attr_, value):
|
||||
if value not in VCS_LIST:
|
||||
raise ValueError("Invalid vcs {0!r}".format(value))
|
||||
|
||||
|
||||
def validate_path(instance, attr_, value):
|
||||
if not os.path.exists(value):
|
||||
raise ValueError("Invalid path {0!r}".format(value))
|
||||
|
||||
|
||||
def validate_specifiers(instance, attr_, value):
|
||||
if value == "":
|
||||
return True
|
||||
try:
|
||||
SpecifierSet(value)
|
||||
except (InvalidMarker, InvalidSpecifier):
|
||||
raise ValueError("Invalid Specifiers {0}".format(value))
|
||||
|
||||
|
||||
def key_from_req(req):
|
||||
"""Get an all-lowercase version of the requirement's name."""
|
||||
if hasattr(req, "key"):
|
||||
# from pkg_resources, such as installed dists for pip-sync
|
||||
key = req.key
|
||||
else:
|
||||
# from packaging, such as install requirements from requirements.txt
|
||||
key = req.name
|
||||
|
||||
key = key.replace("_", "-").lower()
|
||||
return key
|
||||
|
||||
|
||||
def _requirement_to_str_lowercase_name(requirement):
|
||||
"""Formats a packaging.requirements.Requirement with a lowercase name.
|
||||
|
||||
This is simply a copy of
|
||||
https://github.com/pypa/packaging/blob/16.8/packaging/requirements.py#L109-L124
|
||||
modified to lowercase the dependency name.
|
||||
|
||||
Previously, we were invoking the original Requirement.__str__ method and
|
||||
lower-casing the entire result, which would lowercase the name, *and* other,
|
||||
important stuff that should not be lower-cased (such as the marker). See
|
||||
this issue for more information: https://github.com/pypa/pipenv/issues/2113.
|
||||
"""
|
||||
parts = [requirement.name.lower()]
|
||||
|
||||
if requirement.extras:
|
||||
parts.append("[{0}]".format(",".join(sorted(requirement.extras))))
|
||||
|
||||
if requirement.specifier:
|
||||
parts.append(str(requirement.specifier))
|
||||
|
||||
if requirement.url:
|
||||
parts.append("@ {0}".format(requirement.url))
|
||||
|
||||
if requirement.marker:
|
||||
parts.append(" ; {0}".format(requirement.marker))
|
||||
|
||||
return "".join(parts)
|
||||
|
||||
|
||||
def format_requirement(ireq):
|
||||
"""Formats an `InstallRequirement` instance as a string.
|
||||
|
||||
Generic formatter for pretty printing InstallRequirements to the terminal
|
||||
in a less verbose way than using its `__str__` method.
|
||||
|
||||
:param :class:`InstallRequirement` ireq: A pip **InstallRequirement** instance.
|
||||
:return: A formatted string for prettyprinting
|
||||
:rtype: str
|
||||
"""
|
||||
if ireq.editable:
|
||||
line = "-e {}".format(ireq.link)
|
||||
else:
|
||||
line = _requirement_to_str_lowercase_name(ireq.req)
|
||||
|
||||
if str(ireq.req.marker) != str(ireq.markers):
|
||||
if not ireq.req.marker:
|
||||
line = "{} ; {}".format(line, ireq.markers)
|
||||
else:
|
||||
name, markers = line.split(";", 1)
|
||||
markers = markers.strip()
|
||||
line = "{} ; ({}) and ({})".format(name, markers, ireq.markers)
|
||||
|
||||
return line
|
||||
|
||||
|
||||
def get_pinned_version(ireq):
|
||||
"""Get the pinned version of an InstallRequirement.
|
||||
|
||||
An InstallRequirement is considered pinned if:
|
||||
|
||||
- Is not editable
|
||||
- It has exactly one specifier
|
||||
- That specifier is "=="
|
||||
- The version does not contain a wildcard
|
||||
|
||||
Examples:
|
||||
django==1.8 # pinned
|
||||
django>1.8 # NOT pinned
|
||||
django~=1.8 # NOT pinned
|
||||
django==1.* # NOT pinned
|
||||
|
||||
Raises `TypeError` if the input is not a valid InstallRequirement, or
|
||||
`ValueError` if the InstallRequirement is not pinned.
|
||||
"""
|
||||
try:
|
||||
specifier = ireq.specifier
|
||||
except AttributeError:
|
||||
raise TypeError("Expected InstallRequirement, not {}".format(type(ireq).__name__))
|
||||
|
||||
if getattr(ireq, "editable", False):
|
||||
raise ValueError("InstallRequirement is editable")
|
||||
if not specifier:
|
||||
raise ValueError("InstallRequirement has no version specification")
|
||||
if len(specifier._specs) != 1:
|
||||
raise ValueError("InstallRequirement has multiple specifications")
|
||||
|
||||
op, version = next(iter(specifier._specs))._spec
|
||||
if op not in ("==", "===") or version.endswith(".*"):
|
||||
raise ValueError("InstallRequirement not pinned (is {0!r})".format(op + version))
|
||||
|
||||
return version
|
||||
|
||||
|
||||
def is_pinned_requirement(ireq):
|
||||
"""Returns whether an InstallRequirement is a "pinned" requirement.
|
||||
|
||||
An InstallRequirement is considered pinned if:
|
||||
|
||||
- Is not editable
|
||||
- It has exactly one specifier
|
||||
- That specifier is "=="
|
||||
- The version does not contain a wildcard
|
||||
|
||||
Examples:
|
||||
django==1.8 # pinned
|
||||
django>1.8 # NOT pinned
|
||||
django~=1.8 # NOT pinned
|
||||
django==1.* # NOT pinned
|
||||
"""
|
||||
|
||||
try:
|
||||
get_pinned_version(ireq)
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def as_tuple(ireq):
|
||||
"""Pulls out the (name: str, version:str, extras:(str)) tuple from the
|
||||
pinned InstallRequirement."""
|
||||
|
||||
if not is_pinned_requirement(ireq):
|
||||
raise TypeError("Expected a pinned InstallRequirement, got {}".format(ireq))
|
||||
|
||||
name = key_from_req(ireq.req)
|
||||
version = next(iter(ireq.specifier._specs))._spec[1]
|
||||
extras = tuple(sorted(ireq.extras))
|
||||
return name, version, extras
|
||||
|
||||
|
||||
def make_install_requirement(
|
||||
name, version=None, extras=None, markers=None, constraint=False
|
||||
):
|
||||
"""Generates an :class:`~pipenv.patched.pip._internal.req.req_install.InstallRequirement`.
|
||||
|
||||
Create an InstallRequirement from the supplied metadata.
|
||||
|
||||
:param name: The requirement's name.
|
||||
:type name: str
|
||||
:param version: The requirement version (must be pinned).
|
||||
:type version: str.
|
||||
:param extras: The desired extras.
|
||||
:type extras: list[str]
|
||||
:param markers: The desired markers, without a preceding semicolon.
|
||||
:type markers: str
|
||||
:param constraint: Whether to flag the requirement as a constraint, defaults to False.
|
||||
:param constraint: bool, optional
|
||||
:return: A generated InstallRequirement
|
||||
:rtype: :class:`~pipenv.patched.pip._internal.req.req_install.InstallRequirement`
|
||||
"""
|
||||
requirement_string = "{0}".format(name)
|
||||
if extras:
|
||||
# Sort extras for stability
|
||||
extras_string = "[{}]".format(",".join(sorted(extras)))
|
||||
requirement_string = "{0}{1}".format(requirement_string, extras_string)
|
||||
if version:
|
||||
requirement_string = "{0}=={1}".format(requirement_string, str(version))
|
||||
if markers:
|
||||
requirement_string = "{0} ; {1}".format(requirement_string, str(markers))
|
||||
return install_req_from_line(requirement_string, constraint=constraint)
|
||||
|
||||
|
||||
def normalize_name(pkg) -> str:
|
||||
"""Given a package name, return its normalized, non-canonicalized form."""
|
||||
return pkg.replace("_", "-").lower()
|
||||
|
||||
|
||||
def get_name_variants(pkg):
|
||||
# type: (STRING_TYPE) -> Set[STRING_TYPE]
|
||||
"""Given a packager name, get the variants of its name for both the
|
||||
canonicalized and "safe" forms.
|
||||
|
||||
:param AnyStr pkg: The package to lookup
|
||||
:returns: A list of names.
|
||||
:rtype: Set
|
||||
"""
|
||||
|
||||
if not isinstance(pkg, str):
|
||||
raise TypeError("must provide a string to derive package names")
|
||||
|
||||
pkg = pkg.lower()
|
||||
names = {safe_name(pkg), canonicalize_name(pkg), pkg.replace("-", "_")}
|
||||
return names
|
||||
|
||||
|
||||
def expand_env_variables(line):
|
||||
# type: (AnyStr) -> AnyStr
|
||||
"""Expand the env vars in a line following pip's standard.
|
||||
https://pip.pypa.io/en/stable/reference/pip_install/#id10.
|
||||
|
||||
Matches environment variable-style values in '${MY_VARIABLE_1}' with
|
||||
the variable name consisting of only uppercase letters, digits or
|
||||
the '_'
|
||||
"""
|
||||
|
||||
def replace_with_env(match):
|
||||
value = os.getenv(match.group(1))
|
||||
return value if value else match.group()
|
||||
|
||||
return re.sub(r"\$\{([A-Z0-9_]+)\}", replace_with_env, line)
|
||||
|
||||
|
||||
def tuple_to_dict(input_tuple):
|
||||
result_dict = {}
|
||||
i = 0
|
||||
while i < len(input_tuple):
|
||||
key = input_tuple[i]
|
||||
i += 1
|
||||
if i < len(input_tuple):
|
||||
if isinstance(input_tuple[i], tuple):
|
||||
value = tuple_to_dict(input_tuple[i])
|
||||
i += 1
|
||||
else:
|
||||
value = input_tuple[i]
|
||||
i += 1
|
||||
result_dict[key] = value
|
||||
return result_dict
|
||||
-104
@@ -1,104 +0,0 @@
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
from typing import Any, Optional, Tuple
|
||||
|
||||
from pipenv.patched.pip._internal.vcs.versioncontrol import VcsSupport
|
||||
from pipenv.patched.pip._vendor.pyparsing.core import cached_property
|
||||
from pipenv.vendor.pydantic import Field
|
||||
|
||||
from .common import ReqLibBaseModel
|
||||
from .url import URI
|
||||
|
||||
|
||||
class VCSRepository(ReqLibBaseModel):
|
||||
url: str
|
||||
name: str
|
||||
checkout_directory: str
|
||||
vcs_type: str
|
||||
parsed_url: Optional[URI] = Field(default_factory=None)
|
||||
subdirectory: Optional[str] = None
|
||||
commit_sha: Optional[str] = None
|
||||
ref: Optional[str] = None
|
||||
repo_backend: Any = None
|
||||
clone_log: Optional[str] = None
|
||||
DEFAULT_RUN_ARGS: Optional[Any] = None
|
||||
|
||||
class Config:
|
||||
validate_assignment = True
|
||||
arbitrary_types_allowed = True
|
||||
allow_mutation = True
|
||||
include_private_attributes = True
|
||||
keep_untouched = (cached_property,)
|
||||
|
||||
def __init__(self, **data):
|
||||
super().__init__(**data)
|
||||
self.parsed_url = self.get_parsed_url()
|
||||
self.repo_backend = self.get_repo_backend()
|
||||
|
||||
def get_parsed_url(self) -> URI:
|
||||
return URI.parse(self.url)
|
||||
|
||||
def get_repo_backend(self):
|
||||
if self.DEFAULT_RUN_ARGS is None:
|
||||
default_run_args = self.monkeypatch_pip()
|
||||
else:
|
||||
default_run_args = self.DEFAULT_RUN_ARGS
|
||||
|
||||
VCS_SUPPORT = VcsSupport()
|
||||
backend = VCS_SUPPORT.get_backend(self.vcs_type)
|
||||
if backend.run_command.__func__.__defaults__ != default_run_args:
|
||||
backend.run_command.__func__.__defaults__ = default_run_args
|
||||
return backend
|
||||
|
||||
@property
|
||||
def is_local(self) -> bool:
|
||||
url = self.url
|
||||
if "+" in url:
|
||||
url = url.split("+")[1]
|
||||
return url.startswith("file")
|
||||
|
||||
def obtain(self, verbosity=1) -> None:
|
||||
if os.path.exists(
|
||||
self.checkout_directory
|
||||
) and not self.repo_backend.is_repository_directory(self.checkout_directory):
|
||||
self.repo_backend.unpack(self.checkout_directory)
|
||||
elif not os.path.exists(self.checkout_directory):
|
||||
self.repo_backend.obtain(self.checkout_directory, self.parsed_url, verbosity)
|
||||
else:
|
||||
if self.ref:
|
||||
self.checkout_ref(self.ref)
|
||||
if not self.commit_sha:
|
||||
self.commit_sha = self.commit_hash
|
||||
|
||||
def checkout_ref(self, ref: str) -> None:
|
||||
rev_opts = self.repo_backend.make_rev_options(ref)
|
||||
if not any(
|
||||
[
|
||||
self.repo_backend.is_commit_id_equal(self.checkout_directory, ref),
|
||||
self.repo_backend.is_commit_id_equal(self.checkout_directory, rev_opts),
|
||||
self.is_local,
|
||||
]
|
||||
):
|
||||
self.update(ref)
|
||||
|
||||
def update(self, ref: str) -> None:
|
||||
target_ref = self.repo_backend.make_rev_options(ref)
|
||||
self.repo_backend.update(self.checkout_directory, self.url, target_ref)
|
||||
self.commit_sha = self.commit_hash
|
||||
|
||||
@cached_property
|
||||
def commit_hash(self) -> str:
|
||||
return self.repo_backend.get_revision(self.checkout_directory)
|
||||
|
||||
@classmethod
|
||||
def monkeypatch_pip(cls) -> Tuple[Any, ...]:
|
||||
target_module = VcsSupport.__module__
|
||||
pip_vcs = importlib.import_module(target_module)
|
||||
run_command_defaults = pip_vcs.VersionControl.run_command.__func__.__defaults__
|
||||
new_defaults = [False] + list(run_command_defaults)[1:]
|
||||
new_defaults = tuple(new_defaults)
|
||||
pip_vcs.VersionControl.run_command.__func__.__defaults__ = new_defaults
|
||||
sys.modules[target_module] = pip_vcs
|
||||
cls.DEFAULT_RUN_ARGS = new_defaults
|
||||
return new_defaults
|
||||
Vendored
-1
@@ -11,7 +11,6 @@ ptyprocess==0.7.0
|
||||
pydantic==1.10.10
|
||||
python-dotenv==1.0.0
|
||||
pythonfinder==2.0.5
|
||||
requirementslib==3.0.0
|
||||
ruamel.yaml==0.17.21
|
||||
shellingham==1.5.0.post1
|
||||
tomli==2.0.1
|
||||
|
||||
@@ -22,8 +22,7 @@ if sys.argv[-1] == "publish":
|
||||
required = [
|
||||
"certifi",
|
||||
"setuptools>=67.0.0",
|
||||
"virtualenv-clone>=0.2.5",
|
||||
"virtualenv>=20.17.1",
|
||||
"virtualenv>=20.24.2",
|
||||
]
|
||||
extras = {
|
||||
"dev": [
|
||||
@@ -31,7 +30,7 @@ extras = {
|
||||
"beautifulsoup4",
|
||||
"sphinx",
|
||||
"flake8>=3.3.0,<4.0",
|
||||
"black;python_version>='3.7'",
|
||||
"black==23.3.0",
|
||||
"parver",
|
||||
"invoke",
|
||||
],
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import invoke
|
||||
from parver import Version
|
||||
|
||||
from pipenv.__version__ import __version__
|
||||
from pipenv.vendor.requirementslib.utils import temp_environ
|
||||
from pipenv.utils.shell import temp_environ
|
||||
|
||||
from .vendoring import _get_git_root, drop_dir
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import invoke
|
||||
import requests
|
||||
from urllib3.util import parse_url as urllib3_parse
|
||||
|
||||
from pipenv.vendor.requirementslib.fileutils import open_file
|
||||
from pipenv.utils.fileutils import open_file
|
||||
|
||||
TASK_NAME = "update"
|
||||
|
||||
@@ -36,7 +36,6 @@ HARDCODED_LICENSE_URLS = {
|
||||
"pytoml": "https://github.com/avakar/pytoml/raw/master/LICENSE",
|
||||
"webencodings": "https://github.com/SimonSapin/python-webencodings/raw/"
|
||||
"master/LICENSE",
|
||||
"requirementslib": "https://github.com/techalchemy/requirementslib/raw/master/LICENSE",
|
||||
"distlib": "https://github.com/vsajip/distlib/raw/master/LICENSE.txt",
|
||||
"pythonfinder": "https://raw.githubusercontent.com/techalchemy/pythonfinder/master/LICENSE.txt",
|
||||
"pipdeptree": "https://raw.githubusercontent.com/tox-dev/pipdeptree/main/LICENSE",
|
||||
|
||||
@@ -12,11 +12,12 @@ from tempfile import TemporaryDirectory
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
from pipenv.utils.processes import subprocess_run
|
||||
from pipenv.patched.pip._vendor import requests
|
||||
from pipenv.vendor import tomlkit
|
||||
from pipenv.vendor.requirementslib.utils import temp_environ
|
||||
from pipenv.vendor.requirementslib.models.setup_info import handle_remove_readonly
|
||||
|
||||
from pipenv.utils.processes import subprocess_run
|
||||
from pipenv.utils.funktools import handle_remove_readonly
|
||||
from pipenv.utils.shell import temp_environ
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
warnings.simplefilter("default", category=ResourceWarning)
|
||||
@@ -173,7 +174,7 @@ class _Pipfile:
|
||||
|
||||
@classmethod
|
||||
def get_fixture_path(cls, path, fixtures="test_artifacts"):
|
||||
return Path(__file__).absolute().parent.parent / fixtures / path
|
||||
return Path(__file__).resolve().parent.parent / fixtures / path
|
||||
|
||||
|
||||
class _PipenvInstance:
|
||||
@@ -284,17 +285,21 @@ class _PipenvInstance:
|
||||
return os.sep.join([self.path, 'Pipfile.lock'])
|
||||
|
||||
|
||||
# Windows python3.8 fails without this patch. Additional details: https://bugs.python.org/issue42796
|
||||
def _rmtree_func(path, ignore_errors=True, onerror=None):
|
||||
shutil_rmtree = _rmtree
|
||||
if onerror is None:
|
||||
onerror = handle_remove_readonly
|
||||
try:
|
||||
shutil_rmtree(path, ignore_errors=ignore_errors, onerror=onerror)
|
||||
except (OSError, FileNotFoundError, PermissionError) as exc:
|
||||
# Ignore removal failures where the file doesn't exist
|
||||
if exc.errno != errno.ENOENT:
|
||||
raise
|
||||
if sys.version_info[:2] <= (3, 8):
|
||||
# Windows python3.8 fails without this patch. Additional details: https://bugs.python.org/issue42796
|
||||
def _rmtree_func(path, ignore_errors=True, onerror=None):
|
||||
shutil_rmtree = _rmtree
|
||||
if onerror is None:
|
||||
onerror = handle_remove_readonly
|
||||
try:
|
||||
shutil_rmtree(path, ignore_errors=ignore_errors, onerror=onerror)
|
||||
except (OSError, FileNotFoundError, PermissionError) as exc:
|
||||
# Ignore removal failures where the file doesn't exist
|
||||
if exc.errno != errno.ENOENT:
|
||||
raise
|
||||
else:
|
||||
_rmtree_func = _rmtree
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def pipenv_instance_pypi(capfdbinary, monkeypatch):
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
|
||||
@@ -28,6 +29,7 @@ def test_pipenv_venv(pipenv_instance_pypi):
|
||||
|
||||
|
||||
@pytest.mark.cli
|
||||
@pytest.mark.skipif(sys.version_info[:2] == (3, 8) and os.name == "nt", reason="Python 3.8 on Windows is not supported")
|
||||
def test_pipenv_py(pipenv_instance_pypi):
|
||||
with pipenv_instance_pypi() as p:
|
||||
c = p.pipenv('--python python')
|
||||
@@ -39,6 +41,7 @@ def test_pipenv_py(pipenv_instance_pypi):
|
||||
|
||||
|
||||
@pytest.mark.cli
|
||||
@pytest.mark.skipif(os.name == 'nt' and sys.version_info[:2] == (3, 8), reason='Test issue with windows 3.8 CIs')
|
||||
def test_pipenv_site_packages(pipenv_instance_pypi):
|
||||
with pipenv_instance_pypi() as p:
|
||||
c = p.pipenv('--python python --site-packages')
|
||||
@@ -172,8 +175,8 @@ def test_pipenv_check_check_lockfile_categories(pipenv_instance_pypi, category):
|
||||
|
||||
|
||||
@pytest.mark.cli
|
||||
def test_pipenv_clean(pipenv_instance_pypi):
|
||||
with pipenv_instance_pypi() as p:
|
||||
def test_pipenv_clean(pipenv_instance_private_pypi):
|
||||
with pipenv_instance_private_pypi() as p:
|
||||
with open('setup.py', 'w') as f:
|
||||
f.write('from setuptools import setup; setup(name="empty")')
|
||||
c = p.pipenv('install -e .')
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from pipenv.patched.pip._internal.operations.prepare import File
|
||||
|
||||
from pipenv.utils.requirements import import_requirements
|
||||
from pipenv.project import Project
|
||||
|
||||
@@ -11,57 +13,69 @@ from pipenv.project import Project
|
||||
@pytest.mark.cli
|
||||
@pytest.mark.deploy
|
||||
@pytest.mark.system
|
||||
def test_auth_with_pw_redacted(pipenv_instance_pypi):
|
||||
@mock.patch("pipenv.utils.dependencies.unpack_url", mock.MagicMock(return_value=File("/some/path/to/project", content_type=None)))
|
||||
@mock.patch("pipenv.utils.dependencies.find_package_name_from_directory")
|
||||
def test_auth_with_pw_redacted(mock_find_package_name_from_directory, pipenv_instance_pypi):
|
||||
mock_find_package_name_from_directory.return_value = "myproject"
|
||||
with pipenv_instance_pypi() as p:
|
||||
p.pipenv("run shell")
|
||||
project = Project()
|
||||
requirements_file = tempfile.NamedTemporaryFile(mode="w+", delete=False)
|
||||
requirements_file.write("""git+https://${AUTH_USER}:mypw1@github.com/user/myproject.git#egg=myproject""")
|
||||
requirements_file.write("""git+https://${AUTH_USER}:mypw1@github.com/user/myproject.git@main#egg=myproject""")
|
||||
requirements_file.close()
|
||||
import_requirements(project, r=requirements_file.name)
|
||||
os.unlink(requirements_file.name)
|
||||
assert p.pipfile["packages"]["myproject"] == {'git': 'https://${AUTH_USER}:****@github.com/user/myproject.git'}
|
||||
assert p.pipfile["packages"]["myproject"] == {'git': 'git+https://${AUTH_USER}:****@github.com/user/myproject.git', 'ref': 'main'}
|
||||
|
||||
|
||||
@pytest.mark.cli
|
||||
@pytest.mark.deploy
|
||||
@pytest.mark.system
|
||||
def test_auth_with_username_redacted(pipenv_instance_pypi):
|
||||
@mock.patch("pipenv.utils.dependencies.unpack_url", mock.MagicMock(return_value=File("/some/path/to/project", content_type=None)))
|
||||
@mock.patch("pipenv.utils.dependencies.find_package_name_from_directory")
|
||||
def test_auth_with_username_redacted(mock_find_package_name_from_directory, pipenv_instance_pypi):
|
||||
mock_find_package_name_from_directory.return_value = "myproject"
|
||||
with pipenv_instance_pypi() as p:
|
||||
p.pipenv("run shell")
|
||||
project = Project()
|
||||
requirements_file = tempfile.NamedTemporaryFile(mode="w+", delete=False)
|
||||
requirements_file.write("""git+https://username@github.com/user/myproject.git#egg=myproject""")
|
||||
requirements_file.write("""git+https://username@github.com/user/myproject.git@main#egg=myproject""")
|
||||
requirements_file.close()
|
||||
import_requirements(project, r=requirements_file.name)
|
||||
os.unlink(requirements_file.name)
|
||||
assert p.pipfile["packages"]["myproject"] == {'git': 'https://****@github.com/user/myproject.git'}
|
||||
assert p.pipfile["packages"]["myproject"] == {'git': 'git+https://****@github.com/user/myproject.git', 'ref': 'main'}
|
||||
|
||||
|
||||
@pytest.mark.cli
|
||||
@pytest.mark.deploy
|
||||
@pytest.mark.system
|
||||
def test_auth_with_pw_are_variables_passed_to_pipfile(pipenv_instance_pypi):
|
||||
@mock.patch("pipenv.utils.dependencies.unpack_url", mock.MagicMock(return_value=File("/some/path/to/project", content_type=None)))
|
||||
@mock.patch("pipenv.utils.dependencies.find_package_name_from_directory")
|
||||
def test_auth_with_pw_are_variables_passed_to_pipfile(mock_find_package_name_from_directory, pipenv_instance_pypi):
|
||||
mock_find_package_name_from_directory.return_value = "myproject"
|
||||
with pipenv_instance_pypi() as p:
|
||||
p.pipenv("run shell")
|
||||
project = Project()
|
||||
requirements_file = tempfile.NamedTemporaryFile(mode="w+", delete=False)
|
||||
requirements_file.write("""git+https://${AUTH_USER}:${AUTH_PW}@github.com/user/myproject.git#egg=myproject""")
|
||||
requirements_file.write("""git+https://${AUTH_USER}:${AUTH_PW}@github.com/user/myproject.git@main#egg=myproject""")
|
||||
requirements_file.close()
|
||||
import_requirements(project, r=requirements_file.name)
|
||||
os.unlink(requirements_file.name)
|
||||
assert p.pipfile["packages"]["myproject"] == {'git': 'https://${AUTH_USER}:${AUTH_PW}@github.com/user/myproject.git'}
|
||||
assert p.pipfile["packages"]["myproject"] == {'git': 'git+https://${AUTH_USER}:${AUTH_PW}@github.com/user/myproject.git', 'ref': 'main'}
|
||||
|
||||
@pytest.mark.cli
|
||||
@pytest.mark.deploy
|
||||
@pytest.mark.system
|
||||
def test_auth_with_only_username_variable_passed_to_pipfile(pipenv_instance_pypi):
|
||||
@mock.patch("pipenv.utils.dependencies.unpack_url", mock.MagicMock(return_value=File("/some/path/to/project", content_type=None)))
|
||||
@mock.patch("pipenv.utils.dependencies.find_package_name_from_directory")
|
||||
def test_auth_with_only_username_variable_passed_to_pipfile(mock_find_package_name_from_directory, pipenv_instance_pypi):
|
||||
mock_find_package_name_from_directory.return_value = "myproject"
|
||||
with pipenv_instance_pypi() as p:
|
||||
p.pipenv("run shell")
|
||||
project = Project()
|
||||
requirements_file = tempfile.NamedTemporaryFile(mode="w+", delete=False)
|
||||
requirements_file.write("""git+https://${AUTH_USER}@github.com/user/myproject.git#egg=myproject""")
|
||||
requirements_file.write("""git+https://${AUTH_USER}@github.com/user/myproject.git@main#egg=myproject""")
|
||||
requirements_file.close()
|
||||
import_requirements(project, r=requirements_file.name)
|
||||
os.unlink(requirements_file.name)
|
||||
assert p.pipfile["packages"]["myproject"] == {'git': 'https://${AUTH_USER}@github.com/user/myproject.git'}
|
||||
assert p.pipfile["packages"]["myproject"] == {'git': 'git+https://${AUTH_USER}@github.com/user/myproject.git', 'ref': 'main'}
|
||||
|
||||
@@ -162,6 +162,7 @@ dataclasses-json = "==0.5.7"
|
||||
@pytest.mark.install
|
||||
@pytest.mark.resolver
|
||||
@pytest.mark.backup_resolver
|
||||
@pytest.mark.skipif(sys.version_info >= (3, 12), reason="Package does not work with Python 3.12")
|
||||
def test_backup_resolver(pipenv_instance_private_pypi):
|
||||
with pipenv_instance_private_pypi() as p:
|
||||
with open(p.pipfile_path, "w") as f:
|
||||
@@ -298,10 +299,10 @@ def test_clean_on_empty_venv(pipenv_instance_pypi):
|
||||
|
||||
@pytest.mark.basic
|
||||
@pytest.mark.install
|
||||
def test_install_does_not_extrapolate_environ(pipenv_instance_pypi):
|
||||
def test_install_does_not_extrapolate_environ(pipenv_instance_private_pypi):
|
||||
"""Ensure environment variables are not expanded in lock file.
|
||||
"""
|
||||
with temp_environ(), pipenv_instance_pypi() as p:
|
||||
with temp_environ(), pipenv_instance_private_pypi() as p:
|
||||
os.environ["PYPI_URL"] = p.pypi
|
||||
|
||||
with open(p.pipfile_path, "w") as f:
|
||||
@@ -321,7 +322,7 @@ name = 'mockpi'
|
||||
assert p.lockfile["_meta"]["sources"][0]["url"] == "${PYPI_URL}/simple"
|
||||
|
||||
# Ensure package install does not extrapolate.
|
||||
c = p.pipenv("install six")
|
||||
c = p.pipenv("install six -v")
|
||||
assert c.returncode == 0
|
||||
assert p.pipfile["source"][0]["url"] == "${PYPI_URL}/simple"
|
||||
assert p.lockfile["_meta"]["sources"][0]["url"] == "${PYPI_URL}/simple"
|
||||
@@ -528,9 +529,9 @@ def test_install_does_not_exclude_packaging(pipenv_instance_pypi):
|
||||
@pytest.mark.needs_internet
|
||||
def test_install_will_supply_extra_pip_args(pipenv_instance_pypi):
|
||||
with pipenv_instance_pypi() as p:
|
||||
c = p.pipenv("""install dataclasses-json --extra-pip-args="--use-feature=truststore --proxy=test" """)
|
||||
c = p.pipenv("""install -v dataclasses-json --extra-pip-args="--use-feature=truststore --proxy=test" """)
|
||||
assert c.returncode == 1
|
||||
assert "truststore feature" in c.stderr
|
||||
assert "truststore feature" in c.stdout
|
||||
|
||||
|
||||
@pytest.mark.basic
|
||||
|
||||
@@ -84,7 +84,6 @@ def test_multiple_category_install_proceeds_in_order_specified(pipenv_instance_p
|
||||
"""Ensure -e .[extras] installs.
|
||||
"""
|
||||
with pipenv_instance_private_pypi() as p:
|
||||
#os.mkdir(os.path.join(p.path, "testpipenv"))
|
||||
setup_py = os.path.join(p.path, "setup.py")
|
||||
with open(setup_py, "w") as fh:
|
||||
contents = """
|
||||
@@ -106,12 +105,12 @@ setup(
|
||||
with open(os.path.join(p.path, 'Pipfile'), 'w') as fh:
|
||||
fh.write("""
|
||||
[packages]
|
||||
testpipenv = {path = ".", editable = true}
|
||||
testpipenv = {path = ".", editable = true, skip_resolver = true}
|
||||
|
||||
[prereq]
|
||||
six = "*"
|
||||
""".strip())
|
||||
c = p.pipenv("lock")
|
||||
c = p.pipenv("lock -v")
|
||||
assert c.returncode == 0
|
||||
assert "testpipenv" in p.lockfile["default"]
|
||||
assert "testpipenv" not in p.lockfile["prereq"]
|
||||
|
||||
@@ -28,7 +28,11 @@ fake_package = {}
|
||||
|
||||
c = p.pipenv('install -v')
|
||||
assert c.returncode == 0
|
||||
assert 'markers' in p.lockfile['default']['fake-package'], p.lockfile["default"]
|
||||
assert 'markers' in p.lockfile['default']['fake_package'], p.lockfile["default"]
|
||||
assert p.lockfile['default']['fake_package']['markers'] == "os_name == 'splashwear'"
|
||||
assert p.lockfile['default']['fake_package']['hashes'] == [
|
||||
'sha256:1531e01a7f306f496721f425c8404f3cfd8d4933ee6daf4668fcc70059b133f3',
|
||||
'sha256:cf83dc3f6c34050d3360fbdf655b2652c56532e3028b1c95202611ba1ebdd624']
|
||||
|
||||
c = p.pipenv('run python -c "import fake_package;"')
|
||||
assert c.returncode == 1
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import pytest
|
||||
|
||||
from .conftest import DEFAULT_PRIVATE_PYPI_SERVER
|
||||
|
||||
|
||||
@pytest.mark.urls
|
||||
@pytest.mark.extras
|
||||
@pytest.mark.install
|
||||
def test_install_uri_with_extras(pipenv_instance_private_pypi):
|
||||
file_uri = "http://localhost:8080/packages/plette/plette-0.2.2-py2.py3-none-any.whl"
|
||||
with pipenv_instance_private_pypi() as p:
|
||||
def test_install_uri_with_extras(pipenv_instance_pypi):
|
||||
server = DEFAULT_PRIVATE_PYPI_SERVER.replace("/simple", "")
|
||||
file_uri = f"{server}/packages/plette/plette-0.2.2-py2.py3-none-any.whl"
|
||||
with pipenv_instance_pypi() as p:
|
||||
with open(p.pipfile_path, 'w') as f:
|
||||
contents = f"""
|
||||
[[source]]
|
||||
@@ -20,3 +24,4 @@ plette = {{file = "{file_uri}", extras = ["validation"]}}
|
||||
c = p.pipenv("install")
|
||||
assert c.returncode == 0
|
||||
assert "plette" in p.lockfile["default"]
|
||||
assert "cerberus" in p.lockfile["default"]
|
||||
|
||||
@@ -52,7 +52,7 @@ testpipenv = {path = ".", editable = true, extras = ["dev"]}
|
||||
c = p.pipenv(f"install {line}")
|
||||
assert c.returncode == 0
|
||||
assert "testpipenv" in p.pipfile["packages"]
|
||||
assert p.pipfile["packages"]["testpipenv"]["path"] == "."
|
||||
assert p.pipfile["packages"]["testpipenv"]["file"] == "."
|
||||
assert p.pipfile["packages"]["testpipenv"]["extras"] == ["dev"]
|
||||
assert "six" in p.lockfile["default"]
|
||||
|
||||
@@ -178,7 +178,7 @@ def test_local_package(pipenv_instance_private_pypi, testsroot):
|
||||
|
||||
@pytest.mark.files
|
||||
@pytest.mark.local
|
||||
def test_local_zip_file(pipenv_instance_private_pypi, testsroot):
|
||||
def test_local_tar_gz_file(pipenv_instance_private_pypi, testsroot):
|
||||
file_name = "requests-2.19.1.tar.gz"
|
||||
|
||||
with pipenv_instance_private_pypi() as p:
|
||||
@@ -275,3 +275,14 @@ def test_outdated_should_compare_postreleases_without_failing(pipenv_instance_pr
|
||||
c = p.pipenv("update --outdated")
|
||||
assert c.returncode != 0
|
||||
assert "out-of-date" in c.stdout
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info >= (3, 12), reason="Package does not work with Python 3.12")
|
||||
def test_install_remote_wheel_file_with_extras(pipenv_instance_pypi):
|
||||
with pipenv_instance_pypi() as p:
|
||||
c = p.pipenv("install fastapi[dev]@https://files.pythonhosted.org/packages/4e/1a/04887c641b67e6649bde845b9a631f73a7abfbe3afda83957e09b95d88eb/fastapi-0.95.2-py3-none-any.whl")
|
||||
assert c.returncode == 0
|
||||
assert "ruff" in p.lockfile["default"]
|
||||
assert "pre-commit" in p.lockfile["default"]
|
||||
assert "uvicorn" in p.lockfile["default"]
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@@ -17,12 +18,13 @@ def test_basic_vcs_install_with_env_var(pipenv_instance_pypi):
|
||||
# edge case where normal package starts with VCS name shouldn't be flagged as vcs
|
||||
os.environ["GIT_HOST"] = "github.com"
|
||||
cli_runner = CliRunner(mix_stderr=False)
|
||||
c = cli_runner.invoke(cli, "install git+https://${GIT_HOST}/benjaminp/six.git@1.11.0#egg=six gitdb2")
|
||||
c = cli_runner.invoke(cli, "install -v git+https://${GIT_HOST}/benjaminp/six.git@1.11.0 gitdb2")
|
||||
assert c.exit_code == 0
|
||||
assert all(package in p.pipfile["packages"] for package in ["six", "gitdb2"])
|
||||
assert "git" in p.pipfile["packages"]["six"]
|
||||
assert p.lockfile["default"]["six"] == {
|
||||
"git": "https://${GIT_HOST}/benjaminp/six.git",
|
||||
"git": "git+https://${GIT_HOST}/benjaminp/six.git",
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
|
||||
"ref": "15e31431af97e5e64b80af0a3f598d382bcdd49a",
|
||||
}
|
||||
assert "gitdb2" in p.lockfile["default"]
|
||||
@@ -43,7 +45,6 @@ def test_urls_work(pipenv_instance_pypi):
|
||||
dep = list(p.pipfile["packages"].values())[0]
|
||||
assert "file" in dep, p.pipfile
|
||||
|
||||
# now that we handle resolution with requirementslib, this will resolve to a name
|
||||
dep = p.lockfile["default"]["dataclasses-json"]
|
||||
assert "file" in dep, p.lockfile
|
||||
|
||||
@@ -75,10 +76,10 @@ def test_file_urls_work(pipenv_instance_pypi):
|
||||
@pytest.mark.urls
|
||||
@pytest.mark.install
|
||||
@pytest.mark.needs_internet
|
||||
def test_editable_vcs_install(pipenv_instance_pypi):
|
||||
def test_vcs_install(pipenv_instance_pypi):
|
||||
with pipenv_instance_pypi() as p:
|
||||
c = p.pipenv(
|
||||
"install -e git+https://github.com/lidatong/dataclasses-json.git#egg=dataclasses-json"
|
||||
"install git+https://github.com/lidatong/dataclasses-json.git@v0.5.7"
|
||||
)
|
||||
assert c.returncode == 0
|
||||
assert "dataclasses-json" in p.pipfile["packages"]
|
||||
@@ -88,10 +89,10 @@ def test_editable_vcs_install(pipenv_instance_pypi):
|
||||
@pytest.mark.urls
|
||||
@pytest.mark.install
|
||||
@pytest.mark.needs_internet
|
||||
def test_install_editable_git_tag(pipenv_instance_private_pypi):
|
||||
def test_install_git_tag(pipenv_instance_private_pypi):
|
||||
with pipenv_instance_private_pypi() as p:
|
||||
c = p.pipenv(
|
||||
"install -e git+https://github.com/benjaminp/six.git@1.11.0#egg=six"
|
||||
"install git+https://github.com/benjaminp/six.git@1.11.0"
|
||||
)
|
||||
assert c.returncode == 0
|
||||
assert "six" in p.pipfile["packages"]
|
||||
@@ -99,7 +100,7 @@ def test_install_editable_git_tag(pipenv_instance_private_pypi):
|
||||
assert "git" in p.lockfile["default"]["six"]
|
||||
assert (
|
||||
p.lockfile["default"]["six"]["git"]
|
||||
== "https://github.com/benjaminp/six.git"
|
||||
== "git+https://github.com/benjaminp/six.git"
|
||||
)
|
||||
assert "ref" in p.lockfile["default"]["six"]
|
||||
|
||||
@@ -108,6 +109,7 @@ def test_install_editable_git_tag(pipenv_instance_private_pypi):
|
||||
@pytest.mark.index
|
||||
@pytest.mark.install
|
||||
@pytest.mark.needs_internet
|
||||
@pytest.mark.skipif(sys.version_info >= (3, 12), reason="Package does not work with Python 3.12")
|
||||
def test_install_named_index_alias(pipenv_instance_private_pypi):
|
||||
with pipenv_instance_private_pypi() as p:
|
||||
with open(p.pipfile_path, "w") as f:
|
||||
@@ -136,6 +138,7 @@ six = "*"
|
||||
@pytest.mark.index
|
||||
@pytest.mark.install
|
||||
@pytest.mark.needs_internet
|
||||
@pytest.mark.skipif(sys.version_info >= (3, 12), reason="Package does not work with Python 3.12")
|
||||
def test_install_specifying_index_url(pipenv_instance_pypi):
|
||||
with pipenv_instance_pypi() as p:
|
||||
with open(p.pipfile_path, "w") as f:
|
||||
@@ -184,7 +187,7 @@ def test_install_local_vcs_not_in_lockfile(pipenv_instance_pypi):
|
||||
def test_get_vcs_refs(pipenv_instance_private_pypi):
|
||||
with pipenv_instance_private_pypi() as p:
|
||||
c = p.pipenv(
|
||||
"install -e git+https://github.com/benjaminp/six.git@1.9.0#egg=six"
|
||||
"install -e git+https://github.com/benjaminp/six.git@1.9.0"
|
||||
)
|
||||
assert c.returncode == 0
|
||||
assert "six" in p.pipfile["packages"]
|
||||
@@ -236,11 +239,8 @@ Jinja2 = {{ref = "2.11.0", git = "{jinja2_uri}"}}
|
||||
assert all(k in p.pipfile["packages"] for k in installed_packages)
|
||||
assert all(k.lower() in p.lockfile["default"] for k in installed_packages)
|
||||
assert all(k in p.lockfile["default"]["jinja2"] for k in ["ref", "git"]), str(p.lockfile["default"])
|
||||
assert p.lockfile["default"]["jinja2"].get("ref") is not None
|
||||
assert (
|
||||
p.lockfile["default"]["jinja2"]["git"]
|
||||
== jinja2_uri
|
||||
)
|
||||
assert p.lockfile["default"]["jinja2"].get("ref") == "bbdafe33ce9f47e3cbfb9415619e354349f11243"
|
||||
assert p.lockfile["default"]["jinja2"]["git"] == f"{jinja2_uri}"
|
||||
|
||||
|
||||
@pytest.mark.vcs
|
||||
@@ -249,8 +249,8 @@ Jinja2 = {{ref = "2.11.0", git = "{jinja2_uri}"}}
|
||||
@pytest.mark.needs_internet
|
||||
def test_vcs_can_use_markers(pipenv_instance_pypi):
|
||||
with pipenv_instance_pypi() as p:
|
||||
path = p._pipfile.get_fixture_path("git/six/.git")
|
||||
p._pipfile.install("six", {"git": f"{path.as_uri()}", "markers": "sys_platform == 'linux'"})
|
||||
path = p._pipfile.get_fixture_path("git/six/")
|
||||
p._pipfile.install("six", {"git": f"{path.as_uri()}", "ref": "1.11.0", "markers": "sys_platform == 'linux'"})
|
||||
assert "six" in p.pipfile["packages"]
|
||||
c = p.pipenv("install")
|
||||
assert c.returncode == 0
|
||||
|
||||
@@ -157,10 +157,7 @@ def test_resolve_skip_unmatched_requirements(pipenv_instance_pypi):
|
||||
p._pipfile.add("missing-package", {"markers": "os_name=='FakeOS'"})
|
||||
c = p.pipenv("lock")
|
||||
assert c.returncode == 0
|
||||
assert (
|
||||
"Could not find a version of missing-package ; "
|
||||
"os_name == 'FakeOS' that matches your environment"
|
||||
) in c.stderr
|
||||
assert 'Could not find a matching version of missing-package; os_name == "FakeOS"' in c.stderr
|
||||
|
||||
|
||||
@pytest.mark.lock
|
||||
@@ -358,7 +355,7 @@ requests = {git = "%s@883caaf", editable = true}
|
||||
""".strip() % requests_uri)
|
||||
c = p.pipenv('lock')
|
||||
assert c.returncode == 0
|
||||
assert p.lockfile['default']['requests']['git'] == requests_uri
|
||||
assert requests_uri in p.lockfile['default']['requests']['git']
|
||||
assert p.lockfile['default']['requests']['ref'] == '883caaf145fbe93bd0d208a6b864de9146087312'
|
||||
|
||||
|
||||
@@ -509,21 +506,21 @@ def test_lock_nested_vcs_direct_url(pipenv_instance_pypi):
|
||||
assert "git" in p.lockfile["default"]["pep508-package"]
|
||||
assert "sibling-package" in p.lockfile["default"]
|
||||
assert "git" in p.lockfile["default"]["sibling-package"]
|
||||
assert "subdirectory" in p.lockfile["default"]["sibling-package"]
|
||||
assert "subdirectory" in p.lockfile["default"]["sibling-package"]["git"]
|
||||
assert "version" not in p.lockfile["default"]["sibling-package"]
|
||||
|
||||
|
||||
@pytest.mark.lock
|
||||
@pytest.mark.install
|
||||
def test_lock_package_with_wildcard_version(pipenv_instance_pypi):
|
||||
with pipenv_instance_pypi() as p:
|
||||
c = p.pipenv("install 'six==1.11.*'")
|
||||
def test_lock_package_with_compatible_release_specifier(pipenv_instance_private_pypi):
|
||||
with pipenv_instance_private_pypi() as p:
|
||||
c = p.pipenv("install six~=1.11")
|
||||
assert c.returncode == 0
|
||||
assert "six" in p.pipfile["packages"]
|
||||
assert p.pipfile["packages"]["six"] == "==1.11.*"
|
||||
assert p.pipfile["packages"]["six"] == "~=1.11"
|
||||
assert "six" in p.lockfile["default"]
|
||||
assert "version" in p.lockfile["default"]["six"]
|
||||
assert p.lockfile["default"]["six"]["version"] == "==1.11.0"
|
||||
assert p.lockfile["default"]["six"]["version"] == "==1.12.0"
|
||||
|
||||
|
||||
@pytest.mark.lock
|
||||
@@ -649,4 +646,4 @@ dataclasses-json = {extras = ["dev"], version = "==0.5.7"}
|
||||
assert c.returncode == 0
|
||||
assert "dataclasses-json" in p.pipfile["packages"]
|
||||
assert "dataclasses-json" in p.lockfile["default"]
|
||||
assert "markers" not in p.lockfile["default"]["dataclasses-json"]
|
||||
assert p.lockfile["default"]["dataclasses-json"].get("markers", "") is not None
|
||||
|
||||
@@ -6,7 +6,7 @@ import pytest
|
||||
from pipenv.project import Project
|
||||
from pipenv.utils.shell import temp_environ
|
||||
from pipenv.vendor.plette import Pipfile
|
||||
from pipenv.vendor.requirementslib.fileutils import normalize_path
|
||||
from pipenv.utils.fileutils import normalize_path
|
||||
|
||||
|
||||
@pytest.mark.project
|
||||
|
||||
@@ -3,7 +3,8 @@ import os
|
||||
import pytest
|
||||
|
||||
from pipenv.utils.shell import temp_environ
|
||||
from pipenv.routines.requirements import requirements_from_deps
|
||||
from pipenv.utils.requirements import requirements_from_lockfile
|
||||
|
||||
|
||||
@pytest.mark.requirements
|
||||
def test_requirements_generates_requirements_from_lockfile(pipenv_instance_pypi):
|
||||
@@ -289,11 +290,11 @@ def test_requirements_generates_requirements_from_lockfile_without_env_var_expan
|
||||
},
|
||||
True,
|
||||
True,
|
||||
["pyjwt[crypto] @ git+https://github.com/jpadilla/pyjwt.git@7665aa625506a11bae50b56d3e04413a3dc6fdf8"]
|
||||
["pyjwt[crypto]@ git+https://github.com/jpadilla/pyjwt.git@7665aa625506a11bae50b56d3e04413a3dc6fdf8"]
|
||||
)
|
||||
]
|
||||
)
|
||||
def test_requirements_from_deps(deps, include_hashes, include_markers, expected):
|
||||
result = requirements_from_deps(deps, include_hashes, include_markers)
|
||||
result = requirements_from_lockfile(deps, include_hashes, include_markers)
|
||||
assert result == expected
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ multicommand = "bash -c \"cd docs && make html\""
|
||||
c = p.pipenv('run printfoo')
|
||||
assert c.returncode == 0
|
||||
assert c.stdout.strip() == 'foo'
|
||||
assert not c.stderr.strip()
|
||||
|
||||
c = p.pipenv('run notfoundscript')
|
||||
assert c.returncode != 0
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import pytest
|
||||
import sys
|
||||
|
||||
from .conftest import DEFAULT_PRIVATE_PYPI_SERVER
|
||||
|
||||
@@ -7,28 +8,20 @@ from pipenv.utils.shell import temp_environ
|
||||
|
||||
@pytest.mark.uninstall
|
||||
@pytest.mark.install
|
||||
def test_uninstall_requests(pipenv_instance_private_pypi):
|
||||
# Uninstalling requests can fail even when uninstall Django below
|
||||
# succeeds, if requests was de-vendored.
|
||||
# See https://github.com/pypa/pipenv/issues/3644 for problems
|
||||
# caused by devendoring
|
||||
with pipenv_instance_private_pypi() as p:
|
||||
def test_uninstall_requests(pipenv_instance_pypi):
|
||||
with pipenv_instance_pypi() as p:
|
||||
c = p.pipenv("install requests")
|
||||
assert c.returncode == 0
|
||||
assert "requests" in p.pipfile["packages"]
|
||||
|
||||
c = p.pipenv("run python -m requests.help")
|
||||
assert c.returncode == 0
|
||||
|
||||
c = p.pipenv("uninstall requests")
|
||||
assert c.returncode == 0
|
||||
assert "requests" not in p.pipfile["dev-packages"]
|
||||
|
||||
c = p.pipenv("run python -m requests.help")
|
||||
assert c.returncode > 0
|
||||
assert "requests" not in p.pipfile["packages"]
|
||||
assert "requests" not in p.lockfile["default"]
|
||||
|
||||
|
||||
@pytest.mark.uninstall
|
||||
@pytest.mark.skipif(sys.version_info >= (3, 12), reason="Package does not work with Python 3.12")
|
||||
def test_uninstall_django(pipenv_instance_private_pypi):
|
||||
with pipenv_instance_private_pypi() as p:
|
||||
c = p.pipenv("install Django")
|
||||
@@ -52,6 +45,7 @@ def test_uninstall_django(pipenv_instance_private_pypi):
|
||||
|
||||
@pytest.mark.install
|
||||
@pytest.mark.uninstall
|
||||
@pytest.mark.skipif(sys.version_info >= (3, 12), reason="Package does not work with Python 3.12")
|
||||
def test_mirror_uninstall(pipenv_instance_pypi):
|
||||
with temp_environ(), pipenv_instance_pypi() as p:
|
||||
|
||||
@@ -132,8 +126,6 @@ six = "==1.12.0"
|
||||
assert "tablib" in p.lockfile["default"]
|
||||
assert "jinja2" in p.lockfile["develop"]
|
||||
assert "six" in p.lockfile["develop"]
|
||||
|
||||
c = p.pipenv('run python -c "import jinja2"')
|
||||
assert c.returncode == 0
|
||||
|
||||
c = p.pipenv("uninstall --all-dev")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cmd_option", ["", "--dev"])
|
||||
@pytest.mark.basic
|
||||
@pytest.mark.update
|
||||
@@ -7,5 +8,5 @@ def test_update_outdated_with_outdated_package(pipenv_instance_private_pypi, cmd
|
||||
with pipenv_instance_private_pypi() as p:
|
||||
package_name = "six"
|
||||
p.pipenv(f"install {cmd_option} {package_name}==1.11")
|
||||
c = p.pipenv("update --outdated")
|
||||
c = p.pipenv(f"update {package_name} {cmd_option} --outdated")
|
||||
assert f"Package '{package_name}' out-of-date:" in c.stdout
|
||||
|
||||
@@ -48,7 +48,7 @@ def test_local_path_windows(pipenv_instance_pypi):
|
||||
except OSError:
|
||||
whl = whl.absolute()
|
||||
with pipenv_instance_pypi() as p:
|
||||
c = p.pipenv(f'install "{whl}"')
|
||||
c = p.pipenv(f'install "{whl}" -v')
|
||||
assert c.returncode == 0
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ def test_local_path_windows_forward_slash(pipenv_instance_pypi):
|
||||
except OSError:
|
||||
whl = whl.absolute()
|
||||
with pipenv_instance_pypi() as p:
|
||||
c = p.pipenv(f'install "{whl.as_posix()}"')
|
||||
c = p.pipenv(f'install "{whl.as_posix()}" -v')
|
||||
assert c.returncode == 0
|
||||
|
||||
|
||||
|
||||
+1
-1
Submodule tests/pypi updated: f553001342...2a840e0430
+11
-14
@@ -25,11 +25,11 @@ DEP_PIP_PAIRS = [
|
||||
"editable": True,
|
||||
}
|
||||
},
|
||||
"-e git+https://github.com/lidatong/dataclasses-json.git@v0.5.7#egg=dataclasses-json",
|
||||
"dataclasses-json@ git+https://github.com/lidatong/dataclasses-json.git@v0.5.7",
|
||||
),
|
||||
(
|
||||
{"dataclasses-json": {"git": "https://github.com/lidatong/dataclasses-json.git", "ref": "v0.5.7"}},
|
||||
"git+https://github.com/lidatong/dataclasses-json.git@v0.5.7#egg=dataclasses-json",
|
||||
"dataclasses-json@ git+https://github.com/lidatong/dataclasses-json.git@v0.5.7",
|
||||
),
|
||||
(
|
||||
# Extras in url
|
||||
@@ -39,7 +39,7 @@ DEP_PIP_PAIRS = [
|
||||
"extras": ["pipenv"],
|
||||
}
|
||||
},
|
||||
"https://github.com/oz123/dparse/archive/refs/heads/master.zip#egg=dparse[pipenv]"
|
||||
"dparse[pipenv] @ https://github.com/oz123/dparse/archive/refs/heads/master.zip",
|
||||
),
|
||||
(
|
||||
{
|
||||
@@ -50,7 +50,7 @@ DEP_PIP_PAIRS = [
|
||||
"editable": False,
|
||||
}
|
||||
},
|
||||
"git+https://github.com/requests/requests.git@main#egg=requests[security]",
|
||||
"requests[security]@ git+https://github.com/requests/requests.git@main",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -64,8 +64,6 @@ def mock_unpack(link, source_dir, download_dir, only_download=False, session=Non
|
||||
@pytest.mark.parametrize("deps, expected", DEP_PIP_PAIRS)
|
||||
@pytest.mark.needs_internet
|
||||
def test_convert_deps_to_pip(deps, expected):
|
||||
if expected.startswith("Django"):
|
||||
expected = expected.lower()
|
||||
assert dependencies.convert_deps_to_pip(deps) == [expected]
|
||||
|
||||
|
||||
@@ -136,13 +134,12 @@ def test_convert_deps_to_pip_one_way():
|
||||
@pytest.mark.parametrize(
|
||||
"deps, expected",
|
||||
[
|
||||
({"uvicorn": {}}, ["uvicorn"]),
|
||||
({"FooProject": {"path": ".", "editable": "true"}}, []),
|
||||
({"FooProject": {"version": "==1.2"}}, ["fooproject==1.2"]),
|
||||
({"uvicorn": {"extras": ["standard"]}}, []),
|
||||
({"uvicorn": {"extras": []}}, ["uvicorn"]),
|
||||
({"extras": {}}, ["extras"]),
|
||||
({"uvicorn[standard]": {}}, [])
|
||||
({"uvicorn": {}}, {"uvicorn"}),
|
||||
({"FooProject": {"path": ".", "editable": "true"}}, set()),
|
||||
({"FooProject": {"version": "==1.2"}}, {"fooproject==1.2"}),
|
||||
({"uvicorn": {"extras": ["standard"]}}, {"uvicorn"}),
|
||||
({"uvicorn": {"extras": []}}, {"uvicorn"}),
|
||||
({"extras": {}}, {"extras"}),
|
||||
],
|
||||
)
|
||||
def test_get_constraints_from_deps(deps, expected):
|
||||
@@ -209,7 +206,7 @@ class TestUtils:
|
||||
)
|
||||
@pytest.mark.vcs
|
||||
def test_is_vcs(self, entry, expected):
|
||||
from pipenv.vendor.requirementslib.utils import is_vcs
|
||||
from pipenv.utils.requirementslib import is_vcs
|
||||
assert is_vcs(entry) is expected
|
||||
|
||||
@pytest.mark.utils
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import pipenv # noqa
|
||||
|
||||
import datetime
|
||||
import os
|
||||
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
@@ -43,9 +43,3 @@ from pipenv.vendor import tomlkit
|
||||
def test_token_date(dt, content):
|
||||
item = tomlkit.item(dt)
|
||||
assert item.as_string() == content
|
||||
|
||||
|
||||
def test_dump_nonascii_string():
|
||||
content = 'name = "Stažené"\n'
|
||||
toml_content = tomlkit.dumps(tomlkit.loads(content))
|
||||
assert toml_content == content
|
||||
|
||||
Reference in New Issue
Block a user