Sphinx 7.1.2__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 +18 -11
  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.2.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.2.dist-info/RECORD +0 -564
  311. {sphinx-7.1.2.dist-info → sphinx-7.2.0.dist-info}/LICENSE +0 -0
  312. {sphinx-7.1.2.dist-info → sphinx-7.2.0.dist-info}/WHEEL +0 -0
  313. {sphinx-7.1.2.dist-info → sphinx-7.2.0.dist-info}/entry_points.txt +0 -0
sphinx/ext/doctest.py CHANGED
@@ -11,10 +11,9 @@ import sys
11
11
  import time
12
12
  from io import StringIO
13
13
  from os import path
14
- from typing import TYPE_CHECKING, Any, Callable, Iterable, Sequence
14
+ from typing import TYPE_CHECKING, Any, Callable
15
15
 
16
16
  from docutils import nodes
17
- from docutils.nodes import Element, Node, TextElement
18
17
  from docutils.parsers.rst import directives
19
18
  from packaging.specifiers import InvalidSpecifier, SpecifierSet
20
19
  from packaging.version import Version
@@ -23,13 +22,17 @@ import sphinx
23
22
  from sphinx.builders import Builder
24
23
  from sphinx.locale import __
25
24
  from sphinx.util import logging
26
- from sphinx.util.console import bold # type: ignore
25
+ from sphinx.util.console import bold # type: ignore[attr-defined]
27
26
  from sphinx.util.docutils import SphinxDirective
28
27
  from sphinx.util.osutil import relpath
29
- from sphinx.util.typing import OptionSpec
30
28
 
31
29
  if TYPE_CHECKING:
30
+ from collections.abc import Iterable, Sequence
31
+
32
+ from docutils.nodes import Element, Node, TextElement
33
+
32
34
  from sphinx.application import Sphinx
35
+ from sphinx.util.typing import OptionSpec
33
36
 
34
37
 
35
38
  logger = logging.getLogger(__name__)
@@ -236,7 +239,7 @@ class TestCode:
236
239
 
237
240
 
238
241
  class SphinxDocTestRunner(doctest.DocTestRunner):
239
- def summarize(self, out: Callable, verbose: bool | None = None, # type: ignore
242
+ def summarize(self, out: Callable, verbose: bool | None = None, # type: ignore[override]
240
243
  ) -> tuple[int, int]:
241
244
  string_io = StringIO()
242
245
  old_stdout = sys.stdout
@@ -251,7 +254,8 @@ class SphinxDocTestRunner(doctest.DocTestRunner):
251
254
  def _DocTestRunner__patched_linecache_getlines(self, filename: str,
252
255
  module_globals: Any = None) -> Any:
253
256
  # this is overridden from DocTestRunner adding the try-except below
254
- m = self._DocTestRunner__LINECACHE_FILENAME_RE.match(filename) # type: ignore
257
+ m = self._DocTestRunner__LINECACHE_FILENAME_RE.match( # type: ignore[attr-defined]
258
+ filename)
255
259
  if m and m.group('name') == self.test.name:
256
260
  try:
257
261
  example = self.test.examples[int(m.group('examplenum'))]
@@ -262,7 +266,8 @@ class SphinxDocTestRunner(doctest.DocTestRunner):
262
266
  pass
263
267
  else:
264
268
  return example.source.splitlines(True)
265
- return self.save_linecache_getlines(filename, module_globals) # type: ignore
269
+ return self.save_linecache_getlines( # type: ignore[attr-defined]
270
+ filename, module_globals)
266
271
 
267
272
 
268
273
  # the new builder -- use sphinx-build.py -b doctest to run
@@ -284,7 +289,7 @@ class DocTestBuilder(Builder):
284
289
  # for doctest examples but unusable for multi-statement code such
285
290
  # as setup code -- to be able to use doctest error reporting with
286
291
  # that code nevertheless, we monkey-patch the "compile" it uses.
287
- doctest.compile = self.compile # type: ignore
292
+ doctest.compile = self.compile # type: ignore[attr-defined]
288
293
 
289
294
  sys.path[0:0] = self.config.doctest_path
290
295
 
@@ -299,7 +304,8 @@ class DocTestBuilder(Builder):
299
304
 
300
305
  date = time.strftime('%Y-%m-%d %H:%M:%S')
301
306
 
302
- self.outfile = open(path.join(self.outdir, 'output.txt'), 'w', encoding='utf-8')
307
+ outpath = self.outdir.joinpath('output.txt')
308
+ self.outfile = outpath.open('w', encoding='utf-8') # NoQA: SIM115
303
309
  self.outfile.write(('Results of doctest builder run on %s\n'
304
310
  '==================================%s\n') %
305
311
  (date, '=' * len(date)))
@@ -402,8 +408,8 @@ Doctest summary
402
408
  self.cleanup_runner = SphinxDocTestRunner(verbose=False,
403
409
  optionflags=self.opt)
404
410
 
405
- self.test_runner._fakeout = self.setup_runner._fakeout # type: ignore
406
- self.cleanup_runner._fakeout = self.setup_runner._fakeout # type: ignore
411
+ self.test_runner._fakeout = self.setup_runner._fakeout # type: ignore[attr-defined]
412
+ self.cleanup_runner._fakeout = self.setup_runner._fakeout # type: ignore[attr-defined]
407
413
 
408
414
  if self.config.doctest_test_doctest_blocks:
409
415
  def condition(node: Node) -> bool:
@@ -452,8 +458,11 @@ Doctest summary
452
458
  if not groups:
453
459
  return
454
460
 
455
- self._out('\nDocument: %s\n----------%s\n' %
456
- (docname, '-' * len(docname)))
461
+ show_successes = self.config.doctest_show_successes
462
+ if show_successes:
463
+ self._out('\n'
464
+ f'Document: {docname}\n'
465
+ f'----------{"-" * len(docname)}\n')
457
466
  for group in groups.values():
458
467
  self.test_group(group)
459
468
  # Separately count results from setup code
@@ -461,12 +470,13 @@ Doctest summary
461
470
  self.setup_failures += res_f
462
471
  self.setup_tries += res_t
463
472
  if self.test_runner.tries:
464
- res_f, res_t = self.test_runner.summarize(self._out, verbose=True)
473
+ res_f, res_t = self.test_runner.summarize(
474
+ self._out, verbose=show_successes)
465
475
  self.total_failures += res_f
466
476
  self.total_tries += res_t
467
477
  if self.cleanup_runner.tries:
468
- res_f, res_t = self.cleanup_runner.summarize(self._out,
469
- verbose=True)
478
+ res_f, res_t = self.cleanup_runner.summarize(
479
+ self._out, verbose=show_successes)
470
480
  self.cleanup_failures += res_f
471
481
  self.cleanup_tries += res_t
472
482
 
@@ -526,7 +536,7 @@ Doctest summary
526
536
  # disable <BLANKLINE> processing as it is not needed
527
537
  options[doctest.DONT_ACCEPT_BLANKLINE] = True
528
538
  # find out if we're testing an exception
529
- m = parser._EXCEPTION_RE.match(output) # type: ignore
539
+ m = parser._EXCEPTION_RE.match(output) # type: ignore[attr-defined]
530
540
  if m:
531
541
  exc_msg = m.group('msg')
532
542
  else:
@@ -553,6 +563,7 @@ def setup(app: Sphinx) -> dict[str, Any]:
553
563
  app.add_directive('testoutput', TestoutputDirective)
554
564
  app.add_builder(DocTestBuilder)
555
565
  # this config value adds to sys.path
566
+ app.add_config_value('doctest_show_successes', True, False, (bool,))
556
567
  app.add_config_value('doctest_path', [], False)
557
568
  app.add_config_value('doctest_test_doctest_blocks', 'default', False)
558
569
  app.add_config_value('doctest_global_setup', '', False)
sphinx/ext/duration.py CHANGED
@@ -1,20 +1,22 @@
1
- """Measure durations of Sphinx processing."""
1
+ """Measure document reading durations."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from datetime import datetime, timedelta
5
+ import time
6
6
  from itertools import islice
7
7
  from operator import itemgetter
8
- from typing import Any, cast
9
-
10
- from docutils import nodes
8
+ from typing import TYPE_CHECKING, cast
11
9
 
12
10
  import sphinx
13
- from sphinx.application import Sphinx
14
11
  from sphinx.domains import Domain
15
12
  from sphinx.locale import __
16
13
  from sphinx.util import logging
17
14
 
15
+ if TYPE_CHECKING:
16
+ from docutils import nodes
17
+
18
+ from sphinx.application import Sphinx
19
+
18
20
  logger = logging.getLogger(__name__)
19
21
 
20
22
 
@@ -23,10 +25,10 @@ class DurationDomain(Domain):
23
25
  name = 'duration'
24
26
 
25
27
  @property
26
- def reading_durations(self) -> dict[str, timedelta]:
28
+ def reading_durations(self) -> dict[str, float]:
27
29
  return self.data.setdefault('reading_durations', {})
28
30
 
29
- def note_reading_duration(self, duration: timedelta) -> None:
31
+ def note_reading_duration(self, duration: float) -> None:
30
32
  self.reading_durations[self.env.docname] = duration
31
33
 
32
34
  def clear(self) -> None:
@@ -35,7 +37,7 @@ class DurationDomain(Domain):
35
37
  def clear_doc(self, docname: str) -> None:
36
38
  self.reading_durations.pop(docname, None)
37
39
 
38
- def merge_domaindata(self, docnames: list[str], otherdata: dict[str, timedelta]) -> None:
40
+ def merge_domaindata(self, docnames: list[str], otherdata: dict[str, float]) -> None:
39
41
  for docname, duration in otherdata.items():
40
42
  if docname in docnames:
41
43
  self.reading_durations[docname] = duration
@@ -44,7 +46,7 @@ class DurationDomain(Domain):
44
46
  def on_builder_inited(app: Sphinx) -> None:
45
47
  """Initialize DurationDomain on bootstrap.
46
48
 
47
- This clears results of last build.
49
+ This clears the results of the last build.
48
50
  """
49
51
  domain = cast(DurationDomain, app.env.get_domain('duration'))
50
52
  domain.clear()
@@ -52,31 +54,31 @@ def on_builder_inited(app: Sphinx) -> None:
52
54
 
53
55
  def on_source_read(app: Sphinx, docname: str, content: list[str]) -> None:
54
56
  """Start to measure reading duration."""
55
- app.env.temp_data['started_at'] = datetime.now()
57
+ app.env.temp_data['started_at'] = time.monotonic()
56
58
 
57
59
 
58
60
  def on_doctree_read(app: Sphinx, doctree: nodes.document) -> None:
59
61
  """Record a reading duration."""
60
62
  started_at = app.env.temp_data['started_at']
61
- duration = datetime.now() - started_at
63
+ duration = time.monotonic() - started_at
62
64
  domain = cast(DurationDomain, app.env.get_domain('duration'))
63
65
  domain.note_reading_duration(duration)
64
66
 
65
67
 
66
68
  def on_build_finished(app: Sphinx, error: Exception) -> None:
67
- """Display duration ranking on current build."""
69
+ """Display duration ranking on the current build."""
68
70
  domain = cast(DurationDomain, app.env.get_domain('duration'))
69
- durations = sorted(domain.reading_durations.items(), key=itemgetter(1), reverse=True)
70
- if not durations:
71
+ if not domain.reading_durations:
71
72
  return
73
+ durations = sorted(domain.reading_durations.items(), key=itemgetter(1), reverse=True)
72
74
 
73
75
  logger.info('')
74
76
  logger.info(__('====================== slowest reading durations ======================='))
75
77
  for docname, d in islice(durations, 5):
76
- logger.info('%d.%03d %s', d.seconds, d.microseconds / 1000, docname)
78
+ logger.info(f'{d:.3f} {docname}') # NoQA: G004
77
79
 
78
80
 
79
- def setup(app: Sphinx) -> dict[str, Any]:
81
+ def setup(app: Sphinx) -> dict[str, bool | str]:
80
82
  app.add_domain(DurationDomain)
81
83
  app.connect('builder-inited', on_builder_inited)
82
84
  app.connect('source-read', on_source_read)
sphinx/ext/extlinks.py CHANGED
@@ -20,19 +20,24 @@ Both, the url string and the caption string must escape ``%`` as ``%%``.
20
20
  from __future__ import annotations
21
21
 
22
22
  import re
23
- from typing import Any
23
+ from typing import TYPE_CHECKING, Any
24
24
 
25
25
  from docutils import nodes, utils
26
- from docutils.nodes import Node, system_message
27
- from docutils.parsers.rst.states import Inliner
28
26
 
29
27
  import sphinx
30
- from sphinx.application import Sphinx
31
28
  from sphinx.locale import __
32
29
  from sphinx.transforms.post_transforms import SphinxPostTransform
33
30
  from sphinx.util import logging, rst
34
31
  from sphinx.util.nodes import split_explicit_title
35
- from sphinx.util.typing import RoleFunction
32
+
33
+ if TYPE_CHECKING:
34
+ from collections.abc import Sequence
35
+
36
+ from docutils.nodes import Node, system_message
37
+ from docutils.parsers.rst.states import Inliner
38
+
39
+ from sphinx.application import Sphinx
40
+ from sphinx.util.typing import RoleFunction
36
41
 
37
42
  logger = logging.getLogger(__name__)
38
43
 
@@ -91,7 +96,7 @@ def make_link_role(name: str, base_url: str, caption: str) -> RoleFunction:
91
96
  # Remark: It is an implementation detail that we use Pythons %-formatting.
92
97
  # So far we only expose ``%s`` and require quoting of ``%`` using ``%%``.
93
98
  def role(typ: str, rawtext: str, text: str, lineno: int,
94
- inliner: Inliner, options: dict = {}, content: list[str] = [],
99
+ inliner: Inliner, options: dict | None = None, content: Sequence[str] = (),
95
100
  ) -> tuple[list[Node], list[system_message]]:
96
101
  text = utils.unescape(text)
97
102
  has_explicit_title, title, part = split_explicit_title(text)
sphinx/ext/githubpages.py CHANGED
@@ -2,13 +2,16 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import contextlib
5
6
  import os
6
7
  import urllib.parse
7
- from typing import Any
8
+ from typing import TYPE_CHECKING, Any
8
9
 
9
10
  import sphinx
10
- from sphinx.application import Sphinx
11
- from sphinx.environment import BuildEnvironment
11
+
12
+ if TYPE_CHECKING:
13
+ from sphinx.application import Sphinx
14
+ from sphinx.environment import BuildEnvironment
12
15
 
13
16
 
14
17
  def _get_domain_from_url(url: str) -> str:
@@ -34,7 +37,7 @@ def create_nojekyll_and_cname(app: Sphinx, env: BuildEnvironment) -> None:
34
37
  if app.builder.format != 'html':
35
38
  return
36
39
 
37
- open(os.path.join(app.builder.outdir, '.nojekyll'), 'wb').close()
40
+ app.builder.outdir.joinpath('.nojekyll').touch()
38
41
  cname_path = os.path.join(app.builder.outdir, 'CNAME')
39
42
 
40
43
  domain = _get_domain_from_url(app.config.html_baseurl)
@@ -45,10 +48,8 @@ def create_nojekyll_and_cname(app: Sphinx, env: BuildEnvironment) -> None:
45
48
  # auto-generated by the GitHub UI doesn't have one.
46
49
  f.write(domain)
47
50
  else:
48
- try:
51
+ with contextlib.suppress(FileNotFoundError):
49
52
  os.unlink(cname_path)
50
- except FileNotFoundError:
51
- pass
52
53
 
53
54
 
54
55
  def setup(app: Sphinx) -> dict[str, Any]:
sphinx/ext/graphviz.py CHANGED
@@ -6,32 +6,37 @@ from __future__ import annotations
6
6
  import posixpath
7
7
  import re
8
8
  import subprocess
9
+ import xml.etree.ElementTree as ET
10
+ from hashlib import sha1
11
+ from itertools import chain
9
12
  from os import path
10
13
  from subprocess import CalledProcessError
11
14
  from typing import TYPE_CHECKING, Any
15
+ from urllib.parse import urlsplit, urlunsplit
12
16
 
13
17
  from docutils import nodes
14
- from docutils.nodes import Node
15
18
  from docutils.parsers.rst import Directive, directives
16
19
 
17
20
  import sphinx
18
- from sphinx.application import Sphinx
19
21
  from sphinx.errors import SphinxError
20
22
  from sphinx.locale import _, __
21
- from sphinx.util import logging, sha1
23
+ from sphinx.util import logging
22
24
  from sphinx.util.docutils import SphinxDirective, SphinxTranslator
23
25
  from sphinx.util.i18n import search_image_for_language
24
26
  from sphinx.util.nodes import set_source_info
25
27
  from sphinx.util.osutil import ensuredir
26
- from sphinx.util.typing import OptionSpec
27
- from sphinx.writers.html import HTML5Translator
28
- from sphinx.writers.latex import LaTeXTranslator
29
- from sphinx.writers.manpage import ManualPageTranslator
30
- from sphinx.writers.texinfo import TexinfoTranslator
31
- from sphinx.writers.text import TextTranslator
32
28
 
33
29
  if TYPE_CHECKING:
30
+ from docutils.nodes import Node
31
+
32
+ from sphinx.application import Sphinx
34
33
  from sphinx.config import Config
34
+ from sphinx.util.typing import OptionSpec
35
+ from sphinx.writers.html import HTML5Translator
36
+ from sphinx.writers.latex import LaTeXTranslator
37
+ from sphinx.writers.manpage import ManualPageTranslator
38
+ from sphinx.writers.texinfo import TexinfoTranslator
39
+ from sphinx.writers.text import TextTranslator
35
40
 
36
41
  logger = logging.getLogger(__name__)
37
42
 
@@ -62,7 +67,7 @@ class ClickableMapDefinition:
62
67
  if self.id == '%3':
63
68
  # graphviz generates wrong ID if graph name not specified
64
69
  # https://gitlab.com/graphviz/graphviz/issues/1327
65
- hashed = sha1(dot.encode()).hexdigest()
70
+ hashed = sha1(dot.encode(), usedforsecurity=False).hexdigest()
66
71
  self.id = 'grapviz%s' % hashed[-10:]
67
72
  self.content[0] = self.content[0].replace('%3', self.id)
68
73
 
@@ -213,15 +218,50 @@ class GraphvizSimple(SphinxDirective):
213
218
  return [figure]
214
219
 
215
220
 
221
+ def fix_svg_relative_paths(self: SphinxTranslator, filepath: str) -> None:
222
+ """Change relative links in generated svg files to be relative to imgpath."""
223
+ tree = ET.parse(filepath) # NoQA: S314
224
+ root = tree.getroot()
225
+ ns = {'svg': 'http://www.w3.org/2000/svg', 'xlink': 'http://www.w3.org/1999/xlink'}
226
+ href_name = '{http://www.w3.org/1999/xlink}href'
227
+ modified = False
228
+
229
+ for element in chain(
230
+ root.findall('.//svg:image[@xlink:href]', ns),
231
+ root.findall('.//svg:a[@xlink:href]', ns),
232
+ ):
233
+ scheme, hostname, url, query, fragment = urlsplit(element.attrib[href_name])
234
+ if hostname:
235
+ # not a relative link
236
+ continue
237
+
238
+ old_path = path.join(self.builder.outdir, url)
239
+ new_path = path.relpath(
240
+ old_path,
241
+ start=path.join(self.builder.outdir, self.builder.imgpath),
242
+ )
243
+ modified_url = urlunsplit((scheme, hostname, new_path, query, fragment))
244
+
245
+ element.set(href_name, modified_url)
246
+ modified = True
247
+
248
+ if modified:
249
+ tree.write(filepath)
250
+
251
+
216
252
  def render_dot(self: SphinxTranslator, code: str, options: dict, format: str,
217
253
  prefix: str = 'graphviz', filename: str | None = None,
218
254
  ) -> tuple[str | None, str | None]:
219
255
  """Render graphviz code into a PNG or PDF output file."""
220
256
  graphviz_dot = options.get('graphviz_dot', self.builder.config.graphviz_dot)
257
+ if not graphviz_dot:
258
+ raise GraphvizError(
259
+ __('graphviz_dot executable path must be set! %r') % graphviz_dot,
260
+ )
221
261
  hashkey = (code + str(options) + str(graphviz_dot) +
222
262
  str(self.builder.config.graphviz_dot_args)).encode()
223
263
 
224
- fname = f'{prefix}-{sha1(hashkey).hexdigest()}.{format}'
264
+ fname = f'{prefix}-{sha1(hashkey, usedforsecurity=False).hexdigest()}.{format}'
225
265
  relfn = posixpath.join(self.builder.imgpath, fname)
226
266
  outfn = path.join(self.builder.outdir, self.builder.imagedir, fname)
227
267
 
@@ -250,20 +290,24 @@ def render_dot(self: SphinxTranslator, code: str, options: dict, format: str,
250
290
  try:
251
291
  ret = subprocess.run(dot_args, input=code.encode(), capture_output=True,
252
292
  cwd=cwd, check=True)
253
- if not path.isfile(outfn):
254
- raise GraphvizError(__('dot did not produce an output file:\n[stderr]\n%r\n'
255
- '[stdout]\n%r') % (ret.stderr, ret.stdout))
256
- return relfn, outfn
257
293
  except OSError:
258
294
  logger.warning(__('dot command %r cannot be run (needed for graphviz '
259
295
  'output), check the graphviz_dot setting'), graphviz_dot)
260
296
  if not hasattr(self.builder, '_graphviz_warned_dot'):
261
- self.builder._graphviz_warned_dot = {} # type: ignore
262
- self.builder._graphviz_warned_dot[graphviz_dot] = True # type: ignore
297
+ self.builder._graphviz_warned_dot = {} # type: ignore[attr-defined]
298
+ self.builder._graphviz_warned_dot[graphviz_dot] = True # type: ignore[attr-defined]
263
299
  return None, None
264
300
  except CalledProcessError as exc:
265
301
  raise GraphvizError(__('dot exited with error:\n[stderr]\n%r\n'
266
302
  '[stdout]\n%r') % (exc.stderr, exc.stdout)) from exc
303
+ if not path.isfile(outfn):
304
+ raise GraphvizError(__('dot did not produce an output file:\n[stderr]\n%r\n'
305
+ '[stdout]\n%r') % (ret.stderr, ret.stdout))
306
+
307
+ if format == 'svg':
308
+ fix_svg_relative_paths(self, outfn)
309
+
310
+ return relfn, outfn
267
311
 
268
312
 
269
313
  def render_dot_html(self: HTML5Translator, node: graphviz, code: str, options: dict,
sphinx/ext/ifconfig.py CHANGED
@@ -16,16 +16,19 @@ namespace of the project configuration (that is, all variables from
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- from typing import Any
19
+ from typing import TYPE_CHECKING, Any
20
20
 
21
21
  from docutils import nodes
22
- from docutils.nodes import Node
23
22
 
24
23
  import sphinx
25
- from sphinx.application import Sphinx
26
24
  from sphinx.util.docutils import SphinxDirective
27
25
  from sphinx.util.nodes import nested_parse_with_titles
28
- from sphinx.util.typing import OptionSpec
26
+
27
+ if TYPE_CHECKING:
28
+ from docutils.nodes import Node
29
+
30
+ from sphinx.application import Sphinx
31
+ from sphinx.util.typing import OptionSpec
29
32
 
30
33
 
31
34
  class ifconfig(nodes.Element):
@@ -5,15 +5,17 @@ from __future__ import annotations
5
5
  import subprocess
6
6
  import sys
7
7
  from subprocess import CalledProcessError
8
- from typing import Any
8
+ from typing import TYPE_CHECKING, Any
9
9
 
10
10
  import sphinx
11
- from sphinx.application import Sphinx
12
11
  from sphinx.errors import ExtensionError
13
12
  from sphinx.locale import __
14
13
  from sphinx.transforms.post_transforms.images import ImageConverter
15
14
  from sphinx.util import logging
16
15
 
16
+ if TYPE_CHECKING:
17
+ from sphinx.application import Sphinx
18
+
17
19
  logger = logging.getLogger(__name__)
18
20
 
19
21
 
sphinx/ext/imgmath.py CHANGED
@@ -3,30 +3,37 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import base64
6
+ import contextlib
6
7
  import re
7
8
  import shutil
8
9
  import subprocess
9
10
  import tempfile
11
+ from hashlib import sha1
10
12
  from os import path
11
13
  from subprocess import CalledProcessError
12
- from typing import Any
14
+ from typing import TYPE_CHECKING, Any
13
15
 
14
16
  from docutils import nodes
15
- from docutils.nodes import Element
16
17
 
17
18
  import sphinx
18
19
  from sphinx import package_dir
19
- from sphinx.application import Sphinx
20
- from sphinx.builders import Builder
21
- from sphinx.config import Config
22
20
  from sphinx.errors import SphinxError
23
21
  from sphinx.locale import _, __
24
- from sphinx.util import logging, sha1
22
+ from sphinx.util import logging
25
23
  from sphinx.util.math import get_node_equation_number, wrap_displaymath
26
24
  from sphinx.util.osutil import ensuredir
27
25
  from sphinx.util.png import read_png_depth, write_png_depth
28
26
  from sphinx.util.template import LaTeXRenderer
29
- from sphinx.writers.html import HTML5Translator
27
+
28
+ if TYPE_CHECKING:
29
+ import os
30
+
31
+ from docutils.nodes import Element
32
+
33
+ from sphinx.application import Sphinx
34
+ from sphinx.builders import Builder
35
+ from sphinx.config import Config
36
+ from sphinx.writers.html import HTML5Translator
30
37
 
31
38
  logger = logging.getLogger(__name__)
32
39
 
@@ -82,7 +89,7 @@ def write_svg_depth(filename: str, depth: int) -> None:
82
89
  def generate_latex_macro(image_format: str,
83
90
  math: str,
84
91
  config: Config,
85
- confdir: str = '') -> str:
92
+ confdir: str | os.PathLike[str] = '') -> str:
86
93
  """Generate LaTeX macro."""
87
94
  variables = {
88
95
  'fontsize': config.imgmath_font_size,
@@ -115,9 +122,9 @@ def ensure_tempdir(builder: Builder) -> str:
115
122
  just removing the whole directory (see cleanup_tempdir)
116
123
  """
117
124
  if not hasattr(builder, '_imgmath_tempdir'):
118
- builder._imgmath_tempdir = tempfile.mkdtemp() # type: ignore
125
+ builder._imgmath_tempdir = tempfile.mkdtemp() # type: ignore[attr-defined]
119
126
 
120
- return builder._imgmath_tempdir # type: ignore
127
+ return builder._imgmath_tempdir # type: ignore[attr-defined]
121
128
 
122
129
 
123
130
  def compile_math(latex: str, builder: Builder) -> str:
@@ -152,7 +159,8 @@ def compile_math(latex: str, builder: Builder) -> str:
152
159
  builder.config.imgmath_latex)
153
160
  raise InvokeError from exc
154
161
  except CalledProcessError as exc:
155
- raise MathExtError('latex exited with error', exc.stderr, exc.stdout) from exc
162
+ msg = 'latex exited with error'
163
+ raise MathExtError(msg, exc.stderr, exc.stdout) from exc
156
164
 
157
165
 
158
166
  def convert_dvi_to_image(command: list[str], name: str) -> tuple[str, str]:
@@ -232,14 +240,15 @@ def render_math(
232
240
  """
233
241
  image_format = self.builder.config.imgmath_image_format.lower()
234
242
  if image_format not in SUPPORT_FORMAT:
235
- raise MathExtError('imgmath_image_format must be either "png" or "svg"')
243
+ unsupported_format_msg = 'imgmath_image_format must be either "png" or "svg"'
244
+ raise MathExtError(unsupported_format_msg)
236
245
 
237
246
  latex = generate_latex_macro(image_format,
238
247
  math,
239
248
  self.builder.config,
240
249
  self.builder.confdir)
241
250
 
242
- filename = f"{sha1(latex.encode()).hexdigest()}.{image_format}"
251
+ filename = f"{sha1(latex.encode(), usedforsecurity=False).hexdigest()}.{image_format}"
243
252
  generated_path = path.join(self.builder.outdir, self.builder.imagedir, 'math', filename)
244
253
  ensuredir(path.dirname(generated_path))
245
254
  if path.isfile(generated_path):
@@ -258,7 +267,7 @@ def render_math(
258
267
  try:
259
268
  dvipath = compile_math(latex, self.builder)
260
269
  except InvokeError:
261
- self.builder._imgmath_warned_latex = True # type: ignore
270
+ self.builder._imgmath_warned_latex = True # type: ignore[attr-defined]
262
271
  return None, None
263
272
 
264
273
  # .dvi -> .png/.svg
@@ -268,7 +277,7 @@ def render_math(
268
277
  elif image_format == 'svg':
269
278
  depth = convert_dvi_to_svg(dvipath, self.builder, generated_path)
270
279
  except InvokeError:
271
- self.builder._imgmath_warned_image_translator = True # type: ignore
280
+ self.builder._imgmath_warned_image_translator = True # type: ignore[attr-defined]
272
281
  return None, None
273
282
 
274
283
  return generated_path, depth
@@ -281,7 +290,8 @@ def render_maths_to_base64(image_format: str, generated_path: str) -> str:
281
290
  return f'data:image/png;base64,{encoded}'
282
291
  if image_format == 'svg':
283
292
  return f'data:image/svg+xml;base64,{encoded}'
284
- raise MathExtError('imgmath_image_format must be either "png" or "svg"')
293
+ unsupported_format_msg = 'imgmath_image_format must be either "png" or "svg"'
294
+ raise MathExtError(unsupported_format_msg)
285
295
 
286
296
 
287
297
  def clean_up_files(app: Sphinx, exc: Exception) -> None:
@@ -289,18 +299,14 @@ def clean_up_files(app: Sphinx, exc: Exception) -> None:
289
299
  return
290
300
 
291
301
  if hasattr(app.builder, '_imgmath_tempdir'):
292
- try:
302
+ with contextlib.suppress(Exception):
293
303
  shutil.rmtree(app.builder._imgmath_tempdir)
294
- except Exception:
295
- pass
296
304
 
297
305
  if app.builder.config.imgmath_embed:
298
306
  # in embed mode, the images are still generated in the math output dir
299
307
  # to be shared across workers, but are not useful to the final document
300
- try:
308
+ with contextlib.suppress(Exception):
301
309
  shutil.rmtree(path.join(app.builder.outdir, app.builder.imagedir, 'math'))
302
- except Exception:
303
- pass
304
310
 
305
311
 
306
312
  def get_tooltip(self: HTML5Translator, node: Element) -> str:
@@ -358,7 +364,7 @@ def html_visit_displaymath(self: HTML5Translator, node: nodes.math_block) -> Non
358
364
  if node['number']:
359
365
  number = get_node_equation_number(self, node)
360
366
  self.body.append('<span class="eqno">(%s)' % number)
361
- self.add_permalink_ref(node, _('Permalink to this equation'))
367
+ self.add_permalink_ref(node, _('Link to this equation'))
362
368
  self.body.append('</span>')
363
369
 
364
370
  if rendered_path is None: