summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakeshi KOMIYA <i.tkomiya@gmail.com>2019-04-16 14:04:54 +0900
committerGitHub <noreply@github.com>2019-04-16 14:04:54 +0900
commit15bc5a32bb0b78b432803e55fffa10c801182c75 (patch)
tree6005f52022774183f3bc50c1d0cac23452df7270
parent22d7ccd1d99c07731a443b88cb725f76bd9c71f7 (diff)
parent76fb3604fd3f797a7b0a23bf3507a436907e276f (diff)
downloadsphinx-git-15bc5a32bb0b78b432803e55fffa10c801182c75.tar.gz
Merge pull request #6260 from tk0miya/refactor_events
Make EventManager portable
-rw-r--r--doc/extdev/appapi.rst2
-rw-r--r--doc/extdev/builderapi.rst5
-rw-r--r--doc/extdev/envapi.rst4
-rw-r--r--doc/extdev/utils.rst6
-rw-r--r--sphinx/application.py20
-rw-r--r--sphinx/builders/__init__.py14
-rw-r--r--sphinx/builders/html.py2
-rw-r--r--sphinx/environment/__init__.py13
-rw-r--r--sphinx/events.py37
-rw-r--r--sphinx/ext/autodoc/__init__.py6
-rw-r--r--sphinx/ext/todo.py2
11 files changed, 78 insertions, 33 deletions
diff --git a/doc/extdev/appapi.rst b/doc/extdev/appapi.rst
index 4cb8501be..18eea34e7 100644
--- a/doc/extdev/appapi.rst
+++ b/doc/extdev/appapi.rst
@@ -145,7 +145,7 @@ Sphinx core events
------------------
These events are known to the core. The arguments shown are given to the
-registered event handlers. Use :meth:`.connect` in an extension's ``setup``
+registered event handlers. Use :meth:`.Sphinx.connect` in an extension's ``setup``
function (note that ``conf.py`` can also have a ``setup`` function) to connect
handlers to the events. Example:
diff --git a/doc/extdev/builderapi.rst b/doc/extdev/builderapi.rst
index 2c2cf12e3..0ab7a30f4 100644
--- a/doc/extdev/builderapi.rst
+++ b/doc/extdev/builderapi.rst
@@ -38,3 +38,8 @@ Builder API
.. automethod:: write_doc
.. automethod:: finish
+ **Attributes**
+
+ .. attribute:: events
+
+ An :class:`.EventManager` object.
diff --git a/doc/extdev/envapi.rst b/doc/extdev/envapi.rst
index 1dee6a576..d7ec23925 100644
--- a/doc/extdev/envapi.rst
+++ b/doc/extdev/envapi.rst
@@ -27,6 +27,10 @@ Build environment API
Directory for storing pickled doctrees.
+ .. attribute:: events
+
+ An :class:`.EventManager` object.
+
.. attribute:: found_docs
A set of all existing docnames.
diff --git a/doc/extdev/utils.rst b/doc/extdev/utils.rst
index 2a94a34bb..e842f3032 100644
--- a/doc/extdev/utils.rst
+++ b/doc/extdev/utils.rst
@@ -29,3 +29,9 @@ components (e.g. :class:`.Config`, :class:`.BuildEnvironment` and so on) easily.
.. autoclass:: sphinx.transforms.post_transforms.images.ImageConverter
:members:
+
+Utility components
+------------------
+
+.. autoclass:: sphinx.events.EventManager
+ :members:
diff --git a/sphinx/application.py b/sphinx/application.py
index afcdc02ed..e9b950c83 100644
--- a/sphinx/application.py
+++ b/sphinx/application.py
@@ -182,7 +182,7 @@ class Sphinx:
self.warningiserror = warningiserror
logging.setup(self, self._status, self._warning)
- self.events = EventManager()
+ self.events = EventManager(self)
# keep last few messages for traceback
# This will be filled by sphinx.util.logging.LastMessagesWriter
@@ -249,7 +249,7 @@ class Sphinx:
# now that we know all config values, collect them from conf.py
self.config.init_values()
- self.emit('config-inited', self.config)
+ self.events.emit('config-inited', self.config)
# create the project
self.project = Project(self.srcdir, self.config.source_suffix)
@@ -319,7 +319,7 @@ class Sphinx:
# type: () -> None
self.builder.set_environment(self.env)
self.builder.init()
- self.emit('builder-inited')
+ self.events.emit('builder-inited')
# ---- main "build" method -------------------------------------------------
@@ -360,10 +360,10 @@ class Sphinx:
envfile = path.join(self.doctreedir, ENV_PICKLE_FILENAME)
if path.isfile(envfile):
os.unlink(envfile)
- self.emit('build-finished', err)
+ self.events.emit('build-finished', err)
raise
else:
- self.emit('build-finished', None)
+ self.events.emit('build-finished', None)
self.builder.cleanup()
# ---- general extensibility interface -------------------------------------
@@ -420,13 +420,7 @@ class Sphinx:
Return the return values of all callbacks as a list. Do not emit core
Sphinx events in extensions!
"""
- try:
- logger.debug('[app] emitting event: %r%s', event, repr(args)[:100])
- except Exception:
- # not every object likes to be repr()'d (think
- # random stuff coming via autodoc)
- pass
- return self.events.emit(event, self, *args)
+ return self.events.emit(event, *args)
def emit_firstresult(self, event, *args):
# type: (str, Any) -> Any
@@ -436,7 +430,7 @@ class Sphinx:
.. versionadded:: 0.5
"""
- return self.events.emit_firstresult(event, self, *args)
+ return self.events.emit_firstresult(event, *args)
# registering addon parts
diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py
index 1b29fa983..8eaa0e215 100644
--- a/sphinx/builders/__init__.py
+++ b/sphinx/builders/__init__.py
@@ -43,6 +43,7 @@ if False:
from sphinx.application import Sphinx # NOQA
from sphinx.config import Config # NOQA
from sphinx.environment import BuildEnvironment # NOQA
+ from sphinx.events import EventManager # NOQA
from sphinx.util.i18n import CatalogInfo # NOQA
from sphinx.util.tags import Tags # NOQA
@@ -93,6 +94,7 @@ class Builder:
self.app = app # type: Sphinx
self.env = None # type: BuildEnvironment
+ self.events = app.events # type: EventManager
self.config = app.config # type: Config
self.tags = app.tags # type: Tags
self.tags.add(self.format)
@@ -399,7 +401,7 @@ class Builder:
added, changed, removed = self.env.get_outdated_files(updated)
# allow user intervention as well
- for docs in self.app.emit('env-get-outdated', self, added, changed, removed):
+ for docs in self.events.emit('env-get-outdated', self, added, changed, removed):
changed.update(set(docs) & self.env.found_docs)
# if files were added or removed, all documents with globbed toctrees
@@ -416,13 +418,13 @@ class Builder:
# clear all files no longer present
for docname in removed:
- self.app.emit('env-purge-doc', self.env, docname)
+ self.events.emit('env-purge-doc', self.env, docname)
self.env.clear_doc(docname)
# read all new and changed files
docnames = sorted(added | changed)
# allow changing and reordering the list of docs to read
- self.app.emit('env-before-read-docs', self.env, docnames)
+ self.events.emit('env-before-read-docs', self.env, docnames)
# check if we should do parallel or serial read
if parallel_available and len(docnames) > 5 and self.app.parallel > 1:
@@ -439,7 +441,7 @@ class Builder:
raise SphinxError('master file %s not found' %
self.env.doc2path(self.config.master_doc))
- for retval in self.app.emit('env-updated', self.env):
+ for retval in self.events.emit('env-updated', self.env):
if retval is not None:
docnames.extend(retval)
@@ -453,7 +455,7 @@ class Builder:
for docname in status_iterator(docnames, __('reading sources... '), "purple",
len(docnames), self.app.verbosity):
# remove all inventory entries for that file
- self.app.emit('env-purge-doc', self.env, docname)
+ self.events.emit('env-purge-doc', self.env, docname)
self.env.clear_doc(docname)
self.read_doc(docname)
@@ -461,7 +463,7 @@ class Builder:
# type: (List[str], int) -> None
# clear all outdated docs at once
for docname in docnames:
- self.app.emit('env-purge-doc', self.env, docname)
+ self.events.emit('env-purge-doc', self.env, docname)
self.env.clear_doc(docname)
def read_process(docs):
diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py
index 97774c668..81c64d445 100644
--- a/sphinx/builders/html.py
+++ b/sphinx/builders/html.py
@@ -653,7 +653,7 @@ class StandaloneHTMLBuilder(Builder):
def gen_additional_pages(self):
# type: () -> None
# pages from extensions
- for pagelist in self.app.emit('html-collect-pages'):
+ for pagelist in self.events.emit('html-collect-pages'):
for pagename, context, template in pagelist:
self.handle_page(pagename, context, template)
diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py
index f931b3b13..a5adcbb74 100644
--- a/sphinx/environment/__init__.py
+++ b/sphinx/environment/__init__.py
@@ -34,6 +34,7 @@ if False:
from sphinx.application import Sphinx # NOQA
from sphinx.builders import Builder # NOQA
from sphinx.config import Config # NOQA
+ from sphinx.event import EventManager # NOQA
from sphinx.domains import Domain # NOQA
from sphinx.project import Project # NOQA
@@ -95,6 +96,7 @@ class BuildEnvironment:
self.srcdir = None # type: str
self.config = None # type: Config
self.config_status = None # type: int
+ self.events = None # type: EventManager
self.project = None # type: Project
self.version = None # type: Dict[str, str]
@@ -190,7 +192,7 @@ class BuildEnvironment:
# type: () -> Dict
"""Obtains serializable data for pickling."""
__dict__ = self.__dict__.copy()
- __dict__.update(app=None, domains={}) # clear unpickable attributes
+ __dict__.update(app=None, domains={}, events=None) # clear unpickable attributes
return __dict__
def __setstate__(self, state):
@@ -210,6 +212,7 @@ class BuildEnvironment:
self.app = app
self.doctreedir = app.doctreedir
+ self.events = app.events
self.srcdir = app.srcdir
self.project = app.project
self.version = app.registry.get_envversion(app)
@@ -307,7 +310,7 @@ class BuildEnvironment:
for domainname, domain in self.domains.items():
domain.merge_domaindata(docnames, other.domaindata[domainname])
- app.emit('env-merge-info', self, docnames, other)
+ self.events.emit('env-merge-info', self, docnames, other)
def path2doc(self, filename):
# type: (str) -> Optional[str]
@@ -449,7 +452,7 @@ class BuildEnvironment:
def check_dependents(self, app, already):
# type: (Sphinx, Set[str]) -> Iterator[str]
to_rewrite = [] # type: List[str]
- for docnames in app.emit('env-get-updated', self):
+ for docnames in self.events.emit('env-get-updated', self):
to_rewrite.extend(docnames)
for docname in set(to_rewrite):
if docname not in already:
@@ -597,7 +600,7 @@ class BuildEnvironment:
self.temp_data = backup
# allow custom references to be resolved
- self.app.emit('doctree-resolved', doctree, docname)
+ self.events.emit('doctree-resolved', doctree, docname)
def collect_relations(self):
# type: () -> Dict[str, List[str]]
@@ -653,4 +656,4 @@ class BuildEnvironment:
# call check-consistency for all extensions
for domain in self.domains.values():
domain.check_consistency()
- self.app.emit('env-check-consistency', self)
+ self.events.emit('env-check-consistency', self)
diff --git a/sphinx/events.py b/sphinx/events.py
index 25a378d7c..df72f8f21 100644
--- a/sphinx/events.py
+++ b/sphinx/events.py
@@ -10,14 +10,20 @@
:license: BSD, see LICENSE for details.
"""
+import warnings
from collections import OrderedDict, defaultdict
+from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.errors import ExtensionError
from sphinx.locale import __
+from sphinx.util import logging
if False:
# For type annotation
from typing import Any, Callable, Dict, List # NOQA
+ from sphinx.application import Sphinx # NOQA
+
+logger = logging.getLogger(__name__)
# List of all known core events. Maps name to arguments description.
@@ -42,20 +48,28 @@ core_events = {
class EventManager:
- def __init__(self):
- # type: () -> None
+ """Event manager for Sphinx."""
+
+ def __init__(self, app=None):
+ # type: (Sphinx) -> None
+ if app is None:
+ warnings.warn('app argument is required for EventManager.',
+ RemovedInSphinx40Warning)
+ self.app = app
self.events = core_events.copy()
self.listeners = defaultdict(OrderedDict) # type: Dict[str, Dict[int, Callable]]
self.next_listener_id = 0
def add(self, name):
# type: (str) -> None
+ """Register a custom Sphinx event."""
if name in self.events:
raise ExtensionError(__('Event %r already present') % name)
self.events[name] = ''
def connect(self, name, callback):
# type: (str, Callable) -> int
+ """Connect a handler to specific event."""
if name not in self.events:
raise ExtensionError(__('Unknown event name: %s') % name)
@@ -66,18 +80,35 @@ class EventManager:
def disconnect(self, listener_id):
# type: (int) -> None
+ """Disconnect a handler."""
for event in self.listeners.values():
event.pop(listener_id, None)
def emit(self, name, *args):
# type: (str, Any) -> List
+ """Emit a Sphinx event."""
+ try:
+ logger.debug('[app] emitting event: %r%s', name, repr(args)[:100])
+ except Exception:
+ # not every object likes to be repr()'d (think
+ # random stuff coming via autodoc)
+ pass
+
results = []
for callback in self.listeners[name].values():
- results.append(callback(*args))
+ if self.app is None:
+ # for compatibility; RemovedInSphinx40Warning
+ results.append(callback(*args))
+ else:
+ results.append(callback(self.app, *args))
return results
def emit_firstresult(self, name, *args):
# type: (str, Any) -> Any
+ """Emit a Sphinx event and returns first result.
+
+ This returns the result of the first handler that doesn't return ``None``.
+ """
for result in self.emit(name, *args):
if result is not None:
return result
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py
index b3fb52538..aa2f2276f 100644
--- a/sphinx/ext/autodoc/__init__.py
+++ b/sphinx/ext/autodoc/__init__.py
@@ -403,9 +403,9 @@ class Documenter:
retann = self.retann
- result = self.env.app.emit_firstresult(
- 'autodoc-process-signature', self.objtype, self.fullname,
- self.object, self.options, args, retann)
+ result = self.env.events.emit_firstresult('autodoc-process-signature',
+ self.objtype, self.fullname,
+ self.object, self.options, args, retann)
if result:
args, retann = result
diff --git a/sphinx/ext/todo.py b/sphinx/ext/todo.py
index 1922bb49c..f43520036 100644
--- a/sphinx/ext/todo.py
+++ b/sphinx/ext/todo.py
@@ -86,7 +86,7 @@ def process_todos(app, doctree):
if not hasattr(env, 'todo_all_todos'):
env.todo_all_todos = [] # type: ignore
for node in doctree.traverse(todo_node):
- app.emit('todo-defined', node)
+ app.events.emit('todo-defined', node)
newnode = node.deepcopy()
newnode['ids'] = []