1
1
Fork 0
mirror of https://github.com/pypa/pip synced 2023-12-13 21:30:23 +01:00

Merge pull request #3466 from sbidoul/impl-3217-sbi

Allow installing modules from a vcs subdirectory in non-editable mode
This commit is contained in:
Xavier Fernandez 2016-02-10 09:52:08 +01:00
commit 496b7c1e64
7 changed files with 68 additions and 77 deletions

View file

@ -8,6 +8,12 @@
* Exclude the wheel package from the `pip freeze` output (like pip and setuptools).
:issue:`2989`.
* Allow installing modules from a subdirectory of a vcs repository
in non-editable mode (:issue:`3217`, :pull:`3466`).
* Make pip wheel and pip download work with vcs urls with subdirectory option
(:pull:`3466`).
**8.0.2 (2016-01-21)**

View file

@ -919,7 +919,7 @@ class Link(object):
scheme, netloc, path, query, fragment = urllib_parse.urlsplit(self.url)
return urllib_parse.urlunsplit((scheme, netloc, path, query, None))
_egg_fragment_re = re.compile(r'#egg=([^&]*)')
_egg_fragment_re = re.compile(r'[#&]egg=([^&]*)')
@property
def egg_fragment(self):
@ -928,6 +928,15 @@ class Link(object):
return None
return match.group(1)
_subdirectory_fragment_re = re.compile(r'[#&]subdirectory=([^&]*)')
@property
def subdirectory_fragment(self):
match = self._subdirectory_fragment_re.search(self.url)
if not match:
return None
return match.group(1)
_hash_re = re.compile(
r'(sha1|sha224|sha384|sha256|sha512|md5)=([a-f0-9]+)'
)

View file

