Merge pull request #7017 from tk0miya/priority_of_event_handlers

Support priority of event handlers
This commit is contained in:
Takeshi KOMIYA
2020-01-19 22:58:03 +09:00
committed by GitHub
4 changed files with 56 additions and 13 deletions

View File

@@ -18,6 +18,7 @@ Incompatible changes
when ``:inherited-members:`` and ``:special-members:`` are given. when ``:inherited-members:`` and ``:special-members:`` are given.
* #6830: py domain: ``meta`` fields in info-field-list becomes reserved. They * #6830: py domain: ``meta`` fields in info-field-list becomes reserved. They
are not displayed on output document now are not displayed on output document now
* The structure of ``sphinx.events.EventManager.listeners`` has changed
Deprecated Deprecated
---------- ----------
@@ -36,6 +37,8 @@ Features added
* #6558: glossary: emit a warning for duplicated glossary entry * #6558: glossary: emit a warning for duplicated glossary entry
* #6558: std domain: emit a warning for duplicated generic objects * #6558: std domain: emit a warning for duplicated generic objects
* #6830: py domain: Add new event: :event:`object-description-transform` * #6830: py domain: Add new event: :event:`object-description-transform`
* Support priority of event handlers. For more detail, see
:py:meth:`.Sphinx.connect()`
Bugs fixed Bugs fixed
---------- ----------

View File

@@ -404,17 +404,25 @@ class Sphinx:
raise VersionRequirementError(version) raise VersionRequirementError(version)
# event interface # event interface
def connect(self, event: str, callback: Callable) -> int: def connect(self, event: str, callback: Callable, priority: int = 500) -> int:
"""Register *callback* to be called when *event* is emitted. """Register *callback* to be called when *event* is emitted.
For details on available core events and the arguments of callback For details on available core events and the arguments of callback
functions, please see :ref:`events`. functions, please see :ref:`events`.
Registered callbacks will be invoked on event in the order of *priority* and
registration. The priority is ascending order.
The method returns a "listener ID" that can be used as an argument to The method returns a "listener ID" that can be used as an argument to
:meth:`disconnect`. :meth:`disconnect`.
.. versionchanged:: 3.0
Support *priority*
""" """
listener_id = self.events.connect(event, callback) listener_id = self.events.connect(event, callback, priority)
logger.debug('[app] connecting event %r: %r [id=%s]', event, callback, listener_id) logger.debug('[app] connecting event %r (%d): %r [id=%s]',
event, priority, callback, listener_id)
return listener_id return listener_id
def disconnect(self, listener_id: int) -> None: def disconnect(self, listener_id: int) -> None:

View File

@@ -11,8 +11,9 @@
""" """
import warnings import warnings
from collections import OrderedDict, defaultdict from collections import defaultdict
from typing import Any, Callable, Dict, List from operator import attrgetter
from typing import Any, Callable, Dict, List, NamedTuple
from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.errors import ExtensionError from sphinx.errors import ExtensionError
@@ -26,6 +27,10 @@ if False:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
EventListener = NamedTuple('EventListener', [('id', int),
('handler', Callable),
('priority', int)])
# List of all known core events. Maps name to arguments description. # List of all known core events. Maps name to arguments description.
core_events = { core_events = {
@@ -57,7 +62,7 @@ class EventManager:
RemovedInSphinx40Warning) RemovedInSphinx40Warning)
self.app = app self.app = app
self.events = core_events.copy() self.events = core_events.copy()
self.listeners = defaultdict(OrderedDict) # type: Dict[str, Dict[int, Callable]] self.listeners = defaultdict(list) # type: Dict[str, List[EventListener]]
self.next_listener_id = 0 self.next_listener_id = 0
def add(self, name: str) -> None: def add(self, name: str) -> None:
@@ -66,20 +71,22 @@ class EventManager:
raise ExtensionError(__('Event %r already present') % name) raise ExtensionError(__('Event %r already present') % name)
self.events[name] = '' self.events[name] = ''
def connect(self, name: str, callback: Callable) -> int: def connect(self, name: str, callback: Callable, priority: int) -> int:
"""Connect a handler to specific event.""" """Connect a handler to specific event."""
if name not in self.events: if name not in self.events:
raise ExtensionError(__('Unknown event name: %s') % name) raise ExtensionError(__('Unknown event name: %s') % name)
listener_id = self.next_listener_id listener_id = self.next_listener_id
self.next_listener_id += 1 self.next_listener_id += 1
self.listeners[name][listener_id] = callback self.listeners[name].append(EventListener(listener_id, callback, priority))
return listener_id return listener_id
def disconnect(self, listener_id: int) -> None: def disconnect(self, listener_id: int) -> None:
"""Disconnect a handler.""" """Disconnect a handler."""
for event in self.listeners.values(): for listeners in self.listeners.values():
event.pop(listener_id, None) for listener in listeners[:]:
if listener.id == listener_id:
listeners.remove(listener)
def emit(self, name: str, *args: Any) -> List: def emit(self, name: str, *args: Any) -> List:
"""Emit a Sphinx event.""" """Emit a Sphinx event."""
@@ -91,12 +98,13 @@ class EventManager:
pass pass
results = [] results = []
for callback in self.listeners[name].values(): listeners = sorted(self.listeners[name], key=attrgetter("priority"))
for listener in listeners:
if self.app is None: if self.app is None:
# for compatibility; RemovedInSphinx40Warning # for compatibility; RemovedInSphinx40Warning
results.append(callback(*args)) results.append(listener.handler(*args))
else: else:
results.append(callback(self.app, *args)) results.append(listener.handler(self.app, *args))
return results return results
def emit_firstresult(self, name: str, *args: Any) -> Any: def emit_firstresult(self, name: str, *args: Any) -> Any:

24
tests/test_events.py Normal file
View File

@@ -0,0 +1,24 @@
"""
test_events
~~~~~~~~~~~
Test the EventManager class.
:copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
from sphinx.events import EventManager
def test_event_priority():
result = []
events = EventManager(object()) # pass an dummy object as an app
events.connect('builder-inited', lambda app: result.append(1), priority = 500)
events.connect('builder-inited', lambda app: result.append(2), priority = 500)
events.connect('builder-inited', lambda app: result.append(3), priority = 200) # eariler
events.connect('builder-inited', lambda app: result.append(4), priority = 700) # later
events.connect('builder-inited', lambda app: result.append(5), priority = 500)
events.emit('builder-inited')
assert result == [3, 1, 2, 5, 4]