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

1) parse with defaults set as they are naturally (vs forcing to None)

and adjust the logic to match; the result is simpler.
2) Due to #1, we can remove some hairy "format_control" hacks
3) Due to #1, we have to relax the parsing and allow:
   - multiple options per line
   - any supported option on a line with a requirement (not just
     --install-option/--global-option, although they are the only
     options that are passed into a requirement)
This commit is contained in:
Marcus Smith 2015-04-24 22:32:26 -07:00
parent 0d2cc68da9
commit 7c83f8d3cd
3 changed files with 55 additions and 114 deletions

View file

@ -22,15 +22,6 @@ class RequirementsFileParseError(InstallationError):
"""Raised when a general error occurs parsing a requirements file line."""
class ReqFileOnleOneOptionPerLineError(InstallationError):
"""Raised when an option is not allowed in a requirements file."""
class ReqFileOptionNotAllowedWithReqError(InstallationError):
"""Raised when an option is not allowed on a requirement line in a requirements
file."""
class BestVersionAlreadyInstalled(PipError):
"""Raised when the most up-to-date version of a package is already
installed."""

View file

@ -15,9 +15,7 @@ from pip._vendor.six.moves import filterfalse
import pip
from pip.download import get_file_content
from pip.req.req_install import InstallRequirement
from pip.exceptions import (RequirementsFileParseError,
ReqFileOnleOneOptionPerLineError,
ReqFileOptionNotAllowedWithReqError)
from pip.exceptions import (RequirementsFileParseError)
from pip.utils import normalize_name
from pip import cmdoptions
@ -40,16 +38,16 @@ SUPPORTED_OPTIONS = [
cmdoptions.use_wheel,
cmdoptions.no_use_wheel,
cmdoptions.always_unzip,
]
# options allowed on requirement lines
SUPPORTED_OPTIONS_REQ = [
cmdoptions.install_options,
cmdoptions.global_options,
cmdoptions.no_binary,
cmdoptions.only_binary,
]
# options to be passed to requirements
SUPPORTED_OPTIONS_REQ = [
cmdoptions.install_options,
cmdoptions.global_options
]
# the 'dest' string values
SUPPORTED_OPTIONS_REQ_DEST = [o().dest for o in SUPPORTED_OPTIONS_REQ]
@ -94,46 +92,14 @@ def process_line(line, filename, line_number, finder=None, comes_from=None,
Process a single requirements line; This can result in creating/yielding
requirements, or updating the finder.
"""
parser = build_parser()
values = parser.get_default_values()
defaults = parser.get_default_values()
defaults.index_url = None
if finder:
values.format_control = finder.format_control
else:
# Undo the hack that removes defaults so that
# this can be parsed correctly.
values.format_control = pip.index.FormatControl(set(), set())
orig_no_binary = frozenset(values.format_control.no_binary)
orig_only_binary = frozenset(values.format_control.only_binary)
args = shlex.split(line)
opts, args = parser.parse_args(args, values)
if opts.use_wheel is False and finder:
pip.index.fmt_ctl_no_use_wheel(finder.format_control)
setattr(values, 'use_wheel', None)
if (orig_no_binary == opts.format_control.no_binary and
orig_only_binary == opts.format_control.only_binary):
# Make the per-requirement-line check work.
setattr(values, 'format_control', None)
req = None
if args:
for key, value in opts.__dict__.items():
# only certain options can be on req lines
if value is not None and key not in SUPPORTED_OPTIONS_REQ_DEST:
# get the option string
# the option must be supported to get to this point
for o in SUPPORTED_OPTIONS:
o = o()
if o.dest == key:
opt_string = o.get_opt_string()
msg = ('Option not supported on a'
' requirement line: %s' % opt_string)
raise ReqFileOptionNotAllowedWithReqError(msg)
# don't allow multiple/different options (on non-req lines)
if not args and len(
[v for v in opts.__dict__.values() if v is not None]) > 1:
msg = 'Only one option allowed per line.'
raise ReqFileOnleOneOptionPerLineError(msg)
# `finder.format_control` will be updated during parsing
defaults.format_control = finder.format_control
opts, args = parser.parse_args(shlex.split(line), defaults)
# yield a line requirement
if args:
@ -142,12 +108,13 @@ def process_line(line, filename, line_number, finder=None, comes_from=None,
isolated = options.isolated_mode if options else False
if options:
cmdoptions.check_install_build_global(options, opts)
# trim the None items
keys = [opt for opt in opts.__dict__ if getattr(opts, opt) is None]
for key in keys:
delattr(opts, key)
# get the options that apply to requirements
req_options = {}
for dest in SUPPORTED_OPTIONS_REQ_DEST:
if dest in opts.__dict__ and opts.__dict__[dest]:
req_options[dest] = opts.__dict__[dest]
yield InstallRequirement.from_line(
args_line, comes_from, isolated=isolated, options=opts.__dict__,
args_line, comes_from, isolated=isolated, options=req_options,
wheel_cache=wheel_cache
)
@ -181,24 +148,25 @@ def process_line(line, filename, line_number, finder=None, comes_from=None,
# set finder options
elif finder:
if opts.use_wheel is not None:
finder.use_wheel = opts.use_wheel
elif opts.no_index is not None:
finder.index_urls = []
elif opts.allow_all_external is not None:
finder.allow_all_external = opts.allow_all_external
elif opts.index_url is not None:
if opts.index_url:
finder.index_urls = [opts.index_url]
elif opts.extra_index_urls is not None:
if opts.use_wheel is False:
finder.use_wheel = False
pip.index.fmt_ctl_no_use_wheel(finder.format_control)
elif opts.no_index is True:
finder.index_urls = []
elif opts.allow_all_external:
finder.allow_all_external = opts.allow_all_external
elif opts.extra_index_urls:
finder.index_urls.extend(opts.extra_index_urls)
elif opts.allow_external is not None:
elif opts.allow_external:
finder.allow_external |= set(
[normalize_name(v).lower() for v in opts.allow_external])
elif opts.allow_unverified is not None:
elif opts.allow_unverified:
# Remove after 7.0
finder.allow_unverified |= set(
[normalize_name(v).lower() for v in opts.allow_unverified])
elif opts.find_links is not None:
elif opts.find_links:
# FIXME: it would be nice to keep track of the source
# of the find_links: support a find-links local path
# relative to a requirements file.
@ -216,12 +184,9 @@ def build_parser():
"""
parser = optparse.OptionParser(add_help_option=False)
options = SUPPORTED_OPTIONS + SUPPORTED_OPTIONS_REQ
for option_factory in options:
option_factories = SUPPORTED_OPTIONS + SUPPORTED_OPTIONS_REQ
for option_factory in option_factories:
option = option_factory()
# we want no default values; defaults are handled in `pip install`
# parsing. just concerned with values that are specifically set.
option.default = None
parser.add_option(option)
# By default optparse sys.exits on parsing errors. We want to wrap

View file

@ -1,4 +1,3 @@
from optparse import Values
import os
import subprocess
from textwrap import dedent
@ -8,11 +7,9 @@ import pytest
from pretend import stub
import pip
from pip.exceptions import (RequirementsFileParseError,
ReqFileOnleOneOptionPerLineError,
ReqFileOptionNotAllowedWithReqError)
from pip.exceptions import (RequirementsFileParseError)
from pip.download import PipSession
from pip.index import FormatControl, PackageFinder
from pip.index import PackageFinder
from pip.req.req_install import InstallRequirement
from pip.req.req_file import (parse_requirements, process_line, join_lines,
ignore_comments)
@ -28,6 +25,14 @@ def finder(session):
return PackageFinder([], [], session=session)
@pytest.fixture
def options(session):
return stub(
isolated_mode=False, default_vcs=None, index_url='default_url',
skip_requirements_regex=False,
format_control=pip.index.FormatControl(set(), set()))
class TestIgnoreComments(object):
"""tests for `ignore_comment`"""
@ -68,12 +73,6 @@ class TestJoinLines(object):
class TestProcessLine(object):
"""tests for `process_line`"""
def setup(self):
self.options = stub(
isolated_mode=False, default_vcs=None,
skip_requirements_regex=False,
format_control=pip.index.FormatControl(set(), set()))
def test_parser_error(self):
with pytest.raises(RequirementsFileParseError):
list(process_line("--bogus", "file", 1))
@ -83,14 +82,6 @@ class TestProcessLine(object):
with pytest.raises(ValueError):
list(process_line("req1 req2", "file", 1))
def test_only_one_option_per_line(self):
with pytest.raises(ReqFileOnleOneOptionPerLineError):
list(process_line("--index-url=url --no-use-wheel", "file", 1))
def test_option_not_allowed_on_req_line(self):
with pytest.raises(ReqFileOptionNotAllowedWithReqError):
list(process_line("req --index-url=url", "file", 1))
def test_yield_line_requirement(self):
line = 'SomeProject'
filename = 'filename'
@ -136,19 +127,19 @@ class TestProcessLine(object):
'global_options': ['yo3', 'yo4'],
'install_options': ['yo1', 'yo2']}
def test_set_isolated(self):
def test_set_isolated(self, options):
line = 'SomeProject'
filename = 'filename'
self.options.isolated_mode = True
result = process_line(line, filename, 1, options=self.options)
options.isolated_mode = True
result = process_line(line, filename, 1, options=options)
assert list(result)[0].isolated
def test_set_default_vcs(self):
def test_set_default_vcs(self, options):
url = 'https://url#egg=SomeProject'
line = '-e %s' % url
filename = 'filename'
self.options.default_vcs = 'git'
result = process_line(line, filename, 1, options=self.options)
options.default_vcs = 'git'
result = process_line(line, filename, 1, options=options)
assert list(result)[0].link.url == 'git+' + url
def test_set_finder_no_index(self, finder):
@ -237,19 +228,18 @@ class TestParseRequirements(object):
'tests/req_just_comment.txt', session=PipSession()):
pass
def test_multiple_appending_options(self, tmpdir, finder):
def test_multiple_appending_options(self, tmpdir, finder, options):
with open(tmpdir.join("req1.txt"), "w") as fp:
fp.write("--extra-index-url url1 \n")
fp.write("--extra-index-url url2 ")
list(parse_requirements(tmpdir.join("req1.txt"), finder=finder,
session=PipSession()))
session=PipSession(), options=options))
assert finder.index_urls == ['url1', 'url2']
def test_skip_regex(self, tmpdir, finder):
options = stub(isolated_mode=False, default_vcs=None,
skip_requirements_regex='.*Bad.*')
def test_skip_regex(self, tmpdir, finder, options):
options.skip_requirements_regex = '.*Bad.*'
with open(tmpdir.join("req1.txt"), "w") as fp:
fp.write("--extra-index-url Bad \n")
fp.write("--extra-index-url Good ")
@ -268,8 +258,7 @@ class TestParseRequirements(object):
assert finder.index_urls == ['url1', 'url2']
def test_req_file_parse_no_only_binary(self, data):
finder = PackageFinder([], [], session=PipSession())
def test_req_file_parse_no_only_binary(self, data, finder):
list(parse_requirements(
data.reqfiles.join("supported_options2.txt"), finder,
session=PipSession()))
@ -333,7 +322,7 @@ class TestParseRequirements(object):
parse_requirements(tmpdir.join("req.txt"), session=PipSession())
def test_install_requirements_with_options(self, tmpdir, finder, session):
def test_install_requirements_with_options(self, tmpdir, finder, session, options):
global_option = '--dry-run'
install_option = '--prefix=/opt'
@ -347,10 +336,6 @@ class TestParseRequirements(object):
with open(req_path, 'w') as fh:
fh.write(content)
options = Values()
options.format_control = FormatControl(set(), set())
options.skip_requirements_regex = None
options.isolated_mode = False
req = next(parse_requirements(
req_path, finder=finder, options=options, session=session))