Support markers in requirements

It's now possible to specify requirements markers in requirements.
Examples::

    futures; python_version < '2.7'
    mock; python_version < '3.3'
    nose
    ordereddict; python_version < '2.7'
    unittest2; python_version < '2.7'

The separator is "; ". For convinience, ";" alone is also supported, but
no in URLs. The ";" character is a legit and common character in an URL.
Example of valid URL without markers::

    http://foo.com/?p=bar.git;a=snapshot;h=v0.1;sf=tgz

Example of URL with markers::

    http://foo.com/?p=bar.git;a=snapshot;h=v0.1;sf=tgz; python_version < '3.3'
This commit is contained in:
Victor Stinner 2014-01-15 16:57:13 +01:00 committed by Donald Stufft
parent e236842944
commit e498d83db1
3 changed files with 72 additions and 3 deletions

View File

@ -29,6 +29,12 @@ and like arguments to :ref:`pip install`, the following forms are supported::
[-e] <local project path>
[-e] <vcs project url>
Since version 6.0, pip also supports markers using the "; " separator.
Examples::
futures; python_version < '2.7'
http://my.package.repo/SomePackage-1.0.4.zip; python_version >= '3.4'
See the :ref:`pip install Examples<pip install Examples>` for examples of all these forms.
A line that begins with ``#`` is treated as a comment and ignored. Whitespace

View File

@ -13,6 +13,7 @@ from distutils import sysconfig
from email.parser import FeedParser
from pip._vendor import pkg_resources, six
from pip._vendor.distlib.markers import interpret as markers_interpret
from pip._vendor.six.moves import configparser
from pip._vendor.six.moves.urllib import parse as urllib_parse
@ -116,8 +117,12 @@ class InstallRequirement(object):
requirement, directory containing 'setup.py', filename, or URL.
"""
url = None
if ';' in name:
name, markers = name.split(';', 1)
if is_url(name):
marker_sep = '; '
else:
marker_sep = ';'
if marker_sep in name:
name, markers = name.split(marker_sep, 1)
markers = markers.strip()
if not markers:
markers = None
@ -737,7 +742,7 @@ exec(compile(
def match_markers(self):
if self.markers is not None:
return markers.interpret(self.markers)
return markers_interpret(self.markers)
else:
return True

View File

@ -1,5 +1,6 @@
import os
import shutil
import sys
import tempfile
import pytest
@ -104,6 +105,63 @@ class TestInstallRequirement(object):
req = InstallRequirement.from_editable(url)
assert req.url == url
def test_markers(self):
for line in (
# recommanded syntax
'mock3; python_version >= "3"',
# with more spaces
'mock3 ; python_version >= "3" ',
# without spaces
'mock3;python_version >= "3"',
):
req = InstallRequirement.from_line(line)
assert req.req.project_name == 'mock3'
assert req.req.specs == []
assert req.markers == 'python_version >= "3"'
def test_markers_semicolon(self):
# check that the markers can contain a semicolon
req = InstallRequirement.from_line('semicolon; os_name == "a; b"')
assert req.req.project_name == 'semicolon'
assert req.req.specs == []
assert req.markers == 'os_name == "a; b"'
def test_markers_url(self):
# test "URL; markers" syntax
url = 'http://foo.com/?p=bar.git;a=snapshot;h=v0.1;sf=tgz'
line = '%s; python_version >= "3"' % url
req = InstallRequirement.from_line(line)
assert req.url == url, req.url
assert req.markers == 'python_version >= "3"'
# without space, markers are part of the URL
url = 'http://foo.com/?p=bar.git;a=snapshot;h=v0.1;sf=tgz'
line = '%s;python_version >= "3"' % url
req = InstallRequirement.from_line(line)
assert req.url == line, req.url
assert req.markers is None
def test_markers_match(self):
# match
for markers in (
'python_version >= "1.0"',
'sys_platform == %r' % sys.platform,
):
line = 'name; ' + markers
req = InstallRequirement.from_line(line)
assert req.markers == markers
assert req.match_markers()
# don't match
for markers in (
'python_version >= "5.0"',
'sys_platform != %r' % sys.platform,
):
line = 'name; ' + markers
req = InstallRequirement.from_line(line)
assert req.markers == markers
assert not req.match_markers()
def test_requirements_data_structure_keeps_order():
requirements = Requirements()