Sphinx 7.2.6__py3-none-any.whl → 7.3.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 (388) hide show
  1. sphinx/__init__.py +8 -9
  2. sphinx/addnodes.py +31 -28
  3. sphinx/application.py +9 -15
  4. sphinx/builders/__init__.py +5 -6
  5. sphinx/builders/_epub_base.py +17 -9
  6. sphinx/builders/changes.py +10 -5
  7. sphinx/builders/dirhtml.py +4 -2
  8. sphinx/builders/dummy.py +3 -2
  9. sphinx/builders/epub3.py +5 -3
  10. sphinx/builders/gettext.py +24 -7
  11. sphinx/builders/html/__init__.py +88 -96
  12. sphinx/builders/html/_assets.py +16 -16
  13. sphinx/builders/html/transforms.py +4 -2
  14. sphinx/builders/latex/__init__.py +40 -33
  15. sphinx/builders/latex/nodes.py +6 -2
  16. sphinx/builders/latex/transforms.py +17 -8
  17. sphinx/builders/latex/util.py +1 -1
  18. sphinx/builders/linkcheck.py +86 -27
  19. sphinx/builders/manpage.py +8 -6
  20. sphinx/builders/singlehtml.py +5 -4
  21. sphinx/builders/texinfo.py +18 -14
  22. sphinx/builders/text.py +3 -2
  23. sphinx/builders/xml.py +5 -2
  24. sphinx/cmd/build.py +119 -76
  25. sphinx/cmd/make_mode.py +4 -9
  26. sphinx/cmd/quickstart.py +13 -16
  27. sphinx/config.py +432 -250
  28. sphinx/deprecation.py +23 -13
  29. sphinx/directives/__init__.py +8 -8
  30. sphinx/directives/code.py +7 -7
  31. sphinx/directives/other.py +23 -13
  32. sphinx/directives/patches.py +7 -6
  33. sphinx/domains/__init__.py +2 -2
  34. sphinx/domains/c/__init__.py +796 -0
  35. sphinx/domains/c/_ast.py +1421 -0
  36. sphinx/domains/c/_ids.py +65 -0
  37. sphinx/domains/c/_parser.py +1048 -0
  38. sphinx/domains/c/_symbol.py +700 -0
  39. sphinx/domains/changeset.py +11 -7
  40. sphinx/domains/citation.py +5 -2
  41. sphinx/domains/cpp/__init__.py +1089 -0
  42. sphinx/domains/cpp/_ast.py +3635 -0
  43. sphinx/domains/cpp/_ids.py +537 -0
  44. sphinx/domains/cpp/_parser.py +2117 -0
  45. sphinx/domains/cpp/_symbol.py +1092 -0
  46. sphinx/domains/index.py +6 -4
  47. sphinx/domains/javascript.py +16 -13
  48. sphinx/domains/math.py +9 -4
  49. sphinx/domains/python/__init__.py +890 -0
  50. sphinx/domains/python/_annotations.py +507 -0
  51. sphinx/domains/python/_object.py +426 -0
  52. sphinx/domains/rst.py +12 -7
  53. sphinx/domains/{std.py → std/__init__.py} +19 -16
  54. sphinx/environment/__init__.py +21 -19
  55. sphinx/environment/adapters/indexentries.py +2 -2
  56. sphinx/environment/adapters/toctree.py +10 -9
  57. sphinx/environment/collectors/__init__.py +6 -3
  58. sphinx/environment/collectors/asset.py +4 -3
  59. sphinx/environment/collectors/dependencies.py +3 -2
  60. sphinx/environment/collectors/metadata.py +6 -5
  61. sphinx/environment/collectors/title.py +3 -2
  62. sphinx/environment/collectors/toctree.py +5 -4
  63. sphinx/errors.py +13 -2
  64. sphinx/events.py +14 -9
  65. sphinx/ext/apidoc.py +9 -11
  66. sphinx/ext/autodoc/__init__.py +105 -71
  67. sphinx/ext/autodoc/directive.py +7 -6
  68. sphinx/ext/autodoc/importer.py +102 -36
  69. sphinx/ext/autodoc/mock.py +7 -5
  70. sphinx/ext/autodoc/preserve_defaults.py +4 -3
  71. sphinx/ext/autodoc/type_comment.py +2 -1
  72. sphinx/ext/autodoc/typehints.py +5 -4
  73. sphinx/ext/autosectionlabel.py +3 -2
  74. sphinx/ext/autosummary/__init__.py +21 -17
  75. sphinx/ext/autosummary/generate.py +9 -9
  76. sphinx/ext/coverage.py +26 -20
  77. sphinx/ext/doctest.py +38 -33
  78. sphinx/ext/duration.py +1 -0
  79. sphinx/ext/extlinks.py +4 -3
  80. sphinx/ext/githubpages.py +3 -2
  81. sphinx/ext/graphviz.py +10 -7
  82. sphinx/ext/ifconfig.py +5 -5
  83. sphinx/ext/imgconverter.py +6 -5
  84. sphinx/ext/imgmath.py +9 -8
  85. sphinx/ext/inheritance_diagram.py +31 -31
  86. sphinx/ext/intersphinx.py +140 -23
  87. sphinx/ext/linkcode.py +3 -2
  88. sphinx/ext/mathjax.py +2 -1
  89. sphinx/ext/napoleon/__init__.py +12 -7
  90. sphinx/ext/napoleon/docstring.py +34 -32
  91. sphinx/ext/todo.py +10 -7
  92. sphinx/ext/viewcode.py +12 -11
  93. sphinx/extension.py +18 -8
  94. sphinx/highlighting.py +39 -20
  95. sphinx/io.py +17 -8
  96. sphinx/jinja2glue.py +16 -15
  97. sphinx/locale/__init__.py +30 -23
  98. sphinx/locale/ar/LC_MESSAGES/sphinx.mo +0 -0
  99. sphinx/locale/ar/LC_MESSAGES/sphinx.po +818 -761
  100. sphinx/locale/bg/LC_MESSAGES/sphinx.mo +0 -0
  101. sphinx/locale/bg/LC_MESSAGES/sphinx.po +811 -754
  102. sphinx/locale/bn/LC_MESSAGES/sphinx.mo +0 -0
  103. sphinx/locale/bn/LC_MESSAGES/sphinx.po +835 -778
  104. sphinx/locale/ca/LC_MESSAGES/sphinx.mo +0 -0
  105. sphinx/locale/ca/LC_MESSAGES/sphinx.po +864 -807
  106. sphinx/locale/cak/LC_MESSAGES/sphinx.mo +0 -0
  107. sphinx/locale/cak/LC_MESSAGES/sphinx.po +816 -759
  108. sphinx/locale/cs/LC_MESSAGES/sphinx.mo +0 -0
  109. sphinx/locale/cs/LC_MESSAGES/sphinx.po +837 -780
  110. sphinx/locale/cy/LC_MESSAGES/sphinx.mo +0 -0
  111. sphinx/locale/cy/LC_MESSAGES/sphinx.po +819 -762
  112. sphinx/locale/da/LC_MESSAGES/sphinx.mo +0 -0
  113. sphinx/locale/da/LC_MESSAGES/sphinx.po +838 -781
  114. sphinx/locale/de/LC_MESSAGES/sphinx.mo +0 -0
  115. sphinx/locale/de/LC_MESSAGES/sphinx.po +838 -781
  116. sphinx/locale/de_DE/LC_MESSAGES/sphinx.mo +0 -0
  117. sphinx/locale/de_DE/LC_MESSAGES/sphinx.po +811 -754
  118. sphinx/locale/el/LC_MESSAGES/sphinx.mo +0 -0
  119. sphinx/locale/el/LC_MESSAGES/sphinx.po +853 -796
  120. sphinx/locale/en_DE/LC_MESSAGES/sphinx.mo +0 -0
  121. sphinx/locale/en_DE/LC_MESSAGES/sphinx.po +811 -754
  122. sphinx/locale/en_FR/LC_MESSAGES/sphinx.mo +0 -0
  123. sphinx/locale/en_FR/LC_MESSAGES/sphinx.po +811 -754
  124. sphinx/locale/en_GB/LC_MESSAGES/sphinx.mo +0 -0
  125. sphinx/locale/en_GB/LC_MESSAGES/sphinx.po +856 -799
  126. sphinx/locale/en_HK/LC_MESSAGES/sphinx.mo +0 -0
  127. sphinx/locale/en_HK/LC_MESSAGES/sphinx.po +811 -754
  128. sphinx/locale/eo/LC_MESSAGES/sphinx.mo +0 -0
  129. sphinx/locale/eo/LC_MESSAGES/sphinx.po +820 -763
  130. sphinx/locale/es/LC_MESSAGES/sphinx.mo +0 -0
  131. sphinx/locale/es/LC_MESSAGES/sphinx.po +856 -799
  132. sphinx/locale/es_CO/LC_MESSAGES/sphinx.mo +0 -0
  133. sphinx/locale/es_CO/LC_MESSAGES/sphinx.po +811 -754
  134. sphinx/locale/et/LC_MESSAGES/sphinx.mo +0 -0
  135. sphinx/locale/et/LC_MESSAGES/sphinx.po +845 -788
  136. sphinx/locale/eu/LC_MESSAGES/sphinx.mo +0 -0
  137. sphinx/locale/eu/LC_MESSAGES/sphinx.po +837 -780
  138. sphinx/locale/fa/LC_MESSAGES/sphinx.mo +0 -0
  139. sphinx/locale/fa/LC_MESSAGES/sphinx.po +854 -797
  140. sphinx/locale/fi/LC_MESSAGES/sphinx.mo +0 -0
  141. sphinx/locale/fi/LC_MESSAGES/sphinx.po +816 -759
  142. sphinx/locale/fr/LC_MESSAGES/sphinx.js +1 -1
  143. sphinx/locale/fr/LC_MESSAGES/sphinx.mo +0 -0
  144. sphinx/locale/fr/LC_MESSAGES/sphinx.po +904 -847
  145. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.mo +0 -0
  146. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.po +811 -754
  147. sphinx/locale/gl/LC_MESSAGES/sphinx.js +54 -54
  148. sphinx/locale/gl/LC_MESSAGES/sphinx.mo +0 -0
  149. sphinx/locale/gl/LC_MESSAGES/sphinx.po +1506 -1449
  150. sphinx/locale/he/LC_MESSAGES/sphinx.js +1 -1
  151. sphinx/locale/he/LC_MESSAGES/sphinx.mo +0 -0
  152. sphinx/locale/he/LC_MESSAGES/sphinx.po +823 -766
  153. sphinx/locale/hi/LC_MESSAGES/sphinx.mo +0 -0
  154. sphinx/locale/hi/LC_MESSAGES/sphinx.po +853 -796
  155. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.mo +0 -0
  156. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.po +811 -754
  157. sphinx/locale/hr/LC_MESSAGES/sphinx.mo +0 -0
  158. sphinx/locale/hr/LC_MESSAGES/sphinx.po +844 -787
  159. sphinx/locale/hu/LC_MESSAGES/sphinx.mo +0 -0
  160. sphinx/locale/hu/LC_MESSAGES/sphinx.po +837 -780
  161. sphinx/locale/id/LC_MESSAGES/sphinx.mo +0 -0
  162. sphinx/locale/id/LC_MESSAGES/sphinx.po +854 -797
  163. sphinx/locale/is/LC_MESSAGES/sphinx.mo +0 -0
  164. sphinx/locale/is/LC_MESSAGES/sphinx.po +811 -754
  165. sphinx/locale/it/LC_MESSAGES/sphinx.mo +0 -0
  166. sphinx/locale/it/LC_MESSAGES/sphinx.po +837 -780
  167. sphinx/locale/ja/LC_MESSAGES/sphinx.mo +0 -0
  168. sphinx/locale/ja/LC_MESSAGES/sphinx.po +853 -796
  169. sphinx/locale/ka/LC_MESSAGES/sphinx.mo +0 -0
  170. sphinx/locale/ka/LC_MESSAGES/sphinx.po +848 -791
  171. sphinx/locale/ko/LC_MESSAGES/sphinx.mo +0 -0
  172. sphinx/locale/ko/LC_MESSAGES/sphinx.po +855 -798
  173. sphinx/locale/lt/LC_MESSAGES/sphinx.mo +0 -0
  174. sphinx/locale/lt/LC_MESSAGES/sphinx.po +837 -780
  175. sphinx/locale/lv/LC_MESSAGES/sphinx.mo +0 -0
  176. sphinx/locale/lv/LC_MESSAGES/sphinx.po +837 -780
  177. sphinx/locale/mk/LC_MESSAGES/sphinx.mo +0 -0
  178. sphinx/locale/mk/LC_MESSAGES/sphinx.po +825 -768
  179. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.js +27 -27
  180. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.mo +0 -0
  181. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.po +876 -818
  182. sphinx/locale/ne/LC_MESSAGES/sphinx.mo +0 -0
  183. sphinx/locale/ne/LC_MESSAGES/sphinx.po +837 -780
  184. sphinx/locale/nl/LC_MESSAGES/sphinx.mo +0 -0
  185. sphinx/locale/nl/LC_MESSAGES/sphinx.po +844 -787
  186. sphinx/locale/pl/LC_MESSAGES/sphinx.mo +0 -0
  187. sphinx/locale/pl/LC_MESSAGES/sphinx.po +845 -788
  188. sphinx/locale/pt/LC_MESSAGES/sphinx.mo +0 -0
  189. sphinx/locale/pt/LC_MESSAGES/sphinx.po +811 -754
  190. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.mo +0 -0
  191. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.po +908 -851
  192. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.mo +0 -0
  193. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.po +837 -780
  194. sphinx/locale/ro/LC_MESSAGES/sphinx.mo +0 -0
  195. sphinx/locale/ro/LC_MESSAGES/sphinx.po +837 -780
  196. sphinx/locale/ru/LC_MESSAGES/sphinx.mo +0 -0
  197. sphinx/locale/ru/LC_MESSAGES/sphinx.po +838 -781
  198. sphinx/locale/si/LC_MESSAGES/sphinx.mo +0 -0
  199. sphinx/locale/si/LC_MESSAGES/sphinx.po +823 -766
  200. sphinx/locale/sk/LC_MESSAGES/sphinx.mo +0 -0
  201. sphinx/locale/sk/LC_MESSAGES/sphinx.po +854 -797
  202. sphinx/locale/sl/LC_MESSAGES/sphinx.mo +0 -0
  203. sphinx/locale/sl/LC_MESSAGES/sphinx.po +832 -775
  204. sphinx/locale/sphinx.pot +813 -755
  205. sphinx/locale/sq/LC_MESSAGES/sphinx.js +1 -1
  206. sphinx/locale/sq/LC_MESSAGES/sphinx.mo +0 -0
  207. sphinx/locale/sq/LC_MESSAGES/sphinx.po +865 -808
  208. sphinx/locale/sr/LC_MESSAGES/sphinx.mo +0 -0
  209. sphinx/locale/sr/LC_MESSAGES/sphinx.po +835 -778
  210. sphinx/locale/sr@latin/LC_MESSAGES/sphinx.mo +0 -0
  211. sphinx/locale/sr_RS/LC_MESSAGES/sphinx.mo +0 -0
  212. sphinx/locale/sv/LC_MESSAGES/sphinx.mo +0 -0
  213. sphinx/locale/sv/LC_MESSAGES/sphinx.po +837 -780
  214. sphinx/locale/ta/LC_MESSAGES/sphinx.js +54 -54
  215. sphinx/locale/ta/LC_MESSAGES/sphinx.mo +0 -0
  216. sphinx/locale/ta/LC_MESSAGES/sphinx.po +1530 -1473
  217. sphinx/locale/te/LC_MESSAGES/sphinx.mo +0 -0
  218. sphinx/locale/te/LC_MESSAGES/sphinx.po +811 -754
  219. sphinx/locale/tr/LC_MESSAGES/sphinx.mo +0 -0
  220. sphinx/locale/tr/LC_MESSAGES/sphinx.po +853 -796
  221. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.mo +0 -0
  222. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.po +833 -776
  223. sphinx/locale/ur/LC_MESSAGES/sphinx.mo +0 -0
  224. sphinx/locale/ur/LC_MESSAGES/sphinx.po +811 -754
  225. sphinx/locale/vi/LC_MESSAGES/sphinx.mo +0 -0
  226. sphinx/locale/vi/LC_MESSAGES/sphinx.po +837 -780
  227. sphinx/locale/yue/LC_MESSAGES/sphinx.mo +0 -0
  228. sphinx/locale/yue/LC_MESSAGES/sphinx.po +811 -754
  229. sphinx/locale/zh_CN/LC_MESSAGES/sphinx.mo +0 -0
  230. sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po +855 -798
  231. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.mo +0 -0
  232. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.po +811 -754
  233. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.js +1 -1
  234. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.mo +0 -0
  235. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po +879 -822
  236. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.mo +0 -0
  237. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.po +811 -754
  238. sphinx/parsers.py +7 -5
  239. sphinx/project.py +18 -11
  240. sphinx/pycode/__init__.py +6 -5
  241. sphinx/pycode/ast.py +23 -8
  242. sphinx/pycode/parser.py +6 -5
  243. sphinx/registry.py +12 -6
  244. sphinx/roles.py +103 -57
  245. sphinx/search/__init__.py +17 -18
  246. sphinx/search/da.py +2 -2
  247. sphinx/search/de.py +2 -2
  248. sphinx/search/en.py +1 -1
  249. sphinx/search/es.py +2 -2
  250. sphinx/search/fi.py +2 -2
  251. sphinx/search/fr.py +2 -2
  252. sphinx/search/hu.py +2 -2
  253. sphinx/search/it.py +2 -2
  254. sphinx/search/ja.py +13 -22
  255. sphinx/search/nl.py +2 -2
  256. sphinx/search/no.py +2 -2
  257. sphinx/search/pt.py +2 -2
  258. sphinx/search/ro.py +1 -1
  259. sphinx/search/ru.py +2 -2
  260. sphinx/search/sv.py +2 -2
  261. sphinx/search/tr.py +1 -1
  262. sphinx/search/zh.py +2 -3
  263. sphinx/templates/graphviz/graphviz.css +1 -1
  264. sphinx/testing/fixtures.py +41 -24
  265. sphinx/testing/path.py +1 -1
  266. sphinx/testing/util.py +142 -53
  267. sphinx/texinputs/sphinx.xdy +1 -1
  268. sphinx/texinputs/sphinxlatextables.sty +1 -1
  269. sphinx/texinputs/sphinxpackagesubstitutefont.sty +21 -0
  270. sphinx/themes/agogo/layout.html +4 -4
  271. sphinx/themes/agogo/static/agogo.css_t +1 -1
  272. sphinx/themes/agogo/theme.toml +22 -0
  273. sphinx/themes/basic/defindex.html +1 -1
  274. sphinx/themes/basic/domainindex.html +1 -1
  275. sphinx/themes/basic/genindex-single.html +1 -1
  276. sphinx/themes/basic/genindex-split.html +1 -1
  277. sphinx/themes/basic/genindex.html +1 -1
  278. sphinx/themes/basic/globaltoc.html +1 -1
  279. sphinx/themes/basic/layout.html +1 -1
  280. sphinx/themes/basic/localtoc.html +1 -1
  281. sphinx/themes/basic/page.html +1 -1
  282. sphinx/themes/basic/relations.html +1 -1
  283. sphinx/themes/basic/search.html +5 -20
  284. sphinx/themes/basic/searchbox.html +3 -3
  285. sphinx/themes/basic/searchfield.html +3 -3
  286. sphinx/themes/basic/sourcelink.html +1 -1
  287. sphinx/themes/basic/static/basic.css_t +1 -1
  288. sphinx/themes/basic/static/doctools.js +1 -1
  289. sphinx/themes/basic/static/language_data.js_t +2 -2
  290. sphinx/themes/basic/static/searchtools.js +105 -60
  291. sphinx/themes/basic/theme.toml +23 -0
  292. sphinx/themes/bizstyle/layout.html +1 -6
  293. sphinx/themes/bizstyle/static/bizstyle.css_t +1 -1
  294. sphinx/themes/bizstyle/static/bizstyle.js_t +1 -1
  295. sphinx/themes/bizstyle/static/css3-mediaqueries_src.js +3 -3
  296. sphinx/themes/bizstyle/theme.toml +12 -0
  297. sphinx/themes/classic/layout.html +1 -1
  298. sphinx/themes/classic/static/classic.css_t +1 -1
  299. sphinx/themes/classic/static/sidebar.js_t +1 -1
  300. sphinx/themes/classic/theme.toml +34 -0
  301. sphinx/themes/default/theme.toml +2 -0
  302. sphinx/themes/epub/epub-cover.html +1 -1
  303. sphinx/themes/epub/layout.html +1 -1
  304. sphinx/themes/epub/static/epub.css_t +1 -1
  305. sphinx/themes/epub/theme.toml +10 -0
  306. sphinx/themes/haiku/layout.html +3 -3
  307. sphinx/themes/haiku/static/haiku.css_t +2 -2
  308. sphinx/themes/haiku/theme.toml +16 -0
  309. sphinx/themes/nature/static/nature.css_t +1 -1
  310. sphinx/themes/nature/theme.toml +6 -0
  311. sphinx/themes/nonav/layout.html +1 -1
  312. sphinx/themes/nonav/static/nonav.css_t +1 -1
  313. sphinx/themes/nonav/theme.toml +10 -0
  314. sphinx/themes/pyramid/static/epub.css_t +1 -1
  315. sphinx/themes/pyramid/static/pyramid.css_t +1 -1
  316. sphinx/themes/pyramid/theme.toml +6 -0
  317. sphinx/themes/scrolls/artwork/logo.svg +1 -1
  318. sphinx/themes/scrolls/layout.html +2 -2
  319. sphinx/themes/scrolls/static/scrolls.css_t +1 -1
  320. sphinx/themes/scrolls/theme.toml +15 -0
  321. sphinx/themes/sphinxdoc/static/sphinxdoc.css_t +1 -1
  322. sphinx/themes/sphinxdoc/theme.toml +6 -0
  323. sphinx/themes/traditional/static/traditional.css_t +1 -1
  324. sphinx/themes/traditional/theme.toml +9 -0
  325. sphinx/theming.py +427 -131
  326. sphinx/transforms/__init__.py +21 -24
  327. sphinx/transforms/compact_bullet_list.py +5 -5
  328. sphinx/transforms/i18n.py +30 -28
  329. sphinx/transforms/post_transforms/__init__.py +9 -7
  330. sphinx/transforms/post_transforms/code.py +4 -1
  331. sphinx/transforms/post_transforms/images.py +17 -13
  332. sphinx/transforms/references.py +3 -1
  333. sphinx/util/__init__.py +15 -11
  334. sphinx/util/_io.py +34 -0
  335. sphinx/util/_pathlib.py +23 -18
  336. sphinx/util/build_phase.py +1 -0
  337. sphinx/util/cfamily.py +19 -11
  338. sphinx/util/console.py +101 -21
  339. sphinx/util/display.py +3 -2
  340. sphinx/util/docfields.py +12 -8
  341. sphinx/util/docutils.py +21 -35
  342. sphinx/util/exceptions.py +3 -2
  343. sphinx/util/fileutil.py +5 -5
  344. sphinx/util/http_date.py +9 -2
  345. sphinx/util/i18n.py +40 -9
  346. sphinx/util/inspect.py +317 -245
  347. sphinx/util/inventory.py +22 -5
  348. sphinx/util/logging.py +81 -7
  349. sphinx/util/matching.py +2 -1
  350. sphinx/util/math.py +1 -2
  351. sphinx/util/nodes.py +39 -29
  352. sphinx/util/osutil.py +25 -6
  353. sphinx/util/parallel.py +6 -1
  354. sphinx/util/requests.py +8 -5
  355. sphinx/util/rst.py +8 -6
  356. sphinx/util/tags.py +3 -3
  357. sphinx/util/template.py +8 -3
  358. sphinx/util/typing.py +76 -42
  359. sphinx/versioning.py +6 -2
  360. sphinx/writers/html.py +1 -1
  361. sphinx/writers/html5.py +17 -13
  362. sphinx/writers/latex.py +12 -12
  363. sphinx/writers/manpage.py +13 -7
  364. sphinx/writers/texinfo.py +13 -10
  365. sphinx/writers/text.py +13 -23
  366. sphinx/writers/xml.py +1 -1
  367. sphinx-7.2.6.dist-info/LICENSE → sphinx-7.3.0.dist-info/LICENSE.rst +1 -1
  368. {sphinx-7.2.6.dist-info → sphinx-7.3.0.dist-info}/METADATA +13 -12
  369. sphinx-7.3.0.dist-info/RECORD +581 -0
  370. sphinx/domains/c.py +0 -3906
  371. sphinx/domains/cpp.py +0 -8233
  372. sphinx/domains/python.py +0 -1769
  373. sphinx/themes/agogo/theme.conf +0 -20
  374. sphinx/themes/basic/theme.conf +0 -16
  375. sphinx/themes/bizstyle/theme.conf +0 -10
  376. sphinx/themes/classic/theme.conf +0 -32
  377. sphinx/themes/default/theme.conf +0 -2
  378. sphinx/themes/epub/theme.conf +0 -8
  379. sphinx/themes/haiku/theme.conf +0 -14
  380. sphinx/themes/nature/theme.conf +0 -4
  381. sphinx/themes/nonav/theme.conf +0 -8
  382. sphinx/themes/pyramid/theme.conf +0 -4
  383. sphinx/themes/scrolls/theme.conf +0 -13
  384. sphinx/themes/sphinxdoc/theme.conf +0 -4
  385. sphinx/themes/traditional/theme.conf +0 -7
  386. sphinx-7.2.6.dist-info/RECORD +0 -569
  387. {sphinx-7.2.6.dist-info → sphinx-7.3.0.dist-info}/WHEEL +0 -0
  388. {sphinx-7.2.6.dist-info → sphinx-7.3.0.dist-info}/entry_points.txt +0 -0
