Merge pull request #7684 from tk0miya/7683_allowed_exceptions

Add allowed_exceptions parameter to Sphinx.emit() (refs: #7683)
This commit is contained in:
Takeshi KOMIYA 2020-05-17 19:28:11 +09:00 committed by GitHub
commit da88a8234d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 46 additions and 9 deletions

View File

@ -73,6 +73,8 @@ Features added
:rst:dir:`py:exception:` and :rst:dir:`py:method:` directives :rst:dir:`py:exception:` and :rst:dir:`py:method:` directives
* #7596: py domain: Change a type annotation for variables to a hyperlink * #7596: py domain: Change a type annotation for variables to a hyperlink
* #7582: napoleon: a type for attribute are represented like type annotation * #7582: napoleon: a type for attribute are represented like type annotation
* #7683: Add ``allowed_exceptions`` parameter to ``Sphinx.emit()`` to allow
handlers to raise specified exceptions
Bugs fixed Bugs fixed
---------- ----------

View File

@ -436,22 +436,32 @@ class Sphinx:
logger.debug('[app] disconnecting event: [id=%s]', listener_id) logger.debug('[app] disconnecting event: [id=%s]', listener_id)
self.events.disconnect(listener_id) self.events.disconnect(listener_id)
def emit(self, event: str, *args: Any) -> List: def emit(self, event: str, *args: Any,
allowed_exceptions: Tuple["Type[Exception]", ...] = ()) -> List:
"""Emit *event* and pass *arguments* to the callback functions. """Emit *event* and pass *arguments* to the callback functions.
Return the return values of all callbacks as a list. Do not emit core Return the return values of all callbacks as a list. Do not emit core
Sphinx events in extensions! Sphinx events in extensions!
"""
return self.events.emit(event, *args)
def emit_firstresult(self, event: str, *args: Any) -> Any: .. versionchanged:: 3.1
Added *allowed_exceptions* to specify path-through exceptions
"""
return self.events.emit(event, *args, allowed_exceptions=allowed_exceptions)
def emit_firstresult(self, event: str, *args: Any,
allowed_exceptions: Tuple["Type[Exception]", ...] = ()) -> Any:
"""Emit *event* and pass *arguments* to the callback functions. """Emit *event* and pass *arguments* to the callback functions.
Return the result of the first callback that doesn't return ``None``. Return the result of the first callback that doesn't return ``None``.
.. versionadded:: 0.5 .. versionadded:: 0.5
.. versionchanged:: 3.1
Added *allowed_exceptions* to specify path-through exceptions
""" """
return self.events.emit_firstresult(event, *args) return self.events.emit_firstresult(event, *args,
allowed_exceptions=allowed_exceptions)
# registering addon parts # registering addon parts

View File

@ -13,7 +13,7 @@
import warnings import warnings
from collections import defaultdict from collections import defaultdict
from operator import attrgetter from operator import attrgetter
from typing import Any, Callable, Dict, List, NamedTuple from typing import Any, Callable, Dict, List, NamedTuple, Tuple
from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.errors import ExtensionError, SphinxError from sphinx.errors import ExtensionError, SphinxError
@ -22,6 +22,7 @@ from sphinx.util import logging
if False: if False:
# For type annotation # For type annotation
from typing import Type # for python3.5.1
from sphinx.application import Sphinx from sphinx.application import Sphinx
@ -88,7 +89,8 @@ class EventManager:
if listener.id == listener_id: if listener.id == listener_id:
listeners.remove(listener) listeners.remove(listener)
def emit(self, name: str, *args: Any) -> List: def emit(self, name: str, *args: Any,
allowed_exceptions: Tuple["Type[Exception]", ...] = ()) -> List:
"""Emit a Sphinx event.""" """Emit a Sphinx event."""
try: try:
logger.debug('[app] emitting event: %r%s', name, repr(args)[:100]) logger.debug('[app] emitting event: %r%s', name, repr(args)[:100])
@ -106,6 +108,9 @@ class EventManager:
results.append(listener.handler(*args)) results.append(listener.handler(*args))
else: else:
results.append(listener.handler(self.app, *args)) results.append(listener.handler(self.app, *args))
except allowed_exceptions:
# pass through the errors specified as *allowed_exceptions*
raise
except SphinxError: except SphinxError:
raise raise
except Exception as exc: except Exception as exc:
@ -113,12 +118,13 @@ class EventManager:
(listener.handler, name)) from exc (listener.handler, name)) from exc
return results return results
def emit_firstresult(self, name: str, *args: Any) -> Any: def emit_firstresult(self, name: str, *args: Any,
allowed_exceptions: Tuple["Type[Exception]", ...] = ()) -> Any:
"""Emit a Sphinx event and returns first result. """Emit a Sphinx event and returns first result.
This returns the result of the first handler that doesn't return ``None``. This returns the result of the first handler that doesn't return ``None``.
""" """
for result in self.emit(name, *args): for result in self.emit(name, *args, allowed_exceptions=allowed_exceptions):
if result is not None: if result is not None:
return result return result
return None return None

View File

@ -8,6 +8,9 @@
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
import pytest
from sphinx.errors import ExtensionError
from sphinx.events import EventManager from sphinx.events import EventManager
@ -22,3 +25,19 @@ def test_event_priority():
events.emit('builder-inited') events.emit('builder-inited')
assert result == [3, 1, 2, 5, 4] assert result == [3, 1, 2, 5, 4]
def test_event_allowed_exceptions():
def raise_error(app):
raise RuntimeError
events = EventManager(object()) # pass an dummy object as an app
events.connect('builder-inited', raise_error, priority=500)
# all errors are conveted to ExtensionError
with pytest.raises(ExtensionError):
events.emit('builder-inited')
# Allow RuntimeError (pass-through)
with pytest.raises(RuntimeError):
events.emit('builder-inited', allowed_exceptions=(RuntimeError,))