@ -67,7 +67,7 @@ def _strip_extras(path):
class InstallRequirement(object):
def __init__(self, req, comes_from, source_dir=None, editable=False,
link=None, as_egg=False, update=True, editable_options=None,
link=None, as_egg=False, update=True,
pycompile=True, markers=None, isolated=False, options=None,
wheel_cache=None, constraint=False):
self.extras = ()
@ -91,10 +91,6 @@ class InstallRequirement(object):
self.source_dir = source_dir
self.editable = editable
if editable_options is None:
editable_options = {}
self.editable_options = editable_options
self._wheel_cache = wheel_cache
self.link = self.original_link = link
self.as_egg = as_egg
@ -135,7 +131,7 @@ class InstallRequirement(object):
constraint=False):
from pip.index import Link
name, url, extras_override, editable_options = parse_editable(
name, url, extras_override = parse_editable(
editable_req, default_vcs)
if url.startswith('file:'):
source_dir = url_to_path(url)
@ -146,7 +142,6 @@ class InstallRequirement(object):
editable=True,
link=Link(url),
constraint=constraint,
editable_options=editable_options,
isolated=isolated,
options=options if options else {},
wheel_cache=wheel_cache)
@ -368,6 +363,12 @@ class InstallRequirement(object):
return None
return native_str(self.req.project_name)
@property
def setup_py_dir(self):
return os.path.join(
self.source_dir,
self.link and self.link.subdirectory_fragment or '')
@property
def setup_py(self):
assert self.source_dir, "No source dir for %s" % self
@ -384,15 +385,7 @@ class InstallRequirement(object):
"install from a source distribution.\n%s" % add_msg
)
setup_file = 'setup.py'
if self.editable_options and 'subdirectory' in self.editable_options:
setup_py = os.path.join(self.source_dir,
self.editable_options['subdirectory'],
setup_file)
else:
setup_py = os.path.join(self.source_dir, setup_file)
setup_py = os.path.join(self.setup_py_dir, 'setup.py')
# Python2 __file__ should not be unicode
if six.PY2 and isinstance(setup_py, six.text_type):
@ -425,16 +418,12 @@ class InstallRequirement(object):
if self.editable:
egg_base_option = []
else:
egg_info_dir = os.path.join(self.source_dir, 'pip-egg-info')
egg_info_dir = os.path.join(self.setup_py_dir, 'pip-egg-info')
ensure_dir(egg_info_dir)
egg_base_option = ['--egg-base', 'pip-egg-info']
cwd = self.source_dir
if self.editable_options and \
'subdirectory' in self.editable_options:
cwd = os.path.join(cwd, self.editable_options['subdirectory'])
call_subprocess(
egg_info_cmd + egg_base_option,
cwd=cwd,
cwd=self.setup_py_dir,
show_stdout=False,
command_level=logging.DEBUG,
command_desc='python setup.py egg_info')
@ -481,7 +470,7 @@ class InstallRequirement(object):
if self.editable:
base = self.source_dir
else:
base = os.path.join(self.source_dir, 'pip-egg-info')
base = os.path.join(self.setup_py_dir, 'pip-egg-info')
filenames = os.listdir(base)
if self.editable:
filenames = []
@ -799,7 +788,7 @@ class InstallRequirement(object):
archive_path, 'w', zipfile.ZIP_DEFLATED,
allowZip64=True
)
dir = os.path.normcase(os.path.abspath(self.source_dir))
dir = os.path.normcase(os.path.abspath(self.setup_py_dir))
for dirpath, dirnames, filenames in os.walk(dir):
if 'pip-egg-info' in dirnames:
dirnames.remove('pip-egg-info')
@ -889,7 +878,7 @@ class InstallRequirement(object):
with indent_log():
call_subprocess(
install_args + install_options,
cwd=self.source_dir,
cwd=self.setup_py_dir,
show_stdout=False,
spinner=spinner,
)
@ -981,10 +970,6 @@ class InstallRequirement(object):
with indent_log():
# FIXME: should we do --install-headers here too?
cwd = self.source_dir
if self.editable_options and \
'subdirectory' in self.editable_options:
cwd = os.path.join(cwd, self.editable_options['subdirectory'])
call_subprocess(
[
sys.executable,
@ -995,7 +980,7 @@ class InstallRequirement(object):
['develop', '--no-deps'] +
list(install_options),
cwd=cwd,
cwd=self.setup_py_dir,
show_stdout=False)
self.install_succeeded = True
@ -1111,26 +1096,6 @@ def _build_req_from_url(url):
return req
def _build_editable_options(req):
"""
This method generates a dictionary of the query string
parameters contained in a given editable URL.
"""
regexp = re.compile(r"[\?#&](?P<name>[^&=]+)=(?P<value>[^&=]+)")
matched = regexp.findall(req)
if matched:
ret = dict()
for option in matched:
(name, value) = option
if name in ret:
raise Exception("%s option already defined" % name)
ret[name] = value
return ret
return None
def parse_editable(editable_req, default_vcs=None):
"""Parses an editable requirement into:
- a requirement name
@ -1173,10 +1138,9 @@ def parse_editable(editable_req, default_vcs=None):
pkg_resources.Requirement.parse(
'__placeholder__' + extras
).extras,
{},
)
else:
return package_name, url_no_extras, None, {}
return package_name, url_no_extras, None
for version_control in vcs:
if url.lower().startswith('%s:' % version_control):
@ -1201,21 +1165,12 @@ def parse_editable(editable_req, default_vcs=None):
' is currently supported'
raise InstallationError(error_message)
try:
options = _build_editable_options(editable_req)
except Exception as exc:
package_name = Link(url).egg_fragment
if not package_name:
package_name = _build_req_from_url(editable_req)
if not package_name:
raise InstallationError(
'--editable=%s error in editable options:%s' % (editable_req, exc)
'--editable=%s is not the right format; it must have '
'#egg=Package' % editable_req
)
if not options or 'egg' not in options:
req = _build_req_from_url(editable_req)
if not req:
raise InstallationError(
'--editable=%s is not the right format; it must have '
'#egg=Package' % editable_req
)
else:
req = options['egg']
package = _strip_postfix(req)
return package, url, None, options
return _strip_postfix(package_name), url, None

View file

@ -715,7 +715,7 @@ class WheelBuilder(object):
wheel_args += ["--python-tag", python_tag]
try:
call_subprocess(wheel_args, cwd=req.source_dir,
call_subprocess(wheel_args, cwd=req.setup_py_dir,
show_stdout=False, spinner=spinner)
return True
except:

View file

@ -191,6 +191,19 @@ def test_install_local_editable_with_subdirectory(script):
result.assert_installed('version-subpkg', sub_dir='version_subdir')
@pytest.mark.network
def test_install_local_with_subdirectory(script):
version_pkg_path = _create_test_package_with_subdirectory(script,
'version_subdir')
result = script.pip(
'install',
'%s#egg=version_subpkg&subdirectory=version_subdir' %
('git+file://%s' % version_pkg_path,)
)
result.assert_installed('version_subpkg.py', editable=False)
@pytest.mark.network
def test_wheel_user_with_prefix_in_pydistutils_cfg(script, data, virtualenv):
# Make sure wheel is available in the virtualenv

View file

@ -75,6 +75,17 @@ class TestLink(object):
def test_is_wheel_false(self):
assert not Link('http://yo/not_a_wheel').is_wheel
def test_fragments(self):
url = 'git+https://example.com/package#egg=eggname'
assert 'eggname' == Link(url).egg_fragment
assert None is Link(url).subdirectory_fragment
url = 'git+https://example.com/package#egg=eggname&subdirectory=subdir'
assert 'eggname' == Link(url).egg_fragment
assert 'subdir' == Link(url).subdirectory_fragment
url = 'git+https://example.com/package#subdirectory=subdir&egg=eggname'
assert 'eggname' == Link(url).egg_fragment
assert 'subdir' == Link(url).subdirectory_fragment
@pytest.mark.parametrize(
("html", "url", "expected"),

View file

@ -507,10 +507,10 @@ def test_parse_editable_local(
exists_mock.return_value = isdir_mock.return_value = True
# mocks needed to support path operations on windows tests
abspath_mock.return_value = "/some/path"
assert parse_editable('.', 'git') == (None, 'file:///some/path', None, {})
assert parse_editable('.', 'git') == (None, 'file:///some/path', None)
abspath_mock.return_value = "/some/path/foo"
assert parse_editable('foo', 'git') == (
None, 'file:///some/path/foo', None, {},
None, 'file:///some/path/foo', None,
)
@ -519,7 +519,6 @@ def test_parse_editable_default_vcs():
'foo',
'git+https://foo#egg=foo',
None,
{'egg': 'foo'},
)
@ -528,7 +527,6 @@ def test_parse_editable_explicit_vcs():
'foo',
'svn+https://foo#egg=foo',
None,
{'egg': 'foo'},
)
@ -537,7 +535,6 @@ def test_parse_editable_vcs_extras():
'foo[extras]',
'svn+https://foo#egg=foo[extras]',
None,
{'egg': 'foo[extras]'},
)
@ -549,11 +546,11 @@ def test_parse_editable_local_extras(
exists_mock.return_value = isdir_mock.return_value = True
abspath_mock.return_value = "/some/path"
assert parse_editable('.[extras]', 'git') == (
None, 'file://' + "/some/path", ('extras',), {},
None, 'file://' + "/some/path", ('extras',),
)
abspath_mock.return_value = "/some/path/foo"
assert parse_editable('foo[bar,baz]', 'git') == (
None, 'file:///some/path/foo', ('bar', 'baz'), {},
None, 'file:///some/path/foo', ('bar', 'baz'),
)