Sphinx 7.3.6__py3-none-any.whl → 7.4.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 (357) hide show
  1. sphinx/__init__.py +5 -6
  2. sphinx/_cli/__init__.py +296 -0
  3. sphinx/_cli/util/__init__.py +0 -0
  4. sphinx/_cli/util/colour.py +103 -0
  5. sphinx/_cli/util/errors.py +165 -0
  6. sphinx/application.py +78 -43
  7. sphinx/builders/__init__.py +59 -15
  8. sphinx/builders/_epub_base.py +11 -5
  9. sphinx/builders/changes.py +2 -2
  10. sphinx/builders/epub3.py +2 -2
  11. sphinx/builders/gettext.py +10 -10
  12. sphinx/builders/html/__init__.py +56 -54
  13. sphinx/builders/latex/__init__.py +5 -5
  14. sphinx/builders/latex/constants.py +5 -0
  15. sphinx/builders/linkcheck.py +73 -38
  16. sphinx/builders/texinfo.py +1 -1
  17. sphinx/cmd/build.py +1 -1
  18. sphinx/cmd/quickstart.py +11 -11
  19. sphinx/config.py +57 -38
  20. sphinx/directives/__init__.py +7 -9
  21. sphinx/directives/code.py +12 -15
  22. sphinx/directives/other.py +12 -15
  23. sphinx/directives/patches.py +26 -0
  24. sphinx/domains/__init__.py +1 -1
  25. sphinx/domains/c/__init__.py +5 -5
  26. sphinx/domains/c/_ast.py +436 -12
  27. sphinx/domains/c/_symbol.py +89 -134
  28. sphinx/domains/changeset.py +3 -4
  29. sphinx/domains/cpp/__init__.py +5 -6
  30. sphinx/domains/cpp/_ast.py +822 -25
  31. sphinx/domains/cpp/_symbol.py +3 -0
  32. sphinx/domains/javascript.py +3 -6
  33. sphinx/domains/math.py +3 -2
  34. sphinx/domains/python/__init__.py +45 -6
  35. sphinx/domains/python/_object.py +7 -5
  36. sphinx/domains/rst.py +2 -2
  37. sphinx/domains/std/__init__.py +95 -14
  38. sphinx/environment/__init__.py +35 -15
  39. sphinx/environment/adapters/indexentries.py +71 -24
  40. sphinx/environment/adapters/toctree.py +1 -1
  41. sphinx/environment/collectors/__init__.py +18 -4
  42. sphinx/environment/collectors/asset.py +4 -4
  43. sphinx/environment/collectors/toctree.py +27 -14
  44. sphinx/events.py +7 -6
  45. sphinx/ext/apidoc.py +377 -170
  46. sphinx/ext/autodoc/__init__.py +13 -13
  47. sphinx/ext/autodoc/directive.py +10 -13
  48. sphinx/ext/autodoc/mock.py +10 -7
  49. sphinx/ext/autodoc/preserve_defaults.py +1 -1
  50. sphinx/ext/autodoc/typehints.py +2 -2
  51. sphinx/ext/autosummary/__init__.py +15 -9
  52. sphinx/ext/autosummary/generate.py +270 -154
  53. sphinx/ext/coverage.py +108 -18
  54. sphinx/ext/duration.py +10 -3
  55. sphinx/ext/extlinks.py +3 -2
  56. sphinx/ext/graphviz.py +3 -3
  57. sphinx/ext/ifconfig.py +1 -2
  58. sphinx/ext/imgconverter.py +1 -0
  59. sphinx/ext/imgmath.py +7 -6
  60. sphinx/ext/inheritance_diagram.py +3 -3
  61. sphinx/ext/intersphinx/__init__.py +81 -0
  62. sphinx/ext/intersphinx/__main__.py +10 -0
  63. sphinx/ext/intersphinx/_cli.py +44 -0
  64. sphinx/ext/intersphinx/_load.py +253 -0
  65. sphinx/ext/{intersphinx.py → intersphinx/_resolve.py} +17 -368
  66. sphinx/ext/intersphinx/_shared.py +53 -0
  67. sphinx/ext/mathjax.py +1 -1
  68. sphinx/ext/todo.py +2 -2
  69. sphinx/io.py +2 -6
  70. sphinx/locale/__init__.py +1 -5
  71. sphinx/locale/ar/LC_MESSAGES/sphinx.js +1 -1
  72. sphinx/locale/ar/LC_MESSAGES/sphinx.mo +0 -0
  73. sphinx/locale/ar/LC_MESSAGES/sphinx.po +678 -471
  74. sphinx/locale/bg/LC_MESSAGES/sphinx.js +1 -1
  75. sphinx/locale/bg/LC_MESSAGES/sphinx.mo +0 -0
  76. sphinx/locale/bg/LC_MESSAGES/sphinx.po +684 -476
  77. sphinx/locale/bn/LC_MESSAGES/sphinx.js +1 -1
  78. sphinx/locale/bn/LC_MESSAGES/sphinx.mo +0 -0
  79. sphinx/locale/bn/LC_MESSAGES/sphinx.po +679 -472
  80. sphinx/locale/ca/LC_MESSAGES/sphinx.js +1 -1
  81. sphinx/locale/ca/LC_MESSAGES/sphinx.mo +0 -0
  82. sphinx/locale/ca/LC_MESSAGES/sphinx.po +681 -474
  83. sphinx/locale/cak/LC_MESSAGES/sphinx.js +1 -1
  84. sphinx/locale/cak/LC_MESSAGES/sphinx.mo +0 -0
  85. sphinx/locale/cak/LC_MESSAGES/sphinx.po +678 -471
  86. sphinx/locale/cs/LC_MESSAGES/sphinx.js +1 -1
  87. sphinx/locale/cs/LC_MESSAGES/sphinx.mo +0 -0
  88. sphinx/locale/cs/LC_MESSAGES/sphinx.po +679 -472
  89. sphinx/locale/cy/LC_MESSAGES/sphinx.js +1 -1
  90. sphinx/locale/cy/LC_MESSAGES/sphinx.mo +0 -0
  91. sphinx/locale/cy/LC_MESSAGES/sphinx.po +679 -472
  92. sphinx/locale/da/LC_MESSAGES/sphinx.js +1 -1
  93. sphinx/locale/da/LC_MESSAGES/sphinx.mo +0 -0
  94. sphinx/locale/da/LC_MESSAGES/sphinx.po +679 -472
  95. sphinx/locale/de/LC_MESSAGES/sphinx.js +1 -1
  96. sphinx/locale/de/LC_MESSAGES/sphinx.mo +0 -0
  97. sphinx/locale/de/LC_MESSAGES/sphinx.po +679 -472
  98. sphinx/locale/de_DE/LC_MESSAGES/sphinx.js +1 -1
  99. sphinx/locale/de_DE/LC_MESSAGES/sphinx.mo +0 -0
  100. sphinx/locale/de_DE/LC_MESSAGES/sphinx.po +678 -471
  101. sphinx/locale/el/LC_MESSAGES/sphinx.js +1 -1
  102. sphinx/locale/el/LC_MESSAGES/sphinx.mo +0 -0
  103. sphinx/locale/el/LC_MESSAGES/sphinx.po +701 -494
  104. sphinx/locale/en_DE/LC_MESSAGES/sphinx.js +1 -1
  105. sphinx/locale/en_DE/LC_MESSAGES/sphinx.mo +0 -0
  106. sphinx/locale/en_DE/LC_MESSAGES/sphinx.po +700 -493
  107. sphinx/locale/en_FR/LC_MESSAGES/sphinx.js +1 -1
  108. sphinx/locale/en_FR/LC_MESSAGES/sphinx.mo +0 -0
  109. sphinx/locale/en_FR/LC_MESSAGES/sphinx.po +700 -493
  110. sphinx/locale/en_GB/LC_MESSAGES/sphinx.js +1 -1
  111. sphinx/locale/en_GB/LC_MESSAGES/sphinx.mo +0 -0
  112. sphinx/locale/en_GB/LC_MESSAGES/sphinx.po +701 -494
  113. sphinx/locale/en_HK/LC_MESSAGES/sphinx.js +1 -1
  114. sphinx/locale/en_HK/LC_MESSAGES/sphinx.mo +0 -0
  115. sphinx/locale/en_HK/LC_MESSAGES/sphinx.po +700 -493
  116. sphinx/locale/eo/LC_MESSAGES/sphinx.js +1 -1
  117. sphinx/locale/eo/LC_MESSAGES/sphinx.mo +0 -0
  118. sphinx/locale/eo/LC_MESSAGES/sphinx.po +701 -494
  119. sphinx/locale/es/LC_MESSAGES/sphinx.js +1 -1
  120. sphinx/locale/es/LC_MESSAGES/sphinx.mo +0 -0
  121. sphinx/locale/es/LC_MESSAGES/sphinx.po +701 -494
  122. sphinx/locale/es_CO/LC_MESSAGES/sphinx.js +1 -1
  123. sphinx/locale/es_CO/LC_MESSAGES/sphinx.mo +0 -0
  124. sphinx/locale/es_CO/LC_MESSAGES/sphinx.po +700 -493
  125. sphinx/locale/et/LC_MESSAGES/sphinx.js +1 -1
  126. sphinx/locale/et/LC_MESSAGES/sphinx.mo +0 -0
  127. sphinx/locale/et/LC_MESSAGES/sphinx.po +701 -494
  128. sphinx/locale/eu/LC_MESSAGES/sphinx.js +1 -1
  129. sphinx/locale/eu/LC_MESSAGES/sphinx.mo +0 -0
  130. sphinx/locale/eu/LC_MESSAGES/sphinx.po +701 -494
  131. sphinx/locale/fa/LC_MESSAGES/sphinx.js +1 -1
  132. sphinx/locale/fa/LC_MESSAGES/sphinx.mo +0 -0
  133. sphinx/locale/fa/LC_MESSAGES/sphinx.po +701 -494
  134. sphinx/locale/fi/LC_MESSAGES/sphinx.js +1 -1
  135. sphinx/locale/fi/LC_MESSAGES/sphinx.mo +0 -0
  136. sphinx/locale/fi/LC_MESSAGES/sphinx.po +700 -493
  137. sphinx/locale/fr/LC_MESSAGES/sphinx.js +1 -1
  138. sphinx/locale/fr/LC_MESSAGES/sphinx.mo +0 -0
  139. sphinx/locale/fr/LC_MESSAGES/sphinx.po +725 -518
  140. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.js +1 -1
  141. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.mo +0 -0
  142. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.po +700 -493
  143. sphinx/locale/gl/LC_MESSAGES/sphinx.js +1 -1
  144. sphinx/locale/gl/LC_MESSAGES/sphinx.mo +0 -0
  145. sphinx/locale/gl/LC_MESSAGES/sphinx.po +701 -494
  146. sphinx/locale/he/LC_MESSAGES/sphinx.js +1 -1
  147. sphinx/locale/he/LC_MESSAGES/sphinx.mo +0 -0
  148. sphinx/locale/he/LC_MESSAGES/sphinx.po +700 -493
  149. sphinx/locale/hi/LC_MESSAGES/sphinx.js +1 -1
  150. sphinx/locale/hi/LC_MESSAGES/sphinx.mo +0 -0
  151. sphinx/locale/hi/LC_MESSAGES/sphinx.po +701 -494
  152. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.js +1 -1
  153. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.mo +0 -0
  154. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.po +700 -493
  155. sphinx/locale/hr/LC_MESSAGES/sphinx.js +1 -1
  156. sphinx/locale/hr/LC_MESSAGES/sphinx.mo +0 -0
  157. sphinx/locale/hr/LC_MESSAGES/sphinx.po +701 -494
  158. sphinx/locale/hu/LC_MESSAGES/sphinx.js +1 -1
  159. sphinx/locale/hu/LC_MESSAGES/sphinx.mo +0 -0
  160. sphinx/locale/hu/LC_MESSAGES/sphinx.po +701 -494
  161. sphinx/locale/id/LC_MESSAGES/sphinx.js +1 -1
  162. sphinx/locale/id/LC_MESSAGES/sphinx.mo +0 -0
  163. sphinx/locale/id/LC_MESSAGES/sphinx.po +701 -494
  164. sphinx/locale/is/LC_MESSAGES/sphinx.js +1 -1
  165. sphinx/locale/is/LC_MESSAGES/sphinx.mo +0 -0
  166. sphinx/locale/is/LC_MESSAGES/sphinx.po +700 -493
  167. sphinx/locale/it/LC_MESSAGES/sphinx.js +1 -1
  168. sphinx/locale/it/LC_MESSAGES/sphinx.mo +0 -0
  169. sphinx/locale/it/LC_MESSAGES/sphinx.po +708 -500
  170. sphinx/locale/ja/LC_MESSAGES/sphinx.js +1 -1
  171. sphinx/locale/ja/LC_MESSAGES/sphinx.mo +0 -0
  172. sphinx/locale/ja/LC_MESSAGES/sphinx.po +701 -494
  173. sphinx/locale/ka/LC_MESSAGES/sphinx.js +1 -1
  174. sphinx/locale/ka/LC_MESSAGES/sphinx.mo +0 -0
  175. sphinx/locale/ka/LC_MESSAGES/sphinx.po +700 -493
  176. sphinx/locale/ko/LC_MESSAGES/sphinx.js +1 -1
  177. sphinx/locale/ko/LC_MESSAGES/sphinx.mo +0 -0
  178. sphinx/locale/ko/LC_MESSAGES/sphinx.po +701 -494
  179. sphinx/locale/lt/LC_MESSAGES/sphinx.js +1 -1
  180. sphinx/locale/lt/LC_MESSAGES/sphinx.mo +0 -0
  181. sphinx/locale/lt/LC_MESSAGES/sphinx.po +701 -494
  182. sphinx/locale/lv/LC_MESSAGES/sphinx.js +1 -1
  183. sphinx/locale/lv/LC_MESSAGES/sphinx.mo +0 -0
  184. sphinx/locale/lv/LC_MESSAGES/sphinx.po +701 -494
  185. sphinx/locale/mk/LC_MESSAGES/sphinx.js +1 -1
  186. sphinx/locale/mk/LC_MESSAGES/sphinx.mo +0 -0
  187. sphinx/locale/mk/LC_MESSAGES/sphinx.po +700 -493
  188. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.js +1 -1
  189. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.mo +0 -0
  190. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.po +701 -494
  191. sphinx/locale/ne/LC_MESSAGES/sphinx.js +1 -1
  192. sphinx/locale/ne/LC_MESSAGES/sphinx.mo +0 -0
  193. sphinx/locale/ne/LC_MESSAGES/sphinx.po +701 -494
  194. sphinx/locale/nl/LC_MESSAGES/sphinx.js +1 -1
  195. sphinx/locale/nl/LC_MESSAGES/sphinx.mo +0 -0
  196. sphinx/locale/nl/LC_MESSAGES/sphinx.po +701 -494
  197. sphinx/locale/pl/LC_MESSAGES/sphinx.js +1 -1
  198. sphinx/locale/pl/LC_MESSAGES/sphinx.mo +0 -0
  199. sphinx/locale/pl/LC_MESSAGES/sphinx.po +701 -494
  200. sphinx/locale/pt/LC_MESSAGES/sphinx.js +1 -1
  201. sphinx/locale/pt/LC_MESSAGES/sphinx.mo +0 -0
  202. sphinx/locale/pt/LC_MESSAGES/sphinx.po +700 -493
  203. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.js +1 -1
  204. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.mo +0 -0
  205. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.po +705 -498
  206. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.js +1 -1
  207. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.mo +0 -0
  208. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.po +701 -494
  209. sphinx/locale/ro/LC_MESSAGES/sphinx.js +1 -1
  210. sphinx/locale/ro/LC_MESSAGES/sphinx.mo +0 -0
  211. sphinx/locale/ro/LC_MESSAGES/sphinx.po +701 -494
  212. sphinx/locale/ru/LC_MESSAGES/sphinx.js +1 -1
  213. sphinx/locale/ru/LC_MESSAGES/sphinx.mo +0 -0
  214. sphinx/locale/ru/LC_MESSAGES/sphinx.po +890 -680
  215. sphinx/locale/si/LC_MESSAGES/sphinx.js +1 -1
  216. sphinx/locale/si/LC_MESSAGES/sphinx.mo +0 -0
  217. sphinx/locale/si/LC_MESSAGES/sphinx.po +700 -493
  218. sphinx/locale/sk/LC_MESSAGES/sphinx.js +1 -1
  219. sphinx/locale/sk/LC_MESSAGES/sphinx.mo +0 -0
  220. sphinx/locale/sk/LC_MESSAGES/sphinx.po +701 -494
  221. sphinx/locale/sl/LC_MESSAGES/sphinx.js +1 -1
  222. sphinx/locale/sl/LC_MESSAGES/sphinx.mo +0 -0
  223. sphinx/locale/sl/LC_MESSAGES/sphinx.po +701 -494
  224. sphinx/locale/sphinx.pot +702 -494
  225. sphinx/locale/sq/LC_MESSAGES/sphinx.js +1 -1
  226. sphinx/locale/sq/LC_MESSAGES/sphinx.mo +0 -0
  227. sphinx/locale/sq/LC_MESSAGES/sphinx.po +704 -497
  228. sphinx/locale/sr/LC_MESSAGES/sphinx.js +1 -1
  229. sphinx/locale/sr/LC_MESSAGES/sphinx.mo +0 -0
  230. sphinx/locale/sr/LC_MESSAGES/sphinx.po +700 -493
  231. sphinx/locale/sr@latin/LC_MESSAGES/sphinx.mo +0 -0
  232. sphinx/locale/sr_RS/LC_MESSAGES/sphinx.mo +0 -0
  233. sphinx/locale/sv/LC_MESSAGES/sphinx.js +1 -1
  234. sphinx/locale/sv/LC_MESSAGES/sphinx.mo +0 -0
  235. sphinx/locale/sv/LC_MESSAGES/sphinx.po +701 -494
  236. sphinx/locale/ta/LC_MESSAGES/sphinx.po +1016 -808
  237. sphinx/locale/te/LC_MESSAGES/sphinx.js +1 -1
  238. sphinx/locale/te/LC_MESSAGES/sphinx.mo +0 -0
  239. sphinx/locale/te/LC_MESSAGES/sphinx.po +700 -493
  240. sphinx/locale/tr/LC_MESSAGES/sphinx.js +1 -1
  241. sphinx/locale/tr/LC_MESSAGES/sphinx.mo +0 -0
  242. sphinx/locale/tr/LC_MESSAGES/sphinx.po +701 -494
  243. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.js +1 -1
  244. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.mo +0 -0
  245. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.po +701 -494
  246. sphinx/locale/ur/LC_MESSAGES/sphinx.js +1 -1
  247. sphinx/locale/ur/LC_MESSAGES/sphinx.mo +0 -0
  248. sphinx/locale/ur/LC_MESSAGES/sphinx.po +700 -493
  249. sphinx/locale/vi/LC_MESSAGES/sphinx.js +1 -1
  250. sphinx/locale/vi/LC_MESSAGES/sphinx.mo +0 -0
  251. sphinx/locale/vi/LC_MESSAGES/sphinx.po +701 -494
  252. sphinx/locale/yue/LC_MESSAGES/sphinx.js +1 -1
  253. sphinx/locale/yue/LC_MESSAGES/sphinx.mo +0 -0
  254. sphinx/locale/yue/LC_MESSAGES/sphinx.po +700 -493
  255. sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po +704 -496
  256. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.js +1 -1
  257. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.mo +0 -0
  258. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.po +700 -493
  259. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.js +1 -1
  260. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.mo +0 -0
  261. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po +729 -522
  262. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.js +1 -1
  263. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.mo +0 -0
  264. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.po +700 -493
  265. sphinx/roles.py +1 -1
  266. sphinx/search/__init__.py +17 -9
  267. sphinx/templates/quickstart/{root_doc.rst_t → root_doc.rst.jinja} +7 -10
  268. sphinx/testing/fixtures.py +22 -20
  269. sphinx/testing/path.py +6 -2
  270. sphinx/testing/util.py +8 -13
  271. sphinx/texinputs/sphinx.sty +449 -332
  272. sphinx/texinputs/sphinxlatexadmonitions.sty +209 -66
  273. sphinx/texinputs/sphinxlatexliterals.sty +9 -16
  274. sphinx/texinputs/sphinxlatexstyletext.sty +4 -38
  275. sphinx/texinputs/sphinxlatextables.sty +6 -14
  276. sphinx/texinputs/sphinxpackageboxes.sty +15 -42
  277. sphinx/texinputs/sphinxpackagefootnote.sty +4 -3
  278. sphinx/themes/agogo/layout.html +3 -3
  279. sphinx/themes/basic/genindex-single.html +2 -1
  280. sphinx/themes/basic/layout.html +3 -6
  281. sphinx/themes/basic/static/searchtools.js +4 -3
  282. sphinx/themes/haiku/layout.html +4 -4
  283. sphinx/themes/pyramid/layout.html +1 -1
  284. sphinx/themes/scrolls/layout.html +2 -2
  285. sphinx/theming.py +42 -7
  286. sphinx/transforms/__init__.py +34 -20
  287. sphinx/transforms/i18n.py +8 -7
  288. sphinx/transforms/post_transforms/__init__.py +1 -1
  289. sphinx/transforms/post_transforms/images.py +7 -10
  290. sphinx/util/_pathlib.py +2 -2
  291. sphinx/util/cfamily.py +52 -30
  292. sphinx/util/console.py +1 -1
  293. sphinx/util/display.py +16 -11
  294. sphinx/util/docutils.py +88 -40
  295. sphinx/util/fileutil.py +15 -3
  296. sphinx/util/images.py +1 -0
  297. sphinx/util/inspect.py +66 -22
  298. sphinx/util/inventory.py +15 -0
  299. sphinx/util/logging.py +14 -21
  300. sphinx/util/math.py +3 -1
  301. sphinx/util/nodes.py +9 -12
  302. sphinx/util/osutil.py +5 -5
  303. sphinx/util/parsing.py +93 -0
  304. sphinx/util/tags.py +71 -47
  305. sphinx/util/typing.py +261 -143
  306. sphinx/versioning.py +17 -17
  307. sphinx/writers/html5.py +26 -19
  308. sphinx/writers/latex.py +58 -28
  309. sphinx/writers/manpage.py +4 -3
  310. sphinx/writers/texinfo.py +19 -14
  311. {sphinx-7.3.6.dist-info → sphinx-7.4.0.dist-info}/METADATA +21 -20
  312. sphinx-7.4.0.dist-info/RECORD +591 -0
  313. sphinx-7.3.6.dist-info/RECORD +0 -581
  314. /sphinx/templates/apidoc/{module.rst_t → module.rst.jinja} +0 -0
  315. /sphinx/templates/apidoc/{package.rst_t → package.rst.jinja} +0 -0
  316. /sphinx/templates/apidoc/{toc.rst_t → toc.rst.jinja} +0 -0
  317. /sphinx/templates/epub3/{content.opf_t → content.opf.jinja} +0 -0
  318. /sphinx/templates/epub3/{nav.xhtml_t → nav.xhtml.jinja} +0 -0
  319. /sphinx/templates/epub3/{toc.ncx_t → toc.ncx.jinja} +0 -0
  320. /sphinx/templates/gettext/{message.pot_t → message.pot.jinja} +0 -0
  321. /sphinx/templates/imgmath/{preview.tex_t → preview.tex.jinja} +0 -0
  322. /sphinx/templates/imgmath/{template.tex_t → template.tex.jinja} +0 -0
  323. /sphinx/templates/latex/{latex.tex_t → latex.tex.jinja} +0 -0
  324. /sphinx/templates/latex/{longtable.tex_t → longtable.tex.jinja} +0 -0
  325. /sphinx/templates/latex/{sphinxmessages.sty_t → sphinxmessages.sty.jinja} +0 -0
  326. /sphinx/templates/latex/{tabular.tex_t → tabular.tex.jinja} +0 -0
  327. /sphinx/templates/latex/{tabulary.tex_t → tabulary.tex.jinja} +0 -0
  328. /sphinx/templates/quickstart/{Makefile_t → Makefile.jinja} +0 -0
  329. /sphinx/templates/quickstart/{Makefile.new_t → Makefile.new.jinja} +0 -0
  330. /sphinx/templates/quickstart/{conf.py_t → conf.py.jinja} +0 -0
  331. /sphinx/templates/quickstart/{make.bat_t → make.bat.jinja} +0 -0
  332. /sphinx/templates/quickstart/{make.bat.new_t → make.bat.new.jinja} +0 -0
  333. /sphinx/texinputs/{Makefile_t → Makefile.jinja} +0 -0
  334. /sphinx/texinputs/{latexmkjarc_t → latexmkjarc.jinja} +0 -0
  335. /sphinx/texinputs/{latexmkrc_t → latexmkrc.jinja} +0 -0
  336. /sphinx/texinputs/{make.bat_t → make.bat.jinja} +0 -0
  337. /sphinx/texinputs_win/{Makefile_t → Makefile.jinja} +0 -0
  338. /sphinx/themes/agogo/static/{agogo.css_t → agogo.css.jinja} +0 -0
  339. /sphinx/themes/basic/static/{basic.css_t → basic.css.jinja} +0 -0
  340. /sphinx/themes/basic/static/{documentation_options.js_t → documentation_options.js.jinja} +0 -0
  341. /sphinx/themes/basic/static/{language_data.js_t → language_data.js.jinja} +0 -0
  342. /sphinx/themes/bizstyle/static/{bizstyle.css_t → bizstyle.css.jinja} +0 -0
  343. /sphinx/themes/bizstyle/static/{bizstyle.js_t → bizstyle.js.jinja} +0 -0
  344. /sphinx/themes/classic/static/{classic.css_t → classic.css.jinja} +0 -0
  345. /sphinx/themes/classic/static/{sidebar.js_t → sidebar.js.jinja} +0 -0
  346. /sphinx/themes/epub/static/{epub.css_t → epub.css.jinja} +0 -0
  347. /sphinx/themes/haiku/static/{haiku.css_t → haiku.css.jinja} +0 -0
  348. /sphinx/themes/nature/static/{nature.css_t → nature.css.jinja} +0 -0
  349. /sphinx/themes/nonav/static/{nonav.css_t → nonav.css.jinja} +0 -0
  350. /sphinx/themes/pyramid/static/{epub.css_t → epub.css.jinja} +0 -0
  351. /sphinx/themes/pyramid/static/{pyramid.css_t → pyramid.css.jinja} +0 -0
  352. /sphinx/themes/scrolls/static/{scrolls.css_t → scrolls.css.jinja} +0 -0
  353. /sphinx/themes/sphinxdoc/static/{sphinxdoc.css_t → sphinxdoc.css.jinja} +0 -0
  354. /sphinx/themes/traditional/static/{traditional.css_t → traditional.css.jinja} +0 -0
  355. {sphinx-7.3.6.dist-info → sphinx-7.4.0.dist-info}/LICENSE.rst +0 -0
  356. {sphinx-7.3.6.dist-info → sphinx-7.4.0.dist-info}/WHEEL +0 -0
  357. {sphinx-7.3.6.dist-info → sphinx-7.4.0.dist-info}/entry_points.txt +0 -0