sphinx/util/inventory.py CHANGED
@@ -25,7 +25,7 @@ class InventoryFileReader:
25
25
  This reader supports mixture of texts and compressed texts.
26
26
  """
27
27
 
28
- def __init__(self, stream: IO) -> None:
28
+ def __init__(self, stream: IO[bytes]) -> None:
29
29
  self.stream = stream
30
30
  self.buffer = b''
31
31
  self.eof = False
@@ -77,7 +77,12 @@ class InventoryFileReader:
77
77
 
78
78
  class InventoryFile:
79
79
  @classmethod
80
- def load(cls, stream: IO, uri: str, joinfunc: Callable) -> Inventory:
80
+ def load(
81
+ cls: type[InventoryFile],
82
+ stream: IO[bytes],
83
+ uri: str,
84
+ joinfunc: Callable[[str, str], str],
85
+ ) -> Inventory:
81
86
  reader = InventoryFileReader(stream)
82
87
  line = reader.readline().rstrip()
83
88
  if line == '# Sphinx inventory version 1':
@@ -88,7 +93,12 @@ class InventoryFile:
88
93
  raise ValueError('invalid inventory header: %s' % line)
89
94
 
90
95
  @classmethod
91
- def load_v1(cls, stream: InventoryFileReader, uri: str, join: Callable) -> Inventory:
96
+ def load_v1(
97
+ cls: type[InventoryFile],
98
+ stream: InventoryFileReader,
99
+ uri: str,
100
+ join: Callable[[str, str], str],
101
+ ) -> Inventory:
92
102
  invdata: Inventory = {}
93
103
  projname = stream.readline().rstrip()[11:]
94
104
  version = stream.readline().rstrip()[11:]
@@ -106,7 +116,12 @@ class InventoryFile:
106
116
  return invdata
107
117
 
108
118
  @classmethod
109
- def load_v2(cls, stream: InventoryFileReader, uri: str, join: Callable) -> Inventory:
119
+ def load_v2(
120
+ cls: type[InventoryFile],
121
+ stream: InventoryFileReader,
122
+ uri: str,
123
+ join: Callable[[str, str], str],
124
+ ) -> Inventory:
110
125
  invdata: Inventory = {}
111
126
  projname = stream.readline().rstrip()[11:]
112
127
  version = stream.readline().rstrip()[11:]
@@ -140,7 +155,9 @@ class InventoryFile:
140
155
  return invdata
141
156
 
142
157
  @classmethod
143
- def dump(cls, filename: str, env: BuildEnvironment, builder: Builder) -> None:
158
+ def dump(
159
+ cls: type[InventoryFile], filename: str, env: BuildEnvironment, builder: Builder,
160
+ ) -> None:
144
161
  def escape(string: str) -> str:
145
162
  return re.sub("\\s+", " ", string)
146
163
 
sphinx/util/logging.py CHANGED
@@ -16,7 +16,7 @@ from sphinx.util.console import colorize
16
16
  from sphinx.util.osutil import abspath
17
17
 
18
18
  if TYPE_CHECKING:
19
- from collections.abc import Generator
19
+ from collections.abc import Iterator
20
20
 
21
21
  from docutils.nodes import Node
22
22
 
@@ -85,6 +85,7 @@ def convert_serializable(records: list[logging.LogRecord]) -> None:
85
85
 
86
86
  class SphinxLogRecord(logging.LogRecord):
87
87
  """Log record class supporting location"""
88
+
88
89
  prefix = ''
89
90
  location: Any = None
90
91
 
@@ -101,11 +102,13 @@ class SphinxLogRecord(logging.LogRecord):
101
102
 
102
103
  class SphinxInfoLogRecord(SphinxLogRecord):
103
104
  """Info log record class supporting location"""
105
+
104
106
  prefix = '' # do not show any prefix for INFO messages
105
107
 
106
108
 
107
109
  class SphinxWarningLogRecord(SphinxLogRecord):
108
110
  """Warning log record class supporting location"""
111
+
109
112
  @property
110
113
  def prefix(self) -> str: # type: ignore[override]
111
114
  if self.levelno >= logging.CRITICAL:
@@ -118,6 +121,7 @@ class SphinxWarningLogRecord(SphinxLogRecord):
118
121
 
119
122
  class SphinxLoggerAdapter(logging.LoggerAdapter):
120
123
  """LoggerAdapter allowing ``type`` and ``subtype`` keywords."""
124
+
121
125
  KEYWORDS = ['type', 'subtype', 'location', 'nonl', 'color', 'once']
122
126
 
123
127
  def log( # type: ignore[override]
@@ -143,9 +147,56 @@ class SphinxLoggerAdapter(logging.LoggerAdapter):
143
147
  def handle(self, record: logging.LogRecord) -> None:
144
148
  self.logger.handle(record)
145
149
 
150
+ def warning( # type: ignore[override]
151
+ self,
152
+ msg: object,
153
+ *args: object,
154
+ type: None | str = None,
155
+ subtype: None | str = None,
156
+ location: None | str | tuple[str | None, int | None] | Node = None,
157
+ nonl: bool = True,
158
+ color: str | None = None,
159
+ once: bool = False,
160
+ **kwargs: Any,
161
+ ) -> None:
162
+ """Log a sphinx warning.
163
+
164
+ It is recommended to include a ``type`` and ``subtype`` for warnings as
165
+ these can be displayed to the user using :confval:`show_warning_types`
166
+ and used in :confval:`suppress_warnings` to suppress specific warnings.
167
+
168
+ It is also recommended to specify a ``location`` whenever possible
169
+ to help users in correcting the warning.
170
+
171
+ :param msg: The message, which may contain placeholders for ``args``.
172
+ :param args: The arguments to substitute into ``msg``.
173
+ :param type: The type of the warning.
174
+ :param subtype: The subtype of the warning.
175
+ :param location: The source location of the warning's origin,
176
+ which can be a string (the ``docname`` or ``docname:lineno``),
177
+ a tuple of ``(docname, lineno)``,
178
+ or the docutils node object.
179
+ :param nonl: Whether to append a new line terminator to the message.
180
+ :param color: A color code for the message.
181
+ :param once: Do not log this warning,
182
+ if a previous warning already has same ``msg``, ``args`` and ``once=True``.
183
+ """
184
+ return super().warning(
185
+ msg,
186
+ *args,
187
+ type=type,
188
+ subtype=subtype,
189
+ location=location,
190
+ nonl=nonl,
191
+ color=color,
192
+ once=once,
193
+ **kwargs,
194
+ )
195
+
146
196
 
147
197
  class WarningStreamHandler(logging.StreamHandler):
148
198
  """StreamHandler for warnings."""
199
+
149
200
  pass
150
201
 
151
202
 
@@ -195,7 +246,7 @@ class MemoryHandler(logging.handlers.BufferingHandler):
195
246
 
196
247
 
197
248
  @contextmanager
198
- def pending_warnings() -> Generator[logging.Handler, None, None]:
249
+ def pending_warnings() -> Iterator[logging.Handler]:
199
250
  """Context manager to postpone logging warnings temporarily.
200
251
 
201
252
  Similar to :func:`pending_logging`.
@@ -223,7 +274,7 @@ def pending_warnings() -> Generator[logging.Handler, None, None]:
223
274
 
224
275
 
225
276
  @contextmanager
226
- def suppress_logging() -> Generator[MemoryHandler, None, None]:
277
+ def suppress_logging() -> Iterator[MemoryHandler]:
227
278
  """Context manager to suppress logging all logs temporarily.
228
279
 
229
280
  For example::
@@ -252,7 +303,7 @@ def suppress_logging() -> Generator[MemoryHandler, None, None]:
252
303
 
253
304
 
254
305
  @contextmanager
255
- def pending_logging() -> Generator[MemoryHandler, None, None]:
306
+ def pending_logging() -> Iterator[MemoryHandler]:
256
307
  """Context manager to postpone logging all logs temporarily.
257
308
 
258
309
  For example::
@@ -272,7 +323,7 @@ def pending_logging() -> Generator[MemoryHandler, None, None]:
272
323
 
273
324
 
274
325
  @contextmanager
275
- def skip_warningiserror(skip: bool = True) -> Generator[None, None, None]:
326
+ def skip_warningiserror(skip: bool = True) -> Iterator[None]:
276
327
  """Context manager to skip WarningIsErrorFilter temporarily."""
277
328
  logger = logging.getLogger(NAMESPACE)
278
329
 
@@ -292,7 +343,7 @@ def skip_warningiserror(skip: bool = True) -> Generator[None, None, None]:
292
343
 
293
344
 
294
345
  @contextmanager
295
- def prefixed_warnings(prefix: str) -> Generator[None, None, None]:
346
+ def prefixed_warnings(prefix: str) -> Iterator[None]:
296
347
  """Context manager to prepend prefix to all warning log records temporarily.
297
348
 
298
349
  For example::
@@ -342,7 +393,7 @@ class LogCollector:
342
393
  self.logs: list[logging.LogRecord] = []
343
394
 
344
395
  @contextmanager
345
- def collect(self) -> Generator[None, None, None]:
396
+ def collect(self) -> Iterator[None]:
346
397
  with pending_logging() as memhandler:
347
398
  yield
348
399
 
@@ -475,7 +526,9 @@ class SphinxLogRecordTranslator(logging.Filter):
475
526
 
476
527
  * Make a instance of SphinxLogRecord
477
528
  * docname to path if location given
529
+ * append warning type/subtype to message if :confval:`show_warning_types` is ``True``
478
530
  """
531
+
479
532
  LogRecordClass: type[logging.LogRecord]
480
533
 
481
534
  def __init__(self, app: Sphinx) -> None:
@@ -507,13 +560,32 @@ class SphinxLogRecordTranslator(logging.Filter):
507
560
 
508
561
  class InfoLogRecordTranslator(SphinxLogRecordTranslator):
509
562
  """LogRecordTranslator for INFO level log records."""
563
+
510
564
  LogRecordClass = SphinxInfoLogRecord
511
565
 
512
566
 
513
567
  class WarningLogRecordTranslator(SphinxLogRecordTranslator):
514
568
  """LogRecordTranslator for WARNING level log records."""
569
+
515
570
  LogRecordClass = SphinxWarningLogRecord
516
571
 
572
+ def filter(self, record: SphinxWarningLogRecord) -> bool: # type: ignore[override]
573
+ ret = super().filter(record)
574
+
575
+ try:
576
+ show_warning_types = self.app.config.show_warning_types
577
+ except AttributeError:
578
+ # config is not initialized yet (ex. in conf.py)
579
+ show_warning_types = False
580
+ if show_warning_types:
581
+ if log_type := getattr(record, 'type', ''):
582
+ if log_subtype := getattr(record, 'subtype', ''):
583
+ record.msg += f' [{log_type}.{log_subtype}]'
584
+ else:
585
+ record.msg += f' [{log_type}]'
586
+
587
+ return ret
588
+
517
589
 
