Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow local path to be installed non-editable #3018

Merged
merged 8 commits into from
Oct 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/3005.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed a bug which prevented installing the local directory in non-editable mode.
47 changes: 29 additions & 18 deletions pipenv/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1315,6 +1315,12 @@ def pip_install(
from .vendor.urllib3.util import parse_url

src = []
write_to_tmpfile = False
if requirement:
editable_with_markers = requirement.editable and requirement.markers
needs_hashes = not requirement.editable and not ignore_hashes and r is None
write_to_tmpfile = needs_hashes or editable_with_markers

if not trusted_hosts:
trusted_hosts = []
trusted_hosts.extend(os.environ.get("PIP_TRUSTED_HOSTS", []))
Expand All @@ -1326,12 +1332,13 @@ def pip_install(
err=True,
)
# Create files for hash mode.
if requirement and not requirement.editable and (not ignore_hashes) and (r is None):
fd, r = tempfile.mkstemp(
prefix="pipenv-", suffix="-requirement.txt", dir=requirements_dir
)
with os.fdopen(fd, "w") as f:
f.write(requirement.as_line())
if write_to_tmpfile:
with vistir.compat.NamedTemporaryFile(
prefix="pipenv-", suffix="-requirement.txt", dir=requirements_dir,
delete=False
) as f:
f.write(vistir.misc.to_bytes(requirement.as_line()))
r = f.name
# Install dependencies when a package is a VCS dependency.
if requirement and requirement.vcs:
no_deps = False
Expand Down Expand Up @@ -1374,19 +1381,22 @@ def pip_install(
create_mirror_source(pypi_mirror) if is_pypi_url(source["url"]) else source
for source in sources
]
if (requirement and requirement.editable) or not r:
if (requirement and requirement.editable) and not r:
install_reqs = requirement.as_line(as_list=True)
if requirement.editable and install_reqs[0].startswith("-e "):
req, install_reqs = install_reqs[0], install_reqs[1:]
editable_opt, req = req.split(" ", 1)
install_reqs = [editable_opt, req] + install_reqs
if not any(item.startswith("--hash") for item in install_reqs):
ignore_hashes = True
else:
elif r:
install_reqs = ["-r", r]
with open(r) as f:
if "--hash" not in f.read():
ignore_hashes = True
else:
ignore_hashes = True if not requirement.hashes else False
install_reqs = requirement.as_line(as_list=True)
pip_command = [which_pip(allow_global=allow_global), "install"]
if pre:
pip_command.append("--pre")
Expand Down Expand Up @@ -1804,9 +1814,6 @@ def do_install(
for req in import_from_code(code):
click.echo(" Found {0}!".format(crayons.green(req)))
project.add_package_to_pipfile(req)
# Capture . argument and assign it to nothing
if len(packages) == 1 and packages[0] == ".":
packages = False
# Install editable local packages before locking - this gives us access to dist-info
if project.pipfile_exists and (
# double negatives are for english readability, leave them alone.
Expand Down Expand Up @@ -2512,24 +2519,28 @@ def do_sync(

def do_clean(ctx, three=None, python=None, dry_run=False, bare=False, pypi_mirror=None):
# Ensure that virtualenv is available.

from packaging.utils import canonicalize_name
ensure_project(three=three, python=python, validate=False, pypi_mirror=pypi_mirror)
ensure_lockfile(pypi_mirror=pypi_mirror)
installed_package_names = [
pkg.project_name for pkg in project.get_installed_packages()
canonicalize_name(pkg.project_name) for pkg in project.get_installed_packages()
]
# Remove known "bad packages" from the list.
for bad_package in BAD_PACKAGES:
if bad_package in installed_package_names:
if canonicalize_name(bad_package) in installed_package_names:
if environments.is_verbose():
click.echo("Ignoring {0}.".format(repr(bad_package)), err=True)
del installed_package_names[installed_package_names.index(bad_package)]
del installed_package_names[installed_package_names.index(
canonicalize_name(bad_package)
)]
# Intelligently detect if --dev should be used or not.
develop = [k.lower() for k in project.lockfile_content["develop"].keys()]
default = [k.lower() for k in project.lockfile_content["default"].keys()]
develop = [canonicalize_name(k) for k in project.lockfile_content["develop"].keys()]
default = [canonicalize_name(k) for k in project.lockfile_content["default"].keys()]
for used_package in set(develop + default):
if used_package in installed_package_names:
del installed_package_names[installed_package_names.index(used_package)]
del installed_package_names[installed_package_names.index(
canonicalize_name(used_package)
)]
failure = False
for apparent_bad_package in installed_package_names:
if dry_run:
Expand Down
10 changes: 9 additions & 1 deletion pipenv/vendor/pythonfinder/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
from __future__ import print_function, absolute_import

__version__ = '1.1.1'
__version__ = '1.1.2'

# Add NullHandler to "pythonfinder" logger, because Python2's default root
# logger has no handler and warnings like this would be reported:
#
# > No handlers could be found for logger "pythonfinder.models.pyenv"
import logging
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())

__all__ = ["Finder", "WindowsFinder", "SystemPath", "InvalidPythonVersion"]
from .pythonfinder import Finder
Expand Down
8 changes: 4 additions & 4 deletions pipenv/vendor/pythonfinder/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,18 @@
@click.option(
"--version", is_flag=True, default=False, help="Display PythonFinder version."
)
# @click.version_option(prog_name=crayons.normal('pyfinder', bold=True), version=__version__)
@click.option("--ignore-unsupported/--no-unsupported", is_flag=True, default=True, help="Ignore unsupported python versions.")
@click.version_option(prog_name='pyfinder', version=__version__)
@click.pass_context
def cli(ctx, find=False, which=False, findall=False, version=False):
def cli(ctx, find=False, which=False, findall=False, version=False, ignore_unsupported=True):
if version:
click.echo(
"{0} version {1}".format(
crayons.white("PythonFinder", bold=True), crayons.yellow(__version__)
)
)
sys.exit(0)
finder = Finder()
finder = Finder(ignore_unsupported=ignore_unsupported)
if findall:
versions = finder.find_all_python_versions()
if versions:
Expand All @@ -46,7 +47,6 @@ def cli(ctx, find=False, which=False, findall=False, version=False):
fg="red",
)
if find:

if any([find.startswith("{0}".format(n)) for n in range(10)]):
found = finder.find_python_version(find.strip())
else:
Expand Down
8 changes: 6 additions & 2 deletions pipenv/vendor/pythonfinder/models/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class SystemPath(object):
pyenv_finder = attr.ib(default=None, validator=optional_instance_of("PyenvPath"))
system = attr.ib(default=False)
_version_dict = attr.ib(default=attr.Factory(defaultdict))
ignore_unsupported = attr.ib(default=False)

__finders = attr.ib(default=attr.Factory(dict))

Expand Down Expand Up @@ -129,7 +130,7 @@ def _setup_pyenv(self):
pyenv_index = self.path_order.index(last_pyenv)
except ValueError:
return
self.pyenv_finder = PyenvFinder.create(root=PYENV_ROOT)
self.pyenv_finder = PyenvFinder.create(root=PYENV_ROOT, ignore_unsupported=self.ignore_unsupported)
# paths = (v.paths.values() for v in self.pyenv_finder.versions.values())
root_paths = (
p for path in self.pyenv_finder.expanded_paths for p in path if p.is_root
Expand Down Expand Up @@ -268,7 +269,7 @@ def find_python_version(
return ver

@classmethod
def create(cls, path=None, system=False, only_python=False, global_search=True):
def create(cls, path=None, system=False, only_python=False, global_search=True, ignore_unsupported=False):
"""Create a new :class:`pythonfinder.models.SystemPath` instance.

:param path: Search path to prepend when searching, defaults to None
Expand All @@ -277,6 +278,8 @@ def create(cls, path=None, system=False, only_python=False, global_search=True):
:param system: bool, optional
:param only_python: Whether to search only for python executables, defaults to False
:param only_python: bool, optional
:param ignore_unsupported: Whether to ignore unsupported python versions, if False, an error is raised, defaults to True
:param ignore_unsupported: bool, optional
:return: A new :class:`pythonfinder.models.SystemPath` instance.
:rtype: :class:`pythonfinder.models.SystemPath`
"""
Expand All @@ -303,6 +306,7 @@ def create(cls, path=None, system=False, only_python=False, global_search=True):
only_python=only_python,
system=system,
global_search=global_search,
ignore_unsupported=ignore_unsupported,
)


Expand Down
54 changes: 50 additions & 4 deletions pipenv/vendor/pythonfinder/models/pyenv.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,81 @@
# -*- coding=utf-8 -*-
from __future__ import absolute_import, print_function

import logging

from collections import defaultdict

import attr
import sysconfig

from vistir.compat import Path

from ..utils import ensure_path, optional_instance_of
from ..utils import ensure_path, optional_instance_of, get_python_version, filter_pythons
from .mixins import BaseFinder
from .path import VersionPath
from .python import PythonVersion


logger = logging.getLogger(__name__)


@attr.s
class PyenvFinder(BaseFinder):
root = attr.ib(default=None, validator=optional_instance_of(Path))
# ignore_unsupported should come before versions, because its value is used
# in versions's default initializer.
ignore_unsupported = attr.ib(default=False)
versions = attr.ib()
pythons = attr.ib()

@classmethod
def version_from_bin_dir(cls, base_dir):
pythons = [py for py in filter_pythons(base_dir)]
py_version = None
for py in pythons:
version = get_python_version(py.as_posix())
try:
py_version = PythonVersion.parse(version)
except Exception:
continue
if py_version:
return py_version
return

@versions.default
def get_versions(self):
versions = defaultdict(VersionPath)
bin_ = sysconfig._INSTALL_SCHEMES[sysconfig._get_default_scheme()]["scripts"]
for p in self.root.glob("versions/*"):
version = PythonVersion.parse(p.name)
if p.parent.name == "envs":
continue
try:
version = PythonVersion.parse(p.name)
except ValueError:
bin_dir = Path(bin_.format(base=p.as_posix()))
if bin_dir.exists():
version = self.version_from_bin_dir(bin_dir)
if not version:
if not self.ignore_unsupported:
raise
continue
except Exception:
if not self.ignore_unsupported:
raise
logger.warning(
'Unsupported Python version %r, ignoring...',
p.name, exc_info=True
)
continue
if not version:
continue
version_tuple = (
version.get("major"),
version.get("minor"),
version.get("patch"),
version.get("is_prerelease"),
version.get("is_devrelease"),
version.get("is_debug")
)
versions[version_tuple] = VersionPath.create(
path=p.resolve(), only_python=True
Expand All @@ -47,6 +93,6 @@ def get_pythons(self):
return pythons

@classmethod
def create(cls, root):
def create(cls, root, ignore_unsupported=False):
root = ensure_path(root)
return cls(root=root)
return cls(root=root, ignore_unsupported=ignore_unsupported)
8 changes: 5 additions & 3 deletions pipenv/vendor/pythonfinder/models/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import attr

from packaging.version import Version
from packaging.version import Version, LegacyVersion
from packaging.version import parse as parse_version

from ..environment import SYSTEM_ARCH
Expand Down Expand Up @@ -65,10 +65,11 @@ def version_tuple(self):
self.patch,
self.is_prerelease,
self.is_devrelease,
self.is_debug
)

def matches(
self, major=None, minor=None, patch=None, pre=False, dev=False, arch=None
self, major=None, minor=None, patch=None, pre=False, dev=False, arch=None, debug=False
):
if arch and arch.isdigit():
arch = "{0}bit".format(arch)
Expand All @@ -79,6 +80,7 @@ def matches(
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)
and (debug is None or self.is_debug == debug)
)

def as_major(self):
Expand Down Expand Up @@ -108,7 +110,7 @@ def parse(cls, version):
is_debug = False
if version.endswith("-debug"):
is_debug = True
version, _, _ = verson.rpartition("-")
version, _, _ = version.rpartition("-")
try:
version = parse_version(str(version))
except TypeError:
Expand Down
10 changes: 7 additions & 3 deletions pipenv/vendor/pythonfinder/pythonfinder.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,30 @@


class Finder(object):
def __init__(self, path=None, system=False, global_search=True):
def __init__(self, path=None, system=False, global_search=True, ignore_unsupported=False):
"""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
:param ignore_unsupported: Whether to ignore unsupported python versions, if False, an error is raised, defaults to True
:param ignore_unsupported: bool, optional
:returns: a :class:`~pythonfinder.pythonfinder.Finder` object.
"""

self.path_prepend = path
self.global_search = global_search
self.system = system
self.ignore_unsupported = ignore_unsupported
self._system_path = None
self._windows_finder = None

Expand All @@ -38,6 +41,7 @@ def system_path(self):
path=self.path_prepend,
system=self.system,
global_search=self.global_search,
ignore_unsupported=self.ignore_unsupported,
)
return self._system_path

Expand Down
Loading