Support priority of event handlers

This commit is contained in:
Takeshi KOMIYA 2020-01-12 00:07:58 +09:00
parent f169560395
commit 2e338aa5cd
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.
* #6830: py domain: ``meta`` fields in info-field-list becomes reserved. They
are not displayed on output document now
* The structure of ``sphinx.events.EventManager.listeners`` has changed
Deprecated
----------
@ -36,6 +37,8 @@ Features added
* #6558: glossary: emit a warning for duplicated glossary entry
* #6558: std domain: emit a warning for duplicated generic objects
* #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
----------

View File

@ -404,17 +404,25 @@ class Sphinx:
raise VersionRequirementError(version)
# 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.
For details on available core events and the arguments of callback
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
:meth:`disconnect`.
.. versionchanged:: 3.0
Support *priority*
"""
listener_id = self.events.connect(event, callback)
logger.debug('[app] connecting event %r: %r [id=%s]', event, callback, listener_id)
listener_id = self.events.connect(event, callback, priority)
logger.debug('[app] connecting event %r (%d): %r [id=%s]',
event, priority, callback, listener_id)
return listener_id
def disconnect(self, listener_id: int) -> None:

View File

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