mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #7017 from tk0miya/priority_of_event_handlers
Support priority of event handlers
This commit is contained in:
3
CHANGES
3
CHANGES
@@ -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
|
||||||
----------
|
----------
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
24
tests/test_events.py
Normal 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]
|
||||||
Reference in New Issue
Block a user