From f39636b027a7dba0eb75af399d0d2332cfe64e74 Mon Sep 17 00:00:00 2001 From: Fazeel Usmani Date: Mon, 6 Apr 2026 15:43:35 +0530 Subject: [PATCH 1/4] Un-deprecate ``Parser.config`` and ``Parser.env`` PRs #13644 and #13637 deprecated ``Parser.config``, ``Parser.env``, and ``Parser.set_application`` in 9.0, scheduled for removal in 10.0. However, the registry already populates ``_config`` and ``_env`` directly when constructing parser instances, and there is no documented public replacement for the property accessors. Third-party parsers such as ``sphinx_bib_domain`` rely on reading config/env during ``parse()``, and projects using ``-W error`` in CI now fail outright on the deprecation warnings. Reverse the deprecation of ``Parser.config`` and ``Parser.env`` so that they remain the supported way for parsers to access the build configuration and environment. ``Parser.set_application`` is kept deprecated since the hook is genuinely redundant once ``_config`` and ``_env`` are set by the registry. Document the supported access pattern in ``doc/extdev/parserapi.rst`` and add a regression test that asserts no ``RemovedInSphinx10Warning`` is emitted on attribute read. Fixes #14371 --- AUTHORS.rst | 1 + CHANGES.rst | 11 +++++++++++ doc/extdev/parserapi.rst | 30 ++++++++++++++++++++++++++++++ sphinx/parsers.py | 25 +++++++++++++++++-------- tests/test_markup/test_parser.py | 15 +++++++++++++++ 5 files changed, 74 insertions(+), 8 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index fd129bb8f72..8d3e87ef22e 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -56,6 +56,7 @@ Contributors * Erik Bedard -- config options for :mod:`sphinx.ext.duration` * Etienne Desautels -- apidoc module * Ezio Melotti -- collapsible sidebar JavaScript +* Fazeel Usmani -- parser API fixes * Filip Vavera -- napoleon todo directive * Florian Best -- log improvements * Glenn Matthews -- python domain signature improvements diff --git a/CHANGES.rst b/CHANGES.rst index 6f7279e8011..910dd5b425f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,14 @@ +Release 9.2.0 (in development) +============================== + +Bugs fixed +---------- + +* #14371: Un-deprecate :py:attr:`!Parser.config` and :py:attr:`!Parser.env`, + the supported way for a custom parser to access the build configuration + and environment. Only :py:meth:`!Parser.set_application` remains deprecated. + Patch by Fazeel Usmani + Release 9.1.0 (released Dec 31, 2025) ===================================== diff --git a/doc/extdev/parserapi.rst b/doc/extdev/parserapi.rst index b5480996065..79945221c57 100644 --- a/doc/extdev/parserapi.rst +++ b/doc/extdev/parserapi.rst @@ -36,3 +36,33 @@ to configure their settings appropriately. .. autoclass:: Parser :members: + +Accessing the Sphinx config and environment +------------------------------------------- + +A custom parser can read Sphinx :class:`~sphinx.config.Config` values and the +:class:`~sphinx.environment.BuildEnvironment` through the inherited +:attr:`Parser.config` and :attr:`Parser.env` properties. Sphinx sets the +backing ``_config`` and ``_env`` attributes when it constructs the parser via +:meth:`.Sphinx.add_source_parser`, so they are available from the moment +:meth:`Parser.parse` is called. + +.. code-block:: python + + from sphinx.parsers import Parser + + + class MyParser(Parser): + supported = ('mytype',) + + def parse(self, inputstring, document): + # ``self.config`` and ``self.env`` are populated by Sphinx + encoding = self.config.source_encoding + docname = self.env.docname + ... + +.. versionchanged:: 9.0 + The :meth:`Parser.set_application` hook is deprecated. Sphinx now sets + ``_config`` and ``_env`` directly when the parser instance is created, so + custom parsers no longer need to override ``set_application`` to capture + these objects. diff --git a/sphinx/parsers.py b/sphinx/parsers.py index 5731ccd6f66..698b15762a3 100644 --- a/sphinx/parsers.py +++ b/sphinx/parsers.py @@ -36,24 +36,33 @@ class Parser(docutils.parsers.Parser): @property def config(self) -> Config: - """The config object.""" - cls_module = self.__class__.__module__ - cls_name = self.__class__.__qualname__ - _deprecation_warning(cls_module, f'{cls_name}.config', remove=(10, 0)) + """The config object. + + Sphinx sets this attribute when constructing the parser via + :meth:`.Sphinx.add_source_parser`, so it is available throughout + :meth:`parse`. + """ return self._config @property def env(self) -> BuildEnvironment: - """The environment object.""" - cls_module = self.__class__.__module__ - cls_name = self.__class__.__qualname__ - _deprecation_warning(cls_module, f'{cls_name}.env', remove=(10, 0)) + """The environment object. + + Sphinx sets this attribute when constructing the parser via + :meth:`.Sphinx.add_source_parser`, so it is available throughout + :meth:`parse`. + """ return self._env def set_application(self, app: Sphinx) -> None: """set_application will be called from Sphinx to set app and other instance variables :param sphinx.application.Sphinx app: Sphinx application object + + .. versionchanged:: 9.0 + Deprecated. Sphinx now sets :attr:`_config` and :attr:`_env` + directly when the parser is created, so this hook is no longer + needed. It will be removed in Sphinx 10. """ cls_module = self.__class__.__module__ cls_name = self.__class__.__qualname__ diff --git a/tests/test_markup/test_parser.py b/tests/test_markup/test_parser.py index 215fdf04dd6..5fa444e6f27 100644 --- a/tests/test_markup/test_parser.py +++ b/tests/test_markup/test_parser.py @@ -2,11 +2,13 @@ from __future__ import annotations +import warnings from typing import TYPE_CHECKING from unittest.mock import Mock, patch import pytest +from sphinx.deprecation import RemovedInSphinx10Warning from sphinx.parsers import RSTParser from sphinx.util.docutils import new_document @@ -68,3 +70,16 @@ def test_RSTParser_prolog_epilog(RSTStateMachine: Mock, app: SphinxTestApp) -> N ('dummy.rst', 0, ' hello Sphinx world'), ('dummy.rst', 1, ' Sphinx is a document generator'), ] + + +@pytest.mark.sphinx('html', testroot='basic') +def test_parser_config_env_not_deprecated(app: SphinxTestApp) -> None: + """Reading ``Parser.config`` / ``Parser.env`` must not warn (#14371).""" + parser = RSTParser() + parser._config = app.config + parser._env = app.env + + with warnings.catch_warnings(): + warnings.simplefilter('error', RemovedInSphinx10Warning) + assert parser.config is app.config + assert parser.env is app.env From 37186296368cdb69663b3ef91613668d8c76caf1 Mon Sep 17 00:00:00 2001 From: Fazeel Usmani Date: Mon, 6 Apr 2026 15:55:37 +0530 Subject: [PATCH 2/4] Fix unresolved cross-references in parser docstrings The previous commit introduced ``:attr:`_config```, ``:attr:`_env```, ``:meth:`parse``` and ``:meth:`Parser.parse``` cross-references that have no doc targets: * ``_config`` and ``_env`` are private attributes not picked up by ``autoclass :members:`` * ``Parser.parse`` is inherited from ``docutils.parsers.Parser`` and is not autoclass'd by Sphinx This caused 5 ``--fail-on-warning`` errors in the doc build. Replace the broken cross-references with literal ```` formatting. --- doc/extdev/parserapi.rst | 8 ++++---- sphinx/parsers.py | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/extdev/parserapi.rst b/doc/extdev/parserapi.rst index 79945221c57..eb942ab659d 100644 --- a/doc/extdev/parserapi.rst +++ b/doc/extdev/parserapi.rst @@ -42,10 +42,10 @@ Accessing the Sphinx config and environment A custom parser can read Sphinx :class:`~sphinx.config.Config` values and the :class:`~sphinx.environment.BuildEnvironment` through the inherited -:attr:`Parser.config` and :attr:`Parser.env` properties. Sphinx sets the -backing ``_config`` and ``_env`` attributes when it constructs the parser via +``Parser.config`` and ``Parser.env`` properties. Sphinx sets the backing +``_config`` and ``_env`` attributes when it constructs the parser via :meth:`.Sphinx.add_source_parser`, so they are available from the moment -:meth:`Parser.parse` is called. +``parse()`` is called. .. code-block:: python @@ -62,7 +62,7 @@ backing ``_config`` and ``_env`` attributes when it constructs the parser via ... .. versionchanged:: 9.0 - The :meth:`Parser.set_application` hook is deprecated. Sphinx now sets + The ``Parser.set_application`` hook is deprecated. Sphinx now sets ``_config`` and ``_env`` directly when the parser instance is created, so custom parsers no longer need to override ``set_application`` to capture these objects. diff --git a/sphinx/parsers.py b/sphinx/parsers.py index 698b15762a3..ca601c2e7c7 100644 --- a/sphinx/parsers.py +++ b/sphinx/parsers.py @@ -40,7 +40,7 @@ def config(self) -> Config: Sphinx sets this attribute when constructing the parser via :meth:`.Sphinx.add_source_parser`, so it is available throughout - :meth:`parse`. + ``parse()``. """ return self._config @@ -50,7 +50,7 @@ def env(self) -> BuildEnvironment: Sphinx sets this attribute when constructing the parser via :meth:`.Sphinx.add_source_parser`, so it is available throughout - :meth:`parse`. + ``parse()``. """ return self._env @@ -60,9 +60,9 @@ def set_application(self, app: Sphinx) -> None: :param sphinx.application.Sphinx app: Sphinx application object .. versionchanged:: 9.0 - Deprecated. Sphinx now sets :attr:`_config` and :attr:`_env` - directly when the parser is created, so this hook is no longer - needed. It will be removed in Sphinx 10. + Deprecated. Sphinx now sets ``_config`` and ``_env`` directly + when the parser is created, so this hook is no longer needed. + It will be removed in Sphinx 10. """ cls_module = self.__class__.__module__ cls_name = self.__class__.__qualname__ From fd00ff1a25a607a15063b217ae6b1d3d7cb3f244 Mon Sep 17 00:00:00 2001 From: Fazeel Usmani Date: Mon, 6 Apr 2026 20:08:23 +0530 Subject: [PATCH 3/4] clean up --- CHANGES.rst | 12 +++++++----- doc/extdev/parserapi.rst | 21 ++++++++++++--------- sphinx/parsers.py | 29 ++++++++++++++++++----------- tests/test_markup/test_parser.py | 26 +++++++++++++++++++++----- 4 files changed, 58 insertions(+), 30 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 910dd5b425f..17b1a781084 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,13 +1,15 @@ Release 9.2.0 (in development) ============================== -Bugs fixed +Deprecated ---------- -* #14371: Un-deprecate :py:attr:`!Parser.config` and :py:attr:`!Parser.env`, - the supported way for a custom parser to access the build configuration - and environment. Only :py:meth:`!Parser.set_application` remains deprecated. - Patch by Fazeel Usmani +* #14371: Un-deprecate :py:attr:`!Parser.config` and :py:attr:`!Parser.env`. + These remain the supported way for a custom parser to read the build + configuration and environment. + :py:meth:`!Parser.set_application` continues to be deprecated and is + scheduled for removal in Sphinx 10. + Patch by Fazeel Usmani. Release 9.1.0 (released Dec 31, 2025) ===================================== diff --git a/doc/extdev/parserapi.rst b/doc/extdev/parserapi.rst index eb942ab659d..f9347a34dc7 100644 --- a/doc/extdev/parserapi.rst +++ b/doc/extdev/parserapi.rst @@ -42,10 +42,10 @@ Accessing the Sphinx config and environment A custom parser can read Sphinx :class:`~sphinx.config.Config` values and the :class:`~sphinx.environment.BuildEnvironment` through the inherited -``Parser.config`` and ``Parser.env`` properties. Sphinx sets the backing -``_config`` and ``_env`` attributes when it constructs the parser via -:meth:`.Sphinx.add_source_parser`, so they are available from the moment -``parse()`` is called. +``Parser.config`` and ``Parser.env`` properties. Sphinx populates the +backing ``_config`` and ``_env`` attributes when it instantiates a parser +registered via :meth:`.Sphinx.add_source_parser`, so they are available +from the start of ``parse()``. .. code-block:: python @@ -61,8 +61,11 @@ A custom parser can read Sphinx :class:`~sphinx.config.Config` values and the docname = self.env.docname ... -.. versionchanged:: 9.0 - The ``Parser.set_application`` hook is deprecated. Sphinx now sets - ``_config`` and ``_env`` directly when the parser instance is created, so - custom parsers no longer need to override ``set_application`` to capture - these objects. +.. versionchanged:: 9.2 + ``Parser.config`` and ``Parser.env`` are no longer deprecated. + +.. deprecated:: 9.0 + The ``Parser.set_application`` hook is deprecated and will be removed in + Sphinx 10. Sphinx now populates ``_config`` and ``_env`` directly when + the parser instance is created, so custom parsers no longer need to + override ``set_application`` to capture these objects. diff --git a/sphinx/parsers.py b/sphinx/parsers.py index ca601c2e7c7..70abe8ac8c3 100644 --- a/sphinx/parsers.py +++ b/sphinx/parsers.py @@ -38,9 +38,12 @@ class Parser(docutils.parsers.Parser): def config(self) -> Config: """The config object. - Sphinx sets this attribute when constructing the parser via - :meth:`.Sphinx.add_source_parser`, so it is available throughout - ``parse()``. + Populated by Sphinx when it instantiates a parser registered via + :meth:`.Sphinx.add_source_parser`, so this is available from the + start of ``parse()``. + + .. versionchanged:: 9.2 + No longer deprecated. """ return self._config @@ -48,21 +51,25 @@ def config(self) -> Config: def env(self) -> BuildEnvironment: """The environment object. - Sphinx sets this attribute when constructing the parser via - :meth:`.Sphinx.add_source_parser`, so it is available throughout - ``parse()``. + Populated by Sphinx when it instantiates a parser registered via + :meth:`.Sphinx.add_source_parser`, so this is available from the + start of ``parse()``. + + .. versionchanged:: 9.2 + No longer deprecated. """ return self._env def set_application(self, app: Sphinx) -> None: - """set_application will be called from Sphinx to set app and other instance variables + """Legacy compatibility hook for receiving the Sphinx application. :param sphinx.application.Sphinx app: Sphinx application object - .. versionchanged:: 9.0 - Deprecated. Sphinx now sets ``_config`` and ``_env`` directly - when the parser is created, so this hook is no longer needed. - It will be removed in Sphinx 10. + .. deprecated:: 9.0 + Sphinx now makes ``config`` and ``env`` available on the parser + instance automatically, so custom parsers no longer need to + override this method to capture the application. It will be + removed in Sphinx 10. """ cls_module = self.__class__.__module__ cls_name = self.__class__.__qualname__ diff --git a/tests/test_markup/test_parser.py b/tests/test_markup/test_parser.py index 5fa444e6f27..fbddd052724 100644 --- a/tests/test_markup/test_parser.py +++ b/tests/test_markup/test_parser.py @@ -73,13 +73,29 @@ def test_RSTParser_prolog_epilog(RSTStateMachine: Mock, app: SphinxTestApp) -> N @pytest.mark.sphinx('html', testroot='basic') -def test_parser_config_env_not_deprecated(app: SphinxTestApp) -> None: - """Reading ``Parser.config`` / ``Parser.env`` must not warn (#14371).""" - parser = RSTParser() - parser._config = app.config - parser._env = app.env +def test_parser_config_env_populated_by_registry(app: SphinxTestApp) -> None: + """Parsers built via the registry expose config/env without warning (#14371). + + This goes through the real construction path in + :meth:`.SphinxComponentRegistry.create_source_parser` so a regression in + that code path (for example, removal of the ``isinstance(..., SphinxParser)`` + gate) would be caught here. + """ + parser = app.registry.create_source_parser( + 'restructuredtext', config=app.config, env=app.env + ) with warnings.catch_warnings(): warnings.simplefilter('error', RemovedInSphinx10Warning) assert parser.config is app.config assert parser.env is app.env + + +@pytest.mark.sphinx('html', testroot='basic') +def test_parser_set_application_still_deprecated(app: SphinxTestApp) -> None: + """``Parser.set_application`` must still emit a deprecation warning (#14371).""" + parser = RSTParser() + with pytest.warns(RemovedInSphinx10Warning, match='set_application'): + parser.set_application(app) + assert parser.config is app.config + assert parser.env is app.env From b3214b79861937ee1192f4a78debf2a29423573a Mon Sep 17 00:00:00 2001 From: Fazeel Usmani Date: Mon, 6 Apr 2026 20:20:26 +0530 Subject: [PATCH 4/4] Fix mypy error --- tests/test_markup/test_parser.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_markup/test_parser.py b/tests/test_markup/test_parser.py index fbddd052724..34ae11be8ba 100644 --- a/tests/test_markup/test_parser.py +++ b/tests/test_markup/test_parser.py @@ -9,7 +9,7 @@ import pytest from sphinx.deprecation import RemovedInSphinx10Warning -from sphinx.parsers import RSTParser +from sphinx.parsers import Parser, RSTParser from sphinx.util.docutils import new_document if TYPE_CHECKING: @@ -84,6 +84,10 @@ def test_parser_config_env_populated_by_registry(app: SphinxTestApp) -> None: parser = app.registry.create_source_parser( 'restructuredtext', config=app.config, env=app.env ) + # ``create_source_parser`` is typed as ``docutils.parsers.Parser``; narrow + # to the Sphinx base class so ``config`` / ``env`` access type-checks and + # so a future regression that stops wrapping Sphinx parsers is caught here. + assert isinstance(parser, Parser) with warnings.catch_warnings(): warnings.simplefilter('error', RemovedInSphinx10Warning)