Sphinx 7.1.1__py3-none-any.whl → 7.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of Sphinx might be problematic. Click here for more details.

Files changed (313) hide show
  1. sphinx/__init__.py +6 -6
  2. sphinx/__main__.py +3 -1
  3. sphinx/addnodes.py +35 -22
  4. sphinx/application.py +40 -38
  5. sphinx/builders/__init__.py +16 -12
  6. sphinx/builders/_epub_base.py +15 -11
  7. sphinx/builders/changes.py +6 -4
  8. sphinx/builders/dirhtml.py +4 -2
  9. sphinx/builders/dummy.py +6 -4
  10. sphinx/builders/epub3.py +16 -8
  11. sphinx/builders/gettext.py +40 -43
  12. sphinx/builders/html/__init__.py +166 -196
  13. sphinx/builders/html/_assets.py +116 -0
  14. sphinx/builders/html/transforms.py +4 -2
  15. sphinx/builders/latex/__init__.py +12 -7
  16. sphinx/builders/latex/theming.py +5 -2
  17. sphinx/builders/latex/transforms.py +6 -3
  18. sphinx/builders/linkcheck.py +21 -13
  19. sphinx/builders/manpage.py +6 -4
  20. sphinx/builders/singlehtml.py +16 -9
  21. sphinx/builders/texinfo.py +11 -6
  22. sphinx/builders/text.py +8 -3
  23. sphinx/builders/xml.py +9 -4
  24. sphinx/cmd/build.py +27 -14
  25. sphinx/cmd/make_mode.py +13 -4
  26. sphinx/cmd/quickstart.py +13 -4
  27. sphinx/config.py +17 -14
  28. sphinx/deprecation.py +4 -2
  29. sphinx/directives/__init__.py +44 -12
  30. sphinx/directives/code.py +5 -4
  31. sphinx/directives/other.py +92 -44
  32. sphinx/directives/patches.py +1 -1
  33. sphinx/domains/__init__.py +11 -8
  34. sphinx/domains/c.py +67 -57
  35. sphinx/domains/changeset.py +3 -2
  36. sphinx/domains/citation.py +2 -1
  37. sphinx/domains/cpp.py +136 -93
  38. sphinx/domains/index.py +9 -5
  39. sphinx/domains/javascript.py +32 -19
  40. sphinx/domains/math.py +5 -3
  41. sphinx/domains/python.py +69 -57
  42. sphinx/domains/rst.py +20 -11
  43. sphinx/domains/std.py +21 -15
  44. sphinx/environment/__init__.py +97 -65
  45. sphinx/environment/adapters/indexentries.py +13 -10
  46. sphinx/environment/adapters/toctree.py +485 -308
  47. sphinx/environment/collectors/__init__.py +3 -4
  48. sphinx/environment/collectors/asset.py +10 -4
  49. sphinx/environment/collectors/dependencies.py +7 -4
  50. sphinx/environment/collectors/metadata.py +7 -5
  51. sphinx/environment/collectors/title.py +5 -3
  52. sphinx/environment/collectors/toctree.py +13 -8
  53. sphinx/errors.py +1 -1
  54. sphinx/events.py +5 -5
  55. sphinx/ext/apidoc.py +49 -27
  56. sphinx/ext/autodoc/__init__.py +179 -161
  57. sphinx/ext/autodoc/directive.py +10 -6
  58. sphinx/ext/autodoc/importer.py +22 -13
  59. sphinx/ext/autodoc/mock.py +4 -1
  60. sphinx/ext/autodoc/preserve_defaults.py +80 -12
  61. sphinx/ext/autodoc/type_comment.py +14 -10
  62. sphinx/ext/autodoc/typehints.py +7 -3
  63. sphinx/ext/autosectionlabel.py +6 -3
  64. sphinx/ext/autosummary/__init__.py +21 -15
  65. sphinx/ext/autosummary/generate.py +176 -126
  66. sphinx/ext/coverage.py +93 -8
  67. sphinx/ext/doctest.py +28 -17
  68. sphinx/ext/duration.py +19 -17
  69. sphinx/ext/extlinks.py +11 -6
  70. sphinx/ext/githubpages.py +8 -7
  71. sphinx/ext/graphviz.py +61 -17
  72. sphinx/ext/ifconfig.py +7 -4
  73. sphinx/ext/imgconverter.py +4 -2
  74. sphinx/ext/imgmath.py +29 -23
  75. sphinx/ext/inheritance_diagram.py +41 -27
  76. sphinx/ext/intersphinx.py +45 -38
  77. sphinx/ext/linkcode.py +8 -5
  78. sphinx/ext/mathjax.py +13 -9
  79. sphinx/ext/napoleon/__init__.py +3 -3
  80. sphinx/ext/napoleon/docstring.py +40 -31
  81. sphinx/ext/todo.py +10 -7
  82. sphinx/ext/viewcode.py +46 -25
  83. sphinx/extension.py +1 -1
  84. sphinx/highlighting.py +20 -12
  85. sphinx/io.py +5 -4
  86. sphinx/jinja2glue.py +24 -19
  87. sphinx/locale/__init__.py +8 -2
  88. sphinx/locale/ar/LC_MESSAGES/sphinx.mo +0 -0
  89. sphinx/locale/ar/LC_MESSAGES/sphinx.po +756 -740
  90. sphinx/locale/bg/LC_MESSAGES/sphinx.mo +0 -0
  91. sphinx/locale/bg/LC_MESSAGES/sphinx.po +754 -738
  92. sphinx/locale/bn/LC_MESSAGES/sphinx.mo +0 -0
  93. sphinx/locale/bn/LC_MESSAGES/sphinx.po +755 -739
  94. sphinx/locale/ca/LC_MESSAGES/sphinx.mo +0 -0
  95. sphinx/locale/ca/LC_MESSAGES/sphinx.po +768 -752
  96. sphinx/locale/cak/LC_MESSAGES/sphinx.mo +0 -0
  97. sphinx/locale/cak/LC_MESSAGES/sphinx.po +754 -738
  98. sphinx/locale/cs/LC_MESSAGES/sphinx.mo +0 -0
  99. sphinx/locale/cs/LC_MESSAGES/sphinx.po +758 -742
  100. sphinx/locale/cy/LC_MESSAGES/sphinx.mo +0 -0
  101. sphinx/locale/cy/LC_MESSAGES/sphinx.po +759 -743
  102. sphinx/locale/da/LC_MESSAGES/sphinx.mo +0 -0
  103. sphinx/locale/da/LC_MESSAGES/sphinx.po +760 -744
  104. sphinx/locale/de/LC_MESSAGES/sphinx.mo +0 -0
  105. sphinx/locale/de/LC_MESSAGES/sphinx.po +759 -743
  106. sphinx/locale/de_DE/LC_MESSAGES/sphinx.mo +0 -0
  107. sphinx/locale/de_DE/LC_MESSAGES/sphinx.po +754 -738
  108. sphinx/locale/el/LC_MESSAGES/sphinx.mo +0 -0
  109. sphinx/locale/el/LC_MESSAGES/sphinx.po +763 -747
  110. sphinx/locale/en_DE/LC_MESSAGES/sphinx.mo +0 -0
  111. sphinx/locale/en_DE/LC_MESSAGES/sphinx.po +754 -738
  112. sphinx/locale/en_FR/LC_MESSAGES/sphinx.mo +0 -0
  113. sphinx/locale/en_FR/LC_MESSAGES/sphinx.po +754 -738
  114. sphinx/locale/en_GB/LC_MESSAGES/sphinx.mo +0 -0
  115. sphinx/locale/en_GB/LC_MESSAGES/sphinx.po +768 -752
  116. sphinx/locale/en_HK/LC_MESSAGES/sphinx.mo +0 -0
  117. sphinx/locale/en_HK/LC_MESSAGES/sphinx.po +754 -738
  118. sphinx/locale/eo/LC_MESSAGES/sphinx.mo +0 -0
  119. sphinx/locale/eo/LC_MESSAGES/sphinx.po +754 -738
  120. sphinx/locale/es/LC_MESSAGES/sphinx.mo +0 -0
  121. sphinx/locale/es/LC_MESSAGES/sphinx.po +767 -751
  122. sphinx/locale/es_CO/LC_MESSAGES/sphinx.mo +0 -0
  123. sphinx/locale/es_CO/LC_MESSAGES/sphinx.po +754 -738
  124. sphinx/locale/et/LC_MESSAGES/sphinx.mo +0 -0
  125. sphinx/locale/et/LC_MESSAGES/sphinx.po +762 -746
  126. sphinx/locale/eu/LC_MESSAGES/sphinx.mo +0 -0
  127. sphinx/locale/eu/LC_MESSAGES/sphinx.po +755 -739
  128. sphinx/locale/fa/LC_MESSAGES/sphinx.mo +0 -0
  129. sphinx/locale/fa/LC_MESSAGES/sphinx.po +766 -750
  130. sphinx/locale/fi/LC_MESSAGES/sphinx.mo +0 -0
  131. sphinx/locale/fi/LC_MESSAGES/sphinx.po +754 -738
  132. sphinx/locale/fr/LC_MESSAGES/sphinx.mo +0 -0
  133. sphinx/locale/fr/LC_MESSAGES/sphinx.po +768 -752
  134. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.mo +0 -0
  135. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.po +754 -738
  136. sphinx/locale/gl/LC_MESSAGES/sphinx.js +60 -0
  137. sphinx/locale/gl/LC_MESSAGES/sphinx.mo +0 -0
  138. sphinx/locale/gl/LC_MESSAGES/sphinx.po +3695 -0
  139. sphinx/locale/he/LC_MESSAGES/sphinx.mo +0 -0
  140. sphinx/locale/he/LC_MESSAGES/sphinx.po +755 -739
  141. sphinx/locale/hi/LC_MESSAGES/sphinx.mo +0 -0
  142. sphinx/locale/hi/LC_MESSAGES/sphinx.po +763 -747
  143. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.mo +0 -0
  144. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.po +754 -738
  145. sphinx/locale/hr/LC_MESSAGES/sphinx.mo +0 -0
  146. sphinx/locale/hr/LC_MESSAGES/sphinx.po +760 -744
  147. sphinx/locale/hu/LC_MESSAGES/sphinx.mo +0 -0
  148. sphinx/locale/hu/LC_MESSAGES/sphinx.po +759 -743
  149. sphinx/locale/id/LC_MESSAGES/sphinx.mo +0 -0
  150. sphinx/locale/id/LC_MESSAGES/sphinx.po +765 -749
  151. sphinx/locale/is/LC_MESSAGES/sphinx.mo +0 -0
  152. sphinx/locale/is/LC_MESSAGES/sphinx.po +760 -744
  153. sphinx/locale/it/LC_MESSAGES/sphinx.mo +0 -0
  154. sphinx/locale/it/LC_MESSAGES/sphinx.po +760 -744
  155. sphinx/locale/ja/LC_MESSAGES/sphinx.mo +0 -0
  156. sphinx/locale/ja/LC_MESSAGES/sphinx.po +767 -751
  157. sphinx/locale/ka/LC_MESSAGES/sphinx.mo +0 -0
  158. sphinx/locale/ka/LC_MESSAGES/sphinx.po +759 -743
  159. sphinx/locale/ko/LC_MESSAGES/sphinx.mo +0 -0
  160. sphinx/locale/ko/LC_MESSAGES/sphinx.po +767 -751
  161. sphinx/locale/lt/LC_MESSAGES/sphinx.mo +0 -0
  162. sphinx/locale/lt/LC_MESSAGES/sphinx.po +755 -739
  163. sphinx/locale/lv/LC_MESSAGES/sphinx.mo +0 -0
  164. sphinx/locale/lv/LC_MESSAGES/sphinx.po +755 -739
  165. sphinx/locale/mk/LC_MESSAGES/sphinx.mo +0 -0
  166. sphinx/locale/mk/LC_MESSAGES/sphinx.po +754 -738
  167. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.mo +0 -0
  168. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.po +755 -739
  169. sphinx/locale/ne/LC_MESSAGES/sphinx.mo +0 -0
  170. sphinx/locale/ne/LC_MESSAGES/sphinx.po +755 -739
  171. sphinx/locale/nl/LC_MESSAGES/sphinx.mo +0 -0
  172. sphinx/locale/nl/LC_MESSAGES/sphinx.po +760 -744
  173. sphinx/locale/pl/LC_MESSAGES/sphinx.mo +0 -0
  174. sphinx/locale/pl/LC_MESSAGES/sphinx.po +762 -745
  175. sphinx/locale/pt/LC_MESSAGES/sphinx.mo +0 -0
  176. sphinx/locale/pt/LC_MESSAGES/sphinx.po +754 -738
  177. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.mo +0 -0
  178. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.po +768 -752
  179. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.mo +0 -0
  180. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.po +755 -739
  181. sphinx/locale/ro/LC_MESSAGES/sphinx.mo +0 -0
  182. sphinx/locale/ro/LC_MESSAGES/sphinx.po +759 -743
  183. sphinx/locale/ru/LC_MESSAGES/sphinx.mo +0 -0
  184. sphinx/locale/ru/LC_MESSAGES/sphinx.po +760 -744
  185. sphinx/locale/si/LC_MESSAGES/sphinx.mo +0 -0
  186. sphinx/locale/si/LC_MESSAGES/sphinx.po +754 -738
  187. sphinx/locale/sk/LC_MESSAGES/sphinx.mo +0 -0
  188. sphinx/locale/sk/LC_MESSAGES/sphinx.po +765 -749
  189. sphinx/locale/sl/LC_MESSAGES/sphinx.mo +0 -0
  190. sphinx/locale/sl/LC_MESSAGES/sphinx.po +755 -739
  191. sphinx/locale/sphinx.pot +748 -740
  192. sphinx/locale/sq/LC_MESSAGES/sphinx.mo +0 -0
  193. sphinx/locale/sq/LC_MESSAGES/sphinx.po +768 -752
  194. sphinx/locale/sr/LC_MESSAGES/sphinx.mo +0 -0
  195. sphinx/locale/sr/LC_MESSAGES/sphinx.po +754 -738
  196. sphinx/locale/sr@latin/LC_MESSAGES/sphinx.mo +0 -0
  197. sphinx/locale/sr@latin/LC_MESSAGES/sphinx.po +754 -738
  198. sphinx/locale/sr_RS/LC_MESSAGES/sphinx.mo +0 -0
  199. sphinx/locale/sr_RS/LC_MESSAGES/sphinx.po +754 -738
  200. sphinx/locale/sv/LC_MESSAGES/sphinx.mo +0 -0
  201. sphinx/locale/sv/LC_MESSAGES/sphinx.po +755 -739
  202. sphinx/locale/ta/LC_MESSAGES/sphinx.mo +0 -0
  203. sphinx/locale/ta/LC_MESSAGES/sphinx.po +754 -738
  204. sphinx/locale/te/LC_MESSAGES/sphinx.mo +0 -0
  205. sphinx/locale/te/LC_MESSAGES/sphinx.po +754 -738
  206. sphinx/locale/tr/LC_MESSAGES/sphinx.mo +0 -0
  207. sphinx/locale/tr/LC_MESSAGES/sphinx.po +763 -747
  208. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.mo +0 -0
  209. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.po +760 -749
  210. sphinx/locale/ur/LC_MESSAGES/sphinx.mo +0 -0
  211. sphinx/locale/ur/LC_MESSAGES/sphinx.po +759 -748
  212. sphinx/locale/vi/LC_MESSAGES/sphinx.mo +0 -0
  213. sphinx/locale/vi/LC_MESSAGES/sphinx.po +754 -738
  214. sphinx/locale/yue/LC_MESSAGES/sphinx.mo +0 -0
  215. sphinx/locale/yue/LC_MESSAGES/sphinx.po +754 -738
  216. sphinx/locale/zh_CN/LC_MESSAGES/sphinx.mo +0 -0
  217. sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po +768 -752
  218. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.mo +0 -0
  219. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.po +754 -738
  220. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.mo +0 -0
  221. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po +767 -751
  222. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.mo +0 -0
  223. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.po +754 -738
  224. sphinx/parsers.py +5 -4
  225. sphinx/project.py +52 -34
  226. sphinx/pycode/__init__.py +2 -1
  227. sphinx/pycode/ast.py +7 -13
  228. sphinx/pycode/parser.py +42 -38
  229. sphinx/registry.py +35 -29
  230. sphinx/roles.py +9 -4
  231. sphinx/search/__init__.py +5 -17
  232. sphinx/search/da.py +1 -1
  233. sphinx/search/de.py +1 -1
  234. sphinx/search/en.py +1 -1
  235. sphinx/search/es.py +1 -1
  236. sphinx/search/fi.py +1 -1
  237. sphinx/search/fr.py +1 -1
  238. sphinx/search/hu.py +1 -1
  239. sphinx/search/it.py +1 -1
  240. sphinx/search/ja.py +1 -1
  241. sphinx/search/nl.py +1 -1
  242. sphinx/search/no.py +1 -1
  243. sphinx/search/pt.py +1 -1
  244. sphinx/search/ro.py +1 -1
  245. sphinx/search/ru.py +1 -1
  246. sphinx/search/sv.py +1 -1
  247. sphinx/search/tr.py +1 -1
  248. sphinx/search/zh.py +1 -1
  249. sphinx/testing/fixtures.py +23 -30
  250. sphinx/testing/path.py +9 -0
  251. sphinx/testing/restructuredtext.py +13 -5
  252. sphinx/testing/util.py +20 -63
  253. sphinx/texinputs/sphinxlatexobjects.sty +15 -15
  254. sphinx/themes/agogo/static/agogo.css_t +10 -4
  255. sphinx/themes/basic/layout.html +1 -1
  256. sphinx/themes/basic/static/basic.css_t +4 -0
  257. sphinx/themes/basic/static/documentation_options.js_t +1 -2
  258. sphinx/themes/basic/static/searchtools.js +17 -9
  259. sphinx/themes/basic/static/sphinx_highlight.js +13 -3
  260. sphinx/themes/bizstyle/static/bizstyle.css_t +4 -0
  261. sphinx/themes/classic/theme.conf +1 -1
  262. sphinx/themes/epub/static/epub.css_t +6 -1
  263. sphinx/themes/haiku/theme.conf +1 -1
  264. sphinx/themes/nature/static/nature.css_t +4 -0
  265. sphinx/themes/nonav/static/nonav.css_t +6 -1
  266. sphinx/themes/pyramid/static/pyramid.css_t +4 -0
  267. sphinx/themes/scrolls/static/scrolls.css_t +4 -0
  268. sphinx/themes/scrolls/theme.conf +1 -1
  269. sphinx/themes/sphinxdoc/static/sphinxdoc.css_t +4 -0
  270. sphinx/theming.py +9 -7
  271. sphinx/transforms/__init__.py +79 -3
  272. sphinx/transforms/compact_bullet_list.py +6 -3
  273. sphinx/transforms/i18n.py +26 -10
  274. sphinx/transforms/post_transforms/__init__.py +21 -8
  275. sphinx/transforms/post_transforms/code.py +6 -3
  276. sphinx/transforms/post_transforms/images.py +13 -9
  277. sphinx/util/__init__.py +21 -92
  278. sphinx/util/cfamily.py +7 -4
  279. sphinx/util/display.py +3 -2
  280. sphinx/util/docfields.py +7 -6
  281. sphinx/util/docstrings.py +1 -1
  282. sphinx/util/docutils.py +41 -31
  283. sphinx/util/fileutil.py +9 -6
  284. sphinx/util/i18n.py +21 -18
  285. sphinx/util/images.py +2 -1
  286. sphinx/util/index_entries.py +27 -0
  287. sphinx/util/inspect.py +83 -67
  288. sphinx/util/inventory.py +4 -2
  289. sphinx/util/logging.py +9 -6
  290. sphinx/util/matching.py +5 -2
  291. sphinx/util/math.py +6 -3
  292. sphinx/util/nodes.py +70 -31
  293. sphinx/util/osutil.py +22 -40
  294. sphinx/util/parallel.py +4 -1
  295. sphinx/util/rst.py +7 -3
  296. sphinx/util/tags.py +11 -4
  297. sphinx/util/template.py +17 -14
  298. sphinx/util/typing.py +61 -20
  299. sphinx/versioning.py +6 -4
  300. sphinx/writers/html.py +1 -1
  301. sphinx/writers/html5.py +32 -24
  302. sphinx/writers/latex.py +67 -53
  303. sphinx/writers/manpage.py +9 -5
  304. sphinx/writers/texinfo.py +11 -9
  305. sphinx/writers/text.py +14 -9
  306. sphinx/writers/xml.py +3 -2
  307. {sphinx-7.1.1.dist-info → sphinx-7.2.0.dist-info}/METADATA +7 -5
  308. sphinx-7.2.0.dist-info/RECORD +568 -0
  309. sphinx/testing/comparer.py +0 -97
  310. sphinx-7.1.1.dist-info/RECORD +0 -564
  311. {sphinx-7.1.1.dist-info → sphinx-7.2.0.dist-info}/LICENSE +0 -0
  312. {sphinx-7.1.1.dist-info → sphinx-7.2.0.dist-info}/WHEEL +0 -0
  313. {sphinx-7.1.1.dist-info → sphinx-7.2.0.dist-info}/entry_points.txt +0 -0
sphinx/util/nodes.py CHANGED
@@ -5,19 +5,22 @@ from __future__ import annotations
5
5
  import contextlib
6
6
  import re
7
7
  import unicodedata
8
- from typing import TYPE_CHECKING, Any, Callable, Iterable
8
+ from typing import TYPE_CHECKING, Any, Callable
9
9
 
10
10
  from docutils import nodes
11
- from docutils.nodes import Element, Node
12
- from docutils.parsers.rst import Directive
13
- from docutils.parsers.rst.states import Inliner
14
- from docutils.statemachine import StringList
15
11
 
16
12
  from sphinx import addnodes
17
13
  from sphinx.locale import __
18
14
  from sphinx.util import logging
19
15
 
20
16
  if TYPE_CHECKING:
17
+ from collections.abc import Iterable
18
+
19
+ from docutils.nodes import Element, Node
20
+ from docutils.parsers.rst import Directive
21
+ from docutils.parsers.rst.states import Inliner
22
+ from docutils.statemachine import StringList
23
+
21
24
  from sphinx.builders import Builder
22
25
  from sphinx.environment import BuildEnvironment
23
26
  from sphinx.util.tags import Tags
@@ -47,7 +50,7 @@ class NodeMatcher:
47
50
  following example searches ``reference`` node having ``refdomain`` attributes::