518
590
  def get_node_location(node: Node) -> str | None:
519
591
  source, line = get_source_line(node)
@@ -543,6 +615,7 @@ class ColorizeFormatter(logging.Formatter):
543
615
 
544
616
  class SafeEncodingWriter:
545
617
  """Stream writer which ignores UnicodeEncodeError silently"""
618
+
546
619
  def __init__(self, stream: IO) -> None:
547
620
  self.stream = stream
548
621
  self.encoding = getattr(stream, 'encoding', 'ascii') or 'ascii'
@@ -562,6 +635,7 @@ class SafeEncodingWriter:
562
635
 
563
636
  class LastMessagesWriter:
564
637
  """Stream writer storing last 10 messages in memory to save trackback"""
638
+
565
639
  def __init__(self, app: Sphinx, stream: IO) -> None:
566
640
  self.app = app
567
641
 
sphinx/util/matching.py CHANGED
@@ -91,7 +91,8 @@ _pat_cache: dict[str, re.Pattern[str]] = {}
91
91
 
92
92
  def patmatch(name: str, pat: str) -> re.Match[str] | None:
93
93
  """Return if name matches the regular expression (pattern)
94
- ``pat```. Adapted from fnmatch module."""
94
+ ``pat```. Adapted from fnmatch module.
95
+ """
95
96
  if pat not in _pat_cache:
96
97
  _pat_cache[pat] = re.compile(_translate_pattern(pat))
97
98
  return _pat_cache[pat].match(name)
sphinx/util/math.py CHANGED
@@ -54,8 +54,7 @@ def wrap_displaymath(text: str, label: str | None, numbering: bool) -> str:
54
54
  else:
55
55
  begin = r'\begin{align*}%s\!\begin{aligned}' % labeldef
56
56
  end = r'\end{aligned}\end{align*}'
57
- for part in parts:
58
- equations.append('%s\\\\\n' % part.strip())
57
+ equations.extend('%s\\\\\n' % part.strip() for part in parts)
59
58
 
60
59
  concatenated_equations = ''.join(equations)
61
60
  return f'{begin}\n{concatenated_equations}{end}'
sphinx/util/nodes.py CHANGED
@@ -5,18 +5,19 @@ from __future__ import annotations
5
5
  import contextlib
6
6
  import re
7
7
  import unicodedata
8
- from typing import TYPE_CHECKING, Any, Callable
8
+ from typing import TYPE_CHECKING, Any, Callable, Generic, TypeVar, cast
9
9
 
10
10
  from docutils import nodes
11
+ from docutils.nodes import Node
11
12
 
12
13
  from sphinx import addnodes
13
14
  from sphinx.locale import __
14
15
  from sphinx.util import logging
15
16
 
16
17
  if TYPE_CHECKING:
17
- from collections.abc import Iterable
18
+ from collections.abc import Iterable, Iterator
18
19
 
19
- from docutils.nodes import Element, Node
20
+ from docutils.nodes import Element
20
21
  from docutils.parsers.rst import Directive
21
22
  from docutils.parsers.rst.states import Inliner
22
23
  from docutils.statemachine import StringList
@@ -33,7 +34,10 @@ explicit_title_re = re.compile(r'^(.+?)\s*(?<!\x00)<([^<]*?)>$', re.DOTALL)
33
34
  caption_ref_re = explicit_title_re # b/w compat alias
34
35
 
35
36
 
36
- class NodeMatcher:
37
+ N = TypeVar("N", bound=Node)
38
+
39
+
40
+ class NodeMatcher(Generic[N]):
37
41
  """A helper class for Node.findall().
38
42
 
39
43
  It checks that the given node is an instance of the specified node-classes and
@@ -43,20 +47,18 @@ class NodeMatcher:
43
47
  and ``reftype`` attributes::
44
48
 
45
49
  matcher = NodeMatcher(nodes.reference, refdomain='std', reftype='citation')
46
- doctree.findall(matcher)
50
+ matcher.findall(doctree)
47
51
  # => [<reference ...>, <reference ...>, ...]
48
52
 
49
53
  A special value ``typing.Any`` matches any kind of node-attributes. For example,
50
54
  following example searches ``reference`` node having ``refdomain`` attributes::
51
55
 
52
- from __future__ import annotations
53
- from typing import TYPE_CHECKING, Any
54
56
  matcher = NodeMatcher(nodes.reference, refdomain=Any)
55
- doctree.findall(matcher)
57
+ matcher.findall(doctree)
56
58
  # => [<reference ...>, <reference ...>, ...]
57
59
  """
58
60
 
59
- def __init__(self, *node_classes: type[Node], **attrs: Any) -> None:
61
+ def __init__(self, *node_classes: type[N], **attrs: Any) -> None:
60
62
  self.classes = node_classes
61
63
  self.attrs = attrs
62
64
 
@@ -85,6 +87,15 @@ from typing import TYPE_CHECKING, Any
85
87
  def __call__(self, node: Node) -> bool:
86
88
  return self.match(node)
87
89
 
90
+ def findall(self, node: Node) -> Iterator[N]:
91
+ """An alternative to `Node.findall` with improved type safety.
92
+
93
+ While the `NodeMatcher` object can be used as an argument to `Node.findall`, doing so
94
+ confounds type checkers' ability to determine the return type of the iterator.
95
+ """
96
+ for found in node.findall(self):
97
+ yield cast(N, found)
98
+
88
99
 
89
100
  def get_full_module_name(node: Node) -> str:
90
101
  """
@@ -99,7 +110,7 @@ def get_full_module_name(node: Node) -> str:
99
110
  def repr_domxml(node: Node, length: int = 80) -> str:
100
111
  """
101
112
  return DOM XML representation of the specified node like:
102
- '<paragraph translatable="False"><inline classes="versionmodified">New in version...'
113
+ '<paragraph translatable="False"><inline classes="versionadded">Added in version...'
103
114
 
104
115
  :param nodes.Node node: target node
105
116
  :param int length:
@@ -127,7 +138,7 @@ def apply_source_workaround(node: Element) -> None:
127
138
  get_full_module_name(node), repr_domxml(node))
128
139
  definition_list_item = node.parent
129
140
  node.source = definition_list_item.source
130
- node.line = definition_list_item.line - 1
141
+ node.line = definition_list_item.line - 1 # type: ignore[operator]
131
142
  node.rawsource = node.astext() # set 'classifier1' (or 'classifier2')
132
143
  elif isinstance(node, nodes.classifier) and not node.source:
133
144
  # docutils-0.15 fills in rawsource attribute, but not in source.
@@ -220,16 +231,13 @@ def is_translatable(node: Node) -> bool:
220
231
  return False
221
232
  # <field_name>orphan</field_name>
222
233
  # XXX ignore all metadata (== docinfo)
223
- if isinstance(node, nodes.field_name) and node.children[0] == 'orphan':
234
+ if isinstance(node, nodes.field_name) and (node.children[0] == 'orphan'):
224
235
  logger.debug('[i18n] SKIP %r because orphan node: %s',
225
236
  get_full_module_name(node), repr_domxml(node))
226
237
  return False
227
238
  return True
228
239
 
229
- if isinstance(node, nodes.meta): # type: ignore[attr-defined]
230
- return True
231
-
232
- return False
240
+ return isinstance(node, nodes.meta)
233
241
 
234
242
 
235
243
  LITERAL_TYPE_NODES = (
@@ -245,10 +253,10 @@ IMAGE_TYPE_NODES = (
245
253
 
246
254
  def extract_messages(doctree: Element) -> Iterable[tuple[Element, str]]:
247
255
  """Extract translatable messages from a document tree."""
248
- for node in doctree.findall(is_translatable): # type: Element
256
+ for node in doctree.findall(is_translatable):
249
257
  if isinstance(node, addnodes.translatable):
250
258
  for msg in node.extract_original_messages():
251
- yield node, msg
259
+ yield node, msg # type: ignore[misc]
252
260
  continue
253
261
  if isinstance(node, LITERAL_TYPE_NODES):
254
262
  msg = node.rawsource
@@ -262,14 +270,14 @@ def extract_messages(doctree: Element) -> Iterable[tuple[Element, str]]:
262
270
  msg = f'.. image:: {image_uri}'
263
271
  else:
264
272
  msg = ''
265
- elif isinstance(node, nodes.meta): # type: ignore[attr-defined]
273
+ elif isinstance(node, nodes.meta):
266
274
  msg = node["content"]
267
275
  else:
268
- msg = node.rawsource.replace('\n', ' ').strip()
276
+ msg = node.rawsource.replace('\n', ' ').strip() # type: ignore[attr-defined]
269
277
 
270
278
  # XXX nodes rendering empty are likely a bug in sphinx.addnodes
271
279
  if msg:
272
- yield node, msg
280
+ yield node, msg # type: ignore[misc]
273
281
 
274
282
 
275
283
  def get_node_source(node: Element) -> str:
@@ -308,7 +316,7 @@ def traverse_translatable_index(
308
316
  ) -> Iterable[tuple[Element, list[tuple[str, str, str, str, str | None]]]]:
309
317
  """Traverse translatable index node from a document tree."""
310
318
  matcher = NodeMatcher(addnodes.index, inline=False)
311
- for node in doctree.findall(matcher): # type: addnodes.index
319
+ for node in matcher.findall(doctree):
312
320
  if 'raw_entries' in node:
313
321
  entries = node['raw_entries']
314
322
  else:
@@ -402,9 +410,14 @@ def process_index_entry(entry: str, targetid: str,
402
410
  return indexentries
403
411
 
404
412
 
405
- def inline_all_toctrees(builder: Builder, docnameset: set[str], docname: str,
406
- tree: nodes.document, colorfunc: Callable, traversed: list[str],
407
- ) -> nodes.document:
413
+ def inline_all_toctrees(
414
+ builder: Builder,
415
+ docnameset: set[str],
416
+ docname: str,
417
+ tree: nodes.document,
418
+ colorfunc: Callable[[str], str],
419
+ traversed: list[str],
420
+ ) -> nodes.document:
408
421
  """Inline all toctrees in the *tree*.
409
422
 
410
423
  Record all docnames in *docnameset*, and output docnames with *colorfunc*.
@@ -599,10 +612,7 @@ def is_smartquotable(node: Node) -> bool:
599
612
  if pnode.get('support_smartquotes', None) is False:
600
613
  return False
601
614
 
602
- if getattr(node, 'support_smartquotes', None) is False:
603
- return False
604
-
605
- return True
615
+ return getattr(node, 'support_smartquotes', None) is not False
606
616
 
607
617
 
608
618
  def process_only_nodes(document: Node, tags: Tags) -> None:
sphinx/util/osutil.py CHANGED
@@ -11,12 +11,14 @@ import sys
11
11
  import unicodedata
12
12
  from io import StringIO
13
13
  from os import path
14
- from typing import TYPE_CHECKING, Any
14
+ from typing import TYPE_CHECKING
15
15
 
16
16
  from sphinx.deprecation import _deprecation_warning
17
17
 
18
18
  if TYPE_CHECKING:
19
19
  from collections.abc import Iterator
20
+ from types import TracebackType
21
+ from typing import Any
20
22
 
21
23
  # SEP separates path elements in the canonical file names
22
24
  #
@@ -36,7 +38,7 @@ def canon_path(native_path: str | os.PathLike[str], /) -> str:
36
38
 
37
39
 
38
40
  def path_stabilize(filepath: str | os.PathLike[str], /) -> str:
39
- "Normalize path separator and unicode string"
41
+ """Normalize path separator and unicode string"""
40
42
  new_path = canon_path(filepath)
41
43
  return unicodedata.normalize('NFC', new_path)
42
44
 
@@ -88,7 +90,16 @@ def copytimes(source: str | os.PathLike[str], dest: str | os.PathLike[str]) -> N
88
90
  def copyfile(source: str | os.PathLike[str], dest: str | os.PathLike[str]) -> None:
89
91
  """Copy a file and its modification times, if possible.
90
92
 
91
- Note: ``copyfile`` skips copying if the file has not been changed"""
93
+ :param source: An existing source to copy.
94
+ :param dest: The destination path.
95
+ :raise FileNotFoundError: The *source* does not exist.
96
+
97
+ .. note:: :func:`copyfile` is a no-op if *source* and *dest* are identical.
98
+ """
99
+ if not path.exists(source):
100
+ msg = f'{os.fsdecode(source)} does not exist'
101
+ raise FileNotFoundError(msg)
102
+
92
103
  if not path.exists(dest) or not filecmp.cmp(source, dest):
93
104
  shutil.copyfile(source, dest)
94
105
  with contextlib.suppress(OSError):
@@ -131,15 +142,22 @@ abspath = path.abspath
131
142
 
132
143
  class _chdir:
133
144
  """Remove this fall-back once support for Python 3.10 is removed."""
134
- def __init__(self, target_dir: str, /):
145
+
146
+ def __init__(self, target_dir: str, /) -> None:
135
147
  self.path = target_dir
136
148
  self._dirs: list[str] = []
137
149
 
138
- def __enter__(self):
150
+ def __enter__(self) -> None:
139
151
  self._dirs.append(os.getcwd())
140
152
  os.chdir(self.path)
141
153
 
142
- def __exit__(self, _exc_type, _exc_value, _traceback, /):
154
+ def __exit__(
155
+ self,
156
+ type: type[BaseException] | None,
157
+ value: BaseException | None,
158
+ traceback: TracebackType | None,
159
+ /,
160
+ ) -> None:
143
161
  os.chdir(self._dirs.pop())
144
162
 
145
163
 
@@ -163,6 +181,7 @@ class FileAvoidWrite:
163
181
 
164
182
  Objects can be used as context managers.
165
183
  """
184
+
166
185
  def __init__(self, path: str) -> None:
167
186
  self._path = path
168
187
  self._io: StringIO | None = None
sphinx/util/parallel.py CHANGED
@@ -94,7 +94,12 @@ class ParallelTasks:
94
94
  proc = context.Process(target=self._process, args=(psend, task_func, arg))
95
95
  self._procs[tid] = proc
96
96
  self._precvsWaiting[tid] = precv
97
- self._join_one()
97
+ try:
98
+ self._join_one()
99
+ except Exception:
100
+ # shutdown other child processes on failure
101
+ # (e.g. OSError: Failed to allocate memory)
102
+ self.terminate()
98
103
 
99
104
  def join(self) -> None:
100
105
  try:
sphinx/util/requests.py CHANGED
@@ -30,17 +30,19 @@ def _get_tls_cacert(url: str, certs: str | dict[str, str] | None) -> str | bool:
30
30
 
31
31
 
32
32
  def get(url: str, **kwargs: Any) -> requests.Response:
33
- """Sends a GET request like requests.get().
33
+ """Sends a GET request like ``requests.get()``.
34
34
 
35
- This sets up User-Agent header and TLS verification automatically."""
35
+ This sets up User-Agent header and TLS verification automatically.
36
+ """
36
37
  with _Session() as session:
37
38
  return session.get(url, **kwargs)
38
39
 
39
40
 
40
41
  def head(url: str, **kwargs: Any) -> requests.Response:
41
- """Sends a HEAD request like requests.head().
42
+ """Sends a HEAD request like ``requests.head()``.
42
43
 
43
- This sets up User-Agent header and TLS verification automatically."""
44
+ This sets up User-Agent header and TLS verification automatically.
45
+ """
44
46
  with _Session() as session:
45
47
  return session.head(url, **kwargs)
46
48
 
@@ -54,7 +56,8 @@ class _Session(requests.Session):
54
56
  ) -> requests.Response:
55
57
  """Sends a request with an HTTP verb and url.
56
58
 
57
- This sets up User-Agent header and TLS verification automatically."""
59
+ This sets up User-Agent header and TLS verification automatically.
60
+ """
58
61
  headers = kwargs.setdefault('headers', {})
59
62
  headers.setdefault('User-Agent', _user_agent or _USER_AGENT)
60
63
  if _tls_info:
sphinx/util/rst.py CHANGED
@@ -5,11 +5,11 @@ from __future__ import annotations
5
5
  import re
6
6
  from collections import defaultdict
7
7
  from contextlib import contextmanager
8
- from typing import TYPE_CHECKING
8
+ from typing import TYPE_CHECKING, cast
9
9
  from unicodedata import east_asian_width
10
10
 
11
11
  from docutils.parsers.rst import roles
12
- from docutils.parsers.rst.languages import en as english
12
+ from docutils.parsers.rst.languages import en as english # type: ignore[attr-defined]
13
13
  from docutils.parsers.rst.states import Body
14
14
  from docutils.utils import Reporter
15
15
  from jinja2 import Environment, pass_environment
@@ -18,7 +18,7 @@ from sphinx.locale import __
18
18
  from sphinx.util import docutils, logging
19
19
 
20
20
  if TYPE_CHECKING:
21
- from collections.abc import Generator
21
+ from collections.abc import Iterator
22
22
 
23
23
  from docutils.statemachine import StringList
24
24
 
@@ -54,17 +54,18 @@ def textwidth(text: str, widechars: str = 'WF') -> int:
54
54
  def heading(env: Environment, text: str, level: int = 1) -> str:
55
55
  """Create a heading for *level*."""
56
56
  assert level <= 3
57
- width = textwidth(text, WIDECHARS[env.language])
57
+ # ``env.language`` is injected by ``sphinx.util.template.ReSTRenderer``
58
+ width = textwidth(text, WIDECHARS[env.language]) # type: ignore[attr-defined]
58
59
  sectioning_char = SECTIONING_CHARS[level - 1]
59
60
  return f'{text}\n{sectioning_char * width}'
60
61
 
61
62
 
62
63
  @contextmanager
63
- def default_role(docname: str, name: str) -> Generator[None, None, None]:
64
+ def default_role(docname: str, name: str) -> Iterator[None]:
64
65
  if name:
65
66
  dummy_reporter = Reporter('', 4, 4)
66
67
  role_fn, _ = roles.role(name, english, 0, dummy_reporter)
67
- if role_fn: # type: ignore[truthy-function]
68
+ if role_fn:
68
69
  docutils.register_role('', role_fn) # type: ignore[arg-type]
69
70
  else:
70
71
  logger.warning(__('default role %s not found'), name, location=docname)
@@ -102,6 +103,7 @@ def append_epilog(content: StringList, epilog: str) -> None:
102
103
  if epilog:
103
104
  if len(content) > 0:
104
105
  source, lineno = content.info(-1)
106
+ lineno = cast(int, lineno) # lineno will never be None, since len(content) > 0
105
107
  else:
106
108
  source = '<generated>'
107
109
  lineno = 0
sphinx/util/tags.py CHANGED
@@ -20,8 +20,8 @@ class BooleanParser(Parser):
20
20
  Only allow condition exprs and/or/not operations.
21
21
  """
22
22
 
23
- def parse_compare(self) -> Node:
24
- node: Node
23
+ def parse_compare(self) -> nodes.Expr:
24
+ node: nodes.Expr
25
25
  token = self.stream.current
26
26
  if token.type == 'name':
27
27
  if token.value in ('true', 'false', 'True', 'False'):
@@ -67,7 +67,7 @@ class Tags:
67
67
  msg = 'chunk after expression'
68
68
  raise ValueError(msg)
69
69
 
70
- def eval_node(node: Node) -> bool:
70
+ def eval_node(node: Node | None) -> bool:
71
71
  if isinstance(node, nodes.CondExpr):
72
72
  if eval_node(node.test):
73
73
  return eval_node(node.expr1)