Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion sphinx/extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

from typing import TYPE_CHECKING, Any, Dict

from packaging.version import InvalidVersion, Version

from sphinx.config import Config
from sphinx.errors import VersionRequirementError
from sphinx.locale import __
Expand Down Expand Up @@ -51,7 +53,21 @@ def verify_needs_extensions(app: "Sphinx", config: Config) -> None:
'but it is not loaded.'), extname)
continue

if extension.version == 'unknown version' or reqversion > extension.version:
if extension.version == 'unknown version':
raise VersionRequirementError(__('This project needs the extension %s at least in '
'version %s and therefore cannot be built with '
'the loaded version (%s).') %
(extname, reqversion, extension.version))

try:
installed_version = Version(extension.version)
required_version = Version(reqversion)
except InvalidVersion:
version_outdated = reqversion > extension.version
else:
version_outdated = installed_version < required_version

if version_outdated:
raise VersionRequirementError(__('This project needs the extension %s at least in '
'version %s and therefore cannot be built with '
'the loaded version (%s).') %
Expand Down
Empty file.
1 change: 1 addition & 0 deletions tests/roots/test_needs_extensions/ext_dev.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = '0.6.0.dev1'
1 change: 1 addition & 0 deletions tests/roots/test_needs_extensions/ext_exact.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = '0.6.0'
1 change: 1 addition & 0 deletions tests/roots/test_needs_extensions/ext_newer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = '0.10.0'
1 change: 1 addition & 0 deletions tests/roots/test_needs_extensions/ext_post.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = '0.6.0.post1'
1 change: 1 addition & 0 deletions tests/roots/test_needs_extensions/ext_prerelease.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = '0.6.0rc1'
1 change: 1 addition & 0 deletions tests/roots/test_needs_extensions/ext_unknown.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# no __version__ attribute to emulate missing metadata
73 changes: 73 additions & 0 deletions tests/test_extension_needs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from importlib import import_module
from types import SimpleNamespace

import pytest

from sphinx.errors import VersionRequirementError
from sphinx.extension import Extension, verify_needs_extensions


EXTENSION_NAME = 'dummy.ext'
EXTENSION_ROOT = 'tests.roots.test_needs_extensions'


def make_extension(module_basename: str) -> Extension:
module = import_module(f'{EXTENSION_ROOT}.{module_basename}')
kwargs = {}
if hasattr(module, '__version__'):
kwargs['version'] = module.__version__
return Extension(EXTENSION_NAME, module, **kwargs)


def make_app_with_extension(module_basename: str) -> SimpleNamespace:
extension = make_extension(module_basename)
return SimpleNamespace(extensions={EXTENSION_NAME: extension})


def make_config(requirement: str) -> SimpleNamespace:
return SimpleNamespace(needs_extensions={EXTENSION_NAME: requirement})


def test_needs_extensions_accepts_newer_version() -> None:
app = make_app_with_extension('ext_newer')
config = make_config('0.6.0')

verify_needs_extensions(app, config)


def test_needs_extensions_rejects_prerelease_when_final_required() -> None:
app = make_app_with_extension('ext_prerelease')
config = make_config('0.6.0')

with pytest.raises(VersionRequirementError) as exc:
verify_needs_extensions(app, config)

assert 'needs the extension dummy.ext at least in version 0.6.0' in str(exc.value)


def test_needs_extensions_rejects_unknown_version() -> None:
app = make_app_with_extension('ext_unknown')
config = make_config('0.6.0')

with pytest.raises(VersionRequirementError) as exc:
verify_needs_extensions(app, config)

assert 'needs the extension dummy.ext at least in version 0.6.0' in str(exc.value)


def test_needs_extensions_accepts_exact_match() -> None:
app = make_app_with_extension('ext_exact')
config = make_config('0.6.0')

verify_needs_extensions(app, config)


def test_needs_extensions_handles_dev_and_post_releases() -> None:
config = make_config('0.6.0')

app_with_dev = make_app_with_extension('ext_dev')
with pytest.raises(VersionRequirementError):
verify_needs_extensions(app_with_dev, config)

app_with_post = make_app_with_extension('ext_post')
verify_needs_extensions(app_with_post, config)