48
51
 
49
52
  from __future__ import annotations
50
- from typing import Any
53
+ from typing import TYPE_CHECKING, Any
51
54
  matcher = NodeMatcher(nodes.reference, refdomain=Any)
52
55
  doctree.findall(matcher)
53
56
  # => [<reference ...>, <reference ...>, ...]
@@ -196,7 +199,7 @@ def is_translatable(node: Node) -> bool:
196
199
  if isinstance(node, nodes.image) and (node.get('translatable') or node.get('alt')):
197
200
  return True
198
201
 
199
- if isinstance(node, nodes.Inline) and 'translatable' not in node: # type: ignore
202
+ if isinstance(node, nodes.Inline) and 'translatable' not in node: # type: ignore[operator]
200
203
  # inline node must not be translated if 'translatable' is not set
201
204
  return False
202
205
 
@@ -223,7 +226,7 @@ def is_translatable(node: Node) -> bool:
223
226
  return False
224
227
  return True
225
228
 
226
- if isinstance(node, nodes.meta): # type: ignore
229
+ if isinstance(node, nodes.meta): # type: ignore[attr-defined]
227
230
  return True
228
231
 
229
232
  return False
@@ -255,10 +258,11 @@ def extract_messages(doctree: Element) -> Iterable[tuple[Element, str]]:
255
258
  if node.get('alt'):
