From e498d83db191ccd7fe7c1c2e4f9ec201787d1257 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 15 Jan 2014 16:57:13 +0100 Subject: [PATCH] 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' --- docs/reference/pip_install.rst | 6 ++++ pip/req/req_install.py | 11 +++++-- tests/unit/test_req.py | 58 ++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/docs/reference/pip_install.rst b/docs/reference/pip_install.rst index 6e29aa63f..90d6485c0 100644 --- a/docs/reference/pip_install.rst +++ b/docs/reference/pip_install.rst @@ -29,6 +29,12 @@ and like arguments to :ref:`pip install`, the following forms are supported:: [-e] [-e] +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` for examples of all these forms. A line that begins with ``#`` is treated as a comment and ignored. Whitespace diff --git a/pip/req/req_install.py b/pip/req/req_install.py index 62109f4b6..6057dbdbd 100644 --- a/pip/req/req_install.py +++ b/pip/req/req_install.py @@ -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 diff --git a/tests/unit/test_req.py b/tests/unit/test_req.py index dfa306d7e..2ae5d593c 100644 --- a/tests/unit/test_req.py +++ b/tests/unit/test_req.py @@ -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()