Sphinx 7.3.7__py3-none-any.whl → 7.4.1__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 +44 -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 +3 -3
  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 +265 -143
  306. sphinx/versioning.py +17 -17
  307. sphinx/writers/html5.py +26 -19
  308. sphinx/writers/latex.py +60 -30
  309. sphinx/writers/manpage.py +4 -3
  310. sphinx/writers/texinfo.py +19 -14
  311. {sphinx-7.3.7.dist-info → sphinx-7.4.1.dist-info}/METADATA +21 -20
  312. sphinx-7.4.1.dist-info/RECORD +591 -0
  313. sphinx-7.3.7.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.7.dist-info → sphinx-7.4.1.dist-info}/LICENSE.rst +0 -0
  356. {sphinx-7.3.7.dist-info → sphinx-7.4.1.dist-info}/WHEEL +0 -0
  357. {sphinx-7.3.7.dist-info → sphinx-7.4.1.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,40 @@ 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
+ try:
216
+ return obj.__name__
217
+ except AttributeError:
218
+ # e.g. ParamSpecArgs, ParamSpecKwargs
219
+ return ''
220
+ return getattr(obj, '_name', None)
221
+
222
+
223
+ def restify(cls: Any, mode: _RestifyMode = 'fully-qualified-except-typing') -> str:
224
+ """Convert a type-like object to a reST reference.
152
225
 
153
226
  :param mode: Specify a method how annotations will be stringified.
154
227
 
@@ -161,31 +234,53 @@ def restify(cls: type | None, mode: str = 'fully-qualified-except-typing') -> st
161
234
  from sphinx.ext.autodoc.mock import ismock, ismockmodule # lazy loading
162
235
  from sphinx.util import inspect # lazy loading
163
236
 
164
- if mode == 'smart':
165
- modprefix = '~'
166
- else:
167
- modprefix = ''
237
+ valid_modes = {'fully-qualified-except-typing', 'smart'}
238
+ if mode not in valid_modes:
239
+ valid = ', '.join(map(repr, sorted(valid_modes)))
240
+ msg = f'mode must be one of {valid}; got {mode!r}'
241
+ raise ValueError(msg)
242
+
243
+ # things that are not types
244
+ if cls is None or cls == NoneType:
245
+ return ':py:obj:`None`'
246
+ if cls is Ellipsis:
247
+ return '...'
248
+ if isinstance(cls, str):
249
+ return cls
250
+
251
+ cls_module_is_typing = getattr(cls, '__module__', '') == 'typing'
252
+
253
+ # If the mode is 'smart', we always use '~'.
254
+ # If the mode is 'fully-qualified-except-typing',
255
+ # we use '~' only for the objects in the ``typing`` module.
256
+ module_prefix = '~' if mode == 'smart' or cls_module_is_typing else ''
168
257
 
169
258
  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__}`'
259
+ if ismockmodule(cls):
260
+ return f':py:class:`{module_prefix}{cls.__name__}`'
178
261
  elif ismock(cls):
179
- return f':py:class:`{modprefix}{cls.__module__}.{cls.__name__}`'
262
+ return f':py:class:`{module_prefix}{cls.__module__}.{cls.__name__}`'
180
263
  elif is_invalid_builtin_class(cls):
181
- return f':py:class:`{modprefix}{_INVALID_BUILTIN_CLASSES[cls]}`'
264
+ # The above predicate never raises TypeError but should not be
265
+ # evaluated before determining whether *cls* is a mocked object
266
+ # or not; instead of two try-except blocks, we keep it here.
267
+ return f':py:class:`{module_prefix}{_INVALID_BUILTIN_CLASSES[cls]}`'
268
+ elif _is_annotated_form(cls):
269
+ args = restify(cls.__args__[0], mode)
270
+ meta = ', '.join(map(repr, cls.__metadata__))
271
+ if sys.version_info[:2] <= (3, 11):
272
+ # Hardcoded to fix errors on Python 3.11 and earlier.
273
+ return fr':py:class:`~typing.Annotated`\ [{args}, {meta}]'
274
+ return (f':py:class:`{module_prefix}{cls.__module__}.{cls.__name__}`'
275
+ fr'\ [{args}, {meta}]')
182
276
  elif inspect.isNewType(cls):
183
277
  if sys.version_info[:2] >= (3, 10):
184
278
  # 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__}`'
279
+ return f':py:class:`{module_prefix}{cls.__module__}.{cls.__name__}`'
280
+ return f':py:class:`{cls.__name__}`'
188
281
  elif UnionType and isinstance(cls, UnionType):
282
+ # Union types (PEP 585) retain their definition order when they
283
+ # are printed natively and ``None``-like types are kept as is.
189
284
  return ' | '.join(restify(a, mode) for a in cls.__args__)
190
285
  elif cls.__module__ in ('__builtin__', 'builtins'):
191
286
  if hasattr(cls, '__args__'):
@@ -194,73 +289,85 @@ def restify(cls: type | None, mode: str = 'fully-qualified-except-typing') -> st
194
289
 
195
290
  concatenated_args = ', '.join(restify(arg, mode) for arg in cls.__args__)
196
291
  return fr':py:class:`{cls.__name__}`\ [{concatenated_args}]'
197
- else:
198
- return f':py:class:`{cls.__name__}`'
292
+ return f':py:class:`{cls.__name__}`'
199
293
  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]
294
+ and cls_module_is_typing
295
+ and cls.__origin__ is Union):
296
+ # *cls* is defined in ``typing``, and thus ``__args__`` must exist
297
+ return ' | '.join(restify(a, mode) for a in cls.__args__)
203
298
  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}`'
299
+ # A generic alias always has an __origin__, but it is difficult to
300
+ # use a type guard on inspect.isgenericalias()
301
+ # (ideally, we would use ``TypeIs`` introduced in Python 3.13).
302
+ cls_name = _typing_internal_name(cls)
303
+
304
+ if isinstance(cls.__origin__, typing._SpecialForm):
305
+ # ClassVar; Concatenate; Final; Literal; Unpack; TypeGuard; TypeIs
306
+ # Required/NotRequired
307
+ text = restify(cls.__origin__, mode)
308
+ elif cls_name:
309
+ text = f':py:class:`{module_prefix}{cls.__module__}.{cls_name}`'
212
310
  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
311
+ text = restify(cls.__origin__, mode)
312
+
313
+ __args__ = getattr(cls, '__args__', ())
314
+ if not __args__:
315
+ return text
316
+ if all(map(is_system_TypeVar, __args__)):
317
+ # Don't print the arguments; they're all system defined type variables.
318
+ return text
319
+
320
+ # Callable has special formatting
321
+ if (
322
+ (cls_module_is_typing and _typing_internal_name(cls) == 'Callable')
323
+ or (cls.__module__ == 'collections.abc' and cls.__name__ == 'Callable')
324
+ ):
325
+ args = ', '.join(restify(a, mode) for a in __args__[:-1])
326
+ returns = restify(__args__[-1], mode)
327
+ return fr'{text}\ [[{args}], {returns}]'
328
+
329
+ if cls_module_is_typing and _typing_internal_name(cls.__origin__) == 'Literal':
330
+ args = ', '.join(_format_literal_arg_restify(a, mode=mode)
331
+ for a in cls.__args__)
332
+ return fr'{text}\ [{args}]'
333
+
334
+ # generic representation of the parameters
335
+ args = ', '.join(restify(a, mode) for a in __args__)
336
+ return fr'{text}\ [{args}]'
238
337
  elif isinstance(cls, typing._SpecialForm):
239
- return f':py:obj:`~{cls.__module__}.{cls._name}`'
338
+ cls_name = _typing_internal_name(cls)
339
+ return f':py:obj:`~{cls.__module__}.{cls_name}`'
240
340
  elif sys.version_info[:2] >= (3, 11) and cls is typing.Any:
241
341
  # handle bpo-46998
242
342
  return f':py:obj:`~{cls.__module__}.{cls.__name__}`'
243
343
  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__}`'
344
+ return f':py:class:`{module_prefix}{cls.__module__}.{cls.__qualname__}`'
248
345
  elif isinstance(cls, ForwardRef):
249
346
  return f':py:class:`{cls.__forward_arg__}`'
250
347
  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__}`'
348
+ # not a class (ex. TypeVar) but should have a __name__
349
+ return f':py:obj:`{module_prefix}{cls.__module__}.{cls.__name__}`'
256
350
  except (AttributeError, TypeError):
257
351
  return inspect.object_description(cls)
258
352
 
259
353
 
354
+ def _format_literal_arg_restify(arg: Any, /, *, mode: str) -> str:
355
+ from sphinx.util.inspect import isenumattribute # lazy loading
356
+
357
+ if isenumattribute(arg):
358
+ enum_cls = arg.__class__
359
+ if mode == 'smart' or enum_cls.__module__ == 'typing':
360
+ # MyEnum.member
361
+ return f':py:attr:`~{enum_cls.__module__}.{enum_cls.__qualname__}.{arg.name}`'
362
+ # module.MyEnum.member
363
+ return f':py:attr:`{enum_cls.__module__}.{enum_cls.__qualname__}.{arg.name}`'
364
+ return repr(arg)
365
+
366
+
260
367
  def stringify_annotation(
261
368
  annotation: Any,
262
369
  /,
263
- mode: str = 'fully-qualified-except-typing',
370
+ mode: _StringifyMode = 'fully-qualified-except-typing',
264
371
  ) -> str:
265
372
  """Stringify type annotation object.
266
373
 
@@ -278,69 +385,76 @@ def stringify_annotation(
278
385
  from sphinx.ext.autodoc.mock import ismock, ismockmodule # lazy loading
279
386
  from sphinx.util.inspect import isNewType # lazy loading
280
387
 
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}.")
388
+ valid_modes = {'fully-qualified-except-typing', 'fully-qualified', 'smart'}
389
+ if mode not in valid_modes:
390
+ valid = ', '.join(map(repr, sorted(valid_modes)))
391
+ msg = f'mode must be one of {valid}; got {mode!r}'
284
392
  raise ValueError(msg)
285
393
 
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
-
394
+ # things that are not types
395
+ if annotation is None or annotation == NoneType:
396
+ return 'None'
397
+ if annotation is Ellipsis:
398
+ return '...'
296
399
  if isinstance(annotation, str):
297
400
  if annotation.startswith("'") and annotation.endswith("'"):
298
- # might be a double Forward-ref'ed type. Go unquoting.
401
+ # Might be a double Forward-ref'ed type. Go unquoting.
299
402
  return annotation[1:-1]
300
- else:
301
- return annotation
302
- elif isinstance(annotation, TypeVar):
403
+ return annotation
404
+ if not annotation:
405
+ return repr(annotation)
406
+
407
+ module_prefix = '~' if mode == 'smart' else ''
408
+
409
+ # The values below must be strings if the objects are well-formed.
410
+ annotation_qualname: str = getattr(annotation, '__qualname__', '')
411
+ annotation_module: str = getattr(annotation, '__module__', '')
412
+ annotation_name: str = getattr(annotation, '__name__', '')
413
+ annotation_module_is_typing = annotation_module == 'typing'
414
+
415
+ # Extract the annotation's base type by considering formattable cases
416
+ if isinstance(annotation, TypeVar) and not _is_unpack_form(annotation):
417
+ # typing_extensions.Unpack is incorrectly determined as a TypeVar
303
418
  if annotation_module_is_typing and mode in {'fully-qualified-except-typing', 'smart'}:
304
419
  return annotation_name
305
- else:
306
- return module_prefix + f'{annotation_module}.{annotation_name}'
420
+ return module_prefix + f'{annotation_module}.{annotation_name}'
307
421
  elif isNewType(annotation):
308
422
  if sys.version_info[:2] >= (3, 10):
309
423
  # newtypes have correct module info since Python 3.10+
310
424
  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'
425
+ return annotation_name
317
426
  elif ismockmodule(annotation):
318
427
  return module_prefix + annotation_name
319
428
  elif ismock(annotation):
320
429
  return module_prefix + f'{annotation_module}.{annotation_name}'
321
430
  elif is_invalid_builtin_class(annotation):
322
431
  return module_prefix + _INVALID_BUILTIN_CLASSES[annotation]
323
- elif str(annotation).startswith('typing.Annotated'): # for py310+
432
+ elif _is_annotated_form(annotation): # for py39+
324
433
  pass
325
434
  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:
435
+ args = getattr(annotation, '__args__', None)
436
+ if args is None:
333
437
  return annotation_qualname
334
- elif annotation is Ellipsis:
335
- return '...'
438
+
439
+ # PEP 585 generic
440
+ if not args: # Empty tuple, list, ...
441
+ return repr(annotation)
442
+
443
+ concatenated_args = ', '.join(stringify_annotation(arg, mode) for arg in args)
444
+ return f'{annotation_qualname}[{concatenated_args}]'
445
+ else:
446
+ # add other special cases that can be directly formatted
447
+ pass
336
448
 
337
449
  module_prefix = f'{annotation_module}.'
338
- annotation_forward_arg = getattr(annotation, '__forward_arg__', None)
450
+ annotation_forward_arg: str | None = getattr(annotation, '__forward_arg__', None)
339
451
  if annotation_qualname or (annotation_module_is_typing and not annotation_forward_arg):
340
452
  if mode == 'smart':
341
- module_prefix = '~' + module_prefix
453
+ module_prefix = f'~{module_prefix}'
342
454
  if annotation_module_is_typing and mode == 'fully-qualified-except-typing':
343
455
  module_prefix = ''
456
+ elif _is_unpack_form(annotation) and annotation_module == 'typing_extensions':
457
+ module_prefix = '~' if mode == 'smart' else ''
344
458
  else:
345
459
  module_prefix = ''
346
460
 
@@ -349,12 +463,13 @@ def stringify_annotation(
349
463
  # handle ForwardRefs
350
464
  qualname = annotation_forward_arg
351
465
  else:
352
- _name = getattr(annotation, '_name', '')
353
- if _name:
354
- qualname = _name
466
+ if internal_name := _typing_internal_name(annotation):
467
+ qualname = internal_name
355
468
  elif annotation_qualname:
356
469
  qualname = annotation_qualname
357
470
  else:
471
+ # in this case, we know that the annotation is a member
472
+ # of ``typing`` and all of them define ``__origin__``
358
473
  qualname = stringify_annotation(
359
474
  annotation.__origin__, 'fully-qualified-except-typing',
360
475
  ).replace('typing.', '') # ex. Union
@@ -370,36 +485,38 @@ def stringify_annotation(
370
485
  # only make them appear twice
371
486
  return repr(annotation)
372
487
 
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'}:
488
+ # Process the generic arguments (if any).
489
+ # They must be a list or a tuple, otherwise they are considered 'broken'.
490
+ annotation_args = getattr(annotation, '__args__', ())
491
+ if annotation_args and isinstance(annotation_args, (list, tuple)):
492
+ if (
493
+ qualname in {'Union', 'types.UnionType'}
494
+ and all(getattr(a, '__origin__', ...) is typing.Literal for a in annotation_args)
495
+ ):
496
+ # special case to flatten a Union of Literals into a literal
497
+ flattened_args = typing.Literal[annotation_args].__args__ # type: ignore[attr-defined]
498
+ args = ', '.join(_format_literal_arg_stringify(a, mode=mode)
499
+ for a in flattened_args)
500
+ return f'{module_prefix}Literal[{args}]'
501
+ if qualname in {'Optional', 'Union', 'types.UnionType'}:
379
502
  return ' | '.join(stringify_annotation(a, mode) for a in annotation_args)
380
503
  elif qualname == 'Callable':
381
504
  args = ', '.join(stringify_annotation(a, mode) for a in annotation_args[:-1])
382
505
  returns = stringify_annotation(annotation_args[-1], mode)
383
506
  return f'{module_prefix}Callable[[{args}], {returns}]'
384
507
  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))
508
+ args = ', '.join(_format_literal_arg_stringify(a, mode=mode)
509
+ for a in annotation_args)
400
510
  return f'{module_prefix}Literal[{args}]'
401
- elif str(annotation).startswith('typing.Annotated'): # for py39+
402
- return stringify_annotation(annotation_args[0], mode)
511
+ elif _is_annotated_form(annotation): # for py39+
512
+ args = stringify_annotation(annotation_args[0], mode)
513
+ meta = ', '.join(map(repr, annotation.__metadata__))
514
+ if sys.version_info[:2] <= (3, 11):
515
+ if mode == 'fully-qualified-except-typing':
516
+ return f'Annotated[{args}, {meta}]'
517
+ module_prefix = module_prefix.replace('builtins', 'typing')
518
+ return f'{module_prefix}Annotated[{args}, {meta}]'
519
+ return f'{module_prefix}Annotated[{args}, {meta}]'
403
520
  elif all(is_system_TypeVar(a) for a in annotation_args):
404
521
  # Suppress arguments if all system defined TypeVars (ex. Dict[KT, VT])
405
522
  return module_prefix + qualname
@@ -410,12 +527,17 @@ def stringify_annotation(
410
527
  return module_prefix + qualname
411
528
 
412
529
 
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}`'
530
+ def _format_literal_arg_stringify(arg: Any, /, *, mode: str) -> str:
531
+ from sphinx.util.inspect import isenumattribute # lazy loading
532
+
533
+ if isenumattribute(arg):
534
+ enum_cls = arg.__class__
535
+ if mode == 'smart' or enum_cls.__module__ == 'typing':
536
+ # MyEnum.member
537
+ return f'{enum_cls.__qualname__}.{arg.name}'
538
+ # module.MyEnum.member
539
+ return f'{enum_cls.__module__}.{enum_cls.__qualname__}.{arg.name}'
540
+ return repr(arg)
419
541
 
420
542
 
421
543
  # 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: