mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 22:50:18 +00:00
Merge pull request #958 from techalchemy/bugfix/relative-directories
Relative file and path installation
This commit is contained in:
+2
-2
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Vendored
+1669
File diff suppressed because it is too large
Load Diff
+28
-1
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user