mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 22:50:18 +00:00
Merge branch 'master' into feature/keep-outdated-peep
This commit is contained in:
@@ -10,5 +10,6 @@ steps:
|
||||
- bash: |
|
||||
export GIT_SSL_CAINFO=$(python -m certifi)
|
||||
export LANG=C.UTF-8
|
||||
python -m pip install check-manifest
|
||||
check-manifest
|
||||
python -m pip install --upgrade setuptools twine readme_renderer[md]
|
||||
python setup.py sdist
|
||||
twine check dist/*
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
pip install certifi
|
||||
export GIT_SSL_CAINFO=$(python -m certifi)
|
||||
export LANG=C.UTF-8
|
||||
python -m pip install --upgrade invoke requests parver
|
||||
python -m pip install --upgrade invoke requests parver bs4 vistir towncrier
|
||||
python -m invoke vendoring.update
|
||||
|
||||
- template: ./run-manifest-check.yml
|
||||
|
||||
+12
@@ -13,3 +13,15 @@
|
||||
[submodule "tests/test_artifacts/git/dateutil"]
|
||||
path = tests/test_artifacts/git/dateutil
|
||||
url = https://github.com/dateutil/dateutil
|
||||
[submodule "tests/test_artifacts/git/pyinstaller"]
|
||||
path = tests/test_artifacts/git/pyinstaller
|
||||
url = https://github.com/pyinstaller/pyinstaller.git
|
||||
[submodule "tests/test_artifacts/git/jinja2"]
|
||||
path = tests/test_artifacts/git/jinja2
|
||||
url = https://github.com/pallets/jinja.git
|
||||
[submodule "tests/test_artifacts/git/flask"]
|
||||
path = tests/test_artifacts/git/flask
|
||||
url = https://github.com/pallets/flask.git
|
||||
[submodule "tests/test_artifacts/git/requests-2.18.4"]
|
||||
path = tests/test_artifacts/git/requests-2.18.4
|
||||
url = https://github.com/requests/requests
|
||||
|
||||
@@ -21,6 +21,7 @@ jedi = "*"
|
||||
isort = "*"
|
||||
rope = "*"
|
||||
passa = {editable = true, git = "https://github.com/sarugaku/passa.git"}
|
||||
bs4 = "*"
|
||||
|
||||
[packages]
|
||||
|
||||
|
||||
Generated
+30
-14
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "6acc712d82698e574727d19b22d05bf46565ecaa414e288fd0d79e385f8fdd10"
|
||||
"sha256": "f0a57ba6f180e7312f799a460a9d2790c9c26c3556eff2b5d1fe7d9b9174d05b"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
@@ -72,6 +72,14 @@
|
||||
],
|
||||
"version": "==2.6.0"
|
||||
},
|
||||
"beautifulsoup4": {
|
||||
"hashes": [
|
||||
"sha256:194ec62a25438adcb3fdb06378b26559eda1ea8a747367d34c33cef9c7f48d57",
|
||||
"sha256:90f8e61121d6ae58362ce3bed8cd997efb00c914eae0ff3d363c32f9a9822d10",
|
||||
"sha256:f0abd31228055d698bb392a826528ea08ebb9959e6bea17c606fd9c9009db938"
|
||||
],
|
||||
"version": "==4.6.3"
|
||||
},
|
||||
"black": {
|
||||
"hashes": [
|
||||
"sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739",
|
||||
@@ -88,6 +96,13 @@
|
||||
],
|
||||
"version": "==3.0.2"
|
||||
},
|
||||
"bs4": {
|
||||
"hashes": [
|
||||
"sha256:36ecea1fd7cc5c0c6e4a1ff075df26d50da647b75376626cc186e2212886dd3a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.0.1"
|
||||
},
|
||||
"cerberus": {
|
||||
"hashes": [
|
||||
"sha256:f5c2e048fb15ecb3c088d192164316093fcfa602a74b3386eefb2983aa7e800a"
|
||||
@@ -595,10 +610,10 @@
|
||||
},
|
||||
"requirementslib": {
|
||||
"hashes": [
|
||||
"sha256:441a5bfa487d3f3f5fd5d81c27071d9fd36bb385f538b3a87d20556a80b76f76",
|
||||
"sha256:89e1e02ff0b52ce9c610124eb990ae706e0aee08beef8c718e7b87e470cdceeb"
|
||||
"sha256:c2c00c7bd3bd4984c97d10cd4d143efbe33b5ed9e55961bea30ca7a9a4927289",
|
||||
"sha256:dc6b692e8dee03d6e90c29db1e337b0bf8152cce84a57f0fb4765e596afde4e0"
|
||||
],
|
||||
"version": "==1.3.1.post1"
|
||||
"version": "==1.3.3"
|
||||
},
|
||||
"resolvelib": {
|
||||
"hashes": [
|
||||
@@ -675,10 +690,10 @@
|
||||
},
|
||||
"tomlkit": {
|
||||
"hashes": [
|
||||
"sha256:82a8fbb8d8c6af72e96ba00b9db3e20ef61be6c79082552c9363f4559702258b",
|
||||
"sha256:a43e0195edc9b3c198cd4b5f0f3d427a395d47c4a76ceba7cc875ed030756c39"
|
||||
"sha256:d6506342615d051bc961f70bfcfa3d29b6616cc08a3ddfd4bc24196f16fd4ec2",
|
||||
"sha256:f077456d35303e7908cc233b340f71e0bec96f63429997f38ca9272b7d64029e"
|
||||
],
|
||||
"version": "==0.5.2"
|
||||
"version": "==0.5.3"
|
||||
},
|
||||
"towncrier": {
|
||||
"editable": true,
|
||||
@@ -734,14 +749,15 @@
|
||||
"spinner"
|
||||
],
|
||||
"hashes": [
|
||||
"sha256:851bd783f2b85a372e563db741dc689cb9263ce2e067e387facdca0c36b6a6ea",
|
||||
"sha256:b38ffc8ef83f85d81b4efa4cd31ea3bcd37bdb2bc9e8da9f20a40859bc44b57e"
|
||||
"sha256:3a1020fb7be000b268af96641ced9ead844b1f75840c41e20e473647688fc630",
|
||||
"sha256:6d2005ad670f77bd9c9b5415c4e2a4a20dce5b0cf0e0d11598eb463b2e0ebe44"
|
||||
],
|
||||
"version": "==0.2.4"
|
||||
"version": "==0.2.5"
|
||||
},
|
||||
"webencodings": {
|
||||
"hashes": [
|
||||
"sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"
|
||||
"sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
|
||||
"sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
|
||||
],
|
||||
"version": "==0.5.1"
|
||||
},
|
||||
@@ -754,10 +770,10 @@
|
||||
},
|
||||
"wheel": {
|
||||
"hashes": [
|
||||
"sha256:196c9842d79262bb66fcf59faa4bd0deb27da911dbc7c6cdca931080eb1f0783",
|
||||
"sha256:c93e2d711f5f9841e17f53b0e6c0ff85593f3b416b6eec7a9452041a59a42688"
|
||||
"sha256:029703bf514e16c8271c3821806a1c171220cc5bdd325cbf4e7da1e056a01db6",
|
||||
"sha256:1e53cdb3f808d5ccd0df57f964263752aa74ea7359526d3da6c02114ec1e1d44"
|
||||
],
|
||||
"version": "==0.32.2"
|
||||
"version": "==0.32.3"
|
||||
},
|
||||
"yaspin": {
|
||||
"hashes": [
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
``pipenv install`` will now unset the ``PYTHONHOME`` environment variable when not combined with ``--system``.
|
||||
@@ -0,0 +1 @@
|
||||
Fixed an issue which prevented variables from the environment, such as ``PIPENV_DEV`` or ``PIPENV_SYSTEM``, from being parsed and implemented correctly.
|
||||
@@ -0,0 +1,4 @@
|
||||
Update vendored dependencies to resolve resolution output parsing and python finding:
|
||||
- `pythonfinder 1.1.9 -> 1.1.10`
|
||||
- `requirementslib 1.3.1 -> 1.3.3`
|
||||
- `vistir 0.2.3 -> 0.2.5`
|
||||
@@ -0,0 +1 @@
|
||||
Clear pythonfinder cache after Python install
|
||||
@@ -0,0 +1 @@
|
||||
Fixed a race condition in hash resolution for dependencies for certain dependencies with missing cache entries or fresh Pipenv installs.
|
||||
@@ -0,0 +1 @@
|
||||
Pipenv will now respect top-level pins over VCS dependency locks.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
from .cli import cli
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli(auto_envvar_prefix="PIPENV")
|
||||
cli()
|
||||
|
||||
+8
-277
@@ -13,31 +13,7 @@ import six
|
||||
import sys
|
||||
import warnings
|
||||
import vistir
|
||||
from tempfile import _bin_openflags, gettempdir, _mkstemp_inner, mkdtemp
|
||||
|
||||
try:
|
||||
from tempfile import _infer_return_type
|
||||
except ImportError:
|
||||
|
||||
def _infer_return_type(*args):
|
||||
_types = set()
|
||||
for arg in args:
|
||||
if isinstance(type(arg), six.string_types):
|
||||
_types.add(str)
|
||||
elif isinstance(type(arg), bytes):
|
||||
_types.add(bytes)
|
||||
elif arg:
|
||||
_types.add(type(arg))
|
||||
return _types.pop()
|
||||
|
||||
|
||||
if sys.version_info[:2] >= (3, 5):
|
||||
try:
|
||||
from pathlib import Path
|
||||
except ImportError:
|
||||
from .vendor.pathlib2 import Path
|
||||
else:
|
||||
from .vendor.pathlib2 import Path
|
||||
from .vendor.vistir.compat import NamedTemporaryFile, Path, ResourceWarning, TemporaryDirectory
|
||||
|
||||
# Backport required for earlier versions of Python.
|
||||
if sys.version_info < (3, 3):
|
||||
@@ -45,257 +21,14 @@ if sys.version_info < (3, 3):
|
||||
else:
|
||||
from shutil import get_terminal_size
|
||||
|
||||
try:
|
||||
from weakref import finalize
|
||||
except ImportError:
|
||||
try:
|
||||
from .vendor.backports.weakref import finalize
|
||||
except ImportError:
|
||||
|
||||
class finalize(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
from .utils import logging
|
||||
logging.warn("weakref.finalize unavailable, not cleaning...")
|
||||
|
||||
def detach(self):
|
||||
return False
|
||||
|
||||
|
||||
from vistir.compat import ResourceWarning
|
||||
|
||||
|
||||
warnings.filterwarnings("ignore", category=ResourceWarning)
|
||||
|
||||
|
||||
class TemporaryDirectory(object):
|
||||
|
||||
"""
|
||||
Create and return a temporary directory. This has the same
|
||||
behavior as mkdtemp but can be used as a context manager. For
|
||||
example:
|
||||
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
...
|
||||
|
||||
Upon exiting the context, the directory and everything contained
|
||||
in it are removed.
|
||||
"""
|
||||
|
||||
def __init__(self, suffix="", prefix="", dir=None):
|
||||
if "RAM_DISK" in os.environ:
|
||||
import uuid
|
||||
|
||||
name = uuid.uuid4().hex
|
||||
dir_name = os.path.join(os.environ["RAM_DISK"].strip(), name)
|
||||
os.mkdir(dir_name)
|
||||
self.name = dir_name
|
||||
else:
|
||||
self.name = mkdtemp(suffix, prefix, dir)
|
||||
self._finalizer = finalize(
|
||||
self,
|
||||
self._cleanup,
|
||||
self.name,
|
||||
warn_message="Implicitly cleaning up {!r}".format(self),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _cleanup(cls, name, warn_message):
|
||||
vistir.path.rmtree(name)
|
||||
warnings.warn(warn_message, ResourceWarning)
|
||||
|
||||
def __repr__(self):
|
||||
return "<{} {!r}>".format(self.__class__.__name__, self.name)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc, value, tb):
|
||||
self.cleanup()
|
||||
|
||||
def cleanup(self):
|
||||
if self._finalizer.detach():
|
||||
vistir.path.rmtree(self.name)
|
||||
|
||||
|
||||
def _sanitize_params(prefix, suffix, dir):
|
||||
"""Common parameter processing for most APIs in this module."""
|
||||
output_type = _infer_return_type(prefix, suffix, dir)
|
||||
if suffix is None:
|
||||
suffix = output_type()
|
||||
if prefix is None:
|
||||
if output_type is str:
|
||||
prefix = "tmp"
|
||||
else:
|
||||
prefix = os.fsencode("tmp")
|
||||
if dir is None:
|
||||
if output_type is str:
|
||||
dir = gettempdir()
|
||||
else:
|
||||
dir = os.fsencode(gettempdir())
|
||||
return prefix, suffix, dir, output_type
|
||||
|
||||
|
||||
class _TemporaryFileCloser:
|
||||
"""
|
||||
A separate object allowing proper closing of a temporary file's
|
||||
underlying file object, without adding a __del__ method to the
|
||||
temporary file.
|
||||
"""
|
||||
|
||||
file = None # Set here since __del__ checks it
|
||||
close_called = False
|
||||
|
||||
def __init__(self, file, name, delete=True):
|
||||
self.file = file
|
||||
self.name = name
|
||||
self.delete = delete
|
||||
|
||||
# NT provides delete-on-close as a primitive, so we don't need
|
||||
# the wrapper to do anything special. We still use it so that
|
||||
# file.name is useful (i.e. not "(fdopen)") with NamedTemporaryFile.
|
||||
if os.name != "nt":
|
||||
|
||||
# Cache the unlinker so we don't get spurious errors at
|
||||
# shutdown when the module-level "os" is None'd out. Note
|
||||
# that this must be referenced as self.unlink, because the
|
||||
# name TemporaryFileWrapper may also get None'd out before
|
||||
# __del__ is called.
|
||||
|
||||
def close(self, unlink=os.unlink):
|
||||
if not self.close_called and self.file is not None:
|
||||
self.close_called = True
|
||||
try:
|
||||
self.file.close()
|
||||
finally:
|
||||
if self.delete:
|
||||
unlink(self.name)
|
||||
|
||||
# Need to ensure the file is deleted on __del__
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
else:
|
||||
|
||||
def close(self):
|
||||
if not self.close_called:
|
||||
self.close_called = True
|
||||
self.file.close()
|
||||
|
||||
|
||||
class _TemporaryFileWrapper:
|
||||
|
||||
"""
|
||||
Temporary file wrapper
|
||||
This class provides a wrapper around files opened for
|
||||
temporary use. In particular, it seeks to automatically
|
||||
remove the file when it is no longer needed.
|
||||
"""
|
||||
|
||||
def __init__(self, file, name, delete=True):
|
||||
self.file = file
|
||||
self.name = name
|
||||
self.delete = delete
|
||||
self._closer = _TemporaryFileCloser(file, name, delete)
|
||||
|
||||
def __getattr__(self, name):
|
||||
# Attribute lookups are delegated to the underlying file
|
||||
# and cached for non-numeric results
|
||||
# (i.e. methods are cached, closed and friends are not)
|
||||
file = self.__dict__["file"]
|
||||
a = getattr(file, name)
|
||||
if hasattr(a, "__call__"):
|
||||
func = a
|
||||
|
||||
@functools.wraps(func)
|
||||
def func_wrapper(*args, **kwargs):
|
||||
return func(*args, **kwargs)
|
||||
|
||||
# Avoid closing the file as long as the wrapper is alive,
|
||||
# see issue #18879.
|
||||
func_wrapper._closer = self._closer
|
||||
a = func_wrapper
|
||||
if not isinstance(a, int):
|
||||
setattr(self, name, a)
|
||||
return a
|
||||
|
||||
# The underlying __enter__ method returns the wrong object
|
||||
# (self.file) so override it to return the wrapper
|
||||
|
||||
def __enter__(self):
|
||||
self.file.__enter__()
|
||||
return self
|
||||
|
||||
# Need to trap __exit__ as well to ensure the file gets
|
||||
# deleted when used in a with statement
|
||||
|
||||
def __exit__(self, exc, value, tb):
|
||||
result = self.file.__exit__(exc, value, tb)
|
||||
self.close()
|
||||
return result
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close the temporary file, possibly deleting it.
|
||||
"""
|
||||
self._closer.close()
|
||||
|
||||
# iter() doesn't use __getattr__ to find the __iter__ method
|
||||
|
||||
def __iter__(self):
|
||||
# Don't return iter(self.file), but yield from it to avoid closing
|
||||
# file as long as it's being used as iterator (see issue #23700). We
|
||||
# can't use 'yield from' here because iter(file) returns the file
|
||||
# object itself, which has a close method, and thus the file would get
|
||||
# closed when the generator is finalized, due to PEP380 semantics.
|
||||
for line in self.file:
|
||||
yield line
|
||||
|
||||
|
||||
def NamedTemporaryFile(
|
||||
mode="w+b",
|
||||
buffering=-1,
|
||||
encoding=None,
|
||||
newline=None,
|
||||
suffix=None,
|
||||
prefix=None,
|
||||
dir=None,
|
||||
delete=True,
|
||||
):
|
||||
"""
|
||||
Create and return a temporary file.
|
||||
Arguments:
|
||||
'prefix', 'suffix', 'dir' -- as for mkstemp.
|
||||
'mode' -- the mode argument to io.open (default "w+b").
|
||||
'buffering' -- the buffer size argument to io.open (default -1).
|
||||
'encoding' -- the encoding argument to io.open (default None)
|
||||
'newline' -- the newline argument to io.open (default None)
|
||||
'delete' -- whether the file is deleted on close (default True).
|
||||
The file is created as mkstemp() would do it.
|
||||
Returns an object with a file-like interface; the name of the file
|
||||
is accessible as its 'name' attribute. The file will be automatically
|
||||
deleted when it is closed unless the 'delete' argument is set to False.
|
||||
"""
|
||||
prefix, suffix, dir, output_type = _sanitize_params(prefix, suffix, dir)
|
||||
flags = _bin_openflags
|
||||
# Setting O_TEMPORARY in the flags causes the OS to delete
|
||||
# the file when it is closed. This is only supported by Windows.
|
||||
if os.name == "nt" and delete:
|
||||
flags |= os.O_TEMPORARY
|
||||
if sys.version_info < (3, 5):
|
||||
(fd, name) = _mkstemp_inner(dir, prefix, suffix, flags)
|
||||
else:
|
||||
(fd, name) = _mkstemp_inner(dir, prefix, suffix, flags, output_type)
|
||||
try:
|
||||
file = io.open(
|
||||
fd, mode, buffering=buffering, newline=newline, encoding=encoding
|
||||
)
|
||||
return _TemporaryFileWrapper(file, name, delete)
|
||||
|
||||
except BaseException:
|
||||
os.unlink(name)
|
||||
os.close(fd)
|
||||
raise
|
||||
__all__ = [
|
||||
"NamedTemporaryFile", "Path", "ResourceWarning", "TemporaryDirectory",
|
||||
"get_terminal_size", "getpreferredencoding", "DEFAULT_ENCODING", "force_encoding",
|
||||
"UNICODE_TO_ASCII_TRANSLATION_MAP", "decode_output", "fix_utf8"
|
||||
]
|
||||
|
||||
|
||||
def getpreferredencoding():
|
||||
@@ -366,8 +99,8 @@ OUT_ENCODING, ERR_ENCODING = force_encoding()
|
||||
UNICODE_TO_ASCII_TRANSLATION_MAP = {
|
||||
8230: u"...",
|
||||
8211: u"-",
|
||||
10004: u"x",
|
||||
10008: u"Ok"
|
||||
10004: u"OK",
|
||||
10008: u"x",
|
||||
}
|
||||
|
||||
|
||||
@@ -384,13 +117,11 @@ def decode_output(output):
|
||||
output = output.translate(UNICODE_TO_ASCII_TRANSLATION_MAP)
|
||||
output = output.encode(DEFAULT_ENCODING, "replace")
|
||||
return vistir.misc.to_text(output, encoding=DEFAULT_ENCODING, errors="replace")
|
||||
return output
|
||||
|
||||
|
||||
def fix_utf8(text):
|
||||
if not isinstance(text, six.string_types):
|
||||
return text
|
||||
from ._compat import decode_output
|
||||
try:
|
||||
text = decode_output(text)
|
||||
except UnicodeDecodeError:
|
||||
|
||||
+36
-21
@@ -27,16 +27,20 @@ from .options import (
|
||||
# Enable shell completion.
|
||||
click_completion.init()
|
||||
|
||||
subcommand_context = CONTEXT_SETTINGS.copy()
|
||||
subcommand_context.update({
|
||||
"ignore_unknown_options": True,
|
||||
"allow_extra_args": True
|
||||
})
|
||||
subcommand_context_no_interspersion = subcommand_context.copy()
|
||||
subcommand_context_no_interspersion["allow_interspersed_args"] = False
|
||||
|
||||
|
||||
@group(cls=PipenvGroup, invoke_without_command=True, context_settings=CONTEXT_SETTINGS)
|
||||
@option("--where", is_flag=True, default=False, help="Output project home information.")
|
||||
@option("--venv", is_flag=True, default=False, help="Output virtualenv information.")
|
||||
@option(
|
||||
"--py", is_flag=True, default=False, help="Output Python interpreter information."
|
||||
)
|
||||
@option(
|
||||
"--envs", is_flag=True, default=False, help="Output Environment Variable options."
|
||||
)
|
||||
@option("--py", is_flag=True, default=False, help="Output Python interpreter information.")
|
||||
@option("--envs", is_flag=True, default=False, help="Output Environment Variable options.")
|
||||
@option("--rm", is_flag=True, default=False, help="Remove the virtualenv.")
|
||||
@option("--bare", is_flag=True, default=False, help="Minimal output.")
|
||||
@option(
|
||||
@@ -211,7 +215,7 @@ def cli(
|
||||
|
||||
@cli.command(
|
||||
short_help="Installs provided packages and adds them to Pipfile, or (if no packages are given), installs all packages from Pipfile.",
|
||||
context_settings=dict(ignore_unknown_options=True, allow_extra_args=True),
|
||||
context_settings=subcommand_context,
|
||||
)
|
||||
@system_option
|
||||
@code_option
|
||||
@@ -253,7 +257,10 @@ def install(
|
||||
ctx.abort()
|
||||
|
||||
|
||||
@cli.command(short_help="Un-installs a provided package and removes it from Pipfile.")
|
||||
@cli.command(
|
||||
short_help="Un-installs a provided package and removes it from Pipfile.",
|
||||
context_settings=subcommand_context
|
||||
)
|
||||
@option("--skip-lock/--lock", is_flag=True, default=False, help="Lock afterwards.")
|
||||
@option(
|
||||
"--all-dev",
|
||||
@@ -296,7 +303,7 @@ def uninstall(
|
||||
if retcode:
|
||||
sys.exit(retcode)
|
||||
|
||||
@cli.command(short_help="Generates Pipfile.lock.")
|
||||
@cli.command(short_help="Generates Pipfile.lock.", context_settings=CONTEXT_SETTINGS)
|
||||
@lock_options
|
||||
@pass_state
|
||||
@pass_context
|
||||
@@ -328,7 +335,7 @@ def lock(
|
||||
|
||||
@cli.command(
|
||||
short_help="Spawns a shell within the virtualenv.",
|
||||
context_settings=dict(ignore_unknown_options=True, allow_extra_args=True),
|
||||
context_settings=subcommand_context,
|
||||
)
|
||||
@option(
|
||||
"--fancy",
|
||||
@@ -387,11 +394,7 @@ def shell(
|
||||
@cli.command(
|
||||
add_help_option=False,
|
||||
short_help="Spawns a command installed into the virtualenv.",
|
||||
context_settings=dict(
|
||||
ignore_unknown_options=True,
|
||||
allow_interspersed_args=False,
|
||||
allow_extra_args=True,
|
||||
),
|
||||
context_settings=subcommand_context_no_interspersion,
|
||||
)
|
||||
@common_options
|
||||
@argument("command")
|
||||
@@ -408,7 +411,7 @@ def run(state, command, args):
|
||||
|
||||
@cli.command(
|
||||
short_help="Checks for security vulnerabilities and against PEP 508 markers provided in Pipfile.",
|
||||
context_settings=dict(ignore_unknown_options=True, allow_extra_args=True),
|
||||
context_settings=subcommand_context
|
||||
)
|
||||
@option(
|
||||
"--unused",
|
||||
@@ -448,7 +451,7 @@ def check(
|
||||
)
|
||||
|
||||
|
||||
@cli.command(short_help="Runs lock, then sync.")
|
||||
@cli.command(short_help="Runs lock, then sync.", context_settings=CONTEXT_SETTINGS)
|
||||
@option("--bare", is_flag=True, default=False, help="Minimal output.")
|
||||
@option(
|
||||
"--outdated", is_flag=True, default=False, help=u"List out-of-date dependencies."
|
||||
@@ -525,7 +528,10 @@ def update(
|
||||
)
|
||||
|
||||
|
||||
@cli.command(short_help=u"Displays currently-installed dependency graph information.")
|
||||
@cli.command(
|
||||
short_help=u"Displays currently-installed dependency graph information.",
|
||||
context_settings=CONTEXT_SETTINGS
|
||||
)
|
||||
@option("--bare", is_flag=True, default=False, help="Minimal output.")
|
||||
@option("--json", is_flag=True, default=False, help="Output JSON.")
|
||||
@option("--json-tree", is_flag=True, default=False, help="Output JSON in nested tree.")
|
||||
@@ -537,7 +543,10 @@ def graph(bare=False, json=False, json_tree=False, reverse=False):
|
||||
do_graph(bare=bare, json=json, json_tree=json_tree, reverse=reverse)
|
||||
|
||||
|
||||
@cli.command(short_help="View a given module in your editor.", name="open")
|
||||
@cli.command(
|
||||
short_help="View a given module in your editor.", name="open",
|
||||
context_settings=CONTEXT_SETTINGS
|
||||
)
|
||||
@common_options
|
||||
@argument("module", nargs=1)
|
||||
@pass_state
|
||||
@@ -573,7 +582,10 @@ def run_open(state, module, *args, **kwargs):
|
||||
return 0
|
||||
|
||||
|
||||
@cli.command(short_help="Installs all packages specified in Pipfile.lock.")
|
||||
@cli.command(
|
||||
short_help="Installs all packages specified in Pipfile.lock.",
|
||||
context_settings=CONTEXT_SETTINGS
|
||||
)
|
||||
@option("--bare", is_flag=True, default=False, help="Minimal output.")
|
||||
@sync_options
|
||||
@pass_state
|
||||
@@ -606,7 +618,10 @@ def sync(
|
||||
ctx.abort()
|
||||
|
||||
|
||||
@cli.command(short_help="Uninstalls all packages not specified in Pipfile.lock.")
|
||||
@cli.command(
|
||||
short_help="Uninstalls all packages not specified in Pipfile.lock.",
|
||||
context_settings=CONTEXT_SETTINGS
|
||||
)
|
||||
@option("--bare", is_flag=True, default=False, help="Minimal output.")
|
||||
@option("--dry-run", is_flag=True, default=False, help="Just output unneeded packages.")
|
||||
@verbose_option
|
||||
|
||||
+17
-12
@@ -12,7 +12,10 @@ from .. import environments
|
||||
from ..utils import is_valid_url
|
||||
|
||||
|
||||
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
|
||||
CONTEXT_SETTINGS = {
|
||||
"help_option_names": ["-h", "--help"],
|
||||
"auto_envvar_prefix": "PIPENV"
|
||||
}
|
||||
|
||||
|
||||
class PipenvGroup(Group):
|
||||
@@ -117,7 +120,7 @@ def sequential_option(f):
|
||||
return value
|
||||
return option("--sequential", is_flag=True, default=False, expose_value=False,
|
||||
help="Install dependencies one-at-a-time, instead of concurrently.",
|
||||
callback=callback, type=click.types.BOOL)(f)
|
||||
callback=callback, type=click.types.BOOL, show_envvar=True)(f)
|
||||
|
||||
|
||||
def skip_lock_option(f):
|
||||
@@ -127,7 +130,8 @@ def skip_lock_option(f):
|
||||
return value
|
||||
return option("--skip-lock", is_flag=True, default=False, expose_value=False,
|
||||
help=u"Skip locking mechanisms and use the Pipfile instead during operation.",
|
||||
envvar="PIPENV_SKIP_LOCK", callback=callback, type=click.types.BOOL)(f)
|
||||
envvar="PIPENV_SKIP_LOCK", callback=callback, type=click.types.BOOL,
|
||||
show_envvar=True)(f)
|
||||
|
||||
|
||||
def keep_outdated_option(f):
|
||||
@@ -137,7 +141,7 @@ def keep_outdated_option(f):
|
||||
return value
|
||||
return option("--keep-outdated", is_flag=True, default=False, expose_value=False,
|
||||
help=u"Keep out-dated dependencies from being updated in Pipfile.lock.",
|
||||
callback=callback, type=click.types.BOOL)(f)
|
||||
callback=callback, type=click.types.BOOL, show_envvar=True)(f)
|
||||
|
||||
|
||||
def selective_upgrade_option(f):
|
||||
@@ -157,7 +161,7 @@ def ignore_pipfile_option(f):
|
||||
return value
|
||||
return option("--ignore-pipfile", is_flag=True, default=False, expose_value=False,
|
||||
help="Ignore Pipfile when installing, using the Pipfile.lock.",
|
||||
callback=callback, type=click.types.BOOL)(f)
|
||||
callback=callback, type=click.types.BOOL, show_envvar=True)(f)
|
||||
|
||||
|
||||
def dev_option(f):
|
||||
@@ -167,7 +171,7 @@ def dev_option(f):
|
||||
return value
|
||||
return option("--dev", "-d", is_flag=True, default=False, type=click.types.BOOL,
|
||||
help="Install both develop and default packages.", callback=callback,
|
||||
expose_value=False)(f)
|
||||
expose_value=False, show_envvar=True)(f)
|
||||
|
||||
|
||||
def pre_option(f):
|
||||
@@ -208,7 +212,7 @@ def python_option(f):
|
||||
return value
|
||||
return option("--python", default=False, nargs=1, callback=callback,
|
||||
help="Specify which version of Python virtualenv should use.",
|
||||
expose_value=False)(f)
|
||||
expose_value=False, allow_from_autoenv=False)(f)
|
||||
|
||||
|
||||
def pypi_mirror_option(f):
|
||||
@@ -238,7 +242,7 @@ def site_packages_option(f):
|
||||
return value
|
||||
return option("--site-packages", is_flag=True, default=False, type=click.types.BOOL,
|
||||
help="Enable site-packages for the virtualenv.", callback=callback,
|
||||
expose_value=False)(f)
|
||||
expose_value=False, show_envvar=True)(f)
|
||||
|
||||
|
||||
def clear_option(f):
|
||||
@@ -248,7 +252,7 @@ def clear_option(f):
|
||||
return value
|
||||
return option("--clear", is_flag=True, callback=callback, type=click.types.BOOL,
|
||||
help="Clears caches (pipenv, pip, and pip-tools).",
|
||||
expose_value=False)(f)
|
||||
expose_value=False, show_envvar=True)(f)
|
||||
|
||||
|
||||
def system_option(f):
|
||||
@@ -258,7 +262,8 @@ def system_option(f):
|
||||
state.system = value
|
||||
return value
|
||||
return option("--system", is_flag=True, default=False, help="System pip management.",
|
||||
callback=callback, type=click.types.BOOL, expose_value=False)(f)
|
||||
callback=callback, type=click.types.BOOL, expose_value=False,
|
||||
show_envvar=True)(f)
|
||||
|
||||
|
||||
def requirementstxt_option(f):
|
||||
@@ -288,7 +293,7 @@ def code_option(f):
|
||||
state.installstate.code = value
|
||||
return value
|
||||
return option("--code", "-c", nargs=1, default=False, help="Import from codebase.",
|
||||
callback=callback, expose_value=False)(f)
|
||||
callback=callback, expose_value=False)(f)
|
||||
|
||||
|
||||
def deploy_option(f):
|
||||
@@ -298,7 +303,7 @@ def deploy_option(f):
|
||||
return value
|
||||
return option("--deploy", is_flag=True, default=False, type=click.types.BOOL,
|
||||
help=u"Abort if the Pipfile.lock is out-of-date, or Python version is"
|
||||
" wrong.", callback=callback, expose_value=False)(f)
|
||||
" wrong.", callback=callback, expose_value=False)(f)
|
||||
|
||||
|
||||
def setup_verbosity(ctx, param, value):
|
||||
|
||||
+18
-22
@@ -455,6 +455,11 @@ def ensure_python(three=None, python=None):
|
||||
sp.ok(environments.PIPENV_SPINNER_OK_TEXT.format("Success!"))
|
||||
# Print the results, in a beautiful blue…
|
||||
click.echo(crayons.blue(c.out), err=True)
|
||||
# Clear the pythonfinder caches
|
||||
from .vendor.pythonfinder import Finder
|
||||
finder = Finder(system=False, global_search=True)
|
||||
finder.find_python_version.cache_clear()
|
||||
finder.find_all_python_versions.cache_clear()
|
||||
# Find the newly installed Python, hopefully.
|
||||
version = str(version)
|
||||
path_to_python = find_a_system_python(version)
|
||||
@@ -727,11 +732,20 @@ def batch_install(deps_list, procs, failed_deps_queue,
|
||||
with vistir.contextmanagers.temp_environ():
|
||||
if not allow_global:
|
||||
os.environ["PIP_USER"] = vistir.compat.fs_str("0")
|
||||
if "PYTHONHOME" in os.environ:
|
||||
del os.environ["PYTHONHOME"]
|
||||
if no_deps:
|
||||
link = getattr(dep.req, "link", None)
|
||||
is_wheel = False
|
||||
if link:
|
||||
is_wheel = link.is_wheel
|
||||
is_non_editable_vcs = (dep.is_vcs and not dep.editable)
|
||||
no_deps = not (dep.is_file_or_url and not (is_wheel or dep.editable))
|
||||
c = pip_install(
|
||||
dep,
|
||||
ignore_hashes=any([ignore_hashes, dep.editable, dep.is_vcs]),
|
||||
allow_global=allow_global,
|
||||
no_deps=False if is_artifact else no_deps,
|
||||
no_deps=no_deps,
|
||||
block=any([dep.editable, dep.is_vcs, blocking]),
|
||||
index=index,
|
||||
requirements_dir=requirements_dir,
|
||||
@@ -739,7 +753,7 @@ def batch_install(deps_list, procs, failed_deps_queue,
|
||||
trusted_hosts=trusted_hosts,
|
||||
extra_indexes=extra_indexes
|
||||
)
|
||||
if dep.is_vcs:
|
||||
if dep.is_vcs or dep.editable:
|
||||
c.block()
|
||||
if procs.qsize() < nprocs:
|
||||
c.dep = dep
|
||||
@@ -1824,26 +1838,6 @@ def do_install(
|
||||
for req in import_from_code(code):
|
||||
click.echo(" Found {0}!".format(crayons.green(req)))
|
||||
project.add_package_to_pipfile(req)
|
||||
# Install editable local packages before locking - this gives us access to dist-info
|
||||
if project.pipfile_exists and (
|
||||
# double negatives are for english readability, leave them alone.
|
||||
(not project.lockfile_exists and not deploy)
|
||||
or (not project.virtualenv_exists and not system)
|
||||
):
|
||||
section = (
|
||||
project.editable_packages if not dev else project.editable_dev_packages
|
||||
)
|
||||
for package in section.keys():
|
||||
req = convert_deps_to_pip(
|
||||
{package: section[package]}, project=project, r=False
|
||||
)
|
||||
if req:
|
||||
req = req[0]
|
||||
req = req[len("-e ") :] if req.startswith("-e ") else req
|
||||
if not editable_packages:
|
||||
editable_packages = [req]
|
||||
else:
|
||||
editable_packages.extend([req])
|
||||
# Allow more than one package to be provided.
|
||||
package_args = [p for p in packages] + [
|
||||
"-e {0}".format(pkg) for pkg in editable_packages
|
||||
@@ -1916,6 +1910,8 @@ def do_install(
|
||||
with vistir.contextmanagers.temp_environ(), create_spinner("Installing...") as sp:
|
||||
if not system:
|
||||
os.environ["PIP_USER"] = vistir.compat.fs_str("0")
|
||||
if "PYTHONHOME" in os.environ:
|
||||
del os.environ["PYTHONHOME"]
|
||||
try:
|
||||
pkg_requirement = Requirement.from_line(pkg_line)
|
||||
except ValueError as e:
|
||||
|
||||
+6
-6
@@ -752,10 +752,9 @@ class Project(object):
|
||||
"develop": self._lockfile["develop"].copy()
|
||||
}
|
||||
lockfile_dict.update({"_meta": self.get_lockfile_meta()})
|
||||
_created_lockfile = Req_Lockfile.from_data(
|
||||
lockfile = Req_Lockfile.from_data(
|
||||
path=self.lockfile_location, data=lockfile_dict, meta_from_project=False
|
||||
)
|
||||
lockfile._lockfile = _created_lockfile
|
||||
elif self.lockfile_exists:
|
||||
try:
|
||||
lockfile = Req_Lockfile.load(self.lockfile_location)
|
||||
@@ -785,10 +784,11 @@ class Project(object):
|
||||
|
||||
def get_lockfile_meta(self):
|
||||
from .vendor.plette.lockfiles import PIPFILE_SPEC_CURRENT
|
||||
sources = self.lockfile_content.get("_meta", {}).get("sources", [])
|
||||
if not sources:
|
||||
sources = self.pipfile_sources
|
||||
elif not isinstance(sources, list):
|
||||
if self.lockfile_exists:
|
||||
sources = self.lockfile_content.get("_meta", {}).get("sources", [])
|
||||
else:
|
||||
sources = [dict(source) for source in self.parsed_pipfile["source"]]
|
||||
if not isinstance(sources, list):
|
||||
sources = [sources,]
|
||||
return {
|
||||
"hash": {"sha256": self.calculate_pipfile_hash()},
|
||||
|
||||
+89
-58
@@ -19,26 +19,13 @@ from click import echo as click_echo
|
||||
from first import first
|
||||
from vistir.misc import fs_str
|
||||
|
||||
six.add_move(six.MovedAttribute("Mapping", "collections", "collections.abc"))
|
||||
six.add_move(six.MovedAttribute("Sequence", "collections", "collections.abc"))
|
||||
from six.moves import Mapping, Sequence
|
||||
six.add_move(six.MovedAttribute("Mapping", "collections", "collections.abc")) # noqa
|
||||
six.add_move(six.MovedAttribute("Sequence", "collections", "collections.abc")) # noqa
|
||||
six.add_move(six.MovedAttribute("Set", "collections", "collections.abc")) # noqa
|
||||
from six.moves import Mapping, Sequence, Set
|
||||
|
||||
from vistir.compat import ResourceWarning
|
||||
|
||||
try:
|
||||
from weakref import finalize
|
||||
except ImportError:
|
||||
try:
|
||||
from .vendor.backports.weakref import finalize
|
||||
except ImportError:
|
||||
|
||||
class finalize(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
logging.warn("weakref.finalize unavailable, not cleaning...")
|
||||
|
||||
def detach(self):
|
||||
return False
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.ERROR)
|
||||
|
||||
@@ -320,7 +307,7 @@ class Resolver(object):
|
||||
if self.sources:
|
||||
requirementstxt_sources = " ".join(self.pip_args) if self.pip_args else ""
|
||||
requirementstxt_sources = requirementstxt_sources.replace(" --", "\n--")
|
||||
constraints_file.write(u"{0}\n".format(requirementstxt_sources))
|
||||
constraints_file.write(u"{0}\n".format(requirementstxt_sources))
|
||||
constraints = self.initial_constraints
|
||||
constraints_file.write(u"\n".join([c for c in constraints]))
|
||||
constraints_file.close()
|
||||
@@ -411,40 +398,55 @@ class Resolver(object):
|
||||
self.resolved_tree.update(results)
|
||||
return self.resolved_tree
|
||||
|
||||
@staticmethod
|
||||
def _should_include_hash(ireq):
|
||||
from pipenv.vendor.vistir.compat import Path, to_native_string
|
||||
from pipenv.vendor.vistir.path import url_to_path
|
||||
|
||||
# We can only hash artifacts.
|
||||
try:
|
||||
if not ireq.link.is_artifact:
|
||||
return False
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
# But we don't want normal pypi artifcats since the normal resolver
|
||||
# handles those
|
||||
if is_pypi_url(ireq.link.url):
|
||||
return False
|
||||
|
||||
# We also don't want to try to hash directories as this will fail
|
||||
# as these are editable deps and are not hashable.
|
||||
if (ireq.link.scheme == "file" and
|
||||
Path(to_native_string(url_to_path(ireq.link.url))).is_dir()):
|
||||
return False
|
||||
return True
|
||||
|
||||
def resolve_hashes(self):
|
||||
def _should_include_hash(ireq):
|
||||
from pipenv.vendor.vistir.compat import Path, to_native_string
|
||||
from pipenv.vendor.vistir.path import url_to_path
|
||||
|
||||
# We can only hash artifacts.
|
||||
try:
|
||||
if not ireq.link.is_artifact:
|
||||
return False
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
# But we don't want normal pypi artifcats since the normal resolver
|
||||
# handles those
|
||||
if is_pypi_url(ireq.link.url):
|
||||
return False
|
||||
|
||||
# We also don't want to try to hash directories as this will fail
|
||||
# as these are editable deps and are not hashable.
|
||||
if (ireq.link.scheme == "file" and
|
||||
Path(to_native_string(url_to_path(ireq.link.url))).is_dir()):
|
||||
return False
|
||||
return True
|
||||
|
||||
if self.results is not None:
|
||||
resolved_hashes = self.resolver.resolve_hashes(self.results)
|
||||
for ireq, ireq_hashes in resolved_hashes.items():
|
||||
# We _ALWAYS MUST PRIORITIZE_ the inclusion of hashes from local sources
|
||||
# PLEASE *DO NOT MODIFY THIS* TO CHECK WHETHER AN IREQ ALREADY HAS A HASH
|
||||
# RESOLVED. The resolver will pull hashes from PyPI and only from PyPI.
|
||||
# The entire purpose of this approach is to include missing hashes.
|
||||
# This fixes a race condition in resolution for missing dependency caches
|
||||
# see pypa/pipenv#3289
|
||||
if self._should_include_hash(ireq) and (
|
||||
not ireq_hashes or ireq.link.scheme == "file"
|
||||
):
|
||||
if not ireq_hashes:
|
||||
ireq_hashes = set()
|
||||
new_hashes = self.resolver.repository._hash_cache.get_hash(ireq.link)
|
||||
add_to_set(ireq_hashes, new_hashes)
|
||||
else:
|
||||
ireq_hashes = set(ireq_hashes)
|
||||
# The _ONLY CASE_ where we flat out set the value is if it isn't present
|
||||
# It's a set, so otherwise we *always* need to do a union update
|
||||
if ireq not in self.hashes:
|
||||
if _should_include_hash(ireq):
|
||||
self.hashes[ireq] = [
|
||||
self.resolver.repository._hash_cache.get_hash(ireq.link)
|
||||
]
|
||||
else:
|
||||
self.hashes[ireq] = ireq_hashes
|
||||
self.hashes[ireq] = ireq_hashes
|
||||
else:
|
||||
self.hashes[ireq] |= ireq_hashes
|
||||
return self.hashes
|
||||
|
||||
|
||||
@@ -471,12 +473,12 @@ def actually_resolve_deps(
|
||||
warning_list = []
|
||||
|
||||
with warnings.catch_warnings(record=True) as warning_list:
|
||||
constraints = get_resolver_metadata(
|
||||
deps, index_lookup, markers_lookup, project, sources,
|
||||
)
|
||||
resolver = Resolver(constraints, req_dir, project, sources, clear=clear, pre=pre)
|
||||
resolved_tree = resolver.resolve()
|
||||
hashes = resolver.resolve_hashes()
|
||||
constraints = get_resolver_metadata(
|
||||
deps, index_lookup, markers_lookup, project, sources,
|
||||
)
|
||||
resolver = Resolver(constraints, req_dir, project, sources, clear=clear, pre=pre)
|
||||
resolved_tree = resolver.resolve()
|
||||
hashes = resolver.resolve_hashes()
|
||||
|
||||
for warning in warning_list:
|
||||
_show_warning(warning.message, warning.category, warning.filename, warning.lineno,
|
||||
@@ -538,7 +540,10 @@ def resolve(cmd, sp):
|
||||
return c
|
||||
|
||||
|
||||
def get_locked_dep(dep, pipfile_section):
|
||||
def get_locked_dep(dep, pipfile_section, prefer_pipfile=False):
|
||||
# the prefer pipfile flag is not used yet, but we are introducing
|
||||
# it now for development purposes
|
||||
# TODO: Is this implementation clear? How can it be improved?
|
||||
entry = None
|
||||
cleaner_kwargs = {
|
||||
"is_top_level": False,
|
||||
@@ -552,6 +557,14 @@ def get_locked_dep(dep, pipfile_section):
|
||||
if entry:
|
||||
cleaner_kwargs.update({"is_top_level": True, "pipfile_entry": entry})
|
||||
lockfile_entry = clean_resolved_dep(dep, **cleaner_kwargs)
|
||||
if entry and isinstance(entry, Mapping):
|
||||
version = entry.get("version", "") if entry else ""
|
||||
else:
|
||||
version = entry if entry else ""
|
||||
lockfile_version = lockfile_entry.get("version", "")
|
||||
# Keep pins from the lockfile
|
||||
if prefer_pipfile and lockfile_version != version and version.startswith("=="):
|
||||
lockfile_version = version
|
||||
return lockfile_entry
|
||||
|
||||
|
||||
@@ -562,7 +575,10 @@ def prepare_lockfile(results, pipfile, lockfile):
|
||||
# markers, normalized names, URL info, etc that we may have dropped during lock
|
||||
if not is_vcs(dep):
|
||||
lockfile_entry = get_locked_dep(dep, pipfile)
|
||||
lockfile.update(lockfile_entry)
|
||||
name = next(iter(k for k in lockfile_entry.keys()))
|
||||
current_entry = lockfile.get(name)
|
||||
if not current_entry or not is_vcs(current_entry):
|
||||
lockfile.update(lockfile_entry)
|
||||
return lockfile
|
||||
|
||||
|
||||
@@ -591,7 +607,7 @@ def venv_resolve_deps(
|
||||
pipfile_section = "dev_packages" if dev else "packages"
|
||||
lockfile_section = "develop" if dev else "default"
|
||||
vcs_section = "vcs_{0}".format(pipfile_section)
|
||||
vcs_deps = getattr(project, vcs_section, [])
|
||||
vcs_deps = getattr(project, vcs_section, {})
|
||||
if not deps and not vcs_deps:
|
||||
return {}
|
||||
|
||||
@@ -611,6 +627,7 @@ def venv_resolve_deps(
|
||||
dev=dev,
|
||||
)
|
||||
vcs_deps = [req.as_line() for req in vcs_reqs if req.editable]
|
||||
lockfile[lockfile_section].update(vcs_lockfile)
|
||||
cmd = [
|
||||
which("python", allow_global=allow_global),
|
||||
Path(resolver.__file__.rstrip("co")).as_posix()
|
||||
@@ -665,7 +682,9 @@ def venv_resolve_deps(
|
||||
click_echo(err.strip(), err=True)
|
||||
raise RuntimeError("There was a problem with locking.")
|
||||
lockfile[lockfile_section] = prepare_lockfile(results, pipfile, lockfile[lockfile_section])
|
||||
lockfile[lockfile_section].update(vcs_lockfile)
|
||||
for k, v in vcs_lockfile.items():
|
||||
if k in getattr(project, vcs_section, {}) or k not in lockfile[lockfile_section]:
|
||||
lockfile[lockfile_section][k].update(v)
|
||||
|
||||
|
||||
def resolve_deps(
|
||||
@@ -860,7 +879,7 @@ def mkdir_p(newdir):
|
||||
if exn.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
|
||||
|
||||
def is_required_version(version, specified_version):
|
||||
"""Check to see if there's a hard requirement for version
|
||||
number provided in the Pipfile.
|
||||
@@ -1490,3 +1509,15 @@ def sys_version(version_tuple):
|
||||
sys.version_info = version_tuple
|
||||
yield
|
||||
sys.version_info = old_version
|
||||
|
||||
|
||||
def add_to_set(original_set, element):
|
||||
"""Given a set and some arbitrary element, add the element(s) to the set"""
|
||||
if not element:
|
||||
return original_set
|
||||
if isinstance(element, Set):
|
||||
original_set |= element
|
||||
elif isinstance(element, (list, tuple)):
|
||||
original_set |= set(element)
|
||||
else:
|
||||
original_set.add(element)
|
||||
|
||||
Vendored
+1
-1
@@ -3,7 +3,7 @@ __all__ = [
|
||||
"Lockfile", "Pipfile",
|
||||
]
|
||||
|
||||
__version__ = '0.2.2'
|
||||
__version__ = '0.2.3.dev0'
|
||||
|
||||
from .lockfiles import Lockfile
|
||||
from .pipfiles import Pipfile
|
||||
|
||||
Vendored
+1
-1
@@ -22,7 +22,7 @@ def validate(cls, data):
|
||||
v = VALIDATORS[key]
|
||||
except KeyError:
|
||||
v = VALIDATORS[key] = cerberus.Validator(schema, allow_unknown=True)
|
||||
if v.validate(data, normalize=False):
|
||||
if v.validate(dict(data), normalize=False):
|
||||
return
|
||||
raise ValidationError(data, v)
|
||||
|
||||
|
||||
+1
@@ -513,6 +513,7 @@ class PathEntry(BasePath):
|
||||
if self.is_dir:
|
||||
return None
|
||||
if self.is_python:
|
||||
py_version = None
|
||||
try:
|
||||
py_version = PythonVersion.from_path(path=self, name=self.name)
|
||||
except (InvalidPythonVersion, ValueError):
|
||||
|
||||
+11
-9
@@ -15,6 +15,7 @@ from packaging.version import parse as parse_version
|
||||
from vistir.compat import Path
|
||||
|
||||
from ..environment import SYSTEM_ARCH, PYENV_ROOT, ASDF_DATA_DIR
|
||||
from ..exceptions import InvalidPythonVersion
|
||||
from .mixins import BaseFinder, BasePath
|
||||
from ..utils import (
|
||||
_filter_none,
|
||||
@@ -109,7 +110,7 @@ class PythonFinder(BaseFinder, BasePath):
|
||||
version = None
|
||||
try:
|
||||
version = PythonVersion.parse(p.name)
|
||||
except ValueError:
|
||||
except (ValueError, InvalidPythonVersion):
|
||||
entry = next(iter(version_path.find_all_python_versions()), None)
|
||||
if not entry:
|
||||
if self.ignore_unsupported:
|
||||
@@ -246,7 +247,7 @@ class PythonVersion(object):
|
||||
is_postrelease = attr.ib(default=False)
|
||||
is_devrelease = attr.ib(default=False)
|
||||
is_debug = attr.ib(default=False)
|
||||
version = attr.ib(default=None, validator=optional_instance_of(Version))
|
||||
version = attr.ib(default=None)
|
||||
architecture = attr.ib(default=None)
|
||||
comes_from = attr.ib(default=None)
|
||||
executable = attr.ib(default=None)
|
||||
@@ -368,7 +369,7 @@ class PythonVersion(object):
|
||||
return self.architecture
|
||||
|
||||
@classmethod
|
||||
def from_path(cls, path, name=None):
|
||||
def from_path(cls, path, name=None, ignore_unsupported=True):
|
||||
"""Parses a python version from a system path.
|
||||
|
||||
Raises:
|
||||
@@ -377,23 +378,24 @@ class PythonVersion(object):
|
||||
:param path: A string or :class:`~pythonfinder.models.path.PathEntry`
|
||||
:type path: str or :class:`~pythonfinder.models.path.PathEntry` instance
|
||||
:param str name: Name of the python distribution in question
|
||||
:param bool ignore_unsupported: Whether to ignore or error on unsupported paths.
|
||||
:return: An instance of a PythonVersion.
|
||||
:rtype: :class:`~pythonfinder.models.python.PythonVersion`
|
||||
"""
|
||||
|
||||
from .path import PathEntry
|
||||
from ..environment import IGNORE_UNSUPPORTED
|
||||
|
||||
if not isinstance(path, PathEntry):
|
||||
path = PathEntry.create(path, is_root=False, only_python=True, name=name)
|
||||
if not path.is_python and not IGNORE_UNSUPPORTED:
|
||||
raise ValueError("Not a valid python path: %s" % path.path)
|
||||
return
|
||||
from ..environment import IGNORE_UNSUPPORTED
|
||||
ignore_unsupported = ignore_unsupported or IGNORE_UNSUPPORTED
|
||||
if not path.is_python:
|
||||
if not (ignore_unsupported or IGNORE_UNSUPPORTED):
|
||||
raise ValueError("Not a valid python path: %s" % path.path)
|
||||
py_version = get_python_version(path.path.absolute().as_posix())
|
||||
instance_dict = cls.parse(py_version.strip())
|
||||
if not isinstance(instance_dict.get("version"), Version) and not IGNORE_UNSUPPORTED:
|
||||
if not isinstance(instance_dict.get("version"), Version) and not ignore_unsupported:
|
||||
raise ValueError("Not a valid python path: %s" % path.path)
|
||||
return
|
||||
if not name:
|
||||
name = path.name
|
||||
instance_dict.update(
|
||||
|
||||
Vendored
+16
-6
@@ -31,7 +31,7 @@ if MYPY_RUNNING:
|
||||
from attr.validators import _OptionalValidator
|
||||
|
||||
|
||||
version_re = re.compile(r"(?P<major>\d+)\.(?P<minor>\d+)\.?(?P<patch>(?<=\.)[0-9]+)?\.?"
|
||||
version_re = re.compile(r"(?P<major>\d+)(?:\.(?P<minor>\d+))?(?:\.(?P<patch>(?<=\.)[0-9]+))?\.?"
|
||||
r"(?:(?P<prerel>[abc]|rc|dev)(?:(?P<prerelversion>\d+(?:\.\d+)*))?)"
|
||||
r"?(?P<postdev>(\.post(?P<post>\d+))?(\.dev(?P<dev>\d+))?)?")
|
||||
|
||||
@@ -81,10 +81,10 @@ def parse_python_version(version_str):
|
||||
if version_str.endswith("-debug"):
|
||||
is_debug = True
|
||||
version_str, _, _ = version_str.rpartition("-")
|
||||
m = version_re.match(version_str)
|
||||
if not m:
|
||||
match = version_re.match(version_str)
|
||||
if not match:
|
||||
raise InvalidPythonVersion("%s is not a python version" % version_str)
|
||||
version_dict = m.groupdict() # type: Dict[str, str]
|
||||
version_dict = match.groupdict() # type: Dict[str, str]
|
||||
major = int(version_dict.get("major", 0)) if version_dict.get("major") else None
|
||||
minor = int(version_dict.get("minor", 0)) if version_dict.get("minor") else None
|
||||
patch = int(version_dict.get("patch", 0)) if version_dict.get("patch") else None
|
||||
@@ -97,8 +97,18 @@ def parse_python_version(version_str):
|
||||
try:
|
||||
version = parse_version(version_str)
|
||||
except TypeError:
|
||||
version_parts = [str(v) for v in [major, minor, patch] if v is not None]
|
||||
version = parse_version(".".join(version_parts))
|
||||
version = None
|
||||
if isinstance(version, LegacyVersion) or version is None:
|
||||
v_dict = version_dict.copy()
|
||||
pre = ""
|
||||
if v_dict.get("prerel") and v_dict.get("prerelversion"):
|
||||
pre = v_dict.pop("prerel")
|
||||
pre = "{0}{1}".format(pre, v_dict.pop("prerelversion"))
|
||||
v_dict["pre"] = pre
|
||||
keys = ["major", "minor", "patch", "pre", "postdev", "post", "dev"]
|
||||
values = [v_dict.get(val) for val in keys]
|
||||
version_str = ".".join([str(v) for v in values if v])
|
||||
version = parse_version(version_str)
|
||||
return {
|
||||
"major": major,
|
||||
"minor": minor,
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
__version__ = '1.3.1'
|
||||
__version__ = '1.3.2'
|
||||
|
||||
import logging
|
||||
import warnings
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
from __future__ import print_function, absolute_import
|
||||
|
||||
import os
|
||||
from appdirs import user_cache_dir
|
||||
|
||||
|
||||
def is_type_checking():
|
||||
try:
|
||||
from typing import TYPE_CHECKING
|
||||
except ImportError:
|
||||
return False
|
||||
return TYPE_CHECKING
|
||||
|
||||
|
||||
REQUIREMENTSLIB_CACHE_DIR = os.getenv("REQUIREMENTSLIB_CACHE_DIR", user_cache_dir("pipenv"))
|
||||
MYPY_RUNNING = os.environ.get("MYPY_RUNNING", is_type_checking())
|
||||
+102
-22
@@ -1,22 +1,81 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import absolute_import, unicode_literals, print_function
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import attr
|
||||
import copy
|
||||
import os
|
||||
import sys
|
||||
|
||||
import attr
|
||||
import tomlkit
|
||||
|
||||
from vistir.compat import Path, FileNotFoundError
|
||||
|
||||
from .requirements import Requirement
|
||||
from .project import ProjectFile
|
||||
from .utils import optional_instance_of
|
||||
from ..exceptions import RequirementError
|
||||
from ..utils import is_vcs, is_editable, merge_items
|
||||
import plette.models.base
|
||||
import plette.pipfiles
|
||||
|
||||
from vistir.compat import FileNotFoundError, Path
|
||||
|
||||
from ..exceptions import RequirementError
|
||||
from ..utils import is_editable, is_vcs, merge_items
|
||||
from .project import ProjectFile
|
||||
from .requirements import Requirement
|
||||
from .utils import optional_instance_of
|
||||
|
||||
from ..environment import MYPY_RUNNING
|
||||
if MYPY_RUNNING:
|
||||
from typing import Union, Any, Dict, Iterable, Sequence, Mapping, List, NoReturn
|
||||
package_type = Dict[str, Dict[str, Union[List[str], str]]]
|
||||
source_type = Dict[str, Union[str, bool]]
|
||||
sources_type = Iterable[source_type]
|
||||
meta_type = Dict[str, Union[int, Dict[str, str], sources_type]]
|
||||
lockfile_type = Dict[str, Union[package_type, meta_type]]
|
||||
|
||||
|
||||
# Let's start by patching plette to make sure we can validate data without being broken
|
||||
try:
|
||||
import cerberus
|
||||
except ImportError:
|
||||
cerberus = None
|
||||
|
||||
VALIDATORS = plette.models.base.VALIDATORS
|
||||
|
||||
|
||||
def patch_plette():
|
||||
# type: () -> None
|
||||
|
||||
global VALIDATORS
|
||||
|
||||
def validate(cls, data):
|
||||
# type: (Any, Dict[str, Any]) -> None
|
||||
if not cerberus: # Skip validation if Cerberus is not available.
|
||||
return
|
||||
schema = cls.__SCHEMA__
|
||||
key = id(schema)
|
||||
try:
|
||||
v = VALIDATORS[key]
|
||||
except KeyError:
|
||||
v = VALIDATORS[key] = cerberus.Validator(schema, allow_unknown=True)
|
||||
if v.validate(dict(data), normalize=False):
|
||||
return
|
||||
raise plette.models.base.ValidationError(data, v)
|
||||
|
||||
names = ["plette.models.base", plette.models.base.__name__]
|
||||
names = [name for name in names if name in sys.modules]
|
||||
for name in names:
|
||||
if name in sys.modules:
|
||||
module = sys.modules[name]
|
||||
else:
|
||||
module = plette.models.base
|
||||
original_fn = getattr(module, "validate")
|
||||
for key in ["__qualname__", "__name__", "__module__"]:
|
||||
original_val = getattr(original_fn, key, None)
|
||||
if original_val is not None:
|
||||
setattr(validate, key, original_val)
|
||||
setattr(module, "validate", validate)
|
||||
sys.modules[name] = module
|
||||
|
||||
|
||||
patch_plette()
|
||||
|
||||
|
||||
is_pipfile = optional_instance_of(plette.pipfiles.Pipfile)
|
||||
is_path = optional_instance_of(Path)
|
||||
@@ -24,8 +83,10 @@ is_projectfile = optional_instance_of(ProjectFile)
|
||||
|
||||
|
||||
def reorder_source_keys(data):
|
||||
for i, entry in enumerate(data["source"]):
|
||||
table = tomlkit.table()
|
||||
# type: ignore
|
||||
sources = data["source"] # type: sources_type
|
||||
for i, entry in enumerate(sources):
|
||||
table = tomlkit.table() # type: Mapping
|
||||
table["name"] = entry["name"]
|
||||
table["url"] = entry["url"]
|
||||
table["verify_ssl"] = entry["verify_ssl"]
|
||||
@@ -36,6 +97,7 @@ def reorder_source_keys(data):
|
||||
class PipfileLoader(plette.pipfiles.Pipfile):
|
||||
@classmethod
|
||||
def validate(cls, data):
|
||||
# type: (Dict[str, Any]) -> None
|
||||
for key, klass in plette.pipfiles.PIPFILE_SECTIONS.items():
|
||||
if key not in data or key == "source":
|
||||
continue
|
||||
@@ -46,6 +108,7 @@ class PipfileLoader(plette.pipfiles.Pipfile):
|
||||
|
||||
@classmethod
|
||||
def load(cls, f, encoding=None):
|
||||
# type: (Any, str) -> PipfileLoader
|
||||
content = f.read()
|
||||
if encoding is not None:
|
||||
content = content.decode(encoding)
|
||||
@@ -69,6 +132,7 @@ class PipfileLoader(plette.pipfiles.Pipfile):
|
||||
return instance
|
||||
|
||||
def __getattribute__(self, key):
|
||||
# type: (str) -> Any
|
||||
if key == "source":
|
||||
return self._data[key]
|
||||
return super(PipfileLoader, self).__getattribute__(key)
|
||||
@@ -78,7 +142,7 @@ class PipfileLoader(plette.pipfiles.Pipfile):
|
||||
class Pipfile(object):
|
||||
path = attr.ib(validator=is_path, type=Path)
|
||||
projectfile = attr.ib(validator=is_projectfile, type=ProjectFile)
|
||||
_pipfile = attr.ib(type=plette.pipfiles.Pipfile)
|
||||
_pipfile = attr.ib(type=PipfileLoader)
|
||||
_pyproject = attr.ib(default=attr.Factory(tomlkit.document), type=tomlkit.toml_document.TOMLDocument)
|
||||
build_system = attr.ib(default=attr.Factory(dict), type=dict)
|
||||
requirements = attr.ib(default=attr.Factory(list), type=list)
|
||||
@@ -86,22 +150,27 @@ class Pipfile(object):
|
||||
|
||||
@path.default
|
||||
def _get_path(self):
|
||||
# type: () -> Path
|
||||
return Path(os.curdir).absolute()
|
||||
|
||||
@projectfile.default
|
||||
def _get_projectfile(self):
|
||||
# type: () -> ProjectFile
|
||||
return self.load_projectfile(os.curdir, create=False)
|
||||
|
||||
@_pipfile.default
|
||||
def _get_pipfile(self):
|
||||
# type: () -> Union[plette.pipfiles.Pipfile, PipfileLoader]
|
||||
return self.projectfile.model
|
||||
|
||||
@property
|
||||
def pipfile(self):
|
||||
# type: () -> Union[PipfileLoader, plette.pipfiles.Pipfile]
|
||||
return self._pipfile
|
||||
|
||||
def get_deps(self, dev=False, only=True):
|
||||
deps = {}
|
||||
# type: (bool, bool) -> Dict[str, Dict[str, Union[List[str], str]]]
|
||||
deps = {} # type: Dict[str, Dict[str, Union[List[str], str]]]
|
||||
if dev:
|
||||
deps.update(self.pipfile._data["dev-packages"])
|
||||
if only:
|
||||
@@ -109,15 +178,18 @@ class Pipfile(object):
|
||||
return merge_items([deps, self.pipfile._data["packages"]])
|
||||
|
||||
def get(self, k):
|
||||
# type: (str) -> Any
|
||||
return self.__getitem__(k)
|
||||
|
||||
def __contains__(self, k):
|
||||
# type: (str) -> bool
|
||||
check_pipfile = k in self.extended_keys or self.pipfile.__contains__(k)
|
||||
if check_pipfile:
|
||||
return True
|
||||
return super(Pipfile, self).__contains__(k)
|
||||
return False
|
||||
|
||||
def __getitem__(self, k, *args, **kwargs):
|
||||
# type: ignore
|
||||
retval = None
|
||||
pipfile = self._pipfile
|
||||
section = None
|
||||
@@ -139,6 +211,7 @@ class Pipfile(object):
|
||||
return retval
|
||||
|
||||
def __getattr__(self, k, *args, **kwargs):
|
||||
# type: ignore
|
||||
retval = None
|
||||
pipfile = super(Pipfile, self).__getattribute__("_pipfile")
|
||||
try:
|
||||
@@ -151,14 +224,17 @@ class Pipfile(object):
|
||||
|
||||
@property
|
||||
def requires_python(self):
|
||||
# type: () -> bool
|
||||
return self._pipfile.requires.requires_python
|
||||
|
||||
@property
|
||||
def allow_prereleases(self):
|
||||
# type: () -> bool
|
||||
return self._pipfile.get("pipenv", {}).get("allow_prereleases", False)
|
||||
|
||||
@classmethod
|
||||
def read_projectfile(cls, path):
|
||||
# type: (str) -> ProjectFile
|
||||
"""Read the specified project file and provide an interface for writing/updating.
|
||||
|
||||
:param str path: Path to the target file.
|
||||
@@ -174,6 +250,7 @@ class Pipfile(object):
|
||||
|
||||
@classmethod
|
||||
def load_projectfile(cls, path, create=False):
|
||||
# type: (str, bool) -> ProjectFile
|
||||
"""Given a path, load or create the necessary pipfile.
|
||||
|
||||
:param str path: Path to the project root or pipfile
|
||||
@@ -198,6 +275,7 @@ class Pipfile(object):
|
||||
|
||||
@classmethod
|
||||
def load(cls, path, create=False):
|
||||
# type: (str, bool) -> Pipfile
|
||||
"""Given a path, load or create the necessary pipfile.
|
||||
|
||||
:param str path: Path to the project root or pipfile
|
||||
@@ -226,22 +304,22 @@ class Pipfile(object):
|
||||
return cls(**creation_args)
|
||||
|
||||
def write(self):
|
||||
# type: () -> None
|
||||
self.projectfile.model = copy.deepcopy(self._pipfile)
|
||||
self.projectfile.write()
|
||||
|
||||
@property
|
||||
def dev_packages(self, as_requirements=True):
|
||||
if as_requirements:
|
||||
return self.dev_requirements
|
||||
return self._pipfile.get('dev-packages', {})
|
||||
def dev_packages(self):
|
||||
# type: () -> List[Requirement]
|
||||
return self.dev_requirements
|
||||
|
||||
@property
|
||||
def packages(self, as_requirements=True):
|
||||
if as_requirements:
|
||||
return self.requirements
|
||||
return self._pipfile.get('packages', {})
|
||||
def packages(self):
|
||||
# type: () -> List[Requirement]
|
||||
return self.requirements
|
||||
|
||||
def _read_pyproject(self):
|
||||
# type: () -> None
|
||||
pyproject = self.path.parent.joinpath("pyproject.toml")
|
||||
if pyproject.exists():
|
||||
self._pyproject = tomlkit.load(pyproject)
|
||||
@@ -256,8 +334,10 @@ class Pipfile(object):
|
||||
|
||||
@property
|
||||
def build_requires(self):
|
||||
# type: () -> List[str]
|
||||
return self.build_system.get("requires", [])
|
||||
|
||||
@property
|
||||
def build_backend(self):
|
||||
# type: () -> str
|
||||
return self.build_system.get("build-backend", None)
|
||||
|
||||
+26
-6
@@ -3,6 +3,7 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import collections
|
||||
import copy
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
@@ -289,7 +290,7 @@ class FileRequirement(object):
|
||||
if self.path and not self.uri:
|
||||
self._uri_scheme = "path"
|
||||
return pip_shims.shims.path_to_url(os.path.abspath(self.path))
|
||||
elif self.req and getattr(self.req, "url"):
|
||||
elif getattr(self, "req", None) and getattr(self.req, "url"):
|
||||
return self.req.url
|
||||
|
||||
@name.default
|
||||
@@ -312,9 +313,19 @@ class FileRequirement(object):
|
||||
)):
|
||||
if self.editable:
|
||||
line = pip_shims.shims.path_to_url(self.setup_py_dir)
|
||||
if self.extras:
|
||||
line = "{0}[{1}]".format(line, ",".join(self.extras))
|
||||
_ireq = pip_shims.shims.install_req_from_editable(line)
|
||||
else:
|
||||
_ireq = pip_shims.shims.install_req_from_line(Path(self.setup_py_dir).as_posix())
|
||||
line = Path(self.setup_py_dir).as_posix()
|
||||
if self.extras:
|
||||
line = "{0}[{1}]".format(line, ",".join(self.extras))
|
||||
_ireq = pip_shims.shims.install_req_from_line(line)
|
||||
if getattr(self, "req", None):
|
||||
_ireq.req = copy.deepcopy(self.req)
|
||||
else:
|
||||
if self.extras:
|
||||
_ireq.extras = set(self.extras)
|
||||
from .setup_info import SetupInfo
|
||||
subdir = getattr(self, "subdirectory", None)
|
||||
setupinfo = SetupInfo.from_ireq(_ireq, subdir=subdir)
|
||||
@@ -453,10 +464,16 @@ class FileRequirement(object):
|
||||
if not name:
|
||||
_line = unquote(link.url_without_fragment) if link.url else uri
|
||||
if editable:
|
||||
if extras:
|
||||
_line = "{0}[{1}]".format(_line, ",".join(sorted(set(extras))))
|
||||
ireq = pip_shims.shims.install_req_from_editable(_line)
|
||||
else:
|
||||
_line = path if (uri_scheme and uri_scheme == "path") else _line
|
||||
if extras:
|
||||
_line = "{0}[{1}]".format(_line, ",".join(sorted(set(extras))))
|
||||
ireq = pip_shims.shims.install_req_from_line(_line)
|
||||
if extras and not ireq.extras:
|
||||
ireq.extras = set(extras)
|
||||
setup_info = SetupInfo.from_ireq(ireq)
|
||||
setupinfo_dict = setup_info.as_dict()
|
||||
setup_name = setupinfo_dict.get("name", None)
|
||||
@@ -488,7 +505,7 @@ class FileRequirement(object):
|
||||
return cls_inst
|
||||
|
||||
@classmethod
|
||||
def from_line(cls, line):
|
||||
def from_line(cls, line, extras=None):
|
||||
line = line.strip('"').strip("'")
|
||||
link = None
|
||||
path = None
|
||||
@@ -497,6 +514,8 @@ class FileRequirement(object):
|
||||
setup_path = None
|
||||
name = None
|
||||
req = None
|
||||
if not extras:
|
||||
extras = []
|
||||
if not any([is_installable_file(line), is_valid_url(line), is_file_url(line)]):
|
||||
try:
|
||||
req = init_requirement(line)
|
||||
@@ -515,7 +534,8 @@ class FileRequirement(object):
|
||||
"editable": editable,
|
||||
"setup_path": setup_path,
|
||||
"uri_scheme": prefer,
|
||||
"line": line
|
||||
"line": line,
|
||||
"extras": extras
|
||||
}
|
||||
if link and link.is_wheel:
|
||||
from pip_shims import Wheel
|
||||
@@ -690,7 +710,7 @@ class VCSRequirement(FileRequirement):
|
||||
def get_name(self):
|
||||
return (
|
||||
self.link.egg_fragment or self.req.name
|
||||
if self.req
|
||||
if getattr(self, "req", None)
|
||||
else super(VCSRequirement, self).get_name()
|
||||
)
|
||||
|
||||
@@ -1069,7 +1089,7 @@ class Requirement(object):
|
||||
(is_valid_url(possible_url) or is_file_url(line) or is_valid_url(line)) and
|
||||
not (line_is_vcs or is_vcs(possible_url))
|
||||
):
|
||||
r = FileRequirement.from_line(line_with_prefix)
|
||||
r = FileRequirement.from_line(line_with_prefix, extras=extras)
|
||||
elif line_is_vcs:
|
||||
r = VCSRequirement.from_line(line_with_prefix, extras=extras)
|
||||
vcs = r.vcs
|
||||
|
||||
+33
-14
@@ -7,6 +7,7 @@ import attr
|
||||
import packaging.version
|
||||
import packaging.specifiers
|
||||
import packaging.utils
|
||||
import six
|
||||
|
||||
try:
|
||||
from setuptools.dist import distutils
|
||||
@@ -16,7 +17,7 @@ except ImportError:
|
||||
from appdirs import user_cache_dir
|
||||
from six.moves import configparser
|
||||
from six.moves.urllib.parse import unquote
|
||||
from vistir.compat import Path
|
||||
from vistir.compat import Path, Iterable
|
||||
from vistir.contextmanagers import cd
|
||||
from vistir.misc import run
|
||||
from vistir.path import create_tracked_tempdir, ensure_mkdir_p, mkdir_p
|
||||
@@ -67,6 +68,20 @@ def _get_src_dir():
|
||||
return os.path.join(os.getcwd(), "src") # Match pip's behavior.
|
||||
|
||||
|
||||
def ensure_reqs(reqs):
|
||||
import pkg_resources
|
||||
if not isinstance(reqs, Iterable):
|
||||
raise TypeError("Expecting an Iterable, got %r" % reqs)
|
||||
new_reqs = []
|
||||
for req in reqs:
|
||||
if not req:
|
||||
continue
|
||||
if isinstance(req, six.string_types):
|
||||
req = pkg_resources.Requirement.parse("{0}".format(str(req)))
|
||||
new_reqs.append(req)
|
||||
return new_reqs
|
||||
|
||||
|
||||
def _prepare_wheel_building_kwargs(ireq):
|
||||
download_dir = os.path.join(CACHE_DIR, "pkgs")
|
||||
mkdir_p(download_dir)
|
||||
@@ -153,10 +168,8 @@ def get_metadata(path, pkg_name=None):
|
||||
else:
|
||||
marker = ""
|
||||
extra = "{0}".format(k)
|
||||
_deps = [
|
||||
pkg_resources.Requirement.parse("{0}{1}".format(str(req), marker))
|
||||
for req in _deps
|
||||
]
|
||||
_deps = ["{0}{1}".format(str(req), marker) for req in _deps]
|
||||
_deps = ensure_reqs(_deps)
|
||||
if extra:
|
||||
extras[extra] = _deps
|
||||
else:
|
||||
@@ -220,19 +233,25 @@ class SetupInfo(object):
|
||||
python_requires = parser.get("options", "python_requires")
|
||||
if python_requires and not self.python_requires:
|
||||
self.python_requires = python_requires
|
||||
if parser.has_option("options", "extras_require"):
|
||||
if "options.extras_require" in parser.sections():
|
||||
self.extras.update(
|
||||
{
|
||||
section: [
|
||||
dep.strip()
|
||||
init_requirement(dep.strip())
|
||||
for dep in parser.get(
|
||||
"options.extras_require", section
|
||||
).split("\n")
|
||||
if dep
|
||||
]
|
||||
for section in parser.options("options.extras_require")
|
||||
if section not in ["options", "metadata"]
|
||||
}
|
||||
)
|
||||
if self.ireq.extras:
|
||||
self.requires.update({
|
||||
extra: self.extras[extra]
|
||||
for extra in self.ireq.extras if extra in self.extras
|
||||
})
|
||||
|
||||
def run_setup(self):
|
||||
if self.setup_py is not None and self.setup_py.exists():
|
||||
@@ -304,13 +323,13 @@ class SetupInfo(object):
|
||||
)
|
||||
if getattr(self.ireq, "extras", None):
|
||||
for extra in self.ireq.extras:
|
||||
self.requires.update(
|
||||
{
|
||||
req.key: req for req
|
||||
in metadata.get("extras", {}).get(extra)
|
||||
if req is not None
|
||||
}
|
||||
)
|
||||
extras = metadata.get("extras", {}).get(extra, [])
|
||||
if extras:
|
||||
extras = ensure_reqs(extras)
|
||||
self.extras[extra] = set(extras)
|
||||
self.requires.update(
|
||||
{req.key: req for req in extras if req is not None}
|
||||
)
|
||||
|
||||
def run_pyproject(self):
|
||||
if self.pyproject and self.pyproject.exists():
|
||||
|
||||
Vendored
+4
-4
@@ -21,18 +21,18 @@ pipdeptree==0.13.0
|
||||
pipreqs==0.4.9
|
||||
docopt==0.6.2
|
||||
yarg==0.1.9
|
||||
pythonfinder==1.1.9.post1
|
||||
pythonfinder==1.1.10
|
||||
requests==2.20.1
|
||||
chardet==3.0.4
|
||||
idna==2.7
|
||||
urllib3==1.24
|
||||
certifi==2018.10.15
|
||||
requirementslib==1.3.1.post1
|
||||
requirementslib==1.3.3
|
||||
attrs==18.2.0
|
||||
distlib==0.2.8
|
||||
packaging==18.0
|
||||
pyparsing==2.2.2
|
||||
plette==0.2.2
|
||||
git+https://github.com/sarugaku/plette.git@master#egg=plette
|
||||
tomlkit==0.5.2
|
||||
shellingham==1.2.7
|
||||
six==1.11.0
|
||||
@@ -40,7 +40,7 @@ semver==2.8.1
|
||||
shutilwhich==1.1.0
|
||||
toml==0.10.0
|
||||
cached-property==1.4.3
|
||||
vistir==0.2.4
|
||||
vistir==0.2.5
|
||||
pip-shims==0.3.2
|
||||
ptyprocess==0.6.0
|
||||
enum34==1.1.6
|
||||
|
||||
Vendored
+1
-1
@@ -31,7 +31,7 @@ from .path import mkdir_p, rmtree, create_tracked_tempdir, create_tracked_tempfi
|
||||
from .spin import VistirSpinner, create_spinner
|
||||
|
||||
|
||||
__version__ = '0.2.4'
|
||||
__version__ = '0.2.5'
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
||||
Vendored
+3
@@ -83,6 +83,9 @@ if six.PY2:
|
||||
else:
|
||||
from builtins import ResourceWarning, FileNotFoundError, PermissionError, IsADirectoryError
|
||||
|
||||
six.add_move(six.MovedAttribute("Iterable", "collections", "collections.abc"))
|
||||
from six.moves import Iterable
|
||||
|
||||
|
||||
if not sys.warnoptions:
|
||||
warnings.simplefilter("default", ResourceWarning)
|
||||
|
||||
Vendored
+12
-10
@@ -10,12 +10,12 @@ import sys
|
||||
|
||||
from collections import OrderedDict
|
||||
from functools import partial
|
||||
from itertools import islice
|
||||
from itertools import islice, tee
|
||||
|
||||
import six
|
||||
|
||||
from .cmdparse import Script
|
||||
from .compat import Path, fs_str, partialmethod, to_native_string
|
||||
from .compat import Path, fs_str, partialmethod, to_native_string, Iterable
|
||||
from .contextmanagers import spinner as spinner
|
||||
|
||||
if os.name != "nt":
|
||||
@@ -78,15 +78,17 @@ def unnest(elem):
|
||||
[1234, 3456, 4398345, 234234, 2396, 23895750, 9283798, 29384, 289375983275, 293759, 2347, 2098, 7987, 27599]
|
||||
"""
|
||||
|
||||
if _is_iterable(elem):
|
||||
for item in elem:
|
||||
if _is_iterable(item):
|
||||
for sub_item in unnest(item):
|
||||
yield sub_item
|
||||
else:
|
||||
yield item
|
||||
if isinstance(elem, Iterable) and not isinstance(elem, six.string_types):
|
||||
elem, target = tee(elem, 2)
|
||||
else:
|
||||
raise ValueError("Expecting an iterable, got %r" % elem)
|
||||
target = elem
|
||||
for el in target:
|
||||
if isinstance(el, Iterable) and not isinstance(el, six.string_types):
|
||||
el, el_copy = tee(el, 2)
|
||||
for sub in unnest(el_copy):
|
||||
yield sub
|
||||
else:
|
||||
yield el
|
||||
|
||||
|
||||
def _is_iterable(elem):
|
||||
|
||||
Vendored
+29
@@ -30,6 +30,8 @@ __all__ = [
|
||||
"check_for_unc_path",
|
||||
"get_converted_relative_path",
|
||||
"handle_remove_readonly",
|
||||
"normalize_path",
|
||||
"is_in_path",
|
||||
"is_file_url",
|
||||
"is_readonly_path",
|
||||
"is_valid_url",
|
||||
@@ -80,6 +82,33 @@ else:
|
||||
return os.path.normpath(path)
|
||||
|
||||
|
||||
def normalize_path(path):
|
||||
"""
|
||||
Return a case-normalized absolute variable-expanded path.
|
||||
|
||||
:param str path: The non-normalized path
|
||||
:return: A normalized, expanded, case-normalized path
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
return os.path.normpath(os.path.normcase(
|
||||
os.path.abspath(os.path.expandvars(os.path.expanduser(str(path))))
|
||||
))
|
||||
|
||||
|
||||
def is_in_path(path, parent):
|
||||
"""
|
||||
Determine if the provided full path is in the given parent root.
|
||||
|
||||
:param str path: The full path to check the location of.
|
||||
:param str parent: The parent path to check for membership in
|
||||
:return: Whether the full path is a member of the provided parent.
|
||||
:rtype: bool
|
||||
"""
|
||||
|
||||
return normalize_path(str(path)).startswith(normalize_path(str(parent)))
|
||||
|
||||
|
||||
def normalize_drive(path):
|
||||
"""Normalize drive in path so they stay consistent.
|
||||
|
||||
|
||||
Vendored
+4
-2
@@ -12,7 +12,7 @@ import cursor
|
||||
import six
|
||||
|
||||
from .compat import to_native_string
|
||||
from .termcolors import COLOR_MAP, COLORS, colored
|
||||
from .termcolors import COLOR_MAP, COLORS, colored, DISABLE_COLORS
|
||||
from io import StringIO
|
||||
|
||||
try:
|
||||
@@ -34,7 +34,9 @@ CLEAR_LINE = chr(27) + "[K"
|
||||
|
||||
class DummySpinner(object):
|
||||
def __init__(self, text="", **kwargs):
|
||||
colorama.init()
|
||||
super(DummySpinner, self).__init__()
|
||||
if DISABLE_COLORS:
|
||||
colorama.init()
|
||||
from .misc import decode_for_output
|
||||
self.text = to_native_string(decode_for_output(text)) if text else ""
|
||||
self.stdout = kwargs.get("stdout", sys.stdout)
|
||||
|
||||
Vendored
+5
@@ -5,6 +5,11 @@ import os
|
||||
from .compat import to_native_string
|
||||
|
||||
|
||||
DISABLE_COLORS = os.getenv("CI", False) or os.getenv("ANSI_COLORS_DISABLED",
|
||||
os.getenv("VISTIR_DISABLE_COLORS", False)
|
||||
)
|
||||
|
||||
|
||||
ATTRIBUTES = dict(
|
||||
list(zip([
|
||||
'bold',
|
||||
|
||||
+1
-9
@@ -11,12 +11,4 @@ from pathlib import Path
|
||||
ROOT = Path(".").parent.parent.absolute()
|
||||
|
||||
|
||||
@invoke.task
|
||||
def clean_mdchangelog(ctx):
|
||||
changelog = ROOT / "CHANGELOG.md"
|
||||
content = changelog.read_text()
|
||||
content = re.sub(r"([^\n]+)\n?\s+\[[\\]+(#\d+)\]\(https://github\.com/pypa/[\w\-]+/issues/\d+\)", r"\1 \2", content, flags=re.MULTILINE)
|
||||
changelog.write_text(content)
|
||||
|
||||
|
||||
ns = invoke.Collection(vendoring, release, clean_mdchangelog, vendor_passa.vendor_passa)
|
||||
ns = invoke.Collection(vendoring, release, release.clean_mdchangelog, vendor_passa.vendor_passa)
|
||||
|
||||
+145
-44
@@ -1,13 +1,27 @@
|
||||
# -*- coding=utf-8 -*-
|
||||
import datetime
|
||||
import invoke
|
||||
import pathlib
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pipenv.__version__ import __version__
|
||||
|
||||
import invoke
|
||||
|
||||
from parver import Version
|
||||
from towncrier._builder import (
|
||||
find_fragments, render_fragments, split_fragments
|
||||
)
|
||||
from towncrier._settings import load_config
|
||||
|
||||
from pipenv.__version__ import __version__
|
||||
from pipenv.vendor.vistir.contextmanagers import temp_environ
|
||||
|
||||
from .vendoring import _get_git_root, drop_dir
|
||||
|
||||
|
||||
VERSION_FILE = 'pipenv/__version__.py'
|
||||
ROOT = pathlib.Path(".").parent.parent.absolute()
|
||||
PACKAGE_NAME = "pipenv"
|
||||
|
||||
|
||||
def log(msg):
|
||||
@@ -18,6 +32,14 @@ def get_version_file(ctx):
|
||||
return _get_git_root(ctx).joinpath(VERSION_FILE)
|
||||
|
||||
|
||||
def find_version(ctx):
|
||||
version_file = get_version_file(ctx).read_text()
|
||||
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",version_file, re.M)
|
||||
if version_match:
|
||||
return version_match.group(1)
|
||||
raise RuntimeError("Unable to find version string.")
|
||||
|
||||
|
||||
def get_history_file(ctx):
|
||||
return _get_git_root(ctx).joinpath('HISTORY.txt')
|
||||
|
||||
@@ -30,6 +52,66 @@ def get_build_dir(ctx):
|
||||
return _get_git_root(ctx) / 'build'
|
||||
|
||||
|
||||
def _render_log():
|
||||
"""Totally tap into Towncrier internals to get an in-memory result.
|
||||
"""
|
||||
config = load_config(ROOT)
|
||||
definitions = config['types']
|
||||
fragments, fragment_filenames = find_fragments(
|
||||
pathlib.Path(config['directory']).absolute(),
|
||||
config['sections'],
|
||||
None,
|
||||
definitions,
|
||||
)
|
||||
rendered = render_fragments(
|
||||
pathlib.Path(config['template']).read_text(encoding='utf-8'),
|
||||
config['issue_format'],
|
||||
split_fragments(fragments, definitions),
|
||||
definitions,
|
||||
config['underlines'][1:],
|
||||
False, # Don't add newlines to wrapped text.
|
||||
)
|
||||
return rendered
|
||||
|
||||
|
||||
@invoke.task
|
||||
def release(ctx, dry_run=False):
|
||||
drop_dist_dirs(ctx)
|
||||
bump_version(ctx, dry_run=dry_run)
|
||||
version = find_version(ctx)
|
||||
tag_content = _render_log()
|
||||
if dry_run:
|
||||
ctx.run('towncrier --draft > CHANGELOG.draft.rst')
|
||||
log('would remove: news/*')
|
||||
log('would remove: CHANGELOG.draft.rst')
|
||||
log(f'Would commit with message: "Release v{version}"')
|
||||
else:
|
||||
ctx.run('towncrier')
|
||||
ctx.run("git add CHANGELOG.rst news/")
|
||||
ctx.run("git rm CHANGELOG.draft.rst")
|
||||
ctx.run(f'git commit -m "Release v{version}"')
|
||||
|
||||
tag_content = tag_content.replace('"', '\\"')
|
||||
if dry_run:
|
||||
log(f"Generated tag content: {tag_content}")
|
||||
markdown = ctx.run("pandoc CHANGELOG.draft.rst -f rst -t markdown", hide=True).stdout.strip()
|
||||
content = clean_mdchangelog(ctx, markdown)
|
||||
log(f"would generate markdown: {content}")
|
||||
else:
|
||||
generate_markdown(ctx)
|
||||
clean_mdchangelog(ctx)
|
||||
ctx.run(f'git tag -a v{version} -m "Version v{version}\n\n{tag_content}"')
|
||||
build_dists(ctx)
|
||||
if dry_run:
|
||||
dist_pattern = f'{PACKAGE_NAME.replace("-", "[-_]")}-*'
|
||||
artifacts = list(ROOT.joinpath('dist').glob(dist_pattern))
|
||||
filename_display = '\n'.join(f' {a}' for a in artifacts)
|
||||
log(f"Would upload dists: {filename_display}")
|
||||
else:
|
||||
upload_dists(ctx)
|
||||
bump_version(ctx, dev=True)
|
||||
|
||||
|
||||
def drop_dist_dirs(ctx):
|
||||
log('Dropping Dist dir...')
|
||||
drop_dir(get_dist_dir(ctx))
|
||||
@@ -40,20 +122,36 @@ def drop_dist_dirs(ctx):
|
||||
@invoke.task
|
||||
def build_dists(ctx):
|
||||
drop_dist_dirs(ctx)
|
||||
log('Building sdist using %s ....' % sys.executable)
|
||||
for py_version in ['2.7', '3.6', '3.7']:
|
||||
for py_version in ['3.6', '2.7']:
|
||||
env = {'PIPENV_PYTHON': py_version}
|
||||
ctx.run('pipenv install --dev', env=env)
|
||||
if py_version == '3.6':
|
||||
ctx.run('pipenv run python setup.py sdist', env=env)
|
||||
log('Building wheel using python %s ....' % py_version)
|
||||
ctx.run('pipenv run python setup.py bdist_wheel', env=env)
|
||||
with ctx.cd(ROOT.as_posix()), temp_environ():
|
||||
executable = ctx.run("python -c 'import sys; print(sys.executable)'", hide=True).stdout.strip()
|
||||
log('Building sdist using %s ....' % executable)
|
||||
os.environ["PIPENV_PYTHON"] = py_version
|
||||
ctx.run('pipenv install --dev', env=env)
|
||||
ctx.run('pipenv run pip install -e . --upgrade --upgrade-strategy=eager', env=env)
|
||||
log('Building wheel using python %s ....' % py_version)
|
||||
if py_version == '3.6':
|
||||
ctx.run('pipenv run python setup.py sdist bdist_wheel', env=env)
|
||||
else:
|
||||
ctx.run('pipenv run python setup.py bdist_wheel', env=env)
|
||||
|
||||
|
||||
@invoke.task(build_dists)
|
||||
def upload_dists(ctx):
|
||||
log('Uploading distributions to pypi...')
|
||||
ctx.run('twine upload dist/*')
|
||||
def upload_dists(ctx, repo="pypi"):
|
||||
dist_pattern = f'{PACKAGE_NAME.replace("-", "[-_]")}-*'
|
||||
artifacts = list(ROOT.joinpath('dist').glob(dist_pattern))
|
||||
filename_display = '\n'.join(f' {a}' for a in artifacts)
|
||||
print(f'[release] Will upload:\n{filename_display}')
|
||||
try:
|
||||
input('[release] Release ready. ENTER to upload, CTRL-C to abort: ')
|
||||
except KeyboardInterrupt:
|
||||
print('\nAborted!')
|
||||
return
|
||||
|
||||
arg_display = ' '.join(f'"{n}"' for n in artifacts)
|
||||
ctx.run(f'twine upload --repository="{repo}" {arg_display}')
|
||||
|
||||
|
||||
|
||||
@invoke.task
|
||||
@@ -69,68 +167,71 @@ def generate_changelog(ctx, commit=False, draft=False):
|
||||
commit = False
|
||||
log('Writing draft to file...')
|
||||
ctx.run('towncrier --draft > CHANGELOG.draft.rst')
|
||||
if commit:
|
||||
else:
|
||||
ctx.run('towncrier')
|
||||
if commit:
|
||||
log('Committing...')
|
||||
ctx.run('git add CHANGELOG.rst')
|
||||
ctx.run('git rm CHANGELOG.draft.rst')
|
||||
ctx.run('git commit -m "Update changelog."')
|
||||
|
||||
|
||||
@invoke.task
|
||||
def clean_mdchangelog(ctx, content=None):
|
||||
changelog = None
|
||||
if not content:
|
||||
changelog = _get_git_root(ctx) / "CHANGELOG.md"
|
||||
content = changelog.read_text()
|
||||
content = re.sub(r"([^\n]+)\n?\s+\[[\\]+(#\d+)\]\(https://github\.com/pypa/[\w\-]+/issues/\d+\)", r"\1 \2", content, flags=re.MULTILINE)
|
||||
if changelog:
|
||||
changelog.write_text(content)
|
||||
else:
|
||||
return content
|
||||
|
||||
|
||||
@invoke.task
|
||||
def tag_version(ctx, push=False):
|
||||
version = Version.parse(__version__)
|
||||
log('Tagging revision: v%s' % version)
|
||||
ctx.run('git tag v%s' % version)
|
||||
version = find_version(ctx)
|
||||
version = Version.parse(version)
|
||||
log('Tagging revision: v%s' % version.normalize())
|
||||
ctx.run('git tag v%s' % version.normalize())
|
||||
if push:
|
||||
log('Pushing tags...')
|
||||
ctx.run('git push origin master')
|
||||
ctx.run('git push --tags')
|
||||
|
||||
|
||||
@invoke.task
|
||||
def bump_version(ctx, dry_run=False, increment=True, release=False, dev=False, pre=False, tag=None, clear=False, commit=False,):
|
||||
def bump_version(ctx, dry_run=False, dev=False, pre=False, tag=None, commit=False):
|
||||
current_version = Version.parse(__version__)
|
||||
today = datetime.date.today()
|
||||
next_month_number = today.month + 1 if today.month != 12 else 1
|
||||
next_year_number = today.year if next_month_number != 1 else today.year+1
|
||||
next_month = (next_year_number, next_month_number, 0)
|
||||
tomorrow = today + datetime.timedelta(days=1)
|
||||
next_month = datetime.date.today().replace(month=today.month+1, day=1)
|
||||
next_year = datetime.date.today().replace(year=today.year+1, month=1, day=1)
|
||||
if pre and not tag:
|
||||
print('Using "pre" requires a corresponding tag.')
|
||||
return
|
||||
if release and not dev and not pre and increment:
|
||||
if not (dev or pre or tag):
|
||||
new_version = current_version.replace(release=today.timetuple()[:3]).clear(pre=True, dev=True)
|
||||
elif release and (dev or pre):
|
||||
if increment:
|
||||
new_version = current_version.replace(release=today.timetuple()[:3])
|
||||
else:
|
||||
new_version = current_version
|
||||
if pre and dev:
|
||||
raise RuntimeError("Can't use 'pre' and 'dev' together!")
|
||||
if dev or pre:
|
||||
new_version = current_version.replace(release=tomorrow.timetuple()[:3]).clear(pre=True, dev=True)
|
||||
if dev:
|
||||
new_version = new_version.bump_dev()
|
||||
elif pre:
|
||||
new_version = new_version.bump_pre(tag=tag)
|
||||
else:
|
||||
if not release:
|
||||
increment = False
|
||||
if increment:
|
||||
new_version = current_version.replace(release=next_month)
|
||||
else:
|
||||
new_version = current_version
|
||||
if dev:
|
||||
new_version = new_version.bump_dev()
|
||||
elif pre:
|
||||
new_version = new_version.bump_pre(tag=tag)
|
||||
if clear:
|
||||
new_version = new_version.clear(dev=True, pre=True, post=True)
|
||||
log('Updating version to %s' % new_version.normalize())
|
||||
version_file = get_version_file(ctx)
|
||||
file_contents = version_file.read_text()
|
||||
log('Found current version: %s' % __version__)
|
||||
version = find_version(ctx)
|
||||
log('Found current version: %s' % version)
|
||||
if dry_run:
|
||||
log('Would update to: %s' % new_version.normalize())
|
||||
else:
|
||||
log('Updating to: %s' % new_version.normalize())
|
||||
version_file.write_text(file_contents.replace(__version__, str(new_version.normalize())))
|
||||
version_file = get_version_file(ctx)
|
||||
file_contents = version_file.read_text()
|
||||
version_file.write_text(file_contents.replace(version, str(new_version.normalize())))
|
||||
if commit:
|
||||
ctx.run('git add {0}'.format(version_file))
|
||||
ctx.run('git add {0}'.format(version_file.as_posix()))
|
||||
log('Committing...')
|
||||
ctx.run('git commit -s -m "Bumped version."')
|
||||
|
||||
@@ -2,9 +2,13 @@
|
||||
""""Vendoring script, python 3.5 needed"""
|
||||
# Taken from pip
|
||||
# see https://github.com/pypa/pip/blob/95bcf8c5f6394298035a7332c441868f3b0169f4/tasks/vendoring/__init__.py
|
||||
from pipenv._compat import NamedTemporaryFile, TemporaryDirectory
|
||||
from pipenv.vendor.vistir.compat import NamedTemporaryFile, TemporaryDirectory
|
||||
from pipenv.vendor.vistir.contextmanagers import open_file
|
||||
from pathlib import Path
|
||||
from pipenv.utils import mkdir_p
|
||||
import io
|
||||
from urllib3.util import parse_url as urllib3_parse
|
||||
import bs4
|
||||
# from tempfile import TemporaryDirectory
|
||||
import tarfile
|
||||
import zipfile
|
||||
@@ -640,3 +644,22 @@ def main(ctx, package=None):
|
||||
# vendor_passa(ctx)
|
||||
# update_safety(ctx)
|
||||
log('Revendoring complete')
|
||||
|
||||
|
||||
@invoke.task
|
||||
def vendor_artifact(ctx, package, version=None):
|
||||
simple = requests.get("https://pypi.org/simple/{0}/".format(package))
|
||||
pkg_str = "{0}-{1}".format(package, version)
|
||||
soup = bs4.BeautifulSoup(simple.content)
|
||||
links = [
|
||||
a.attrs["href"] for a in soup.find_all("a") if a.getText().startswith(pkg_str)
|
||||
]
|
||||
for link in links:
|
||||
dest_dir = _get_git_root(ctx) / "tests" / "pypi" / package
|
||||
if not dest_dir.exists():
|
||||
dest_dir.mkdir()
|
||||
_, _, dest_path = urllib3_parse(link).path.rpartition("/")
|
||||
dest_file = dest_dir / dest_path
|
||||
with io.open(dest_file.as_posix(), "wb") as target_handle:
|
||||
with open_file(link) as fp:
|
||||
shutil.copyfileobj(fp, target_handle)
|
||||
|
||||
@@ -12,7 +12,7 @@ from pipenv.vendor import delegator
|
||||
from pipenv.vendor import requests
|
||||
from pipenv.vendor import toml
|
||||
from pipenv.vendor import tomlkit
|
||||
from pytest_pypi.app import prepare_packages as prepare_pypi_packages
|
||||
from pytest_pypi.app import prepare_packages as prepare_pypi_packages, prepare_fixtures
|
||||
from vistir.compat import ResourceWarning, fs_str
|
||||
from vistir.path import mkdir_p
|
||||
|
||||
@@ -71,6 +71,7 @@ TESTS_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
PYPI_VENDOR_DIR = os.path.join(TESTS_ROOT, 'pypi')
|
||||
WE_HAVE_HG = check_for_mercurial()
|
||||
prepare_pypi_packages(PYPI_VENDOR_DIR)
|
||||
prepare_fixtures(os.path.join(PYPI_VENDOR_DIR, "fixtures"))
|
||||
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
@@ -144,6 +145,9 @@ class _Pipfile(object):
|
||||
self.document[section][package] = value
|
||||
self.write()
|
||||
|
||||
def add(self, package, value, dev=False):
|
||||
self.install(package, value, dev=dev)
|
||||
|
||||
def loads(self):
|
||||
self.document = tomlkit.loads(self.path.read_text())
|
||||
|
||||
@@ -162,6 +166,25 @@ class _Pipfile(object):
|
||||
def get_fixture_path(cls, path):
|
||||
return Path(__file__).absolute().parent.parent / "test_artifacts" / path
|
||||
|
||||
@classmethod
|
||||
def get_url(cls, pkg=None, filename=None):
|
||||
pypi = os.environ.get("PIPENV_PYPI_URL")
|
||||
if not pkg and not filename:
|
||||
return pypi if pypi else "https://pypi.org/"
|
||||
file_path = filename
|
||||
if pkg and filename:
|
||||
file_path = os.path.join(pkg, filename)
|
||||
if filename and not pkg:
|
||||
pkg = os.path.basename(filename)
|
||||
if pypi:
|
||||
if pkg and not filename:
|
||||
url = "{0}/artifacts/{1}".format(pypi, pkg)
|
||||
else:
|
||||
url = "{0}/artifacts/{1}/{2}".format(pypi, pkg, filename)
|
||||
return url
|
||||
if pkg and not filename:
|
||||
return cls.get_fixture_path(file_path).as_uri()
|
||||
|
||||
|
||||
class _PipenvInstance(object):
|
||||
"""An instance of a Pipenv Project..."""
|
||||
@@ -189,6 +212,7 @@ class _PipenvInstance(object):
|
||||
self.chdir = chdir
|
||||
|
||||
if self.pypi:
|
||||
os.environ['PIPENV_PYPI_URL'] = fs_str('{0}'.format(self.pypi.url))
|
||||
os.environ['PIPENV_TEST_INDEX'] = fs_str('{0}/simple'.format(self.pypi.url))
|
||||
|
||||
if pipfile:
|
||||
|
||||
@@ -2,7 +2,7 @@ import os
|
||||
import shutil
|
||||
from pipenv.project import Project
|
||||
from pipenv._compat import Path
|
||||
|
||||
from pipenv.vendor import delegator
|
||||
from pipenv.utils import mkdir_p, temp_environ
|
||||
|
||||
import pytest
|
||||
@@ -338,7 +338,7 @@ six = {{path = "./artifacts/{}"}}
|
||||
@pytest.mark.files
|
||||
@pytest.mark.install
|
||||
@pytest.mark.run
|
||||
def test_multiple_editable_packages_should_not_race(PipenvInstance, pypi, tmpdir, testsroot):
|
||||
def test_multiple_editable_packages_should_not_race(PipenvInstance, pypi, testsroot):
|
||||
"""Test for a race condition that can occur when installing multiple 'editable' packages at
|
||||
once, and which causes some of them to not be importable.
|
||||
|
||||
@@ -347,40 +347,27 @@ def test_multiple_editable_packages_should_not_race(PipenvInstance, pypi, tmpdir
|
||||
So this test locally installs packages from tarballs that have already been committed in
|
||||
the local `pypi` dir to avoid using VCS packages.
|
||||
"""
|
||||
pkgs = {
|
||||
"requests-2.19.1": "requests/requests-2.19.1.tar.gz",
|
||||
"Flask-0.12.2": "flask/Flask-0.12.2.tar.gz",
|
||||
"six-1.11.0": "six/six-1.11.0.tar.gz",
|
||||
"Jinja2-2.10": "jinja2/Jinja2-2.10.tar.gz",
|
||||
}
|
||||
pkgs = ["requests", "flask", "six", "jinja2"]
|
||||
|
||||
pipfile_string = """
|
||||
[dev-packages]
|
||||
|
||||
pipfile_string="""
|
||||
[packages]
|
||||
"""
|
||||
# Unzip tarballs to known location, and update Pipfile template.
|
||||
for pkg_name, file_name in pkgs.items():
|
||||
source_path = str(Path(testsroot, "pypi", file_name))
|
||||
unzip_path = str(Path(tmpdir.strpath, pkg_name))
|
||||
|
||||
import tarfile
|
||||
|
||||
with tarfile.open(source_path, "r:gz") as tgz:
|
||||
tgz.extractall(path=tmpdir.strpath)
|
||||
|
||||
pipfile_string += "'{0}' = {{path = '{1}', editable = true}}\n".format(pkg_name, unzip_path)
|
||||
|
||||
with PipenvInstance(pypi=pypi, chdir=True) as p:
|
||||
for pkg_name in pkgs:
|
||||
source_path = p._pipfile.get_fixture_path("git/{0}/".format(pkg_name)).as_posix()
|
||||
c = delegator.run("git clone {0} ./{1}".format(source_path, pkg_name))
|
||||
assert c.return_code == 0
|
||||
|
||||
pipfile_string += '"{0}" = {{path = "./{0}", editable = true}}\n'.format(pkg_name)
|
||||
|
||||
with open(p.pipfile_path, 'w') as f:
|
||||
f.write(pipfile_string.strip())
|
||||
|
||||
c = p.pipenv('install')
|
||||
assert c.return_code == 0
|
||||
|
||||
c = p.pipenv('run python -c "import requests"')
|
||||
assert c.return_code == 0
|
||||
c = p.pipenv('run python -c "import flask"')
|
||||
assert c.return_code == 0
|
||||
c = p.pipenv('run python -c "import six"')
|
||||
assert c.return_code == 0
|
||||
c = p.pipenv('run python -c "import jinja2"')
|
||||
assert c.return_code == 0
|
||||
c = p.pipenv('run python -c "import requests, flask, six, jinja2"')
|
||||
assert c.return_code == 0, c.err
|
||||
|
||||
@@ -63,9 +63,10 @@ def test_ssh_vcs_install(PipenvInstance, pip_src_dir, pypi):
|
||||
@pytest.mark.needs_internet
|
||||
@flaky
|
||||
def test_urls_work(PipenvInstance, pypi, pip_src_dir):
|
||||
with PipenvInstance(pypi=pypi) as p:
|
||||
with PipenvInstance(pypi=pypi, chdir=True) as p:
|
||||
path = p._pipfile.get_url("django", "3.4.x.zip")
|
||||
c = p.pipenv(
|
||||
"install https://github.com/divio/django-cms/archive/release/3.4.x.zip"
|
||||
"install {0}".format(path)
|
||||
)
|
||||
assert c.return_code == 0
|
||||
|
||||
@@ -187,27 +188,15 @@ six = "*"
|
||||
@pytest.mark.needs_internet
|
||||
def test_install_local_vcs_not_in_lockfile(PipenvInstance, pip_src_dir):
|
||||
with PipenvInstance(chdir=True) as p:
|
||||
six_path = os.path.join(p.path, "six")
|
||||
c = delegator.run(
|
||||
"git clone https://github.com/benjaminp/six.git {0}".format(six_path)
|
||||
)
|
||||
# six_path = os.path.join(p.path, "six")
|
||||
six_path = p._pipfile.get_fixture_path("git/six/").as_posix()
|
||||
c = delegator.run("git clone {0} ./six".format(six_path))
|
||||
assert c.return_code == 0
|
||||
c = p.pipenv("install -e ./six")
|
||||
c = p.pipenv("install -e ./six".format(six_path))
|
||||
assert c.return_code == 0
|
||||
six_key = list(p.pipfile["packages"].keys())[0]
|
||||
c = p.pipenv(
|
||||
"install -e git+https://github.com/requests/requests.git#egg=requests"
|
||||
)
|
||||
assert c.return_code == 0
|
||||
c = p.pipenv("lock")
|
||||
assert c.return_code == 0
|
||||
assert "requests" in p.pipfile["packages"]
|
||||
assert "requests" in p.lockfile["default"]
|
||||
# This is the hash of ./six
|
||||
assert six_key in p.pipfile["packages"]
|
||||
assert six_key in p.lockfile["default"]
|
||||
# The hash isn't a hash anymore, its actually the name of the package (we now resolve this)
|
||||
assert "six" in p.pipfile["packages"]
|
||||
# we don't need the rest of the test anymore, this just works on its own
|
||||
assert six_key == "six"
|
||||
|
||||
|
||||
@pytest.mark.vcs
|
||||
@@ -247,6 +236,7 @@ def test_vcs_entry_supersedes_non_vcs(PipenvInstance, pip_src_dir):
|
||||
the resolution graph of non-editable vcs dependencies.
|
||||
"""
|
||||
with PipenvInstance(chdir=True) as p:
|
||||
pyinstaller_path = p._pipfile.get_fixture_path("git/pyinstaller")
|
||||
with open(p.pipfile_path, "w") as f:
|
||||
f.write(
|
||||
"""
|
||||
@@ -257,18 +247,19 @@ name = "pypi"
|
||||
|
||||
[packages]
|
||||
PyUpdater = "*"
|
||||
PyInstaller = {ref = "develop", git = "https://github.com/pyinstaller/pyinstaller.git"}
|
||||
""".strip()
|
||||
PyInstaller = {{ref = "develop", git = "{0}"}}
|
||||
""".format(pyinstaller_path.as_uri()).strip()
|
||||
)
|
||||
p.pipenv("install")
|
||||
c = p.pipenv("install")
|
||||
assert c.return_code == 0
|
||||
installed_packages = ["PyUpdater", "PyInstaller"]
|
||||
assert all([k in p.pipfile["packages"] for k in installed_packages])
|
||||
assert all([k.lower() in p.lockfile["default"] for k in installed_packages])
|
||||
assert all([k in p.lockfile["default"]["pyinstaller"] for k in ["ref", "git"]])
|
||||
assert all([k in p.lockfile["default"]["pyinstaller"] for k in ["ref", "git"]]), str(p.lockfile["default"])
|
||||
assert p.lockfile["default"]["pyinstaller"].get("ref") is not None
|
||||
assert (
|
||||
p.lockfile["default"]["pyinstaller"]["git"]
|
||||
== "https://github.com/pyinstaller/pyinstaller.git"
|
||||
== pyinstaller_path.as_uri()
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import pytest
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pipenv.utils import temp_environ
|
||||
|
||||
@@ -512,4 +513,50 @@ def test_lock_no_warnings(PipenvInstance, pypi):
|
||||
os.environ["PYTHONWARNINGS"] = str("once")
|
||||
c = p.pipenv("install six")
|
||||
assert c.return_code == 0
|
||||
c = p.pipenv('run python -c "import warnings; warnings.warn(\\"This is a warning\\", DeprecationWarning); print(\\"hello\\")"')
|
||||
assert c.return_code == 0
|
||||
assert "Warning" in c.err
|
||||
assert "Warning" not in c.out
|
||||
assert "hello" in c.out
|
||||
|
||||
|
||||
@pytest.mark.lock
|
||||
@pytest.mark.install
|
||||
@pytest.mark.skipif(sys.version_info >= (3, 5), reason="scandir doesn't get installed on python 3.5+")
|
||||
def test_lock_missing_cache_entries_gets_all_hashes(monkeypatch, PipenvInstance, pypi, tmpdir):
|
||||
"""
|
||||
Test locking pathlib2 on python2.7 which needs `scandir`, but fails to resolve when
|
||||
using a fresh dependency cache.
|
||||
"""
|
||||
|
||||
with monkeypatch.context() as m:
|
||||
monkeypatch.setattr("pipenv.patched.piptools.locations.CACHE_DIR", tmpdir.strpath)
|
||||
with PipenvInstance(pypi=pypi, chdir=True) as p:
|
||||
p._pipfile.add("pathlib2", "*")
|
||||
assert "pathlib2" in p.pipfile["packages"]
|
||||
c = p.pipenv("install")
|
||||
assert c.return_code == 0, c.err
|
||||
assert "pathlib2" in p.lockfile["default"]
|
||||
assert "scandir" in p.lockfile["default"]
|
||||
assert isinstance(p.lockfile["default"]["scandir"]["hashes"], list)
|
||||
assert len(p.lockfile["default"]["scandir"]["hashes"]) > 1
|
||||
|
||||
|
||||
@pytest.mark.lock
|
||||
@pytest.mark.vcs
|
||||
def test_vcs_lock_respects_top_level_pins(PipenvInstance, pypi):
|
||||
"""Test that locking VCS dependencies respects top level packages pinned in Pipfiles"""
|
||||
|
||||
with PipenvInstance(pypi=pypi, chdir=True) as p:
|
||||
requests_uri = p._pipfile.get_fixture_path("git/requests").as_uri()
|
||||
p._pipfile.add("requests", {
|
||||
"editable": True, "git": "{0}".format(requests_uri),
|
||||
"ref": "v2.18.4"
|
||||
})
|
||||
p._pipfile.add("urllib3", "==1.21.1")
|
||||
c = p.pipenv("install")
|
||||
assert c.return_code == 0
|
||||
assert "requests" in p.lockfile["default"]
|
||||
assert "git" in p.lockfile["default"]["requests"]
|
||||
assert "urllib3" in p.lockfile["default"]
|
||||
assert p.lockfile["default"]["urllib3"]["version"] == "==1.21.1"
|
||||
|
||||
@@ -98,7 +98,7 @@ def test_directory_with_leading_dash(PipenvInstance):
|
||||
prefix = '-dir-with-leading-dash'
|
||||
return mkdtemp(suffix, prefix, dir)
|
||||
|
||||
with mock.patch('pipenv._compat.mkdtemp', side_effect=mocked_mkdtemp):
|
||||
with mock.patch('pipenv.vendor.vistir.compat.mkdtemp', side_effect=mocked_mkdtemp):
|
||||
with temp_environ(), PipenvInstance(chdir=True) as p:
|
||||
del os.environ['PIPENV_VENV_IN_PROJECT']
|
||||
p.pipenv('--python python')
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,14 +1,17 @@
|
||||
import os
|
||||
import json
|
||||
import sys
|
||||
|
||||
import requests
|
||||
from flask import Flask, redirect, abort, render_template, send_file, jsonify
|
||||
from zipfile import is_zipfile
|
||||
from tarfile import is_tarfile
|
||||
|
||||
app = Flask(__name__)
|
||||
session = requests.Session()
|
||||
|
||||
packages = {}
|
||||
|
||||
ARTIFACTS = {}
|
||||
|
||||
class Package(object):
|
||||
"""Package represents a collection of releases from one or more directories"""
|
||||
@@ -38,6 +41,46 @@ class Package(object):
|
||||
self._package_dirs.add(path)
|
||||
|
||||
|
||||
class Artifact(object):
|
||||
"""Represents an artifact for download"""
|
||||
|
||||
def __init__(self, name):
|
||||
super(Artifact, self).__init__()
|
||||
self.name = name
|
||||
self.files = {}
|
||||
self._artifact_dirs = set()
|
||||
|
||||
def __repr__(self):
|
||||
return "<Artifact name={0!r} files={1!r}".format(self.name, len(self.files))
|
||||
|
||||
def add_file(self, path):
|
||||
path = os.path.abspath(path)
|
||||
base_path, fn = os.path.split(path)
|
||||
self.files[fn] = path
|
||||
self._artifact_dirs.add(base_path)
|
||||
|
||||
|
||||
def prepare_fixtures(path):
|
||||
path = os.path.abspath(path)
|
||||
if not (os.path.exists(path) and os.path.isdir(path)):
|
||||
raise ValueError("{} is not a directory!".format(path))
|
||||
for root, dirs, files in os.walk(path):
|
||||
package_name, _, _ = os.path.relpath(root, start=path).partition(os.path.sep)
|
||||
if package_name not in ARTIFACTS:
|
||||
ARTIFACTS[package_name] = Artifact(package_name)
|
||||
for file in files:
|
||||
file_path = os.path.join(root, file)
|
||||
rel_path = os.path.relpath(file_path, start=path)
|
||||
_, _, subpkg = rel_path.partition(os.path.sep)
|
||||
subpkg, _, _ = subpkg.partition(os.path.sep)
|
||||
pkg, ext = os.path.splitext(subpkg)
|
||||
if not (is_tarfile(file_path) or is_zipfile(file_path) or ext == ".git"):
|
||||
continue
|
||||
if subpkg not in ARTIFACTS[package_name].files:
|
||||
ARTIFACTS[package_name].add_file(os.path.join(root, file))
|
||||
ARTIFACTS[package_name].add_file(os.path.join(root, file))
|
||||
|
||||
|
||||
def prepare_packages(path):
|
||||
"""Add packages in path to the registry."""
|
||||
path = os.path.abspath(path)
|
||||
@@ -47,7 +90,9 @@ def prepare_packages(path):
|
||||
for file in files:
|
||||
if not file.startswith('.') and not file.endswith('.json'):
|
||||
package_name = os.path.basename(root)
|
||||
|
||||
if package_name and package_name == "fixtures":
|
||||
prepare_fixtures(root)
|
||||
continue
|
||||
if package_name not in packages:
|
||||
packages[package_name] = Package(package_name)
|
||||
|
||||
@@ -64,6 +109,11 @@ def simple():
|
||||
return render_template('simple.html', packages=packages.values())
|
||||
|
||||
|
||||
@app.route('/artifacts')
|
||||
def artifacts():
|
||||
return render_template('artifacts.html', artifacts=ARTIFACTS.values())
|
||||
|
||||
|
||||
@app.route('/simple/<package>/')
|
||||
def simple_package(package):
|
||||
if package in packages:
|
||||
@@ -72,6 +122,14 @@ def simple_package(package):
|
||||
abort(404)
|
||||
|
||||
|
||||
@app.route('/artifacts/<artifact>/')
|
||||
def simple_artifact(artifact):
|
||||
if artifact in ARTIFACTS:
|
||||
return render_template('artifact.html', artifact=ARTIFACTS[artifact])
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
|
||||
@app.route('/<package>/<release>')
|
||||
def serve_package(package, release):
|
||||
if package in packages:
|
||||
@@ -83,6 +141,15 @@ def serve_package(package, release):
|
||||
abort(404)
|
||||
|
||||
|
||||
@app.route('/artifacts/<artifact>/<fn>')
|
||||
def serve_artifact(artifact, fn):
|
||||
if artifact in ARTIFACTS:
|
||||
artifact = ARTIFACTS[artifact]
|
||||
if fn in artifact.files:
|
||||
return send_file(artifact.files[fn])
|
||||
abort(404)
|
||||
|
||||
|
||||
@app.route('/pypi/<package>/json')
|
||||
def json_for_package(package):
|
||||
try:
|
||||
@@ -98,5 +165,6 @@ if __name__ == '__main__':
|
||||
PYPI_VENDOR_DIR = os.environ.get('PYPI_VENDOR_DIR', './pypi')
|
||||
PYPI_VENDOR_DIR = os.path.abspath(PYPI_VENDOR_DIR)
|
||||
prepare_packages(PYPI_VENDOR_DIR)
|
||||
prepare_fixtures(os.path.join(PYPI_VENDOR_DIR, "fixtures"))
|
||||
|
||||
app.run()
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Links for {{ artifact.name }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Links for {{ artifact.name }}</h1>
|
||||
{% for fn in artifact.files %}
|
||||
<a href="/{{ artifact.name }}/{{ fn }}">{{ fn }}</a>
|
||||
<br>
|
||||
{% endfor %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Artifact Index</title>
|
||||
</head>
|
||||
<body>
|
||||
{% for artifact in artifacts %}
|
||||
<a href="/artifacts/{{ artifact.name }}/" rel="internal">{{ artifact.name }}</a>
|
||||
<br>
|
||||
{% endfor %}
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
Submodule
+1
Submodule tests/test_artifacts/git/flask added at dcc02d6e7d
Submodule
+1
Submodule tests/test_artifacts/git/jinja2 added at a7f1f528f5
Submodule
+1
Submodule tests/test_artifacts/git/pyinstaller added at 19d8a37898
Submodule
+1
Submodule tests/test_artifacts/git/requests-2.18.4 added at a3d7cf3f27
Reference in New Issue
Block a user