256
259
  yield node, node['alt']
257
260
  if node.get('translatable'):
258
- msg = '.. image:: %s' % node['uri']
261
+ image_uri = node.get('original_uri', node['uri'])
262
+ msg = f'.. image:: {image_uri}'
259
263
  else:
260
264
  msg = ''
261
- elif isinstance(node, nodes.meta): # type: ignore
265
+ elif isinstance(node, nodes.meta): # type: ignore[attr-defined]
262
266
  msg = node["content"]
263
267
  else:
264
268
  msg = node.rawsource.replace('\n', ' ').strip()
@@ -272,14 +276,16 @@ def get_node_source(node: Element) -> str:
272
276
  for pnode in traverse_parent(node):
273
277
  if pnode.source:
274
278
  return pnode.source
275
- raise ValueError("node source not found")
279
+ msg = 'node source not found'
280
+ raise ValueError(msg)
276
281
 
277
282
 
278
283
  def get_node_line(node: Element) -> int:
279
284
  for pnode in traverse_parent(node):
280
285
  if pnode.line:
281
286
  return pnode.line
282
- raise ValueError("node line not found")
287
+ msg = 'node line not found'
288
+ raise ValueError(msg)
283
289
 
284
290
 
285
291
  def traverse_parent(node: Element, cls: Any = None) -> Iterable[Element]:
@@ -564,7 +570,8 @@ def set_source_info(directive: Directive, node: Node) -> None:
564
570
 
565
571
 
566
572
  def set_role_source_info(inliner: Inliner, lineno: int, node: Node) -> None:
567
- node.source, node.line = inliner.reporter.get_source_and_line(lineno) # type: ignore
573
+ gsal = inliner.reporter.get_source_and_line # type: ignore[attr-defined]
574
+ node.source, node.line = gsal(lineno)
568
575
 
569
576
 
570
577
  def copy_source_info(src: Element, dst: Element) -> None:
@@ -601,33 +608,65 @@ def is_smartquotable(node: Node) -> bool:
601
608
  def process_only_nodes(document: Node, tags: Tags) -> None:
602
609
  """Filter ``only`` nodes which do not match *tags*."""
603
610
  for node in document.findall(addnodes.only):
604
- try:
605
- ret = tags.eval_condition(node['expr'])
606
- except Exception as err:
607
- logger.warning(__('exception while evaluating only directive expression: %s'), err,
608
- location=node)
611
+ if _only_node_keep_children(node, tags):
609
612
  node.replace_self(node.children or nodes.comment())
610
613
  else:
611
- if ret:
612
- node.replace_self(node.children or nodes.comment())
613
- else:
614
- # A comment on the comment() nodes being inserted: replacing by [] would
615
- # result in a "Losing ids" exception if there is a target node before
616
- # the only node, so we make sure docutils can transfer the id to
617
- # something, even if it's just a comment and will lose the id anyway...
618
- node.replace_self(nodes.comment())
614
+ # A comment on the comment() nodes being inserted: replacing by [] would
615
+ # result in a "Losing ids" exception if there is a target node before
616
+ # the only node, so we make sure docutils can transfer the id to
617
+ # something, even if it's just a comment and will lose the id anyway...
618
+ node.replace_self(nodes.comment())
619
619
 