sphinx/util/typing.py CHANGED
@@ -8,23 +8,45 @@ import typing
8
8
  from collections.abc import Sequence
9
9
  from contextvars import Context, ContextVar, Token
10
10
  from struct import Struct
11
- from typing import TYPE_CHECKING, Any, Callable, ForwardRef, TypedDict, TypeVar, Union
11
+ from typing import (
12
+ TYPE_CHECKING,
13
+ Annotated,
14
+ Any,
15
+ Callable,
16
+ ForwardRef,
17
+ TypedDict,
18
+ TypeVar,
19
+ Union,
20
+ )
12
21
 
13
22
  from docutils import nodes
14
23
  from docutils.parsers.rst.states import Inliner
15
24
 
16
25
  if TYPE_CHECKING:
17
- import enum
26
+ from collections.abc import Mapping
27
+ from typing import Final, Literal, Protocol
28
+
29
+ from typing_extensions import TypeAlias, TypeIs
18
30
 
19
31
  from sphinx.application import Sphinx
20
32
 
33
+ _RestifyMode: TypeAlias = Literal[
34
+ 'fully-qualified-except-typing',
35
+ 'smart',
36
+ ]
37
+ _StringifyMode: TypeAlias = Literal[
38
+ 'fully-qualified-except-typing',
39
+ 'fully-qualified',
40
+ 'smart',
41
+ ]
42
+
21
43
  if sys.version_info >= (3, 10):
