Add type-check annotations to sphinx.application

This commit is contained in:
Takeshi KOMIYA 2016-11-09 11:45:27 +09:00
parent 8cfb281b05
commit 9c66ac71ab

View File

@ -22,13 +22,13 @@ from collections import deque
from six import iteritems, itervalues, text_type
from six.moves import cStringIO
from docutils import nodes
from docutils.parsers.rst import convert_directive_function, \
directives, roles
import sphinx
from sphinx import package_dir, locale
from sphinx.roles import XRefRole
from sphinx.config import Config
from sphinx.errors import SphinxError, SphinxWarning, ExtensionError, \
VersionRequirementError, ConfigError
@ -36,15 +36,25 @@ from sphinx.domains import ObjType
from sphinx.domains.std import GenericObject, Target, StandardDomain
from sphinx.environment import BuildEnvironment
from sphinx.io import SphinxStandaloneReader
from sphinx.roles import XRefRole
from sphinx.util import pycompat # noqa: F401
from sphinx.util import import_object
from sphinx.util.tags import Tags
from sphinx.util.osutil import ENOENT
from sphinx.util.logging import is_suppressed_warning
from sphinx.util.console import bold, lightgray, darkgray, darkred, darkgreen, \
term_width_line
from sphinx.util.console import ( # type: ignore
bold, lightgray, darkgray, darkred, darkgreen, term_width_line
)
from sphinx.util.i18n import find_catalog_source_files
if False:
# For type annotation
from typing import Any, Callable, IO, Iterable, Iterator, Tuple, Type, Union # NOQA
from docutils.parsers import Parser # NOQA
from docutils.transform import Transform # NOQA
from sphinx.builders import Builder # NOQA
from sphinx.domains import Domain # NOQA
# List of all known core events. Maps name to arguments description.
events = {
'builder-inited': '',
@ -60,7 +70,7 @@ events = {
'html-collect-pages': 'builder',
'html-page-context': 'pagename, context, doctree or None',
'build-finished': 'exception',
}
} # type: Dict[unicode, unicode]
builtin_extensions = (
'sphinx.builders.applehelp',
'sphinx.builders.changes',
@ -90,14 +100,14 @@ builtin_extensions = (
'sphinx.directives.other',
'sphinx.directives.patches',
'sphinx.roles',
)
) # type: Tuple[unicode, ...]
CONFIG_FILENAME = 'conf.py'
ENV_PICKLE_FILENAME = 'environment.pickle'
# list of deprecated extensions. Keys are extension name.
# Values are Sphinx version that merge the extension.
EXTENSION_BLACKLIST = {"sphinxjp.themecore": "1.2"}
EXTENSION_BLACKLIST = {"sphinxjp.themecore": "1.2"} # type: Dict[unicode, unicode]
class Sphinx(object):
@ -106,19 +116,20 @@ class Sphinx(object):
confoverrides=None, status=sys.stdout, warning=sys.stderr,
freshenv=False, warningiserror=False, tags=None, verbosity=0,
parallel=0):
# type: (unicode, unicode, unicode, unicode, unicode, Dict, IO, IO, bool, bool, unicode, int, int) -> None # NOQA
self.verbosity = verbosity
self.next_listener_id = 0
self._extensions = {}
self._extension_metadata = {}
self._additional_source_parsers = {}
self._listeners = {}
self._setting_up_extension = ['?']
self.domains = {}
self._extensions = {} # type: Dict[unicode, Any]
self._extension_metadata = {} # type: Dict[unicode, Dict[unicode, Any]]
self._additional_source_parsers = {} # type: Dict[unicode, Parser]
self._listeners = {} # type: Dict[unicode, Dict[int, Callable]]
self._setting_up_extension = ['?'] # type: List[unicode]
self.domains = {} # type: Dict[unicode, Type[Domain]]
self.buildername = buildername
self.builderclasses = {}
self.builder = None
self.env = None
self.enumerable_nodes = {}
self.builderclasses = {} # type: Dict[unicode, Type[Builder]]
self.builder = None # type: Builder
self.env = None # type: BuildEnvironment
self.enumerable_nodes = {} # type: Dict[nodes.Node, Tuple[unicode, Callable]] # NOQA
self.srcdir = srcdir
self.confdir = confdir
@ -128,24 +139,24 @@ class Sphinx(object):
self.parallel = parallel
if status is None:
self._status = cStringIO()
self._status = cStringIO() # type: IO
self.quiet = True
else:
self._status = status
self.quiet = False
if warning is None:
self._warning = cStringIO()
self._warning = cStringIO() # type: IO
else:
self._warning = warning
self._warncount = 0
self.warningiserror = warningiserror
self._events = events.copy()
self._translators = {}
self._translators = {} # type: Dict[unicode, nodes.GenericNodeVisitor]
# keep last few messages for traceback
self.messagelog = deque(maxlen=10)
self.messagelog = deque(maxlen=10) # type: deque
# say hello to the world
self.info(bold('Running Sphinx v%s' % sphinx.__display_version__))
@ -246,6 +257,7 @@ class Sphinx(object):
self._init_enumerable_nodes()
def _init_i18n(self):
# type: () -> None
"""Load translated strings from the configured localedirs if enabled in
the configuration.
"""
@ -271,6 +283,7 @@ class Sphinx(object):
self.info('not available for built-in messages')
def _init_source_parsers(self):
# type: () -> None
for suffix, parser in iteritems(self._additional_source_parsers):
if suffix not in self.config.source_suffix:
self.config.source_suffix.append(suffix)
@ -278,6 +291,7 @@ class Sphinx(object):
self.config.source_parsers[suffix] = parser
def _init_env(self, freshenv):
# type: (bool) -> None
if freshenv:
self.env = BuildEnvironment(self.srcdir, self.doctreedir, self.config)
self.env.set_warnfunc(self.warn)
@ -304,6 +318,7 @@ class Sphinx(object):
return self._init_env(freshenv=True)
def _init_builder(self, buildername):
# type: (unicode) -> None
if buildername is None:
print('No builder selected, using default: html', file=self._status)
buildername = 'html'
@ -315,12 +330,14 @@ class Sphinx(object):
self.emit('builder-inited')
def _init_enumerable_nodes(self):
# type: () -> None
for node, settings in iteritems(self.enumerable_nodes):
self.env.domains['std'].enumerable_nodes[node] = settings
self.env.domains['std'].enumerable_nodes[node] = settings # type: ignore
# ---- main "build" method -------------------------------------------------
def build(self, force_all=False, filenames=None):
# type: (bool, List[unicode]) -> None
try:
if force_all:
self.builder.compile_all_catalogs()
@ -354,6 +371,7 @@ class Sphinx(object):
# ---- logging handling ----------------------------------------------------
def _log(self, message, wfile, nonl=False):
# type: (unicode, IO, bool) -> None
try:
wfile.write(message)
except UnicodeEncodeError:
@ -367,6 +385,7 @@ class Sphinx(object):
def warn(self, message, location=None, prefix='WARNING: ',
type=None, subtype=None, colorfunc=darkred):
# type: (unicode, unicode, unicode, unicode, unicode, Callable) -> None
"""Emit a warning.
If *location* is given, it should either be a tuple of (docname, lineno)
@ -399,6 +418,7 @@ class Sphinx(object):
self._log(colorfunc(warntext), self._warning, True)
def info(self, message='', nonl=False):
# type: (unicode, bool) -> None
"""Emit an informational message.
If *nonl* is true, don't emit a newline at the end (which implies that
@ -407,6 +427,7 @@ class Sphinx(object):
self._log(message, self._status, nonl)
def verbose(self, message, *args, **kwargs):
# type: (unicode, Any, Any) -> None
"""Emit a verbose informational message.
The message will only be emitted for verbosity levels >= 1 (i.e. at
@ -422,6 +443,7 @@ class Sphinx(object):
self._log(message, self._status)
def debug(self, message, *args, **kwargs):
# type: (unicode, Any, Any) -> None
"""Emit a debug-level informational message.
The message will only be emitted for verbosity levels >= 2 (i.e. at
@ -437,6 +459,7 @@ class Sphinx(object):
self._log(darkgray(message), self._status)
def debug2(self, message, *args, **kwargs):
# type: (unicode, Any, Any) -> None
"""Emit a lowlevel debug-level informational message.
The message will only be emitted for verbosity level 3 (i.e. three
@ -452,6 +475,7 @@ class Sphinx(object):
self._log(lightgray(message), self._status)
def _display_chunk(chunk):
# type: (Any) -> unicode
if isinstance(chunk, (list, tuple)):
if len(chunk) == 1:
return text_type(chunk[0])
@ -460,6 +484,7 @@ class Sphinx(object):
def old_status_iterator(self, iterable, summary, colorfunc=darkgreen,
stringify_func=_display_chunk):
# type: (Iterable, unicode, Callable, Callable) -> Iterator
l = 0
for item in iterable:
if l == 0:
@ -473,6 +498,7 @@ class Sphinx(object):
# new version with progress info
def status_iterator(self, iterable, summary, colorfunc=darkgreen, length=0,
stringify_func=_display_chunk):
# type: (Iterable, unicode, Callable, int, Callable) -> Iterable
if length == 0:
for item in self.old_status_iterator(iterable, summary, colorfunc,
stringify_func):
@ -496,6 +522,7 @@ class Sphinx(object):
# ---- general extensibility interface -------------------------------------
def setup_extension(self, extension):
# type: (unicode) -> None
"""Import and setup a Sphinx extension module. No-op if called twice."""
self.debug('[app] setting up extension: %r', extension)
if extension in self._extensions:
@ -543,21 +570,25 @@ class Sphinx(object):
self._setting_up_extension.pop()
def require_sphinx(self, version):
# type: (unicode) -> None
# check the Sphinx version if requested
if version > sphinx.__display_version__[:3]:
raise VersionRequirementError(version)
def import_object(self, objname, source=None):
# type: (str, unicode) -> Any
"""Import an object from a 'module.name' string."""
return import_object(objname, source=None)
# event interface
def _validate_event(self, event):
# type: (unicode) -> None
if event not in self._events:
raise ExtensionError('Unknown event name: %s' % event)
def connect(self, event, callback):
# type: (unicode, Callable) -> int
self._validate_event(event)
listener_id = self.next_listener_id
if event not in self._listeners:
@ -570,11 +601,13 @@ class Sphinx(object):
return listener_id
def disconnect(self, listener_id):
# type: (int) -> None
self.debug('[app] disconnecting event: [id=%s]', listener_id)
for event in itervalues(self._listeners):
event.pop(listener_id, None)
def emit(self, event, *args):
# type: (unicode, Any) -> List
try:
self.debug2('[app] emitting event: %r%s', event, repr(args)[:100])
except Exception:
@ -588,6 +621,7 @@ class Sphinx(object):
return results
def emit_firstresult(self, event, *args):
# type: (unicode, Any) -> Any
for result in self.emit(event, *args):
if result is not None:
return result
@ -596,6 +630,7 @@ class Sphinx(object):
# registering addon parts
def add_builder(self, builder):
# type: (Type[Builder]) -> None
self.debug('[app] adding builder: %r', builder)
if not hasattr(builder, 'name'):
raise ExtensionError('Builder class %s has no "name" attribute'
@ -607,8 +642,9 @@ class Sphinx(object):
self.builderclasses[builder.name] = builder
def add_config_value(self, name, default, rebuild, types=()):
# type: (unicode, Any, Union[bool, unicode], Any) -> None
self.debug('[app] adding config value: %r',
(name, default, rebuild) + ((types,) if types else ()))
(name, default, rebuild) + ((types,) if types else ())) # type: ignore
if name in self.config.values:
raise ExtensionError('Config value %r already present' % name)
if rebuild in (False, True):
@ -616,16 +652,19 @@ class Sphinx(object):
self.config.values[name] = (default, rebuild, types)
def add_event(self, name):
# type: (unicode) -> None
self.debug('[app] adding event: %r', name)
if name in self._events:
raise ExtensionError('Event %r already present' % name)
self._events[name] = ''
def set_translator(self, name, translator_class):
# type: (unicode, Any) -> None
self.info(bold('A Translator for the %s builder is changed.' % name))
self._translators[name] = translator_class
def add_node(self, node, **kwds):
# type: (nodes.Node, Any) -> None
self.debug('[app] adding node: %r', (node, kwds))
if not kwds.pop('override', False) and \
hasattr(nodes.GenericNodeVisitor, 'visit_' + node.__name__):
@ -644,17 +683,15 @@ class Sphinx(object):
if translator is not None:
pass
elif key == 'html':
from sphinx.writers.html import HTMLTranslator as translator
from sphinx.writers.html import HTMLTranslator as translator # type: ignore
elif key == 'latex':
from sphinx.writers.latex import LaTeXTranslator as translator
from sphinx.writers.latex import LaTeXTranslator as translator # type: ignore
elif key == 'text':
from sphinx.writers.text import TextTranslator as translator
from sphinx.writers.text import TextTranslator as translator # type: ignore
elif key == 'man':
from sphinx.writers.manpage import ManualPageTranslator \
as translator
from sphinx.writers.manpage import ManualPageTranslator as translator # type: ignore # NOQA
elif key == 'texinfo':
from sphinx.writers.texinfo import TexinfoTranslator \
as translator
from sphinx.writers.texinfo import TexinfoTranslator as translator # type: ignore # NOQA
else:
# ignore invalid keys for compatibility
continue
@ -663,14 +700,16 @@ class Sphinx(object):
setattr(translator, 'depart_'+node.__name__, depart)
def add_enumerable_node(self, node, figtype, title_getter=None, **kwds):
# type: (nodes.Node, unicode, Callable, Any) -> None
self.enumerable_nodes[node] = (figtype, title_getter)
self.add_node(node, **kwds)
def _directive_helper(self, obj, content=None, arguments=None, **options):
# type: (Any, unicode, Any, Any) -> Any
if isinstance(obj, (types.FunctionType, types.MethodType)):
obj.content = content
obj.arguments = arguments or (0, 0, False)
obj.options = options
obj.content = content # type: ignore
obj.arguments = arguments or (0, 0, False) # type: ignore
obj.options = options # type: ignore
return convert_directive_function(obj)
else:
if content or arguments or options:
@ -679,6 +718,7 @@ class Sphinx(object):
return obj
def add_directive(self, name, obj, content=None, arguments=None, **options):
# type: (unicode, Any, unicode, Any, Any) -> None
self.debug('[app] adding directive: %r',
(name, obj, content, arguments, options))
if name in directives._directives:
@ -690,6 +730,7 @@ class Sphinx(object):
name, self._directive_helper(obj, content, arguments, **options))
def add_role(self, name, role):
# type: (unicode, Any) -> None
self.debug('[app] adding role: %r', (name, role))
if name in roles._roles:
self.warn('while setting up extension %s: role %r is '
@ -699,6 +740,7 @@ class Sphinx(object):
roles.register_local_role(name, role)
def add_generic_role(self, name, nodeclass):
# type: (unicode, Any) -> None
# don't use roles.register_generic_role because it uses
# register_canonical_role
self.debug('[app] adding generic role: %r', (name, nodeclass))
@ -711,12 +753,14 @@ class Sphinx(object):
roles.register_local_role(name, role)
def add_domain(self, domain):
# type: (Type[Domain]) -> None
self.debug('[app] adding domain: %r', domain)
if domain.name in self.domains:
raise ExtensionError('domain %s already registered' % domain.name)
self.domains[domain.name] = domain
def override_domain(self, domain):
# type: (Type[Domain]) -> None
self.debug('[app] overriding domain: %r', domain)
if domain.name not in self.domains:
raise ExtensionError('domain %s not yet registered' % domain.name)
@ -727,6 +771,7 @@ class Sphinx(object):
def add_directive_to_domain(self, domain, name, obj,
content=None, arguments=None, **options):
# type: (unicode, unicode, Any, unicode, Any, Any) -> None
self.debug('[app] adding directive to domain: %r',
(domain, name, obj, content, arguments, options))
if domain not in self.domains:
@ -735,12 +780,14 @@ class Sphinx(object):
self._directive_helper(obj, content, arguments, **options)
def add_role_to_domain(self, domain, name, role):
# type: (unicode, unicode, Any) -> None
self.debug('[app] adding role to domain: %r', (domain, name, role))
if domain not in self.domains:
raise ExtensionError('domain %s not yet registered' % domain)
self.domains[domain].roles[name] = role
def add_index_to_domain(self, domain, index):
# type: (unicode, unicode) -> None
self.debug('[app] adding index to domain: %r', (domain, index))
if domain not in self.domains:
raise ExtensionError('domain %s not yet registered' % domain)
@ -749,15 +796,16 @@ class Sphinx(object):
def add_object_type(self, directivename, rolename, indextemplate='',
parse_node=None, ref_nodeclass=None, objname='',
doc_field_types=[]):
# type: (unicode, unicode, unicode, Callable, nodes.Node, unicode, List) -> None
self.debug('[app] adding object type: %r',
(directivename, rolename, indextemplate, parse_node,
ref_nodeclass, objname, doc_field_types))
StandardDomain.object_types[directivename] = \
ObjType(objname or directivename, rolename)
# create a subclass of GenericObject as the new directive
new_directive = type(directivename, (GenericObject, object),
new_directive = type(directivename, (GenericObject, object), # type: ignore
{'indextemplate': indextemplate,
'parse_node': staticmethod(parse_node),
'parse_node': staticmethod(parse_node), # type: ignore
'doc_field_types': doc_field_types})
StandardDomain.directives[directivename] = new_directive
# XXX support more options?
@ -768,23 +816,26 @@ class Sphinx(object):
def add_crossref_type(self, directivename, rolename, indextemplate='',
ref_nodeclass=None, objname=''):
# type: (unicode, unicode, unicode, nodes.Node, unicode) -> None
self.debug('[app] adding crossref type: %r',
(directivename, rolename, indextemplate, ref_nodeclass,
objname))
StandardDomain.object_types[directivename] = \
ObjType(objname or directivename, rolename)
# create a subclass of Target as the new directive
new_directive = type(directivename, (Target, object),
new_directive = type(directivename, (Target, object), # type: ignore
{'indextemplate': indextemplate})
StandardDomain.directives[directivename] = new_directive
# XXX support more options?
StandardDomain.roles[rolename] = XRefRole(innernodeclass=ref_nodeclass)
def add_transform(self, transform):
# type: (Transform) -> None
self.debug('[app] adding transform: %r', transform)
SphinxStandaloneReader.transforms.append(transform)
def add_javascript(self, filename):
# type: (unicode) -> None
self.debug('[app] adding javascript: %r', filename)
from sphinx.builders.html import StandaloneHTMLBuilder
if '://' in filename:
@ -794,6 +845,7 @@ class Sphinx(object):
posixpath.join('_static', filename))
def add_stylesheet(self, filename):
# type: (unicode) -> None
self.debug('[app] adding stylesheet: %r', filename)
from sphinx.builders.html import StandaloneHTMLBuilder
if '://' in filename:
@ -803,10 +855,12 @@ class Sphinx(object):
posixpath.join('_static', filename))
def add_latex_package(self, packagename, options=None):
# type: (unicode, unicode) -> None
self.debug('[app] adding latex package: %r', packagename)
self.builder.usepackages.append((packagename, options))
def add_lexer(self, alias, lexer):
# type: (unicode, Any) -> None
self.debug('[app] adding lexer: %r', (alias, lexer))
from sphinx.highlighting import lexers
if lexers is None:
@ -814,23 +868,27 @@ class Sphinx(object):
lexers[alias] = lexer
def add_autodocumenter(self, cls):
# type: (Any) -> None
self.debug('[app] adding autodocumenter: %r', cls)
from sphinx.ext import autodoc
autodoc.add_documenter(cls)
self.add_directive('auto' + cls.objtype, autodoc.AutoDirective)
def add_autodoc_attrgetter(self, type, getter):
# type: (Any, Callable) -> None
self.debug('[app] adding autodoc attrgetter: %r', (type, getter))
from sphinx.ext import autodoc
autodoc.AutoDirective._special_attrgetters[type] = getter
def add_search_language(self, cls):
# type: (Any) -> None
self.debug('[app] adding search language: %r', cls)
from sphinx.search import languages, SearchLanguage
assert issubclass(cls, SearchLanguage)
languages[cls.lang] = cls
def add_source_parser(self, suffix, parser):
# type: (unicode, Parser) -> None
self.debug('[app] adding search source_parser: %r, %r', suffix, parser)
if suffix in self._additional_source_parsers:
self.warn('while setting up extension %s: source_parser for %r is '
@ -847,6 +905,7 @@ class TemplateBridge(object):
"""
def init(self, builder, theme=None, dirs=None):
# type: (Builder, unicode, List[unicode]) -> None
"""Called by the builder to initialize the template system.
*builder* is the builder object; you'll probably want to look at the
@ -858,6 +917,7 @@ class TemplateBridge(object):
raise NotImplementedError('must be implemented in subclasses')
def newest_template_mtime(self):
# type: () -> float
"""Called by the builder to determine if output files are outdated
because of template changes. Return the mtime of the newest template
file that was changed. The default implementation returns ``0``.
@ -865,12 +925,14 @@ class TemplateBridge(object):
return 0
def render(self, template, context):
# type: (unicode, Dict) -> None
"""Called by the builder to render a template given as a filename with
a specified context (a Python dictionary).
"""
raise NotImplementedError('must be implemented in subclasses')
def render_string(self, template, context):
# type: (unicode, Dict) -> unicode
"""Called by the builder to render a template given as a string with a
specified context (a Python dictionary).
"""