620
620
 
621
- def _copy_except__document(self: Element) -> Element:
621
+ def _only_node_keep_children(node: addnodes.only, tags: Tags) -> bool:
622
+ """Keep children if tags match or error."""
623
+ try:
624
+ return tags.eval_condition(node['expr'])
625
+ except Exception as err:
626
+ logger.warning(
627
+ __('exception while evaluating only directive expression: %s'),
628
+ err,
629
+ location=node)
630
+ return True
631
+
632
+
633
+ def _copy_except__document(el: Element) -> Element:
622
634
  """Monkey-patch ```nodes.Element.copy``` to not copy the ``_document``
623
635
  attribute.
624
636
 
625
637
  xref: https://github.com/sphinx-doc/sphinx/issues/11116#issuecomment-1376767086
626
638
  """
627
- newnode = self.__class__(rawsource=self.rawsource, **self.attributes)
628
- newnode.source = self.source
629
- newnode.line = self.line
639
+ newnode = object.__new__(el.__class__)
640
+ # set in Element.__init__()
641
+ newnode.children = []
642
+ newnode.rawsource = el.rawsource
643
+ newnode.tagname = el.tagname
644
+ # copied in Element.copy()
645
+ newnode.attributes = {k: (v
646
+ if k not in {'ids', 'classes', 'names', 'dupnames', 'backrefs'}
647
+ else v[:])
648
+ for k, v in el.attributes.items()}
649
+ newnode.line = el.line
650
+ newnode.source = el.source
651
+ return newnode
652
+
653
+
654
+ nodes.Element.copy = _copy_except__document # type: ignore[assignment]
655
+
656
+
657
+ def _deepcopy(el: Element) -> Element:
658
+ """Monkey-patch ```nodes.Element.deepcopy``` for speed."""
659
+ newnode = el.copy()
660
+ newnode.children = [child.deepcopy() for child in el.children]
661
+ for child in newnode.children:
662
+ child.parent = newnode
663
+ if el.document:
664
+ child.document = el.document
665
+ if child.source is None:
666
+ child.source = el.document.current_source
667
+ if child.line is None:
668
+ child.line = el.document.current_line
630
669
  return newnode
631
670
 
632
671
 
633
- nodes.Element.copy = _copy_except__document # type: ignore
672
+ nodes.Element.deepcopy = _deepcopy # type: ignore[assignment]
sphinx/util/osutil.py CHANGED
@@ -11,16 +11,12 @@ import sys
11
11
  import unicodedata
12
12
  from io import StringIO
13
13
  from os import path
14
- from typing import Any, Iterator
14
+ from typing import TYPE_CHECKING, Any
15
15
 
16
16
  from sphinx.deprecation import _deprecation_warning
17
17
 
18
- try:
19
- # for ALT Linux (#6712)
20
- from sphinx.testing.path import path as Path
21
- except ImportError:
22
- Path = None # type: ignore
23
-
18
+ if TYPE_CHECKING:
19
+ from collections.abc import Iterator
24
20
 
25
21
  # SEP separates path elements in the canonical file names
26
22
  #
@@ -30,16 +26,16 @@ except ImportError:
30
26
  SEP = "/"
31
27
 
32
28
 
33
- def os_path(canonicalpath: str) -> str:
34
- return canonicalpath.replace(SEP, path.sep)
29
+ def os_path(canonical_path: str, /) -> str:
30
+ return canonical_path.replace(SEP, path.sep)
35
31
 
36
32
 
37
- def canon_path(nativepath: str) -> str:
33
+ def canon_path(native_path: str | os.PathLike[str], /) -> str:
38
34
  """Return path in OS-independent form"""
39
- return nativepath.replace(path.sep, SEP)
35
+ return os.fspath(native_path).replace(path.sep, SEP)
40
36
 
41
37
 
42
- def path_stabilize(filepath: str) -> str:
38
+ def path_stabilize(filepath: str | os.PathLike[str], /) -> str:
43
39
  "Normalize path separator and unicode string"
44
40
  new_path = canon_path(filepath)
45
41
  return unicodedata.normalize('NFC', new_path)
@@ -68,9 +64,9 @@ def relative_uri(base: str, to: str) -> str:
68
64
  return ('..' + SEP) * (len(b2) - 1) + SEP.join(t2)
69
65
 
70
66
 
71
- def ensuredir(path: str) -> None:
67
+ def ensuredir(file: str | os.PathLike[str]) -> None:
72
68
  """Ensure that a path exists."""
73
- os.makedirs(path, exist_ok=True)
69
+ os.makedirs(file, exist_ok=True)
74
70
 
75
71
 
76
72
  def mtimes_of_files(dirnames: list[str], suffix: str) -> Iterator[float]:
@@ -78,30 +74,26 @@ def mtimes_of_files(dirnames: list[str], suffix: str) -> Iterator[float]:
78
74
  for root, _dirs, files in os.walk(dirname):
79
75
  for sfile in files:
80
76
  if sfile.endswith(suffix):
81
- try:
77
+ with contextlib.suppress(OSError):
82
78
  yield path.getmtime(path.join(root, sfile))
83
- except OSError:
84
- pass
85
79
 
86
80
 
87
- def copytimes(source: str, dest: str) -> None:
81
+ def copytimes(source: str | os.PathLike[str], dest: str | os.PathLike[str]) -> None:
88
82
  """Copy a file's modification times."""
89
83
  st = os.stat(source)
90
84
  if hasattr(os, 'utime'):