22
44
  from types import UnionType
23
45
  else:
24
46
  UnionType = None
25
47
 
26
48
  # classes that have an incorrect .__module__ attribute
27
- _INVALID_BUILTIN_CLASSES = {
49
+ _INVALID_BUILTIN_CLASSES: Final[Mapping[object, str]] = {
28
50
  Context: 'contextvars.Context', # Context.__module__ == '_contextvars'
29
51
  ContextVar: 'contextvars.ContextVar', # ContextVar.__module__ == '_contextvars'
30
52
  Token: 'contextvars.Token', # Token.__module__ == '_contextvars'
@@ -71,8 +93,25 @@ NoneType = type(None)
71
93
  PathMatcher = Callable[[str], bool]
72
94
 
73
95
  # common role functions
74
- RoleFunction = Callable[[str, str, str, int, Inliner, dict[str, Any], Sequence[str]],
75
- tuple[list[nodes.Node], list[nodes.system_message]]]
96
+ if TYPE_CHECKING:
97
+ class RoleFunction(Protocol):
98
+ def __call__(
99
+ self,
100
+ name: str,
101
+ rawtext: str,
102
+ text: str,
103
+ lineno: int,
104
+ inliner: Inliner,
105
+ /,
106
+ options: dict[str, Any] | None = None,
107
+ content: Sequence[str] = (),
108
+ ) -> tuple[list[nodes.Node], list[nodes.system_message]]:
109
+ ...
110
+ else:
111
+ RoleFunction = Callable[
112
+ [str, str, str, int, Inliner, dict[str, Any], Sequence[str]],
113
+ tuple[list[nodes.Node], list[nodes.system_message]],
114
+ ]
76
115
 
77
116
  # A option spec for directive
78
117
  OptionSpec = dict[str, Callable[[str], Any]]
@@ -115,7 +154,9 @@ if TYPE_CHECKING:
115
154
 
116
155
 
117
156
  def get_type_hints(
118
- obj: Any, globalns: dict[str, Any] | None = None, localns: dict[str, Any] | None = None,
157
+ obj: Any,
158
+ globalns: dict[str, Any] | None = None,
159
+ localns: dict[str, Any] | None = None,
119
160
  ) -> dict[str, Any]:
120
161
  """Return a dictionary containing type hints for a function, method, module or class
121
162
  object.
@@ -147,8 +188,36 @@ def is_system_TypeVar(typ: Any) -> bool:
147
188
  return modname == 'typing' and isinstance(typ, TypeVar)
148
189
 
149
190
 
150
- def restify(cls: type | None, mode: str = 'fully-qualified-except-typing') -> str:
151
- """Convert python class to a reST reference.
191
+ def _is_annotated_form(obj: Any) -> TypeIs[Annotated[Any, ...]]:
192
+ """Check if *obj* is an annotated type."""
193
+ return typing.get_origin(obj) is Annotated or str(obj).startswith('typing.Annotated')
194
+
195
+
196
+ def _is_unpack_form(obj: Any) -> bool:
197
+ """Check if the object is :class:`typing.Unpack` or equivalent."""
198
+ if sys.version_info >= (3, 11):
199
+ from typing import Unpack
200
+
201
+ # typing_extensions.Unpack != typing.Unpack for 3.11, but we assume
202
+ # that typing_extensions.Unpack should not be used in that case
203
+ return typing.get_origin(obj) is Unpack
204
+
205
+ # 3.9 and 3.10 require typing_extensions.Unpack
206
+ origin = typing.get_origin(obj)
207
+ return (
208
+ getattr(origin, '__module__', None) == 'typing_extensions'
209
+ and _typing_internal_name(origin) == 'Unpack'
210
+ )
211
+
212
+
213
+ def _typing_internal_name(obj: Any) -> str | None:
214
+ if sys.version_info[:2] >= (3, 10):
215
+ return obj.__name__
216
+ return getattr(obj, '_name', None)
217
+
218
+
219
+ def restify(cls: Any, mode: _RestifyMode = 'fully-qualified-except-typing') -> str:
220
+ """Convert a type-like object to a reST reference.
152
221
 
153
222
  :param mode: Specify a method how annotations will be stringified.
154
223
 
@@ -161,31 +230,53 @@ def restify(cls: type | None, mode: str = 'fully-qualified-except-typing') -> st
161
230
  from sphinx.ext.autodoc.mock import ismock, ismockmodule # lazy loading
162
231
  from sphinx.util import inspect # lazy loading
163
232
 
164
- if mode == 'smart':
165
- modprefix = '~'
166
- else:
167
- modprefix = ''
233
+ valid_modes = {'fully-qualified-except-typing', 'smart'}
234
+ if mode not in valid_modes:
235
+ valid = ', '.join(map(repr, sorted(valid_modes)))
236
+ msg = f'mode must be one of {valid}; got {mode!r}'
237
+ raise ValueError(msg)
238
+
239
+ # things that are not types
240
+ if cls in {None, NoneType}:
241
+ return ':py:obj:`None`'
242
+ if cls is Ellipsis:
243
+ return '...'
244
+ if isinstance(cls, str):
245
+ return cls
246
+
247
+ cls_module_is_typing = getattr(cls, '__module__', '') == 'typing'
248
+
249
+ # If the mode is 'smart', we always use '~'.
250
+ # If the mode is 'fully-qualified-except-typing',
251
+ # we use '~' only for the objects in the ``typing`` module.
252
+ module_prefix = '~' if mode == 'smart' or cls_module_is_typing else ''
168
253
 
169
254
  try:
170
- if cls is None or cls is NoneType:
171
- return ':py:obj:`None`'
172
- elif cls is Ellipsis:
173
- return '...'
174
- elif isinstance(cls, str):
175
- return cls
176
- elif ismockmodule(cls):
177
- return f':py:class:`{modprefix}{cls.__name__}`'
255
+ if ismockmodule(cls):
256
+ return f':py:class:`{module_prefix}{cls.__name__}`'
178
257
  elif ismock(cls):
179
- return f':py:class:`{modprefix}{cls.__module__}.{cls.__name__}`'
258
+ return f':py:class:`{module_prefix}{cls.__module__}.{cls.__name__}`'
180
259
  elif is_invalid_builtin_class(cls):
181
- return f':py:class:`{modprefix}{_INVALID_BUILTIN_CLASSES[cls]}`'
260
+ # The above predicate never raises TypeError but should not be
261
+ # evaluated before determining whether *cls* is a mocked object
262
+ # or not; instead of two try-except blocks, we keep it here.
263
+ return f':py:class:`{module_prefix}{_INVALID_BUILTIN_CLASSES[cls]}`'
264
+ elif _is_annotated_form(cls):
265
+ args = restify(cls.__args__[0], mode)
266
+ meta = ', '.join(map(repr, cls.__metadata__))
267
+ if sys.version_info[:2] <= (3, 11):
268
+ # Hardcoded to fix errors on Python 3.11 and earlier.
269
+ return fr':py:class:`~typing.Annotated`\ [{args}, {meta}]'
270
+ return (f':py:class:`{module_prefix}{cls.__module__}.{cls.__name__}`'
271
+ fr'\ [{args}, {meta}]')
182
272
  elif inspect.isNewType(cls):
183
273
  if sys.version_info[:2] >= (3, 10):
184
274
  # newtypes have correct module info since Python 3.10+
185
- return f':py:class:`{modprefix}{cls.__module__}.{cls.__name__}`'
186
- else:
187
- return f':py:class:`{cls.__name__}`'
275
+ return f':py:class:`{module_prefix}{cls.__module__}.{cls.__name__}`'
276
+ return f':py:class:`{cls.__name__}`'
188
277
  elif UnionType and isinstance(cls, UnionType):
278
+ # Union types (PEP 585) retain their definition order when they
279
+ # are printed natively and ``None``-like types are kept as is.
189
280
  return ' | '.join(restify(a, mode) for a in cls.__args__)
190
281
  elif cls.__module__ in ('__builtin__', 'builtins'):
191
282
  if hasattr(cls, '__args__'):
@@ -194,73 +285,85 @@ def restify(cls: type | None, mode: str = 'fully-qualified-except-typing') -> st
194
285
 
195
286
  concatenated_args = ', '.join(restify(arg, mode) for arg in cls.__args__)
196
287
  return fr':py:class:`{cls.__name__}`\ [{concatenated_args}]'
197
- else:
198
- return f':py:class:`{cls.__name__}`'
288
+ return f':py:class:`{cls.__name__}`'
199
289
  elif (inspect.isgenericalias(cls)
200
- and cls.__module__ == 'typing'
201
- and cls.__origin__ is Union): # type: ignore[attr-defined]
202
- return ' | '.join(restify(a, mode) for a in cls.__args__) # type: ignore[attr-defined]
290
+ and cls_module_is_typing
291
+ and cls.__origin__ is Union):
292
+ # *cls* is defined in ``typing``, and thus ``__args__`` must exist
293
+ return ' | '.join(restify(a, mode) for a in cls.__args__)
203
294
  elif inspect.isgenericalias(cls):
204
- if isinstance(cls.__origin__, typing._SpecialForm): # type: ignore[attr-defined]
205
- text = restify(cls.__origin__, mode) # type: ignore[attr-defined,arg-type]
206
- elif getattr(cls, '_name', None):
207
- cls_name = cls._name # type: ignore[attr-defined]
208
- if cls.__module__ == 'typing':
209
- text = f':py:class:`~{cls.__module__}.{cls_name}`'
210
- else:
211
- text = f':py:class:`{modprefix}{cls.__module__}.{cls_name}`'
295
+ # A generic alias always has an __origin__, but it is difficult to
296
+ # use a type guard on inspect.isgenericalias()
297
+ # (ideally, we would use ``TypeIs`` introduced in Python 3.13).
298
+ cls_name = _typing_internal_name(cls)
299
+
300
+ if isinstance(cls.__origin__, typing._SpecialForm):
301
+ # ClassVar; Concatenate; Final; Literal; Unpack; TypeGuard; TypeIs
302
+ # Required/NotRequired
303
+ text = restify(cls.__origin__, mode)
304
+ elif cls_name:
305
+ text = f':py:class:`{module_prefix}{cls.__module__}.{cls_name}`'
212
306
  else:
213
- text = restify(cls.__origin__, mode) # type: ignore[attr-defined]
214
-
215
- origin = getattr(cls, '__origin__', None)
216
- if not hasattr(cls, '__args__'): # NoQA: SIM114
217
- pass
218
- elif all(is_system_TypeVar(a) for a in cls.__args__):
219
- # Suppress arguments if all system defined TypeVars (ex. Dict[KT, VT])
220
- pass
221
- elif (cls.__module__ == 'typing'
222
- and cls._name == 'Callable'): # type: ignore[attr-defined]
223
- args = ', '.join(restify(a, mode) for a in cls.__args__[:-1])
224
- text += fr"\ [[{args}], {restify(cls.__args__[-1], mode)}]"
225
- elif cls.__module__ == 'typing' and getattr(origin, '_name', None) == 'Literal':
226
- literal_args = []
227
- for a in cls.__args__:
228
- if inspect.isenumattribute(a):
229
- literal_args.append(_format_literal_enum_arg(a, mode=mode))
230
- else:
231
- literal_args.append(repr(a))
232
- text += fr"\ [{', '.join(literal_args)}]"
233
- del literal_args
234
- elif cls.__args__:
235
- text += fr"\ [{', '.join(restify(a, mode) for a in cls.__args__)}]"
236
-
237
- return text
307
+ text = restify(cls.__origin__, mode)
308
+
309
+ __args__ = getattr(cls, '__args__', ())
310
+ if not __args__:
311
+ return text
312
+ if all(map(is_system_TypeVar, __args__)):
313
+ # Don't print the arguments; they're all system defined type variables.
314
+ return text
315
+
316
+ # Callable has special formatting
317
+ if (
318
+ (cls_module_is_typing and _typing_internal_name(cls) == 'Callable')
319
+ or (cls.__module__ == 'collections.abc' and cls.__name__ == 'Callable')
320
+ ):
321
+ args = ', '.join(restify(a, mode) for a in __args__[:-1])
322
+ returns = restify(__args__[-1], mode)
323
+ return fr'{text}\ [[{args}], {returns}]'
324
+
325
+ if cls_module_is_typing and _typing_internal_name(cls.__origin__) == 'Literal':
326
+ args = ', '.join(_format_literal_arg_restify(a, mode=mode)
327
+ for a in cls.__args__)
328
+ return fr'{text}\ [{args}]'
329
+
330
+ # generic representation of the parameters
331
+ args = ', '.join(restify(a, mode) for a in __args__)
332
+ return fr'{text}\ [{args}]'
238
333
  elif isinstance(cls, typing._SpecialForm):
239
- return f':py:obj:`~{cls.__module__}.{cls._name}`'
334
+ cls_name = _typing_internal_name(cls)
335
+ return f':py:obj:`~{cls.__module__}.{cls_name}`'
240
336
  elif sys.version_info[:2] >= (3, 11) and cls is typing.Any:
241
337
  # handle bpo-46998
242
338
  return f':py:obj:`~{cls.__module__}.{cls.__name__}`'
243
339
  elif hasattr(cls, '__qualname__'):
244
- if cls.__module__ == 'typing':
245
- return f':py:class:`~{cls.__module__}.{cls.__qualname__}`'
246
- else:
247
- return f':py:class:`{modprefix}{cls.__module__}.{cls.__qualname__}`'
340
+ return f':py:class:`{module_prefix}{cls.__module__}.{cls.__qualname__}`'
248
341
  elif isinstance(cls, ForwardRef):
249
342
  return f':py:class:`{cls.__forward_arg__}`'
250
343
  else:
251
- # not a class (ex. TypeVar)
252
- if cls.__module__ == 'typing':
253
- return f':py:obj:`~{cls.__module__}.{cls.__name__}`'
254
- else:
255
- return f':py:obj:`{modprefix}{cls.__module__}.{cls.__name__}`'
344
+ # not a class (ex. TypeVar) but should have a __name__
345
+ return f':py:obj:`{module_prefix}{cls.__module__}.{cls.__name__}`'
256
346
  except (AttributeError, TypeError):
257
347
  return inspect.object_description(cls)
258
348
 
259
349
 
350
+ def _format_literal_arg_restify(arg: Any, /, *, mode: str) -> str:
351
+ from sphinx.util.inspect import isenumattribute # lazy loading
352
+
353
+ if isenumattribute(arg):
354
+ enum_cls = arg.__class__
355
+ if mode == 'smart' or enum_cls.__module__ == 'typing':
356
+ # MyEnum.member
357
+ return f':py:attr:`~{enum_cls.__module__}.{enum_cls.__qualname__}.{arg.name}`'
358
+ # module.MyEnum.member
359
+ return f':py:attr:`{enum_cls.__module__}.{enum_cls.__qualname__}.{arg.name}`'
360
+ return repr(arg)
361
+
362
+
260
363
  def stringify_annotation(
261
364
  annotation: Any,
262
365
  /,
263
- mode: str = 'fully-qualified-except-typing',
366
+ mode: _StringifyMode = 'fully-qualified-except-typing',
264
367
  ) -> str:
265
368
  """Stringify type annotation object.
266
369
 
@@ -278,69 +381,76 @@ def stringify_annotation(
278
381
  from sphinx.ext.autodoc.mock import ismock, ismockmodule # lazy loading
279
382
  from sphinx.util.inspect import isNewType # lazy loading
280
383
 
281
- if mode not in {'fully-qualified-except-typing', 'fully-qualified', 'smart'}:
282
- msg = ("'mode' must be one of 'fully-qualified-except-typing', "
283
- f"'fully-qualified', or 'smart'; got {mode!r}.")
384
+ valid_modes = {'fully-qualified-except-typing', 'fully-qualified', 'smart'}
385
+ if mode not in valid_modes:
386
+ valid = ', '.join(map(repr, sorted(valid_modes)))
387
+ msg = f'mode must be one of {valid}; got {mode!r}'
284
388
  raise ValueError(msg)
285
389
 
286
- if mode == 'smart':
287
- module_prefix = '~'
288
- else:
289
- module_prefix = ''
290
-
291
- annotation_qualname = getattr(annotation, '__qualname__', '')
292
- annotation_module = getattr(annotation, '__module__', '')
293
- annotation_name = getattr(annotation, '__name__', '')
294
- annotation_module_is_typing = annotation_module == 'typing'
295
-
390
+ # things that are not types
391
+ if annotation in {None, NoneType}:
392
+ return 'None'
393
+ if annotation is Ellipsis:
394
+ return '...'
296
395
  if isinstance(annotation, str):
297
396
  if annotation.startswith("'") and annotation.endswith("'"):
298
- # might be a double Forward-ref'ed type. Go unquoting.
397
+ # Might be a double Forward-ref'ed type. Go unquoting.
299
398
  return annotation[1:-1]
300
- else:
301
- return annotation
302
- elif isinstance(annotation, TypeVar):
399
+ return annotation
400
+ if not annotation:
401
+ return repr(annotation)
402
+
403
+ module_prefix = '~' if mode == 'smart' else ''
404
+
405
+ # The values below must be strings if the objects are well-formed.
406
+ annotation_qualname: str = getattr(annotation, '__qualname__', '')
407
+ annotation_module: str = getattr(annotation, '__module__', '')
408
+ annotation_name: str = getattr(annotation, '__name__', '')
409
+ annotation_module_is_typing = annotation_module == 'typing'
410
+
411
+ # Extract the annotation's base type by considering formattable cases
412
+ if isinstance(annotation, TypeVar) and not _is_unpack_form(annotation):
413
+ # typing_extensions.Unpack is incorrectly determined as a TypeVar
303
414
  if annotation_module_is_typing and mode in {'fully-qualified-except-typing', 'smart'}:
304
415
  return annotation_name
305
- else:
306
- return module_prefix + f'{annotation_module}.{annotation_name}'
416
+ return module_prefix + f'{annotation_module}.{annotation_name}'
307
417
  elif isNewType(annotation):
308
418
  if sys.version_info[:2] >= (3, 10):
309
419
  # newtypes have correct module info since Python 3.10+
310
420
  return module_prefix + f'{annotation_module}.{annotation_name}'
311
- else:
312
- return annotation_name
313
- elif not annotation:
314
- return repr(annotation)
315
- elif annotation is NoneType:
316
- return 'None'
421
+ return annotation_name
317
422
  elif ismockmodule(annotation):
318
423
  return module_prefix + annotation_name
319
424
  elif ismock(annotation):
320
425
  return module_prefix + f'{annotation_module}.{annotation_name}'
321
426
  elif is_invalid_builtin_class(annotation):
322
427
  return module_prefix + _INVALID_BUILTIN_CLASSES[annotation]
323
- elif str(annotation).startswith('typing.Annotated'): # for py310+
428
+ elif _is_annotated_form(annotation): # for py39+
324
429
  pass
325
430
  elif annotation_module == 'builtins' and annotation_qualname:
326
- if (args := getattr(annotation, '__args__', None)) is not None: # PEP 585 generic
327
- if not args: # Empty tuple, list, ...
328
- return repr(annotation)
329
-
330
- concatenated_args = ', '.join(stringify_annotation(arg, mode) for arg in args)
331
- return f'{annotation_qualname}[{concatenated_args}]'
332
- else:
431
+ args = getattr(annotation, '__args__', None)
432
+ if args is None:
333
433
  return annotation_qualname
334
- elif annotation is Ellipsis:
335
- return '...'
434
+
435
+ # PEP 585 generic
436
+ if not args: # Empty tuple, list, ...
437
+ return repr(annotation)
438
+
439
+ concatenated_args = ', '.join(stringify_annotation(arg, mode) for arg in args)
440
+ return f'{annotation_qualname}[{concatenated_args}]'
441
+ else:
442
+ # add other special cases that can be directly formatted
443
+ pass
336
444
 
337
445
  module_prefix = f'{annotation_module}.'
338
- annotation_forward_arg = getattr(annotation, '__forward_arg__', None)
446
+ annotation_forward_arg: str | None = getattr(annotation, '__forward_arg__', None)
339
447
  if annotation_qualname or (annotation_module_is_typing and not annotation_forward_arg):
340
448
  if mode == 'smart':
341
- module_prefix = '~' + module_prefix
449
+ module_prefix = f'~{module_prefix}'
342
450
  if annotation_module_is_typing and mode == 'fully-qualified-except-typing':
343
451
  module_prefix = ''
452
+ elif _is_unpack_form(annotation) and annotation_module == 'typing_extensions':
453
+ module_prefix = '~' if mode == 'smart' else ''
344
454
  else:
345
455
  module_prefix = ''
346
456
 
@@ -349,12 +459,13 @@ def stringify_annotation(
349
459
  # handle ForwardRefs
350
460
  qualname = annotation_forward_arg
351
461
  else:
352
- _name = getattr(annotation, '_name', '')
353
- if _name:
354
- qualname = _name
462
+ if internal_name := _typing_internal_name(annotation):
463
+ qualname = internal_name
355
464
  elif annotation_qualname:
356
465
  qualname = annotation_qualname
357
466
  else:
467
+ # in this case, we know that the annotation is a member
468
+ # of ``typing`` and all of them define ``__origin__``
358
469
  qualname = stringify_annotation(
359
470
  annotation.__origin__, 'fully-qualified-except-typing',
360
471
  ).replace('typing.', '') # ex. Union
@@ -370,36 +481,38 @@ def stringify_annotation(
370
481
  # only make them appear twice
371
482
  return repr(annotation)
372
483
 
373
- annotation_args = getattr(annotation, '__args__', None)
374
- if annotation_args:
375
- if not isinstance(annotation_args, (list, tuple)):
376
- # broken __args__ found
377
- pass
378
- elif qualname in {'Optional', 'Union', 'types.UnionType'}:
484
+ # Process the generic arguments (if any).
485
+ # They must be a list or a tuple, otherwise they are considered 'broken'.
486
+ annotation_args = getattr(annotation, '__args__', ())
487
+ if annotation_args and isinstance(annotation_args, (list, tuple)):
488
+ if (
489
+ qualname in {'Union', 'types.UnionType'}
490
+ and all(getattr(a, '__origin__', ...) is typing.Literal for a in annotation_args)
491
+ ):
492
+ # special case to flatten a Union of Literals into a literal
493
+ flattened_args = typing.Literal[annotation_args].__args__ # type: ignore[attr-defined]
494
+ args = ', '.join(_format_literal_arg_stringify(a, mode=mode)
495
+ for a in flattened_args)
496
+ return f'{module_prefix}Literal[{args}]'
497
+ if qualname in {'Optional', 'Union', 'types.UnionType'}:
379
498
  return ' | '.join(stringify_annotation(a, mode) for a in annotation_args)
380
499
  elif qualname == 'Callable':
381
500
  args = ', '.join(stringify_annotation(a, mode) for a in annotation_args[:-1])
382
501
  returns = stringify_annotation(annotation_args[-1], mode)
383
502
  return f'{module_prefix}Callable[[{args}], {returns}]'
384
503
  elif qualname == 'Literal':
385
- from sphinx.util.inspect import isenumattribute # lazy loading
386
-
387
- def format_literal_arg(arg: Any) -> str:
388
- if isenumattribute(arg):
389
- enumcls = arg.__class__
390
-
391
- if mode == 'smart':
392
- # MyEnum.member
393
- return f'{enumcls.__qualname__}.{arg.name}'
394
-
395
- # module.MyEnum.member
396
- return f'{enumcls.__module__}.{enumcls.__qualname__}.{arg.name}'
397
- return repr(arg)
398
-
399
- args = ', '.join(map(format_literal_arg, annotation_args))
504
+ args = ', '.join(_format_literal_arg_stringify(a, mode=mode)
505
+ for a in annotation_args)
400
506
  return f'{module_prefix}Literal[{args}]'
401
- elif str(annotation).startswith('typing.Annotated'): # for py39+
402
- return stringify_annotation(annotation_args[0], mode)
507
+ elif _is_annotated_form(annotation): # for py39+
508
+ args = stringify_annotation(annotation_args[0], mode)
509
+ meta = ', '.join(map(repr, annotation.__metadata__))
510
+ if sys.version_info[:2] <= (3, 11):
511
+ if mode == 'fully-qualified-except-typing':
512
+ return f'Annotated[{args}, {meta}]'
513
+ module_prefix = module_prefix.replace('builtins', 'typing')
514
+ return f'{module_prefix}Annotated[{args}, {meta}]'
515
+ return f'{module_prefix}Annotated[{args}, {meta}]'
403
516
  elif all(is_system_TypeVar(a) for a in annotation_args):
404
517
  # Suppress arguments if all system defined TypeVars (ex. Dict[KT, VT])
405
518
  return module_prefix + qualname
@@ -410,12 +523,17 @@ def stringify_annotation(
410
523
  return module_prefix + qualname
411
524
 
412
525
 
413
- def _format_literal_enum_arg(arg: enum.Enum, /, *, mode: str) -> str:
414
- enum_cls = arg.__class__
415
- if mode == 'smart' or enum_cls.__module__ == 'typing':
416
- return f':py:attr:`~{enum_cls.__module__}.{enum_cls.__qualname__}.{arg.name}`'
417
- else:
418
- return f':py:attr:`{enum_cls.__module__}.{enum_cls.__qualname__}.{arg.name}`'
526
+ def _format_literal_arg_stringify(arg: Any, /, *, mode: str) -> str:
527
+ from sphinx.util.inspect import isenumattribute # lazy loading
528
+
529
+ if isenumattribute(arg):
530
+ enum_cls = arg.__class__
531
+ if mode == 'smart' or enum_cls.__module__ == 'typing':
532
+ # MyEnum.member
533
+ return f'{enum_cls.__qualname__}.{arg.name}'
534
+ # module.MyEnum.member
535
+ return f'{enum_cls.__module__}.{enum_cls.__qualname__}.{arg.name}'
536
+ return repr(arg)
419
537
 
420
538
 
421
539
  # deprecated name -> (object to return, canonical path or empty string, removal version)
sphinx/versioning.py CHANGED
@@ -12,7 +12,7 @@ from uuid import uuid4
12
12
  from sphinx.transforms import SphinxTransform
13
13
 
14
14
  if TYPE_CHECKING:
15
- from collections.abc import Iterator
15
+ from collections.abc import Callable, Iterator
16
16
 
17
17
  from docutils.nodes import Node
18
18
 
@@ -30,7 +30,7 @@ except ImportError:
30
30
  VERSIONING_RATIO = 65
31
31
 
32
32
 
33
- def add_uids(doctree: Node, condition: Any) -> Iterator[Node]:
33
+ def add_uids(doctree: Node, condition: Callable[[Node], bool]) -> Iterator[Node]:
34
34
  """Add a unique id to every node in the `doctree` which matches the
35
35
  condition and yield the nodes.
36
36
 
@@ -41,11 +41,11 @@ def add_uids(doctree: Node, condition: Any) -> Iterator[Node]:
41
41
  A callable which returns either ``True`` or ``False`` for a given node.
42
42
  """
43
43
  for node in doctree.findall(condition):
44
- node.uid = uuid4().hex
44
+ node.uid = uuid4().hex # type: ignore[attr-defined]
45
45
  yield node
46
46
 
47
47
 
48
- def merge_doctrees(old: Node, new: Node, condition: Any) -> Iterator[Node]:
48
+ def merge_doctrees(old: Node, new: Node, condition: Callable[[Node], bool]) -> Iterator[Node]:
49
49
  """Merge the `old` doctree with the `new` one while looking at nodes
50
50
  matching the `condition`.
51
51
 
@@ -68,13 +68,13 @@ def merge_doctrees(old: Node, new: Node, condition: Any) -> Iterator[Node]:
68
68
  continue
69
69
  if not getattr(old_node, 'uid', None):
70
70
  # maybe config.gettext_uuid has been changed.
71
- old_node.uid = uuid4().hex
71
+ old_node.uid = uuid4().hex # type: ignore[union-attr]
72
72
  if new_node is None:
73
73
  old_nodes.append(old_node)
74
74
  continue
75
- ratio = get_ratio(old_node.rawsource, new_node.rawsource)
75
+ ratio = get_ratio(old_node.rawsource, new_node.rawsource) # type: ignore[union-attr]
76
76
  if ratio == 0:
77
- new_node.uid = old_node.uid
77
+ new_node.uid = old_node.uid # type: ignore[union-attr]
78
78
  seen.add(new_node)
79
79
  else:
80
80
  ratios[old_node, new_node] = ratio
@@ -85,30 +85,29 @@ def merge_doctrees(old: Node, new: Node, condition: Any) -> Iterator[Node]:
85
85
  for old_node, new_node in product(old_nodes, new_nodes):
86
86
  if new_node in seen or (old_node, new_node) in ratios:
87
87
  continue
88
- ratio = get_ratio(old_node.rawsource, new_node.rawsource)
88
+ ratio = get_ratio(old_node.rawsource, new_node.rawsource) # type: ignore[union-attr]
89
89
  if ratio == 0:
90
- new_node.uid = old_node.uid
90
+ new_node.uid = old_node.uid # type: ignore[union-attr]
91
91
  seen.add(new_node)
92
92
  else:
93
93
  ratios[old_node, new_node] = ratio
94
94
  # choose the old node with the best ratio for each new node and set the uid
95
95
  # as long as the ratio is under a certain value, in which case we consider
96
96
  # them not changed but different
97
- ratios = sorted(ratios.items(), key=itemgetter(1)) # type: ignore[assignment]
98
- for (old_node, new_node), ratio in ratios:
97
+ for (old_node, new_node), ratio in sorted(ratios.items(), key=itemgetter(1)):
99
98
  if new_node in seen:
100
99
  continue
101
100
  else:
102
101
  seen.add(new_node)
103
102
  if ratio < VERSIONING_RATIO:
104
- new_node.uid = old_node.uid
103
+ new_node.uid = old_node.uid # type: ignore[union-attr]
105
104
  else:
106
- new_node.uid = uuid4().hex
105
+ new_node.uid = uuid4().hex # type: ignore[union-attr]
107
106
  yield new_node
108
107
  # create new uuids for any new node we left out earlier, this happens
109
108
  # if one or more nodes are simply added.
110
109
  for new_node in set(new_nodes) - seen:
111
- new_node.uid = uuid4().hex
110
+ new_node.uid = uuid4().hex # type: ignore[union-attr]
112
111
  yield new_node
113
112
 
114
113
 
@@ -153,7 +152,8 @@ class UIDTransform(SphinxTransform):
153
152
  def apply(self, **kwargs: Any) -> None:
154
153
  env = self.env
155
154
  old_doctree = None
156
- if not env.versioning_condition:
155
+ versioning_condition = env.versioning_condition
156
+ if not versioning_condition:
157
157
  return
158
158
 
159
159
  if env.versioning_compare:
@@ -167,9 +167,9 @@ class UIDTransform(SphinxTransform):
167
167
 
168
168
  # add uids for versioning
169
169
  if not env.versioning_compare or old_doctree is None:
170
- list(add_uids(self.document, env.versioning_condition))
170
+ list(add_uids(self.document, versioning_condition))
171
171
  else:
172
- list(merge_doctrees(old_doctree, self.document, env.versioning_condition))
172
+ list(merge_doctrees(old_doctree, self.document, versioning_condition))
173
173
 
174
174
 
175
175
  def setup(app: Sphinx) -> ExtensionMetadata: