Requirementslib==2.3.0 (#5672)

* Bump requirementslib that drops vistir.

* Add news fragment

* Add missing file

* Remove vistir entirely

* fix circular import

* revert issue that may be causing circular import issue

* Hackaround current pythonfinder version importlib usage.

* PR Feedback

* fix lint
This commit is contained in:
Matt Davis
2023-04-29 12:22:28 -05:00
committed by GitHub
parent 44f4250906
commit 99800ec7a2
42 changed files with 872 additions and 3166 deletions
+1 -1
View File
@@ -66,7 +66,7 @@ jobs:
with:
python-version: "3.11"
- run: |
python -m pip install --upgrade wheel invoke parver beautifulsoup4 vistir towncrier requests parse hatch-fancy-pypi-readme
python -m pip install --upgrade wheel invoke parver beautifulsoup4 towncrier requests parse hatch-fancy-pypi-readme
python -m invoke vendoring.update
tests:
name: ${{matrix.os}} / ${{ matrix.python-version }}
+1
View File
@@ -0,0 +1 @@
Vendor in ``requirementslib==2.3.0`` which drops usage of ``vistir``.
-35
View File
@@ -4,7 +4,6 @@
# |
import os
import sys
import warnings
from pipenv.__version__ import __version__ # noqa
@@ -13,50 +12,16 @@ from pipenv.patched.pip._vendor.urllib3.exceptions import DependencyWarning
warnings.filterwarnings("ignore", category=DependencyWarning)
warnings.filterwarnings("ignore", category=ResourceWarning)
warnings.filterwarnings("ignore", category=UserWarning)
PIPENV_ROOT = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
PIPENV_VENDOR = os.sep.join([PIPENV_ROOT, "vendor"])
PIPENV_PATCHED = os.sep.join([PIPENV_ROOT, "patched"])
# PIP_VENDOR = os.sep.join([PIPENV_ROOT, "patched", "pip", "_vendor"])
# sys.path.insert(0, PIP_VENDOR)
# Inject vendored directory into system path.
sys.path.insert(0, PIPENV_VENDOR)
# Inject patched directory into system path.
sys.path.insert(0, PIPENV_PATCHED)
# Load patched pip instead of system pip
os.environ["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
# Hack to make things work better.
try:
if "concurrency" in sys.modules:
del sys.modules["concurrency"]
except Exception:
pass
if "urllib3" in sys.modules:
del sys.modules["urllib3"]
if os.name == "nt":
from pipenv.vendor import colorama
# Backward compatability with vistir
# These variables will be removed in vistir 0.8.0
no_color = False
for item in ("ANSI_COLORS_DISABLED", "VISTIR_DISABLE_COLORS"):
if os.getenv(item):
warnings.warn(
(
f"Please do not use {item}, as it will be removed in future versions."
"\nUse NO_COLOR instead."
),
DeprecationWarning,
stacklevel=2,
)
no_color = True
if not os.getenv("NO_COLOR") or no_color:
colorama.just_fix_windows_console()
+9 -157
View File
@@ -5,7 +5,6 @@ import importlib
import importlib.util
import itertools
import json
import operator
import os
import site
import sys
@@ -14,17 +13,18 @@ from pathlib import Path
from sysconfig import get_paths, get_python_version, get_scheme_names
import pipenv
from pipenv import cmdparse
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_uninstall import UninstallPathSet
from pipenv.patched.pip._vendor import pkg_resources
from pipenv.patched.pip._vendor.packaging.utils import canonicalize_name
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, normalize_path
from pipenv.vendor import click, vistir
from pipenv.utils.shell import make_posix
from pipenv.vendor import click
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
@@ -490,7 +490,7 @@ class Environment:
@cached_property
def paths(self) -> Dict[str, str]:
paths = {}
with vistir.contextmanagers.temp_environ(), vistir.contextmanagers.temp_path():
with temp_environ(), temp_path():
os.environ["PYTHONIOENCODING"] = "utf-8"
os.environ["PYTHONDONTWRITEBYTECODE"] = "1"
paths = self.base_paths
@@ -519,7 +519,7 @@ class Environment:
prefixes = [
Path(prefix)
for prefix in self.base_paths["libdirs"].split(os.pathsep)
if vistir.path.is_in_path(prefix, self.prefix.as_posix())
if is_in_path(prefix, self.prefix.as_posix())
]
for loc in prefixes:
if not loc.exists():
@@ -528,8 +528,7 @@ class Environment:
if not pth.suffix == ".egg-link":
continue
contents = [
vistir.path.normalize_path(line.strip())
for line in pth.read_text().splitlines()
normalize_path(line.strip()) for line in pth.read_text().splitlines()
]
pth.write_text("\n".join(contents))
@@ -796,53 +795,6 @@ class Environment:
return True
return False
def run(self, cmd, cwd=os.curdir):
"""Run a command with :class:`~subprocess.Popen` in the context of the environment
:param cmd: A command to run in the environment
:type cmd: str or list
:param str cwd: The working directory in which to execute the command, defaults to :data:`os.curdir`
:return: A finished command object
:rtype: :class:`~subprocess.Popen`
"""
c = None
with self.activated():
script = cmdparse.Script.parse(cmd)
c = vistir.misc.run(
script._parts,
return_object=True,
nospin=True,
cwd=cwd,
write_to_stdout=False,
)
return c
def run_py(self, cmd, cwd=os.curdir):
"""Run a python command in the environment context.
:param cmd: A command to run in the environment - runs with `python -c`
:type cmd: str or list
:param str cwd: The working directory in which to execute the command, defaults to :data:`os.curdir`
:return: A finished command object
:rtype: :class:`~subprocess.Popen`
"""
c = None
if isinstance(cmd, str):
script = cmdparse.Script.parse(f"{self.python} -c {cmd}")
else:
script = cmdparse.Script.parse([self.python, "-c"] + list(cmd))
with self.activated():
c = vistir.misc.run(
script._parts,
return_object=True,
nospin=True,
cwd=cwd,
write_to_stdout=False,
)
return c
def run_activate_this(self):
"""Runs the environment's inline activation script"""
if self.is_venv:
@@ -876,7 +828,7 @@ class Environment:
original_path = sys.path
original_prefix = sys.prefix
prefix = self.prefix.as_posix()
with vistir.contextmanagers.temp_environ(), vistir.contextmanagers.temp_path():
with temp_environ(), temp_path():
os.environ["PATH"] = os.pathsep.join(
[
self.script_basedir,
@@ -902,103 +854,3 @@ class Environment:
finally:
sys.path = original_path
sys.prefix = original_prefix
@cached_property
def finders(self):
from pipenv.vendor.pythonfinder import Finder
finders = [
Finder(path=self.base_paths["scripts"], global_search=gs, system=False)
for gs in (False, True)
]
return finders
@property
def finder(self):
return next(iter(self.finders), None)
def which(self, search, as_path=True):
find = operator.methodcaller("which", search)
result = next(iter(filter(None, (find(finder) for finder in self.finders))), None)
if not result:
result = self._which(search)
else:
if as_path:
result = str(result.path)
return result
def install(self, requirements):
if not isinstance(requirements, (tuple, list)):
requirements = [requirements]
with self.get_finder() as finder:
args = []
for format_control in ("no_binary", "only_binary"):
formats = getattr(finder.format_control, format_control)
args.extend(
(
"--" + format_control.replace("_", "-"),
",".join(sorted(formats or {":none:"})),
)
)
if finder.index_urls:
args.extend(["-i", finder.index_urls[0]])
for extra_index in finder.index_urls[1:]:
args.extend(["--extra-index-url", extra_index])
else:
args.append("--no-index")
for link in finder.find_links:
args.extend(["--find-links", link])
for _, host, _ in finder.secure_origins:
args.extend(["--trusted-host", host])
if finder.allow_all_prereleases:
args.append("--pre")
if finder.process_dependency_links:
args.append("--process-dependency-links")
args.append("--")
args.extend(requirements)
out, _ = vistir.misc.run(
args, return_object=False, nospin=True, block=True, combine_stderr=False
)
@contextlib.contextmanager
def uninstall(self, pkgname, *args, **kwargs):
"""A context manager which allows uninstallation of packages from the environment
:param str pkgname: The name of a package to uninstall
>>> env = Environment("/path/to/env/root")
>>> with env.uninstall("pytz", auto_confirm=True, verbose=False) as uninstaller:
cleaned = uninstaller.paths
>>> if cleaned:
print("uninstalled packages: %s" % cleaned)
"""
auto_confirm = kwargs.pop("auto_confirm", True)
verbose = kwargs.pop("verbose", False)
with self.activated():
monkey_patch = next(
iter(
dist
for dist in self.base_working_set
if dist.project_name == "recursive-monkey-patch"
),
None,
)
if monkey_patch:
monkey_patch.activate()
dist = next(
iter(d for d in self.get_working_set() if d.project_name == pkgname), None
)
path_set = UninstallPathSet.from_dist(dist)
if path_set is not None:
path_set.remove(auto_confirm=auto_confirm, verbose=verbose)
try:
yield path_set
except Exception:
if path_set is not None:
path_set.rollback()
else:
if path_set is not None:
path_set.commit()
if path_set is None:
return
+3 -5
View File
@@ -4,11 +4,9 @@ import pathlib
import re
import sys
from vistir.path import normalize_drive
from pipenv.patched.pip._vendor.platformdirs import user_cache_dir
from pipenv.utils.shell import env_to_bool, is_env_truthy
from pipenv.vendor.vistir.misc import _isatty
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.
@@ -73,7 +71,7 @@ def normalize_pipfile_path(p):
# https://bugs.python.org/issue22490
os.environ.pop("__PYVENV_LAUNCHER__", None)
# Internal, to tell whether the command line session is interactive.
SESSION_IS_INTERACTIVE = _isatty(sys.stdout)
SESSION_IS_INTERACTIVE = isatty(sys.stdout)
# TF_BUILD indicates to Azure pipelines it is a build step
PIPENV_IS_CI = get_from_env("CI", prefix="", check_for_negation=False) or is_env_truthy(
+2 -1
View File
@@ -3,7 +3,6 @@ import sys
from collections import namedtuple
from traceback import format_tb
from pipenv import environments
from pipenv.vendor import click
from pipenv.vendor.click.exceptions import ClickException, FileError, UsageError
@@ -31,6 +30,8 @@ KNOWN_EXCEPTIONS = [
def handle_exception(exc_type, exception, traceback, hook=sys.excepthook):
from pipenv import environments
if environments.Setting().is_verbose() or not issubclass(exc_type, ClickException):
hook(exc_type, exception, traceback)
else:
+3 -6
View File
@@ -13,8 +13,6 @@ import urllib.parse
from json.decoder import JSONDecodeError
from pathlib import Path
import click
from pipenv.cmdparse import Script
from pipenv.environment import Environment
from pipenv.environments import Setting, is_in_virtualenv, normalize_pipfile_path
@@ -30,6 +28,7 @@ from pipenv.utils.dependencies import (
python_version,
)
from pipenv.utils.internet import get_url_name, is_pypi_url, is_valid_url, proper_case
from pipenv.utils.locking import atomic_open_for_write
from pipenv.utils.shell import (
find_requirements,
find_windows_executable,
@@ -41,7 +40,7 @@ from pipenv.utils.shell import (
system_which,
)
from pipenv.utils.toml import cleanup_toml, convert_toml_outline_tables
from pipenv.vendor import plette, toml, tomlkit, vistir
from pipenv.vendor import click, plette, toml, tomlkit
from pipenv.vendor.requirementslib.models.utils import get_default_pyproject_backend
try:
@@ -842,9 +841,7 @@ class Project:
"""Write out the lockfile."""
s = self._lockfile_encoder.encode(content)
open_kwargs = {"newline": self._lockfile_newlines, "encoding": "utf-8"}
with vistir.contextmanagers.atomic_open_for_write(
self.lockfile_location, **open_kwargs
) as f:
with atomic_open_for_write(self.lockfile_location, **open_kwargs) as f:
f.write(s)
# Write newline at end of document. GH-319.
# Only need '\n' here; the file object handles the rest.
+1 -1
View File
@@ -691,8 +691,8 @@ def clean_outdated(results, resolver, project, category):
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
from pipenv.vendor.vistir.contextmanagers import cd, temp_path
parsed_packages = []
for package in packages:
+4 -5
View File
@@ -1,7 +1,8 @@
import shutil
from pipenv import environments
from pipenv.vendor import click, vistir
from pipenv.vendor import click
from pipenv.vendor.requirementslib.models.setup_info import handle_remove_readonly
def do_clear(project):
@@ -9,14 +10,12 @@ def do_clear(project):
click.secho("Clearing caches...", bold=True)
try:
shutil.rmtree(
project.s.PIPENV_CACHE_DIR, onerror=vistir.path.handle_remove_readonly
)
shutil.rmtree(project.s.PIPENV_CACHE_DIR, onerror=handle_remove_readonly)
# Other processes may be writing into this directory simultaneously.
shutil.rmtree(
locations.USER_CACHE_DIR,
ignore_errors=environments.PIPENV_IS_CI,
onerror=vistir.path.handle_remove_readonly,
onerror=handle_remove_readonly,
)
except OSError as e:
# Ignore FileNotFoundError. This is needed for Python 2.7.
+10 -7
View File
@@ -3,6 +3,7 @@ import queue
import sys
import warnings
from collections import defaultdict
from tempfile import NamedTemporaryFile
from pipenv import environments, exceptions
from pipenv.patched.pip._internal.exceptions import PipError
@@ -22,8 +23,10 @@ from pipenv.utils.pipfile import ensure_pipfile
from pipenv.utils.project import ensure_project
from pipenv.utils.requirements import import_requirements
from pipenv.utils.virtualenv import cleanup_virtualenv, do_create_virtualenv
from pipenv.vendor import click, vistir
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)
@@ -49,7 +52,7 @@ def do_install(
extra_pip_args=None,
categories=None,
):
requirements_directory = vistir.path.create_tracked_tempdir(
requirements_directory = fileutils.create_tracked_tempdir(
suffix="-requirements", prefix="pipenv-"
)
warnings.filterwarnings("default", category=ResourceWarning)
@@ -109,7 +112,7 @@ def do_install(
bold=True,
err=True,
)
fd = vistir.path.create_tracked_tempfile(
fd = NamedTemporaryFile(
prefix="pipenv-", suffix="-requirement.txt", dir=requirements_directory
)
temp_reqs = fd.name
@@ -235,7 +238,7 @@ def do_install(
bold=True,
)
# pip install:
with vistir.contextmanagers.temp_environ(), console.status(
with temp_environ(), console.status(
"Installing...", spinner=project.s.PIPENV_SPINNER
) as st:
if not system:
@@ -426,7 +429,7 @@ def do_sync(
)
# Install everything.
requirements_dir = vistir.path.create_tracked_tempdir(
requirements_dir = fileutils.create_tracked_tempdir(
suffix="-requirements", prefix="pipenv-"
)
if system:
@@ -592,7 +595,7 @@ def batch_install_iteration(
elif dep.is_vcs:
is_artifact = True
with vistir.contextmanagers.temp_environ():
with temp_environ():
if not allow_global:
os.environ["PIP_USER"] = "0"
if "PYTHONHOME" in os.environ:
@@ -790,7 +793,7 @@ def do_init(
if not deploy:
ensure_pipfile(project, system=system)
if not requirements_dir:
requirements_dir = vistir.path.create_tracked_tempdir(
requirements_dir = fileutils.create_tracked_tempdir(
suffix="-requirements", prefix="pipenv-"
)
# Write out the lockfile if it doesn't exist, but not if the Pipfile is being ignored
+1 -1
View File
@@ -9,7 +9,7 @@ from pathlib import Path
from shutil import get_terminal_size
from pipenv.vendor import shellingham
from pipenv.vendor.vistir.contextmanagers import temp_environ
from pipenv.vendor.requirementslib.utils import temp_environ
ShellDetectionFailure = shellingham.ShellDetectionFailure
+3 -8
View File
@@ -1,9 +1,11 @@
import os
from contextlib import contextmanager
from tempfile import NamedTemporaryFile
from typing import Mapping, Sequence
from pipenv.patched.pip._vendor.packaging.markers import Marker
from pipenv.patched.pip._vendor.packaging.version import parse
from pipenv.vendor.requirementslib.fileutils import create_tracked_tempdir
from pipenv.vendor.requirementslib.models.requirements import (
InstallRequirement,
Requirement,
@@ -314,15 +316,10 @@ def prepare_constraint_file(
sources=None,
pip_args=None,
):
from pipenv.vendor.vistir.path import (
create_tracked_tempdir,
create_tracked_tempfile,
)
if not directory:
directory = create_tracked_tempdir(suffix="-requirements", prefix="pipenv-")
constraints_file = create_tracked_tempfile(
constraints_file = NamedTemporaryFile(
mode="w",
prefix="pipenv-",
suffix="-constraints.txt",
@@ -367,8 +364,6 @@ def is_editable(pipfile_entry):
@contextmanager
def locked_repository(requirement):
from pipenv.vendor.vistir.path import create_tracked_tempdir
if not requirement.is_vcs:
return
src_dir = create_tracked_tempdir(prefix="pipenv-", suffix="-src")
+86
View File
@@ -1,3 +1,7 @@
import os
import stat
from contextlib import contextmanager
from tempfile import NamedTemporaryFile
from typing import Mapping
from .dependencies import clean_resolved_dep, pep423_name, translate_markers
@@ -84,3 +88,85 @@ def prepare_lockfile(results, pipfile, lockfile):
else:
lockfile[name] = lockfile_entry[name]
return lockfile
@contextmanager
def atomic_open_for_write(target, binary=False, newline=None, encoding=None) -> None:
"""Atomically open `target` for writing.
This is based on Lektor's `atomic_open()` utility, but simplified a lot
to handle only writing, and skip many multi-process/thread edge cases
handled by Werkzeug.
:param str target: Target filename to write
:param bool binary: Whether to open in binary mode, default False
:param Optional[str] newline: The newline character to use when writing, determined
from system if not supplied.
:param Optional[str] encoding: The encoding to use when writing, defaults to system
encoding.
How this works:
* Create a temp file (in the same directory of the actual target), and
yield for surrounding code to write to it.
* If some thing goes wrong, try to remove the temp file. The actual target
is not touched whatsoever.
* If everything goes well, close the temp file, and replace the actual
target with this new file.
.. code:: python
>>> fn = "test_file.txt"
>>> def read_test_file(filename=fn):
with open(filename, 'r') as fh:
print(fh.read().strip())
>>> with open(fn, "w") as fh:
fh.write("this is some test text")
>>> read_test_file()
this is some test text
>>> def raise_exception_while_writing(filename):
with open(filename, "w") as fh:
fh.write("writing some new text")
raise RuntimeError("Uh oh, hope your file didn't get overwritten")
>>> raise_exception_while_writing(fn)
Traceback (most recent call last):
...
RuntimeError: Uh oh, hope your file didn't get overwritten
>>> read_test_file()
writing some new text
>>> def raise_exception_while_writing(filename):
with atomic_open_for_write(filename) as fh:
fh.write("Overwriting all the text from before with even newer text")
raise RuntimeError("But did it get overwritten now?")
>>> raise_exception_while_writing(fn)
Traceback (most recent call last):
...
RuntimeError: But did it get overwritten now?
>>> read_test_file()
writing some new text
"""
mode = "w+b" if binary else "w"
f = NamedTemporaryFile(
dir=os.path.dirname(target),
prefix=".__atomic-write",
mode=mode,
encoding=encoding,
newline=newline,
delete=False,
)
# set permissions to 0644
try:
os.chmod(f.name, stat.S_IWUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
except OSError:
pass
try:
yield f
except BaseException:
f.close()
try:
os.remove(f.name)
except OSError:
pass
raise
else:
f.close()
try:
os.remove(target) # This is needed on Windows.
except OSError:
pass
os.rename(f.name, target) # No os.replace() on Python 2.
+5 -8
View File
@@ -9,9 +9,10 @@ from pipenv.project import Project
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.processes import subprocess_run
from pipenv.utils.shell import cmd_list_to_shell, normalize_path, project_python
from pipenv.vendor import click, vistir
from pipenv.utils.shell import cmd_list_to_shell, project_python
from pipenv.vendor import click
from pipenv.vendor.requirementslib import Requirement
from pipenv.vendor.requirementslib.fileutils import create_tracked_tempdir, normalize_path
def format_pip_output(out, r=None):
@@ -93,9 +94,7 @@ def pip_install_deps(
else:
src_dir = os.getenv("PIP_SRC", os.getenv("PIP_SRC_DIR"))
if not requirements_dir:
requirements_dir = vistir.path.create_tracked_tempdir(
prefix="pipenv", suffix="requirements"
)
requirements_dir = create_tracked_tempdir(prefix="pipenv", suffix="requirements")
standard_requirements = tempfile.NamedTemporaryFile(
prefix="pipenv-", suffix="-hashed-reqs.txt", dir=requirements_dir, delete=False
@@ -411,9 +410,7 @@ def write_requirement_to_file(
include_hashes: bool = True,
) -> str:
if not requirements_dir:
requirements_dir = vistir.path.create_tracked_tempdir(
prefix="pipenv", suffix="requirements"
)
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
)
+1 -2
View File
@@ -30,10 +30,9 @@ from pipenv.patched.pip._vendor import pkg_resources, rich
from pipenv.project import Project
from pipenv.vendor import click
from pipenv.vendor.requirementslib import Requirement
from pipenv.vendor.requirementslib.fileutils import create_tracked_tempdir, open_file
from pipenv.vendor.requirementslib.models.requirements import Line
from pipenv.vendor.requirementslib.models.utils import DIRECT_URL_RE
from pipenv.vendor.vistir.contextmanagers import open_file
from pipenv.vendor.vistir.path import create_tracked_tempdir
try:
# this is only in Python3.8 and later
+9 -32
View File
@@ -11,15 +11,12 @@ from contextlib import contextmanager
from functools import lru_cache
from pathlib import Path
from pipenv.utils.constants import MYPY_RUNNING
from pipenv.vendor import click
from pipenv.vendor.requirementslib.fileutils import normalize_drive, normalize_path
from .constants import FALSE_VALUES, SCHEME_LIST, TRUE_VALUES
from .processes import subprocess_run
if MYPY_RUNNING:
from typing import Text # noqa
@lru_cache()
def make_posix(path: str) -> str:
@@ -84,12 +81,6 @@ def path_to_url(path):
return Path(normalize_drive(os.path.abspath(path))).as_uri()
def normalize_path(path):
return os.path.expandvars(
os.path.expanduser(os.path.normcase(os.path.normpath(os.path.abspath(str(path)))))
)
def get_windows_path(*args):
"""Sanitize a path for windows environments
@@ -307,28 +298,6 @@ def is_python_command(line):
return False
# TODO This code is basically a duplicate of pipenv.vendor.vistir.path.normalize_drive
# Proposal: Try removing this method and replacing usages in separate PR
def normalize_drive(path):
"""Normalize drive in path so they stay consistent.
This currently only affects local drives on Windows, which can be
identified with either upper or lower cased drive names. The case is
always converted to uppercase because it seems to be preferred.
See: <https://github.com/pypa/pipenv/issues/1218>
"""
if os.name != "nt" or not isinstance(path, str):
return path
drive, tail = os.path.splitdrive(path)
# Only match (lower cased) local drives (e.g. 'c:'), not UNC mounts.
if drive.islower() and len(drive) == 2 and drive[1] == ":":
return f"{drive.upper()}{tail}"
return path
@contextmanager
def temp_path():
"""Allow the ability to set os.environ temporarily"""
@@ -466,3 +435,11 @@ def shorten_path(location, bold=False):
if bold:
short[-1] = str(click.style(short[-1], bold=True))
return os.sep.join(short)
def isatty(stream):
try:
is_a_tty = stream.isatty()
except Exception: # pragma: no cover
is_a_tty = False
return is_a_tty
+2 -2
View File
@@ -73,7 +73,7 @@ class Finder(object):
def create_system_path(self):
# type: () -> SystemPath
pyfinder_path = importlib.import_module("pythonfinder.models.path")
pyfinder_path = importlib.import_module("pipenv.vendor.pythonfinder.models.path")
return pyfinder_path.SystemPath.create(
path=self.path_prepend,
system=self.system,
@@ -92,7 +92,7 @@ class Finder(object):
if self._system_path is not None:
self._system_path = self._system_path.clear_caches()
self._system_path = None
pyfinder_path = importlib.import_module("pythonfinder.models.path")
pyfinder_path = importlib.import_module("pipenv.vendor.pythonfinder.models.path")
importlib.reload(pyfinder_path)
self._system_path = self.create_system_path()
+1 -1
View File
@@ -5,7 +5,7 @@ from .models.lockfile import Lockfile
from .models.pipfile import Pipfile
from .models.requirements import Requirement
__version__ = "2.2.5"
__version__ = "2.3.0"
logger = logging.getLogger(__name__)
+317
View File
@@ -0,0 +1,317 @@
"""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 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
_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:
return False
if not isinstance(url, str):
try:
url = url.url
except AttributeError:
raise ValueError("Cannot parse url from unknown type: {!r}".format(url))
return urllib_parse.urlparse(url.lower()).scheme == "file"
def is_valid_url(url: str) -> bool:
"""Checks if a given string is an url."""
pieces = urlparse(url)
return all([pieces.scheme, pieces.netloc])
def url_to_path(url: str) -> str:
"""Convert a valid file url to a local filesystem path.
Follows logic taken from pip's equivalent function
"""
assert is_file_url(url), "Only file: urls can be converted to local paths"
_, netloc, path, _, _ = urllib_parse.urlsplit(url)
# Netlocs are UNC paths
if netloc:
netloc = "\\\\" + netloc
path = urllib_request.url2pathname(netloc + path)
return urllib_parse.unquote(path)
if os.name == "nt":
# from click _winconsole.py
from ctypes import create_unicode_buffer, windll
def get_long_path(short_path: Text) -> Text:
BUFFER_SIZE = 500
buffer = create_unicode_buffer(BUFFER_SIZE)
get_long_path_name = windll.kernel32.GetLongPathNameW
get_long_path_name(short_path, buffer, BUFFER_SIZE)
return buffer.value
def normalize_path(path):
return os.path.expandvars(
os.path.expanduser(os.path.normcase(os.path.normpath(os.path.abspath(str(path)))))
)
def normalize_drive(path):
"""Normalize drive in path so they stay consistent.
This currently only affects local drives on Windows, which can be
identified with either upper or lower cased drive names. The case is
always converted to uppercase because it seems to be preferred.
See: <https://github.com/pypa/pipenv/issues/1218>
"""
if os.name != "nt" or not isinstance(path, str):
return path
drive, tail = os.path.splitdrive(path)
# Only match (lower cased) local drives (e.g. 'c:'), not UNC mounts.
if drive.islower() and len(drive) == 2 and drive[1] == ":":
return f"{drive.upper()}{tail}"
return path
def path_to_url(path):
"""Convert the supplied local path to a file uri.
:param str path: A string pointing to or representing a local path
:return: A `file://` uri for the same location
:rtype: str
>>> path_to_url("/home/user/code/myrepo/myfile.zip")
'file:///home/user/code/myrepo/myfile.zip'
"""
if not path:
return path # type: ignore
normalized_path = Path(normalize_drive(os.path.abspath(path))).as_posix()
if os.name == "nt" and normalized_path[1] == ":":
drive, _, path = normalized_path.partition(":")
# XXX: This enables us to handle half-surrogates that were never
# 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)
# 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
# bytes_path = to_bytes(normalized_path, errors="backslashreplace")
return "file://{}".format(quote(path, errors="backslashreplace"))
@contextmanager
def open_file(
link: Union[_T, str], session: Optional[Session] = None, stream: bool = True
) -> ContextManager[Union[IO[bytes], Urllib3_HTTPResponse, Urllib_HTTPResponse]]:
"""Open local or remote file for reading.
:param pipenv.patched.pip._internal.index.Link link: A link object from resolving dependencies with
pip, or else a URL.
:param Optional[Session] session: A :class:`~requests.Session` instance
:param bool stream: Whether to stream the content if remote, default True
:raises ValueError: If link points to a local directory.
:return: a context manager to the opened file-like object
"""
if not isinstance(link, str):
try:
link = link.url_without_fragment
except AttributeError:
raise ValueError("Cannot parse url from unknown type: {0!r}".format(link))
if not is_valid_url(link) and os.path.exists(link):
link = path_to_url(link)
if is_file_url(link):
# Local URL
local_path = url_to_path(link)
if os.path.isdir(local_path):
raise ValueError("Cannot open directory for read: {}".format(link))
else:
with io.open(local_path, "rb") as local_file:
yield local_file
else:
# Remote URL
headers = {"Accept-Encoding": "identity"}
if not session:
try:
from pipenv.patched.pip._vendor.requests import Session # noqa
except ImportError:
session = None
else:
session = Session()
if session is None:
with closing(urllib_request.urlopen(link)) as f:
yield f
else:
with session.get(link, headers=headers, stream=stream) as resp:
try:
raw = getattr(resp, "raw", None)
result = raw if raw else resp
yield result
finally:
if raw:
conn = raw._connection
if conn is not None:
conn.close()
result.close()
@contextmanager
def temp_path():
# type: () -> Iterator[None]
"""A context manager which allows the ability to set sys.path temporarily.
>>> path_from_virtualenv = load_path("/path/to/venv/bin/python")
>>> print(sys.path)
[
'/home/user/.pyenv/versions/3.7.0/bin',
'/home/user/.pyenv/versions/3.7.0/lib/python37.zip',
'/home/user/.pyenv/versions/3.7.0/lib/python3.7',
'/home/user/.pyenv/versions/3.7.0/lib/python3.7/lib-dynload',
'/home/user/.pyenv/versions/3.7.0/lib/python3.7/site-packages'
]
>>> with temp_path():
sys.path = path_from_virtualenv
# Running in the context of the path above
run(["pip", "install", "stuff"])
>>> print(sys.path)
[
'/home/user/.pyenv/versions/3.7.0/bin',
'/home/user/.pyenv/versions/3.7.0/lib/python37.zip',
'/home/user/.pyenv/versions/3.7.0/lib/python3.7',
'/home/user/.pyenv/versions/3.7.0/lib/python3.7/lib-dynload',
'/home/user/.pyenv/versions/3.7.0/lib/python3.7/site-packages'
]
"""
path = [p for p in sys.path]
try:
yield
finally:
sys.path = [p for p in path]
TRACKED_TEMPORARY_DIRECTORIES = []
def create_tracked_tempdir(*args: Any, **kwargs: Any) -> str:
"""Create a tracked temporary directory.
This uses `TemporaryDirectory`, but does not remove the directory
when the return value goes out of scope, instead registers a handler
to cleanup on program exit. The return value is the path to the
created directory.
"""
tempdir = TemporaryDirectory(*args, **kwargs)
TRACKED_TEMPORARY_DIRECTORIES.append(tempdir)
atexit.register(tempdir.cleanup)
warnings.simplefilter("ignore", ResourceWarning)
return tempdir.name
def check_for_unc_path(path):
# type: (Path) -> bool
"""Checks to see if a pathlib `Path` object is a unc path or not."""
if (
os.name == "nt"
and len(path.drive) > 2
and not path.drive[0].isalpha()
and path.drive[1] != ":"
):
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
+4 -3
View File
@@ -4,13 +4,13 @@ import hashlib
import json
import os
import pipenv.vendor.vistir as vistir
from pipenv.patched.pip._internal.utils.hashes import FAVORITE_HASH
from pipenv.patched.pip._internal.vcs.versioncontrol import VcsSupport
from pipenv.patched.pip._vendor.cachecontrol.cache import DictCache
from pipenv.patched.pip._vendor.packaging.requirements import Requirement
from pipenv.patched.pip._vendor.platformdirs import user_cache_dir
from ..fileutils import open_file
from .utils import as_tuple, get_pinned_version, key_from_req, lookup_table
CACHE_DIR = os.environ.get("PIPENV_CACHE_DIR", user_cache_dir("pipenv"))
@@ -30,7 +30,8 @@ class DependencyCache(object):
("ipython", "2.1.0")
For a requirement with extras, the extras will be comma-separated and appended to the version, inside brackets,
For a requirement with extras, the extras will be comma-separated and appended to the
version, inside brackets,
like so:
("ipython", "2.1.0[nbconvert,notebook]")
@@ -160,7 +161,7 @@ class HashCache(DictCache):
def _get_file_hash(self, location):
h = hashlib.new(FAVORITE_HASH)
with vistir.contextmanagers.open_file(location, self.session) as fp:
with open_file(location, self.session) as fp:
for chunk in iter(lambda: fp.read(8096), b""):
h.update(chunk)
return ":".join([FAVORITE_HASH, h.hexdigest()])
+7 -3
View File
@@ -16,11 +16,15 @@ from pipenv.patched.pip._internal.utils.temp_dir import TempDirectory, global_te
from pipenv.patched.pip._vendor.packaging.markers import Marker
from pipenv.patched.pip._vendor.packaging.utils import canonicalize_name
from pipenv.patched.pip._vendor.packaging.version import parse
from pipenv.vendor.vistir.contextmanagers import temp_environ
from pipenv.vendor.vistir.path import create_tracked_tempdir
from ..environment import MYPY_RUNNING
from ..utils import get_package_finder, get_pip_command, prepare_pip_source_args
from ..fileutils import create_tracked_tempdir
from ..utils import (
get_package_finder,
get_pip_command,
prepare_pip_source_args,
temp_environ,
)
from .cache import CACHE_DIR, DependencyCache
from .setup_info import SetupInfo
from .utils import (
+2 -2
View File
@@ -12,7 +12,6 @@ from typing import Sequence
import pipenv.vendor.attr as attr
import pipenv.patched.pip._vendor.requests as requests
import pipenv.vendor.vistir as vistir
from pipenv.patched.pip._vendor.distlib import wheel
from pipenv.patched.pip._vendor.distlib.metadata import Metadata
from pipenv.patched.pip._vendor.packaging.markers import Marker
@@ -22,6 +21,7 @@ from pipenv.patched.pip._vendor.packaging.tags import Tag
from pipenv.patched.pip._vendor.packaging.version import _BaseVersion, parse
from ..environment import MYPY_RUNNING
from ..fileutils import open_file
from .markers import (
get_contained_extras,
get_contained_pyversions,
@@ -166,7 +166,7 @@ def get_remote_wheel_metadata(whl_file):
# type: (str) -> Optional[Metadata]
parsed_metadata = None
data = io.BytesIO()
with vistir.contextmanagers.open_file(whl_file) as fp:
with open_file(whl_file) as fp:
for chunk in iter(lambda: fp.read(8096), b""):
data.write(chunk)
with zipfile.ZipFile(data, mode="r", compression=zipfile.ZIP_DEFLATED) as zf:
+7 -6
View File
@@ -10,7 +10,6 @@ from urllib import parse as urllib_parse
from urllib.parse import unquote
import pipenv.vendor.attr as attr
from pipenv.patched.pip._vendor.pyparsing.core import cached_property
from pipenv.patched.pip._internal.models.link import Link
from pipenv.patched.pip._internal.models.wheel import Wheel
from pipenv.patched.pip._internal.req.constructors import (
@@ -21,6 +20,7 @@ from pipenv.patched.pip._internal.req.constructors import (
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._internal.utils.urls import path_to_url, url_to_path
from pipenv.patched.pip._vendor.distlib.util import cached_property
from pipenv.patched.pip._vendor.packaging.markers import Marker
from pipenv.patched.pip._vendor.packaging.requirements import Requirement as PackagingRequirement
from pipenv.patched.pip._vendor.packaging.specifiers import (
@@ -31,17 +31,17 @@ from pipenv.patched.pip._vendor.packaging.specifiers import (
)
from pipenv.patched.pip._vendor.packaging.utils import canonicalize_name
from pipenv.patched.pip._vendor.packaging.version import parse
from pipenv.vendor.vistir.contextmanagers import temp_path
from pipenv.vendor.vistir.path import (
from ..environment import MYPY_RUNNING
from ..exceptions import RequirementError
from ..fileutils import (
create_tracked_tempdir,
get_converted_relative_path,
is_file_url,
is_valid_url,
normalize_path,
temp_path,
)
from ..environment import MYPY_RUNNING
from ..exceptions import RequirementError
from ..funktools import dedup
from ..utils import (
VCS_LIST,
@@ -95,6 +95,7 @@ if MYPY_RUNNING:
Dict,
FrozenSet,
Generator,
Iterator,
List,
Optional,
Sequence,
+322 -4
View File
@@ -2,15 +2,22 @@ import ast
import atexit
import configparser
import contextlib
import errno
import locale
import os
import shutil
import stat
import subprocess as sp
import sys
import time
import warnings
from collections.abc import Iterable, Mapping
from contextlib import ExitStack
from functools import lru_cache
from itertools import count
from os import scandir
from pathlib import Path
from typing import Callable, Optional
from urllib.parse import parse_qs, urlparse, urlunparse
from weakref import finalize
@@ -31,11 +38,10 @@ from pipenv.patched.pip._vendor.pkg_resources import (
find_distributions,
)
from pipenv.patched.pip._vendor.platformdirs import user_cache_dir
from pipenv.vendor.vistir.contextmanagers import cd, temp_path
from pipenv.vendor.vistir.path import create_tracked_tempdir, rmtree
from ..environment import MYPY_RUNNING
from ..exceptions import RequirementError
from ..fileutils import cd, create_tracked_tempdir, temp_path
from ..utils import get_pip_command
from .old_pip_utils import _copy_source_tree
from .utils import (
@@ -54,7 +60,6 @@ if MYPY_RUNNING:
Dict,
Generator,
List,
Optional,
Sequence,
Set,
Text,
@@ -126,6 +131,229 @@ class HookCaller(wrappers.Pep517HookCaller):
self.backend_path = backend_path
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 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 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 _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 _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 = sp.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 = sp.run(args, capture_output=True)
return sid.stdout.strip()
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, r"{0}\{1}".format(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 _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
remaining = []
while timeout < 1.0:
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, IOError, PermissionError): # noqa:B014
time.sleep(timeout)
timeout *= 2
remaining.append(path)
else:
return
return remaining
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 = sp.run(
[
icacls_exe,
"''{}''".format(fn),
"/grant",
"{}:WD".format(user_sid),
"/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 make_base_requirements(reqs):
# type: (Sequence[STRING_TYPE]) -> Set[BaseRequirement]
requirements = set()
@@ -141,6 +369,96 @@ def make_base_requirements(reqs):
return requirements
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 ( # noqa:B014
OSError,
IOError,
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, IOError, 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
def rmtree(
directory: str, ignore_errors: bool = False, onerror: Optional[Callable] = None
) -> None:
"""Stand-in for :func:`~shutil.rmtree` with additional error-handling.
This version of `rmtree` handles read-only paths, especially in the case of index
files written by certain source control systems.
:param str directory: The target directory to remove
:param bool ignore_errors: Whether to ignore errors, defaults to False
:param func onerror: An error handling function, defaults to :func:`handle_remove_readonly`
.. note::
Setting `ignore_errors=True` may cause this to silently fail to delete the path
"""
if onerror is None:
onerror = handle_remove_readonly
try:
shutil.rmtree(directory, ignore_errors=ignore_errors, onerror=onerror)
except (IOError, OSError, FileNotFoundError, PermissionError) as exc: # noqa:B014
# Ignore removal failures where the file doesn't exist
if exc.errno != errno.ENOENT:
raise
def suppress_unparsable(func, *args, **kwargs):
try:
return func(*args, **kwargs)
@@ -158,7 +476,7 @@ class SetupReader:
@classmethod
def read_setup_py(cls, file: Path, raising: bool = True) -> "Dict[str, Any]":
with file.open(encoding="utf-8") as f:
with file.open(encoding="utf-8-sig") as f:
content = f.read()
body = ast.parse(content).body
+1 -1
View File
@@ -22,9 +22,9 @@ from pipenv.vendor.tomlkit.container import Container
from pipenv.vendor.tomlkit.items import AoT, Array, Bool, InlineTable, Item, String, Table
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.vistir.path import is_valid_url
from ..environment import MYPY_RUNNING
from ..fileutils import is_valid_url
from ..utils import SCHEME_LIST, VCS_LIST, is_star
if MYPY_RUNNING:
+51 -6
View File
@@ -1,23 +1,54 @@
# 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 urllib.parse import urlparse, urlsplit, urlunparse
import pipenv.vendor.tomlkit as tomlkit
import pipenv.vendor.vistir as vistir
from pipenv.patched.pip._internal.commands.install import InstallCommand
from pipenv.patched.pip._internal.models.target_python import TargetPython
from pipenv.patched.pip._internal.utils.filetypes import is_archive_file
from pipenv.patched.pip._internal.utils.misc import is_installable_dir
from pipenv.patched.pip._vendor.packaging import specifiers
from pipenv.vendor.vistir.path import is_valid_url
from .environment import MYPY_RUNNING
from .fileutils import is_valid_url, normalize_path, url_to_path
if MYPY_RUNNING:
from typing import Dict, List, Optional, Text, Tuple, TypeVar, Union
from typing import Dict, Iterator, List, Optional, Text, Tuple, TypeVar, Union
STRING_TYPE = Union[bytes, str, Text]
S = TypeVar("S", bytes, str, Text)
@@ -145,7 +176,7 @@ def convert_entry_to_path(path):
raise ValueError("missing path-like entry in supplied mapping {0!r}".format(path))
if "file" in path:
path = vistir.path.url_to_path(path["file"])
path = url_to_path(path["file"])
elif "path" in path:
path = path["path"]
@@ -179,8 +210,8 @@ def is_installable_file(path):
or (len(parsed.scheme) == 1 and os.name == "nt")
)
if parsed.scheme and parsed.scheme == "file":
path = os.fsdecode(vistir.path.url_to_path(path))
normalized_path = vistir.path.normalize_path(path)
path = os.fsdecode(url_to_path(path))
normalized_path = normalize_path(path)
if is_local and not os.path.exists(normalized_path):
return False
@@ -673,3 +704,17 @@ def get_pip_command() -> InstallCommand:
name="InstallCommand", summary="requirementslib 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)
+1 -2
View File
@@ -12,9 +12,8 @@ plette[validation]==0.4.4
ptyprocess==0.7.0
python-dotenv==1.0.0
pythonfinder==1.3.2
requirementslib==2.2.5
requirementslib==2.3.0
ruamel.yaml==0.17.21
shellingham==1.5.0.post1
toml==0.10.2
tomlkit==0.11.7
vistir==0.8.0
-13
View File
@@ -1,13 +0,0 @@
Copyright (c) 2018, Dan Ryan <dan@danryan.co>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-3
View File
@@ -1,3 +0,0 @@
# -*- coding=utf-8 -*-
__version__ = "0.8.0"
-485
View File
@@ -1,485 +0,0 @@
# -*- coding: utf-8 -*-
# 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.
# This module is based on the excellent work by Adam Bartoš who
# provided a lot of what went into the implementation here in
# the discussion to issue1602 in the Python bug tracker.
#
# There are some general differences in regards to how this works
# compared to the original patches as we do not need to patch
# the entire interpreter but just work in our little world of
# echo and prmopt.
import ctypes
import io
import os
import sys
import time
import typing
import zlib
from ctypes import (
POINTER,
WINFUNCTYPE,
Structure,
byref,
c_char,
c_char_p,
c_int,
c_ssize_t,
c_ulong,
c_void_p,
create_unicode_buffer,
py_object,
windll,
)
from ctypes.wintypes import HANDLE, LPCWSTR, LPWSTR
from itertools import count
import msvcrt
from .misc import StreamWrapper, run, to_text
try:
from ctypes import pythonapi
PyObject_GetBuffer = pythonapi.PyObject_GetBuffer
PyBuffer_Release = pythonapi.PyBuffer_Release
except ImportError:
pythonapi = None
if typing.TYPE_CHECKING:
from typing import Text
c_ssize_p = POINTER(c_ssize_t)
CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int))(
("CommandLineToArgvW", windll.shell32)
)
kernel32 = windll.kernel32
GetLastError = kernel32.GetLastError
GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32))
GetConsoleCursorInfo = kernel32.GetConsoleCursorInfo
GetStdHandle = kernel32.GetStdHandle
LocalFree = WINFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p)(("LocalFree", windll.kernel32))
ReadConsoleW = kernel32.ReadConsoleW
SetConsoleCursorInfo = kernel32.SetConsoleCursorInfo
WriteConsoleW = kernel32.WriteConsoleW
# XXX: Added for cursor hiding on windows
STDOUT_HANDLE_ID = ctypes.c_ulong(-11)
STDERR_HANDLE_ID = ctypes.c_ulong(-12)
STDIN_HANDLE = GetStdHandle(-10)
STDOUT_HANDLE = GetStdHandle(-11)
STDERR_HANDLE = GetStdHandle(-12)
STREAM_MAP = {0: STDIN_HANDLE, 1: STDOUT_HANDLE, 2: STDERR_HANDLE}
PyBUF_SIMPLE = 0
PyBUF_WRITABLE = 1
ERROR_SUCCESS = 0
ERROR_NOT_ENOUGH_MEMORY = 8
ERROR_OPERATION_ABORTED = 995
STDIN_FILENO = 0
STDOUT_FILENO = 1
STDERR_FILENO = 2
EOF = b"\x1a"
MAX_BYTES_WRITTEN = 32767
class Py_buffer(Structure):
_fields_ = [
("buf", c_void_p),
("obj", py_object),
("len", c_ssize_t),
("itemsize", c_ssize_t),
("readonly", c_int),
("ndim", c_int),
("format", c_char_p),
("shape", c_ssize_p),
("strides", c_ssize_p),
("suboffsets", c_ssize_p),
("internal", c_void_p),
]
# XXX: This was added for the use of cursors
class CONSOLE_CURSOR_INFO(Structure):
_fields_ = [("dwSize", ctypes.c_int), ("bVisible", ctypes.c_int)]
# On PyPy we cannot get buffers so our ability to operate here is
# serverly limited.
if pythonapi is None:
get_buffer = None
else:
def get_buffer(obj, writable=False):
buf = Py_buffer()
flags = PyBUF_WRITABLE if writable else PyBUF_SIMPLE
PyObject_GetBuffer(py_object(obj), byref(buf), flags)
try:
buffer_type = c_char * buf.len
return buffer_type.from_address(buf.buf)
finally:
PyBuffer_Release(byref(buf))
def get_long_path(short_path):
# type: (Text, str) -> Text
BUFFER_SIZE = 500
buffer = create_unicode_buffer(BUFFER_SIZE)
get_long_path_name = windll.kernel32.GetLongPathNameW
get_long_path_name(to_text(short_path), buffer, BUFFER_SIZE)
return buffer.value
class _WindowsConsoleRawIOBase(io.RawIOBase):
def __init__(self, handle):
self.handle = handle
def isatty(self):
io.RawIOBase.isatty(self)
return True
class _WindowsConsoleReader(_WindowsConsoleRawIOBase):
def readable(self):
return True
def readinto(self, b):
bytes_to_be_read = len(b)
if not bytes_to_be_read:
return 0
elif bytes_to_be_read % 2:
raise ValueError(
"cannot read odd number of bytes from " "UTF-16-LE encoded console"
)
buffer = get_buffer(b, writable=True)
code_units_to_be_read = bytes_to_be_read // 2
code_units_read = c_ulong()
rv = ReadConsoleW(
self.handle, buffer, code_units_to_be_read, byref(code_units_read), None
)
if GetLastError() == ERROR_OPERATION_ABORTED:
# wait for KeyboardInterrupt
time.sleep(0.1)
if not rv:
raise OSError("Windows error: %s" % GetLastError())
if buffer[0] == EOF:
return 0
return 2 * code_units_read.value
class _WindowsConsoleWriter(_WindowsConsoleRawIOBase):
def writable(self):
return True
@staticmethod
def _get_error_message(errno):
if errno == ERROR_SUCCESS:
return "ERROR_SUCCESS"
elif errno == ERROR_NOT_ENOUGH_MEMORY:
return "ERROR_NOT_ENOUGH_MEMORY"
return "Windows error %s" % errno
def write(self, b):
bytes_to_be_written = len(b)
buf = get_buffer(b)
code_units_to_be_written = min(bytes_to_be_written, MAX_BYTES_WRITTEN) // 2
code_units_written = c_ulong()
WriteConsoleW(
self.handle, buf, code_units_to_be_written, byref(code_units_written), None
)
bytes_written = 2 * code_units_written.value
if bytes_written == 0 and bytes_to_be_written > 0:
raise OSError(self._get_error_message(GetLastError()))
return bytes_written
class ConsoleStream(object):
def __init__(self, text_stream, byte_stream):
self._text_stream = text_stream
self.buffer = byte_stream
@property
def name(self):
return self.buffer.name
@property
def fileno(self):
return self.buffer.fileno
def write(self, x):
if isinstance(x, str):
return self._text_stream.write(x)
try:
self.flush()
except Exception:
pass
return self.buffer.write(x)
def writelines(self, lines):
for line in lines:
self.write(line)
def __getattr__(self, name):
try:
return getattr(self._text_stream, name)
except io.UnsupportedOperation:
return getattr(self.buffer, name)
def isatty(self):
return self.buffer.isatty()
def __repr__(self):
return "<ConsoleStream name=%r encoding=%r>" % (self.name, self.encoding)
class WindowsChunkedWriter(object):
"""
Wraps a stream (such as stdout), acting as a transparent proxy for all
attribute access apart from method 'write()' which we wrap to write in
limited chunks due to a Windows limitation on binary console streams.
"""
def __init__(self, wrapped):
# double-underscore everything to prevent clashes with names of
# attributes on the wrapped stream object.
self.__wrapped = wrapped
def __getattr__(self, name):
return getattr(self.__wrapped, name)
def write(self, text):
total_to_write = len(text)
written = 0
while written < total_to_write:
to_write = min(total_to_write - written, MAX_BYTES_WRITTEN)
self.__wrapped.write(text[written : written + to_write])
written += to_write
_wrapped_std_streams = set()
def _get_text_stdin(buffer_stream):
text_stream = StreamWrapper(
io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)),
"utf-16-le",
"strict",
line_buffering=True,
)
return ConsoleStream(text_stream, buffer_stream)
def _get_text_stdout(buffer_stream):
text_stream = StreamWrapper(
io.BufferedWriter(_WindowsConsoleWriter(STDOUT_HANDLE)),
"utf-16-le",
"strict",
line_buffering=True,
)
return ConsoleStream(text_stream, buffer_stream)
def _get_text_stderr(buffer_stream):
text_stream = StreamWrapper(
io.BufferedWriter(_WindowsConsoleWriter(STDERR_HANDLE)),
"utf-16-le",
"strict",
line_buffering=True,
)
return ConsoleStream(text_stream, buffer_stream)
_stream_factories = {0: _get_text_stdin, 1: _get_text_stdout, 2: _get_text_stderr}
def _get_windows_console_stream(f, encoding, errors):
if (
get_buffer is not None
and encoding in ("utf-16-le", None)
and errors in ("strict", None)
and hasattr(f, "isatty")
and f.isatty()
):
if isinstance(f, ConsoleStream):
return f
func = _stream_factories.get(f.fileno())
if func is not None:
f = getattr(f, "buffer", None)
if f is None:
return None
else:
# If we are on Python 2 we need to set the stream that we
# deal with to binary mode as otherwise the exercise if a
# bit moot. The same problems apply as for
# get_binary_stdin and friends from _compat.
msvcrt.setmode(f.fileno(), os.O_BINARY)
return func(f)
def hide_cursor():
cursor_info = CONSOLE_CURSOR_INFO()
GetConsoleCursorInfo(STDOUT_HANDLE, ctypes.byref(cursor_info))
cursor_info.visible = False
SetConsoleCursorInfo(STDOUT_HANDLE, ctypes.byref(cursor_info))
def show_cursor():
cursor_info = CONSOLE_CURSOR_INFO()
GetConsoleCursorInfo(STDOUT_HANDLE, ctypes.byref(cursor_info))
cursor_info.visible = True
SetConsoleCursorInfo(STDOUT_HANDLE, ctypes.byref(cursor_info))
def get_stream_handle(stream):
return STREAM_MAP.get(stream.fileno())
def _walk_for_powershell(directory):
for path, 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, _ = run(
["where", "powershell"], block=True, nospin=True, return_object=False
)
if powershell_path:
return powershell_path.strip()
return None
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, _ = run(args, nospin=True)
return sid.strip()
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, r"{0}\{1}".format(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_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_current_user():
fns = (_get_sid_from_registry, _get_sid_with_powershell)
for fn in fns:
result = fn()
if result:
return result
return None
-83
View File
@@ -1,83 +0,0 @@
# -*- coding=utf-8 -*-
from __future__ import absolute_import, unicode_literals
import itertools
import re
import shlex
__all__ = ["ScriptEmptyError", "Script"]
class ScriptEmptyError(ValueError):
pass
def _quote_if_contains(value, pattern):
if next(re.finditer(pattern, value), None):
return '"{0}"'.format(re.sub(r'(\\*)"', r'\1\1\\"', value))
return value
class Script(object):
"""Parse a script line (in Pipfile's [scripts] section).
This always works in POSIX mode, even on Windows.
"""
def __init__(self, command, args=None):
self._parts = [command]
if args:
self._parts.extend(args)
@classmethod
def parse(cls, value):
if isinstance(value, str):
value = shlex.split(value)
if not value:
raise ScriptEmptyError(value)
return cls(value[0], value[1:])
def __repr__(self):
return "Script({0!r})".format(self._parts)
@property
def command(self):
return self._parts[0]
@property
def args(self):
return self._parts[1:]
def extend(self, extra_args):
self._parts.extend(extra_args)
def cmdify(self):
"""Encode into a cmd-executable string.
This re-implements CreateProcess's quoting logic to turn a list of
arguments into one single string for the shell to interpret.
* All double quotes are escaped with a backslash.
* Existing backslashes before a quote are doubled, so they are all
escaped properly.
* Backslashes elsewhere are left as-is; cmd will interpret them
literally.
The result is then quoted into a pair of double quotes to be grouped.
An argument is intentionally not quoted if it does not contain
whitespaces. This is done to be compatible with Windows built-in
commands that don't work well with quotes, e.g. everything with `echo`,
and DOS-style (forward slash) switches.
The intended use of this function is to pre-process an argument list
before passing it into ``subprocess.Popen(..., shell=True)``.
See also: https://docs.python.org/3/library/subprocess.html#converting-argument-sequence
"""
return " ".join(
itertools.chain(
[_quote_if_contains(self.command, r"[\s^()]")],
(_quote_if_contains(arg, r"[\s^]") for arg in self.args),
)
)
-338
View File
@@ -1,338 +0,0 @@
# -*- coding=utf-8 -*-
import io
import os
import stat
import sys
import typing
from contextlib import closing, contextmanager
from pathlib import Path
from tempfile import NamedTemporaryFile
from urllib import request
from .path import is_file_url, is_valid_url, path_to_url, url_to_path
if typing.TYPE_CHECKING:
from typing import (
Any,
Bytes,
Callable,
ContextManager,
Dict,
IO,
Iterator,
Optional,
Union,
Text,
Tuple,
TypeVar,
)
from types import ModuleType
from pipenv.patched.pip._vendor.requests import Session
from http.client import HTTPResponse as Urllib_HTTPResponse
from pipenv.patched.pip._vendor.urllib3.response import HTTPResponse as Urllib3_HTTPResponse
from .spin import VistirSpinner, DummySpinner
TSpinner = Union[VistirSpinner, DummySpinner]
_T = TypeVar("_T")
__all__ = [
"temp_environ",
"temp_path",
"cd",
"atomic_open_for_write",
"open_file",
"replaced_stream",
"replaced_streams",
]
# 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)
@contextmanager
def temp_path():
# type: () -> Iterator[None]
"""A context manager which allows the ability to set sys.path temporarily
>>> path_from_virtualenv = load_path("/path/to/venv/bin/python")
>>> print(sys.path)
[
'/home/user/.pyenv/versions/3.7.0/bin',
'/home/user/.pyenv/versions/3.7.0/lib/python37.zip',
'/home/user/.pyenv/versions/3.7.0/lib/python3.7',
'/home/user/.pyenv/versions/3.7.0/lib/python3.7/lib-dynload',
'/home/user/.pyenv/versions/3.7.0/lib/python3.7/site-packages'
]
>>> with temp_path():
sys.path = path_from_virtualenv
# Running in the context of the path above
run(["pip", "install", "stuff"])
>>> print(sys.path)
[
'/home/user/.pyenv/versions/3.7.0/bin',
'/home/user/.pyenv/versions/3.7.0/lib/python37.zip',
'/home/user/.pyenv/versions/3.7.0/lib/python3.7',
'/home/user/.pyenv/versions/3.7.0/lib/python3.7/lib-dynload',
'/home/user/.pyenv/versions/3.7.0/lib/python3.7/site-packages'
]
"""
path = [p for p in sys.path]
try:
yield
finally:
sys.path = [p for p in path]
@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)
@contextmanager
def atomic_open_for_write(target, binary=False, newline=None, encoding=None):
# type: (str, bool, Optional[str], Optional[str]) -> None
"""Atomically open `target` for writing.
This is based on Lektor's `atomic_open()` utility, but simplified a lot
to handle only writing, and skip many multi-process/thread edge cases
handled by Werkzeug.
:param str target: Target filename to write
:param bool binary: Whether to open in binary mode, default False
:param Optional[str] newline: The newline character to use when writing, determined
from system if not supplied.
:param Optional[str] encoding: The encoding to use when writing, defaults to system
encoding.
How this works:
* Create a temp file (in the same directory of the actual target), and
yield for surrounding code to write to it.
* If some thing goes wrong, try to remove the temp file. The actual target
is not touched whatsoever.
* If everything goes well, close the temp file, and replace the actual
target with this new file.
.. code:: python
>>> fn = "test_file.txt"
>>> def read_test_file(filename=fn):
with open(filename, 'r') as fh:
print(fh.read().strip())
>>> with open(fn, "w") as fh:
fh.write("this is some test text")
>>> read_test_file()
this is some test text
>>> def raise_exception_while_writing(filename):
with open(filename, "w") as fh:
fh.write("writing some new text")
raise RuntimeError("Uh oh, hope your file didn't get overwritten")
>>> raise_exception_while_writing(fn)
Traceback (most recent call last):
...
RuntimeError: Uh oh, hope your file didn't get overwritten
>>> read_test_file()
writing some new text
# Now try with vistir
>>> def raise_exception_while_writing(filename):
with vistir.contextmanagers.atomic_open_for_write(filename) as fh:
fh.write("Overwriting all the text from before with even newer text")
raise RuntimeError("But did it get overwritten now?")
>>> raise_exception_while_writing(fn)
Traceback (most recent call last):
...
RuntimeError: But did it get overwritten now?
>>> read_test_file()
writing some new text
"""
mode = "w+b" if binary else "w"
f = NamedTemporaryFile(
dir=os.path.dirname(target),
prefix=".__atomic-write",
mode=mode,
encoding=encoding,
newline=newline,
delete=False,
)
# set permissions to 0644
try:
os.chmod(f.name, stat.S_IWUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
except OSError:
pass
try:
yield f
except BaseException:
f.close()
try:
os.remove(f.name)
except OSError:
pass
raise
else:
f.close()
try:
os.remove(target) # This is needed on Windows.
except OSError:
pass
os.rename(f.name, target) # No os.replace() on Python 2.
@contextmanager
def open_file(
link, # type: Union[_T, str]
session=None, # type: Optional[Session]
stream=True, # type: bool
):
# type: (...) -> ContextManager[Union[IO[bytes], Urllib3_HTTPResponse, Urllib_HTTPResponse]]
"""
Open local or remote file for reading.
:param pipenv.patched.pip._internal.index.Link link: A link object from resolving dependencies with
pip, or else a URL.
:param Optional[Session] session: A :class:`~requests.Session` instance
:param bool stream: Whether to stream the content if remote, default True
:raises ValueError: If link points to a local directory.
:return: a context manager to the opened file-like object
"""
if not isinstance(link, str):
try:
link = link.url_without_fragment
except AttributeError:
raise ValueError("Cannot parse url from unknown type: {0!r}".format(link))
if not is_valid_url(link) and os.path.exists(link):
link = path_to_url(link)
if is_file_url(link):
# Local URL
local_path = url_to_path(link)
if os.path.isdir(local_path):
raise ValueError("Cannot open directory for read: {}".format(link))
else:
with io.open(local_path, "rb") as local_file:
yield local_file
else:
# Remote URL
headers = {"Accept-Encoding": "identity"}
if not session:
try:
from pipenv.patched.pip._vendor.requests import Session # noqa
except ImportError:
session = None
else:
session = Session()
if session is None:
with closing(request.urlopen(link)) as f:
yield f
else:
with session.get(link, headers=headers, stream=stream) as resp:
try:
raw = getattr(resp, "raw", None)
result = raw if raw else resp
yield result
finally:
if raw:
conn = raw._connection
if conn is not None:
conn.close()
result.close()
@contextmanager
def replaced_stream(stream_name):
# type: (str) -> Iterator[IO[Text]]
"""
Context manager to temporarily swap out *stream_name* with a stream wrapper.
:param str stream_name: The name of a sys stream to wrap
:returns: A ``StreamWrapper`` replacement, temporarily
>>> orig_stdout = sys.stdout
>>> with replaced_stream("stdout") as stdout:
... sys.stdout.write("hello")
... assert stdout.getvalue() == "hello"
>>> sys.stdout.write("hello")
'hello'
"""
orig_stream = getattr(sys, stream_name)
new_stream = io.StringIO()
try:
setattr(sys, stream_name, new_stream)
yield getattr(sys, stream_name)
finally:
setattr(sys, stream_name, orig_stream)
@contextmanager
def replaced_streams():
# type: () -> Iterator[Tuple[IO[Text], IO[Text]]]
"""
Context manager to replace both ``sys.stdout`` and ``sys.stderr`` using
``replaced_stream``
returns: *(stdout, stderr)*
>>> import sys
>>> with vistir.contextmanagers.replaced_streams() as streams:
>>> stdout, stderr = streams
>>> sys.stderr.write("test")
>>> sys.stdout.write("hello")
>>> assert stdout.getvalue() == "hello"
>>> assert stderr.getvalue() == "test"
>>> stdout.getvalue()
'hello'
>>> stderr.getvalue()
'test'
"""
with replaced_stream("stdout") as stdout:
with replaced_stream("stderr") as stderr:
yield (stdout, stderr)
-61
View File
@@ -1,61 +0,0 @@
# -*- coding=utf-8 -*-
from __future__ import absolute_import, print_function
import os
import sys
__all__ = ["hide_cursor", "show_cursor", "get_stream_handle"]
def get_stream_handle(stream=sys.stdout):
"""
Get the OS appropriate handle for the corresponding output stream.
:param str stream: The the stream to get the handle for
:return: A handle to the appropriate stream, either a ctypes buffer
or **sys.stdout** or **sys.stderr**.
"""
handle = stream
if os.name == "nt":
from ._winconsole import get_stream_handle as get_win_stream_handle
return get_win_stream_handle(stream)
return handle
def hide_cursor(stream=sys.stdout):
"""
Hide the console cursor on the given stream
:param stream: The name of the stream to get the handle for
:return: None
:rtype: None
"""
handle = get_stream_handle(stream=stream)
if os.name == "nt":
from ._winconsole import hide_cursor
hide_cursor()
else:
handle.write("\033[?25l")
handle.flush()
def show_cursor(stream=sys.stdout):
"""
Show the console cursor on the given stream
:param stream: The name of the stream to get the handle for
:return: None
:rtype: None
"""
handle = get_stream_handle(stream=stream)
if os.name == "nt":
from ._winconsole import show_cursor
show_cursor()
else:
handle.write("\033[?25h")
handle.flush()
-1119
View File
File diff suppressed because it is too large Load Diff
-618
View File
@@ -1,618 +0,0 @@
# -*- coding=utf-8 -*-
from __future__ import absolute_import, print_function, unicode_literals
import atexit
import errno
import functools
import locale
import os
import posixpath
import shutil
import stat
import sys
import typing
import time
import unicodedata
import warnings
from pathlib import Path
from tempfile import NamedTemporaryFile, TemporaryDirectory
from typing import Optional, Callable
from urllib import parse as urllib_parse
from urllib import request as urllib_request
from urllib.parse import quote
if typing.TYPE_CHECKING:
from types import TracebackType
from typing import (
Any,
AnyStr,
ByteString,
Generator,
Iterator,
List,
Text,
Tuple,
Type,
Union,
)
TPath = os.PathLike
TFunc = Callable[..., Any]
__all__ = [
"check_for_unc_path",
"get_converted_relative_path",
"handle_remove_readonly",
"normalize_path",
"is_in_path",
"is_file_url",
"is_readonly_path",
"is_valid_url",
"mkdir_p",
"ensure_mkdir_p",
"create_tracked_tempdir",
"create_tracked_tempfile",
"path_to_url",
"rmtree",
"safe_expandvars",
"set_write_bit",
"url_to_path",
"walk_up",
]
if os.name == "nt":
warnings.filterwarnings(
"ignore",
category=DeprecationWarning,
message="The Windows bytes API has been deprecated.*",
)
def unicode_path(path):
# type: (TPath) -> Text
# Paths are supposed to be represented as unicode here
return path
def native_path(path):
# type: (TPath) -> str
return str(path)
# once again thank you django...
# https://github.com/django/django/blob/fc6b90b/django/utils/_os.py
if os.name == "nt":
abspathu = os.path.abspath
else:
def abspathu(path):
# type: (TPath) -> Text
"""Version of os.path.abspath that uses the unicode representation of
the current working directory, thus avoiding a UnicodeDecodeError in
join when the cwd has non-ASCII characters."""
if not os.path.isabs(path):
path = os.path.join(os.getcwd(), path)
return os.path.normpath(path)
def normalize_path(path):
# type: (TPath) -> Text
"""Return a case-normalized absolute variable-expanded path.
:param str path: The non-normalized path
:return: A normalized, expanded, case-normalized path
:rtype: str
"""
path = os.path.abspath(os.path.expandvars(os.path.expanduser(str(path))))
if os.name == "nt" and os.path.exists(path):
from ._winconsole import get_long_path
path = get_long_path(path)
return os.path.normpath(os.path.normcase(path))
def is_in_path(path, parent):
# type: (TPath, TPath) -> bool
"""Determine if the provided full path is in the given parent root.
:param str path: The full path to check the location of.
:param str parent: The parent path to check for membership in
:return: Whether the full path is a member of the provided parent.
:rtype: bool
"""
return normalize_path(path).startswith(normalize_path(parent))
def normalize_drive(path):
# type: (TPath) -> Text
"""Normalize drive in path so they stay consistent.
This currently only affects local drives on Windows, which can be
identified with either upper or lower cased drive names. The case is
always converted to uppercase because it seems to be preferred.
"""
from .misc import to_text
if os.name != "nt" or not (
isinstance(path, str) or getattr(path, "__fspath__", None)
):
return path # type: ignore
drive, tail = os.path.splitdrive(path)
# Only match (lower cased) local drives (e.g. 'c:'), not UNC mounts.
if drive.islower() and len(drive) == 2 and drive[1] == ":":
return "{}{}".format(drive.upper(), tail)
return to_text(path, encoding="utf-8")
def path_to_url(path):
# type: (TPath) -> Text
"""Convert the supplied local path to a file uri.
:param str path: A string pointing to or representing a local path
:return: A `file://` uri for the same location
:rtype: str
>>> path_to_url("/home/user/code/myrepo/myfile.zip")
'file:///home/user/code/myrepo/myfile.zip'
"""
from .misc import to_bytes
if not path:
return path # type: ignore
normalized_path = Path(normalize_drive(os.path.abspath(path))).as_posix()
if os.name == "nt" and normalized_path[1] == ":":
drive, _, path = normalized_path.partition(":")
# XXX: This enables us to handle half-surrogates that were never
# 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)
# 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
# bytes_path = to_bytes(normalized_path, errors="backslashreplace")
return "file://{}".format(quote(path, errors="backslashreplace"))
def url_to_path(url):
# type: (str) -> str
"""Convert a valid file url to a local filesystem path.
Follows logic taken from pip's equivalent function
"""
assert is_file_url(url), "Only file: urls can be converted to local paths"
_, netloc, path, _, _ = urllib_parse.urlsplit(url)
# Netlocs are UNC paths
if netloc:
netloc = "\\\\" + netloc
path = urllib_request.url2pathname(netloc + path)
return urllib_parse.unquote(path)
def is_valid_url(url):
# type: (Union[str, bytes]) -> bool
"""Checks if a given string is an url."""
from .misc import to_text
if not url:
return url # type: ignore
pieces = urllib_parse.urlparse(to_text(url))
return all([pieces.scheme, pieces.netloc])
def is_file_url(url):
# type: (Any) -> bool
"""Returns true if the given url is a file url."""
from .misc import to_text
if not url:
return False
if not isinstance(url, str):
try:
url = url.url
except AttributeError:
raise ValueError("Cannot parse url from unknown type: {!r}".format(url))
url = to_text(url, encoding="utf-8")
return urllib_parse.urlparse(url.lower()).scheme == "file"
def is_readonly_path(fn):
# type: (TPath) -> 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 mkdir_p(newdir, mode=0o777):
warnings.warn(
('This function is deprecated and will be removed in version 0.8.'
'Use os.makedirs instead'), DeprecationWarning, stacklevel=2)
# This exists in shutil already
# type: (TPath, int) -> None
"""Recursively creates the target directory and all of its parents if they
do not already exist. Fails silently if they do.
:param str newdir: The directory path to ensure
:raises: OSError if a file is encountered along the way
"""
if os.path.exists(newdir):
if not os.path.isdir(newdir):
raise OSError(
"a file with the same name as the desired dir, '{}', already exists.".format(
newdir
)
)
return None
os.makedirs(newdir, mode)
def ensure_mkdir_p(mode=0o777):
# type: (int) -> Callable[Callable[..., Any], Callable[..., Any]]
"""Decorator to ensure `mkdir_p` is called to the function's return
value."""
warnings.warn('This function is deprecated and will be removed in version 0.8.',
DeprecationWarning, stacklevel=2)
# This exists in shutil already
def decorator(f):
# type: (Callable[..., Any]) -> Callable[..., Any]
@functools.wraps(f)
def decorated(*args, **kwargs):
# type: () -> str
path = f(*args, **kwargs)
mkdir_p(path, mode=mode)
return path
return decorated
return decorator
TRACKED_TEMPORARY_DIRECTORIES = []
def create_tracked_tempdir(*args, **kwargs):
# type: (Any, Any) -> str
"""Create a tracked temporary directory.
This uses `TemporaryDirectory`, but does not remove the directory when
the return value goes out of scope, instead registers a handler to cleanup
on program exit.
The return value is the path to the created directory.
"""
tempdir = TemporaryDirectory(*args, **kwargs)
TRACKED_TEMPORARY_DIRECTORIES.append(tempdir)
atexit.register(tempdir.cleanup)
warnings.simplefilter("ignore", ResourceWarning)
return tempdir.name
def create_tracked_tempfile(*args, **kwargs):
# type: (Any, Any) -> str
"""Create a tracked temporary file.
This uses the `NamedTemporaryFile` construct, but does not remove the file
until the interpreter exits.
The return value is the file object.
"""
return NamedTemporaryFile(*args, **kwargs)
def _find_icacls_exe():
# type: () -> Optional[Text]
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":
from ._winconsole import get_current_user
user_sid = get_current_user()
icacls_exe = _find_icacls_exe() or "icacls"
from .misc import run
if user_sid:
c = run(
[
icacls_exe,
"''{}''".format(fn),
"/grant",
"{}:WD".format(user_sid),
"/T",
"/C",
"/Q",
],
nospin=True,
return_object=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 rmtree(directory: str,
ignore_errors: bool = False,
onerror: Optional[Callable] = None) -> None :
"""Stand-in for :func:`~shutil.rmtree` with additional error-handling.
This version of `rmtree` handles read-only paths, especially in the case of index
files written by certain source control systems.
:param str directory: The target directory to remove
:param bool ignore_errors: Whether to ignore errors, defaults to False
:param func onerror: An error handling function, defaults to :func:`handle_remove_readonly`
.. note::
Setting `ignore_errors=True` may cause this to silently fail to delete the path
"""
if onerror is None:
onerror = handle_remove_readonly
try:
shutil.rmtree(directory, ignore_errors=ignore_errors, onerror=onerror)
except (IOError, OSError, FileNotFoundError, PermissionError) as exc: # noqa:B014
# Ignore removal failures where the file doesn't exist
if exc.errno != errno.ENOENT:
raise
def _wait_for_files(path): # pragma: no cover
# type: (Union[str, TPath]) -> Optional[List[TPath]]
"""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
remaining = []
while timeout < 1.0:
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, IOError, PermissionError): # noqa:B014
time.sleep(timeout)
timeout *= 2
remaining.append(path)
else:
return
return remaining
def handle_remove_readonly(func, path, exc):
# type: (Callable[..., str], TPath, Tuple[Type[OSError], OSError, TracebackType]) -> None
"""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 ( # noqa:B014
OSError,
IOError,
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)
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, IOError, 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)
return
else:
raise exc_exception
def walk_up(bottom):
# type: (Union[TPath, str]) -> Generator[Tuple[str, List[str], List[str]], None, None]
"""Mimic os.walk, but walk 'up' instead of down the directory tree.
From: https://gist.github.com/zdavkeos/1098474
"""
bottom = os.path.realpath(str(bottom))
# Get files in current dir.
try:
names = os.listdir(bottom)
except Exception:
return
dirs, nondirs = [], []
for name in names:
if os.path.isdir(os.path.join(bottom, name)):
dirs.append(name)
else:
nondirs.append(name)
yield bottom, dirs, nondirs
new_path = os.path.realpath(os.path.join(bottom, ".."))
# See if we are at the top.
if new_path == bottom:
return
for x in walk_up(new_path):
yield x
def check_for_unc_path(path):
# type: (Path) -> bool
"""Checks to see if a pathlib `Path` object is a unc path or not."""
if (
os.name == "nt"
and len(path.drive) > 2
and not path.drive[0].isalpha()
and path.drive[1] != ":"
):
return True
else:
return False
def get_converted_relative_path(path, relative_to=None):
# type: (TPath, Optional[TPath]) -> str
"""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')
'.'
"""
from .misc import to_text, to_bytes # noqa
if not relative_to:
relative_to = os.getcwd()
path = to_text(path, encoding="utf-8")
relative_to = to_text(relative_to, encoding="utf-8")
start_path = Path(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(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 = to_text(posixpath.normpath(path.as_posix()))
if not (relpath_s == "." or relpath_s.startswith("./")):
relpath_s = posixpath.join(".", relpath_s)
return relpath_s
def safe_expandvars(value):
# type: (TPath) -> str
"""Call os.path.expandvars if value is a string, otherwise do nothing."""
if isinstance(value, str):
return os.path.expandvars(value)
return value # type: ignore
-123
View File
@@ -1,123 +0,0 @@
# -*- coding=utf-8 -*-
import os
import re
import pipenv.vendor.colorama as colorama
from .misc import to_text as to_native_string
DISABLE_COLORS = os.getenv("CI", False) or os.getenv(
"ANSI_COLORS_DISABLED", os.getenv("VISTIR_DISABLE_COLORS", False)
)
ATTRIBUTE_NAMES = ["bold", "dark", "", "underline", "blink", "", "reverse", "concealed"]
ATTRIBUTES = dict(zip(ATTRIBUTE_NAMES, range(1, 9)))
del ATTRIBUTES[""]
colors = ["grey", "red", "green", "yellow", "blue", "magenta", "cyan", "white"]
COLORS = dict(zip(colors, range(30, 38)))
HIGHLIGHTS = dict(zip(["on_{0}".format(c) for c in colors], range(40, 48)))
ANSI_REMOVAL_RE = re.compile(r"\033\[((?:\d|;)*)([a-zA-Z])")
COLOR_MAP = {
# name: type
"blink": "attrs",
"bold": "attrs",
"concealed": "attrs",
"dark": "attrs",
"reverse": "attrs",
"underline": "attrs",
"blue": "color",
"cyan": "color",
"green": "color",
"magenta": "color",
"red": "color",
"white": "color",
"yellow": "color",
"on_blue": "on_color",
"on_cyan": "on_color",
"on_green": "on_color",
"on_grey": "on_color",
"on_magenta": "on_color",
"on_red": "on_color",
"on_white": "on_color",
"on_yellow": "on_color",
}
COLOR_ATTRS = COLOR_MAP.keys()
RESET = colorama.Style.RESET_ALL
def colored(text, color=None, on_color=None, attrs=None):
"""Colorize text using a reimplementation of the colorizer from
https://github.com/pavdmyt/yaspin so that it works on windows.
Available text colors:
red, green, yellow, blue, magenta, cyan, white.
Available text highlights:
on_red, on_green, on_yellow, on_blue, on_magenta, on_cyan, on_white.
Available attributes:
bold, dark, underline, blink, reverse, concealed.
Example:
colored('Hello, World!', 'red', 'on_grey', ['blue', 'blink'])
colored('Hello, World!', 'green')
"""
return colorize(text, fg=color, bg=on_color, attrs=attrs)
def colorize(text, fg=None, bg=None, attrs=None):
if os.getenv("ANSI_COLORS_DISABLED") is None:
style = "NORMAL"
if attrs is not None and not isinstance(attrs, list):
_attrs = []
if isinstance(attrs, str):
_attrs.append(attrs)
else:
_attrs = list(attrs)
attrs = _attrs
if attrs and "bold" in attrs:
style = "BRIGHT"
attrs.remove("bold")
if fg is not None:
fg = fg.upper()
text = to_native_string("%s%s%s%s%s") % (
to_native_string(getattr(colorama.Fore, fg)),
to_native_string(getattr(colorama.Style, style)),
to_native_string(text),
to_native_string(colorama.Fore.RESET),
to_native_string(colorama.Style.NORMAL),
)
if bg is not None:
bg = bg.upper()
text = to_native_string("%s%s%s%s") % (
to_native_string(getattr(colorama.Back, bg)),
to_native_string(text),
to_native_string(colorama.Back.RESET),
to_native_string(colorama.Style.NORMAL),
)
if attrs is not None:
fmt_str = to_native_string("%s[%%dm%%s%s[9m") % (chr(27), chr(27))
for attr in attrs:
text = fmt_str % (ATTRIBUTES[attr], text)
text += RESET
else:
text = ANSI_REMOVAL_RE.sub("", text)
return text
def cprint(text, color=None, on_color=None, attrs=None, **kwargs):
"""Print colorize text.
It accepts arguments of print function.
"""
print((colored(text, color, on_color, attrs)), **kwargs)
+1 -1
View File
@@ -9,7 +9,7 @@ import invoke
from parver import Version
from pipenv.__version__ import __version__
from pipenv.vendor.vistir.contextmanagers import temp_environ
from pipenv.vendor.requirementslib.utils import temp_environ
from .vendoring import _get_git_root, drop_dir
+2 -7
View File
@@ -15,6 +15,8 @@ import invoke
import requests
from urllib3.util import parse_url as urllib3_parse
from pipenv.vendor.requirementslib.fileutils import open_file
TASK_NAME = "update"
LIBRARY_DIRNAMES = {
@@ -328,11 +330,6 @@ def post_install_cleanup(ctx, vendor_dir):
drop_dir(vendor_dir / "colorama" / "tests")
remove_all(vendor_dir.glob("toml.py"))
# this function is called twice hence try ... except ...
try:
(vendor_dir / "vistir" / "spin.py").unlink()
except FileNotFoundError:
pass
@invoke.task
@@ -770,8 +767,6 @@ def main(ctx, package=None, type=None):
@invoke.task
def vendor_artifact(ctx, package, version=None):
from pipenv.vendor.vistir.contextmanagers import open_file
simple = requests.get(f"https://pypi.org/simple/{package}/")
pkg_str = f"{package}-{version}"
soup = bs4.BeautifulSoup(simple.content)
+9 -10
View File
@@ -21,10 +21,9 @@ from pipenv.cli import cli
from pipenv.exceptions import VirtualenvActivationException
from pipenv.utils.processes import subprocess_run
from pipenv.vendor import toml, tomlkit
from pipenv.vendor.vistir.contextmanagers import temp_environ
from pipenv.vendor.vistir.path import (
create_tracked_tempdir, handle_remove_readonly
)
from pipenv.vendor.requirementslib.fileutils import create_tracked_tempdir
from pipenv.vendor.requirementslib.utils import temp_environ
from pipenv.vendor.requirementslib.models.setup_info import handle_remove_readonly
log = logging.getLogger(__name__)
warnings.simplefilter("default", category=ResourceWarning)
@@ -137,7 +136,7 @@ def _create_tracked_dir():
@pytest.fixture
def vistir_tmpdir():
def tracked_tmpdir():
temp_path = _create_tracked_dir()
yield Path(temp_path)
@@ -159,7 +158,7 @@ def local_tempdir(request):
@pytest.fixture(name='create_tmpdir')
def vistir_tmpdir_factory():
def tmpdir_factory():
def create_tmpdir():
return Path(_create_tracked_dir())
@@ -417,9 +416,9 @@ def _rmtree_func(path, ignore_errors=True, onerror=None):
@pytest.fixture()
def pip_src_dir(request, vistir_tmpdir):
def pip_src_dir(request, tracked_tmpdir):
old_src_dir = os.environ.get('PIP_SRC', '')
os.environ['PIP_SRC'] = vistir_tmpdir.as_posix()
os.environ['PIP_SRC'] = tracked_tmpdir.as_posix()
def finalize():
os.environ['PIP_SRC'] = old_src_dir
@@ -509,8 +508,8 @@ class VirtualEnv:
@pytest.fixture()
def virtualenv(vistir_tmpdir):
with VirtualEnv(base_dir=vistir_tmpdir) as venv:
def virtualenv(tracked_tmpdir):
with VirtualEnv(base_dir=tracked_tmpdir) as venv:
yield venv
+1 -2
View File
@@ -5,7 +5,6 @@ from pathlib import Path
import pytest
from flaky import flaky
from pipenv.vendor.vistir.misc import to_text
from pipenv.utils.shell import temp_environ
@@ -193,7 +192,7 @@ def test_keep_outdated_keeps_markers_not_removed(pipenv_instance_pypi):
lockfile_json = json.loads(lockfile_content)
assert "six" in lockfile_json["default"]
lockfile_json["default"]["six"]["markers"] = "python_version >= '2.7'"
lockfile.write_text(to_text(json.dumps(lockfile_json)))
lockfile.write_text(json.dumps(lockfile_json))
c = p.pipenv("lock --keep-outdated")
assert c.returncode == 0
assert p.lockfile["default"]["six"].get("markers", "") == "python_version >= '2.7'"
+2 -1
View File
@@ -7,8 +7,9 @@ import pytest
from pipenv.project import Project
from pipenv.utils.shell import temp_environ
from pipenv.vendor.vistir.path import is_in_path, normalize_path
from pipenv.vendor.plette import Pipfile
from pipenv.vendor.requirementslib.fileutils import normalize_path
from pipenv.vendor.pythonfinder.utils import is_in_path
@pytest.mark.project
+3 -3
View File
@@ -23,7 +23,7 @@ def test_suppress_nested_venv_warning(capsys, project):
def test_load_dot_env_from_environment_variable_location(monkeypatch, capsys, project):
with temp_environ(), monkeypatch.context() as m, TemporaryDirectory(prefix='pipenv-', suffix='') as tempdir:
if os.name == "nt":
import click
from pipenv.vendor import click
is_console = False
m.setattr(click._winconsole, "_is_console", lambda x: is_console)
dotenv_path = os.path.join(tempdir, 'test.env')
@@ -40,7 +40,7 @@ def test_load_dot_env_from_environment_variable_location(monkeypatch, capsys, pr
def test_doesnt_load_dot_env_if_disabled(monkeypatch, capsys, project):
with temp_environ(), monkeypatch.context() as m, TemporaryDirectory(prefix='pipenv-', suffix='') as tempdir:
if os.name == "nt":
import click
from pipenv.vendor import click
is_console = False
m.setattr(click._winconsole, "_is_console", lambda x: is_console)
dotenv_path = os.path.join(tempdir, 'test.env')
@@ -61,7 +61,7 @@ def test_doesnt_load_dot_env_if_disabled(monkeypatch, capsys, project):
def test_load_dot_env_warns_if_file_doesnt_exist(monkeypatch, capsys, project):
with temp_environ(), monkeypatch.context() as m, TemporaryDirectory(prefix='pipenv-', suffix='') as tempdir:
if os.name == "nt":
import click
from pipenv.vendor import click
is_console = False
m.setattr(click._winconsole, "_is_console", lambda x: is_console)
dotenv_path = os.path.join(tempdir, 'does-not-exist.env')