diff --git a/CHANGES b/CHANGES index c4f4c3c52..10b188ca0 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,8 @@ Dependencies Incompatible changes -------------------- +* apidoc: template files are renamed to ``.rst_t`` + Deprecated ---------- @@ -25,22 +27,37 @@ Features added -------------- * #5124: graphviz: ``:graphviz_dot:`` option is renamed to ``:layout:`` +* #1464: html: emit a warning if :confval:`html_static_path` and + :confval:`html_extra_path` directories are inside output directory +* #6514: html: Add a label to search input for accessability purposes +* #5602: apidoc: Add ``--templatedir`` option +* #6475: Add ``override`` argument to ``app.add_autodocumenter()`` +* #6533: LaTeX: refactor visit_enumerated_list() to use ``\sphinxsetlistlabels`` Bugs fixed ---------- * py domain: duplicated warning does not point the location of source code -* #5592: std domain: :rst:dir:`option` directive registers an index entry for - each comma separated option +* #6499: html: Sphinx never updates a copy of :confval:`html_logo` even if + original file has changed * #1125: html theme: scrollbar is hard to see on classic theme and macOS * #5502: linkcheck: Consider HTTP 503 response as not an error * #6439: Make generated download links reproducible * #6486: UnboundLocalError is raised if broken extension installed +* #6498: autosummary: crashed with wrong autosummary_generate setting +* #6507: autosummary: crashes without no autosummary_generate setting +* #6511: LaTeX: autonumbered list can not be customized in LaTeX + since Sphinx 1.8.0 (refs: #6533) +* #6531: Failed to load last environment object when extension added +* #736: Invalid sort in pair index +* #6527: :confval:`last_updated` wrongly assumes timezone as UTC +* #5592: std domain: :rst:dir:`option` directive registers an index entry for + each comma separated option Testing -------- -Release 2.1.2 (in development) +Release 2.1.3 (in development) ============================== Dependencies @@ -61,6 +78,15 @@ Bugs fixed Testing -------- +Release 2.1.2 (released Jun 19, 2019) +===================================== + +Bugs fixed +---------- + +* #6497: custom lexers fails highlighting when syntax error +* #6478, #6488: info field lists are incorrectly recognized + Release 2.1.1 (released Jun 10, 2019) ===================================== diff --git a/doc/man/sphinx-apidoc.rst b/doc/man/sphinx-apidoc.rst index aef497e0d..78c0735cb 100644 --- a/doc/man/sphinx-apidoc.rst +++ b/doc/man/sphinx-apidoc.rst @@ -126,6 +126,30 @@ These options are used when :option:`--full` is specified: Sets the project release to put in generated files (see :confval:`release`). +.. rubric:: Project templating + +.. versionadded:: 2.2 + Project templating options for sphinx-apidoc + +.. option:: -t, --templatedir=TEMPLATEDIR + + Template directory for template files. You can modify the templates of + sphinx project files generated by apidoc. Following Jinja2 template + files are allowed: + + * ``module.rst_t`` + * ``package.rst_t`` + * ``toc.rst_t`` + * ``master_doc.rst_t`` + * ``conf.py_t`` + * ``Makefile_t`` + * ``Makefile.new_t`` + * ``make.bat_t`` + * ``make.bat.new_t`` + + In detail, please refer the system template files Sphinx provides. + (``sphinx/templates/apidoc`` and ``sphinx/templates/quickstart``) + Environment ----------- diff --git a/doc/usage/extensions/autodoc.rst b/doc/usage/extensions/autodoc.rst index 8fdee214b..74c0b609d 100644 --- a/doc/usage/extensions/autodoc.rst +++ b/doc/usage/extensions/autodoc.rst @@ -243,21 +243,23 @@ inserting them into the page source under a suitable :rst:dir:`py:module`, These work exactly like :rst:dir:`autoclass` etc., but do not offer the options used for automatic member documentation. - :rst:dir:`autodata` and :rst:dir:`autoattribute` support - the ``annotation`` option. - Without this option, the representation of the object - will be shown in the documentation. - When the option is given without arguments, - only the name of the object will be printed:: + :rst:dir:`autodata` and :rst:dir:`autoattribute` support the ``annotation`` + option. The option controls how the value of variable is shown. If specified + without arguments, only the name of the variable will be printed, and its value + is not shown:: .. autodata:: CD_DRIVE :annotation: - You can tell sphinx what should be printed after the name:: + If the option specified with arguments, it is printed after the name as a value + of the variable:: .. autodata:: CD_DRIVE :annotation: = your CD device name + By default, without ``annotation`` option, Sphinx tries to obtain the value of + the variable and print it after the name. + For module data members and class attributes, documentation can either be put into a comment with special formatting (using a ``#:`` to start the comment instead of just ``#``), or in a docstring *after* the definition. Comments diff --git a/setup.py b/setup.py index 91b3e12cc..96bffccfa 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ extras_require = { 'html5lib', 'flake8>=3.5.0', 'flake8-import-order', - 'mypy>=0.590', + 'mypy>=0.711', 'docutils-stubs', ], } diff --git a/sphinx/application.py b/sphinx/application.py index 96471be0c..a841f52b1 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -1054,8 +1054,8 @@ class Sphinx: else: lexer_classes[alias] = lexer - def add_autodocumenter(self, cls): - # type: (Any) -> None + def add_autodocumenter(self, cls, override=False): + # type: (Any, bool) -> None """Register a new documenter class for the autodoc extension. Add *cls* as a new documenter class for the :mod:`sphinx.ext.autodoc` @@ -1067,11 +1067,13 @@ class Sphinx: .. todo:: Add real docs for Documenter and subclassing .. versionadded:: 0.6 + .. versionchanged:: 2.2 + Add *override* keyword. """ logger.debug('[app] adding autodocumenter: %r', cls) from sphinx.ext.autodoc.directive import AutodocDirective self.registry.add_documenter(cls.objtype, cls) - self.add_directive('auto' + cls.objtype, AutodocDirective) + self.add_directive('auto' + cls.objtype, AutodocDirective, override=override) def add_autodoc_attrgetter(self, typ, getter): # type: (Type, Callable[[Any, str, Any], Any]) -> None diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 1fd9e6cac..1a7054bdc 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -38,8 +38,7 @@ from sphinx.highlighting import PygmentsBridge from sphinx.locale import _, __ from sphinx.search import js_index from sphinx.theming import HTMLThemeFactory -from sphinx.util import logging, status_iterator -from sphinx.util.console import bold # type: ignore +from sphinx.util import logging, progress_message, status_iterator from sphinx.util.docutils import is_html5_writer_available, new_document from sphinx.util.fileutil import copy_asset from sphinx.util.i18n import format_date @@ -626,6 +625,7 @@ class StandaloneHTMLBuilder(Builder): def finish(self) -> None: self.finish_tasks.add_task(self.gen_indices) + self.finish_tasks.add_task(self.gen_pages_from_extensions) self.finish_tasks.add_task(self.gen_additional_pages) self.finish_tasks.add_task(self.copy_image_files) self.finish_tasks.add_task(self.copy_download_files) @@ -636,9 +636,8 @@ class StandaloneHTMLBuilder(Builder): # dump the search index self.handle_finish() + @progress_message(__('generating indices')) def gen_indices(self) -> None: - logger.info(bold(__('generating indices...')), nonl=True) - # the global general index if self.use_index: self.write_genindex() @@ -646,16 +645,14 @@ class StandaloneHTMLBuilder(Builder): # the global domain-specific indices self.write_domain_indices() - logger.info('') - - def gen_additional_pages(self) -> None: + def gen_pages_from_extensions(self) -> None: # pages from extensions for pagelist in self.events.emit('html-collect-pages'): for pagename, context, template in pagelist: self.handle_page(pagename, context, template) - logger.info(bold(__('writing additional pages...')), nonl=True) - + @progress_message(__('writing additional pages')) + def gen_additional_pages(self) -> None: # additional pages from conf.py for pagename, template in self.config.html_additional_pages.items(): logger.info(' ' + pagename, nonl=True) @@ -672,8 +669,6 @@ class StandaloneHTMLBuilder(Builder): fn = path.join(self.outdir, '_static', 'opensearch.xml') self.handle_page('opensearch', {}, 'opensearch.xml', outfilename=fn) - logger.info('') - def write_genindex(self) -> None: # the total count of lines for each index letter, used to distribute # the entries into two columns @@ -746,84 +741,77 @@ class StandaloneHTMLBuilder(Builder): logger.warning(__('cannot copy downloadable file %r: %s'), path.join(self.srcdir, src), err) + def create_pygments_style_file(self) -> None: + """create a style file for pygments.""" + with open(path.join(self.outdir, '_static', 'pygments.css'), 'w') as f: + f.write(self.highlighter.get_stylesheet()) + + def copy_translation_js(self) -> None: + """Copy a JavaScript file for translations.""" + if self.config.language is not None: + jsfile = self._get_translations_js() + if jsfile: + copyfile(jsfile, path.join(self.outdir, '_static', 'translations.js')) + + def copy_stemmer_js(self) -> None: + """Copy a JavaScript file for stemmer.""" + if self.indexer is not None: + jsfile = self.indexer.get_js_stemmer_rawcode() + if jsfile: + copyfile(jsfile, path.join(self.outdir, '_static', '_stemmer.js')) + + def copy_theme_static_files(self, context: Dict) -> None: + if self.theme: + for entry in self.theme.get_theme_dirs()[::-1]: + copy_asset(path.join(entry, 'static'), + path.join(self.outdir, '_static'), + excluded=DOTFILES, context=context, renderer=self.templates) + + def copy_html_static_files(self, context: Dict) -> None: + excluded = Matcher(self.config.exclude_patterns + ["**/.*"]) + for entry in self.config.html_static_path: + copy_asset(path.join(self.confdir, entry), + path.join(self.outdir, '_static'), + excluded, context=context, renderer=self.templates) + + def copy_html_logo(self) -> None: + if self.config.html_logo: + copy_asset(path.join(self.confdir, self.config.html_logo), + path.join(self.outdir, '_static')) + + def copy_html_favicon(self) -> None: + if self.config.html_favicon: + copy_asset(path.join(self.confdir, self.config.html_favicon), + path.join(self.outdir, '_static')) + def copy_static_files(self) -> None: try: - # copy static files - logger.info(bold(__('copying static files... ')), nonl=True) - ensuredir(path.join(self.outdir, '_static')) - # first, create pygments style file - with open(path.join(self.outdir, '_static', 'pygments.css'), 'w') as f: - f.write(self.highlighter.get_stylesheet()) - # then, copy translations JavaScript file - if self.config.language is not None: - jsfile = self._get_translations_js() - if jsfile: - copyfile(jsfile, path.join(self.outdir, '_static', - 'translations.js')) + with progress_message(__('copying static files... ')): + ensuredir(path.join(self.outdir, '_static')) - # copy non-minified stemmer JavaScript file - if self.indexer is not None: - jsfile = self.indexer.get_js_stemmer_rawcode() - if jsfile: - copyfile(jsfile, path.join(self.outdir, '_static', '_stemmer.js')) + # prepare context for templates + context = self.globalcontext.copy() + if self.indexer is not None: + context.update(self.indexer.context_for_searchtool()) - ctx = self.globalcontext.copy() - - # add context items for search function used in searchtools.js_t - if self.indexer is not None: - ctx.update(self.indexer.context_for_searchtool()) - - # then, copy over theme-supplied static files - if self.theme: - for theme_path in self.theme.get_theme_dirs()[::-1]: - entry = path.join(theme_path, 'static') - copy_asset(entry, path.join(self.outdir, '_static'), excluded=DOTFILES, - context=ctx, renderer=self.templates) - # then, copy over all user-supplied static files - excluded = Matcher(self.config.exclude_patterns + ["**/.*"]) - for static_path in self.config.html_static_path: - entry = path.join(self.confdir, static_path) - if not path.exists(entry): - logger.warning(__('html_static_path entry %r does not exist'), entry) - continue - copy_asset(entry, path.join(self.outdir, '_static'), excluded, - context=ctx, renderer=self.templates) - # copy logo and favicon files if not already in static path - if self.config.html_logo: - logobase = path.basename(self.config.html_logo) - logotarget = path.join(self.outdir, '_static', logobase) - if not path.isfile(path.join(self.confdir, self.config.html_logo)): - logger.warning(__('logo file %r does not exist'), self.config.html_logo) - elif not path.isfile(logotarget): - copyfile(path.join(self.confdir, self.config.html_logo), - logotarget) - if self.config.html_favicon: - iconbase = path.basename(self.config.html_favicon) - icontarget = path.join(self.outdir, '_static', iconbase) - if not path.isfile(path.join(self.confdir, self.config.html_favicon)): - logger.warning(__('favicon file %r does not exist'), - self.config.html_favicon) - elif not path.isfile(icontarget): - copyfile(path.join(self.confdir, self.config.html_favicon), - icontarget) - logger.info(__('done')) + self.create_pygments_style_file() + self.copy_translation_js() + self.copy_stemmer_js() + self.copy_theme_static_files(context) + self.copy_html_static_files(context) + self.copy_html_logo() + self.copy_html_favicon() except OSError as err: logger.warning(__('cannot copy static file %r'), err) def copy_extra_files(self) -> None: + """copy html_extra_path files.""" try: - # copy html_extra_path files - logger.info(bold(__('copying extra files... ')), nonl=True) - excluded = Matcher(self.config.exclude_patterns) - - for extra_path in self.config.html_extra_path: - entry = path.join(self.confdir, extra_path) - if not path.exists(entry): - logger.warning(__('html_extra_path entry %r does not exist'), entry) - continue - - copy_asset(entry, self.outdir, excluded) - logger.info(__('done')) + with progress_message(__('copying extra files')): + excluded = Matcher(self.config.exclude_patterns) + for extra_path in self.config.html_extra_path: + entry = path.join(self.confdir, extra_path) + copy_asset(entry, self.outdir, excluded) except OSError as err: logger.warning(__('cannot copy extra file %r'), err) @@ -1067,27 +1055,23 @@ class StandaloneHTMLBuilder(Builder): self.finish_tasks.add_task(self.dump_search_index) self.finish_tasks.add_task(self.dump_inventory) + @progress_message(__('dumping object inventory')) def dump_inventory(self) -> None: - logger.info(bold(__('dumping object inventory... ')), nonl=True) InventoryFile.dump(path.join(self.outdir, INVENTORY_FILENAME), self.env, self) - logger.info(__('done')) def dump_search_index(self) -> None: - logger.info( - bold(__('dumping search index in %s ... ') % self.indexer.label()), - nonl=True) - self.indexer.prune(self.env.all_docs) - searchindexfn = path.join(self.outdir, self.searchindex_filename) - # first write to a temporary file, so that if dumping fails, - # the existing index won't be overwritten - if self.indexer_dumps_unicode: - with open(searchindexfn + '.tmp', 'w', encoding='utf-8') as ft: - self.indexer.dump(ft, self.indexer_format) - else: - with open(searchindexfn + '.tmp', 'wb') as fb: - self.indexer.dump(fb, self.indexer_format) - movefile(searchindexfn + '.tmp', searchindexfn) - logger.info(__('done')) + with progress_message(__('dumping search index in %s') % self.indexer.label()): + self.indexer.prune(self.env.all_docs) + searchindexfn = path.join(self.outdir, self.searchindex_filename) + # first write to a temporary file, so that if dumping fails, + # the existing index won't be overwritten + if self.indexer_dumps_unicode: + with open(searchindexfn + '.tmp', 'w', encoding='utf-8') as ft: + self.indexer.dump(ft, self.indexer_format) + else: + with open(searchindexfn + '.tmp', 'wb') as fb: + self.indexer.dump(fb, self.indexer_format) + movefile(searchindexfn + '.tmp', searchindexfn) def convert_html_css_files(app: Sphinx, config: Config) -> None: @@ -1166,6 +1150,44 @@ def validate_math_renderer(app: Sphinx) -> None: raise ConfigError(__('Unknown math_renderer %r is given.') % name) +def validate_html_extra_path(app: Sphinx, config: Config) -> None: + """Check html_extra_paths setting.""" + for entry in config.html_extra_path[:]: + extra_path = path.normpath(path.join(app.confdir, entry)) + if not path.exists(extra_path): + logger.warning(__('html_extra_path entry %r does not exist'), entry) + config.html_extra_path.remove(entry) + elif path.commonpath([app.outdir, extra_path]) == app.outdir: + logger.warning(__('html_extra_path entry %r is placed inside outdir'), entry) + config.html_extra_path.remove(entry) + + +def validate_html_static_path(app: Sphinx, config: Config) -> None: + """Check html_static_paths setting.""" + for entry in config.html_static_path[:]: + static_path = path.normpath(path.join(app.confdir, entry)) + if not path.exists(static_path): + logger.warning(__('html_static_path entry %r does not exist'), entry) + config.html_static_path.remove(entry) + elif path.commonpath([app.outdir, static_path]) == app.outdir: + logger.warning(__('html_static_path entry %r is placed inside outdir'), entry) + config.html_static_path.remove(entry) + + +def validate_html_logo(app: Sphinx, config: Config) -> None: + """Check html_logo setting.""" + if config.html_logo and not path.isfile(path.join(app.confdir, config.html_logo)): + logger.warning(__('logo file %r does not exist'), config.html_logo) + config.html_logo = None # type: ignore + + +def validate_html_favicon(app: Sphinx, config: Config) -> None: + """Check html_favicon setting.""" + if config.html_favicon and not path.isfile(path.join(app.confdir, config.html_favicon)): + logger.warning(__('favicon file %r does not exist'), config.html_favicon) + config.html_favicon = None # type: ignore + + # for compatibility import sphinx.builders.dirhtml # NOQA import sphinx.builders.singlehtml # NOQA @@ -1221,6 +1243,10 @@ def setup(app: Sphinx) -> Dict[str, Any]: # event handlers app.connect('config-inited', convert_html_css_files) app.connect('config-inited', convert_html_js_files) + app.connect('config-inited', validate_html_extra_path) + app.connect('config-inited', validate_html_static_path) + app.connect('config-inited', validate_html_logo) + app.connect('config-inited', validate_html_favicon) app.connect('builder-inited', validate_math_renderer) app.connect('html-page-context', setup_js_tag_helper) diff --git a/sphinx/cmd/build.py b/sphinx/cmd/build.py index 810d69078..d2f6c13b7 100644 --- a/sphinx/cmd/build.py +++ b/sphinx/cmd/build.py @@ -14,6 +14,7 @@ import multiprocessing import os import sys import traceback +from typing import Any, IO, List from docutils.utils import SystemMessage @@ -26,13 +27,8 @@ from sphinx.util import Tee, format_exception_cut_frames, save_traceback from sphinx.util.console import red, nocolor, color_terminal, terminal_safe # type: ignore from sphinx.util.docutils import docutils_namespace, patch_docutils -if False: - # For type annotation - from typing import Any, IO, List, Union # NOQA - -def handle_exception(app, args, exception, stderr=sys.stderr): - # type: (Sphinx, Any, Union[Exception, KeyboardInterrupt], IO) -> None +def handle_exception(app: Sphinx, args: Any, exception: BaseException, stderr: IO = sys.stderr) -> None: # NOQA if args.pdb: import pdb print(red(__('Exception occurred while building, starting debugger:')), @@ -82,8 +78,7 @@ def handle_exception(app, args, exception, stderr=sys.stderr): file=stderr) -def jobs_argument(value): - # type: (str) -> int +def jobs_argument(value: str) -> int: """ Special type to handle 'auto' flags passed to 'sphinx-build' via -j flag. Can be expanded to handle other special scaling requests, such as setting job count @@ -99,8 +94,7 @@ def jobs_argument(value): return jobs -def get_parser(): - # type: () -> argparse.ArgumentParser +def get_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( usage='%(prog)s [OPTIONS] SOURCEDIR OUTPUTDIR [FILENAMES...]', epilog=__('For more information, visit .'), @@ -195,15 +189,13 @@ files can be built by specifying individual filenames. return parser -def make_main(argv=sys.argv[1:]): - # type: (List[str]) -> int +def make_main(argv: List[str] = sys.argv[1:]) -> int: """Sphinx build "make mode" entry.""" from sphinx.cmd import make_mode return make_mode.run_make_mode(argv[1:]) -def build_main(argv=sys.argv[1:]): - # type: (List[str]) -> int +def build_main(argv: List[str] = sys.argv[1:]) -> int: """Sphinx build "main" command-line entry.""" parser = get_parser() @@ -288,8 +280,7 @@ def build_main(argv=sys.argv[1:]): return 2 -def main(argv=sys.argv[1:]): - # type: (List[str]) -> int +def main(argv: List[str] = sys.argv[1:]) -> int: sphinx.locale.setlocale(locale.LC_ALL, '') sphinx.locale.init_console(os.path.join(package_dir, 'locale'), 'sphinx') diff --git a/sphinx/cmd/make_mode.py b/sphinx/cmd/make_mode.py index e87aa02fc..508b60959 100644 --- a/sphinx/cmd/make_mode.py +++ b/sphinx/cmd/make_mode.py @@ -18,16 +18,13 @@ import os import subprocess import sys from os import path +from typing import List import sphinx from sphinx.cmd.build import build_main from sphinx.util.console import color_terminal, nocolor, bold, blue # type: ignore from sphinx.util.osutil import cd, rmtree -if False: - # For type annotation - from typing import List # NOQA - BUILDERS = [ ("", "html", "to make standalone HTML files"), @@ -58,20 +55,16 @@ BUILDERS = [ class Make: - - def __init__(self, srcdir, builddir, opts): - # type: (str, str, List[str]) -> None + def __init__(self, srcdir: str, builddir: str, opts: List[str]) -> None: self.srcdir = srcdir self.builddir = builddir self.opts = opts self.makecmd = os.environ.get('MAKE', 'make') # refer $MAKE to determine make command - def builddir_join(self, *comps): - # type: (str) -> str + def builddir_join(self, *comps: str) -> str: return path.join(self.builddir, *comps) - def build_clean(self): - # type: () -> int + def build_clean(self) -> int: srcdir = path.abspath(self.srcdir) builddir = path.abspath(self.builddir) if not path.exists(self.builddir): @@ -90,8 +83,7 @@ class Make: rmtree(self.builddir_join(item)) return 0 - def build_help(self): - # type: () -> None + def build_help(self) -> None: if not color_terminal(): nocolor() @@ -101,8 +93,7 @@ class Make: if not osname or os.name == osname: print(' %s %s' % (blue(bname.ljust(10)), description)) - def build_latexpdf(self): - # type: () -> int + def build_latexpdf(self) -> int: if self.run_generic_build('latex') > 0: return 1 @@ -117,8 +108,7 @@ class Make: print('Error: Failed to run: %s' % makecmd) return 1 - def build_latexpdfja(self): - # type: () -> int + def build_latexpdfja(self) -> int: if self.run_generic_build('latex') > 0: return 1 @@ -133,8 +123,7 @@ class Make: print('Error: Failed to run: %s' % makecmd) return 1 - def build_info(self): - # type: () -> int + def build_info(self) -> int: if self.run_generic_build('texinfo') > 0: return 1 try: @@ -144,15 +133,13 @@ class Make: print('Error: Failed to run: %s' % self.makecmd) return 1 - def build_gettext(self): - # type: () -> int + def build_gettext(self) -> int: dtdir = self.builddir_join('gettext', '.doctrees') if self.run_generic_build('gettext', doctreedir=dtdir) > 0: return 1 return 0 - def run_generic_build(self, builder, doctreedir=None): - # type: (str, str) -> int + def run_generic_build(self, builder: str, doctreedir: str = None) -> int: # compatibility with old Makefile papersize = os.getenv('PAPER', '') opts = self.opts @@ -168,8 +155,7 @@ class Make: return build_main(args + opts) -def run_make_mode(args): - # type: (List[str]) -> int +def run_make_mode(args: List[str]) -> int: if len(args) < 3: print('Error: at least 3 arguments (builder, source ' 'dir, build dir) are required.', file=sys.stderr) diff --git a/sphinx/cmd/quickstart.py b/sphinx/cmd/quickstart.py index 2a509bacb..846750a68 100644 --- a/sphinx/cmd/quickstart.py +++ b/sphinx/cmd/quickstart.py @@ -17,6 +17,7 @@ import time import warnings from collections import OrderedDict from os import path +from typing import Any, Callable, Dict, List, Pattern, Union # try to import readline, unix specific enhancement try: @@ -42,10 +43,6 @@ from sphinx.util.console import ( # type: ignore from sphinx.util.osutil import ensuredir from sphinx.util.template import SphinxRenderer -if False: - # For type annotation - from typing import Any, Callable, Dict, List, Pattern, Union # NOQA - TERM_ENCODING = getattr(sys.stdin, 'encoding', None) # RemovedInSphinx40Warning EXTENSIONS = OrderedDict([ @@ -84,8 +81,7 @@ else: # function to get input from terminal -- overridden by the test suite -def term_input(prompt): - # type: (str) -> str +def term_input(prompt: str) -> str: if sys.platform == 'win32': # Important: On windows, readline is not enabled by default. In these # environment, escape sequences have been broken. To avoid the @@ -100,58 +96,49 @@ class ValidationError(Exception): """Raised for validation errors.""" -def is_path(x): - # type: (str) -> str +def is_path(x: str) -> str: x = path.expanduser(x) if not path.isdir(x): raise ValidationError(__("Please enter a valid path name.")) return x -def allow_empty(x): - # type: (str) -> str +def allow_empty(x: str) -> str: return x -def nonempty(x): - # type: (str) -> str +def nonempty(x: str) -> str: if not x: raise ValidationError(__("Please enter some text.")) return x -def choice(*l): - # type: (str) -> Callable[[str], str] - def val(x): - # type: (str) -> str +def choice(*l: str) -> Callable[[str], str]: + def val(x: str) -> str: if x not in l: raise ValidationError(__('Please enter one of %s.') % ', '.join(l)) return x return val -def boolean(x): - # type: (str) -> bool +def boolean(x: str) -> bool: if x.upper() not in ('Y', 'YES', 'N', 'NO'): raise ValidationError(__("Please enter either 'y' or 'n'.")) return x.upper() in ('Y', 'YES') -def suffix(x): - # type: (str) -> str +def suffix(x: str) -> str: if not (x[0:1] == '.' and len(x) > 1): raise ValidationError(__("Please enter a file suffix, " "e.g. '.rst' or '.txt'.")) return x -def ok(x): - # type: (str) -> str +def ok(x: str) -> str: return x -def term_decode(text): - # type: (Union[bytes,str]) -> str +def term_decode(text: Union[bytes, str]) -> str: warnings.warn('term_decode() is deprecated.', RemovedInSphinx40Warning, stacklevel=2) @@ -175,8 +162,7 @@ def term_decode(text): return text.decode('latin1') -def do_prompt(text, default=None, validator=nonempty): - # type: (str, str, Callable[[str], Any]) -> Union[str, bool] +def do_prompt(text: str, default: str = None, validator: Callable[[str], Any] = nonempty) -> Union[str, bool]: # NOQA while True: if default is not None: prompt = PROMPT_PREFIX + '%s [%s]: ' % (text, default) @@ -201,8 +187,7 @@ def do_prompt(text, default=None, validator=nonempty): return x -def convert_python_source(source, rex=re.compile(r"[uU]('.*?')")): - # type: (str, Pattern) -> str +def convert_python_source(source: str, rex: Pattern = re.compile(r"[uU]('.*?')")) -> str: # remove Unicode literal prefixes warnings.warn('convert_python_source() is deprecated.', RemovedInSphinx40Warning) @@ -210,13 +195,11 @@ def convert_python_source(source, rex=re.compile(r"[uU]('.*?')")): class QuickstartRenderer(SphinxRenderer): - def __init__(self, templatedir): - # type: (str) -> None + def __init__(self, templatedir: str) -> None: self.templatedir = templatedir or '' super().__init__() - def render(self, template_name, context): - # type: (str, Dict) -> str + def render(self, template_name: str, context: Dict) -> str: user_template = path.join(self.templatedir, path.basename(template_name)) if self.templatedir and path.exists(user_template): return self.render_from_file(user_template, context) @@ -224,8 +207,7 @@ class QuickstartRenderer(SphinxRenderer): return super().render(template_name, context) -def ask_user(d): - # type: (Dict) -> None +def ask_user(d: Dict) -> None: """Ask the user for quickstart values missing from *d*. Values are: @@ -367,8 +349,8 @@ directly.''')) print() -def generate(d, overwrite=True, silent=False, templatedir=None): - # type: (Dict, bool, bool, str) -> None +def generate(d: Dict, overwrite: bool = True, silent: bool = False, templatedir: str = None + ) -> None: """Generate project based on values in *d*.""" template = QuickstartRenderer(templatedir=templatedir) @@ -401,8 +383,7 @@ def generate(d, overwrite=True, silent=False, templatedir=None): ensuredir(path.join(srcdir, d['dot'] + 'templates')) ensuredir(path.join(srcdir, d['dot'] + 'static')) - def write_file(fpath, content, newline=None): - # type: (str, str, str) -> None + def write_file(fpath: str, content: str, newline: str = None) -> None: if overwrite or not path.isfile(fpath): if 'quiet' not in d: print(__('Creating file %s.') % fpath) @@ -460,8 +441,7 @@ where "builder" is one of the supported builders, e.g. html, latex or linkcheck. ''')) -def valid_dir(d): - # type: (Dict) -> bool +def valid_dir(d: Dict) -> bool: dir = d['path'] if not path.exists(dir): return True @@ -490,8 +470,7 @@ def valid_dir(d): return True -def get_parser(): - # type: () -> argparse.ArgumentParser +def get_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( usage='%(prog)s [OPTIONS] ', epilog=__("For more information, visit ."), @@ -572,8 +551,7 @@ Makefile to be used with sphinx-build. return parser -def main(argv=sys.argv[1:]): - # type: (List[str]) -> int +def main(argv: List[str] = sys.argv[1:]) -> int: sphinx.locale.setlocale(locale.LC_ALL, '') sphinx.locale.init_console(os.path.join(package_dir, 'locale'), 'sphinx') diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py index 13f48e827..393df0ca9 100644 --- a/sphinx/directives/__init__.py +++ b/sphinx/directives/__init__.py @@ -73,6 +73,7 @@ class ObjectDescription(SphinxDirective): def get_field_type_map(self) -> Dict[str, Tuple[Field, bool]]: if self._doc_field_type_map == {}: + self._doc_field_type_map = {} for field in self.doc_field_types: for name in field.names: self._doc_field_type_map[name] = (field, False) diff --git a/sphinx/domains/__init__.py b/sphinx/domains/__init__.py index 07e05568a..1fe98950b 100644 --- a/sphinx/domains/__init__.py +++ b/sphinx/domains/__init__.py @@ -10,21 +10,22 @@ """ import copy -from typing import NamedTuple +from typing import Any, Callable, Dict, Iterable, List, NamedTuple, Tuple, Type, Union +from docutils import nodes +from docutils.nodes import Element, Node, system_message +from docutils.parsers.rst.states import Inliner + +from sphinx.addnodes import pending_xref from sphinx.errors import SphinxError from sphinx.locale import _ +from sphinx.roles import XRefRole +from sphinx.util.typing import RoleFunction if False: # For type annotation - from typing import Any, Callable, Dict, Iterable, List, Tuple, Type, Union # NOQA - from docutils import nodes # NOQA - from docutils.parsers.rst.states import Inliner # NOQA - from sphinx import addnodes # NOQA - from sphinx.builders import Builder # NOQA - from sphinx.environment import BuildEnvironment # NOQA - from sphinx.roles import XRefRole # NOQA - from sphinx.util.typing import RoleFunction # NOQA + from sphinx.builders import Builder + from sphinx.environment import BuildEnvironment class ObjType: @@ -46,8 +47,7 @@ class ObjType: 'searchprio': 1, } - def __init__(self, lname, *roles, **attrs): - # type: (str, Any, Any) -> None + def __init__(self, lname: str, *roles, **attrs) -> None: self.lname = lname self.roles = roles # type: Tuple self.attrs = self.known_attrs.copy() # type: Dict @@ -82,15 +82,14 @@ class Index: localname = None # type: str shortname = None # type: str - def __init__(self, domain): - # type: (Domain) -> None + def __init__(self, domain: "Domain") -> None: if self.name is None or self.localname is None: raise SphinxError('Index subclass %s has no valid name or localname' % self.__class__.__name__) self.domain = domain - def generate(self, docnames=None): - # type: (Iterable[str]) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool] + def generate(self, docnames: Iterable[str] = None + ) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]: """Get entries for the index. If ``docnames`` is given, restrict to entries referring to these @@ -181,7 +180,7 @@ class Domain: #: role name -> a warning message if reference is missing dangling_warnings = {} # type: Dict[str, str] #: node_class -> (enum_node_type, title_getter) - enumerable_nodes = {} # type: Dict[Type[nodes.Node], Tuple[str, Callable]] + enumerable_nodes = {} # type: Dict[Type[Node], Tuple[str, Callable]] #: data value for a fresh environment initial_data = {} # type: Dict @@ -190,8 +189,7 @@ class Domain: #: data version, bump this when the format of `self.data` changes data_version = 0 - def __init__(self, env): - # type: (BuildEnvironment) -> None + def __init__(self, env: "BuildEnvironment") -> None: self.env = env # type: BuildEnvironment self._role_cache = {} # type: Dict[str, Callable] self._directive_cache = {} # type: Dict[str, Callable] @@ -220,8 +218,7 @@ class Domain: self.objtypes_for_role = self._role2type.get # type: Callable[[str], List[str]] self.role_for_objtype = self._type2role.get # type: Callable[[str], str] - def add_object_type(self, name, objtype): - # type: (str, ObjType) -> None + def add_object_type(self, name: str, objtype: ObjType) -> None: """Add an object type.""" self.object_types[name] = objtype if objtype.roles: @@ -232,8 +229,7 @@ class Domain: for role in objtype.roles: self._role2type.setdefault(role, []).append(name) - def role(self, name): - # type: (str) -> RoleFunction + def role(self, name: str) -> RoleFunction: """Return a role adapter function that always gives the registered role its full name ('domain:name') as the first argument. """ @@ -243,15 +239,15 @@ class Domain: return None fullname = '%s:%s' % (self.name, name) - def role_adapter(typ, rawtext, text, lineno, inliner, options={}, content=[]): - # type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA + def role_adapter(typ: str, rawtext: str, text: str, lineno: int, + inliner: Inliner, options: Dict = {}, content: List[str] = [] + ) -> Tuple[List[Node], List[system_message]]: return self.roles[name](fullname, rawtext, text, lineno, inliner, options, content) self._role_cache[name] = role_adapter return role_adapter - def directive(self, name): - # type: (str) -> Callable + def directive(self, name: str) -> Callable: """Return a directive adapter class that always gives the registered directive its full name ('domain:name') as ``self.name``. """ @@ -263,8 +259,7 @@ class Domain: BaseDirective = self.directives[name] class DirectiveAdapter(BaseDirective): # type: ignore - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: self.name = fullname return super().run() self._directive_cache[name] = DirectiveAdapter @@ -272,13 +267,11 @@ class Domain: # methods that should be overwritten - def clear_doc(self, docname): - # type: (str) -> None + def clear_doc(self, docname: str) -> None: """Remove traces of a document in the domain-specific inventories.""" pass - def merge_domaindata(self, docnames, otherdata): - # type: (List[str], Dict) -> None + def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: """Merge in data regarding *docnames* from a different domaindata inventory (coming from a subprocess in parallel builds). """ @@ -286,26 +279,24 @@ class Domain: 'to be able to do parallel builds!' % self.__class__) - def process_doc(self, env, docname, document): - # type: (BuildEnvironment, str, nodes.document) -> None + def process_doc(self, env: "BuildEnvironment", docname: str, + document: nodes.document) -> None: """Process a document after it is read by the environment.""" pass - def check_consistency(self): - # type: () -> None + def check_consistency(self) -> None: """Do consistency checks (**experimental**).""" pass - def process_field_xref(self, pnode): - # type: (addnodes.pending_xref) -> None + def process_field_xref(self, pnode: pending_xref) -> None: """Process a pending xref created in a doc field. For example, attach information about the current scope. """ pass - def resolve_xref(self, env, fromdocname, builder, - typ, target, node, contnode): - # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA + def resolve_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder", + typ: str, target: str, node: pending_xref, contnode: Element + ) -> Element: """Resolve the pending_xref *node* with the given *typ* and *target*. This method should return a new node, to replace the xref node, @@ -321,8 +312,9 @@ class Domain: """ pass - def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode): - # type: (BuildEnvironment, str, Builder, str, addnodes.pending_xref, nodes.Element) -> List[Tuple[str, nodes.Element]] # NOQA + def resolve_any_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder", + target: str, node: pending_xref, contnode: Element + ) -> List[Tuple[str, Element]]: """Resolve the pending_xref *node* with the given *target*. The reference comes from an "any" or similar role, which means that we @@ -338,8 +330,7 @@ class Domain: """ raise NotImplementedError - def get_objects(self): - # type: () -> Iterable[Tuple[str, str, str, str, str, int]] + def get_objects(self) -> Iterable[Tuple[str, str, str, str, str, int]]: """Return an iterable of "object descriptions". Object descriptions are tuples with six items: @@ -374,20 +365,17 @@ class Domain: """ return [] - def get_type_name(self, type, primary=False): - # type: (ObjType, bool) -> str + def get_type_name(self, type: ObjType, primary: bool = False) -> str: """Return full name for given ObjType.""" if primary: return type.lname return _('%s %s') % (self.label, type.lname) - def get_enumerable_node_type(self, node): - # type: (nodes.Node) -> str + def get_enumerable_node_type(self, node: Node) -> str: """Get type of enumerable nodes (experimental).""" enum_node_type, _ = self.enumerable_nodes.get(node.__class__, (None, None)) return enum_node_type - def get_full_qualified_name(self, node): - # type: (nodes.Element) -> str + def get_full_qualified_name(self, node: Element) -> str: """Return full qualified name for given node.""" return None diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index ec311cfc7..e319771be 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -10,24 +10,27 @@ import re import string +from typing import Any, Dict, Iterator, List, Tuple +from typing import cast from docutils import nodes +from docutils.nodes import Element from sphinx import addnodes +from sphinx.addnodes import pending_xref, desc_signature +from sphinx.application import Sphinx +from sphinx.builders import Builder from sphinx.directives import ObjectDescription from sphinx.domains import Domain, ObjType -from sphinx.locale import _ +from sphinx.environment import BuildEnvironment +from sphinx.locale import _, __ from sphinx.roles import XRefRole +from sphinx.util import logging from sphinx.util.docfields import Field, TypedField from sphinx.util.nodes import make_refnode -if False: - # For type annotation - from typing import Any, Dict, Iterator, List, Tuple # NOQA - from sphinx.application import Sphinx # NOQA - from sphinx.builders import Builder # NOQA - from sphinx.environment import BuildEnvironment # NOQA +logger = logging.getLogger(__name__) # RE to split at word boundaries wsplit_re = re.compile(r'(\W+)') @@ -79,23 +82,20 @@ class CObject(ObjectDescription): 'struct', '_Bool', } - def _parse_type(self, node, ctype): - # type: (nodes.Element, str) -> None + def _parse_type(self, node: Element, ctype: str) -> None: # add cross-ref nodes for all words for part in [_f for _f in wsplit_re.split(ctype) if _f]: tnode = nodes.Text(part, part) if part[0] in string.ascii_letters + '_' and \ part not in self.stopwords: - pnode = addnodes.pending_xref( - '', refdomain='c', reftype='type', reftarget=part, - modname=None, classname=None) + pnode = pending_xref('', refdomain='c', reftype='type', reftarget=part, + modname=None, classname=None) pnode += tnode node += pnode else: node += tnode - def _parse_arglist(self, arglist): - # type: (str) -> Iterator[str] + def _parse_arglist(self, arglist: str) -> Iterator[str]: while True: m = c_funcptr_arg_sig_re.match(arglist) if m: @@ -113,8 +113,7 @@ class CObject(ObjectDescription): yield arglist break - def handle_signature(self, sig, signode): - # type: (str, addnodes.desc_signature) -> str + def handle_signature(self, sig: str, signode: desc_signature) -> str: """Transform a C signature into RST nodes.""" # first try the function pointer signature regex, it's more specific m = c_funcptr_sig_re.match(sig) @@ -183,8 +182,7 @@ class CObject(ObjectDescription): signode += addnodes.desc_addname(const, const) return fullname - def get_index_text(self, name): - # type: (str) -> str + def get_index_text(self, name: str) -> str: if self.objtype == 'function': return _('%s (C function)') % name elif self.objtype == 'member': @@ -198,8 +196,7 @@ class CObject(ObjectDescription): else: return '' - def add_target_and_index(self, name, sig, signode): - # type: (str, str, addnodes.desc_signature) -> None + def add_target_and_index(self, name: str, sig: str, signode: desc_signature) -> None: # for C API items we add a prefix since names are usually not qualified # by a module name and so easily clash with e.g. section titles targetname = 'c.' + name @@ -208,36 +205,30 @@ class CObject(ObjectDescription): signode['ids'].append(targetname) signode['first'] = (not self.names) self.state.document.note_explicit_target(signode) - inv = self.env.domaindata['c']['objects'] - if name in inv: - self.state_machine.reporter.warning( - 'duplicate C object description of %s, ' % name + - 'other instance in ' + self.env.doc2path(inv[name][0]), - line=self.lineno) - inv[name] = (self.env.docname, self.objtype) + + domain = cast(CDomain, self.env.get_domain('c')) + domain.note_object(name, self.objtype) indextext = self.get_index_text(name) if indextext: self.indexnode['entries'].append(('single', indextext, targetname, '', None)) - def before_content(self): - # type: () -> None + def before_content(self) -> None: self.typename_set = False if self.name == 'c:type': if self.names: self.env.ref_context['c:type'] = self.names[0] self.typename_set = True - def after_content(self): - # type: () -> None + def after_content(self) -> None: if self.typename_set: self.env.ref_context.pop('c:type', None) class CXRefRole(XRefRole): - def process_link(self, env, refnode, has_explicit_title, title, target): - # type: (BuildEnvironment, nodes.Element, bool, str, str) -> Tuple[str, str] + def process_link(self, env: BuildEnvironment, refnode: Element, + has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]: if not has_explicit_title: target = target.lstrip('~') # only has a meaning for the title # if the first character is a tilde, don't display the module/class @@ -280,53 +271,61 @@ class CDomain(Domain): 'objects': {}, # fullname -> docname, objtype } # type: Dict[str, Dict[str, Tuple[str, Any]]] - def clear_doc(self, docname): - # type: (str) -> None - for fullname, (fn, _l) in list(self.data['objects'].items()): - if fn == docname: - del self.data['objects'][fullname] + @property + def objects(self) -> Dict[str, Tuple[str, str]]: + return self.data.setdefault('objects', {}) # fullname -> docname, objtype - def merge_domaindata(self, docnames, otherdata): - # type: (List[str], Dict) -> None + def note_object(self, name: str, objtype: str, location: Any = None) -> None: + if name in self.objects: + docname = self.objects[name][0] + logger.warning(__('duplicate C object description of %s, ' + 'other instance in %s, use :noindex: for one of them'), + name, docname, location=location) + self.objects[name] = (self.env.docname, objtype) + + def clear_doc(self, docname: str) -> None: + for fullname, (fn, _l) in list(self.objects.items()): + if fn == docname: + del self.objects[fullname] + + def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: # XXX check duplicates for fullname, (fn, objtype) in otherdata['objects'].items(): if fn in docnames: self.data['objects'][fullname] = (fn, objtype) - def resolve_xref(self, env, fromdocname, builder, - typ, target, node, contnode): - # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA + def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, + typ: str, target: str, node: pending_xref, contnode: Element + ) -> Element: # strip pointer asterisk target = target.rstrip(' *') # becase TypedField can generate xrefs if target in CObject.stopwords: return contnode - if target not in self.data['objects']: + if target not in self.objects: return None - obj = self.data['objects'][target] + obj = self.objects[target] return make_refnode(builder, fromdocname, obj[0], 'c.' + target, contnode, target) - def resolve_any_xref(self, env, fromdocname, builder, target, - node, contnode): - # type: (BuildEnvironment, str, Builder, str, addnodes.pending_xref, nodes.Element) -> List[Tuple[str, nodes.Element]] # NOQA + def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, + target: str, node: pending_xref, contnode: Element + ) -> List[Tuple[str, Element]]: # strip pointer asterisk target = target.rstrip(' *') - if target not in self.data['objects']: + if target not in self.objects: return [] - obj = self.data['objects'][target] + obj = self.objects[target] return [('c:' + self.role_for_objtype(obj[1]), make_refnode(builder, fromdocname, obj[0], 'c.' + target, contnode, target))] - def get_objects(self): - # type: () -> Iterator[Tuple[str, str, str, str, str, int]] - for refname, (docname, type) in list(self.data['objects'].items()): + def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]: + for refname, (docname, type) in list(self.objects.items()): yield (refname, refname, type, docname, 'c.' + refname, 1) -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: Sphinx) -> Dict[str, Any]: app.add_domain(CDomain) return { diff --git a/sphinx/domains/changeset.py b/sphinx/domains/changeset.py index aeea645c1..53cdec41c 100644 --- a/sphinx/domains/changeset.py +++ b/sphinx/domains/changeset.py @@ -9,9 +9,11 @@ """ from collections import namedtuple +from typing import Any, Dict, List from typing import cast from docutils import nodes +from docutils.nodes import Node from sphinx import addnodes from sphinx import locale @@ -23,9 +25,8 @@ from sphinx.util.docutils import SphinxDirective if False: # For type annotation - from typing import Any, Dict, List # NOQA - from sphinx.application import Sphinx # NOQA - from sphinx.environment import BuildEnvironment # NOQA + from sphinx.application import Sphinx + from sphinx.environment import BuildEnvironment versionlabels = { @@ -63,8 +64,7 @@ class VersionChange(SphinxDirective): final_argument_whitespace = True option_spec = {} # type: Dict - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: node = addnodes.versionmodified() node.document = self.state.document self.set_source_info(node) @@ -102,7 +102,7 @@ class VersionChange(SphinxDirective): domain = cast(ChangeSetDomain, self.env.get_domain('changeset')) domain.note_changeset(node) - ret = [node] # type: List[nodes.Node] + ret = [node] # type: List[Node] ret += messages return ret @@ -117,42 +117,40 @@ class ChangeSetDomain(Domain): 'changes': {}, # version -> list of ChangeSet } # type: Dict - def clear_doc(self, docname): - # type: (str) -> None - for version, changes in self.data['changes'].items(): - for changeset in changes[:]: - if changeset.docname == docname: - changes.remove(changeset) + @property + def changesets(self) -> Dict[str, List[ChangeSet]]: + return self.data.setdefault('changes', {}) # version -> list of ChangeSet - def merge_domaindata(self, docnames, otherdata): - # type: (List[str], Dict) -> None - # XXX duplicates? - for version, otherchanges in otherdata['changes'].items(): - changes = self.data['changes'].setdefault(version, []) - for changeset in otherchanges: - if changeset.docname in docnames: - changes.append(changeset) - - def process_doc(self, env, docname, document): - # type: (BuildEnvironment, str, nodes.document) -> None - pass # nothing to do here. All changesets are registered on calling directive. - - def note_changeset(self, node): - # type: (addnodes.versionmodified) -> None + def note_changeset(self, node: addnodes.versionmodified) -> None: version = node['version'] module = self.env.ref_context.get('py:module') objname = self.env.temp_data.get('object') changeset = ChangeSet(node['type'], self.env.docname, node.line, module, objname, node.astext()) - self.data['changes'].setdefault(version, []).append(changeset) + self.changesets.setdefault(version, []).append(changeset) - def get_changesets_for(self, version): - # type: (str) -> List[ChangeSet] - return self.data['changes'].get(version, []) + def clear_doc(self, docname: str) -> None: + for version, changes in self.changesets.items(): + for changeset in changes[:]: + if changeset.docname == docname: + changes.remove(changeset) + + def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: + # XXX duplicates? + for version, otherchanges in otherdata['changes'].items(): + changes = self.changesets.setdefault(version, []) + for changeset in otherchanges: + if changeset.docname in docnames: + changes.append(changeset) + + def process_doc(self, env: "BuildEnvironment", docname: str, document: nodes.document) -> None: # NOQA + pass # nothing to do here. All changesets are registered on calling directive. + + def get_changesets_for(self, version: str) -> List[ChangeSet]: + return self.changesets.get(version, []) -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: "Sphinx") -> Dict[str, Any]: app.add_domain(ChangeSetDomain) app.add_directive('deprecated', VersionChange) app.add_directive('versionadded', VersionChange) diff --git a/sphinx/domains/citation.py b/sphinx/domains/citation.py index 2bb49def9..0b512c365 100644 --- a/sphinx/domains/citation.py +++ b/sphinx/domains/citation.py @@ -8,11 +8,13 @@ :license: BSD, see LICENSE for details. """ +from typing import Any, Dict, List, Set, Tuple from typing import cast from docutils import nodes +from docutils.nodes import Element -from sphinx import addnodes +from sphinx.addnodes import pending_xref from sphinx.domains import Domain from sphinx.locale import __ from sphinx.transforms import SphinxTransform @@ -21,10 +23,10 @@ from sphinx.util.nodes import copy_source_info, make_refnode if False: # For type annotation - from typing import Any, Dict, List, Set, Tuple, Union # NOQA - from sphinx.application import Sphinx # NOQA - from sphinx.builders import Builder # NOQA - from sphinx.environment import BuildEnvironment # NOQA + from sphinx.application import Sphinx + from sphinx.builders import Builder + from sphinx.environment import BuildEnvironment + logger = logging.getLogger(__name__) @@ -40,17 +42,14 @@ class CitationDomain(Domain): } @property - def citations(self): - # type: () -> Dict[str, Tuple[str, str, int]] + def citations(self) -> Dict[str, Tuple[str, str, int]]: return self.data.setdefault('citations', {}) @property - def citation_refs(self): - # type: () -> Dict[str, Set[str]] + def citation_refs(self) -> Dict[str, Set[str]]: return self.data.setdefault('citation_refs', {}) - def clear_doc(self, docname): - # type: (str) -> None + def clear_doc(self, docname: str) -> None: for key, (fn, _l, lineno) in list(self.citations.items()): if fn == docname: del self.citations[key] @@ -60,8 +59,7 @@ class CitationDomain(Domain): elif docname in docnames: docnames.remove(docname) - def merge_domaindata(self, docnames, otherdata): - # type: (List[str], Dict) -> None + def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: # XXX duplicates? for key, data in otherdata['citations'].items(): if data[0] in docnames: @@ -72,8 +70,7 @@ class CitationDomain(Domain): if docname in docnames: citation_refs.add(docname) - def note_citation(self, node): - # type: (nodes.citation) -> None + def note_citation(self, node: nodes.citation) -> None: label = node[0].astext() if label in self.citations: path = self.env.doc2path(self.citations[label][0]) @@ -81,20 +78,19 @@ class CitationDomain(Domain): location=node, type='ref', subtype='citation') self.citations[label] = (node['docname'], node['ids'][0], node.line) - def note_citation_reference(self, node): - # type: (addnodes.pending_xref) -> None + def note_citation_reference(self, node: pending_xref) -> None: docnames = self.citation_refs.setdefault(node['reftarget'], set()) docnames.add(self.env.docname) - def check_consistency(self): - # type: () -> None + def check_consistency(self) -> None: for name, (docname, labelid, lineno) in self.citations.items(): if name not in self.citation_refs: logger.warning(__('Citation [%s] is not referenced.'), name, type='ref', subtype='citation', location=(docname, lineno)) - def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): - # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA + def resolve_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder", + typ: str, target: str, node: pending_xref, contnode: Element + ) -> Element: docname, labelid, lineno = self.citations.get(target, ('', '', 0)) if not docname: return None @@ -102,8 +98,9 @@ class CitationDomain(Domain): return make_refnode(builder, fromdocname, docname, labelid, contnode) - def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode): - # type: (BuildEnvironment, str, Builder, str, addnodes.pending_xref, nodes.Element) -> List[Tuple[str, nodes.Element]] # NOQA + def resolve_any_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder", + target: str, node: pending_xref, contnode: Element + ) -> List[Tuple[str, Element]]: refnode = self.resolve_xref(env, fromdocname, builder, 'ref', target, node, contnode) if refnode is None: return [] @@ -115,8 +112,7 @@ class CitationDefinitionTransform(SphinxTransform): """Mark citation definition labels as not smartquoted.""" default_priority = 619 - def apply(self, **kwargs): - # type: (Any) -> None + def apply(self, **kwargs) -> None: domain = cast(CitationDomain, self.env.get_domain('citation')) for node in self.document.traverse(nodes.citation): # register citation node to domain @@ -135,16 +131,15 @@ class CitationReferenceTransform(SphinxTransform): """ default_priority = 619 - def apply(self, **kwargs): - # type: (Any) -> None + def apply(self, **kwargs) -> None: domain = cast(CitationDomain, self.env.get_domain('citation')) for node in self.document.traverse(nodes.citation_reference): target = node.astext() - ref = addnodes.pending_xref(target, refdomain='citation', reftype='ref', - reftarget=target, refwarn=True, - support_smartquotes=False, - ids=node["ids"], - classes=node.get('classes', [])) + ref = pending_xref(target, refdomain='citation', reftype='ref', + reftarget=target, refwarn=True, + support_smartquotes=False, + ids=node["ids"], + classes=node.get('classes', [])) ref += nodes.inline(target, '[%s]' % target) copy_source_info(node, ref) node.replace_self(ref) @@ -153,8 +148,7 @@ class CitationReferenceTransform(SphinxTransform): domain.note_citation_reference(ref) -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: "Sphinx") -> Dict[str, Any]: app.add_domain(CitationDomain) app.add_transform(CitationDefinitionTransform) app.add_transform(CitationReferenceTransform) diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py index 5d8b4f1e0..121d5582d 100644 --- a/sphinx/domains/javascript.py +++ b/sphinx/domains/javascript.py @@ -8,25 +8,30 @@ :license: BSD, see LICENSE for details. """ +from typing import Any, Dict, Iterator, List, Tuple +from typing import cast + from docutils import nodes +from docutils.nodes import Element, Node from docutils.parsers.rst import directives from sphinx import addnodes +from sphinx.addnodes import desc_signature, pending_xref +from sphinx.application import Sphinx +from sphinx.builders import Builder from sphinx.directives import ObjectDescription from sphinx.domains import Domain, ObjType from sphinx.domains.python import _pseudo_parse_arglist -from sphinx.locale import _ +from sphinx.environment import BuildEnvironment +from sphinx.locale import _, __ from sphinx.roles import XRefRole +from sphinx.util import logging from sphinx.util.docfields import Field, GroupedField, TypedField from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import make_refnode -if False: - # For type annotation - from typing import Any, Dict, Iterator, List, Tuple # NOQA - from sphinx.application import Sphinx # NOQA - from sphinx.builders import Builder # NOQA - from sphinx.environment import BuildEnvironment # NOQA + +logger = logging.getLogger(__name__) class JSObject(ObjectDescription): @@ -44,8 +49,7 @@ class JSObject(ObjectDescription): #: based on directive nesting allow_nesting = False - def handle_signature(self, sig, signode): - # type: (str, addnodes.desc_signature) -> Tuple[str, str] + def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]: """Breaks down construct signatures Parses out prefix and argument list from construct definition. The @@ -98,8 +102,8 @@ class JSObject(ObjectDescription): _pseudo_parse_arglist(signode, arglist) return fullname, prefix - def add_target_and_index(self, name_obj, sig, signode): - # type: (Tuple[str, str], str, addnodes.desc_signature) -> None + def add_target_and_index(self, name_obj: Tuple[str, str], sig: str, + signode: desc_signature) -> None: mod_name = self.env.ref_context.get('js:module') fullname = (mod_name and mod_name + '.' or '') + name_obj[0] if fullname not in self.state.document.ids: @@ -107,14 +111,10 @@ class JSObject(ObjectDescription): signode['ids'].append(fullname.replace('$', '_S_')) signode['first'] = not self.names self.state.document.note_explicit_target(signode) - objects = self.env.domaindata['js']['objects'] - if fullname in objects: - self.state_machine.reporter.warning( - 'duplicate object description of %s, ' % fullname + - 'other instance in ' + - self.env.doc2path(objects[fullname][0]), - line=self.lineno) - objects[fullname] = self.env.docname, self.objtype + + domain = cast(JavaScriptDomain, self.env.get_domain('js')) + domain.note_object(fullname, self.objtype, + location=(self.env.docname, self.lineno)) indextext = self.get_index_text(mod_name, name_obj) if indextext: @@ -122,8 +122,7 @@ class JSObject(ObjectDescription): fullname.replace('$', '_S_'), '', None)) - def get_index_text(self, objectname, name_obj): - # type: (str, Tuple[str, str]) -> str + def get_index_text(self, objectname: str, name_obj: Tuple[str, str]) -> str: name, obj = name_obj if self.objtype == 'function': if not obj: @@ -137,8 +136,7 @@ class JSObject(ObjectDescription): return _('%s (%s attribute)') % (name, obj) return '' - def before_content(self): - # type: () -> None + def before_content(self) -> None: """Handle object nesting before content :py:class:`JSObject` represents JavaScript language constructs. For @@ -174,8 +172,7 @@ class JSObject(ObjectDescription): objects = self.env.ref_context.setdefault('js:objects', []) objects.append(prefix) - def after_content(self): - # type: () -> None + def after_content(self) -> None: """Handle object de-nesting after content If this class is a nestable object, removing the last nested class prefix @@ -246,17 +243,19 @@ class JSModule(SphinxDirective): 'noindex': directives.flag } - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: mod_name = self.arguments[0].strip() self.env.ref_context['js:module'] = mod_name noindex = 'noindex' in self.options - ret = [] # type: List[nodes.Node] + ret = [] # type: List[Node] if not noindex: - self.env.domaindata['js']['modules'][mod_name] = self.env.docname + domain = cast(JavaScriptDomain, self.env.get_domain('js')) + + domain.note_module(mod_name) # Make a duplicate entry in 'objects' to facilitate searching for # the module in JavaScriptDomain.find_obj() - self.env.domaindata['js']['objects'][mod_name] = (self.env.docname, 'module') + domain.note_object(mod_name, 'module', location=(self.env.docname, self.lineno)) + targetnode = nodes.target('', '', ids=['module-' + mod_name], ismod=True) self.state.document.note_explicit_target(targetnode) @@ -269,8 +268,8 @@ class JSModule(SphinxDirective): class JSXRefRole(XRefRole): - def process_link(self, env, refnode, has_explicit_title, title, target): - # type: (BuildEnvironment, nodes.Element, bool, str, str) -> Tuple[str, str] + def process_link(self, env: BuildEnvironment, refnode: Element, + has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]: # basically what sphinx.domains.python.PyXRefRole does refnode['js:object'] = env.ref_context.get('js:object') refnode['js:module'] = env.ref_context.get('js:module') @@ -319,33 +318,48 @@ class JavaScriptDomain(Domain): } initial_data = { 'objects': {}, # fullname -> docname, objtype - 'modules': {}, # mod_name -> docname + 'modules': {}, # modname -> docname } # type: Dict[str, Dict[str, Tuple[str, str]]] - def clear_doc(self, docname): - # type: (str) -> None - for fullname, (pkg_docname, _l) in list(self.data['objects'].items()): - if pkg_docname == docname: - del self.data['objects'][fullname] - for mod_name, pkg_docname in list(self.data['modules'].items()): - if pkg_docname == docname: - del self.data['modules'][mod_name] + @property + def objects(self) -> Dict[str, Tuple[str, str]]: + return self.data.setdefault('objects', {}) # fullname -> docname, objtype - def merge_domaindata(self, docnames, otherdata): - # type: (List[str], Dict) -> None + def note_object(self, fullname: str, objtype: str, location: Any = None) -> None: + if fullname in self.objects: + docname = self.objects[fullname][0] + logger.warning(__('duplicate object description of %s, other instance in %s'), + fullname, docname, location=location) + self.objects[fullname] = (self.env.docname, objtype) + + @property + def modules(self) -> Dict[str, str]: + return self.data.setdefault('modules', {}) # modname -> docname + + def note_module(self, modname: str) -> None: + self.modules[modname] = self.env.docname + + def clear_doc(self, docname: str) -> None: + for fullname, (pkg_docname, _l) in list(self.objects.items()): + if pkg_docname == docname: + del self.objects[fullname] + for modname, pkg_docname in list(self.modules.items()): + if pkg_docname == docname: + del self.modules[modname] + + def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: # XXX check duplicates for fullname, (fn, objtype) in otherdata['objects'].items(): if fn in docnames: - self.data['objects'][fullname] = (fn, objtype) + self.objects[fullname] = (fn, objtype) for mod_name, pkg_docname in otherdata['modules'].items(): if pkg_docname in docnames: - self.data['modules'][mod_name] = pkg_docname + self.modules[mod_name] = pkg_docname - def find_obj(self, env, mod_name, prefix, name, typ, searchorder=0): - # type: (BuildEnvironment, str, str, str, str, int) -> Tuple[str, Tuple[str, str]] + def find_obj(self, env: BuildEnvironment, mod_name: str, prefix: str, name: str, + typ: str, searchorder: int = 0) -> Tuple[str, Tuple[str, str]]: if name[-2:] == '()': name = name[:-2] - objects = self.data['objects'] searches = [] if mod_name and prefix: @@ -361,14 +375,14 @@ class JavaScriptDomain(Domain): newname = None for search_name in searches: - if search_name in objects: + if search_name in self.objects: newname = search_name - return newname, objects.get(newname) + return newname, self.objects.get(newname) - def resolve_xref(self, env, fromdocname, builder, typ, target, node, - contnode): - # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA + def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, + typ: str, target: str, node: pending_xref, contnode: Element + ) -> Element: mod_name = node.get('js:module') prefix = node.get('js:object') searchorder = node.hasattr('refspecific') and 1 or 0 @@ -378,9 +392,9 @@ class JavaScriptDomain(Domain): return make_refnode(builder, fromdocname, obj[0], name.replace('$', '_S_'), contnode, name) - def resolve_any_xref(self, env, fromdocname, builder, target, node, - contnode): - # type: (BuildEnvironment, str, Builder, str, addnodes.pending_xref, nodes.Element) -> List[Tuple[str, nodes.Element]] # NOQA + def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, + target: str, node: pending_xref, contnode: Element + ) -> List[Tuple[str, Element]]: mod_name = node.get('js:module') prefix = node.get('js:object') name, obj = self.find_obj(env, mod_name, prefix, target, None, 1) @@ -390,14 +404,11 @@ class JavaScriptDomain(Domain): make_refnode(builder, fromdocname, obj[0], name.replace('$', '_S_'), contnode, name))] - def get_objects(self): - # type: () -> Iterator[Tuple[str, str, str, str, str, int]] - for refname, (docname, type) in list(self.data['objects'].items()): - yield refname, refname, type, docname, \ - refname.replace('$', '_S_'), 1 + def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]: + for refname, (docname, type) in list(self.objects.items()): + yield refname, refname, type, docname, refname.replace('$', '_S_'), 1 - def get_full_qualified_name(self, node): - # type: (nodes.Element) -> str + def get_full_qualified_name(self, node: Element) -> str: modname = node.get('js:module') prefix = node.get('js:object') target = node.get('reftarget') @@ -407,8 +418,7 @@ class JavaScriptDomain(Domain): return '.'.join(filter(None, [modname, prefix, target])) -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: Sphinx) -> Dict[str, Any]: app.add_domain(JavaScriptDomain) return { diff --git a/sphinx/domains/math.py b/sphinx/domains/math.py index 45e33ea6f..d3cacc5ba 100644 --- a/sphinx/domains/math.py +++ b/sphinx/domains/math.py @@ -9,13 +9,17 @@ """ import warnings +from typing import Any, Dict, Iterable, List, Tuple from docutils import nodes +from docutils.nodes import Element, Node, system_message from docutils.nodes import make_id from sphinx.addnodes import math_block as displaymath +from sphinx.addnodes import pending_xref from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.domains import Domain +from sphinx.environment import BuildEnvironment from sphinx.locale import __ from sphinx.roles import XRefRole from sphinx.util import logging @@ -23,18 +27,16 @@ from sphinx.util.nodes import make_refnode if False: # For type annotation - from typing import Any, Dict, Iterable, List, Tuple # NOQA - from sphinx import addnodes # NOQA - from sphinx.application import Sphinx # NOQA - from sphinx.builders import Builder # NOQA - from sphinx.environment import BuildEnvironment # NOQA + from sphinx.application import Sphinx + from sphinx.builders import Builder + logger = logging.getLogger(__name__) class MathReferenceRole(XRefRole): - def result_nodes(self, document, env, node, is_ref): - # type: (nodes.document, BuildEnvironment, nodes.Element, bool) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA + def result_nodes(self, document: nodes.document, env: BuildEnvironment, node: Element, + is_ref: bool) -> Tuple[List[Node], List[system_message]]: node['refdomain'] = 'math' return [node], [] @@ -60,12 +62,10 @@ class MathDomain(Domain): } @property - def equations(self): - # type: () -> Dict[str, Tuple[str, int]] + def equations(self) -> Dict[str, Tuple[str, int]]: return self.data.setdefault('objects', {}) # labelid -> (docname, eqno) - def note_equation(self, docname, labelid, location=None): - # type: (str, str, Any) -> None + def note_equation(self, docname: str, labelid: str, location: Any = None) -> None: if labelid in self.equations: other = self.equations[labelid][0] logger.warning(__('duplicate label of equation %s, other instance in %s') % @@ -73,31 +73,27 @@ class MathDomain(Domain): self.equations[labelid] = (docname, self.env.new_serialno('eqno') + 1) - def get_equation_number_for(self, labelid): - # type: (str) -> int + def get_equation_number_for(self, labelid: str) -> int: if labelid in self.equations: return self.equations[labelid][1] else: return None - def process_doc(self, env, docname, document): - # type: (BuildEnvironment, str, nodes.document) -> None - def math_node(node): - # type: (nodes.Node) -> bool + def process_doc(self, env: BuildEnvironment, docname: str, + document: nodes.document) -> None: + def math_node(node: Node) -> bool: return isinstance(node, (nodes.math, nodes.math_block)) self.data['has_equations'][docname] = any(document.traverse(math_node)) - def clear_doc(self, docname): - # type: (str) -> None + def clear_doc(self, docname: str) -> None: for equation_id, (doc, eqno) in list(self.equations.items()): if doc == docname: del self.equations[equation_id] self.data['has_equations'].pop(docname, None) - def merge_domaindata(self, docnames, otherdata): - # type: (Iterable[str], Dict) -> None + def merge_domaindata(self, docnames: Iterable[str], otherdata: Dict) -> None: for labelid, (doc, eqno) in otherdata['objects'].items(): if doc in docnames: self.equations[labelid] = (doc, eqno) @@ -105,8 +101,9 @@ class MathDomain(Domain): for docname in docnames: self.data['has_equations'][docname] = otherdata['has_equations'][docname] - def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): - # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA + def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: "Builder", + typ: str, target: str, node: pending_xref, contnode: Element + ) -> Element: assert typ in ('eq', 'numref') docname, number = self.equations.get(target, (None, None)) if docname: @@ -133,20 +130,19 @@ class MathDomain(Domain): else: return None - def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode): - # type: (BuildEnvironment, str, Builder, str, addnodes.pending_xref, nodes.Element) -> List[Tuple[str, nodes.Element]] # NOQA + def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: "Builder", + target: str, node: pending_xref, contnode: Element + ) -> List[Tuple[str, Element]]: refnode = self.resolve_xref(env, fromdocname, builder, 'eq', target, node, contnode) if refnode is None: return [] else: return [('eq', refnode)] - def get_objects(self): - # type: () -> List + def get_objects(self) -> List: return [] - def add_equation(self, env, docname, labelid): - # type: (BuildEnvironment, str, str) -> int + def add_equation(self, env: BuildEnvironment, docname: str, labelid: str) -> int: warnings.warn('MathDomain.add_equation() is deprecated.', RemovedInSphinx40Warning) if labelid in self.equations: @@ -158,20 +154,17 @@ class MathDomain(Domain): self.equations[labelid] = (docname, eqno) return eqno - def get_next_equation_number(self, docname): - # type: (str) -> int + def get_next_equation_number(self, docname: str) -> int: warnings.warn('MathDomain.get_next_equation_number() is deprecated.', RemovedInSphinx40Warning) targets = [eq for eq in self.equations.values() if eq[0] == docname] return len(targets) + 1 - def has_equations(self): - # type: () -> bool + def has_equations(self) -> bool: return any(self.data['has_equations'].values()) -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: "Sphinx") -> Dict[str, Any]: app.add_domain(MathDomain) app.add_role('eq', MathReferenceRole(warn_dangling=True)) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 2a9a120ef..b70481198 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -10,31 +10,31 @@ import re import warnings +from typing import Any, Dict, Iterable, Iterator, List, Tuple, Type from typing import cast from docutils import nodes +from docutils.nodes import Element, Node from docutils.parsers.rst import directives from sphinx import addnodes, locale +from sphinx.addnodes import pending_xref, desc_signature +from sphinx.application import Sphinx +from sphinx.builders import Builder from sphinx.deprecation import ( DeprecatedDict, RemovedInSphinx30Warning, RemovedInSphinx40Warning ) from sphinx.directives import ObjectDescription from sphinx.domains import Domain, ObjType, Index, IndexEntry +from sphinx.environment import BuildEnvironment from sphinx.locale import _, __ from sphinx.roles import XRefRole from sphinx.util import logging from sphinx.util.docfields import Field, GroupedField, TypedField from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import make_refnode +from sphinx.util.typing import TextlikeNode -if False: - # For type annotation - from typing import Any, Dict, Iterable, Iterator, List, Tuple, Type # NOQA - from sphinx.application import Sphinx # NOQA - from sphinx.builders import Builder # NOQA - from sphinx.environment import BuildEnvironment # NOQA - from sphinx.util.typing import TextlikeNode # NOQA logger = logging.getLogger(__name__) @@ -67,8 +67,7 @@ locale.pairindextypes = DeprecatedDict( ) -def _pseudo_parse_arglist(signode, arglist): - # type: (addnodes.desc_signature, str) -> None +def _pseudo_parse_arglist(signode: desc_signature, arglist: str) -> None: """"Parse" a list of arguments separated by commas. Arguments can have "optional" annotations given by enclosing them in @@ -76,7 +75,7 @@ def _pseudo_parse_arglist(signode, arglist): string literal (e.g. default argument value). """ paramlist = addnodes.desc_parameterlist() - stack = [paramlist] # type: List[nodes.Element] + stack = [paramlist] # type: List[Element] try: for argument in arglist.split(','): argument = argument.strip() @@ -119,15 +118,9 @@ def _pseudo_parse_arglist(signode, arglist): # This override allows our inline type specifiers to behave like :class: link # when it comes to handling "." and "~" prefixes. class PyXrefMixin: - def make_xref(self, - rolename, # type: str - domain, # type: str - target, # type: str - innernode=nodes.emphasis, # type: Type[TextlikeNode] - contnode=None, # type: nodes.Node - env=None, # type: BuildEnvironment - ): - # type: (...) -> nodes.Node + def make_xref(self, rolename: str, domain: str, target: str, + innernode: Type[TextlikeNode] = nodes.emphasis, + contnode: Node = None, env: BuildEnvironment = None) -> Node: result = super().make_xref(rolename, domain, target, # type: ignore innernode, contnode, env) result['refspecific'] = True @@ -142,15 +135,9 @@ class PyXrefMixin: break return result - def make_xrefs(self, - rolename, # type: str - domain, # type: str - target, # type: str - innernode=nodes.emphasis, # type: Type[TextlikeNode] - contnode=None, # type: nodes.Node - env=None, # type: BuildEnvironment - ): - # type: (...) -> List[nodes.Node] + def make_xrefs(self, rolename: str, domain: str, target: str, + innernode: Type[TextlikeNode] = nodes.emphasis, + contnode: Node = None, env: BuildEnvironment = None) -> List[Node]: delims = r'(\s*[\[\]\(\),](?:\s*or\s)?\s*|\s+or\s+)' delims_re = re.compile(delims) sub_targets = re.split(delims, target) @@ -172,9 +159,9 @@ class PyXrefMixin: class PyField(PyXrefMixin, Field): - def make_xref(self, rolename, domain, target, - innernode=nodes.emphasis, contnode=None, env=None): - # type: (str, str, str, Type[TextlikeNode], nodes.Node, BuildEnvironment) -> nodes.Node # NOQA + def make_xref(self, rolename: str, domain: str, target: str, + innernode: Type[TextlikeNode] = nodes.emphasis, + contnode: Node = None, env: BuildEnvironment = None) -> Node: if rolename == 'class' and target == 'None': # None is not a type, so use obj role instead. rolename = 'obj' @@ -187,9 +174,9 @@ class PyGroupedField(PyXrefMixin, GroupedField): class PyTypedField(PyXrefMixin, TypedField): - def make_xref(self, rolename, domain, target, - innernode=nodes.emphasis, contnode=None, env=None): - # type: (str, str, str, Type[TextlikeNode], nodes.Node, BuildEnvironment) -> nodes.Node # NOQA + def make_xref(self, rolename: str, domain: str, target: str, + innernode: Type[TextlikeNode] = nodes.emphasis, + contnode: Node = None, env: BuildEnvironment = None) -> Node: if rolename == 'class' and target == 'None': # None is not a type, so use obj role instead. rolename = 'obj' @@ -231,22 +218,19 @@ class PyObject(ObjectDescription): allow_nesting = False - def get_signature_prefix(self, sig): - # type: (str) -> str + def get_signature_prefix(self, sig: str) -> str: """May return a prefix to put before the object name in the signature. """ return '' - def needs_arglist(self): - # type: () -> bool + def needs_arglist(self) -> bool: """May return true if an empty argument list is to be generated even if the document contains none. """ return False - def handle_signature(self, sig, signode): - # type: (str, addnodes.desc_signature) -> Tuple[str, str] + def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]: """Transform a Python signature into RST nodes. Return (fully qualified name of the thing, classname if any). @@ -320,13 +304,12 @@ class PyObject(ObjectDescription): return fullname, prefix - def get_index_text(self, modname, name): - # type: (str, Tuple[str, str]) -> str + def get_index_text(self, modname: str, name: Tuple[str, str]) -> str: """Return the text for the index entry of the object.""" raise NotImplementedError('must be implemented in subclasses') - def add_target_and_index(self, name_cls, sig, signode): - # type: (Tuple[str, str], str, addnodes.desc_signature) -> None + def add_target_and_index(self, name_cls: Tuple[str, str], sig: str, + signode: desc_signature) -> None: modname = self.options.get('module', self.env.ref_context.get('py:module')) fullname = (modname and modname + '.' or '') + name_cls[0] # note target @@ -345,8 +328,7 @@ class PyObject(ObjectDescription): self.indexnode['entries'].append(('single', indextext, fullname, '', None)) - def before_content(self): - # type: () -> None + def before_content(self) -> None: """Handle object nesting before content :py:class:`PyObject` represents Python language constructs. For @@ -379,8 +361,7 @@ class PyObject(ObjectDescription): modules.append(self.env.ref_context.get('py:module')) self.env.ref_context['py:module'] = self.options['module'] - def after_content(self): - # type: () -> None + def after_content(self) -> None: """Handle object de-nesting after content If this class is a nestable object, removing the last nested class prefix @@ -411,19 +392,16 @@ class PyModulelevel(PyObject): Description of an object on module level (functions, data). """ - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: warnings.warn('PyClassmember is deprecated.', RemovedInSphinx40Warning) return super().run() - def needs_arglist(self): - # type: () -> bool + def needs_arglist(self) -> bool: return self.objtype == 'function' - def get_index_text(self, modname, name_cls): - # type: (str, Tuple[str, str]) -> str + def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: if self.objtype == 'function': if not modname: return _('%s() (built-in function)') % name_cls[0] @@ -444,19 +422,16 @@ class PyFunction(PyObject): 'async': directives.flag, }) - def get_signature_prefix(self, sig): - # type: (str) -> str + def get_signature_prefix(self, sig: str) -> str: if 'async' in self.options: return 'async ' else: return '' - def needs_arglist(self): - # type: () -> bool + def needs_arglist(self) -> bool: return True - def get_index_text(self, modname, name_cls): - # type: (str, Tuple[str, str]) -> str + def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: name, cls = name_cls if modname: return _('%s() (in module %s)') % (name, modname) @@ -467,8 +442,7 @@ class PyFunction(PyObject): class PyVariable(PyObject): """Description of a variable.""" - def get_index_text(self, modname, name_cls): - # type: (str, Tuple[str, str]) -> str + def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: name, cls = name_cls if modname: return _('%s (in module %s)') % (name, modname) @@ -483,12 +457,10 @@ class PyClasslike(PyObject): allow_nesting = True - def get_signature_prefix(self, sig): - # type: (str) -> str + def get_signature_prefix(self, sig: str) -> str: return self.objtype + ' ' - def get_index_text(self, modname, name_cls): - # type: (str, Tuple[str, str]) -> str + def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: if self.objtype == 'class': if not modname: return _('%s (built-in class)') % name_cls[0] @@ -504,27 +476,23 @@ class PyClassmember(PyObject): Description of a class member (methods, attributes). """ - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: warnings.warn('PyClassmember is deprecated.', RemovedInSphinx40Warning) return super().run() - def needs_arglist(self): - # type: () -> bool + def needs_arglist(self) -> bool: return self.objtype.endswith('method') - def get_signature_prefix(self, sig): - # type: (str) -> str + def get_signature_prefix(self, sig: str) -> str: if self.objtype == 'staticmethod': return 'static ' elif self.objtype == 'classmethod': return 'classmethod ' return '' - def get_index_text(self, modname, name_cls): - # type: (str, Tuple[str, str]) -> str + def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: name, cls = name_cls add_modules = self.env.config.add_module_names if self.objtype == 'method': @@ -593,15 +561,13 @@ class PyMethod(PyObject): 'staticmethod': directives.flag, }) - def needs_arglist(self): - # type: () -> bool + def needs_arglist(self) -> bool: if 'property' in self.options: return False else: return True - def get_signature_prefix(self, sig): - # type: (str) -> str + def get_signature_prefix(self, sig: str) -> str: prefix = [] if 'abstractmethod' in self.options: prefix.append('abstract') @@ -619,8 +585,7 @@ class PyMethod(PyObject): else: return '' - def get_index_text(self, modname, name_cls): - # type: (str, Tuple[str, str]) -> str + def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: name, cls = name_cls try: clsname, methname = name.rsplit('.', 1) @@ -647,8 +612,7 @@ class PyClassMethod(PyMethod): option_spec = PyObject.option_spec.copy() - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: self.name = 'py:method' self.options['classmethod'] = True @@ -660,8 +624,7 @@ class PyStaticMethod(PyMethod): option_spec = PyObject.option_spec.copy() - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: self.name = 'py:method' self.options['staticmethod'] = True @@ -671,8 +634,7 @@ class PyStaticMethod(PyMethod): class PyAttribute(PyObject): """Description of an attribute.""" - def get_index_text(self, modname, name_cls): - # type: (str, Tuple[str, str]) -> str + def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: name, cls = name_cls try: clsname, attrname = name.rsplit('.', 1) @@ -691,14 +653,12 @@ class PyDecoratorMixin: """ Mixin for decorator directives. """ - def handle_signature(self, sig, signode): - # type: (str, addnodes.desc_signature) -> Tuple[str, str] + def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]: ret = super().handle_signature(sig, signode) # type: ignore signode.insert(0, addnodes.desc_addname('@', '@')) return ret - def needs_arglist(self): - # type: () -> bool + def needs_arglist(self) -> bool: return False @@ -706,8 +666,7 @@ class PyDecoratorFunction(PyDecoratorMixin, PyModulelevel): """ Directive to mark functions meant to be used as decorators. """ - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: # a decorator function is a function after all self.name = 'py:function' return super().run() @@ -717,8 +676,7 @@ class PyDecoratorMethod(PyDecoratorMixin, PyClassmember): """ Directive to mark methods meant to be used as decorators. """ - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: self.name = 'py:method' return super().run() @@ -739,14 +697,13 @@ class PyModule(SphinxDirective): 'deprecated': directives.flag, } - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: domain = cast(PythonDomain, self.env.get_domain('py')) modname = self.arguments[0].strip() noindex = 'noindex' in self.options self.env.ref_context['py:module'] = modname - ret = [] # type: List[nodes.Node] + ret = [] # type: List[Node] if not noindex: # note module to the domain domain.note_module(modname, @@ -780,8 +737,7 @@ class PyCurrentModule(SphinxDirective): final_argument_whitespace = False option_spec = {} # type: Dict - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: modname = self.arguments[0].strip() if modname == 'None': self.env.ref_context.pop('py:module', None) @@ -791,8 +747,8 @@ class PyCurrentModule(SphinxDirective): class PyXRefRole(XRefRole): - def process_link(self, env, refnode, has_explicit_title, title, target): - # type: (BuildEnvironment, nodes.Element, bool, str, str) -> Tuple[str, str] + def process_link(self, env: BuildEnvironment, refnode: Element, + has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]: refnode['py:module'] = env.ref_context.get('py:module') refnode['py:class'] = env.ref_context.get('py:class') if not has_explicit_title: @@ -822,8 +778,8 @@ class PythonModuleIndex(Index): localname = _('Python Module Index') shortname = _('modules') - def generate(self, docnames=None): - # type: (Iterable[str]) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool] + def generate(self, docnames: Iterable[str] = None + ) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]: content = {} # type: Dict[str, List[IndexEntry]] # list of prefixes to ignore ignores = None # type: List[str] @@ -937,12 +893,10 @@ class PythonDomain(Domain): ] @property - def objects(self): - # type: () -> Dict[str, Tuple[str, str]] + def objects(self) -> Dict[str, Tuple[str, str]]: return self.data.setdefault('objects', {}) # fullname -> docname, objtype - def note_object(self, name, objtype, location=None): - # type: (str, str, Any) -> None + def note_object(self, name: str, objtype: str, location: Any = None) -> None: """Note a python object for cross reference. .. versionadded:: 2.1 @@ -955,20 +909,17 @@ class PythonDomain(Domain): self.objects[name] = (self.env.docname, objtype) @property - def modules(self): - # type: () -> Dict[str, Tuple[str, str, str, bool]] + def modules(self) -> Dict[str, Tuple[str, str, str, bool]]: return self.data.setdefault('modules', {}) # modname -> docname, synopsis, platform, deprecated # NOQA - def note_module(self, name, synopsis, platform, deprecated): - # type: (str, str, str, bool) -> None + def note_module(self, name: str, synopsis: str, platform: str, deprecated: bool) -> None: """Note a python module for cross reference. .. versionadded:: 2.1 """ self.modules[name] = (self.env.docname, synopsis, platform, deprecated) - def clear_doc(self, docname): - # type: (str) -> None + def clear_doc(self, docname: str) -> None: for fullname, (fn, _l) in list(self.objects.items()): if fn == docname: del self.objects[fullname] @@ -976,8 +927,7 @@ class PythonDomain(Domain): if fn == docname: del self.modules[modname] - def merge_domaindata(self, docnames, otherdata): - # type: (List[str], Dict) -> None + def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: # XXX check duplicates? for fullname, (fn, objtype) in otherdata['objects'].items(): if fn in docnames: @@ -986,8 +936,8 @@ class PythonDomain(Domain): if data[0] in docnames: self.modules[modname] = data - def find_obj(self, env, modname, classname, name, type, searchmode=0): - # type: (BuildEnvironment, str, str, str, str, int) -> List[Tuple[str, Any]] + def find_obj(self, env: BuildEnvironment, modname: str, classname: str, + name: str, type: str, searchmode: int = 0) -> List[Tuple[str, Any]]: """Find a Python object for "name", perhaps using the given module and/or classname. Returns a list of (name, object entry) tuples. """ @@ -1049,9 +999,9 @@ class PythonDomain(Domain): matches.append((newname, self.objects[newname])) return matches - def resolve_xref(self, env, fromdocname, builder, - type, target, node, contnode): - # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA + def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, + type: str, target: str, node: pending_xref, contnode: Element + ) -> Element: modname = node.get('py:module') clsname = node.get('py:class') searchmode = node.hasattr('refspecific') and 1 or 0 @@ -1070,12 +1020,12 @@ class PythonDomain(Domain): else: return make_refnode(builder, fromdocname, obj[0], name, contnode, name) - def resolve_any_xref(self, env, fromdocname, builder, target, - node, contnode): - # type: (BuildEnvironment, str, Builder, str, addnodes.pending_xref, nodes.Element) -> List[Tuple[str, nodes.Element]] # NOQA + def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, + target: str, node: pending_xref, contnode: Element + ) -> List[Tuple[str, Element]]: modname = node.get('py:module') clsname = node.get('py:class') - results = [] # type: List[Tuple[str, nodes.Element]] + results = [] # type: List[Tuple[str, Element]] # always search in "refspecific" mode with the :any: role matches = self.find_obj(env, modname, clsname, target, None, 1) @@ -1090,8 +1040,8 @@ class PythonDomain(Domain): contnode, name))) return results - def _make_module_refnode(self, builder, fromdocname, name, contnode): - # type: (Builder, str, str, nodes.Node) -> nodes.Element + def _make_module_refnode(self, builder: Builder, fromdocname: str, name: str, + contnode: Node) -> Element: # get additional info for modules docname, synopsis, platform, deprecated = self.modules[name] title = name @@ -1104,16 +1054,14 @@ class PythonDomain(Domain): return make_refnode(builder, fromdocname, docname, 'module-' + name, contnode, title) - def get_objects(self): - # type: () -> Iterator[Tuple[str, str, str, str, str, int]] + def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]: for modname, info in self.modules.items(): yield (modname, modname, 'module', info[0], 'module-' + modname, 0) for refname, (docname, type) in self.objects.items(): if type != 'module': # modules are already handled yield (refname, refname, type, docname, refname, 1) - def get_full_qualified_name(self, node): - # type: (nodes.Element) -> str + def get_full_qualified_name(self, node: Element) -> str: modname = node.get('py:module') clsname = node.get('py:class') target = node.get('reftarget') @@ -1123,8 +1071,7 @@ class PythonDomain(Domain): return '.'.join(filter(None, [modname, clsname, target])) -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: Sphinx) -> Dict[str, Any]: app.add_domain(PythonDomain) return { diff --git a/sphinx/domains/rst.py b/sphinx/domains/rst.py index 349043294..85f10e15c 100644 --- a/sphinx/domains/rst.py +++ b/sphinx/domains/rst.py @@ -9,26 +9,24 @@ """ import re +from typing import Any, Dict, Iterator, List, Tuple from typing import cast +from docutils.nodes import Element from docutils.parsers.rst import directives from sphinx import addnodes +from sphinx.addnodes import desc_signature, pending_xref +from sphinx.application import Sphinx +from sphinx.builders import Builder from sphinx.directives import ObjectDescription from sphinx.domains import Domain, ObjType +from sphinx.environment import BuildEnvironment from sphinx.locale import _, __ from sphinx.roles import XRefRole from sphinx.util import logging from sphinx.util.nodes import make_refnode -if False: - # For type annotation - from typing import Any, Dict, Iterator, List, Tuple # NOQA - from docutils import nodes # NOQA - from sphinx.application import Sphinx # NOQA - from sphinx.builders import Builder # NOQA - from sphinx.environment import BuildEnvironment # NOQA - logger = logging.getLogger(__name__) @@ -40,8 +38,7 @@ class ReSTMarkup(ObjectDescription): Description of generic reST markup. """ - def add_target_and_index(self, name, sig, signode): - # type: (str, str, addnodes.desc_signature) -> None + def add_target_and_index(self, name: str, sig: str, signode: desc_signature) -> None: targetname = self.objtype + '-' + name if targetname not in self.state.document.ids: signode['names'].append(targetname) @@ -57,13 +54,11 @@ class ReSTMarkup(ObjectDescription): self.indexnode['entries'].append(('single', indextext, targetname, '', None)) - def get_index_text(self, objectname, name): - # type: (str, str) -> str + def get_index_text(self, objectname: str, name: str) -> str: return '' -def parse_directive(d): - # type: (str) -> Tuple[str, str] +def parse_directive(d: str) -> Tuple[str, str]: """Parse a directive signature. Returns (directive, arguments) string tuple. If no arguments are given, @@ -87,8 +82,7 @@ class ReSTDirective(ReSTMarkup): """ Description of a reST directive. """ - def handle_signature(self, sig, signode): - # type: (str, addnodes.desc_signature) -> str + def handle_signature(self, sig: str, signode: desc_signature) -> str: name, args = parse_directive(sig) desc_name = '.. %s::' % name signode += addnodes.desc_name(desc_name, desc_name) @@ -96,18 +90,15 @@ class ReSTDirective(ReSTMarkup): signode += addnodes.desc_addname(args, args) return name - def get_index_text(self, objectname, name): - # type: (str, str) -> str + def get_index_text(self, objectname: str, name: str) -> str: return _('%s (directive)') % name - def before_content(self): - # type: () -> None + def before_content(self) -> None: if self.names: directives = self.env.ref_context.setdefault('rst:directives', []) directives.append(self.names[0]) - def after_content(self): - # type: () -> None + def after_content(self) -> None: directives = self.env.ref_context.setdefault('rst:directives', []) if directives: directives.pop() @@ -122,8 +113,7 @@ class ReSTDirectiveOption(ReSTMarkup): 'type': directives.unchanged, }) - def handle_signature(self, sig, signode): - # type: (str, addnodes.desc_signature) -> str + def handle_signature(self, sig: str, signode: desc_signature) -> str: try: name, argument = re.split(r'\s*:\s+', sig.strip(), 1) except ValueError: @@ -137,8 +127,7 @@ class ReSTDirectiveOption(ReSTMarkup): signode += addnodes.desc_annotation(text, text) return name - def add_target_and_index(self, name, sig, signode): - # type: (str, str, addnodes.desc_signature) -> None + def add_target_and_index(self, name: str, sig: str, signode: desc_signature) -> None: directive_name = self.current_directive targetname = '-'.join([self.objtype, self.current_directive, name]) if targetname not in self.state.document.ids: @@ -162,8 +151,7 @@ class ReSTDirectiveOption(ReSTMarkup): self.indexnode['entries'].append(('single', text, targetname, '', key)) @property - def current_directive(self): - # type: () -> str + def current_directive(self) -> str: directives = self.env.ref_context.get('rst:directives') if directives: return directives[-1] @@ -175,13 +163,11 @@ class ReSTRole(ReSTMarkup): """ Description of a reST role. """ - def handle_signature(self, sig, signode): - # type: (str, addnodes.desc_signature) -> str + def handle_signature(self, sig: str, signode: desc_signature) -> str: signode += addnodes.desc_name(':%s:' % sig, ':%s:' % sig) return sig - def get_index_text(self, objectname, name): - # type: (str, str) -> str + def get_index_text(self, objectname: str, name: str) -> str: return _('%s (role)') % name @@ -209,12 +195,10 @@ class ReSTDomain(Domain): } # type: Dict[str, Dict[Tuple[str, str], str]] @property - def objects(self): - # type: () -> Dict[Tuple[str, str], str] + def objects(self) -> Dict[Tuple[str, str], str]: return self.data.setdefault('objects', {}) # (objtype, fullname) -> docname - def note_object(self, objtype, name, location=None): - # type: (str, str, Any) -> None + def note_object(self, objtype: str, name: str, location: Any = None) -> None: if (objtype, name) in self.objects: docname = self.objects[objtype, name] logger.warning(__('duplicate description of %s %s, other instance in %s') % @@ -222,21 +206,20 @@ class ReSTDomain(Domain): self.objects[objtype, name] = self.env.docname - def clear_doc(self, docname): - # type: (str) -> None + def clear_doc(self, docname: str) -> None: for (typ, name), doc in list(self.objects.items()): if doc == docname: del self.objects[typ, name] - def merge_domaindata(self, docnames, otherdata): - # type: (List[str], Dict) -> None + def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: # XXX check duplicates for (typ, name), doc in otherdata['objects'].items(): if doc in docnames: self.objects[typ, name] = doc - def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): - # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA + def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, + typ: str, target: str, node: pending_xref, contnode: Element + ) -> Element: objtypes = self.objtypes_for_role(typ) for objtype in objtypes: todocname = self.objects.get((objtype, target)) @@ -246,9 +229,10 @@ class ReSTDomain(Domain): contnode, target + ' ' + objtype) return None - def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode): - # type: (BuildEnvironment, str, Builder, str, addnodes.pending_xref, nodes.Element) -> List[Tuple[str, nodes.Element]] # NOQA - results = [] # type: List[Tuple[str, nodes.Element]] + def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, + target: str, node: pending_xref, contnode: Element + ) -> List[Tuple[str, Element]]: + results = [] # type: List[Tuple[str, Element]] for objtype in self.object_types: todocname = self.objects.get((objtype, target)) if todocname: @@ -258,14 +242,12 @@ class ReSTDomain(Domain): contnode, target + ' ' + objtype))) return results - def get_objects(self): - # type: () -> Iterator[Tuple[str, str, str, str, str, int]] + def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]: for (typ, name), docname in self.data['objects'].items(): yield name, name, typ, docname, typ + '-' + name, 1 -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: Sphinx) -> Dict[str, Any]: app.add_domain(ReSTDomain) return { diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 5c35083fd..66171646a 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -12,13 +12,16 @@ import re import unicodedata import warnings from copy import copy +from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Type, Union from typing import cast from docutils import nodes -from docutils.parsers.rst import directives +from docutils.nodes import Element, Node, system_message +from docutils.parsers.rst import Directive, directives from docutils.statemachine import StringList from sphinx import addnodes +from sphinx.addnodes import desc_signature, pending_xref from sphinx.deprecation import RemovedInSphinx30Warning, RemovedInSphinx40Warning from sphinx.directives import ObjectDescription from sphinx.domains import Domain, ObjType @@ -28,15 +31,13 @@ from sphinx.roles import XRefRole from sphinx.util import ws_re, logging, docname_join from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import clean_astext, make_refnode +from sphinx.util.typing import RoleFunction if False: # For type annotation - from typing import Any, Callable, Dict, Iterable, Iterator, List, Tuple, Type, Union # NOQA - from docutils.parsers.rst import Directive # NOQA - from sphinx.application import Sphinx # NOQA - from sphinx.builders import Builder # NOQA - from sphinx.environment import BuildEnvironment # NOQA - from sphinx.util.typing import RoleFunction # NOQA + from sphinx.application import Sphinx + from sphinx.builders import Builder + from sphinx.environment import BuildEnvironment logger = logging.getLogger(__name__) @@ -52,10 +53,9 @@ class GenericObject(ObjectDescription): A generic x-ref directive registered with Sphinx.add_object_type(). """ indextemplate = '' - parse_node = None # type: Callable[[GenericObject, BuildEnvironment, str, addnodes.desc_signature], str] # NOQA + parse_node = None # type: Callable[[GenericObject, BuildEnvironment, str, desc_signature], str] # NOQA - def handle_signature(self, sig, signode): - # type: (str, addnodes.desc_signature) -> str + def handle_signature(self, sig: str, signode: desc_signature) -> str: if self.parse_node: name = self.parse_node(self.env, sig, signode) else: @@ -65,8 +65,7 @@ class GenericObject(ObjectDescription): name = ws_re.sub('', sig) return name - def add_target_and_index(self, name, sig, signode): - # type: (str, str, addnodes.desc_signature) -> None + def add_target_and_index(self, name: str, sig: str, signode: desc_signature) -> None: targetname = '%s-%s' % (self.objtype, name) signode['ids'].append(targetname) self.state.document.note_explicit_target(signode) @@ -94,8 +93,8 @@ class EnvVarXRefRole(XRefRole): Cross-referencing role for environment variables (adds an index entry). """ - def result_nodes(self, document, env, node, is_ref): - # type: (nodes.document, BuildEnvironment, nodes.Element, bool) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA + def result_nodes(self, document: nodes.document, env: "BuildEnvironment", node: Element, + is_ref: bool) -> Tuple[List[Node], List[system_message]]: if not is_ref: return [node], [] varname = node['reftarget'] @@ -122,8 +121,7 @@ class Target(SphinxDirective): final_argument_whitespace = True option_spec = {} # type: Dict - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: # normalize whitespace in fullname like XRefRole does fullname = ws_re.sub(' ', self.arguments[0].strip()) targetname = '%s-%s' % (self.name, fullname) @@ -155,8 +153,7 @@ class Cmdoption(ObjectDescription): Description of a command-line option (.. option). """ - def handle_signature(self, sig, signode): - # type: (str, addnodes.desc_signature) -> str + def handle_signature(self, sig: str, signode: desc_signature) -> str: """Transform an option description into RST nodes.""" count = 0 firstname = '' @@ -184,8 +181,7 @@ class Cmdoption(ObjectDescription): raise ValueError return firstname - def add_target_and_index(self, firstname, sig, signode): - # type: (str, str, addnodes.desc_signature) -> None + def add_target_and_index(self, firstname: str, sig: str, signode: desc_signature) -> None: currprogram = self.env.ref_context.get('std:program') for optname in signode.get('allnames', []): targetname = optname.replace('/', '-') @@ -223,8 +219,7 @@ class Program(SphinxDirective): final_argument_whitespace = True option_spec = {} # type: Dict - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: program = ws_re.sub('-', self.arguments[0].strip()) if program == 'None': self.env.ref_context.pop('std:program', None) @@ -234,21 +229,20 @@ class Program(SphinxDirective): class OptionXRefRole(XRefRole): - def process_link(self, env, refnode, has_explicit_title, title, target): - # type: (BuildEnvironment, nodes.Element, bool, str, str) -> Tuple[str, str] + def process_link(self, env: "BuildEnvironment", refnode: Element, has_explicit_title: bool, + title: str, target: str) -> Tuple[str, str]: refnode['std:program'] = env.ref_context.get('std:program') return title, target -def split_term_classifiers(line): - # type: (str) -> List[Union[str, None]] +def split_term_classifiers(line: str) -> List[Optional[str]]: # split line into a term and classifiers. if no classifier, None is used.. parts = re.split(' +: +', line) + [None] return parts -def make_glossary_term(env, textnodes, index_key, source, lineno, new_id=None): - # type: (BuildEnvironment, Iterable[nodes.Node], str, str, int, str) -> nodes.term +def make_glossary_term(env: "BuildEnvironment", textnodes: Iterable[Node], index_key: str, + source: str, lineno: int, new_id: str = None) -> nodes.term: # get a text-only representation of the term and register it # as a cross-reference target term = nodes.term('', '', *textnodes) @@ -294,8 +288,7 @@ class Glossary(SphinxDirective): 'sorted': directives.flag, } - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: node = addnodes.glossary() node.document = self.state.document @@ -401,16 +394,15 @@ class Glossary(SphinxDirective): return messages + [node] -def token_xrefs(text): - # type: (str) -> List[nodes.Node] +def token_xrefs(text: str) -> List[Node]: retnodes = [] # type: List[nodes.Node] pos = 0 for m in token_re.finditer(text): if m.start() > pos: txt = text[pos:m.start()] retnodes.append(nodes.Text(txt, txt)) - refnode = addnodes.pending_xref( - m.group(1), reftype='token', refdomain='std', reftarget=m.group(1)) + refnode = pending_xref(m.group(1), reftype='token', refdomain='std', + reftarget=m.group(1)) refnode += nodes.literal(m.group(1), m.group(1), classes=['xref']) retnodes.append(refnode) pos = m.end() @@ -430,8 +422,7 @@ class ProductionList(SphinxDirective): final_argument_whitespace = True option_spec = {} # type: Dict - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: domain = cast(StandardDomain, self.env.get_domain('std')) node = addnodes.productionlist() # type: nodes.Element i = 0 @@ -536,8 +527,7 @@ class StandardDomain(Domain): nodes.container: ('code-block', None), } # type: Dict[Type[nodes.Node], Tuple[str, Callable]] - def __init__(self, env): - # type: (BuildEnvironment) -> None + def __init__(self, env: "BuildEnvironment") -> None: super().__init__(env) # set up enumerable nodes @@ -546,27 +536,22 @@ class StandardDomain(Domain): self.enumerable_nodes[node] = settings @property - def objects(self): - # type: () -> Dict[Tuple[str, str], Tuple[str, str]] + def objects(self) -> Dict[Tuple[str, str], Tuple[str, str]]: return self.data.setdefault('objects', {}) # (objtype, name) -> docname, labelid @property - def progoptions(self): - # type: () -> Dict[Tuple[str, str], Tuple[str, str]] + def progoptions(self) -> Dict[Tuple[str, str], Tuple[str, str]]: return self.data.setdefault('progoptions', {}) # (program, name) -> docname, labelid @property - def labels(self): - # type: () -> Dict[str, Tuple[str, str, str]] + def labels(self) -> Dict[str, Tuple[str, str, str]]: return self.data.setdefault('labels', {}) # labelname -> docname, labelid, sectionname @property - def anonlabels(self): - # type: () -> Dict[str, Tuple[str, str]] + def anonlabels(self) -> Dict[str, Tuple[str, str]]: return self.data.setdefault('anonlabels', {}) # labelname -> docname, labelid - def clear_doc(self, docname): - # type: (str) -> None + def clear_doc(self, docname: str) -> None: key = None # type: Any for key, (fn, _l) in list(self.progoptions.items()): if fn == docname: @@ -581,8 +566,7 @@ class StandardDomain(Domain): if fn == docname: del self.anonlabels[key] - def merge_domaindata(self, docnames, otherdata): - # type: (List[str], Dict) -> None + def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: # XXX duplicates? for key, data in otherdata['progoptions'].items(): if data[0] in docnames: @@ -597,8 +581,7 @@ class StandardDomain(Domain): if data[0] in docnames: self.anonlabels[key] = data - def process_doc(self, env, docname, document): - # type: (BuildEnvironment, str, nodes.document) -> None + def process_doc(self, env: "BuildEnvironment", docname: str, document: nodes.document) -> None: # NOQA for name, explicit in document.nametypes.items(): if not explicit: continue @@ -639,17 +622,15 @@ class StandardDomain(Domain): continue self.labels[name] = docname, labelid, sectname - def add_object(self, objtype, name, docname, labelid): - # type: (str, str, str, str) -> None + def add_object(self, objtype: str, name: str, docname: str, labelid: str) -> None: self.objects[objtype, name] = (docname, labelid) - def add_program_option(self, program, name, docname, labelid): - # type: (str, str, str, str) -> None + def add_program_option(self, program: str, name: str, docname: str, labelid: str) -> None: self.progoptions[program, name] = (docname, labelid) - def build_reference_node(self, fromdocname, builder, docname, labelid, - sectname, rolename, **options): - # type: (str, Builder, str, str, str, str, Any) -> nodes.Element + def build_reference_node(self, fromdocname: str, builder: "Builder", docname: str, + labelid: str, sectname: str, rolename: str, **options + ) -> Element: nodeclass = options.pop('nodeclass', nodes.reference) newnode = nodeclass('', '', internal=True, **options) innernode = nodes.inline(sectname, sectname) @@ -662,7 +643,7 @@ class StandardDomain(Domain): # set more info in contnode; in case the # get_relative_uri call raises NoUri, # the builder will then have to resolve these - contnode = addnodes.pending_xref('') + contnode = pending_xref('') contnode['refdocname'] = docname contnode['refsectname'] = sectname newnode['refuri'] = builder.get_relative_uri( @@ -672,8 +653,8 @@ class StandardDomain(Domain): newnode.append(innernode) return newnode - def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): - # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA + def resolve_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder", + typ: str, target: str, node: pending_xref, contnode: Element) -> Element: if typ == 'ref': resolver = self._resolve_ref_xref elif typ == 'numref': @@ -694,8 +675,9 @@ class StandardDomain(Domain): return resolver(env, fromdocname, builder, typ, target, node, contnode) - def _resolve_ref_xref(self, env, fromdocname, builder, typ, target, node, contnode): - # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA + def _resolve_ref_xref(self, env: "BuildEnvironment", fromdocname: str, + builder: "Builder", typ: str, target: str, node: pending_xref, + contnode: Element) -> Element: if node['refexplicit']: # reference to anonymous label; the reference uses # the supplied link caption @@ -711,8 +693,9 @@ class StandardDomain(Domain): return self.build_reference_node(fromdocname, builder, docname, labelid, sectname, 'ref') - def _resolve_numref_xref(self, env, fromdocname, builder, typ, target, node, contnode): - # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA + def _resolve_numref_xref(self, env: "BuildEnvironment", fromdocname: str, + builder: "Builder", typ: str, target: str, + node: pending_xref, contnode: Element) -> Element: if target in self.labels: docname, labelid, figname = self.labels.get(target, ('', '', '')) else: @@ -772,8 +755,9 @@ class StandardDomain(Domain): nodeclass=addnodes.number_reference, title=title) - def _resolve_keyword_xref(self, env, fromdocname, builder, typ, target, node, contnode): - # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA + def _resolve_keyword_xref(self, env: "BuildEnvironment", fromdocname: str, + builder: "Builder", typ: str, target: str, + node: pending_xref, contnode: Element) -> Element: # keywords are oddballs: they are referenced by named labels docname, labelid, _ = self.labels.get(target, ('', '', '')) if not docname: @@ -781,8 +765,9 @@ class StandardDomain(Domain): return make_refnode(builder, fromdocname, docname, labelid, contnode) - def _resolve_doc_xref(self, env, fromdocname, builder, typ, target, node, contnode): - # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA + def _resolve_doc_xref(self, env: "BuildEnvironment", fromdocname: str, + builder: "Builder", typ: str, target: str, + node: pending_xref, contnode: Element) -> Element: # directly reference to document by source name; can be absolute or relative refdoc = node.get('refdoc', fromdocname) docname = docname_join(refdoc, node['reftarget']) @@ -797,8 +782,9 @@ class StandardDomain(Domain): innernode = nodes.inline(caption, caption, classes=['doc']) return make_refnode(builder, fromdocname, docname, None, innernode) - def _resolve_option_xref(self, env, fromdocname, builder, typ, target, node, contnode): - # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA + def _resolve_option_xref(self, env: "BuildEnvironment", fromdocname: str, + builder: "Builder", typ: str, target: str, + node: pending_xref, contnode: Element) -> Element: progname = node.get('std:program') target = target.strip() docname, labelid = self.progoptions.get((progname, target), ('', '')) @@ -818,8 +804,9 @@ class StandardDomain(Domain): return make_refnode(builder, fromdocname, docname, labelid, contnode) - def _resolve_citation_xref(self, env, fromdocname, builder, typ, target, node, contnode): - # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA + def _resolve_citation_xref(self, env: "BuildEnvironment", fromdocname: str, + builder: "Builder", typ: str, target: str, + node: pending_xref, contnode: Element) -> Element: warnings.warn('StandardDomain._resolve_citation_xref() is deprecated.', RemovedInSphinx30Warning) docname, labelid, lineno = self.data['citations'].get(target, ('', '', 0)) @@ -841,8 +828,9 @@ class StandardDomain(Domain): del node['ids'][:] raise - def _resolve_obj_xref(self, env, fromdocname, builder, typ, target, node, contnode): - # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA + def _resolve_obj_xref(self, env: "BuildEnvironment", fromdocname: str, + builder: "Builder", typ: str, target: str, + node: pending_xref, contnode: Element) -> Element: objtypes = self.objtypes_for_role(typ) or [] for objtype in objtypes: if (objtype, target) in self.objects: @@ -855,8 +843,9 @@ class StandardDomain(Domain): return make_refnode(builder, fromdocname, docname, labelid, contnode) - def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode): - # type: (BuildEnvironment, str, Builder, str, addnodes.pending_xref, nodes.Element) -> List[Tuple[str, nodes.Element]] # NOQA + def resolve_any_xref(self, env: "BuildEnvironment", fromdocname: str, + builder: "Builder", target: str, node: pending_xref, + contnode: Element) -> List[Tuple[str, Element]]: results = [] # type: List[Tuple[str, nodes.Element]] ltarget = target.lower() # :ref: lowercases its target automatically for role in ('ref', 'option'): # do not try "keyword" @@ -877,8 +866,7 @@ class StandardDomain(Domain): labelid, contnode))) return results - def get_objects(self): - # type: () -> Iterator[Tuple[str, str, str, str, str, int]] + def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]: # handle the special 'doc' reference here for doc in self.env.all_docs: yield (doc, clean_astext(self.env.titles[doc]), 'doc', doc, '', -1) @@ -899,34 +887,30 @@ class StandardDomain(Domain): if name not in non_anon_labels: yield (name, name, 'label', docname, labelid, -1) - def get_type_name(self, type, primary=False): - # type: (ObjType, bool) -> str + def get_type_name(self, type: ObjType, primary: bool = False) -> str: # never prepend "Default" return type.lname - def is_enumerable_node(self, node): - # type: (nodes.Node) -> bool + def is_enumerable_node(self, node: Node) -> bool: return node.__class__ in self.enumerable_nodes - def get_numfig_title(self, node): - # type: (nodes.Node) -> str + def get_numfig_title(self, node: Node) -> str: """Get the title of enumerable nodes to refer them using its title""" if self.is_enumerable_node(node): - _, title_getter = self.enumerable_nodes.get(node.__class__, (None, None)) + elem = cast(nodes.Element, node) + _, title_getter = self.enumerable_nodes.get(elem.__class__, (None, None)) if title_getter: - return title_getter(node) + return title_getter(elem) else: - for subnode in node: - if subnode.tagname in ('caption', 'title'): + for subnode in elem: + if isinstance(subnode, (nodes.caption, nodes.title)): return clean_astext(subnode) return None - def get_enumerable_node_type(self, node): - # type: (nodes.Node) -> str + def get_enumerable_node_type(self, node: Node) -> str: """Get type of enumerable nodes.""" - def has_child(node, cls): - # type: (nodes.Element, Type) -> bool + def has_child(node: Element, cls: Type) -> bool: return any(isinstance(child, cls) for child in node) if isinstance(node, nodes.section): @@ -940,8 +924,7 @@ class StandardDomain(Domain): figtype, _ = self.enumerable_nodes.get(node.__class__, (None, None)) return figtype - def get_figtype(self, node): - # type: (nodes.Node) -> str + def get_figtype(self, node: Node) -> str: """Get figure type of nodes. .. deprecated:: 1.8 @@ -951,8 +934,8 @@ class StandardDomain(Domain): RemovedInSphinx30Warning, stacklevel=2) return self.get_enumerable_node_type(node) - def get_fignumber(self, env, builder, figtype, docname, target_node): - # type: (BuildEnvironment, Builder, str, str, nodes.Element) -> Tuple[int, ...] + def get_fignumber(self, env: "BuildEnvironment", builder: "Builder", + figtype: str, docname: str, target_node: Element) -> Tuple[int, ...]: if figtype == 'section': if builder.name == 'latex': return tuple() @@ -974,8 +957,7 @@ class StandardDomain(Domain): # Maybe it is defined in orphaned document. raise ValueError - def get_full_qualified_name(self, node): - # type: (nodes.Element) -> str + def get_full_qualified_name(self, node: Element) -> str: if node.get('reftype') == 'option': progname = node.get('std:program') command = ws_re.split(node.get('reftarget')) @@ -989,24 +971,20 @@ class StandardDomain(Domain): else: return None - def note_citations(self, env, docname, document): - # type: (BuildEnvironment, str, nodes.document) -> None + def note_citations(self, env: "BuildEnvironment", docname: str, document: nodes.document) -> None: # NOQA warnings.warn('StandardDomain.note_citations() is deprecated.', RemovedInSphinx40Warning) - def note_citation_refs(self, env, docname, document): - # type: (BuildEnvironment, str, nodes.document) -> None + def note_citation_refs(self, env: "BuildEnvironment", docname: str, document: nodes.document) -> None: # NOQA warnings.warn('StandardDomain.note_citation_refs() is deprecated.', RemovedInSphinx40Warning) - def note_labels(self, env, docname, document): - # type: (BuildEnvironment, str, nodes.document) -> None + def note_labels(self, env: "BuildEnvironment", docname: str, document: nodes.document) -> None: # NOQA warnings.warn('StandardDomain.note_labels() is deprecated.', RemovedInSphinx40Warning) -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: "Sphinx") -> Dict[str, Any]: app.add_domain(StandardDomain) return { diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index af3b6cf1f..59d120e82 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -15,14 +15,22 @@ from collections import defaultdict from copy import copy from io import BytesIO from os import path +from typing import Any, Callable, Dict, Generator, IO, Iterator, List, Set, Tuple, Union + +from docutils import nodes +from docutils.nodes import Node from sphinx import addnodes +from sphinx.config import Config from sphinx.deprecation import ( RemovedInSphinx30Warning, RemovedInSphinx40Warning, deprecated_alias ) +from sphinx.domains import Domain from sphinx.environment.adapters.toctree import TocTree from sphinx.errors import SphinxError, BuildEnvironmentError, DocumentError, ExtensionError +from sphinx.events import EventManager from sphinx.locale import __ +from sphinx.project import Project from sphinx.transforms import SphinxTransformer from sphinx.util import DownloadFiles, FilenameUniqDict from sphinx.util import logging @@ -32,14 +40,8 @@ from sphinx.util.nodes import is_translatable if False: # For type annotation - from typing import Any, Callable, Dict, IO, Iterator, List, Optional, Set, Tuple, Union # NOQA - from docutils import nodes # NOQA - 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 + from sphinx.application import Sphinx + from sphinx.builders import Builder logger = logging.getLogger(__name__) @@ -92,8 +94,7 @@ class BuildEnvironment: # --------- ENVIRONMENT INITIALIZATION ------------------------------------- - def __init__(self, app=None): - # type: (Sphinx) -> None + def __init__(self, app: "Sphinx" = None): self.app = None # type: Sphinx self.doctreedir = None # type: str self.srcdir = None # type: str @@ -191,19 +192,16 @@ class BuildEnvironment: if app: self.setup(app) - def __getstate__(self): - # type: () -> Dict + def __getstate__(self) -> Dict: """Obtains serializable data for pickling.""" __dict__ = self.__dict__.copy() __dict__.update(app=None, domains={}, events=None) # clear unpickable attributes return __dict__ - def __setstate__(self, state): - # type: (Dict) -> None + def __setstate__(self, state: Dict) -> None: self.__dict__.update(state) - def setup(self, app): - # type: (Sphinx) -> None + def setup(self, app: "Sphinx") -> None: """Set up BuildEnvironment object.""" if self.version and self.version != app.registry.get_envversion(app): raise BuildEnvironmentError(__('build environment version not current')) @@ -231,12 +229,13 @@ class BuildEnvironment: # initialie settings self._update_settings(app.config) - def _update_config(self, config): - # type: (Config) -> None + def _update_config(self, config: Config) -> None: """Update configurations by new one.""" self.config_status = CONFIG_OK if self.config is None: self.config_status = CONFIG_NEW + elif self.config.extensions != config.extensions: + self.config_status = CONFIG_EXTENSIONS_CHANGED else: # check if a config value was changed that affects how # doctrees are read @@ -245,15 +244,9 @@ class BuildEnvironment: self.config_status = CONFIG_CHANGED break - # this value is not covered by the above loop because it is handled - # specially by the config class - if self.config.extensions != config.extensions: - self.config_status = CONFIG_EXTENSIONS_CHANGED - self.config = config - def _update_settings(self, config): - # type: (Config) -> None + def _update_settings(self, config: Config) -> None: """Update settings by new config.""" self.settings['input_encoding'] = config.source_encoding self.settings['trim_footnote_reference_space'] = config.trim_footnote_reference_space @@ -262,8 +255,7 @@ class BuildEnvironment: # Allow to disable by 3rd party extension (workaround) self.settings.setdefault('smart_quotes', True) - def set_versioning_method(self, method, compare): - # type: (Union[str, Callable], bool) -> None + def set_versioning_method(self, method: Union[str, Callable], compare: bool) -> None: """This sets the doctree versioning method for this environment. Versioning methods are a builder property; only builders with the same @@ -286,8 +278,7 @@ class BuildEnvironment: self.versioning_condition = condition self.versioning_compare = compare - def clear_doc(self, docname): - # type: (str) -> None + def clear_doc(self, docname: str) -> None: """Remove all traces of a source file in the inventory.""" if docname in self.all_docs: self.all_docs.pop(docname, None) @@ -297,8 +288,8 @@ class BuildEnvironment: for domain in self.domains.values(): domain.clear_doc(docname) - def merge_info_from(self, docnames, other, app): - # type: (List[str], BuildEnvironment, Sphinx) -> None + def merge_info_from(self, docnames: List[str], other: "BuildEnvironment", + app: "Sphinx") -> None: """Merge global information gathered about *docnames* while reading them from the *other* environment. @@ -315,16 +306,14 @@ class BuildEnvironment: domain.merge_domaindata(docnames, other.domaindata[domainname]) self.events.emit('env-merge-info', self, docnames, other) - def path2doc(self, filename): - # type: (str) -> Optional[str] + def path2doc(self, filename: str) -> str: """Return the docname for the filename if the file is document. *filename* should be absolute or relative to the source directory. """ return self.project.path2doc(filename) - def doc2path(self, docname, base=True, suffix=None): - # type: (str, Union[bool, str], str) -> str + def doc2path(self, docname: str, base: Union[bool, str] = True, suffix: str = None) -> str: """Return the filename for the document name. If *base* is True, return absolute path under self.srcdir. @@ -347,8 +336,7 @@ class BuildEnvironment: pathname = path.join(base, pathname) # type: ignore return pathname - def relfn2path(self, filename, docname=None): - # type: (str, str) -> Tuple[str, str] + def relfn2path(self, filename: str, docname: str = None) -> Tuple[str, str]: """Return paths to a file referenced from a document, relative to documentation root and absolute. @@ -367,13 +355,11 @@ class BuildEnvironment: return rel_fn, path.abspath(path.join(self.srcdir, rel_fn)) @property - def found_docs(self): - # type: () -> Set[str] + def found_docs(self) -> Set[str]: """contains all existing docnames.""" return self.project.docnames - def find_files(self, config, builder): - # type: (Config, Builder) -> None + def find_files(self, config: Config, builder: "Builder") -> None: """Find all source files in the source dir and put them in self.found_docs. """ @@ -401,8 +387,7 @@ class BuildEnvironment: except OSError as exc: raise DocumentError(__('Failed to scan documents in %s: %r') % (self.srcdir, exc)) - def get_outdated_files(self, config_changed): - # type: (bool) -> Tuple[Set[str], Set[str], Set[str]] + def get_outdated_files(self, config_changed: bool) -> Tuple[Set[str], Set[str], Set[str]]: """Return (added, changed, removed) sets.""" # clear all files no longer present removed = set(self.all_docs) - self.found_docs @@ -452,8 +437,7 @@ class BuildEnvironment: return added, changed, removed - def check_dependents(self, app, already): - # type: (Sphinx, Set[str]) -> Iterator[str] + def check_dependents(self, app: "Sphinx", already: Set[str]) -> Generator[str, None, None]: to_rewrite = [] # type: List[str] for docnames in self.events.emit('env-get-updated', self): to_rewrite.extend(docnames) @@ -463,8 +447,7 @@ class BuildEnvironment: # --------- SINGLE FILE READING -------------------------------------------- - def prepare_settings(self, docname): - # type: (str) -> None + def prepare_settings(self, docname: str) -> None: """Prepare to set up environment for reading.""" self.temp_data['docname'] = docname # defaults to the global default, but can be re-set in a document @@ -475,13 +458,11 @@ class BuildEnvironment: # utilities to use while reading a document @property - def docname(self): - # type: () -> str + def docname(self) -> str: """Returns the docname of the document currently being parsed.""" return self.temp_data['docname'] - def new_serialno(self, category=''): - # type: (str) -> int + def new_serialno(self, category: str = '') -> int: """Return a serial number, e.g. for index entry targets. The number is guaranteed to be unique in the current document. @@ -491,8 +472,7 @@ class BuildEnvironment: self.temp_data[key] = cur + 1 return cur - def note_dependency(self, filename): - # type: (str) -> None + def note_dependency(self, filename: str) -> None: """Add *filename* as a dependency of the current document. This means that the document will be rebuilt if this file changes. @@ -501,8 +481,7 @@ class BuildEnvironment: """ self.dependencies[self.docname].add(filename) - def note_included(self, filename): - # type: (str) -> None + def note_included(self, filename: str) -> None: """Add *filename* as a included from other document. This means the document is not orphaned. @@ -511,15 +490,13 @@ class BuildEnvironment: """ self.included[self.docname].add(self.path2doc(filename)) - def note_reread(self): - # type: () -> None + def note_reread(self) -> None: """Add the current document to the list of documents that will automatically be re-read at the next build. """ self.reread_always.add(self.docname) - def get_domain(self, domainname): - # type: (str) -> Domain + def get_domain(self, domainname: str) -> Domain: """Return the domain instance with the specified name. Raises an ExtensionError if the domain is not registered. @@ -531,8 +508,7 @@ class BuildEnvironment: # --------- RESOLVING REFERENCES AND TOCTREES ------------------------------ - def get_doctree(self, docname): - # type: (str) -> nodes.document + def get_doctree(self, docname: str) -> nodes.document: """Read the doctree for a file from the pickle and return it.""" filename = path.join(self.doctreedir, docname + '.doctree') with open(filename, 'rb') as f: @@ -541,9 +517,9 @@ class BuildEnvironment: doctree.reporter = LoggingReporter(self.doc2path(docname)) return doctree - def get_and_resolve_doctree(self, docname, builder, doctree=None, - prune_toctrees=True, includehidden=False): - # type: (str, Builder, nodes.document, bool, bool) -> nodes.document + def get_and_resolve_doctree(self, docname: str, builder: "Builder", + doctree: nodes.document = None, prune_toctrees: bool = True, + includehidden: bool = False) -> nodes.document: """Read the doctree from the pickle, resolve cross-references and toctrees and return it. """ @@ -565,9 +541,9 @@ class BuildEnvironment: return doctree - def resolve_toctree(self, docname, builder, toctree, prune=True, maxdepth=0, - titles_only=False, collapse=False, includehidden=False): - # type: (str, Builder, addnodes.toctree, bool, int, bool, bool, bool) -> nodes.Node + def resolve_toctree(self, docname: str, builder: "Builder", toctree: addnodes.toctree, + prune: bool = True, maxdepth: int = 0, titles_only: bool = False, + collapse: bool = False, includehidden: bool = False) -> Node: """Resolve a *toctree* node into individual bullet lists with titles as items, returning None (if no containing titles are found) or a new node. @@ -583,12 +559,11 @@ class BuildEnvironment: maxdepth, titles_only, collapse, includehidden) - def resolve_references(self, doctree, fromdocname, builder): - # type: (nodes.document, str, Builder) -> None + def resolve_references(self, doctree: nodes.document, fromdocname: str, + builder: "Builder") -> None: self.apply_post_transforms(doctree, fromdocname) - def apply_post_transforms(self, doctree, docname): - # type: (nodes.document, str) -> None + def apply_post_transforms(self, doctree: nodes.document, docname: str) -> None: """Apply all post-transforms.""" try: # set env.docname during applying post-transforms @@ -605,12 +580,10 @@ class BuildEnvironment: # allow custom references to be resolved self.events.emit('doctree-resolved', doctree, docname) - def collect_relations(self): - # type: () -> Dict[str, List[str]] + def collect_relations(self) -> Dict[str, List[str]]: traversed = set() - def traverse_toctree(parent, docname): - # type: (str, str) -> Iterator[Tuple[str, str]] + def traverse_toctree(parent: str, docname: str) -> Iterator[Tuple[str, str]]: if parent == docname: logger.warning(__('self referenced toctree found. Ignored.'), location=docname) return @@ -639,8 +612,7 @@ class BuildEnvironment: return relations - def check_consistency(self): - # type: () -> None + def check_consistency(self) -> None: """Do consistency checks.""" included = set().union(*self.included.values()) # type: ignore for docname in sorted(self.all_docs): @@ -663,48 +635,41 @@ class BuildEnvironment: # --------- METHODS FOR COMPATIBILITY -------------------------------------- - def update(self, config, srcdir, doctreedir): - # type: (Config, str, str) -> List[str] + def update(self, config: Config, srcdir: str, doctreedir: str) -> List[str]: warnings.warn('env.update() is deprecated. Please use builder.read() instead.', RemovedInSphinx30Warning, stacklevel=2) return self.app.builder.read() - def _read_serial(self, docnames, app): - # type: (List[str], Sphinx) -> None + def _read_serial(self, docnames: List[str], app: "Sphinx") -> None: warnings.warn('env._read_serial() is deprecated. Please use builder.read() instead.', RemovedInSphinx30Warning, stacklevel=2) return self.app.builder._read_serial(docnames) - def _read_parallel(self, docnames, app, nproc): - # type: (List[str], Sphinx, int) -> None + def _read_parallel(self, docnames: List[str], app: "Sphinx", nproc: int) -> None: warnings.warn('env._read_parallel() is deprecated. Please use builder.read() instead.', RemovedInSphinx30Warning, stacklevel=2) return self.app.builder._read_parallel(docnames, nproc) - def read_doc(self, docname, app=None): - # type: (str, Sphinx) -> None + def read_doc(self, docname: str, app: "Sphinx" = None) -> None: warnings.warn('env.read_doc() is deprecated. Please use builder.read_doc() instead.', RemovedInSphinx30Warning, stacklevel=2) self.app.builder.read_doc(docname) - def write_doctree(self, docname, doctree): - # type: (str, nodes.document) -> None + def write_doctree(self, docname: str, doctree: nodes.document) -> None: warnings.warn('env.write_doctree() is deprecated. ' 'Please use builder.write_doctree() instead.', RemovedInSphinx30Warning, stacklevel=2) self.app.builder.write_doctree(docname, doctree) @property - def _nitpick_ignore(self): - # type: () -> List[str] + def _nitpick_ignore(self) -> List[str]: warnings.warn('env._nitpick_ignore is deprecated. ' 'Please use config.nitpick_ignore instead.', RemovedInSphinx30Warning, stacklevel=2) return self.config.nitpick_ignore @staticmethod - def load(f, app=None): - # type: (IO, Sphinx) -> BuildEnvironment + def load(f: IO, app: "Sphinx" = None) -> "BuildEnvironment": warnings.warn('BuildEnvironment.load() is deprecated. ' 'Please use pickle.load() instead.', RemovedInSphinx30Warning, stacklevel=2) @@ -720,8 +685,7 @@ class BuildEnvironment: return env @classmethod - def loads(cls, string, app=None): - # type: (bytes, Sphinx) -> BuildEnvironment + def loads(cls, string: bytes, app: "Sphinx" = None) -> "BuildEnvironment": warnings.warn('BuildEnvironment.loads() is deprecated. ' 'Please use pickle.loads() instead.', RemovedInSphinx30Warning, stacklevel=2) @@ -729,8 +693,7 @@ class BuildEnvironment: return cls.load(io, app) @classmethod - def frompickle(cls, filename, app): - # type: (str, Sphinx) -> BuildEnvironment + def frompickle(cls, filename: str, app: "Sphinx") -> "BuildEnvironment": warnings.warn('BuildEnvironment.frompickle() is deprecated. ' 'Please use pickle.load() instead.', RemovedInSphinx30Warning, stacklevel=2) @@ -738,16 +701,14 @@ class BuildEnvironment: return cls.load(f, app) @staticmethod - def dump(env, f): - # type: (BuildEnvironment, IO) -> None + def dump(env: "BuildEnvironment", f: IO) -> None: warnings.warn('BuildEnvironment.dump() is deprecated. ' 'Please use pickle.dump() instead.', RemovedInSphinx30Warning, stacklevel=2) pickle.dump(env, f, pickle.HIGHEST_PROTOCOL) @classmethod - def dumps(cls, env): - # type: (BuildEnvironment) -> bytes + def dumps(cls, env: "BuildEnvironment") -> bytes: warnings.warn('BuildEnvironment.dumps() is deprecated. ' 'Please use pickle.dumps() instead.', RemovedInSphinx30Warning, stacklevel=2) @@ -755,8 +716,7 @@ class BuildEnvironment: cls.dump(env, io) return io.getvalue() - def topickle(self, filename): - # type: (str) -> None + def topickle(self, filename: str) -> None: warnings.warn('env.topickle() is deprecated. ' 'Please use pickle.dump() instead.', RemovedInSphinx30Warning, stacklevel=2) @@ -764,15 +724,14 @@ class BuildEnvironment: self.dump(self, f) @property - def versionchanges(self): - # type: () -> Dict[str, List[Tuple[str, str, int, str, str, str]]] + def versionchanges(self) -> Dict[str, List[Tuple[str, str, int, str, str, str]]]: warnings.warn('env.versionchanges() is deprecated. ' 'Please use ChangeSetDomain instead.', RemovedInSphinx30Warning, stacklevel=2) return self.domaindata['changeset']['changes'] - def note_versionchange(self, type, version, node, lineno): - # type: (str, str, addnodes.versionmodified, int) -> None + def note_versionchange(self, type: str, version: str, + node: addnodes.versionmodified, lineno: int) -> None: warnings.warn('env.note_versionchange() is deprecated. ' 'Please use ChangeSetDomain.note_changeset() instead.', RemovedInSphinx30Warning, stacklevel=2) diff --git a/sphinx/environment/adapters/asset.py b/sphinx/environment/adapters/asset.py index b57943967..bc282de0c 100644 --- a/sphinx/environment/adapters/asset.py +++ b/sphinx/environment/adapters/asset.py @@ -8,18 +8,14 @@ :license: BSD, see LICENSE for details. """ -if False: - # For type annotation - from sphinx.environment import BuildEnvironment # NOQA +from sphinx.environment import BuildEnvironment class ImageAdapter: - def __init__(self, env): - # type: (BuildEnvironment) -> None + def __init__(self, env: BuildEnvironment) -> None: self.env = env - def get_original_image_uri(self, name): - # type: (str) -> str + def get_original_image_uri(self, name: str) -> str: """Get the original image URI.""" while name in self.env.original_image_uri: name = self.env.original_image_uri[name] diff --git a/sphinx/environment/adapters/indexentries.py b/sphinx/environment/adapters/indexentries.py index 38d28f0b0..68198040d 100644 --- a/sphinx/environment/adapters/indexentries.py +++ b/sphinx/environment/adapters/indexentries.py @@ -11,33 +11,30 @@ import bisect import re import unicodedata from itertools import groupby +from typing import Any, Dict, Pattern, List, Tuple +from sphinx.builders import Builder +from sphinx.environment import BuildEnvironment from sphinx.errors import NoUri from sphinx.locale import _, __ from sphinx.util import split_into, logging -if False: - # For type annotation - from typing import Any, Dict, Pattern, List, Tuple # NOQA - from sphinx.builders import Builder # NOQA - from sphinx.environment import BuildEnvironment # NOQA logger = logging.getLogger(__name__) class IndexEntries: - def __init__(self, env): - # type: (BuildEnvironment) -> None + def __init__(self, env: BuildEnvironment) -> None: self.env = env - def create_index(self, builder, group_entries=True, - _fixre=re.compile(r'(.*) ([(][^()]*[)])')): - # type: (Builder, bool, Pattern) -> List[Tuple[str, List[Tuple[str, Any]]]] + def create_index(self, builder: Builder, group_entries: bool = True, + _fixre: Pattern = re.compile(r'(.*) ([(][^()]*[)])') + ) -> List[Tuple[str, List[Tuple[str, Any]]]]: """Create the real index from the collected index entries.""" new = {} # type: Dict[str, List] - def add_entry(word, subword, main, link=True, dic=new, key=None): - # type: (str, str, str, bool, Dict, str) -> None + def add_entry(word: str, subword: str, main: str, link: bool = True, + dic: Dict = new, key: str = None) -> None: # Force the word to be unicode if it's a ASCII bytestring. # This will solve problems with unicode normalization later. # For instance the RFC role will add bytestrings at the moment @@ -91,8 +88,7 @@ class IndexEntries: # sort the index entries; put all symbols at the front, even those # following the letters in ASCII, this is where the chr(127) comes from - def keyfunc(entry): - # type: (Tuple[str, List]) -> Tuple[str, str] + def keyfunc(entry: Tuple[str, List]) -> Tuple[str, str]: key, (void, void, category_key) = entry if category_key: # using specified category key to sort @@ -137,12 +133,21 @@ class IndexEntries: oldsubitems = subitems i += 1 + # sort the sub-index entries + def keyfunc2(entry: Tuple[str, List]) -> str: + key = unicodedata.normalize('NFD', entry[0].lower()) + if key.startswith('\N{RIGHT-TO-LEFT MARK}'): + key = key[1:] + if key[0:1].isalpha() or key.startswith('_'): + key = chr(127) + key + return key + # group the entries by letter - def keyfunc2(item): - # type: (Tuple[str, List]) -> str + def keyfunc3(item: Tuple[str, List]) -> str: # hack: mutating the subitems dicts to a list in the keyfunc k, v = item - v[1] = sorted((si, se) for (si, (se, void, void)) in v[1].items()) + v[1] = sorted(((si, se) for (si, (se, void, void)) in v[1].items()), + key=keyfunc2) if v[2] is None: # now calculate the key if k.startswith('\N{RIGHT-TO-LEFT MARK}'): @@ -156,4 +161,4 @@ class IndexEntries: else: return v[2] return [(key_, list(group)) - for (key_, group) in groupby(newlist, keyfunc2)] + for (key_, group) in groupby(newlist, keyfunc3)] diff --git a/sphinx/environment/adapters/toctree.py b/sphinx/environment/adapters/toctree.py index fe7bd3d0d..d5f827dde 100644 --- a/sphinx/environment/adapters/toctree.py +++ b/sphinx/environment/adapters/toctree.py @@ -8,9 +8,11 @@ :license: BSD, see LICENSE for details. """ -from typing import Iterable, cast +from typing import cast +from typing import Iterable, List from docutils import nodes +from docutils.nodes import Element, Node from sphinx import addnodes from sphinx.locale import __ @@ -20,20 +22,18 @@ from sphinx.util.nodes import clean_astext, process_only_nodes if False: # For type annotation - from typing import Any, Dict, List # NOQA - from sphinx.builders import Builder # NOQA - from sphinx.environment import BuildEnvironment # NOQA + from sphinx.builders import Builder + from sphinx.environment import BuildEnvironment + logger = logging.getLogger(__name__) class TocTree: - def __init__(self, env): - # type: (BuildEnvironment) -> None + def __init__(self, env: "BuildEnvironment") -> None: self.env = env - def note(self, docname, toctreenode): - # type: (str, addnodes.toctree) -> None + def note(self, docname: str, toctreenode: addnodes.toctree) -> None: """Note a TOC tree directive in a document and gather information about file relations from it. """ @@ -48,9 +48,9 @@ class TocTree: self.env.files_to_rebuild.setdefault(includefile, set()).add(docname) self.env.toctree_includes.setdefault(docname, []).extend(includefiles) - def resolve(self, docname, builder, toctree, prune=True, maxdepth=0, - titles_only=False, collapse=False, includehidden=False): - # type: (str, Builder, addnodes.toctree, bool, int, bool, bool, bool) -> nodes.Element + def resolve(self, docname: str, builder: "Builder", toctree: addnodes.toctree, + prune: bool = True, maxdepth: int = 0, titles_only: bool = False, + collapse: bool = False, includehidden: bool = False) -> Element: """Resolve a *toctree* node into individual bullet lists with titles as items, returning None (if no containing titles are found) or a new node. @@ -86,8 +86,7 @@ class TocTree: toctree_ancestors = self.get_toctree_ancestors(docname) excluded = Matcher(self.env.config.exclude_patterns) - def _toctree_add_classes(node, depth): - # type: (nodes.Element, int) -> None + def _toctree_add_classes(node: Element, depth: int) -> None: """Add 'toctree-l%d' and 'current' classes to the toctree.""" for subnode in node.children: if isinstance(subnode, (addnodes.compact_paragraph, @@ -105,7 +104,7 @@ class TocTree: if not subnode['anchorname']: # give the whole branch a 'current' class # (useful for styling it differently) - branchnode = subnode # type: nodes.Element + branchnode = subnode # type: Element while branchnode: branchnode['classes'].append('current') branchnode = branchnode.parent @@ -117,11 +116,12 @@ class TocTree: subnode['iscurrent'] = True subnode = subnode.parent - def _entries_from_toctree(toctreenode, parents, separate=False, subtree=False): - # type: (addnodes.toctree, List[str], bool, bool) -> List[nodes.Element] + def _entries_from_toctree(toctreenode: addnodes.toctree, parents: List[str], + separate: bool = False, subtree: bool = False + ) -> List[Element]: """Return TOC entries for a toctree node.""" refs = [(e[0], e[1]) for e in toctreenode['entries']] - entries = [] # type: List[nodes.Element] + entries = [] # type: List[Element] for (title, ref) in refs: try: refdoc = None @@ -265,8 +265,7 @@ class TocTree: docname, refnode['refuri']) + refnode['anchorname'] return newnode - def get_toctree_ancestors(self, docname): - # type: (str) -> List[str] + def get_toctree_ancestors(self, docname: str) -> List[str]: parent = {} for p, children in self.env.toctree_includes.items(): for child in children: @@ -278,8 +277,8 @@ class TocTree: d = parent[d] return ancestors - def _toctree_prune(self, node, depth, maxdepth, collapse=False): - # type: (nodes.Element, int, int, bool) -> None + def _toctree_prune(self, node: Element, depth: int, maxdepth: int, collapse: bool = False + ) -> None: """Utility: Cut a TOC at a specified depth.""" for subnode in node.children[:]: if isinstance(subnode, (addnodes.compact_paragraph, @@ -300,8 +299,7 @@ class TocTree: # recurse on visible children self._toctree_prune(subnode, depth + 1, maxdepth, collapse) - def get_toc_for(self, docname, builder): - # type: (str, Builder) -> nodes.Node + def get_toc_for(self, docname: str, builder: "Builder") -> Node: """Return a TOC nodetree -- for use on the same page only!""" tocdepth = self.env.metadata[docname].get('tocdepth', 0) try: @@ -316,11 +314,11 @@ class TocTree: node['refuri'] = node['anchorname'] or '#' return toc - def get_toctree_for(self, docname, builder, collapse, **kwds): - # type: (str, Builder, bool, Any) -> nodes.Element + def get_toctree_for(self, docname: str, builder: "Builder", collapse: bool, **kwds + ) -> Element: """Return the global TOC nodetree.""" doctree = self.env.get_doctree(self.env.config.master_doc) - toctrees = [] # type: List[nodes.Element] + toctrees = [] # type: List[Element] if 'includehidden' not in kwds: kwds['includehidden'] = True if 'maxdepth' not in kwds: diff --git a/sphinx/environment/collectors/__init__.py b/sphinx/environment/collectors/__init__.py index 3f7b9344b..eb16a9f25 100644 --- a/sphinx/environment/collectors/__init__.py +++ b/sphinx/environment/collectors/__init__.py @@ -8,12 +8,12 @@ :license: BSD, see LICENSE for details. """ -if False: - # For type annotation - from typing import Dict, List, Set # NOQA - from docutils import nodes # NOQA - from sphinx.sphinx import Sphinx # NOQA - from sphinx.environment import BuildEnvironment # NOQA +from typing import Dict, List, Set + +from docutils import nodes + +from sphinx.application import Sphinx +from sphinx.environment import BuildEnvironment class EnvironmentCollector: @@ -27,8 +27,7 @@ class EnvironmentCollector: listener_ids = None # type: Dict[str, int] - def enable(self, app): - # type: (Sphinx) -> None + def enable(self, app: Sphinx) -> None: assert self.listener_ids is None self.listener_ids = { 'doctree-read': app.connect('doctree-read', self.process_doc), @@ -38,43 +37,39 @@ class EnvironmentCollector: 'env-get-outdated': app.connect('env-get-outdated', self.get_outdated_docs), } - def disable(self, app): - # type: (Sphinx) -> None + def disable(self, app: Sphinx) -> None: assert self.listener_ids is not None for listener_id in self.listener_ids.values(): app.disconnect(listener_id) self.listener_ids = None - def clear_doc(self, app, env, docname): - # type: (Sphinx, BuildEnvironment, str) -> None + def clear_doc(self, app: Sphinx, env: BuildEnvironment, docname: str) -> None: """Remove specified data of a document. This method is called on the removal of the document.""" raise NotImplementedError - def merge_other(self, app, env, docnames, other): - # type: (Sphinx, BuildEnvironment, Set[str], BuildEnvironment) -> None + def merge_other(self, app: Sphinx, env: BuildEnvironment, + docnames: Set[str], other: BuildEnvironment) -> None: """Merge in specified data regarding docnames from a different `BuildEnvironment` object which coming from a subprocess in parallel builds.""" raise NotImplementedError - def process_doc(self, app, doctree): - # type: (Sphinx, nodes.document) -> None + def process_doc(self, app: Sphinx, doctree: nodes.document) -> None: """Process a document and gather specific data from it. This method is called after the document is read.""" raise NotImplementedError - def get_updated_docs(self, app, env): - # type: (Sphinx, BuildEnvironment) -> List[str] + def get_updated_docs(self, app: Sphinx, env: BuildEnvironment) -> List[str]: """Return a list of docnames to re-read. This methods is called after reading the whole of documents (experimental). """ return [] - def get_outdated_docs(self, app, env, added, changed, removed): - # type: (Sphinx, BuildEnvironment, str, Set[str], Set[str], Set[str]) -> List[str] + def get_outdated_docs(self, app: Sphinx, env: BuildEnvironment, + added: Set[str], changed: Set[str], removed: Set[str]) -> List[str]: """Return a list of docnames to re-read. This methods is called before reading the documents. diff --git a/sphinx/environment/collectors/asset.py b/sphinx/environment/collectors/asset.py index fed8280c1..e1d3abef1 100644 --- a/sphinx/environment/collectors/asset.py +++ b/sphinx/environment/collectors/asset.py @@ -11,22 +11,21 @@ import os from glob import glob from os import path +from typing import Any, Dict, List, Set from docutils import nodes +from docutils.nodes import Node from docutils.utils import relative_path from sphinx import addnodes +from sphinx.application import Sphinx +from sphinx.environment import BuildEnvironment from sphinx.environment.collectors import EnvironmentCollector from sphinx.locale import __ from sphinx.util import logging from sphinx.util.i18n import get_image_filename_for_language, search_image_for_language from sphinx.util.images import guess_mimetype -if False: - # For type annotation - from typing import Dict, List, Set # NOQA - from sphinx.sphinx import Sphinx # NOQA - from sphinx.environment import BuildEnvironment # NOQA logger = logging.getLogger(__name__) @@ -34,16 +33,14 @@ logger = logging.getLogger(__name__) class ImageCollector(EnvironmentCollector): """Image files collector for sphinx.environment.""" - def clear_doc(self, app, env, docname): - # type: (Sphinx, BuildEnvironment, str) -> None + def clear_doc(self, app: Sphinx, env: BuildEnvironment, docname: str) -> None: env.images.purge_doc(docname) - def merge_other(self, app, env, docnames, other): - # type: (Sphinx, BuildEnvironment, Set[str], BuildEnvironment) -> None + def merge_other(self, app: Sphinx, env: BuildEnvironment, + docnames: Set[str], other: BuildEnvironment) -> None: env.images.merge_other(docnames, other.images) - def process_doc(self, app, doctree): - # type: (Sphinx, nodes.document) -> None + def process_doc(self, app: Sphinx, doctree: nodes.document) -> None: """Process and rewrite image URIs.""" docname = app.env.docname @@ -92,8 +89,8 @@ class ImageCollector(EnvironmentCollector): continue app.env.images.add_file(docname, imgpath) - def collect_candidates(self, env, imgpath, candidates, node): - # type: (BuildEnvironment, str, Dict[str, str], nodes.Node) -> None + def collect_candidates(self, env: BuildEnvironment, imgpath: str, + candidates: Dict[str, str], node: Node) -> None: globbed = {} # type: Dict[str, List[str]] for filename in glob(imgpath): new_imgpath = relative_path(path.join(env.srcdir, 'dummy'), @@ -115,16 +112,14 @@ class ImageCollector(EnvironmentCollector): class DownloadFileCollector(EnvironmentCollector): """Download files collector for sphinx.environment.""" - def clear_doc(self, app, env, docname): - # type: (Sphinx, BuildEnvironment, str) -> None + def clear_doc(self, app: Sphinx, env: BuildEnvironment, docname: str) -> None: env.dlfiles.purge_doc(docname) - def merge_other(self, app, env, docnames, other): - # type: (Sphinx, BuildEnvironment, Set[str], BuildEnvironment) -> None + def merge_other(self, app: Sphinx, env: BuildEnvironment, + docnames: Set[str], other: BuildEnvironment) -> None: env.dlfiles.merge_other(docnames, other.dlfiles) - def process_doc(self, app, doctree): - # type: (Sphinx, nodes.document) -> None + def process_doc(self, app: Sphinx, doctree: nodes.document) -> None: """Process downloadable file paths. """ for node in doctree.traverse(addnodes.download_reference): targetname = node['reftarget'] @@ -140,8 +135,7 @@ class DownloadFileCollector(EnvironmentCollector): node['filename'] = app.env.dlfiles.add_file(app.env.docname, rel_filename) -def setup(app): - # type: (Sphinx) -> Dict +def setup(app: Sphinx) -> Dict[str, Any]: app.add_env_collector(ImageCollector) app.add_env_collector(DownloadFileCollector) diff --git a/sphinx/environment/collectors/dependencies.py b/sphinx/environment/collectors/dependencies.py index b091b371d..63ae63e84 100644 --- a/sphinx/environment/collectors/dependencies.py +++ b/sphinx/environment/collectors/dependencies.py @@ -10,35 +10,30 @@ import os from os import path +from typing import Any, Dict, Set +from docutils import nodes from docutils.utils import relative_path +from sphinx.application import Sphinx +from sphinx.environment import BuildEnvironment from sphinx.environment.collectors import EnvironmentCollector from sphinx.util.osutil import fs_encoding -if False: - # For type annotation - from typing import Dict, Set # NOQA - from docutils import nodes # NOQA - from sphinx.sphinx import Sphinx # NOQA - from sphinx.environment import BuildEnvironment # NOQA - class DependenciesCollector(EnvironmentCollector): """dependencies collector for sphinx.environment.""" - def clear_doc(self, app, env, docname): - # type: (Sphinx, BuildEnvironment, str) -> None + def clear_doc(self, app: Sphinx, env: BuildEnvironment, docname: str) -> None: env.dependencies.pop(docname, None) - def merge_other(self, app, env, docnames, other): - # type: (Sphinx, BuildEnvironment, Set[str], BuildEnvironment) -> None + def merge_other(self, app: Sphinx, env: BuildEnvironment, + docnames: Set[str], other: BuildEnvironment) -> None: for docname in docnames: if docname in other.dependencies: env.dependencies[docname] = other.dependencies[docname] - def process_doc(self, app, doctree): - # type: (Sphinx, nodes.document) -> None + def process_doc(self, app: Sphinx, doctree: nodes.document) -> None: """Process docutils-generated dependency info.""" cwd = os.getcwd() frompath = path.join(path.normpath(app.srcdir), 'dummy') @@ -55,8 +50,7 @@ class DependenciesCollector(EnvironmentCollector): app.env.dependencies[app.env.docname].add(relpath) -def setup(app): - # type: (Sphinx) -> Dict +def setup(app: Sphinx) -> Dict[str, Any]: app.add_env_collector(DependenciesCollector) return { diff --git a/sphinx/environment/collectors/indexentries.py b/sphinx/environment/collectors/indexentries.py index 2d8af7887..9c86779fc 100644 --- a/sphinx/environment/collectors/indexentries.py +++ b/sphinx/environment/collectors/indexentries.py @@ -8,34 +8,31 @@ :license: BSD, see LICENSE for details. """ +from typing import Any, Dict, Set + +from docutils import nodes + from sphinx import addnodes +from sphinx.application import Sphinx +from sphinx.environment import BuildEnvironment from sphinx.environment.collectors import EnvironmentCollector from sphinx.util import split_index_msg, logging -if False: - # For type annotation - from typing import Dict, Set # NOQA - from docutils import nodes # NOQA - from sphinx.applicatin import Sphinx # NOQA - from sphinx.environment import BuildEnvironment # NOQA - logger = logging.getLogger(__name__) class IndexEntriesCollector(EnvironmentCollector): name = 'indices' - def clear_doc(self, app, env, docname): - # type: (Sphinx, BuildEnvironment, str) -> None + def clear_doc(self, app: Sphinx, env: BuildEnvironment, docname: str) -> None: env.indexentries.pop(docname, None) - def merge_other(self, app, env, docnames, other): - # type: (Sphinx, BuildEnvironment, Set[str], BuildEnvironment) -> None + def merge_other(self, app: Sphinx, env: BuildEnvironment, + docnames: Set[str], other: BuildEnvironment) -> None: for docname in docnames: env.indexentries[docname] = other.indexentries[docname] - def process_doc(self, app, doctree): - # type: (Sphinx, nodes.document) -> None + def process_doc(self, app: Sphinx, doctree: nodes.document) -> None: docname = app.env.docname entries = app.env.indexentries[docname] = [] for node in doctree.traverse(addnodes.index): @@ -50,8 +47,7 @@ class IndexEntriesCollector(EnvironmentCollector): entries.append(entry) -def setup(app): - # type: (Sphinx) -> Dict +def setup(app: Sphinx) -> Dict[str, Any]: app.add_env_collector(IndexEntriesCollector) return { diff --git a/sphinx/environment/collectors/metadata.py b/sphinx/environment/collectors/metadata.py index 9168e7840..a547d2551 100644 --- a/sphinx/environment/collectors/metadata.py +++ b/sphinx/environment/collectors/metadata.py @@ -8,33 +8,28 @@ :license: BSD, see LICENSE for details. """ -from typing import List, cast +from typing import Any, Dict, List, Set +from typing import cast from docutils import nodes +from sphinx.application import Sphinx +from sphinx.environment import BuildEnvironment from sphinx.environment.collectors import EnvironmentCollector -if False: - # For type annotation - from typing import Dict, Set # NOQA - from sphinx.sphinx import Sphinx # NOQA - from sphinx.environment import BuildEnvironment # NOQA - class MetadataCollector(EnvironmentCollector): """metadata collector for sphinx.environment.""" - def clear_doc(self, app, env, docname): - # type: (Sphinx, BuildEnvironment, str) -> None + def clear_doc(self, app: Sphinx, env: BuildEnvironment, docname: str) -> None: env.metadata.pop(docname, None) - def merge_other(self, app, env, docnames, other): - # type: (Sphinx, BuildEnvironment, Set[str], BuildEnvironment) -> None + def merge_other(self, app: Sphinx, env: BuildEnvironment, + docnames: Set[str], other: BuildEnvironment) -> None: for docname in docnames: env.metadata[docname] = other.metadata[docname] - def process_doc(self, app, doctree): - # type: (Sphinx, nodes.document) -> None + def process_doc(self, app: Sphinx, doctree: nodes.document) -> None: """Process the docinfo part of the doctree as metadata. Keep processing minimal -- just return what docutils says. @@ -67,8 +62,7 @@ class MetadataCollector(EnvironmentCollector): doctree.pop(0) -def setup(app): - # type: (Sphinx) -> Dict +def setup(app: Sphinx) -> Dict[str, Any]: app.add_env_collector(MetadataCollector) return { diff --git a/sphinx/environment/collectors/title.py b/sphinx/environment/collectors/title.py index 0f43eb0af..7d464f874 100644 --- a/sphinx/environment/collectors/title.py +++ b/sphinx/environment/collectors/title.py @@ -8,34 +8,30 @@ :license: BSD, see LICENSE for details. """ +from typing import Any, Dict, Set + from docutils import nodes +from sphinx.application import Sphinx +from sphinx.environment import BuildEnvironment from sphinx.environment.collectors import EnvironmentCollector from sphinx.transforms import SphinxContentsFilter -if False: - # For type annotation - from typing import Dict, Set # NOQA - from sphinx.sphinx import Sphinx # NOQA - from sphinx.environment import BuildEnvironment # NOQA - class TitleCollector(EnvironmentCollector): """title collector for sphinx.environment.""" - def clear_doc(self, app, env, docname): - # type: (Sphinx, BuildEnvironment, str) -> None + def clear_doc(self, app: Sphinx, env: BuildEnvironment, docname: str) -> None: env.titles.pop(docname, None) env.longtitles.pop(docname, None) - def merge_other(self, app, env, docnames, other): - # type: (Sphinx, BuildEnvironment, Set[str], BuildEnvironment) -> None + def merge_other(self, app: Sphinx, env: BuildEnvironment, + docnames: Set[str], other: BuildEnvironment) -> None: for docname in docnames: env.titles[docname] = other.titles[docname] env.longtitles[docname] = other.longtitles[docname] - def process_doc(self, app, doctree): - # type: (Sphinx, nodes.document) -> None + def process_doc(self, app: Sphinx, doctree: nodes.document) -> None: """Add a title node to the document (just copy the first section title), and store that title in the environment. """ @@ -59,8 +55,7 @@ class TitleCollector(EnvironmentCollector): app.env.longtitles[app.env.docname] = longtitlenode -def setup(app): - # type: (Sphinx) -> Dict +def setup(app: Sphinx) -> Dict[str, Any]: app.add_env_collector(TitleCollector) return { diff --git a/sphinx/environment/collectors/toctree.py b/sphinx/environment/collectors/toctree.py index 53009c753..d1e211d2c 100644 --- a/sphinx/environment/collectors/toctree.py +++ b/sphinx/environment/collectors/toctree.py @@ -8,31 +8,29 @@ :license: BSD, see LICENSE for details. """ +from typing import Any, Dict, List, Set, Tuple, Type, TypeVar from typing import cast from docutils import nodes +from docutils.nodes import Element, Node from sphinx import addnodes +from sphinx.application import Sphinx +from sphinx.environment import BuildEnvironment from sphinx.environment.adapters.toctree import TocTree from sphinx.environment.collectors import EnvironmentCollector from sphinx.locale import __ from sphinx.transforms import SphinxContentsFilter from sphinx.util import url_re, logging -if False: - # For type annotation - from typing import Dict, List, Set, Tuple, Type, TypeVar # NOQA - from sphinx.application import Sphinx # NOQA - from sphinx.environment import BuildEnvironment # NOQA - N = TypeVar('N') +N = TypeVar('N') logger = logging.getLogger(__name__) class TocTreeCollector(EnvironmentCollector): - def clear_doc(self, app, env, docname): - # type: (Sphinx, BuildEnvironment, str) -> None + def clear_doc(self, app: Sphinx, env: BuildEnvironment, docname: str) -> None: env.tocs.pop(docname, None) env.toc_secnumbers.pop(docname, None) env.toc_fignumbers.pop(docname, None) @@ -46,8 +44,8 @@ class TocTreeCollector(EnvironmentCollector): if not fnset: del env.files_to_rebuild[subfn] - def merge_other(self, app, env, docnames, other): - # type: (Sphinx, BuildEnvironment, Set[str], BuildEnvironment) -> None + def merge_other(self, app: Sphinx, env: BuildEnvironment, docnames: Set[str], + other: BuildEnvironment) -> None: for docname in docnames: env.tocs[docname] = other.tocs[docname] env.toc_num_entries[docname] = other.toc_num_entries[docname] @@ -61,14 +59,12 @@ class TocTreeCollector(EnvironmentCollector): for subfn, fnset in other.files_to_rebuild.items(): env.files_to_rebuild.setdefault(subfn, set()).update(fnset & set(docnames)) - def process_doc(self, app, doctree): - # type: (Sphinx, nodes.document) -> None + def process_doc(self, app: Sphinx, doctree: nodes.document) -> None: """Build a TOC from the doctree and store it in the inventory.""" docname = app.env.docname numentries = [0] # nonlocal again... - def traverse_in_section(node, cls): - # type: (nodes.Element, Type[N]) -> List[N] + def traverse_in_section(node: Element, cls: Type[N]) -> List[N]: """Like traverse(), but stay within the same section.""" result = [] # type: List[N] if isinstance(node, cls): @@ -80,9 +76,8 @@ class TocTreeCollector(EnvironmentCollector): result.extend(traverse_in_section(child, cls)) return result - def build_toc(node, depth=1): - # type: (nodes.Element, int) -> nodes.bullet_list - entries = [] # type: List[nodes.Element] + def build_toc(node: Element, depth: int = 1) -> nodes.bullet_list: + entries = [] # type: List[Element] for sectionnode in node: # find all toctree nodes in this section and add them # to the toc (just copying the toctree node which is then @@ -107,7 +102,7 @@ class TocTreeCollector(EnvironmentCollector): '', '', internal=True, refuri=docname, anchorname=anchorname, *nodetext) para = addnodes.compact_paragraph('', '', reference) - item = nodes.list_item('', para) # type: nodes.Element + item = nodes.list_item('', para) # type: Element sub_item = build_toc(sectionnode, depth + 1) if sub_item: item += sub_item @@ -135,12 +130,10 @@ class TocTreeCollector(EnvironmentCollector): app.env.tocs[docname] = nodes.bullet_list('') app.env.toc_num_entries[docname] = numentries[0] - def get_updated_docs(self, app, env): - # type: (Sphinx, BuildEnvironment) -> List[str] + def get_updated_docs(self, app: Sphinx, env: BuildEnvironment) -> List[str]: return self.assign_section_numbers(env) + self.assign_figure_numbers(env) - def assign_section_numbers(self, env): - # type: (BuildEnvironment) -> List[str] + def assign_section_numbers(self, env: BuildEnvironment) -> List[str]: """Assign a section number to each heading under a numbered toctree.""" # a list of all docnames whose section numbers changed rewrite_needed = [] @@ -149,8 +142,7 @@ class TocTreeCollector(EnvironmentCollector): old_secnumbers = env.toc_secnumbers env.toc_secnumbers = {} - def _walk_toc(node, secnums, depth, titlenode=None): - # type: (nodes.Element, Dict, int, nodes.title) -> None + def _walk_toc(node: Element, secnums: Dict, depth: int, titlenode: nodes.title = None) -> None: # NOQA # titlenode is the title of the document, it will get assigned a # secnumber too, so that it shows up in next/prev/parent rellinks for subnode in node.children: @@ -184,8 +176,7 @@ class TocTreeCollector(EnvironmentCollector): elif isinstance(subnode, addnodes.toctree): _walk_toctree(subnode, depth) - def _walk_toctree(toctreenode, depth): - # type: (addnodes.toctree, int) -> None + def _walk_toctree(toctreenode: addnodes.toctree, depth: int) -> None: if depth == 0: return for (title, ref) in toctreenode['entries']: @@ -216,8 +207,7 @@ class TocTreeCollector(EnvironmentCollector): return rewrite_needed - def assign_figure_numbers(self, env): - # type: (BuildEnvironment) -> List[str] + def assign_figure_numbers(self, env: BuildEnvironment) -> List[str]: """Assign a figure number to each figure under a numbered toctree.""" rewrite_needed = [] @@ -227,8 +217,7 @@ class TocTreeCollector(EnvironmentCollector): env.toc_fignumbers = {} fignum_counter = {} # type: Dict[str, Dict[Tuple[int, ...], int]] - def get_figtype(node): - # type: (nodes.Node) -> str + def get_figtype(node: Node) -> str: for domain in env.domains.values(): figtype = domain.get_enumerable_node_type(node) if figtype: @@ -236,8 +225,7 @@ class TocTreeCollector(EnvironmentCollector): return None - def get_section_number(docname, section): - # type: (str, nodes.section) -> Tuple[int, ...] + def get_section_number(docname: str, section: nodes.section) -> Tuple[int, ...]: anchorname = '#' + section['ids'][0] secnumbers = env.toc_secnumbers.get(docname, {}) if anchorname in secnumbers: @@ -247,24 +235,22 @@ class TocTreeCollector(EnvironmentCollector): return secnum or tuple() - def get_next_fignumber(figtype, secnum): - # type: (str, Tuple[int, ...]) -> Tuple[int, ...] + def get_next_fignumber(figtype: str, secnum: Tuple[int, ...]) -> Tuple[int, ...]: counter = fignum_counter.setdefault(figtype, {}) secnum = secnum[:env.config.numfig_secnum_depth] counter[secnum] = counter.get(secnum, 0) + 1 return secnum + (counter[secnum],) - def register_fignumber(docname, secnum, figtype, fignode): - # type: (str, Tuple[int, ...], str, nodes.Element) -> None + def register_fignumber(docname: str, secnum: Tuple[int, ...], + figtype: str, fignode: Element) -> None: env.toc_fignumbers.setdefault(docname, {}) fignumbers = env.toc_fignumbers[docname].setdefault(figtype, {}) figure_id = fignode['ids'][0] fignumbers[figure_id] = get_next_fignumber(figtype, secnum) - def _walk_doctree(docname, doctree, secnum): - # type: (str, nodes.Element, Tuple[int, ...]) -> None + def _walk_doctree(docname: str, doctree: Element, secnum: Tuple[int, ...]) -> None: for subnode in doctree.children: if isinstance(subnode, nodes.section): next_secnum = get_section_number(docname, subnode) @@ -286,8 +272,7 @@ class TocTreeCollector(EnvironmentCollector): _walk_doctree(docname, subnode, secnum) - def _walk_doc(docname, secnum): - # type: (str, Tuple[int, ...]) -> None + def _walk_doc(docname: str, secnum: Tuple[int, ...]) -> None: if docname not in assigned: assigned.add(docname) doctree = env.get_doctree(docname) @@ -302,8 +287,7 @@ class TocTreeCollector(EnvironmentCollector): return rewrite_needed -def setup(app): - # type: (Sphinx) -> Dict +def setup(app: Sphinx) -> Dict[str, Any]: app.add_env_collector(TocTreeCollector) return { diff --git a/sphinx/ext/apidoc.py b/sphinx/ext/apidoc.py index d38b749f0..b1d19e5cb 100644 --- a/sphinx/ext/apidoc.py +++ b/sphinx/ext/apidoc.py @@ -22,6 +22,7 @@ import sys import warnings from fnmatch import fnmatch from os import path +from typing import Any, List, Tuple import sphinx.locale from sphinx import __display_version__, package_dir @@ -32,10 +33,6 @@ from sphinx.util import rst from sphinx.util.osutil import FileAvoidWrite, ensuredir from sphinx.util.template import ReSTRenderer -if False: - # For type annotation - from typing import Any, List, Tuple # NOQA - # automodule options if 'SPHINX_APIDOC_OPTIONS' in os.environ: OPTIONS = os.environ['SPHINX_APIDOC_OPTIONS'].split(',') @@ -53,8 +50,7 @@ PY_SUFFIXES = {'.py', '.pyx'} template_dir = path.join(package_dir, 'templates', 'apidoc') -def makename(package, module): - # type: (str, str) -> str +def makename(package: str, module: str) -> str: """Join package and module with a dot.""" warnings.warn('makename() is deprecated.', RemovedInSphinx40Warning) @@ -68,14 +64,12 @@ def makename(package, module): return name -def module_join(*modnames): - # type: (*str) -> str +def module_join(*modnames: str) -> str: """Join module names with dots.""" return '.'.join(filter(None, modnames)) -def write_file(name, text, opts): - # type: (str, str, Any) -> None +def write_file(name: str, text: str, opts: Any) -> None: """Write the output file for module/package .""" fname = path.join(opts.destdir, '%s.%s' % (name, opts.suffix)) if opts.dryrun: @@ -89,8 +83,7 @@ def write_file(name, text, opts): f.write(text) -def format_heading(level, text, escape=True): - # type: (int, str, bool) -> str +def format_heading(level: int, text: str, escape: bool = True) -> str: """Create a heading of [1, 2 or 3 supported].""" warnings.warn('format_warning() is deprecated.', RemovedInSphinx40Warning) @@ -100,8 +93,7 @@ def format_heading(level, text, escape=True): return '%s\n%s\n\n' % (text, underlining) -def format_directive(module, package=None): - # type: (str, str) -> str +def format_directive(module: str, package: str = None) -> str: """Create the automodule directive and add the options.""" warnings.warn('format_directive() is deprecated.', RemovedInSphinx40Warning) @@ -111,8 +103,8 @@ def format_directive(module, package=None): return directive -def create_module_file(package, basename, opts): - # type: (str, str, Any) -> None +def create_module_file(package: str, basename: str, opts: Any, + user_template_dir: str = None) -> None: """Build the text of the file and write the file.""" qualname = module_join(package, basename) context = { @@ -121,12 +113,13 @@ def create_module_file(package, basename, opts): 'qualname': qualname, 'automodule_options': OPTIONS, } - text = ReSTRenderer(template_dir).render('module.rst', context) + text = ReSTRenderer([user_template_dir, template_dir]).render('module.rst_t', context) write_file(qualname, text, opts) -def create_package_file(root, master_package, subroot, py_files, opts, subs, is_namespace, excludes=[]): # NOQA - # type: (str, str, str, List[str], Any, List[str], bool, List[str]) -> None +def create_package_file(root: str, master_package: str, subroot: str, py_files: List[str], + opts: Any, subs: List[str], is_namespace: bool, + excludes: List[str] = [], user_template_dir: str = None) -> None: """Build the text of the file and write the file.""" # build a list of sub packages (directories containing an INITPY file) subpackages = [sub for sub in subs if not @@ -151,16 +144,16 @@ def create_package_file(root, master_package, subroot, py_files, opts, subs, is_ 'automodule_options': OPTIONS, 'show_headings': not opts.noheadings, } - text = ReSTRenderer(template_dir).render('package.rst', context) + text = ReSTRenderer([user_template_dir, template_dir]).render('package.rst_t', context) write_file(pkgname, text, opts) if submodules and opts.separatemodules: for submodule in submodules: - create_module_file(None, submodule, opts) + create_module_file(None, submodule, opts, user_template_dir) -def create_modules_toc_file(modules, opts, name='modules'): - # type: (List[str], Any, str) -> None +def create_modules_toc_file(modules: List[str], opts: Any, name: str = 'modules', + user_template_dir: str = None) -> None: """Create the module's index.""" modules.sort() prev_module = '' @@ -176,12 +169,11 @@ def create_modules_toc_file(modules, opts, name='modules'): 'maxdepth': opts.maxdepth, 'docnames': modules, } - text = ReSTRenderer(template_dir).render('toc.rst', context) + text = ReSTRenderer([user_template_dir, template_dir]).render('toc.rst_t', context) write_file(name, text, opts) -def shall_skip(module, opts, excludes=[]): - # type: (str, Any, List[str]) -> bool +def shall_skip(module: str, opts: Any, excludes: List[str] = []) -> bool: """Check if we want to skip this module.""" # skip if the file doesn't exist and not using implicit namespaces if not opts.implicit_namespaces and not path.exists(module): @@ -207,8 +199,7 @@ def shall_skip(module, opts, excludes=[]): return False -def is_skipped_module(filename, opts, excludes): - # type: (str, Any, List[str]) -> bool +def is_skipped_module(filename: str, opts: Any, excludes: List[str]) -> bool: """Check if we want to skip this module.""" if not path.exists(filename): # skip if the file doesn't exist @@ -220,8 +211,8 @@ def is_skipped_module(filename, opts, excludes): return False -def recurse_tree(rootpath, excludes, opts): - # type: (str, List[str], Any) -> List[str] +def recurse_tree(rootpath: str, excludes: List[str], opts: Any, + user_template_dir: str = None) -> List[str]: """ Look for every file in the directory tree and create the corresponding ReST files. @@ -271,7 +262,8 @@ def recurse_tree(rootpath, excludes, opts): # a namespace and there is something there to document if not is_namespace or len(py_files) > 0: create_package_file(root, root_package, subpackage, - py_files, opts, subs, is_namespace, excludes) + py_files, opts, subs, is_namespace, excludes, + user_template_dir) toplevels.append(module_join(root_package, subpackage)) else: # if we are at the root level, we don't require it to be a package @@ -279,14 +271,13 @@ def recurse_tree(rootpath, excludes, opts): for py_file in py_files: if not is_skipped_module(path.join(rootpath, py_file), opts, excludes): module = path.splitext(py_file)[0] - create_module_file(root_package, module, opts) + create_module_file(root_package, module, opts, user_template_dir) toplevels.append(module) return toplevels -def is_excluded(root, excludes): - # type: (str, List[str]) -> bool +def is_excluded(root: str, excludes: List[str]) -> bool: """Check if the directory is in the exclude list. Note: by having trailing slashes, we avoid common prefix issues, like @@ -298,8 +289,7 @@ def is_excluded(root, excludes): return False -def get_parser(): - # type: () -> argparse.ArgumentParser +def get_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( usage='%(prog)s [OPTIONS] -o ' '[EXCLUDE_PATTERN, ...]', @@ -386,11 +376,15 @@ Note: By default this script will not overwrite already created files.""")) const='sphinx.ext.%s' % ext, dest='extensions', help=__('enable %s extension') % ext) + group = parser.add_argument_group(__('Project templating')) + group.add_argument('-t', '--templatedir', metavar='TEMPLATEDIR', + dest='templatedir', + help=__('template directory for template files')) + return parser -def main(argv=sys.argv[1:]): - # type: (List[str]) -> int +def main(argv: List[str] = sys.argv[1:]) -> int: """Parse and check the command line arguments.""" sphinx.locale.setlocale(locale.LC_ALL, '') sphinx.locale.init_console(os.path.join(package_dir, 'locale'), 'sphinx') @@ -412,7 +406,7 @@ def main(argv=sys.argv[1:]): if not args.dryrun: ensuredir(args.destdir) excludes = [path.abspath(exclude) for exclude in args.exclude_pattern] - modules = recurse_tree(rootpath, excludes, args) + modules = recurse_tree(rootpath, excludes, args, args.templatedir) if args.full: from sphinx.cmd import quickstart as qs @@ -455,9 +449,10 @@ def main(argv=sys.argv[1:]): d['extensions'].extend(ext.split(',')) if not args.dryrun: - qs.generate(d, silent=True, overwrite=args.force) + qs.generate(d, silent=True, overwrite=args.force, + templatedir=args.templatedir) elif args.tocfile: - create_modules_toc_file(modules, args, args.tocfile) + create_modules_toc_file(modules, args, args.tocfile, args.templatedir) return 0 diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 7172553ac..4273351bf 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -12,15 +12,18 @@ import re import warnings -from typing import Any +from types import ModuleType +from typing import Any, Callable, Dict, Iterator, List, Sequence, Set, Tuple, Type, Union from docutils.statemachine import StringList import sphinx -from sphinx.config import ENUM +from sphinx.application import Sphinx +from sphinx.config import Config, ENUM from sphinx.deprecation import ( RemovedInSphinx30Warning, RemovedInSphinx40Warning, deprecated_alias ) +from sphinx.environment import BuildEnvironment from sphinx.ext.autodoc.importer import import_object, get_object_members from sphinx.ext.autodoc.mock import mock from sphinx.locale import _, __ @@ -35,12 +38,8 @@ from sphinx.util.inspect import ( if False: # For type annotation - from types import ModuleType # NOQA - from typing import Callable, Dict, Iterator, List, Sequence, Set, Tuple, Type, Union # NOQA - from sphinx.application import Sphinx # NOQA - from sphinx.config import Config # NOQA - from sphinx.environment import BuildEnvironment # NOQA - from sphinx.ext.autodoc.directive import DocumenterBridge # NOQA + from sphinx.ext.autodoc.directive import DocumenterBridge + logger = logging.getLogger(__name__) @@ -61,8 +60,7 @@ py_ext_sig_re = re.compile( ''', re.VERBOSE) -def identity(x): - # type: (Any) -> Any +def identity(x: Any) -> Any: return x @@ -71,16 +69,14 @@ INSTANCEATTR = object() SLOTSATTR = object() -def members_option(arg): - # type: (Any) -> Union[object, List[str]] +def members_option(arg: Any) -> Union[object, List[str]]: """Used to convert the :members: option to auto directives.""" if arg is None or arg is True: return ALL return [x.strip() for x in arg.split(',')] -def members_set_option(arg): - # type: (Any) -> Union[object, Set[str]] +def members_set_option(arg: Any) -> Union[object, Set[str]]: """Used to convert the :members: option to auto directives.""" if arg is None: return ALL @@ -90,8 +86,7 @@ def members_set_option(arg): SUPPRESS = object() -def annotation_option(arg): - # type: (Any) -> Any +def annotation_option(arg: Any) -> Any: if arg is None: # suppress showing the representation of the object return SUPPRESS @@ -99,16 +94,14 @@ def annotation_option(arg): return arg -def bool_option(arg): - # type: (Any) -> bool +def bool_option(arg: Any) -> bool: """Used to convert flag options to auto directives. (Instead of directives.flag(), which returns None). """ return True -def merge_special_members_option(options): - # type: (Dict) -> None +def merge_special_members_option(options: Dict) -> None: """Merge :special-members: option to :members: option.""" if 'special-members' in options and options['special-members'] is not ALL: if options.get('members') is ALL: @@ -123,8 +116,7 @@ def merge_special_members_option(options): # Some useful event listener factories for autodoc-process-docstring. -def cut_lines(pre, post=0, what=None): - # type: (int, int, str) -> Callable +def cut_lines(pre: int, post: int = 0, what: str = None) -> Callable: """Return a listener that removes the first *pre* and last *post* lines of every docstring. If *what* is a sequence of strings, only docstrings of a type in *what* will be processed. @@ -136,8 +128,8 @@ def cut_lines(pre, post=0, what=None): This can (and should) be used in place of :confval:`automodule_skip_lines`. """ - def process(app, what_, name, obj, options, lines): - # type: (Sphinx, str, str, Any, Any, List[str]) -> None + def process(app: Sphinx, what_: str, name: str, obj: Any, options: Any, lines: List[str] + ) -> None: if what and what_ not in what: return del lines[:pre] @@ -152,8 +144,8 @@ def cut_lines(pre, post=0, what=None): return process -def between(marker, what=None, keepempty=False, exclude=False): - # type: (str, Sequence[str], bool, bool) -> Callable +def between(marker: str, what: Sequence[str] = None, keepempty: bool = False, + exclude: bool = False) -> Callable: """Return a listener that either keeps, or if *exclude* is True excludes, lines between lines that match the *marker* regular expression. If no line matches, the resulting docstring would be empty, so no change will be made @@ -164,8 +156,8 @@ def between(marker, what=None, keepempty=False, exclude=False): """ marker_re = re.compile(marker) - def process(app, what_, name, obj, options, lines): - # type: (Sphinx, str, str, Any, Any, List[str]) -> None + def process(app: Sphinx, what_: str, name: str, obj: Any, options: Any, lines: List[str] + ) -> None: if what and what_ not in what: return deleted = 0 @@ -192,8 +184,7 @@ def between(marker, what=None, keepempty=False, exclude=False): # But we define this class here to keep compatibility (see #4538) class Options(dict): """A dict/attribute hybrid that returns None on nonexisting keys.""" - def __getattr__(self, name): - # type: (str) -> Any + def __getattr__(self, name: str) -> Any: try: return self[name.replace('_', '-')] except KeyError: @@ -229,19 +220,17 @@ class Documenter: option_spec = {'noindex': bool_option} # type: Dict[str, Callable] - def get_attr(self, obj, name, *defargs): - # type: (Any, str, Any) -> Any + def get_attr(self, obj: Any, name: str, *defargs) -> Any: """getattr() override for types such as Zope interfaces.""" return autodoc_attrgetter(self.env.app, obj, name, *defargs) @classmethod - def can_document_member(cls, member, membername, isattr, parent): - # type: (Any, str, bool, Any) -> bool + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: """Called to see if a member can be documented by this documenter.""" raise NotImplementedError('must be implemented in subclasses') - def __init__(self, directive, name, indent=''): - # type: (DocumenterBridge, str, str) -> None + def __init__(self, directive: "DocumenterBridge", name: str, indent: str = '') -> None: self.directive = directive self.env = directive.env # type: BuildEnvironment self.options = directive.genopt @@ -266,18 +255,16 @@ class Documenter: self.analyzer = None # type: ModuleAnalyzer @property - def documenters(self): - # type: () -> Dict[str, Type[Documenter]] + def documenters(self) -> Dict[str, Type["Documenter"]]: """Returns registered Documenter classes""" return get_documenters(self.env.app) - def add_line(self, line, source, *lineno): - # type: (str, str, int) -> None + def add_line(self, line: str, source: str, *lineno: int) -> None: """Append one line of generated reST to the output.""" self.directive.result.append(self.indent + line, source, *lineno) - def resolve_name(self, modname, parents, path, base): - # type: (str, Any, str, Any) -> Tuple[str, List[str]] + def resolve_name(self, modname: str, parents: Any, path: str, base: Any + ) -> Tuple[str, List[str]]: """Resolve the module and name of the object to document given by the arguments and the current module/class. @@ -287,8 +274,7 @@ class Documenter: """ raise NotImplementedError('must be implemented in subclasses') - def parse_name(self): - # type: () -> bool + def parse_name(self) -> bool: """Determine what module to import and what attribute to document. Returns True and sets *self.modname*, *self.objpath*, *self.fullname*, @@ -324,8 +310,7 @@ class Documenter: (self.objpath and '.' + '.'.join(self.objpath) or '') return True - def import_object(self): - # type: () -> bool + def import_object(self) -> bool: """Import the object given by *self.modname* and *self.objpath* and set it as *self.object*. @@ -343,8 +328,7 @@ class Documenter: self.env.note_reread() return False - def get_real_modname(self): - # type: () -> str + def get_real_modname(self) -> str: """Get the real module name of an object to document. It can differ from the name of the module through which the object was @@ -352,8 +336,7 @@ class Documenter: """ return self.get_attr(self.object, '__module__', None) or self.modname - def check_module(self): - # type: () -> bool + def check_module(self) -> bool: """Check if *self.object* is really defined in the module given by *self.modname*. """ @@ -367,16 +350,14 @@ class Documenter: return False return True - def format_args(self, **kwargs): - # type: (Any) -> str + def format_args(self, **kwargs) -> str: """Format the argument signature of *self.object*. Should return None if the object does not have a signature. """ return None - def format_name(self): - # type: () -> str + def format_name(self) -> str: """Format the name of *self.object*. This normally should be something that can be parsed by the generated @@ -387,8 +368,7 @@ class Documenter: # directives of course) return '.'.join(self.objpath) or self.modname - def format_signature(self, **kwargs): - # type: (Any) -> str + def format_signature(self, **kwargs) -> str: """Format the signature (arguments and return annotation) of the object. Let the user process it via the ``autodoc-process-signature`` event. @@ -422,8 +402,7 @@ class Documenter: else: return '' - def add_directive_header(self, sig): - # type: (str) -> None + def add_directive_header(self, sig: str) -> None: """Add the directive header and options to the generated content.""" domain = getattr(self, 'domain', 'py') directive = getattr(self, 'directivetype', self.objtype) @@ -438,8 +417,7 @@ class Documenter: # etc. don't support a prepended module name self.add_line(' :module: %s' % self.modname, sourcename) - def get_doc(self, encoding=None, ignore=1): - # type: (str, int) -> List[List[str]] + def get_doc(self, encoding: str = None, ignore: int = 1) -> List[List[str]]: """Decode and return lines of the docstring(s) for the object.""" if encoding is not None: warnings.warn("The 'encoding' argument to autodoc.%s.get_doc() is deprecated." @@ -452,8 +430,7 @@ class Documenter: return [prepare_docstring(docstring, ignore, tab_width)] return [] - def process_doc(self, docstrings): - # type: (List[List[str]]) -> Iterator[str] + def process_doc(self, docstrings: List[List[str]]) -> Iterator[str]: """Let the user process the docstrings before adding them.""" for docstringlines in docstrings: if self.env.app: @@ -463,14 +440,12 @@ class Documenter: self.options, docstringlines) yield from docstringlines - def get_sourcename(self): - # type: () -> str + def get_sourcename(self) -> str: if self.analyzer: return '%s:docstring of %s' % (self.analyzer.srcname, self.fullname) return 'docstring of %s' % self.fullname - def add_content(self, more_content, no_docstring=False): - # type: (Any, bool) -> None + def add_content(self, more_content: Any, no_docstring: bool = False) -> None: """Add content from docstrings, attribute documentation and user.""" # set sourcename and add content from attribute documentation sourcename = self.get_sourcename() @@ -500,8 +475,7 @@ class Documenter: for line, src in zip(more_content.data, more_content.items): self.add_line(line, src[0], src[1]) - def get_object_members(self, want_all): - # type: (bool) -> Tuple[bool, List[Tuple[str, Any]]] + def get_object_members(self, want_all: bool) -> Tuple[bool, List[Tuple[str, Any]]]: """Return `(members_check_module, members)` where `members` is a list of `(membername, member)` pairs of the members of *self.object*. @@ -527,8 +501,8 @@ class Documenter: return False, sorted((m.name, m.value) for m in members.values() if m.directly_defined) - def filter_members(self, members, want_all): - # type: (List[Tuple[str, Any]], bool) -> List[Tuple[str, Any, bool]] + def filter_members(self, members: List[Tuple[str, Any]], want_all: bool + ) -> List[Tuple[str, Any, bool]]: """Filter the given member list. Members are skipped if @@ -616,8 +590,7 @@ class Documenter: return ret - def document_members(self, all_members=False): - # type: (bool) -> None + def document_members(self, all_members: bool = False) -> None: """Generate reST for member documentation. If *all_members* is True, do all members, else those given by @@ -669,8 +642,7 @@ class Documenter: # sort by source order, by virtue of the module analyzer tagorder = self.analyzer.tagorder - def keyfunc(entry): - # type: (Tuple[Documenter, bool]) -> int + def keyfunc(entry: Tuple[Documenter, bool]) -> int: fullname = entry[0].name.split('::')[1] return tagorder.get(fullname, len(tagorder)) memberdocumenters.sort(key=keyfunc) @@ -684,9 +656,8 @@ class Documenter: self.env.temp_data['autodoc:module'] = None self.env.temp_data['autodoc:class'] = None - def generate(self, more_content=None, real_modname=None, - check_module=False, all_members=False): - # type: (Any, str, bool, bool) -> None + def generate(self, more_content: Any = None, real_modname: str = None, + check_module: bool = False, all_members: bool = False) -> None: """Generate reST for the object given by *self.name*, and possibly for its members. @@ -778,26 +749,24 @@ class ModuleDocumenter(Documenter): 'imported-members': bool_option, 'ignore-module-all': bool_option } # type: Dict[str, Callable] - def __init__(self, *args): - # type: (Any) -> None + def __init__(self, *args) -> None: super().__init__(*args) merge_special_members_option(self.options) @classmethod - def can_document_member(cls, member, membername, isattr, parent): - # type: (Any, str, bool, Any) -> bool + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: # don't document submodules automatically return False - def resolve_name(self, modname, parents, path, base): - # type: (str, Any, str, Any) -> Tuple[str, List[str]] + def resolve_name(self, modname: str, parents: Any, path: str, base: Any + ) -> Tuple[str, List[str]]: if modname is not None: logger.warning(__('"::" in automodule name doesn\'t make sense'), type='autodoc') return (path or '') + base, [] - def parse_name(self): - # type: () -> bool + def parse_name(self) -> bool: ret = super().parse_name() if self.args or self.retann: logger.warning(__('signature arguments or return annotation ' @@ -805,8 +774,7 @@ class ModuleDocumenter(Documenter): type='autodoc') return ret - def add_directive_header(self, sig): - # type: (str) -> None + def add_directive_header(self, sig: str) -> None: Documenter.add_directive_header(self, sig) sourcename = self.get_sourcename() @@ -819,8 +787,7 @@ class ModuleDocumenter(Documenter): if self.options.deprecated: self.add_line(' :deprecated:', sourcename) - def get_object_members(self, want_all): - # type: (bool) -> Tuple[bool, List[Tuple[str, object]]] + def get_object_members(self, want_all: bool) -> Tuple[bool, List[Tuple[str, object]]]: if want_all: if (self.options.ignore_module_all or not hasattr(self.object, '__all__')): @@ -861,8 +828,8 @@ class ModuleLevelDocumenter(Documenter): Specialized Documenter subclass for objects on module level (functions, classes, data/constants). """ - def resolve_name(self, modname, parents, path, base): - # type: (str, Any, str, Any) -> Tuple[str, List[str]] + def resolve_name(self, modname: str, parents: Any, path: str, base: Any + ) -> Tuple[str, List[str]]: if modname is None: if path: modname = path.rstrip('.') @@ -882,8 +849,8 @@ class ClassLevelDocumenter(Documenter): Specialized Documenter subclass for objects on class level (methods, attributes). """ - def resolve_name(self, modname, parents, path, base): - # type: (str, Any, str, Any) -> Tuple[str, List[str]] + def resolve_name(self, modname: str, parents: Any, path: str, base: Any + ) -> Tuple[str, List[str]]: if modname is None: if path: mod_cls = path.rstrip('.') @@ -916,8 +883,7 @@ class DocstringSignatureMixin: feature of reading the signature from the docstring. """ - def _find_signature(self, encoding=None): - # type: (str) -> Tuple[str, str] + def _find_signature(self, encoding: str = None) -> Tuple[str, str]: if encoding is not None: warnings.warn("The 'encoding' argument to autodoc.%s._find_signature() is " "deprecated." % self.__class__.__name__, @@ -951,8 +917,7 @@ class DocstringSignatureMixin: break return result - def get_doc(self, encoding=None, ignore=1): - # type: (str, int) -> List[List[str]] + def get_doc(self, encoding: str = None, ignore: int = 1) -> List[List[str]]: if encoding is not None: warnings.warn("The 'encoding' argument to autodoc.%s.get_doc() is deprecated." % self.__class__.__name__, @@ -962,8 +927,7 @@ class DocstringSignatureMixin: return lines return super().get_doc(None, ignore) # type: ignore - def format_signature(self, **kwargs): - # type: (Any) -> str + def format_signature(self, **kwargs) -> str: if self.args is None and self.env.config.autodoc_docstring_signature: # type: ignore # only act if a signature is not explicitly given already, and if # the feature is enabled @@ -978,8 +942,7 @@ class DocstringStripSignatureMixin(DocstringSignatureMixin): Mixin for AttributeDocumenter to provide the feature of stripping any function signature from the docstring. """ - def format_signature(self, **kwargs): - # type: (Any) -> str + def format_signature(self, **kwargs) -> str: if self.args is None and self.env.config.autodoc_docstring_signature: # type: ignore # only act if a signature is not explicitly given already, and if # the feature is enabled @@ -1000,14 +963,13 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ member_order = 30 @classmethod - def can_document_member(cls, member, membername, isattr, parent): - # type: (Any, str, bool, Any) -> bool + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: # supports functions, builtins and bound methods exported at the module level return (inspect.isfunction(member) or inspect.isbuiltin(member) or (inspect.isroutine(member) and isinstance(parent, ModuleDocumenter))) - def format_args(self, **kwargs): - # type: (Any) -> str + def format_args(self, **kwargs) -> str: if self.env.config.autodoc_typehints == 'none': kwargs.setdefault('show_annotation', False) @@ -1042,12 +1004,10 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ args = args.replace('\\', '\\\\') return args - def document_members(self, all_members=False): - # type: (bool) -> None + def document_members(self, all_members: bool = False) -> None: pass - def add_directive_header(self, sig): - # type: (str) -> None + def add_directive_header(self, sig: str) -> None: sourcename = self.get_sourcename() super().add_directive_header(sig) @@ -1086,18 +1046,16 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: 'private-members': bool_option, 'special-members': members_option, } # type: Dict[str, Callable] - def __init__(self, *args): - # type: (Any) -> None + def __init__(self, *args) -> None: super().__init__(*args) merge_special_members_option(self.options) @classmethod - def can_document_member(cls, member, membername, isattr, parent): - # type: (Any, str, bool, Any) -> bool + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: return isinstance(member, type) - def import_object(self): - # type: () -> Any + def import_object(self) -> Any: ret = super().import_object() # if the class is documented under another name, document it # as data/attribute @@ -1108,8 +1066,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: self.doc_as_attr = True return ret - def format_args(self, **kwargs): - # type: (Any) -> str + def format_args(self, **kwargs) -> str: if self.env.config.autodoc_typehints == 'none': kwargs.setdefault('show_annotation', False) @@ -1129,15 +1086,13 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: # with __init__ in C return None - def format_signature(self, **kwargs): - # type: (Any) -> str + def format_signature(self, **kwargs) -> str: if self.doc_as_attr: return '' return super().format_signature(**kwargs) - def add_directive_header(self, sig): - # type: (str) -> None + def add_directive_header(self, sig: str) -> None: if self.doc_as_attr: self.directivetype = 'attribute' super().add_directive_header(sig) @@ -1154,8 +1109,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: self.add_line(' ' + _('Bases: %s') % ', '.join(bases), sourcename) - def get_doc(self, encoding=None, ignore=1): - # type: (str, int) -> List[List[str]] + def get_doc(self, encoding: str = None, ignore: int = 1) -> List[List[str]]: if encoding is not None: warnings.warn("The 'encoding' argument to autodoc.%s.get_doc() is deprecated." % self.__class__.__name__, @@ -1199,8 +1153,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: tab_width = self.directive.state.document.settings.tab_width return [prepare_docstring(docstring, ignore, tab_width) for docstring in docstrings] - def add_content(self, more_content, no_docstring=False): - # type: (Any, bool) -> None + def add_content(self, more_content: Any, no_docstring: bool = False) -> None: if self.doc_as_attr: classname = safe_getattr(self.object, '__qualname__', None) if not classname: @@ -1215,15 +1168,13 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: else: super().add_content(more_content) - def document_members(self, all_members=False): - # type: (bool) -> None + def document_members(self, all_members: bool = False) -> None: if self.doc_as_attr: return super().document_members(all_members) - def generate(self, more_content=None, real_modname=None, - check_module=False, all_members=False): - # type: (Any, str, bool, bool) -> None + def generate(self, more_content: Any = None, real_modname: str = None, + check_module: bool = False, all_members: bool = False) -> None: # Do not pass real_modname and use the name from the __module__ # attribute of the class. # If a class gets imported into the module real_modname @@ -1245,8 +1196,8 @@ class ExceptionDocumenter(ClassDocumenter): priority = 10 @classmethod - def can_document_member(cls, member, membername, isattr, parent): - # type: (Any, str, bool, Any) -> bool + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: return isinstance(member, type) and issubclass(member, BaseException) @@ -1261,12 +1212,11 @@ class DataDocumenter(ModuleLevelDocumenter): option_spec["annotation"] = annotation_option @classmethod - def can_document_member(cls, member, membername, isattr, parent): - # type: (Any, str, bool, Any) -> bool + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: return isinstance(parent, ModuleDocumenter) and isattr - def add_directive_header(self, sig): - # type: (str) -> None + def add_directive_header(self, sig: str) -> None: super().add_directive_header(sig) sourcename = self.get_sourcename() if not self.options.annotation: @@ -1282,12 +1232,10 @@ class DataDocumenter(ModuleLevelDocumenter): self.add_line(' :annotation: %s' % self.options.annotation, sourcename) - def document_members(self, all_members=False): - # type: (bool) -> None + def document_members(self, all_members: bool = False) -> None: pass - def get_real_modname(self): - # type: () -> str + def get_real_modname(self) -> str: return self.get_attr(self.parent or self.object, '__module__', None) \ or self.modname @@ -1302,13 +1250,12 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: priority = 1 # must be more than FunctionDocumenter @classmethod - def can_document_member(cls, member, membername, isattr, parent): - # type: (Any, str, bool, Any) -> bool + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: return inspect.isroutine(member) and \ not isinstance(parent, ModuleDocumenter) - def import_object(self): - # type: () -> Any + def import_object(self) -> Any: ret = super().import_object() if not ret: return ret @@ -1325,8 +1272,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: return ret - def format_args(self, **kwargs): - # type: (Any) -> str + def format_args(self, **kwargs) -> str: if self.env.config.autodoc_typehints == 'none': kwargs.setdefault('show_annotation', False) @@ -1341,8 +1287,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: args = args.replace('\\', '\\\\') return args - def add_directive_header(self, sig): - # type: (str) -> None + def add_directive_header(self, sig) -> None: super().add_directive_header(sig) sourcename = self.get_sourcename() @@ -1356,8 +1301,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: if inspect.isstaticmethod(obj, cls=self.parent, name=self.object_name): self.add_line(' :staticmethod:', sourcename) - def document_members(self, all_members=False): - # type: (bool) -> None + def document_members(self, all_members: bool = False) -> None: pass @@ -1375,13 +1319,12 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): priority = 10 @staticmethod - def is_function_or_method(obj): - # type: (Any) -> bool + def is_function_or_method(obj: Any) -> bool: return inspect.isfunction(obj) or inspect.isbuiltin(obj) or inspect.ismethod(obj) @classmethod - def can_document_member(cls, member, membername, isattr, parent): - # type: (Any, str, bool, Any) -> bool + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: if inspect.isattributedescriptor(member): return True elif (not isinstance(parent, ModuleDocumenter) and @@ -1391,12 +1334,10 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): else: return False - def document_members(self, all_members=False): - # type: (bool) -> None + def document_members(self, all_members: bool = False) -> None: pass - def import_object(self): - # type: () -> Any + def import_object(self) -> Any: ret = super().import_object() if inspect.isenumattribute(self.object): self.object = self.object.value @@ -1407,13 +1348,11 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): self._datadescriptor = False return ret - def get_real_modname(self): - # type: () -> str + def get_real_modname(self) -> str: return self.get_attr(self.parent or self.object, '__module__', None) \ or self.modname - def add_directive_header(self, sig): - # type: (str) -> None + def add_directive_header(self, sig: str) -> None: super().add_directive_header(sig) sourcename = self.get_sourcename() if not self.options.annotation: @@ -1429,8 +1368,7 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): else: self.add_line(' :annotation: %s' % self.options.annotation, sourcename) - def add_content(self, more_content, no_docstring=False): - # type: (Any, bool) -> None + def add_content(self, more_content: Any, no_docstring: bool = False) -> None: if not self._datadescriptor: # if it's not a data descriptor, its docstring is very probably the # wrong thing to display @@ -1450,21 +1388,18 @@ class PropertyDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # priority = AttributeDocumenter.priority + 1 @classmethod - def can_document_member(cls, member, membername, isattr, parent): - # type: (Any, str, bool, Any) -> bool + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: return inspect.isproperty(member) and isinstance(parent, ClassDocumenter) - def document_members(self, all_members=False): - # type: (bool) -> None + def document_members(self, all_members: bool = False) -> None: pass - def get_real_modname(self): - # type: () -> str + def get_real_modname(self) -> str: return self.get_attr(self.parent or self.object, '__module__', None) \ or self.modname - def add_directive_header(self, sig): - # type: (str) -> None + def add_directive_header(self, sig: str) -> None: super().add_directive_header(sig) sourcename = self.get_sourcename() if inspect.isabstractmethod(self.object): @@ -1485,21 +1420,19 @@ class InstanceAttributeDocumenter(AttributeDocumenter): priority = 11 @classmethod - def can_document_member(cls, member, membername, isattr, parent): - # type: (Any, str, bool, Any) -> bool + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: """This documents only INSTANCEATTR members.""" return isattr and (member is INSTANCEATTR) - def import_object(self): - # type: () -> bool + def import_object(self) -> bool: """Never import anything.""" # disguise as an attribute self.objtype = 'attribute' self._datadescriptor = False return True - def add_content(self, more_content, no_docstring=False): - # type: (Any, bool) -> None + def add_content(self, more_content: Any, no_docstring: bool = False) -> None: """Never try to get a docstring from the object.""" super().add_content(more_content, no_docstring=True) @@ -1517,13 +1450,12 @@ class SlotsAttributeDocumenter(AttributeDocumenter): priority = 11 @classmethod - def can_document_member(cls, member, membername, isattr, parent): - # type: (Any, str, bool, Any) -> bool + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: """This documents only SLOTSATTR members.""" return member is SLOTSATTR - def import_object(self): - # type: () -> bool + def import_object(self) -> Any: """Never import anything.""" # disguise as an attribute self.objtype = 'attribute' @@ -1541,8 +1473,7 @@ class SlotsAttributeDocumenter(AttributeDocumenter): self.env.note_reread() return False - def get_doc(self, encoding=None, ignore=1): - # type: (str, int) -> List[List[str]] + def get_doc(self, encoding: str = None, ignore: int = 1) -> List[List[str]]: """Decode and return lines of the docstring(s) for the object.""" name = self.objpath[-1] __slots__ = safe_getattr(self.parent, '__slots__', []) @@ -1553,14 +1484,12 @@ class SlotsAttributeDocumenter(AttributeDocumenter): return [] -def get_documenters(app): - # type: (Sphinx) -> Dict[str, Type[Documenter]] +def get_documenters(app: Sphinx) -> Dict[str, Type[Documenter]]: """Returns registered Documenter classes""" return app.registry.documenters -def autodoc_attrgetter(app, obj, name, *defargs): - # type: (Sphinx, Any, str, Any) -> Any +def autodoc_attrgetter(app: Sphinx, obj: Any, name: str, *defargs) -> Any: """Alternative getattr() for types""" for typ, func in app.registry.autodoc_attrgettrs.items(): if isinstance(obj, typ): @@ -1569,8 +1498,7 @@ def autodoc_attrgetter(app, obj, name, *defargs): return safe_getattr(obj, name, *defargs) -def merge_autodoc_default_flags(app, config): - # type: (Sphinx, Config) -> None +def merge_autodoc_default_flags(app: Sphinx, config: Config) -> None: """This merges the autodoc_default_flags to autodoc_default_options.""" if not config.autodoc_default_flags: return @@ -1602,8 +1530,7 @@ deprecated_alias('sphinx.ext.autodoc', RemovedInSphinx40Warning) -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: Sphinx) -> Dict[str, Any]: app.add_autodocumenter(ModuleDocumenter) app.add_autodocumenter(ClassDocumenter) app.add_autodocumenter(ExceptionDocumenter) diff --git a/sphinx/ext/autodoc/directive.py b/sphinx/ext/autodoc/directive.py index 6b002b101..953e3c44c 100644 --- a/sphinx/ext/autodoc/directive.py +++ b/sphinx/ext/autodoc/directive.py @@ -7,27 +7,22 @@ """ import warnings +from typing import Any, Callable, Dict, List, Set, Type from docutils import nodes -from docutils.parsers.rst.states import Struct +from docutils.nodes import Element, Node +from docutils.parsers.rst.states import RSTState, Struct from docutils.statemachine import StringList -from docutils.utils import assemble_option_dict +from docutils.utils import Reporter, assemble_option_dict +from sphinx.config import Config from sphinx.deprecation import RemovedInSphinx40Warning -from sphinx.ext.autodoc import Options, get_documenters +from sphinx.environment import BuildEnvironment +from sphinx.ext.autodoc import Documenter, Options, get_documenters from sphinx.util import logging from sphinx.util.docutils import SphinxDirective, switch_source_input from sphinx.util.nodes import nested_parse_with_titles -if False: - # For type annotation - from typing import Any, Callable, Dict, List, Set, Type # NOQA - from docutils.parsers.rst.state import RSTState # NOQA - from docutils.utils import Reporter # NOQA - from sphinx.config import Config # NOQA - from sphinx.environment import BuildEnvironment # NOQA - from sphinx.ext.autodoc import Documenter # NOQA - logger = logging.getLogger(__name__) @@ -41,21 +36,19 @@ AUTODOC_DEFAULT_OPTIONS = ['members', 'undoc-members', 'inherited-members', class DummyOptionSpec(dict): """An option_spec allows any options.""" - def __bool__(self): - # type: () -> bool + def __bool__(self) -> bool: """Behaves like some options are defined.""" return True - def __getitem__(self, key): - # type: (str) -> Callable[[str], str] + def __getitem__(self, key: str) -> Callable[[str], str]: return lambda x: x class DocumenterBridge: """A parameters container for Documenters.""" - def __init__(self, env, reporter, options, lineno, state=None): - # type: (BuildEnvironment, Reporter, Options, int, Any) -> None + def __init__(self, env: BuildEnvironment, reporter: Reporter, options: Options, + lineno: int, state: Any = None) -> None: self.env = env self.reporter = reporter self.genopt = options @@ -73,13 +66,12 @@ class DocumenterBridge: document = Struct(settings=settings) self.state = Struct(document=document) - def warn(self, msg): - # type: (str) -> None + def warn(self, msg: str) -> None: logger.warning(msg, location=(self.env.docname, self.lineno)) -def process_documenter_options(documenter, config, options): - # type: (Type[Documenter], Config, Dict) -> Options +def process_documenter_options(documenter: Type[Documenter], config: Config, options: Dict + ) -> Options: """Recognize options of Documenter from user input.""" for name in AUTODOC_DEFAULT_OPTIONS: if name not in documenter.option_spec: @@ -92,12 +84,12 @@ def process_documenter_options(documenter, config, options): return Options(assemble_option_dict(options.items(), documenter.option_spec)) -def parse_generated_content(state, content, documenter): - # type: (RSTState, StringList, Documenter) -> List[nodes.Node] +def parse_generated_content(state: RSTState, content: StringList, documenter: Documenter + ) -> List[Node]: """Parse a generated content by Documenter.""" with switch_source_input(state, content): if documenter.titles_allowed: - node = nodes.section() # type: nodes.Element + node = nodes.section() # type: Element # necessary so that the child nodes get the right source/line set node.document = state.document nested_parse_with_titles(state, content, node) @@ -121,8 +113,7 @@ class AutodocDirective(SphinxDirective): optional_arguments = 0 final_argument_whitespace = True - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: reporter = self.state.document.reporter try: diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py index 0c6f47039..fc934958d 100644 --- a/sphinx/ext/autodoc/importer.py +++ b/sphinx/ext/autodoc/importer.py @@ -12,20 +12,16 @@ import sys import traceback import warnings from collections import namedtuple +from typing import Any, Callable, Dict, List from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias from sphinx.util import logging from sphinx.util.inspect import isclass, isenumclass, safe_getattr -if False: - # For type annotation - from typing import Any, Callable, Dict, List # NOQA - logger = logging.getLogger(__name__) -def import_module(modname, warningiserror=False): - # type: (str, bool) -> Any +def import_module(modname: str, warningiserror: bool = False) -> Any: """ Call __import__(modname), convert exceptions to ImportError """ @@ -41,8 +37,9 @@ def import_module(modname, warningiserror=False): raise ImportError(exc, traceback.format_exc()) -def import_object(modname, objpath, objtype='', attrgetter=safe_getattr, warningiserror=False): - # type: (str, List[str], str, Callable[[Any, str], Any], bool) -> Any +def import_object(modname: str, objpath: List[str], objtype: str = '', + attrgetter: Callable[[Any, str], Any] = safe_getattr, + warningiserror: bool = False) -> Any: if objpath: logger.debug('[autodoc] from %s import %s', modname, '.'.join(objpath)) else: @@ -108,8 +105,8 @@ def import_object(modname, objpath, objtype='', attrgetter=safe_getattr, warning Attribute = namedtuple('Attribute', ['name', 'directly_defined', 'value']) -def get_object_members(subject, objpath, attrgetter, analyzer=None): - # type: (Any, List[str], Callable, Any) -> Dict[str, Attribute] # NOQA +def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable, + analyzer: Any = None) -> Dict[str, Attribute]: """Get members and attributes of target object.""" # the members directly defined in the class obj_dict = attrgetter(subject, '__dict__', {}) diff --git a/sphinx/ext/autodoc/mock.py b/sphinx/ext/autodoc/mock.py index 6ae389258..4f534a452 100644 --- a/sphinx/ext/autodoc/mock.py +++ b/sphinx/ext/autodoc/mock.py @@ -15,14 +15,11 @@ import warnings from importlib.abc import Loader, MetaPathFinder from importlib.machinery import ModuleSpec from types import FunctionType, MethodType, ModuleType +from typing import Any, Generator, Iterator, List, Sequence, Tuple, Union from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.util import logging -if False: - # For type annotation - from typing import Any, Generator, Iterator, List, Sequence, Tuple, Union # NOQA - logger = logging.getLogger(__name__) @@ -31,8 +28,7 @@ class _MockObject: __display_name__ = '_MockObject' - def __new__(cls, *args, **kwargs): - # type: (Any, Any) -> Any + def __new__(cls, *args, **kwargs) -> Any: if len(args) == 3 and isinstance(args[1], tuple): superclass = args[1][-1].__class__ if superclass is cls: @@ -42,48 +38,39 @@ class _MockObject: return super().__new__(cls) - def __init__(self, *args, **kwargs): - # type: (Any, Any) -> None + def __init__(self, *args, **kwargs) -> None: self.__qualname__ = '' - def __len__(self): - # type: () -> int + def __len__(self) -> int: return 0 - def __contains__(self, key): - # type: (str) -> bool + def __contains__(self, key: str) -> bool: return False - def __iter__(self): - # type: () -> Iterator + def __iter__(self) -> Iterator: return iter([]) - def __mro_entries__(self, bases): - # type: (Tuple) -> Tuple + def __mro_entries__(self, bases: Tuple) -> Tuple: return (self.__class__,) - def __getitem__(self, key): - # type: (str) -> _MockObject + def __getitem__(self, key: str) -> "_MockObject": return _make_subclass(key, self.__display_name__, self.__class__)() - def __getattr__(self, key): - # type: (str) -> _MockObject + def __getattr__(self, key: str) -> "_MockObject": return _make_subclass(key, self.__display_name__, self.__class__)() - def __call__(self, *args, **kw): - # type: (Any, Any) -> Any + def __call__(self, *args, **kw) -> Any: if args and type(args[0]) in [FunctionType, MethodType]: # Appears to be a decorator, pass through unchanged return args[0] return self - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: return self.__display_name__ -def _make_subclass(name, module, superclass=_MockObject, attributes=None): - # type: (str, str, Any, dict) -> Any +def _make_subclass(name: str, module: str, superclass: Any = _MockObject, + attributes: Any = None) -> Any: attrs = {'__module__': module, '__display_name__': module + '.' + name} attrs.update(attributes or {}) @@ -94,8 +81,7 @@ class _MockModule(ModuleType): """Used by autodoc_mock_imports.""" __file__ = os.devnull - def __init__(self, name, loader=None): - # type: (str, _MockImporter) -> None + def __init__(self, name: str, loader: "_MockImporter" = None) -> None: super().__init__(name) self.__all__ = [] # type: List[str] self.__path__ = [] # type: List[str] @@ -104,18 +90,15 @@ class _MockModule(ModuleType): warnings.warn('The loader argument for _MockModule is deprecated.', RemovedInSphinx30Warning) - def __getattr__(self, name): - # type: (str) -> _MockObject + def __getattr__(self, name: str) -> _MockObject: return _make_subclass(name, self.__name__)() - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: return self.__name__ class _MockImporter(MetaPathFinder): - def __init__(self, names): - # type: (List[str]) -> None + def __init__(self, names: List[str]) -> None: self.names = names self.mocked_modules = [] # type: List[str] # enable hook by adding itself to meta_path @@ -124,8 +107,7 @@ class _MockImporter(MetaPathFinder): warnings.warn('_MockImporter is now deprecated.', RemovedInSphinx30Warning) - def disable(self): - # type: () -> None + def disable(self) -> None: # remove `self` from `sys.meta_path` to disable import hook sys.meta_path = [i for i in sys.meta_path if i is not self] # remove mocked modules from sys.modules to avoid side effects after @@ -134,16 +116,14 @@ class _MockImporter(MetaPathFinder): if m in sys.modules: del sys.modules[m] - def find_module(self, name, path=None): - # type: (str, Sequence[Union[bytes, str]]) -> Any + def find_module(self, name: str, path: Sequence[Union[bytes, str]] = None) -> Any: # check if name is (or is a descendant of) one of our base_packages for n in self.names: if n == name or name.startswith(n + '.'): return self return None - def load_module(self, name): - # type: (str) -> ModuleType + def load_module(self, name: str) -> ModuleType: if name in sys.modules: # module has already been imported, return it return sys.modules[name] @@ -157,34 +137,30 @@ class _MockImporter(MetaPathFinder): class MockLoader(Loader): """A loader for mocking.""" - def __init__(self, finder): - # type: (MockFinder) -> None + def __init__(self, finder: "MockFinder") -> None: super().__init__() self.finder = finder - def create_module(self, spec): - # type: (ModuleSpec) -> ModuleType + def create_module(self, spec: ModuleSpec) -> ModuleType: logger.debug('[autodoc] adding a mock module as %s!', spec.name) self.finder.mocked_modules.append(spec.name) return _MockModule(spec.name) - def exec_module(self, module): - # type: (ModuleType) -> None + def exec_module(self, module: ModuleType) -> None: pass # nothing to do class MockFinder(MetaPathFinder): """A finder for mocking.""" - def __init__(self, modnames): - # type: (List[str]) -> None + def __init__(self, modnames: List[str]) -> None: super().__init__() self.modnames = modnames self.loader = MockLoader(self) self.mocked_modules = [] # type: List[str] - def find_spec(self, fullname, path, target=None): - # type: (str, Sequence[Union[bytes, str]], ModuleType) -> ModuleSpec + def find_spec(self, fullname: str, path: Sequence[Union[bytes, str]], + target: ModuleType = None) -> ModuleSpec: for modname in self.modnames: # check if fullname is (or is a descendant of) one of our targets if modname == fullname or fullname.startswith(modname + '.'): @@ -192,16 +168,14 @@ class MockFinder(MetaPathFinder): return None - def invalidate_caches(self): - # type: () -> None + def invalidate_caches(self) -> None: """Invalidate mocked modules on sys.modules.""" for modname in self.mocked_modules: sys.modules.pop(modname, None) @contextlib.contextmanager -def mock(modnames): - # type: (List[str]) -> Generator[None, None, None] +def mock(modnames: List[str]) -> Generator[None, None, None]: """Insert mock modules during context:: with mock(['target.module.name']): diff --git a/sphinx/ext/autosectionlabel.py b/sphinx/ext/autosectionlabel.py index 3d55ad749..9173fecf0 100644 --- a/sphinx/ext/autosectionlabel.py +++ b/sphinx/ext/autosectionlabel.py @@ -8,24 +8,22 @@ :license: BSD, see LICENSE for details. """ +from typing import Any, Dict from typing import cast from docutils import nodes +from docutils.nodes import Node +from sphinx.application import Sphinx from sphinx.locale import __ from sphinx.util import logging from sphinx.util.nodes import clean_astext -if False: - # For type annotation - from typing import Any, Dict # NOQA - from sphinx.application import Sphinx # NOQA - logger = logging.getLogger(__name__) -def get_node_depth(node): +def get_node_depth(node: Node) -> int: i = 0 cur_node = node while cur_node.parent != node.document: @@ -34,8 +32,7 @@ def get_node_depth(node): return i -def register_sections_as_label(app, document): - # type: (Sphinx, nodes.Node) -> None +def register_sections_as_label(app: Sphinx, document: Node) -> None: labels = app.env.domaindata['std']['labels'] anonlabels = app.env.domaindata['std']['anonlabels'] for node in document.traverse(nodes.section): @@ -61,8 +58,7 @@ def register_sections_as_label(app, document): labels[name] = docname, labelid, sectname -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value('autosectionlabel_prefix_document', False, 'env') app.add_config_value('autosectionlabel_maxdepth', None, 'env') app.connect('doctree-read', register_sections_as_label) diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index 6eb0fea9b..7c92bb8b5 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -58,19 +58,24 @@ import posixpath import re import sys import warnings +from os import path from types import ModuleType -from typing import List, cast +from typing import Any, Dict, List, Tuple, Type +from typing import cast from docutils import nodes +from docutils.nodes import Element, Node, system_message from docutils.parsers.rst import directives -from docutils.parsers.rst.states import RSTStateMachine, Struct, state_classes +from docutils.parsers.rst.states import Inliner, RSTStateMachine, Struct, state_classes from docutils.statemachine import StringList import sphinx from sphinx import addnodes +from sphinx.application import Sphinx from sphinx.deprecation import RemovedInSphinx40Warning +from sphinx.environment import BuildEnvironment from sphinx.environment.adapters.toctree import TocTree -from sphinx.ext.autodoc import get_documenters +from sphinx.ext.autodoc import Documenter, get_documenters from sphinx.ext.autodoc.directive import DocumenterBridge, Options from sphinx.ext.autodoc.importer import import_module from sphinx.ext.autodoc.mock import mock @@ -81,15 +86,8 @@ from sphinx.util.docutils import ( NullReporter, SphinxDirective, SphinxRole, new_document, switch_source_input ) from sphinx.util.matching import Matcher +from sphinx.writers.html import HTMLTranslator -if False: - # For type annotation - from typing import Any, Dict, Tuple, Type # NOQA - from docutils.parsers.rst.states import Inliner # NOQA - from sphinx.application import Sphinx # NOQA - from sphinx.environment import BuildEnvironment # NOQA - from sphinx.ext.autodoc import Documenter # NOQA - from sphinx.writers.html import HTMLTranslator # NOQA logger = logging.getLogger(__name__) @@ -104,16 +102,14 @@ class autosummary_toc(nodes.comment): pass -def process_autosummary_toc(app, doctree): - # type: (Sphinx, nodes.document) -> None +def process_autosummary_toc(app: Sphinx, doctree: nodes.document) -> None: """Insert items described in autosummary:: to the TOC tree, but do not generate the toctree:: list. """ env = app.builder.env crawled = {} - def crawl_toc(node, depth=1): - # type: (nodes.Element, int) -> None + def crawl_toc(node: Element, depth: int = 1) -> None: crawled[node] = True for j, subnode in enumerate(node): try: @@ -130,14 +126,12 @@ def process_autosummary_toc(app, doctree): crawl_toc(doctree) -def autosummary_toc_visit_html(self, node): - # type: (nodes.NodeVisitor, autosummary_toc) -> None +def autosummary_toc_visit_html(self: nodes.NodeVisitor, node: autosummary_toc) -> None: """Hide autosummary toctree list in HTML output.""" raise nodes.SkipNode -def autosummary_noop(self, node): - # type: (nodes.NodeVisitor, nodes.Node) -> None +def autosummary_noop(self: nodes.NodeVisitor, node: Node) -> None: pass @@ -147,8 +141,7 @@ class autosummary_table(nodes.comment): pass -def autosummary_table_visit_html(self, node): - # type: (HTMLTranslator, autosummary_table) -> None +def autosummary_table_visit_html(self: HTMLTranslator, node: autosummary_table) -> None: """Make the first column of the table non-breaking.""" try: table = cast(nodes.table, node[0]) @@ -173,16 +166,14 @@ _app = None # type: Sphinx class FakeDirective(DocumenterBridge): - def __init__(self): - # type: () -> None + def __init__(self) -> None: settings = Struct(tab_width=8) document = Struct(settings=settings) state = Struct(document=document) super().__init__({}, None, Options(), 0, state) # type: ignore -def get_documenter(app, obj, parent): - # type: (Sphinx, Any, Any) -> Type[Documenter] +def get_documenter(app: Sphinx, obj: Any, parent: Any) -> Type[Documenter]: """Get an autodoc.Documenter class suitable for documenting the given object. @@ -236,8 +227,7 @@ class Autosummary(SphinxDirective): 'template': directives.unchanged, } - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: self.bridge = DocumenterBridge(self.env, self.state.document.reporter, Options(), self.lineno, self.state) @@ -256,24 +246,30 @@ class Autosummary(SphinxDirective): docname = posixpath.join(tree_prefix, real_name) docname = posixpath.normpath(posixpath.join(dirname, docname)) if docname not in self.env.found_docs: + location = self.state_machine.get_source_and_line(self.lineno) if excluded(self.env.doc2path(docname, None)): - logger.warning(__('toctree references excluded document %r'), docname) + msg = __('autosummary references excluded document %r. Ignored.') else: - logger.warning(__('toctree references unknown document %r'), docname) + msg = __('autosummary: stub file not found %r. ' + 'Check your autosummary_generate setting.') + + logger.warning(msg, real_name, location=location) + continue + docnames.append(docname) - tocnode = addnodes.toctree() - tocnode['includefiles'] = docnames - tocnode['entries'] = [(None, docn) for docn in docnames] - tocnode['maxdepth'] = -1 - tocnode['glob'] = None + if docnames: + tocnode = addnodes.toctree() + tocnode['includefiles'] = docnames + tocnode['entries'] = [(None, docn) for docn in docnames] + tocnode['maxdepth'] = -1 + tocnode['glob'] = None - nodes.append(autosummary_toc('', '', tocnode)) + nodes.append(autosummary_toc('', '', tocnode)) return nodes - def get_items(self, names): - # type: (List[str]) -> List[Tuple[str, str, str, str]] + def get_items(self, names: List[str]) -> List[Tuple[str, str, str, str]]: """Try to import the given names, and return a list of ``[(name, signature, summary_string, real_name), ...]``. """ @@ -353,8 +349,7 @@ class Autosummary(SphinxDirective): return items - def get_table(self, items): - # type: (List[Tuple[str, str, str, str]]) -> List[nodes.Node] + def get_table(self, items: List[Tuple[str, str, str, str]]) -> List[Node]: """Generate a proper list of table nodes for autosummary:: directive. *items* is a list produced by :meth:`get_items`. @@ -372,8 +367,7 @@ class Autosummary(SphinxDirective): body = nodes.tbody('') group.append(body) - def append_row(*column_texts): - # type: (str) -> None + def append_row(*column_texts: str) -> None: row = nodes.row('') source, line = self.state_machine.get_source_and_line() for text in column_texts: @@ -401,42 +395,36 @@ class Autosummary(SphinxDirective): return [table_spec, table] - def warn(self, msg): - # type: (str) -> None + def warn(self, msg: str) -> None: warnings.warn('Autosummary.warn() is deprecated', RemovedInSphinx40Warning, stacklevel=2) logger.warning(msg) @property - def genopt(self): - # type: () -> Options + def genopt(self) -> Options: warnings.warn('Autosummary.genopt is deprecated', RemovedInSphinx40Warning, stacklevel=2) return self.bridge.genopt @property - def warnings(self): - # type: () -> List[nodes.Node] + def warnings(self) -> List[Node]: warnings.warn('Autosummary.warnings is deprecated', RemovedInSphinx40Warning, stacklevel=2) return [] @property - def result(self): - # type: () -> StringList + def result(self) -> StringList: warnings.warn('Autosummary.result is deprecated', RemovedInSphinx40Warning, stacklevel=2) return self.bridge.result -def strip_arg_typehint(s): - # type: (str) -> str +def strip_arg_typehint(s: str) -> str: """Strip a type hint from argument definition.""" return s.split(':')[0].strip() -def mangle_signature(sig, max_chars=30): - # type: (str, int) -> str +def mangle_signature(sig: str, max_chars: int = 30) -> str: """Reformat a function signature to a more compact form.""" # Strip return type annotation s = re.sub(r"\)\s*->\s.*$", ")", sig) @@ -493,8 +481,7 @@ def mangle_signature(sig, max_chars=30): return "(%s)" % sig -def extract_summary(doc, document): - # type: (List[str], Any) -> str +def extract_summary(doc: List[str], document: Any) -> str: """Extract summary from docstring.""" # Skip a blank lines at the top @@ -542,8 +529,8 @@ def extract_summary(doc, document): return summary -def limited_join(sep, items, max_chars=30, overflow_marker="..."): - # type: (str, List[str], int, str) -> str +def limited_join(sep: str, items: List[str], max_chars: int = 30, + overflow_marker: str = "...") -> str: """Join a number of strings to one, limiting the length to *max_chars*. If the string overflows this limit, replace the last fitting item by @@ -569,8 +556,7 @@ def limited_join(sep, items, max_chars=30, overflow_marker="..."): # -- Importing items ----------------------------------------------------------- -def get_import_prefixes_from_env(env): - # type: (BuildEnvironment) -> List[str] +def get_import_prefixes_from_env(env: BuildEnvironment) -> List[str]: """ Obtain current Python import prefixes (for `import_by_name`) from ``document.env`` @@ -591,8 +577,7 @@ def get_import_prefixes_from_env(env): return prefixes -def import_by_name(name, prefixes=[None]): - # type: (str, List[str]) -> Tuple[str, Any, Any, str] +def import_by_name(name: str, prefixes: List[str] = [None]) -> Tuple[str, Any, Any, str]: """Import a Python object that has the given *name*, under one of the *prefixes*. The first name that succeeds is used. """ @@ -610,8 +595,7 @@ def import_by_name(name, prefixes=[None]): raise ImportError('no module named %s' % ' or '.join(tried)) -def _import_by_name(name): - # type: (str) -> Tuple[Any, Any, str] +def _import_by_name(name: str) -> Tuple[Any, Any, str]: """Import a Python object given its full name.""" try: name_parts = name.split('.') @@ -654,8 +638,9 @@ def _import_by_name(name): # -- :autolink: (smart default role) ------------------------------------------- -def autolink_role(typ, rawtext, etext, lineno, inliner, options={}, content=[]): - # type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA +def autolink_role(typ: str, rawtext: str, etext: str, lineno: int, inliner: Inliner, + options: Dict = {}, content: List[str] = [] + ) -> Tuple[List[Node], List[system_message]]: """Smart linking role. Expands to ':obj:`text`' if `text` is an object that can be imported; @@ -686,8 +671,7 @@ class AutoLink(SphinxRole): Expands to ':obj:`text`' if `text` is an object that can be imported; otherwise expands to '*text*'. """ - def run(self): - # type: () -> Tuple[List[nodes.Node], List[nodes.system_message]] + def run(self) -> Tuple[List[Node], List[system_message]]: pyobj_role = self.env.get_domain('py').role('obj') objects, errors = pyobj_role('obj', self.rawtext, self.text, self.lineno, self.inliner, self.options, self.content) @@ -708,10 +692,8 @@ class AutoLink(SphinxRole): return objects, errors -def get_rst_suffix(app): - # type: (Sphinx) -> str - def get_supported_format(suffix): - # type: (str) -> Tuple[str, ...] +def get_rst_suffix(app: Sphinx) -> str: + def get_supported_format(suffix: str) -> Tuple[str, ...]: parser_class = app.registry.get_source_parsers().get(suffix) if parser_class is None: return ('restructuredtext',) @@ -727,30 +709,34 @@ def get_rst_suffix(app): return None -def process_generate_options(app): - # type: (Sphinx) -> None +def process_generate_options(app: Sphinx) -> None: genfiles = app.config.autosummary_generate - if genfiles and not hasattr(genfiles, '__len__'): + if genfiles is True: env = app.builder.env genfiles = [env.doc2path(x, base=None) for x in env.found_docs if os.path.isfile(env.doc2path(x))] + else: + ext = list(app.config.source_suffix) + genfiles = [genfile + (not genfile.endswith(tuple(ext)) and ext[0] or '') + for genfile in genfiles] + + for entry in genfiles[:]: + if not path.isfile(path.join(app.srcdir, entry)): + logger.warning(__('autosummary_generate: file not found: %s'), entry) + genfiles.remove(entry) if not genfiles: return - from sphinx.ext.autosummary.generate import generate_autosummary_docs - - ext = list(app.config.source_suffix) - genfiles = [genfile + (not genfile.endswith(tuple(ext)) and ext[0] or '') - for genfile in genfiles] - suffix = get_rst_suffix(app) if suffix is None: logger.warning(__('autosummary generats .rst files internally. ' 'But your source_suffix does not contain .rst. Skipped.')) return + from sphinx.ext.autosummary.generate import generate_autosummary_docs + imported_members = app.config.autosummary_imported_members with mock(app.config.autosummary_mock_imports): generate_autosummary_docs(genfiles, builder=app.builder, @@ -758,8 +744,7 @@ def process_generate_options(app): app=app, imported_members=imported_members) -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: Sphinx) -> Dict[str, Any]: # I need autodoc app.setup_extension('sphinx.ext.autodoc') app.add_node(autosummary_toc, diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index 4a699dd9f..2a23f1289 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -24,6 +24,7 @@ import pydoc import re import sys import warnings +from typing import Any, Callable, Dict, List, Set, Tuple, Type from jinja2 import BaseLoader, FileSystemLoader, TemplateNotFound from jinja2.sandbox import SandboxedEnvironment @@ -31,7 +32,9 @@ from jinja2.sandbox import SandboxedEnvironment import sphinx.locale from sphinx import __display_version__ from sphinx import package_dir +from sphinx.builders import Builder from sphinx.deprecation import RemovedInSphinx40Warning +from sphinx.ext.autodoc import Documenter from sphinx.ext.autosummary import import_by_name, get_documenter from sphinx.jinja2glue import BuiltinTemplateLoader from sphinx.locale import __ @@ -41,12 +44,6 @@ from sphinx.util import rst from sphinx.util.inspect import safe_getattr from sphinx.util.osutil import ensuredir -if False: - # For type annotation - from typing import Any, Callable, Dict, List, Set, Tuple, Type, Union # NOQA - from sphinx.builders import Builder # NOQA - from sphinx.ext.autodoc import Documenter # NOQA - logger = logging.getLogger(__name__) @@ -54,15 +51,13 @@ logger = logging.getLogger(__name__) class DummyApplication: """Dummy Application class for sphinx-autogen command.""" - def __init__(self): - # type: () -> None + def __init__(self) -> None: self.registry = SphinxComponentRegistry() self.messagelog = [] # type: List[str] self.verbosity = 0 -def setup_documenters(app): - # type: (Any) -> None +def setup_documenters(app: Any) -> None: from sphinx.ext.autodoc import ( ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter, FunctionDocumenter, MethodDocumenter, AttributeDocumenter, @@ -79,18 +74,15 @@ def setup_documenters(app): app.registry.add_documenter(documenter.objtype, documenter) -def _simple_info(msg): - # type: (str) -> None +def _simple_info(msg: str) -> None: print(msg) -def _simple_warn(msg): - # type: (str) -> None +def _simple_warn(msg: str) -> None: print('WARNING: ' + msg, file=sys.stderr) -def _underline(title, line='='): - # type: (str, str) -> str +def _underline(title: str, line: str = '=') -> str: if '\n' in title: raise ValueError('Can only underline single lines') return title + '\n' + line * len(title) @@ -99,8 +91,7 @@ def _underline(title, line='='): class AutosummaryRenderer: """A helper class for rendering.""" - def __init__(self, builder, template_dir): - # type: (Builder, str) -> None + def __init__(self, builder: Builder, template_dir: str) -> None: loader = None # type: BaseLoader template_dirs = [os.path.join(package_dir, 'ext', 'autosummary', 'templates')] if builder is None: @@ -117,8 +108,7 @@ class AutosummaryRenderer: self.env.filters['e'] = rst.escape self.env.filters['underline'] = _underline - def exists(self, template_name): - # type: (str) -> bool + def exists(self, template_name: str) -> bool: """Check if template file exists.""" try: self.env.get_template(template_name) @@ -126,38 +116,107 @@ class AutosummaryRenderer: except TemplateNotFound: return False - def render(self, template_name, context): - # type: (str, Dict) -> str + def render(self, template_name: str, context: Dict) -> str: """Render a template file.""" return self.env.get_template(template_name).render(context) # -- Generating output --------------------------------------------------------- -def generate_autosummary_docs(sources, output_dir=None, suffix='.rst', - warn=None, info=None, base_path=None, builder=None, - template_dir=None, imported_members=False, app=None): - # type: (List[str], str, str, Callable, Callable, str, Builder, str, bool, Any) -> None + +def generate_autosummary_content(name: str, obj: Any, parent: Any, + template: AutosummaryRenderer, template_name: str, + imported_members: bool, app: Any) -> str: + doc = get_documenter(app, obj, parent) + + if template_name is None: + template_name = 'autosummary/%s.rst' % doc.objtype + if not template.exists(template_name): + template_name = 'autosummary/base.rst' + + def get_members(obj: Any, types: Set[str], include_public: List[str] = [], + imported: bool = True) -> Tuple[List[str], List[str]]: + items = [] # type: List[str] + for name in dir(obj): + try: + value = safe_getattr(obj, name) + except AttributeError: + continue + documenter = get_documenter(app, value, obj) + if documenter.objtype in types: + if imported or getattr(value, '__module__', None) == obj.__name__: + # skip imported members if expected + items.append(name) + public = [x for x in items + if x in include_public or not x.startswith('_')] + return public, items + + ns = {} # type: Dict[str, Any] + + if doc.objtype == 'module': + ns['members'] = dir(obj) + ns['functions'], ns['all_functions'] = \ + get_members(obj, {'function'}, imported=imported_members) + ns['classes'], ns['all_classes'] = \ + get_members(obj, {'class'}, imported=imported_members) + ns['exceptions'], ns['all_exceptions'] = \ + get_members(obj, {'exception'}, imported=imported_members) + elif doc.objtype == 'class': + ns['members'] = dir(obj) + ns['inherited_members'] = \ + set(dir(obj)) - set(obj.__dict__.keys()) + ns['methods'], ns['all_methods'] = \ + get_members(obj, {'method'}, ['__init__']) + ns['attributes'], ns['all_attributes'] = \ + get_members(obj, {'attribute', 'property'}) + + parts = name.split('.') + if doc.objtype in ('method', 'attribute', 'property'): + mod_name = '.'.join(parts[:-2]) + cls_name = parts[-2] + obj_name = '.'.join(parts[-2:]) + ns['class'] = cls_name + else: + mod_name, obj_name = '.'.join(parts[:-1]), parts[-1] + + ns['fullname'] = name + ns['module'] = mod_name + ns['objname'] = obj_name + ns['name'] = parts[-1] + + ns['objtype'] = doc.objtype + ns['underline'] = len(name) * '=' + + return template.render(template_name, ns) + + +def generate_autosummary_docs(sources: List[str], output_dir: str = None, + suffix: str = '.rst', warn: Callable = None, + info: Callable = None, base_path: str = None, + builder: Builder = None, template_dir: str = None, + imported_members: bool = False, app: Any = None) -> None: if info: warnings.warn('info argument for generate_autosummary_docs() is deprecated.', RemovedInSphinx40Warning) + _info = info else: - info = logger.info + _info = logger.info if warn: warnings.warn('warn argument for generate_autosummary_docs() is deprecated.', RemovedInSphinx40Warning) + _warn = warn else: - warn = logger.warning + _warn = logger.warning showed_sources = list(sorted(sources)) if len(showed_sources) > 20: showed_sources = showed_sources[:10] + ['...'] + showed_sources[-10:] - info(__('[autosummary] generating autosummary for: %s') % - ', '.join(showed_sources)) + _info(__('[autosummary] generating autosummary for: %s') % + ', '.join(showed_sources)) if output_dir: - info(__('[autosummary] writing to %s') % output_dir) + _info(__('[autosummary] writing to %s') % output_dir) if base_path is not None: sources = [os.path.join(base_path, filename) for filename in sources] @@ -183,7 +242,7 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst', try: name, obj, parent, mod_name = import_by_name(name) except ImportError as e: - warn('[autosummary] failed to import %r: %s' % (name, e)) + _warn('[autosummary] failed to import %r: %s' % (name, e)) continue fn = os.path.join(path, name + suffix) @@ -195,67 +254,9 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst', new_files.append(fn) with open(fn, 'w') as f: - doc = get_documenter(app, obj, parent) - - if template_name is None: - template_name = 'autosummary/%s.rst' % doc.objtype - if not template.exists(template_name): - template_name = 'autosummary/base.rst' - - def get_members(obj, types, include_public=[], imported=True): - # type: (Any, Set[str], List[str], bool) -> Tuple[List[str], List[str]] # NOQA - items = [] # type: List[str] - for name in dir(obj): - try: - value = safe_getattr(obj, name) - except AttributeError: - continue - documenter = get_documenter(app, value, obj) - if documenter.objtype in types: - if imported or getattr(value, '__module__', None) == obj.__name__: - # skip imported members if expected - items.append(name) - public = [x for x in items - if x in include_public or not x.startswith('_')] - return public, items - - ns = {} # type: Dict[str, Any] - - if doc.objtype == 'module': - ns['members'] = dir(obj) - ns['functions'], ns['all_functions'] = \ - get_members(obj, {'function'}, imported=imported_members) - ns['classes'], ns['all_classes'] = \ - get_members(obj, {'class'}, imported=imported_members) - ns['exceptions'], ns['all_exceptions'] = \ - get_members(obj, {'exception'}, imported=imported_members) - elif doc.objtype == 'class': - ns['members'] = dir(obj) - ns['inherited_members'] = \ - set(dir(obj)) - set(obj.__dict__.keys()) - ns['methods'], ns['all_methods'] = \ - get_members(obj, {'method'}, ['__init__']) - ns['attributes'], ns['all_attributes'] = \ - get_members(obj, {'attribute', 'property'}) - - parts = name.split('.') - if doc.objtype in ('method', 'attribute', 'property'): - mod_name = '.'.join(parts[:-2]) - cls_name = parts[-2] - obj_name = '.'.join(parts[-2:]) - ns['class'] = cls_name - else: - mod_name, obj_name = '.'.join(parts[:-1]), parts[-1] - - ns['fullname'] = name - ns['module'] = mod_name - ns['objname'] = obj_name - ns['name'] = parts[-1] - - ns['objtype'] = doc.objtype - ns['underline'] = len(name) * '=' - - rendered = template.render(template_name, ns) + rendered = generate_autosummary_content(name, obj, parent, + template, template_name, + imported_members, app) f.write(rendered) # descend recursively to new files @@ -268,8 +269,7 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst', # -- Finding documented entries in files --------------------------------------- -def find_autosummary_in_files(filenames): - # type: (List[str]) -> List[Tuple[str, str, str]] +def find_autosummary_in_files(filenames: List[str]) -> List[Tuple[str, str, str]]: """Find out what items are documented in source/*.rst. See `find_autosummary_in_lines`. @@ -282,8 +282,8 @@ def find_autosummary_in_files(filenames): return documented -def find_autosummary_in_docstring(name, module=None, filename=None): - # type: (str, Any, str) -> List[Tuple[str, str, str]] +def find_autosummary_in_docstring(name: str, module: Any = None, filename: str = None + ) -> List[Tuple[str, str, str]]: """Find out what items are documented in the given object's docstring. See `find_autosummary_in_lines`. @@ -302,8 +302,8 @@ def find_autosummary_in_docstring(name, module=None, filename=None): return [] -def find_autosummary_in_lines(lines, module=None, filename=None): - # type: (List[str], Any, str) -> List[Tuple[str, str, str]] +def find_autosummary_in_lines(lines: List[str], module: Any = None, filename: str = None + ) -> List[Tuple[str, str, str]]: """Find out what items appear in autosummary:: directives in the given lines. @@ -389,8 +389,7 @@ def find_autosummary_in_lines(lines, module=None, filename=None): return documented -def get_parser(): - # type: () -> argparse.ArgumentParser +def get_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( usage='%(prog)s [OPTIONS] ...', epilog=__('For more information, visit .'), @@ -432,8 +431,7 @@ The format of the autosummary directive is documented in the return parser -def main(argv=sys.argv[1:]): - # type: (List[str]) -> None +def main(argv: List[str] = sys.argv[1:]) -> None: sphinx.locale.setlocale(locale.LC_ALL, '') sphinx.locale.init_console(os.path.join(package_dir, 'locale'), 'sphinx') diff --git a/sphinx/ext/coverage.py b/sphinx/ext/coverage.py index 6c9489046..24f51b856 100644 --- a/sphinx/ext/coverage.py +++ b/sphinx/ext/coverage.py @@ -14,30 +14,25 @@ import inspect import pickle import re from os import path +from typing import Any, Dict, IO, List, Pattern, Set, Tuple import sphinx +from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.locale import __ from sphinx.util import logging from sphinx.util.inspect import safe_getattr -if False: - # For type annotation - from typing import Any, Dict, IO, List, Pattern, Set, Tuple # NOQA - from sphinx.application import Sphinx # NOQA - logger = logging.getLogger(__name__) # utility -def write_header(f, text, char='-'): - # type:(IO, str, str) -> None +def write_header(f: IO, text: str, char: str = '-') -> None: f.write(text + '\n') f.write(char * len(text) + '\n') -def compile_regex_list(name, exps): - # type: (str, str) -> List[Pattern] +def compile_regex_list(name: str, exps: str) -> List[Pattern]: lst = [] for exp in exps: try: @@ -55,8 +50,7 @@ class CoverageBuilder(Builder): epilog = __('Testing of coverage in the sources finished, look at the ' 'results in %(outdir)s' + path.sep + 'python.txt.') - def init(self): - # type: () -> None + def init(self) -> None: self.c_sourcefiles = [] # type: List[str] for pattern in self.config.coverage_c_path: pattern = path.join(self.srcdir, pattern) @@ -82,12 +76,10 @@ class CoverageBuilder(Builder): self.py_ignorexps = compile_regex_list('coverage_ignore_pyobjects', self.config.coverage_ignore_pyobjects) - def get_outdated_docs(self): - # type: () -> str + def get_outdated_docs(self) -> str: return 'coverage overview' - def write(self, *ignored): - # type: (Any) -> None + def write(self, *ignored) -> None: self.py_undoc = {} # type: Dict[str, Dict[str, Any]] self.build_py_coverage() self.write_py_coverage() @@ -96,8 +88,7 @@ class CoverageBuilder(Builder): self.build_c_coverage() self.write_c_coverage() - def build_c_coverage(self): - # type: () -> None + def build_c_coverage(self) -> None: # Fetch all the info from the header files c_objects = self.env.domaindata['c']['objects'] for filename in self.c_sourcefiles: @@ -118,8 +109,7 @@ class CoverageBuilder(Builder): if undoc: self.c_undoc[filename] = undoc - def write_c_coverage(self): - # type: () -> None + def write_c_coverage(self) -> None: output_file = path.join(self.outdir, 'c.txt') with open(output_file, 'w') as op: if self.config.coverage_write_headline: @@ -138,8 +128,7 @@ class CoverageBuilder(Builder): return True return False - def build_py_coverage(self): - # type: () -> None + def build_py_coverage(self) -> None: objects = self.env.domaindata['py']['objects'] modules = self.env.domaindata['py']['modules'] @@ -230,8 +219,7 @@ class CoverageBuilder(Builder): self.py_undoc[mod_name] = {'funcs': funcs, 'classes': classes} - def write_py_coverage(self): - # type: () -> None + def write_py_coverage(self) -> None: output_file = path.join(self.outdir, 'python.txt') failed = [] with open(output_file, 'w') as op: @@ -266,16 +254,14 @@ class CoverageBuilder(Builder): write_header(op, 'Modules that failed to import') op.writelines(' * %s -- %s\n' % x for x in failed) - def finish(self): - # type: () -> None + def finish(self) -> None: # dump the coverage data to a pickle file too picklepath = path.join(self.outdir, 'undoc.pickle') with open(picklepath, 'wb') as dumpfile: pickle.dump((self.py_undoc, self.c_undoc), dumpfile) -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: Sphinx) -> Dict[str, Any]: app.add_builder(CoverageBuilder) app.add_config_value('coverage_ignore_modules', [], False) app.add_config_value('coverage_ignore_functions', [], False) diff --git a/sphinx/ext/doctest.py b/sphinx/ext/doctest.py index 01ab38efe..68df253d5 100644 --- a/sphinx/ext/doctest.py +++ b/sphinx/ext/doctest.py @@ -16,8 +16,10 @@ import time import warnings from io import StringIO from os import path +from typing import Any, Callable, Dict, Iterable, List, Sequence, Set, Tuple, Type from docutils import nodes +from docutils.nodes import Element, Node, TextElement from docutils.parsers.rst import directives from packaging.specifiers import SpecifierSet, InvalidSpecifier from packaging.version import Version @@ -33,8 +35,8 @@ from sphinx.util.osutil import relpath if False: # For type annotation - from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Set, Tuple, Type # NOQA - from sphinx.application import Sphinx # NOQA + from sphinx.application import Sphinx + logger = logging.getLogger(__name__) @@ -42,15 +44,13 @@ blankline_re = re.compile(r'^\s*', re.MULTILINE) doctestopt_re = re.compile(r'#\s*doctest:.+$', re.MULTILINE) -def doctest_encode(text, encoding): - # type: (str, str) -> str +def doctest_encode(text: str, encoding: str) -> str: warnings.warn('doctest_encode() is deprecated.', RemovedInSphinx40Warning) return text -def is_allowed_version(spec, version): - # type: (str, str) -> bool +def is_allowed_version(spec: str, version: str) -> bool: """Check `spec` satisfies `version` or not. This obeys PEP-440 specifiers: @@ -80,8 +80,7 @@ class TestDirective(SphinxDirective): optional_arguments = 1 final_argument_whitespace = True - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: # use ordinary docutils nodes for test code: they get special attributes # so that our builder recognizes them, and the other builders are happy. code = '\n'.join(self.content) @@ -95,7 +94,7 @@ class TestDirective(SphinxDirective): if not test: test = code code = doctestopt_re.sub('', code) - nodetype = nodes.literal_block # type: Type[nodes.TextElement] + nodetype = nodes.literal_block # type: Type[TextElement] if self.name in ('testsetup', 'testcleanup') or 'hide' in self.options: nodetype = nodes.comment if self.arguments: @@ -194,15 +193,13 @@ parser = doctest.DocTestParser() # helper classes class TestGroup: - def __init__(self, name): - # type: (str) -> None + def __init__(self, name: str) -> None: self.name = name self.setup = [] # type: List[TestCode] self.tests = [] # type: List[List[TestCode]] self.cleanup = [] # type: List[TestCode] - def add_code(self, code, prepend=False): - # type: (TestCode, bool) -> None + def add_code(self, code: "TestCode", prepend: bool = False) -> None: if code.type == 'testsetup': if prepend: self.setup.insert(0, code) @@ -220,30 +217,28 @@ class TestGroup: else: raise RuntimeError(__('invalid TestCode type')) - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: return 'TestGroup(name=%r, setup=%r, cleanup=%r, tests=%r)' % ( self.name, self.setup, self.cleanup, self.tests) class TestCode: - def __init__(self, code, type, filename, lineno, options=None): - # type: (str, str, Optional[str], int, Optional[Dict]) -> None + def __init__(self, code: str, type: str, filename: str, + lineno: int, options: Dict = None) -> None: self.code = code self.type = type self.filename = filename self.lineno = lineno self.options = options or {} - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: return 'TestCode(%r, %r, filename=%r, lineno=%r, options=%r)' % ( self.code, self.type, self.filename, self.lineno, self.options) class SphinxDocTestRunner(doctest.DocTestRunner): - def summarize(self, out, verbose=None): # type: ignore - # type: (Callable, bool) -> Tuple[int, int] + def summarize(self, out: Callable, verbose: bool = None # type: ignore + ) -> Tuple[int, int]: string_io = StringIO() old_stdout = sys.stdout sys.stdout = string_io @@ -254,9 +249,8 @@ class SphinxDocTestRunner(doctest.DocTestRunner): out(string_io.getvalue()) return res - def _DocTestRunner__patched_linecache_getlines(self, filename, - module_globals=None): - # type: (str, Any) -> Any + def _DocTestRunner__patched_linecache_getlines(self, filename: str, + module_globals: Any = None) -> Any: # this is overridden from DocTestRunner adding the try-except below m = self._DocTestRunner__LINECACHE_FILENAME_RE.match(filename) # type: ignore if m and m.group('name') == self.test.name: @@ -282,8 +276,7 @@ class DocTestBuilder(Builder): epilog = __('Testing of doctests in the sources finished, look at the ' 'results in %(outdir)s/output.txt.') - def init(self): - # type: () -> None + def init(self) -> None: # default options self.opt = self.config.doctest_default_flags @@ -312,32 +305,26 @@ class DocTestBuilder(Builder): '==================================%s\n') % (date, '=' * len(date))) - def _out(self, text): - # type: (str) -> None + def _out(self, text: str) -> None: logger.info(text, nonl=True) self.outfile.write(text) - def _warn_out(self, text): - # type: (str) -> None + def _warn_out(self, text: str) -> None: if self.app.quiet or self.app.warningiserror: logger.warning(text) else: logger.info(text, nonl=True) self.outfile.write(text) - def get_target_uri(self, docname, typ=None): - # type: (str, str) -> str + def get_target_uri(self, docname: str, typ: str = None) -> str: return '' - def get_outdated_docs(self): - # type: () -> Set[str] + def get_outdated_docs(self) -> Set[str]: return self.env.found_docs - def finish(self): - # type: () -> None + def finish(self) -> None: # write executive summary - def s(v): - # type: (int) -> str + def s(v: int) -> str: return v != 1 and 's' or '' repl = (self.total_tries, s(self.total_tries), self.total_failures, s(self.total_failures), @@ -356,8 +343,8 @@ Doctest summary if self.total_failures or self.setup_failures or self.cleanup_failures: self.app.statuscode = 1 - def write(self, build_docnames, updated_docnames, method='update'): - # type: (Iterable[str], Sequence[str], str) -> None + def write(self, build_docnames: Iterable[str], updated_docnames: Sequence[str], + method: str = 'update') -> None: if build_docnames is None: build_docnames = sorted(self.env.all_docs) @@ -367,8 +354,7 @@ Doctest summary doctree = self.env.get_doctree(docname) self.test_doc(docname, doctree) - def get_filename_for_node(self, node, docname): - # type: (nodes.Node, str) -> str + def get_filename_for_node(self, node: Node, docname: str) -> str: """Try to get the file which actually contains the doctest, not the filename of the document it's included in.""" try: @@ -379,8 +365,7 @@ Doctest summary return filename @staticmethod - def get_line_number(node): - # type: (nodes.Node) -> Optional[int] + def get_line_number(node: Node) -> int: """Get the real line number or admit we don't know.""" # TODO: Work out how to store or calculate real (file-relative) # line numbers for doctest blocks in docstrings. @@ -395,8 +380,7 @@ Doctest summary return node.line - 1 return None - def skipped(self, node): - # type: (nodes.Element) -> bool + def skipped(self, node: Element) -> bool: if 'skipif' not in node: return False else: @@ -409,8 +393,7 @@ Doctest summary exec(self.config.doctest_global_cleanup, context) return should_skip - def test_doc(self, docname, doctree): - # type: (str, nodes.Node) -> None + def test_doc(self, docname: str, doctree: Node) -> None: groups = {} # type: Dict[str, TestGroup] add_to_all_groups = [] self.setup_runner = SphinxDocTestRunner(verbose=False, @@ -424,17 +407,15 @@ Doctest summary self.cleanup_runner._fakeout = self.setup_runner._fakeout # type: ignore if self.config.doctest_test_doctest_blocks: - def condition(node): - # type: (nodes.Node) -> bool + def condition(node: Node) -> bool: return (isinstance(node, (nodes.literal_block, nodes.comment)) and 'testnodetype' in node) or \ isinstance(node, nodes.doctest_block) else: - def condition(node): - # type: (nodes.Node) -> bool + def condition(node: Node) -> bool: return isinstance(node, (nodes.literal_block, nodes.comment)) \ and 'testnodetype' in node - for node in doctree.traverse(condition): # type: nodes.Element + for node in doctree.traverse(condition): # type: Element if self.skipped(node): continue @@ -490,16 +471,13 @@ Doctest summary self.cleanup_failures += res_f self.cleanup_tries += res_t - def compile(self, code, name, type, flags, dont_inherit): - # type: (str, str, str, Any, bool) -> Any + def compile(self, code: str, name: str, type: str, flags: Any, dont_inherit: bool) -> Any: return compile(code, name, self.type, flags, dont_inherit) - def test_group(self, group): - # type: (TestGroup) -> None + def test_group(self, group: TestGroup) -> None: ns = {} # type: Dict - def run_setup_cleanup(runner, testcodes, what): - # type: (Any, List[TestCode], Any) -> bool + def run_setup_cleanup(runner: Any, testcodes: List[TestCode], what: Any) -> bool: examples = [] for testcode in testcodes: example = doctest.Example(testcode.code, '', lineno=testcode.lineno) @@ -568,8 +546,7 @@ Doctest summary run_setup_cleanup(self.cleanup_runner, group.cleanup, 'cleanup') -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: "Sphinx") -> Dict[str, Any]: app.add_directive('testsetup', TestsetupDirective) app.add_directive('testcleanup', TestcleanupDirective) app.add_directive('doctest', DoctestDirective) diff --git a/sphinx/ext/extlinks.py b/sphinx/ext/extlinks.py index 7ad721b8f..39a989f23 100644 --- a/sphinx/ext/extlinks.py +++ b/sphinx/ext/extlinks.py @@ -23,23 +23,22 @@ :license: BSD, see LICENSE for details. """ +from typing import Any, Dict, List, Tuple + from docutils import nodes, utils +from docutils.nodes import Node, system_message +from docutils.parsers.rst.states import Inliner import sphinx +from sphinx.application import Sphinx from sphinx.util.nodes import split_explicit_title - -if False: - # For type annotation - from typing import Any, Dict, List, Tuple # NOQA - from docutils.parsers.rst.states import Inliner # NOQA - from sphinx.application import Sphinx # NOQA - from sphinx.util.typing import RoleFunction # NOQA +from sphinx.util.typing import RoleFunction -def make_link_role(base_url, prefix): - # type: (str, str) -> RoleFunction - def role(typ, rawtext, text, lineno, inliner, options={}, content=[]): - # type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA +def make_link_role(base_url: str, prefix: str) -> RoleFunction: + def role(typ: str, rawtext: str, text: str, lineno: int, + inliner: Inliner, options: Dict = {}, content: List[str] = [] + ) -> Tuple[List[Node], List[system_message]]: text = utils.unescape(text) has_explicit_title, title, part = split_explicit_title(text) try: @@ -60,14 +59,12 @@ def make_link_role(base_url, prefix): return role -def setup_link_roles(app): - # type: (Sphinx) -> None +def setup_link_roles(app: Sphinx) -> None: for name, (base_url, prefix) in app.config.extlinks.items(): app.add_role(name, make_link_role(base_url, prefix)) -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value('extlinks', {}, 'env') app.connect('builder-inited', setup_link_roles) return {'version': sphinx.__display_version__, 'parallel_read_safe': True} diff --git a/sphinx/ext/githubpages.py b/sphinx/ext/githubpages.py index 5cecddd09..6a07c651a 100644 --- a/sphinx/ext/githubpages.py +++ b/sphinx/ext/githubpages.py @@ -10,18 +10,14 @@ import os import urllib +from typing import Any, Dict import sphinx - -if False: - # For type annotation - from typing import Any, Dict # NOQA - from sphinx.application import Sphinx # NOQA - from sphinx.environment import BuildEnvironment # NOQA +from sphinx.application import Sphinx +from sphinx.environment import BuildEnvironment -def create_nojekyll_and_cname(app, env): - # type: (Sphinx, BuildEnvironment) -> None +def create_nojekyll_and_cname(app: Sphinx, env: BuildEnvironment) -> None: if app.builder.format == 'html': open(os.path.join(app.builder.outdir, '.nojekyll'), 'wt').close() @@ -35,7 +31,6 @@ def create_nojekyll_and_cname(app, env): f.write(domain) -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: Sphinx) -> Dict[str, Any]: app.connect('env-updated', create_nojekyll_and_cname) return {'version': sphinx.__display_version__, 'parallel_read_safe': True} diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py index ab45b55fc..c16952bc0 100644 --- a/sphinx/ext/graphviz.py +++ b/sphinx/ext/graphviz.py @@ -15,31 +15,27 @@ import subprocess from hashlib import sha1 from os import path from subprocess import CalledProcessError, PIPE +from typing import Any, Dict, List, Tuple from docutils import nodes -from docutils.parsers.rst import directives +from docutils.nodes import Node +from docutils.parsers.rst import Directive, directives import sphinx +from sphinx.application import Sphinx from sphinx.errors import SphinxError from sphinx.locale import _, __ from sphinx.util import logging -from sphinx.util.docutils import SphinxDirective +from sphinx.util.docutils import SphinxDirective, SphinxTranslator from sphinx.util.fileutil import copy_asset from sphinx.util.i18n import search_image_for_language from sphinx.util.nodes import set_source_info from sphinx.util.osutil import ensuredir - -if False: - # For type annotation - from docutils.parsers.rst import Directive # NOQA - from typing import Any, Dict, List, Tuple # NOQA - from sphinx.application import Sphinx # NOQA - from sphinx.util.docutils import SphinxTranslator # NOQA - from sphinx.writers.html import HTMLTranslator # NOQA - from sphinx.writers.latex import LaTeXTranslator # NOQA - from sphinx.writers.manpage import ManualPageTranslator # NOQA - from sphinx.writers.texinfo import TexinfoTranslator # NOQA - from sphinx.writers.text import TextTranslator # NOQA +from sphinx.writers.html import HTMLTranslator +from sphinx.writers.latex import LaTeXTranslator +from sphinx.writers.manpage import ManualPageTranslator +from sphinx.writers.texinfo import TexinfoTranslator +from sphinx.writers.text import TextTranslator logger = logging.getLogger(__name__) @@ -53,8 +49,7 @@ class ClickableMapDefinition: maptag_re = re.compile(' None + def __init__(self, filename: str, content: str, dot: str = '') -> None: self.id = None # type: str self.filename = filename self.content = content.splitlines() @@ -62,8 +57,7 @@ class ClickableMapDefinition: self.parse(dot=dot) - def parse(self, dot=None): - # type: (str) -> None + def parse(self, dot: str = None) -> None: matched = self.maptag_re.match(self.content[0]) if not matched: raise GraphvizError('Invalid clickable map file found: %s' % self.filename) @@ -80,8 +74,7 @@ class ClickableMapDefinition: if self.href_re.search(line): self.clickable.append(line) - def generate_clickable_map(self): - # type: () -> str + def generate_clickable_map(self) -> str: """Generate clickable map tags if clickable item exists. If not exists, this only returns empty string. @@ -96,8 +89,7 @@ class graphviz(nodes.General, nodes.Inline, nodes.Element): pass -def figure_wrapper(directive, node, caption): - # type: (Directive, graphviz, str) -> nodes.figure +def figure_wrapper(directive: Directive, node: graphviz, caption: str) -> nodes.figure: figure_node = nodes.figure('', node) if 'align' in node: figure_node['align'] = node.attributes.pop('align') @@ -110,8 +102,7 @@ def figure_wrapper(directive, node, caption): return figure_node -def align_spec(argument): - # type: (Any) -> str +def align_spec(argument: Any) -> str: return directives.choice(argument, ('left', 'center', 'right')) @@ -132,8 +123,7 @@ class Graphviz(SphinxDirective): 'name': directives.unchanged, } - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: if self.arguments: document = self.state.document if self.content: @@ -194,8 +184,7 @@ class GraphvizSimple(SphinxDirective): 'name': directives.unchanged, } - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: node = graphviz() node['code'] = '%s %s {\n%s\n}\n' % \ (self.name, self.arguments[0], '\n'.join(self.content)) @@ -216,8 +205,8 @@ class GraphvizSimple(SphinxDirective): return [figure] -def render_dot(self, code, options, format, prefix='graphviz'): - # type: (SphinxTranslator, str, Dict, str, str) -> Tuple[str, str] +def render_dot(self: SphinxTranslator, code: str, options: Dict, + format: str, prefix: str = 'graphviz') -> Tuple[str, str]: """Render graphviz code into a PNG or PDF output file.""" graphviz_dot = options.get('graphviz_dot', self.builder.config.graphviz_dot) hashkey = (code + str(options) + str(graphviz_dot) + @@ -265,9 +254,9 @@ def render_dot(self, code, options, format, prefix='graphviz'): '[stdout]\n%r') % (exc.stderr, exc.stdout)) -def render_dot_html(self, node, code, options, prefix='graphviz', - imgcls=None, alt=None): - # type: (HTMLTranslator, graphviz, str, Dict, str, str, str) -> Tuple[str, str] +def render_dot_html(self: HTMLTranslator, node: graphviz, code: str, options: Dict, + prefix: str = 'graphviz', imgcls: str = None, alt: str = None + ) -> Tuple[str, str]: format = self.builder.config.graphviz_output_format try: if format not in ('png', 'svg'): @@ -319,13 +308,12 @@ def render_dot_html(self, node, code, options, prefix='graphviz', raise nodes.SkipNode -def html_visit_graphviz(self, node): - # type: (HTMLTranslator, graphviz) -> None +def html_visit_graphviz(self: HTMLTranslator, node: graphviz) -> None: render_dot_html(self, node, node['code'], node['options']) -def render_dot_latex(self, node, code, options, prefix='graphviz'): - # type: (LaTeXTranslator, graphviz, str, Dict, str) -> None +def render_dot_latex(self: LaTeXTranslator, node: graphviz, code: str, + options: Dict, prefix: str = 'graphviz') -> None: try: fname, outfn = render_dot(self, code, options, 'pdf', prefix) except GraphvizError as exc: @@ -357,13 +345,12 @@ def render_dot_latex(self, node, code, options, prefix='graphviz'): raise nodes.SkipNode -def latex_visit_graphviz(self, node): - # type: (LaTeXTranslator, graphviz) -> None +def latex_visit_graphviz(self: LaTeXTranslator, node: graphviz) -> None: render_dot_latex(self, node, node['code'], node['options']) -def render_dot_texinfo(self, node, code, options, prefix='graphviz'): - # type: (TexinfoTranslator, graphviz, str, Dict, str) -> None +def render_dot_texinfo(self: TexinfoTranslator, node: graphviz, code: str, + options: Dict, prefix: str = 'graphviz') -> None: try: fname, outfn = render_dot(self, code, options, 'png', prefix) except GraphvizError as exc: @@ -374,13 +361,11 @@ def render_dot_texinfo(self, node, code, options, prefix='graphviz'): raise nodes.SkipNode -def texinfo_visit_graphviz(self, node): - # type: (TexinfoTranslator, graphviz) -> None +def texinfo_visit_graphviz(self: TexinfoTranslator, node: graphviz) -> None: render_dot_texinfo(self, node, node['code'], node['options']) -def text_visit_graphviz(self, node): - # type: (TextTranslator, graphviz) -> None +def text_visit_graphviz(self: TextTranslator, node: graphviz) -> None: if 'alt' in node.attributes: self.add_text(_('[graph: %s]') % node['alt']) else: @@ -388,8 +373,7 @@ def text_visit_graphviz(self, node): raise nodes.SkipNode -def man_visit_graphviz(self, node): - # type: (ManualPageTranslator, graphviz) -> None +def man_visit_graphviz(self: ManualPageTranslator, node: graphviz) -> None: if 'alt' in node.attributes: self.body.append(_('[graph: %s]') % node['alt']) else: @@ -397,16 +381,14 @@ def man_visit_graphviz(self, node): raise nodes.SkipNode -def on_build_finished(app, exc): - # type: (Sphinx, Exception) -> None +def on_build_finished(app: Sphinx, exc: Exception) -> None: if exc is None: src = path.join(sphinx.package_dir, 'templates', 'graphviz', 'graphviz.css') dst = path.join(app.outdir, '_static') copy_asset(src, dst) -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: Sphinx) -> Dict[str, Any]: app.add_node(graphviz, html=(html_visit_graphviz, None), latex=(latex_visit_graphviz, None), diff --git a/sphinx/ext/ifconfig.py b/sphinx/ext/ifconfig.py index 1768acf18..a6d3f78e8 100644 --- a/sphinx/ext/ifconfig.py +++ b/sphinx/ext/ifconfig.py @@ -19,17 +19,16 @@ :license: BSD, see LICENSE for details. """ +from typing import Any, Dict, List + from docutils import nodes +from docutils.nodes import Node import sphinx +from sphinx.application import Sphinx from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import nested_parse_with_titles -if False: - # For type annotation - from typing import Any, Dict, List # NOQA - from sphinx.application import Sphinx # NOQA - class ifconfig(nodes.Element): pass @@ -43,8 +42,7 @@ class IfConfig(SphinxDirective): final_argument_whitespace = True option_spec = {} # type: Dict - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: node = ifconfig() node.document = self.state.document self.set_source_info(node) @@ -53,8 +51,7 @@ class IfConfig(SphinxDirective): return [node] -def process_ifconfig_nodes(app, doctree, docname): - # type: (Sphinx, nodes.document, str) -> None +def process_ifconfig_nodes(app: Sphinx, doctree: nodes.document, docname: str) -> None: ns = {confval.name: confval.value for confval in app.config} ns.update(app.config.__dict__.copy()) ns['builder'] = app.builder.name @@ -76,8 +73,7 @@ def process_ifconfig_nodes(app, doctree, docname): node.replace_self(node.children) -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: Sphinx) -> Dict[str, Any]: app.add_node(ifconfig) app.add_directive('ifconfig', IfConfig) app.connect('doctree-resolved', process_ifconfig_nodes) diff --git a/sphinx/ext/imgconverter.py b/sphinx/ext/imgconverter.py index dd78883e2..3799cbe64 100644 --- a/sphinx/ext/imgconverter.py +++ b/sphinx/ext/imgconverter.py @@ -7,19 +7,17 @@ :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ + import subprocess from subprocess import CalledProcessError, PIPE +from typing import Any, Dict +from sphinx.application import Sphinx from sphinx.errors import ExtensionError from sphinx.locale import __ from sphinx.transforms.post_transforms.images import ImageConverter from sphinx.util import logging -if False: - # For type annotation - from typing import Any, Dict # NOQA - from sphinx.application import Sphinx # NOQA - logger = logging.getLogger(__name__) @@ -31,8 +29,7 @@ class ImagemagickConverter(ImageConverter): ('application/pdf', 'image/png'), ] - def is_available(self): - # type: () -> bool + def is_available(self) -> bool: """Confirms the converter is available or not.""" try: args = [self.config.image_converter, '-version'] @@ -50,8 +47,7 @@ class ImagemagickConverter(ImageConverter): exc.stderr, exc.stdout) return False - def convert(self, _from, _to): - # type: (str, str) -> bool + def convert(self, _from: str, _to: str) -> bool: """Converts the image to expected one.""" try: # append an index 0 to source filename to pick up the first frame @@ -75,8 +71,7 @@ class ImagemagickConverter(ImageConverter): (exc.stderr, exc.stdout)) -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: Sphinx) -> Dict[str, Any]: app.add_post_transform(ImagemagickConverter) app.add_config_value('image_converter', 'convert', 'env') app.add_config_value('image_converter_args', [], 'env') diff --git a/sphinx/ext/imgmath.py b/sphinx/ext/imgmath.py index c71db8b12..327be8973 100644 --- a/sphinx/ext/imgmath.py +++ b/sphinx/ext/imgmath.py @@ -17,11 +17,16 @@ import tempfile from hashlib import sha1 from os import path from subprocess import CalledProcessError, PIPE +from typing import Any, Dict, List, Tuple from docutils import nodes +from docutils.nodes import Element import sphinx from sphinx import package_dir +from sphinx.application import Sphinx +from sphinx.builders import Builder +from sphinx.config import Config from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias from sphinx.errors import SphinxError from sphinx.locale import _, __ @@ -30,14 +35,7 @@ from sphinx.util.math import get_node_equation_number, wrap_displaymath from sphinx.util.osutil import ensuredir from sphinx.util.png import read_png_depth, write_png_depth from sphinx.util.template import LaTeXRenderer - -if False: - # For type annotation - from typing import Any, Dict, List, Tuple, Union # NOQA - from sphinx.application import Sphinx # NOQA - from sphinx.builders import Builder # NOQA - from sphinx.config import Config # NOQA - from sphinx.writers.html import HTMLTranslator # NOQA +from sphinx.writers.html import HTMLTranslator logger = logging.getLogger(__name__) @@ -47,8 +45,7 @@ templates_path = path.join(package_dir, 'templates', 'imgmath') class MathExtError(SphinxError): category = 'Math extension error' - def __init__(self, msg, stderr=None, stdout=None): - # type: (str, bytes, bytes) -> None + def __init__(self, msg: str, stderr: bytes = None, stdout: bytes = None) -> None: if stderr: msg += '\n[stderr]\n' + stderr.decode(sys.getdefaultencoding(), 'replace') if stdout: @@ -92,8 +89,7 @@ DOC_BODY_PREVIEW = r''' depth_re = re.compile(br'\[\d+ depth=(-?\d+)\]') -def generate_latex_macro(math, config, confdir=''): - # type: (str, Config, str) -> str +def generate_latex_macro(math: str, config: Config, confdir: str = '') -> str: """Generate LaTeX macro.""" variables = { 'fontsize': config.imgmath_font_size, @@ -115,8 +111,7 @@ def generate_latex_macro(math, config, confdir=''): return LaTeXRenderer(templates_path).render(template_name, variables) -def ensure_tempdir(builder): - # type: (Builder) -> str +def ensure_tempdir(builder: Builder) -> str: """Create temporary directory. use only one tempdir per build -- the use of a directory is cleaner @@ -129,8 +124,7 @@ def ensure_tempdir(builder): return builder._imgmath_tempdir # type: ignore -def compile_math(latex, builder): - # type: (str, Builder) -> str +def compile_math(latex: str, builder: Builder) -> str: """Compile LaTeX macros for math to DVI.""" tempdir = ensure_tempdir(builder) filename = path.join(tempdir, 'math.tex') @@ -157,8 +151,7 @@ def compile_math(latex, builder): raise MathExtError('latex exited with error', exc.stderr, exc.stdout) -def convert_dvi_to_image(command, name): - # type: (List[str], str) -> Tuple[bytes, bytes] +def convert_dvi_to_image(command: List[str], name: str) -> Tuple[bytes, bytes]: """Convert DVI file to specific image format.""" try: ret = subprocess.run(command, stdout=PIPE, stderr=PIPE, check=True) @@ -172,8 +165,7 @@ def convert_dvi_to_image(command, name): raise MathExtError('%s exited with error' % name, exc.stderr, exc.stdout) -def convert_dvi_to_png(dvipath, builder): - # type: (str, Builder) -> Tuple[str, int] +def convert_dvi_to_png(dvipath: str, builder: Builder) -> Tuple[str, int]: """Convert DVI file to PNG image.""" tempdir = ensure_tempdir(builder) filename = path.join(tempdir, 'math.png') @@ -199,8 +191,7 @@ def convert_dvi_to_png(dvipath, builder): return filename, depth -def convert_dvi_to_svg(dvipath, builder): - # type: (str, Builder) -> Tuple[str, int] +def convert_dvi_to_svg(dvipath: str, builder: Builder) -> Tuple[str, int]: """Convert DVI file to SVG image.""" tempdir = ensure_tempdir(builder) filename = path.join(tempdir, 'math.svg') @@ -214,8 +205,7 @@ def convert_dvi_to_svg(dvipath, builder): return filename, None -def render_math(self, math): - # type: (HTMLTranslator, str) -> Tuple[str, int] +def render_math(self: HTMLTranslator, math: str) -> Tuple[str, int]: """Render the LaTeX math expression *math* using latex and dvipng or dvisvgm. @@ -271,8 +261,7 @@ def render_math(self, math): return relfn, depth -def cleanup_tempdir(app, exc): - # type: (Sphinx, Exception) -> None +def cleanup_tempdir(app: Sphinx, exc: Exception) -> None: if exc: return if not hasattr(app.builder, '_imgmath_tempdir'): @@ -283,15 +272,13 @@ def cleanup_tempdir(app, exc): pass -def get_tooltip(self, node): - # type: (HTMLTranslator, Union[nodes.math, nodes.math_block]) -> str +def get_tooltip(self: HTMLTranslator, node: Element) -> str: if self.builder.config.imgmath_add_tooltips: return ' alt="%s"' % self.encode(node.astext()).strip() return '' -def html_visit_math(self, node): - # type: (HTMLTranslator, nodes.math) -> None +def html_visit_math(self: HTMLTranslator, node: nodes.math) -> None: try: fname, depth = render_math(self, '$' + node.astext() + '$') except MathExtError as exc: @@ -313,8 +300,7 @@ def html_visit_math(self, node): raise nodes.SkipNode -def html_visit_displaymath(self, node): - # type: (HTMLTranslator, nodes.math_block) -> None +def html_visit_displaymath(self: HTMLTranslator, node: nodes.math_block) -> None: if node['nowrap']: latex = node.astext() else: @@ -354,8 +340,7 @@ deprecated_alias('sphinx.ext.imgmath', RemovedInSphinx40Warning) -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: Sphinx) -> Dict[str, Any]: app.add_html_math_renderer('imgmath', (html_visit_math, None), (html_visit_displaymath, None)) diff --git a/sphinx/ext/inheritance_diagram.py b/sphinx/ext/inheritance_diagram.py index df3ff01ed..f52a990bf 100644 --- a/sphinx/ext/inheritance_diagram.py +++ b/sphinx/ext/inheritance_diagram.py @@ -40,27 +40,25 @@ import inspect import re import sys from hashlib import md5 -from typing import Iterable, cast +from typing import Any, Dict, Iterable, List, Tuple +from typing import cast from docutils import nodes +from docutils.nodes import Node from docutils.parsers.rst import directives import sphinx from sphinx import addnodes +from sphinx.application import Sphinx +from sphinx.environment import BuildEnvironment from sphinx.ext.graphviz import ( graphviz, figure_wrapper, render_dot_html, render_dot_latex, render_dot_texinfo ) from sphinx.util.docutils import SphinxDirective - -if False: - # For type annotation - from typing import Any, Dict, List, Optional, Tuple # NOQA - from sphinx.application import Sphinx # NOQA - from sphinx.environment import BuildEnvironment # NOQA - from sphinx.writers.html import HTMLTranslator # NOQA - from sphinx.writers.latex import LaTeXTranslator # NOQA - from sphinx.writers.texinfo import TexinfoTranslator # NOQA +from sphinx.writers.html import HTMLTranslator +from sphinx.writers.latex import LaTeXTranslator +from sphinx.writers.texinfo import TexinfoTranslator module_sig_re = re.compile(r'''^(?:([\w.]*)\.)? # module names @@ -68,8 +66,7 @@ module_sig_re = re.compile(r'''^(?:([\w.]*)\.)? # module names ''', re.VERBOSE) -def try_import(objname): - # type: (str) -> Any +def try_import(objname: str) -> Any: """Import a object or module using *name* and *currentmodule*. *name* should be a relative name from *currentmodule* or a fully-qualified name. @@ -96,8 +93,7 @@ def try_import(objname): return None -def import_classes(name, currmodule): - # type: (str, str) -> Any +def import_classes(name: str, currmodule: str) -> Any: """Import a class using its fully-qualified *name*.""" target = None @@ -138,9 +134,9 @@ class InheritanceGraph: from all the way to the root "object", and then is able to generate a graphviz dot graph from them. """ - def __init__(self, class_names, currmodule, show_builtins=False, - private_bases=False, parts=0, aliases=None, top_classes=[]): - # type: (List[str], str, bool, bool, int, Optional[Dict[str, str]], List[Any]) -> None + def __init__(self, class_names: List[str], currmodule: str, show_builtins: bool = False, + private_bases: bool = False, parts: int = 0, aliases: Dict[str, str] = None, + top_classes: List[Any] = []) -> None: """*class_names* is a list of child classes to show bases from. If *show_builtins* is True, then Python builtins will be shown @@ -154,16 +150,16 @@ class InheritanceGraph: raise InheritanceException('No classes found for ' 'inheritance diagram') - def _import_classes(self, class_names, currmodule): - # type: (List[str], str) -> List[Any] + def _import_classes(self, class_names: List[str], currmodule: str) -> List[Any]: """Import a list of classes.""" classes = [] # type: List[Any] for name in class_names: classes.extend(import_classes(name, currmodule)) return classes - def _class_info(self, classes, show_builtins, private_bases, parts, aliases, top_classes): - # type: (List[Any], bool, bool, int, Optional[Dict[str, str]], List[Any]) -> List[Tuple[str, str, List[str], str]] # NOQA + def _class_info(self, classes: List[Any], show_builtins: bool, private_bases: bool, + parts: int, aliases: Dict[str, str], top_classes: List[Any] + ) -> List[Tuple[str, str, List[str], str]]: """Return name and bases for all classes that are ancestors of *classes*. @@ -182,8 +178,7 @@ class InheritanceGraph: all_classes = {} py_builtins = vars(builtins).values() - def recurse(cls): - # type: (Any) -> None + def recurse(cls: Any) -> None: if not show_builtins and cls in py_builtins: return if not private_bases and cls.__name__.startswith('_'): @@ -222,8 +217,7 @@ class InheritanceGraph: return list(all_classes.values()) - def class_name(self, cls, parts=0, aliases=None): - # type: (Any, int, Optional[Dict[str, str]]) -> str + def class_name(self, cls: Any, parts: int = 0, aliases: Dict[str, str] = None) -> str: """Given a class object, return a fully-qualified name. This works for things I've tested in matplotlib so far, but may not be @@ -243,8 +237,7 @@ class InheritanceGraph: return aliases[result] return result - def get_all_class_names(self): - # type: () -> List[str] + def get_all_class_names(self) -> List[str]: """Get all of the class names involved in the graph.""" return [fullname for (_, fullname, _, _) in self.class_info] @@ -266,17 +259,15 @@ class InheritanceGraph: 'style': '"setlinewidth(0.5)"', } - def _format_node_attrs(self, attrs): - # type: (Dict) -> str + def _format_node_attrs(self, attrs: Dict) -> str: return ','.join(['%s=%s' % x for x in sorted(attrs.items())]) - def _format_graph_attrs(self, attrs): - # type: (Dict) -> str + def _format_graph_attrs(self, attrs: Dict) -> str: return ''.join(['%s=%s;\n' % x for x in sorted(attrs.items())]) - def generate_dot(self, name, urls={}, env=None, - graph_attrs={}, node_attrs={}, edge_attrs={}): - # type: (str, Dict, BuildEnvironment, Dict, Dict, Dict) -> str + def generate_dot(self, name: str, urls: Dict = {}, env: BuildEnvironment = None, + graph_attrs: Dict = {}, node_attrs: Dict = {}, edge_attrs: Dict = {} + ) -> str: """Generate a graphviz dot graph from the classes that were passed in to __init__. @@ -344,8 +335,7 @@ class InheritanceDiagram(SphinxDirective): 'top-classes': directives.unchanged_required, } - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: node = inheritance_diagram() node.document = self.state.document class_names = self.arguments[0].split() @@ -391,14 +381,12 @@ class InheritanceDiagram(SphinxDirective): return [figure] -def get_graph_hash(node): - # type: (inheritance_diagram) -> str +def get_graph_hash(node: inheritance_diagram) -> str: encoded = (node['content'] + str(node['parts'])).encode() return md5(encoded).hexdigest()[-10:] -def html_visit_inheritance_diagram(self, node): - # type: (HTMLTranslator, inheritance_diagram) -> None +def html_visit_inheritance_diagram(self: HTMLTranslator, node: inheritance_diagram) -> None: """ Output the graph for HTML. This will insert a PNG with clickable image map. @@ -431,8 +419,7 @@ def html_visit_inheritance_diagram(self, node): raise nodes.SkipNode -def latex_visit_inheritance_diagram(self, node): - # type: (LaTeXTranslator, inheritance_diagram) -> None +def latex_visit_inheritance_diagram(self: LaTeXTranslator, node: inheritance_diagram) -> None: """ Output the graph for LaTeX. This will insert a PDF. """ @@ -447,8 +434,8 @@ def latex_visit_inheritance_diagram(self, node): raise nodes.SkipNode -def texinfo_visit_inheritance_diagram(self, node): - # type: (TexinfoTranslator, inheritance_diagram) -> None +def texinfo_visit_inheritance_diagram(self: TexinfoTranslator, node: inheritance_diagram + ) -> None: """ Output the graph for Texinfo. This will insert a PNG. """ @@ -463,13 +450,11 @@ def texinfo_visit_inheritance_diagram(self, node): raise nodes.SkipNode -def skip(self, node): - # type: (nodes.NodeVisitor, inheritance_diagram) -> None +def skip(self: nodes.NodeVisitor, node: inheritance_diagram) -> None: raise nodes.SkipNode -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: Sphinx) -> Dict[str, Any]: app.setup_extension('sphinx.ext.graphviz') app.add_node( inheritance_diagram, diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py index 6b9bc3825..e627da62a 100644 --- a/sphinx/ext/intersphinx.py +++ b/sphinx/ext/intersphinx.py @@ -28,24 +28,23 @@ import posixpath import sys import time from os import path +from typing import Any, Dict, IO, List, Tuple from urllib.parse import urlsplit, urlunsplit from docutils import nodes +from docutils.nodes import Element, TextElement from docutils.utils import relative_path import sphinx +from sphinx.application import Sphinx from sphinx.builders.html import INVENTORY_FILENAME +from sphinx.config import Config +from sphinx.environment import BuildEnvironment from sphinx.locale import _, __ from sphinx.util import requests, logging from sphinx.util.inventory import InventoryFile +from sphinx.util.typing import Inventory -if False: - # For type annotation - from typing import Any, Dict, IO, List, Tuple # NOQA - from sphinx.application import Sphinx # NOQA - from sphinx.config import Config # NOQA - from sphinx.environment import BuildEnvironment # NOQA - from sphinx.util.typing import Inventory # NOQA logger = logging.getLogger(__name__) @@ -53,8 +52,7 @@ logger = logging.getLogger(__name__) class InventoryAdapter: """Inventory adapter for environment""" - def __init__(self, env): - # type: (BuildEnvironment) -> None + def __init__(self, env: BuildEnvironment) -> None: self.env = env if not hasattr(env, 'intersphinx_cache'): @@ -63,28 +61,23 @@ class InventoryAdapter: self.env.intersphinx_named_inventory = {} # type: ignore @property - def cache(self): - # type: () -> Dict[str, Tuple[str, int, Inventory]] + def cache(self) -> Dict[str, Tuple[str, int, Inventory]]: return self.env.intersphinx_cache # type: ignore @property - def main_inventory(self): - # type: () -> Inventory + def main_inventory(self) -> Inventory: return self.env.intersphinx_inventory # type: ignore @property - def named_inventory(self): - # type: () -> Dict[str, Inventory] + def named_inventory(self) -> Dict[str, Inventory]: return self.env.intersphinx_named_inventory # type: ignore - def clear(self): - # type: () -> None + def clear(self) -> None: self.env.intersphinx_inventory.clear() # type: ignore self.env.intersphinx_named_inventory.clear() # type: ignore -def _strip_basic_auth(url): - # type: (str) -> str +def _strip_basic_auth(url: str) -> str: """Returns *url* with basic auth credentials removed. Also returns the basic auth username and password if they're present in *url*. @@ -105,8 +98,7 @@ def _strip_basic_auth(url): return urlunsplit(frags) -def _read_from_url(url, config=None): - # type: (str, Config) -> IO +def _read_from_url(url: str, config: Config = None) -> IO: """Reads data from *url* with an HTTP *GET*. This function supports fetching from resources which use basic HTTP auth as @@ -131,8 +123,7 @@ def _read_from_url(url, config=None): return r.raw -def _get_safe_url(url): - # type: (str) -> str +def _get_safe_url(url: str) -> str: """Gets version of *url* with basic auth passwords obscured. This function returns results suitable for printing and logging. @@ -157,8 +148,7 @@ def _get_safe_url(url): return urlunsplit(frags) -def fetch_inventory(app, uri, inv): - # type: (Sphinx, str, Any) -> Any +def fetch_inventory(app: Sphinx, uri: str, inv: Any) -> Any: """Fetch, parse and return an intersphinx inventory file.""" # both *uri* (base URI of the links to generate) and *inv* (actual # location of the inventory file) can be local or remote URIs @@ -197,8 +187,7 @@ def fetch_inventory(app, uri, inv): return invdata -def load_mappings(app): - # type: (Sphinx) -> None +def load_mappings(app: Sphinx) -> None: """Load all intersphinx mappings into the environment.""" now = int(time.time()) cache_time = now - app.config.intersphinx_cache_limit * 86400 @@ -258,8 +247,8 @@ def load_mappings(app): inventories.main_inventory.setdefault(type, {}).update(objects) -def missing_reference(app, env, node, contnode): - # type: (Sphinx, BuildEnvironment, nodes.Element, nodes.TextElement) -> nodes.reference +def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnode: TextElement + ) -> nodes.reference: """Attempt to resolve a missing reference via intersphinx references.""" target = node['reftarget'] inventories = InventoryAdapter(env) @@ -336,8 +325,7 @@ def missing_reference(app, env, node, contnode): return None -def normalize_intersphinx_mapping(app, config): - # type: (Sphinx, Config) -> None +def normalize_intersphinx_mapping(app: Sphinx, config: Config) -> None: for key, value in config.intersphinx_mapping.copy().items(): try: if isinstance(value, (list, tuple)): @@ -361,8 +349,7 @@ def normalize_intersphinx_mapping(app, config): config.intersphinx_mapping.pop(key) -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value('intersphinx_mapping', {}, True) app.add_config_value('intersphinx_cache_limit', 5, False) app.add_config_value('intersphinx_timeout', None, False) @@ -376,8 +363,7 @@ def setup(app): } -def inspect_main(argv): - # type: (List[str]) -> None +def inspect_main(argv: List[str]) -> None: """Debug functionality to print out an inventory""" if len(argv) < 1: print("Print out an inventory file.\n" @@ -393,8 +379,7 @@ def inspect_main(argv): srcdir = '' config = MockConfig() - def warn(self, msg): - # type: (str) -> None + def warn(self, msg: str) -> None: print(msg, file=sys.stderr) try: diff --git a/sphinx/ext/jsmath.py b/sphinx/ext/jsmath.py index 47e1b1836..84f191a5e 100644 --- a/sphinx/ext/jsmath.py +++ b/sphinx/ext/jsmath.py @@ -10,6 +10,7 @@ """ import warnings +from typing import Any, Dict from sphinxcontrib.jsmath import ( # NOQA html_visit_math, @@ -18,16 +19,11 @@ from sphinxcontrib.jsmath import ( # NOQA ) import sphinx +from sphinx.application import Sphinx from sphinx.deprecation import RemovedInSphinx40Warning -if False: - # For type annotation - from typing import Any, Dict # NOQA - from sphinx.application import Sphinx # NOQA - -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: Sphinx) -> Dict[str, Any]: warnings.warn('sphinx.ext.jsmath has been moved to sphinxcontrib-jsmath.', RemovedInSphinx40Warning) diff --git a/sphinx/ext/linkcode.py b/sphinx/ext/linkcode.py index 3ebdf7bc9..e8635d926 100644 --- a/sphinx/ext/linkcode.py +++ b/sphinx/ext/linkcode.py @@ -8,25 +8,23 @@ :license: BSD, see LICENSE for details. """ +from typing import Any, Dict, Set + from docutils import nodes +from docutils.nodes import Node import sphinx from sphinx import addnodes +from sphinx.application import Sphinx from sphinx.errors import SphinxError from sphinx.locale import _ -if False: - # For type annotation - from typing import Any, Dict, Set # NOQA - from sphinx.application import Sphinx # NOQA - class LinkcodeError(SphinxError): category = "linkcode error" -def doctree_read(app, doctree): - # type: (Sphinx, nodes.Node) -> None +def doctree_read(app: Sphinx, doctree: Node) -> None: env = app.builder.env resolve_target = getattr(env.config, 'linkcode_resolve', None) @@ -75,8 +73,7 @@ def doctree_read(app, doctree): signode += onlynode -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: Sphinx) -> Dict[str, Any]: app.connect('doctree-read', doctree_read) app.add_config_value('linkcode_resolve', None, '') return {'version': sphinx.__display_version__, 'parallel_read_safe': True} diff --git a/sphinx/ext/mathbase.py b/sphinx/ext/mathbase.py index 0c89fd1ed..34e8cf0b2 100644 --- a/sphinx/ext/mathbase.py +++ b/sphinx/ext/mathbase.py @@ -9,26 +9,24 @@ """ import warnings +from typing import Callable, List, Tuple from docutils import nodes +from docutils.nodes import Element, Node from docutils.parsers.rst.roles import math_role as math_role_base from sphinx.addnodes import math, math_block as displaymath # NOQA # to keep compatibility +from sphinx.application import Sphinx from sphinx.builders.latex.nodes import math_reference as eqref # NOQA # to keep compatibility from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.directives.patches import MathDirective as MathDirectiveBase from sphinx.domains.math import MathDomain # NOQA # to keep compatibility from sphinx.domains.math import MathReferenceRole as EqXRefRole # NOQA # to keep compatibility - -if False: - # For type annotation - from typing import Callable, Tuple # NOQA - from sphinx.application import Sphinx # NOQA - from sphinx.writers.html import HTMLTranslator # NOQA +from sphinx.writers.html import HTMLTranslator class MathDirective(MathDirectiveBase): - def run(self): + def run(self) -> List[Node]: warnings.warn('sphinx.ext.mathbase.MathDirective is moved to ' 'sphinx.directives.patches package.', RemovedInSphinx30Warning, stacklevel=2) @@ -42,8 +40,7 @@ def math_role(role, rawtext, text, lineno, inliner, options={}, content=[]): return math_role_base(role, rawtext, text, lineno, inliner, options, content) -def get_node_equation_number(writer, node): - # type: (HTMLTranslator, nodes.math_block) -> str +def get_node_equation_number(writer: HTMLTranslator, node: nodes.math_block) -> str: warnings.warn('sphinx.ext.mathbase.get_node_equation_number() is moved to ' 'sphinx.util.math package.', RemovedInSphinx30Warning, stacklevel=2) @@ -51,8 +48,7 @@ def get_node_equation_number(writer, node): return get_node_equation_number(writer, node) -def wrap_displaymath(text, label, numbering): - # type: (str, str, bool) -> str +def wrap_displaymath(text: str, label: str, numbering: bool) -> str: warnings.warn('sphinx.ext.mathbase.wrap_displaymath() is moved to ' 'sphinx.util.math package.', RemovedInSphinx30Warning, stacklevel=2) @@ -60,8 +56,7 @@ def wrap_displaymath(text, label, numbering): return wrap_displaymath(text, label, numbering) -def is_in_section_title(node): - # type: (nodes.Element) -> bool +def is_in_section_title(node: Element) -> bool: """Determine whether the node is in a section title""" from sphinx.util.nodes import traverse_parent @@ -75,8 +70,9 @@ def is_in_section_title(node): return False -def setup_math(app, htmlinlinevisitors, htmldisplayvisitors): - # type: (Sphinx, Tuple[Callable, Callable], Tuple[Callable, Callable]) -> None +def setup_math(app: Sphinx, + htmlinlinevisitors: Tuple[Callable, Callable], + htmldisplayvisitors: Tuple[Callable, Callable]) -> None: warnings.warn('setup_math() is deprecated. ' 'Please use app.add_html_math_renderer() instead.', RemovedInSphinx30Warning, stacklevel=2) diff --git a/sphinx/ext/mathjax.py b/sphinx/ext/mathjax.py index 5a84e738f..ebf2bc0e0 100644 --- a/sphinx/ext/mathjax.py +++ b/sphinx/ext/mathjax.py @@ -11,27 +11,23 @@ """ import json +from typing import Any, Dict from typing import cast from docutils import nodes import sphinx +from sphinx.application import Sphinx from sphinx.builders.html import StandaloneHTMLBuilder from sphinx.domains.math import MathDomain +from sphinx.environment import BuildEnvironment from sphinx.errors import ExtensionError from sphinx.locale import _ from sphinx.util.math import get_node_equation_number - -if False: - # For type annotation - from typing import Any, Dict # NOQA - from sphinx.application import Sphinx # NOQA - from sphinx.environment import BuildEnvironment # NOQA - from sphinx.writers.html import HTMLTranslator # NOQA +from sphinx.writers.html import HTMLTranslator -def html_visit_math(self, node): - # type: (HTMLTranslator, nodes.math) -> None +def html_visit_math(self: HTMLTranslator, node: nodes.math) -> None: self.body.append(self.starttag(node, 'span', '', CLASS='math notranslate nohighlight')) self.body.append(self.builder.config.mathjax_inline[0] + self.encode(node.astext()) + @@ -39,8 +35,7 @@ def html_visit_math(self, node): raise nodes.SkipNode -def html_visit_displaymath(self, node): - # type: (HTMLTranslator, nodes.math_block) -> None +def html_visit_displaymath(self: HTMLTranslator, node: nodes.math_block) -> None: self.body.append(self.starttag(node, 'div', CLASS='math notranslate nohighlight')) if node['nowrap']: self.body.append(self.encode(node.astext())) @@ -72,8 +67,7 @@ def html_visit_displaymath(self, node): raise nodes.SkipNode -def install_mathjax(app, env): - # type: (Sphinx, BuildEnvironment) -> None +def install_mathjax(app: Sphinx, env: BuildEnvironment) -> None: if app.builder.format != 'html' or app.builder.math_renderer_name != 'mathjax': # type: ignore # NOQA return if not app.config.mathjax_path: @@ -94,8 +88,7 @@ def install_mathjax(app, env): builder.add_js_file(None, type="text/x-mathjax-config", body=body) -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: Sphinx) -> Dict[str, Any]: app.add_html_math_renderer('mathjax', (html_visit_math, None), (html_visit_displaymath, None)) diff --git a/sphinx/ext/napoleon/__init__.py b/sphinx/ext/napoleon/__init__.py index b475dadef..d211a050e 100644 --- a/sphinx/ext/napoleon/__init__.py +++ b/sphinx/ext/napoleon/__init__.py @@ -8,14 +8,12 @@ :license: BSD, see LICENSE for details. """ +from typing import Any, Dict, List + from sphinx import __display_version__ as __version__ from sphinx.application import Sphinx from sphinx.ext.napoleon.docstring import GoogleDocstring, NumpyDocstring -if False: - # For type annotation - from typing import Any, Dict, List # NOQA - class Config: """Sphinx napoleon extension settings in `conf.py`. @@ -267,16 +265,14 @@ class Config: 'napoleon_custom_sections': (None, 'env') } - def __init__(self, **settings): - # type: (Any) -> None + def __init__(self, **settings) -> None: for name, (default, rebuild) in self._config_values.items(): setattr(self, name, default) for name, value in settings.items(): setattr(self, name, value) -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: Sphinx) -> Dict[str, Any]: """Sphinx extension setup function. When the extension is loaded, Sphinx imports this module and executes @@ -313,8 +309,7 @@ def setup(app): return {'version': __version__, 'parallel_read_safe': True} -def _patch_python_domain(): - # type: () -> None +def _patch_python_domain() -> None: try: from sphinx.domains.python import PyTypedField except ImportError: @@ -333,8 +328,8 @@ def _patch_python_domain(): can_collapse=True)) -def _process_docstring(app, what, name, obj, options, lines): - # type: (Sphinx, str, str, Any, Any, List[str]) -> None +def _process_docstring(app: Sphinx, what: str, name: str, obj: Any, + options: Any, lines: List[str]) -> None: """Process the docstring for a given python object. Called when autodoc has read and processed a docstring. `lines` is a list @@ -383,8 +378,8 @@ def _process_docstring(app, what, name, obj, options, lines): lines[:] = result_lines[:] -def _skip_member(app, what, name, obj, skip, options): - # type: (Sphinx, str, str, Any, bool, Any) -> bool +def _skip_member(app: Sphinx, what: str, name: str, obj: Any, + skip: bool, options: Any) -> bool: """Determine if private and special class members are included in docs. The following settings in conf.py determine if private and special class diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index 7ff184961..097c00e29 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -13,16 +13,13 @@ import inspect import re from functools import partial +from typing import Any, Callable, Dict, List, Tuple, Type, Union +from sphinx.application import Sphinx +from sphinx.config import Config as SphinxConfig from sphinx.ext.napoleon.iterators import modify_iter from sphinx.locale import _ -if False: - # For type annotation - from typing import Any, Callable, Dict, List, Tuple, Type, Union # NOQA - from sphinx.application import Sphinx # NOQA - from sphinx.config import Config as SphinxConfig # NOQA - _directive_regex = re.compile(r'\.\. \S+::') _google_section_regex = re.compile(r'^(\s|\w)+:\s*$') @@ -103,9 +100,9 @@ class GoogleDocstring: _name_rgx = re.compile(r"^\s*((?::(?P\S+):)?`(?P[a-zA-Z0-9_.-]+)`|" r" (?P[a-zA-Z0-9_.-]+))\s*", re.X) - def __init__(self, docstring, config=None, app=None, what='', name='', - obj=None, options=None): - # type: (Union[str, List[str]], SphinxConfig, Sphinx, str, str, Any, Any) -> None + def __init__(self, docstring: Union[str, List[str]], config: SphinxConfig = None, + app: Sphinx = None, what: str = '', name: str = '', + obj: Any = None, options: Any = None) -> None: self._config = config self._app = app @@ -175,8 +172,7 @@ class GoogleDocstring: self._parse() - def __str__(self): - # type: () -> str + def __str__(self) -> str: """Return the parsed docstring in reStructuredText format. Returns @@ -187,8 +183,7 @@ class GoogleDocstring: """ return '\n'.join(self.lines()) - def lines(self): - # type: () -> List[str] + def lines(self) -> List[str]: """Return the parsed lines of the docstring in reStructuredText format. Returns @@ -199,8 +194,7 @@ class GoogleDocstring: """ return self._parsed_lines - def _consume_indented_block(self, indent=1): - # type: (int) -> List[str] + def _consume_indented_block(self, indent: int = 1) -> List[str]: lines = [] line = self._line_iter.peek() while(not self._is_section_break() and @@ -209,8 +203,7 @@ class GoogleDocstring: line = self._line_iter.peek() return lines - def _consume_contiguous(self): - # type: () -> List[str] + def _consume_contiguous(self) -> List[str]: lines = [] while (self._line_iter.has_next() and self._line_iter.peek() and @@ -218,8 +211,7 @@ class GoogleDocstring: lines.append(next(self._line_iter)) return lines - def _consume_empty(self): - # type: () -> List[str] + def _consume_empty(self) -> List[str]: lines = [] line = self._line_iter.peek() while self._line_iter.has_next() and not line: @@ -227,8 +219,8 @@ class GoogleDocstring: line = self._line_iter.peek() return lines - def _consume_field(self, parse_type=True, prefer_type=False): - # type: (bool, bool) -> Tuple[str, str, List[str]] + def _consume_field(self, parse_type: bool = True, prefer_type: bool = False + ) -> Tuple[str, str, List[str]]: line = next(self._line_iter) before, colon, after = self._partition_field_on_colon(line) @@ -249,8 +241,8 @@ class GoogleDocstring: _descs = self.__class__(_descs, self._config).lines() return _name, _type, _descs - def _consume_fields(self, parse_type=True, prefer_type=False): - # type: (bool, bool) -> List[Tuple[str, str, List[str]]] + def _consume_fields(self, parse_type: bool = True, prefer_type: bool = False + ) -> List[Tuple[str, str, List[str]]]: self._consume_empty() fields = [] while not self._is_section_break(): @@ -259,8 +251,7 @@ class GoogleDocstring: fields.append((_name, _type, _desc,)) return fields - def _consume_inline_attribute(self): - # type: () -> Tuple[str, List[str]] + def _consume_inline_attribute(self) -> Tuple[str, List[str]]: line = next(self._line_iter) _type, colon, _desc = self._partition_field_on_colon(line) if not colon or not _desc: @@ -270,8 +261,7 @@ class GoogleDocstring: _descs = self.__class__(_descs, self._config).lines() return _type, _descs - def _consume_returns_section(self): - # type: () -> List[Tuple[str, str, List[str]]] + def _consume_returns_section(self) -> List[Tuple[str, str, List[str]]]: lines = self._dedent(self._consume_to_next_section()) if lines: before, colon, after = self._partition_field_on_colon(lines[0]) @@ -290,44 +280,38 @@ class GoogleDocstring: else: return [] - def _consume_usage_section(self): - # type: () -> List[str] + def _consume_usage_section(self) -> List[str]: lines = self._dedent(self._consume_to_next_section()) return lines - def _consume_section_header(self): - # type: () -> str + def _consume_section_header(self) -> str: section = next(self._line_iter) stripped_section = section.strip(':') if stripped_section.lower() in self._sections: section = stripped_section return section - def _consume_to_end(self): - # type: () -> List[str] + def _consume_to_end(self) -> List[str]: lines = [] while self._line_iter.has_next(): lines.append(next(self._line_iter)) return lines - def _consume_to_next_section(self): - # type: () -> List[str] + def _consume_to_next_section(self) -> List[str]: self._consume_empty() lines = [] while not self._is_section_break(): lines.append(next(self._line_iter)) return lines + self._consume_empty() - def _dedent(self, lines, full=False): - # type: (List[str], bool) -> List[str] + def _dedent(self, lines: List[str], full: bool = False) -> List[str]: if full: return [line.lstrip() for line in lines] else: min_indent = self._get_min_indent(lines) return [line[min_indent:] for line in lines] - def _escape_args_and_kwargs(self, name): - # type: (str) -> str + def _escape_args_and_kwargs(self, name: str) -> str: if name.endswith('_'): name = name[:-1] + r'\_' @@ -338,8 +322,7 @@ class GoogleDocstring: else: return name - def _fix_field_desc(self, desc): - # type: (List[str]) -> List[str] + def _fix_field_desc(self, desc: List[str]) -> List[str]: if self._is_list(desc): desc = [''] + desc elif desc[0].endswith('::'): @@ -352,8 +335,7 @@ class GoogleDocstring: desc = ['', desc[0]] + self._indent(desc_block, 4) return desc - def _format_admonition(self, admonition, lines): - # type: (str, List[str]) -> List[str] + def _format_admonition(self, admonition: str, lines: List[str]) -> List[str]: lines = self._strip_empty(lines) if len(lines) == 1: return ['.. %s:: %s' % (admonition, lines[0].strip()), ''] @@ -363,8 +345,7 @@ class GoogleDocstring: else: return ['.. %s::' % admonition, ''] - def _format_block(self, prefix, lines, padding=None): - # type: (str, List[str], str) -> List[str] + def _format_block(self, prefix: str, lines: List[str], padding: str = None) -> List[str]: if lines: if padding is None: padding = ' ' * len(prefix) @@ -380,9 +361,9 @@ class GoogleDocstring: else: return [prefix] - def _format_docutils_params(self, fields, field_role='param', - type_role='type'): - # type: (List[Tuple[str, str, List[str]]], str, str) -> List[str] + def _format_docutils_params(self, fields: List[Tuple[str, str, List[str]]], + field_role: str = 'param', type_role: str = 'type' + ) -> List[str]: lines = [] for _name, _type, _desc in fields: _desc = self._strip_empty(_desc) @@ -397,8 +378,7 @@ class GoogleDocstring: lines.append(':%s %s: %s' % (type_role, _name, _type)) return lines + [''] - def _format_field(self, _name, _type, _desc): - # type: (str, str, List[str]) -> List[str] + def _format_field(self, _name: str, _type: str, _desc: List[str]) -> List[str]: _desc = self._strip_empty(_desc) has_desc = any(_desc) separator = has_desc and ' -- ' or '' @@ -427,8 +407,8 @@ class GoogleDocstring: else: return [field] - def _format_fields(self, field_type, fields): - # type: (str, List[Tuple[str, str, List[str]]]) -> List[str] + def _format_fields(self, field_type: str, fields: List[Tuple[str, str, List[str]]] + ) -> List[str]: field_type = ':%s:' % field_type.strip() padding = ' ' * len(field_type) multi = len(fields) > 1 @@ -446,8 +426,7 @@ class GoogleDocstring: lines.append('') return lines - def _get_current_indent(self, peek_ahead=0): - # type: (int) -> int + def _get_current_indent(self, peek_ahead: int = 0) -> int: line = self._line_iter.peek(peek_ahead + 1)[peek_ahead] while line != self._line_iter.sentinel: if line: @@ -456,22 +435,19 @@ class GoogleDocstring: line = self._line_iter.peek(peek_ahead + 1)[peek_ahead] return 0 - def _get_indent(self, line): - # type: (str) -> int + def _get_indent(self, line: str) -> int: for i, s in enumerate(line): if not s.isspace(): return i return len(line) - def _get_initial_indent(self, lines): - # type: (List[str]) -> int + def _get_initial_indent(self, lines: List[str]) -> int: for line in lines: if line: return self._get_indent(line) return 0 - def _get_min_indent(self, lines): - # type: (List[str]) -> int + def _get_min_indent(self, lines: List[str]) -> int: min_indent = None for line in lines: if line: @@ -482,12 +458,10 @@ class GoogleDocstring: min_indent = indent return min_indent or 0 - def _indent(self, lines, n=4): - # type: (List[str], int) -> List[str] + def _indent(self, lines: List[str], n: int = 4) -> List[str]: return [(' ' * n) + line for line in lines] - def _is_indented(self, line, indent=1): - # type: (str, int) -> bool + def _is_indented(self, line: str, indent: int = 1) -> bool: for i, s in enumerate(line): if i >= indent: return True @@ -495,8 +469,7 @@ class GoogleDocstring: return False return False - def _is_list(self, lines): - # type: (List[str]) -> bool + def _is_list(self, lines: List[str]) -> bool: if not lines: return False if _bullet_list_regex.match(lines[0]): @@ -513,8 +486,7 @@ class GoogleDocstring: break return next_indent > indent - def _is_section_header(self): - # type: () -> bool + def _is_section_header(self) -> bool: section = self._line_iter.peek().lower() match = _google_section_regex.match(section) if match and section.strip(':') in self._sections: @@ -528,8 +500,7 @@ class GoogleDocstring: return True return False - def _is_section_break(self): - # type: () -> bool + def _is_section_break(self) -> bool: line = self._line_iter.peek() return (not self._line_iter.has_next() or self._is_section_header() or @@ -537,9 +508,7 @@ class GoogleDocstring: line and not self._is_indented(line, self._section_indent))) - def _load_custom_sections(self): - # type: () -> None - + def _load_custom_sections(self) -> None: if self._config.napoleon_custom_sections is not None: for entry in self._config.napoleon_custom_sections: if isinstance(entry, str): @@ -554,8 +523,7 @@ class GoogleDocstring: self._sections.get(entry[1].lower(), self._parse_custom_generic_section) - def _parse(self): - # type: () -> None + def _parse(self) -> None: self._parsed_lines = self._consume_empty() if self._name and self._what in ('attribute', 'data', 'property'): @@ -594,16 +562,14 @@ class GoogleDocstring: lines = self._consume_to_next_section() return self._format_admonition(admonition, lines) - def _parse_attribute_docstring(self): - # type: () -> List[str] + def _parse_attribute_docstring(self) -> List[str]: _type, _desc = self._consume_inline_attribute() lines = self._format_field('', '', _desc) if _type: lines.extend(['', ':type: %s' % _type]) return lines - def _parse_attributes_section(self, section): - # type: (str) -> List[str] + def _parse_attributes_section(self, section: str) -> List[str]: lines = [] for _name, _type, _desc in self._consume_fields(): if self._config.napoleon_use_ivar: @@ -624,8 +590,7 @@ class GoogleDocstring: lines.append('') return lines - def _parse_examples_section(self, section): - # type: (str) -> List[str] + def _parse_examples_section(self, section: str) -> List[str]: labels = { 'example': _('Example'), 'examples': _('Examples'), @@ -638,16 +603,14 @@ class GoogleDocstring: # for now, no admonition for simple custom sections return self._parse_generic_section(section, False) - def _parse_usage_section(self, section): - # type: (str) -> List[str] + def _parse_usage_section(self, section: str) -> List[str]: header = ['.. rubric:: Usage:', ''] block = ['.. code-block:: python', ''] lines = self._consume_usage_section() lines = self._indent(lines, 3) return header + block + lines + [''] - def _parse_generic_section(self, section, use_admonition): - # type: (str, bool) -> List[str] + def _parse_generic_section(self, section: str, use_admonition: bool) -> List[str]: lines = self._strip_empty(self._consume_to_next_section()) lines = self._dedent(lines) if use_admonition: @@ -660,8 +623,7 @@ class GoogleDocstring: else: return [header, ''] - def _parse_keyword_arguments_section(self, section): - # type: (str) -> List[str] + def _parse_keyword_arguments_section(self, section: str) -> List[str]: fields = self._consume_fields() if self._config.napoleon_use_keyword: return self._format_docutils_params( @@ -671,8 +633,7 @@ class GoogleDocstring: else: return self._format_fields(_('Keyword Arguments'), fields) - def _parse_methods_section(self, section): - # type: (str) -> List[str] + def _parse_methods_section(self, section: str) -> List[str]: lines = [] # type: List[str] for _name, _type, _desc in self._consume_fields(parse_type=False): lines.append('.. method:: %s' % _name) @@ -681,25 +642,21 @@ class GoogleDocstring: lines.append('') return lines - def _parse_notes_section(self, section): - # type: (str) -> List[str] + def _parse_notes_section(self, section: str) -> List[str]: use_admonition = self._config.napoleon_use_admonition_for_notes return self._parse_generic_section(_('Notes'), use_admonition) - def _parse_other_parameters_section(self, section): - # type: (str) -> List[str] + def _parse_other_parameters_section(self, section: str) -> List[str]: return self._format_fields(_('Other Parameters'), self._consume_fields()) - def _parse_parameters_section(self, section): - # type: (str) -> List[str] + def _parse_parameters_section(self, section: str) -> List[str]: fields = self._consume_fields() if self._config.napoleon_use_param: return self._format_docutils_params(fields) else: return self._format_fields(_('Parameters'), fields) - def _parse_raises_section(self, section): - # type: (str) -> List[str] + def _parse_raises_section(self, section: str) -> List[str]: fields = self._consume_fields(parse_type=False, prefer_type=True) lines = [] # type: List[str] for _name, _type, _desc in fields: @@ -714,13 +671,11 @@ class GoogleDocstring: lines.append('') return lines - def _parse_references_section(self, section): - # type: (str) -> List[str] + def _parse_references_section(self, section: str) -> List[str]: use_admonition = self._config.napoleon_use_admonition_for_references return self._parse_generic_section(_('References'), use_admonition) - def _parse_returns_section(self, section): - # type: (str) -> List[str] + def _parse_returns_section(self, section: str) -> List[str]: fields = self._consume_returns_section() multi = len(fields) > 1 if multi: @@ -748,21 +703,17 @@ class GoogleDocstring: lines.append('') return lines - def _parse_see_also_section(self, section): - # type (str) -> List[str] + def _parse_see_also_section(self, section: str) -> List[str]: return self._parse_admonition('seealso', section) - def _parse_warns_section(self, section): - # type: (str) -> List[str] + def _parse_warns_section(self, section: str) -> List[str]: return self._format_fields(_('Warns'), self._consume_fields()) - def _parse_yields_section(self, section): - # type: (str) -> List[str] + def _parse_yields_section(self, section: str) -> List[str]: fields = self._consume_returns_section() return self._format_fields(_('Yields'), fields) - def _partition_field_on_colon(self, line): - # type: (str) -> Tuple[str, str, str] + def _partition_field_on_colon(self, line: str) -> Tuple[str, str, str]: before_colon = [] after_colon = [] colon = '' @@ -784,8 +735,7 @@ class GoogleDocstring: colon, "".join(after_colon).strip()) - def _qualify_name(self, attr_name, klass): - # type: (str, Type) -> str + def _qualify_name(self, attr_name: str, klass: Type) -> str: if klass and '.' not in attr_name: if attr_name.startswith('~'): attr_name = attr_name[1:] @@ -796,8 +746,7 @@ class GoogleDocstring: return '~%s.%s' % (q, attr_name) return attr_name - def _strip_empty(self, lines): - # type: (List[str]) -> List[str] + def _strip_empty(self, lines: List[str]) -> List[str]: if lines: start = -1 for i, line in enumerate(lines): @@ -910,14 +859,14 @@ class NumpyDocstring(GoogleDocstring): The lines of the docstring in a list. """ - def __init__(self, docstring, config=None, app=None, what='', name='', - obj=None, options=None): - # type: (Union[str, List[str]], SphinxConfig, Sphinx, str, str, Any, Any) -> None + def __init__(self, docstring: Union[str, List[str]], config: SphinxConfig = None, + app: Sphinx = None, what: str = '', name: str = '', + obj: Any = None, options: Any = None) -> None: self._directive_sections = ['.. index::'] super().__init__(docstring, config, app, what, name, obj, options) - def _consume_field(self, parse_type=True, prefer_type=False): - # type: (bool, bool) -> Tuple[str, str, List[str]] + def _consume_field(self, parse_type: bool = True, prefer_type: bool = False + ) -> Tuple[str, str, List[str]]: line = next(self._line_iter) if parse_type: _name, _, _type = self._partition_field_on_colon(line) @@ -933,20 +882,17 @@ class NumpyDocstring(GoogleDocstring): _desc = self.__class__(_desc, self._config).lines() return _name, _type, _desc - def _consume_returns_section(self): - # type: () -> List[Tuple[str, str, List[str]]] + def _consume_returns_section(self) -> List[Tuple[str, str, List[str]]]: return self._consume_fields(prefer_type=True) - def _consume_section_header(self): - # type: () -> str + def _consume_section_header(self) -> str: section = next(self._line_iter) if not _directive_regex.match(section): # Consume the header underline next(self._line_iter) return section - def _is_section_break(self): - # type: () -> bool + def _is_section_break(self) -> bool: line1, line2 = self._line_iter.peek(2) return (not self._line_iter.has_next() or self._is_section_header() or @@ -955,8 +901,7 @@ class NumpyDocstring(GoogleDocstring): line1 and not self._is_indented(line1, self._section_indent))) - def _is_section_header(self): - # type: () -> bool + def _is_section_header(self) -> bool: section, underline = self._line_iter.peek(2) section = section.lower() if section in self._sections and isinstance(underline, str): @@ -968,16 +913,14 @@ class NumpyDocstring(GoogleDocstring): return True return False - def _parse_see_also_section(self, section): - # type: (str) -> List[str] + def _parse_see_also_section(self, section: str) -> List[str]: lines = self._consume_to_next_section() try: return self._parse_numpydoc_see_also_section(lines) except ValueError: return self._format_admonition('seealso', lines) - def _parse_numpydoc_see_also_section(self, content): - # type: (List[str]) -> List[str] + def _parse_numpydoc_see_also_section(self, content: List[str]) -> List[str]: """ Derived from the NumpyDoc implementation of _parse_see_also. @@ -991,8 +934,7 @@ class NumpyDocstring(GoogleDocstring): """ items = [] - def parse_item_name(text): - # type: (str) -> Tuple[str, str] + def parse_item_name(text: str) -> Tuple[str, str]: """Match ':role:`name`' or 'name'""" m = self._name_rgx.match(text) if m: @@ -1003,8 +945,7 @@ class NumpyDocstring(GoogleDocstring): return g[2], g[1] raise ValueError("%s is not a item name" % text) - def push_item(name, rest): - # type: (str, List[str]) -> None + def push_item(name: str, rest: List[str]) -> None: if not name: return name, role = parse_item_name(name) diff --git a/sphinx/ext/napoleon/iterators.py b/sphinx/ext/napoleon/iterators.py index d9de4fb80..3aa728e44 100644 --- a/sphinx/ext/napoleon/iterators.py +++ b/sphinx/ext/napoleon/iterators.py @@ -11,10 +11,7 @@ """ import collections - -if False: - # For type annotation - from typing import Any, Iterable # NOQA +from typing import Any, Iterable class peek_iter: @@ -50,8 +47,7 @@ class peek_iter: be set to a new object instance: ``object()``. """ - def __init__(self, *args): - # type: (Any) -> None + def __init__(self, *args) -> None: """__init__(o, sentinel=None)""" self._iterable = iter(*args) # type: Iterable self._cache = collections.deque() # type: collections.deque @@ -60,18 +56,15 @@ class peek_iter: else: self.sentinel = object() - def __iter__(self): - # type: () -> peek_iter + def __iter__(self) -> "peek_iter": return self - def __next__(self, n=None): - # type: (int) -> Any + def __next__(self, n: int = None) -> Any: # note: prevent 2to3 to transform self.next() in next(self) which # causes an infinite loop ! return getattr(self, 'next')(n) - def _fillcache(self, n): - # type: (int) -> None + def _fillcache(self, n: int) -> None: """Cache `n` items. If `n` is 0 or None, then 1 item is cached.""" if not n: n = 1 @@ -82,8 +75,7 @@ class peek_iter: while len(self._cache) < n: self._cache.append(self.sentinel) - def has_next(self): - # type: () -> bool + def has_next(self) -> bool: """Determine if iterator is exhausted. Returns @@ -98,8 +90,7 @@ class peek_iter: """ return self.peek() != self.sentinel - def next(self, n=None): - # type: (int) -> Any + def next(self, n: int = None) -> Any: """Get the next item or `n` items of the iterator. Parameters @@ -134,8 +125,7 @@ class peek_iter: result = [self._cache.popleft() for i in range(n)] return result - def peek(self, n=None): - # type: (int) -> Any + def peek(self, n: int = None) -> Any: """Preview the next item or `n` items of the iterator. The iterator is not advanced when peek is called. @@ -218,8 +208,7 @@ class modify_iter(peek_iter): "whitespace." """ - def __init__(self, *args, **kwargs): - # type: (Any, Any) -> None + def __init__(self, *args, **kwargs) -> None: """__init__(o, sentinel=None, modifier=lambda x: x)""" if 'modifier' in kwargs: self.modifier = kwargs['modifier'] @@ -233,8 +222,7 @@ class modify_iter(peek_iter): 'modifier must be callable') super().__init__(*args) - def _fillcache(self, n): - # type: (int) -> None + def _fillcache(self, n: int) -> None: """Cache `n` modified items. If `n` is 0 or None, 1 item is cached. Each item returned by the iterator is passed through the diff --git a/sphinx/ext/todo.py b/sphinx/ext/todo.py index 2da27700b..49cd5019e 100644 --- a/sphinx/ext/todo.py +++ b/sphinx/ext/todo.py @@ -12,29 +12,27 @@ """ import warnings +from typing import Any, Dict, Iterable, List, Tuple from typing import cast from docutils import nodes +from docutils.nodes import Element, Node from docutils.parsers.rst import directives from docutils.parsers.rst.directives.admonitions import BaseAdmonition import sphinx +from sphinx.application import Sphinx from sphinx.deprecation import RemovedInSphinx40Warning from sphinx.domains import Domain +from sphinx.environment import BuildEnvironment from sphinx.errors import NoUri from sphinx.locale import _, __ from sphinx.util import logging from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import make_refnode from sphinx.util.texescape import tex_escape_map - -if False: - # For type annotation - from typing import Any, Dict, Iterable, List, Tuple # NOQA - from sphinx.application import Sphinx # NOQA - from sphinx.environment import BuildEnvironment # NOQA - from sphinx.writers.html import HTMLTranslator # NOQA - from sphinx.writers.latex import LaTeXTranslator # NOQA +from sphinx.writers.html import HTMLTranslator +from sphinx.writers.latex import LaTeXTranslator logger = logging.getLogger(__name__) @@ -62,12 +60,11 @@ class Todo(BaseAdmonition, SphinxDirective): 'name': directives.unchanged, } - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: if not self.options.get('class'): self.options['class'] = ['admonition-todo'] - (todo,) = super().run() # type: Tuple[nodes.Node] + (todo,) = super().run() # type: Tuple[Node] if isinstance(todo, nodes.system_message): return [todo] elif isinstance(todo, todo_node): @@ -86,21 +83,18 @@ class TodoDomain(Domain): label = 'todo' @property - def todos(self): - # type: () -> Dict[str, List[todo_node]] + def todos(self) -> Dict[str, List[todo_node]]: return self.data.setdefault('todos', {}) - def clear_doc(self, docname): - # type: (str) -> None + def clear_doc(self, docname: str) -> None: self.todos.pop(docname, None) - def merge_domaindata(self, docnames, otherdata): - # type: (List[str], Dict) -> None + def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: for docname in docnames: self.todos[docname] = otherdata['todos'][docname] - def process_doc(self, env, docname, document): - # type: (BuildEnvironment, str, nodes.document) -> None + def process_doc(self, env: BuildEnvironment, docname: str, + document: nodes.document) -> None: todos = self.todos.setdefault(docname, []) for todo in document.traverse(todo_node): env.app.emit('todo-defined', todo) @@ -111,8 +105,7 @@ class TodoDomain(Domain): location=todo) -def process_todos(app, doctree): - # type: (Sphinx, nodes.document) -> None +def process_todos(app: Sphinx, doctree: nodes.document) -> None: warnings.warn('process_todos() is deprecated.', RemovedInSphinx40Warning) # collect all todos in the environment # this is not done in the directive itself because it some transformations @@ -150,16 +143,14 @@ class TodoList(SphinxDirective): final_argument_whitespace = False option_spec = {} # type: Dict - def run(self): - # type: () -> List[nodes.Node] + def run(self) -> List[Node]: # Simply insert an empty todolist node which will be replaced later # when process_todo_nodes is called return [todolist('')] class TodoListProcessor: - def __init__(self, app, doctree, docname): - # type: (Sphinx, nodes.document, str) -> None + def __init__(self, app: Sphinx, doctree: nodes.document, docname: str) -> None: self.builder = app.builder self.config = app.config self.env = app.env @@ -167,8 +158,7 @@ class TodoListProcessor: self.process(doctree, docname) - def process(self, doctree, docname): - # type: (nodes.document, str) -> None + def process(self, doctree: nodes.document, docname: str) -> None: todos = sum(self.domain.todos.values(), []) for node in doctree.traverse(todolist): if not self.config.todo_include_todos: @@ -176,7 +166,7 @@ class TodoListProcessor: continue if node.get('ids'): - content = [nodes.target()] # type: List[nodes.Element] + content = [nodes.target()] # type: List[Element] else: content = [] @@ -194,8 +184,7 @@ class TodoListProcessor: node.replace_self(content) - def create_todo_reference(self, todo, docname): - # type: (todo_node, str) -> nodes.paragraph + def create_todo_reference(self, todo: todo_node, docname: str) -> nodes.paragraph: if self.config.todo_link_only: description = _('<>') else: @@ -224,8 +213,7 @@ class TodoListProcessor: return para -def process_todo_nodes(app, doctree, fromdocname): - # type: (Sphinx, nodes.document, str) -> None +def process_todo_nodes(app: Sphinx, doctree: nodes.document, fromdocname: str) -> None: """Replace all todolist nodes with a list of the collected todos. Augment each todo with a backlink to the original location. """ @@ -236,7 +224,7 @@ def process_todo_nodes(app, doctree, fromdocname): for node in doctree.traverse(todolist): if node.get('ids'): - content = [nodes.target()] # type: List[nodes.Element] + content = [nodes.target()] # type: List[Element] else: content = [] @@ -280,8 +268,7 @@ def process_todo_nodes(app, doctree, fromdocname): node.replace_self(content) -def purge_todos(app, env, docname): - # type: (Sphinx, BuildEnvironment, str) -> None +def purge_todos(app: Sphinx, env: BuildEnvironment, docname: str) -> None: warnings.warn('purge_todos() is deprecated.', RemovedInSphinx40Warning) if not hasattr(env, 'todo_all_todos'): return @@ -289,8 +276,8 @@ def purge_todos(app, env, docname): if todo['docname'] != docname] -def merge_info(app, env, docnames, other): - # type: (Sphinx, BuildEnvironment, Iterable[str], BuildEnvironment) -> None +def merge_info(app: Sphinx, env: BuildEnvironment, docnames: Iterable[str], + other: BuildEnvironment) -> None: warnings.warn('merge_info() is deprecated.', RemovedInSphinx40Warning) if not hasattr(other, 'todo_all_todos'): return @@ -299,21 +286,18 @@ def merge_info(app, env, docnames, other): env.todo_all_todos.extend(other.todo_all_todos) # type: ignore -def visit_todo_node(self, node): - # type: (HTMLTranslator, todo_node) -> None +def visit_todo_node(self: HTMLTranslator, node: todo_node) -> None: if self.config.todo_include_todos: self.visit_admonition(node) else: raise nodes.SkipNode -def depart_todo_node(self, node): - # type: (HTMLTranslator, todo_node) -> None +def depart_todo_node(self: HTMLTranslator, node: todo_node) -> None: self.depart_admonition(node) -def latex_visit_todo_node(self, node): - # type: (LaTeXTranslator, todo_node) -> None +def latex_visit_todo_node(self: LaTeXTranslator, node: todo_node) -> None: if self.config.todo_include_todos: self.body.append('\n\\begin{sphinxadmonition}{note}{') self.body.append(self.hypertarget_to(node)) @@ -324,13 +308,11 @@ def latex_visit_todo_node(self, node): raise nodes.SkipNode -def latex_depart_todo_node(self, node): - # type: (LaTeXTranslator, todo_node) -> None +def latex_depart_todo_node(self: LaTeXTranslator, node: todo_node) -> None: self.body.append('\\end{sphinxadmonition}\n') -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: Sphinx) -> Dict[str, Any]: app.add_event('todo-defined') app.add_config_value('todo_include_todos', False, 'html') app.add_config_value('todo_link_only', False, 'html') diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py index ee6710495..36ddfb580 100644 --- a/sphinx/ext/viewcode.py +++ b/sphinx/ext/viewcode.py @@ -10,29 +10,27 @@ import traceback import warnings +from typing import Any, Dict, Iterable, Iterator, Set, Tuple from docutils import nodes +from docutils.nodes import Element, Node import sphinx from sphinx import addnodes +from sphinx.application import Sphinx +from sphinx.config import Config from sphinx.deprecation import RemovedInSphinx30Warning +from sphinx.environment import BuildEnvironment from sphinx.locale import _, __ from sphinx.pycode import ModuleAnalyzer from sphinx.util import get_full_modname, logging, status_iterator from sphinx.util.nodes import make_refnode -if False: - # For type annotation - from typing import Any, Dict, Iterable, Iterator, Set, Tuple # NOQA - from sphinx.application import Sphinx # NOQA - from sphinx.config import Config # NOQA - from sphinx.environment import BuildEnvironment # NOQA logger = logging.getLogger(__name__) -def _get_full_modname(app, modname, attribute): - # type: (Sphinx, str, str) -> str +def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> str: try: return get_full_modname(modname, attribute) except AttributeError: @@ -50,8 +48,7 @@ def _get_full_modname(app, modname, attribute): return None -def doctree_read(app, doctree): - # type: (Sphinx, nodes.Node) -> None +def doctree_read(app: Sphinx, doctree: Node) -> None: env = app.builder.env if not hasattr(env, '_viewcode_modules'): env._viewcode_modules = {} # type: ignore @@ -122,8 +119,8 @@ def doctree_read(app, doctree): signode += onlynode -def env_merge_info(app, env, docnames, other): - # type: (Sphinx, BuildEnvironment, Iterable[str], BuildEnvironment) -> None +def env_merge_info(app: Sphinx, env: BuildEnvironment, docnames: Iterable[str], + other: BuildEnvironment) -> None: if not hasattr(other, '_viewcode_modules'): return # create a _viewcode_modules dict on the main environment @@ -133,8 +130,8 @@ def env_merge_info(app, env, docnames, other): env._viewcode_modules.update(other._viewcode_modules) # type: ignore -def missing_reference(app, env, node, contnode): - # type: (Sphinx, BuildEnvironment, nodes.Element, nodes.Node) -> nodes.Node +def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnode: Node + ) -> Node: # resolve our "viewcode" reference nodes -- they need special treatment if node['reftype'] == 'viewcode': return make_refnode(app.builder, node['refdoc'], node['reftarget'], @@ -143,8 +140,7 @@ def missing_reference(app, env, node, contnode): return None -def collect_pages(app): - # type: (Sphinx) -> Iterator[Tuple[str, Dict[str, Any], str]] +def collect_pages(app: Sphinx) -> Iterator[Tuple[str, Dict[str, Any], str]]: env = app.builder.env if not hasattr(env, '_viewcode_modules'): return @@ -238,16 +234,14 @@ def collect_pages(app): yield ('_modules/index', context, 'page.html') -def migrate_viewcode_import(app, config): - # type: (Sphinx, Config) -> None +def migrate_viewcode_import(app: Sphinx, config: Config) -> None: if config.viewcode_import is not None: warnings.warn('viewcode_import was renamed to viewcode_follow_imported_members. ' 'Please update your configuration.', RemovedInSphinx30Warning, stacklevel=2) -def setup(app): - # type: (Sphinx) -> Dict[str, Any] +def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value('viewcode_import', None, False) app.add_config_value('viewcode_enable_epub', False, False) app.add_config_value('viewcode_follow_imported_members', True, False) diff --git a/sphinx/highlighting.py b/sphinx/highlighting.py index 2d825e9f1..b4e63209f 100644 --- a/sphinx/highlighting.py +++ b/sphinx/highlighting.py @@ -139,7 +139,8 @@ class PygmentsBridge: lexer = lexers['none'] if lang in lexers: - lexer = lexers[lang] + # just return custom lexers here (without installing raiseonerror filter) + return lexers[lang] elif lang in lexer_classes: lexer = lexer_classes[lang](**opts) else: diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py index e11a75941..92153d1d0 100644 --- a/sphinx/pycode/__init__.py +++ b/sphinx/pycode/__init__.py @@ -11,29 +11,25 @@ import re from io import StringIO from os import path +from typing import Any, Dict, IO, List, Tuple from zipfile import ZipFile from sphinx.errors import PycodeError from sphinx.pycode.parser import Parser from sphinx.util import get_module_source, detect_encoding -if False: - # For type annotation - from typing import Any, Dict, IO, List, Tuple # NOQA - class ModuleAnalyzer: # cache for analyzer objects -- caches both by module and file name cache = {} # type: Dict[Tuple[str, str], Any] @classmethod - def for_string(cls, string, modname, srcname=''): - # type: (str, str, str) -> ModuleAnalyzer + def for_string(cls, string: str, modname: str, srcname: str = '' + ) -> "ModuleAnalyzer": return cls(StringIO(string), modname, srcname, decoded=True) @classmethod - def for_file(cls, filename, modname): - # type: (str, str) -> ModuleAnalyzer + def for_file(cls, filename: str, modname: str) -> "ModuleAnalyzer": if ('file', filename) in cls.cache: return cls.cache['file', filename] try: @@ -48,8 +44,7 @@ class ModuleAnalyzer: return obj @classmethod - def for_egg(cls, filename, modname): - # type: (str, str) -> ModuleAnalyzer + def for_egg(cls, filename: str, modname: str) -> "ModuleAnalyzer": SEP = re.escape(path.sep) eggpath, relpath = re.split('(?<=\\.egg)' + SEP, filename) try: @@ -60,8 +55,7 @@ class ModuleAnalyzer: raise PycodeError('error opening %r' % filename, exc) @classmethod - def for_module(cls, modname): - # type: (str) -> ModuleAnalyzer + def for_module(cls, modname: str) -> "ModuleAnalyzer": if ('module', modname) in cls.cache: entry = cls.cache['module', modname] if isinstance(entry, PycodeError): @@ -80,8 +74,7 @@ class ModuleAnalyzer: cls.cache['module', modname] = obj return obj - def __init__(self, source, modname, srcname, decoded=False): - # type: (IO, str, str, bool) -> None + def __init__(self, source: IO, modname: str, srcname: str, decoded: bool = False) -> None: self.modname = modname # name of the module self.srcname = srcname # name of the source file @@ -100,8 +93,7 @@ class ModuleAnalyzer: self.tagorder = None # type: Dict[str, int] self.tags = None # type: Dict[str, Tuple[str, int, int]] - def parse(self): - # type: () -> None + def parse(self) -> None: """Parse the source code.""" try: parser = Parser(self.code, self.encoding) @@ -119,16 +111,14 @@ class ModuleAnalyzer: except Exception as exc: raise PycodeError('parsing %r failed: %r' % (self.srcname, exc)) - def find_attr_docs(self): - # type: () -> Dict[Tuple[str, str], List[str]] + def find_attr_docs(self) -> Dict[Tuple[str, str], List[str]]: """Find class and module-level attributes and their documentation.""" if self.attr_docs is None: self.parse() return self.attr_docs - def find_tags(self): - # type: () -> Dict[str, Tuple[str, int, int]] + def find_tags(self) -> Dict[str, Tuple[str, int, int]]: """Find class, function and method definitions and their location.""" if self.tags is None: self.parse() diff --git a/sphinx/pycode/parser.py b/sphinx/pycode/parser.py index 1746537bb..3603e3cc3 100644 --- a/sphinx/pycode/parser.py +++ b/sphinx/pycode/parser.py @@ -15,10 +15,8 @@ import sys import tokenize from token import NAME, NEWLINE, INDENT, DEDENT, NUMBER, OP, STRING from tokenize import COMMENT, NL +from typing import Any, Dict, List, Tuple -if False: - # For type annotation - from typing import Any, Dict, List, Tuple # NOQA comment_re = re.compile('^\\s*#: ?(.*)\r?\n?$') indent_re = re.compile('^\\s*$') @@ -31,13 +29,11 @@ else: ASSIGN_NODES = (ast.Assign) -def filter_whitespace(code): - # type: (str) -> str +def filter_whitespace(code: str) -> str: return code.replace('\f', ' ') # replace FF (form feed) with whitespace -def get_assign_targets(node): - # type: (ast.AST) -> List[ast.expr] +def get_assign_targets(node: ast.AST) -> List[ast.expr]: """Get list of targets from Assign and AnnAssign node.""" if isinstance(node, ast.Assign): return node.targets @@ -45,8 +41,7 @@ def get_assign_targets(node): return [node.target] # type: ignore -def get_lvar_names(node, self=None): - # type: (ast.AST, ast.arg) -> List[str] +def get_lvar_names(node: ast.AST, self: ast.arg = None) -> List[str]: """Convert assignment-AST to variable names. This raises `TypeError` if the assignment does not create new variable:: @@ -88,11 +83,9 @@ def get_lvar_names(node, self=None): raise NotImplementedError('Unexpected node name %r' % node_name) -def dedent_docstring(s): - # type: (str) -> str +def dedent_docstring(s: str) -> str: """Remove common leading indentation from docstring.""" - def dummy(): - # type: () -> None + def dummy() -> None: # dummy function to mock `inspect.getdoc`. pass @@ -104,16 +97,15 @@ def dedent_docstring(s): class Token: """Better token wrapper for tokenize module.""" - def __init__(self, kind, value, start, end, source): - # type: (int, Any, Tuple[int, int], Tuple[int, int], str) -> None + def __init__(self, kind: int, value: Any, start: Tuple[int, int], end: Tuple[int, int], + source: str) -> None: self.kind = kind self.value = value self.start = start self.end = end self.source = source - def __eq__(self, other): - # type: (Any) -> bool + def __eq__(self, other: Any) -> bool: if isinstance(other, int): return self.kind == other elif isinstance(other, str): @@ -125,32 +117,27 @@ class Token: else: raise ValueError('Unknown value: %r' % other) - def match(self, *conditions): - # type: (Any) -> bool + def match(self, *conditions) -> bool: return any(self == candidate for candidate in conditions) - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: return '' % (tokenize.tok_name[self.kind], self.value.strip()) class TokenProcessor: - def __init__(self, buffers): - # type: (List[str]) -> None + def __init__(self, buffers: List[str]) -> None: lines = iter(buffers) self.buffers = buffers self.tokens = tokenize.generate_tokens(lambda: next(lines)) self.current = None # type: Token self.previous = None # type: Token - def get_line(self, lineno): - # type: (int) -> str + def get_line(self, lineno: int) -> str: """Returns specified line.""" return self.buffers[lineno - 1] - def fetch_token(self): - # type: () -> Token + def fetch_token(self) -> Token: """Fetch a next token from source code. Returns ``False`` if sequence finished. @@ -163,8 +150,7 @@ class TokenProcessor: return self.current - def fetch_until(self, condition): - # type: (Any) -> List[Token] + def fetch_until(self, condition: Any) -> List[Token]: """Fetch tokens until specified token appeared. .. note:: This also handles parenthesis well. @@ -191,13 +177,11 @@ class AfterCommentParser(TokenProcessor): and returns the comments for variable if exists. """ - def __init__(self, lines): - # type: (List[str]) -> None + def __init__(self, lines: List[str]) -> None: super().__init__(lines) self.comment = None # type: str - def fetch_rvalue(self): - # type: () -> List[Token] + def fetch_rvalue(self) -> List[Token]: """Fetch right-hand value of assignment.""" tokens = [] while self.fetch_token(): @@ -217,8 +201,7 @@ class AfterCommentParser(TokenProcessor): return tokens - def parse(self): - # type: () -> None + def parse(self) -> None: """Parse the code and obtain comment after assignment.""" # skip lvalue (or whole of AnnAssign) while not self.fetch_token().match([OP, '='], NEWLINE, COMMENT): @@ -235,8 +218,7 @@ class AfterCommentParser(TokenProcessor): class VariableCommentPicker(ast.NodeVisitor): """Python source code parser to pick up variable comments.""" - def __init__(self, buffers, encoding): - # type: (List[str], str) -> None + def __init__(self, buffers: List[str], encoding: str) -> None: self.counter = itertools.count() self.buffers = buffers self.encoding = encoding @@ -248,8 +230,7 @@ class VariableCommentPicker(ast.NodeVisitor): self.deforders = {} # type: Dict[str, int] super().__init__() - def add_entry(self, name): - # type: (str) -> None + def add_entry(self, name: str) -> None: if self.current_function: if self.current_classes and self.context[-1] == "__init__": # store variable comments inside __init__ method of classes @@ -261,8 +242,7 @@ class VariableCommentPicker(ast.NodeVisitor): self.deforders[".".join(definition)] = next(self.counter) - def add_variable_comment(self, name, comment): - # type: (str, str) -> None + def add_variable_comment(self, name: str, comment: str) -> None: if self.current_function: if self.current_classes and self.context[-1] == "__init__": # store variable comments inside __init__ method of classes @@ -274,27 +254,23 @@ class VariableCommentPicker(ast.NodeVisitor): self.comments[(context, name)] = comment - def get_self(self): - # type: () -> ast.arg + def get_self(self) -> ast.arg: """Returns the name of first argument if in function.""" if self.current_function and self.current_function.args.args: return self.current_function.args.args[0] else: return None - def get_line(self, lineno): - # type: (int) -> str + def get_line(self, lineno: int) -> str: """Returns specified line.""" return self.buffers[lineno - 1] - def visit(self, node): - # type: (ast.AST) -> None + def visit(self, node: ast.AST) -> None: """Updates self.previous to .""" super().visit(node) self.previous = node - def visit_Assign(self, node): - # type: (ast.Assign) -> None + def visit_Assign(self, node: ast.Assign) -> None: """Handles Assign node and pick up a variable comment.""" try: targets = get_assign_targets(node) @@ -334,13 +310,11 @@ class VariableCommentPicker(ast.NodeVisitor): for varname in varnames: self.add_entry(varname) - def visit_AnnAssign(self, node): - # type: (ast.AST) -> None + def visit_AnnAssign(self, node: ast.AST) -> None: # Note: ast.AnnAssign not found in py35 """Handles AnnAssign node and pick up a variable comment.""" self.visit_Assign(node) # type: ignore - def visit_Expr(self, node): - # type: (ast.Expr) -> None + def visit_Expr(self, node: ast.Expr) -> None: """Handles Expr node and pick up a comment if string.""" if (isinstance(self.previous, ASSIGN_NODES) and isinstance(node.value, ast.Str)): try: @@ -357,8 +331,7 @@ class VariableCommentPicker(ast.NodeVisitor): except TypeError: pass # this assignment is not new definition! - def visit_Try(self, node): - # type: (ast.Try) -> None + def visit_Try(self, node: ast.Try) -> None: """Handles Try node and processes body and else-clause. .. note:: pycode parser ignores objects definition in except-clause. @@ -368,8 +341,7 @@ class VariableCommentPicker(ast.NodeVisitor): for subnode in node.orelse: self.visit(subnode) - def visit_ClassDef(self, node): - # type: (ast.ClassDef) -> None + def visit_ClassDef(self, node: ast.ClassDef) -> None: """Handles ClassDef node and set context.""" self.current_classes.append(node.name) self.add_entry(node.name) @@ -380,8 +352,7 @@ class VariableCommentPicker(ast.NodeVisitor): self.context.pop() self.current_classes.pop() - def visit_FunctionDef(self, node): - # type: (ast.FunctionDef) -> None + def visit_FunctionDef(self, node: ast.FunctionDef) -> None: """Handles FunctionDef node and set context.""" if self.current_function is None: self.add_entry(node.name) # should be called before setting self.current_function @@ -392,8 +363,7 @@ class VariableCommentPicker(ast.NodeVisitor): self.context.pop() self.current_function = None - def visit_AsyncFunctionDef(self, node): - # type: (ast.AsyncFunctionDef) -> None + def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None: """Handles AsyncFunctionDef node and set context.""" self.visit_FunctionDef(node) # type: ignore @@ -403,16 +373,14 @@ class DefinitionFinder(TokenProcessor): classes and methods. """ - def __init__(self, lines): - # type: (List[str]) -> None + def __init__(self, lines: List[str]) -> None: super().__init__(lines) self.decorator = None # type: Token self.context = [] # type: List[str] self.indents = [] # type: List self.definitions = {} # type: Dict[str, Tuple[str, int, int]] - def add_definition(self, name, entry): - # type: (str, Tuple[str, int, int]) -> None + def add_definition(self, name: str, entry: Tuple[str, int, int]) -> None: """Add a location of definition.""" if self.indents and self.indents[-1][0] == 'def' and entry[0] == 'def': # ignore definition of inner function @@ -420,8 +388,7 @@ class DefinitionFinder(TokenProcessor): else: self.definitions[name] = entry - def parse(self): - # type: () -> None + def parse(self) -> None: """Parse the code to obtain location of definitions.""" while True: token = self.fetch_token() @@ -442,8 +409,7 @@ class DefinitionFinder(TokenProcessor): elif token == DEDENT: self.finalize_block() - def parse_definition(self, typ): - # type: (str) -> None + def parse_definition(self, typ: str) -> None: """Parse AST of definition.""" name = self.fetch_token() self.context.append(name.value) @@ -464,8 +430,7 @@ class DefinitionFinder(TokenProcessor): self.add_definition(funcname, (typ, start_pos, name.end[0])) self.context.pop() - def finalize_block(self): - # type: () -> None + def finalize_block(self) -> None: """Finalize definition block.""" definition = self.indents.pop() if definition[0] != 'other': @@ -484,22 +449,19 @@ class Parser: This is a better wrapper for ``VariableCommentPicker``. """ - def __init__(self, code, encoding='utf-8'): - # type: (str, str) -> None + def __init__(self, code: str, encoding: str = 'utf-8') -> None: self.code = filter_whitespace(code) self.encoding = encoding self.comments = {} # type: Dict[Tuple[str, str], str] self.deforders = {} # type: Dict[str, int] self.definitions = {} # type: Dict[str, Tuple[str, int, int]] - def parse(self): - # type: () -> None + def parse(self) -> None: """Parse the source code.""" self.parse_comments() self.parse_definition() - def parse_comments(self): - # type: () -> None + def parse_comments(self) -> None: """Parse the code and pick up comments.""" tree = ast.parse(self.code.encode()) picker = VariableCommentPicker(self.code.splitlines(True), self.encoding) @@ -507,8 +469,7 @@ class Parser: self.comments = picker.comments self.deforders = picker.deforders - def parse_definition(self): - # type: () -> None + def parse_definition(self) -> None: """Parse the location of definitions from the code.""" parser = DefinitionFinder(self.code.splitlines(True)) parser.parse() diff --git a/sphinx/templates/apidoc/module.rst b/sphinx/templates/apidoc/module.rst_t similarity index 100% rename from sphinx/templates/apidoc/module.rst rename to sphinx/templates/apidoc/module.rst_t diff --git a/sphinx/templates/apidoc/package.rst b/sphinx/templates/apidoc/package.rst_t similarity index 100% rename from sphinx/templates/apidoc/package.rst rename to sphinx/templates/apidoc/package.rst_t diff --git a/sphinx/templates/apidoc/toc.rst b/sphinx/templates/apidoc/toc.rst_t similarity index 100% rename from sphinx/templates/apidoc/toc.rst rename to sphinx/templates/apidoc/toc.rst_t diff --git a/sphinx/testing/fixtures.py b/sphinx/testing/fixtures.py index 93195fc40..443a7dd5c 100644 --- a/sphinx/testing/fixtures.py +++ b/sphinx/testing/fixtures.py @@ -14,25 +14,20 @@ import sys from collections import namedtuple from io import StringIO from subprocess import PIPE +from typing import Any, Dict import pytest from . import util -if False: - # For type annotation - from typing import Any, Dict, Union # NOQA - @pytest.fixture(scope='session') -def rootdir(): - # type: () -> None +def rootdir() -> None: return None @pytest.fixture def app_params(request, test_params, shared_result, sphinx_test_tempdir, rootdir): - # type: (Any, Any, Any, Any, Any) -> None """ parameters that is specified by 'pytest.mark.sphinx' for sphinx.application.Sphinx initialization @@ -158,10 +153,10 @@ def make_app(test_params, monkeypatch): status, warning = StringIO(), StringIO() kwargs.setdefault('status', status) kwargs.setdefault('warning', warning) - app_ = util.SphinxTestApp(*args, **kwargs) # type: Union[util.SphinxTestApp, util.SphinxTestAppWrapperForSkipBuilding] # NOQA + app_ = util.SphinxTestApp(*args, **kwargs) # type: Any apps.append(app_) if test_params['shared_result']: - app_ = util.SphinxTestAppWrapperForSkipBuilding(app_) # type: ignore + app_ = util.SphinxTestAppWrapperForSkipBuilding(app_) return app_ yield make diff --git a/sphinx/testing/path.py b/sphinx/testing/path.py index f71bf509b..d2fc4f31e 100644 --- a/sphinx/testing/path.py +++ b/sphinx/testing/path.py @@ -5,14 +5,12 @@ :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ + +import builtins import os import shutil import sys - -if False: - # For type annotation - import builtins # NOQA - from typing import Any, Callable, IO, List # NOQA +from typing import Any, Callable, IO, List FILESYSTEMENCODING = sys.getfilesystemencoding() or sys.getdefaultencoding() @@ -24,61 +22,52 @@ class path(str): """ @property - def parent(self): - # type: () -> path + def parent(self) -> "path": """ The name of the directory the file or directory is in. """ return self.__class__(os.path.dirname(self)) - def basename(self): - # type: () -> str + def basename(self) -> str: return os.path.basename(self) - def abspath(self): - # type: () -> path + def abspath(self) -> "path": """ Returns the absolute path. """ return self.__class__(os.path.abspath(self)) - def isabs(self): - # type: () -> bool + def isabs(self) -> bool: """ Returns ``True`` if the path is absolute. """ return os.path.isabs(self) - def isdir(self): - # type: () -> bool + def isdir(self) -> bool: """ Returns ``True`` if the path is a directory. """ return os.path.isdir(self) - def isfile(self): - # type: () -> bool + def isfile(self) -> bool: """ Returns ``True`` if the path is a file. """ return os.path.isfile(self) - def islink(self): - # type: () -> bool + def islink(self) -> bool: """ Returns ``True`` if the path is a symbolic link. """ return os.path.islink(self) - def ismount(self): - # type: () -> bool + def ismount(self) -> bool: """ Returns ``True`` if the path is a mount point. """ return os.path.ismount(self) - def rmtree(self, ignore_errors=False, onerror=None): - # type: (bool, Callable) -> None + def rmtree(self, ignore_errors: bool = False, onerror: Callable = None) -> None: """ Removes the file or directory and any files or directories it may contain. @@ -96,8 +85,7 @@ class path(str): """ shutil.rmtree(self, ignore_errors=ignore_errors, onerror=onerror) - def copytree(self, destination, symlinks=False): - # type: (str, bool) -> None + def copytree(self, destination: str, symlinks: bool = False) -> None: """ Recursively copy a directory to the given `destination`. If the given `destination` does not exist it will be created. @@ -109,8 +97,7 @@ class path(str): """ shutil.copytree(self, destination, symlinks=symlinks) - def movetree(self, destination): - # type: (str) -> None + def movetree(self, destination: str) -> None: """ Recursively move the file or directory to the given `destination` similar to the Unix "mv" command. @@ -122,54 +109,46 @@ class path(str): move = movetree - def unlink(self): - # type: () -> None + def unlink(self) -> None: """ Removes a file. """ os.unlink(self) - def stat(self): - # type: () -> Any + def stat(self) -> Any: """ Returns a stat of the file. """ return os.stat(self) - def utime(self, arg): - # type: (Any) -> None + def utime(self, arg: Any) -> None: os.utime(self, arg) - def open(self, mode='r', **kwargs): - # type: (str, Any) -> IO + def open(self, mode: str = 'r', **kwargs) -> IO: return open(self, mode, **kwargs) - def write_text(self, text, encoding='utf-8', **kwargs): - # type: (str, str, Any) -> None + def write_text(self, text: str, encoding: str = 'utf-8', **kwargs) -> None: """ Writes the given `text` to the file. """ with open(self, 'w', encoding=encoding, **kwargs) as f: f.write(text) - def text(self, encoding='utf-8', **kwargs): - # type: (str, Any) -> str + def text(self, encoding: str = 'utf-8', **kwargs) -> str: """ Returns the text in the file. """ with open(self, encoding=encoding, **kwargs) as f: return f.read() - def bytes(self): - # type: () -> builtins.bytes + def bytes(self) -> builtins.bytes: """ Returns the bytes in the file. """ with open(self, mode='rb') as f: return f.read() - def write_bytes(self, bytes, append=False): - # type: (str, bool) -> None + def write_bytes(self, bytes: str, append: bool = False) -> None: """ Writes the given `bytes` to the file. @@ -183,41 +162,35 @@ class path(str): with open(self, mode=mode) as f: f.write(bytes) - def exists(self): - # type: () -> bool + def exists(self) -> bool: """ Returns ``True`` if the path exist. """ return os.path.exists(self) - def lexists(self): - # type: () -> bool + def lexists(self) -> bool: """ Returns ``True`` if the path exists unless it is a broken symbolic link. """ return os.path.lexists(self) - def makedirs(self, mode=0o777, exist_ok=False): - # type: (int, bool) -> None + def makedirs(self, mode: int = 0o777, exist_ok: bool = False) -> None: """ Recursively create directories. """ os.makedirs(self, mode, exist_ok=exist_ok) - def joinpath(self, *args): - # type: (Any) -> path + def joinpath(self, *args) -> "path": """ Joins the path with the argument given and returns the result. """ return self.__class__(os.path.join(self, *map(self.__class__, args))) - def listdir(self): - # type: () -> List[str] + def listdir(self) -> List[str]: return os.listdir(self) __div__ = __truediv__ = joinpath - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: return '%s(%s)' % (self.__class__.__name__, super().__repr__()) diff --git a/sphinx/testing/restructuredtext.py b/sphinx/testing/restructuredtext.py index 8bf1c041e..1da8d9a60 100644 --- a/sphinx/testing/restructuredtext.py +++ b/sphinx/testing/restructuredtext.py @@ -8,21 +8,16 @@ from os import path +from docutils import nodes from docutils.core import publish_doctree +from sphinx.application import Sphinx from sphinx.io import SphinxStandaloneReader from sphinx.parsers import RSTParser from sphinx.util.docutils import sphinx_domains -if False: - # For type annotation - from docutils import nodes # NOQA - from sphinx.application import Sphinx # NOQA - - -def parse(app, text, docname='index'): - # type: (Sphinx, str, str) -> nodes.document +def parse(app: Sphinx, text: str, docname: str = 'index') -> nodes.document: """Parse a string as reStructuredText with Sphinx application.""" try: app.env.temp_data['docname'] = docname diff --git a/sphinx/testing/util.py b/sphinx/testing/util.py index 7b73059e9..1ba3237c4 100644 --- a/sphinx/testing/util.py +++ b/sphinx/testing/util.py @@ -11,6 +11,7 @@ import os import re import sys import warnings +from typing import Any, Dict, Generator, IO, List, Pattern from xml.etree import ElementTree from docutils import nodes @@ -23,10 +24,6 @@ from sphinx.pycode import ModuleAnalyzer from sphinx.testing.path import path from sphinx.util.osutil import relpath -if False: - # For type annotation - from typing import Any, Dict, Generator, IO, List, Pattern # NOQA - __all__ = [ 'Struct', @@ -35,26 +32,22 @@ __all__ = [ ] -def assert_re_search(regex, text, flags=0): - # type: (Pattern, str, int) -> None +def assert_re_search(regex: Pattern, text: str, flags: int = 0) -> None: if not re.search(regex, text, flags): assert False, '%r did not match %r' % (regex, text) -def assert_not_re_search(regex, text, flags=0): - # type: (Pattern, str, int) -> None +def assert_not_re_search(regex: Pattern, text: str, flags: int = 0) -> None: if re.search(regex, text, flags): assert False, '%r did match %r' % (regex, text) -def assert_startswith(thing, prefix): - # type: (str, str) -> None +def assert_startswith(thing: str, prefix: str) -> None: if not thing.startswith(prefix): assert False, '%r does not start with %r' % (thing, prefix) -def assert_node(node, cls=None, xpath="", **kwargs): - # type: (nodes.Node, Any, str, Any) -> None +def assert_node(node: nodes.Node, cls: Any = None, xpath: str = "", **kwargs) -> None: if cls: if isinstance(cls, list): assert_node(node, cls[0], xpath=xpath, **kwargs) @@ -92,16 +85,14 @@ def assert_node(node, cls=None, xpath="", **kwargs): 'The node%s[%s] is not %r: %r' % (xpath, key, value, node[key]) -def etree_parse(path): - # type: (str) -> Any +def etree_parse(path: str) -> Any: with warnings.catch_warnings(record=False): warnings.filterwarnings("ignore", category=DeprecationWarning) return ElementTree.parse(path) class Struct: - def __init__(self, **kwds): - # type: (Any) -> None + def __init__(self, **kwds) -> None: self.__dict__.update(kwds) @@ -111,10 +102,9 @@ class SphinxTestApp(application.Sphinx): better default values for the initialization parameters. """ - def __init__(self, buildername='html', srcdir=None, - freshenv=False, confoverrides=None, status=None, warning=None, - tags=None, docutilsconf=None): - # type: (str, path, bool, Dict, IO, IO, List[str], str) -> None + def __init__(self, buildername: str = 'html', srcdir: path = None, freshenv: bool = False, + confoverrides: Dict = None, status: IO = None, warning: IO = None, + tags: List[str] = None, docutilsconf: str = None) -> None: if docutilsconf is not None: (srcdir / 'docutils.conf').write_text(docutilsconf) @@ -144,8 +134,7 @@ class SphinxTestApp(application.Sphinx): self.cleanup() raise - def cleanup(self, doctrees=False): - # type: (bool) -> None + def cleanup(self, doctrees: bool = False) -> None: ModuleAnalyzer.cache.clear() LaTeXBuilder.usepackages = [] locale.translators.clear() @@ -159,8 +148,7 @@ class SphinxTestApp(application.Sphinx): delattr(nodes.GenericNodeVisitor, 'visit_' + method[6:]) delattr(nodes.GenericNodeVisitor, 'depart_' + method[6:]) - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: return '<%s buildername=%r>' % (self.__class__.__name__, self.builder.name) @@ -171,16 +159,13 @@ class SphinxTestAppWrapperForSkipBuilding: file. """ - def __init__(self, app_): - # type: (SphinxTestApp) -> None + def __init__(self, app_: SphinxTestApp) -> None: self.app = app_ - def __getattr__(self, name): - # type: (str) -> Any + def __getattr__(self, name: str) -> Any: return getattr(self.app, name) - def build(self, *args, **kw): - # type: (Any, Any) -> None + def build(self, *args, **kw) -> None: if not self.app.outdir.listdir(): # type: ignore # if listdir is empty, do build. self.app.build(*args, **kw) @@ -190,15 +175,13 @@ class SphinxTestAppWrapperForSkipBuilding: _unicode_literals_re = re.compile(r'u(".*?")|u(\'.*?\')') -def remove_unicode_literals(s): - # type: (str) -> str +def remove_unicode_literals(s: str) -> str: warnings.warn('remove_unicode_literals() is deprecated.', RemovedInSphinx40Warning, stacklevel=2) return _unicode_literals_re.sub(lambda x: x.group(1) or x.group(2), s) -def find_files(root, suffix=None): - # type: (str, bool) -> Generator +def find_files(root: str, suffix: bool = None) -> Generator[str, None, None]: for dirpath, dirs, files in os.walk(root, followlinks=True): dirpath = path(dirpath) for f in [f for f in files if not suffix or f.endswith(suffix)]: # type: ignore @@ -206,6 +189,5 @@ def find_files(root, suffix=None): yield relpath(fpath, root) -def strip_escseq(text): - # type: (str) -> str +def strip_escseq(text: str) -> str: return re.sub('\x1b.*?m', '', text) diff --git a/sphinx/texinputs/sphinx.sty b/sphinx/texinputs/sphinx.sty index 184b0d820..2eebbb914 100644 --- a/sphinx/texinputs/sphinx.sty +++ b/sphinx/texinputs/sphinx.sty @@ -413,6 +413,18 @@ \newcommand\sphinxsetup[1]{\setkeys{sphinx}{#1}} +%% ALPHANUMERIC LIST ITEMS +\newcommand\sphinxsetlistlabels[5] +{% #1 = style, #2 = enum, #3 = enumnext, #4 = prefix, #5 = suffix + % #2 and #3 are counters used by enumerate environement e.g. enumi, enumii. + % #1 is a macro such as \arabic or \alph + % prefix and suffix are strings (by default empty and a dot). + \@namedef{the#2}{#1{#2}}% + \@namedef{label#2}{#4\@nameuse{the#2}#5}% + \@namedef{p@#3}{\@nameuse{p@#2}#4\@nameuse{the#2}#5}% +}% + + %% MAXLISTDEPTH % % remove LaTeX's cap on nesting depth if 'maxlistdepth' key used. diff --git a/sphinx/themes/basic/search.html b/sphinx/themes/basic/search.html index 7446a8f25..6ac6b3899 100644 --- a/sphinx/themes/basic/search.html +++ b/sphinx/themes/basic/search.html @@ -33,7 +33,7 @@ containing fewer words won't appear in the result list.{% endtrans %}

- +
diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index 1fb7bcde6..a36c256d3 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -169,7 +169,7 @@ class DownloadFiles(dict): Hence don't hack this directly. """ - def add_file(self, docname: str, filename: str) -> None: + def add_file(self, docname: str, filename: str) -> str: if filename not in self: digest = md5(filename.encode()).hexdigest() dest = '%s/%s' % (digest, os.path.basename(filename)) diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py index 14b190406..bfd43e19e 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -12,7 +12,7 @@ import os import re import warnings from collections import namedtuple -from datetime import datetime +from datetime import datetime, timezone from os import path from typing import Callable, Generator, List, Set, Tuple @@ -274,7 +274,7 @@ def format_date(format: str, date: datetime = None, language: str = None) -> str if source_date_epoch is not None: date = datetime.utcfromtimestamp(float(source_date_epoch)) else: - date = datetime.now() + date = datetime.now(timezone.utc).astimezone() result = [] tokens = date_format_re.split(format) diff --git a/sphinx/util/stemmer/__init__.py b/sphinx/util/stemmer/__init__.py index bda5d2bc2..8d8d36c68 100644 --- a/sphinx/util/stemmer/__init__.py +++ b/sphinx/util/stemmer/__init__.py @@ -30,7 +30,7 @@ class PyStemmer(BaseStemmer): return self.stemmer.stemWord(word) -class StandardStemmer(PorterStemmer, BaseStemmer): # type: ignore +class StandardStemmer(PorterStemmer, BaseStemmer): """All those porter stemmer implementations look hideous; make at least the stem method nicer. """ diff --git a/sphinx/util/template.py b/sphinx/util/template.py index 636767d41..fd8886944 100644 --- a/sphinx/util/template.py +++ b/sphinx/util/template.py @@ -9,7 +9,7 @@ """ import os -from typing import Dict +from typing import Dict, List, Union from jinja2.loaders import BaseLoader from jinja2.sandbox import SandboxedEnvironment @@ -34,7 +34,13 @@ class BaseRenderer: class FileRenderer(BaseRenderer): - def __init__(self, search_path: str) -> None: + def __init__(self, search_path: Union[str, List[str]]) -> None: + if isinstance(search_path, str): + search_path = [search_path] + else: + # filter "None" paths + search_path = list(filter(None, search_path)) + loader = SphinxFileSystemLoader(search_path) super().__init__(loader) @@ -46,7 +52,7 @@ class FileRenderer(BaseRenderer): class SphinxRenderer(FileRenderer): - def __init__(self, template_path: str = None) -> None: + def __init__(self, template_path: Union[str, List[str]] = None) -> None: if template_path is None: template_path = os.path.join(package_dir, 'templates') super().__init__(template_path) @@ -76,7 +82,7 @@ class LaTeXRenderer(SphinxRenderer): class ReSTRenderer(SphinxRenderer): - def __init__(self, template_path: str = None, language: str = None) -> None: + def __init__(self, template_path: Union[str, List[str]] = None, language: str = None) -> None: # NOQA super().__init__(template_path) # add language to environment diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 5c7f52507..5fdcb5b93 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -1373,11 +1373,8 @@ class LaTeXTranslator(SphinxTranslator): suffix = node.get('suffix', '.') self.body.append('\\begin{enumerate}\n') - self.body.append('\\def\\the%s{%s{%s}}\n' % (enum, style, enum)) - self.body.append('\\def\\label%s{%s\\the%s %s}\n' % - (enum, prefix, enum, suffix)) - self.body.append('\\makeatletter\\def\\p@%s{\\p@%s %s\\the%s %s}\\makeatother\n' % - (enumnext, enum, prefix, enum, suffix)) + self.body.append('\\sphinxsetlistlabels{%s}{%s}{%s}{%s}{%s}%%\n' % + (style, enum, enumnext, prefix, suffix)) if 'start' in node: self.body.append('\\setcounter{%s}{%d}\n' % (enum, node['start'] - 1)) if self.table: @@ -2598,7 +2595,7 @@ class LaTeXTranslator(SphinxTranslator): RemovedInSphinx30Warning) def visit_admonition(self, node): - # type: (nodes.Element) -> None + # type: (LaTeXTranslator, nodes.Element) -> None self.body.append('\n\\begin{sphinxadmonition}{%s}{%s:}' % (name, admonitionlabels[name])) return visit_admonition diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py index 4262ccd66..b952812f0 100644 --- a/sphinx/writers/texinfo.py +++ b/sphinx/writers/texinfo.py @@ -1752,6 +1752,6 @@ class TexinfoTranslator(SphinxTranslator): RemovedInSphinx30Warning) def visit(self, node): - # type: (nodes.Element) -> None + # type: (TexinfoTranslator, nodes.Element) -> None self.visit_admonition(node, admonitionlabels[name]) return visit diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py index 1447510c3..dc8a7963a 100644 --- a/sphinx/writers/text.py +++ b/sphinx/writers/text.py @@ -1375,6 +1375,6 @@ class TextTranslator(SphinxTranslator): RemovedInSphinx30Warning) def depart_admonition(self, node): - # type: (nodes.Element) -> None + # type: (TextTranslator, nodes.Element) -> None self.end_state(first=admonitionlabels[name] + ': ') return depart_admonition diff --git a/tests/roots/test-ext-autodoc/target/inheritance.py b/tests/roots/test-ext-autodoc/target/inheritance.py index 3a5fc0711..ffac84bb6 100644 --- a/tests/roots/test-ext-autodoc/target/inheritance.py +++ b/tests/roots/test-ext-autodoc/target/inheritance.py @@ -15,5 +15,3 @@ class Derived(Base): def inheritedmeth(self): # no docstring here pass - - diff --git a/tests/roots/test-images/index.rst b/tests/roots/test-images/index.rst index 67b742b27..14a2987a0 100644 --- a/tests/roots/test-images/index.rst +++ b/tests/roots/test-images/index.rst @@ -26,4 +26,4 @@ test-image .. image:: https://www.python.org/static/img/python-logo.png .. non-exist remote image -.. image:: http://example.com/NOT_EXIST.PNG +.. image:: https://www.google.com/NOT_EXIST.PNG diff --git a/tests/roots/test-intl/external_links.txt b/tests/roots/test-intl/external_links.txt index 96e3973de..1cecbeeb8 100644 --- a/tests/roots/test-intl/external_links.txt +++ b/tests/roots/test-intl/external_links.txt @@ -23,8 +23,8 @@ link to external1_ and external2_. link to `Sphinx Site `_ and `Python Site `_. -.. _external1: http://example.com/external1 -.. _external2: http://example.com/external2 +.. _external1: https://www.google.com/external1 +.. _external2: https://www.google.com/external2 Multiple references in the same line diff --git a/tests/roots/test-linkcheck/links.txt b/tests/roots/test-linkcheck/links.txt index ac5ed3246..fa8f11e4c 100644 --- a/tests/roots/test-linkcheck/links.txt +++ b/tests/roots/test-linkcheck/links.txt @@ -6,11 +6,11 @@ This is from CPython documentation. Some additional anchors to exercise ignore code -* `Example Bar invalid `_ -* `Example Bar invalid `_ tests that default ignore anchor of #! does not need to be prefixed with / -* `Example Bar invalid `_ +* `Example Bar invalid `_ +* `Example Bar invalid `_ tests that default ignore anchor of #! does not need to be prefixed with / +* `Example Bar invalid `_ * `Example anchor invalid `_ * `Complete nonsense `_ -.. image:: http://example.com/image.png -.. figure:: http://example.com/image2.png +.. image:: https://www.google.com/image.png +.. figure:: https://www.google.com/image2.png diff --git a/tests/test_build_gettext.py b/tests/test_build_gettext.py index 26c63771a..b41ceb2d3 100644 --- a/tests/test_build_gettext.py +++ b/tests/test_build_gettext.py @@ -52,7 +52,8 @@ def test_msgfmt(app): assert (app.outdir / 'en_US.po').isfile(), 'msginit failed' try: - args = ['msgfmt', 'en_US.po', '-o', os.path.join('en', 'LC_MESSAGES', 'test_root.mo')] + args = ['msgfmt', 'en_US.po', + '-o', os.path.join('en', 'LC_MESSAGES', 'test_root.mo')] subprocess.run(args, stdout=PIPE, stderr=PIPE, check=True) except OSError: pytest.skip() # most likely msgfmt was not found diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 677ca9de0..3255bb71e 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -16,6 +16,7 @@ from itertools import cycle, chain import pytest from html5lib import HTMLParser +from sphinx.builders.html import validate_html_extra_path, validate_html_static_path from sphinx.errors import ConfigError from sphinx.testing.util import strip_escseq from sphinx.util import docutils @@ -1239,25 +1240,25 @@ def test_html_entity(app): def test_html_inventory(app): app.builder.build_all() with open(app.outdir / 'objects.inv', 'rb') as f: - invdata = InventoryFile.load(f, 'http://example.com', os.path.join) + invdata = InventoryFile.load(f, 'https://www.google.com', os.path.join) assert set(invdata.keys()) == {'std:label', 'std:doc'} assert set(invdata['std:label'].keys()) == {'modindex', 'genindex', 'search'} assert invdata['std:label']['modindex'] == ('Python', '', - 'http://example.com/py-modindex.html', + 'https://www.google.com/py-modindex.html', 'Module Index') assert invdata['std:label']['genindex'] == ('Python', '', - 'http://example.com/genindex.html', + 'https://www.google.com/genindex.html', 'Index') assert invdata['std:label']['search'] == ('Python', '', - 'http://example.com/search.html', + 'https://www.google.com/search.html', 'Search Page') assert set(invdata['std:doc'].keys()) == {'index'} assert invdata['std:doc']['index'] == ('Python', '', - 'http://example.com/index.html', + 'https://www.google.com/index.html', 'The basic Sphinx documentation for testing') @@ -1496,3 +1497,29 @@ def test_html_pygments_style_manually(app): def test_html_pygments_for_classic_theme(app): style = app.builder.highlighter.formatter_args.get('style') assert style.__name__ == 'SphinxStyle' + + +@pytest.mark.sphinx(testroot='basic', srcdir='validate_html_extra_path') +def test_validate_html_extra_path(app): + (app.confdir / '_static').makedirs() + app.config.html_extra_path = [ + '/path/to/not_found', # not found + '_static', + app.outdir, # outdir + app.outdir / '_static', # inside outdir + ] + validate_html_extra_path(app, app.config) + assert app.config.html_extra_path == ['_static'] + + +@pytest.mark.sphinx(testroot='basic', srcdir='validate_html_static_path') +def test_validate_html_static_path(app): + (app.confdir / '_static').makedirs() + app.config.html_static_path = [ + '/path/to/not_found', # not found + '_static', + app.outdir, # outdir + app.outdir / '_static', # inside outdir + ] + validate_html_static_path(app, app.config) + assert app.config.html_static_path == ['_static'] diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index 13bb22e96..67491ddc9 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -1242,7 +1242,7 @@ def test_latex_images(app, status, warning): # not found images assert '\\sphinxincludegraphics{{NOT_EXIST}.PNG}' not in result assert ('WARNING: Could not fetch remote image: ' - 'http://example.com/NOT_EXIST.PNG [404]' in warning.getvalue()) + 'https://www.google.com/NOT_EXIST.PNG [404]' in warning.getvalue()) # an image having target assert ('\\sphinxhref{https://www.sphinx-doc.org/}' @@ -1292,25 +1292,15 @@ def test_latex_nested_enumerated_list(app, status, warning): app.builder.build_all() result = (app.outdir / 'python.tex').text(encoding='utf8') - assert ('\\def\\theenumi{\\arabic{enumi}}\n' - '\\def\\labelenumi{\\theenumi .}\n' - '\\makeatletter\\def\\p@enumii{\\p@enumi \\theenumi .}\\makeatother\n' + assert ('\\sphinxsetlistlabels{\\arabic}{enumi}{enumii}{}{.}%\n' '\\setcounter{enumi}{4}\n' in result) - assert ('\\def\\theenumii{\\alph{enumii}}\n' - '\\def\\labelenumii{\\theenumii .}\n' - '\\makeatletter\\def\\p@enumiii{\\p@enumii \\theenumii .}\\makeatother\n' + assert ('\\sphinxsetlistlabels{\\alph}{enumii}{enumiii}{}{.}%\n' '\\setcounter{enumii}{3}\n' in result) - assert ('\\def\\theenumiii{\\arabic{enumiii}}\n' - '\\def\\labelenumiii{\\theenumiii )}\n' - '\\makeatletter\\def\\p@enumiv{\\p@enumiii \\theenumiii )}\\makeatother\n' + assert ('\\sphinxsetlistlabels{\\arabic}{enumiii}{enumiv}{}{)}%\n' '\\setcounter{enumiii}{9}\n' in result) - assert ('\\def\\theenumiv{\\arabic{enumiv}}\n' - '\\def\\labelenumiv{(\\theenumiv )}\n' - '\\makeatletter\\def\\p@enumv{\\p@enumiv (\\theenumiv )}\\makeatother\n' + assert ('\\sphinxsetlistlabels{\\arabic}{enumiv}{enumv}{(}{)}%\n' '\\setcounter{enumiv}{23}\n' in result) - assert ('\\def\\theenumii{\\roman{enumii}}\n' - '\\def\\labelenumii{\\theenumii .}\n' - '\\makeatletter\\def\\p@enumiii{\\p@enumii \\theenumii .}\\makeatother\n' + assert ('\\sphinxsetlistlabels{\\roman}{enumii}{enumiii}{}{.}%\n' '\\setcounter{enumii}{2}\n' in result) @@ -1405,6 +1395,7 @@ def test_latex_figure_in_admonition(app, status, warning): result = (app.outdir / 'python.tex').text(encoding='utf8') assert(r'\begin{figure}[H]' in result) + def test_default_latex_documents(): from sphinx.util import texescape texescape.init() diff --git a/tests/test_build_linkcheck.py b/tests/test_build_linkcheck.py index 6d25058eb..4bf47a962 100644 --- a/tests/test_build_linkcheck.py +++ b/tests/test_build_linkcheck.py @@ -25,8 +25,8 @@ def test_defaults(app, status, warning): # looking for non-existent URL should fail assert " Max retries exceeded with url: /doesnotexist" in content # images should fail - assert "Not Found for url: http://example.com/image.png" in content - assert "Not Found for url: http://example.com/image2.png" in content + assert "Not Found for url: https://www.google.com/image.png" in content + assert "Not Found for url: https://www.google.com/image2.png" in content assert len(content.splitlines()) == 5 @@ -36,8 +36,8 @@ def test_defaults(app, status, warning): 'linkcheck_ignore': [ 'https://localhost:7777/doesnotexist', 'http://www.sphinx-doc.org/en/1.7/intro.html#', - 'http://example.com/image.png', - 'http://example.com/image2.png'] + 'https://www.google.com/image.png', + 'https://www.google.com/image2.png'] }) def test_anchors_ignored(app, status, warning): app.builder.build_all() diff --git a/tests/test_directive_code.py b/tests/test_directive_code.py index d1f7c03dc..13b451078 100644 --- a/tests/test_directive_code.py +++ b/tests/test_directive_code.py @@ -617,6 +617,6 @@ def test_linenothreshold(app, status, warning): # literal include not using linenothreshold html, matched, _ = html.partition(lineos_head + '1\n' - '2\n' + '2\n' '3' + lineos_tail) assert not matched diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index c81d66b17..bbb4cc55e 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -129,14 +129,14 @@ def test_expressions(): '5e42', '5e+42', '5e-42', '5.', '5.e42', '5.e+42', '5.e-42', '.5', '.5e42', '.5e+42', '.5e-42', - '5.0', '5.0e42','5.0e+42', '5.0e-42']: + '5.0', '5.0e42', '5.0e+42', '5.0e-42']: expr = e + suffix exprCheck(expr, 'L' + expr + 'E') for e in [ 'ApF', 'Ap+F', 'Ap-F', 'A.', 'A.pF', 'A.p+F', 'A.p-F', '.A', '.ApF', '.Ap+F', '.Ap-F', - 'A.B', 'A.BpF','A.Bp+F', 'A.Bp-F']: + 'A.B', 'A.BpF', 'A.Bp+F', 'A.Bp-F']: expr = "0x" + e + suffix exprCheck(expr, 'L' + expr + 'E') exprCheck('"abc\\"cba"', 'LA8_KcE') # string @@ -676,7 +676,7 @@ def test_template_args(): def test_initializers(): - idsMember = {1: 'v__T', 2:'1v'} + idsMember = {1: 'v__T', 2: '1v'} idsFunction = {1: 'f__T', 2: '1f1T'} idsTemplate = {2: 'I_1TE1fv', 4: 'I_1TE1fvv'} # no init @@ -748,7 +748,7 @@ def test_attributes(): {1: 'f', 2: '1fv'}, output='[[attr1]] [[attr2]] void f()') # position: declarator - check('member', 'int *[[attr]] i', {1: 'i__iP', 2:'1i'}) + check('member', 'int *[[attr]] i', {1: 'i__iP', 2: '1i'}) check('member', 'int *const [[attr]] volatile i', {1: 'i__iPVC', 2: '1i'}, output='int *[[attr]] volatile const i') check('member', 'int &[[attr]] i', {1: 'i__iR', 2: '1i'}) diff --git a/tests/test_environment.py b/tests/test_environment.py index 15562536f..1c6b49e64 100644 --- a/tests/test_environment.py +++ b/tests/test_environment.py @@ -11,9 +11,32 @@ import pytest from sphinx.builders.html import StandaloneHTMLBuilder from sphinx.builders.latex import LaTeXBuilder +from sphinx.environment import CONFIG_OK, CONFIG_CHANGED, CONFIG_EXTENSIONS_CHANGED, CONFIG_NEW from sphinx.testing.comparer import PathComparer +@pytest.mark.sphinx('dummy', testroot='basic') +def test_config_status(make_app, app_params): + args, kwargs = app_params + + # clean build + app1 = make_app(*args, freshenv=True, **kwargs) + assert app1.env.config_status == CONFIG_NEW + app1.build() + + # incremental build (no config changed) + app2 = make_app(*args, **kwargs) + assert app2.env.config_status == CONFIG_OK + + # incremental build (config entry changed) + app3 = make_app(*args, confoverrides={'master_doc': 'content'}, **kwargs) + assert app3.env.config_status == CONFIG_CHANGED + + # incremental build (extension changed) + app4 = make_app(*args, confoverrides={'extensions': ['sphinx.ext.autodoc']}, **kwargs) + assert app4.env.config_status == CONFIG_EXTENSIONS_CHANGED + + @pytest.mark.sphinx('dummy') def test_images(app): app.build() diff --git a/tests/test_environment_indexentries.py b/tests/test_environment_indexentries.py index ec76acdc0..4083eae04 100644 --- a/tests/test_environment_indexentries.py +++ b/tests/test_environment_indexentries.py @@ -47,19 +47,30 @@ def test_create_pair_index(app): app.env.indexentries.clear() text = (".. index:: pair: docutils; reStructuredText\n" ".. index:: pair: Python; interpreter\n" - ".. index:: pair: Sphinx; documentation tool\n") + ".. index:: pair: Sphinx; documentation tool\n" + ".. index:: pair: Sphinx; :+1:\n" + ".. index:: pair: Sphinx; Ель\n" + ".. index:: pair: Sphinx; ёлка\n") restructuredtext.parse(app, text) index = IndexEntries(app.env).create_index(app.builder) - assert len(index) == 5 - assert index[0] == ('D', + assert len(index) == 7 + assert index[0] == ('Symbols', [(':+1:', [[], [('Sphinx', [('', '#index-3')])], None])]) + assert index[1] == ('D', [('documentation tool', [[], [('Sphinx', [('', '#index-2')])], None]), ('docutils', [[], [('reStructuredText', [('', '#index-0')])], None])]) - assert index[1] == ('I', [('interpreter', [[], [('Python', [('', '#index-1')])], None])]) - assert index[2] == ('P', [('Python', [[], [('interpreter', [('', '#index-1')])], None])]) - assert index[3] == ('R', + assert index[2] == ('I', [('interpreter', [[], [('Python', [('', '#index-1')])], None])]) + assert index[3] == ('P', [('Python', [[], [('interpreter', [('', '#index-1')])], None])]) + assert index[4] == ('R', [('reStructuredText', [[], [('docutils', [('', '#index-0')])], None])]) - assert index[4] == ('S', - [('Sphinx', [[], [('documentation tool', [('', '#index-2')])], None])]) + assert index[5] == ('S', + [('Sphinx', [[], + [(':+1:', [('', '#index-3')]), + ('documentation tool', [('', '#index-2')]), + ('ёлка', [('', '#index-5')]), + ('Ель', [('', '#index-4')])], + None])]) + assert index[6] == ('Е', [('ёлка', [[], [('Sphinx', [('', '#index-5')])], None]), + ('Ель', [[], [('Sphinx', [('', '#index-4')])], None])]) @pytest.mark.sphinx('dummy') diff --git a/tests/test_ext_autosummary.py b/tests/test_ext_autosummary.py index ae97d3b57..21841cc74 100644 --- a/tests/test_ext_autosummary.py +++ b/tests/test_ext_autosummary.py @@ -302,3 +302,17 @@ def test_generate_autosummary_docs_property(app): ".. currentmodule:: target.methods\n" "\n" ".. autoproperty:: Base.prop") + + +@pytest.mark.sphinx('dummy', testroot='ext-autosummary', + confoverrides={'autosummary_generate': []}) +def test_empty_autosummary_generate(app, status, warning): + app.build() + assert ("WARNING: autosummary: stub file not found 'autosummary_importfail'" + in warning.getvalue()) + + +@pytest.mark.sphinx('dummy', testroot='ext-autosummary', + confoverrides={'autosummary_generate': ['unknown']}) +def test_invalid_autosummary_generate(app, status, warning): + assert 'WARNING: autosummary_generate: file not found: unknown.rst' in warning.getvalue() diff --git a/tests/test_ext_githubpages.py b/tests/test_ext_githubpages.py index 450a25498..132d95e03 100644 --- a/tests/test_ext_githubpages.py +++ b/tests/test_ext_githubpages.py @@ -18,14 +18,16 @@ def test_githubpages(app, status, warning): assert not (app.outdir / 'CNAME').exists() -@pytest.mark.sphinx('html', testroot='ext-githubpages', confoverrides={'html_baseurl': 'https://sphinx-doc.github.io'}) +@pytest.mark.sphinx('html', testroot='ext-githubpages', + confoverrides={'html_baseurl': 'https://sphinx-doc.github.io'}) def test_no_cname_for_github_io_domain(app, status, warning): app.builder.build_all() assert (app.outdir / '.nojekyll').exists() assert not (app.outdir / 'CNAME').exists() -@pytest.mark.sphinx('html', testroot='ext-githubpages', confoverrides={'html_baseurl': 'https://sphinx-doc.org'}) +@pytest.mark.sphinx('html', testroot='ext-githubpages', + confoverrides={'html_baseurl': 'https://sphinx-doc.org'}) def test_cname_for_custom_domain(app, status, warning): app.builder.build_all() assert (app.outdir / '.nojekyll').exists() diff --git a/tests/test_intl.py b/tests/test_intl.py index a052266b8..3815f8357 100644 --- a/tests/test_intl.py +++ b/tests/test_intl.py @@ -107,7 +107,7 @@ def test_text_emit_warnings(app, warning): # test warnings in translation warnings = getwarning(warning) warning_expr = ('.*/warnings.txt:4::1: ' - 'WARNING: Inline literal start-string without end-string.\n') + 'WARNING: Inline literal start-string without end-string.\n') assert_re_search(warning_expr, warnings) @@ -885,8 +885,8 @@ def test_xml_keep_external_links(app): assert_elem( para1[0], ['LINK TO', 'external2', 'AND', 'external1', '.'], - ['http://example.com/external2', - 'http://example.com/external1']) + ['https://www.google.com/external2', + 'https://www.google.com/external1']) assert_elem( para1[1], ['LINK TO', 'THE PYTHON SITE', 'AND', 'THE SPHINX SITE', '.'], diff --git a/tests/test_markup.py b/tests/test_markup.py index 006d19caa..b8c9b66d9 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -279,9 +279,9 @@ def get_verifier(verify, verify_re): ( # in URIs 'verify_re', - '`test `_', + '`test `_', None, - r'\\sphinxhref{http://example.com/~me/}{test}.*', + r'\\sphinxhref{https://www.google.com/~me/}{test}.*', ), ( # description list: simple diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py index 22f8ded6f..0681352e3 100644 --- a/tests/test_util_inspect.py +++ b/tests/test_util_inspect.py @@ -13,7 +13,6 @@ import datetime import functools import sys import types -from textwrap import dedent import pytest diff --git a/tests/test_util_pycompat.py b/tests/test_util_pycompat.py index 6ffc83f84..a5d743972 100644 --- a/tests/test_util_pycompat.py +++ b/tests/test_util_pycompat.py @@ -8,8 +8,6 @@ :license: BSD, see LICENSE for details. """ -import tempfile - from sphinx.testing.util import strip_escseq from sphinx.util import logging from sphinx.util.pycompat import execfile_