mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 22:50:18 +00:00
Vendor in pip 22.1.2
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
from typing import List, Optional
|
||||
|
||||
__version__ = "22.0.4"
|
||||
__version__ = "22.1.2"
|
||||
|
||||
|
||||
def main(args: Optional[List[str]] = None) -> int:
|
||||
|
||||
@@ -11,7 +11,7 @@ import zipfile
|
||||
from collections import OrderedDict
|
||||
from sysconfig import get_paths
|
||||
from types import TracebackType
|
||||
from typing import TYPE_CHECKING, Iterable, Iterator, List, Optional, Set, Tuple, Type
|
||||
from typing import TYPE_CHECKING, Generator, Iterable, List, Optional, Set, Tuple, Type
|
||||
|
||||
from pipenv.patched.notpip._vendor.certifi import where
|
||||
from pipenv.patched.notpip._vendor.packaging.requirements import Requirement
|
||||
@@ -20,7 +20,7 @@ from pipenv.patched.notpip._vendor.packaging.version import Version
|
||||
from pip import __file__ as pip_location
|
||||
from pipenv.patched.notpip._internal.cli.spinners import open_spinner
|
||||
from pipenv.patched.notpip._internal.locations import get_platlib, get_prefixed_libs, get_purelib
|
||||
from pipenv.patched.notpip._internal.metadata import get_environment
|
||||
from pipenv.patched.notpip._internal.metadata import get_default_environment, get_environment
|
||||
from pipenv.patched.notpip._internal.utils.subprocess import call_subprocess
|
||||
from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
|
||||
|
||||
@@ -42,7 +42,7 @@ class _Prefix:
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _create_standalone_pip() -> Iterator[str]:
|
||||
def _create_standalone_pip() -> Generator[str, None, None]:
|
||||
"""Create a "standalone pip" zip file.
|
||||
|
||||
The zip file's content is identical to the currently-running pip.
|
||||
@@ -168,9 +168,17 @@ class BuildEnvironment:
|
||||
missing = set()
|
||||
conflicting = set()
|
||||
if reqs:
|
||||
env = get_environment(self._lib_dirs)
|
||||
env = (
|
||||
get_environment(self._lib_dirs)
|
||||
if hasattr(self, "_lib_dirs")
|
||||
else get_default_environment()
|
||||
)
|
||||
for req_str in reqs:
|
||||
req = Requirement(req_str)
|
||||
# We're explicitly evaluating with an empty extra value, since build
|
||||
# environments are not provided any mechanism to select specific extras.
|
||||
if req.marker is not None and not req.marker.evaluate({"extra": ""}):
|
||||
continue
|
||||
dist = env.get_distribution(req.name)
|
||||
if not dist:
|
||||
missing.add(req_str)
|
||||
@@ -179,7 +187,7 @@ class BuildEnvironment:
|
||||
installed_req_str = f"{req.name}=={dist.version}"
|
||||
else:
|
||||
installed_req_str = f"{req.name}==={dist.version}"
|
||||
if dist.version not in req.specifier:
|
||||
if not req.specifier.contains(dist.version, prereleases=True):
|
||||
conflicting.add((installed_req_str, req_str))
|
||||
# FIXME: Consider direct URL?
|
||||
return conflicting, missing
|
||||
|
||||
@@ -168,7 +168,7 @@ class Command(CommandContextMixIn):
|
||||
assert isinstance(status, int)
|
||||
return status
|
||||
except DiagnosticPipError as exc:
|
||||
logger.error("[present-diagnostic] %s", exc)
|
||||
logger.error("[present-rich] %s", exc)
|
||||
logger.debug("Exception information:", exc_info=True)
|
||||
|
||||
return ERROR
|
||||
|
||||
@@ -10,6 +10,7 @@ pass on state. To be consistent, all options will follow this design.
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
|
||||
import importlib.util
|
||||
import logging
|
||||
import os
|
||||
import textwrap
|
||||
@@ -21,7 +22,6 @@ from typing import Any, Callable, Dict, Optional, Tuple
|
||||
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
from pipenv.patched.notpip._internal.cli.parser import ConfigOptionParser
|
||||
from pipenv.patched.notpip._internal.cli.progress_bars import BAR_TYPES
|
||||
from pipenv.patched.notpip._internal.exceptions import CommandError
|
||||
from pipenv.patched.notpip._internal.locations import USER_CACHE_DIR, get_src_prefix
|
||||
from pipenv.patched.notpip._internal.models.format_control import FormatControl
|
||||
@@ -236,13 +236,9 @@ progress_bar: Callable[..., Option] = partial(
|
||||
"--progress-bar",
|
||||
dest="progress_bar",
|
||||
type="choice",
|
||||
choices=list(BAR_TYPES.keys()),
|
||||
choices=["on", "off"],
|
||||
default="on",
|
||||
help=(
|
||||
"Specify type of progress to be displayed ["
|
||||
+ "|".join(BAR_TYPES.keys())
|
||||
+ "] (default: %default)"
|
||||
),
|
||||
help="Specify whether the progress bar should be used [on, off] (default: on)",
|
||||
)
|
||||
|
||||
log: Callable[..., Option] = partial(
|
||||
@@ -272,7 +268,7 @@ proxy: Callable[..., Option] = partial(
|
||||
dest="proxy",
|
||||
type="str",
|
||||
default="",
|
||||
help="Specify a proxy in the form [user:passwd@]proxy.server:port.",
|
||||
help="Specify a proxy in the form scheme://[user:passwd@]proxy.server:port.",
|
||||
)
|
||||
|
||||
retries: Callable[..., Option] = partial(
|
||||
@@ -753,6 +749,15 @@ no_build_isolation: Callable[..., Option] = partial(
|
||||
"if this option is used.",
|
||||
)
|
||||
|
||||
check_build_deps: Callable[..., Option] = partial(
|
||||
Option,
|
||||
"--check-build-dependencies",
|
||||
dest="check_build_deps",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Check the build dependencies when PEP517 is used.",
|
||||
)
|
||||
|
||||
|
||||
def _handle_no_use_pep517(
|
||||
option: Option, opt: str, value: str, parser: OptionParser
|
||||
@@ -775,6 +780,12 @@ def _handle_no_use_pep517(
|
||||
"""
|
||||
raise_option_error(parser, option=option, msg=msg)
|
||||
|
||||
# If user doesn't wish to use pep517, we check if setuptools is installed
|
||||
# and raise error if it is not.
|
||||
if not importlib.util.find_spec("setuptools"):
|
||||
msg = "It is not possible to use --no-use-pep517 without setuptools installed."
|
||||
raise_option_error(parser, option=option, msg=msg)
|
||||
|
||||
# Otherwise, --no-use-pep517 was passed via the command-line.
|
||||
parser.values.use_pep517 = False
|
||||
|
||||
@@ -799,6 +810,33 @@ no_use_pep517: Any = partial(
|
||||
help=SUPPRESS_HELP,
|
||||
)
|
||||
|
||||
|
||||
def _handle_config_settings(
|
||||
option: Option, opt_str: str, value: str, parser: OptionParser
|
||||
) -> None:
|
||||
key, sep, val = value.partition("=")
|
||||
if sep != "=":
|
||||
parser.error(f"Arguments to {opt_str} must be of the form KEY=VAL") # noqa
|
||||
dest = getattr(parser.values, option.dest)
|
||||
if dest is None:
|
||||
dest = {}
|
||||
setattr(parser.values, option.dest, dest)
|
||||
dest[key] = val
|
||||
|
||||
|
||||
config_settings: Callable[..., Option] = partial(
|
||||
Option,
|
||||
"--config-settings",
|
||||
dest="config_settings",
|
||||
type=str,
|
||||
action="callback",
|
||||
callback=_handle_config_settings,
|
||||
metavar="settings",
|
||||
help="Configuration settings to be passed to the PEP 517 build backend. "
|
||||
"Settings take the form KEY=VALUE. Use multiple --config-settings options "
|
||||
"to pass multiple keys to the backend.",
|
||||
)
|
||||
|
||||
install_options: Callable[..., Option] = partial(
|
||||
Option,
|
||||
"--install-option",
|
||||
@@ -858,6 +896,15 @@ disable_pip_version_check: Callable[..., Option] = partial(
|
||||
"of pip is available for download. Implied with --no-index.",
|
||||
)
|
||||
|
||||
root_user_action: Callable[..., Option] = partial(
|
||||
Option,
|
||||
"--root-user-action",
|
||||
dest="root_user_action",
|
||||
default="warn",
|
||||
choices=["warn", "ignore"],
|
||||
help="Action if pip is run as a root user. By default, a warning message is shown.",
|
||||
)
|
||||
|
||||
|
||||
def _handle_merge_hash(
|
||||
option: Option, opt_str: str, value: str, parser: OptionParser
|
||||
@@ -953,7 +1000,7 @@ use_new_feature: Callable[..., Option] = partial(
|
||||
metavar="feature",
|
||||
action="append",
|
||||
default=[],
|
||||
choices=["2020-resolver", "fast-deps", "in-tree-build"],
|
||||
choices=["2020-resolver", "fast-deps"],
|
||||
help="Enable new functionality, that may be backward incompatible.",
|
||||
)
|
||||
|
||||
@@ -966,7 +1013,6 @@ use_deprecated_feature: Callable[..., Option] = partial(
|
||||
default=[],
|
||||
choices=[
|
||||
"legacy-resolver",
|
||||
"out-of-tree-build",
|
||||
"backtrack-on-build-failures",
|
||||
"html5lib",
|
||||
],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from contextlib import ExitStack, contextmanager
|
||||
from typing import ContextManager, Iterator, TypeVar
|
||||
from typing import ContextManager, Generator, TypeVar
|
||||
|
||||
_T = TypeVar("_T", covariant=True)
|
||||
|
||||
@@ -11,7 +11,7 @@ class CommandContextMixIn:
|
||||
self._main_context = ExitStack()
|
||||
|
||||
@contextmanager
|
||||
def main_context(self) -> Iterator[None]:
|
||||
def main_context(self) -> Generator[None, None, None]:
|
||||
assert not self._in_main_context
|
||||
|
||||
self._in_main_context = True
|
||||
|
||||
@@ -6,7 +6,7 @@ import shutil
|
||||
import sys
|
||||
import textwrap
|
||||
from contextlib import suppress
|
||||
from typing import Any, Dict, Iterator, List, Tuple
|
||||
from typing import Any, Dict, Generator, List, Tuple
|
||||
|
||||
from pipenv.patched.notpip._internal.cli.status_codes import UNKNOWN_ERROR
|
||||
from pipenv.patched.notpip._internal.configuration import Configuration, ConfigurationError
|
||||
@@ -175,7 +175,9 @@ class ConfigOptionParser(CustomOptionParser):
|
||||
print(f"An error occurred during configuration: {exc}")
|
||||
sys.exit(3)
|
||||
|
||||
def _get_ordered_configuration_items(self) -> Iterator[Tuple[str, Any]]:
|
||||
def _get_ordered_configuration_items(
|
||||
self,
|
||||
) -> Generator[Tuple[str, Any], None, None]:
|
||||
# Configuration gives keys in an unordered manner. Order them.
|
||||
override_order = ["global", self.name, ":env:"]
|
||||
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import functools
|
||||
import itertools
|
||||
import sys
|
||||
from signal import SIGINT, default_int_handler, signal
|
||||
from typing import Any, Callable, Iterator, Optional, Tuple
|
||||
from typing import Callable, Generator, Iterable, Iterator, Optional, Tuple
|
||||
|
||||
from pipenv.patched.notpip._vendor.progress.bar import Bar, FillingCirclesBar, IncrementalBar
|
||||
from pipenv.patched.notpip._vendor.progress.spinner import Spinner
|
||||
from pipenv.patched.notpip._vendor.rich.progress import (
|
||||
BarColumn,
|
||||
DownloadColumn,
|
||||
@@ -19,263 +14,17 @@ from pipenv.patched.notpip._vendor.rich.progress import (
|
||||
TransferSpeedColumn,
|
||||
)
|
||||
|
||||
from pipenv.patched.notpip._internal.utils.compat import WINDOWS
|
||||
from pipenv.patched.notpip._internal.utils.logging import get_indentation
|
||||
from pipenv.patched.notpip._internal.utils.misc import format_size
|
||||
|
||||
try:
|
||||
from pipenv.patched.notpip._vendor import colorama
|
||||
# Lots of different errors can come from this, including SystemError and
|
||||
# ImportError.
|
||||
except Exception:
|
||||
colorama = None
|
||||
|
||||
DownloadProgressRenderer = Callable[[Iterator[bytes]], Iterator[bytes]]
|
||||
DownloadProgressRenderer = Callable[[Iterable[bytes]], Iterator[bytes]]
|
||||
|
||||
|
||||
def _select_progress_class(preferred: Bar, fallback: Bar) -> Bar:
|
||||
encoding = getattr(preferred.file, "encoding", None)
|
||||
|
||||
# If we don't know what encoding this file is in, then we'll just assume
|
||||
# that it doesn't support unicode and use the ASCII bar.
|
||||
if not encoding:
|
||||
return fallback
|
||||
|
||||
# Collect all of the possible characters we want to use with the preferred
|
||||
# bar.
|
||||
characters = [
|
||||
getattr(preferred, "empty_fill", ""),
|
||||
getattr(preferred, "fill", ""),
|
||||
]
|
||||
characters += list(getattr(preferred, "phases", []))
|
||||
|
||||
# Try to decode the characters we're using for the bar using the encoding
|
||||
# of the given file, if this works then we'll assume that we can use the
|
||||
# fancier bar and if not we'll fall back to the plaintext bar.
|
||||
try:
|
||||
"".join(characters).encode(encoding)
|
||||
except UnicodeEncodeError:
|
||||
return fallback
|
||||
else:
|
||||
return preferred
|
||||
|
||||
|
||||
_BaseBar: Any = _select_progress_class(IncrementalBar, Bar)
|
||||
|
||||
|
||||
class InterruptibleMixin:
|
||||
"""
|
||||
Helper to ensure that self.finish() gets called on keyboard interrupt.
|
||||
|
||||
This allows downloads to be interrupted without leaving temporary state
|
||||
(like hidden cursors) behind.
|
||||
|
||||
This class is similar to the progress library's existing SigIntMixin
|
||||
helper, but as of version 1.2, that helper has the following problems:
|
||||
|
||||
1. It calls sys.exit().
|
||||
2. It discards the existing SIGINT handler completely.
|
||||
3. It leaves its own handler in place even after an uninterrupted finish,
|
||||
which will have unexpected delayed effects if the user triggers an
|
||||
unrelated keyboard interrupt some time after a progress-displaying
|
||||
download has already completed, for example.
|
||||
"""
|
||||
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
"""
|
||||
Save the original SIGINT handler for later.
|
||||
"""
|
||||
# https://github.com/python/mypy/issues/5887
|
||||
super().__init__(*args, **kwargs) # type: ignore
|
||||
|
||||
self.original_handler = signal(SIGINT, self.handle_sigint)
|
||||
|
||||
# If signal() returns None, the previous handler was not installed from
|
||||
# Python, and we cannot restore it. This probably should not happen,
|
||||
# but if it does, we must restore something sensible instead, at least.
|
||||
# The least bad option should be Python's default SIGINT handler, which
|
||||
# just raises KeyboardInterrupt.
|
||||
if self.original_handler is None:
|
||||
self.original_handler = default_int_handler
|
||||
|
||||
def finish(self) -> None:
|
||||
"""
|
||||
Restore the original SIGINT handler after finishing.
|
||||
|
||||
This should happen regardless of whether the progress display finishes
|
||||
normally, or gets interrupted.
|
||||
"""
|
||||
super().finish() # type: ignore
|
||||
signal(SIGINT, self.original_handler)
|
||||
|
||||
def handle_sigint(self, signum, frame): # type: ignore
|
||||
"""
|
||||
Call self.finish() before delegating to the original SIGINT handler.
|
||||
|
||||
This handler should only be in place while the progress display is
|
||||
active.
|
||||
"""
|
||||
self.finish()
|
||||
self.original_handler(signum, frame)
|
||||
|
||||
|
||||
class SilentBar(Bar):
|
||||
def update(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class BlueEmojiBar(IncrementalBar):
|
||||
|
||||
suffix = "%(percent)d%%"
|
||||
bar_prefix = " "
|
||||
bar_suffix = " "
|
||||
phases = ("\U0001F539", "\U0001F537", "\U0001F535")
|
||||
|
||||
|
||||
class DownloadProgressMixin:
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
# https://github.com/python/mypy/issues/5887
|
||||
super().__init__(*args, **kwargs) # type: ignore
|
||||
self.message: str = (" " * (get_indentation() + 2)) + self.message
|
||||
|
||||
@property
|
||||
def downloaded(self) -> str:
|
||||
return format_size(self.index) # type: ignore
|
||||
|
||||
@property
|
||||
def download_speed(self) -> str:
|
||||
# Avoid zero division errors...
|
||||
if self.avg == 0.0: # type: ignore
|
||||
return "..."
|
||||
return format_size(1 / self.avg) + "/s" # type: ignore
|
||||
|
||||
@property
|
||||
def pretty_eta(self) -> str:
|
||||
if self.eta: # type: ignore
|
||||
return f"eta {self.eta_td}" # type: ignore
|
||||
return ""
|
||||
|
||||
def iter(self, it): # type: ignore
|
||||
for x in it:
|
||||
yield x
|
||||
# B305 is incorrectly raised here
|
||||
# https://github.com/PyCQA/flake8-bugbear/issues/59
|
||||
self.next(len(x)) # noqa: B305
|
||||
self.finish()
|
||||
|
||||
|
||||
class WindowsMixin:
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
# The Windows terminal does not support the hide/show cursor ANSI codes
|
||||
# even with colorama. So we'll ensure that hide_cursor is False on
|
||||
# Windows.
|
||||
# This call needs to go before the super() call, so that hide_cursor
|
||||
# is set in time. The base progress bar class writes the "hide cursor"
|
||||
# code to the terminal in its init, so if we don't set this soon
|
||||
# enough, we get a "hide" with no corresponding "show"...
|
||||
if WINDOWS and self.hide_cursor: # type: ignore
|
||||
self.hide_cursor = False
|
||||
|
||||
# https://github.com/python/mypy/issues/5887
|
||||
super().__init__(*args, **kwargs) # type: ignore
|
||||
|
||||
# Check if we are running on Windows and we have the colorama module,
|
||||
# if we do then wrap our file with it.
|
||||
if WINDOWS and colorama:
|
||||
self.file = colorama.AnsiToWin32(self.file) # type: ignore
|
||||
# The progress code expects to be able to call self.file.isatty()
|
||||
# but the colorama.AnsiToWin32() object doesn't have that, so we'll
|
||||
# add it.
|
||||
self.file.isatty = lambda: self.file.wrapped.isatty()
|
||||
# The progress code expects to be able to call self.file.flush()
|
||||
# but the colorama.AnsiToWin32() object doesn't have that, so we'll
|
||||
# add it.
|
||||
self.file.flush = lambda: self.file.wrapped.flush()
|
||||
|
||||
|
||||
class BaseDownloadProgressBar(WindowsMixin, InterruptibleMixin, DownloadProgressMixin):
|
||||
|
||||
file = sys.stdout
|
||||
message = "%(percent)d%%"
|
||||
suffix = "%(downloaded)s %(download_speed)s %(pretty_eta)s"
|
||||
|
||||
|
||||
class DefaultDownloadProgressBar(BaseDownloadProgressBar, _BaseBar):
|
||||
pass
|
||||
|
||||
|
||||
class DownloadSilentBar(BaseDownloadProgressBar, SilentBar):
|
||||
pass
|
||||
|
||||
|
||||
class DownloadBar(BaseDownloadProgressBar, Bar):
|
||||
pass
|
||||
|
||||
|
||||
class DownloadFillingCirclesBar(BaseDownloadProgressBar, FillingCirclesBar):
|
||||
pass
|
||||
|
||||
|
||||
class DownloadBlueEmojiProgressBar(BaseDownloadProgressBar, BlueEmojiBar):
|
||||
pass
|
||||
|
||||
|
||||
class DownloadProgressSpinner(
|
||||
WindowsMixin, InterruptibleMixin, DownloadProgressMixin, Spinner
|
||||
):
|
||||
|
||||
file = sys.stdout
|
||||
suffix = "%(downloaded)s %(download_speed)s"
|
||||
|
||||
def next_phase(self) -> str:
|
||||
if not hasattr(self, "_phaser"):
|
||||
self._phaser = itertools.cycle(self.phases)
|
||||
return next(self._phaser)
|
||||
|
||||
def update(self) -> None:
|
||||
message = self.message % self
|
||||
phase = self.next_phase()
|
||||
suffix = self.suffix % self
|
||||
line = "".join(
|
||||
[
|
||||
message,
|
||||
" " if message else "",
|
||||
phase,
|
||||
" " if suffix else "",
|
||||
suffix,
|
||||
]
|
||||
)
|
||||
|
||||
self.writeln(line)
|
||||
|
||||
|
||||
BAR_TYPES = {
|
||||
"off": (DownloadSilentBar, DownloadSilentBar),
|
||||
"on": (DefaultDownloadProgressBar, DownloadProgressSpinner),
|
||||
"ascii": (DownloadBar, DownloadProgressSpinner),
|
||||
"pretty": (DownloadFillingCirclesBar, DownloadProgressSpinner),
|
||||
"emoji": (DownloadBlueEmojiProgressBar, DownloadProgressSpinner),
|
||||
}
|
||||
|
||||
|
||||
def _legacy_progress_bar(
|
||||
progress_bar: str, max: Optional[int]
|
||||
) -> DownloadProgressRenderer:
|
||||
if max is None or max == 0:
|
||||
return BAR_TYPES[progress_bar][1]().iter # type: ignore
|
||||
else:
|
||||
return BAR_TYPES[progress_bar][0](max=max).iter
|
||||
|
||||
|
||||
#
|
||||
# Modern replacement, for our legacy progress bars.
|
||||
#
|
||||
def _rich_progress_bar(
|
||||
iterable: Iterator[bytes],
|
||||
iterable: Iterable[bytes],
|
||||
*,
|
||||
bar_type: str,
|
||||
size: int,
|
||||
) -> Iterator[bytes]:
|
||||
) -> Generator[bytes, None, None]:
|
||||
assert bar_type == "on", "This should only be used in the default mode."
|
||||
|
||||
if not size:
|
||||
@@ -315,7 +64,5 @@ def get_download_progress_renderer(
|
||||
"""
|
||||
if bar_type == "on":
|
||||
return functools.partial(_rich_progress_bar, bar_type=bar_type, size=size)
|
||||
elif bar_type == "off":
|
||||
return iter # no-op, when passed an iterator
|
||||
else:
|
||||
return _legacy_progress_bar(bar_type, size)
|
||||
return iter # no-op, when passed an iterator
|
||||
|
||||
@@ -22,6 +22,7 @@ from pipenv.patched.notpip._internal.index.package_finder import PackageFinder
|
||||
from pipenv.patched.notpip._internal.models.selection_prefs import SelectionPreferences
|
||||
from pipenv.patched.notpip._internal.models.target_python import TargetPython
|
||||
from pipenv.patched.notpip._internal.network.session import PipSession
|
||||
from pipenv.patched.notpip._internal.operations.build.build_tracker import BuildTracker
|
||||
from pipenv.patched.notpip._internal.operations.prepare import RequirementPreparer
|
||||
from pipenv.patched.notpip._internal.req.constructors import (
|
||||
install_req_from_editable,
|
||||
@@ -31,7 +32,6 @@ from pipenv.patched.notpip._internal.req.constructors import (
|
||||
)
|
||||
from pipenv.patched.notpip._internal.req.req_file import parse_requirements
|
||||
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
|
||||
from pipenv.patched.notpip._internal.req.req_tracker import RequirementTracker
|
||||
from pipenv.patched.notpip._internal.resolution.base import BaseResolver
|
||||
from pipenv.patched.notpip._internal.self_outdated_check import pip_self_version_check
|
||||
from pipenv.patched.notpip._internal.utils.deprecation import deprecated
|
||||
@@ -63,6 +63,9 @@ class SessionCommandMixin(CommandContextMixIn):
|
||||
url = getattr(options, "index_url", None)
|
||||
if url:
|
||||
index_urls.append(url)
|
||||
urls = getattr(options, "extra_index_urls", None)
|
||||
if urls:
|
||||
index_urls.extend(urls)
|
||||
# Return None rather than an empty list
|
||||
return index_urls or None
|
||||
|
||||
@@ -254,7 +257,7 @@ class RequirementCommand(IndexGroupCommand):
|
||||
cls,
|
||||
temp_build_dir: TempDirectory,
|
||||
options: Values,
|
||||
req_tracker: RequirementTracker,
|
||||
build_tracker: BuildTracker,
|
||||
session: PipSession,
|
||||
finder: PackageFinder,
|
||||
use_user_site: bool,
|
||||
@@ -285,33 +288,13 @@ class RequirementCommand(IndexGroupCommand):
|
||||
"fast-deps has no effect when used with the legacy resolver."
|
||||
)
|
||||
|
||||
in_tree_build = "out-of-tree-build" not in options.deprecated_features_enabled
|
||||
if "in-tree-build" in options.features_enabled:
|
||||
deprecated(
|
||||
reason="In-tree builds are now the default.",
|
||||
replacement="to remove the --use-feature=in-tree-build flag",
|
||||
gone_in="22.1",
|
||||
)
|
||||
if "out-of-tree-build" in options.deprecated_features_enabled:
|
||||
deprecated(
|
||||
reason="Out-of-tree builds are deprecated.",
|
||||
replacement=None,
|
||||
gone_in="22.1",
|
||||
)
|
||||
|
||||
if options.progress_bar not in {"on", "off"}:
|
||||
deprecated(
|
||||
reason="Custom progress bar styles are deprecated",
|
||||
replacement="to use the default progress bar style.",
|
||||
gone_in="22.1",
|
||||
)
|
||||
|
||||
return RequirementPreparer(
|
||||
build_dir=temp_build_dir_path,
|
||||
src_dir=options.src_dir,
|
||||
download_dir=download_dir,
|
||||
build_isolation=options.build_isolation,
|
||||
req_tracker=req_tracker,
|
||||
check_build_deps=options.check_build_deps,
|
||||
build_tracker=build_tracker,
|
||||
session=session,
|
||||
progress_bar=options.progress_bar,
|
||||
finder=finder,
|
||||
@@ -319,7 +302,6 @@ class RequirementCommand(IndexGroupCommand):
|
||||
use_user_site=use_user_site,
|
||||
lazy_wheel=lazy_wheel,
|
||||
verbosity=verbosity,
|
||||
in_tree_build=in_tree_build,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -344,6 +326,7 @@ class RequirementCommand(IndexGroupCommand):
|
||||
install_req_from_req_string,
|
||||
isolated=options.isolated_mode,
|
||||
use_pep517=use_pep517,
|
||||
config_settings=getattr(options, "config_settings", None),
|
||||
)
|
||||
suppress_build_failures = cls.determine_build_failure_suppression(options)
|
||||
resolver_variant = cls.determine_resolver_variant(options)
|
||||
@@ -416,6 +399,7 @@ class RequirementCommand(IndexGroupCommand):
|
||||
isolated=options.isolated_mode,
|
||||
use_pep517=options.use_pep517,
|
||||
user_supplied=True,
|
||||
config_settings=getattr(options, "config_settings", None),
|
||||
)
|
||||
requirements.append(req_to_add)
|
||||
|
||||
@@ -425,6 +409,7 @@ class RequirementCommand(IndexGroupCommand):
|
||||
user_supplied=True,
|
||||
isolated=options.isolated_mode,
|
||||
use_pep517=options.use_pep517,
|
||||
config_settings=getattr(options, "config_settings", None),
|
||||
)
|
||||
requirements.append(req_to_add)
|
||||
|
||||
|
||||
@@ -3,9 +3,7 @@ import itertools
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
from typing import IO, Iterator
|
||||
|
||||
from pipenv.patched.notpip._vendor.progress import HIDE_CURSOR, SHOW_CURSOR
|
||||
from typing import IO, Generator
|
||||
|
||||
from pipenv.patched.notpip._internal.utils.compat import WINDOWS
|
||||
from pipenv.patched.notpip._internal.utils.logging import get_indentation
|
||||
@@ -115,7 +113,7 @@ class RateLimiter:
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def open_spinner(message: str) -> Iterator[SpinnerInterface]:
|
||||
def open_spinner(message: str) -> Generator[SpinnerInterface, None, None]:
|
||||
# Interactive spinner goes directly to sys.stdout rather than being routed
|
||||
# through the logging system, but it acts like it has level INFO,
|
||||
# i.e. it's only displayed if we're at level INFO or better.
|
||||
@@ -138,8 +136,12 @@ def open_spinner(message: str) -> Iterator[SpinnerInterface]:
|
||||
spinner.finish("done")
|
||||
|
||||
|
||||
HIDE_CURSOR = "\x1b[?25l"
|
||||
SHOW_CURSOR = "\x1b[?25h"
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def hidden_cursor(file: IO[str]) -> Iterator[None]:
|
||||
def hidden_cursor(file: IO[str]) -> Generator[None, None, None]:
|
||||
# The Windows terminal does not support the hide/show cursor ANSI codes,
|
||||
# even via colorama. So don't even try.
|
||||
if WINDOWS:
|
||||
|
||||
@@ -43,6 +43,28 @@ COMPLETION_SCRIPTS = {
|
||||
end
|
||||
complete -fa "(__fish_complete_pip)" -c {prog}
|
||||
""",
|
||||
"powershell": """
|
||||
if ((Test-Path Function:\\TabExpansion) -and -not `
|
||||
(Test-Path Function:\\_pip_completeBackup)) {{
|
||||
Rename-Item Function:\\TabExpansion _pip_completeBackup
|
||||
}}
|
||||
function TabExpansion($line, $lastWord) {{
|
||||
$lastBlock = [regex]::Split($line, '[|;]')[-1].TrimStart()
|
||||
if ($lastBlock.StartsWith("{prog} ")) {{
|
||||
$Env:COMP_WORDS=$lastBlock
|
||||
$Env:COMP_CWORD=$lastBlock.Split().Length - 1
|
||||
$Env:PIP_AUTO_COMPLETE=1
|
||||
(& {prog}).Split()
|
||||
Remove-Item Env:COMP_WORDS
|
||||
Remove-Item Env:COMP_CWORD
|
||||
Remove-Item Env:PIP_AUTO_COMPLETE
|
||||
}}
|
||||
elseif (Test-Path Function:\\_pip_completeBackup) {{
|
||||
# Fall back on existing tab expansion
|
||||
_pip_completeBackup $line $lastWord
|
||||
}}
|
||||
}}
|
||||
""",
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +98,14 @@ class CompletionCommand(Command):
|
||||
dest="shell",
|
||||
help="Emit completion code for fish",
|
||||
)
|
||||
self.cmd_opts.add_option(
|
||||
"--powershell",
|
||||
"-p",
|
||||
action="store_const",
|
||||
const="powershell",
|
||||
dest="shell",
|
||||
help="Emit completion code for powershell",
|
||||
)
|
||||
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
|
||||
@@ -27,11 +27,17 @@ class ConfigurationCommand(Command):
|
||||
|
||||
- list: List the active configuration (or from the file specified)
|
||||
- edit: Edit the configuration file in an editor
|
||||
- get: Get the value associated with name
|
||||
- set: Set the name=value
|
||||
- unset: Unset the value associated with name
|
||||
- get: Get the value associated with command.option
|
||||
- set: Set the command.option=value
|
||||
- unset: Unset the value associated with command.option
|
||||
- debug: List the configuration files and values defined under them
|
||||
|
||||
Configuration keys should be dot separated command and option name,
|
||||
with the special prefix "global" affecting any command. For example,
|
||||
"pip config set global.index-url https://example.org/" would configure
|
||||
the index url for all commands, but "pip config set download.timeout 10"
|
||||
would configure a 10 second timeout only for "pip download" commands.
|
||||
|
||||
If none of --user, --global and --site are passed, a virtual
|
||||
environment configuration file is used if one is active and the file
|
||||
exists. Otherwise, all modifications happen to the user file by
|
||||
@@ -43,9 +49,9 @@ class ConfigurationCommand(Command):
|
||||
%prog [<file-option>] list
|
||||
%prog [<file-option>] [--editor <editor-path>] edit
|
||||
|
||||
%prog [<file-option>] get name
|
||||
%prog [<file-option>] set name value
|
||||
%prog [<file-option>] unset name
|
||||
%prog [<file-option>] get command.option
|
||||
%prog [<file-option>] set command.option value
|
||||
%prog [<file-option>] unset command.option
|
||||
%prog [<file-option>] debug
|
||||
"""
|
||||
|
||||
@@ -225,6 +231,10 @@ class ConfigurationCommand(Command):
|
||||
|
||||
try:
|
||||
subprocess.check_call([editor, fname])
|
||||
except FileNotFoundError as e:
|
||||
if not e.filename:
|
||||
e.filename = editor
|
||||
raise
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise PipError(
|
||||
"Editor Subprocess exited with exit code {}".format(e.returncode)
|
||||
|
||||
@@ -47,7 +47,7 @@ def create_vendor_txt_map() -> Dict[str, str]:
|
||||
]
|
||||
|
||||
# Transform into "module" -> version dict.
|
||||
return dict(line.split("==", 1) for line in lines) # type: ignore
|
||||
return dict(line.split("==", 1) for line in lines)
|
||||
|
||||
|
||||
def get_module_from_module_name(module_name: str) -> ModuleType:
|
||||
@@ -67,6 +67,7 @@ def get_vendor_version_from_module(module_name: str) -> Optional[str]:
|
||||
|
||||
if not version:
|
||||
# Try to find version in debundled module info.
|
||||
assert module.__file__ is not None
|
||||
env = get_environment([os.path.dirname(module.__file__)])
|
||||
dist = env.get_distribution(module_name)
|
||||
if dist:
|
||||
|
||||
@@ -7,7 +7,7 @@ from pipenv.patched.notpip._internal.cli import cmdoptions
|
||||
from pipenv.patched.notpip._internal.cli.cmdoptions import make_target_python
|
||||
from pipenv.patched.notpip._internal.cli.req_command import RequirementCommand, with_cleanup
|
||||
from pipenv.patched.notpip._internal.cli.status_codes import SUCCESS
|
||||
from pipenv.patched.notpip._internal.req.req_tracker import get_requirement_tracker
|
||||
from pipenv.patched.notpip._internal.operations.build.build_tracker import get_build_tracker
|
||||
from pipenv.patched.notpip._internal.utils.misc import ensure_dir, normalize_path, write_output
|
||||
from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory
|
||||
|
||||
@@ -49,6 +49,7 @@ class DownloadCommand(RequirementCommand):
|
||||
self.cmd_opts.add_option(cmdoptions.no_build_isolation())
|
||||
self.cmd_opts.add_option(cmdoptions.use_pep517())
|
||||
self.cmd_opts.add_option(cmdoptions.no_use_pep517())
|
||||
self.cmd_opts.add_option(cmdoptions.check_build_deps())
|
||||
self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
@@ -95,7 +96,7 @@ class DownloadCommand(RequirementCommand):
|
||||
ignore_requires_python=options.ignore_requires_python,
|
||||
)
|
||||
|
||||
req_tracker = self.enter_context(get_requirement_tracker())
|
||||
build_tracker = self.enter_context(get_build_tracker())
|
||||
|
||||
directory = TempDirectory(
|
||||
delete=not options.no_clean,
|
||||
@@ -108,7 +109,7 @@ class DownloadCommand(RequirementCommand):
|
||||
preparer = self.make_requirement_preparer(
|
||||
temp_build_dir=directory,
|
||||
options=options,
|
||||
req_tracker=req_tracker,
|
||||
build_tracker=build_tracker,
|
||||
session=session,
|
||||
finder=finder,
|
||||
download_dir=options.download_dir,
|
||||
|
||||
@@ -21,10 +21,10 @@ from pipenv.patched.notpip._internal.exceptions import CommandError, Installatio
|
||||
from pipenv.patched.notpip._internal.locations import get_scheme
|
||||
from pipenv.patched.notpip._internal.metadata import get_environment
|
||||
from pipenv.patched.notpip._internal.models.format_control import FormatControl
|
||||
from pipenv.patched.notpip._internal.operations.build.build_tracker import get_build_tracker
|
||||
from pipenv.patched.notpip._internal.operations.check import ConflictDetails, check_install_conflicts
|
||||
from pipenv.patched.notpip._internal.req import install_given_reqs
|
||||
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
|
||||
from pipenv.patched.notpip._internal.req.req_tracker import get_requirement_tracker
|
||||
from pipenv.patched.notpip._internal.utils.compat import WINDOWS
|
||||
from pipenv.patched.notpip._internal.utils.distutils_args import parse_distutils_args
|
||||
from pipenv.patched.notpip._internal.utils.filesystem import test_writable_dir
|
||||
@@ -189,7 +189,9 @@ class InstallCommand(RequirementCommand):
|
||||
self.cmd_opts.add_option(cmdoptions.no_build_isolation())
|
||||
self.cmd_opts.add_option(cmdoptions.use_pep517())
|
||||
self.cmd_opts.add_option(cmdoptions.no_use_pep517())
|
||||
self.cmd_opts.add_option(cmdoptions.check_build_deps())
|
||||
|
||||
self.cmd_opts.add_option(cmdoptions.config_settings())
|
||||
self.cmd_opts.add_option(cmdoptions.install_options())
|
||||
self.cmd_opts.add_option(cmdoptions.global_options())
|
||||
|
||||
@@ -222,12 +224,12 @@ class InstallCommand(RequirementCommand):
|
||||
default=True,
|
||||
help="Do not warn about broken dependencies",
|
||||
)
|
||||
|
||||
self.cmd_opts.add_option(cmdoptions.no_binary())
|
||||
self.cmd_opts.add_option(cmdoptions.only_binary())
|
||||
self.cmd_opts.add_option(cmdoptions.prefer_binary())
|
||||
self.cmd_opts.add_option(cmdoptions.require_hashes())
|
||||
self.cmd_opts.add_option(cmdoptions.progress_bar())
|
||||
self.cmd_opts.add_option(cmdoptions.root_user_action())
|
||||
|
||||
index_opts = cmdoptions.make_option_group(
|
||||
cmdoptions.index_group,
|
||||
@@ -293,7 +295,7 @@ class InstallCommand(RequirementCommand):
|
||||
)
|
||||
wheel_cache = WheelCache(options.cache_dir, options.format_control)
|
||||
|
||||
req_tracker = self.enter_context(get_requirement_tracker())
|
||||
build_tracker = self.enter_context(get_build_tracker())
|
||||
|
||||
directory = TempDirectory(
|
||||
delete=not options.no_clean,
|
||||
@@ -315,7 +317,7 @@ class InstallCommand(RequirementCommand):
|
||||
preparer = self.make_requirement_preparer(
|
||||
temp_build_dir=directory,
|
||||
options=options,
|
||||
req_tracker=req_tracker,
|
||||
build_tracker=build_tracker,
|
||||
session=session,
|
||||
finder=finder,
|
||||
use_user_site=options.use_user_site,
|
||||
@@ -464,8 +466,8 @@ class InstallCommand(RequirementCommand):
|
||||
self._handle_target_dir(
|
||||
options.target_dir, target_temp_dir, options.upgrade
|
||||
)
|
||||
|
||||
warn_if_run_as_root()
|
||||
if options.root_user_action == "warn":
|
||||
warn_if_run_as_root()
|
||||
return SUCCESS
|
||||
|
||||
def _handle_target_dir(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import json
|
||||
import logging
|
||||
from optparse import Values
|
||||
from typing import TYPE_CHECKING, Iterator, List, Optional, Sequence, Tuple, cast
|
||||
from typing import TYPE_CHECKING, Generator, List, Optional, Sequence, Tuple, cast
|
||||
|
||||
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
@@ -222,7 +222,7 @@ class ListCommand(IndexGroupCommand):
|
||||
|
||||
def iter_packages_latest_infos(
|
||||
self, packages: "_ProcessedDists", options: Values
|
||||
) -> Iterator["_DistWithLatestInfo"]:
|
||||
) -> Generator["_DistWithLatestInfo", None, None]:
|
||||
with self._build_session(options) as session:
|
||||
finder = self._build_package_finder(options, session)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import logging
|
||||
from optparse import Values
|
||||
from typing import Iterator, List, NamedTuple, Optional
|
||||
from typing import Generator, Iterable, Iterator, List, NamedTuple, Optional
|
||||
|
||||
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
@@ -60,6 +60,7 @@ class _PackageInfo(NamedTuple):
|
||||
classifiers: List[str]
|
||||
summary: str
|
||||
homepage: str
|
||||
project_urls: List[str]
|
||||
author: str
|
||||
author_email: str
|
||||
license: str
|
||||
@@ -67,7 +68,7 @@ class _PackageInfo(NamedTuple):
|
||||
files: Optional[List[str]]
|
||||
|
||||
|
||||
def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]:
|
||||
def search_packages_info(query: List[str]) -> Generator[_PackageInfo, None, None]:
|
||||
"""
|
||||
Gather details from installed distributions. Print distribution name,
|
||||
version, location, and installed files. Installed files requires a
|
||||
@@ -76,7 +77,7 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]:
|
||||
"""
|
||||
env = get_default_environment()
|
||||
|
||||
installed = {dist.canonical_name: dist for dist in env.iter_distributions()}
|
||||
installed = {dist.canonical_name: dist for dist in env.iter_all_distributions()}
|
||||
query_names = [canonicalize_name(name) for name in query]
|
||||
missing = sorted(
|
||||
[name for name, pkg in zip(query, query_names) if pkg not in installed]
|
||||
@@ -126,6 +127,7 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]:
|
||||
classifiers=metadata.get_all("Classifier", []),
|
||||
summary=metadata.get("Summary", ""),
|
||||
homepage=metadata.get("Home-page", ""),
|
||||
project_urls=metadata.get_all("Project-URL", []),
|
||||
author=metadata.get("Author", ""),
|
||||
author_email=metadata.get("Author-email", ""),
|
||||
license=metadata.get("License", ""),
|
||||
@@ -135,7 +137,7 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]:
|
||||
|
||||
|
||||
def print_results(
|
||||
distributions: Iterator[_PackageInfo],
|
||||
distributions: Iterable[_PackageInfo],
|
||||
list_files: bool,
|
||||
verbose: bool,
|
||||
) -> bool:
|
||||
@@ -168,6 +170,9 @@ def print_results(
|
||||
write_output("Entry-points:")
|
||||
for entry in dist.entry_points:
|
||||
write_output(" %s", entry.strip())
|
||||
write_output("Project-URLs:")
|
||||
for project_url in dist.project_urls:
|
||||
write_output(" %s", project_url)
|
||||
if list_files:
|
||||
write_output("Files:")
|
||||
if dist.files is None:
|
||||
|
||||
@@ -4,6 +4,7 @@ from typing import List
|
||||
|
||||
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
from pipenv.patched.notpip._internal.cli import cmdoptions
|
||||
from pipenv.patched.notpip._internal.cli.base_command import Command
|
||||
from pipenv.patched.notpip._internal.cli.req_command import SessionCommandMixin, warn_if_run_as_root
|
||||
from pipenv.patched.notpip._internal.cli.status_codes import SUCCESS
|
||||
@@ -53,7 +54,7 @@ class UninstallCommand(Command, SessionCommandMixin):
|
||||
action="store_true",
|
||||
help="Don't ask for confirmation of uninstall deletions.",
|
||||
)
|
||||
|
||||
self.cmd_opts.add_option(cmdoptions.root_user_action())
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
@@ -100,6 +101,6 @@ class UninstallCommand(Command, SessionCommandMixin):
|
||||
)
|
||||
if uninstall_pathset:
|
||||
uninstall_pathset.commit()
|
||||
|
||||
warn_if_run_as_root()
|
||||
if options.root_user_action == "warn":
|
||||
warn_if_run_as_root()
|
||||
return SUCCESS
|
||||
|
||||
@@ -9,8 +9,8 @@ from pipenv.patched.notpip._internal.cli import cmdoptions
|
||||
from pipenv.patched.notpip._internal.cli.req_command import RequirementCommand, with_cleanup
|
||||
from pipenv.patched.notpip._internal.cli.status_codes import SUCCESS
|
||||
from pipenv.patched.notpip._internal.exceptions import CommandError
|
||||
from pipenv.patched.notpip._internal.operations.build.build_tracker import get_build_tracker
|
||||
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
|
||||
from pipenv.patched.notpip._internal.req.req_tracker import get_requirement_tracker
|
||||
from pipenv.patched.notpip._internal.utils.misc import ensure_dir, normalize_path
|
||||
from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory
|
||||
from pipenv.patched.notpip._internal.wheel_builder import build, should_build_for_wheel_command
|
||||
@@ -26,10 +26,8 @@ class WheelCommand(RequirementCommand):
|
||||
recompiling your software during every install. For more details, see the
|
||||
wheel docs: https://wheel.readthedocs.io/en/latest/
|
||||
|
||||
Requirements: setuptools>=0.8, and wheel.
|
||||
|
||||
'pip wheel' uses the bdist_wheel setuptools extension from the wheel
|
||||
package to build individual wheels.
|
||||
'pip wheel' uses the build system interface as described here:
|
||||
https://pip.pypa.io/en/stable/reference/build-system/
|
||||
|
||||
"""
|
||||
|
||||
@@ -59,6 +57,7 @@ class WheelCommand(RequirementCommand):
|
||||
self.cmd_opts.add_option(cmdoptions.no_build_isolation())
|
||||
self.cmd_opts.add_option(cmdoptions.use_pep517())
|
||||
self.cmd_opts.add_option(cmdoptions.no_use_pep517())
|
||||
self.cmd_opts.add_option(cmdoptions.check_build_deps())
|
||||
self.cmd_opts.add_option(cmdoptions.constraints())
|
||||
self.cmd_opts.add_option(cmdoptions.editable())
|
||||
self.cmd_opts.add_option(cmdoptions.requirements())
|
||||
@@ -75,6 +74,7 @@ class WheelCommand(RequirementCommand):
|
||||
help="Don't verify if built wheel is valid.",
|
||||
)
|
||||
|
||||
self.cmd_opts.add_option(cmdoptions.config_settings())
|
||||
self.cmd_opts.add_option(cmdoptions.build_options())
|
||||
self.cmd_opts.add_option(cmdoptions.global_options())
|
||||
|
||||
@@ -110,7 +110,7 @@ class WheelCommand(RequirementCommand):
|
||||
options.wheel_dir = normalize_path(options.wheel_dir)
|
||||
ensure_dir(options.wheel_dir)
|
||||
|
||||
req_tracker = self.enter_context(get_requirement_tracker())
|
||||
build_tracker = self.enter_context(get_build_tracker())
|
||||
|
||||
directory = TempDirectory(
|
||||
delete=not options.no_clean,
|
||||
@@ -123,7 +123,7 @@ class WheelCommand(RequirementCommand):
|
||||
preparer = self.make_requirement_preparer(
|
||||
temp_build_dir=directory,
|
||||
options=options,
|
||||
req_tracker=req_tracker,
|
||||
build_tracker=build_tracker,
|
||||
session=session,
|
||||
finder=finder,
|
||||
download_dir=options.wheel_dir,
|
||||
|
||||
@@ -142,13 +142,19 @@ class Configuration:
|
||||
|
||||
def get_value(self, key: str) -> Any:
|
||||
"""Get a value from the configuration."""
|
||||
orig_key = key
|
||||
key = _normalize_name(key)
|
||||
try:
|
||||
return self._dictionary[key]
|
||||
except KeyError:
|
||||
raise ConfigurationError(f"No such key - {key}")
|
||||
# disassembling triggers a more useful error message than simply
|
||||
# "No such key" in the case that the key isn't in the form command.option
|
||||
_disassemble_key(key)
|
||||
raise ConfigurationError(f"No such key - {orig_key}")
|
||||
|
||||
def set_value(self, key: str, value: Any) -> None:
|
||||
"""Modify a value in the configuration."""
|
||||
key = _normalize_name(key)
|
||||
self._ensure_have_load_only()
|
||||
|
||||
assert self.load_only
|
||||
@@ -167,11 +173,13 @@ class Configuration:
|
||||
|
||||
def unset_value(self, key: str) -> None:
|
||||
"""Unset a value in the configuration."""
|
||||
orig_key = key
|
||||
key = _normalize_name(key)
|
||||
self._ensure_have_load_only()
|
||||
|
||||
assert self.load_only
|
||||
if key not in self._config[self.load_only]:
|
||||
raise ConfigurationError(f"No such key - {key}")
|
||||
raise ConfigurationError(f"No such key - {orig_key}")
|
||||
|
||||
fname, parser = self._get_parser_to_modify()
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@ class AbstractDistribution(metaclass=abc.ABCMeta):
|
||||
|
||||
@abc.abstractmethod
|
||||
def prepare_distribution_metadata(
|
||||
self, finder: PackageFinder, build_isolation: bool
|
||||
self,
|
||||
finder: PackageFinder,
|
||||
build_isolation: bool,
|
||||
check_build_deps: bool,
|
||||
) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -15,6 +15,9 @@ class InstalledDistribution(AbstractDistribution):
|
||||
return self.req.satisfied_by
|
||||
|
||||
def prepare_distribution_metadata(
|
||||
self, finder: PackageFinder, build_isolation: bool
|
||||
self,
|
||||
finder: PackageFinder,
|
||||
build_isolation: bool,
|
||||
check_build_deps: bool,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@@ -22,7 +22,10 @@ class SourceDistribution(AbstractDistribution):
|
||||
return self.req.get_dist()
|
||||
|
||||
def prepare_distribution_metadata(
|
||||
self, finder: PackageFinder, build_isolation: bool
|
||||
self,
|
||||
finder: PackageFinder,
|
||||
build_isolation: bool,
|
||||
check_build_deps: bool,
|
||||
) -> None:
|
||||
# Load pyproject.toml, to determine whether PEP 517 is to be used
|
||||
self.req.load_pyproject_toml()
|
||||
@@ -43,7 +46,18 @@ class SourceDistribution(AbstractDistribution):
|
||||
self.req.isolated_editable_sanity_check()
|
||||
# Install the dynamic build requirements.
|
||||
self._install_build_reqs(finder)
|
||||
|
||||
# Check if the current environment provides build dependencies
|
||||
should_check_deps = self.req.use_pep517 and check_build_deps
|
||||
if should_check_deps:
|
||||
pyproject_requires = self.req.pyproject_requires
|
||||
assert pyproject_requires is not None
|
||||
conflicting, missing = self.req.build_env.check_requirements(
|
||||
pyproject_requires
|
||||
)
|
||||
if conflicting:
|
||||
self._raise_conflicts("the backend dependencies", conflicting)
|
||||
if missing:
|
||||
self._raise_missing_reqs(missing)
|
||||
self.req.prepare_metadata()
|
||||
|
||||
def _prepare_build_backend(self, finder: PackageFinder) -> None:
|
||||
@@ -125,3 +139,12 @@ class SourceDistribution(AbstractDistribution):
|
||||
),
|
||||
)
|
||||
raise InstallationError(error_message)
|
||||
|
||||
def _raise_missing_reqs(self, missing: Set[str]) -> None:
|
||||
format_string = (
|
||||
"Some build dependencies for {requirement} are missing: {missing}."
|
||||
)
|
||||
error_message = format_string.format(
|
||||
requirement=self.req, missing=", ".join(map(repr, sorted(missing)))
|
||||
)
|
||||
raise InstallationError(error_message)
|
||||
|
||||
@@ -26,6 +26,9 @@ class WheelDistribution(AbstractDistribution):
|
||||
return get_wheel_distribution(wheel, canonicalize_name(self.req.name))
|
||||
|
||||
def prepare_distribution_metadata(
|
||||
self, finder: PackageFinder, build_isolation: bool
|
||||
self,
|
||||
finder: PackageFinder,
|
||||
build_isolation: bool,
|
||||
check_build_deps: bool,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
|
||||
import enum
|
||||
import functools
|
||||
import itertools
|
||||
import logging
|
||||
@@ -94,6 +95,16 @@ def _check_link_requires_python(
|
||||
return True
|
||||
|
||||
|
||||
class LinkType(enum.Enum):
|
||||
candidate = enum.auto()
|
||||
different_project = enum.auto()
|
||||
yanked = enum.auto()
|
||||
format_unsupported = enum.auto()
|
||||
format_invalid = enum.auto()
|
||||
platform_mismatch = enum.auto()
|
||||
requires_python_mismatch = enum.auto()
|
||||
|
||||
|
||||
class LinkEvaluator:
|
||||
|
||||
"""
|
||||
@@ -137,31 +148,30 @@ class LinkEvaluator:
|
||||
"""
|
||||
if ignore_requires_python is None:
|
||||
ignore_requires_python = False
|
||||
if ignore_compatibility is None:
|
||||
ignore_compatibility = True
|
||||
|
||||
self._allow_yanked = allow_yanked
|
||||
self._canonical_name = canonical_name
|
||||
self._ignore_requires_python = ignore_requires_python
|
||||
self._formats = formats
|
||||
self._target_python = target_python
|
||||
|
||||
self.project_name = project_name
|
||||
self._ignore_compatibility = ignore_compatibility
|
||||
|
||||
def evaluate_link(self, link: Link) -> Tuple[bool, Optional[str]]:
|
||||
self.project_name = project_name
|
||||
|
||||
def evaluate_link(self, link: Link) -> Tuple[LinkType, str]:
|
||||
"""
|
||||
Determine whether a link is a candidate for installation.
|
||||
|
||||
:return: A tuple (is_candidate, result), where `result` is (1) a
|
||||
version string if `is_candidate` is True, and (2) if
|
||||
`is_candidate` is False, an optional string to log the reason
|
||||
the link fails to qualify.
|
||||
:return: A tuple (result, detail), where *result* is an enum
|
||||
representing whether the evaluation found a candidate, or the reason
|
||||
why one is not found. If a candidate is found, *detail* will be the
|
||||
candidate's version string; if one is not found, it contains the
|
||||
reason the link fails to qualify.
|
||||
"""
|
||||
version = None
|
||||
if link.is_yanked and not self._allow_yanked:
|
||||
reason = link.yanked_reason or "<none given>"
|
||||
return (False, f"yanked for reason: {reason}")
|
||||
return (LinkType.yanked, f"yanked for reason: {reason}")
|
||||
|
||||
if link.egg_fragment:
|
||||
egg_info = link.egg_fragment
|
||||
@@ -169,42 +179,46 @@ class LinkEvaluator:
|
||||
else:
|
||||
egg_info, ext = link.splitext()
|
||||
if not ext:
|
||||
return (False, "not a file")
|
||||
return (LinkType.format_unsupported, "not a file")
|
||||
if ext not in SUPPORTED_EXTENSIONS:
|
||||
return (False, f"unsupported archive format: {ext}")
|
||||
return (
|
||||
LinkType.format_unsupported,
|
||||
f"unsupported archive format: {ext}",
|
||||
)
|
||||
if "binary" not in self._formats and ext == WHEEL_EXTENSION and not self._ignore_compatibility:
|
||||
reason = "No binaries permitted for {}".format(self.project_name)
|
||||
return (False, reason)
|
||||
if "macosx10" in link.path and ext == '.zip' and not self._ignore_compatibility:
|
||||
return (False, "macosx10 one")
|
||||
reason = f"No binaries permitted for {self.project_name}"
|
||||
return (LinkType.format_unsupported, reason)
|
||||
if "macosx10" in link.path and ext == ".zip" and not self._ignore_compatibility:
|
||||
return (LinkType.format_unsupported, "macosx10 one")
|
||||
if ext == WHEEL_EXTENSION:
|
||||
try:
|
||||
wheel = Wheel(link.filename)
|
||||
except InvalidWheelFilename:
|
||||
return (False, "invalid wheel filename")
|
||||
return (
|
||||
LinkType.format_invalid,
|
||||
"invalid wheel filename",
|
||||
)
|
||||
if canonicalize_name(wheel.name) != self._canonical_name:
|
||||
reason = "wrong project name (not {})".format(self.project_name)
|
||||
return (False, reason)
|
||||
reason = f"wrong project name (not {self.project_name})"
|
||||
return (LinkType.different_project, reason)
|
||||
|
||||
supported_tags = self._target_python.get_tags()
|
||||
if not wheel.supported(supported_tags) and not self._ignore_compatibility:
|
||||
# Include the wheel's tags in the reason string to
|
||||
# simplify troubleshooting compatibility issues.
|
||||
file_tags = wheel.get_formatted_file_tags()
|
||||
file_tags = ", ".join(wheel.get_formatted_file_tags())
|
||||
reason = (
|
||||
"none of the wheel's tags ({}) are compatible "
|
||||
"(run pip debug --verbose to show compatible tags)".format(
|
||||
", ".join(file_tags)
|
||||
)
|
||||
f"none of the wheel's tags ({file_tags}) are compatible "
|
||||
f"(run pip debug --verbose to show compatible tags)"
|
||||
)
|
||||
return (False, reason)
|
||||
return (LinkType.platform_mismatch, reason)
|
||||
|
||||
version = wheel.version
|
||||
|
||||
# This should be up by the self.ok_binary check, but see issue 2700.
|
||||
if "source" not in self._formats and ext != WHEEL_EXTENSION:
|
||||
reason = f"No sources permitted for {self.project_name}"
|
||||
return (False, reason)
|
||||
return (LinkType.format_unsupported, reason)
|
||||
|
||||
if not version:
|
||||
version = _extract_version_from_fragment(
|
||||
@@ -213,14 +227,17 @@ class LinkEvaluator:
|
||||
)
|
||||
if not version:
|
||||
reason = f"Missing project version for {self.project_name}"
|
||||
return (False, reason)
|
||||
return (LinkType.format_invalid, reason)
|
||||
|
||||
match = self._py_version_re.search(version)
|
||||
if match:
|
||||
version = version[: match.start()]
|
||||
py_version = match.group(1)
|
||||
if py_version != self._target_python.py_version:
|
||||
return (False, "Python version is incorrect")
|
||||
return (
|
||||
LinkType.platform_mismatch,
|
||||
"Python version is incorrect",
|
||||
)
|
||||
|
||||
supports_python = _check_link_requires_python(
|
||||
link,
|
||||
@@ -228,13 +245,12 @@ class LinkEvaluator:
|
||||
ignore_requires_python=self._ignore_requires_python,
|
||||
)
|
||||
if not supports_python and not self._ignore_compatibility:
|
||||
# Return None for the reason text to suppress calling
|
||||
# _log_skipped_link().
|
||||
return (False, None)
|
||||
reason = f"{version} Requires-Python {link.requires_python}"
|
||||
return (LinkType.requires_python_mismatch, reason)
|
||||
|
||||
logger.debug("Found link %s, version: %s", link, version)
|
||||
|
||||
return (True, version)
|
||||
return (LinkType.candidate, version)
|
||||
|
||||
|
||||
def filter_unallowed_hashes(
|
||||
@@ -624,7 +640,7 @@ class PackageFinder:
|
||||
self.format_control = format_control
|
||||
|
||||
# These are boring links that have already been logged somehow.
|
||||
self._logged_links: Set[Link] = set()
|
||||
self._logged_links: Set[Tuple[Link, LinkType, str]] = set()
|
||||
|
||||
# Don't include an allow_yanked default value to make sure each call
|
||||
# site considers whether yanked releases are allowed. This also causes
|
||||
@@ -704,6 +720,14 @@ class PackageFinder:
|
||||
def set_prefer_binary(self) -> None:
|
||||
self._candidate_prefs.prefer_binary = True
|
||||
|
||||
def requires_python_skipped_reasons(self) -> List[str]:
|
||||
reasons = {
|
||||
detail
|
||||
for _, result, detail in self._logged_links
|
||||
if result == LinkType.requires_python_mismatch
|
||||
}
|
||||
return sorted(reasons)
|
||||
|
||||
def make_link_evaluator(self, project_name: str) -> LinkEvaluator:
|
||||
canonical_name = canonicalize_name(project_name)
|
||||
formats = self.format_control.get_allowed_formats(canonical_name)
|
||||
@@ -734,12 +758,13 @@ class PackageFinder:
|
||||
no_eggs.append(link)
|
||||
return no_eggs + eggs
|
||||
|
||||
def _log_skipped_link(self, link: Link, reason: str) -> None:
|
||||
if link not in self._logged_links:
|
||||
def _log_skipped_link(self, link: Link, result: LinkType, detail: str) -> None:
|
||||
entry = (link, result, detail)
|
||||
if entry not in self._logged_links:
|
||||
# Put the link at the end so the reason is more visible and because
|
||||
# the link string is usually very long.
|
||||
logger.debug("Skipping link: %s: %s", reason, link)
|
||||
self._logged_links.add(link)
|
||||
logger.debug("Skipping link: %s: %s", detail, link)
|
||||
self._logged_links.add(entry)
|
||||
|
||||
def get_install_candidate(
|
||||
self, link_evaluator: LinkEvaluator, link: Link
|
||||
@@ -748,16 +773,15 @@ class PackageFinder:
|
||||
If the link is a candidate for install, convert it to an
|
||||
InstallationCandidate and return it. Otherwise, return None.
|
||||
"""
|
||||
is_candidate, result = link_evaluator.evaluate_link(link)
|
||||
if not is_candidate:
|
||||
if result:
|
||||
self._log_skipped_link(link, reason=result)
|
||||
result, detail = link_evaluator.evaluate_link(link)
|
||||
if result != LinkType.candidate:
|
||||
self._log_skipped_link(link, result, detail)
|
||||
return None
|
||||
|
||||
return InstallationCandidate(
|
||||
name=link_evaluator.project_name,
|
||||
link=link,
|
||||
version=result,
|
||||
version=detail,
|
||||
)
|
||||
|
||||
def evaluate_links(
|
||||
|
||||
@@ -4,7 +4,7 @@ import os
|
||||
import pathlib
|
||||
import sys
|
||||
import sysconfig
|
||||
from typing import Any, Dict, Iterator, List, Optional, Tuple
|
||||
from typing import Any, Dict, Generator, List, Optional, Tuple
|
||||
|
||||
from pipenv.patched.notpip._internal.models.scheme import SCHEME_KEYS, Scheme
|
||||
from pipenv.patched.notpip._internal.utils.compat import WINDOWS
|
||||
@@ -70,9 +70,10 @@ else:
|
||||
|
||||
def _looks_like_bpo_44860() -> bool:
|
||||
"""The resolution to bpo-44860 will change this incorrect platlib.
|
||||
|
||||
See <https://bugs.python.org/issue44860>.
|
||||
"""
|
||||
from distutils.command.install import INSTALL_SCHEMES # type: ignore
|
||||
from distutils.command.install import INSTALL_SCHEMES
|
||||
|
||||
try:
|
||||
unix_user_platlib = INSTALL_SCHEMES["unix_user"]["platlib"]
|
||||
@@ -97,7 +98,7 @@ def _looks_like_red_hat_lib() -> bool:
|
||||
|
||||
This is the only way I can see to tell a Red Hat-patched Python.
|
||||
"""
|
||||
from distutils.command.install import INSTALL_SCHEMES # type: ignore
|
||||
from distutils.command.install import INSTALL_SCHEMES
|
||||
|
||||
return all(
|
||||
k in INSTALL_SCHEMES
|
||||
@@ -109,7 +110,7 @@ def _looks_like_red_hat_lib() -> bool:
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def _looks_like_debian_scheme() -> bool:
|
||||
"""Debian adds two additional schemes."""
|
||||
from distutils.command.install import INSTALL_SCHEMES # type: ignore
|
||||
from distutils.command.install import INSTALL_SCHEMES
|
||||
|
||||
return "deb_system" in INSTALL_SCHEMES and "unix_local" in INSTALL_SCHEMES
|
||||
|
||||
@@ -137,6 +138,7 @@ def _looks_like_red_hat_scheme() -> bool:
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def _looks_like_slackware_scheme() -> bool:
|
||||
"""Slackware patches sysconfig but fails to patch distutils and site.
|
||||
|
||||
Slackware changes sysconfig's user scheme to use ``"lib64"`` for the lib
|
||||
path, but does not do the same to the site module.
|
||||
"""
|
||||
@@ -152,9 +154,11 @@ def _looks_like_slackware_scheme() -> bool:
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def _looks_like_msys2_mingw_scheme() -> bool:
|
||||
"""MSYS2 patches distutils and sysconfig to use a UNIX-like scheme.
|
||||
|
||||
However, MSYS2 incorrectly patches sysconfig ``nt`` scheme. The fix is
|
||||
likely going to be included in their 3.10 release, so we ignore the warning.
|
||||
See msys2/MINGW-packages#9319.
|
||||
|
||||
MSYS2 MINGW's patch uses lowercase ``"lib"`` instead of the usual uppercase,
|
||||
and is missing the final ``"site-packages"``.
|
||||
"""
|
||||
@@ -165,9 +169,9 @@ def _looks_like_msys2_mingw_scheme() -> bool:
|
||||
)
|
||||
|
||||
|
||||
def _fix_abiflags(parts: Tuple[str]) -> Iterator[str]:
|
||||
def _fix_abiflags(parts: Tuple[str]) -> Generator[str, None, None]:
|
||||
ldversion = sysconfig.get_config_var("LDVERSION")
|
||||
abiflags: str = getattr(sys, "abiflags", None)
|
||||
abiflags = getattr(sys, "abiflags", None)
|
||||
|
||||
# LDVERSION does not end with sys.abiflags. Just return the path unchanged.
|
||||
if not ldversion or not abiflags or not ldversion.endswith(abiflags):
|
||||
|
||||
@@ -84,7 +84,7 @@ def distutils_scheme(
|
||||
if home:
|
||||
prefix = home
|
||||
elif user:
|
||||
prefix = i.install_userbase # type: ignore
|
||||
prefix = i.install_userbase
|
||||
else:
|
||||
prefix = i.prefix
|
||||
scheme["headers"] = os.path.join(
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
from typing import List, Optional
|
||||
import contextlib
|
||||
import functools
|
||||
import os
|
||||
import sys
|
||||
from typing import TYPE_CHECKING, List, Optional, Type, cast
|
||||
|
||||
from pipenv.patched.notpip._internal.utils.misc import strtobool
|
||||
|
||||
from .base import BaseDistribution, BaseEnvironment, FilesystemWheel, MemoryWheel, Wheel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Protocol
|
||||
else:
|
||||
Protocol = object
|
||||
|
||||
__all__ = [
|
||||
"BaseDistribution",
|
||||
"BaseEnvironment",
|
||||
@@ -11,9 +22,49 @@ __all__ = [
|
||||
"get_default_environment",
|
||||
"get_environment",
|
||||
"get_wheel_distribution",
|
||||
"select_backend",
|
||||
]
|
||||
|
||||
|
||||
def _should_use_importlib_metadata() -> bool:
|
||||
"""Whether to use the ``importlib.metadata`` or ``pkg_resources`` backend.
|
||||
|
||||
By default, pip uses ``importlib.metadata`` on Python 3.11+, and
|
||||
``pkg_resourcess`` otherwise. This can be overriden by a couple of ways:
|
||||
|
||||
* If environment variable ``_PIP_USE_IMPORTLIB_METADATA`` is set, it
|
||||
dictates whether ``importlib.metadata`` is used, regardless of Python
|
||||
version.
|
||||
* On Python 3.11+, Python distributors can patch ``importlib.metadata``
|
||||
to add a global constant ``_PIP_USE_IMPORTLIB_METADATA = False``. This
|
||||
makes pip use ``pkg_resources`` (unless the user set the aforementioned
|
||||
environment variable to *True*).
|
||||
"""
|
||||
with contextlib.suppress(KeyError, ValueError):
|
||||
return bool(strtobool(os.environ["_PIP_USE_IMPORTLIB_METADATA"]))
|
||||
if sys.version_info < (3, 11):
|
||||
return False
|
||||
import importlib.metadata
|
||||
|
||||
return bool(getattr(importlib.metadata, "_PIP_USE_IMPORTLIB_METADATA", True))
|
||||
|
||||
|
||||
class Backend(Protocol):
|
||||
Distribution: Type[BaseDistribution]
|
||||
Environment: Type[BaseEnvironment]
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def select_backend() -> Backend:
|
||||
if _should_use_importlib_metadata():
|
||||
from . import importlib
|
||||
|
||||
return cast(Backend, importlib)
|
||||
from . import pkg_resources
|
||||
|
||||
return cast(Backend, pkg_resources)
|
||||
|
||||
|
||||
def get_default_environment() -> BaseEnvironment:
|
||||
"""Get the default representation for the current environment.
|
||||
|
||||
@@ -21,9 +72,7 @@ def get_default_environment() -> BaseEnvironment:
|
||||
Environment instance should be built from ``sys.path`` and may use caching
|
||||
to share instance state accorss calls.
|
||||
"""
|
||||
from .pkg_resources import Environment
|
||||
|
||||
return Environment.default()
|
||||
return select_backend().Environment.default()
|
||||
|
||||
|
||||
def get_environment(paths: Optional[List[str]]) -> BaseEnvironment:
|
||||
@@ -33,9 +82,7 @@ def get_environment(paths: Optional[List[str]]) -> BaseEnvironment:
|
||||
given import paths. The backend must build a fresh instance representing
|
||||
the state of installed distributions when this function is called.
|
||||
"""
|
||||
from .pkg_resources import Environment
|
||||
|
||||
return Environment.from_paths(paths)
|
||||
return select_backend().Environment.from_paths(paths)
|
||||
|
||||
|
||||
def get_directory_distribution(directory: str) -> BaseDistribution:
|
||||
@@ -44,9 +91,7 @@ def get_directory_distribution(directory: str) -> BaseDistribution:
|
||||
This returns a Distribution instance from the chosen backend based on
|
||||
the given on-disk ``.dist-info`` directory.
|
||||
"""
|
||||
from .pkg_resources import Distribution
|
||||
|
||||
return Distribution.from_directory(directory)
|
||||
return select_backend().Distribution.from_directory(directory)
|
||||
|
||||
|
||||
def get_wheel_distribution(wheel: Wheel, canonical_name: str) -> BaseDistribution:
|
||||
@@ -57,6 +102,4 @@ def get_wheel_distribution(wheel: Wheel, canonical_name: str) -> BaseDistributio
|
||||
|
||||
:param canonical_name: Normalized project name of the given wheel.
|
||||
"""
|
||||
from .pkg_resources import Distribution
|
||||
|
||||
return Distribution.from_wheel(wheel, canonical_name)
|
||||
return select_backend().Distribution.from_wheel(wheel, canonical_name)
|
||||
|
||||
@@ -31,10 +31,7 @@ from pipenv.patched.notpip._internal.models.direct_url import (
|
||||
DirectUrlValidationError,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.compat import stdlib_pkgs # TODO: Move definition here.
|
||||
from pipenv.patched.notpip._internal.utils.egg_link import (
|
||||
egg_link_path_from_location,
|
||||
egg_link_path_from_sys_path,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.egg_link import egg_link_path_from_sys_path
|
||||
from pipenv.patched.notpip._internal.utils.misc import is_local, normalize_path
|
||||
from pipenv.patched.notpip._internal.utils.urls import url_to_path
|
||||
|
||||
@@ -45,7 +42,7 @@ else:
|
||||
|
||||
DistributionVersion = Union[LegacyVersion, Version]
|
||||
|
||||
InfoPath = Union[str, pathlib.PurePosixPath]
|
||||
InfoPath = Union[str, pathlib.PurePath]
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -95,6 +92,28 @@ def _convert_installed_files_path(
|
||||
|
||||
|
||||
class BaseDistribution(Protocol):
|
||||
@classmethod
|
||||
def from_directory(cls, directory: str) -> "BaseDistribution":
|
||||
"""Load the distribution from a metadata directory.
|
||||
|
||||
:param directory: Path to a metadata directory, e.g. ``.dist-info``.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
def from_wheel(cls, wheel: "Wheel", name: str) -> "BaseDistribution":
|
||||
"""Load the distribution from a given wheel.
|
||||
|
||||
:param wheel: A concrete wheel definition.
|
||||
:param name: File name of the wheel.
|
||||
|
||||
:raises InvalidWheel: Whenever loading of the wheel causes a
|
||||
:py:exc:`zipfile.BadZipFile` exception to be thrown.
|
||||
:raises UnsupportedWheel: If the wheel is a valid zip, but malformed
|
||||
internally.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.raw_name} {self.version} ({self.location})"
|
||||
|
||||
@@ -148,14 +167,7 @@ class BaseDistribution(Protocol):
|
||||
|
||||
The returned location is normalized (in particular, with symlinks removed).
|
||||
"""
|
||||
egg_link = egg_link_path_from_location(self.raw_name)
|
||||
if egg_link:
|
||||
location = egg_link
|
||||
elif self.location:
|
||||
location = self.location
|
||||
else:
|
||||
return None
|
||||
return normalize_path(location)
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def info_location(self) -> Optional[str]:
|
||||
@@ -316,21 +328,19 @@ class BaseDistribution(Protocol):
|
||||
"""Check whether an entry in the info directory is a file."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def iterdir(self, path: InfoPath) -> Iterator[pathlib.PurePosixPath]:
|
||||
"""Iterate through a directory in the info directory.
|
||||
def iter_distutils_script_names(self) -> Iterator[str]:
|
||||
"""Find distutils 'scripts' entries metadata.
|
||||
|
||||
Each item yielded would be a path relative to the info directory.
|
||||
|
||||
:raise FileNotFoundError: If ``name`` does not exist in the directory.
|
||||
:raise NotADirectoryError: If ``name`` does not point to a directory.
|
||||
If 'scripts' is supplied in ``setup.py``, distutils records those in the
|
||||
installed distribution's ``scripts`` directory, a file for each script.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def read_text(self, path: InfoPath) -> str:
|
||||
"""Read a file in the info directory.
|
||||
|
||||
:raise FileNotFoundError: If ``name`` does not exist in the directory.
|
||||
:raise NoneMetadataError: If ``name`` exists in the info directory, but
|
||||
:raise FileNotFoundError: If ``path`` does not exist in the directory.
|
||||
:raise NoneMetadataError: If ``path`` exists in the info directory, but
|
||||
cannot be read.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
@@ -470,8 +480,8 @@ class BaseEnvironment:
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def iter_distributions(self) -> Iterator["BaseDistribution"]:
|
||||
"""Iterate through installed distributions."""
|
||||
def iter_all_distributions(self) -> Iterator[BaseDistribution]:
|
||||
"""Iterate through all installed distributions without any filtering."""
|
||||
for dist in self._iter_distributions():
|
||||
# Make sure the distribution actually comes from a valid Python
|
||||
# packaging distribution. Pip's AdjacentTempDirectory leaves folders
|
||||
@@ -501,6 +511,11 @@ class BaseEnvironment:
|
||||
) -> Iterator[BaseDistribution]:
|
||||
"""Return a list of installed distributions.
|
||||
|
||||
This is based on ``iter_all_distributions()`` with additional filtering
|
||||
options. Note that ``iter_installed_distributions()`` without arguments
|
||||
is *not* equal to ``iter_all_distributions()``, since some of the
|
||||
configurations exclude packages by default.
|
||||
|
||||
:param local_only: If True (default), only return installations
|
||||
local to the current virtualenv, if in a virtualenv.
|
||||
:param skip: An iterable of canonicalized project names to ignore;
|
||||
@@ -510,7 +525,7 @@ class BaseEnvironment:
|
||||
:param user_only: If True, only report installations in the user
|
||||
site directory.
|
||||
"""
|
||||
it = self.iter_distributions()
|
||||
it = self.iter_all_distributions()
|
||||
if local_only:
|
||||
it = (d for d in it if d.local)
|
||||
if not include_editables:
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
from ._dists import Distribution
|
||||
from ._envs import Environment
|
||||
|
||||
__all__ = ["Distribution", "Environment"]
|
||||
@@ -0,0 +1,41 @@
|
||||
import importlib.metadata
|
||||
from typing import Any, Optional, Protocol, cast
|
||||
|
||||
|
||||
class BasePath(Protocol):
|
||||
"""A protocol that various path objects conform.
|
||||
|
||||
This exists because importlib.metadata uses both ``pathlib.Path`` and
|
||||
``zipfile.Path``, and we need a common base for type hints (Union does not
|
||||
work well since ``zipfile.Path`` is too new for our linter setup).
|
||||
|
||||
This does not mean to be exhaustive, but only contains things that present
|
||||
in both classes *that we need*.
|
||||
"""
|
||||
|
||||
name: str
|
||||
|
||||
@property
|
||||
def parent(self) -> "BasePath":
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def get_info_location(d: importlib.metadata.Distribution) -> Optional[BasePath]:
|
||||
"""Find the path to the distribution's metadata directory.
|
||||
|
||||
HACK: This relies on importlib.metadata's private ``_path`` attribute. Not
|
||||
all distributions exist on disk, so importlib.metadata is correct to not
|
||||
expose the attribute as public. But pip's code base is old and not as clean,
|
||||
so we do this to avoid having to rewrite too many things. Hopefully we can
|
||||
eliminate this some day.
|
||||
"""
|
||||
return getattr(d, "_path", None)
|
||||
|
||||
|
||||
def get_dist_name(dist: importlib.metadata.Distribution) -> str:
|
||||
"""Get the distribution's project name.
|
||||
|
||||
The ``name`` attribute is only available in Python 3.10 or later. We are
|
||||
targeting exactly that, but Mypy does not know this.
|
||||
"""
|
||||
return cast(Any, dist).name
|
||||
@@ -0,0 +1,274 @@
|
||||
import email.message
|
||||
import importlib.metadata
|
||||
import os
|
||||
import pathlib
|
||||
import zipfile
|
||||
from typing import (
|
||||
Collection,
|
||||
Dict,
|
||||
Iterable,
|
||||
Iterator,
|
||||
Mapping,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Sequence,
|
||||
)
|
||||
|
||||
from pipenv.patched.notpip._vendor.packaging.requirements import Requirement
|
||||
from pipenv.patched.notpip._vendor.packaging.utils import NormalizedName, canonicalize_name
|
||||
from pipenv.patched.notpip._vendor.packaging.version import parse as parse_version
|
||||
|
||||
from pipenv.patched.notpip._internal.exceptions import InvalidWheel, UnsupportedWheel
|
||||
from pipenv.patched.notpip._internal.metadata.base import (
|
||||
BaseDistribution,
|
||||
BaseEntryPoint,
|
||||
DistributionVersion,
|
||||
InfoPath,
|
||||
Wheel,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.misc import normalize_path
|
||||
from pipenv.patched.notpip._internal.utils.packaging import safe_extra
|
||||
from pipenv.patched.notpip._internal.utils.wheel import parse_wheel, read_wheel_metadata_file
|
||||
|
||||
from ._compat import BasePath, get_dist_name
|
||||
|
||||
|
||||
class WheelDistribution(importlib.metadata.Distribution):
|
||||
"""An ``importlib.metadata.Distribution`` read from a wheel.
|
||||
|
||||
Although ``importlib.metadata.PathDistribution`` accepts ``zipfile.Path``,
|
||||
its implementation is too "lazy" for pip's needs (we can't keep the ZipFile
|
||||
handle open for the entire lifetime of the distribution object).
|
||||
|
||||
This implementation eagerly reads the entire metadata directory into the
|
||||
memory instead, and operates from that.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
files: Mapping[pathlib.PurePosixPath, bytes],
|
||||
info_location: pathlib.PurePosixPath,
|
||||
) -> None:
|
||||
self._files = files
|
||||
self.info_location = info_location
|
||||
|
||||
@classmethod
|
||||
def from_zipfile(
|
||||
cls,
|
||||
zf: zipfile.ZipFile,
|
||||
name: str,
|
||||
location: str,
|
||||
) -> "WheelDistribution":
|
||||
info_dir, _ = parse_wheel(zf, name)
|
||||
paths = (
|
||||
(name, pathlib.PurePosixPath(name.split("/", 1)[-1]))
|
||||
for name in zf.namelist()
|
||||
if name.startswith(f"{info_dir}/")
|
||||
)
|
||||
files = {
|
||||
relpath: read_wheel_metadata_file(zf, fullpath)
|
||||
for fullpath, relpath in paths
|
||||
}
|
||||
info_location = pathlib.PurePosixPath(location, info_dir)
|
||||
return cls(files, info_location)
|
||||
|
||||
def iterdir(self, path: InfoPath) -> Iterator[pathlib.PurePosixPath]:
|
||||
# Only allow iterating through the metadata directory.
|
||||
if pathlib.PurePosixPath(str(path)) in self._files:
|
||||
return iter(self._files)
|
||||
raise FileNotFoundError(path)
|
||||
|
||||
def read_text(self, filename: str) -> Optional[str]:
|
||||
try:
|
||||
data = self._files[pathlib.PurePosixPath(filename)]
|
||||
except KeyError:
|
||||
return None
|
||||
try:
|
||||
text = data.decode("utf-8")
|
||||
except UnicodeDecodeError as e:
|
||||
wheel = self.info_location.parent
|
||||
error = f"Error decoding metadata for {wheel}: {e} in {filename} file"
|
||||
raise UnsupportedWheel(error)
|
||||
return text
|
||||
|
||||
|
||||
class RequiresEntry(NamedTuple):
|
||||
requirement: str
|
||||
extra: str
|
||||
marker: str
|
||||
|
||||
|
||||
class Distribution(BaseDistribution):
|
||||
def __init__(
|
||||
self,
|
||||
dist: importlib.metadata.Distribution,
|
||||
info_location: Optional[BasePath],
|
||||
installed_location: Optional[BasePath],
|
||||
) -> None:
|
||||
self._dist = dist
|
||||
self._info_location = info_location
|
||||
self._installed_location = installed_location
|
||||
|
||||
@classmethod
|
||||
def from_directory(cls, directory: str) -> BaseDistribution:
|
||||
info_location = pathlib.Path(directory)
|
||||
dist = importlib.metadata.Distribution.at(info_location)
|
||||
return cls(dist, info_location, info_location.parent)
|
||||
|
||||
@classmethod
|
||||
def from_wheel(cls, wheel: Wheel, name: str) -> BaseDistribution:
|
||||
try:
|
||||
with wheel.as_zipfile() as zf:
|
||||
dist = WheelDistribution.from_zipfile(zf, name, wheel.location)
|
||||
except zipfile.BadZipFile as e:
|
||||
raise InvalidWheel(wheel.location, name) from e
|
||||
except UnsupportedWheel as e:
|
||||
raise UnsupportedWheel(f"{name} has an invalid wheel, {e}")
|
||||
return cls(dist, dist.info_location, pathlib.PurePosixPath(wheel.location))
|
||||
|
||||
@property
|
||||
def location(self) -> Optional[str]:
|
||||
if self._info_location is None:
|
||||
return None
|
||||
return str(self._info_location.parent)
|
||||
|
||||
@property
|
||||
def info_location(self) -> Optional[str]:
|
||||
if self._info_location is None:
|
||||
return None
|
||||
return str(self._info_location)
|
||||
|
||||
@property
|
||||
def installed_location(self) -> Optional[str]:
|
||||
if self._installed_location is None:
|
||||
return None
|
||||
return normalize_path(str(self._installed_location))
|
||||
|
||||
def _get_dist_name_from_location(self) -> Optional[str]:
|
||||
"""Try to get the name from the metadata directory name.
|
||||
|
||||
This is much faster than reading metadata.
|
||||
"""
|
||||
if self._info_location is None:
|
||||
return None
|
||||
stem, suffix = os.path.splitext(self._info_location.name)
|
||||
if suffix not in (".dist-info", ".egg-info"):
|
||||
return None
|
||||
return stem.split("-", 1)[0]
|
||||
|
||||
@property
|
||||
def canonical_name(self) -> NormalizedName:
|
||||
name = self._get_dist_name_from_location() or get_dist_name(self._dist)
|
||||
return canonicalize_name(name)
|
||||
|
||||
@property
|
||||
def version(self) -> DistributionVersion:
|
||||
return parse_version(self._dist.version)
|
||||
|
||||
def is_file(self, path: InfoPath) -> bool:
|
||||
return self._dist.read_text(str(path)) is not None
|
||||
|
||||
def iter_distutils_script_names(self) -> Iterator[str]:
|
||||
# A distutils installation is always "flat" (not in e.g. egg form), so
|
||||
# if this distribution's info location is NOT a pathlib.Path (but e.g.
|
||||
# zipfile.Path), it can never contain any distutils scripts.
|
||||
if not isinstance(self._info_location, pathlib.Path):
|
||||
return
|
||||
for child in self._info_location.joinpath("scripts").iterdir():
|
||||
yield child.name
|
||||
|
||||
def read_text(self, path: InfoPath) -> str:
|
||||
content = self._dist.read_text(str(path))
|
||||
if content is None:
|
||||
raise FileNotFoundError(path)
|
||||
return content
|
||||
|
||||
def iter_entry_points(self) -> Iterable[BaseEntryPoint]:
|
||||
# importlib.metadata's EntryPoint structure sasitfies BaseEntryPoint.
|
||||
return self._dist.entry_points
|
||||
|
||||
@property
|
||||
def metadata(self) -> email.message.Message:
|
||||
return self._dist.metadata
|
||||
|
||||
def _iter_requires_txt_entries(self) -> Iterator[RequiresEntry]:
|
||||
"""Parse a ``requires.txt`` in an egg-info directory.
|
||||
|
||||
This is an INI-ish format where an egg-info stores dependencies. A
|
||||
section name describes extra other environment markers, while each entry
|
||||
is an arbitrary string (not a key-value pair) representing a dependency
|
||||
as a requirement string (no markers).
|
||||
|
||||
There is a construct in ``importlib.metadata`` called ``Sectioned`` that
|
||||
does mostly the same, but the format is currently considered private.
|
||||
"""
|
||||
content = self._dist.read_text("requires.txt")
|
||||
if content is None:
|
||||
return
|
||||
extra = marker = "" # Section-less entries don't have markers.
|
||||
for line in content.splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"): # Comment; ignored.
|
||||
continue
|
||||
if line.startswith("[") and line.endswith("]"): # A section header.
|
||||
extra, _, marker = line.strip("[]").partition(":")
|
||||
continue
|
||||
yield RequiresEntry(requirement=line, extra=extra, marker=marker)
|
||||
|
||||
def _iter_egg_info_extras(self) -> Iterable[str]:
|
||||
"""Get extras from the egg-info directory."""
|
||||
known_extras = {""}
|
||||
for entry in self._iter_requires_txt_entries():
|
||||
if entry.extra in known_extras:
|
||||
continue
|
||||
known_extras.add(entry.extra)
|
||||
yield entry.extra
|
||||
|
||||
def iter_provided_extras(self) -> Iterable[str]:
|
||||
iterator = (
|
||||
self._dist.metadata.get_all("Provides-Extra")
|
||||
or self._iter_egg_info_extras()
|
||||
)
|
||||
return (safe_extra(extra) for extra in iterator)
|
||||
|
||||
def _iter_egg_info_dependencies(self) -> Iterable[str]:
|
||||
"""Get distribution dependencies from the egg-info directory.
|
||||
|
||||
To ease parsing, this converts a legacy dependency entry into a PEP 508
|
||||
requirement string. Like ``_iter_requires_txt_entries()``, there is code
|
||||
in ``importlib.metadata`` that does mostly the same, but not do exactly
|
||||
what we need.
|
||||
|
||||
Namely, ``importlib.metadata`` does not normalize the extra name before
|
||||
putting it into the requirement string, which causes marker comparison
|
||||
to fail because the dist-info format do normalize. This is consistent in
|
||||
all currently available PEP 517 backends, although not standardized.
|
||||
"""
|
||||
for entry in self._iter_requires_txt_entries():
|
||||
if entry.extra and entry.marker:
|
||||
marker = f'({entry.marker}) and extra == "{safe_extra(entry.extra)}"'
|
||||
elif entry.extra:
|
||||
marker = f'extra == "{safe_extra(entry.extra)}"'
|
||||
elif entry.marker:
|
||||
marker = entry.marker
|
||||
else:
|
||||
marker = ""
|
||||
if marker:
|
||||
yield f"{entry.requirement} ; {marker}"
|
||||
else:
|
||||
yield entry.requirement
|
||||
|
||||
def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requirement]:
|
||||
req_string_iterator = (
|
||||
self._dist.metadata.get_all("Requires-Dist")
|
||||
or self._iter_egg_info_dependencies()
|
||||
)
|
||||
contexts: Sequence[Dict[str, str]] = [{"extra": safe_extra(e)} for e in extras]
|
||||
for req_string in req_string_iterator:
|
||||
req = Requirement(req_string)
|
||||
if not req.marker:
|
||||
yield req
|
||||
elif not extras and req.marker.evaluate({"extra": ""}):
|
||||
yield req
|
||||
elif any(req.marker.evaluate(context) for context in contexts):
|
||||
yield req
|
||||
@@ -0,0 +1,163 @@
|
||||
import functools
|
||||
import importlib.metadata
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
import zipfile
|
||||
import zipimport
|
||||
from typing import Iterator, List, Optional, Sequence, Set, Tuple
|
||||
|
||||
from pipenv.patched.notpip._vendor.packaging.utils import NormalizedName, canonicalize_name
|
||||
|
||||
from pipenv.patched.notpip._internal.metadata.base import BaseDistribution, BaseEnvironment
|
||||
from pipenv.patched.notpip._internal.utils.deprecation import deprecated
|
||||
|
||||
from ._compat import BasePath, get_dist_name, get_info_location
|
||||
from ._dists import Distribution
|
||||
|
||||
|
||||
class _DistributionFinder:
|
||||
"""Finder to locate distributions.
|
||||
|
||||
The main purpose of this class is to memoize found distributions' names, so
|
||||
only one distribution is returned for each package name. At lot of pip code
|
||||
assumes this (because it is setuptools's behavior), and not doing the same
|
||||
can potentially cause a distribution in lower precedence path to override a
|
||||
higher precedence one if the caller is not careful.
|
||||
|
||||
Eventually we probably want to make it possible to see lower precedence
|
||||
installations as well. It's useful feature, after all.
|
||||
"""
|
||||
|
||||
FoundResult = Tuple[importlib.metadata.Distribution, Optional[BasePath]]
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._found_names: Set[NormalizedName] = set()
|
||||
|
||||
def _find_impl(self, location: str) -> Iterator[FoundResult]:
|
||||
"""Find distributions in a location."""
|
||||
# To know exactly where we find a distribution, we have to feed in the
|
||||
# paths one by one, instead of dumping the list to importlib.metadata.
|
||||
for dist in importlib.metadata.distributions(path=[location]):
|
||||
normalized_name = canonicalize_name(get_dist_name(dist))
|
||||
if normalized_name in self._found_names:
|
||||
continue
|
||||
self._found_names.add(normalized_name)
|
||||
info_location = get_info_location(dist)
|
||||
yield dist, info_location
|
||||
|
||||
def find(self, location: str) -> Iterator[BaseDistribution]:
|
||||
"""Find distributions in a location.
|
||||
|
||||
The path can be either a directory, or a ZIP archive.
|
||||
"""
|
||||
for dist, info_location in self._find_impl(location):
|
||||
if info_location is None:
|
||||
installed_location: Optional[BasePath] = None
|
||||
else:
|
||||
installed_location = info_location.parent
|
||||
yield Distribution(dist, info_location, installed_location)
|
||||
|
||||
def find_linked(self, location: str) -> Iterator[BaseDistribution]:
|
||||
"""Read location in egg-link files and return distributions in there.
|
||||
|
||||
The path should be a directory; otherwise this returns nothing. This
|
||||
follows how setuptools does this for compatibility. The first non-empty
|
||||
line in the egg-link is read as a path (resolved against the egg-link's
|
||||
containing directory if relative). Distributions found at that linked
|
||||
location are returned.
|
||||
"""
|
||||
path = pathlib.Path(location)
|
||||
if not path.is_dir():
|
||||
return
|
||||
for child in path.iterdir():
|
||||
if child.suffix != ".egg-link":
|
||||
continue
|
||||
with child.open() as f:
|
||||
lines = (line.strip() for line in f)
|
||||
target_rel = next((line for line in lines if line), "")
|
||||
if not target_rel:
|
||||
continue
|
||||
target_location = str(path.joinpath(target_rel))
|
||||
for dist, info_location in self._find_impl(target_location):
|
||||
yield Distribution(dist, info_location, path)
|
||||
|
||||
def _find_eggs_in_dir(self, location: str) -> Iterator[BaseDistribution]:
|
||||
from pipenv.patched.notpip._vendor.pkg_resources import find_distributions
|
||||
|
||||
from pipenv.patched.notpip._internal.metadata import pkg_resources as legacy
|
||||
|
||||
with os.scandir(location) as it:
|
||||
for entry in it:
|
||||
if not entry.name.endswith(".egg"):
|
||||
continue
|
||||
for dist in find_distributions(entry.path):
|
||||
yield legacy.Distribution(dist)
|
||||
|
||||
def _find_eggs_in_zip(self, location: str) -> Iterator[BaseDistribution]:
|
||||
from pipenv.patched.notpip._vendor.pkg_resources import find_eggs_in_zip
|
||||
|
||||
from pipenv.patched.notpip._internal.metadata import pkg_resources as legacy
|
||||
|
||||
try:
|
||||
importer = zipimport.zipimporter(location)
|
||||
except zipimport.ZipImportError:
|
||||
return
|
||||
for dist in find_eggs_in_zip(importer, location):
|
||||
yield legacy.Distribution(dist)
|
||||
|
||||
def find_eggs(self, location: str) -> Iterator[BaseDistribution]:
|
||||
"""Find eggs in a location.
|
||||
|
||||
This actually uses the old *pkg_resources* backend. We likely want to
|
||||
deprecate this so we can eventually remove the *pkg_resources*
|
||||
dependency entirely. Before that, this should first emit a deprecation
|
||||
warning for some versions when using the fallback since importing
|
||||
*pkg_resources* is slow for those who don't need it.
|
||||
"""
|
||||
if os.path.isdir(location):
|
||||
yield from self._find_eggs_in_dir(location)
|
||||
if zipfile.is_zipfile(location):
|
||||
yield from self._find_eggs_in_zip(location)
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=None) # Warn a distribution exactly once.
|
||||
def _emit_egg_deprecation(location: Optional[str]) -> None:
|
||||
deprecated(
|
||||
reason=f"Loading egg at {location} is deprecated.",
|
||||
replacement="to use pip for package installation.",
|
||||
gone_in=None,
|
||||
)
|
||||
|
||||
|
||||
class Environment(BaseEnvironment):
|
||||
def __init__(self, paths: Sequence[str]) -> None:
|
||||
self._paths = paths
|
||||
|
||||
@classmethod
|
||||
def default(cls) -> BaseEnvironment:
|
||||
return cls(sys.path)
|
||||
|
||||
@classmethod
|
||||
def from_paths(cls, paths: Optional[List[str]]) -> BaseEnvironment:
|
||||
if paths is None:
|
||||
return cls(sys.path)
|
||||
return cls(paths)
|
||||
|
||||
def _iter_distributions(self) -> Iterator[BaseDistribution]:
|
||||
finder = _DistributionFinder()
|
||||
for location in self._paths:
|
||||
yield from finder.find(location)
|
||||
for dist in finder.find_eggs(location):
|
||||
# _emit_egg_deprecation(dist.location) # TODO: Enable this.
|
||||
yield dist
|
||||
# This must go last because that's how pkg_resources tie-breaks.
|
||||
yield from finder.find_linked(location)
|
||||
|
||||
def get_distribution(self, name: str) -> Optional[BaseDistribution]:
|
||||
matches = (
|
||||
distribution
|
||||
for distribution in self.iter_all_distributions()
|
||||
if distribution.canonical_name == canonicalize_name(name)
|
||||
)
|
||||
return next(matches, None)
|
||||
@@ -2,7 +2,6 @@ import email.message
|
||||
import email.parser
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import zipfile
|
||||
from typing import Collection, Iterable, Iterator, List, Mapping, NamedTuple, Optional
|
||||
|
||||
@@ -12,7 +11,8 @@ from pipenv.patched.notpip._vendor.packaging.utils import NormalizedName, canoni
|
||||
from pipenv.patched.notpip._vendor.packaging.version import parse as parse_version
|
||||
|
||||
from pipenv.patched.notpip._internal.exceptions import InvalidWheel, NoneMetadataError, UnsupportedWheel
|
||||
from pipenv.patched.notpip._internal.utils.misc import display_path
|
||||
from pipenv.patched.notpip._internal.utils.egg_link import egg_link_path_from_location
|
||||
from pipenv.patched.notpip._internal.utils.misc import display_path, normalize_path
|
||||
from pipenv.patched.notpip._internal.utils.wheel import parse_wheel, read_wheel_metadata_file
|
||||
|
||||
from .base import (
|
||||
@@ -73,7 +73,7 @@ class Distribution(BaseDistribution):
|
||||
self._dist = dist
|
||||
|
||||
@classmethod
|
||||
def from_directory(cls, directory: str) -> "Distribution":
|
||||
def from_directory(cls, directory: str) -> BaseDistribution:
|
||||
dist_dir = directory.rstrip(os.sep)
|
||||
|
||||
# Build a PathMetadata object, from path to metadata. :wink:
|
||||
@@ -93,14 +93,7 @@ class Distribution(BaseDistribution):
|
||||
return cls(dist)
|
||||
|
||||
@classmethod
|
||||
def from_wheel(cls, wheel: Wheel, name: str) -> "Distribution":
|
||||
"""Load the distribution from a given wheel.
|
||||
|
||||
:raises InvalidWheel: Whenever loading of the wheel causes a
|
||||
:py:exc:`zipfile.BadZipFile` exception to be thrown.
|
||||
:raises UnsupportedWheel: If the wheel is a valid zip, but malformed
|
||||
internally.
|
||||
"""
|
||||
def from_wheel(cls, wheel: Wheel, name: str) -> BaseDistribution:
|
||||
try:
|
||||
with wheel.as_zipfile() as zf:
|
||||
info_dir, _ = parse_wheel(zf, name)
|
||||
@@ -124,6 +117,17 @@ class Distribution(BaseDistribution):
|
||||
def location(self) -> Optional[str]:
|
||||
return self._dist.location
|
||||
|
||||
@property
|
||||
def installed_location(self) -> Optional[str]:
|
||||
egg_link = egg_link_path_from_location(self.raw_name)
|
||||
if egg_link:
|
||||
location = egg_link
|
||||
elif self.location:
|
||||
location = self.location
|
||||
else:
|
||||
return None
|
||||
return normalize_path(location)
|
||||
|
||||
@property
|
||||
def info_location(self) -> Optional[str]:
|
||||
return self._dist.egg_info
|
||||
@@ -149,14 +153,8 @@ class Distribution(BaseDistribution):
|
||||
def is_file(self, path: InfoPath) -> bool:
|
||||
return self._dist.has_metadata(str(path))
|
||||
|
||||
def iterdir(self, path: InfoPath) -> Iterator[pathlib.PurePosixPath]:
|
||||
name = str(path)
|
||||
if not self._dist.has_metadata(name):
|
||||
raise FileNotFoundError(name)
|
||||
if not self._dist.isdir(name):
|
||||
raise NotADirectoryError(name)
|
||||
for child in self._dist.metadata_listdir(name):
|
||||
yield pathlib.PurePosixPath(path, child)
|
||||
def iter_distutils_script_names(self) -> Iterator[str]:
|
||||
yield from self._dist.metadata_listdir("scripts")
|
||||
|
||||
def read_text(self, path: InfoPath) -> str:
|
||||
name = str(path)
|
||||
@@ -217,6 +215,10 @@ class Environment(BaseEnvironment):
|
||||
def from_paths(cls, paths: Optional[List[str]]) -> BaseEnvironment:
|
||||
return cls(pkg_resources.WorkingSet(paths))
|
||||
|
||||
def _iter_distributions(self) -> Iterator[BaseDistribution]:
|
||||
for dist in self._ws:
|
||||
yield Distribution(dist)
|
||||
|
||||
def _search_distribution(self, name: str) -> Optional[BaseDistribution]:
|
||||
"""Find a distribution matching the ``name`` in the environment.
|
||||
|
||||
@@ -224,7 +226,7 @@ class Environment(BaseEnvironment):
|
||||
match the behavior of ``pkg_resources.get_distribution()``.
|
||||
"""
|
||||
canonical_name = canonicalize_name(name)
|
||||
for dist in self.iter_distributions():
|
||||
for dist in self.iter_all_distributions():
|
||||
if dist.canonical_name == canonical_name:
|
||||
return dist
|
||||
return None
|
||||
@@ -250,7 +252,3 @@ class Environment(BaseEnvironment):
|
||||
except pkg_resources.DistributionNotFound:
|
||||
return None
|
||||
return self._search_distribution(name)
|
||||
|
||||
def _iter_distributions(self) -> Iterator[BaseDistribution]:
|
||||
for dist in self._ws:
|
||||
yield Distribution(dist)
|
||||
|
||||
@@ -74,14 +74,10 @@ class VcsInfo:
|
||||
vcs: str,
|
||||
commit_id: str,
|
||||
requested_revision: Optional[str] = None,
|
||||
resolved_revision: Optional[str] = None,
|
||||
resolved_revision_type: Optional[str] = None,
|
||||
) -> None:
|
||||
self.vcs = vcs
|
||||
self.requested_revision = requested_revision
|
||||
self.commit_id = commit_id
|
||||
self.resolved_revision = resolved_revision
|
||||
self.resolved_revision_type = resolved_revision_type
|
||||
|
||||
@classmethod
|
||||
def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["VcsInfo"]:
|
||||
@@ -91,8 +87,6 @@ class VcsInfo:
|
||||
vcs=_get_required(d, str, "vcs"),
|
||||
commit_id=_get_required(d, str, "commit_id"),
|
||||
requested_revision=_get(d, str, "requested_revision"),
|
||||
resolved_revision=_get(d, str, "resolved_revision"),
|
||||
resolved_revision_type=_get(d, str, "resolved_revision_type"),
|
||||
)
|
||||
|
||||
def _to_dict(self) -> Dict[str, Any]:
|
||||
@@ -100,8 +94,6 @@ class VcsInfo:
|
||||
vcs=self.vcs,
|
||||
requested_revision=self.requested_revision,
|
||||
commit_id=self.commit_id,
|
||||
resolved_revision=self.resolved_revision,
|
||||
resolved_revision_type=self.resolved_revision_type,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import os
|
||||
from contextlib import contextmanager
|
||||
from typing import Iterator, Optional
|
||||
from typing import Generator, Optional
|
||||
|
||||
from pipenv.patched.notpip._vendor.cachecontrol.cache import BaseCache
|
||||
from pipenv.patched.notpip._vendor.cachecontrol.caches import FileCache
|
||||
@@ -18,7 +18,7 @@ def is_from_cache(response: Response) -> bool:
|
||||
|
||||
|
||||
@contextmanager
|
||||
def suppressed_cache_errors() -> Iterator[None]:
|
||||
def suppressed_cache_errors() -> Generator[None, None, None]:
|
||||
"""If we can't access the cache then we can just skip caching and process
|
||||
requests as if caching wasn't enabled.
|
||||
"""
|
||||
|
||||
@@ -5,7 +5,7 @@ __all__ = ["HTTPRangeRequestUnsupported", "dist_from_wheel_url"]
|
||||
from bisect import bisect_left, bisect_right
|
||||
from contextlib import contextmanager
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import Any, Dict, Iterator, List, Optional, Tuple
|
||||
from typing import Any, Dict, Generator, List, Optional, Tuple
|
||||
from zipfile import BadZipfile, ZipFile
|
||||
|
||||
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
|
||||
@@ -139,7 +139,7 @@ class LazyZipOverHTTP:
|
||||
return self._file.__exit__(*exc)
|
||||
|
||||
@contextmanager
|
||||
def _stay(self) -> Iterator[None]:
|
||||
def _stay(self) -> Generator[None, None, None]:
|
||||
"""Return a context manager keeping the position.
|
||||
|
||||
At the end of the block, seek back to original position.
|
||||
@@ -177,8 +177,8 @@ class LazyZipOverHTTP:
|
||||
|
||||
def _merge(
|
||||
self, start: int, end: int, left: int, right: int
|
||||
) -> Iterator[Tuple[int, int]]:
|
||||
"""Return an iterator of intervals to be fetched.
|
||||
) -> Generator[Tuple[int, int], None, None]:
|
||||
"""Return a generator of intervals to be fetched.
|
||||
|
||||
Args:
|
||||
start (int): Start of needed interval
|
||||
|
||||
@@ -15,7 +15,7 @@ import subprocess
|
||||
import sys
|
||||
import urllib.parse
|
||||
import warnings
|
||||
from typing import Any, Dict, Iterator, List, Mapping, Optional, Sequence, Tuple, Union
|
||||
from typing import Any, Dict, Generator, List, Mapping, Optional, Sequence, Tuple, Union
|
||||
|
||||
from pipenv.patched.notpip._vendor import requests, urllib3
|
||||
from pipenv.patched.notpip._vendor.cachecontrol import CacheControlAdapter
|
||||
@@ -374,7 +374,7 @@ class PipSession(requests.Session):
|
||||
# Mount wildcard ports for the same host.
|
||||
self.mount(build_url_from_netloc(host) + ":", self._trusted_host_adapter)
|
||||
|
||||
def iter_secure_origins(self) -> Iterator[SecureOrigin]:
|
||||
def iter_secure_origins(self) -> Generator[SecureOrigin, None, None]:
|
||||
yield from SECURE_ORIGINS
|
||||
for host, port in self.pip_trusted_origins:
|
||||
yield ("*", host, "*" if port is None else port)
|
||||
@@ -449,6 +449,8 @@ class PipSession(requests.Session):
|
||||
def request(self, method: str, url: str, *args: Any, **kwargs: Any) -> Response:
|
||||
# Allow setting a default timeout on a session
|
||||
kwargs.setdefault("timeout", self.timeout)
|
||||
# Allow setting a default proxies on a session
|
||||
kwargs.setdefault("proxies", self.proxies)
|
||||
|
||||
# Dispatch the actual request
|
||||
return super().request(method, url, *args, **kwargs)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Dict, Iterator
|
||||
from typing import Dict, Generator
|
||||
|
||||
from pipenv.patched.notpip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
|
||||
|
||||
@@ -56,7 +56,7 @@ def raise_for_status(resp: Response) -> None:
|
||||
|
||||
def response_chunks(
|
||||
response: Response, chunk_size: int = CONTENT_CHUNK_SIZE
|
||||
) -> Iterator[bytes]:
|
||||
) -> Generator[bytes, None, None]:
|
||||
"""Given a requests Response, provide the data chunks."""
|
||||
try:
|
||||
# Special case for urllib3.
|
||||
|
||||
+10
-10
@@ -3,7 +3,7 @@ import hashlib
|
||||
import logging
|
||||
import os
|
||||
from types import TracebackType
|
||||
from typing import Dict, Iterator, Optional, Set, Type, Union
|
||||
from typing import Dict, Generator, Optional, Set, Type, Union
|
||||
|
||||
from pipenv.patched.notpip._internal.models.link import Link
|
||||
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
|
||||
@@ -13,7 +13,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def update_env_context_manager(**changes: str) -> Iterator[None]:
|
||||
def update_env_context_manager(**changes: str) -> Generator[None, None, None]:
|
||||
target = os.environ
|
||||
|
||||
# Save values from the target and change them.
|
||||
@@ -39,25 +39,25 @@ def update_env_context_manager(**changes: str) -> Iterator[None]:
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def get_requirement_tracker() -> Iterator["RequirementTracker"]:
|
||||
root = os.environ.get("PIP_REQ_TRACKER")
|
||||
def get_build_tracker() -> Generator["BuildTracker", None, None]:
|
||||
root = os.environ.get("PIP_BUILD_TRACKER")
|
||||
with contextlib.ExitStack() as ctx:
|
||||
if root is None:
|
||||
root = ctx.enter_context(TempDirectory(kind="req-tracker")).path
|
||||
ctx.enter_context(update_env_context_manager(PIP_REQ_TRACKER=root))
|
||||
root = ctx.enter_context(TempDirectory(kind="build-tracker")).path
|
||||
ctx.enter_context(update_env_context_manager(PIP_BUILD_TRACKER=root))
|
||||
logger.debug("Initialized build tracking at %s", root)
|
||||
|
||||
with RequirementTracker(root) as tracker:
|
||||
with BuildTracker(root) as tracker:
|
||||
yield tracker
|
||||
|
||||
|
||||
class RequirementTracker:
|
||||
class BuildTracker:
|
||||
def __init__(self, root: str) -> None:
|
||||
self._root = root
|
||||
self._entries: Set[InstallRequirement] = set()
|
||||
logger.debug("Created build tracker: %s", self._root)
|
||||
|
||||
def __enter__(self) -> "RequirementTracker":
|
||||
def __enter__(self) -> "BuildTracker":
|
||||
logger.debug("Entered build tracker: %s", self._root)
|
||||
return self
|
||||
|
||||
@@ -118,7 +118,7 @@ class RequirementTracker:
|
||||
logger.debug("Removed build tracker: %r", self._root)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def track(self, req: InstallRequirement) -> Iterator[None]:
|
||||
def track(self, req: InstallRequirement) -> Generator[None, None, None]:
|
||||
self.add(req)
|
||||
yield
|
||||
self.remove(req)
|
||||
@@ -1,7 +1,7 @@
|
||||
import collections
|
||||
import logging
|
||||
import os
|
||||
from typing import Container, Dict, Iterable, Iterator, List, NamedTuple, Optional, Set
|
||||
from typing import Container, Dict, Generator, Iterable, List, NamedTuple, Optional, Set
|
||||
|
||||
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
|
||||
from pipenv.patched.notpip._vendor.packaging.version import Version
|
||||
@@ -31,7 +31,7 @@ def freeze(
|
||||
isolated: bool = False,
|
||||
exclude_editable: bool = False,
|
||||
skip: Container[str] = (),
|
||||
) -> Iterator[str]:
|
||||
) -> Generator[str, None, None]:
|
||||
installations: Dict[str, FrozenRequirement] = {}
|
||||
|
||||
dists = get_environment(paths).iter_installed_distributions(
|
||||
|
||||
@@ -22,6 +22,7 @@ from typing import (
|
||||
BinaryIO,
|
||||
Callable,
|
||||
Dict,
|
||||
Generator,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
@@ -589,7 +590,7 @@ def _install_wheel(
|
||||
file.save()
|
||||
record_installed(file.src_record_path, file.dest_path, file.changed)
|
||||
|
||||
def pyc_source_file_paths() -> Iterator[str]:
|
||||
def pyc_source_file_paths() -> Generator[str, None, None]:
|
||||
# We de-duplicate installation paths, since there can be overlap (e.g.
|
||||
# file in .data maps to same location as file in wheel root).
|
||||
# Sorting installation paths makes it easier to reproduce and debug
|
||||
@@ -656,7 +657,7 @@ def _install_wheel(
|
||||
generated_file_mode = 0o666 & ~current_umask()
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _generate_file(path: str, **kwargs: Any) -> Iterator[BinaryIO]:
|
||||
def _generate_file(path: str, **kwargs: Any) -> Generator[BinaryIO, None, None]:
|
||||
with adjacent_tmp_file(path, **kwargs) as f:
|
||||
yield f
|
||||
os.chmod(f.name, generated_file_mode)
|
||||
@@ -706,7 +707,7 @@ def _install_wheel(
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def req_error_context(req_description: str) -> Iterator[None]:
|
||||
def req_error_context(req_description: str) -> Generator[None, None, None]:
|
||||
try:
|
||||
yield
|
||||
except InstallationError as e:
|
||||
|
||||
@@ -33,12 +33,11 @@ from pipenv.patched.notpip._internal.network.lazy_wheel import (
|
||||
dist_from_wheel_url,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.network.session import PipSession
|
||||
from pipenv.patched.notpip._internal.operations.build.build_tracker import BuildTracker
|
||||
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
|
||||
from pipenv.patched.notpip._internal.req.req_tracker import RequirementTracker
|
||||
from pipenv.patched.notpip._internal.utils.filesystem import copy2_fixed
|
||||
from pipenv.patched.notpip._internal.utils.hashes import Hashes, MissingHashes
|
||||
from pipenv.patched.notpip._internal.utils.logging import indent_log
|
||||
from pipenv.patched.notpip._internal.utils.misc import display_path, hide_url, is_installable_dir, rmtree
|
||||
from pipenv.patched.notpip._internal.utils.misc import display_path, hide_url, is_installable_dir
|
||||
from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory
|
||||
from pipenv.patched.notpip._internal.utils.unpacking import unpack_file
|
||||
from pipenv.patched.notpip._internal.vcs import vcs
|
||||
@@ -48,14 +47,17 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
def _get_prepared_distribution(
|
||||
req: InstallRequirement,
|
||||
req_tracker: RequirementTracker,
|
||||
build_tracker: BuildTracker,
|
||||
finder: PackageFinder,
|
||||
build_isolation: bool,
|
||||
check_build_deps: bool,
|
||||
) -> BaseDistribution:
|
||||
"""Prepare a distribution for installation."""
|
||||
abstract_dist = make_distribution_for_install_requirement(req)
|
||||
with req_tracker.track(req):
|
||||
abstract_dist.prepare_distribution_metadata(finder, build_isolation)
|
||||
with build_tracker.track(req):
|
||||
abstract_dist.prepare_distribution_metadata(
|
||||
finder, build_isolation, check_build_deps
|
||||
)
|
||||
return abstract_dist.get_metadata_distribution()
|
||||
|
||||
|
||||
@@ -98,55 +100,6 @@ def get_http_url(
|
||||
return File(from_path, content_type)
|
||||
|
||||
|
||||
def _copy2_ignoring_special_files(src: str, dest: str) -> None:
|
||||
"""Copying special files is not supported, but as a convenience to users
|
||||
we skip errors copying them. This supports tools that may create e.g.
|
||||
socket files in the project source directory.
|
||||
"""
|
||||
try:
|
||||
copy2_fixed(src, dest)
|
||||
except shutil.SpecialFileError as e:
|
||||
# SpecialFileError may be raised due to either the source or
|
||||
# destination. If the destination was the cause then we would actually
|
||||
# care, but since the destination directory is deleted prior to
|
||||
# copy we ignore all of them assuming it is caused by the source.
|
||||
logger.warning(
|
||||
"Ignoring special file error '%s' encountered copying %s to %s.",
|
||||
str(e),
|
||||
src,
|
||||
dest,
|
||||
)
|
||||
|
||||
|
||||
def _copy_source_tree(source: str, target: str) -> None:
|
||||
target_abspath = os.path.abspath(target)
|
||||
target_basename = os.path.basename(target_abspath)
|
||||
target_dirname = os.path.dirname(target_abspath)
|
||||
|
||||
def ignore(d: str, names: List[str]) -> List[str]:
|
||||
skipped: List[str] = []
|
||||
if d == source:
|
||||
# Pulling in those directories can potentially be very slow,
|
||||
# exclude the following directories if they appear in the top
|
||||
# level dir (and only it).
|
||||
# See discussion at https://github.com/pypa/pip/pull/6770
|
||||
skipped += [".tox", ".nox"]
|
||||
if os.path.abspath(d) == target_dirname:
|
||||
# Prevent an infinite recursion if the target is in source.
|
||||
# This can happen when TMPDIR is set to ${PWD}/...
|
||||
# and we copy PWD to TMPDIR.
|
||||
skipped += [target_basename]
|
||||
return skipped
|
||||
|
||||
shutil.copytree(
|
||||
source,
|
||||
target,
|
||||
ignore=ignore,
|
||||
symlinks=True,
|
||||
copy_function=_copy2_ignoring_special_files,
|
||||
)
|
||||
|
||||
|
||||
def get_file_url(
|
||||
link: Link, download_dir: Optional[str] = None, hashes: Optional[Hashes] = None
|
||||
) -> File:
|
||||
@@ -191,19 +144,7 @@ def unpack_url(
|
||||
unpack_vcs_link(link, location, verbosity=verbosity)
|
||||
return None
|
||||
|
||||
# Once out-of-tree-builds are no longer supported, could potentially
|
||||
# replace the below condition with `assert not link.is_existing_dir`
|
||||
# - unpack_url does not need to be called for in-tree-builds.
|
||||
#
|
||||
# As further cleanup, _copy_source_tree and accompanying tests can
|
||||
# be removed.
|
||||
#
|
||||
# TODO when use-deprecated=out-of-tree-build is removed
|
||||
if link.is_existing_dir():
|
||||
if os.path.isdir(location):
|
||||
rmtree(location)
|
||||
_copy_source_tree(link.file_path, location)
|
||||
return None
|
||||
assert not link.is_existing_dir()
|
||||
|
||||
# file urls
|
||||
if link.is_file:
|
||||
@@ -261,7 +202,8 @@ class RequirementPreparer:
|
||||
download_dir: Optional[str],
|
||||
src_dir: str,
|
||||
build_isolation: bool,
|
||||
req_tracker: RequirementTracker,
|
||||
check_build_deps: bool,
|
||||
build_tracker: BuildTracker,
|
||||
session: PipSession,
|
||||
progress_bar: str,
|
||||
finder: PackageFinder,
|
||||
@@ -269,13 +211,12 @@ class RequirementPreparer:
|
||||
use_user_site: bool,
|
||||
lazy_wheel: bool,
|
||||
verbosity: int,
|
||||
in_tree_build: bool,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.src_dir = src_dir
|
||||
self.build_dir = build_dir
|
||||
self.req_tracker = req_tracker
|
||||
self.build_tracker = build_tracker
|
||||
self._session = session
|
||||
self._download = Downloader(session, progress_bar)
|
||||
self._batch_download = BatchDownloader(session, progress_bar)
|
||||
@@ -288,6 +229,9 @@ class RequirementPreparer:
|
||||
# Is build isolation allowed?
|
||||
self.build_isolation = build_isolation
|
||||
|
||||
# Should check build dependencies?
|
||||
self.check_build_deps = check_build_deps
|
||||
|
||||
# Should hash-checking be required?
|
||||
self.require_hashes = require_hashes
|
||||
|
||||
@@ -300,9 +244,6 @@ class RequirementPreparer:
|
||||
# How verbose should underlying tooling be?
|
||||
self.verbosity = verbosity
|
||||
|
||||
# Should in-tree builds be used for local paths?
|
||||
self.in_tree_build = in_tree_build
|
||||
|
||||
# Memoized downloaded files, as mapping of url: path.
|
||||
self._downloaded: Dict[str, str] = {}
|
||||
|
||||
@@ -336,7 +277,7 @@ class RequirementPreparer:
|
||||
# directory.
|
||||
return
|
||||
assert req.source_dir is None
|
||||
if req.link.is_existing_dir() and self.in_tree_build:
|
||||
if req.link.is_existing_dir():
|
||||
# build local directories in-tree
|
||||
req.source_dir = req.link.file_path
|
||||
return
|
||||
@@ -525,7 +466,7 @@ class RequirementPreparer:
|
||||
self._ensure_link_req_src_dir(req, parallel_builds)
|
||||
hashes = self._get_linked_req_hashes(req)
|
||||
|
||||
if link.is_existing_dir() and self.in_tree_build:
|
||||
if link.is_existing_dir():
|
||||
local_file = None
|
||||
elif link.url not in self._downloaded:
|
||||
try:
|
||||
@@ -555,9 +496,10 @@ class RequirementPreparer:
|
||||
|
||||
dist = _get_prepared_distribution(
|
||||
req,
|
||||
self.req_tracker,
|
||||
self.build_tracker,
|
||||
self.finder,
|
||||
self.build_isolation,
|
||||
self.check_build_deps,
|
||||
)
|
||||
return dist
|
||||
|
||||
@@ -608,9 +550,10 @@ class RequirementPreparer:
|
||||
|
||||
dist = _get_prepared_distribution(
|
||||
req,
|
||||
self.req_tracker,
|
||||
self.build_tracker,
|
||||
self.finder,
|
||||
self.build_isolation,
|
||||
self.check_build_deps,
|
||||
)
|
||||
|
||||
req.check_if_exists(self.use_user_site)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import importlib.util
|
||||
import os
|
||||
from collections import namedtuple
|
||||
from typing import Any, List, Optional
|
||||
@@ -89,9 +90,15 @@ def load_pyproject_toml(
|
||||
|
||||
# If we haven't worked out whether to use PEP 517 yet,
|
||||
# and the user hasn't explicitly stated a preference,
|
||||
# we do so if the project has a pyproject.toml file.
|
||||
# we do so if the project has a pyproject.toml file
|
||||
# or if we cannot import setuptools.
|
||||
|
||||
# We fallback to PEP 517 when without setuptools,
|
||||
# so setuptools can be installed as a default build backend.
|
||||
# For more info see:
|
||||
# https://discuss.python.org/t/pip-without-setuptools-could-the-experience-be-improved/11810/9
|
||||
elif use_pep517 is None:
|
||||
use_pep517 = has_pyproject
|
||||
use_pep517 = has_pyproject or not importlib.util.find_spec("setuptools")
|
||||
|
||||
# At this point, we know whether we're going to use PEP 517.
|
||||
assert use_pep517 is not None
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import collections
|
||||
import logging
|
||||
from typing import Iterator, List, Optional, Sequence, Tuple
|
||||
from typing import Generator, List, Optional, Sequence, Tuple
|
||||
|
||||
from pipenv.patched.notpip._internal.utils.logging import indent_log
|
||||
|
||||
@@ -28,7 +28,7 @@ class InstallationResult:
|
||||
|
||||
def _validate_requirements(
|
||||
requirements: List[InstallRequirement],
|
||||
) -> Iterator[Tuple[str, InstallRequirement]]:
|
||||
) -> Generator[Tuple[str, InstallRequirement], None, None]:
|
||||
for req in requirements:
|
||||
assert req.name, f"invalid to-be-installed requirement: {req}"
|
||||
yield req.name, req
|
||||
|
||||
@@ -207,6 +207,7 @@ def install_req_from_editable(
|
||||
constraint: bool = False,
|
||||
user_supplied: bool = False,
|
||||
permit_editable_wheels: bool = False,
|
||||
config_settings: Optional[Dict[str, str]] = None,
|
||||
) -> InstallRequirement:
|
||||
|
||||
parts = parse_req_from_editable(editable_req)
|
||||
@@ -224,6 +225,7 @@ def install_req_from_editable(
|
||||
install_options=options.get("install_options", []) if options else [],
|
||||
global_options=options.get("global_options", []) if options else [],
|
||||
hash_options=options.get("hashes", {}) if options else {},
|
||||
config_settings=config_settings,
|
||||
extras=parts.extras,
|
||||
)
|
||||
|
||||
@@ -380,6 +382,7 @@ def install_req_from_line(
|
||||
constraint: bool = False,
|
||||
line_source: Optional[str] = None,
|
||||
user_supplied: bool = False,
|
||||
config_settings: Optional[Dict[str, str]] = None,
|
||||
) -> InstallRequirement:
|
||||
"""Creates an InstallRequirement from a name, which might be a
|
||||
requirement, directory containing 'setup.py', filename, or URL.
|
||||
@@ -399,6 +402,7 @@ def install_req_from_line(
|
||||
install_options=options.get("install_options", []) if options else [],
|
||||
global_options=options.get("global_options", []) if options else [],
|
||||
hash_options=options.get("hashes", {}) if options else {},
|
||||
config_settings=config_settings,
|
||||
constraint=constraint,
|
||||
extras=parts.extras,
|
||||
user_supplied=user_supplied,
|
||||
@@ -411,6 +415,7 @@ def install_req_from_req_string(
|
||||
isolated: bool = False,
|
||||
use_pep517: Optional[bool] = None,
|
||||
user_supplied: bool = False,
|
||||
config_settings: Optional[Dict[str, str]] = None,
|
||||
) -> InstallRequirement:
|
||||
try:
|
||||
req = get_requirement(req_string)
|
||||
@@ -440,6 +445,7 @@ def install_req_from_req_string(
|
||||
isolated=isolated,
|
||||
use_pep517=use_pep517,
|
||||
user_supplied=user_supplied,
|
||||
config_settings=config_settings,
|
||||
)
|
||||
|
||||
|
||||
@@ -448,6 +454,7 @@ def install_req_from_parsed_requirement(
|
||||
isolated: bool = False,
|
||||
use_pep517: Optional[bool] = None,
|
||||
user_supplied: bool = False,
|
||||
config_settings: Optional[Dict[str, str]] = None,
|
||||
) -> InstallRequirement:
|
||||
if parsed_req.is_editable:
|
||||
req = install_req_from_editable(
|
||||
@@ -457,6 +464,7 @@ def install_req_from_parsed_requirement(
|
||||
constraint=parsed_req.constraint,
|
||||
isolated=isolated,
|
||||
user_supplied=user_supplied,
|
||||
config_settings=config_settings,
|
||||
)
|
||||
|
||||
else:
|
||||
@@ -469,6 +477,7 @@ def install_req_from_parsed_requirement(
|
||||
constraint=parsed_req.constraint,
|
||||
line_source=parsed_req.line_source,
|
||||
user_supplied=user_supplied,
|
||||
config_settings=config_settings,
|
||||
)
|
||||
return req
|
||||
|
||||
@@ -487,4 +496,6 @@ def install_req_from_link_and_ireq(
|
||||
install_options=ireq.install_options,
|
||||
global_options=ireq.global_options,
|
||||
hash_options=ireq.hash_options,
|
||||
config_settings=ireq.config_settings,
|
||||
user_supplied=ireq.user_supplied,
|
||||
)
|
||||
|
||||
@@ -13,8 +13,8 @@ from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Generator,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
Tuple,
|
||||
@@ -129,7 +129,7 @@ def parse_requirements(
|
||||
finder: Optional["PackageFinder"] = None,
|
||||
options: Optional[optparse.Values] = None,
|
||||
constraint: bool = False,
|
||||
) -> Iterator[ParsedRequirement]:
|
||||
) -> Generator[ParsedRequirement, None, None]:
|
||||
"""Parse a requirements file and yield ParsedRequirement instances.
|
||||
|
||||
:param filename: Path or url of requirements file.
|
||||
@@ -233,6 +233,8 @@ def handle_option_line(
|
||||
index_urls = [opts.index_url]
|
||||
if opts.no_index is True:
|
||||
index_urls = []
|
||||
if opts.extra_index_urls:
|
||||
index_urls.extend(opts.extra_index_urls)
|
||||
if opts.find_links:
|
||||
# FIXME: it would be nice to keep track of the source
|
||||
# of the find_links: support a find-links local path
|
||||
@@ -319,13 +321,15 @@ class RequirementsFileParser:
|
||||
self._session = session
|
||||
self._line_parser = line_parser
|
||||
|
||||
def parse(self, filename: str, constraint: bool) -> Iterator[ParsedLine]:
|
||||
def parse(
|
||||
self, filename: str, constraint: bool
|
||||
) -> Generator[ParsedLine, None, None]:
|
||||
"""Parse a given file, yielding parsed lines."""
|
||||
yield from self._parse_and_recurse(filename, constraint)
|
||||
|
||||
def _parse_and_recurse(
|
||||
self, filename: str, constraint: bool
|
||||
) -> Iterator[ParsedLine]:
|
||||
) -> Generator[ParsedLine, None, None]:
|
||||
for line in self._parse_file(filename, constraint):
|
||||
if not line.is_requirement and (
|
||||
line.opts.requirements or line.opts.constraints
|
||||
@@ -354,7 +358,9 @@ class RequirementsFileParser:
|
||||
else:
|
||||
yield line
|
||||
|
||||
def _parse_file(self, filename: str, constraint: bool) -> Iterator[ParsedLine]:
|
||||
def _parse_file(
|
||||
self, filename: str, constraint: bool
|
||||
) -> Generator[ParsedLine, None, None]:
|
||||
_, content = get_file_content(filename, self._session)
|
||||
|
||||
lines_enum = preprocess(content)
|
||||
|
||||
@@ -46,6 +46,7 @@ from pipenv.patched.notpip._internal.utils.direct_url_helpers import (
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.hashes import Hashes
|
||||
from pipenv.patched.notpip._internal.utils.misc import (
|
||||
ConfiguredPep517HookCaller,
|
||||
ask_path_exists,
|
||||
backup_dir,
|
||||
display_path,
|
||||
@@ -80,6 +81,7 @@ class InstallRequirement:
|
||||
install_options: Optional[List[str]] = None,
|
||||
global_options: Optional[List[str]] = None,
|
||||
hash_options: Optional[Dict[str, List[str]]] = None,
|
||||
config_settings: Optional[Dict[str, str]] = None,
|
||||
constraint: bool = False,
|
||||
extras: Collection[str] = (),
|
||||
user_supplied: bool = False,
|
||||
@@ -138,6 +140,7 @@ class InstallRequirement:
|
||||
self.install_options = install_options if install_options else []
|
||||
self.global_options = global_options if global_options else []
|
||||
self.hash_options = hash_options if hash_options else {}
|
||||
self.config_settings = config_settings
|
||||
# Set to True after successful preparation of this requirement
|
||||
self.prepared = False
|
||||
# User supplied requirement are explicitly requested for installation
|
||||
@@ -470,7 +473,8 @@ class InstallRequirement:
|
||||
requires, backend, check, backend_path = pyproject_toml_data
|
||||
self.requirements_to_check = check
|
||||
self.pyproject_requires = requires
|
||||
self.pep517_backend = Pep517HookCaller(
|
||||
self.pep517_backend = ConfiguredPep517HookCaller(
|
||||
self,
|
||||
self.unpacked_source_directory,
|
||||
backend,
|
||||
backend_path=backend_path,
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
from typing import Dict, Iterable, List, Optional, Tuple
|
||||
from typing import Dict, List
|
||||
|
||||
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
from pipenv.patched.notpip._internal.exceptions import InstallationError
|
||||
from pipenv.patched.notpip._internal.models.wheel import Wheel
|
||||
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
|
||||
from pipenv.patched.notpip._internal.utils import compatibility_tags
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -51,123 +48,6 @@ class RequirementSet:
|
||||
project_name = canonicalize_name(install_req.name)
|
||||
self.requirements[project_name] = install_req
|
||||
|
||||
def add_requirement(
|
||||
self,
|
||||
install_req: InstallRequirement,
|
||||
parent_req_name: Optional[str] = None,
|
||||
extras_requested: Optional[Iterable[str]] = None,
|
||||
) -> Tuple[List[InstallRequirement], Optional[InstallRequirement]]:
|
||||
"""Add install_req as a requirement to install.
|
||||
|
||||
:param parent_req_name: The name of the requirement that needed this
|
||||
added. The name is used because when multiple unnamed requirements
|
||||
resolve to the same name, we could otherwise end up with dependency
|
||||
links that point outside the Requirements set. parent_req must
|
||||
already be added. Note that None implies that this is a user
|
||||
supplied requirement, vs an inferred one.
|
||||
:param extras_requested: an iterable of extras used to evaluate the
|
||||
environment markers.
|
||||
:return: Additional requirements to scan. That is either [] if
|
||||
the requirement is not applicable, or [install_req] if the
|
||||
requirement is applicable and has just been added.
|
||||
"""
|
||||
# If the markers do not match, ignore this requirement.
|
||||
if not install_req.match_markers(extras_requested):
|
||||
logger.info(
|
||||
"Ignoring %s: markers '%s' don't match your environment",
|
||||
install_req.name,
|
||||
install_req.markers,
|
||||
)
|
||||
return [], None
|
||||
|
||||
# If the wheel is not supported, raise an error.
|
||||
# Should check this after filtering out based on environment markers to
|
||||
# allow specifying different wheels based on the environment/OS, in a
|
||||
# single requirements file.
|
||||
if install_req.link and install_req.link.is_wheel:
|
||||
wheel = Wheel(install_req.link.filename)
|
||||
tags = compatibility_tags.get_supported()
|
||||
if self.check_supported_wheels and not wheel.supported(tags):
|
||||
raise InstallationError(
|
||||
"{} is not a supported wheel on this platform.".format(
|
||||
wheel.filename
|
||||
)
|
||||
)
|
||||
|
||||
# This next bit is really a sanity check.
|
||||
assert (
|
||||
not install_req.user_supplied or parent_req_name is None
|
||||
), "a user supplied req shouldn't have a parent"
|
||||
|
||||
# Unnamed requirements are scanned again and the requirement won't be
|
||||
# added as a dependency until after scanning.
|
||||
if not install_req.name:
|
||||
self.add_unnamed_requirement(install_req)
|
||||
return [install_req], None
|
||||
|
||||
try:
|
||||
existing_req: Optional[InstallRequirement] = self.get_requirement(
|
||||
install_req.name
|
||||
)
|
||||
except KeyError:
|
||||
existing_req = None
|
||||
|
||||
has_conflicting_requirement = (
|
||||
parent_req_name is None
|
||||
and existing_req
|
||||
and not existing_req.constraint
|
||||
and existing_req.extras == install_req.extras
|
||||
and existing_req.req
|
||||
and install_req.req
|
||||
and existing_req.req.specifier != install_req.req.specifier
|
||||
)
|
||||
if has_conflicting_requirement:
|
||||
raise InstallationError(
|
||||
"Double requirement given: {} (already in {}, name={!r})".format(
|
||||
install_req, existing_req, install_req.name
|
||||
)
|
||||
)
|
||||
|
||||
# When no existing requirement exists, add the requirement as a
|
||||
# dependency and it will be scanned again after.
|
||||
if not existing_req:
|
||||
self.add_named_requirement(install_req)
|
||||
# We'd want to rescan this requirement later
|
||||
return [install_req], install_req
|
||||
|
||||
# Assume there's no need to scan, and that we've already
|
||||
# encountered this for scanning.
|
||||
if install_req.constraint or not existing_req.constraint:
|
||||
return [], existing_req
|
||||
|
||||
does_not_satisfy_constraint = install_req.link and not (
|
||||
existing_req.link and install_req.link.path == existing_req.link.path
|
||||
)
|
||||
if does_not_satisfy_constraint:
|
||||
raise InstallationError(
|
||||
"Could not satisfy constraints for '{}': "
|
||||
"installation from path or url cannot be "
|
||||
"constrained to a version".format(install_req.name)
|
||||
)
|
||||
# If we're now installing a constraint, mark the existing
|
||||
# object for real installation.
|
||||
existing_req.constraint = False
|
||||
# If we're now installing a user supplied requirement,
|
||||
# mark the existing object as such.
|
||||
if install_req.user_supplied:
|
||||
existing_req.user_supplied = True
|
||||
existing_req.extras = tuple(
|
||||
sorted(set(existing_req.extras) | set(install_req.extras))
|
||||
)
|
||||
logger.debug(
|
||||
"Setting %s extras to: %s",
|
||||
existing_req,
|
||||
existing_req.extras,
|
||||
)
|
||||
# Return the existing requirement for addition to the parent and
|
||||
# scanning again.
|
||||
return [existing_req], existing_req
|
||||
|
||||
def has_requirement(self, name: str) -> bool:
|
||||
project_name = canonicalize_name(name)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import os
|
||||
import sys
|
||||
import sysconfig
|
||||
from importlib.util import cache_from_source
|
||||
from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Set, Tuple
|
||||
from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Set, Tuple
|
||||
|
||||
from pipenv.patched.notpip._internal.exceptions import UninstallationError
|
||||
from pipenv.patched.notpip._internal.locations import get_bin_prefix, get_bin_user
|
||||
@@ -17,7 +17,9 @@ from pipenv.patched.notpip._internal.utils.temp_dir import AdjacentTempDirectory
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
def _script_names(bin_dir: str, script_name: str, is_gui: bool) -> Iterator[str]:
|
||||
def _script_names(
|
||||
bin_dir: str, script_name: str, is_gui: bool
|
||||
) -> Generator[str, None, None]:
|
||||
"""Create the fully qualified name of the files created by
|
||||
{console,gui}_scripts for the given ``dist``.
|
||||
Returns the list of file names
|
||||
@@ -34,9 +36,11 @@ def _script_names(bin_dir: str, script_name: str, is_gui: bool) -> Iterator[str]
|
||||
yield f"{exe_name}-script.py"
|
||||
|
||||
|
||||
def _unique(fn: Callable[..., Iterator[Any]]) -> Callable[..., Iterator[Any]]:
|
||||
def _unique(
|
||||
fn: Callable[..., Generator[Any, None, None]]
|
||||
) -> Callable[..., Generator[Any, None, None]]:
|
||||
@functools.wraps(fn)
|
||||
def unique(*args: Any, **kw: Any) -> Iterator[Any]:
|
||||
def unique(*args: Any, **kw: Any) -> Generator[Any, None, None]:
|
||||
seen: Set[Any] = set()
|
||||
for item in fn(*args, **kw):
|
||||
if item not in seen:
|
||||
@@ -47,7 +51,7 @@ def _unique(fn: Callable[..., Iterator[Any]]) -> Callable[..., Iterator[Any]]:
|
||||
|
||||
|
||||
@_unique
|
||||
def uninstallation_paths(dist: BaseDistribution) -> Iterator[str]:
|
||||
def uninstallation_paths(dist: BaseDistribution) -> Generator[str, None, None]:
|
||||
"""
|
||||
Yield all the uninstallation paths for dist based on RECORD-without-.py[co]
|
||||
|
||||
@@ -527,7 +531,10 @@ class UninstallPathSet:
|
||||
# above, so this only covers the setuptools-style editable.
|
||||
with open(develop_egg_link) as fh:
|
||||
link_pointer = os.path.normcase(fh.readline().strip())
|
||||
assert link_pointer == dist_location, (
|
||||
normalized_link_pointer = normalize_path(link_pointer)
|
||||
assert os.path.samefile(
|
||||
normalized_link_pointer, normalized_dist_location
|
||||
), (
|
||||
f"Egg-link {link_pointer} does not match installed location of "
|
||||
f"{dist.raw_name} (at {dist_location})"
|
||||
)
|
||||
@@ -551,10 +558,10 @@ class UninstallPathSet:
|
||||
|
||||
# find distutils scripts= scripts
|
||||
try:
|
||||
for script in dist.iterdir("scripts"):
|
||||
paths_to_remove.add(os.path.join(bin_dir, script.name))
|
||||
for script in dist.iter_distutils_script_names():
|
||||
paths_to_remove.add(os.path.join(bin_dir, script))
|
||||
if WINDOWS:
|
||||
paths_to_remove.add(os.path.join(bin_dir, f"{script.name}.bat"))
|
||||
paths_to_remove.add(os.path.join(bin_dir, f"{script}.bat"))
|
||||
except (FileNotFoundError, NotADirectoryError):
|
||||
pass
|
||||
|
||||
@@ -562,7 +569,7 @@ class UninstallPathSet:
|
||||
def iter_scripts_to_remove(
|
||||
dist: BaseDistribution,
|
||||
bin_dir: str,
|
||||
) -> Iterator[str]:
|
||||
) -> Generator[str, None, None]:
|
||||
for entry_point in dist.iter_entry_points():
|
||||
if entry_point.group == "console_scripts":
|
||||
yield from _script_names(bin_dir, entry_point.name, False)
|
||||
|
||||
@@ -28,12 +28,14 @@ from pipenv.patched.notpip._internal.exceptions import (
|
||||
DistributionNotFound,
|
||||
HashError,
|
||||
HashErrors,
|
||||
InstallationError,
|
||||
NoneMetadataError,
|
||||
UnsupportedPythonVersion,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.index.package_finder import PackageFinder
|
||||
from pipenv.patched.notpip._internal.metadata import BaseDistribution
|
||||
from pipenv.patched.notpip._internal.models.link import Link
|
||||
from pipenv.patched.notpip._internal.models.wheel import Wheel
|
||||
from pipenv.patched.notpip._internal.operations.prepare import RequirementPreparer
|
||||
from pipenv.patched.notpip._internal.req.req_install import (
|
||||
InstallRequirement,
|
||||
@@ -41,6 +43,7 @@ from pipenv.patched.notpip._internal.req.req_install import (
|
||||
)
|
||||
from pipenv.patched.notpip._internal.req.req_set import RequirementSet
|
||||
from pipenv.patched.notpip._internal.resolution.base import BaseResolver, InstallRequirementProvider
|
||||
from pipenv.patched.notpip._internal.utils import compatibility_tags
|
||||
from pipenv.patched.notpip._internal.utils.compatibility_tags import get_supported
|
||||
from pipenv.patched.notpip._internal.utils.logging import indent_log
|
||||
from pipenv.patched.notpip._internal.utils.misc import normalize_version_info
|
||||
@@ -168,7 +171,7 @@ class Resolver(BaseResolver):
|
||||
for req in root_reqs:
|
||||
if req.constraint:
|
||||
check_invalid_constraint_type(req)
|
||||
requirement_set.add_requirement(req)
|
||||
self._add_requirement_to_set(requirement_set, req)
|
||||
|
||||
# Actually prepare the files, and collect any exceptions. Most hash
|
||||
# exceptions cannot be checked ahead of time, because
|
||||
@@ -188,6 +191,124 @@ class Resolver(BaseResolver):
|
||||
|
||||
return requirement_set
|
||||
|
||||
def _add_requirement_to_set(
|
||||
self,
|
||||
requirement_set: RequirementSet,
|
||||
install_req: InstallRequirement,
|
||||
parent_req_name: Optional[str] = None,
|
||||
extras_requested: Optional[Iterable[str]] = None,
|
||||
) -> Tuple[List[InstallRequirement], Optional[InstallRequirement]]:
|
||||
"""Add install_req as a requirement to install.
|
||||
|
||||
:param parent_req_name: The name of the requirement that needed this
|
||||
added. The name is used because when multiple unnamed requirements
|
||||
resolve to the same name, we could otherwise end up with dependency
|
||||
links that point outside the Requirements set. parent_req must
|
||||
already be added. Note that None implies that this is a user
|
||||
supplied requirement, vs an inferred one.
|
||||
:param extras_requested: an iterable of extras used to evaluate the
|
||||
environment markers.
|
||||
:return: Additional requirements to scan. That is either [] if
|
||||
the requirement is not applicable, or [install_req] if the
|
||||
requirement is applicable and has just been added.
|
||||
"""
|
||||
# If the markers do not match, ignore this requirement.
|
||||
if not install_req.match_markers(extras_requested):
|
||||
logger.info(
|
||||
"Ignoring %s: markers '%s' don't match your environment",
|
||||
install_req.name,
|
||||
install_req.markers,
|
||||
)
|
||||
return [], None
|
||||
|
||||
# If the wheel is not supported, raise an error.
|
||||
# Should check this after filtering out based on environment markers to
|
||||
# allow specifying different wheels based on the environment/OS, in a
|
||||
# single requirements file.
|
||||
if install_req.link and install_req.link.is_wheel:
|
||||
wheel = Wheel(install_req.link.filename)
|
||||
tags = compatibility_tags.get_supported()
|
||||
if requirement_set.check_supported_wheels and not wheel.supported(tags):
|
||||
raise InstallationError(
|
||||
"{} is not a supported wheel on this platform.".format(
|
||||
wheel.filename
|
||||
)
|
||||
)
|
||||
|
||||
# This next bit is really a sanity check.
|
||||
assert (
|
||||
not install_req.user_supplied or parent_req_name is None
|
||||
), "a user supplied req shouldn't have a parent"
|
||||
|
||||
# Unnamed requirements are scanned again and the requirement won't be
|
||||
# added as a dependency until after scanning.
|
||||
if not install_req.name:
|
||||
requirement_set.add_unnamed_requirement(install_req)
|
||||
return [install_req], None
|
||||
|
||||
try:
|
||||
existing_req: Optional[
|
||||
InstallRequirement
|
||||
] = requirement_set.get_requirement(install_req.name)
|
||||
except KeyError:
|
||||
existing_req = None
|
||||
|
||||
has_conflicting_requirement = (
|
||||
parent_req_name is None
|
||||
and existing_req
|
||||
and not existing_req.constraint
|
||||
and existing_req.extras == install_req.extras
|
||||
and existing_req.req
|
||||
and install_req.req
|
||||
and existing_req.req.specifier != install_req.req.specifier
|
||||
)
|
||||
if has_conflicting_requirement:
|
||||
raise InstallationError(
|
||||
"Double requirement given: {} (already in {}, name={!r})".format(
|
||||
install_req, existing_req, install_req.name
|
||||
)
|
||||
)
|
||||
|
||||
# When no existing requirement exists, add the requirement as a
|
||||
# dependency and it will be scanned again after.
|
||||
if not existing_req:
|
||||
requirement_set.add_named_requirement(install_req)
|
||||
# We'd want to rescan this requirement later
|
||||
return [install_req], install_req
|
||||
|
||||
# Assume there's no need to scan, and that we've already
|
||||
# encountered this for scanning.
|
||||
if install_req.constraint or not existing_req.constraint:
|
||||
return [], existing_req
|
||||
|
||||
does_not_satisfy_constraint = install_req.link and not (
|
||||
existing_req.link and install_req.link.path == existing_req.link.path
|
||||
)
|
||||
if does_not_satisfy_constraint:
|
||||
raise InstallationError(
|
||||
"Could not satisfy constraints for '{}': "
|
||||
"installation from path or url cannot be "
|
||||
"constrained to a version".format(install_req.name)
|
||||
)
|
||||
# If we're now installing a constraint, mark the existing
|
||||
# object for real installation.
|
||||
existing_req.constraint = False
|
||||
# If we're now installing a user supplied requirement,
|
||||
# mark the existing object as such.
|
||||
if install_req.user_supplied:
|
||||
existing_req.user_supplied = True
|
||||
existing_req.extras = tuple(
|
||||
sorted(set(existing_req.extras) | set(install_req.extras))
|
||||
)
|
||||
logger.debug(
|
||||
"Setting %s extras to: %s",
|
||||
existing_req,
|
||||
existing_req.extras,
|
||||
)
|
||||
# Return the existing requirement for addition to the parent and
|
||||
# scanning again.
|
||||
return [existing_req], existing_req
|
||||
|
||||
def _is_upgrade_allowed(self, req: InstallRequirement) -> bool:
|
||||
if self.upgrade_strategy == "to-satisfy-only":
|
||||
return False
|
||||
@@ -393,7 +514,8 @@ class Resolver(BaseResolver):
|
||||
# the legacy resolver so I'm just not going to bother refactoring.
|
||||
sub_install_req = self._make_install_req(str(subreq), req_to_install)
|
||||
parent_req_name = req_to_install.name
|
||||
to_scan_again, add_to_parent = requirement_set.add_requirement(
|
||||
to_scan_again, add_to_parent = self._add_requirement_to_set(
|
||||
requirement_set,
|
||||
sub_install_req,
|
||||
parent_req_name=parent_req_name,
|
||||
extras_requested=extras_requested,
|
||||
@@ -410,7 +532,9 @@ class Resolver(BaseResolver):
|
||||
# 'unnamed' requirements can only come from being directly
|
||||
# provided by the user.
|
||||
assert req_to_install.user_supplied
|
||||
requirement_set.add_requirement(req_to_install, parent_req_name=None)
|
||||
self._add_requirement_to_set(
|
||||
requirement_set, req_to_install, parent_req_name=None
|
||||
)
|
||||
|
||||
if not self.ignore_dependencies:
|
||||
if req_to_install.extras:
|
||||
|
||||
@@ -70,6 +70,7 @@ def make_install_req_from_link(
|
||||
global_options=template.global_options,
|
||||
hashes=template.hash_options,
|
||||
),
|
||||
config_settings=template.config_settings,
|
||||
)
|
||||
ireq.original_link = template.original_link
|
||||
ireq.link = link
|
||||
@@ -93,6 +94,7 @@ def make_install_req_from_editable(
|
||||
global_options=template.global_options,
|
||||
hashes=template.hash_options,
|
||||
),
|
||||
config_settings=template.config_settings,
|
||||
)
|
||||
|
||||
|
||||
@@ -117,6 +119,7 @@ def _make_install_req_from_dist(
|
||||
global_options=template.global_options,
|
||||
hashes=template.hash_options,
|
||||
),
|
||||
config_settings=template.config_settings,
|
||||
)
|
||||
ireq.satisfied_by = dist
|
||||
return ireq
|
||||
|
||||
@@ -617,8 +617,15 @@ class Factory:
|
||||
req_disp = f"{req} (from {parent.name})"
|
||||
|
||||
cands = self._finder.find_all_candidates(req.project_name)
|
||||
skipped_by_requires_python = self._finder.requires_python_skipped_reasons()
|
||||
versions = [str(v) for v in sorted({c.version for c in cands})]
|
||||
|
||||
if skipped_by_requires_python:
|
||||
logger.critical(
|
||||
"Ignored the following versions that require a different python "
|
||||
"version: %s",
|
||||
"; ".join(skipped_by_requires_python) or "none",
|
||||
)
|
||||
logger.critical(
|
||||
"Could not find a version that satisfies the requirement %s "
|
||||
"(from versions: %s)",
|
||||
|
||||
@@ -1,23 +1,34 @@
|
||||
import datetime
|
||||
import functools
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import optparse
|
||||
import os.path
|
||||
import sys
|
||||
from typing import Any, Dict
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable, Dict, Optional
|
||||
|
||||
from pipenv.patched.notpip._vendor.packaging.version import parse as parse_version
|
||||
from pipenv.patched.notpip._vendor.rich.console import Group
|
||||
from pipenv.patched.notpip._vendor.rich.markup import escape
|
||||
from pipenv.patched.notpip._vendor.rich.text import Text
|
||||
|
||||
from pipenv.patched.notpip._internal.index.collector import LinkCollector
|
||||
from pipenv.patched.notpip._internal.index.package_finder import PackageFinder
|
||||
from pipenv.patched.notpip._internal.metadata import get_default_environment
|
||||
from pipenv.patched.notpip._internal.metadata.base import DistributionVersion
|
||||
from pipenv.patched.notpip._internal.models.selection_prefs import SelectionPreferences
|
||||
from pipenv.patched.notpip._internal.network.session import PipSession
|
||||
from pipenv.patched.notpip._internal.utils.compat import WINDOWS
|
||||
from pipenv.patched.notpip._internal.utils.entrypoints import (
|
||||
get_best_invocation_for_this_pip,
|
||||
get_best_invocation_for_this_python,
|
||||
)
|
||||
from pipenv.patched.notpip._internal.utils.filesystem import adjacent_tmp_file, check_path_owner, replace
|
||||
from pipenv.patched.notpip._internal.utils.misc import ensure_dir
|
||||
|
||||
SELFCHECK_DATE_FMT = "%Y-%m-%dT%H:%M:%SZ"
|
||||
_DATE_FMT = "%Y-%m-%dT%H:%M:%SZ"
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -31,17 +42,17 @@ def _get_statefile_name(key: str) -> str:
|
||||
|
||||
class SelfCheckState:
|
||||
def __init__(self, cache_dir: str) -> None:
|
||||
self.state: Dict[str, Any] = {}
|
||||
self.statefile_path = None
|
||||
self._state: Dict[str, Any] = {}
|
||||
self._statefile_path = None
|
||||
|
||||
# Try to load the existing state
|
||||
if cache_dir:
|
||||
self.statefile_path = os.path.join(
|
||||
self._statefile_path = os.path.join(
|
||||
cache_dir, "selfcheck", _get_statefile_name(self.key)
|
||||
)
|
||||
try:
|
||||
with open(self.statefile_path, encoding="utf-8") as statefile:
|
||||
self.state = json.load(statefile)
|
||||
with open(self._statefile_path, encoding="utf-8") as statefile:
|
||||
self._state = json.load(statefile)
|
||||
except (OSError, ValueError, KeyError):
|
||||
# Explicitly suppressing exceptions, since we don't want to
|
||||
# error out if the cache file is invalid.
|
||||
@@ -51,41 +62,87 @@ class SelfCheckState:
|
||||
def key(self) -> str:
|
||||
return sys.prefix
|
||||
|
||||
def save(self, pypi_version: str, current_time: datetime.datetime) -> None:
|
||||
def get(self, current_time: datetime.datetime) -> Optional[str]:
|
||||
"""Check if we have a not-outdated version loaded already."""
|
||||
if not self._state:
|
||||
return None
|
||||
|
||||
if "last_check" not in self._state:
|
||||
return None
|
||||
|
||||
if "pypi_version" not in self._state:
|
||||
return None
|
||||
|
||||
seven_days_in_seconds = 7 * 24 * 60 * 60
|
||||
|
||||
# Determine if we need to refresh the state
|
||||
last_check = datetime.datetime.strptime(self._state["last_check"], _DATE_FMT)
|
||||
seconds_since_last_check = (current_time - last_check).total_seconds()
|
||||
if seconds_since_last_check > seven_days_in_seconds:
|
||||
return None
|
||||
|
||||
return self._state["pypi_version"]
|
||||
|
||||
def set(self, pypi_version: str, current_time: datetime.datetime) -> None:
|
||||
# If we do not have a path to cache in, don't bother saving.
|
||||
if not self.statefile_path:
|
||||
if not self._statefile_path:
|
||||
return
|
||||
|
||||
# Check to make sure that we own the directory
|
||||
if not check_path_owner(os.path.dirname(self.statefile_path)):
|
||||
if not check_path_owner(os.path.dirname(self._statefile_path)):
|
||||
return
|
||||
|
||||
# Now that we've ensured the directory is owned by this user, we'll go
|
||||
# ahead and make sure that all our directories are created.
|
||||
ensure_dir(os.path.dirname(self.statefile_path))
|
||||
ensure_dir(os.path.dirname(self._statefile_path))
|
||||
|
||||
state = {
|
||||
# Include the key so it's easy to tell which pip wrote the
|
||||
# file.
|
||||
"key": self.key,
|
||||
"last_check": current_time.strftime(SELFCHECK_DATE_FMT),
|
||||
"last_check": current_time.strftime(_DATE_FMT),
|
||||
"pypi_version": pypi_version,
|
||||
}
|
||||
|
||||
text = json.dumps(state, sort_keys=True, separators=(",", ":"))
|
||||
|
||||
with adjacent_tmp_file(self.statefile_path) as f:
|
||||
with adjacent_tmp_file(self._statefile_path) as f:
|
||||
f.write(text.encode())
|
||||
|
||||
try:
|
||||
# Since we have a prefix-specific state file, we can just
|
||||
# overwrite whatever is there, no need to check.
|
||||
replace(f.name, self.statefile_path)
|
||||
replace(f.name, self._statefile_path)
|
||||
except OSError:
|
||||
# Best effort.
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class UpgradePrompt:
|
||||
old: str
|
||||
new: str
|
||||
|
||||
def __rich__(self) -> Group:
|
||||
if WINDOWS:
|
||||
pip_cmd = f"{get_best_invocation_for_this_python()} -m pip"
|
||||
else:
|
||||
pip_cmd = get_best_invocation_for_this_pip()
|
||||
|
||||
notice = "[bold][[reset][blue]notice[reset][bold]][reset]"
|
||||
return Group(
|
||||
Text(),
|
||||
Text.from_markup(
|
||||
f"{notice} A new release of pip available: "
|
||||
f"[red]{self.old}[reset] -> [green]{self.new}[reset]"
|
||||
),
|
||||
Text.from_markup(
|
||||
f"{notice} To update, run: "
|
||||
f"[green]{escape(pip_cmd)} install --upgrade pip"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def was_installed_by_pip(pkg: str) -> bool:
|
||||
"""Checks whether pkg was installed by pip
|
||||
|
||||
@@ -96,6 +153,66 @@ def was_installed_by_pip(pkg: str) -> bool:
|
||||
return dist is not None and "pip" == dist.installer
|
||||
|
||||
|
||||
def _get_current_remote_pip_version(
|
||||
session: PipSession, options: optparse.Values
|
||||
) -> str:
|
||||
# Lets use PackageFinder to see what the latest pip version is
|
||||
link_collector = LinkCollector.create(
|
||||
session,
|
||||
options=options,
|
||||
suppress_no_index=True,
|
||||
)
|
||||
|
||||
# Pass allow_yanked=False so we don't suggest upgrading to a
|
||||
# yanked version.
|
||||
selection_prefs = SelectionPreferences(
|
||||
allow_yanked=False,
|
||||
allow_all_prereleases=False, # Explicitly set to False
|
||||
)
|
||||
|
||||
finder = PackageFinder.create(
|
||||
link_collector=link_collector,
|
||||
selection_prefs=selection_prefs,
|
||||
use_deprecated_html5lib=("html5lib" in options.deprecated_features_enabled),
|
||||
)
|
||||
best_candidate = finder.find_best_candidate("pip").best_candidate
|
||||
if best_candidate is None:
|
||||
return
|
||||
|
||||
return str(best_candidate.version)
|
||||
|
||||
|
||||
def _self_version_check_logic(
|
||||
*,
|
||||
state: SelfCheckState,
|
||||
current_time: datetime.datetime,
|
||||
local_version: DistributionVersion,
|
||||
get_remote_version: Callable[[], str],
|
||||
) -> Optional[UpgradePrompt]:
|
||||
remote_version_str = state.get(current_time)
|
||||
if remote_version_str is None:
|
||||
remote_version_str = get_remote_version()
|
||||
state.set(remote_version_str, current_time)
|
||||
|
||||
remote_version = parse_version(remote_version_str)
|
||||
logger.debug("Remote version of pip: %s", remote_version)
|
||||
logger.debug("Local version of pip: %s", local_version)
|
||||
|
||||
pip_installed_by_pip = was_installed_by_pip("pip")
|
||||
logger.debug("Was pip installed by pip? %s", pip_installed_by_pip)
|
||||
if not pip_installed_by_pip:
|
||||
return None # Only suggest upgrade if pip is installed by pip.
|
||||
|
||||
local_version_is_older = (
|
||||
local_version < remote_version
|
||||
and local_version.base_version != remote_version.base_version
|
||||
)
|
||||
if local_version_is_older:
|
||||
return UpgradePrompt(old=str(local_version), new=remote_version_str)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def pip_self_version_check(session: PipSession, options: optparse.Values) -> None:
|
||||
"""Check for an update for pip.
|
||||
|
||||
@@ -107,83 +224,17 @@ def pip_self_version_check(session: PipSession, options: optparse.Values) -> Non
|
||||
if not installed_dist:
|
||||
return
|
||||
|
||||
pip_version = installed_dist.version
|
||||
pypi_version = None
|
||||
|
||||
try:
|
||||
state = SelfCheckState(cache_dir=options.cache_dir)
|
||||
|
||||
current_time = datetime.datetime.utcnow()
|
||||
# Determine if we need to refresh the state
|
||||
if "last_check" in state.state and "pypi_version" in state.state:
|
||||
last_check = datetime.datetime.strptime(
|
||||
state.state["last_check"], SELFCHECK_DATE_FMT
|
||||
)
|
||||
if (current_time - last_check).total_seconds() < 7 * 24 * 60 * 60:
|
||||
pypi_version = state.state["pypi_version"]
|
||||
|
||||
# Refresh the version if we need to or just see if we need to warn
|
||||
if pypi_version is None:
|
||||
# Lets use PackageFinder to see what the latest pip version is
|
||||
link_collector = LinkCollector.create(
|
||||
session,
|
||||
options=options,
|
||||
suppress_no_index=True,
|
||||
)
|
||||
|
||||
# Pass allow_yanked=False so we don't suggest upgrading to a
|
||||
# yanked version.
|
||||
selection_prefs = SelectionPreferences(
|
||||
allow_yanked=False,
|
||||
allow_all_prereleases=False, # Explicitly set to False
|
||||
)
|
||||
|
||||
finder = PackageFinder.create(
|
||||
link_collector=link_collector,
|
||||
selection_prefs=selection_prefs,
|
||||
use_deprecated_html5lib=(
|
||||
"html5lib" in options.deprecated_features_enabled
|
||||
),
|
||||
)
|
||||
best_candidate = finder.find_best_candidate("pip").best_candidate
|
||||
if best_candidate is None:
|
||||
return
|
||||
pypi_version = str(best_candidate.version)
|
||||
|
||||
# save that we've performed a check
|
||||
state.save(pypi_version, current_time)
|
||||
|
||||
remote_version = parse_version(pypi_version)
|
||||
|
||||
local_version_is_older = (
|
||||
pip_version < remote_version
|
||||
and pip_version.base_version != remote_version.base_version
|
||||
and was_installed_by_pip("pip")
|
||||
)
|
||||
|
||||
# Determine if our pypi_version is older
|
||||
if not local_version_is_older:
|
||||
return
|
||||
|
||||
# We cannot tell how the current pip is available in the current
|
||||
# command context, so be pragmatic here and suggest the command
|
||||
# that's always available. This does not accommodate spaces in
|
||||
# `sys.executable` on purpose as it is not possible to do it
|
||||
# correctly without knowing the user's shell. Thus,
|
||||
# it won't be done until possible through the standard library.
|
||||
# Do not be tempted to use the undocumented subprocess.list2cmdline.
|
||||
# It is considered an internal implementation detail for a reason.
|
||||
pip_cmd = f"{sys.executable} -m pip"
|
||||
logger.warning(
|
||||
"You are using pip version %s; however, version %s is "
|
||||
"available.\nYou should consider upgrading via the "
|
||||
"'%s install --upgrade pip' command.",
|
||||
pip_version,
|
||||
pypi_version,
|
||||
pip_cmd,
|
||||
upgrade_prompt = _self_version_check_logic(
|
||||
state=SelfCheckState(cache_dir=options.cache_dir),
|
||||
current_time=datetime.datetime.utcnow(),
|
||||
local_version=installed_dist.version,
|
||||
get_remote_version=functools.partial(
|
||||
_get_current_remote_pip_version, session, options
|
||||
),
|
||||
)
|
||||
if upgrade_prompt is not None:
|
||||
logger.info("[present-rich] %s", upgrade_prompt)
|
||||
except Exception:
|
||||
logger.debug(
|
||||
"There was an error checking the latest version of pip",
|
||||
exc_info=True,
|
||||
)
|
||||
logger.warning("There was an error checking the latest version of pip.")
|
||||
logger.debug("See below for error", exc_info=True)
|
||||
|
||||
@@ -14,7 +14,7 @@ BOMS: List[Tuple[bytes, str]] = [
|
||||
(codecs.BOM_UTF32_LE, "utf-32-le"),
|
||||
]
|
||||
|
||||
ENCODING_RE = re.compile(br"coding[:=]\s*([-\w.]+)")
|
||||
ENCODING_RE = re.compile(rb"coding[:=]\s*([-\w.]+)")
|
||||
|
||||
|
||||
def auto_decode(data: bytes) -> str:
|
||||
|
||||
@@ -1,7 +1,23 @@
|
||||
import itertools
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from typing import List, Optional
|
||||
|
||||
from pipenv.patched.notpip._internal.cli.main import main
|
||||
from pipenv.patched.notpip._internal.utils.compat import WINDOWS
|
||||
|
||||
_EXECUTABLE_NAMES = [
|
||||
"pip",
|
||||
f"pip{sys.version_info.major}",
|
||||
f"pip{sys.version_info.major}.{sys.version_info.minor}",
|
||||
]
|
||||
if WINDOWS:
|
||||
_allowed_extensions = {"", ".exe"}
|
||||
_EXECUTABLE_NAMES = [
|
||||
"".join(parts)
|
||||
for parts in itertools.product(_EXECUTABLE_NAMES, _allowed_extensions)
|
||||
]
|
||||
|
||||
|
||||
def _wrapper(args: Optional[List[str]] = None) -> int:
|
||||
@@ -25,3 +41,39 @@ def _wrapper(args: Optional[List[str]] = None) -> int:
|
||||
"running pip directly.\n"
|
||||
)
|
||||
return main(args)
|
||||
|
||||
|
||||
def get_best_invocation_for_this_pip() -> str:
|
||||
"""Try to figure out the best way to invoke pip in the current environment."""
|
||||
binary_directory = "Scripts" if WINDOWS else "bin"
|
||||
binary_prefix = os.path.join(sys.prefix, binary_directory)
|
||||
|
||||
# Try to use pip[X[.Y]] names, if those executables for this environment are
|
||||
# the first on PATH with that name.
|
||||
path_parts = os.path.normcase(os.environ.get("PATH", "")).split(os.pathsep)
|
||||
exe_are_in_PATH = os.path.normcase(binary_prefix) in path_parts
|
||||
if exe_are_in_PATH:
|
||||
for exe_name in _EXECUTABLE_NAMES:
|
||||
found_executable = shutil.which(exe_name)
|
||||
if found_executable and os.path.samefile(
|
||||
found_executable,
|
||||
os.path.join(binary_prefix, exe_name),
|
||||
):
|
||||
return exe_name
|
||||
|
||||
# Use the `-m` invocation, if there's no "nice" invocation.
|
||||
return f"{get_best_invocation_for_this_python()} -m pip"
|
||||
|
||||
|
||||
def get_best_invocation_for_this_python() -> str:
|
||||
"""Try to figure out the best way to invoke the current Python."""
|
||||
exe = sys.executable
|
||||
exe_name = os.path.basename(exe)
|
||||
|
||||
# Try to use the basename, if it's the first executable.
|
||||
found_executable = shutil.which(exe_name)
|
||||
if found_executable and os.path.samefile(found_executable, exe):
|
||||
return exe_name
|
||||
|
||||
# Use the full executable name, because we couldn't find something simpler.
|
||||
return exe
|
||||
|
||||
@@ -2,12 +2,10 @@ import fnmatch
|
||||
import os
|
||||
import os.path
|
||||
import random
|
||||
import shutil
|
||||
import stat
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import Any, BinaryIO, Iterator, List, Union, cast
|
||||
from typing import Any, BinaryIO, Generator, List, Union, cast
|
||||
|
||||
from pipenv.patched.notpip._vendor.tenacity import retry, stop_after_delay, wait_fixed
|
||||
|
||||
@@ -42,35 +40,8 @@ def check_path_owner(path: str) -> bool:
|
||||
return False # assume we don't own the path
|
||||
|
||||
|
||||
def copy2_fixed(src: str, dest: str) -> None:
|
||||
"""Wrap shutil.copy2() but map errors copying socket files to
|
||||
SpecialFileError as expected.
|
||||
|
||||
See also https://bugs.python.org/issue37700.
|
||||
"""
|
||||
try:
|
||||
shutil.copy2(src, dest)
|
||||
except OSError:
|
||||
for f in [src, dest]:
|
||||
try:
|
||||
is_socket_file = is_socket(f)
|
||||
except OSError:
|
||||
# An error has already occurred. Another error here is not
|
||||
# a problem and we can ignore it.
|
||||
pass
|
||||
else:
|
||||
if is_socket_file:
|
||||
raise shutil.SpecialFileError(f"`{f}` is a socket")
|
||||
|
||||
raise
|
||||
|
||||
|
||||
def is_socket(path: str) -> bool:
|
||||
return stat.S_ISSOCK(os.lstat(path).st_mode)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def adjacent_tmp_file(path: str, **kwargs: Any) -> Iterator[BinaryIO]:
|
||||
def adjacent_tmp_file(path: str, **kwargs: Any) -> Generator[BinaryIO, None, None]:
|
||||
"""Return a file-like object pointing to a tmp file next to path.
|
||||
|
||||
The file is created securely and is ensured to be written to disk
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import hashlib
|
||||
from typing import TYPE_CHECKING, BinaryIO, Dict, Iterator, List
|
||||
from typing import TYPE_CHECKING, BinaryIO, Dict, Iterable, List
|
||||
|
||||
from pipenv.patched.notpip._internal.exceptions import HashMismatch, HashMissing, InstallationError
|
||||
from pipenv.patched.notpip._internal.utils.misc import read_chunks
|
||||
@@ -67,7 +67,7 @@ class Hashes:
|
||||
"""Return whether the given hex digest is allowed."""
|
||||
return hex_digest in self._allowed.get(hash_name, [])
|
||||
|
||||
def check_against_chunks(self, chunks: Iterator[bytes]) -> None:
|
||||
def check_against_chunks(self, chunks: Iterable[bytes]) -> None:
|
||||
"""Check good hashes against ones built from iterable of chunks of
|
||||
data.
|
||||
|
||||
|
||||
@@ -6,21 +6,23 @@ import os
|
||||
import sys
|
||||
import threading
|
||||
from dataclasses import dataclass
|
||||
from io import TextIOWrapper
|
||||
from logging import Filter
|
||||
from typing import IO, Any, ClassVar, Iterator, List, Optional, TextIO, Type
|
||||
from typing import Any, ClassVar, Generator, List, Optional, TextIO, Type
|
||||
|
||||
from pipenv.patched.notpip._vendor.rich.console import (
|
||||
Console,
|
||||
ConsoleOptions,
|
||||
ConsoleRenderable,
|
||||
RenderableType,
|
||||
RenderResult,
|
||||
RichCast,
|
||||
)
|
||||
from pipenv.patched.notpip._vendor.rich.highlighter import NullHighlighter
|
||||
from pipenv.patched.notpip._vendor.rich.logging import RichHandler
|
||||
from pipenv.patched.notpip._vendor.rich.segment import Segment
|
||||
from pipenv.patched.notpip._vendor.rich.style import Style
|
||||
|
||||
from pipenv.patched.notpip._internal.exceptions import DiagnosticPipError
|
||||
from pipenv.patched.notpip._internal.utils._log import VERBOSE, getLogger
|
||||
from pipenv.patched.notpip._internal.utils.compat import WINDOWS
|
||||
from pipenv.patched.notpip._internal.utils.deprecation import DEPRECATION_MSG_PREFIX
|
||||
@@ -50,7 +52,7 @@ def _is_broken_pipe_error(exc_class: Type[BaseException], exc: BaseException) ->
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def indent_log(num: int = 2) -> Iterator[None]:
|
||||
def indent_log(num: int = 2) -> Generator[None, None, None]:
|
||||
"""
|
||||
A context manager which will cause the log output to be indented for any
|
||||
log messages emitted inside it.
|
||||
@@ -121,7 +123,7 @@ class IndentingFormatter(logging.Formatter):
|
||||
|
||||
@dataclass
|
||||
class IndentedRenderable:
|
||||
renderable: ConsoleRenderable
|
||||
renderable: RenderableType
|
||||
indent: int
|
||||
|
||||
def __rich_console__(
|
||||
@@ -152,12 +154,15 @@ class RichPipStreamHandler(RichHandler):
|
||||
style: Optional[Style] = None
|
||||
|
||||
# If we are given a diagnostic error to present, present it with indentation.
|
||||
if record.msg == "[present-diagnostic] %s" and len(record.args) == 1:
|
||||
diagnostic_error: DiagnosticPipError = record.args[0] # type: ignore[index]
|
||||
assert isinstance(diagnostic_error, DiagnosticPipError)
|
||||
assert isinstance(record.args, tuple)
|
||||
if record.msg == "[present-rich] %s" and len(record.args) == 1:
|
||||
rich_renderable = record.args[0]
|
||||
assert isinstance(
|
||||
rich_renderable, (ConsoleRenderable, RichCast, str)
|
||||
), f"{rich_renderable} is not rich-console-renderable"
|
||||
|
||||
renderable: ConsoleRenderable = IndentedRenderable(
|
||||
diagnostic_error, indent=get_indentation()
|
||||
renderable: RenderableType = IndentedRenderable(
|
||||
rich_renderable, indent=get_indentation()
|
||||
)
|
||||
else:
|
||||
message = self.format(record)
|
||||
@@ -193,7 +198,7 @@ class RichPipStreamHandler(RichHandler):
|
||||
|
||||
|
||||
class BetterRotatingFileHandler(logging.handlers.RotatingFileHandler):
|
||||
def _open(self) -> IO[Any]:
|
||||
def _open(self) -> TextIOWrapper:
|
||||
ensure_dir(os.path.dirname(self.baseFilename))
|
||||
return super()._open()
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ from typing import (
|
||||
BinaryIO,
|
||||
Callable,
|
||||
ContextManager,
|
||||
Dict,
|
||||
Generator,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
@@ -32,6 +34,7 @@ from typing import (
|
||||
cast,
|
||||
)
|
||||
|
||||
from pipenv.patched.notpip._vendor.pep517 import Pep517HookCaller
|
||||
from pipenv.patched.notpip._vendor.tenacity import retry, stop_after_delay, wait_fixed
|
||||
|
||||
from pipenv.patched.notpip import __version__
|
||||
@@ -54,6 +57,7 @@ __all__ = [
|
||||
"captured_stdout",
|
||||
"ensure_dir",
|
||||
"remove_auth_from_url",
|
||||
"ConfiguredPep517HookCaller",
|
||||
]
|
||||
|
||||
|
||||
@@ -264,7 +268,9 @@ def is_installable_dir(path: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def read_chunks(file: BinaryIO, size: int = io.DEFAULT_BUFFER_SIZE) -> Iterator[bytes]:
|
||||
def read_chunks(
|
||||
file: BinaryIO, size: int = io.DEFAULT_BUFFER_SIZE
|
||||
) -> Generator[bytes, None, None]:
|
||||
"""Yield pieces of data from a file-like object until EOF."""
|
||||
while True:
|
||||
chunk = file.read(size)
|
||||
@@ -346,7 +352,7 @@ class StreamWrapper(StringIO):
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def captured_output(stream_name: str) -> Iterator[StreamWrapper]:
|
||||
def captured_output(stream_name: str) -> Generator[StreamWrapper, None, None]:
|
||||
"""Return a context manager used by captured_stdout/stdin/stderr
|
||||
that temporarily replaces the sys stream *stream_name* with a StringIO.
|
||||
|
||||
@@ -556,9 +562,9 @@ def protect_pip_from_modification_on_windows(modifying_pip: bool) -> None:
|
||||
python -m pip ...
|
||||
"""
|
||||
pip_names = [
|
||||
"pip.exe",
|
||||
"pip{}.exe".format(sys.version_info[0]),
|
||||
"pip{}.{}.exe".format(*sys.version_info[:2]),
|
||||
"pip",
|
||||
f"pip{sys.version_info.major}",
|
||||
f"pip{sys.version_info.major}.{sys.version_info.minor}",
|
||||
]
|
||||
|
||||
# See https://github.com/pypa/pip/issues/1299 for more discussion
|
||||
@@ -627,3 +633,91 @@ def partition(
|
||||
"""
|
||||
t1, t2 = tee(iterable)
|
||||
return filterfalse(pred, t1), filter(pred, t2)
|
||||
|
||||
|
||||
class ConfiguredPep517HookCaller(Pep517HookCaller):
|
||||
def __init__(
|
||||
self,
|
||||
config_holder: Any,
|
||||
source_dir: str,
|
||||
build_backend: str,
|
||||
backend_path: Optional[str] = None,
|
||||
runner: Optional[Callable[..., None]] = None,
|
||||
python_executable: Optional[str] = None,
|
||||
):
|
||||
super().__init__(
|
||||
source_dir, build_backend, backend_path, runner, python_executable
|
||||
)
|
||||
self.config_holder = config_holder
|
||||
|
||||
def build_wheel(
|
||||
self,
|
||||
wheel_directory: str,
|
||||
config_settings: Optional[Dict[str, str]] = None,
|
||||
metadata_directory: Optional[str] = None,
|
||||
) -> str:
|
||||
cs = self.config_holder.config_settings
|
||||
return super().build_wheel(
|
||||
wheel_directory, config_settings=cs, metadata_directory=metadata_directory
|
||||
)
|
||||
|
||||
def build_sdist(
|
||||
self, sdist_directory: str, config_settings: Optional[Dict[str, str]] = None
|
||||
) -> str:
|
||||
cs = self.config_holder.config_settings
|
||||
return super().build_sdist(sdist_directory, config_settings=cs)
|
||||
|
||||
def build_editable(
|
||||
self,
|
||||
wheel_directory: str,
|
||||
config_settings: Optional[Dict[str, str]] = None,
|
||||
metadata_directory: Optional[str] = None,
|
||||
) -> str:
|
||||
cs = self.config_holder.config_settings
|
||||
return super().build_editable(
|
||||
wheel_directory, config_settings=cs, metadata_directory=metadata_directory
|
||||
)
|
||||
|
||||
def get_requires_for_build_wheel(
|
||||
self, config_settings: Optional[Dict[str, str]] = None
|
||||
) -> List[str]:
|
||||
cs = self.config_holder.config_settings
|
||||
return super().get_requires_for_build_wheel(config_settings=cs)
|
||||
|
||||
def get_requires_for_build_sdist(
|
||||
self, config_settings: Optional[Dict[str, str]] = None
|
||||
) -> List[str]:
|
||||
cs = self.config_holder.config_settings
|
||||
return super().get_requires_for_build_sdist(config_settings=cs)
|
||||
|
||||
def get_requires_for_build_editable(
|
||||
self, config_settings: Optional[Dict[str, str]] = None
|
||||
) -> List[str]:
|
||||
cs = self.config_holder.config_settings
|
||||
return super().get_requires_for_build_editable(config_settings=cs)
|
||||
|
||||
def prepare_metadata_for_build_wheel(
|
||||
self,
|
||||
metadata_directory: str,
|
||||
config_settings: Optional[Dict[str, str]] = None,
|
||||
_allow_fallback: bool = True,
|
||||
) -> str:
|
||||
cs = self.config_holder.config_settings
|
||||
return super().prepare_metadata_for_build_wheel(
|
||||
metadata_directory=metadata_directory,
|
||||
config_settings=cs,
|
||||
_allow_fallback=_allow_fallback,
|
||||
)
|
||||
|
||||
def prepare_metadata_for_build_editable(
|
||||
self,
|
||||
metadata_directory: str,
|
||||
config_settings: Optional[Dict[str, str]] = None,
|
||||
_allow_fallback: bool = True,
|
||||
) -> str:
|
||||
cs = self.config_holder.config_settings
|
||||
return super().prepare_metadata_for_build_editable(
|
||||
metadata_directory=metadata_directory,
|
||||
config_settings=cs,
|
||||
_allow_fallback=_allow_fallback,
|
||||
)
|
||||
|
||||
@@ -116,7 +116,7 @@ def call_subprocess(
|
||||
# replaced by INFO.
|
||||
if show_stdout:
|
||||
# Then log the subprocess output at INFO level.
|
||||
log_subprocess = subprocess_logger.info
|
||||
log_subprocess: Callable[..., None] = subprocess_logger.info
|
||||
used_level = logging.INFO
|
||||
else:
|
||||
# Then log the subprocess output using VERBOSE. This also ensures
|
||||
@@ -209,7 +209,7 @@ def call_subprocess(
|
||||
output_lines=all_output if not showing_subprocess else None,
|
||||
)
|
||||
if log_failed_cmd:
|
||||
subprocess_logger.error("[present-diagnostic] %s", error)
|
||||
subprocess_logger.error("[present-rich] %s", error)
|
||||
subprocess_logger.verbose(
|
||||
"[bold magenta]full command[/]: [blue]%s[/]",
|
||||
escape(format_command_args(cmd)),
|
||||
|
||||
@@ -4,7 +4,7 @@ import logging
|
||||
import os.path
|
||||
import tempfile
|
||||
from contextlib import ExitStack, contextmanager
|
||||
from typing import Any, Dict, Iterator, Optional, TypeVar, Union
|
||||
from typing import Any, Dict, Generator, Optional, TypeVar, Union
|
||||
|
||||
from pipenv.patched.notpip._internal.utils.misc import enum, rmtree
|
||||
|
||||
@@ -26,7 +26,7 @@ _tempdir_manager: Optional[ExitStack] = None
|
||||
|
||||
|
||||
@contextmanager
|
||||
def global_tempdir_manager() -> Iterator[None]:
|
||||
def global_tempdir_manager() -> Generator[None, None, None]:
|
||||
global _tempdir_manager
|
||||
with ExitStack() as stack:
|
||||
old_tempdir_manager, _tempdir_manager = _tempdir_manager, stack
|
||||
@@ -59,7 +59,7 @@ _tempdir_registry: Optional[TempDirectoryTypeRegistry] = None
|
||||
|
||||
|
||||
@contextmanager
|
||||
def tempdir_registry() -> Iterator[TempDirectoryTypeRegistry]:
|
||||
def tempdir_registry() -> Generator[TempDirectoryTypeRegistry, None, None]:
|
||||
"""Provides a scoped global tempdir registry that can be used to dictate
|
||||
whether directories should be deleted.
|
||||
"""
|
||||
@@ -200,7 +200,7 @@ class AdjacentTempDirectory(TempDirectory):
|
||||
super().__init__(delete=delete)
|
||||
|
||||
@classmethod
|
||||
def _generate_names(cls, name: str) -> Iterator[str]:
|
||||
def _generate_names(cls, name: str) -> Generator[str, None, None]:
|
||||
"""Generates a series of temporary names.
|
||||
|
||||
The algorithm replaces the leading characters in the name
|
||||
|
||||
@@ -188,8 +188,7 @@ def untar_file(filename: str, location: str) -> None:
|
||||
ensure_dir(path)
|
||||
elif member.issym():
|
||||
try:
|
||||
# https://github.com/python/typeshed/issues/2673
|
||||
tar._extract_member(member, path) # type: ignore
|
||||
tar._extract_member(member, path)
|
||||
except Exception as exc:
|
||||
# Some corrupt tar files seem to produce this
|
||||
# (specifically bad symlinks)
|
||||
|
||||
@@ -8,7 +8,7 @@ Make it easy to import from cachecontrol without long namespaces.
|
||||
"""
|
||||
__author__ = "Eric Larson"
|
||||
__email__ = "eric@ionrock.org"
|
||||
__version__ = "0.12.10"
|
||||
__version__ = "0.12.11"
|
||||
|
||||
from .wrapper import CacheControl
|
||||
from .adapter import CacheControlAdapter
|
||||
|
||||
@@ -41,3 +41,25 @@ class DictCache(BaseCache):
|
||||
with self.lock:
|
||||
if key in self.data:
|
||||
self.data.pop(key)
|
||||
|
||||
|
||||
class SeparateBodyBaseCache(BaseCache):
|
||||
"""
|
||||
In this variant, the body is not stored mixed in with the metadata, but is
|
||||
passed in (as a bytes-like object) in a separate call to ``set_body()``.
|
||||
|
||||
That is, the expected interaction pattern is::
|
||||
|
||||
cache.set(key, serialized_metadata)
|
||||
cache.set_body(key)
|
||||
|
||||
Similarly, the body should be loaded separately via ``get_body()``.
|
||||
"""
|
||||
def set_body(self, key, body):
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_body(self, key):
|
||||
"""
|
||||
Return the body as file-like object.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -2,5 +2,8 @@
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from .file_cache import FileCache # noqa
|
||||
from .redis_cache import RedisCache # noqa
|
||||
from .file_cache import FileCache, SeparateBodyFileCache
|
||||
from .redis_cache import RedisCache
|
||||
|
||||
|
||||
__all__ = ["FileCache", "SeparateBodyFileCache", "RedisCache"]
|
||||
|
||||
@@ -6,7 +6,7 @@ import hashlib
|
||||
import os
|
||||
from textwrap import dedent
|
||||
|
||||
from ..cache import BaseCache
|
||||
from ..cache import BaseCache, SeparateBodyBaseCache
|
||||
from ..controller import CacheController
|
||||
|
||||
try:
|
||||
@@ -57,7 +57,8 @@ def _secure_open_write(filename, fmode):
|
||||
raise
|
||||
|
||||
|
||||
class FileCache(BaseCache):
|
||||
class _FileCacheMixin:
|
||||
"""Shared implementation for both FileCache variants."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -120,20 +121,25 @@ class FileCache(BaseCache):
|
||||
|
||||
def set(self, key, value, expires=None):
|
||||
name = self._fn(key)
|
||||
self._write(name, value)
|
||||
|
||||
def _write(self, path, data: bytes):
|
||||
"""
|
||||
Safely write the data to the given path.
|
||||
"""
|
||||
# Make sure the directory exists
|
||||
try:
|
||||
os.makedirs(os.path.dirname(name), self.dirmode)
|
||||
os.makedirs(os.path.dirname(path), self.dirmode)
|
||||
except (IOError, OSError):
|
||||
pass
|
||||
|
||||
with self.lock_class(name) as lock:
|
||||
with self.lock_class(path) as lock:
|
||||
# Write our actual file
|
||||
with _secure_open_write(lock.path, self.filemode) as fh:
|
||||
fh.write(value)
|
||||
fh.write(data)
|
||||
|
||||
def delete(self, key):
|
||||
name = self._fn(key)
|
||||
def _delete(self, key, suffix):
|
||||
name = self._fn(key) + suffix
|
||||
if not self.forever:
|
||||
try:
|
||||
os.remove(name)
|
||||
@@ -141,6 +147,38 @@ class FileCache(BaseCache):
|
||||
pass
|
||||
|
||||
|
||||
class FileCache(_FileCacheMixin, BaseCache):
|
||||
"""
|
||||
Traditional FileCache: body is stored in memory, so not suitable for large
|
||||
downloads.
|
||||
"""
|
||||
|
||||
def delete(self, key):
|
||||
self._delete(key, "")
|
||||
|
||||
|
||||
class SeparateBodyFileCache(_FileCacheMixin, SeparateBodyBaseCache):
|
||||
"""
|
||||
Memory-efficient FileCache: body is stored in a separate file, reducing
|
||||
peak memory usage.
|
||||
"""
|
||||
|
||||
def get_body(self, key):
|
||||
name = self._fn(key) + ".body"
|
||||
try:
|
||||
return open(name, "rb")
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
|
||||
def set_body(self, key, body):
|
||||
name = self._fn(key) + ".body"
|
||||
self._write(name, body)
|
||||
|
||||
def delete(self, key):
|
||||
self._delete(key, "")
|
||||
self._delete(key, ".body")
|
||||
|
||||
|
||||
def url_to_file_path(url, filecache):
|
||||
"""Return the file cache path based on the URL.
|
||||
|
||||
|
||||
@@ -19,9 +19,11 @@ class RedisCache(BaseCache):
|
||||
def set(self, key, value, expires=None):
|
||||
if not expires:
|
||||
self.conn.set(key, value)
|
||||
else:
|
||||
elif isinstance(expires, datetime):
|
||||
expires = expires - datetime.utcnow()
|
||||
self.conn.setex(key, int(expires.total_seconds()), value)
|
||||
else:
|
||||
self.conn.setex(key, expires, value)
|
||||
|
||||
def delete(self, key):
|
||||
self.conn.delete(key)
|
||||
|
||||
@@ -13,7 +13,7 @@ from email.utils import parsedate_tz
|
||||
|
||||
from pipenv.patched.notpip._vendor.requests.structures import CaseInsensitiveDict
|
||||
|
||||
from .cache import DictCache
|
||||
from .cache import DictCache, SeparateBodyBaseCache
|
||||
from .serialize import Serializer
|
||||
|
||||
|
||||
@@ -27,15 +27,14 @@ PERMANENT_REDIRECT_STATUSES = (301, 308)
|
||||
def parse_uri(uri):
|
||||
"""Parses a URI using the regex given in Appendix B of RFC 3986.
|
||||
|
||||
(scheme, authority, path, query, fragment) = parse_uri(uri)
|
||||
(scheme, authority, path, query, fragment) = parse_uri(uri)
|
||||
"""
|
||||
groups = URI.match(uri).groups()
|
||||
return (groups[1], groups[3], groups[4], groups[6], groups[8])
|
||||
|
||||
|
||||
class CacheController(object):
|
||||
"""An interface to see if request should cached or not.
|
||||
"""
|
||||
"""An interface to see if request should cached or not."""
|
||||
|
||||
def __init__(
|
||||
self, cache=None, cache_etags=True, serializer=None, status_codes=None
|
||||
@@ -147,8 +146,13 @@ class CacheController(object):
|
||||
logger.debug("No cache entry available")
|
||||
return False
|
||||
|
||||
if isinstance(self.cache, SeparateBodyBaseCache):
|
||||
body_file = self.cache.get_body(cache_url)
|
||||
else:
|
||||
body_file = None
|
||||
|
||||
# Check whether it can be deserialized
|
||||
resp = self.serializer.loads(request, cache_data)
|
||||
resp = self.serializer.loads(request, cache_data, body_file)
|
||||
if not resp:
|
||||
logger.warning("Cache entry deserialization failed, entry ignored")
|
||||
return False
|
||||
@@ -251,6 +255,26 @@ class CacheController(object):
|
||||
|
||||
return new_headers
|
||||
|
||||
def _cache_set(self, cache_url, request, response, body=None, expires_time=None):
|
||||
"""
|
||||
Store the data in the cache.
|
||||
"""
|
||||
if isinstance(self.cache, SeparateBodyBaseCache):
|
||||
# We pass in the body separately; just put a placeholder empty
|
||||
# string in the metadata.
|
||||
self.cache.set(
|
||||
cache_url,
|
||||
self.serializer.dumps(request, response, b""),
|
||||
expires=expires_time,
|
||||
)
|
||||
self.cache.set_body(cache_url, body)
|
||||
else:
|
||||
self.cache.set(
|
||||
cache_url,
|
||||
self.serializer.dumps(request, response, body),
|
||||
expires=expires_time,
|
||||
)
|
||||
|
||||
def cache_response(self, request, response, body=None, status_codes=None):
|
||||
"""
|
||||
Algorithm for caching requests.
|
||||
@@ -326,17 +350,13 @@ class CacheController(object):
|
||||
|
||||
logger.debug("etag object cached for {0} seconds".format(expires_time))
|
||||
logger.debug("Caching due to etag")
|
||||
self.cache.set(
|
||||
cache_url,
|
||||
self.serializer.dumps(request, response, body),
|
||||
expires=expires_time,
|
||||
)
|
||||
self._cache_set(cache_url, request, response, body, expires_time)
|
||||
|
||||
# Add to the cache any permanent redirects. We do this before looking
|
||||
# that the Date headers.
|
||||
elif int(response.status) in PERMANENT_REDIRECT_STATUSES:
|
||||
logger.debug("Caching permanent redirect")
|
||||
self.cache.set(cache_url, self.serializer.dumps(request, response, b""))
|
||||
self._cache_set(cache_url, request, response, b"")
|
||||
|
||||
# Add to the cache if the response headers demand it. If there
|
||||
# is no date header then we can't do anything about expiring
|
||||
@@ -347,10 +367,12 @@ class CacheController(object):
|
||||
if "max-age" in cc and cc["max-age"] > 0:
|
||||
logger.debug("Caching b/c date exists and max-age > 0")
|
||||
expires_time = cc["max-age"]
|
||||
self.cache.set(
|
||||
self._cache_set(
|
||||
cache_url,
|
||||
self.serializer.dumps(request, response, body),
|
||||
expires=expires_time,
|
||||
request,
|
||||
response,
|
||||
body,
|
||||
expires_time,
|
||||
)
|
||||
|
||||
# If the request can expire, it means we should cache it
|
||||
@@ -368,10 +390,12 @@ class CacheController(object):
|
||||
expires_time
|
||||
)
|
||||
)
|
||||
self.cache.set(
|
||||
self._cache_set(
|
||||
cache_url,
|
||||
self.serializer.dumps(request, response, body=body),
|
||||
expires=expires_time,
|
||||
request,
|
||||
response,
|
||||
body,
|
||||
expires_time,
|
||||
)
|
||||
|
||||
def update_cached_response(self, request, response):
|
||||
@@ -410,6 +434,6 @@ class CacheController(object):
|
||||
cached_response.status = 200
|
||||
|
||||
# update our cache
|
||||
self.cache.set(cache_url, self.serializer.dumps(request, cached_response))
|
||||
self._cache_set(cache_url, request, cached_response)
|
||||
|
||||
return cached_response
|
||||
|
||||
@@ -44,7 +44,7 @@ class Serializer(object):
|
||||
# enough to have msgpack know the difference.
|
||||
data = {
|
||||
u"response": {
|
||||
u"body": body,
|
||||
u"body": body, # Empty bytestring if body is stored separately
|
||||
u"headers": dict(
|
||||
(text_type(k), text_type(v)) for k, v in response.headers.items()
|
||||
),
|
||||
@@ -69,7 +69,7 @@ class Serializer(object):
|
||||
|
||||
return b",".join([b"cc=4", msgpack.dumps(data, use_bin_type=True)])
|
||||
|
||||
def loads(self, request, data):
|
||||
def loads(self, request, data, body_file=None):
|
||||
# Short circuit if we've been given an empty set of data
|
||||
if not data:
|
||||
return
|
||||
@@ -92,14 +92,14 @@ class Serializer(object):
|
||||
|
||||
# Dispatch to the actual load method for the given version
|
||||
try:
|
||||
return getattr(self, "_loads_v{}".format(ver))(request, data)
|
||||
return getattr(self, "_loads_v{}".format(ver))(request, data, body_file)
|
||||
|
||||
except AttributeError:
|
||||
# This is a version we don't have a loads function for, so we'll
|
||||
# just treat it as a miss and return None
|
||||
return
|
||||
|
||||
def prepare_response(self, request, cached):
|
||||
def prepare_response(self, request, cached, body_file=None):
|
||||
"""Verify our vary headers match and construct a real urllib3
|
||||
HTTPResponse object.
|
||||
"""
|
||||
@@ -125,7 +125,10 @@ class Serializer(object):
|
||||
cached["response"]["headers"] = headers
|
||||
|
||||
try:
|
||||
body = io.BytesIO(body_raw)
|
||||
if body_file is None:
|
||||
body = io.BytesIO(body_raw)
|
||||
else:
|
||||
body = body_file
|
||||
except TypeError:
|
||||
# This can happen if cachecontrol serialized to v1 format (pickle)
|
||||
# using Python 2. A Python 2 str(byte string) will be unpickled as
|
||||
@@ -137,21 +140,22 @@ class Serializer(object):
|
||||
|
||||
return HTTPResponse(body=body, preload_content=False, **cached["response"])
|
||||
|
||||
def _loads_v0(self, request, data):
|
||||
def _loads_v0(self, request, data, body_file=None):
|
||||
# The original legacy cache data. This doesn't contain enough
|
||||
# information to construct everything we need, so we'll treat this as
|
||||
# a miss.
|
||||
return
|
||||
|
||||
def _loads_v1(self, request, data):
|
||||
def _loads_v1(self, request, data, body_file=None):
|
||||
try:
|
||||
cached = pickle.loads(data)
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
return self.prepare_response(request, cached)
|
||||
return self.prepare_response(request, cached, body_file)
|
||||
|
||||
def _loads_v2(self, request, data):
|
||||
def _loads_v2(self, request, data, body_file=None):
|
||||
assert body_file is None
|
||||
try:
|
||||
cached = json.loads(zlib.decompress(data).decode("utf8"))
|
||||
except (ValueError, zlib.error):
|
||||
@@ -169,18 +173,18 @@ class Serializer(object):
|
||||
for k, v in cached["vary"].items()
|
||||
)
|
||||
|
||||
return self.prepare_response(request, cached)
|
||||
return self.prepare_response(request, cached, body_file)
|
||||
|
||||
def _loads_v3(self, request, data):
|
||||
def _loads_v3(self, request, data, body_file):
|
||||
# Due to Python 2 encoding issues, it's impossible to know for sure
|
||||
# exactly how to load v3 entries, thus we'll treat these as a miss so
|
||||
# that they get rewritten out as v4 entries.
|
||||
return
|
||||
|
||||
def _loads_v4(self, request, data):
|
||||
def _loads_v4(self, request, data, body_file=None):
|
||||
try:
|
||||
cached = msgpack.loads(data, raw=False)
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
return self.prepare_response(request, cached)
|
||||
return self.prepare_response(request, cached, body_file)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
This package contains a modified version of ca-bundle.crt:
|
||||
|
||||
ca-bundle.crt -- Bundle of CA Root Certificates
|
||||
|
||||
Certificate data from Mozilla as of: Thu Nov 3 19:04:19 2011#
|
||||
This is a bundle of X.509 certificates of public Certificate Authorities
|
||||
(CA). These were automatically extracted from Mozilla's root certificates
|
||||
file (certdata.txt). This file can be found in the mozilla source tree:
|
||||
http://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1#
|
||||
It contains the certificates in PEM format and therefore
|
||||
can be directly used with curl / libcurl / php_curl, or with
|
||||
an Apache+mod_ssl webserver for SSL client authentication.
|
||||
Just configure this file as the SSLCACertificateFile.#
|
||||
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||
v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain
|
||||
one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
@(#) $RCSfile: certdata.txt,v $ $Revision: 1.80 $ $Date: 2011/11/03 15:11:58 $
|
||||
@@ -0,0 +1,504 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2010 Jonathan Hartley
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holders, nor those of its contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,284 @@
|
||||
A. HISTORY OF THE SOFTWARE
|
||||
==========================
|
||||
|
||||
Python was created in the early 1990s by Guido van Rossum at Stichting
|
||||
Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
|
||||
as a successor of a language called ABC. Guido remains Python's
|
||||
principal author, although it includes many contributions from others.
|
||||
|
||||
In 1995, Guido continued his work on Python at the Corporation for
|
||||
National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
|
||||
in Reston, Virginia where he released several versions of the
|
||||
software.
|
||||
|
||||
In May 2000, Guido and the Python core development team moved to
|
||||
BeOpen.com to form the BeOpen PythonLabs team. In October of the same
|
||||
year, the PythonLabs team moved to Digital Creations (now Zope
|
||||
Corporation, see http://www.zope.com). In 2001, the Python Software
|
||||
Foundation (PSF, see http://www.python.org/psf/) was formed, a
|
||||
non-profit organization created specifically to own Python-related
|
||||
Intellectual Property. Zope Corporation is a sponsoring member of
|
||||
the PSF.
|
||||
|
||||
All Python releases are Open Source (see http://www.opensource.org for
|
||||
the Open Source Definition). Historically, most, but not all, Python
|
||||
releases have also been GPL-compatible; the table below summarizes
|
||||
the various releases.
|
||||
|
||||
Release Derived Year Owner GPL-
|
||||
from compatible? (1)
|
||||
|
||||
0.9.0 thru 1.2 1991-1995 CWI yes
|
||||
1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
|
||||
1.6 1.5.2 2000 CNRI no
|
||||
2.0 1.6 2000 BeOpen.com no
|
||||
1.6.1 1.6 2001 CNRI yes (2)
|
||||
2.1 2.0+1.6.1 2001 PSF no
|
||||
2.0.1 2.0+1.6.1 2001 PSF yes
|
||||
2.1.1 2.1+2.0.1 2001 PSF yes
|
||||
2.2 2.1.1 2001 PSF yes
|
||||
2.1.2 2.1.1 2002 PSF yes
|
||||
2.1.3 2.1.2 2002 PSF yes
|
||||
2.2.1 2.2 2002 PSF yes
|
||||
2.2.2 2.2.1 2002 PSF yes
|
||||
2.2.3 2.2.2 2003 PSF yes
|
||||
2.3 2.2.2 2002-2003 PSF yes
|
||||
2.3.1 2.3 2002-2003 PSF yes
|
||||
2.3.2 2.3.1 2002-2003 PSF yes
|
||||
2.3.3 2.3.2 2002-2003 PSF yes
|
||||
2.3.4 2.3.3 2004 PSF yes
|
||||
2.3.5 2.3.4 2005 PSF yes
|
||||
2.4 2.3 2004 PSF yes
|
||||
2.4.1 2.4 2005 PSF yes
|
||||
2.4.2 2.4.1 2005 PSF yes
|
||||
2.4.3 2.4.2 2006 PSF yes
|
||||
2.4.4 2.4.3 2006 PSF yes
|
||||
2.5 2.4 2006 PSF yes
|
||||
2.5.1 2.5 2007 PSF yes
|
||||
2.5.2 2.5.1 2008 PSF yes
|
||||
2.5.3 2.5.2 2008 PSF yes
|
||||
2.6 2.5 2008 PSF yes
|
||||
2.6.1 2.6 2008 PSF yes
|
||||
2.6.2 2.6.1 2009 PSF yes
|
||||
2.6.3 2.6.2 2009 PSF yes
|
||||
2.6.4 2.6.3 2009 PSF yes
|
||||
2.6.5 2.6.4 2010 PSF yes
|
||||
3.0 2.6 2008 PSF yes
|
||||
3.0.1 3.0 2009 PSF yes
|
||||
3.1 3.0.1 2009 PSF yes
|
||||
3.1.1 3.1 2009 PSF yes
|
||||
3.1.2 3.1 2010 PSF yes
|
||||
3.2 3.1 2010 PSF yes
|
||||
|
||||
Footnotes:
|
||||
|
||||
(1) GPL-compatible doesn't mean that we're distributing Python under
|
||||
the GPL. All Python licenses, unlike the GPL, let you distribute
|
||||
a modified version without making your changes open source. The
|
||||
GPL-compatible licenses make it possible to combine Python with
|
||||
other software that is released under the GPL; the others don't.
|
||||
|
||||
(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
|
||||
because its license has a choice of law clause. According to
|
||||
CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
|
||||
is "not incompatible" with the GPL.
|
||||
|
||||
Thanks to the many outside volunteers who have worked under Guido's
|
||||
direction to make these releases possible.
|
||||
|
||||
|
||||
B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
|
||||
===============================================================
|
||||
|
||||
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
||||
--------------------------------------------
|
||||
|
||||
1. This LICENSE AGREEMENT is between the Python Software Foundation
|
||||
("PSF"), and the Individual or Organization ("Licensee") accessing and
|
||||
otherwise using this software ("Python") in source or binary form and
|
||||
its associated documentation.
|
||||
|
||||
2. Subject to the terms and conditions of this License Agreement, PSF hereby
|
||||
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
|
||||
analyze, test, perform and/or display publicly, prepare derivative works,
|
||||
distribute, and otherwise use Python alone or in any derivative version,
|
||||
provided, however, that PSF's License Agreement and PSF's notice of copyright,
|
||||
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
|
||||
Python Software Foundation; All Rights Reserved" are retained in Python alone or
|
||||
in any derivative version prepared by Licensee.
|
||||
|
||||
3. In the event Licensee prepares a derivative work that is based on
|
||||
or incorporates Python or any part thereof, and wants to make
|
||||
the derivative work available to others as provided herein, then
|
||||
Licensee hereby agrees to include in any such work a brief summary of
|
||||
the changes made to Python.
|
||||
|
||||
4. PSF is making Python available to Licensee on an "AS IS"
|
||||
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
|
||||
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
6. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
7. Nothing in this License Agreement shall be deemed to create any
|
||||
relationship of agency, partnership, or joint venture between PSF and
|
||||
Licensee. This License Agreement does not grant permission to use PSF
|
||||
trademarks or trade name in a trademark sense to endorse or promote
|
||||
products or services of Licensee, or any third party.
|
||||
|
||||
8. By copying, installing or otherwise using Python, Licensee
|
||||
agrees to be bound by the terms and conditions of this License
|
||||
Agreement.
|
||||
|
||||
|
||||
BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
|
||||
-------------------------------------------
|
||||
|
||||
BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
|
||||
|
||||
1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
|
||||
office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
|
||||
Individual or Organization ("Licensee") accessing and otherwise using
|
||||
this software in source or binary form and its associated
|
||||
documentation ("the Software").
|
||||
|
||||
2. Subject to the terms and conditions of this BeOpen Python License
|
||||
Agreement, BeOpen hereby grants Licensee a non-exclusive,
|
||||
royalty-free, world-wide license to reproduce, analyze, test, perform
|
||||
and/or display publicly, prepare derivative works, distribute, and
|
||||
otherwise use the Software alone or in any derivative version,
|
||||
provided, however, that the BeOpen Python License is retained in the
|
||||
Software, alone or in any derivative version prepared by Licensee.
|
||||
|
||||
3. BeOpen is making the Software available to Licensee on an "AS IS"
|
||||
basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
|
||||
SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
|
||||
AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
|
||||
DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
5. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
6. This License Agreement shall be governed by and interpreted in all
|
||||
respects by the law of the State of California, excluding conflict of
|
||||
law provisions. Nothing in this License Agreement shall be deemed to
|
||||
create any relationship of agency, partnership, or joint venture
|
||||
between BeOpen and Licensee. This License Agreement does not grant
|
||||
permission to use BeOpen trademarks or trade names in a trademark
|
||||
sense to endorse or promote products or services of Licensee, or any
|
||||
third party. As an exception, the "BeOpen Python" logos available at
|
||||
http://www.pythonlabs.com/logos.html may be used according to the
|
||||
permissions granted on that web page.
|
||||
|
||||
7. By copying, installing or otherwise using the software, Licensee
|
||||
agrees to be bound by the terms and conditions of this License
|
||||
Agreement.
|
||||
|
||||
|
||||
CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
|
||||
---------------------------------------
|
||||
|
||||
1. This LICENSE AGREEMENT is between the Corporation for National
|
||||
Research Initiatives, having an office at 1895 Preston White Drive,
|
||||
Reston, VA 20191 ("CNRI"), and the Individual or Organization
|
||||
("Licensee") accessing and otherwise using Python 1.6.1 software in
|
||||
source or binary form and its associated documentation.
|
||||
|
||||
2. Subject to the terms and conditions of this License Agreement, CNRI
|
||||
hereby grants Licensee a nonexclusive, royalty-free, world-wide
|
||||
license to reproduce, analyze, test, perform and/or display publicly,
|
||||
prepare derivative works, distribute, and otherwise use Python 1.6.1
|
||||
alone or in any derivative version, provided, however, that CNRI's
|
||||
License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
|
||||
1995-2001 Corporation for National Research Initiatives; All Rights
|
||||
Reserved" are retained in Python 1.6.1 alone or in any derivative
|
||||
version prepared by Licensee. Alternately, in lieu of CNRI's License
|
||||
Agreement, Licensee may substitute the following text (omitting the
|
||||
quotes): "Python 1.6.1 is made available subject to the terms and
|
||||
conditions in CNRI's License Agreement. This Agreement together with
|
||||
Python 1.6.1 may be located on the Internet using the following
|
||||
unique, persistent identifier (known as a handle): 1895.22/1013. This
|
||||
Agreement may also be obtained from a proxy server on the Internet
|
||||
using the following URL: http://hdl.handle.net/1895.22/1013".
|
||||
|
||||
3. In the event Licensee prepares a derivative work that is based on
|
||||
or incorporates Python 1.6.1 or any part thereof, and wants to make
|
||||
the derivative work available to others as provided herein, then
|
||||
Licensee hereby agrees to include in any such work a brief summary of
|
||||
the changes made to Python 1.6.1.
|
||||
|
||||
4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
|
||||
basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||
1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
|
||||
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
6. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
7. This License Agreement shall be governed by the federal
|
||||
intellectual property law of the United States, including without
|
||||
limitation the federal copyright law, and, to the extent such
|
||||
U.S. federal law does not apply, by the law of the Commonwealth of
|
||||
Virginia, excluding Virginia's conflict of law provisions.
|
||||
Notwithstanding the foregoing, with regard to derivative works based
|
||||
on Python 1.6.1 that incorporate non-separable material that was
|
||||
previously distributed under the GNU General Public License (GPL), the
|
||||
law of the Commonwealth of Virginia shall govern this License
|
||||
Agreement only as to issues arising under or with respect to
|
||||
Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
|
||||
License Agreement shall be deemed to create any relationship of
|
||||
agency, partnership, or joint venture between CNRI and Licensee. This
|
||||
License Agreement does not grant permission to use CNRI trademarks or
|
||||
trade name in a trademark sense to endorse or promote products or
|
||||
services of Licensee, or any third party.
|
||||
|
||||
8. By clicking on the "ACCEPT" button where indicated, or by copying,
|
||||
installing or otherwise using Python 1.6.1, Licensee agrees to be
|
||||
bound by the terms and conditions of this License Agreement.
|
||||
|
||||
ACCEPT
|
||||
|
||||
|
||||
CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
|
||||
--------------------------------------------------
|
||||
|
||||
Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
|
||||
The Netherlands. All rights reserved.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software and its
|
||||
documentation for any purpose and without fee is hereby granted,
|
||||
provided that the above copyright notice appear in all copies and that
|
||||
both that copyright notice and this permission notice appear in
|
||||
supporting documentation, and that the name of Stichting Mathematisch
|
||||
Centrum or CWI not be used in advertising or publicity pertaining to
|
||||
distribution of the software without specific, written prior
|
||||
permission.
|
||||
|
||||
STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
|
||||
FOR ANY SPECIAL, 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.
|
||||
@@ -0,0 +1,6 @@
|
||||
"""Modules copied from Python 3 standard libraries, for internal use only.
|
||||
|
||||
Individual classes and functions are found in d2._backport.misc. Intended
|
||||
usage is to always import things missing from 3.1 from that module: the
|
||||
built-in/stdlib objects will be used if found.
|
||||
"""
|
||||
@@ -0,0 +1,41 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012 The Python Software Foundation.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
"""Backports for individual classes and functions."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
__all__ = ['cache_from_source', 'callable', 'fsencode']
|
||||
|
||||
|
||||
try:
|
||||
from imp import cache_from_source
|
||||
except ImportError:
|
||||
def cache_from_source(py_file, debug=__debug__):
|
||||
ext = debug and 'c' or 'o'
|
||||
return py_file + ext
|
||||
|
||||
|
||||
try:
|
||||
callable = callable
|
||||
except NameError:
|
||||
from collections import Callable
|
||||
|
||||
def callable(obj):
|
||||
return isinstance(obj, Callable)
|
||||
|
||||
|
||||
try:
|
||||
fsencode = os.fsencode
|
||||
except AttributeError:
|
||||
def fsencode(filename):
|
||||
if isinstance(filename, bytes):
|
||||
return filename
|
||||
elif isinstance(filename, str):
|
||||
return filename.encode(sys.getfilesystemencoding())
|
||||
else:
|
||||
raise TypeError("expect bytes or str, not %s" %
|
||||
type(filename).__name__)
|
||||
@@ -0,0 +1,764 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012 The Python Software Foundation.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
"""Utility functions for copying and archiving files and directory trees.
|
||||
|
||||
XXX The functions here don't copy the resource fork or other metadata on Mac.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import stat
|
||||
from os.path import abspath
|
||||
import fnmatch
|
||||
try:
|
||||
from collections.abc import Callable
|
||||
except ImportError:
|
||||
from collections import Callable
|
||||
import errno
|
||||
from . import tarfile
|
||||
|
||||
try:
|
||||
import bz2
|
||||
_BZ2_SUPPORTED = True
|
||||
except ImportError:
|
||||
_BZ2_SUPPORTED = False
|
||||
|
||||
try:
|
||||
from pwd import getpwnam
|
||||
except ImportError:
|
||||
getpwnam = None
|
||||
|
||||
try:
|
||||
from grp import getgrnam
|
||||
except ImportError:
|
||||
getgrnam = None
|
||||
|
||||
__all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2",
|
||||
"copytree", "move", "rmtree", "Error", "SpecialFileError",
|
||||
"ExecError", "make_archive", "get_archive_formats",
|
||||
"register_archive_format", "unregister_archive_format",
|
||||
"get_unpack_formats", "register_unpack_format",
|
||||
"unregister_unpack_format", "unpack_archive", "ignore_patterns"]
|
||||
|
||||
class Error(EnvironmentError):
|
||||
pass
|
||||
|
||||
class SpecialFileError(EnvironmentError):
|
||||
"""Raised when trying to do a kind of operation (e.g. copying) which is
|
||||
not supported on a special file (e.g. a named pipe)"""
|
||||
|
||||
class ExecError(EnvironmentError):
|
||||
"""Raised when a command could not be executed"""
|
||||
|
||||
class ReadError(EnvironmentError):
|
||||
"""Raised when an archive cannot be read"""
|
||||
|
||||
class RegistryError(Exception):
|
||||
"""Raised when a registry operation with the archiving
|
||||
and unpacking registries fails"""
|
||||
|
||||
|
||||
try:
|
||||
WindowsError
|
||||
except NameError:
|
||||
WindowsError = None
|
||||
|
||||
def copyfileobj(fsrc, fdst, length=16*1024):
|
||||
"""copy data from file-like object fsrc to file-like object fdst"""
|
||||
while 1:
|
||||
buf = fsrc.read(length)
|
||||
if not buf:
|
||||
break
|
||||
fdst.write(buf)
|
||||
|
||||
def _samefile(src, dst):
|
||||
# Macintosh, Unix.
|
||||
if hasattr(os.path, 'samefile'):
|
||||
try:
|
||||
return os.path.samefile(src, dst)
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
# All other platforms: check for same pathname.
|
||||
return (os.path.normcase(os.path.abspath(src)) ==
|
||||
os.path.normcase(os.path.abspath(dst)))
|
||||
|
||||
def copyfile(src, dst):
|
||||
"""Copy data from src to dst"""
|
||||
if _samefile(src, dst):
|
||||
raise Error("`%s` and `%s` are the same file" % (src, dst))
|
||||
|
||||
for fn in [src, dst]:
|
||||
try:
|
||||
st = os.stat(fn)
|
||||
except OSError:
|
||||
# File most likely does not exist
|
||||
pass
|
||||
else:
|
||||
# XXX What about other special files? (sockets, devices...)
|
||||
if stat.S_ISFIFO(st.st_mode):
|
||||
raise SpecialFileError("`%s` is a named pipe" % fn)
|
||||
|
||||
with open(src, 'rb') as fsrc:
|
||||
with open(dst, 'wb') as fdst:
|
||||
copyfileobj(fsrc, fdst)
|
||||
|
||||
def copymode(src, dst):
|
||||
"""Copy mode bits from src to dst"""
|
||||
if hasattr(os, 'chmod'):
|
||||
st = os.stat(src)
|
||||
mode = stat.S_IMODE(st.st_mode)
|
||||
os.chmod(dst, mode)
|
||||
|
||||
def copystat(src, dst):
|
||||
"""Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
|
||||
st = os.stat(src)
|
||||
mode = stat.S_IMODE(st.st_mode)
|
||||
if hasattr(os, 'utime'):
|
||||
os.utime(dst, (st.st_atime, st.st_mtime))
|
||||
if hasattr(os, 'chmod'):
|
||||
os.chmod(dst, mode)
|
||||
if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
|
||||
try:
|
||||
os.chflags(dst, st.st_flags)
|
||||
except OSError as why:
|
||||
if (not hasattr(errno, 'EOPNOTSUPP') or
|
||||
why.errno != errno.EOPNOTSUPP):
|
||||
raise
|
||||
|
||||
def copy(src, dst):
|
||||
"""Copy data and mode bits ("cp src dst").
|
||||
|
||||
The destination may be a directory.
|
||||
|
||||
"""
|
||||
if os.path.isdir(dst):
|
||||
dst = os.path.join(dst, os.path.basename(src))
|
||||
copyfile(src, dst)
|
||||
copymode(src, dst)
|
||||
|
||||
def copy2(src, dst):
|
||||
"""Copy data and all stat info ("cp -p src dst").
|
||||
|
||||
The destination may be a directory.
|
||||
|
||||
"""
|
||||
if os.path.isdir(dst):
|
||||
dst = os.path.join(dst, os.path.basename(src))
|
||||
copyfile(src, dst)
|
||||
copystat(src, dst)
|
||||
|
||||
def ignore_patterns(*patterns):
|
||||
"""Function that can be used as copytree() ignore parameter.
|
||||
|
||||
Patterns is a sequence of glob-style patterns
|
||||
that are used to exclude files"""
|
||||
def _ignore_patterns(path, names):
|
||||
ignored_names = []
|
||||
for pattern in patterns:
|
||||
ignored_names.extend(fnmatch.filter(names, pattern))
|
||||
return set(ignored_names)
|
||||
return _ignore_patterns
|
||||
|
||||
def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
|
||||
ignore_dangling_symlinks=False):
|
||||
"""Recursively copy a directory tree.
|
||||
|
||||
The destination directory must not already exist.
|
||||
If exception(s) occur, an Error is raised with a list of reasons.
|
||||
|
||||
If the optional symlinks flag is true, symbolic links in the
|
||||
source tree result in symbolic links in the destination tree; if
|
||||
it is false, the contents of the files pointed to by symbolic
|
||||
links are copied. If the file pointed by the symlink doesn't
|
||||
exist, an exception will be added in the list of errors raised in
|
||||
an Error exception at the end of the copy process.
|
||||
|
||||
You can set the optional ignore_dangling_symlinks flag to true if you
|
||||
want to silence this exception. Notice that this has no effect on
|
||||
platforms that don't support os.symlink.
|
||||
|
||||
The optional ignore argument is a callable. If given, it
|
||||
is called with the `src` parameter, which is the directory
|
||||
being visited by copytree(), and `names` which is the list of
|
||||
`src` contents, as returned by os.listdir():
|
||||
|
||||
callable(src, names) -> ignored_names
|
||||
|
||||
Since copytree() is called recursively, the callable will be
|
||||
called once for each directory that is copied. It returns a
|
||||
list of names relative to the `src` directory that should
|
||||
not be copied.
|
||||
|
||||
The optional copy_function argument is a callable that will be used
|
||||
to copy each file. It will be called with the source path and the
|
||||
destination path as arguments. By default, copy2() is used, but any
|
||||
function that supports the same signature (like copy()) can be used.
|
||||
|
||||
"""
|
||||
names = os.listdir(src)
|
||||
if ignore is not None:
|
||||
ignored_names = ignore(src, names)
|
||||
else:
|
||||
ignored_names = set()
|
||||
|
||||
os.makedirs(dst)
|
||||
errors = []
|
||||
for name in names:
|
||||
if name in ignored_names:
|
||||
continue
|
||||
srcname = os.path.join(src, name)
|
||||
dstname = os.path.join(dst, name)
|
||||
try:
|
||||
if os.path.islink(srcname):
|
||||
linkto = os.readlink(srcname)
|
||||
if symlinks:
|
||||
os.symlink(linkto, dstname)
|
||||
else:
|
||||
# ignore dangling symlink if the flag is on
|
||||
if not os.path.exists(linkto) and ignore_dangling_symlinks:
|
||||
continue
|
||||
# otherwise let the copy occurs. copy2 will raise an error
|
||||
copy_function(srcname, dstname)
|
||||
elif os.path.isdir(srcname):
|
||||
copytree(srcname, dstname, symlinks, ignore, copy_function)
|
||||
else:
|
||||
# Will raise a SpecialFileError for unsupported file types
|
||||
copy_function(srcname, dstname)
|
||||
# catch the Error from the recursive copytree so that we can
|
||||
# continue with other files
|
||||
except Error as err:
|
||||
errors.extend(err.args[0])
|
||||
except EnvironmentError as why:
|
||||
errors.append((srcname, dstname, str(why)))
|
||||
try:
|
||||
copystat(src, dst)
|
||||
except OSError as why:
|
||||
if WindowsError is not None and isinstance(why, WindowsError):
|
||||
# Copying file access times may fail on Windows
|
||||
pass
|
||||
else:
|
||||
errors.extend((src, dst, str(why)))
|
||||
if errors:
|
||||
raise Error(errors)
|
||||
|
||||
def rmtree(path, ignore_errors=False, onerror=None):
|
||||
"""Recursively delete a directory tree.
|
||||
|
||||
If ignore_errors is set, errors are ignored; otherwise, if onerror
|
||||
is set, it is called to handle the error with arguments (func,
|
||||
path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
|
||||
path is the argument to that function that caused it to fail; and
|
||||
exc_info is a tuple returned by sys.exc_info(). If ignore_errors
|
||||
is false and onerror is None, an exception is raised.
|
||||
|
||||
"""
|
||||
if ignore_errors:
|
||||
def onerror(*args):
|
||||
pass
|
||||
elif onerror is None:
|
||||
def onerror(*args):
|
||||
raise
|
||||
try:
|
||||
if os.path.islink(path):
|
||||
# symlinks to directories are forbidden, see bug #1669
|
||||
raise OSError("Cannot call rmtree on a symbolic link")
|
||||
except OSError:
|
||||
onerror(os.path.islink, path, sys.exc_info())
|
||||
# can't continue even if onerror hook returns
|
||||
return
|
||||
names = []
|
||||
try:
|
||||
names = os.listdir(path)
|
||||
except os.error:
|
||||
onerror(os.listdir, path, sys.exc_info())
|
||||
for name in names:
|
||||
fullname = os.path.join(path, name)
|
||||
try:
|
||||
mode = os.lstat(fullname).st_mode
|
||||
except os.error:
|
||||
mode = 0
|
||||
if stat.S_ISDIR(mode):
|
||||
rmtree(fullname, ignore_errors, onerror)
|
||||
else:
|
||||
try:
|
||||
os.remove(fullname)
|
||||
except os.error:
|
||||
onerror(os.remove, fullname, sys.exc_info())
|
||||
try:
|
||||
os.rmdir(path)
|
||||
except os.error:
|
||||
onerror(os.rmdir, path, sys.exc_info())
|
||||
|
||||
|
||||
def _basename(path):
|
||||
# A basename() variant which first strips the trailing slash, if present.
|
||||
# Thus we always get the last component of the path, even for directories.
|
||||
return os.path.basename(path.rstrip(os.path.sep))
|
||||
|
||||
def move(src, dst):
|
||||
"""Recursively move a file or directory to another location. This is
|
||||
similar to the Unix "mv" command.
|
||||
|
||||
If the destination is a directory or a symlink to a directory, the source
|
||||
is moved inside the directory. The destination path must not already
|
||||
exist.
|
||||
|
||||
If the destination already exists but is not a directory, it may be
|
||||
overwritten depending on os.rename() semantics.
|
||||
|
||||
If the destination is on our current filesystem, then rename() is used.
|
||||
Otherwise, src is copied to the destination and then removed.
|
||||
A lot more could be done here... A look at a mv.c shows a lot of
|
||||
the issues this implementation glosses over.
|
||||
|
||||
"""
|
||||
real_dst = dst
|
||||
if os.path.isdir(dst):
|
||||
if _samefile(src, dst):
|
||||
# We might be on a case insensitive filesystem,
|
||||
# perform the rename anyway.
|
||||
os.rename(src, dst)
|
||||
return
|
||||
|
||||
real_dst = os.path.join(dst, _basename(src))
|
||||
if os.path.exists(real_dst):
|
||||
raise Error("Destination path '%s' already exists" % real_dst)
|
||||
try:
|
||||
os.rename(src, real_dst)
|
||||
except OSError:
|
||||
if os.path.isdir(src):
|
||||
if _destinsrc(src, dst):
|
||||
raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst))
|
||||
copytree(src, real_dst, symlinks=True)
|
||||
rmtree(src)
|
||||
else:
|
||||
copy2(src, real_dst)
|
||||
os.unlink(src)
|
||||
|
||||
def _destinsrc(src, dst):
|
||||
src = abspath(src)
|
||||
dst = abspath(dst)
|
||||
if not src.endswith(os.path.sep):
|
||||
src += os.path.sep
|
||||
if not dst.endswith(os.path.sep):
|
||||
dst += os.path.sep
|
||||
return dst.startswith(src)
|
||||
|
||||
def _get_gid(name):
|
||||
"""Returns a gid, given a group name."""
|
||||
if getgrnam is None or name is None:
|
||||
return None
|
||||
try:
|
||||
result = getgrnam(name)
|
||||
except KeyError:
|
||||
result = None
|
||||
if result is not None:
|
||||
return result[2]
|
||||
return None
|
||||
|
||||
def _get_uid(name):
|
||||
"""Returns an uid, given a user name."""
|
||||
if getpwnam is None or name is None:
|
||||
return None
|
||||
try:
|
||||
result = getpwnam(name)
|
||||
except KeyError:
|
||||
result = None
|
||||
if result is not None:
|
||||
return result[2]
|
||||
return None
|
||||
|
||||
def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
|
||||
owner=None, group=None, logger=None):
|
||||
"""Create a (possibly compressed) tar file from all the files under
|
||||
'base_dir'.
|
||||
|
||||
'compress' must be "gzip" (the default), "bzip2", or None.
|
||||
|
||||
'owner' and 'group' can be used to define an owner and a group for the
|
||||
archive that is being built. If not provided, the current owner and group
|
||||
will be used.
|
||||
|
||||
The output tar file will be named 'base_name' + ".tar", possibly plus
|
||||
the appropriate compression extension (".gz", or ".bz2").
|
||||
|
||||
Returns the output filename.
|
||||
"""
|
||||
tar_compression = {'gzip': 'gz', None: ''}
|
||||
compress_ext = {'gzip': '.gz'}
|
||||
|
||||
if _BZ2_SUPPORTED:
|
||||
tar_compression['bzip2'] = 'bz2'
|
||||
compress_ext['bzip2'] = '.bz2'
|
||||
|
||||
# flags for compression program, each element of list will be an argument
|
||||
if compress is not None and compress not in compress_ext:
|
||||
raise ValueError("bad value for 'compress', or compression format not "
|
||||
"supported : {0}".format(compress))
|
||||
|
||||
archive_name = base_name + '.tar' + compress_ext.get(compress, '')
|
||||
archive_dir = os.path.dirname(archive_name)
|
||||
|
||||
if not os.path.exists(archive_dir):
|
||||
if logger is not None:
|
||||
logger.info("creating %s", archive_dir)
|
||||
if not dry_run:
|
||||
os.makedirs(archive_dir)
|
||||
|
||||
# creating the tarball
|
||||
if logger is not None:
|
||||
logger.info('Creating tar archive')
|
||||
|
||||
uid = _get_uid(owner)
|
||||
gid = _get_gid(group)
|
||||
|
||||
def _set_uid_gid(tarinfo):
|
||||
if gid is not None:
|
||||
tarinfo.gid = gid
|
||||
tarinfo.gname = group
|
||||
if uid is not None:
|
||||
tarinfo.uid = uid
|
||||
tarinfo.uname = owner
|
||||
return tarinfo
|
||||
|
||||
if not dry_run:
|
||||
tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
|
||||
try:
|
||||
tar.add(base_dir, filter=_set_uid_gid)
|
||||
finally:
|
||||
tar.close()
|
||||
|
||||
return archive_name
|
||||
|
||||
def _call_external_zip(base_dir, zip_filename, verbose=False, dry_run=False):
|
||||
# XXX see if we want to keep an external call here
|
||||
if verbose:
|
||||
zipoptions = "-r"
|
||||
else:
|
||||
zipoptions = "-rq"
|
||||
from distutils.errors import DistutilsExecError
|
||||
from distutils.spawn import spawn
|
||||
try:
|
||||
spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run)
|
||||
except DistutilsExecError:
|
||||
# XXX really should distinguish between "couldn't find
|
||||
# external 'zip' command" and "zip failed".
|
||||
raise ExecError("unable to create zip file '%s': "
|
||||
"could neither import the 'zipfile' module nor "
|
||||
"find a standalone zip utility") % zip_filename
|
||||
|
||||
def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None):
|
||||
"""Create a zip file from all the files under 'base_dir'.
|
||||
|
||||
The output zip file will be named 'base_name' + ".zip". Uses either the
|
||||
"zipfile" Python module (if available) or the InfoZIP "zip" utility
|
||||
(if installed and found on the default search path). If neither tool is
|
||||
available, raises ExecError. Returns the name of the output zip
|
||||
file.
|
||||
"""
|
||||
zip_filename = base_name + ".zip"
|
||||
archive_dir = os.path.dirname(base_name)
|
||||
|
||||
if not os.path.exists(archive_dir):
|
||||
if logger is not None:
|
||||
logger.info("creating %s", archive_dir)
|
||||
if not dry_run:
|
||||
os.makedirs(archive_dir)
|
||||
|
||||
# If zipfile module is not available, try spawning an external 'zip'
|
||||
# command.
|
||||
try:
|
||||
import zipfile
|
||||
except ImportError:
|
||||
zipfile = None
|
||||
|
||||
if zipfile is None:
|
||||
_call_external_zip(base_dir, zip_filename, verbose, dry_run)
|
||||
else:
|
||||
if logger is not None:
|
||||
logger.info("creating '%s' and adding '%s' to it",
|
||||
zip_filename, base_dir)
|
||||
|
||||
if not dry_run:
|
||||
zip = zipfile.ZipFile(zip_filename, "w",
|
||||
compression=zipfile.ZIP_DEFLATED)
|
||||
|
||||
for dirpath, dirnames, filenames in os.walk(base_dir):
|
||||
for name in filenames:
|
||||
path = os.path.normpath(os.path.join(dirpath, name))
|
||||
if os.path.isfile(path):
|
||||
zip.write(path, path)
|
||||
if logger is not None:
|
||||
logger.info("adding '%s'", path)
|
||||
zip.close()
|
||||
|
||||
return zip_filename
|
||||
|
||||
_ARCHIVE_FORMATS = {
|
||||
'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
|
||||
'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
|
||||
'tar': (_make_tarball, [('compress', None)], "uncompressed tar file"),
|
||||
'zip': (_make_zipfile, [], "ZIP file"),
|
||||
}
|
||||
|
||||
if _BZ2_SUPPORTED:
|
||||
_ARCHIVE_FORMATS['bztar'] = (_make_tarball, [('compress', 'bzip2')],
|
||||
"bzip2'ed tar-file")
|
||||
|
||||
def get_archive_formats():
|
||||
"""Returns a list of supported formats for archiving and unarchiving.
|
||||
|
||||
Each element of the returned sequence is a tuple (name, description)
|
||||
"""
|
||||
formats = [(name, registry[2]) for name, registry in
|
||||
_ARCHIVE_FORMATS.items()]
|
||||
formats.sort()
|
||||
return formats
|
||||
|
||||
def register_archive_format(name, function, extra_args=None, description=''):
|
||||
"""Registers an archive format.
|
||||
|
||||
name is the name of the format. function is the callable that will be
|
||||
used to create archives. If provided, extra_args is a sequence of
|
||||
(name, value) tuples that will be passed as arguments to the callable.
|
||||
description can be provided to describe the format, and will be returned
|
||||
by the get_archive_formats() function.
|
||||
"""
|
||||
if extra_args is None:
|
||||
extra_args = []
|
||||
if not isinstance(function, Callable):
|
||||
raise TypeError('The %s object is not callable' % function)
|
||||
if not isinstance(extra_args, (tuple, list)):
|
||||
raise TypeError('extra_args needs to be a sequence')
|
||||
for element in extra_args:
|
||||
if not isinstance(element, (tuple, list)) or len(element) !=2:
|
||||
raise TypeError('extra_args elements are : (arg_name, value)')
|
||||
|
||||
_ARCHIVE_FORMATS[name] = (function, extra_args, description)
|
||||
|
||||
def unregister_archive_format(name):
|
||||
del _ARCHIVE_FORMATS[name]
|
||||
|
||||
def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
|
||||
dry_run=0, owner=None, group=None, logger=None):
|
||||
"""Create an archive file (eg. zip or tar).
|
||||
|
||||
'base_name' is the name of the file to create, minus any format-specific
|
||||
extension; 'format' is the archive format: one of "zip", "tar", "bztar"
|
||||
or "gztar".
|
||||
|
||||
'root_dir' is a directory that will be the root directory of the
|
||||
archive; ie. we typically chdir into 'root_dir' before creating the
|
||||
archive. 'base_dir' is the directory where we start archiving from;
|
||||
ie. 'base_dir' will be the common prefix of all files and
|
||||
directories in the archive. 'root_dir' and 'base_dir' both default
|
||||
to the current directory. Returns the name of the archive file.
|
||||
|
||||
'owner' and 'group' are used when creating a tar archive. By default,
|
||||
uses the current owner and group.
|
||||
"""
|
||||
save_cwd = os.getcwd()
|
||||
if root_dir is not None:
|
||||
if logger is not None:
|
||||
logger.debug("changing into '%s'", root_dir)
|
||||
base_name = os.path.abspath(base_name)
|
||||
if not dry_run:
|
||||
os.chdir(root_dir)
|
||||
|
||||
if base_dir is None:
|
||||
base_dir = os.curdir
|
||||
|
||||
kwargs = {'dry_run': dry_run, 'logger': logger}
|
||||
|
||||
try:
|
||||
format_info = _ARCHIVE_FORMATS[format]
|
||||
except KeyError:
|
||||
raise ValueError("unknown archive format '%s'" % format)
|
||||
|
||||
func = format_info[0]
|
||||
for arg, val in format_info[1]:
|
||||
kwargs[arg] = val
|
||||
|
||||
if format != 'zip':
|
||||
kwargs['owner'] = owner
|
||||
kwargs['group'] = group
|
||||
|
||||
try:
|
||||
filename = func(base_name, base_dir, **kwargs)
|
||||
finally:
|
||||
if root_dir is not None:
|
||||
if logger is not None:
|
||||
logger.debug("changing back to '%s'", save_cwd)
|
||||
os.chdir(save_cwd)
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
def get_unpack_formats():
|
||||
"""Returns a list of supported formats for unpacking.
|
||||
|
||||
Each element of the returned sequence is a tuple
|
||||
(name, extensions, description)
|
||||
"""
|
||||
formats = [(name, info[0], info[3]) for name, info in
|
||||
_UNPACK_FORMATS.items()]
|
||||
formats.sort()
|
||||
return formats
|
||||
|
||||
def _check_unpack_options(extensions, function, extra_args):
|
||||
"""Checks what gets registered as an unpacker."""
|
||||
# first make sure no other unpacker is registered for this extension
|
||||
existing_extensions = {}
|
||||
for name, info in _UNPACK_FORMATS.items():
|
||||
for ext in info[0]:
|
||||
existing_extensions[ext] = name
|
||||
|
||||
for extension in extensions:
|
||||
if extension in existing_extensions:
|
||||
msg = '%s is already registered for "%s"'
|
||||
raise RegistryError(msg % (extension,
|
||||
existing_extensions[extension]))
|
||||
|
||||
if not isinstance(function, Callable):
|
||||
raise TypeError('The registered function must be a callable')
|
||||
|
||||
|
||||
def register_unpack_format(name, extensions, function, extra_args=None,
|
||||
description=''):
|
||||
"""Registers an unpack format.
|
||||
|
||||
`name` is the name of the format. `extensions` is a list of extensions
|
||||
corresponding to the format.
|
||||
|
||||
`function` is the callable that will be
|
||||
used to unpack archives. The callable will receive archives to unpack.
|
||||
If it's unable to handle an archive, it needs to raise a ReadError
|
||||
exception.
|
||||
|
||||
If provided, `extra_args` is a sequence of
|
||||
(name, value) tuples that will be passed as arguments to the callable.
|
||||
description can be provided to describe the format, and will be returned
|
||||
by the get_unpack_formats() function.
|
||||
"""
|
||||
if extra_args is None:
|
||||
extra_args = []
|
||||
_check_unpack_options(extensions, function, extra_args)
|
||||
_UNPACK_FORMATS[name] = extensions, function, extra_args, description
|
||||
|
||||
def unregister_unpack_format(name):
|
||||
"""Removes the pack format from the registry."""
|
||||
del _UNPACK_FORMATS[name]
|
||||
|
||||
def _ensure_directory(path):
|
||||
"""Ensure that the parent directory of `path` exists"""
|
||||
dirname = os.path.dirname(path)
|
||||
if not os.path.isdir(dirname):
|
||||
os.makedirs(dirname)
|
||||
|
||||
def _unpack_zipfile(filename, extract_dir):
|
||||
"""Unpack zip `filename` to `extract_dir`
|
||||
"""
|
||||
try:
|
||||
import zipfile
|
||||
except ImportError:
|
||||
raise ReadError('zlib not supported, cannot unpack this archive.')
|
||||
|
||||
if not zipfile.is_zipfile(filename):
|
||||
raise ReadError("%s is not a zip file" % filename)
|
||||
|
||||
zip = zipfile.ZipFile(filename)
|
||||
try:
|
||||
for info in zip.infolist():
|
||||
name = info.filename
|
||||
|
||||
# don't extract absolute paths or ones with .. in them
|
||||
if name.startswith('/') or '..' in name:
|
||||
continue
|
||||
|
||||
target = os.path.join(extract_dir, *name.split('/'))
|
||||
if not target:
|
||||
continue
|
||||
|
||||
_ensure_directory(target)
|
||||
if not name.endswith('/'):
|
||||
# file
|
||||
data = zip.read(info.filename)
|
||||
f = open(target, 'wb')
|
||||
try:
|
||||
f.write(data)
|
||||
finally:
|
||||
f.close()
|
||||
del data
|
||||
finally:
|
||||
zip.close()
|
||||
|
||||
def _unpack_tarfile(filename, extract_dir):
|
||||
"""Unpack tar/tar.gz/tar.bz2 `filename` to `extract_dir`
|
||||
"""
|
||||
try:
|
||||
tarobj = tarfile.open(filename)
|
||||
except tarfile.TarError:
|
||||
raise ReadError(
|
||||
"%s is not a compressed or uncompressed tar file" % filename)
|
||||
try:
|
||||
tarobj.extractall(extract_dir)
|
||||
finally:
|
||||
tarobj.close()
|
||||
|
||||
_UNPACK_FORMATS = {
|
||||
'gztar': (['.tar.gz', '.tgz'], _unpack_tarfile, [], "gzip'ed tar-file"),
|
||||
'tar': (['.tar'], _unpack_tarfile, [], "uncompressed tar file"),
|
||||
'zip': (['.zip'], _unpack_zipfile, [], "ZIP file")
|
||||
}
|
||||
|
||||
if _BZ2_SUPPORTED:
|
||||
_UNPACK_FORMATS['bztar'] = (['.bz2'], _unpack_tarfile, [],
|
||||
"bzip2'ed tar-file")
|
||||
|
||||
def _find_unpack_format(filename):
|
||||
for name, info in _UNPACK_FORMATS.items():
|
||||
for extension in info[0]:
|
||||
if filename.endswith(extension):
|
||||
return name
|
||||
return None
|
||||
|
||||
def unpack_archive(filename, extract_dir=None, format=None):
|
||||
"""Unpack an archive.
|
||||
|
||||
`filename` is the name of the archive.
|
||||
|
||||
`extract_dir` is the name of the target directory, where the archive
|
||||
is unpacked. If not provided, the current working directory is used.
|
||||
|
||||
`format` is the archive format: one of "zip", "tar", or "gztar". Or any
|
||||
other registered format. If not provided, unpack_archive will use the
|
||||
filename extension and see if an unpacker was registered for that
|
||||
extension.
|
||||
|
||||
In case none is found, a ValueError is raised.
|
||||
"""
|
||||
if extract_dir is None:
|
||||
extract_dir = os.getcwd()
|
||||
|
||||
if format is not None:
|
||||
try:
|
||||
format_info = _UNPACK_FORMATS[format]
|
||||
except KeyError:
|
||||
raise ValueError("Unknown unpack format '{0}'".format(format))
|
||||
|
||||
func = format_info[1]
|
||||
func(filename, extract_dir, **dict(format_info[2]))
|
||||
else:
|
||||
# we need to look at the registered unpackers supported extensions
|
||||
format = _find_unpack_format(filename)
|
||||
if format is None:
|
||||
raise ReadError("Unknown archive format '{0}'".format(filename))
|
||||
|
||||
func = _UNPACK_FORMATS[format][1]
|
||||
kwargs = dict(_UNPACK_FORMATS[format][2])
|
||||
func(filename, extract_dir, **kwargs)
|
||||
@@ -0,0 +1,84 @@
|
||||
[posix_prefix]
|
||||
# Configuration directories. Some of these come straight out of the
|
||||
# configure script. They are for implementing the other variables, not to
|
||||
# be used directly in [resource_locations].
|
||||
confdir = /etc
|
||||
datadir = /usr/share
|
||||
libdir = /usr/lib
|
||||
statedir = /var
|
||||
# User resource directory
|
||||
local = ~/.local/{distribution.name}
|
||||
|
||||
stdlib = {base}/lib/python{py_version_short}
|
||||
platstdlib = {platbase}/lib/python{py_version_short}
|
||||
purelib = {base}/lib/python{py_version_short}/site-packages
|
||||
platlib = {platbase}/lib/python{py_version_short}/site-packages
|
||||
include = {base}/include/python{py_version_short}{abiflags}
|
||||
platinclude = {platbase}/include/python{py_version_short}{abiflags}
|
||||
data = {base}
|
||||
|
||||
[posix_home]
|
||||
stdlib = {base}/lib/python
|
||||
platstdlib = {base}/lib/python
|
||||
purelib = {base}/lib/python
|
||||
platlib = {base}/lib/python
|
||||
include = {base}/include/python
|
||||
platinclude = {base}/include/python
|
||||
scripts = {base}/bin
|
||||
data = {base}
|
||||
|
||||
[nt]
|
||||
stdlib = {base}/Lib
|
||||
platstdlib = {base}/Lib
|
||||
purelib = {base}/Lib/site-packages
|
||||
platlib = {base}/Lib/site-packages
|
||||
include = {base}/Include
|
||||
platinclude = {base}/Include
|
||||
scripts = {base}/Scripts
|
||||
data = {base}
|
||||
|
||||
[os2]
|
||||
stdlib = {base}/Lib
|
||||
platstdlib = {base}/Lib
|
||||
purelib = {base}/Lib/site-packages
|
||||
platlib = {base}/Lib/site-packages
|
||||
include = {base}/Include
|
||||
platinclude = {base}/Include
|
||||
scripts = {base}/Scripts
|
||||
data = {base}
|
||||
|
||||
[os2_home]
|
||||
stdlib = {userbase}/lib/python{py_version_short}
|
||||
platstdlib = {userbase}/lib/python{py_version_short}
|
||||
purelib = {userbase}/lib/python{py_version_short}/site-packages
|
||||
platlib = {userbase}/lib/python{py_version_short}/site-packages
|
||||
include = {userbase}/include/python{py_version_short}
|
||||
scripts = {userbase}/bin
|
||||
data = {userbase}
|
||||
|
||||
[nt_user]
|
||||
stdlib = {userbase}/Python{py_version_nodot}
|
||||
platstdlib = {userbase}/Python{py_version_nodot}
|
||||
purelib = {userbase}/Python{py_version_nodot}/site-packages
|
||||
platlib = {userbase}/Python{py_version_nodot}/site-packages
|
||||
include = {userbase}/Python{py_version_nodot}/Include
|
||||
scripts = {userbase}/Scripts
|
||||
data = {userbase}
|
||||
|
||||
[posix_user]
|
||||
stdlib = {userbase}/lib/python{py_version_short}
|
||||
platstdlib = {userbase}/lib/python{py_version_short}
|
||||
purelib = {userbase}/lib/python{py_version_short}/site-packages
|
||||
platlib = {userbase}/lib/python{py_version_short}/site-packages
|
||||
include = {userbase}/include/python{py_version_short}
|
||||
scripts = {userbase}/bin
|
||||
data = {userbase}
|
||||
|
||||
[osx_framework_user]
|
||||
stdlib = {userbase}/lib/python
|
||||
platstdlib = {userbase}/lib/python
|
||||
purelib = {userbase}/lib/python/site-packages
|
||||
platlib = {userbase}/lib/python/site-packages
|
||||
include = {userbase}/include
|
||||
scripts = {userbase}/bin
|
||||
data = {userbase}
|
||||
@@ -0,0 +1,786 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012 The Python Software Foundation.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
"""Access to Python's configuration information."""
|
||||
|
||||
import codecs
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from os.path import pardir, realpath
|
||||
try:
|
||||
import configparser
|
||||
except ImportError:
|
||||
import ConfigParser as configparser
|
||||
|
||||
|
||||
__all__ = [
|
||||
'get_config_h_filename',
|
||||
'get_config_var',
|
||||
'get_config_vars',
|
||||
'get_makefile_filename',
|
||||
'get_path',
|
||||
'get_path_names',
|
||||
'get_paths',
|
||||
'get_platform',
|
||||
'get_python_version',
|
||||
'get_scheme_names',
|
||||
'parse_config_h',
|
||||
]
|
||||
|
||||
|
||||
def _safe_realpath(path):
|
||||
try:
|
||||
return realpath(path)
|
||||
except OSError:
|
||||
return path
|
||||
|
||||
|
||||
if sys.executable:
|
||||
_PROJECT_BASE = os.path.dirname(_safe_realpath(sys.executable))
|
||||
else:
|
||||
# sys.executable can be empty if argv[0] has been changed and Python is
|
||||
# unable to retrieve the real program name
|
||||
_PROJECT_BASE = _safe_realpath(os.getcwd())
|
||||
|
||||
if os.name == "nt" and "pcbuild" in _PROJECT_BASE[-8:].lower():
|
||||
_PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir))
|
||||
# PC/VS7.1
|
||||
if os.name == "nt" and "\\pc\\v" in _PROJECT_BASE[-10:].lower():
|
||||
_PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir))
|
||||
# PC/AMD64
|
||||
if os.name == "nt" and "\\pcbuild\\amd64" in _PROJECT_BASE[-14:].lower():
|
||||
_PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir))
|
||||
|
||||
|
||||
def is_python_build():
|
||||
for fn in ("Setup.dist", "Setup.local"):
|
||||
if os.path.isfile(os.path.join(_PROJECT_BASE, "Modules", fn)):
|
||||
return True
|
||||
return False
|
||||
|
||||
_PYTHON_BUILD = is_python_build()
|
||||
|
||||
_cfg_read = False
|
||||
|
||||
def _ensure_cfg_read():
|
||||
global _cfg_read
|
||||
if not _cfg_read:
|
||||
from ..resources import finder
|
||||
backport_package = __name__.rsplit('.', 1)[0]
|
||||
_finder = finder(backport_package)
|
||||
_cfgfile = _finder.find('sysconfig.cfg')
|
||||
assert _cfgfile, 'sysconfig.cfg exists'
|
||||
with _cfgfile.as_stream() as s:
|
||||
_SCHEMES.readfp(s)
|
||||
if _PYTHON_BUILD:
|
||||
for scheme in ('posix_prefix', 'posix_home'):
|
||||
_SCHEMES.set(scheme, 'include', '{srcdir}/Include')
|
||||
_SCHEMES.set(scheme, 'platinclude', '{projectbase}/.')
|
||||
|
||||
_cfg_read = True
|
||||
|
||||
|
||||
_SCHEMES = configparser.RawConfigParser()
|
||||
_VAR_REPL = re.compile(r'\{([^{]*?)\}')
|
||||
|
||||
def _expand_globals(config):
|
||||
_ensure_cfg_read()
|
||||
if config.has_section('globals'):
|
||||
globals = config.items('globals')
|
||||
else:
|
||||
globals = tuple()
|
||||
|
||||
sections = config.sections()
|
||||
for section in sections:
|
||||
if section == 'globals':
|
||||
continue
|
||||
for option, value in globals:
|
||||
if config.has_option(section, option):
|
||||
continue
|
||||
config.set(section, option, value)
|
||||
config.remove_section('globals')
|
||||
|
||||
# now expanding local variables defined in the cfg file
|
||||
#
|
||||
for section in config.sections():
|
||||
variables = dict(config.items(section))
|
||||
|
||||
def _replacer(matchobj):
|
||||
name = matchobj.group(1)
|
||||
if name in variables:
|
||||
return variables[name]
|
||||
return matchobj.group(0)
|
||||
|
||||
for option, value in config.items(section):
|
||||
config.set(section, option, _VAR_REPL.sub(_replacer, value))
|
||||
|
||||
#_expand_globals(_SCHEMES)
|
||||
|
||||
_PY_VERSION = '%s.%s.%s' % sys.version_info[:3]
|
||||
_PY_VERSION_SHORT = '%s.%s' % sys.version_info[:2]
|
||||
_PY_VERSION_SHORT_NO_DOT = '%s%s' % sys.version_info[:2]
|
||||
_PREFIX = os.path.normpath(sys.prefix)
|
||||
_EXEC_PREFIX = os.path.normpath(sys.exec_prefix)
|
||||
_CONFIG_VARS = None
|
||||
_USER_BASE = None
|
||||
|
||||
|
||||
def _subst_vars(path, local_vars):
|
||||
"""In the string `path`, replace tokens like {some.thing} with the
|
||||
corresponding value from the map `local_vars`.
|
||||
|
||||
If there is no corresponding value, leave the token unchanged.
|
||||
"""
|
||||
def _replacer(matchobj):
|
||||
name = matchobj.group(1)
|
||||
if name in local_vars:
|
||||
return local_vars[name]
|
||||
elif name in os.environ:
|
||||
return os.environ[name]
|
||||
return matchobj.group(0)
|
||||
return _VAR_REPL.sub(_replacer, path)
|
||||
|
||||
|
||||
def _extend_dict(target_dict, other_dict):
|
||||
target_keys = target_dict.keys()
|
||||
for key, value in other_dict.items():
|
||||
if key in target_keys:
|
||||
continue
|
||||
target_dict[key] = value
|
||||
|
||||
|
||||
def _expand_vars(scheme, vars):
|
||||
res = {}
|
||||
if vars is None:
|
||||
vars = {}
|
||||
_extend_dict(vars, get_config_vars())
|
||||
|
||||
for key, value in _SCHEMES.items(scheme):
|
||||
if os.name in ('posix', 'nt'):
|
||||
value = os.path.expanduser(value)
|
||||
res[key] = os.path.normpath(_subst_vars(value, vars))
|
||||
return res
|
||||
|
||||
|
||||
def format_value(value, vars):
|
||||
def _replacer(matchobj):
|
||||
name = matchobj.group(1)
|
||||
if name in vars:
|
||||
return vars[name]
|
||||
return matchobj.group(0)
|
||||
return _VAR_REPL.sub(_replacer, value)
|
||||
|
||||
|
||||
def _get_default_scheme():
|
||||
if os.name == 'posix':
|
||||
# the default scheme for posix is posix_prefix
|
||||
return 'posix_prefix'
|
||||
return os.name
|
||||
|
||||
|
||||
def _getuserbase():
|
||||
env_base = os.environ.get("PYTHONUSERBASE", None)
|
||||
|
||||
def joinuser(*args):
|
||||
return os.path.expanduser(os.path.join(*args))
|
||||
|
||||
# what about 'os2emx', 'riscos' ?
|
||||
if os.name == "nt":
|
||||
base = os.environ.get("APPDATA") or "~"
|
||||
if env_base:
|
||||
return env_base
|
||||
else:
|
||||
return joinuser(base, "Python")
|
||||
|
||||
if sys.platform == "darwin":
|
||||
framework = get_config_var("PYTHONFRAMEWORK")
|
||||
if framework:
|
||||
if env_base:
|
||||
return env_base
|
||||
else:
|
||||
return joinuser("~", "Library", framework, "%d.%d" %
|
||||
sys.version_info[:2])
|
||||
|
||||
if env_base:
|
||||
return env_base
|
||||
else:
|
||||
return joinuser("~", ".local")
|
||||
|
||||
|
||||
def _parse_makefile(filename, vars=None):
|
||||
"""Parse a Makefile-style file.
|
||||
|
||||
A dictionary containing name/value pairs is returned. If an
|
||||
optional dictionary is passed in as the second argument, it is
|
||||
used instead of a new dictionary.
|
||||
"""
|
||||
# Regexes needed for parsing Makefile (and similar syntaxes,
|
||||
# like old-style Setup files).
|
||||
_variable_rx = re.compile(r"([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)")
|
||||
_findvar1_rx = re.compile(r"\$\(([A-Za-z][A-Za-z0-9_]*)\)")
|
||||
_findvar2_rx = re.compile(r"\${([A-Za-z][A-Za-z0-9_]*)}")
|
||||
|
||||
if vars is None:
|
||||
vars = {}
|
||||
done = {}
|
||||
notdone = {}
|
||||
|
||||
with codecs.open(filename, encoding='utf-8', errors="surrogateescape") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
for line in lines:
|
||||
if line.startswith('#') or line.strip() == '':
|
||||
continue
|
||||
m = _variable_rx.match(line)
|
||||
if m:
|
||||
n, v = m.group(1, 2)
|
||||
v = v.strip()
|
||||
# `$$' is a literal `$' in make
|
||||
tmpv = v.replace('$$', '')
|
||||
|
||||
if "$" in tmpv:
|
||||
notdone[n] = v
|
||||
else:
|
||||
try:
|
||||
v = int(v)
|
||||
except ValueError:
|
||||
# insert literal `$'
|
||||
done[n] = v.replace('$$', '$')
|
||||
else:
|
||||
done[n] = v
|
||||
|
||||
# do variable interpolation here
|
||||
variables = list(notdone.keys())
|
||||
|
||||
# Variables with a 'PY_' prefix in the makefile. These need to
|
||||
# be made available without that prefix through sysconfig.
|
||||
# Special care is needed to ensure that variable expansion works, even
|
||||
# if the expansion uses the name without a prefix.
|
||||
renamed_variables = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS')
|
||||
|
||||
while len(variables) > 0:
|
||||
for name in tuple(variables):
|
||||
value = notdone[name]
|
||||
m = _findvar1_rx.search(value) or _findvar2_rx.search(value)
|
||||
if m is not None:
|
||||
n = m.group(1)
|
||||
found = True
|
||||
if n in done:
|
||||
item = str(done[n])
|
||||
elif n in notdone:
|
||||
# get it on a subsequent round
|
||||
found = False
|
||||
elif n in os.environ:
|
||||
# do it like make: fall back to environment
|
||||
item = os.environ[n]
|
||||
|
||||
elif n in renamed_variables:
|
||||
if (name.startswith('PY_') and
|
||||
name[3:] in renamed_variables):
|
||||
item = ""
|
||||
|
||||
elif 'PY_' + n in notdone:
|
||||
found = False
|
||||
|
||||
else:
|
||||
item = str(done['PY_' + n])
|
||||
|
||||
else:
|
||||
done[n] = item = ""
|
||||
|
||||
if found:
|
||||
after = value[m.end():]
|
||||
value = value[:m.start()] + item + after
|
||||
if "$" in after:
|
||||
notdone[name] = value
|
||||
else:
|
||||
try:
|
||||
value = int(value)
|
||||
except ValueError:
|
||||
done[name] = value.strip()
|
||||
else:
|
||||
done[name] = value
|
||||
variables.remove(name)
|
||||
|
||||
if (name.startswith('PY_') and
|
||||
name[3:] in renamed_variables):
|
||||
|
||||
name = name[3:]
|
||||
if name not in done:
|
||||
done[name] = value
|
||||
|
||||
else:
|
||||
# bogus variable reference (e.g. "prefix=$/opt/python");
|
||||
# just drop it since we can't deal
|
||||
done[name] = value
|
||||
variables.remove(name)
|
||||
|
||||
# strip spurious spaces
|
||||
for k, v in done.items():
|
||||
if isinstance(v, str):
|
||||
done[k] = v.strip()
|
||||
|
||||
# save the results in the global dictionary
|
||||
vars.update(done)
|
||||
return vars
|
||||
|
||||
|
||||
def get_makefile_filename():
|
||||
"""Return the path of the Makefile."""
|
||||
if _PYTHON_BUILD:
|
||||
return os.path.join(_PROJECT_BASE, "Makefile")
|
||||
if hasattr(sys, 'abiflags'):
|
||||
config_dir_name = 'config-%s%s' % (_PY_VERSION_SHORT, sys.abiflags)
|
||||
else:
|
||||
config_dir_name = 'config'
|
||||
return os.path.join(get_path('stdlib'), config_dir_name, 'Makefile')
|
||||
|
||||
|
||||
def _init_posix(vars):
|
||||
"""Initialize the module as appropriate for POSIX systems."""
|
||||
# load the installed Makefile:
|
||||
makefile = get_makefile_filename()
|
||||
try:
|
||||
_parse_makefile(makefile, vars)
|
||||
except IOError as e:
|
||||
msg = "invalid Python installation: unable to open %s" % makefile
|
||||
if hasattr(e, "strerror"):
|
||||
msg = msg + " (%s)" % e.strerror
|
||||
raise IOError(msg)
|
||||
# load the installed pyconfig.h:
|
||||
config_h = get_config_h_filename()
|
||||
try:
|
||||
with open(config_h) as f:
|
||||
parse_config_h(f, vars)
|
||||
except IOError as e:
|
||||
msg = "invalid Python installation: unable to open %s" % config_h
|
||||
if hasattr(e, "strerror"):
|
||||
msg = msg + " (%s)" % e.strerror
|
||||
raise IOError(msg)
|
||||
# On AIX, there are wrong paths to the linker scripts in the Makefile
|
||||
# -- these paths are relative to the Python source, but when installed
|
||||
# the scripts are in another directory.
|
||||
if _PYTHON_BUILD:
|
||||
vars['LDSHARED'] = vars['BLDSHARED']
|
||||
|
||||
|
||||
def _init_non_posix(vars):
|
||||
"""Initialize the module as appropriate for NT"""
|
||||
# set basic install directories
|
||||
vars['LIBDEST'] = get_path('stdlib')
|
||||
vars['BINLIBDEST'] = get_path('platstdlib')
|
||||
vars['INCLUDEPY'] = get_path('include')
|
||||
vars['SO'] = '.pyd'
|
||||
vars['EXE'] = '.exe'
|
||||
vars['VERSION'] = _PY_VERSION_SHORT_NO_DOT
|
||||
vars['BINDIR'] = os.path.dirname(_safe_realpath(sys.executable))
|
||||
|
||||
#
|
||||
# public APIs
|
||||
#
|
||||
|
||||
|
||||
def parse_config_h(fp, vars=None):
|
||||
"""Parse a config.h-style file.
|
||||
|
||||
A dictionary containing name/value pairs is returned. If an
|
||||
optional dictionary is passed in as the second argument, it is
|
||||
used instead of a new dictionary.
|
||||
"""
|
||||
if vars is None:
|
||||
vars = {}
|
||||
define_rx = re.compile("#define ([A-Z][A-Za-z0-9_]+) (.*)\n")
|
||||
undef_rx = re.compile("/[*] #undef ([A-Z][A-Za-z0-9_]+) [*]/\n")
|
||||
|
||||
while True:
|
||||
line = fp.readline()
|
||||
if not line:
|
||||
break
|
||||
m = define_rx.match(line)
|
||||
if m:
|
||||
n, v = m.group(1, 2)
|
||||
try:
|
||||
v = int(v)
|
||||
except ValueError:
|
||||
pass
|
||||
vars[n] = v
|
||||
else:
|
||||
m = undef_rx.match(line)
|
||||
if m:
|
||||
vars[m.group(1)] = 0
|
||||
return vars
|
||||
|
||||
|
||||
def get_config_h_filename():
|
||||
"""Return the path of pyconfig.h."""
|
||||
if _PYTHON_BUILD:
|
||||
if os.name == "nt":
|
||||
inc_dir = os.path.join(_PROJECT_BASE, "PC")
|
||||
else:
|
||||
inc_dir = _PROJECT_BASE
|
||||
else:
|
||||
inc_dir = get_path('platinclude')
|
||||
return os.path.join(inc_dir, 'pyconfig.h')
|
||||
|
||||
|
||||
def get_scheme_names():
|
||||
"""Return a tuple containing the schemes names."""
|
||||
return tuple(sorted(_SCHEMES.sections()))
|
||||
|
||||
|
||||
def get_path_names():
|
||||
"""Return a tuple containing the paths names."""
|
||||
# xxx see if we want a static list
|
||||
return _SCHEMES.options('posix_prefix')
|
||||
|
||||
|
||||
def get_paths(scheme=_get_default_scheme(), vars=None, expand=True):
|
||||
"""Return a mapping containing an install scheme.
|
||||
|
||||
``scheme`` is the install scheme name. If not provided, it will
|
||||
return the default scheme for the current platform.
|
||||
"""
|
||||
_ensure_cfg_read()
|
||||
if expand:
|
||||
return _expand_vars(scheme, vars)
|
||||
else:
|
||||
return dict(_SCHEMES.items(scheme))
|
||||
|
||||
|
||||
def get_path(name, scheme=_get_default_scheme(), vars=None, expand=True):
|
||||
"""Return a path corresponding to the scheme.
|
||||
|
||||
``scheme`` is the install scheme name.
|
||||
"""
|
||||
return get_paths(scheme, vars, expand)[name]
|
||||
|
||||
|
||||
def get_config_vars(*args):
|
||||
"""With no arguments, return a dictionary of all configuration
|
||||
variables relevant for the current platform.
|
||||
|
||||
On Unix, this means every variable defined in Python's installed Makefile;
|
||||
On Windows and Mac OS it's a much smaller set.
|
||||
|
||||
With arguments, return a list of values that result from looking up
|
||||
each argument in the configuration variable dictionary.
|
||||
"""
|
||||
global _CONFIG_VARS
|
||||
if _CONFIG_VARS is None:
|
||||
_CONFIG_VARS = {}
|
||||
# Normalized versions of prefix and exec_prefix are handy to have;
|
||||
# in fact, these are the standard versions used most places in the
|
||||
# distutils2 module.
|
||||
_CONFIG_VARS['prefix'] = _PREFIX
|
||||
_CONFIG_VARS['exec_prefix'] = _EXEC_PREFIX
|
||||
_CONFIG_VARS['py_version'] = _PY_VERSION
|
||||
_CONFIG_VARS['py_version_short'] = _PY_VERSION_SHORT
|
||||
_CONFIG_VARS['py_version_nodot'] = _PY_VERSION[0] + _PY_VERSION[2]
|
||||
_CONFIG_VARS['base'] = _PREFIX
|
||||
_CONFIG_VARS['platbase'] = _EXEC_PREFIX
|
||||
_CONFIG_VARS['projectbase'] = _PROJECT_BASE
|
||||
try:
|
||||
_CONFIG_VARS['abiflags'] = sys.abiflags
|
||||
except AttributeError:
|
||||
# sys.abiflags may not be defined on all platforms.
|
||||
_CONFIG_VARS['abiflags'] = ''
|
||||
|
||||
if os.name in ('nt', 'os2'):
|
||||
_init_non_posix(_CONFIG_VARS)
|
||||
if os.name == 'posix':
|
||||
_init_posix(_CONFIG_VARS)
|
||||
# Setting 'userbase' is done below the call to the
|
||||
# init function to enable using 'get_config_var' in
|
||||
# the init-function.
|
||||
if sys.version >= '2.6':
|
||||
_CONFIG_VARS['userbase'] = _getuserbase()
|
||||
|
||||
if 'srcdir' not in _CONFIG_VARS:
|
||||
_CONFIG_VARS['srcdir'] = _PROJECT_BASE
|
||||
else:
|
||||
_CONFIG_VARS['srcdir'] = _safe_realpath(_CONFIG_VARS['srcdir'])
|
||||
|
||||
# Convert srcdir into an absolute path if it appears necessary.
|
||||
# Normally it is relative to the build directory. However, during
|
||||
# testing, for example, we might be running a non-installed python
|
||||
# from a different directory.
|
||||
if _PYTHON_BUILD and os.name == "posix":
|
||||
base = _PROJECT_BASE
|
||||
try:
|
||||
cwd = os.getcwd()
|
||||
except OSError:
|
||||
cwd = None
|
||||
if (not os.path.isabs(_CONFIG_VARS['srcdir']) and
|
||||
base != cwd):
|
||||
# srcdir is relative and we are not in the same directory
|
||||
# as the executable. Assume executable is in the build
|
||||
# directory and make srcdir absolute.
|
||||
srcdir = os.path.join(base, _CONFIG_VARS['srcdir'])
|
||||
_CONFIG_VARS['srcdir'] = os.path.normpath(srcdir)
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
kernel_version = os.uname()[2] # Kernel version (8.4.3)
|
||||
major_version = int(kernel_version.split('.')[0])
|
||||
|
||||
if major_version < 8:
|
||||
# On Mac OS X before 10.4, check if -arch and -isysroot
|
||||
# are in CFLAGS or LDFLAGS and remove them if they are.
|
||||
# This is needed when building extensions on a 10.3 system
|
||||
# using a universal build of python.
|
||||
for key in ('LDFLAGS', 'BASECFLAGS',
|
||||
# a number of derived variables. These need to be
|
||||
# patched up as well.
|
||||
'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
|
||||
flags = _CONFIG_VARS[key]
|
||||
flags = re.sub(r'-arch\s+\w+\s', ' ', flags)
|
||||
flags = re.sub('-isysroot [^ \t]*', ' ', flags)
|
||||
_CONFIG_VARS[key] = flags
|
||||
else:
|
||||
# Allow the user to override the architecture flags using
|
||||
# an environment variable.
|
||||
# NOTE: This name was introduced by Apple in OSX 10.5 and
|
||||
# is used by several scripting languages distributed with
|
||||
# that OS release.
|
||||
if 'ARCHFLAGS' in os.environ:
|
||||
arch = os.environ['ARCHFLAGS']
|
||||
for key in ('LDFLAGS', 'BASECFLAGS',
|
||||
# a number of derived variables. These need to be
|
||||
# patched up as well.
|
||||
'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
|
||||
|
||||
flags = _CONFIG_VARS[key]
|
||||
flags = re.sub(r'-arch\s+\w+\s', ' ', flags)
|
||||
flags = flags + ' ' + arch
|
||||
_CONFIG_VARS[key] = flags
|
||||
|
||||
# If we're on OSX 10.5 or later and the user tries to
|
||||
# compiles an extension using an SDK that is not present
|
||||
# on the current machine it is better to not use an SDK
|
||||
# than to fail.
|
||||
#
|
||||
# The major usecase for this is users using a Python.org
|
||||
# binary installer on OSX 10.6: that installer uses
|
||||
# the 10.4u SDK, but that SDK is not installed by default
|
||||
# when you install Xcode.
|
||||
#
|
||||
CFLAGS = _CONFIG_VARS.get('CFLAGS', '')
|
||||
m = re.search(r'-isysroot\s+(\S+)', CFLAGS)
|
||||
if m is not None:
|
||||
sdk = m.group(1)
|
||||
if not os.path.exists(sdk):
|
||||
for key in ('LDFLAGS', 'BASECFLAGS',
|
||||
# a number of derived variables. These need to be
|
||||
# patched up as well.
|
||||
'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
|
||||
|
||||
flags = _CONFIG_VARS[key]
|
||||
flags = re.sub(r'-isysroot\s+\S+(\s|$)', ' ', flags)
|
||||
_CONFIG_VARS[key] = flags
|
||||
|
||||
if args:
|
||||
vals = []
|
||||
for name in args:
|
||||
vals.append(_CONFIG_VARS.get(name))
|
||||
return vals
|
||||
else:
|
||||
return _CONFIG_VARS
|
||||
|
||||
|
||||
def get_config_var(name):
|
||||
"""Return the value of a single variable using the dictionary returned by
|
||||
'get_config_vars()'.
|
||||
|
||||
Equivalent to get_config_vars().get(name)
|
||||
"""
|
||||
return get_config_vars().get(name)
|
||||
|
||||
|
||||
def get_platform():
|
||||
"""Return a string that identifies the current platform.
|
||||
|
||||
This is used mainly to distinguish platform-specific build directories and
|
||||
platform-specific built distributions. Typically includes the OS name
|
||||
and version and the architecture (as supplied by 'os.uname()'),
|
||||
although the exact information included depends on the OS; eg. for IRIX
|
||||
the architecture isn't particularly important (IRIX only runs on SGI
|
||||
hardware), but for Linux the kernel version isn't particularly
|
||||
important.
|
||||
|
||||
Examples of returned values:
|
||||
linux-i586
|
||||
linux-alpha (?)
|
||||
solaris-2.6-sun4u
|
||||
irix-5.3
|
||||
irix64-6.2
|
||||
|
||||
Windows will return one of:
|
||||
win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc)
|
||||
win-ia64 (64bit Windows on Itanium)
|
||||
win32 (all others - specifically, sys.platform is returned)
|
||||
|
||||
For other non-POSIX platforms, currently just returns 'sys.platform'.
|
||||
"""
|
||||
if os.name == 'nt':
|
||||
# sniff sys.version for architecture.
|
||||
prefix = " bit ("
|
||||
i = sys.version.find(prefix)
|
||||
if i == -1:
|
||||
return sys.platform
|
||||
j = sys.version.find(")", i)
|
||||
look = sys.version[i+len(prefix):j].lower()
|
||||
if look == 'amd64':
|
||||
return 'win-amd64'
|
||||
if look == 'itanium':
|
||||
return 'win-ia64'
|
||||
return sys.platform
|
||||
|
||||
if os.name != "posix" or not hasattr(os, 'uname'):
|
||||
# XXX what about the architecture? NT is Intel or Alpha,
|
||||
# Mac OS is M68k or PPC, etc.
|
||||
return sys.platform
|
||||
|
||||
# Try to distinguish various flavours of Unix
|
||||
osname, host, release, version, machine = os.uname()
|
||||
|
||||
# Convert the OS name to lowercase, remove '/' characters
|
||||
# (to accommodate BSD/OS), and translate spaces (for "Power Macintosh")
|
||||
osname = osname.lower().replace('/', '')
|
||||
machine = machine.replace(' ', '_')
|
||||
machine = machine.replace('/', '-')
|
||||
|
||||
if osname[:5] == "linux":
|
||||
# At least on Linux/Intel, 'machine' is the processor --
|
||||
# i386, etc.
|
||||
# XXX what about Alpha, SPARC, etc?
|
||||
return "%s-%s" % (osname, machine)
|
||||
elif osname[:5] == "sunos":
|
||||
if release[0] >= "5": # SunOS 5 == Solaris 2
|
||||
osname = "solaris"
|
||||
release = "%d.%s" % (int(release[0]) - 3, release[2:])
|
||||
# fall through to standard osname-release-machine representation
|
||||
elif osname[:4] == "irix": # could be "irix64"!
|
||||
return "%s-%s" % (osname, release)
|
||||
elif osname[:3] == "aix":
|
||||
return "%s-%s.%s" % (osname, version, release)
|
||||
elif osname[:6] == "cygwin":
|
||||
osname = "cygwin"
|
||||
rel_re = re.compile(r'[\d.]+')
|
||||
m = rel_re.match(release)
|
||||
if m:
|
||||
release = m.group()
|
||||
elif osname[:6] == "darwin":
|
||||
#
|
||||
# For our purposes, we'll assume that the system version from
|
||||
# distutils' perspective is what MACOSX_DEPLOYMENT_TARGET is set
|
||||
# to. This makes the compatibility story a bit more sane because the
|
||||
# machine is going to compile and link as if it were
|
||||
# MACOSX_DEPLOYMENT_TARGET.
|
||||
cfgvars = get_config_vars()
|
||||
macver = cfgvars.get('MACOSX_DEPLOYMENT_TARGET')
|
||||
|
||||
if True:
|
||||
# Always calculate the release of the running machine,
|
||||
# needed to determine if we can build fat binaries or not.
|
||||
|
||||
macrelease = macver
|
||||
# Get the system version. Reading this plist is a documented
|
||||
# way to get the system version (see the documentation for
|
||||
# the Gestalt Manager)
|
||||
try:
|
||||
f = open('/System/Library/CoreServices/SystemVersion.plist')
|
||||
except IOError:
|
||||
# We're on a plain darwin box, fall back to the default
|
||||
# behaviour.
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
m = re.search(r'<key>ProductUserVisibleVersion</key>\s*'
|
||||
r'<string>(.*?)</string>', f.read())
|
||||
finally:
|
||||
f.close()
|
||||
if m is not None:
|
||||
macrelease = '.'.join(m.group(1).split('.')[:2])
|
||||
# else: fall back to the default behaviour
|
||||
|
||||
if not macver:
|
||||
macver = macrelease
|
||||
|
||||
if macver:
|
||||
release = macver
|
||||
osname = "macosx"
|
||||
|
||||
if ((macrelease + '.') >= '10.4.' and
|
||||
'-arch' in get_config_vars().get('CFLAGS', '').strip()):
|
||||
# The universal build will build fat binaries, but not on
|
||||
# systems before 10.4
|
||||
#
|
||||
# Try to detect 4-way universal builds, those have machine-type
|
||||
# 'universal' instead of 'fat'.
|
||||
|
||||
machine = 'fat'
|
||||
cflags = get_config_vars().get('CFLAGS')
|
||||
|
||||
archs = re.findall(r'-arch\s+(\S+)', cflags)
|
||||
archs = tuple(sorted(set(archs)))
|
||||
|
||||
if len(archs) == 1:
|
||||
machine = archs[0]
|
||||
elif archs == ('i386', 'ppc'):
|
||||
machine = 'fat'
|
||||
elif archs == ('i386', 'x86_64'):
|
||||
machine = 'intel'
|
||||
elif archs == ('i386', 'ppc', 'x86_64'):
|
||||
machine = 'fat3'
|
||||
elif archs == ('ppc64', 'x86_64'):
|
||||
machine = 'fat64'
|
||||
elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'):
|
||||
machine = 'universal'
|
||||
else:
|
||||
raise ValueError(
|
||||
"Don't know machine value for archs=%r" % (archs,))
|
||||
|
||||
elif machine == 'i386':
|
||||
# On OSX the machine type returned by uname is always the
|
||||
# 32-bit variant, even if the executable architecture is
|
||||
# the 64-bit variant
|
||||
if sys.maxsize >= 2**32:
|
||||
machine = 'x86_64'
|
||||
|
||||
elif machine in ('PowerPC', 'Power_Macintosh'):
|
||||
# Pick a sane name for the PPC architecture.
|
||||
# See 'i386' case
|
||||
if sys.maxsize >= 2**32:
|
||||
machine = 'ppc64'
|
||||
else:
|
||||
machine = 'ppc'
|
||||
|
||||
return "%s-%s-%s" % (osname, release, machine)
|
||||
|
||||
|
||||
def get_python_version():
|
||||
return _PY_VERSION_SHORT
|
||||
|
||||
|
||||
def _print_dict(title, data):
|
||||
for index, (key, value) in enumerate(sorted(data.items())):
|
||||
if index == 0:
|
||||
print('%s: ' % (title))
|
||||
print('\t%s = "%s"' % (key, value))
|
||||
|
||||
|
||||
def _main():
|
||||
"""Display all information sysconfig detains."""
|
||||
print('Platform: "%s"' % get_platform())
|
||||
print('Python version: "%s"' % get_python_version())
|
||||
print('Current installation scheme: "%s"' % _get_default_scheme())
|
||||
print()
|
||||
_print_dict('Paths', get_paths())
|
||||
print()
|
||||
_print_dict('Variables', get_config_vars())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
_main()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,54 @@
|
||||
from .distro import (
|
||||
NORMALIZED_DISTRO_ID,
|
||||
NORMALIZED_LSB_ID,
|
||||
NORMALIZED_OS_ID,
|
||||
LinuxDistribution,
|
||||
__version__,
|
||||
build_number,
|
||||
codename,
|
||||
distro_release_attr,
|
||||
distro_release_info,
|
||||
id,
|
||||
info,
|
||||
like,
|
||||
linux_distribution,
|
||||
lsb_release_attr,
|
||||
lsb_release_info,
|
||||
major_version,
|
||||
minor_version,
|
||||
name,
|
||||
os_release_attr,
|
||||
os_release_info,
|
||||
uname_attr,
|
||||
uname_info,
|
||||
version,
|
||||
version_parts,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"NORMALIZED_DISTRO_ID",
|
||||
"NORMALIZED_LSB_ID",
|
||||
"NORMALIZED_OS_ID",
|
||||
"LinuxDistribution",
|
||||
"build_number",
|
||||
"codename",
|
||||
"distro_release_attr",
|
||||
"distro_release_info",
|
||||
"id",
|
||||
"info",
|
||||
"like",
|
||||
"linux_distribution",
|
||||
"lsb_release_attr",
|
||||
"lsb_release_info",
|
||||
"major_version",
|
||||
"minor_version",
|
||||
"name",
|
||||
"os_release_attr",
|
||||
"os_release_info",
|
||||
"uname_attr",
|
||||
"uname_info",
|
||||
"version",
|
||||
"version_parts",
|
||||
]
|
||||
|
||||
__version__ = __version__
|
||||
@@ -0,0 +1,4 @@
|
||||
from .distro import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+199
-211
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright 2015,2016,2017 Nir Cohen
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -36,40 +37,39 @@ import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
import warnings
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
Optional,
|
||||
Sequence,
|
||||
TextIO,
|
||||
Tuple,
|
||||
Type,
|
||||
)
|
||||
|
||||
__version__ = "1.6.0"
|
||||
try:
|
||||
from typing import TypedDict
|
||||
except ImportError:
|
||||
# Python 3.7
|
||||
TypedDict = dict
|
||||
|
||||
# Use `if False` to avoid an ImportError on Python 2. After dropping Python 2
|
||||
# support, can use typing.TYPE_CHECKING instead. See:
|
||||
# https://docs.python.org/3/library/typing.html#typing.TYPE_CHECKING
|
||||
if False: # pragma: nocover
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
Optional,
|
||||
Sequence,
|
||||
TextIO,
|
||||
Tuple,
|
||||
Type,
|
||||
TypedDict,
|
||||
Union,
|
||||
)
|
||||
__version__ = "1.7.0"
|
||||
|
||||
VersionDict = TypedDict(
|
||||
"VersionDict", {"major": str, "minor": str, "build_number": str}
|
||||
)
|
||||
InfoDict = TypedDict(
|
||||
"InfoDict",
|
||||
{
|
||||
"id": str,
|
||||
"version": str,
|
||||
"version_parts": VersionDict,
|
||||
"like": str,
|
||||
"codename": str,
|
||||
},
|
||||
)
|
||||
|
||||
class VersionDict(TypedDict):
|
||||
major: str
|
||||
minor: str
|
||||
build_number: str
|
||||
|
||||
|
||||
class InfoDict(TypedDict):
|
||||
id: str
|
||||
version: str
|
||||
version_parts: VersionDict
|
||||
like: str
|
||||
codename: str
|
||||
|
||||
|
||||
_UNIXCONFDIR = os.environ.get("UNIXCONFDIR", "/etc")
|
||||
@@ -85,6 +85,7 @@ _OS_RELEASE_BASENAME = "os-release"
|
||||
#: * Value: Normalized value.
|
||||
NORMALIZED_OS_ID = {
|
||||
"ol": "oracle", # Oracle Linux
|
||||
"opensuse-leap": "opensuse", # Newer versions of OpenSuSE report as opensuse-leap
|
||||
}
|
||||
|
||||
#: Translation table for normalizing the "Distributor ID" attribute returned by
|
||||
@@ -133,8 +134,7 @@ _DISTRO_RELEASE_IGNORE_BASENAMES = (
|
||||
)
|
||||
|
||||
|
||||
def linux_distribution(full_distribution_name=True):
|
||||
# type: (bool) -> Tuple[str, str, str]
|
||||
def linux_distribution(full_distribution_name: bool = True) -> Tuple[str, str, str]:
|
||||
"""
|
||||
.. deprecated:: 1.6.0
|
||||
|
||||
@@ -151,7 +151,8 @@ def linux_distribution(full_distribution_name=True):
|
||||
|
||||
* ``version``: The result of :func:`distro.version`.
|
||||
|
||||
* ``codename``: The result of :func:`distro.codename`.
|
||||
* ``codename``: The extra item (usually in parentheses) after the
|
||||
os-release version number, or the result of :func:`distro.codename`.
|
||||
|
||||
The interface of this function is compatible with the original
|
||||
:py:func:`platform.linux_distribution` function, supporting a subset of
|
||||
@@ -176,8 +177,7 @@ def linux_distribution(full_distribution_name=True):
|
||||
return _distro.linux_distribution(full_distribution_name)
|
||||
|
||||
|
||||
def id():
|
||||
# type: () -> str
|
||||
def id() -> str:
|
||||
"""
|
||||
Return the distro ID of the current distribution, as a
|
||||
machine-readable string.
|
||||
@@ -198,7 +198,7 @@ def id():
|
||||
"fedora" Fedora
|
||||
"sles" SUSE Linux Enterprise Server
|
||||
"opensuse" openSUSE
|
||||
"amazon" Amazon Linux
|
||||
"amzn" Amazon Linux
|
||||
"arch" Arch Linux
|
||||
"cloudlinux" CloudLinux OS
|
||||
"exherbo" Exherbo Linux
|
||||
@@ -219,6 +219,8 @@ def id():
|
||||
"netbsd" NetBSD
|
||||
"freebsd" FreeBSD
|
||||
"midnightbsd" MidnightBSD
|
||||
"rocky" Rocky Linux
|
||||
"aix" AIX
|
||||
============== =========================================
|
||||
|
||||
If you have a need to get distros for reliable IDs added into this set,
|
||||
@@ -256,8 +258,7 @@ def id():
|
||||
return _distro.id()
|
||||
|
||||
|
||||
def name(pretty=False):
|
||||
# type: (bool) -> str
|
||||
def name(pretty: bool = False) -> str:
|
||||
"""
|
||||
Return the name of the current OS distribution, as a human-readable
|
||||
string.
|
||||
@@ -296,8 +297,7 @@ def name(pretty=False):
|
||||
return _distro.name(pretty)
|
||||
|
||||
|
||||
def version(pretty=False, best=False):
|
||||
# type: (bool, bool) -> str
|
||||
def version(pretty: bool = False, best: bool = False) -> str:
|
||||
"""
|
||||
Return the version of the current OS distribution, as a human-readable
|
||||
string.
|
||||
@@ -313,6 +313,10 @@ def version(pretty=False, best=False):
|
||||
sources in a fixed priority order does not always yield the most precise
|
||||
version (e.g. for Debian 8.2, or CentOS 7.1).
|
||||
|
||||
Some other distributions may not provide this kind of information. In these
|
||||
cases, an empty string would be returned. This behavior can be observed
|
||||
with rolling releases distributions (e.g. Arch Linux).
|
||||
|
||||
The *best* parameter can be used to control the approach for the returned
|
||||
version:
|
||||
|
||||
@@ -341,8 +345,7 @@ def version(pretty=False, best=False):
|
||||
return _distro.version(pretty, best)
|
||||
|
||||
|
||||
def version_parts(best=False):
|
||||
# type: (bool) -> Tuple[str, str, str]
|
||||
def version_parts(best: bool = False) -> Tuple[str, str, str]:
|
||||
"""
|
||||
Return the version of the current OS distribution as a tuple
|
||||
``(major, minor, build_number)`` with items as follows:
|
||||
@@ -359,8 +362,7 @@ def version_parts(best=False):
|
||||
return _distro.version_parts(best)
|
||||
|
||||
|
||||
def major_version(best=False):
|
||||
# type: (bool) -> str
|
||||
def major_version(best: bool = False) -> str:
|
||||
"""
|
||||
Return the major version of the current OS distribution, as a string,
|
||||
if provided.
|
||||
@@ -373,8 +375,7 @@ def major_version(best=False):
|
||||
return _distro.major_version(best)
|
||||
|
||||
|
||||
def minor_version(best=False):
|
||||
# type: (bool) -> str
|
||||
def minor_version(best: bool = False) -> str:
|
||||
"""
|
||||
Return the minor version of the current OS distribution, as a string,
|
||||
if provided.
|
||||
@@ -387,8 +388,7 @@ def minor_version(best=False):
|
||||
return _distro.minor_version(best)
|
||||
|
||||
|
||||
def build_number(best=False):
|
||||
# type: (bool) -> str
|
||||
def build_number(best: bool = False) -> str:
|
||||
"""
|
||||
Return the build number of the current OS distribution, as a string,
|
||||
if provided.
|
||||
@@ -401,8 +401,7 @@ def build_number(best=False):
|
||||
return _distro.build_number(best)
|
||||
|
||||
|
||||
def like():
|
||||
# type: () -> str
|
||||
def like() -> str:
|
||||
"""
|
||||
Return a space-separated list of distro IDs of distributions that are
|
||||
closely related to the current OS distribution in regards to packaging
|
||||
@@ -419,8 +418,7 @@ def like():
|
||||
return _distro.like()
|
||||
|
||||
|
||||
def codename():
|
||||
# type: () -> str
|
||||
def codename() -> str:
|
||||
"""
|
||||
Return the codename for the release of the current OS distribution,
|
||||
as a string.
|
||||
@@ -444,8 +442,7 @@ def codename():
|
||||
return _distro.codename()
|
||||
|
||||
|
||||
def info(pretty=False, best=False):
|
||||
# type: (bool, bool) -> InfoDict
|
||||
def info(pretty: bool = False, best: bool = False) -> InfoDict:
|
||||
"""
|
||||
Return certain machine-readable information items about the current OS
|
||||
distribution in a dictionary, as shown in the following example:
|
||||
@@ -489,8 +486,7 @@ def info(pretty=False, best=False):
|
||||
return _distro.info(pretty, best)
|
||||
|
||||
|
||||
def os_release_info():
|
||||
# type: () -> Dict[str, str]
|
||||
def os_release_info() -> Dict[str, str]:
|
||||
"""
|
||||
Return a dictionary containing key-value pairs for the information items
|
||||
from the os-release file data source of the current OS distribution.
|
||||
@@ -500,8 +496,7 @@ def os_release_info():
|
||||
return _distro.os_release_info()
|
||||
|
||||
|
||||
def lsb_release_info():
|
||||
# type: () -> Dict[str, str]
|
||||
def lsb_release_info() -> Dict[str, str]:
|
||||
"""
|
||||
Return a dictionary containing key-value pairs for the information items
|
||||
from the lsb_release command data source of the current OS distribution.
|
||||
@@ -512,8 +507,7 @@ def lsb_release_info():
|
||||
return _distro.lsb_release_info()
|
||||
|
||||
|
||||
def distro_release_info():
|
||||
# type: () -> Dict[str, str]
|
||||
def distro_release_info() -> Dict[str, str]:
|
||||
"""
|
||||
Return a dictionary containing key-value pairs for the information items
|
||||
from the distro release file data source of the current OS distribution.
|
||||
@@ -523,8 +517,7 @@ def distro_release_info():
|
||||
return _distro.distro_release_info()
|
||||
|
||||
|
||||
def uname_info():
|
||||
# type: () -> Dict[str, str]
|
||||
def uname_info() -> Dict[str, str]:
|
||||
"""
|
||||
Return a dictionary containing key-value pairs for the information items
|
||||
from the distro release file data source of the current OS distribution.
|
||||
@@ -532,8 +525,7 @@ def uname_info():
|
||||
return _distro.uname_info()
|
||||
|
||||
|
||||
def os_release_attr(attribute):
|
||||
# type: (str) -> str
|
||||
def os_release_attr(attribute: str) -> str:
|
||||
"""
|
||||
Return a single named information item from the os-release file data source
|
||||
of the current OS distribution.
|
||||
@@ -552,8 +544,7 @@ def os_release_attr(attribute):
|
||||
return _distro.os_release_attr(attribute)
|
||||
|
||||
|
||||
def lsb_release_attr(attribute):
|
||||
# type: (str) -> str
|
||||
def lsb_release_attr(attribute: str) -> str:
|
||||
"""
|
||||
Return a single named information item from the lsb_release command output
|
||||
data source of the current OS distribution.
|
||||
@@ -573,8 +564,7 @@ def lsb_release_attr(attribute):
|
||||
return _distro.lsb_release_attr(attribute)
|
||||
|
||||
|
||||
def distro_release_attr(attribute):
|
||||
# type: (str) -> str
|
||||
def distro_release_attr(attribute: str) -> str:
|
||||
"""
|
||||
Return a single named information item from the distro release file
|
||||
data source of the current OS distribution.
|
||||
@@ -593,8 +583,7 @@ def distro_release_attr(attribute):
|
||||
return _distro.distro_release_attr(attribute)
|
||||
|
||||
|
||||
def uname_attr(attribute):
|
||||
# type: (str) -> str
|
||||
def uname_attr(attribute: str) -> str:
|
||||
"""
|
||||
Return a single named information item from the distro release file
|
||||
data source of the current OS distribution.
|
||||
@@ -615,25 +604,23 @@ try:
|
||||
from functools import cached_property
|
||||
except ImportError:
|
||||
# Python < 3.8
|
||||
class cached_property(object): # type: ignore
|
||||
class cached_property: # type: ignore
|
||||
"""A version of @property which caches the value. On access, it calls the
|
||||
underlying function and sets the value in `__dict__` so future accesses
|
||||
will not re-call the property.
|
||||
"""
|
||||
|
||||
def __init__(self, f):
|
||||
# type: (Callable[[Any], Any]) -> None
|
||||
def __init__(self, f: Callable[[Any], Any]) -> None:
|
||||
self._fname = f.__name__
|
||||
self._f = f
|
||||
|
||||
def __get__(self, obj, owner):
|
||||
# type: (Any, Type[Any]) -> Any
|
||||
assert obj is not None, "call {} on an instance".format(self._fname)
|
||||
def __get__(self, obj: Any, owner: Type[Any]) -> Any:
|
||||
assert obj is not None, f"call {self._fname} on an instance"
|
||||
ret = obj.__dict__[self._fname] = self._f(obj)
|
||||
return ret
|
||||
|
||||
|
||||
class LinuxDistribution(object):
|
||||
class LinuxDistribution:
|
||||
"""
|
||||
Provides information about a OS distribution.
|
||||
|
||||
@@ -653,13 +640,13 @@ class LinuxDistribution(object):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
include_lsb=True,
|
||||
os_release_file="",
|
||||
distro_release_file="",
|
||||
include_uname=True,
|
||||
root_dir=None,
|
||||
):
|
||||
# type: (bool, str, str, bool, Optional[str]) -> None
|
||||
include_lsb: Optional[bool] = None,
|
||||
os_release_file: str = "",
|
||||
distro_release_file: str = "",
|
||||
include_uname: Optional[bool] = None,
|
||||
root_dir: Optional[str] = None,
|
||||
include_oslevel: Optional[bool] = None,
|
||||
) -> None:
|
||||
"""
|
||||
The initialization method of this class gathers information from the
|
||||
available data sources, and stores that in private instance attributes.
|
||||
@@ -699,7 +686,13 @@ class LinuxDistribution(object):
|
||||
be empty.
|
||||
|
||||
* ``root_dir`` (string): The absolute path to the root directory to use
|
||||
to find distro-related information files.
|
||||
to find distro-related information files. Note that ``include_*``
|
||||
parameters must not be enabled in combination with ``root_dir``.
|
||||
|
||||
* ``include_oslevel`` (bool): Controls whether (AIX) oslevel command
|
||||
output is included as a data source. If the oslevel command is not
|
||||
available in the program execution path the data source will be
|
||||
empty.
|
||||
|
||||
Public instance attributes:
|
||||
|
||||
@@ -718,14 +711,21 @@ class LinuxDistribution(object):
|
||||
parameter. This controls whether the uname information will
|
||||
be loaded.
|
||||
|
||||
* ``include_oslevel`` (bool): The result of the ``include_oslevel``
|
||||
parameter. This controls whether (AIX) oslevel information will be
|
||||
loaded.
|
||||
|
||||
* ``root_dir`` (string): The result of the ``root_dir`` parameter.
|
||||
The absolute path to the root directory to use to find distro-related
|
||||
information files.
|
||||
|
||||
Raises:
|
||||
|
||||
* :py:exc:`IOError`: Some I/O issue with an os-release file or distro
|
||||
release file.
|
||||
* :py:exc:`ValueError`: Initialization parameters combination is not
|
||||
supported.
|
||||
|
||||
* :py:exc:`subprocess.CalledProcessError`: The lsb_release command had
|
||||
some issue (other than not being available in the program execution
|
||||
path).
|
||||
* :py:exc:`OSError`: Some I/O issue with an os-release file or distro
|
||||
release file.
|
||||
|
||||
* :py:exc:`UnicodeError`: A data source has unexpected characters or
|
||||
uses an unexpected encoding.
|
||||
@@ -754,11 +754,24 @@ class LinuxDistribution(object):
|
||||
self.os_release_file = usr_lib_os_release_file
|
||||
|
||||
self.distro_release_file = distro_release_file or "" # updated later
|
||||
self.include_lsb = include_lsb
|
||||
self.include_uname = include_uname
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
is_root_dir_defined = root_dir is not None
|
||||
if is_root_dir_defined and (include_lsb or include_uname or include_oslevel):
|
||||
raise ValueError(
|
||||
"Including subprocess data sources from specific root_dir is disallowed"
|
||||
" to prevent false information"
|
||||
)
|
||||
self.include_lsb = (
|
||||
include_lsb if include_lsb is not None else not is_root_dir_defined
|
||||
)
|
||||
self.include_uname = (
|
||||
include_uname if include_uname is not None else not is_root_dir_defined
|
||||
)
|
||||
self.include_oslevel = (
|
||||
include_oslevel if include_oslevel is not None else not is_root_dir_defined
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Return repr of all info"""
|
||||
return (
|
||||
"LinuxDistribution("
|
||||
@@ -766,14 +779,18 @@ class LinuxDistribution(object):
|
||||
"distro_release_file={self.distro_release_file!r}, "
|
||||
"include_lsb={self.include_lsb!r}, "
|
||||
"include_uname={self.include_uname!r}, "
|
||||
"include_oslevel={self.include_oslevel!r}, "
|
||||
"root_dir={self.root_dir!r}, "
|
||||
"_os_release_info={self._os_release_info!r}, "
|
||||
"_lsb_release_info={self._lsb_release_info!r}, "
|
||||
"_distro_release_info={self._distro_release_info!r}, "
|
||||
"_uname_info={self._uname_info!r})".format(self=self)
|
||||
"_uname_info={self._uname_info!r}, "
|
||||
"_oslevel_info={self._oslevel_info!r})".format(self=self)
|
||||
)
|
||||
|
||||
def linux_distribution(self, full_distribution_name=True):
|
||||
# type: (bool) -> Tuple[str, str, str]
|
||||
def linux_distribution(
|
||||
self, full_distribution_name: bool = True
|
||||
) -> Tuple[str, str, str]:
|
||||
"""
|
||||
Return information about the OS distribution that is compatible
|
||||
with Python's :func:`platform.linux_distribution`, supporting a subset
|
||||
@@ -784,18 +801,16 @@ class LinuxDistribution(object):
|
||||
return (
|
||||
self.name() if full_distribution_name else self.id(),
|
||||
self.version(),
|
||||
self.codename(),
|
||||
self._os_release_info.get("release_codename") or self.codename(),
|
||||
)
|
||||
|
||||
def id(self):
|
||||
# type: () -> str
|
||||
def id(self) -> str:
|
||||
"""Return the distro ID of the OS distribution, as a string.
|
||||
|
||||
For details, see :func:`distro.id`.
|
||||
"""
|
||||
|
||||
def normalize(distro_id, table):
|
||||
# type: (str, Dict[str, str]) -> str
|
||||
def normalize(distro_id: str, table: Dict[str, str]) -> str:
|
||||
distro_id = distro_id.lower().replace(" ", "_")
|
||||
return table.get(distro_id, distro_id)
|
||||
|
||||
@@ -817,8 +832,7 @@ class LinuxDistribution(object):
|
||||
|
||||
return ""
|
||||
|
||||
def name(self, pretty=False):
|
||||
# type: (bool) -> str
|
||||
def name(self, pretty: bool = False) -> str:
|
||||
"""
|
||||
Return the name of the OS distribution, as a string.
|
||||
|
||||
@@ -838,11 +852,10 @@ class LinuxDistribution(object):
|
||||
name = self.distro_release_attr("name") or self.uname_attr("name")
|
||||
version = self.version(pretty=True)
|
||||
if version:
|
||||
name = name + " " + version
|
||||
name = f"{name} {version}"
|
||||
return name or ""
|
||||
|
||||
def version(self, pretty=False, best=False):
|
||||
# type: (bool, bool) -> str
|
||||
def version(self, pretty: bool = False, best: bool = False) -> str:
|
||||
"""
|
||||
Return the version of the OS distribution, as a string.
|
||||
|
||||
@@ -860,6 +873,9 @@ class LinuxDistribution(object):
|
||||
).get("version_id", ""),
|
||||
self.uname_attr("release"),
|
||||
]
|
||||
if self.uname_attr("id").startswith("aix"):
|
||||
# On AIX platforms, prefer oslevel command output.
|
||||
versions.insert(0, self.oslevel_info())
|
||||
version = ""
|
||||
if best:
|
||||
# This algorithm uses the last version in priority order that has
|
||||
@@ -875,11 +891,10 @@ class LinuxDistribution(object):
|
||||
version = v
|
||||
break
|
||||
if pretty and version and self.codename():
|
||||
version = "{0} ({1})".format(version, self.codename())
|
||||
version = f"{version} ({self.codename()})"
|
||||
return version
|
||||
|
||||
def version_parts(self, best=False):
|
||||
# type: (bool) -> Tuple[str, str, str]
|
||||
def version_parts(self, best: bool = False) -> Tuple[str, str, str]:
|
||||
"""
|
||||
Return the version of the OS distribution, as a tuple of version
|
||||
numbers.
|
||||
@@ -895,8 +910,7 @@ class LinuxDistribution(object):
|
||||
return major, minor or "", build_number or ""
|
||||
return "", "", ""
|
||||
|
||||
def major_version(self, best=False):
|
||||
# type: (bool) -> str
|
||||
def major_version(self, best: bool = False) -> str:
|
||||
"""
|
||||
Return the major version number of the current distribution.
|
||||
|
||||
@@ -904,8 +918,7 @@ class LinuxDistribution(object):
|
||||
"""
|
||||
return self.version_parts(best)[0]
|
||||
|
||||
def minor_version(self, best=False):
|
||||
# type: (bool) -> str
|
||||
def minor_version(self, best: bool = False) -> str:
|
||||
"""
|
||||
Return the minor version number of the current distribution.
|
||||
|
||||
@@ -913,8 +926,7 @@ class LinuxDistribution(object):
|
||||
"""
|
||||
return self.version_parts(best)[1]
|
||||
|
||||
def build_number(self, best=False):
|
||||
# type: (bool) -> str
|
||||
def build_number(self, best: bool = False) -> str:
|
||||
"""
|
||||
Return the build number of the current distribution.
|
||||
|
||||
@@ -922,8 +934,7 @@ class LinuxDistribution(object):
|
||||
"""
|
||||
return self.version_parts(best)[2]
|
||||
|
||||
def like(self):
|
||||
# type: () -> str
|
||||
def like(self) -> str:
|
||||
"""
|
||||
Return the IDs of distributions that are like the OS distribution.
|
||||
|
||||
@@ -931,8 +942,7 @@ class LinuxDistribution(object):
|
||||
"""
|
||||
return self.os_release_attr("id_like") or ""
|
||||
|
||||
def codename(self):
|
||||
# type: () -> str
|
||||
def codename(self) -> str:
|
||||
"""
|
||||
Return the codename of the OS distribution.
|
||||
|
||||
@@ -949,8 +959,7 @@ class LinuxDistribution(object):
|
||||
or ""
|
||||
)
|
||||
|
||||
def info(self, pretty=False, best=False):
|
||||
# type: (bool, bool) -> InfoDict
|
||||
def info(self, pretty: bool = False, best: bool = False) -> InfoDict:
|
||||
"""
|
||||
Return certain machine-readable information about the OS
|
||||
distribution.
|
||||
@@ -969,8 +978,7 @@ class LinuxDistribution(object):
|
||||
codename=self.codename(),
|
||||
)
|
||||
|
||||
def os_release_info(self):
|
||||
# type: () -> Dict[str, str]
|
||||
def os_release_info(self) -> Dict[str, str]:
|
||||
"""
|
||||
Return a dictionary containing key-value pairs for the information
|
||||
items from the os-release file data source of the OS distribution.
|
||||
@@ -979,8 +987,7 @@ class LinuxDistribution(object):
|
||||
"""
|
||||
return self._os_release_info
|
||||
|
||||
def lsb_release_info(self):
|
||||
# type: () -> Dict[str, str]
|
||||
def lsb_release_info(self) -> Dict[str, str]:
|
||||
"""
|
||||
Return a dictionary containing key-value pairs for the information
|
||||
items from the lsb_release command data source of the OS
|
||||
@@ -990,8 +997,7 @@ class LinuxDistribution(object):
|
||||
"""
|
||||
return self._lsb_release_info
|
||||
|
||||
def distro_release_info(self):
|
||||
# type: () -> Dict[str, str]
|
||||
def distro_release_info(self) -> Dict[str, str]:
|
||||
"""
|
||||
Return a dictionary containing key-value pairs for the information
|
||||
items from the distro release file data source of the OS
|
||||
@@ -1001,8 +1007,7 @@ class LinuxDistribution(object):
|
||||
"""
|
||||
return self._distro_release_info
|
||||
|
||||
def uname_info(self):
|
||||
# type: () -> Dict[str, str]
|
||||
def uname_info(self) -> Dict[str, str]:
|
||||
"""
|
||||
Return a dictionary containing key-value pairs for the information
|
||||
items from the uname command data source of the OS distribution.
|
||||
@@ -1011,8 +1016,13 @@ class LinuxDistribution(object):
|
||||
"""
|
||||
return self._uname_info
|
||||
|
||||
def os_release_attr(self, attribute):
|
||||
# type: (str) -> str
|
||||
def oslevel_info(self) -> str:
|
||||
"""
|
||||
Return AIX' oslevel command output.
|
||||
"""
|
||||
return self._oslevel_info
|
||||
|
||||
def os_release_attr(self, attribute: str) -> str:
|
||||
"""
|
||||
Return a single named information item from the os-release file data
|
||||
source of the OS distribution.
|
||||
@@ -1021,8 +1031,7 @@ class LinuxDistribution(object):
|
||||
"""
|
||||
return self._os_release_info.get(attribute, "")
|
||||
|
||||
def lsb_release_attr(self, attribute):
|
||||
# type: (str) -> str
|
||||
def lsb_release_attr(self, attribute: str) -> str:
|
||||
"""
|
||||
Return a single named information item from the lsb_release command
|
||||
output data source of the OS distribution.
|
||||
@@ -1031,8 +1040,7 @@ class LinuxDistribution(object):
|
||||
"""
|
||||
return self._lsb_release_info.get(attribute, "")
|
||||
|
||||
def distro_release_attr(self, attribute):
|
||||
# type: (str) -> str
|
||||
def distro_release_attr(self, attribute: str) -> str:
|
||||
"""
|
||||
Return a single named information item from the distro release file
|
||||
data source of the OS distribution.
|
||||
@@ -1041,8 +1049,7 @@ class LinuxDistribution(object):
|
||||
"""
|
||||
return self._distro_release_info.get(attribute, "")
|
||||
|
||||
def uname_attr(self, attribute):
|
||||
# type: (str) -> str
|
||||
def uname_attr(self, attribute: str) -> str:
|
||||
"""
|
||||
Return a single named information item from the uname command
|
||||
output data source of the OS distribution.
|
||||
@@ -1052,8 +1059,7 @@ class LinuxDistribution(object):
|
||||
return self._uname_info.get(attribute, "")
|
||||
|
||||
@cached_property
|
||||
def _os_release_info(self):
|
||||
# type: () -> Dict[str, str]
|
||||
def _os_release_info(self) -> Dict[str, str]:
|
||||
"""
|
||||
Get the information items from the specified os-release file.
|
||||
|
||||
@@ -1061,13 +1067,12 @@ class LinuxDistribution(object):
|
||||
A dictionary containing all information items.
|
||||
"""
|
||||
if os.path.isfile(self.os_release_file):
|
||||
with open(self.os_release_file) as release_file:
|
||||
with open(self.os_release_file, encoding="utf-8") as release_file:
|
||||
return self._parse_os_release_content(release_file)
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def _parse_os_release_content(lines):
|
||||
# type: (TextIO) -> Dict[str, str]
|
||||
def _parse_os_release_content(lines: TextIO) -> Dict[str, str]:
|
||||
"""
|
||||
Parse the lines of an os-release file.
|
||||
|
||||
@@ -1084,16 +1089,6 @@ class LinuxDistribution(object):
|
||||
lexer = shlex.shlex(lines, posix=True)
|
||||
lexer.whitespace_split = True
|
||||
|
||||
# The shlex module defines its `wordchars` variable using literals,
|
||||
# making it dependent on the encoding of the Python source file.
|
||||
# In Python 2.6 and 2.7, the shlex source file is encoded in
|
||||
# 'iso-8859-1', and the `wordchars` variable is defined as a byte
|
||||
# string. This causes a UnicodeDecodeError to be raised when the
|
||||
# parsed content is a unicode object. The following fix resolves that
|
||||
# (... but it should be fixed in shlex...):
|
||||
if sys.version_info[0] == 2 and isinstance(lexer.wordchars, bytes):
|
||||
lexer.wordchars = lexer.wordchars.decode("iso-8859-1")
|
||||
|
||||
tokens = list(lexer)
|
||||
for token in tokens:
|
||||
# At this point, all shell-like parsing has been done (i.e.
|
||||
@@ -1102,12 +1097,17 @@ class LinuxDistribution(object):
|
||||
# stripped, etc.), so the tokens are now either:
|
||||
# * variable assignments: var=value
|
||||
# * commands or their arguments (not allowed in os-release)
|
||||
# Ignore any tokens that are not variable assignments
|
||||
if "=" in token:
|
||||
k, v = token.split("=", 1)
|
||||
props[k.lower()] = v
|
||||
else:
|
||||
# Ignore any tokens that are not variable assignments
|
||||
pass
|
||||
|
||||
if "version" in props:
|
||||
# extract release codename (if any) from version attribute
|
||||
match = re.search(r"\((\D+)\)|,\s*(\D+)", props["version"])
|
||||
if match:
|
||||
release_codename = match.group(1) or match.group(2)
|
||||
props["codename"] = props["release_codename"] = release_codename
|
||||
|
||||
if "version_codename" in props:
|
||||
# os-release added a version_codename field. Use that in
|
||||
@@ -1118,22 +1118,11 @@ class LinuxDistribution(object):
|
||||
elif "ubuntu_codename" in props:
|
||||
# Same as above but a non-standard field name used on older Ubuntus
|
||||
props["codename"] = props["ubuntu_codename"]
|
||||
elif "version" in props:
|
||||
# If there is no version_codename, parse it from the version
|
||||
match = re.search(r"(\(\D+\))|,(\s+)?\D+", props["version"])
|
||||
if match:
|
||||
codename = match.group()
|
||||
codename = codename.strip("()")
|
||||
codename = codename.strip(",")
|
||||
codename = codename.strip()
|
||||
# codename appears within paranthese.
|
||||
props["codename"] = codename
|
||||
|
||||
return props
|
||||
|
||||
@cached_property
|
||||
def _lsb_release_info(self):
|
||||
# type: () -> Dict[str, str]
|
||||
def _lsb_release_info(self) -> Dict[str, str]:
|
||||
"""
|
||||
Get the information items from the lsb_release command output.
|
||||
|
||||
@@ -1142,19 +1131,17 @@ class LinuxDistribution(object):
|
||||
"""
|
||||
if not self.include_lsb:
|
||||
return {}
|
||||
with open(os.devnull, "wb") as devnull:
|
||||
try:
|
||||
cmd = ("lsb_release", "-a")
|
||||
stdout = subprocess.check_output(cmd, stderr=devnull)
|
||||
# Command not found or lsb_release returned error
|
||||
except (OSError, subprocess.CalledProcessError):
|
||||
return {}
|
||||
try:
|
||||
cmd = ("lsb_release", "-a")
|
||||
stdout = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
|
||||
# Command not found or lsb_release returned error
|
||||
except (OSError, subprocess.CalledProcessError):
|
||||
return {}
|
||||
content = self._to_str(stdout).splitlines()
|
||||
return self._parse_lsb_release_content(content)
|
||||
|
||||
@staticmethod
|
||||
def _parse_lsb_release_content(lines):
|
||||
# type: (Iterable[str]) -> Dict[str, str]
|
||||
def _parse_lsb_release_content(lines: Iterable[str]) -> Dict[str, str]:
|
||||
"""
|
||||
Parse the output of the lsb_release command.
|
||||
|
||||
@@ -1178,20 +1165,31 @@ class LinuxDistribution(object):
|
||||
return props
|
||||
|
||||
@cached_property
|
||||
def _uname_info(self):
|
||||
# type: () -> Dict[str, str]
|
||||
with open(os.devnull, "wb") as devnull:
|
||||
try:
|
||||
cmd = ("uname", "-rs")
|
||||
stdout = subprocess.check_output(cmd, stderr=devnull)
|
||||
except OSError:
|
||||
return {}
|
||||
def _uname_info(self) -> Dict[str, str]:
|
||||
if not self.include_uname:
|
||||
return {}
|
||||
try:
|
||||
cmd = ("uname", "-rs")
|
||||
stdout = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
|
||||
except OSError:
|
||||
return {}
|
||||
content = self._to_str(stdout).splitlines()
|
||||
return self._parse_uname_content(content)
|
||||
|
||||
@cached_property
|
||||
def _oslevel_info(self) -> str:
|
||||
if not self.include_oslevel:
|
||||
return ""
|
||||
try:
|
||||
stdout = subprocess.check_output("oslevel", stderr=subprocess.DEVNULL)
|
||||
except (OSError, subprocess.CalledProcessError):
|
||||
return ""
|
||||
return self._to_str(stdout).strip()
|
||||
|
||||
@staticmethod
|
||||
def _parse_uname_content(lines):
|
||||
# type: (Sequence[str]) -> Dict[str, str]
|
||||
def _parse_uname_content(lines: Sequence[str]) -> Dict[str, str]:
|
||||
if not lines:
|
||||
return {}
|
||||
props = {}
|
||||
match = re.search(r"^([^\s]+)\s+([\d\.]+)", lines[0].strip())
|
||||
if match:
|
||||
@@ -1208,23 +1206,12 @@ class LinuxDistribution(object):
|
||||
return props
|
||||
|
||||
@staticmethod
|
||||
def _to_str(text):
|
||||
# type: (Union[bytes, str]) -> str
|
||||
def _to_str(bytestring: bytes) -> str:
|
||||
encoding = sys.getfilesystemencoding()
|
||||
encoding = "utf-8" if encoding == "ascii" else encoding
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
if isinstance(text, bytes):
|
||||
return text.decode(encoding)
|
||||
else:
|
||||
if isinstance(text, unicode): # noqa
|
||||
return text.encode(encoding)
|
||||
|
||||
return text
|
||||
return bytestring.decode(encoding)
|
||||
|
||||
@cached_property
|
||||
def _distro_release_info(self):
|
||||
# type: () -> Dict[str, str]
|
||||
def _distro_release_info(self) -> Dict[str, str]:
|
||||
"""
|
||||
Get the information items from the specified distro release file.
|
||||
|
||||
@@ -1272,6 +1259,7 @@ class LinuxDistribution(object):
|
||||
"manjaro-release",
|
||||
"oracle-release",
|
||||
"redhat-release",
|
||||
"rocky-release",
|
||||
"sl-release",
|
||||
"slackware-version",
|
||||
]
|
||||
@@ -1291,8 +1279,7 @@ class LinuxDistribution(object):
|
||||
return distro_info
|
||||
return {}
|
||||
|
||||
def _parse_distro_release_file(self, filepath):
|
||||
# type: (str) -> Dict[str, str]
|
||||
def _parse_distro_release_file(self, filepath: str) -> Dict[str, str]:
|
||||
"""
|
||||
Parse a distro release file.
|
||||
|
||||
@@ -1304,19 +1291,18 @@ class LinuxDistribution(object):
|
||||
A dictionary containing all information items.
|
||||
"""
|
||||
try:
|
||||
with open(filepath) as fp:
|
||||
with open(filepath, encoding="utf-8") as fp:
|
||||
# Only parse the first line. For instance, on SLES there
|
||||
# are multiple lines. We don't want them...
|
||||
return self._parse_distro_release_content(fp.readline())
|
||||
except (OSError, IOError):
|
||||
except OSError:
|
||||
# Ignore not being able to read a specific, seemingly version
|
||||
# related file.
|
||||
# See https://github.com/python-distro/distro/issues/162
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def _parse_distro_release_content(line):
|
||||
# type: (str) -> Dict[str, str]
|
||||
def _parse_distro_release_content(line: str) -> Dict[str, str]:
|
||||
"""
|
||||
Parse a line from a distro release file.
|
||||
|
||||
@@ -1344,8 +1330,7 @@ class LinuxDistribution(object):
|
||||
_distro = LinuxDistribution()
|
||||
|
||||
|
||||
def main():
|
||||
# type: () -> None
|
||||
def main() -> None:
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||
@@ -1367,7 +1352,10 @@ def main():
|
||||
|
||||
if args.root_dir:
|
||||
dist = LinuxDistribution(
|
||||
include_lsb=False, include_uname=False, root_dir=args.root_dir
|
||||
include_lsb=False,
|
||||
include_uname=False,
|
||||
include_oslevel=False,
|
||||
root_dir=args.root_dir,
|
||||
)
|
||||
else:
|
||||
dist = _distro
|
||||
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2006-2013 James Graham and other contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,29 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2013-2021, Kim Davies
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. 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.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,14 @@
|
||||
Copyright (C) 2008-2011 INADA Naoki <songofacandy@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
This software is made available under the terms of *either* of the licenses
|
||||
found in LICENSE.APACHE or LICENSE.BSD. Contributions to this software is made
|
||||
under the terms of *both* these licenses.
|
||||
@@ -0,0 +1,177 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -0,0 +1,23 @@
|
||||
Copyright (c) Donald Stufft and individual contributors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. 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.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Thomas Kluyver
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,19 @@
|
||||
Copyright (C) 2016 Jason R Coombs <jaraco@jaraco.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -4,7 +4,6 @@ usage.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
@@ -18,16 +17,26 @@ from .version import __version__, __version_info__
|
||||
|
||||
|
||||
def _set_platform_dir_class() -> type[PlatformDirsABC]:
|
||||
if os.getenv("ANDROID_DATA") == "/data" and os.getenv("ANDROID_ROOT") == "/system":
|
||||
module, name = "pipenv.patched.notpip._vendor.platformdirs.android", "Android"
|
||||
elif sys.platform == "win32":
|
||||
module, name = "pipenv.patched.notpip._vendor.platformdirs.windows", "Windows"
|
||||
if sys.platform == "win32":
|
||||
from pipenv.patched.notpip._vendor.platformdirs.windows import Windows as Result
|
||||
elif sys.platform == "darwin":
|
||||
module, name = "pipenv.patched.notpip._vendor.platformdirs.macos", "MacOS"
|
||||
from pipenv.patched.notpip._vendor.platformdirs.macos import MacOS as Result
|
||||
else:
|
||||
module, name = "pipenv.patched.notpip._vendor.platformdirs.unix", "Unix"
|
||||
result: type[PlatformDirsABC] = getattr(importlib.import_module(module), name)
|
||||
return result
|
||||
from pipenv.patched.notpip._vendor.platformdirs.unix import Unix as Result
|
||||
|
||||
if os.getenv("ANDROID_DATA") == "/data" and os.getenv("ANDROID_ROOT") == "/system":
|
||||
|
||||
if os.getenv("SHELL") is not None:
|
||||
return Result
|
||||
|
||||
from pipenv.patched.notpip._vendor.platformdirs.android import _android_folder
|
||||
|
||||
if _android_folder() is not None:
|
||||
from pipenv.patched.notpip._vendor.platformdirs.android import Android
|
||||
|
||||
return Android # return to avoid redefinition of result
|
||||
|
||||
return Result
|
||||
|
||||
|
||||
PlatformDirs = _set_platform_dir_class() #: Currently active platform
|
||||
|
||||
@@ -4,6 +4,7 @@ import os
|
||||
import re
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
from typing import cast
|
||||
|
||||
from .api import PlatformDirsABC
|
||||
|
||||
@@ -18,7 +19,7 @@ class Android(PlatformDirsABC):
|
||||
@property
|
||||
def user_data_dir(self) -> str:
|
||||
""":return: data directory tied to the user, e.g. ``/data/user/<userid>/<packagename>/files/<AppName>``"""
|
||||
return self._append_app_name_and_version(_android_folder(), "files")
|
||||
return self._append_app_name_and_version(cast(str, _android_folder()), "files")
|
||||
|
||||
@property
|
||||
def site_data_dir(self) -> str:
|
||||
@@ -30,7 +31,7 @@ class Android(PlatformDirsABC):
|
||||
"""
|
||||
:return: config directory tied to the user, e.g. ``/data/user/<userid>/<packagename>/shared_prefs/<AppName>``
|
||||
"""
|
||||
return self._append_app_name_and_version(_android_folder(), "shared_prefs")
|
||||
return self._append_app_name_and_version(cast(str, _android_folder()), "shared_prefs")
|
||||
|
||||
@property
|
||||
def site_config_dir(self) -> str:
|
||||
@@ -40,7 +41,7 @@ class Android(PlatformDirsABC):
|
||||
@property
|
||||
def user_cache_dir(self) -> str:
|
||||
""":return: cache directory tied to the user, e.g. e.g. ``/data/user/<userid>/<packagename>/cache/<AppName>``"""
|
||||
return self._append_app_name_and_version(_android_folder(), "cache")
|
||||
return self._append_app_name_and_version(cast(str, _android_folder()), "cache")
|
||||
|
||||
@property
|
||||
def user_state_dir(self) -> str:
|
||||
@@ -78,14 +79,14 @@ class Android(PlatformDirsABC):
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _android_folder() -> str:
|
||||
""":return: base folder for the Android OS"""
|
||||
def _android_folder() -> str | None:
|
||||
""":return: base folder for the Android OS or None if cannot be found"""
|
||||
try:
|
||||
# First try to get path to android app via pyjnius
|
||||
from jnius import autoclass
|
||||
|
||||
Context = autoclass("android.content.Context") # noqa: N806
|
||||
result: str = Context.getFilesDir().getParentFile().getAbsolutePath()
|
||||
result: str | None = Context.getFilesDir().getParentFile().getAbsolutePath()
|
||||
except Exception:
|
||||
# if fails find an android folder looking path on the sys.path
|
||||
pattern = re.compile(r"/data/(data|user/\d+)/(.+)/files")
|
||||
@@ -94,7 +95,7 @@ def _android_folder() -> str:
|
||||
result = path.split("/files")[0]
|
||||
break
|
||||
else:
|
||||
raise OSError("Cannot find path to android app folder")
|
||||
result = None
|
||||
return result
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
""" Version information """
|
||||
"""Version information"""
|
||||
|
||||
__version__ = "2.4.1"
|
||||
__version_info__ = (2, 4, 1)
|
||||
__version__ = "2.5.2"
|
||||
__version_info__ = (2, 5, 2)
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
# Copyright (c) 2012 Georgios Verigakis <verigak@gmail.com>
|
||||
#
|
||||
# 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.
|
||||
|
||||
from __future__ import division, print_function
|
||||
|
||||
from collections import deque
|
||||
from datetime import timedelta
|
||||
from math import ceil
|
||||
from sys import stderr
|
||||
try:
|
||||
from time import monotonic
|
||||
except ImportError:
|
||||
from time import time as monotonic
|
||||
|
||||
|
||||
__version__ = '1.6'
|
||||
|
||||
HIDE_CURSOR = '\x1b[?25l'
|
||||
SHOW_CURSOR = '\x1b[?25h'
|
||||
|
||||
|
||||
class Infinite(object):
|
||||
file = stderr
|
||||
sma_window = 10 # Simple Moving Average window
|
||||
check_tty = True
|
||||
hide_cursor = True
|
||||
|
||||
def __init__(self, message='', **kwargs):
|
||||
self.index = 0
|
||||
self.start_ts = monotonic()
|
||||
self.avg = 0
|
||||
self._avg_update_ts = self.start_ts
|
||||
self._ts = self.start_ts
|
||||
self._xput = deque(maxlen=self.sma_window)
|
||||
for key, val in kwargs.items():
|
||||
setattr(self, key, val)
|
||||
|
||||
self._max_width = 0
|
||||
self._hidden_cursor = False
|
||||
self.message = message
|
||||
|
||||
if self.file and self.is_tty():
|
||||
if self.hide_cursor:
|
||||
print(HIDE_CURSOR, end='', file=self.file)
|
||||
self._hidden_cursor = True
|
||||
self.writeln('')
|
||||
|
||||
def __del__(self):
|
||||
if self._hidden_cursor:
|
||||
print(SHOW_CURSOR, end='', file=self.file)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key.startswith('_'):
|
||||
return None
|
||||
return getattr(self, key, None)
|
||||
|
||||
@property
|
||||
def elapsed(self):
|
||||
return int(monotonic() - self.start_ts)
|
||||
|
||||
@property
|
||||
def elapsed_td(self):
|
||||
return timedelta(seconds=self.elapsed)
|
||||
|
||||
def update_avg(self, n, dt):
|
||||
if n > 0:
|
||||
xput_len = len(self._xput)
|
||||
self._xput.append(dt / n)
|
||||
now = monotonic()
|
||||
# update when we're still filling _xput, then after every second
|
||||
if (xput_len < self.sma_window or
|
||||
now - self._avg_update_ts > 1):
|
||||
self.avg = sum(self._xput) / len(self._xput)
|
||||
self._avg_update_ts = now
|
||||
|
||||
def update(self):
|
||||
pass
|
||||
|
||||
def start(self):
|
||||
pass
|
||||
|
||||
def writeln(self, line):
|
||||
if self.file and self.is_tty():
|
||||
width = len(line)
|
||||
if width < self._max_width:
|
||||
# Add padding to cover previous contents
|
||||
line += ' ' * (self._max_width - width)
|
||||
else:
|
||||
self._max_width = width
|
||||
print('\r' + line, end='', file=self.file)
|
||||
self.file.flush()
|
||||
|
||||
def finish(self):
|
||||
if self.file and self.is_tty():
|
||||
print(file=self.file)
|
||||
if self._hidden_cursor:
|
||||
print(SHOW_CURSOR, end='', file=self.file)
|
||||
self._hidden_cursor = False
|
||||
|
||||
def is_tty(self):
|
||||
try:
|
||||
return self.file.isatty() if self.check_tty else True
|
||||
except AttributeError:
|
||||
msg = "%s has no attribute 'isatty'. Try setting check_tty=False." % self
|
||||
raise AttributeError(msg)
|
||||
|
||||
def next(self, n=1):
|
||||
now = monotonic()
|
||||
dt = now - self._ts
|
||||
self.update_avg(n, dt)
|
||||
self._ts = now
|
||||
self.index = self.index + n
|
||||
self.update()
|
||||
|
||||
def iter(self, it):
|
||||
self.iter_value = None
|
||||
with self:
|
||||
for x in it:
|
||||
self.iter_value = x
|
||||
yield x
|
||||
self.next()
|
||||
del self.iter_value
|
||||
|
||||
def __enter__(self):
|
||||
self.start()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.finish()
|
||||
|
||||
|
||||
class Progress(Infinite):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Progress, self).__init__(*args, **kwargs)
|
||||
self.max = kwargs.get('max', 100)
|
||||
|
||||
@property
|
||||
def eta(self):
|
||||
return int(ceil(self.avg * self.remaining))
|
||||
|
||||
@property
|
||||
def eta_td(self):
|
||||
return timedelta(seconds=self.eta)
|
||||
|
||||
@property
|
||||
def percent(self):
|
||||
return self.progress * 100
|
||||
|
||||
@property
|
||||
def progress(self):
|
||||
if self.max == 0:
|
||||
return 0
|
||||
return min(1, self.index / self.max)
|
||||
|
||||
@property
|
||||
def remaining(self):
|
||||
return max(self.max - self.index, 0)
|
||||
|
||||
def start(self):
|
||||
self.update()
|
||||
|
||||
def goto(self, index):
|
||||
incr = index - self.index
|
||||
self.next(incr)
|
||||
|
||||
def iter(self, it):
|
||||
try:
|
||||
self.max = len(it)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
self.iter_value = None
|
||||
with self:
|
||||
for x in it:
|
||||
self.iter_value = x
|
||||
yield x
|
||||
self.next()
|
||||
del self.iter_value
|
||||
@@ -1,93 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2012 Georgios Verigakis <verigak@gmail.com>
|
||||
#
|
||||
# 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.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
|
||||
from . import Progress
|
||||
from .colors import color
|
||||
|
||||
|
||||
class Bar(Progress):
|
||||
width = 32
|
||||
suffix = '%(index)d/%(max)d'
|
||||
bar_prefix = ' |'
|
||||
bar_suffix = '| '
|
||||
empty_fill = ' '
|
||||
fill = '#'
|
||||
color = None
|
||||
|
||||
def update(self):
|
||||
filled_length = int(self.width * self.progress)
|
||||
empty_length = self.width - filled_length
|
||||
|
||||
message = self.message % self
|
||||
bar = color(self.fill * filled_length, fg=self.color)
|
||||
empty = self.empty_fill * empty_length
|
||||
suffix = self.suffix % self
|
||||
line = ''.join([message, self.bar_prefix, bar, empty, self.bar_suffix,
|
||||
suffix])
|
||||
self.writeln(line)
|
||||
|
||||
|
||||
class ChargingBar(Bar):
|
||||
suffix = '%(percent)d%%'
|
||||
bar_prefix = ' '
|
||||
bar_suffix = ' '
|
||||
empty_fill = '∙'
|
||||
fill = '█'
|
||||
|
||||
|
||||
class FillingSquaresBar(ChargingBar):
|
||||
empty_fill = '▢'
|
||||
fill = '▣'
|
||||
|
||||
|
||||
class FillingCirclesBar(ChargingBar):
|
||||
empty_fill = '◯'
|
||||
fill = '◉'
|
||||
|
||||
|
||||
class IncrementalBar(Bar):
|
||||
if sys.platform.startswith('win'):
|
||||
phases = (u' ', u'▌', u'█')
|
||||
else:
|
||||
phases = (' ', '▏', '▎', '▍', '▌', '▋', '▊', '▉', '█')
|
||||
|
||||
def update(self):
|
||||
nphases = len(self.phases)
|
||||
filled_len = self.width * self.progress
|
||||
nfull = int(filled_len) # Number of full chars
|
||||
phase = int((filled_len - nfull) * nphases) # Phase of last char
|
||||
nempty = self.width - nfull # Number of empty chars
|
||||
|
||||
message = self.message % self
|
||||
bar = color(self.phases[-1] * nfull, fg=self.color)
|
||||
current = self.phases[phase] if phase > 0 else ''
|
||||
empty = self.empty_fill * max(0, nempty - len(current))
|
||||
suffix = self.suffix % self
|
||||
line = ''.join([message, self.bar_prefix, bar, current, empty,
|
||||
self.bar_suffix, suffix])
|
||||
self.writeln(line)
|
||||
|
||||
|
||||
class PixelBar(IncrementalBar):
|
||||
phases = ('⡀', '⡄', '⡆', '⡇', '⣇', '⣧', '⣷', '⣿')
|
||||
|
||||
|
||||
class ShadyBar(IncrementalBar):
|
||||
phases = (' ', '░', '▒', '▓', '█')
|
||||
@@ -1,79 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2020 Georgios Verigakis <verigak@gmail.com>
|
||||
#
|
||||
# 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.
|
||||
|
||||
from functools import partial
|
||||
|
||||
|
||||
COLORS = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan',
|
||||
'white')
|
||||
STYLES = ('bold', 'faint', 'italic', 'underline', 'blink', 'blink2',
|
||||
'negative', 'concealed', 'crossed')
|
||||
|
||||
|
||||
def color(s, fg=None, bg=None, style=None):
|
||||
sgr = []
|
||||
|
||||
if fg:
|
||||
if fg in COLORS:
|
||||
sgr.append(str(30 + COLORS.index(fg)))
|
||||
elif isinstance(fg, int) and 0 <= fg <= 255:
|
||||
sgr.append('38;5;%d' % int(fg))
|
||||
else:
|
||||
raise Exception('Invalid color "%s"' % fg)
|
||||
|
||||
if bg:
|
||||
if bg in COLORS:
|
||||
sgr.append(str(40 + COLORS.index(bg)))
|
||||
elif isinstance(bg, int) and 0 <= bg <= 255:
|
||||
sgr.append('48;5;%d' % bg)
|
||||
else:
|
||||
raise Exception('Invalid color "%s"' % bg)
|
||||
|
||||
if style:
|
||||
for st in style.split('+'):
|
||||
if st in STYLES:
|
||||
sgr.append(str(1 + STYLES.index(st)))
|
||||
else:
|
||||
raise Exception('Invalid style "%s"' % st)
|
||||
|
||||
if sgr:
|
||||
prefix = '\x1b[' + ';'.join(sgr) + 'm'
|
||||
suffix = '\x1b[0m'
|
||||
return prefix + s + suffix
|
||||
else:
|
||||
return s
|
||||
|
||||
|
||||
# Foreground shortcuts
|
||||
black = partial(color, fg='black')
|
||||
red = partial(color, fg='red')
|
||||
green = partial(color, fg='green')
|
||||
yellow = partial(color, fg='yellow')
|
||||
blue = partial(color, fg='blue')
|
||||
magenta = partial(color, fg='magenta')
|
||||
cyan = partial(color, fg='cyan')
|
||||
white = partial(color, fg='white')
|
||||
|
||||
# Style shortcuts
|
||||
bold = partial(color, style='bold')
|
||||
faint = partial(color, style='faint')
|
||||
italic = partial(color, style='italic')
|
||||
underline = partial(color, style='underline')
|
||||
blink = partial(color, style='blink')
|
||||
blink2 = partial(color, style='blink2')
|
||||
negative = partial(color, style='negative')
|
||||
concealed = partial(color, style='concealed')
|
||||
crossed = partial(color, style='crossed')
|
||||
@@ -1,47 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2012 Georgios Verigakis <verigak@gmail.com>
|
||||
#
|
||||
# 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.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from . import Infinite, Progress
|
||||
|
||||
|
||||
class Counter(Infinite):
|
||||
def update(self):
|
||||
message = self.message % self
|
||||
line = ''.join([message, str(self.index)])
|
||||
self.writeln(line)
|
||||
|
||||
|
||||
class Countdown(Progress):
|
||||
def update(self):
|
||||
message = self.message % self
|
||||
line = ''.join([message, str(self.remaining)])
|
||||
self.writeln(line)
|
||||
|
||||
|
||||
class Stack(Progress):
|
||||
phases = (' ', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█')
|
||||
|
||||
def update(self):
|
||||
nphases = len(self.phases)
|
||||
i = min(nphases - 1, int(self.progress * nphases))
|
||||
message = self.message % self
|
||||
line = ''.join([message, self.phases[i]])
|
||||
self.writeln(line)
|
||||
|
||||
|
||||
class Pie(Stack):
|
||||
phases = ('○', '◔', '◑', '◕', '●')
|
||||
@@ -1,45 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2012 Georgios Verigakis <verigak@gmail.com>
|
||||
#
|
||||
# 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.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from . import Infinite
|
||||
|
||||
|
||||
class Spinner(Infinite):
|
||||
phases = ('-', '\\', '|', '/')
|
||||
hide_cursor = True
|
||||
|
||||
def update(self):
|
||||
i = self.index % len(self.phases)
|
||||
message = self.message % self
|
||||
line = ''.join([message, self.phases[i]])
|
||||
self.writeln(line)
|
||||
|
||||
|
||||
class PieSpinner(Spinner):
|
||||
phases = ['◷', '◶', '◵', '◴']
|
||||
|
||||
|
||||
class MoonSpinner(Spinner):
|
||||
phases = ['◑', '◒', '◐', '◓']
|
||||
|
||||
|
||||
class LineSpinner(Spinner):
|
||||
phases = ['⎺', '⎻', '⎼', '⎽', '⎼', '⎻']
|
||||
|
||||
|
||||
class PixelSpinner(Spinner):
|
||||
phases = ['⣾', '⣷', '⣯', '⣟', '⡿', '⢿', '⣻', '⣽']
|
||||
@@ -0,0 +1,25 @@
|
||||
Copyright (c) 2006-2021 by the respective authors (see AUTHORS file).
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user