Merge branch 'master' into patch-2

This commit is contained in:
Dan Ryan
2018-08-04 18:16:54 -04:00
committed by GitHub
49 changed files with 1287 additions and 473 deletions
+29
View File
@@ -0,0 +1,29 @@
Thank you for contributing to Pipenv!
##### The issue
What is the thing you want to fix? Is it associated with an issue on GitHub? Please mention it.
Always consider opening an issue first to describe your problem, so we can discuss what is the best way to amend it. Note that if you do not describe the goal of this change or link to a related issue, the maintainers may close the PR without further review.
##### The fix
How does this pull request fix your problem? Did you consider any alternatives? Why is this the *best* solution, in your opinion?
##### The checklist
* [ ] Associated issue
* [ ] A news fragment in the `news/` directory to describe this fix with the extension `.bugfix`, `.feature`, `.behavior`, `.doc`. `.vendor`. or `.trivial` (this will appear in the release changelog). Use semantic line breaks and name the file after the issue number or the PR #.
<!--
##### If this is a patch to the `vendor` directory…
Please try to refrain from submitting patches directly to `vendor` or `patched`, but raise your issue to the upstream project instead, and inform Pipenv to upgrade when the upstream project accepts the fix.
A pull request to upgrade vendor packages is strongly discouraged, unless there is a very good reason (e.g. you need to test Pipenvs integration to a new vendor feature). Pipenv audits and performs vendor upgrades regularly, generally before a new release is about to drop.
If your patch is not or cannot be accepted by upstream, but is essential to Pipenv (make sure to discuss this with maintainers!), please remember to attach a patch file in `tasks/vendoring/patched`, so this divergence from upstream can be recorded and replayed afterwards.
-->
+151
View File
@@ -0,0 +1,151 @@
/*
* Konami-JS ~
* :: Now with support for touch events and multiple instances for
* :: those situations that call for multiple easter eggs!
* Code: https://github.com/snaptortoise/konami-js
* Copyright (c) 2009 George Mandis (georgemandis.com, snaptortoise.com)
* Version: 1.6.2 (7/17/2018)
* Licensed under the MIT License (http://opensource.org/licenses/MIT)
* Tested in: Safari 4+, Google Chrome 4+, Firefox 3+, IE7+, Mobile Safari 2.2.1+ and Android
*/
var Konami = function (callback) {
var konami = {
addEvent: function (obj, type, fn, ref_obj) {
if (obj.addEventListener)
obj.addEventListener(type, fn, false);
else if (obj.attachEvent) {
// IE
obj["e" + type + fn] = fn;
obj[type + fn] = function () {
obj["e" + type + fn](window.event, ref_obj);
}
obj.attachEvent("on" + type, obj[type + fn]);
}
},
removeEvent: function (obj, eventName, eventCallback) {
if (obj.removeEventListener) {
obj.removeEventListener(eventName, eventCallback);
} else if (obj.attachEvent) {
obj.detachEvent(eventName);
}
},
input: "",
pattern: "38384040373937396665",
keydownHandler: function (e, ref_obj) {
if (ref_obj) {
konami = ref_obj;
} // IE
konami.input += e ? e.keyCode : event.keyCode;
if (konami.input.length > konami.pattern.length) {
konami.input = konami.input.substr((konami.input.length - konami.pattern.length));
}
if (konami.input === konami.pattern) {
konami.code(konami._currentLink);
konami.input = '';
e.preventDefault();
return false;
}
},
load: function (link) {
this._currentLink = link;
this.addEvent(document, "keydown", this.keydownHandler, this);
this.iphone.load(link);
},
unload: function () {
this.removeEvent(document, 'keydown', this.keydownHandler);
this.iphone.unload();
},
code: function (link) {
window.location = link
},
iphone: {
start_x: 0,
start_y: 0,
stop_x: 0,
stop_y: 0,
tap: false,
capture: false,
orig_keys: "",
keys: ["UP", "UP", "DOWN", "DOWN", "LEFT", "RIGHT", "LEFT", "RIGHT", "TAP", "TAP"],
input: [],
code: function (link) {
konami.code(link);
},
touchmoveHandler: function (e) {
if (e.touches.length === 1 && konami.iphone.capture === true) {
var touch = e.touches[0];
konami.iphone.stop_x = touch.pageX;
konami.iphone.stop_y = touch.pageY;
konami.iphone.tap = false;
konami.iphone.capture = false;
konami.iphone.check_direction();
}
},
touchendHandler: function () {
konami.iphone.input.push(konami.iphone.check_direction());
if (konami.iphone.input.length > konami.iphone.keys.length) konami.iphone.input.shift();
if (konami.iphone.input.length === konami.iphone.keys.length) {
var match = true;
for (var i = 0; i < konami.iphone.keys.length; i++) {
if (konami.iphone.input[i] !== konami.iphone.keys[i]) {
match = false;
}
}
if (match) {
konami.iphone.code(konami._currentLink);
}
}
},
touchstartHandler: function (e) {
konami.iphone.start_x = e.changedTouches[0].pageX;
konami.iphone.start_y = e.changedTouches[0].pageY;
konami.iphone.tap = true;
konami.iphone.capture = true;
},
load: function (link) {
this.orig_keys = this.keys;
konami.addEvent(document, "touchmove", this.touchmoveHandler);
konami.addEvent(document, "touchend", this.touchendHandler, false);
konami.addEvent(document, "touchstart", this.touchstartHandler);
},
unload: function () {
konami.removeEvent(document, 'touchmove', this.touchmoveHandler);
konami.removeEvent(document, 'touchend', this.touchendHandler);
konami.removeEvent(document, 'touchstart', this.touchstartHandler);
},
check_direction: function () {
x_magnitude = Math.abs(this.start_x - this.stop_x);
y_magnitude = Math.abs(this.start_y - this.stop_y);
x = ((this.start_x - this.stop_x) < 0) ? "RIGHT" : "LEFT";
y = ((this.start_y - this.stop_y) < 0) ? "DOWN" : "UP";
result = (x_magnitude > y_magnitude) ? x : y;
result = (this.tap === true) ? "TAP" : result;
return result;
}
}
}
typeof callback === "string" && konami.load(callback);
if (typeof callback === "function") {
konami.code = callback;
konami.load();
}
return konami;
};
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = Konami;
} else {
if (typeof define === 'function' && define.amd) {
define([], function() {
return Konami;
});
} else {
window.Konami = Konami;
}
}
+1 -1
View File
@@ -49,7 +49,7 @@
<script src="{{ pathto('_static/', 1) }}/konami.js"></script>
<script>
var easter_egg = new Konami('http://fortunes.herokuapp.com/random/raw');
var easter_egg = new Konami('https://www.myfortunecookie.co.uk/fortunes/' + (Math.floor(Math.random() * 152) + 1));
</script>
<style>
+1 -1
View File
@@ -17,7 +17,7 @@
<h3>Stay Informed</h3>
<p>Receive updates on new releases and upcoming projects.</p>
<p><iframe src="http://ghbtns.com/github-btn.html?user=kennethreitz&type=follow&count=true"
<p><iframe src="https://ghbtns.com/github-btn.html?user=kennethreitz&type=follow&count=true"
allowtransparency="true" frameborder="0" scrolling="0" width="200" height="20"></iframe></p>
<p><a href="https://twitter.com/kennethreitz" class="twitter-follow-button" data-show-count="false">Follow @kennethreitz</a> <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script></p>
+1
View File
@@ -0,0 +1 @@
Fixed the ability of pipenv to parse ``dependency_links`` from ``setup.py`` when ``PIP_PROCESS_DEPENDENCY_LINKS`` is enabled.
+2 -2
View File
@@ -1,2 +1,2 @@
Added environment variable `PIPENV_VERBOSITY` to control output verbosity
without needing to pass options.
Added environment variables `PIPENV_VERBOSE` and `PIPENV_QUIET` to control
output verbosity without needing to pass options.
+1
View File
@@ -0,0 +1 @@
Fixed multiple issues with finding the correct system python locations.
+4
View File
@@ -0,0 +1,4 @@
Greatly enhanced python discovery functionality:
- Added pep514 (windows launcher/finder) support for python discovery.
- Introduced architecture discovery for python installations which support different architectures.
+1
View File
@@ -0,0 +1 @@
Update ``pythonfinder`` to major release ``1.0.0`` for integration.
+2
View File
@@ -0,0 +1,2 @@
Catch JSON decoding error to prevent exception when the lock file is of
invalid format.
+1
View File
@@ -0,0 +1 @@
Dependency links to private repositories defined via ``ssh://`` schemes will now install correctly and skip hashing as long as ``PIP_PROCESS_DEPENDENCY_LINKS=1``.
+1
View File
@@ -0,0 +1 @@
Enhanced resolution of editable and VCS dependencies.
+1
View File
@@ -0,0 +1 @@
Updated documentation to use working fortune cookie addon.
+2
View File
@@ -0,0 +1,2 @@
Add ``COMSPEC`` to fallback option (along with ``SHELL`` and ``PYENV_SHELL``)
if shell detection fails, improving robustness on Windows.
+1
View File
@@ -0,0 +1 @@
Fixed a bug which sometimes caused pipenv to parse the ``trusted_host`` argument to pip incorrectly when parsing source URLs which specify ``verify_ssl = false``.
+1
View File
@@ -0,0 +1 @@
Prevent crashing when a virtual environment in ``WORKON_HOME`` is faulty.
+1
View File
@@ -0,0 +1 @@
Fixed virtualenv creation failure when a .venv file is present in the project root.
+31 -34
View File
@@ -61,12 +61,12 @@ class PipenvGroup(Group):
)
def setup_verbose(ctx, param, value):
if value:
import logging
logging.getLogger("pip").setLevel(logging.INFO)
return value
def setup_verbosity(ctx, param, value):
if not value:
return
import logging
logging.getLogger("pip").setLevel(logging.INFO)
environments.PIPENV_VERBOSITY = 1
def validate_python_path(ctx, param, value):
@@ -333,9 +333,9 @@ def cli(
"--verbose",
"-v",
is_flag=True,
default=False,
expose_value=False,
callback=setup_verbosity,
help="Verbose mode.",
callback=setup_verbose,
)
@option(
"--ignore-pipfile",
@@ -385,7 +385,6 @@ def install(
lock=True,
ignore_pipfile=False,
skip_lock=False,
verbose=False,
requirements=False,
sequential=False,
pre=False,
@@ -408,7 +407,6 @@ def install(
lock=lock,
ignore_pipfile=ignore_pipfile,
skip_lock=skip_lock,
verbose=verbose,
requirements=requirements,
sequential=sequential,
pre=pre,
@@ -440,9 +438,9 @@ def install(
"--verbose",
"-v",
is_flag=True,
default=False,
expose_value=False,
help="Verbose mode.",
callback=setup_verbose,
callback=setup_verbosity,
)
@option("--lock", is_flag=True, default=True, help="Lock afterwards.")
@option(
@@ -479,7 +477,6 @@ def uninstall(
lock=False,
all_dev=False,
all=False,
verbose=False,
keep_outdated=False,
pypi_mirror=None,
):
@@ -495,7 +492,6 @@ def uninstall(
lock=lock,
all_dev=all_dev,
all=all,
verbose=verbose,
keep_outdated=keep_outdated,
pypi_mirror=pypi_mirror,
)
@@ -526,9 +522,9 @@ def uninstall(
"--verbose",
"-v",
is_flag=True,
default=False,
expose_value=False,
help="Verbose mode.",
callback=setup_verbose,
callback=setup_verbosity,
)
@option(
"--requirements",
@@ -556,7 +552,6 @@ def lock(
three=None,
python=False,
pypi_mirror=None,
verbose=False,
requirements=False,
dev=False,
clear=False,
@@ -571,7 +566,6 @@ def lock(
if requirements:
do_init(dev=dev, requirements=requirements, pypi_mirror=pypi_mirror)
do_lock(
verbose=verbose,
clear=clear,
pre=pre,
keep_outdated=keep_outdated,
@@ -783,9 +777,9 @@ def check(
"--verbose",
"-v",
is_flag=True,
default=False,
expose_value=False,
help="Verbose mode.",
callback=setup_verbose,
callback=setup_verbosity,
)
@option(
"--dev",
@@ -821,7 +815,6 @@ def update(
python=False,
pypi_mirror=None,
system=False,
verbose=False,
clear=False,
keep_outdated=False,
pre=False,
@@ -869,8 +862,8 @@ def update(
err=True,
)
sys.exit(1)
do_lock(
verbose=verbose,
clear=clear,
pre=pre,
keep_outdated=keep_outdated,
@@ -884,7 +877,6 @@ def update(
bare=bare,
dont_upgrade=False,
user=False,
verbose=verbose,
clear=clear,
unused=False,
sequential=sequential,
@@ -954,9 +946,9 @@ def run_open(module, three=None, python=None, pypi_mirror=None):
"--verbose",
"-v",
is_flag=True,
default=False,
expose_value=False,
help="Verbose mode.",
callback=setup_verbose,
callback=setup_verbosity,
)
@option(
"--dev",
@@ -1002,7 +994,6 @@ def sync(
bare=False,
dont_upgrade=False,
user=False,
verbose=False,
clear=False,
unused=False,
package_name=None,
@@ -1020,7 +1011,6 @@ def sync(
bare=bare,
dont_upgrade=dont_upgrade,
user=user,
verbose=verbose,
clear=clear,
unused=unused,
sequential=sequential,
@@ -1033,9 +1023,9 @@ def sync(
"--verbose",
"-v",
is_flag=True,
default=False,
expose_value=False,
help="Verbose mode.",
callback=setup_verbose,
callback=setup_verbosity,
)
@option(
"--three/--two",
@@ -1050,15 +1040,22 @@ def sync(
callback=validate_python_path,
help="Specify which version of Python virtualenv should use.",
)
@option("--dry-run", is_flag=True, default=False, help="Just output unneeded packages.")
@option(
"--dry-run",
is_flag=True,
default=False,
help="Just output unneeded packages.",
)
@pass_context
def clean(
ctx, three=None, python=None, dry_run=False, bare=False, user=False, verbose=False
):
def clean(ctx, three=None, python=None, dry_run=False, bare=False, user=False):
"""Uninstalls all packages not specified in Pipfile.lock."""
from .core import do_clean
do_clean(ctx=ctx, three=three, python=python, dry_run=dry_run, verbose=verbose)
do_clean(
ctx=ctx,
three=three, python=python,
dry_run=dry_run,
)
# Install click commands.
+62 -166
View File
@@ -6,7 +6,6 @@ import sys
import shutil
import time
import tempfile
from glob import glob
import json as simplejson
import click
import click_completion
@@ -42,7 +41,7 @@ from .utils import (
clean_resolved_dep,
)
from ._compat import TemporaryDirectory, Path
from . import pep508checker, progress
from . import environments, pep508checker, progress
from .environments import (
PIPENV_COLORBLIND,
PIPENV_NOSPIN,
@@ -50,9 +49,6 @@ from .environments import (
PIPENV_TIMEOUT,
PIPENV_SKIP_VALIDATION,
PIPENV_HIDE_EMOJIS,
PIPENV_INSTALL_TIMEOUT,
PYENV_ROOT,
PYENV_INSTALLED,
PIPENV_YES,
PIPENV_DONT_LOAD_ENV,
PIPENV_DEFAULT_PYTHON_VERSION,
@@ -317,76 +313,38 @@ def ensure_pipfile(validate=True, skip_requirements=False, system=False):
project.write_toml(p)
def find_python_from_py(python):
"""Find a Python executable from on Windows.
def find_a_system_python(line):
"""Find a Python installation from a given line.
Ask py.exe for its opinion.
This tries to parse the line in various of ways:
* Looks like an absolute path? Use it directly.
* Looks like a py.exe call? Use py.exe to get the executable.
* Starts with "py" something? Looks like a python command. Try to find it
in PATH, and use it directly.
* Search for "python" and "pythonX.Y" executables in PATH to find a match.
* Nothing fits, return None.
"""
py = system_which("py")
if not py:
if not line:
return None
version_args = ["-{0}".format(python[0])]
if len(python) >= 2:
version_args.append("-{0}.{1}".format(python[0], python[2]))
import subprocess
for ver_arg in reversed(version_args):
try:
python_exe = subprocess.check_output(
[py, ver_arg, "-c", "import sys; print(sys.executable)"]
)
except subprocess.CalledProcessError:
continue
if not isinstance(python_exe, str):
python_exe = python_exe.decode(sys.getdefaultencoding())
python_exe = python_exe.strip()
version = python_version(python_exe)
if (version or "").startswith(python):
return python_exe
def find_python_in_path(python):
"""Find a Python executable from a version number.
This uses the PATH environment variable to locate an appropriate Python.
"""
possibilities = ["python", "python{0}".format(python[0])]
if len(python) >= 2:
possibilities.extend(
[
"python{0}{1}".format(python[0], python[2]),
"python{0}.{1}".format(python[0], python[2]),
"python{0}.{1}m".format(python[0], python[2]),
]
)
# Reverse the list, so we find specific ones first.
possibilities = reversed(possibilities)
for possibility in possibilities:
# Windows compatibility.
if os.name == "nt":
possibility = "{0}.exe".format(possibility)
pythons = system_which(possibility, mult=True)
for p in pythons:
version = python_version(p)
if (version or "").startswith(python):
return p
def find_a_system_python(python):
"""Finds a system python, given a version (e.g. 2 / 2.7 / 3.6.2), or a full path."""
if python.startswith("py"):
return system_which(python)
elif os.path.isabs(python):
return python
python_from_py = find_python_from_py(python)
if python_from_py:
return python_from_py
return find_python_in_path(python)
if os.path.isabs(line):
return line
from .vendor.pythonfinder import Finder
finder = Finder(system=False, global_search=True)
if ((line.startswith("py ") or line.startswith("py.exe "))
and os.name == 'nt'):
line = line.split(" ", 1)[1].lstrip("-")
elif line.startswith("py"):
python_entry = finder.which(line)
if python_entry:
return python_entry.path.as_posix()
return None
python_entry = finder.find_python_version(line)
if not python_entry:
python_entry = finder.which("python{0}".format(line))
if python_entry:
return python_entry.path.as_posix()
return None
def ensure_python(three=None, python=None):
@@ -409,35 +367,7 @@ def ensure_python(three=None, python=None):
)
sys.exit(1)
def activate_pyenv():
from notpip._vendor.packaging.version import parse as parse_version
"""Adds all pyenv installations to the PATH."""
if PYENV_INSTALLED:
if PYENV_ROOT:
pyenv_paths = {}
for found in glob("{0}{1}versions{1}*".format(PYENV_ROOT, os.sep)):
pyenv_paths[os.path.split(found)[1]] = "{0}{1}bin".format(
found, os.sep
)
for version_str, pyenv_path in pyenv_paths.items():
version = parse_version(version_str)
if version.is_prerelease and pyenv_paths.get(version.base_version):
continue
add_to_path(pyenv_path)
else:
click.echo(
"{0}: PYENV_ROOT is not set. New python paths will "
"probably not be exported properly after installation."
"".format(crayons.red("Warning", bold=True)),
err=True,
)
global USING_DEFAULT_PYTHON
# Add pyenv paths to PATH.
activate_pyenv()
path_to_python = None
USING_DEFAULT_PYTHON = three is None and not python
# Find out which python is desired.
if not python:
@@ -446,8 +376,7 @@ def ensure_python(three=None, python=None):
python = project.required_python_version
if not python:
python = PIPENV_DEFAULT_PYTHON_VERSION
if python:
path_to_python = find_a_system_python(python)
path_to_python = find_a_system_python(python)
if not path_to_python and python is not None:
# We need to install Python.
click.echo(
@@ -459,6 +388,7 @@ def ensure_python(three=None, python=None):
err=True,
)
# Pyenv is installed
from .vendor.pythonfinder.environment import PYENV_INSTALLED
if not PYENV_INSTALLED:
abort()
else:
@@ -501,8 +431,6 @@ def ensure_python(three=None, python=None):
click.echo(crayons.blue(e.err), err=True)
# Print the results, in a beautiful blue…
click.echo(crayons.blue(c.out), err=True)
# Add new paths to PATH.
activate_pyenv()
# Find the newly installed Python, hopefully.
version = str(version)
path_to_python = find_a_system_python(version)
@@ -704,7 +632,6 @@ def do_install_dependencies(
allow_global=False,
ignore_hashes=False,
skip_lock=False,
verbose=False,
concurrent=True,
requirements_dir=None,
pypi_mirror=False,
@@ -720,7 +647,7 @@ def do_install_dependencies(
c.block()
if "Ignoring" in c.out:
click.echo(crayons.yellow(c.out.strip()))
elif verbose:
elif environments.is_verbose():
click.echo(crayons.blue(c.out or c.err))
# The Installation failed…
if c.return_code != 0:
@@ -803,7 +730,6 @@ def do_install_dependencies(
ignore_hashes=ignore_hash,
allow_global=allow_global,
no_deps=no_deps,
verbose=verbose,
block=block,
index=index,
requirements_dir=requirements_dir,
@@ -832,7 +758,6 @@ def do_install_dependencies(
ignore_hashes=ignore_hash,
allow_global=allow_global,
no_deps=no_deps,
verbose=verbose,
index=index,
requirements_dir=requirements_dir,
extra_indexes=extra_indexes,
@@ -915,21 +840,17 @@ def do_create_virtualenv(python=None, site_packages=False, pypi_mirror=None):
# Actually create the virtualenv.
with spinner():
try:
c = delegator.run(cmd, block=False, timeout=PIPENV_TIMEOUT, env=pip_config)
except OSError:
click.echo(
"{0}: it looks like {1} is not in your {2}. "
"We cannot continue until this is resolved."
"".format(
crayons.red("Warning", bold=True),
crayons.red(cmd[0]),
crayons.normal("PATH", bold=True),
),
err=True,
)
sys.exit(1)
click.echo(crayons.blue(c.out), err=True)
c = delegator.run(
cmd, block=False, timeout=PIPENV_TIMEOUT, env=pip_config,
)
c.block()
click.echo(crayons.blue("{0}".format(c.out)), err=True)
if c.return_code != 0:
click.echo(crayons.blue("{0}".format(c.err)), err=True)
click.echo(u"{0}: Failed to create virtual environment.".format(
crayons.red("Warning", bold=True),
), err=True)
sys.exit(1)
# Associate project directory with the environment.
# This mimics Pew's "setproject".
@@ -981,7 +902,6 @@ def get_downloads_info(names_map, section):
def do_lock(
verbose=False,
system=False,
clear=False,
pre=False,
@@ -1057,7 +977,6 @@ def do_lock(
results = venv_resolve_deps(
deps,
which=which,
verbose=verbose,
project=project,
clear=clear,
pre=pre,
@@ -1078,7 +997,6 @@ def do_lock(
project,
pip_freeze,
which=which,
verbose=verbose,
clear=clear,
pre=pre,
allow_global=system,
@@ -1088,7 +1006,6 @@ def do_lock(
vcs_results = venv_resolve_deps(
vcs_lines,
which=which,
verbose=verbose,
project=project,
clear=clear,
pre=pre,
@@ -1147,7 +1064,7 @@ def do_lock(
return lockfile
def do_purge(bare=False, downloads=False, allow_global=False, verbose=False):
def do_purge(bare=False, downloads=False, allow_global=False):
"""Executes the purge functionality."""
from .vendor.requirementslib.models.requirements import Requirement
@@ -1188,7 +1105,7 @@ def do_purge(bare=False, downloads=False, allow_global=False, verbose=False):
escape_grouped_arguments(which_pip(allow_global=allow_global)),
" ".join(actually_installed),
)
if verbose:
if environments.is_verbose():
click.echo("$ {0}".format(command))
c = delegator.run(command)
if not bare:
@@ -1202,7 +1119,6 @@ def do_init(
allow_global=False,
ignore_pipfile=False,
skip_lock=False,
verbose=False,
system=False,
concurrent=True,
deploy=False,
@@ -1258,15 +1174,14 @@ def do_init(
err=True,
)
else:
click.echo(
crayons.red(
u"Pipfile.lock ({0}) out of date, updating to ({1})…".format(
old_hash[-6:], new_hash[-6:]
),
bold=True,
),
err=True,
)
if old_hash:
msg = u"Pipfile.lock ({1}) out of date, updating to ({0})…"
else:
msg = u"Pipfile.lock is corrupted, replaced with ({0})…"
click.echo(crayons.red(
msg.format(old_hash[-6:], new_hash[-6:]),
bold=True,
), err=True)
do_lock(
system=system,
pre=pre,
@@ -1298,7 +1213,6 @@ def do_init(
system=system,
pre=pre,
keep_outdated=keep_outdated,
verbose=verbose,
write=True,
pypi_mirror=pypi_mirror,
)
@@ -1307,7 +1221,6 @@ def do_init(
requirements=requirements,
allow_global=allow_global,
skip_lock=skip_lock,
verbose=verbose,
concurrent=concurrent,
requirements_dir=requirements_dir.name,
pypi_mirror=pypi_mirror,
@@ -1332,7 +1245,6 @@ def pip_install(
allow_global=False,
ignore_hashes=False,
no_deps=True,
verbose=False,
block=True,
index=None,
pre=False,
@@ -1345,7 +1257,7 @@ def pip_install(
from notpip._vendor.pyparsing import ParseException
from .vendor.requirementslib import Requirement
if verbose:
if environments.is_verbose():
click.echo(
crayons.normal("Installing {0!r}".format(package_name), bold=True), err=True
)
@@ -1439,13 +1351,13 @@ def pip_install(
),
"sources": " ".join(prepare_pip_source_args(sources)),
"src": src,
"verbose_flag": "--verbose" if verbose else "",
"verbose_flag": "--verbose" if environments.is_verbose() else "",
"install_reqs": install_reqs
}
pip_command = "{quoted_pip} install {pre} {src} {verbose_flag} {upgrade_strategy} {no_deps} {install_reqs} {sources}".format(
**pip_args
)
if verbose:
if environments.is_verbose():
click.echo("$ {0}".format(pip_command), err=True)
cache_dir = Path(PIPENV_CACHE_DIR)
pip_config = {
@@ -1612,18 +1524,10 @@ def format_pip_output(out, r=None):
def warn_in_virtualenv():
from .environments import (
PIPENV_USE_SYSTEM,
PIPENV_VIRTUALENV,
PIPENV_VERBOSITY,
)
# Only warn if pipenv isn't already active.
pipenv_active = os.environ.get("PIPENV_ACTIVE")
if (
(PIPENV_USE_SYSTEM or PIPENV_VIRTUALENV)
and not (pipenv_active or PIPENV_VERBOSITY < 0)
):
if ((environments.PIPENV_USE_SYSTEM or environments.PIPENV_VIRTUALENV) and
not (pipenv_active or environments.is_quiet())):
click.echo(
"{0}: Pipenv found itself running within a virtual environment, "
"so it will automatically use that environment, instead of "
@@ -1714,7 +1618,6 @@ def do_install(
lock=True,
ignore_pipfile=False,
skip_lock=False,
verbose=False,
requirements=False,
sequential=False,
pre=False,
@@ -1916,7 +1819,6 @@ def do_install(
ignore_pipfile=ignore_pipfile,
system=system,
skip_lock=skip_lock,
verbose=verbose,
concurrent=concurrent,
deploy=deploy,
pre=pre,
@@ -1943,7 +1845,6 @@ def do_install(
allow_global=system,
selective_upgrade=selective_upgrade,
no_deps=False,
verbose=verbose,
pre=pre,
requirements_dir=requirements_directory.name,
index=index,
@@ -2013,7 +1914,6 @@ def do_install(
system=system,
allow_global=system,
concurrent=concurrent,
verbose=verbose,
keep_outdated=keep_outdated,
requirements_dir=requirements_directory,
deploy=deploy,
@@ -2033,7 +1933,6 @@ def do_uninstall(
lock=False,
all_dev=False,
all=False,
verbose=False,
keep_outdated=False,
pypi_mirror=None,
):
@@ -2051,7 +1950,7 @@ def do_uninstall(
click.echo(
crayons.normal(u"Un-installing all packages from virtualenv…", bold=True)
)
do_purge(allow_global=system, verbose=verbose)
do_purge(allow_global=system)
sys.exit(0)
# Uninstall [dev-packages], if --dev was provided.
if all_dev:
@@ -2077,7 +1976,7 @@ def do_uninstall(
cmd = "{0} uninstall {1} -y".format(
escape_grouped_arguments(which_pip(allow_global=system)), package_name
)
if verbose:
if environments.is_verbose():
click.echo("$ {0}".format(cmd))
c = delegator.run(cmd)
click.echo(crayons.blue(c.out))
@@ -2499,7 +2398,6 @@ def do_sync(
bare=False,
dont_upgrade=False,
user=False,
verbose=False,
clear=False,
unused=False,
sequential=False,
@@ -2530,7 +2428,6 @@ def do_sync(
requirements_dir = TemporaryDirectory(suffix="-requirements", prefix="pipenv-")
do_init(
dev=dev,
verbose=verbose,
concurrent=(not sequential),
requirements_dir=requirements_dir,
ignore_pipfile=True, # Don't check if Pipfile and lock match.
@@ -2548,7 +2445,6 @@ def do_clean(
python=None,
dry_run=False,
bare=False,
verbose=False,
pypi_mirror=None,
):
# Ensure that virtualenv is available.
@@ -2559,7 +2455,7 @@ def do_clean(
# Remove known "bad packages" from the list.
for bad_package in BAD_PACKAGES:
if bad_package in installed_package_names:
if verbose:
if environments.is_verbose():
click.echo("Ignoring {0}.".format(repr(bad_package)), err=True)
del installed_package_names[installed_package_names.index(bad_package)]
# Intelligently detect if --dev should be used or not.
+38 -11
View File
@@ -146,6 +146,12 @@ Default is to not mirror PyPI, i.e. use the real one, pypi.org. The
``--pypi-mirror`` command line flag overwrites this.
"""
PIPENV_QUIET = bool(os.environ.get("PIPENV_QUIET"))
"""If set, makes Pipenv quieter.
Default is unset, for normal verbosity. ``PIPENV_VERBOSE`` overrides this.
"""
PIPENV_SHELL = os.environ.get("PIPENV_SHELL")
"""An absolute path to the preferred shell for ``pipenv shell``.
@@ -174,11 +180,11 @@ PIPENV_VENV_IN_PROJECT = bool(os.environ.get("PIPENV_VENV_IN_PROJECT"))
Default is to create new virtual environments in a global location.
"""
PIPENV_VERBOSITY = int(os.environ.get("PIPENV_VERBOSITY", 0))
"""Verbosity setting for pipenv.
PIPENV_VERBOSE = bool(os.environ.get("PIPENV_VERBOSE"))
"""If set, makes Pipenv more wordy.
Higher values make pipenv more verbose, lower values less so. Default is 0,
for normal verbosity.
Default is unset, for normal verbosity. This takes precedence over
``PIPENV_QUIET``.
"""
PIPENV_YES = bool(os.environ.get("PIPENV_YES"))
@@ -207,14 +213,35 @@ if "PIPENV_ACTIVE" not in os.environ and not PIPENV_IGNORE_VIRTUALENVS:
PIPENV_SKIP_VALIDATION = True
# Internal, the default shell to use if shell detection fails.
PIPENV_SHELL = os.environ.get("SHELL") or os.environ.get("PYENV_SHELL")
# Internal, to tell if pyenv is installed.
PYENV_ROOT = os.environ.get("PYENV_ROOT", os.path.expanduser("~/.pyenv"))
PYENV_INSTALLED = (
bool(os.environ.get("PYENV_SHELL")) or
bool(os.environ.get("PYENV_ROOT"))
PIPENV_SHELL = (
os.environ.get("SHELL") or
os.environ.get("PYENV_SHELL") or
os.environ.get("COMSPEC")
)
# Internal, to tell whether the command line session is interactive.
SESSION_IS_INTERACTIVE = bool(os.isatty(sys.stdout.fileno()))
# Internal, consolidated verbosity representation as an integer. The default
# level is 0, increased for wordiness and decreased for terseness.
PIPENV_VERBOSITY = os.environ.get("PIPENV_VERBOSITY", "")
if PIPENV_VERBOSITY.isdigit():
PIPENV_VERBOSITY = int(PIPENV_VERBOSITY)
else:
if PIPENV_VERBOSE:
PIPENV_VERBOSITY = 1
elif PIPENV_QUIET:
PIPENV_VERBOSITY = -1
else:
PIPENV_VERBOSITY = 0
del PIPENV_QUIET
del PIPENV_VERBOSE
def is_verbose(threshold=1):
return PIPENV_VERBOSITY >= threshold
def is_quiet(threshold=-1):
return PIPENV_VERBOSITY <= threshold
+13 -19
View File
@@ -1,12 +1,13 @@
# coding: utf-8
import os
import pprint
import sys
import pipenv
from pprint import pprint
from .__version__ import __version__
from .core import project, system_which, find_python_in_path, python_version
from .core import project
from .pep508checker import lookup
from .vendor import pythonfinder
def print_utf(line):
@@ -19,32 +20,25 @@ def print_utf(line):
def get_pipenv_diagnostics():
print("<details><summary>$ pipenv --support</summary>")
print("")
print("Pipenv version: `{0!r}`".format(__version__))
print("Pipenv version: `{0!r}`".format(pipenv.__version__))
print("")
print("Pipenv location: `{0!r}`".format(os.path.dirname(pipenv.__file__)))
print("")
print("Python location: `{0!r}`".format(sys.executable))
print("")
print("Other Python installations in `PATH`:")
print("Python installations found:")
print("")
for python_v in ("2.5", "2.6", "2.7", "3.4", "3.5", "3.6", "3.7"):
found = find_python_in_path(python_v)
if found:
print(" - `{0}`: `{1}`".format(python_v, found))
found = system_which("python{0}".format(python_v), mult=True)
if found:
for f in found:
print(" - `{0}`: `{1}`".format(python_v, f))
print("")
for p in ("python", "python2", "python3", "py"):
found = system_which(p, mult=True)
for f in found:
print(" - `{0}`: `{1}`".format(python_version(f), f))
finder = pythonfinder.Finder(system=False, global_search=True)
python_paths = finder.find_all_python_versions()
for python in python_paths:
print(" - `{}`: `{}`".format(python.py_version.version, python.path))
print("")
print("PEP 508 Information:")
print("")
print("```")
pprint(lookup)
pprint.pprint(lookup)
print("```")
print("")
print("System environment variables:")
+27 -13
View File
@@ -1,7 +1,7 @@
# coding: utf-8
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import copy
import hashlib
import os
import sys
@@ -21,18 +21,16 @@ from .._compat import (
SafeFileCache,
)
from pipenv.patched.notpip._vendor.packaging.requirements import InvalidRequirement, Requirement
from pipenv.patched.notpip._vendor.packaging.version import Version, InvalidVersion, parse as parse_version
from pipenv.patched.notpip._vendor.packaging.specifiers import SpecifierSet, InvalidSpecifier, Specifier
from pipenv.patched.notpip._vendor.packaging.markers import Marker, Op, Value, Variable
from pipenv.patched.notpip._vendor.pyparsing import ParseException
from pipenv.patched.notpip._vendor.packaging.requirements import Requirement
from pipenv.patched.notpip._vendor.packaging.specifiers import SpecifierSet, Specifier
from pipenv.patched.notpip._vendor.packaging.markers import Op, Value, Variable
from pipenv.patched.notpip._internal.exceptions import InstallationError
from pipenv.patched.notpip._internal.vcs import VcsSupport
from ..cache import CACHE_DIR
from pipenv.environments import PIPENV_CACHE_DIR
from ..exceptions import NoCandidateFound
from ..utils import (fs_str, is_pinned_requirement, lookup_table, as_tuple, key_from_req,
make_install_requirement, format_requirement, dedup, clean_requires_python)
from ..utils import (fs_str, is_pinned_requirement, lookup_table,
make_install_requirement, clean_requires_python)
from .base import BaseRepository
@@ -64,15 +62,20 @@ class HashCache(SafeFileCache):
def get_hash(self, location):
# if there is no location hash (i.e., md5 / sha256 / etc) we on't want to store it
hash_value = None
can_hash = location.hash
vcs = VcsSupport()
orig_scheme = location.scheme
new_location = copy.deepcopy(location)
if orig_scheme in vcs.all_schemes:
new_location.url = new_location.url.split("+", 1)[-1]
can_hash = new_location.hash
if can_hash:
# hash url WITH fragment
hash_value = self.get(location.url)
hash_value = self.get(new_location.url)
if not hash_value:
hash_value = self._get_file_hash(location)
hash_value = self._get_file_hash(new_location)
hash_value = hash_value.encode('utf8')
if can_hash:
self.set(location.url, hash_value)
self.set(new_location.url, hash_value)
return hash_value.decode('utf8')
def _get_file_hash(self, location):
@@ -276,6 +279,13 @@ class PyPIRepository(BaseRepository):
setup_requires = {}
dist = None
if ireq.editable:
try:
from pipenv.utils import chdir
with chdir(ireq.setup_py_dir):
from setuptools.dist import distutils
distutils.core.run_setup(ireq.setup_py)
except (ImportError, InstallationError, TypeError, AttributeError):
pass
try:
dist = ireq.get_dist()
except InstallationError:
@@ -425,6 +435,10 @@ class PyPIRepository(BaseRepository):
if ireq.editable:
return set()
vcs = VcsSupport()
if ireq.link and ireq.link.scheme in vcs.all_schemes and 'ssh' in ireq.link.scheme:
return set()
if not is_pinned_requirement(ireq):
raise TypeError(
"Expected pinned requirement, got {}".format(ireq))
+6 -2
View File
@@ -249,7 +249,7 @@ class Project(object):
def is_venv_in_project(self):
return PIPENV_VENV_IN_PROJECT or (
self.project_directory
and os.path.exists(os.path.join(self.project_directory, ".venv"))
and os.path.isdir(os.path.join(self.project_directory, ".venv"))
)
@property
@@ -815,7 +815,11 @@ class Project(object):
if not os.path.exists(self.lockfile_location):
return
lockfile = self.load_lockfile(expand_env_vars=False)
try:
lockfile = self.load_lockfile(expand_env_vars=False)
except ValueError:
# Lockfile corrupted
return ""
if "_meta" in lockfile and hasattr(lockfile, "keys"):
return lockfile["_meta"].get("hash", {}).get("sha256")
# Lockfile exists but has no hash at all
+2 -5
View File
@@ -20,7 +20,6 @@ def which(*args, **kwargs):
def main():
is_verbose = "--verbose" in " ".join(sys.argv)
do_pre = "--pre" in " ".join(sys.argv)
do_clear = "--clear" in " ".join(sys.argv)
is_debug = "--debug" in " ".join(sys.argv)
@@ -36,7 +35,7 @@ def main():
os.environ["PIP_PYTHON_VERSION"] = ".".join([str(s) for s in sys.version_info[:3]])
os.environ["PIP_PYTHON_PATH"] = sys.executable
if is_verbose:
if int(os.environ.get("PIPENV_VERBOSITY", 0)) > 0:
logging.getLogger("notpip").setLevel(logging.INFO)
if is_debug:
# Shit's getting real at this point.
@@ -56,7 +55,7 @@ def main():
else None
)
def resolve(packages, pre, project, sources, verbose, clear, system):
def resolve(packages, pre, project, sources, clear, system):
return resolve_deps(
packages,
which,
@@ -64,7 +63,6 @@ def main():
pre=pre,
sources=sources,
clear=clear,
verbose=verbose,
allow_global=system,
)
@@ -81,7 +79,6 @@ def main():
pre=do_pre,
project=project,
sources=sources,
verbose=is_verbose,
clear=do_clear,
system=system,
)
+4 -4
View File
@@ -29,7 +29,7 @@ def detect_info():
raise ShellDetectionFailure
def _get_activate_script(venv):
def _get_activate_script(cmd, venv):
"""Returns the string to activate a virtualenv.
This is POSIX-only at the moment since the compat (pexpect-based) shell
@@ -37,11 +37,11 @@ def _get_activate_script(venv):
"""
# Suffix and source command for other shells.
# Support for fish shell.
if PIPENV_SHELL and "fish" in PIPENV_SHELL:
if "fish" in cmd:
suffix = ".fish"
command = "source"
# Support for csh shell.
elif PIPENV_SHELL and "csh" in PIPENV_SHELL:
elif "csh" in cmd:
suffix = ".csh"
command = "source"
else:
@@ -104,7 +104,7 @@ class Shell(object):
dims = get_terminal_size()
with temp_environ():
c = pexpect.spawn(self.cmd, ["-i"], dimensions=(dims.lines, dims.columns))
c.sendline(_get_activate_script(venv))
c.sendline(_get_activate_script(self.cmd, venv))
if args:
c.sendline(" ".join(args))
+31 -18
View File
@@ -41,6 +41,7 @@ except ImportError:
from distutils.spawn import find_executable
from contextlib import contextmanager
from . import environments
from .pep508checker import lookup
from .environments import PIPENV_MAX_ROUNDS, PIPENV_CACHE_DIR, PIPENV_MAX_RETRIES
@@ -199,7 +200,7 @@ def prepare_pip_source_args(sources, pip_args=None):
# Trust the host if it's not verified.
if not sources[0].get("verify_ssl", True):
pip_args.extend(
["--trusted-host", urlparse(sources[0]["url"]).netloc.split(":")[0]]
["--trusted-host", urlparse(sources[0]["url"]).hostname]
)
# Add additional sources as extra indexes.
if len(sources) > 1:
@@ -219,14 +220,11 @@ def actually_resolve_deps(
markers_lookup,
project,
sources,
verbose,
clear,
pre,
req_dir=None,
):
from .vendor.packaging.markers import default_environment
from .patched.notpip._internal import basecommand
from .patched.notpip._internal.cmdoptions import no_binary, only_binary
from .patched.notpip._internal.req import parse_requirements
from .patched.notpip._internal.exceptions import DistributionNotFound
from .patched.notpip._vendor.requests.exceptions import HTTPError
@@ -272,7 +270,7 @@ def actually_resolve_deps(
pip_args = []
if sources:
pip_args = prepare_pip_source_args(sources, pip_args)
if verbose:
if environments.is_verbose():
print("Using pip: {0}".format(" ".join(pip_args)))
with NamedTemporaryFile(
mode="w",
@@ -295,7 +293,7 @@ def actually_resolve_deps(
constraints_file, finder=pypi.finder, session=pypi.session, options=pip_options
)
constraints = [c for c in constraints]
if verbose:
if environments.is_verbose():
logging.log.verbose = True
piptools_logging.log.verbose = True
resolved_tree = set()
@@ -343,7 +341,6 @@ def venv_resolve_deps(
which,
project,
pre=False,
verbose=False,
clear=False,
allow_global=False,
pypi_mirror=None,
@@ -359,7 +356,7 @@ def venv_resolve_deps(
escape_grouped_arguments(which("python", allow_global=allow_global)),
resolver,
"--pre" if pre else "",
"--verbose" if verbose else "",
"--verbose" if (environments.is_verbose()) else "",
"--clear" if clear else "",
"--system" if allow_global else "",
)
@@ -371,13 +368,13 @@ def venv_resolve_deps(
try:
assert c.return_code == 0
except AssertionError:
if verbose:
if environments.is_verbose():
click_echo(c.out, err=True)
click_echo(c.err, err=True)
else:
click_echo(c.err[int(len(c.err) / 2) - 1 :], err=True)
click_echo(c.err[(int(len(c.err) / 2) - 1):], err=True)
sys.exit(c.return_code)
if verbose:
if environments.is_verbose():
click_echo(c.out.split("RESULTS:")[0], err=True)
try:
return json.loads(c.out.split("RESULTS:")[1].strip())
@@ -391,7 +388,6 @@ def resolve_deps(
which,
project,
sources=None,
verbose=False,
python=False,
clear=False,
pre=False,
@@ -420,7 +416,6 @@ def resolve_deps(
markers_lookup,
project,
sources,
verbose,
clear,
pre,
req_dir=req_dir,
@@ -443,7 +438,6 @@ def resolve_deps(
markers_lookup,
project,
sources,
verbose,
clear,
pre,
req_dir=req_dir,
@@ -485,7 +479,7 @@ def resolve_deps(
collected_hashes.append(release["digests"]["sha256"])
collected_hashes = ["sha256:" + s for s in collected_hashes]
except (ValueError, KeyError, ConnectionError):
if verbose:
if environments.is_verbose():
click_echo(
"{0}: Error generating hash for {1}".format(
crayons.red("Warning", bold=True), name
@@ -1204,7 +1198,6 @@ def get_vcs_deps(
project,
pip_freeze=None,
which=None,
verbose=False,
clear=False,
pre=False,
allow_global=False,
@@ -1357,7 +1350,27 @@ def is_virtual_environment(path):
if not path.is_dir():
return False
for bindir_name in ('bin', 'Scripts'):
for python_like in path.joinpath(bindir_name).glob('python*'):
if python_like.is_file() and os.access(str(python_like), os.X_OK):
for python in path.joinpath(bindir_name).glob('python*'):
try:
exeness = python.is_file() and os.access(str(python), os.X_OK)
except OSError:
exeness = False
if exeness:
return True
return False
@contextmanager
def chdir(path):
"""Context manager to change working directories."""
from ._compat import Path
if not path:
return
prev_cwd = Path.cwd().as_posix()
if isinstance(path, Path):
path = path.as_posix()
os.chdir(str(path))
try:
yield
finally:
os.chdir(prev_cwd)
+15 -4
View File
@@ -2,10 +2,21 @@
These packages are copied as-is from upstream to reduce Pipenv dependencies.
They should always be kept synced with upstream. DO NOT MODIFY DIRECTLY! If
you need to patch anything, move the package to `patched`.
you need to patch anything, move the package to `patched` and generate a
patch for it using `git diff -p <dependency_root_dir>`. This patch belongs
in `./pipenv/tasks/vendoring/patches/patched/<packagename.patchdesc>.patch`.
Known vendored versions:
To add a vendored dependency or to update a single dependency, use the
vendoring scripts:
```
pipenv run inv vendoring.update --package="pkgname==versionnum"
```
- python-dotenv: 0.8.2
This will automatically pin the package in `./pipenv/vendor/vendor.txt`
or it will update the pin if the package is already present, and it will
then update the package and download any necessary licenses (if available).
Note that this will not download any dependencies, you must add those each
individually.
When updating, update the corresponding LICENSE files as well.
When updating, ensure that the corresponding LICENSE files are still
up-to-date.
+12
View File
@@ -0,0 +1,12 @@
Copyright (c) 2015, Daniel Greenfeld
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of cached-property nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+152
View File
@@ -0,0 +1,152 @@
# -*- coding: utf-8 -*-
__author__ = "Daniel Greenfeld"
__email__ = "pydanny@gmail.com"
__version__ = "1.4.3"
__license__ = "BSD"
from time import time
import threading
try:
import asyncio
except (ImportError, SyntaxError):
asyncio = None
class cached_property(object):
"""
A property that is only computed once per instance and then replaces itself
with an ordinary attribute. Deleting the attribute resets the property.
Source: https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76
""" # noqa
def __init__(self, func):
self.__doc__ = getattr(func, "__doc__")
self.func = func
def __get__(self, obj, cls):
if obj is None:
return self
if asyncio and asyncio.iscoroutinefunction(self.func):
return self._wrap_in_coroutine(obj)
value = obj.__dict__[self.func.__name__] = self.func(obj)
return value
def _wrap_in_coroutine(self, obj):
@asyncio.coroutine
def wrapper():
future = asyncio.ensure_future(self.func(obj))
obj.__dict__[self.func.__name__] = future
return future
return wrapper()
class threaded_cached_property(object):
"""
A cached_property version for use in environments where multiple threads
might concurrently try to access the property.
"""
def __init__(self, func):
self.__doc__ = getattr(func, "__doc__")
self.func = func
self.lock = threading.RLock()
def __get__(self, obj, cls):
if obj is None:
return self
obj_dict = obj.__dict__
name = self.func.__name__
with self.lock:
try:
# check if the value was computed before the lock was acquired
return obj_dict[name]
except KeyError:
# if not, do the calculation and release the lock
return obj_dict.setdefault(name, self.func(obj))
class cached_property_with_ttl(object):
"""
A property that is only computed once per instance and then replaces itself
with an ordinary attribute. Setting the ttl to a number expresses how long
the property will last before being timed out.
"""
def __init__(self, ttl=None):
if callable(ttl):
func = ttl
ttl = None
else:
func = None
self.ttl = ttl
self._prepare_func(func)
def __call__(self, func):
self._prepare_func(func)
return self
def __get__(self, obj, cls):
if obj is None:
return self
now = time()
obj_dict = obj.__dict__
name = self.__name__
try:
value, last_updated = obj_dict[name]
except KeyError:
pass
else:
ttl_expired = self.ttl and self.ttl < now - last_updated
if not ttl_expired:
return value
value = self.func(obj)
obj_dict[name] = (value, now)
return value
def __delete__(self, obj):
obj.__dict__.pop(self.__name__, None)
def __set__(self, obj, value):
obj.__dict__[self.__name__] = (value, time())
def _prepare_func(self, func):
self.func = func
if func:
self.__doc__ = func.__doc__
self.__name__ = func.__name__
self.__module__ = func.__module__
# Aliases to make cached_property_with_ttl easier to use
cached_property_ttl = cached_property_with_ttl
timed_cached_property = cached_property_with_ttl
class threaded_cached_property_with_ttl(cached_property_with_ttl):
"""
A cached_property version for use in environments where multiple threads
might concurrently try to access the property.
"""
def __init__(self, ttl=None):
super(threaded_cached_property_with_ttl, self).__init__(ttl)
self.lock = threading.RLock()
def __get__(self, obj, cls):
with self.lock:
return super(threaded_cached_property_with_ttl, self).__get__(obj, cls)
# Alias to make threaded_cached_property_with_ttl easier to use
threaded_cached_property_ttl = threaded_cached_property_with_ttl
timed_threaded_cached_property = threaded_cached_property_with_ttl
+1 -1
View File
@@ -1,6 +1,6 @@
from __future__ import print_function, absolute_import
__version__ = "0.1.4.dev0"
__version__ = "1.0.0"
__all__ = ["Finder", "WindowsFinder", "SystemPath", "InvalidPythonVersion"]
from .pythonfinder import Finder
-14
View File
@@ -1,14 +0,0 @@
# Taken from pip: https://github.com/pypa/pip/blob/95bcf8c5f6394298035a7332c441868f3b0169f4/src/pip/_vendor/Makefile
all: clean vendor
clean:
@# Delete vendored items
find . -maxdepth 1 -mindepth 1 -type d -exec rm -rf {} \;
vendor:
@# Install vendored libraries
pip install -t . -r vendor.txt
@# Cleanup .egg-info directories
rm -rf *.egg-info
rm -rf *.dist-info
-1
View File
@@ -1 +0,0 @@
-e git+https://github.com/zooba/pep514tools.git@320e48745660b696e2dcaee888fc2e516b435e48#egg=pep514tools
+1
View File
@@ -4,4 +4,5 @@ from __future__ import print_function, absolute_import
class InvalidPythonVersion(Exception):
"""Raised when parsing an invalid python version"""
pass
+67 -12
View File
@@ -3,7 +3,8 @@ from __future__ import print_function, absolute_import
import abc
import operator
import six
from ..utils import KNOWN_EXTS
from itertools import chain
from ..utils import KNOWN_EXTS, unnest
@six.add_metaclass(abc.ABCMeta)
@@ -39,37 +40,91 @@ class BasePath(object):
for ext in KNOWN_EXTS
]
children = self.children
found = next((children[(self.path / child).as_posix()] for child in valid_names if (self.path / child).as_posix() in children), None)
found = next(
(
children[(self.path / child).as_posix()]
for child in valid_names
if (self.path / child).as_posix() in children
),
None,
)
return found
def find_python_version(self, major, minor=None, patch=None, pre=None, dev=None):
def find_all_python_versions(
self, major=None, minor=None, patch=None, pre=None, dev=None, arch=None
):
"""Search for a specific python version on the path. Return all copies
:param major: Major python version to search for.
:type major: int
:param int minor: Minor python version to search for, defaults to None
:param int patch: Patch python version to search for, defaults to None
:param bool pre: Search for prereleases (default None) - prioritize releases if None
:param bool dev: Search for devreleases (default None) - prioritize releases if None
:param str arch: Architecture to include, e.g. '64bit', defaults to None
:return: A list of :class:`~pythonfinder.models.PathEntry` instances matching the version requested.
:rtype: List[:class:`~pythonfinder.models.PathEntry`]
"""
call_method = (
"find_all_python_versions" if self.is_dir else "find_python_version"
)
sub_finder = operator.methodcaller(
call_method, major, minor=minor, patch=patch, pre=pre, dev=dev, arch=arch
)
if not self.is_dir:
return sub_finder(self)
path_filter = filter(None, (sub_finder(p) for p in self.children.values()))
version_sort = operator.attrgetter("as_python.version_sort")
return [c for c in sorted(path_filter, key=version_sort, reverse=True)]
def find_python_version(
self, major=None, minor=None, patch=None, pre=None, dev=None, arch=None
):
"""Search or self for the specified Python version and return the first match.
:param major: Major version number.
:type major: int
:param minor: Minor python version, defaults to None
:param minor: int, optional
:param patch: Patch python version, defaults to None
:param patch: int, optional
:param int minor: Minor python version to search for, defaults to None
:param int patch: Patch python version to search for, defaults to None
:param bool pre: Search for prereleases (default None) - prioritize releases if None
:param bool dev: Search for devreleases (default None) - prioritize releases if None
:param str arch: Architecture to include, e.g. '64bit', defaults to None
:returns: A :class:`~pythonfinder.models.PathEntry` instance matching the version requested.
"""
version_matcher = operator.methodcaller(
"matches", major, minor=minor, patch=patch, pre=pre, dev=dev
"matches",
major=major,
minor=minor,
patch=patch,
pre=pre,
dev=dev,
arch=arch,
)
is_py = operator.attrgetter("is_python")
py_version = operator.attrgetter("as_python")
if not self.is_dir:
if self.is_python and self.as_python.matches(major, minor=minor, patch=patch, pre=pre, dev=dev):
if self.is_python and self.as_python and version_matcher(self.as_python):
return self
return
finder = ((child, child.as_python) for child in self.children.values() if child.is_python and child.as_python)
finder = (
(child, child.as_python)
for child in unnest(self.pythons.values())
if child.as_python
)
py_filter = filter(
None, filter(lambda child: version_matcher(child[1]), finder)
)
version_sort = operator.attrgetter("version")
version_sort = operator.attrgetter("version_sort")
return next(
(c[0] for c in sorted(py_filter, key=lambda child: child[1].version, reverse=True)), None
(
c[0]
for c in sorted(
py_filter, key=lambda child: child[1].version_sort, reverse=True
)
),
None,
)
+168 -67
View File
@@ -1,10 +1,13 @@
# -*- coding=utf-8 -*-
from __future__ import print_function, absolute_import
import attr
import copy
import operator
import os
import sys
from collections import defaultdict
from cached_property import cached_property
from itertools import chain
from . import BasePath
from .python import PythonVersion
from ..environment import PYENV_INSTALLED, PYENV_ROOT
@@ -13,9 +16,10 @@ from ..utils import (
optional_instance_of,
filter_pythons,
path_is_known_executable,
is_python_name,
looks_like_python,
ensure_path,
fs_str
fs_str,
unnest,
)
try:
@@ -26,37 +30,69 @@ except ImportError:
@attr.s
class SystemPath(object):
global_search = attr.ib(default=True)
paths = attr.ib(default=attr.Factory(defaultdict))
_executables = attr.ib(default=attr.Factory(list))
_python_executables = attr.ib(default=attr.Factory(list))
path_order = attr.ib(default=attr.Factory(list))
python_version_dict = attr.ib()
python_version_dict = attr.ib(default=attr.Factory(defaultdict))
only_python = attr.ib(default=False)
pyenv_finder = attr.ib(default=None, validator=optional_instance_of("PyenvPath"))
system = attr.ib(default=False)
_version_dict = attr.ib(default=attr.Factory(defaultdict))
@property
__finders = attr.ib(default=attr.Factory(dict))
def _register_finder(self, finder_name, finder):
if finder_name not in self.__finders:
self.__finders[finder_name] = finder
@cached_property
def executables(self):
if not self._executables:
self._executables = [p for p in self.paths.values() if p.is_executable]
return self._executables
self.executables = [
p
for p in chain(*(child.children.values() for child in self.paths.values()))
if p.is_executable
]
return self.executables
@property
@cached_property
def python_executables(self):
if not self._python_executables:
self._python_executables = [p for p in self.paths.values() if p.is_python]
python_executables = {}
for child in self.paths.values():
if child.pythons:
python_executables.update(dict(child.pythons))
for finder_name, finder in self.__finders.items():
if finder.pythons:
python_executables.update(dict(finder.pythons))
self._python_executables = python_executables
return self._python_executables
@python_version_dict.default
def get_python_version_dict(self):
version_dict = defaultdict(list)
for p in self.python_executables:
try:
version_object = PythonVersion.from_path(p)
except (ValueError, InvalidPythonVersion):
@cached_property
def version_dict(self):
self._version_dict = defaultdict(list)
for finder_name, finder in self.__finders.items():
for version, entry in finder.versions.items():
if finder_name == "windows":
if entry not in self._version_dict[version]:
self._version_dict[version].append(entry)
continue
if isinstance(entry, VersionPath):
for path in entry.paths.values():
if path not in self._version_dict[version] and path.is_python:
self._version_dict[version].append(path)
continue
continue
elif entry not in self._version_dict[version] and entry.is_python:
self._version_dict[version].append(entry)
for p, entry in self.python_executables.items():
version = entry.as_python
if not version:
continue
version_dict[version_object.version_tuple].append(version_object)
return version_dict
version = version.version_tuple
if version and entry not in self._version_dict[version]:
self._version_dict[version].append(entry)
return self._version_dict
def __attrs_post_init__(self):
#: slice in pyenv
@@ -66,24 +102,22 @@ class SystemPath(object):
self._setup_windows()
if PYENV_INSTALLED:
self._setup_pyenv()
venv = os.environ.get('VIRTUAL_ENV')
if os.name == 'nt':
bin_dir = 'Scripts'
venv = os.environ.get("VIRTUAL_ENV")
if os.name == "nt":
bin_dir = "Scripts"
else:
bin_dir = 'bin'
if venv:
p = Path(venv)
bin_dir = "bin"
if venv and (self.system or self.global_search):
p = ensure_path(venv)
self.path_order = [(p / bin_dir).as_posix()] + self.path_order
self.paths[p] = PathEntry.create(
path=p, is_root=True, only_python=False
)
self.paths[p] = PathEntry.create(path=p, is_root=True, only_python=False)
if self.system:
syspath = Path(sys.executable)
syspath_bin = syspath.parent
if syspath_bin.name != bin_dir and syspath_bin.joinpath(bin_dir).exists():
syspath_bin = syspath_bin / bin_dir
self.path_order = [syspath_bin.as_posix()] + self.path_order
self.paths[syspath_bin.as_posix()] = PathEntry.create(
self.paths[syspath_bin] = PathEntry.create(
path=syspath_bin, is_root=True, only_python=False
)
@@ -94,7 +128,10 @@ class SystemPath(object):
(p for p in reversed(self.path_order) if PYENV_ROOT.lower() in p.lower()),
None,
)
pyenv_index = self.path_order.index(last_pyenv)
try:
pyenv_index = self.path_order.index(last_pyenv)
except ValueError:
return
self.pyenv_finder = PyenvFinder.create(root=PYENV_ROOT)
# paths = (v.paths.values() for v in self.pyenv_finder.versions.values())
root_paths = (
@@ -106,6 +143,7 @@ class SystemPath(object):
before_path + [p.path.as_posix() for p in root_paths] + after_path
)
self.paths.update({p.path: p for p in root_paths})
self._register_finder("pyenv", self.pyenv_finder)
def _setup_windows(self):
from .windows import WindowsFinder
@@ -115,15 +153,17 @@ class SystemPath(object):
path_addition = [p.path.as_posix() for p in root_paths]
self.path_order = self.path_order[:] + path_addition
self.paths.update({p.path: p for p in root_paths})
self._register_finder("windows", self.windows_finder)
def get_path(self, path):
path = Path(path)
path = ensure_path(path)
_path = self.paths.get(path.as_posix())
if not _path and path.as_posix() in self.path_order:
self.paths[path.as_posix()] = PathEntry.create(
path=path.resolve(), is_root=True, only_python=self.only_python
_path = PathEntry.create(
path=path.absolute(), is_root=True, only_python=self.only_python
)
return self.paths.get(path.as_posix())
self.paths[path.as_posix()] = _path
return _path
def find_all(self, executable):
"""Search the path for an executable. Return all copies.
@@ -147,60 +187,91 @@ class SystemPath(object):
filtered = filter(None, (sub_which(self.get_path(k)) for k in self.path_order))
return next((f for f in filtered), None)
def find_all_python_versions(self, major, minor=None, patch=None, pre=None, dev=None):
def find_all_python_versions(
self, major=None, minor=None, patch=None, pre=None, dev=None, arch=None
):
"""Search for a specific python version on the path. Return all copies
:param major: Major python version to search for.
:type major: int
:param minor: Minor python version to search for, defaults to None
:param minor: int, optional
:param path: Patch python version to search for, defaults to None
:param path: int, optional
:param int minor: Minor python version to search for, defaults to None
:param int patch: Patch python version to search for, defaults to None
:param bool pre: Search for prereleases (default None) - prioritize releases if None
:param bool dev: Search for devreleases (default None) - prioritize releases if None
:param str arch: Architecture to include, e.g. '64bit', defaults to None
:return: A list of :class:`~pythonfinder.models.PathEntry` instances matching the version requested.
:rtype: List[:class:`~pythonfinder.models.PathEntry`]
"""
sub_finder = operator.methodcaller(
"find_python_version", major, minor=minor, patch=patch, pre=pre, dev=dev
"find_all_python_versions",
major,
minor=minor,
patch=patch,
pre=pre,
dev=dev,
arch=arch,
)
if os.name == "nt" and self.windows_finder:
windows_finder_version = sub_finder(self.windows_finder)
if windows_finder_version:
return windows_finder_version
paths = (self.get_path(k) for k in self.path_order)
path_filter = filter(None, (sub_finder(p) for p in paths if p is not None))
version_sort = operator.attrgetter("as_python.version")
path_filter = filter(
None, unnest((sub_finder(p) for p in paths if p is not None))
)
version_sort = operator.attrgetter("as_python.version_sort")
return [c for c in sorted(path_filter, key=version_sort, reverse=True)]
def find_python_version(self, major, minor=None, patch=None, pre=None, dev=None):
def find_python_version(
self, major=None, minor=None, patch=None, pre=None, dev=None, arch=None
):
"""Search for a specific python version on the path.
:param major: Major python version to search for.
:type major: int
:param minor: Minor python version to search for, defaults to None
:param minor: int, optional
:param path: Patch python version to search for, defaults to None
:param path: int, optional
:param int minor: Minor python version to search for, defaults to None
:param int patch: Patch python version to search for, defaults to None
:param bool pre: Search for prereleases (default None) - prioritize releases if None
:param bool dev: Search for devreleases (default None) - prioritize releases if None
:param str arch: Architecture to include, e.g. '64bit', defaults to None
:return: A :class:`~pythonfinder.models.PathEntry` instance matching the version requested.
:rtype: :class:`~pythonfinder.models.PathEntry`
"""
sub_finder = operator.methodcaller(
"find_python_version", major, minor=minor, patch=patch, pre=pre, dev=dev
"find_python_version",
major,
minor=minor,
patch=patch,
pre=pre,
dev=dev,
arch=arch,
)
if major and minor and patch:
_tuple_pre = pre if pre is not None else False
_tuple_dev = dev if dev is not None else False
version_tuple = (major, minor_, patch, _tuple_pre, _tuple_dev)
version_tuple_pre = (major, minor, patch, True, False)
if os.name == "nt" and self.windows_finder:
windows_finder_version = sub_finder(self.windows_finder)
if windows_finder_version:
return windows_finder_version
paths = (self.get_path(k) for k in self.path_order)
path_filter = filter(None, (sub_finder(p) for p in paths if p is not None))
version_sort = operator.attrgetter("as_python.version")
return next(
version_sort = operator.attrgetter("as_python.version_sort")
ver = next(
(c for c in sorted(path_filter, key=version_sort, reverse=True)), None
)
if ver:
if ver.as_python.version_tuple[:5] in self.python_version_dict:
self.python_version_dict[ver.as_python.version_tuple[:5]].append(ver)
else:
self.python_version_dict[ver.as_python.version_tuple[:5]] = [ver]
return ver
@classmethod
def create(cls, path=None, system=False, only_python=False):
def create(cls, path=None, system=False, only_python=False, global_search=True):
"""Create a new :class:`pythonfinder.models.SystemPath` instance.
:param path: Search path to prepend when searching, defaults to None
@@ -214,7 +285,9 @@ class SystemPath(object):
"""
path_entries = defaultdict(PathEntry)
paths = os.environ.get("PATH").split(os.pathsep)
paths = []
if global_search:
paths = os.environ.get("PATH").split(os.pathsep)
if path:
paths = [path] + paths
_path_objects = [ensure_path(p.strip('"')) for p in paths]
@@ -227,7 +300,13 @@ class SystemPath(object):
for p in _path_objects
}
)
return cls(paths=path_entries, path_order=paths, only_python=only_python, system=system)
return cls(
paths=path_entries,
path_order=paths,
only_python=only_python,
system=system,
global_search=global_search,
)
@attr.s
@@ -237,10 +316,10 @@ class PathEntry(BasePath):
is_root = attr.ib(default=True)
only_python = attr.ib(default=False)
py_version = attr.ib(default=None)
pythons = attr.ib(default=None)
pythons = attr.ib()
def __str__(self):
return fs_str('{0}'.format(self.path.as_posix()))
return fs_str("{0}".format(self.path.as_posix()))
def _filter_children(self):
if self.only_python:
@@ -249,21 +328,38 @@ class PathEntry(BasePath):
children = self.path.iterdir()
return children
@property
@cached_property
def children(self):
if not self._children and self.is_dir and self.is_root:
self._children = {
child.as_posix(): PathEntry(path=child, is_root=False)
child.as_posix(): PathEntry.create(path=child, is_root=False)
for child in self._filter_children()
}
elif not self.is_dir:
self._children = {self.path.as_posix(): self}
return self._children
@property
@pythons.default
def get_pythons(self):
pythons = defaultdict()
if self.is_dir:
for path, entry in self.children.items():
_path = ensure_path(entry.path)
if entry.is_python:
pythons[_path.as_posix()] = entry
else:
if self.is_python:
_path = ensure_path(self.path)
pythons[_path.as_posix()] = copy.deepcopy(self)
return pythons
@cached_property
def as_python(self):
if not self.is_dir and self.is_python:
if not self.py_version:
try:
from .python import PythonVersion
self.py_version = PythonVersion.from_path(self.path)
except (ValueError, InvalidPythonVersion):
self.py_version = None
@@ -286,9 +382,10 @@ class PathEntry(BasePath):
"""
target = ensure_path(path)
_new = cls(
path=target, is_root=is_root, only_python=only_python, pythons=pythons
)
creation_args = {"path": target, "is_root": is_root, "only_python": only_python}
if pythons:
creation_args["pythons"] = pythons
_new = cls(**creation_args)
if pythons and only_python:
children = {}
for pth, python in pythons.items():
@@ -299,22 +396,26 @@ class PathEntry(BasePath):
_new._children = children
return _new
@property
@cached_property
def name(self):
return self.path.name
@property
@cached_property
def is_dir(self):
return self.path.is_dir()
try:
ret_val = self.path.is_dir()
except OSError:
ret_val = False
return ret_val
@property
@cached_property
def is_executable(self):
return path_is_known_executable(self.path)
@property
@cached_property
def is_python(self):
return self.is_executable and (
self.py_version or is_python_name(self.path.name)
self.py_version or looks_like_python(self.path.name)
)
+14 -1
View File
@@ -18,6 +18,7 @@ except ImportError:
class PyenvFinder(BaseFinder):
root = attr.ib(default=None, validator=optional_instance_of(Path))
versions = attr.ib()
pythons = attr.ib()
@versions.default
def get_versions(self):
@@ -31,9 +32,21 @@ class PyenvFinder(BaseFinder):
version.get("is_prerelease"),
version.get("is_devrelease"),
)
versions[version_tuple] = VersionPath.create(path=p.resolve(), only_python=True)
versions[version_tuple] = VersionPath.create(
path=p.resolve(), only_python=True
)
return versions
@pythons.default
def get_pythons(self):
pythons = defaultdict()
for v in self.versions.values():
for p in v.paths.values():
_path = ensure_path(p.path)
if p.is_python:
pythons[_path] = p
return pythons
@classmethod
def create(cls, root):
root = ensure_path(root)
+58 -6
View File
@@ -2,6 +2,7 @@
from __future__ import print_function, absolute_import
import attr
import copy
from collections import defaultdict
import platform
from packaging.version import parse as parse_version, Version
from ..environment import SYSTEM_ARCH
@@ -18,7 +19,7 @@ except ImportError:
class PythonVersion(object):
major = attr.ib(default=0)
minor = attr.ib(default=None)
patch = attr.ib(default=None)
patch = attr.ib(default=0)
is_prerelease = attr.ib(default=False)
is_postrelease = attr.ib(default=False)
is_devrelease = attr.ib(default=False)
@@ -27,6 +28,24 @@ class PythonVersion(object):
comes_from = attr.ib(default=None)
executable = attr.ib(default=None)
@property
def version_sort(self):
"""version_sort tuple for sorting against other instances of the same class.
Returns a tuple of the python version but includes a point for non-dev,
and a point for non-prerelease versions. So released versions will have 2 points
for this value. E.g. `(3, 6, 6, 2)` is a release, `(3, 6, 6, 1)` is a prerelease,
`(3, 6, 6, 0)` is a dev release, and `(3, 6, 6, 3)` is a postrelease.
"""
release_sort = 2
if self.is_postrelease:
release_sort = 3
elif self.is_prerelease:
release_sort = 1
elif self.is_devrelease:
release_sort = 0
return (self.major, self.minor, self.patch if self.patch else 0, release_sort)
@property
def version_tuple(self):
"""Provides a version tuple for using as a dictionary key.
@@ -43,13 +62,18 @@ class PythonVersion(object):
self.is_devrelease,
)
def matches(self, major, minor=None, patch=None, pre=False, dev=False):
def matches(
self, major=None, minor=None, patch=None, pre=False, dev=False, arch=None
):
if arch and arch.isdigit():
arch = "{0}bit".format(arch)
return (
self.major == major
(major is None or self.major == major)
and (minor is None or self.minor == minor)
and (patch is None or self.patch == patch)
and (pre is None or self.is_prerelease == pre)
and (dev is None or self.is_devrelease == dev)
and (arch is None or self.architecture == arch)
)
def as_major(self):
@@ -77,7 +101,7 @@ class PythonVersion(object):
"""
try:
version = parse_version(version)
version = parse_version(str(version))
except TypeError:
raise ValueError("Unable to parse version: %s" % version)
if not version or not version.release:
@@ -119,7 +143,7 @@ class PythonVersion(object):
from .path import PathEntry
if not isinstance(path, PathEntry):
path = PathEntry(path)
path = PathEntry.create(path, is_root=False, only_python=True)
if not path.is_python:
raise ValueError("Not a valid python path: %s" % path.path)
return
@@ -154,7 +178,7 @@ class PythonVersion(object):
creation_dict.update(
{
"architecture": getattr(
launcher_entry, "sys_architecture", SYSTEM_ARCH
launcher_entry.info, "sys_architecture", SYSTEM_ARCH
),
"executable": exe_path,
}
@@ -167,4 +191,32 @@ class PythonVersion(object):
@classmethod
def create(cls, **kwargs):
if "architecture" in kwargs:
if kwargs["architecture"].isdigit():
kwargs["architecture"] = "{0}bit".format(kwargs["architecture"])
return cls(**kwargs)
@attr.s
class VersionMap(object):
versions = attr.ib(default=attr.Factory(defaultdict(list)))
def add_entry(self, entry):
version = entry.as_python
if version:
entries = versions[version.version_tuple]
paths = {p.path for p in self.versions.get(version.version_tuple, [])}
if entry.path not in paths:
self.versions[version.version_tuple].append(entry)
def merge(self, target):
for version, entries in target.versions.items():
if version not in self.versions:
self.versions[version] = entries
else:
current_entries = {p.path for p in self.versions.get(version)}
new_entries = {p.path for p in entries}
new_entries -= current_entries
self.versions[version].append(
[e for e in entries if e.path in new_entries]
)
+33 -5
View File
@@ -5,7 +5,7 @@ import operator
from collections import defaultdict
from . import BaseFinder
from .path import PathEntry
from .python import PythonVersion
from .python import PythonVersion, VersionMap
from ..exceptions import InvalidPythonVersion
from ..utils import ensure_path
@@ -15,17 +15,37 @@ class WindowsFinder(BaseFinder):
paths = attr.ib(default=attr.Factory(list))
version_list = attr.ib(default=attr.Factory(list))
versions = attr.ib()
pythons = attr.ib()
def find_python_version(self, major, minor=None, patch=None, pre=None, dev=None):
def find_all_python_versions(
self, major=None, minor=None, patch=None, pre=None, dev=None, arch=None
):
version_matcher = operator.methodcaller(
"matches", major, minor=minor, patch=patch, pre=pre, dev=dev
"matches",
major=major,
minor=minor,
patch=patch,
pre=pre,
dev=dev,
arch=arch,
)
py_filter = filter(
None, filter(lambda c: version_matcher(c), self.version_list)
)
version_sort = operator.attrgetter("version")
version_sort = operator.attrgetter("version_sort")
return [c.comes_from for c in sorted(py_filter, key=version_sort, reverse=True)]
def find_python_version(
self, major=None, minor=None, patch=None, pre=None, dev=None, arch=None
):
return next(
(c.comes_from for c in sorted(py_filter, key=version_sort, reverse=True)), None
(
v
for v in self.find_all_python_versions(
major=major, minor=minor, patch=patch, pre=pre, dev=dev, arch=arch
)
),
None,
)
@versions.default
@@ -52,6 +72,14 @@ class WindowsFinder(BaseFinder):
self.paths.append(base_dir)
return versions
@pythons.default
def get_pythons(self):
pythons = defaultdict()
for version in self.version_list:
_path = ensure_path(version.comes_from.path)
pythons[_path.as_posix()] = version.comes_from
return pythons
@classmethod
def create(cls):
return cls()
+78 -25
View File
@@ -2,12 +2,31 @@
from __future__ import print_function, absolute_import
import os
import six
import operator
from .models import SystemPath
class Finder(object):
def __init__(self, path=None, system=False):
def __init__(self, path=None, system=False, global_search=True):
"""Finder A cross-platform Finder for locating python and other executables.
Searches for python and other specified binaries starting in `path`, if supplied,
but searching the bin path of `sys.executable` if `system=True`, and then
searching in the `os.environ['PATH']` if `global_search=True`. When `global_search`
is `False`, this search operation is restricted to the allowed locations of
`path` and `system`.
:param path: A bin-directory search location, defaults to None
:param path: str, optional
:param system: Whether to include the bin-dir of `sys.executable`, defaults to False
:param system: bool, optional
:param global_search: Whether to search the global path from os.environ, defaults to True
:param global_search: bool, optional
:returns: a :class:`~pythonfinder.pythonfinder.Finder` object.
"""
self.path_prepend = path
self.global_search = global_search
self.system = system
self._system_path = None
self._windows_finder = None
@@ -16,7 +35,9 @@ class Finder(object):
def system_path(self):
if not self._system_path:
self._system_path = SystemPath.create(
path=self.path_prepend, system=self.system
path=self.path_prepend,
system=self.system,
global_search=self.global_search,
)
return self._system_path
@@ -31,38 +52,70 @@ class Finder(object):
def which(self, exe):
return self.system_path.which(exe)
def find_python_version(self, major, minor=None, patch=None, pre=None, dev=None):
def find_python_version(
self, major, minor=None, patch=None, pre=None, dev=None, arch=None
):
from .models import PythonVersion
if (
major
and not minor
and not patch
and not pre
and not dev
and isinstance(major, six.string_types)
isinstance(major, six.string_types)
and pre is None
and minor is None
and dev is None
and patch is None
):
from .models import PythonVersion
version_dict = {}
if "." in major:
version_dict = PythonVersion.parse(major)
elif len(major) == 1:
version_dict = {
'major': int(major),
'minor': None,
'patch': None,
'is_prerelease': False,
'is_devrelease': False
}
if arch is None and "-" in major:
major, arch = major.rsplit("-", 1)
if not arch.isdigit():
major = "{0}-{1}".format(major, arch)
else:
arch = "{0}bit".format(arch)
version_dict = PythonVersion.parse(major)
major = version_dict.get("major", major)
minor = version_dict.get("minor", minor)
patch = version_dict.get("patch", patch)
pre = version_dict.get("is_prerelease", pre)
dev = version_dict.get("is_devrelease", dev)
pre = version_dict.get("is_prerelease", pre) if pre is None else pre
dev = version_dict.get("is_devrelease", dev) if dev is None else dev
arch = version_dict.get("architecture", arch) if arch is None else arch
if os.name == "nt":
match = self.windows_finder.find_python_version(
major, minor=minor, patch=patch, pre=pre, dev=dev
major, minor=minor, patch=patch, pre=pre, dev=dev, arch=arch
)
if match:
return match
return self.system_path.find_python_version(
major, minor=minor, patch=patch, pre=pre, dev=dev
major=major, minor=minor, patch=patch, pre=pre, dev=dev, arch=arch
)
def find_all_python_versions(
self, major=None, minor=None, patch=None, pre=None, dev=None, arch=None
):
version_sort = operator.attrgetter("as_python.version_sort")
python_version_dict = getattr(self.system_path, "python_version_dict")
if python_version_dict:
paths = filter(
None,
[
path
for version in python_version_dict.values()
for path in version
if path.as_python
],
)
paths = sorted(paths, key=version_sort, reverse=True)
return paths
versions = self.system_path.find_all_python_versions(
major=major, minor=minor, patch=patch, pre=pre, dev=dev, arch=arch
)
if not isinstance(versions, list):
versions = [versions]
paths = sorted(versions, key=version_sort, reverse=True)
path_map = {}
for path in paths:
try:
resolved_path = path.path.resolve()
except OSError:
resolved_path = path.path.absolute()
if not path_map.get(resolved_path.as_posix()):
path_map[resolved_path.as_posix()] = path
return list(path_map.values())
+17 -8
View File
@@ -8,6 +8,7 @@ import subprocess
import sys
from fnmatch import fnmatch
from .exceptions import InvalidPythonVersion
from itertools import chain
try:
from pathlib import Path
@@ -31,10 +32,7 @@ def _run(cmd):
"""
encoding = locale.getdefaultlocale()[1] or "utf-8"
c = subprocess.Popen(
cmd,
env=os.environ.copy(),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cmd, env=os.environ.copy(), stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
out, err = c.communicate()
return out.decode(encoding).strip(), err.decode(encoding).strip()
@@ -72,7 +70,7 @@ def path_is_known_executable(path):
)
def is_python_name(name):
def looks_like_python(name):
rules = ["*python", "*python?", "*python?.?", "*python?.?m"]
match_rules = []
for rule in rules:
@@ -88,7 +86,7 @@ def is_python_name(name):
def path_is_python(path):
return path_is_executable(path) and is_python_name(path.name)
return path_is_executable(path) and looks_like_python(path.name)
def ensure_path(path):
@@ -101,8 +99,13 @@ def ensure_path(path):
"""
if isinstance(path, Path):
return Path(os.path.expandvars(path.as_posix()))
return Path(os.path.expandvars(path))
path = path.as_posix()
path = Path(os.path.expandvars(path))
try:
path = path.resolve()
except OSError:
path = path.absolute()
return path
def _filter_none(k, v):
@@ -132,3 +135,9 @@ def fs_str(string):
_fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
def unnest(item):
if isinstance(next((i for i in item), None), (list, tuple)):
return chain(*filter(None, item))
return chain(filter(None, item))
+2 -1
View File
@@ -21,7 +21,7 @@ git+https://github.com/naiquevin/pipdeptree.git@ee5eaf86ed0f49ea97601475e048d81e
pipreqs==0.4.9
docopt==0.6.2
yarg==0.1.9
pythonfinder
pythonfinder==1.0.0
requests==2.19.1
chardet==3.0.4
idna==2.7
@@ -39,3 +39,4 @@ six==1.11.0
semver==2.8.0
shutilwhich==1.1.0
toml==0.9.4
cached-property==1.4.3
+10 -1
View File
@@ -15,8 +15,17 @@ if [[ ! -z "$TEST_SUITE" ]]; then
echo "Using TEST_SUITE=$TEST_SUITE"
fi
export PATH="$HOME/.local/bin:$PATH"
HOME=$(readlink -f ~/)
if [[ -z "$HOME" ]]; then
if [[ "$USER" == "root" ]]; then
HOME="/root"
fi
fi
if [[ ! -z "$HOME" ]]; then
export PATH="${HOME}/.local/bin:${PATH}"
fi
# pip uninstall -y pipenv
echo "Path: $PATH"
echo "Installing Pipenv…"
pip install -e "$(pwd)" --upgrade
pipenv install --deploy --dev
+70 -26
View File
@@ -280,19 +280,47 @@ def write_backport_imports(ctx, vendor_dir):
backport_init.write_text('\n'.join(init_py_lines) + '\n')
def vendor(ctx, vendor_dir, rewrite=True):
log('Reinstalling vendored libraries')
is_patched = vendor_dir.name == 'patched'
requirements_file = vendor_dir.name
def _ensure_package_in_requirements(ctx, requirements_file, package):
requirement = None
log('using requirements file: %s' % requirements_file)
req_file_lines = [l for l in requirements_file.read_text().splitlines()]
if package:
match = [r for r in req_file_lines if r.strip().lower().startswith(package)]
matched_req = None
if match:
for m in match:
specifiers = [m.index(s) for s in ['>', '<', '=', '~'] if s in m]
if m.lower() == package or (specifiers and m[:min(specifiers)].lower() == package):
matched_req = "{0}".format(m)
requirement = matched_req
log("Matched req: %r" % matched_req)
if not matched_req:
req_file_lines.append("{0}".format(package))
log("Writing requirements file: %s" % requirements_file)
requirements_file.write_text('\n'.join(req_file_lines))
requirement = "{0}".format(package)
return requirement
def install(ctx, vendor_dir, package=None):
requirements_file = vendor_dir / "{0}.txt".format(vendor_dir.name)
requirement = "-r {0}".format(requirements_file.as_posix())
log('Using requirements file: %s' % requirement)
if package:
requirement = _ensure_package_in_requirements(ctx, requirements_file, package)
# We use --no-deps because we want to ensure that all of our dependencies
# are added to vendor.txt, this includes all dependencies recursively up
# the chain.
ctx.run(
'pip install -t {0} -r {0}/{1}.txt --no-compile --no-deps'.format(
str(vendor_dir),
requirements_file,
'pip install -t {0} --no-compile --no-deps --upgrade {1}'.format(
vendor_dir.as_posix(),
requirement,
)
)
def post_install_cleanup(ctx, vendor_dir):
remove_all(vendor_dir.glob('*.dist-info'))
remove_all(vendor_dir.glob('*.egg-info'))
@@ -300,6 +328,13 @@ def vendor(ctx, vendor_dir, rewrite=True):
drop_dir(vendor_dir / 'bin')
drop_dir(vendor_dir / 'tests')
def vendor(ctx, vendor_dir, package=None, rewrite=True):
log('Reinstalling vendored libraries')
is_patched = vendor_dir.name == 'patched'
install(ctx, vendor_dir, package=package)
log('Running post-install cleanup...')
post_install_cleanup(ctx, vendor_dir)
# Detect the vendored packages/modules
vendored_libs = detect_vendored_libs(_get_vendor_dir(ctx))
patched_libs = detect_vendored_libs(_get_patched_dir(ctx))
@@ -320,25 +355,26 @@ def vendor(ctx, vendor_dir, rewrite=True):
log('Renaming specified libs...')
for item in vendor_dir.iterdir():
if item.is_dir():
if rewrite:
if rewrite and not package or (package and item.name.lower() in package):
log('Rewriting imports for %s...' % item)
rewrite_imports(item, vendored_libs, vendor_dir)
rename_if_needed(ctx, vendor_dir, item)
elif item.name not in FILE_WHITE_LIST:
if rewrite:
if rewrite and not package or (package and item.stem.lower() in package):
rewrite_file_imports(item, vendored_libs, vendor_dir)
write_backport_imports(ctx, vendor_dir)
log('Applying post-patches...')
patches = patch_dir.glob('*.patch' if not is_patched else '_post*.patch')
for patch in patches:
apply_patch(ctx, patch)
if is_patched:
piptools_vendor = vendor_dir / 'piptools' / '_vendored'
if piptools_vendor.exists():
drop_dir(piptools_vendor)
msgpack = vendor_dir / 'notpip' / '_vendor' / 'msgpack'
if msgpack.exists():
remove_all(msgpack.glob('*.so'))
if not package:
log('Applying post-patches...')
patches = patch_dir.glob('*.patch' if not is_patched else '_post*.patch')
for patch in patches:
apply_patch(ctx, patch)
if is_patched:
piptools_vendor = vendor_dir / 'piptools' / '_vendored'
if piptools_vendor.exists():
drop_dir(piptools_vendor)
msgpack = vendor_dir / 'notpip' / '_vendor' / 'msgpack'
if msgpack.exists():
remove_all(msgpack.glob('*.so'))
@invoke.task
@@ -371,16 +407,19 @@ def rewrite_all_imports(ctx):
@invoke.task
def download_licenses(ctx, vendor_dir, requirements_file='vendor.txt'):
def download_licenses(ctx, vendor_dir=None, requirements_file='vendor.txt', package=None):
log('Downloading licenses')
if not vendor_dir:
vendor_dir = _get_vendor_dir(ctx)
requirements_file = vendor_dir / requirements_file
requirement = "-r {0}".format(requirements_file.as_posix())
if package:
requirement = _ensure_package_in_requirements(ctx, requirements_file, package)
tmp_dir = vendor_dir / '__tmp__'
ctx.run(
'pip download -r {0}/{1} --no-binary :all: --no-deps -d {2}'.format(
str(vendor_dir),
requirements_file,
str(tmp_dir),
'pip download --no-binary :all: --no-deps -d {0} {1}'.format(
tmp_dir.as_posix(),
requirement,
)
)
for sdist in tmp_dir.iterdir():
@@ -503,10 +542,15 @@ def generate_patch(ctx, package_path, patch_description, base='HEAD'):
@invoke.task(name=TASK_NAME)
def main(ctx):
def main(ctx, package=None):
vendor_dir = _get_vendor_dir(ctx)
patched_dir = _get_patched_dir(ctx)
log('Using vendor dir: %s' % vendor_dir)
if package:
vendor(ctx, vendor_dir, package=package)
download_licenses(ctx, vendor_dir, package=package)
log("Vendored %s" % package)
return
clean_vendor(ctx, vendor_dir)
clean_vendor(ctx, patched_dir)
vendor(ctx, vendor_dir)
+50 -25
View File
@@ -19,18 +19,22 @@ index 4e6174c..75f9b49 100644
# NOTE
# We used to store the cache dir under ~/.pip-tools, which is not the
diff --git a/pipenv/patched/piptools/repositories/pypi.py b/pipenv/patched/piptools/repositories/pypi.py
index 1c4b943..c922be1 100644
index 1c4b943..91902dc 100644
--- a/pipenv/patched/piptools/repositories/pypi.py
+++ b/pipenv/patched/piptools/repositories/pypi.py
@@ -4,6 +4,7 @@ from __future__ import (absolute_import, division, print_function,
@@ -1,9 +1,10 @@
# coding: utf-8
from __future__ import (absolute_import, division, print_function,
unicode_literals)
-
+import copy
import hashlib
import os
+import sys
from contextlib import contextmanager
from shutil import rmtree
@@ -15,13 +16,24 @@ from .._compat import (
@@ -15,13 +16,22 @@ from .._compat import (
Wheel,
FAVORITE_HASH,
TemporaryDirectory,
@@ -40,25 +44,23 @@ index 1c4b943..c922be1 100644
+ SafeFileCache,
)
+from pip._vendor.packaging.requirements import InvalidRequirement, Requirement
+from pip._vendor.packaging.version import Version, InvalidVersion, parse as parse_version
+from pip._vendor.packaging.specifiers import SpecifierSet, InvalidSpecifier, Specifier
+from pip._vendor.packaging.markers import Marker, Op, Value, Variable
+from pip._vendor.pyparsing import ParseException
-from ..cache import CACHE_DIR
+from pip._vendor.packaging.requirements import Requirement
+from pip._vendor.packaging.specifiers import SpecifierSet, Specifier
+from pip._vendor.packaging.markers import Op, Value, Variable
+from pip._internal.exceptions import InstallationError
+from pip._internal.vcs import VcsSupport
+
from ..cache import CACHE_DIR
+from pipenv.environments import PIPENV_CACHE_DIR
from ..exceptions import NoCandidateFound
-from ..utils import (fs_str, is_pinned_requirement, lookup_table,
from ..utils import (fs_str, is_pinned_requirement, lookup_table,
- make_install_requirement)
+from ..utils import (fs_str, is_pinned_requirement, lookup_table, as_tuple, key_from_req,
+ make_install_requirement, format_requirement, dedup, clean_requires_python)
+ make_install_requirement, clean_requires_python)
+
from .base import BaseRepository
@@ -37,6 +49,40 @@ except ImportError:
@@ -37,6 +47,45 @@ except ImportError:
from pip.wheel import WheelCache
@@ -77,15 +79,20 @@ index 1c4b943..c922be1 100644
+ def get_hash(self, location):
+ # if there is no location hash (i.e., md5 / sha256 / etc) we on't want to store it
+ hash_value = None
+ can_hash = location.hash
+ vcs = VcsSupport()
+ orig_scheme = location.scheme
+ new_location = copy.deepcopy(location)
+ if orig_scheme in vcs.all_schemes:
+ new_location.url = new_location.url.split("+", 1)[-1]
+ can_hash = new_location.hash
+ if can_hash:
+ # hash url WITH fragment
+ hash_value = self.get(location.url)
+ hash_value = self.get(new_location.url)
+ if not hash_value:
+ hash_value = self._get_file_hash(location)
+ hash_value = self._get_file_hash(new_location)
+ hash_value = hash_value.encode('utf8')
+ if can_hash:
+ self.set(location.url, hash_value)
+ self.set(new_location.url, hash_value)
+ return hash_value.decode('utf8')
+
+ def _get_file_hash(self, location):
@@ -99,7 +106,7 @@ index 1c4b943..c922be1 100644
class PyPIRepository(BaseRepository):
DEFAULT_INDEX_URL = PyPI.simple_url
@@ -46,10 +92,11 @@ class PyPIRepository(BaseRepository):
@@ -46,10 +95,11 @@ class PyPIRepository(BaseRepository):
config), but any other PyPI mirror can be used if index_urls is
changed/configured on the Finder.
"""
@@ -113,7 +120,7 @@ index 1c4b943..c922be1 100644
index_urls = [pip_options.index_url] + pip_options.extra_index_urls
if pip_options.no_index:
@@ -74,11 +121,15 @@ class PyPIRepository(BaseRepository):
@@ -74,11 +124,15 @@ class PyPIRepository(BaseRepository):
# of all secondary dependencies for the given requirement, so we
# only have to go to disk once for each requirement
self._dependencies_cache = {}
@@ -131,7 +138,7 @@ index 1c4b943..c922be1 100644
def freshen_build_caches(self):
"""
@@ -114,10 +165,14 @@ class PyPIRepository(BaseRepository):
@@ -114,10 +168,14 @@ class PyPIRepository(BaseRepository):
if ireq.editable:
return ireq # return itself as the best match
@@ -148,7 +155,7 @@ index 1c4b943..c922be1 100644
# Reuses pip's internal candidate sort key to sort
matching_candidates = [candidates_by_version[ver] for ver in matching_versions]
@@ -126,11 +181,71 @@ class PyPIRepository(BaseRepository):
@@ -126,11 +184,71 @@ class PyPIRepository(BaseRepository):
best_candidate = max(matching_candidates, key=self.finder._candidate_sort_key)
# Turn the candidate into a pinned InstallRequirement
@@ -223,7 +230,7 @@ index 1c4b943..c922be1 100644
"""
Given a pinned or an editable InstallRequirement, returns a set of
dependencies (also InstallRequirements, but not necessarily pinned).
@@ -155,20 +270,40 @@ class PyPIRepository(BaseRepository):
@@ -155,20 +273,47 @@ class PyPIRepository(BaseRepository):
os.makedirs(download_dir)
if not os.path.isdir(self._wheel_download_dir):
os.makedirs(self._wheel_download_dir)
@@ -235,6 +242,13 @@ index 1c4b943..c922be1 100644
+ dist = None
+ if ireq.editable:
+ try:
+ from pipenv.utils import chdir
+ with chdir(ireq.setup_py_dir):
+ from setuptools.dist import distutils
+ distutils.core.run_setup(ireq.setup_py)
+ except (ImportError, InstallationError, TypeError, AttributeError):
+ pass
+ try:
+ dist = ireq.get_dist()
+ except InstallationError:
+ ireq.run_egg_info()
@@ -268,7 +282,7 @@ index 1c4b943..c922be1 100644
)
except TypeError:
# Pip >= 10 (new resolver!)
@@ -188,17 +323,97 @@ class PyPIRepository(BaseRepository):
@@ -188,17 +333,97 @@ class PyPIRepository(BaseRepository):
finder=self.finder,
session=self.session,
upgrade_strategy="to-satisfy-only",
@@ -369,7 +383,18 @@ index 1c4b943..c922be1 100644
return set(self._dependencies_cache[ireq])
def get_hashes(self, ireq):
@@ -217,24 +432,22 @@ class PyPIRepository(BaseRepository):
@@ -210,6 +435,10 @@ class PyPIRepository(BaseRepository):
if ireq.editable:
return set()
+ vcs = VcsSupport()
+ if ireq.link and ireq.link.scheme in vcs.all_schemes and 'ssh' in ireq.link.scheme:
+ return set()
+
if not is_pinned_requirement(ireq):
raise TypeError(
"Expected pinned requirement, got {}".format(ireq))
@@ -217,24 +446,22 @@ class PyPIRepository(BaseRepository):
# We need to get all of the candidates that match our current version
# pin, these will represent all of the files that could possibly
# satisfy this constraint.
+25
View File
@@ -28,7 +28,30 @@ def check_internet():
return True
def check_github_ssh():
res = False
try:
# `ssh -T git@github.com` will return successfully with return_code==1
# and message 'Hi <username>! You've successfully authenticated, but
# GitHub does not provide shell access.' if ssh keys are available and
# registered with GitHub. Otherwise, the command will fail with
# return_code=255 and say 'Permission denied (publickey).'
c = delegator.run('ssh -T git@github.com')
res = True if c.return_code == 1 else False
except Exception:
pass
if not res:
warnings.warn(
'Cannot connect to GitHub via SSH', ResourceWarning
)
warnings.warn(
'Will skip tests requiring SSH access to GitHub', ResourceWarning
)
return res
WE_HAVE_INTERNET = check_internet()
WE_HAVE_GITHUB_SSH_KEYS = check_github_ssh()
TESTS_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PYPI_VENDOR_DIR = os.path.join(TESTS_ROOT, 'pypi')
@@ -38,6 +61,8 @@ prepare_pypi_packages(PYPI_VENDOR_DIR)
def pytest_runtest_setup(item):
if item.get_marker('needs_internet') is not None and not WE_HAVE_INTERNET:
pytest.skip('requires internet')
if item.get_marker('needs_github_ssh') is not None and not WE_HAVE_GITHUB_SSH_KEYS:
pytest.skip('requires github ssh')
class _PipenvInstance(object):
+28
View File
@@ -1,5 +1,6 @@
import os
from pipenv._compat import TemporaryDirectory, Path
from pipenv.project import Project
from pipenv.utils import temp_environ, normalize_drive, get_windows_path
from pipenv.vendor import delegator
@@ -39,3 +40,30 @@ def test_reuse_previous_venv(PipenvInstance, pypi):
c = p.pipenv('install requests')
assert c.return_code == 0
assert normalize_drive(p.path) in p.pipenv('--venv').out
@pytest.mark.dotvenv
def test_venv_file_exists(PipenvInstance, pypi):
"""Tests virtualenv creation & package installation when a .venv file exists
at the project root.
"""
with PipenvInstance(pypi=pypi, chdir=True) as p:
file_path = os.path.join(p.path, '.venv')
with open(file_path, 'w') as f:
f.write('')
with temp_environ(), TemporaryDirectory(
prefix='pipenv-', suffix='temp_workon_home'
) as workon_home:
os.environ['WORKON_HOME'] = workon_home.name
if 'PIPENV_VENV_IN_PROJECT' in os.environ:
del os.environ['PIPENV_VENV_IN_PROJECT']
c = p.pipenv('install requests')
assert c.return_code == 0
venv_loc = None
for line in c.err.splitlines():
if line.startswith('Virtualenv location:'):
venv_loc = Path(line.split(':', 1)[-1].strip())
assert venv_loc is not None
assert venv_loc.joinpath('.project').exists()
+33
View File
@@ -25,6 +25,39 @@ def test_basic_vcs_install(PipenvInstance, pip_src_dir, pypi):
assert "gitdb2" in p.lockfile["default"]
@pytest.mark.vcs
@pytest.mark.install
@pytest.mark.needs_internet
@flaky
def test_git_vcs_install(PipenvInstance, pip_src_dir, pypi):
with PipenvInstance(pypi=pypi, chdir=True) as p:
c = p.pipenv("install git+git://github.com/benjaminp/six.git@1.11.0#egg=six")
assert c.return_code == 0
assert "six" in p.pipfile["packages"]
assert "git" in p.pipfile["packages"]["six"]
assert p.lockfile["default"]["six"] == {
"git": "git://github.com/benjaminp/six.git",
"ref": "15e31431af97e5e64b80af0a3f598d382bcdd49a",
}
@pytest.mark.vcs
@pytest.mark.install
@pytest.mark.needs_github_ssh
@pytest.mark.needs_internet
@flaky
def test_ssh_vcs_install(PipenvInstance, pip_src_dir, pypi):
with PipenvInstance(pypi=pypi, chdir=True) as p:
c = p.pipenv("install git+ssh://git@github.com/benjaminp/six.git@1.11.0#egg=six")
assert c.return_code == 0
assert "six" in p.pipfile["packages"]
assert "git" in p.pipfile["packages"]["six"]
assert p.lockfile["default"]["six"] == {
"git": "ssh://git@github.com/benjaminp/six.git",
"ref": "15e31431af97e5e64b80af0a3f598d382bcdd49a",
}
@pytest.mark.files
@pytest.mark.urls
@pytest.mark.needs_internet
+24
View File
@@ -385,3 +385,27 @@ django = "*"
django_version = '==2.0.6' if py_version.startswith('3') else '==1.11.13'
assert py_version == '2.7.14'
assert p.lockfile['default']['django']['version'] == django_version
@pytest.mark.lock
@pytest.mark.install
def test_lockfile_corrupted(PipenvInstance):
with PipenvInstance() as p:
with open(p.lockfile_path, 'w') as f:
f.write('{corrupted}')
c = p.pipenv('install')
assert c.return_code == 0
assert 'Pipfile.lock is corrupted' in c.err
assert p.lockfile['_meta']
@pytest.mark.lock
@pytest.mark.install
def test_lockfile_with_empty_dict(PipenvInstance):
with PipenvInstance() as p:
with open(p.lockfile_path, 'w') as f:
f.write('{}')
c = p.pipenv('install')
assert c.return_code == 0
assert 'Pipfile.lock is corrupted' in c.err
assert p.lockfile['_meta']
+14
View File
@@ -371,6 +371,20 @@ twine = "*"
"https://user:password@custom.example.com/simple",
],
),
(
[
{
"url": "https://user:password@custom.example.com/simple",
"verify_ssl": False,
},
],
[
"-i",
"https://user:password@custom.example.com/simple",
"--trusted-host",
"custom.example.com",
],
),
],
)
def test_prepare_pip_source_args(self, sources, expected_args):