Fix #7535: sphinx-autogen: crashes when custom template uses inheritance

This commit is contained in:
Takeshi KOMIYA 2020-04-23 01:19:47 +09:00
parent fd0a55223f
commit b15e3f6639
3 changed files with 64 additions and 15 deletions

View File

@ -30,6 +30,7 @@ Features added
caption to the toctree caption to the toctree
* #248, #6040: autosummary: Add ``:recursive:`` option to autosummary directive * #248, #6040: autosummary: Add ``:recursive:`` option to autosummary directive
to generate stub files recursively to generate stub files recursively
* #7535: sphinx-autogen: crashes when custom template uses inheritance
* #7481: html theme: Add right margin to footnote/citation labels * #7481: html theme: Add right margin to footnote/citation labels
* #7482: html theme: CSS spacing for code blocks with captions and line numbers * #7482: html theme: CSS spacing for code blocks with captions and line numbers
* #7443: html theme: Add new options :confval:`globaltoc_collapse` and * #7443: html theme: Add new options :confval:`globaltoc_collapse` and

View File

@ -25,25 +25,27 @@ import pydoc
import re import re
import sys import sys
import warnings import warnings
from os import path
from typing import Any, Callable, Dict, List, NamedTuple, Set, Tuple from typing import Any, Callable, Dict, List, NamedTuple, Set, Tuple
from jinja2 import BaseLoader, FileSystemLoader, TemplateNotFound from jinja2 import TemplateNotFound
from jinja2.sandbox import SandboxedEnvironment from jinja2.sandbox import SandboxedEnvironment
import sphinx.locale import sphinx.locale
from sphinx import __display_version__ from sphinx import __display_version__
from sphinx import package_dir from sphinx import package_dir
from sphinx.builders import Builder from sphinx.builders import Builder
from sphinx.config import Config
from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning
from sphinx.ext.autodoc import Documenter from sphinx.ext.autodoc import Documenter
from sphinx.ext.autosummary import import_by_name, get_documenter from sphinx.ext.autosummary import import_by_name, get_documenter
from sphinx.jinja2glue import BuiltinTemplateLoader
from sphinx.locale import __ from sphinx.locale import __
from sphinx.registry import SphinxComponentRegistry from sphinx.registry import SphinxComponentRegistry
from sphinx.util import logging from sphinx.util import logging
from sphinx.util import rst from sphinx.util import rst
from sphinx.util.inspect import safe_getattr from sphinx.util.inspect import safe_getattr
from sphinx.util.osutil import ensuredir from sphinx.util.osutil import ensuredir
from sphinx.util.template import SphinxTemplateLoader
if False: if False:
# For type annotation # For type annotation
@ -59,6 +61,7 @@ class DummyApplication:
def __init__(self) -> None: def __init__(self) -> None:
self.registry = SphinxComponentRegistry() self.registry = SphinxComponentRegistry()
self.messagelog = [] # type: List[str] self.messagelog = [] # type: List[str]
self.translator = None
self.verbosity = 0 self.verbosity = 0
self._warncount = 0 self._warncount = 0
self.warningiserror = False self.warningiserror = False
@ -67,6 +70,21 @@ class DummyApplication:
pass pass
class DummyBuilder:
"""Dummy builder class for sphinx-autogen command."""
def __init__(self, app: DummyApplication, template_path: str) -> None:
if template_path:
templates_path = [path.abspath(template_path)]
else:
templates_path = []
self.app = app
self.srcdir = "/"
self.config = Config(overrides={'templates_path': templates_path})
self.config.init_values()
AutosummaryEntry = NamedTuple('AutosummaryEntry', [('name', str), AutosummaryEntry = NamedTuple('AutosummaryEntry', [('name', str),
('path', str), ('path', str),
('template', str), ('template', str),
@ -110,16 +128,10 @@ class AutosummaryRenderer:
"""A helper class for rendering.""" """A helper class for rendering."""
def __init__(self, builder: Builder, template_dir: str) -> None: def __init__(self, builder: Builder, template_dir: str) -> None:
loader = None # type: BaseLoader system_templates_path = [os.path.join(package_dir, 'ext', 'autosummary', 'templates')]
template_dirs = [os.path.join(package_dir, 'ext', 'autosummary', 'templates')] loader = SphinxTemplateLoader(builder.srcdir,
if builder is None: builder.config.templates_path,
if template_dir: system_templates_path)
template_dirs.insert(0, template_dir)
loader = FileSystemLoader(template_dirs)
else:
# allow the user to override the templates
loader = BuiltinTemplateLoader()
loader.init(builder, dirs=template_dirs)
self.env = SandboxedEnvironment(loader=loader) self.env = SandboxedEnvironment(loader=loader)
self.env.filters['escape'] = rst.escape self.env.filters['escape'] = rst.escape
@ -514,9 +526,9 @@ def main(argv: List[str] = sys.argv[1:]) -> None:
logging.setup(app, sys.stdout, sys.stderr) # type: ignore logging.setup(app, sys.stdout, sys.stderr) # type: ignore
setup_documenters(app) setup_documenters(app)
args = get_parser().parse_args(argv) args = get_parser().parse_args(argv)
builder = DummyBuilder(app, args.templates)
generate_autosummary_docs(args.source_file, args.output_dir, generate_autosummary_docs(args.source_file, args.output_dir,
'.' + args.suffix, '.' + args.suffix, builder=builder, # type: ignore
template_dir=args.templates,
imported_members=args.imported_members, imported_members=args.imported_members,
app=app) app=app)

View File

@ -10,8 +10,11 @@
import os import os
from functools import partial from functools import partial
from typing import Dict, List, Union from os import path
from typing import Callable, Dict, List, Tuple, Union
from jinja2 import TemplateNotFound
from jinja2.environment import Environment
from jinja2.loaders import BaseLoader from jinja2.loaders import BaseLoader
from jinja2.sandbox import SandboxedEnvironment from jinja2.sandbox import SandboxedEnvironment
@ -94,3 +97,36 @@ class ReSTRenderer(SphinxRenderer):
self.env.filters['e'] = rst.escape self.env.filters['e'] = rst.escape
self.env.filters['escape'] = rst.escape self.env.filters['escape'] = rst.escape
self.env.filters['heading'] = rst.heading self.env.filters['heading'] = rst.heading
class SphinxTemplateLoader(BaseLoader):
"""A loader supporting template inheritance"""
def __init__(self, confdir: str, templates_paths: List[str],
system_templates_paths: List[str]) -> None:
self.loaders = []
self.sysloaders = []
for templates_path in templates_paths:
loader = SphinxFileSystemLoader(path.join(confdir, templates_path))
self.loaders.append(loader)
for templates_path in system_templates_paths:
loader = SphinxFileSystemLoader(templates_path)
self.loaders.append(loader)
self.sysloaders.append(loader)
def get_source(self, environment: Environment, template: str) -> Tuple[str, str, Callable]:
if template.startswith('!'):
# search a template from ``system_templates_paths``
loaders = self.sysloaders
template = template[1:]
else:
loaders = self.loaders
for loader in loaders:
try:
return loader.get_source(environment, template)
except TemplateNotFound:
pass
raise TemplateNotFound(template)