Merge pull request #4393 from tk0miya/4183_follow_pep440

Fix #4183: doctest: ``:pyversion:`` option also follows PEP-440 specification
This commit is contained in:
Takeshi KOMIYA 2018-01-13 14:26:33 +09:00 committed by GitHub
commit 854a227501
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 63 additions and 40 deletions

View File

@ -1,6 +1,11 @@
Release 1.7 (in development)
============================
Dependencies
------------
* Add ``packaging`` package
Incompatible changes
--------------------
@ -67,6 +72,7 @@ Features added
* #4093: sphinx-build creates empty directories for unknown targets/builders
* Add ``top-classes`` option for the ``sphinx.ext.inheritance_diagram``
extension to limit the scope of inheritance graphs.
* #4183: doctest: ``:pyversion:`` option also follows PEP-440 specification
Features removed
----------------

View File

@ -80,12 +80,24 @@ a comma-separated list of group names.
.. doctest::
:pyversion: > 3.3
The supported operands are ``<``, ``<=``, ``==``, ``>=``, ``>``, and
comparison is performed by `distutils.version.LooseVersion
<https://www.python.org/dev/peps/pep-0386/#distutils>`__.
The following operands are supported:
* ``~=``: Compatible release clause
* ``==``: Version matching clause
* ``!=``: Version exclusion clause
* ``<=``, ``>=``: Inclusive ordered comparison clause
* ``<``, ``>``: Exclusive ordered comparison clause
* ``===``: Arbitrary equality clause.
``pyversion`` option is followed `PEP-440: Version Specifiers
<https://www.python.org/dev/peps/pep-0440/#version-specifiers>`__.
.. versionadded:: 1.6
.. versionchanged:: 1.7
Supported PEP-440 operands and notations
Note that like with standard doctests, you have to use ``<BLANKLINE>`` to
signal a blank line in the expected output. The ``<BLANKLINE>`` is removed
when building presentation output (HTML, LaTeX etc.).

View File

@ -26,6 +26,7 @@ requires = [
'imagesize',
'requests>=2.0.0',
'setuptools',
'packaging',
'sphinxcontrib-websupport',
]

View File

@ -20,7 +20,8 @@ from os import path
import doctest
from six import itervalues, StringIO, binary_type, text_type, PY2
from distutils.version import LooseVersion
from packaging.specifiers import SpecifierSet, InvalidSpecifier
from packaging.version import Version
from docutils import nodes
from docutils.parsers.rst import Directive, directives
@ -57,28 +58,23 @@ else:
return text
def compare_version(ver1, ver2, operand):
# type: (unicode, unicode, unicode) -> bool
"""Compare `ver1` to `ver2`, relying on `operand`.
def is_allowed_version(spec, version):
# type: (unicode, unicode) -> bool
"""Check `spec` satisfies `version` or not.
This obeys PEP-440 specifiers:
https://www.python.org/dev/peps/pep-0440/#version-specifiers
Some examples:
>>> compare_version('3.3', '3.5', '<=')
>>> is_allowed_version('3.3', '<=3.5')
True
>>> compare_version('3.3', '3.2', '<=')
>>> is_allowed_version('3.3', '<=3.2')
False
>>> compare_version('3.3a0', '3.3', '<=')
>>> is_allowed_version('3.3', '>3.2, <4.0')
True
"""
if operand not in ('<=', '<', '==', '>=', '>'):
raise ValueError("'%s' is not a valid operand.")
v1 = LooseVersion(ver1)
v2 = LooseVersion(ver2)
return ((operand == '<=' and (v1 <= v2)) or
(operand == '<' and (v1 < v2)) or
(operand == '==' and (v1 == v2)) or
(operand == '>=' and (v1 >= v2)) or
(operand == '>' and (v1 > v2)))
return Version(version) in SpecifierSet(spec)
# set up the necessary directives
@ -143,16 +139,13 @@ class TestDirective(Directive):
node['options'][flag] = (option[0] == '+')
if self.name == 'doctest' and 'pyversion' in self.options:
try:
option = self.options['pyversion']
# :pyversion: >= 3.6 --> operand='>=', option_version='3.6'
operand, option_version = [item.strip() for item in option.split()]
running_version = platform.python_version()
if not compare_version(running_version, option_version, operand):
spec = self.options['pyversion']
if not is_allowed_version(spec, platform.python_version()):
flag = doctest.OPTIONFLAGS_BY_NAME['SKIP']
node['options'][flag] = True # Skip the test
except ValueError:
except InvalidSpecifier:
self.state.document.reporter.warning(
_("'%s' is not a valid pyversion option") % option,
_("'%s' is not a valid pyversion option") % spec,
line=self.lineno)
return [node]

View File

@ -9,7 +9,9 @@
:license: BSD, see LICENSE for details.
"""
import pytest
from sphinx.ext.doctest import compare_version
from sphinx.ext.doctest import is_allowed_version
from packaging.version import InvalidVersion
from packaging.specifiers import InvalidSpecifier
cleanup_called = 0
@ -26,19 +28,28 @@ def test_build(app, status, warning):
assert cleanup_called == 3, 'testcleanup did not get executed enough times'
def test_compare_version():
assert compare_version('3.3', '3.4', '<') is True
assert compare_version('3.3', '3.2', '<') is False
assert compare_version('3.3', '3.4', '<=') is True
assert compare_version('3.3', '3.2', '<=') is False
assert compare_version('3.3', '3.3', '==') is True
assert compare_version('3.3', '3.4', '==') is False
assert compare_version('3.3', '3.2', '>=') is True
assert compare_version('3.3', '3.4', '>=') is False
assert compare_version('3.3', '3.2', '>') is True
assert compare_version('3.3', '3.4', '>') is False
with pytest.raises(ValueError):
compare_version('3.3', '3.4', '+')
def test_is_allowed_version():
assert is_allowed_version('<3.4', '3.3') is True
assert is_allowed_version('<3.4', '3.3') is True
assert is_allowed_version('<3.2', '3.3') is False
assert is_allowed_version('<=3.4', '3.3') is True
assert is_allowed_version('<=3.2', '3.3') is False
assert is_allowed_version('==3.3', '3.3') is True
assert is_allowed_version('==3.4', '3.3') is False
assert is_allowed_version('>=3.2', '3.3') is True
assert is_allowed_version('>=3.4', '3.3') is False
assert is_allowed_version('>3.2', '3.3') is True
assert is_allowed_version('>3.4', '3.3') is False
assert is_allowed_version('~=3.4', '3.4.5') is True
assert is_allowed_version('~=3.4', '3.5.0') is True
# invalid spec
with pytest.raises(InvalidSpecifier):
is_allowed_version('&3.4', '3.5')
# invalid version
with pytest.raises(InvalidVersion):
is_allowed_version('>3.4', 'Sphinx')
def cleanup_call():