Avoid attribute indirection with `env` (#13212)

This commit is contained in:
Adam Turner authored and GitHub committed 2025-01-05 09:10:55 +00:00
1 parent 56c9e32fbc
commit 83bf866059
42 files changed
+181 -160

No files matched your search

+1 -1
View File
@@ -76,7 +76,7 @@ def process_todo_nodes(app, doctree, fromdocname):
# Replace all todolist nodes with a list of the collected todos.
# Augment each todo with a backlink to the original location.
env = app.builder.env
env = app.env
if not hasattr(env, 'todo_all_todos'):
env.todo_all_todos = []
+11 -9
View File
@@ -48,6 +48,7 @@ from sphinx import roles # NoQA: F401 isort:skip
if TYPE_CHECKING:
from collections.abc import Iterable, Sequence, Set
from pathlib import Path
from docutils.nodes import Node
@@ -242,8 +243,8 @@ class Builder:
if not self.config.gettext_auto_build:
return
def cat2relpath(cat: CatalogInfo) -> str:
return relpath(cat.mo_path, self.env.srcdir).replace(os.path.sep, SEP)
def cat2relpath(cat: CatalogInfo, srcdir: Path = self.srcdir) -> str:
return relpath(cat.mo_path, srcdir).replace(os.path.sep, SEP)
logger.info(bold(__('building [mo]: ')) + message)
for catalog in status_iterator(
@@ -615,14 +616,15 @@ class Builder:
@final
def read_doc(self, docname: str, *, _cache: bool = True) -> None:
"""Parse a file and add/update inventory entries for the doctree."""
self.env.prepare_settings(docname)
env = self.env
env.prepare_settings(docname)
# Add confdir/docutils.conf to dependencies list if exists
docutilsconf = os.path.join(self.confdir, 'docutils.conf')
if os.path.isfile(docutilsconf):
self.env.note_dependency(docutilsconf)
env.note_dependency(docutilsconf)
filename = str(self.env.doc2path(docname))
filename = str(env.doc2path(docname))
filetype = get_filetype(self.app.config.source_suffix, filename)
publisher = self.app.registry.get_publisher(self.app, filetype)
self.env.current_document._parser = publisher.parser
@@ -630,7 +632,7 @@ class Builder:
# explicitly re-initialise for each document
publisher.settings.record_dependencies = DependencyList()
with (
sphinx_domains(self.env),
sphinx_domains(env),
rst.default_role(docname, self.config.default_role),
):
# set up error_handler for the target document
@@ -642,11 +644,11 @@ class Builder:
doctree = publisher.document
# store time of reading, for outdated files detection
self.env.all_docs[docname] = time.time_ns() // 1_000
env.all_docs[docname] = time.time_ns() // 1_000
# cleanup
self.env.current_document = _CurrentDocument()
self.env.ref_context.clear()
env.current_document = _CurrentDocument()
env.ref_context.clear()
self.write_doctree(docname, doctree, _cache=_cache)
+1 -1
View File
@@ -128,7 +128,7 @@ class ChangesBuilder(Builder):
logger.info(bold(__('copying source files...')))
for docname in self.env.all_docs:
with open(
self.env.doc2path(docname), encoding=self.env.config.source_encoding
self.env.doc2path(docname), encoding=self.config.source_encoding
) as f:
try:
lines = f.readlines()
+3 -5
View File
@@ -146,10 +146,8 @@ class I18nBuilder(Builder):
def init(self) -> None:
super().init()
self.env.set_versioning_method(
self.versioning_method, self.env.config.gettext_uuid
)
self.tags = I18nTags()
self.env.set_versioning_method(self.versioning_method, self.config.gettext_uuid)
self.tags = self.app.tags = I18nTags()
self.catalogs: defaultdict[str, Catalog] = defaultdict(Catalog)
def get_target_uri(self, docname: str, typ: str | None = None) -> str:
@@ -174,7 +172,7 @@ class I18nBuilder(Builder):
if not _is_node_in_substitution_definition(node):
catalog.add(msg, node)
if 'index' in self.env.config.gettext_additional_targets:
if 'index' in self.config.gettext_additional_targets:
# Extract translatable messages from index entries.
for node, entries in traverse_translatable_index(doctree):
for entry_type, value, _target_id, _main, _category_key in entries:
+1 -1
View File
@@ -771,7 +771,7 @@ class StandaloneHTMLBuilder(Builder):
def copy_image_files(self) -> None:
if self.images:
stringify_func = ImageAdapter(self.app.env).get_original_image_uri
stringify_func = ImageAdapter(self.env).get_original_image_uri
ensuredir(self._images_dir)
for src in status_iterator(
self.images,
+1 -1
View File
@@ -476,7 +476,7 @@ class LaTeXBuilder(Builder):
def copy_image_files(self) -> None:
if self.images:
stringify_func = ImageAdapter(self.app.env).get_original_image_uri
stringify_func = ImageAdapter(self.env).get_original_image_uri
for src in status_iterator(
self.images,
__('copying images... '),
+1 -1
View File
@@ -196,7 +196,7 @@ class TexinfoBuilder(Builder):
def copy_image_files(self, targetname: str) -> None:
if self.images:
stringify_func = ImageAdapter(self.app.env).get_original_image_uri
stringify_func = ImageAdapter(self.env).get_original_image_uri
for src in status_iterator(
self.images,
__('copying images... '),
+1 -1
View File
@@ -267,7 +267,7 @@ class ObjectDescription(SphinxDirective, Generic[ObjDescT]):
finally:
# Private attributes for ToC generation. Will be modified or removed
# without notice.
if self.env.config.toc_object_entries:
if self.config.toc_object_entries:
signode['_toc_parts'] = self._object_hierarchy_parts(signode)
signode['_toc_name'] = self._toc_entry_name(signode)
else:
+4 -3
View File
@@ -107,6 +107,7 @@ class Domain:
data_version = 0
def __init__(self, env: BuildEnvironment) -> None:
domain_data: dict[str, dict[str, Any]] = env.domaindata
self.env: BuildEnvironment = env
self._role_cache: dict[str, RoleFunction] = {}
self._directive_cache: dict[str, type[Directive]] = {}
@@ -119,13 +120,13 @@ class Domain:
self.roles = dict(self.roles)
self.indices = list(self.indices)
if self.name not in env.domaindata:
if self.name not in domain_data:
assert isinstance(self.initial_data, dict)
new_data = copy.deepcopy(self.initial_data)
new_data['version'] = self.data_version
self.data = env.domaindata[self.name] = new_data
self.data = domain_data[self.name] = new_data
else:
self.data = env.domaindata[self.name]
self.data = domain_data[self.name]
if self.data['version'] != self.data_version:
raise OSError('data of %r domain out of date' % self.label)
for name, obj in self.object_types.items():
+8 -8
View File
@@ -219,7 +219,7 @@ class CObject(ObjectDescription[ASTDeclaration]):
ast.describe_signature(signode, 'lastIsName', self.env, options)
def run(self) -> list[Node]:
env = self.state.document.settings.env # from ObjectDescription.run
env = self.env
if env.current_document.c_parent_symbol is None:
root = env.domaindata['c']['root_symbol']
env.current_document.c_parent_symbol = root
@@ -235,8 +235,8 @@ class CObject(ObjectDescription[ASTDeclaration]):
parent_symbol: Symbol = self.env.current_document.c_parent_symbol
max_len = (
self.env.config.c_maximum_signature_line_length
or self.env.config.maximum_signature_line_length
self.config.c_maximum_signature_line_length
or self.config.maximum_signature_line_length
or 0
)
signode['multi_line_parameter_list'] = (
@@ -244,7 +244,7 @@ class CObject(ObjectDescription[ASTDeclaration]):
and (len(sig) > max_len > 0)
)
parser = DefinitionParser(sig, location=signode, config=self.env.config)
parser = DefinitionParser(sig, location=signode, config=self.config)
try:
ast = self.parse_definition(parser)
parser.assert_end()
@@ -400,7 +400,7 @@ class CNamespaceObject(SphinxDirective):
stack: list[Symbol] = []
else:
parser = DefinitionParser(
self.arguments[0], location=self.get_location(), config=self.env.config
self.arguments[0], location=self.get_location(), config=self.config
)
try:
name = parser.parse_namespace_object()
@@ -427,7 +427,7 @@ class CNamespacePushObject(SphinxDirective):
if self.arguments[0].strip() in {'NULL', '0', 'nullptr'}:
return []
parser = DefinitionParser(
self.arguments[0], location=self.get_location(), config=self.env.config
self.arguments[0], location=self.get_location(), config=self.config
)
try:
name = parser.parse_namespace_object()
@@ -567,7 +567,7 @@ class AliasTransform(SphinxTransform):
sig = node.sig
parent_key = node.parentKey
try:
parser = DefinitionParser(sig, location=node, config=self.env.config)
parser = DefinitionParser(sig, location=node, config=self.config)
name = parser.parse_xref_object()
except DefinitionError as e:
logger.warning(e, location=node)
@@ -716,7 +716,7 @@ class CExprRole(SphinxRole):
def run(self) -> tuple[list[Node], list[system_message]]:
text = self.text.replace('\n', ' ')
parser = DefinitionParser(
text, location=self.get_location(), config=self.env.config
text, location=self.get_location(), config=self.config
)
# attempt to mimic XRefRole classes, except that...
try:
+7 -7
View File
@@ -259,7 +259,7 @@ class CPPObject(ObjectDescription[ASTDeclaration]):
break
if not is_in_concept and 'no-index-entry' not in self.options:
stripped_name = name
for prefix in self.env.config.cpp_index_common_prefix:
for prefix in self.config.cpp_index_common_prefix:
if name.startswith(prefix):
stripped_name = stripped_name[len(prefix) :]
break
@@ -308,7 +308,7 @@ class CPPObject(ObjectDescription[ASTDeclaration]):
ast.describe_signature(signode, 'lastIsName', self.env, options)
def run(self) -> list[Node]:
env = self.state.document.settings.env # from ObjectDescription.run
env = self.env
if env.current_document.cpp_parent_symbol is None:
root = env.domaindata['cpp']['root_symbol']
env.current_document.cpp_parent_symbol = root
@@ -348,8 +348,8 @@ class CPPObject(ObjectDescription[ASTDeclaration]):
parent_symbol: Symbol = self.env.current_document.cpp_parent_symbol
max_len = (
self.env.config.cpp_maximum_signature_line_length
or self.env.config.maximum_signature_line_length
self.config.cpp_maximum_signature_line_length
or self.config.maximum_signature_line_length
or 0
)
signode['multi_line_parameter_list'] = (
@@ -357,7 +357,7 @@ class CPPObject(ObjectDescription[ASTDeclaration]):
and (len(sig) > max_len > 0)
)
parser = DefinitionParser(sig, location=signode, config=self.env.config)
parser = DefinitionParser(sig, location=signode, config=self.config)
try:
ast = self.parse_definition(parser)
parser.assert_end()
@@ -440,7 +440,7 @@ class CPPObject(ObjectDescription[ASTDeclaration]):
if not sig_node.get('_toc_parts'):
return ''
config = self.env.config
config = self.config
objtype = sig_node.parent.get('objtype')
if config.add_function_parentheses and objtype in {'function', 'method'}:
parens = '()'
@@ -710,7 +710,7 @@ class AliasTransform(SphinxTransform):
sig = node.sig
parent_key = node.parentKey
try:
parser = DefinitionParser(sig, location=node, config=self.env.config)
parser = DefinitionParser(sig, location=node, config=self.config)
ast, is_shorthand = parser.parse_xref_object()
parser.assert_end()
except DefinitionError as e:
+3 -3
View File
@@ -101,8 +101,8 @@ class JSObject(ObjectDescription[tuple[str, str]]):
signode['fullname'] = fullname
max_len = (
self.env.config.javascript_maximum_signature_line_length
or self.env.config.maximum_signature_line_length
self.config.javascript_maximum_signature_line_length
or self.config.maximum_signature_line_length
or 0
)
multi_line_parameter_list = (
@@ -238,7 +238,7 @@ class JSObject(ObjectDescription[tuple[str, str]]):
if not sig_node.get('_toc_parts'):
return ''
config = self.env.config
config = self.config
objtype = sig_node.parent.get('objtype')
if config.add_function_parentheses and objtype in {'function', 'method'}:
parens = '()'
+4 -4
View File
@@ -261,7 +261,7 @@ class PyMethod(PyObject):
name, cls = name_cls
try:
clsname, methname = name.rsplit('.', 1)
if modname and self.env.config.add_module_names:
if modname and self.config.add_module_names:
clsname = f'{modname}.{clsname}'
except ValueError:
if modname:
@@ -357,7 +357,7 @@ class PyAttribute(PyObject):
name, cls = name_cls
try:
clsname, attrname = name.rsplit('.', 1)
if modname and self.env.config.add_module_names:
if modname and self.config.add_module_names:
clsname = f'{modname}.{clsname}'
except ValueError:
if modname:
@@ -417,7 +417,7 @@ class PyProperty(PyObject):
name, cls = name_cls
try:
clsname, attrname = name.rsplit('.', 1)
if modname and self.env.config.add_module_names:
if modname and self.config.add_module_names:
clsname = f'{modname}.{clsname}'
except ValueError:
if modname:
@@ -457,7 +457,7 @@ class PyTypeAlias(PyObject):
name, cls = name_cls
try:
clsname, attrname = name.rsplit('.', 1)
if modname and self.env.config.add_module_names:
if modname and self.config.add_module_names:
clsname = f'{modname}.{clsname}'
except ValueError:
if modname:
+4 -4
View File
@@ -286,8 +286,8 @@ class PyObject(ObjectDescription[tuple[str, str]]):
signode['fullname'] = fullname
max_len = (
self.env.config.python_maximum_signature_line_length
or self.env.config.maximum_signature_line_length
self.config.python_maximum_signature_line_length
or self.config.maximum_signature_line_length
or 0
)
@@ -321,7 +321,7 @@ class PyObject(ObjectDescription[tuple[str, str]]):
if prefix:
signode += addnodes.desc_addname(prefix, prefix)
elif modname and add_module and self.env.config.add_module_names:
elif modname and add_module and self.config.add_module_names:
nodetext = f'{modname}.'
signode += addnodes.desc_addname(nodetext, nodetext)
@@ -474,7 +474,7 @@ class PyObject(ObjectDescription[tuple[str, str]]):
if not sig_node.get('_toc_parts'):
return ''
config = self.env.config
config = self.config
objtype = sig_node.parent.get('objtype')
if config.add_function_parentheses and objtype in {'function', 'method'}:
parens = '()'
+1 -2
View File
@@ -84,12 +84,11 @@ class ReSTMarkup(ObjectDescription[str]):
if not sig_node.get('_toc_parts'):
return ''
config = self.env.config
objtype = sig_node.parent.get('objtype')
*parents, name = sig_node['_toc_parts']
if objtype == 'directive:option':
return f':{name}:'
if config.toc_object_entries_show_parents in {'domain', 'all'}:
if self.config.toc_object_entries_show_parents in {'domain', 'all'}:
name = ':'.join(sig_node['_toc_parts'])
if objtype == 'role':
return f':{name}:'
+2 -2
View File
@@ -258,13 +258,13 @@ class Cmdoption(ObjectDescription[str]):
args = '[' + args
if count:
if self.env.config.option_emphasise_placeholders:
if self.config.option_emphasise_placeholders:
signode += addnodes.desc_sig_punctuation(',', ',')
signode += addnodes.desc_sig_space()
else:
signode += addnodes.desc_addname(', ', ', ')
signode += addnodes.desc_name(optname, optname)
if self.env.config.option_emphasise_placeholders:
if self.config.option_emphasise_placeholders:
add_end_bracket = False
if args:
if args[0] == '[' and args[-1] == ']':
+2
View File
@@ -695,6 +695,7 @@ class BuildEnvironment:
toctreenode,
prune=prune_toctrees,
includehidden=includehidden,
tags=builder.tags,
)
if result is None:
toctreenode.parent.replace(toctreenode, [])
@@ -735,6 +736,7 @@ class BuildEnvironment:
titles_only=titles_only,
collapse=collapse,
includehidden=includehidden,
tags=builder.tags,
)
def resolve_references(
+5 -2
View File
@@ -86,6 +86,7 @@ def global_toctree_for_doc(
titles_only=titles_only,
collapse=collapse,
includehidden=includehidden,
tags=builder.tags,
)
for toctree_node in env.master_doctree.findall(addnodes.toctree)
)
@@ -110,6 +111,7 @@ def _resolve_toctree(
titles_only: bool = False,
collapse: bool = False,
includehidden: bool = False,
tags: Tags,
) -> Element | None:
"""Resolve a *toctree* node into individual bullet lists with titles
as items, returning None (if no containing titles are found) or
@@ -159,7 +161,7 @@ def _resolve_toctree(
titles_only,
collapse,
includehidden,
builder.tags,
tags,
toctree_ancestors,
included,
excluded,
@@ -558,13 +560,14 @@ class TocTree:
titles_only=titles_only,
collapse=collapse,
includehidden=includehidden,
tags=builder.tags,
)
def get_toctree_ancestors(self, docname: str) -> list[str]:
return [*_get_toctree_ancestors(self.env.toctree_includes, docname)]
def get_toc_for(self, docname: str, builder: Builder) -> Node:
return document_toc(self.env, docname, self.env.app.builder.tags)
return document_toc(self.env, docname, self.env.app.tags)
def get_toctree_for(
self,
+4 -4
View File
@@ -73,9 +73,9 @@ class ImageCollector(EnvironmentCollector):
# Search language-specific figures at first
i18n_imguri = get_image_filename_for_language(imguri, app.env)
_, full_i18n_imgpath = app.env.relfn2path(i18n_imguri, docname)
self.collect_candidates(app.env, full_i18n_imgpath, candidates, node)
self.collect_candidates(app.srcdir, full_i18n_imgpath, candidates, node)
self.collect_candidates(app.env, full_imgpath, candidates, node)
self.collect_candidates(app.srcdir, full_imgpath, candidates, node)
else:
# substitute imguri by figure_language_filename
# (ex. foo.png -> foo.en.png)
@@ -106,14 +106,14 @@ class ImageCollector(EnvironmentCollector):
def collect_candidates(
self,
env: BuildEnvironment,
srcdir: Path,
imgpath: str,
candidates: dict[str, str],
node: Node,
) -> None:
globbed: dict[str, list[str]] = {}
for filename in glob(imgpath):
new_imgpath = _relative_path(Path(filename), env.srcdir)
new_imgpath = _relative_path(Path(filename), srcdir)
try:
mimetype = guess_mimetype(filename)
if mimetype is None:
+25 -22
View File
@@ -46,7 +46,8 @@ if TYPE_CHECKING:
from typing import ClassVar, Literal, TypeAlias
from sphinx.application import Sphinx
from sphinx.environment import BuildEnvironment
from sphinx.environment import BuildEnvironment, _CurrentDocument
from sphinx.events import EventManager
from sphinx.ext.autodoc.directive import DocumenterBridge
_AutodocObjType = Literal[
@@ -370,6 +371,8 @@ class Documenter:
self.directive = directive
self.config: Config = directive.env.config
self.env: BuildEnvironment = directive.env
self._current_document: _CurrentDocument = directive.env.current_document
self._events: EventManager = directive.env.events
self.options = directive.genopt
self.name = name
self.indent = indent
@@ -554,7 +557,7 @@ class Documenter:
)
args = None
result = self.env.events.emit_firstresult(
result = self._events.emit_firstresult(
'autodoc-process-signature',
self.objtype,
self.fullname,
@@ -613,9 +616,9 @@ class Documenter:
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:
if self._events is not None:
# let extensions preprocess docstrings
self.env.events.emit(
self._events.emit(
'autodoc-process-docstring',
self.objtype,
self.fullname,
@@ -840,9 +843,9 @@ class Documenter:
# give the user a chance to decide whether this member
# should be skipped
if self.env.app:
if self._events is not None:
# let extensions preprocess docstrings
skip_user = self.env.events.emit_firstresult(
skip_user = self._events.emit_firstresult(
'autodoc-skip-member',
self.objtype,
membername,
@@ -878,9 +881,9 @@ class Documenter:
*self.options.members*.
"""
# set current namespace for finding members
self.env.current_document.autodoc_module = self.modname
self._current_document.autodoc_module = self.modname
if self.objpath:
self.env.current_document.autodoc_class = self.objpath[0]
self._current_document.autodoc_class = self.objpath[0]
want_all = (
all_members or self.options.inherited_members or self.options.members is ALL
@@ -918,8 +921,8 @@ class Documenter:
)
# reset current objects
self.env.current_document.autodoc_module = ''
self.env.current_document.autodoc_class = ''
self._current_document.autodoc_module = ''
self._current_document.autodoc_class = ''
def sort_members(
self, documenters: list[tuple[Documenter, bool]], order: str
@@ -1261,7 +1264,7 @@ class ModuleLevelDocumenter(Documenter):
# if documenting a toplevel object without explicit module,
# it can be contained in another auto directive ...
modname = self.env.current_document.autodoc_module
modname = self._current_document.autodoc_module
# ... or in the scope of a module directive
if not modname:
modname = self.env.ref_context.get('py:module')
@@ -1287,7 +1290,7 @@ class ClassLevelDocumenter(Documenter):
# if documenting a class-level object without path,
# there must be a current class, either from a parent
# auto directive ...
mod_cls = self.env.current_document.autodoc_class
mod_cls = self._current_document.autodoc_class
# ... or from a class directive
if not mod_cls:
mod_cls = self.env.ref_context.get('py:class', '')
@@ -1298,7 +1301,7 @@ class ClassLevelDocumenter(Documenter):
parents = [cls]
# if the module name is still missing, get it like above
if not modname:
modname = self.env.current_document.autodoc_module
modname = self._current_document.autodoc_module
if not modname:
modname = self.env.ref_context.get('py:module')
# ... else, it stays None, which means invalid
@@ -1432,7 +1435,7 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
kwargs.setdefault('unqualified_typehints', True)
try:
self.env.events.emit('autodoc-before-process-signature', self.object, False)
self._events.emit('autodoc-before-process-signature', self.object, False)
sig = inspect.signature(
self.object, type_aliases=self.config.autodoc_type_aliases
)
@@ -1685,7 +1688,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
call = None
if call is not None:
self.env.events.emit('autodoc-before-process-signature', call, True)
self._events.emit('autodoc-before-process-signature', call, True)
try:
sig = inspect.signature(
call,
@@ -1704,7 +1707,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
new = None
if new is not None:
self.env.events.emit('autodoc-before-process-signature', new, True)
self._events.emit('autodoc-before-process-signature', new, True)
try:
sig = inspect.signature(
new,
@@ -1718,7 +1721,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
# Finally, we should have at least __init__ implemented
init = get_user_defined_function_or_method(self.object, '__init__')
if init is not None:
self.env.events.emit('autodoc-before-process-signature', init, True)
self._events.emit('autodoc-before-process-signature', init, True)
try:
sig = inspect.signature(
init,
@@ -1733,7 +1736,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
# handle it.
# We don't know the exact method that inspect.signature will read
# the signature from, so just pass the object itself to our hook.
self.env.events.emit('autodoc-before-process-signature', self.object, False)
self._events.emit('autodoc-before-process-signature', self.object, False)
try:
sig = inspect.signature(
self.object,
@@ -1886,7 +1889,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
else:
bases = []
self.env.events.emit(
self._events.emit(
'autodoc-process-bases', self.fullname, self.object, self.options, bases
)
@@ -2387,7 +2390,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
if inspect.isstaticmethod(
self.object, cls=self.parent, name=self.object_name
):
self.env.events.emit(
self._events.emit(
'autodoc-before-process-signature', self.object, False
)
sig = inspect.signature(
@@ -2396,7 +2399,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
type_aliases=self.config.autodoc_type_aliases,
)
else:
self.env.events.emit(
self._events.emit(
'autodoc-before-process-signature', self.object, True
)
sig = inspect.signature(
@@ -3049,7 +3052,7 @@ class PropertyDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): #
return ''
# update the annotations of the property getter
self.env.events.emit('autodoc-before-process-signature', func, False)
self._events.emit('autodoc-before-process-signature', func, False)
# correctly format the arguments for a property
return super().format_args(**kwargs)
+1 -1
View File
@@ -870,7 +870,7 @@ def process_generate_options(app: Sphinx) -> None:
genfiles = app.config.autosummary_generate
if genfiles is True:
env = app.builder.env
env = app.env
genfiles = [
str(env.doc2path(x, base=False))
for x in env.found_docs
+3 -2
View File
@@ -240,6 +240,7 @@ def fix_svg_relative_paths(
self: HTML5Translator | LaTeXTranslator | TexinfoTranslator, filepath: str
) -> None:
"""Change relative links in generated svg files to be relative to imgpath."""
env = self.builder.env
tree = ET.parse(filepath) # NoQA: S314
root = tree.getroot()
ns = {'svg': 'http://www.w3.org/2000/svg', 'xlink': 'http://www.w3.org/1999/xlink'}
@@ -255,11 +256,11 @@ def fix_svg_relative_paths(
# not a relative link
continue
docname = self.builder.env.path2doc(self.document['source'])
docname = env.path2doc(self.document['source'])
if docname is None:
# This shouldn't happen!
continue
doc_dir = self.builder.app.outdir.joinpath(docname).resolve().parent
doc_dir = self.builder.outdir.joinpath(docname).resolve().parent
old_path = doc_dir / rel_uri
img_path = doc_dir / self.builder.imgpath
+10 -10
View File
@@ -58,7 +58,7 @@ if TYPE_CHECKING:
from docutils.nodes import Node
from sphinx.application import Sphinx
from sphinx.environment import BuildEnvironment
from sphinx.config import Config
from sphinx.util.typing import ExtensionMetadata, OptionSpec
from sphinx.writers.html5 import HTML5Translator
from sphinx.writers.latex import LaTeXTranslator
@@ -302,7 +302,7 @@ class InheritanceGraph:
self,
name: str,
urls: dict[str, str] | None = None,
env: BuildEnvironment | None = None,
config: Config | None = None,
graph_attrs: dict | None = None,
node_attrs: dict | None = None,
edge_attrs: dict | None = None,
@@ -328,10 +328,10 @@ class InheritanceGraph:
n_attrs.update(node_attrs)
if edge_attrs is not None:
e_attrs.update(edge_attrs)
if env:
g_attrs.update(env.config.inheritance_graph_attrs)
n_attrs.update(env.config.inheritance_node_attrs)
e_attrs.update(env.config.inheritance_edge_attrs)
if config:
g_attrs.update(config.inheritance_graph_attrs)
n_attrs.update(config.inheritance_node_attrs)
e_attrs.update(config.inheritance_edge_attrs)
res: list[str] = [
f'digraph {name} {{\n',
@@ -450,7 +450,7 @@ def html_visit_inheritance_diagram(
name = 'inheritance%s' % graph_hash
# Create a mapping from fully-qualified class names to URLs.
graphviz_output_format = self.builder.env.config.graphviz_output_format.upper()
graphviz_output_format = self.config.graphviz_output_format.upper()
current_filename = os.path.basename(
self.builder.current_docname + self.builder.out_suffix
)
@@ -471,7 +471,7 @@ def html_visit_inheritance_diagram(
else:
urls[child['reftitle']] = '#' + child.get('refid')
dotcode = graph.generate_dot(name, urls, env=self.builder.env)
dotcode = graph.generate_dot(name, urls, config=self.config)
render_dot_html(
self,
node,
@@ -496,7 +496,7 @@ def latex_visit_inheritance_diagram(
name = 'inheritance%s' % graph_hash
dotcode = graph.generate_dot(
name, env=self.builder.env, graph_attrs={'size': '"6.0,6.0"'}
name, config=self.config, graph_attrs={'size': '"6.0,6.0"'}
)
render_dot_latex(self, node, dotcode, {}, 'inheritance')
raise nodes.SkipNode
@@ -515,7 +515,7 @@ def texinfo_visit_inheritance_diagram(
name = 'inheritance%s' % graph_hash
dotcode = graph.generate_dot(
name, env=self.builder.env, graph_attrs={'size': '"6.0,6.0"'}
name, config=self.config, graph_attrs={'size': '"6.0,6.0"'}
)
render_dot_texinfo(self, node, dotcode, {}, 'inheritance')
raise nodes.SkipNode
+2 -1
View File
@@ -140,8 +140,9 @@ def load_mappings(app: Sphinx) -> None:
The intersphinx mappings are expected to be normalized.
"""
env = app.env
now = int(time.time())
inventories = InventoryAdapter(app.builder.env)
inventories = InventoryAdapter(env)
intersphinx_cache: dict[InventoryURI, InventoryCacheEntry] = inventories.cache
intersphinx_mapping: IntersphinxMapping = app.config.intersphinx_mapping
+1 -1
View File
@@ -40,7 +40,7 @@ class LinkcodeError(SphinxError):
def doctree_read(app: Sphinx, doctree: Node) -> None:
env = app.builder.env
env = app.env
resolve_target = getattr(env.config, 'linkcode_resolve', None)
if not callable(env.config.linkcode_resolve):
+2 -2
View File
@@ -87,7 +87,7 @@ def is_supported_builder(builder: Builder) -> bool:
def doctree_read(app: Sphinx, doctree: Node) -> None:
env = app.builder.env
env = app.env
if not hasattr(env, '_viewcode_modules'):
env._viewcode_modules = {} # type: ignore[attr-defined]
@@ -251,7 +251,7 @@ def should_generate_module_page(app: Sphinx, modname: str) -> bool:
def collect_pages(app: Sphinx) -> Iterator[tuple[str, dict[str, Any], str]]:
env = app.builder.env
env = app.env
if not hasattr(env, '_viewcode_modules'):
return
if not is_supported_builder(app.builder):
+8 -4
View File
@@ -272,7 +272,8 @@ class IndexBuilder:
def __init__(
self, env: BuildEnvironment, lang: str, options: dict[str, str], scoring: str
) -> None:
self.env = env
self._domains = env.domains
self._env_version = env.version
# docname -> title
self._titles: dict[str, str | None] = env._search_index_titles
# docname -> filename
@@ -323,7 +324,10 @@ class IndexBuilder:
format = self.formats[format]
frozen = format.load(stream)
# if an old index is present, we treat it as not existing.
if not isinstance(frozen, dict) or frozen.get('envversion') != self.env.version:
if (
not isinstance(frozen, dict)
or frozen.get('envversion') != self._env_version
):
msg = 'old format'
raise ValueError(msg)
index2fn = frozen['docnames']
@@ -362,7 +366,7 @@ class IndexBuilder:
rv: dict[str, list[tuple[int, int, int, str, str]]] = {}
otypes = self._objtypes
onames = self._objnames
for domain in self.env.domains.sorted():
for domain in self._domains.sorted():
sorted_objects = sorted(domain.get_objects())
for fullname, dispname, type, docname, anchor, prio in sorted_objects:
if docname not in fn2index:
@@ -452,7 +456,7 @@ class IndexBuilder:
'objtypes': objtypes,
'objnames': objnames,
'titleterms': title_terms,
'envversion': self.env.version,
'envversion': self._env_version,
'alltitles': alltitles,
'indexentries': index_entries,
}
+4 -3
View File
@@ -16,20 +16,21 @@ if TYPE_CHECKING:
def parse(app: Sphinx, text: str, docname: str = 'index') -> nodes.document:
"""Parse a string as reStructuredText with Sphinx application."""
env = app.env
try:
app.env.current_document.docname = docname
reader = SphinxStandaloneReader()
reader.setup(app)
parser = RSTParser()
parser.set_application(app)
with sphinx_domains(app.env):
with sphinx_domains(env):
return publish_doctree(
text,
str(app.srcdir / f'{docname}.rst'),
reader=reader,
parser=parser,
settings_overrides={
'env': app.env,
'env': env,
'gettext_compact': True,
'input_encoding': 'utf-8',
'output_encoding': 'unicode',
@@ -37,4 +38,4 @@ def parse(app: Sphinx, text: str, docname: str = 'index') -> nodes.document:
},
)
finally:
app.env.current_document.docname = ''
env.current_document.docname = ''
@@ -277,7 +277,7 @@ class OnlyNodeTransform(SphinxPostTransform):
# result in a "Losing ids" exception if there is a target node before
# the only node, so we make sure docutils can transfer the id to
# something, even if it's just a comment and will lose the id anyway...
process_only_nodes(self.document, self.app.builder.tags)
process_only_nodes(self.document, self.app.tags)
class SigElementFallbackTransform(SphinxPostTransform):
+8 -8
View File
@@ -83,7 +83,7 @@ class ImageDownloader(BaseImageConverter):
timestamp: float = ceil(path.stat().st_mtime)
headers['If-Modified-Since'] = epoch_to_rfc1123(timestamp)
config = self.app.config
config = self.config
r = requests.get(
node['uri'],
headers=headers,
@@ -94,7 +94,7 @@ class ImageDownloader(BaseImageConverter):
msg = __('Could not fetch remote image: %s [%d]')
logger.warning(msg, node['uri'], r.status_code)
else:
self.app.env.original_image_uri[_StrPath(path)] = node['uri']
self.env.original_image_uri[_StrPath(path)] = node['uri']
if r.status_code == 200:
path.write_bytes(r.content)
@@ -106,7 +106,7 @@ class ImageDownloader(BaseImageConverter):
def _process_image(self, node: nodes.image, path: Path) -> None:
str_path = _StrPath(path)
self.app.env.original_image_uri[str_path] = node['uri']
self.env.original_image_uri[str_path] = node['uri']
mimetype = guess_mimetype(path, default='*')
if mimetype != '*' and not path.suffix:
@@ -114,14 +114,14 @@ class ImageDownloader(BaseImageConverter):
ext = get_image_extension(mimetype) or ''
with_ext = path.with_name(path.name + ext)
os.replace(path, with_ext)
self.app.env.original_image_uri.pop(str_path)
self.app.env.original_image_uri[_StrPath(with_ext)] = node['uri']
self.env.original_image_uri.pop(str_path)
self.env.original_image_uri[_StrPath(with_ext)] = node['uri']
path = with_ext
path_str = str(path)
node['candidates'].pop('?')
node['candidates'][mimetype] = path_str
node['uri'] = path_str
self.app.env.images.add_file(self.env.docname, path_str)
self.env.images.add_file(self.env.docname, path_str)
class DataURIExtractor(BaseImageConverter):
@@ -145,7 +145,7 @@ class DataURIExtractor(BaseImageConverter):
ensuredir(os.path.join(self.imagedir, 'embeded'))
digest = sha1(image.data, usedforsecurity=False).hexdigest()
path = _StrPath(self.imagedir, 'embeded', digest + ext)
self.app.env.original_image_uri[path] = node['uri']
self.env.original_image_uri[path] = node['uri']
with open(path, 'wb') as f:
f.write(image.data)
@@ -154,7 +154,7 @@ class DataURIExtractor(BaseImageConverter):
node['candidates'].pop('?')
node['candidates'][image.mimetype] = path_str
node['uri'] = path_str
self.app.env.images.add_file(self.env.docname, path_str)
self.env.images.add_file(self.env.docname, path_str)
def get_filename_for(filename: str, mimetype: str) -> str:
+2 -2
View File
@@ -410,7 +410,7 @@ class DocFieldTransformer:
self.directive.domain or '',
target,
contnode=content[0],
env=self.directive.state.document.settings.env,
env=self.directive.env,
)
if _is_single_paragraph(field_body):
paragraph = cast('nodes.paragraph', field_body[0])
@@ -477,7 +477,7 @@ class DocFieldTransformer:
else:
fieldtype, items, location = entry
fieldtypes = types.get(fieldtype.name, {})
env = self.directive.state.document.settings.env
env = self.directive.env
inliner = self.directive.state.inliner
domain = self.directive.domain or ''
new_list += fieldtype.make_field(
+1
View File
@@ -726,6 +726,7 @@ class SphinxTranslator(nodes.NodeVisitor):
self.builder = builder
self.config = builder.config
self.settings = document.settings
self._domains = builder.env.domains
def dispatch_visit(self, node: Node) -> None:
"""
+1 -3
View File
@@ -419,9 +419,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): # type: ignore[misc]
self.body.append(prefix % '.'.join(map(str, numbers)) + ' ')
self.body.append('</span>')
figtype = self.builder.env.domains.standard_domain.get_enumerable_node_type(
node
)
figtype = self._domains.standard_domain.get_enumerable_node_type(node)
if figtype:
if len(node['ids']) == 0:
msg = __('Any IDs not assigned for %s node') % node.tagname
+2 -2
View File
@@ -560,7 +560,7 @@ class LaTeXTranslator(SphinxTranslator):
indices_config = frozenset(indices_config)
else:
check_names = False
for domain in self.builder.env.domains.sorted():
for domain in self._domains.sorted():
for index_cls in domain.indices:
index_name = f'{domain.name}-{index_cls.name}'
if check_names and index_name not in indices_config:
@@ -1816,7 +1816,7 @@ class LaTeXTranslator(SphinxTranslator):
while isinstance(next_node, nodes.target):
next_node = next_node.next_node(ascend=True)
domain = self.builder.env.domains.standard_domain
domain = self._domains.standard_domain
if isinstance(next_node, HYPERLINK_SUPPORT_NODES):
return
if (
+4 -5
View File
@@ -10,7 +10,6 @@ from typing import TYPE_CHECKING, Any, ClassVar, cast
from docutils import nodes, writers
from sphinx import __display_version__, addnodes
from sphinx.errors import ExtensionError
from sphinx.locale import _, __, admonitionlabels
from sphinx.util import logging
from sphinx.util.docutils import SphinxTranslator
@@ -493,7 +492,7 @@ class TexinfoTranslator(SphinxTranslator):
indices_config = frozenset(indices_config)
else:
check_names = False
for domain in self.builder.env.domains.sorted():
for domain in self._domains.sorted():
for index_cls in domain.indices:
index_name = f'{domain.name}-{index_cls.name}'
if check_names and index_name not in indices_config:
@@ -507,7 +506,7 @@ class TexinfoTranslator(SphinxTranslator):
generate(content, collapsed),
))
# only add the main Index if it's not empty
domain = self.builder.env.domains.index_domain
domain = self._domains.index_domain
for docname in self.builder.docnames:
if domain.entries[docname]:
self.indices.append((_('Index'), '\n@printindex ge\n'))
@@ -1420,11 +1419,11 @@ class TexinfoTranslator(SphinxTranslator):
self.add_anchor(id, node)
# use the full name of the objtype for the category
try:
domain = self.builder.env.get_domain(node.parent['domain'])
domain = self._domains[node.parent['domain']]
name = domain.get_type_name(
domain.object_types[objtype], self.config.primary_domain == domain.name
)
except (KeyError, ExtensionError):
except KeyError:
name = objtype
# by convention, the deffn category should be capitalized like a title
category = self.escape_arg(smart_capwords(name))
+2 -1
View File
@@ -16,10 +16,11 @@ class XMLWriter(BaseXMLWriter): # type: ignore[misc]
def __init__(self, builder: Builder) -> None:
super().__init__()
self.builder = builder
self._config = builder.config
def translate(self, *args: Any, **kwargs: Any) -> None:
self.document.settings.newlines = self.document.settings.indents = (
self.builder.env.config.xml_pretty
self._config.xml_pretty
)
self.document.settings.xml_declaration = True
self.document.settings.doctype_declaration = True
@@ -14,21 +14,24 @@ from sphinx.testing import restructuredtext
from sphinx.util.docutils import sphinx_domains
if TYPE_CHECKING:
from sphinx.builders import Builder
from sphinx.application import Sphinx
from sphinx.environment import BuildEnvironment
def _doctree_for_test(builder: Builder, docname: str) -> nodes.document:
builder.env.prepare_settings(docname)
publisher = create_publisher(builder.app, 'restructuredtext')
with sphinx_domains(builder.env):
publisher.set_source(source_path=str(builder.env.doc2path(docname)))
def _doctree_for_test(
app: Sphinx, env: BuildEnvironment, docname: str
) -> nodes.document:
env.prepare_settings(docname)
publisher = create_publisher(app, 'restructuredtext')
with sphinx_domains(env):
publisher.set_source(source_path=str(env.doc2path(docname)))
publisher.publish()
return publisher.document
@pytest.mark.sphinx('text', testroot='object-description-sections')
def test_object_description_sections(app):
doctree = _doctree_for_test(app.builder, 'index')
doctree = _doctree_for_test(app, app.env, 'index')
# <document>
# <index>
# <desc>
@@ -452,7 +452,7 @@ def test_domain_objects_document_scoping(app):
@pytest.mark.test_params(shared_result='test_environment_toctree_basic')
def test_document_toc(app):
app.build()
toctree = document_toc(app.env, 'index', app.builder.tags)
toctree = document_toc(app.env, 'index', app.tags)
assert_node(
toctree,
@@ -502,8 +502,8 @@ def test_document_toc(app):
@pytest.mark.test_params(shared_result='test_environment_toctree_basic')
def test_document_toc_only(app):
app.build()
builder = StandaloneHTMLBuilder(app, app.env)
toctree = document_toc(app.env, 'index', builder.tags)
StandaloneHTMLBuilder(app, app.env) # adds format/builder tags
toctree = document_toc(app.env, 'index', app.tags)
assert_node(
toctree,
@@ -561,7 +561,7 @@ def test_document_toc_only(app):
@pytest.mark.test_params(shared_result='test_environment_toctree_basic')
def test_document_toc_tocdepth(app):
app.build()
toctree = document_toc(app.env, 'tocdepth', app.builder.tags)
toctree = document_toc(app.env, 'tocdepth', app.tags)
assert_node(
toctree,
+10 -7
View File
@@ -10,17 +10,18 @@ import functools
import itertools
import operator
import sys
from types import SimpleNamespace
from typing import TYPE_CHECKING
from unittest.mock import Mock
from warnings import catch_warnings
import pytest
from docutils.statemachine import ViewList
from sphinx import addnodes
from sphinx.ext.autodoc import ALL, ModuleLevelDocumenter, Options
# NEVER import these objects from sphinx.ext.autodoc directly
from sphinx.ext.autodoc.directive import DocumenterBridge
from tests.test_extensions.autodoc_util import do_autodoc
try:
@@ -34,8 +35,10 @@ except ImportError:
if TYPE_CHECKING:
from typing import Any
from sphinx.environment import BuildEnvironment
def make_directive_bridge(env):
def make_directive_bridge(env: BuildEnvironment) -> DocumenterBridge:
options = Options(
inherited_members=False,
undoc_members=False,
@@ -54,11 +57,11 @@ def make_directive_bridge(env):
ignore_module_all=False,
)
directive = SimpleNamespace(
directive = DocumenterBridge(
env=env,
genopt=options,
result=ViewList(),
record_dependencies=set(),
reporter=None,
options=options,
lineno=0,
state=Mock(),
)
directive.state.document.settings.tab_width = 8
+3 -2
View File
@@ -26,6 +26,7 @@ from sphinx.writers.latex import LaTeXTranslator, LaTeXWriter
@pytest.fixture
def settings(app):
env = app.env
texescape.init() # otherwise done by the latex builder
with warnings.catch_warnings():
warnings.filterwarnings('ignore', category=DeprecationWarning)
@@ -37,10 +38,10 @@ def settings(app):
)
settings = optparser.get_default_values()
settings.smart_quotes = True
settings.env = app.builder.env
settings.env = env
settings.env.current_document.docname = 'dummy'
settings.contentsname = 'dummy'
domain_context = sphinx_domains(settings.env)
domain_context = sphinx_domains(env)
domain_context.enable()
yield settings
domain_context.disable()
+5 -5
View File
@@ -29,7 +29,7 @@ def test_RSTParser_prolog_epilog(RSTStateMachine, app):
]
# with rst_prolog
app.env.config.rst_prolog = 'this is rst_prolog\nhello reST!'
app.config.rst_prolog = 'this is rst_prolog\nhello reST!'
parser.parse(text, document)
(content, _), _ = RSTStateMachine().run.call_args
assert list(content.xitems()) == [
@@ -41,8 +41,8 @@ def test_RSTParser_prolog_epilog(RSTStateMachine, app):
]
# with rst_epilog
app.env.config.rst_prolog = None
app.env.config.rst_epilog = 'this is rst_epilog\ngood-bye reST!'
app.config.rst_prolog = None
app.config.rst_epilog = 'this is rst_epilog\ngood-bye reST!'
parser.parse(text, document)
(content, _), _ = RSTStateMachine().run.call_args
assert list(content.xitems()) == [
@@ -54,8 +54,8 @@ def test_RSTParser_prolog_epilog(RSTStateMachine, app):
]
# expandtabs / convert whitespaces
app.env.config.rst_prolog = None
app.env.config.rst_epilog = None
app.config.rst_prolog = None
app.config.rst_epilog = None
text = '\thello Sphinx world\n\v\fSphinx is a document generator'
parser.parse(text, document)
(content, _), _ = RSTStateMachine().run.call_args
+8 -8
View File
@@ -119,7 +119,7 @@ def test_get_filename_for_language(app):
app.env.current_document.docname = 'index'
# language is en
app.env.config.language = 'en'
app.config.language = 'en'
assert get_filename('foo.png', app.env) == 'foo.en.png'
assert get_filename('foo.bar.png', app.env) == 'foo.bar.en.png'
assert get_filename('dir/foo.png', app.env) == 'dir/foo.en.png'
@@ -127,8 +127,8 @@ def test_get_filename_for_language(app):
assert get_filename('foo', app.env) == 'foo.en'
# modify figure_language_filename and language is 'en'
app.env.config.language = 'en'
app.env.config.figure_language_filename = 'images/{language}/{root}{ext}'
app.config.language = 'en'
app.config.figure_language_filename = 'images/{language}/{root}{ext}'
assert get_filename('foo.png', app.env) == 'images/en/foo.png'
assert get_filename('foo.bar.png', app.env) == 'images/en/foo.bar.png'
assert get_filename('subdir/foo.png', app.env) == 'images/en/subdir/foo.png'
@@ -136,8 +136,8 @@ def test_get_filename_for_language(app):
assert get_filename('foo', app.env) == 'images/en/foo'
# new path and basename tokens
app.env.config.language = 'en'
app.env.config.figure_language_filename = '{path}{language}/{basename}{ext}'
app.config.language = 'en'
app.config.figure_language_filename = '{path}{language}/{basename}{ext}'
assert get_filename('foo.png', app.env) == 'en/foo.png'
assert get_filename('foo.bar.png', app.env) == 'en/foo.bar.png'
assert get_filename('subdir/foo.png', app.env) == 'subdir/en/foo.png'
@@ -145,13 +145,13 @@ def test_get_filename_for_language(app):
assert get_filename('foo', app.env) == 'en/foo'
# invalid figure_language_filename
app.env.config.figure_language_filename = '{root}.{invalid}{ext}'
app.config.figure_language_filename = '{root}.{invalid}{ext}'
with pytest.raises(SphinxError):
get_filename('foo.png', app.env)
# docpath (for a document in the top of source directory)
app.env.config.language = 'en'
app.env.config.figure_language_filename = '/{docpath}{language}/{basename}{ext}'
app.config.language = 'en'
app.config.figure_language_filename = '/{docpath}{language}/{basename}{ext}'
assert get_filename('foo.png', app.env) == '/en/foo.png'
# docpath (for a document in the sub directory)