Merge pull request #958 from techalchemy/bugfix/relative-directories

Relative file and path installation
This commit is contained in:
Nate Prewitt
2017-11-14 16:09:37 -08:00
committed by GitHub
6 changed files with 1776 additions and 10 deletions
+2 -2
View File
@@ -36,7 +36,7 @@ from .utils import (
proper_case, pep423_name, split_vcs, resolve_deps, shellquote, is_vcs,
python_version, suggest_package, find_windows_executable, is_file,
prepare_pip_source_args, temp_environ, is_valid_url, download_file,
need_update_check, touch_update_stamp
get_requirement, need_update_check, touch_update_stamp
)
from .__version__ import __version__
from . import pep508checker, progress
@@ -1352,7 +1352,7 @@ def pip_install(
f.write(package_name)
# Install dependencies when a package is a VCS dependency.
if [x for x in requirements.parse(package_name.split('--hash')[0].split('--trusted-host')[0])][0].vcs:
if get_requirement(package_name.split('--hash')[0].split('--trusted-host')[0]).vcs:
no_deps = False
# Don't specify a source directory when using --system.
+7 -3
View File
@@ -13,7 +13,8 @@ import toml
from .utils import (
mkdir_p, convert_deps_from_pip, pep423_name, recase_file,
find_requirements, is_file, is_vcs, python_version, cleanup_toml
find_requirements, is_file, is_vcs, python_version, cleanup_toml,
is_installable_file, is_valid_url
)
from .environments import PIPENV_MAX_DEPTH, PIPENV_VENV_IN_PROJECT
from .environments import PIPENV_VIRTUALENV, PIPENV_PIPFILE
@@ -66,10 +67,13 @@ class Project(object):
else:
ps.update({k: v})
else:
if not is_file(v) and not is_file(k):
if not (is_installable_file(k) or is_installable_file(v) or
any(file_prefix in v for file_prefix in ['path', 'file'])):
ps.update({k: v})
else:
if not is_vcs(k) and not is_file(k) and not is_vcs(v):
if not (any(is_vcs(i) for i in [k, v]) or
any(is_installable_file(i) for i in [k, v]) or
any(is_valid_url(i) for i in [k, v])):
ps.update({k: v})
return ps
+52 -4
View File
@@ -23,6 +23,10 @@ try:
from urllib.parse import urlparse
except ImportError:
from urlparse import urlparse
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
from distutils.spawn import find_executable
from contextlib import contextmanager
@@ -41,7 +45,7 @@ specifiers = [k for k in lookup.keys()]
# List of version control systems we support.
VCS_LIST = ('git', 'svn', 'hg', 'bzr')
SCHEME_LIST = ('http://', 'https://', 'ftp://', 'file:///')
SCHEME_LIST = ('http://', 'https://', 'ftp://', 'file://')
requests = requests.Session()
@@ -266,6 +270,35 @@ packages = [
]
def get_requirement(dep):
"""Pre-clean requirement strings passed to the requirements parser.
Ensures that we can accept both local and relative paths, file and VCS URIs,
remote URIs, and package names, and that we pass only valid requirement strings
to the requirements parser. Performs necessary modifications to requirements
object if the user input was a local relative path.
"""
path = None
# Only operate on local, existing, non-URI formatted paths
if (is_file(dep) and isinstance(dep, six.string_types) and
not any(dep.startswith(uri_prefix) for uri_prefix in SCHEME_LIST)):
dep_path = Path(dep)
# Only parse if it is a file or an installable dir
if dep_path.is_file() or (dep_path.is_dir() and pip.utils.is_installable_dir(dep)):
if dep_path.is_absolute():
path = dep
else:
path = get_converted_relative_path(dep)
dep = dep_path.resolve().as_uri()
req = [r for r in requirements.parse(dep)][0]
# If the result is a local file with a URI and we have a local path, unset the URI
# and set the path instead
if req.local_file and req.uri and not req.path and path:
req.path = path
req.uri = None
return req
def cleanup_toml(tml):
toml = tml.split('\n')
new_toml = []
@@ -539,12 +572,11 @@ def convert_deps_from_pip(dep):
dependency = {}
req = [r for r in requirements.parse(dep)][0]
req = get_requirement(dep)
extras = {'extras': req.extras}
# File installs.
if (req.uri or (os.path.isfile(req.path) if req.path else False) or
os.path.isfile(req.name)) and not req.vcs:
if (req.uri or req.path or (os.path.isfile(req.name) if req.name else False)) and not req.vcs:
# Assign a package name to the file, last 7 of it's sha256 hex digest.
if not req.uri and not req.path:
req.path = os.path.abspath(req.name)
@@ -778,6 +810,17 @@ def is_vcs(pipfile_entry):
return False
def is_installable_file(path):
"""Determine if a path can potentially be installed"""
if hasattr(path, 'keys') and any(key for key in path.keys() if key in ['file', 'path']):
path = urlparse(path['file']).path if 'file' in path else path['path']
if not isinstance(path, six.string_types) or path == '*':
return False
lookup_path = Path(path)
return lookup_path.is_file() or (lookup_path.is_dir() and
pip.utils.is_installable_dir(lookup_path.resolve().as_posix()))
def is_file(package):
"""Determine if a package name is for a File dependency."""
if hasattr(package, 'keys'):
@@ -885,6 +928,11 @@ def find_windows_executable(bin_path, exe_name):
return find_executable(exe_name)
def get_converted_relative_path(path, relative_to=os.curdir):
"""Given a vague relative path, return the path relative to the given location"""
return os.path.join('.', os.path.relpath(path, start=relative_to))
def walk_up(bottom):
"""Mimic os.walk, but walk 'up' instead of down the directory tree.
From: https://gist.github.com/zdavkeos/1098474
+1669
View File
File diff suppressed because it is too large Load Diff
+28 -1
View File
@@ -7,10 +7,14 @@ import json
import pytest
from pipenv.cli import activate_virtualenv
from pipenv.utils import temp_environ, get_windows_path
from pipenv.utils import temp_environ, get_windows_path, mkdir_p
from pipenv.vendor import toml
from pipenv.vendor import delegator
from pipenv.project import Project
try:
from pathlib import Path
except:
from pipenv.vendor.pathlib2 import Path
os.environ['PIPENV_DONT_USE_PYENV'] = '1'
@@ -843,6 +847,7 @@ requests = "==2.14.0"
assert 'file' in dep or 'path' in dep
@pytest.mark.install
@pytest.mark.files
@pytest.mark.urls
@@ -861,3 +866,25 @@ requests = "==2.14.0"
# check Pipfile.lock
assert 'requests' in p.lockfile['default']
assert 'records' in p.lockfile['default']
@pytest.mark.install
@pytest.mark.files
def test_relative_paths(self):
file_name = 'tablib-0.12.1.tar.gz'
test_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)))
source_path = os.path.abspath(os.path.join(test_dir, 'test_artifacts', file_name))
with PipenvInstance() as p:
artifact_dir = 'artifacts'
artifact_path = os.path.join(p.path, artifact_dir)
mkdir_p(artifact_path)
shutil.copy(source_path, os.path.join(artifact_path, file_name))
# Test installing a relative path in a subdirectory
c = p.pipenv('install {}/{}'.format(artifact_dir, file_name))
key = [k for k in p.pipfile['packages'].keys()][0]
dep = p.pipfile['packages'][key]
assert 'path' in dep
assert Path(os.path.join('.', artifact_dir, file_name)) == Path(dep['path'])
assert c.return_code == 0
+18
View File
@@ -182,6 +182,24 @@ class TestUtils:
assert pipenv.utils.is_valid_url(url)
assert pipenv.utils.is_valid_url(not_url) is False
@pytest.mark.parametrize('input_path, expected', [
('artifacts/file.zip', './artifacts/file.zip'),
('./artifacts/file.zip', './artifacts/file.zip'),
('../otherproject/file.zip', './../otherproject/file.zip')
])
@pytest.mark.skipif(os.name == 'nt', reason='Nix-based file paths tested')
def test_nix_converted_relative_path(self, input_path, expected):
assert pipenv.utils.get_converted_relative_path(input_path) == expected
@pytest.mark.parametrize('input_path, expected', [
('artifacts/file.zip', '.\\artifacts\\file.zip'),
('./artifacts/file.zip', '.\\artifacts\\file.zip'),
('../otherproject/file.zip', '.\\..\\otherproject\\file.zip')
])
@pytest.mark.skipif(os.name != 'nt', reason='Windows-based file paths tested')
def test_win_converted_relative_path(self, input_path, expected):
assert pipenv.utils.get_converted_relative_path(input_path) == expected
def test_download_file(self):
url = "https://github.com/kennethreitz/pipenv/blob/master/README.rst"
output = "test_download.rst"