From 7b731f7ceb8161b11a529f182bf567b77993bdeb Mon Sep 17 00:00:00 2001 From: Karolina Surma Date: Wed, 5 Mar 2025 12:14:49 +0100 Subject: [PATCH] Make the first party extensions optional, add [extensions] extra MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Miro HronĨok --- pyproject.toml | 35 ++++++++++++++++---- sphinx/application.py | 6 ++-- sphinx/registry.py | 10 +++--- sphinx/testing/fixtures.py | 7 ++++ tests/test_builders/test_build_html_maths.py | 3 ++ tests/test_writers/test_api_translator.py | 2 ++ 6 files changed, 50 insertions(+), 13 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1195b04..9a7213f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,12 +67,6 @@ classifiers = [ "Typing :: Typed", ] dependencies = [ - "sphinxcontrib-applehelp>=1.0.7", - "sphinxcontrib-devhelp>=1.0.6", - "sphinxcontrib-htmlhelp>=2.0.6", - "sphinxcontrib-jsmath>=1.0.1", - "sphinxcontrib-qthelp>=1.0.6", - "sphinxcontrib-serializinghtml>=1.1.9", "Jinja2>=3.1", "Pygments>=2.17", "docutils>=0.21,<0.23", @@ -87,6 +81,34 @@ dependencies = [ ] dynamic = ["version"] +[project.optional-dependencies] +applehelp = [ + "sphinxcontrib-applehelp>=1.0.7", +] +devhelp = [ + "sphinxcontrib-devhelp>=1.0.6", +] +jsmath = [ + "sphinxcontrib-jsmath>=1.0.1", +] +htmlhelp = [ + "sphinxcontrib-htmlhelp>=2.0.6", +] +serializinghtml = [ + "sphinxcontrib-serializinghtml>=1.1.9", +] +qthelp = [ + "sphinxcontrib-qthelp>=1.0.6", +] +extensions = [ + "sphinx[applehelp]", + "sphinx[devhelp]", + "sphinx[jsmath]", + "sphinx[htmlhelp]", + "sphinx[serializinghtml]", + "sphinx[qthelp]", +] + [[project.authors]] name = "Adam Turner" email = "aa-turner@users.noreply.github.com" @@ -104,6 +126,7 @@ sphinx-autogen = "sphinx.ext.autosummary.generate:main" [dependency-groups] docs = [ "sphinxcontrib-websupport", + "sphinx[extensions]", ] lint = [ "ruff==0.14.9", diff --git a/sphinx/application.py b/sphinx/application.py index 5349c36..4da1635 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -292,7 +292,7 @@ class Sphinx: # load all built-in extension modules, first-party extension modules, # and first-party themes for extension in builtin_extensions: - self.setup_extension(extension) + self.setup_extension(extension, skip_nonimportable=extension in _first_party_extensions) # load all user-given extension modules for extension in self.config.extensions: @@ -494,7 +494,7 @@ class Sphinx: # ---- general extensibility interface ------------------------------------- - def setup_extension(self, extname: str) -> None: + def setup_extension(self, extname: str, skip_nonimportable: bool = False) -> None: """Import and setup a Sphinx extension module. Load the extension given by the module *name*. Use this if your @@ -502,7 +502,7 @@ class Sphinx: called twice. """ logger.debug('[app] setting up extension: %r', extname) - self.registry.load_extension(self, extname) + self.registry.load_extension(self, extname, skip_nonimportable=skip_nonimportable) @staticmethod def require_sphinx(version: tuple[int, int] | str) -> None: diff --git a/sphinx/registry.py b/sphinx/registry.py index da72924..b6f6122 100644 --- a/sphinx/registry.py +++ b/sphinx/registry.py @@ -528,7 +528,7 @@ class SphinxComponentRegistry: def add_html_theme(self, name: str, theme_path: str | os.PathLike[str]) -> None: self.html_themes[name] = _StrPath(theme_path) - def load_extension(self, app: Sphinx, extname: str) -> None: + def load_extension(self, app: Sphinx, extname: str, skip_nonimportable: bool = False) -> None: """Load a Sphinx extension.""" if extname in app.extensions: # already loaded return @@ -549,10 +549,12 @@ class SphinxComponentRegistry: try: mod = import_module(extname) except ImportError as err: + msg = __('Could not import extension %s') + if skip_nonimportable: + logger.debug(msg % extname) + return logger.verbose(__('Original exception:\n') + traceback.format_exc()) - raise ExtensionError( - __('Could not import extension %s') % extname, err - ) from err + raise ExtensionError(msg % extname, err) from err setup: _ExtensionSetupFunc | None = getattr(mod, 'setup', None) if setup is None: diff --git a/sphinx/testing/fixtures.py b/sphinx/testing/fixtures.py index c6fdebe..e9f98a8 100644 --- a/sphinx/testing/fixtures.py +++ b/sphinx/testing/fixtures.py @@ -31,6 +31,7 @@ DEFAULT_ENABLED_MARKERS = [ 'builddir=None, docutils_conf=None' '): arguments to initialize the sphinx test application.' ), + 'sphinxcontrib(...): required sphinxcontrib.* extensions', 'test_params(shared_result=...): test parameters.', ] @@ -79,6 +80,12 @@ def app_params( """Parameters that are specified by 'pytest.mark.sphinx' for sphinx.application.Sphinx initialization """ + + # ##### process pytest.mark.sphinxcontrib + for info in reversed(list(request.node.iter_markers("sphinxcontrib"))): + for arg in info.args: + pytest.importorskip("sphinxcontrib." + arg) + # ##### process pytest.mark.sphinx pargs: dict[int, Any] = {} diff --git a/tests/test_builders/test_build_html_maths.py b/tests/test_builders/test_build_html_maths.py index 8654ca9..8ad006f 100644 --- a/tests/test_builders/test_build_html_maths.py +++ b/tests/test_builders/test_build_html_maths.py @@ -40,6 +40,7 @@ def test_html_math_renderer_is_imgmath(app: SphinxTestApp) -> None: assert app.builder.math_renderer_name == 'imgmath' +@pytest.mark.sphinxcontrib('serializinghtml', 'jsmath') @pytest.mark.sphinx( 'html', testroot='basic', @@ -67,6 +68,7 @@ def test_html_math_renderer_is_duplicated2(app: SphinxTestApp) -> None: assert app.builder.math_renderer_name == 'imgmath' # The another one is chosen +@pytest.mark.sphinxcontrib('jsmath') @pytest.mark.sphinx( 'html', testroot='basic', @@ -80,6 +82,7 @@ def test_html_math_renderer_is_chosen(app: SphinxTestApp) -> None: assert app.builder.math_renderer_name == 'imgmath' +@pytest.mark.sphinxcontrib('jsmath') @pytest.mark.sphinx( 'html', testroot='basic', diff --git a/tests/test_writers/test_api_translator.py b/tests/test_writers/test_api_translator.py index 1220192..8e8bb33 100644 --- a/tests/test_writers/test_api_translator.py +++ b/tests/test_writers/test_api_translator.py @@ -47,6 +47,7 @@ def test_singlehtml_set_translator_for_singlehtml(app: SphinxTestApp) -> None: assert translator_class.__name__ == 'ConfSingleHTMLTranslator' +@pytest.mark.sphinxcontrib('serializinghtml') @pytest.mark.sphinx('pickle', testroot='api-set-translator') def test_pickle_set_translator_for_pickle(app: SphinxTestApp) -> None: translator_class = app.builder.get_translator_class() @@ -54,6 +55,7 @@ def test_pickle_set_translator_for_pickle(app: SphinxTestApp) -> None: assert translator_class.__name__ == 'ConfPickleTranslator' +@pytest.mark.sphinxcontrib('serializinghtml') @pytest.mark.sphinx('json', testroot='api-set-translator') def test_json_set_translator_for_json(app: SphinxTestApp) -> None: translator_class = app.builder.get_translator_class() -- 2.52.0