From 9699465414515f0eba76d05069e755b5bcf34eef Mon Sep 17 00:00:00 2001 From: Karolina Surma Date: Mon, 15 Jan 2024 16:19:32 +0100 Subject: [PATCH] Make the first party extensions optional, add [extensions] extra Co-authored-by: Miro HronĨok --- pyproject.toml | 33 +++++++++++++++++++++++++++------ sphinx/application.py | 6 +++--- sphinx/registry.py | 9 ++++++--- sphinx/testing/fixtures.py | 6 ++++++ tests/test_api_translator.py | 2 ++ tests/test_build_html.py | 3 +++ 6 files changed, 47 insertions(+), 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8f93701..41c28c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,12 +55,6 @@ classifiers = [ "Topic :: Utilities", ] dependencies = [ - "sphinxcontrib-applehelp", - "sphinxcontrib-devhelp", - "sphinxcontrib-jsmath", - "sphinxcontrib-htmlhelp>=2.0.0", - "sphinxcontrib-serializinghtml>=1.1.9", - "sphinxcontrib-qthelp", "Jinja2>=3.0", "Pygments>=2.14", "docutils>=0.18.1,<0.21", @@ -76,8 +70,35 @@ dependencies = [ dynamic = ["version"] [project.optional-dependencies] +applehelp = [ + "sphinxcontrib-applehelp", +] +devhelp = [ + "sphinxcontrib-devhelp", +] +jsmath = [ + "sphinxcontrib-jsmath", +] +htmlhelp = [ + "sphinxcontrib-htmlhelp>=2.0.0", +] +serializinghtml = [ + "sphinxcontrib-serializinghtml>=1.1.9", +] +qthelp = [ + "sphinxcontrib-qthelp", +] +extensions = [ + "sphinx[applehelp]", + "sphinx[devhelp]", + "sphinx[jsmath]", + "sphinx[htmlhelp]", + "sphinx[serializinghtml]", + "sphinx[qthelp]", +] docs = [ "sphinxcontrib-websupport", + "sphinx[extensions]", ] lint = [ "flake8>=3.5.0", diff --git a/sphinx/application.py b/sphinx/application.py index d5fbaa9..b030dab 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -226,7 +226,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: @@ -395,7 +395,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 @@ -403,7 +403,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 501661d..96d4554 100644 --- a/sphinx/registry.py +++ b/sphinx/registry.py @@ -430,7 +430,7 @@ class SphinxComponentRegistry: def add_html_theme(self, name: str, theme_path: str) -> None: self.html_themes[name] = 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 @@ -446,9 +446,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 = getattr(mod, 'setup', None) if setup is None: diff --git a/sphinx/testing/fixtures.py b/sphinx/testing/fixtures.py index 0cc4882..f57f709 100644 --- a/sphinx/testing/fixtures.py +++ b/sphinx/testing/fixtures.py @@ -22,6 +22,7 @@ DEFAULT_ENABLED_MARKERS = [ 'sphinx(builder, testroot=None, freshenv=False, confoverrides=None, tags=None,' ' docutilsconf=None, parallel=0): arguments to initialize the sphinx test application.' ), + 'sphinxcontrib(...): required sphinxcontrib.* extensions', 'test_params(shared_result=...): test parameters.', ] @@ -67,6 +68,11 @@ def app_params(request: Any, test_params: dict, shared_result: SharedResult, 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 = {} diff --git a/tests/test_api_translator.py b/tests/test_api_translator.py index 9f2bd44..81575b7 100644 --- a/tests/test_api_translator.py +++ b/tests/test_api_translator.py @@ -36,6 +36,7 @@ def test_singlehtml_set_translator_for_singlehtml(app, status, warning): 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, status, warning): translator_class = app.builder.get_translator_class() @@ -43,6 +44,7 @@ def test_pickle_set_translator_for_pickle(app, status, warning): 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, status, warning): translator_class = app.builder.get_translator_class() diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 07f101d..c512a33 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -1544,6 +1544,7 @@ def test_html_math_renderer_is_imgmath(app, status, warning): assert app.builder.math_renderer_name == 'imgmath' +@pytest.mark.sphinxcontrib('serializinghtml', 'jsmath') @pytest.mark.sphinx('html', testroot='basic', confoverrides={'extensions': ['sphinxcontrib.jsmath', 'sphinx.ext.imgmath']}) @@ -1564,6 +1565,7 @@ def test_html_math_renderer_is_duplicated2(app, status, warning): assert app.builder.math_renderer_name == 'imgmath' # The another one is chosen +@pytest.mark.sphinxcontrib('jsmath') @pytest.mark.sphinx('html', testroot='basic', confoverrides={'extensions': ['sphinxcontrib.jsmath', 'sphinx.ext.imgmath'], @@ -1572,6 +1574,7 @@ def test_html_math_renderer_is_chosen(app, status, warning): assert app.builder.math_renderer_name == 'imgmath' +@pytest.mark.sphinxcontrib('jsmath') @pytest.mark.sphinx('html', testroot='basic', confoverrides={'extensions': ['sphinxcontrib.jsmath', 'sphinx.ext.mathjax'], -- 2.43.0