91
85
  os.utime(dest, (st.st_atime, st.st_mtime))
92
86
 
93
87
 
94
- def copyfile(source: str, dest: str) -> None:
88
+ def copyfile(source: str | os.PathLike[str], dest: str | os.PathLike[str]) -> None:
95
89
  """Copy a file and its modification times, if possible.
96
90
 
97
91
  Note: ``copyfile`` skips copying if the file has not been changed"""
98
92
  if not path.exists(dest) or not filecmp.cmp(source, dest):
99
93
  shutil.copyfile(source, dest)
100
- try:
94
+ with contextlib.suppress(OSError):
101
95
  # don't do full copystat because the source may be read-only
102
96
  copytimes(source, dest)
103
- except OSError:
104
- pass
105
97
 
106
98
 
107
99
  no_fn_re = re.compile(r'[^a-zA-Z0-9_-]')
@@ -116,7 +108,8 @@ def make_filename_from_project(project: str) -> str:
116
108
  return make_filename(project_suffix_re.sub('', project)).lower()
117
109
 
118
110
 
119
- def relpath(path: str, start: str | None = os.curdir) -> str:
111
+ def relpath(path: str | os.PathLike[str],
112
+ start: str | os.PathLike[str] | None = os.curdir) -> str:
120
113
  """Return a relative filepath to *path* either from the current directory or
121
114
  from an optional *start* directory.
122
115
 
@@ -126,26 +119,14 @@ def relpath(path: str, start: str | None = os.curdir) -> str:
126
119
  try:
127
120
  return os.path.relpath(path, start)
128
121
  except ValueError:
129
- return path
122
+ return str(path)
130
123
 
131
124
 
132
125
  safe_relpath = relpath # for compatibility
133
126
  fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
134
127
 
135
128
 
136
- def abspath(pathdir: str) -> str:
137
- if Path is not None and isinstance(pathdir, Path):
138
- return pathdir.abspath()
139
- else:
140
- pathdir = path.abspath(pathdir)
141
- if isinstance(pathdir, bytes):
142
- try:
143
- pathdir = pathdir.decode(fs_encoding)
144
- except UnicodeDecodeError as exc:
145
- raise UnicodeDecodeError('multibyte filename not supported on '
146
- 'this filesystem encoding '
147
- '(%r)' % fs_encoding) from exc
148
- return pathdir
129
+ abspath = path.abspath
149
130
 
150
131
 
151
132
  class _chdir:
@@ -194,7 +175,8 @@ class FileAvoidWrite:
194
175
  def close(self) -> None:
195
176
  """Stop accepting writes and write file, if needed."""
196
177
  if not self._io:
197
- raise Exception('FileAvoidWrite does not support empty files.')
178
+ msg = 'FileAvoidWrite does not support empty files.'
179
+ raise Exception(msg)
198
180
 
199
181
  buf = self.getvalue()
200
182
  self._io.close()
@@ -222,8 +204,8 @@ class FileAvoidWrite:
222
204
  def __getattr__(self, name: str) -> Any:
223
205
  # Proxy to _io instance.
224
206
  if not self._io:
225
- raise Exception('Must write to FileAvoidWrite before other '
226
- 'methods can be used')
207
+ msg = 'Must write to FileAvoidWrite before other methods can be used'
208
+ raise Exception(msg)
227
209
 
228
210
  return getattr(self._io, name)
229
211
 
sphinx/util/parallel.py CHANGED
@@ -6,7 +6,7 @@ import os
6
6
  import time
7
7
  import traceback
8
8
  from math import sqrt
9
- from typing import Any, Callable, Sequence
9
+ from typing import TYPE_CHECKING, Any, Callable
10
10
 
11
11
  try:
12
12
  import multiprocessing
@@ -17,6 +17,9 @@ except ImportError:
17
17
  from sphinx.errors import SphinxParallelError
18
18
  from sphinx.util import logging
19
19
 
20
+ if TYPE_CHECKING:
21
+ from collections.abc import Sequence
22
+
20
23
  logger = logging.getLogger(__name__)
21
24
 
22
25
  # our parallel functionality only works for the forking Process
sphinx/util/rst.py CHANGED
@@ -5,19 +5,23 @@ from __future__ import annotations
5
5
  import re
6
6
  from collections import defaultdict
7
7
  from contextlib import contextmanager
8
- from typing import Generator
8
+ from typing import TYPE_CHECKING
9
9
  from unicodedata import east_asian_width
10
10
 
11
11
  from docutils.parsers.rst import roles
12
12
  from docutils.parsers.rst.languages import en as english
13
13
  from docutils.parsers.rst.states import Body
14
- from docutils.statemachine import StringList
15
14
  from docutils.utils import Reporter
16
15
  from jinja2 import Environment, pass_environment
17
16
 
18
17
  from sphinx.locale import __
19
18
  from sphinx.util import docutils, logging
20
19
 
20
+ if TYPE_CHECKING:
21
+ from collections.abc import Generator
22
+
23
+ from docutils.statemachine import StringList
24
+
21
25
  logger = logging.getLogger(__name__)
22
26
 
23
27
  FIELD_NAME_RE = re.compile(Body.patterns['field_marker'])
@@ -61,7 +65,7 @@ def default_role(docname: str, name: str) -> Generator[None, None, None]:
61
65
  dummy_reporter = Reporter('', 4, 4)
62
66
  role_fn, _ = roles.role(name, english, 0, dummy_reporter)
63
67
  if role_fn: # type: ignore[truthy-function]
64
- docutils.register_role('', role_fn)
68
+ docutils.register_role('', role_fn) # type: ignore[arg-type]
65
69
  else:
66
70
  logger.warning(__('default role %s not found'), name, location=docname)
67
71
 
sphinx/util/tags.py CHANGED
@@ -1,12 +1,17 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Iterator
3
+ from typing import TYPE_CHECKING
4
4
 
5
5
  from jinja2 import nodes
6
6
  from jinja2.environment import Environment
7
- from jinja2.nodes import Node
8
7
  from jinja2.parser import Parser
9
8
 
9
+ if TYPE_CHECKING:
10
+ from collections.abc import Iterator
11
+
12
+ from jinja2.nodes import Node
13
+
14
+
10
15
  env = Environment()
11
16
 
12
17
 
@@ -59,7 +64,8 @@ class Tags:
59
64
  parser = BooleanParser(env, condition, state='variable')
60
65
  expr = parser.parse_expression()
61
66
  if not parser.stream.eos:
62
- raise ValueError('chunk after expression')
67
+ msg = 'chunk after expression'
68
+ raise ValueError(msg)
63
69
 
64
70
  def eval_node(node: Node) -> bool:
65
71
  if isinstance(node, nodes.CondExpr):
@@ -76,6 +82,7 @@ class Tags:
76
82
  elif isinstance(node, nodes.Name):
77
83
  return self.tags.get(node.name, False)
78
84
  else:
79
- raise ValueError('invalid node, check parsing')
85
+ msg = 'invalid node, check parsing'
86
+ raise ValueError(msg)
80
87
 
81
88
  return eval_node(expr)
sphinx/util/template.py CHANGED
@@ -5,10 +5,9 @@ from __future__ import annotations
5
5
  import os
6
6
  from functools import partial
7
7
  from os import path
8
- from typing import Any, Callable
8
+ from typing import TYPE_CHECKING, Any, Callable
9
9
 
10
10
  from jinja2 import TemplateNotFound
11
- from jinja2.environment import Environment
12
11
  from jinja2.loaders import BaseLoader
13
12
  from jinja2.sandbox import SandboxedEnvironment
14
13
 
@@ -17,6 +16,11 @@ from sphinx.jinja2glue import SphinxFileSystemLoader
17
16
  from sphinx.locale import get_translator
18
17
  from sphinx.util import rst, texescape
19
18
 
19
+ if TYPE_CHECKING:
20
+ from collections.abc import Sequence
21
+
22
+ from jinja2.environment import Environment
23
+
20
24
 
21
25
  class BaseRenderer:
22
26
  def __init__(self, loader: BaseLoader | None = None) -> None:
@@ -32,8 +36,8 @@ class BaseRenderer:
32
36
 
33
37
 
34
38
  class FileRenderer(BaseRenderer):
35
- def __init__(self, search_path: str | list[str]) -> None:
36
- if isinstance(search_path, str):
39
+ def __init__(self, search_path: Sequence[str | os.PathLike[str]]) -> None:
40
+ if isinstance(search_path, (str, os.PathLike)):
37
41
  search_path = [search_path]
38
42
  else:
39
43
  # filter "None" paths
@@ -50,7 +54,7 @@ class FileRenderer(BaseRenderer):
50
54
 
51
55
 
52
56
  class SphinxRenderer(FileRenderer):
53
- def __init__(self, template_path: None | str | list[str] = None) -> None:
57
+ def __init__(self, template_path: Sequence[str | os.PathLike[str]] | None = None) -> None:
54
58
  if template_path is None:
55
59
  template_path = os.path.join(package_dir, 'templates')
56
60
  super().__init__(template_path)
@@ -61,11 +65,10 @@ class SphinxRenderer(FileRenderer):
61
65
 
62
66
 
63
67
  class LaTeXRenderer(SphinxRenderer):
64
- def __init__(
65
- self, template_path: str | None = None, latex_engine: str | None = None,
66
- ) -> None:
68
+ def __init__(self, template_path: Sequence[str | os.PathLike[str]] | None = None,
69
+ latex_engine: str | None = None) -> None:
67
70
  if template_path is None:
68
- template_path = os.path.join(package_dir, 'templates', 'latex')
71
+ template_path = [os.path.join(package_dir, 'templates', 'latex')]
69
72
  super().__init__(template_path)
70
73
 
71
74
  # use texescape as escape filter
@@ -85,9 +88,8 @@ class LaTeXRenderer(SphinxRenderer):
85
88
 
86
89
 
87
90
  class ReSTRenderer(SphinxRenderer):
88
- def __init__(
89
- self, template_path: None | str | list[str] = None, language: str | None = None,
90
- ) -> None:
91
+ def __init__(self, template_path: Sequence[str | os.PathLike[str]] | None = None,
92
+ language: str | None = None) -> None:
91
93
  super().__init__(template_path)
92
94
 
93
95
  # add language to environment
@@ -102,8 +104,9 @@ class ReSTRenderer(SphinxRenderer):
102
104
  class SphinxTemplateLoader(BaseLoader):
103
105
  """A loader supporting template inheritance"""
104
106
 
105
- def __init__(self, confdir: str, templates_paths: list[str],
106
- system_templates_paths: list[str]) -> None:
107
+ def __init__(self, confdir: str | os.PathLike[str],
108
+ templates_paths: Sequence[str | os.PathLike[str]],
109
+ system_templates_paths: Sequence[str | os.PathLike[str]]) -> None:
107
110
  self.loaders = []
108
111
  self.sysloaders = []
109
112