"""Utilities for Sphinx extensions.""" from __future__ import annotations from typing import TYPE_CHECKING, Any from packaging.version import InvalidVersion, Version from sphinx.errors import VersionRequirementError from sphinx.locale import __ from sphinx.util import logging if TYPE_CHECKING: from sphinx.application import Sphinx from sphinx.config import Config logger = logging.getLogger(__name__) class Extension: def __init__(self, name: str, module: Any, **kwargs: Any) -> None: self.name = name self.module = module self.metadata = kwargs self.version = kwargs.pop('version', 'unknown version') # The extension supports parallel read or not. The default value # is ``None``. It means the extension does not tell the status. # It will be warned on parallel reading. self.parallel_read_safe = kwargs.pop('parallel_read_safe', None) # The extension supports parallel write or not. The default value # is ``True``. Sphinx writes parallelly documents even if # the extension does not tell its status. self.parallel_write_safe = kwargs.pop('parallel_write_safe', True) def verify_needs_extensions(app: Sphinx, config: Config) -> None: """Check that extensions mentioned in :confval:`needs_extensions` satisfy the version requirement, and warn if an extension is not loaded. Warns if an extension in :confval:`needs_extension` is not loaded. :raises VersionRequirementError: if the version of an extension in :confval:`needs_extension` is unknown or older than the required version. """ if config.needs_extensions is None: return for extname, reqversion in config.needs_extensions.items(): extension = app.extensions.get(extname) if extension is None: logger.warning(__('The %s extension is required by needs_extensions settings, ' 'but it is not loaded.'), extname) continue fulfilled = True if extension.version == 'unknown version': fulfilled = False else: try: if Version(reqversion) > Version(extension.version): fulfilled = False except InvalidVersion: if reqversion > extension.version: fulfilled = False if not fulfilled: 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)) def setup(app: Sphinx) -> dict[str, Any]: app.connect('config-inited', verify_needs_extensions, priority=800) return { 'version': 'builtin', 'parallel_read_safe': True, 'parallel_write_safe': True, }