mirror of
https://github.com/pypa/pip
synced 2023-12-13 21:30:23 +01:00
Issue #2731: Constraints files.
This adds constraints files. Like requirements files constraints files control what version of a package is installed, but unlike requirements files this doesn't itself choose to install the package. This allows things that aren't explicitly desired to be constrained if and only if they are installed.
This commit is contained in:
parent
19623d820f
commit
bb0b429a49
14 changed files with 191 additions and 34 deletions
|
@ -1,5 +1,7 @@
|
||||||
**7.1.0 (unreleased)**
|
**7.1.0 (unreleased)**
|
||||||
|
|
||||||
|
* Allow constraining versions globally without having to know exactly what will
|
||||||
|
be installed by the pip command. :issue:`2731`.
|
||||||
|
|
||||||
**7.0.3 (2015-06-01)**
|
**7.0.3 (2015-06-01)**
|
||||||
|
|
||||||
|
|
|
@ -120,10 +120,14 @@ For example, to specify :ref:`--no-index <--no-index>` and 2 :ref:`--find-links
|
||||||
--find-links http://some.archives.com/archives
|
--find-links http://some.archives.com/archives
|
||||||
|
|
||||||
|
|
||||||
Lastly, if you wish, you can refer to other requirements files, like this::
|
If you wish, you can refer to other requirements files, like this::
|
||||||
|
|
||||||
-r more_requirements.txt
|
-r more_requirements.txt
|
||||||
|
|
||||||
|
You can also refer to constraints files, like this::
|
||||||
|
|
||||||
|
-c some_constraints.txt
|
||||||
|
|
||||||
.. _`Requirement Specifiers`:
|
.. _`Requirement Specifiers`:
|
||||||
|
|
||||||
Requirement Specifiers
|
Requirement Specifiers
|
||||||
|
|
|
@ -110,6 +110,39 @@ See also:
|
||||||
<https://caremad.io/blog/setup-vs-requirement/>`_
|
<https://caremad.io/blog/setup-vs-requirement/>`_
|
||||||
|
|
||||||
|
|
||||||
|
.. _`Constraints Files`:
|
||||||
|
|
||||||
|
Constraints Files
|
||||||
|
*****************
|
||||||
|
|
||||||
|
Constraints files are requirements files that only control which version of a
|
||||||
|
requirement is installed, not whether it is installed or not. Their syntax and
|
||||||
|
contents is nearly identical to :ref:`Requirements Files`. There is one key
|
||||||
|
difference: Including a package in a constraints file does not trigger
|
||||||
|
installation of the package.
|
||||||
|
|
||||||
|
Use a constraints file like so:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
pip install -c constraints.txt
|
||||||
|
|
||||||
|
Constraints files are used for exactly the same reason as requirements files
|
||||||
|
when you don't know exactly what things you want to install. For instance, say
|
||||||
|
that the "helloworld" package doesn't work in your environment, so you have a
|
||||||
|
local patched version. Some things you install depend on "helloworld", and some
|
||||||
|
don't.
|
||||||
|
|
||||||
|
One way to ensure that the patched version is used consistently is to
|
||||||
|
manually audit the dependencies of everything you install, and if "helloworld"
|
||||||
|
is present, write a requirements file to use when installing that thing.
|
||||||
|
|
||||||
|
Constraints files offer a better way: write a single constraints file for your
|
||||||
|
organisation and use that everywhere. If the thing being installed requires
|
||||||
|
"helloworld" to be installed, your fixed version specified in your constraints
|
||||||
|
file will be used.
|
||||||
|
|
||||||
|
Constraints file support was added in pip 7.1.
|
||||||
|
|
||||||
.. _`Installing from Wheels`:
|
.. _`Installing from Wheels`:
|
||||||
|
|
||||||
|
|
|
@ -261,6 +261,13 @@ class RequirementCommand(Command):
|
||||||
"""
|
"""
|
||||||
Marshal cmd line args into a requirement set.
|
Marshal cmd line args into a requirement set.
|
||||||
"""
|
"""
|
||||||
|
for filename in options.constraints:
|
||||||
|
for req in parse_requirements(
|
||||||
|
filename,
|
||||||
|
constraint=True, finder=finder, options=options,
|
||||||
|
session=session, wheel_cache=wheel_cache):
|
||||||
|
requirement_set.add_requirement(req)
|
||||||
|
|
||||||
for req in args:
|
for req in args:
|
||||||
requirement_set.add_requirement(
|
requirement_set.add_requirement(
|
||||||
InstallRequirement.from_line(
|
InstallRequirement.from_line(
|
||||||
|
|
|
@ -337,6 +337,17 @@ process_dependency_links = partial(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def constraints():
|
||||||
|
return Option(
|
||||||
|
'-c', '--constraint',
|
||||||
|
dest='constraints',
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
metavar='file',
|
||||||
|
help='Constrain versions using the given constraints file. '
|
||||||
|
'This option can be used multiple times.')
|
||||||
|
|
||||||
|
|
||||||
def requirements():
|
def requirements():
|
||||||
return Option(
|
return Option(
|
||||||
'-r', '--requirement',
|
'-r', '--requirement',
|
||||||
|
|
|
@ -56,6 +56,7 @@ class InstallCommand(RequirementCommand):
|
||||||
|
|
||||||
cmd_opts = self.cmd_opts
|
cmd_opts = self.cmd_opts
|
||||||
|
|
||||||
|
cmd_opts.add_option(cmdoptions.constraints())
|
||||||
cmd_opts.add_option(cmdoptions.editable())
|
cmd_opts.add_option(cmdoptions.editable())
|
||||||
cmd_opts.add_option(cmdoptions.requirements())
|
cmd_opts.add_option(cmdoptions.requirements())
|
||||||
cmd_opts.add_option(cmdoptions.build_dir())
|
cmd_opts.add_option(cmdoptions.build_dir())
|
||||||
|
|
|
@ -69,6 +69,7 @@ class WheelCommand(RequirementCommand):
|
||||||
metavar='options',
|
metavar='options',
|
||||||
action='append',
|
action='append',
|
||||||
help="Extra arguments to be supplied to 'setup.py bdist_wheel'.")
|
help="Extra arguments to be supplied to 'setup.py bdist_wheel'.")
|
||||||
|
cmd_opts.add_option(cmdoptions.constraints())
|
||||||
cmd_opts.add_option(cmdoptions.editable())
|
cmd_opts.add_option(cmdoptions.editable())
|
||||||
cmd_opts.add_option(cmdoptions.requirements())
|
cmd_opts.add_option(cmdoptions.requirements())
|
||||||
cmd_opts.add_option(cmdoptions.download_cache())
|
cmd_opts.add_option(cmdoptions.download_cache())
|
||||||
|
|
|
@ -25,6 +25,7 @@ SCHEME_RE = re.compile(r'^(http|https|file):', re.I)
|
||||||
COMMENT_RE = re.compile(r'(^|\s)+#.*$')
|
COMMENT_RE = re.compile(r'(^|\s)+#.*$')
|
||||||
|
|
||||||
SUPPORTED_OPTIONS = [
|
SUPPORTED_OPTIONS = [
|
||||||
|
cmdoptions.constraints,
|
||||||
cmdoptions.editable,
|
cmdoptions.editable,
|
||||||
cmdoptions.requirements,
|
cmdoptions.requirements,
|
||||||
cmdoptions.no_index,
|
cmdoptions.no_index,
|
||||||
|
@ -54,15 +55,16 @@ SUPPORTED_OPTIONS_REQ_DEST = [o().dest for o in SUPPORTED_OPTIONS_REQ]
|
||||||
|
|
||||||
|
|
||||||
def parse_requirements(filename, finder=None, comes_from=None, options=None,
|
def parse_requirements(filename, finder=None, comes_from=None, options=None,
|
||||||
session=None, wheel_cache=None):
|
session=None, constraint=False, wheel_cache=None):
|
||||||
"""
|
"""Parse a requirements file and yield InstallRequirement instances.
|
||||||
Parse a requirements file and yield InstallRequirement instances.
|
|
||||||
|
|
||||||
:param filename: Path or url of requirements file.
|
:param filename: Path or url of requirements file.
|
||||||
:param finder: Instance of pip.index.PackageFinder.
|
:param finder: Instance of pip.index.PackageFinder.
|
||||||
:param comes_from: Origin description of requirements.
|
:param comes_from: Origin description of requirements.
|
||||||
:param options: Global options.
|
:param options: Global options.
|
||||||
:param session: Instance of pip.download.PipSession.
|
:param session: Instance of pip.download.PipSession.
|
||||||
|
:param constraint: If true, parsing a constraint file rather than
|
||||||
|
requirements file.
|
||||||
:param wheel_cache: Instance of pip.wheel.WheelCache
|
:param wheel_cache: Instance of pip.wheel.WheelCache
|
||||||
"""
|
"""
|
||||||
if session is None:
|
if session is None:
|
||||||
|
@ -82,13 +84,15 @@ def parse_requirements(filename, finder=None, comes_from=None, options=None,
|
||||||
|
|
||||||
for line_number, line in enumerate(lines, 1):
|
for line_number, line in enumerate(lines, 1):
|
||||||
req_iter = process_line(line, filename, line_number, finder,
|
req_iter = process_line(line, filename, line_number, finder,
|
||||||
comes_from, options, session, wheel_cache)
|
comes_from, options, session, wheel_cache,
|
||||||
|
constraint=constraint)
|
||||||
for req in req_iter:
|
for req in req_iter:
|
||||||
yield req
|
yield req
|
||||||
|
|
||||||
|
|
||||||
def process_line(line, filename, line_number, finder=None, comes_from=None,
|
def process_line(line, filename, line_number, finder=None, comes_from=None,
|
||||||
options=None, session=None, wheel_cache=None):
|
options=None, session=None, wheel_cache=None,
|
||||||
|
constraint=False):
|
||||||
"""Process a single requirements line; This can result in creating/yielding
|
"""Process a single requirements line; This can result in creating/yielding
|
||||||
requirements, or updating the finder.
|
requirements, or updating the finder.
|
||||||
|
|
||||||
|
@ -103,8 +107,8 @@ def process_line(line, filename, line_number, finder=None, comes_from=None,
|
||||||
(although our docs imply only one is supported), and all our parsed and
|
(although our docs imply only one is supported), and all our parsed and
|
||||||
affect the finder.
|
affect the finder.
|
||||||
|
|
||||||
|
:param constraint: If True, parsing a constraints file.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
parser = build_parser()
|
parser = build_parser()
|
||||||
defaults = parser.get_default_values()
|
defaults = parser.get_default_values()
|
||||||
defaults.index_url = None
|
defaults.index_url = None
|
||||||
|
@ -114,9 +118,12 @@ def process_line(line, filename, line_number, finder=None, comes_from=None,
|
||||||
args_str, options_str = break_args_options(line)
|
args_str, options_str = break_args_options(line)
|
||||||
opts, _ = parser.parse_args(shlex.split(options_str), defaults)
|
opts, _ = parser.parse_args(shlex.split(options_str), defaults)
|
||||||
|
|
||||||
|
# preserve for the nested code path
|
||||||
|
line_comes_from = '%s %s (line %s)' % (
|
||||||
|
'-c' if constraint else '-r', filename, line_number)
|
||||||
|
|
||||||
# yield a line requirement
|
# yield a line requirement
|
||||||
if args_str:
|
if args_str:
|
||||||
comes_from = '-r %s (line %s)' % (filename, line_number)
|
|
||||||
isolated = options.isolated_mode if options else False
|
isolated = options.isolated_mode if options else False
|
||||||
if options:
|
if options:
|
||||||
cmdoptions.check_install_build_global(options, opts)
|
cmdoptions.check_install_build_global(options, opts)
|
||||||
|
@ -126,24 +133,28 @@ def process_line(line, filename, line_number, finder=None, comes_from=None,
|
||||||
if dest in opts.__dict__ and opts.__dict__[dest]:
|
if dest in opts.__dict__ and opts.__dict__[dest]:
|
||||||
req_options[dest] = opts.__dict__[dest]
|
req_options[dest] = opts.__dict__[dest]
|
||||||
yield InstallRequirement.from_line(
|
yield InstallRequirement.from_line(
|
||||||
args_str, comes_from, isolated=isolated, options=req_options,
|
args_str, line_comes_from, constraint=constraint,
|
||||||
wheel_cache=wheel_cache
|
isolated=isolated, options=req_options, wheel_cache=wheel_cache
|
||||||
)
|
)
|
||||||
|
|
||||||
# yield an editable requirement
|
# yield an editable requirement
|
||||||
elif opts.editables:
|
elif opts.editables:
|
||||||
comes_from = '-r %s (line %s)' % (filename, line_number)
|
|
||||||
isolated = options.isolated_mode if options else False
|
isolated = options.isolated_mode if options else False
|
||||||
default_vcs = options.default_vcs if options else None
|
default_vcs = options.default_vcs if options else None
|
||||||
yield InstallRequirement.from_editable(
|
yield InstallRequirement.from_editable(
|
||||||
opts.editables[0], comes_from=comes_from,
|
opts.editables[0], comes_from=line_comes_from,
|
||||||
default_vcs=default_vcs, isolated=isolated,
|
constraint=constraint, default_vcs=default_vcs, isolated=isolated,
|
||||||
wheel_cache=wheel_cache
|
wheel_cache=wheel_cache
|
||||||
)
|
)
|
||||||
|
|
||||||
# parse a nested requirements file
|
# parse a nested requirements file
|
||||||
elif opts.requirements:
|
elif opts.requirements or opts.constraints:
|
||||||
|
if opts.requirements:
|
||||||
req_path = opts.requirements[0]
|
req_path = opts.requirements[0]
|
||||||
|
nested_constraint = False
|
||||||
|
else:
|
||||||
|
req_path = opts.constraints[0]
|
||||||
|
nested_constraint = True
|
||||||
# original file is over http
|
# original file is over http
|
||||||
if SCHEME_RE.search(filename):
|
if SCHEME_RE.search(filename):
|
||||||
# do a url join so relative paths work
|
# do a url join so relative paths work
|
||||||
|
@ -156,7 +167,7 @@ def process_line(line, filename, line_number, finder=None, comes_from=None,
|
||||||
# TODO: Why not use `comes_from='-r {} (line {})'` here as well?
|
# TODO: Why not use `comes_from='-r {} (line {})'` here as well?
|
||||||
parser = parse_requirements(
|
parser = parse_requirements(
|
||||||
req_path, finder, comes_from, options, session,
|
req_path, finder, comes_from, options, session,
|
||||||
wheel_cache=wheel_cache
|
constraint=nested_constraint, wheel_cache=wheel_cache
|
||||||
)
|
)
|
||||||
for req in parser:
|
for req in parser:
|
||||||
yield req
|
yield req
|
||||||
|
|
|
@ -60,7 +60,7 @@ class InstallRequirement(object):
|
||||||
def __init__(self, req, comes_from, source_dir=None, editable=False,
|
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, editable_options=None,
|
||||||
pycompile=True, markers=None, isolated=False, options=None,
|
pycompile=True, markers=None, isolated=False, options=None,
|
||||||
wheel_cache=None):
|
wheel_cache=None, constraint=False):
|
||||||
self.extras = ()
|
self.extras = ()
|
||||||
if isinstance(req, six.string_types):
|
if isinstance(req, six.string_types):
|
||||||
req = pkg_resources.Requirement.parse(req)
|
req = pkg_resources.Requirement.parse(req)
|
||||||
|
@ -68,6 +68,7 @@ class InstallRequirement(object):
|
||||||
|
|
||||||
self.req = req
|
self.req = req
|
||||||
self.comes_from = comes_from
|
self.comes_from = comes_from
|
||||||
|
self.constraint = constraint
|
||||||
self.source_dir = source_dir
|
self.source_dir = source_dir
|
||||||
self.editable = editable
|
self.editable = editable
|
||||||
|
|
||||||
|
@ -106,7 +107,8 @@ class InstallRequirement(object):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_editable(cls, editable_req, comes_from=None, default_vcs=None,
|
def from_editable(cls, editable_req, comes_from=None, default_vcs=None,
|
||||||
isolated=False, options=None, wheel_cache=None):
|
isolated=False, options=None, wheel_cache=None,
|
||||||
|
constraint=False):
|
||||||
from pip.index import Link
|
from pip.index import Link
|
||||||
|
|
||||||
name, url, extras_override, editable_options = parse_editable(
|
name, url, extras_override, editable_options = parse_editable(
|
||||||
|
@ -119,6 +121,7 @@ class InstallRequirement(object):
|
||||||
res = cls(name, comes_from, source_dir=source_dir,
|
res = cls(name, comes_from, source_dir=source_dir,
|
||||||
editable=True,
|
editable=True,
|
||||||
link=Link(url),
|
link=Link(url),
|
||||||
|
constraint=constraint,
|
||||||
editable_options=editable_options,
|
editable_options=editable_options,
|
||||||
isolated=isolated,
|
isolated=isolated,
|
||||||
options=options if options else {},
|
options=options if options else {},
|
||||||
|
@ -132,7 +135,7 @@ class InstallRequirement(object):
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_line(
|
def from_line(
|
||||||
cls, name, comes_from=None, isolated=False, options=None,
|
cls, name, comes_from=None, isolated=False, options=None,
|
||||||
wheel_cache=None):
|
wheel_cache=None, constraint=False):
|
||||||
"""Creates an InstallRequirement from a name, which might be a
|
"""Creates an InstallRequirement from a name, which might be a
|
||||||
requirement, directory containing 'setup.py', filename, or URL.
|
requirement, directory containing 'setup.py', filename, or URL.
|
||||||
"""
|
"""
|
||||||
|
@ -204,7 +207,7 @@ class InstallRequirement(object):
|
||||||
options = options if options else {}
|
options = options if options else {}
|
||||||
res = cls(req, comes_from, link=link, markers=markers,
|
res = cls(req, comes_from, link=link, markers=markers,
|
||||||
isolated=isolated, options=options,
|
isolated=isolated, options=options,
|
||||||
wheel_cache=wheel_cache)
|
wheel_cache=wheel_cache, constraint=constraint)
|
||||||
|
|
||||||
if extras:
|
if extras:
|
||||||
res.extras = pkg_resources.Requirement.parse('__placeholder__' +
|
res.extras = pkg_resources.Requirement.parse('__placeholder__' +
|
||||||
|
|
|
@ -231,11 +231,16 @@ class RequirementSet(object):
|
||||||
self.unnamed_requirements.append(install_req)
|
self.unnamed_requirements.append(install_req)
|
||||||
return [install_req]
|
return [install_req]
|
||||||
else:
|
else:
|
||||||
if parent_req_name is None and self.has_requirement(name):
|
try:
|
||||||
|
existing_req = self.get_requirement(name)
|
||||||
|
except KeyError:
|
||||||
|
existing_req = None
|
||||||
|
if (parent_req_name is None and existing_req and not
|
||||||
|
existing_req.constraint):
|
||||||
raise InstallationError(
|
raise InstallationError(
|
||||||
'Double requirement given: %s (already in %s, name=%r)'
|
'Double requirement given: %s (already in %s, name=%r)'
|
||||||
% (install_req, self.get_requirement(name), name))
|
% (install_req, existing_req, name))
|
||||||
if not self.has_requirement(name):
|
if not existing_req:
|
||||||
# Add requirement
|
# Add requirement
|
||||||
self.requirements[name] = install_req
|
self.requirements[name] = install_req
|
||||||
# FIXME: what about other normalizations? E.g., _ vs. -?
|
# FIXME: what about other normalizations? E.g., _ vs. -?
|
||||||
|
@ -243,10 +248,19 @@ class RequirementSet(object):
|
||||||
self.requirement_aliases[name.lower()] = name
|
self.requirement_aliases[name.lower()] = name
|
||||||
result = [install_req]
|
result = [install_req]
|
||||||
else:
|
else:
|
||||||
# Canonicalise to the already-added object
|
if not existing_req.constraint:
|
||||||
install_req = self.get_requirement(name)
|
# No need to scan, we've already encountered this for
|
||||||
# No need to scan, this is a duplicate requirement.
|
# scanning.
|
||||||
result = []
|
result = []
|
||||||
|
elif not install_req.constraint:
|
||||||
|
# If we're now installing a constraint, mark the existing
|
||||||
|
# object for real installation.
|
||||||
|
existing_req.constraint = False
|
||||||
|
# And now we need to scan this.
|
||||||
|
result = [existing_req]
|
||||||
|
# Canonicalise to the already-added object for the backref
|
||||||
|
# check below.
|
||||||
|
install_req = existing_req
|
||||||
if parent_req_name:
|
if parent_req_name:
|
||||||
parent_req = self.get_requirement(parent_req_name)
|
parent_req = self.get_requirement(parent_req_name)
|
||||||
self._dependencies[parent_req].append(install_req)
|
self._dependencies[parent_req].append(install_req)
|
||||||
|
@ -260,7 +274,8 @@ class RequirementSet(object):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_requirements(self):
|
def has_requirements(self):
|
||||||
return list(self.requirements.values()) or self.unnamed_requirements
|
return list(req for req in self.requirements.values() if not
|
||||||
|
req.constraint) or self.unnamed_requirements
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_download(self):
|
def is_download(self):
|
||||||
|
@ -285,6 +300,8 @@ class RequirementSet(object):
|
||||||
|
|
||||||
def uninstall(self, auto_confirm=False):
|
def uninstall(self, auto_confirm=False):
|
||||||
for req in self.requirements.values():
|
for req in self.requirements.values():
|
||||||
|
if req.constraint:
|
||||||
|
continue
|
||||||
req.uninstall(auto_confirm=auto_confirm)
|
req.uninstall(auto_confirm=auto_confirm)
|
||||||
req.commit_uninstall()
|
req.commit_uninstall()
|
||||||
|
|
||||||
|
@ -376,6 +393,9 @@ class RequirementSet(object):
|
||||||
# Tell user what we are doing for this requirement:
|
# Tell user what we are doing for this requirement:
|
||||||
# obtain (editable), skipping, processing (local url), collecting
|
# obtain (editable), skipping, processing (local url), collecting
|
||||||
# (remote url or package name)
|
# (remote url or package name)
|
||||||
|
if req_to_install.constraint:
|
||||||
|
return []
|
||||||
|
|
||||||
if req_to_install.editable:
|
if req_to_install.editable:
|
||||||
logger.info('Obtaining %s', req_to_install)
|
logger.info('Obtaining %s', req_to_install)
|
||||||
else:
|
else:
|
||||||
|
@ -584,6 +604,8 @@ class RequirementSet(object):
|
||||||
def schedule(req):
|
def schedule(req):
|
||||||
if req.satisfied_by or req in ordered_reqs:
|
if req.satisfied_by or req in ordered_reqs:
|
||||||
return
|
return
|
||||||
|
if req.constraint:
|
||||||
|
return
|
||||||
ordered_reqs.add(req)
|
ordered_reqs.add(req)
|
||||||
for dep in self._dependencies[req]:
|
for dep in self._dependencies[req]:
|
||||||
schedule(dep)
|
schedule(dep)
|
||||||
|
|
|
@ -708,6 +708,8 @@ class WheelBuilder(object):
|
||||||
|
|
||||||
buildset = []
|
buildset = []
|
||||||
for req in reqset:
|
for req in reqset:
|
||||||
|
if req.constraint:
|
||||||
|
continue
|
||||||
if req.is_wheel:
|
if req.is_wheel:
|
||||||
if not autobuilding:
|
if not autobuilding:
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|
|
@ -106,6 +106,16 @@ def test_multiple_requirements_files(script, tmpdir):
|
||||||
assert script.venv / 'src' / 'initools' in result.files_created
|
assert script.venv / 'src' / 'initools' in result.files_created
|
||||||
|
|
||||||
|
|
||||||
|
def test_multiple_constraints_files(script, data):
|
||||||
|
script.scratch_path.join("outer.txt").write("-c inner.txt")
|
||||||
|
script.scratch_path.join("inner.txt").write(
|
||||||
|
"Upper==1.0")
|
||||||
|
result = script.pip(
|
||||||
|
'install', '--no-index', '-f', data.find_links, '-c',
|
||||||
|
script.scratch_path / 'outer.txt', 'Upper')
|
||||||
|
assert 'installed Upper-1.0' in result.stdout
|
||||||
|
|
||||||
|
|
||||||
def test_respect_order_in_requirements_file(script, data):
|
def test_respect_order_in_requirements_file(script, data):
|
||||||
script.scratch_path.join("frameworks-req.txt").write(textwrap.dedent("""\
|
script.scratch_path.join("frameworks-req.txt").write(textwrap.dedent("""\
|
||||||
parent
|
parent
|
||||||
|
@ -192,3 +202,19 @@ def test_install_option_in_requirements_file(script, data, virtualenv):
|
||||||
|
|
||||||
package_dir = script.scratch / 'home1' / 'lib' / 'python' / 'simple'
|
package_dir = script.scratch / 'home1' / 'lib' / 'python' / 'simple'
|
||||||
assert package_dir in result.files_created
|
assert package_dir in result.files_created
|
||||||
|
|
||||||
|
|
||||||
|
def test_constraints_not_installed_by_default(script, data):
|
||||||
|
script.scratch_path.join("c.txt").write("requiresupper")
|
||||||
|
result = script.pip(
|
||||||
|
'install', '--no-index', '-f', data.find_links, '-c',
|
||||||
|
script.scratch_path / 'c.txt', 'Upper')
|
||||||
|
assert 'requiresupper' not in result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def test_constraints_only_causes_error(script, data):
|
||||||
|
script.scratch_path.join("c.txt").write("requiresupper")
|
||||||
|
result = script.pip(
|
||||||
|
'install', '--no-index', '-f', data.find_links, '-c',
|
||||||
|
script.scratch_path / 'c.txt', expect_error=True)
|
||||||
|
assert 'installed requiresupper' not in result.stdout
|
||||||
|
|
|
@ -89,6 +89,16 @@ class TestProcessLine(object):
|
||||||
req = InstallRequirement.from_line(line, comes_from=comes_from)
|
req = InstallRequirement.from_line(line, comes_from=comes_from)
|
||||||
assert repr(list(process_line(line, filename, 1))[0]) == repr(req)
|
assert repr(list(process_line(line, filename, 1))[0]) == repr(req)
|
||||||
|
|
||||||
|
def test_yield_line_constraint(self):
|
||||||
|
line = 'SomeProject'
|
||||||
|
filename = 'filename'
|
||||||
|
comes_from = '-c %s (line %s)' % (filename, 1)
|
||||||
|
req = InstallRequirement.from_line(
|
||||||
|
line, comes_from=comes_from, constraint=True)
|
||||||
|
found_req = list(process_line(line, filename, 1, constraint=True))[0]
|
||||||
|
assert repr(found_req) == repr(req)
|
||||||
|
assert found_req.constraint is True
|
||||||
|
|
||||||
def test_yield_line_requirement_with_spaces_in_specifier(self):
|
def test_yield_line_requirement_with_spaces_in_specifier(self):
|
||||||
line = 'SomeProject >= 2'
|
line = 'SomeProject >= 2'
|
||||||
filename = 'filename'
|
filename = 'filename'
|
||||||
|
@ -105,18 +115,42 @@ class TestProcessLine(object):
|
||||||
req = InstallRequirement.from_editable(url, comes_from=comes_from)
|
req = InstallRequirement.from_editable(url, comes_from=comes_from)
|
||||||
assert repr(list(process_line(line, filename, 1))[0]) == repr(req)
|
assert repr(list(process_line(line, filename, 1))[0]) == repr(req)
|
||||||
|
|
||||||
|
def test_yield_editable_constraint(self):
|
||||||
|
url = 'git+https://url#egg=SomeProject'
|
||||||
|
line = '-e %s' % url
|
||||||
|
filename = 'filename'
|
||||||
|
comes_from = '-c %s (line %s)' % (filename, 1)
|
||||||
|
req = InstallRequirement.from_editable(
|
||||||
|
url, comes_from=comes_from, constraint=True)
|
||||||
|
found_req = list(process_line(line, filename, 1, constraint=True))[0]
|
||||||
|
assert repr(found_req) == repr(req)
|
||||||
|
assert found_req.constraint is True
|
||||||
|
|
||||||
def test_nested_requirements_file(self, monkeypatch):
|
def test_nested_requirements_file(self, monkeypatch):
|
||||||
line = '-r another_file'
|
line = '-r another_file'
|
||||||
req = InstallRequirement.from_line('SomeProject')
|
req = InstallRequirement.from_line('SomeProject')
|
||||||
import pip.req.req_file
|
import pip.req.req_file
|
||||||
|
|
||||||
def stub_parse_requirements(req_url, finder, comes_from, options,
|
def stub_parse_requirements(req_url, finder, comes_from, options,
|
||||||
session, wheel_cache):
|
session, wheel_cache, constraint):
|
||||||
return [req]
|
return [(req, constraint)]
|
||||||
parse_requirements_stub = stub(call=stub_parse_requirements)
|
parse_requirements_stub = stub(call=stub_parse_requirements)
|
||||||
monkeypatch.setattr(pip.req.req_file, 'parse_requirements',
|
monkeypatch.setattr(pip.req.req_file, 'parse_requirements',
|
||||||
parse_requirements_stub.call)
|
parse_requirements_stub.call)
|
||||||
assert list(process_line(line, 'filename', 1)) == [req]
|
assert list(process_line(line, 'filename', 1)) == [(req, False)]
|
||||||
|
|
||||||
|
def test_nested_constraints_file(self, monkeypatch):
|
||||||
|
line = '-c another_file'
|
||||||
|
req = InstallRequirement.from_line('SomeProject')
|
||||||
|
import pip.req.req_file
|
||||||
|
|
||||||
|
def stub_parse_requirements(req_url, finder, comes_from, options,
|
||||||
|
session, wheel_cache, constraint):
|
||||||
|
return [(req, constraint)]
|
||||||
|
parse_requirements_stub = stub(call=stub_parse_requirements)
|
||||||
|
monkeypatch.setattr(pip.req.req_file, 'parse_requirements',
|
||||||
|
parse_requirements_stub.call)
|
||||||
|
assert list(process_line(line, 'filename', 1)) == [(req, True)]
|
||||||
|
|
||||||
def test_options_on_a_requirement_line(self):
|
def test_options_on_a_requirement_line(self):
|
||||||
line = 'SomeProject --install-option=yo1 --install-option yo2 '\
|
line = 'SomeProject --install-option=yo1 --install-option yo2 '\
|
||||||
|
|
|
@ -373,7 +373,7 @@ class TestWheelBuilder(object):
|
||||||
|
|
||||||
def test_skip_building_wheels(self, caplog):
|
def test_skip_building_wheels(self, caplog):
|
||||||
with patch('pip.wheel.WheelBuilder._build_one') as mock_build_one:
|
with patch('pip.wheel.WheelBuilder._build_one') as mock_build_one:
|
||||||
wheel_req = Mock(is_wheel=True, editable=False)
|
wheel_req = Mock(is_wheel=True, editable=False, constraint=False)
|
||||||
reqset = Mock(requirements=Mock(values=lambda: [wheel_req]),
|
reqset = Mock(requirements=Mock(values=lambda: [wheel_req]),
|
||||||
wheel_download_dir='/wheel/dir')
|
wheel_download_dir='/wheel/dir')
|
||||||
wb = wheel.WheelBuilder(reqset, Mock())
|
wb = wheel.WheelBuilder(reqset, Mock())
|
||||||
|
@ -383,8 +383,8 @@ class TestWheelBuilder(object):
|
||||||
|
|
||||||
def test_skip_building_editables(self, caplog):
|
def test_skip_building_editables(self, caplog):
|
||||||
with patch('pip.wheel.WheelBuilder._build_one') as mock_build_one:
|
with patch('pip.wheel.WheelBuilder._build_one') as mock_build_one:
|
||||||
editable_req = Mock(editable=True, is_wheel=False)
|
editable = Mock(editable=True, is_wheel=False, constraint=False)
|
||||||
reqset = Mock(requirements=Mock(values=lambda: [editable_req]),
|
reqset = Mock(requirements=Mock(values=lambda: [editable]),
|
||||||
wheel_download_dir='/wheel/dir')
|
wheel_download_dir='/wheel/dir')
|
||||||
wb = wheel.WheelBuilder(reqset, Mock())
|
wb = wheel.WheelBuilder(reqset, Mock())
|
||||||
wb.build()
|
wb.build()
|
||||||
|
|
Loading…
Reference in a new issue