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

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

Potentially problematic release.


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

Files changed (313) hide show
  1. sphinx/__init__.py +6 -6
  2. sphinx/__main__.py +3 -1
  3. sphinx/addnodes.py +35 -22
  4. sphinx/application.py +40 -38
  5. sphinx/builders/__init__.py +16 -12
  6. sphinx/builders/_epub_base.py +15 -11
  7. sphinx/builders/changes.py +6 -4
  8. sphinx/builders/dirhtml.py +4 -2
  9. sphinx/builders/dummy.py +6 -4
  10. sphinx/builders/epub3.py +16 -8
  11. sphinx/builders/gettext.py +40 -43
  12. sphinx/builders/html/__init__.py +166 -196
  13. sphinx/builders/html/_assets.py +116 -0
  14. sphinx/builders/html/transforms.py +4 -2
  15. sphinx/builders/latex/__init__.py +12 -7
  16. sphinx/builders/latex/theming.py +5 -2
  17. sphinx/builders/latex/transforms.py +6 -3
  18. sphinx/builders/linkcheck.py +18 -11
  19. sphinx/builders/manpage.py +6 -4
  20. sphinx/builders/singlehtml.py +16 -9
  21. sphinx/builders/texinfo.py +11 -6
  22. sphinx/builders/text.py +8 -3
  23. sphinx/builders/xml.py +9 -4
  24. sphinx/cmd/build.py +27 -14
  25. sphinx/cmd/make_mode.py +13 -4
  26. sphinx/cmd/quickstart.py +13 -4
  27. sphinx/config.py +17 -14
  28. sphinx/deprecation.py +4 -2
  29. sphinx/directives/__init__.py +44 -12
  30. sphinx/directives/code.py +5 -4
  31. sphinx/directives/other.py +92 -44
  32. sphinx/directives/patches.py +1 -1
  33. sphinx/domains/__init__.py +11 -8
  34. sphinx/domains/c.py +67 -57
  35. sphinx/domains/changeset.py +3 -2
  36. sphinx/domains/citation.py +2 -1
  37. sphinx/domains/cpp.py +136 -93
  38. sphinx/domains/index.py +9 -5
  39. sphinx/domains/javascript.py +32 -19
  40. sphinx/domains/math.py +5 -3
  41. sphinx/domains/python.py +69 -57
  42. sphinx/domains/rst.py +20 -11
  43. sphinx/domains/std.py +21 -15
  44. sphinx/environment/__init__.py +97 -65
  45. sphinx/environment/adapters/indexentries.py +13 -10
  46. sphinx/environment/adapters/toctree.py +485 -308
  47. sphinx/environment/collectors/__init__.py +3 -4
  48. sphinx/environment/collectors/asset.py +10 -4
  49. sphinx/environment/collectors/dependencies.py +7 -4
  50. sphinx/environment/collectors/metadata.py +7 -5
  51. sphinx/environment/collectors/title.py +5 -3
  52. sphinx/environment/collectors/toctree.py +13 -8
  53. sphinx/errors.py +1 -1
  54. sphinx/events.py +5 -5
  55. sphinx/ext/apidoc.py +49 -27
  56. sphinx/ext/autodoc/__init__.py +179 -161
  57. sphinx/ext/autodoc/directive.py +10 -6
  58. sphinx/ext/autodoc/importer.py +22 -13
  59. sphinx/ext/autodoc/mock.py +4 -1
  60. sphinx/ext/autodoc/preserve_defaults.py +80 -12
  61. sphinx/ext/autodoc/type_comment.py +14 -10
  62. sphinx/ext/autodoc/typehints.py +7 -3
  63. sphinx/ext/autosectionlabel.py +6 -3
  64. sphinx/ext/autosummary/__init__.py +21 -15
  65. sphinx/ext/autosummary/generate.py +176 -126
  66. sphinx/ext/coverage.py +93 -8
  67. sphinx/ext/doctest.py +28 -17
  68. sphinx/ext/duration.py +19 -17
  69. sphinx/ext/extlinks.py +11 -6
  70. sphinx/ext/githubpages.py +8 -7
  71. sphinx/ext/graphviz.py +61 -17
  72. sphinx/ext/ifconfig.py +7 -4
  73. sphinx/ext/imgconverter.py +4 -2
  74. sphinx/ext/imgmath.py +29 -23
  75. sphinx/ext/inheritance_diagram.py +41 -27
  76. sphinx/ext/intersphinx.py +45 -38
  77. sphinx/ext/linkcode.py +8 -5
  78. sphinx/ext/mathjax.py +13 -9
  79. sphinx/ext/napoleon/__init__.py +3 -3
  80. sphinx/ext/napoleon/docstring.py +40 -31
  81. sphinx/ext/todo.py +10 -7
  82. sphinx/ext/viewcode.py +46 -25
  83. sphinx/extension.py +1 -1
  84. sphinx/highlighting.py +20 -12
  85. sphinx/io.py +5 -4
  86. sphinx/jinja2glue.py +24 -19
  87. sphinx/locale/__init__.py +8 -2
  88. sphinx/locale/ar/LC_MESSAGES/sphinx.mo +0 -0
  89. sphinx/locale/ar/LC_MESSAGES/sphinx.po +756 -740
  90. sphinx/locale/bg/LC_MESSAGES/sphinx.mo +0 -0
  91. sphinx/locale/bg/LC_MESSAGES/sphinx.po +754 -738
  92. sphinx/locale/bn/LC_MESSAGES/sphinx.mo +0 -0
  93. sphinx/locale/bn/LC_MESSAGES/sphinx.po +755 -739
  94. sphinx/locale/ca/LC_MESSAGES/sphinx.mo +0 -0
  95. sphinx/locale/ca/LC_MESSAGES/sphinx.po +768 -752
  96. sphinx/locale/cak/LC_MESSAGES/sphinx.mo +0 -0
  97. sphinx/locale/cak/LC_MESSAGES/sphinx.po +754 -738
  98. sphinx/locale/cs/LC_MESSAGES/sphinx.mo +0 -0
  99. sphinx/locale/cs/LC_MESSAGES/sphinx.po +758 -742
  100. sphinx/locale/cy/LC_MESSAGES/sphinx.mo +0 -0
  101. sphinx/locale/cy/LC_MESSAGES/sphinx.po +759 -743
  102. sphinx/locale/da/LC_MESSAGES/sphinx.mo +0 -0
  103. sphinx/locale/da/LC_MESSAGES/sphinx.po +760 -744
  104. sphinx/locale/de/LC_MESSAGES/sphinx.mo +0 -0
  105. sphinx/locale/de/LC_MESSAGES/sphinx.po +759 -743
  106. sphinx/locale/de_DE/LC_MESSAGES/sphinx.mo +0 -0
  107. sphinx/locale/de_DE/LC_MESSAGES/sphinx.po +754 -738
  108. sphinx/locale/el/LC_MESSAGES/sphinx.mo +0 -0
  109. sphinx/locale/el/LC_MESSAGES/sphinx.po +763 -747
  110. sphinx/locale/en_DE/LC_MESSAGES/sphinx.mo +0 -0
  111. sphinx/locale/en_DE/LC_MESSAGES/sphinx.po +754 -738
  112. sphinx/locale/en_FR/LC_MESSAGES/sphinx.mo +0 -0
  113. sphinx/locale/en_FR/LC_MESSAGES/sphinx.po +754 -738
  114. sphinx/locale/en_GB/LC_MESSAGES/sphinx.mo +0 -0
  115. sphinx/locale/en_GB/LC_MESSAGES/sphinx.po +768 -752
  116. sphinx/locale/en_HK/LC_MESSAGES/sphinx.mo +0 -0
  117. sphinx/locale/en_HK/LC_MESSAGES/sphinx.po +754 -738
  118. sphinx/locale/eo/LC_MESSAGES/sphinx.mo +0 -0
  119. sphinx/locale/eo/LC_MESSAGES/sphinx.po +754 -738
  120. sphinx/locale/es/LC_MESSAGES/sphinx.mo +0 -0
  121. sphinx/locale/es/LC_MESSAGES/sphinx.po +767 -751
  122. sphinx/locale/es_CO/LC_MESSAGES/sphinx.mo +0 -0
  123. sphinx/locale/es_CO/LC_MESSAGES/sphinx.po +754 -738
  124. sphinx/locale/et/LC_MESSAGES/sphinx.mo +0 -0
  125. sphinx/locale/et/LC_MESSAGES/sphinx.po +762 -746
  126. sphinx/locale/eu/LC_MESSAGES/sphinx.mo +0 -0
  127. sphinx/locale/eu/LC_MESSAGES/sphinx.po +755 -739
  128. sphinx/locale/fa/LC_MESSAGES/sphinx.mo +0 -0
  129. sphinx/locale/fa/LC_MESSAGES/sphinx.po +766 -750
  130. sphinx/locale/fi/LC_MESSAGES/sphinx.mo +0 -0
  131. sphinx/locale/fi/LC_MESSAGES/sphinx.po +754 -738
  132. sphinx/locale/fr/LC_MESSAGES/sphinx.mo +0 -0
  133. sphinx/locale/fr/LC_MESSAGES/sphinx.po +768 -752
  134. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.mo +0 -0
  135. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.po +754 -738
  136. sphinx/locale/gl/LC_MESSAGES/sphinx.js +60 -0
  137. sphinx/locale/gl/LC_MESSAGES/sphinx.mo +0 -0
  138. sphinx/locale/gl/LC_MESSAGES/sphinx.po +3695 -0
  139. sphinx/locale/he/LC_MESSAGES/sphinx.mo +0 -0
  140. sphinx/locale/he/LC_MESSAGES/sphinx.po +755 -739
  141. sphinx/locale/hi/LC_MESSAGES/sphinx.mo +0 -0
  142. sphinx/locale/hi/LC_MESSAGES/sphinx.po +763 -747
  143. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.mo +0 -0
  144. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.po +754 -738
  145. sphinx/locale/hr/LC_MESSAGES/sphinx.mo +0 -0
  146. sphinx/locale/hr/LC_MESSAGES/sphinx.po +760 -744
  147. sphinx/locale/hu/LC_MESSAGES/sphinx.mo +0 -0
  148. sphinx/locale/hu/LC_MESSAGES/sphinx.po +759 -743
  149. sphinx/locale/id/LC_MESSAGES/sphinx.mo +0 -0
  150. sphinx/locale/id/LC_MESSAGES/sphinx.po +765 -749
  151. sphinx/locale/is/LC_MESSAGES/sphinx.mo +0 -0
  152. sphinx/locale/is/LC_MESSAGES/sphinx.po +760 -744
  153. sphinx/locale/it/LC_MESSAGES/sphinx.mo +0 -0
  154. sphinx/locale/it/LC_MESSAGES/sphinx.po +760 -744
  155. sphinx/locale/ja/LC_MESSAGES/sphinx.mo +0 -0
  156. sphinx/locale/ja/LC_MESSAGES/sphinx.po +767 -751
  157. sphinx/locale/ka/LC_MESSAGES/sphinx.mo +0 -0
  158. sphinx/locale/ka/LC_MESSAGES/sphinx.po +759 -743
  159. sphinx/locale/ko/LC_MESSAGES/sphinx.mo +0 -0
  160. sphinx/locale/ko/LC_MESSAGES/sphinx.po +767 -751
  161. sphinx/locale/lt/LC_MESSAGES/sphinx.mo +0 -0
  162. sphinx/locale/lt/LC_MESSAGES/sphinx.po +755 -739
  163. sphinx/locale/lv/LC_MESSAGES/sphinx.mo +0 -0
  164. sphinx/locale/lv/LC_MESSAGES/sphinx.po +755 -739
  165. sphinx/locale/mk/LC_MESSAGES/sphinx.mo +0 -0
  166. sphinx/locale/mk/LC_MESSAGES/sphinx.po +754 -738
  167. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.mo +0 -0
  168. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.po +755 -739
  169. sphinx/locale/ne/LC_MESSAGES/sphinx.mo +0 -0
  170. sphinx/locale/ne/LC_MESSAGES/sphinx.po +755 -739
  171. sphinx/locale/nl/LC_MESSAGES/sphinx.mo +0 -0
  172. sphinx/locale/nl/LC_MESSAGES/sphinx.po +760 -744
  173. sphinx/locale/pl/LC_MESSAGES/sphinx.mo +0 -0
  174. sphinx/locale/pl/LC_MESSAGES/sphinx.po +762 -745
  175. sphinx/locale/pt/LC_MESSAGES/sphinx.mo +0 -0
  176. sphinx/locale/pt/LC_MESSAGES/sphinx.po +754 -738
  177. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.mo +0 -0
  178. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.po +768 -752
  179. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.mo +0 -0
  180. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.po +755 -739
  181. sphinx/locale/ro/LC_MESSAGES/sphinx.mo +0 -0
  182. sphinx/locale/ro/LC_MESSAGES/sphinx.po +759 -743
  183. sphinx/locale/ru/LC_MESSAGES/sphinx.mo +0 -0
  184. sphinx/locale/ru/LC_MESSAGES/sphinx.po +760 -744
  185. sphinx/locale/si/LC_MESSAGES/sphinx.mo +0 -0
  186. sphinx/locale/si/LC_MESSAGES/sphinx.po +754 -738
  187. sphinx/locale/sk/LC_MESSAGES/sphinx.mo +0 -0
  188. sphinx/locale/sk/LC_MESSAGES/sphinx.po +765 -749
  189. sphinx/locale/sl/LC_MESSAGES/sphinx.mo +0 -0
  190. sphinx/locale/sl/LC_MESSAGES/sphinx.po +755 -739
  191. sphinx/locale/sphinx.pot +748 -740
  192. sphinx/locale/sq/LC_MESSAGES/sphinx.mo +0 -0
  193. sphinx/locale/sq/LC_MESSAGES/sphinx.po +768 -752
  194. sphinx/locale/sr/LC_MESSAGES/sphinx.mo +0 -0
  195. sphinx/locale/sr/LC_MESSAGES/sphinx.po +754 -738
  196. sphinx/locale/sr@latin/LC_MESSAGES/sphinx.mo +0 -0
  197. sphinx/locale/sr@latin/LC_MESSAGES/sphinx.po +754 -738
  198. sphinx/locale/sr_RS/LC_MESSAGES/sphinx.mo +0 -0
  199. sphinx/locale/sr_RS/LC_MESSAGES/sphinx.po +754 -738
  200. sphinx/locale/sv/LC_MESSAGES/sphinx.mo +0 -0
  201. sphinx/locale/sv/LC_MESSAGES/sphinx.po +755 -739
  202. sphinx/locale/ta/LC_MESSAGES/sphinx.mo +0 -0
  203. sphinx/locale/ta/LC_MESSAGES/sphinx.po +754 -738
  204. sphinx/locale/te/LC_MESSAGES/sphinx.mo +0 -0
  205. sphinx/locale/te/LC_MESSAGES/sphinx.po +754 -738
  206. sphinx/locale/tr/LC_MESSAGES/sphinx.mo +0 -0
  207. sphinx/locale/tr/LC_MESSAGES/sphinx.po +763 -747
  208. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.mo +0 -0
  209. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.po +760 -749
  210. sphinx/locale/ur/LC_MESSAGES/sphinx.mo +0 -0
  211. sphinx/locale/ur/LC_MESSAGES/sphinx.po +759 -748
  212. sphinx/locale/vi/LC_MESSAGES/sphinx.mo +0 -0
  213. sphinx/locale/vi/LC_MESSAGES/sphinx.po +754 -738
  214. sphinx/locale/yue/LC_MESSAGES/sphinx.mo +0 -0
  215. sphinx/locale/yue/LC_MESSAGES/sphinx.po +754 -738
  216. sphinx/locale/zh_CN/LC_MESSAGES/sphinx.mo +0 -0
  217. sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po +768 -752
  218. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.mo +0 -0
  219. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.po +754 -738
  220. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.mo +0 -0
  221. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po +767 -751
  222. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.mo +0 -0
  223. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.po +754 -738
  224. sphinx/parsers.py +5 -4
  225. sphinx/project.py +52 -34
  226. sphinx/pycode/__init__.py +2 -1
  227. sphinx/pycode/ast.py +7 -13
  228. sphinx/pycode/parser.py +42 -38
  229. sphinx/registry.py +35 -29
  230. sphinx/roles.py +9 -4
  231. sphinx/search/__init__.py +5 -17
  232. sphinx/search/da.py +1 -1
  233. sphinx/search/de.py +1 -1
  234. sphinx/search/en.py +1 -1
  235. sphinx/search/es.py +1 -1
  236. sphinx/search/fi.py +1 -1
  237. sphinx/search/fr.py +1 -1
  238. sphinx/search/hu.py +1 -1
  239. sphinx/search/it.py +1 -1
  240. sphinx/search/ja.py +1 -1
  241. sphinx/search/nl.py +1 -1
  242. sphinx/search/no.py +1 -1
  243. sphinx/search/pt.py +1 -1
  244. sphinx/search/ro.py +1 -1
  245. sphinx/search/ru.py +1 -1
  246. sphinx/search/sv.py +1 -1
  247. sphinx/search/tr.py +1 -1
  248. sphinx/search/zh.py +1 -1
  249. sphinx/testing/fixtures.py +23 -30
  250. sphinx/testing/path.py +9 -0
  251. sphinx/testing/restructuredtext.py +13 -5
  252. sphinx/testing/util.py +20 -63
  253. sphinx/texinputs/sphinxlatexobjects.sty +15 -15
  254. sphinx/themes/agogo/static/agogo.css_t +10 -4
  255. sphinx/themes/basic/layout.html +1 -1
  256. sphinx/themes/basic/static/basic.css_t +4 -0
  257. sphinx/themes/basic/static/documentation_options.js_t +1 -2
  258. sphinx/themes/basic/static/searchtools.js +17 -9
  259. sphinx/themes/basic/static/sphinx_highlight.js +13 -3
  260. sphinx/themes/bizstyle/static/bizstyle.css_t +4 -0
  261. sphinx/themes/classic/theme.conf +1 -1
  262. sphinx/themes/epub/static/epub.css_t +6 -1
  263. sphinx/themes/haiku/theme.conf +1 -1
  264. sphinx/themes/nature/static/nature.css_t +4 -0
  265. sphinx/themes/nonav/static/nonav.css_t +6 -1
  266. sphinx/themes/pyramid/static/pyramid.css_t +4 -0
  267. sphinx/themes/scrolls/static/scrolls.css_t +4 -0
  268. sphinx/themes/scrolls/theme.conf +1 -1
  269. sphinx/themes/sphinxdoc/static/sphinxdoc.css_t +4 -0
  270. sphinx/theming.py +9 -7
  271. sphinx/transforms/__init__.py +79 -3
  272. sphinx/transforms/compact_bullet_list.py +6 -3
  273. sphinx/transforms/i18n.py +26 -10
  274. sphinx/transforms/post_transforms/__init__.py +21 -8
  275. sphinx/transforms/post_transforms/code.py +6 -3
  276. sphinx/transforms/post_transforms/images.py +13 -9
  277. sphinx/util/__init__.py +21 -92
  278. sphinx/util/cfamily.py +7 -4
  279. sphinx/util/display.py +3 -2
  280. sphinx/util/docfields.py +7 -6
  281. sphinx/util/docstrings.py +1 -1
  282. sphinx/util/docutils.py +41 -31
  283. sphinx/util/fileutil.py +9 -6
  284. sphinx/util/i18n.py +21 -18
  285. sphinx/util/images.py +2 -1
  286. sphinx/util/index_entries.py +27 -0
  287. sphinx/util/inspect.py +83 -67
  288. sphinx/util/inventory.py +4 -2
  289. sphinx/util/logging.py +9 -6
  290. sphinx/util/matching.py +5 -2
  291. sphinx/util/math.py +6 -3
  292. sphinx/util/nodes.py +70 -31
  293. sphinx/util/osutil.py +22 -40
  294. sphinx/util/parallel.py +4 -1
  295. sphinx/util/rst.py +7 -3
  296. sphinx/util/tags.py +11 -4
  297. sphinx/util/template.py +17 -14
  298. sphinx/util/typing.py +61 -20
  299. sphinx/versioning.py +6 -4
  300. sphinx/writers/html.py +1 -1
  301. sphinx/writers/html5.py +32 -24
  302. sphinx/writers/latex.py +67 -53
  303. sphinx/writers/manpage.py +9 -5
  304. sphinx/writers/texinfo.py +11 -9
  305. sphinx/writers/text.py +14 -9
  306. sphinx/writers/xml.py +3 -2
  307. {sphinx-7.1.2.dist-info → sphinx-7.2.0.dist-info}/METADATA +7 -5
  308. sphinx-7.2.0.dist-info/RECORD +568 -0
  309. sphinx/testing/comparer.py +0 -97
  310. sphinx-7.1.2.dist-info/RECORD +0 -564
  311. {sphinx-7.1.2.dist-info → sphinx-7.2.0.dist-info}/LICENSE +0 -0
  312. {sphinx-7.1.2.dist-info → sphinx-7.2.0.dist-info}/WHEEL +0 -0
  313. {sphinx-7.1.2.dist-info → sphinx-7.2.0.dist-info}/entry_points.txt +0 -0
sphinx/util/i18n.py CHANGED
@@ -6,7 +6,7 @@ import os
6
6
  import re
7
7
  from datetime import datetime, timezone
8
8
  from os import path
9
- from typing import TYPE_CHECKING, Callable, Generator, NamedTuple
9
+ from typing import TYPE_CHECKING, Callable, NamedTuple
10
10
 
11
11
  import babel.dates
12
12
  from babel.messages.mofile import write_mo
@@ -18,6 +18,8 @@ from sphinx.util import logging
18
18
  from sphinx.util.osutil import SEP, canon_path, relpath
19
19
 
20
20
  if TYPE_CHECKING:
21
+ from collections.abc import Generator
22
+
21
23
  from sphinx.environment import BuildEnvironment
22
24
 
23
25
 
@@ -71,7 +73,7 @@ class CatalogInfo(LocaleFileInfoBase):
71
73
  class CatalogRepository:
72
74
  """A repository for message catalogs."""
73
75
 
74
- def __init__(self, basedir: str, locale_dirs: list[str],
76
+ def __init__(self, basedir: str | os.PathLike[str], locale_dirs: list[str],
75
77
  language: str, encoding: str) -> None:
76
78
  self.basedir = basedir
77
79
  self._locale_dirs = locale_dirs
@@ -89,7 +91,7 @@ class CatalogRepository:
89
91
  if path.exists(locale_path):
90
92
  yield locale_dir
91
93
  else:
92
- logger.verbose(__('locale_dir %s does not exists'), locale_path)
94
+ logger.verbose(__('locale_dir %s does not exist'), locale_path)
93
95
 
94
96
  @property
95
97
  def pofiles(self) -> Generator[tuple[str, str], None, None]:
@@ -221,24 +223,25 @@ def format_date(
221
223
  return "".join(result)
222
224
 
223
225
 
224
- def get_image_filename_for_language(filename: str, env: BuildEnvironment) -> str:
225
- filename_format = env.config.figure_language_filename
226
- d = {}
227
- d['root'], d['ext'] = path.splitext(filename)
228
- dirname = path.dirname(d['root'])
229
- if dirname and not dirname.endswith(path.sep):
230
- dirname += path.sep
226
+ def get_image_filename_for_language(
227
+ filename: str | os.PathLike[str],
228
+ env: BuildEnvironment,
229
+ ) -> str:
230
+ root, ext = path.splitext(filename)
231
+ dirname = path.dirname(root)
231
232
  docpath = path.dirname(env.docname)
232
- if docpath and not docpath.endswith(path.sep):
233
- docpath += path.sep
234
- d['path'] = dirname
235
- d['basename'] = path.basename(d['root'])
236
- d['docpath'] = docpath
237
- d['language'] = env.config.language
238
233
  try:
239
- return filename_format.format(**d)
234
+ return env.config.figure_language_filename.format(
235
+ root=root,
236
+ ext=ext,
237
+ path=dirname and dirname + SEP,
238
+ basename=path.basename(root),
239
+ docpath=docpath and docpath + SEP,
240
+ language=env.config.language,
241
+ )
240
242
  except KeyError as exc:
241
- raise SphinxError('Invalid figure_language_filename: %r' % exc) from exc
243
+ msg = f'Invalid figure_language_filename: {exc!r}'
244
+ raise SphinxError(msg) from exc
242
245
 
243
246
 
244
247
  def search_image_for_language(filename: str, env: BuildEnvironment) -> str:
sphinx/util/images.py CHANGED
@@ -142,4 +142,5 @@ def _image_type_from_file(filename: PathLike[str] | str) -> str:
142
142
  if header.startswith(b'RIFF') and header[8:12] == b'WEBP':
143
143
  return 'webp'
144
144
 
145
- raise ValueError('Could not detect image type!')
145
+ msg = 'Could not detect image type!'
146
+ raise ValueError(msg)
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ def split_index_msg(entry_type: str, value: str) -> list[str]:
5
+ # new entry types must be listed in util/nodes.py!
6
+ if entry_type == 'single':
7
+ try:
8
+ return _split_into(2, 'single', value)
9
+ except ValueError:
10
+ return _split_into(1, 'single', value)
11
+ if entry_type == 'pair':
12
+ return _split_into(2, 'pair', value)
13
+ if entry_type == 'triple':
14
+ return _split_into(3, 'triple', value)
15
+ if entry_type in {'see', 'seealso'}:
16
+ return _split_into(2, 'see', value)
17
+ msg = f'invalid {entry_type} index entry {value!r}'
18
+ raise ValueError(msg)
19
+
20
+
21
+ def _split_into(n: int, type: str, value: str) -> list[str]:
22
+ """Split an index entry into a given number of parts at semicolons."""
23
+ parts = [x.strip() for x in value.split(';', n - 1)]
24
+ if len(list(filter(None, parts))) < n:
25
+ msg = f'invalid {type} index entry {value!r}'
26
+ raise ValueError(msg)
27
+ return parts
sphinx/util/inspect.py CHANGED
@@ -11,6 +11,7 @@ import re
11
11
  import sys
12
12
  import types
13
13
  import typing
14
+ from collections.abc import Mapping, Sequence
14
15
  from functools import cached_property, partial, partialmethod, singledispatchmethod
15
16
  from importlib import import_module
16
17
  from inspect import ( # noqa: F401
@@ -29,7 +30,7 @@ from types import (
29
30
  ModuleType,
30
31
  WrapperDescriptorType,
31
32
  )
32
- from typing import Any, Callable, Dict, Mapping, Sequence, cast
33
+ from typing import Any, Callable, cast
33
34
 
34
35
  from sphinx.pycode.ast import unparse as ast_unparse
35
36
  from sphinx.util import logging
@@ -42,12 +43,11 @@ memory_address_re = re.compile(r' at 0x[0-9a-f]{8,16}(?=>)', re.IGNORECASE)
42
43
 
43
44
  def unwrap(obj: Any) -> Any:
44
45
  """Get an original object from wrapped object (wrapped functions)."""
46
+ if hasattr(obj, '__sphinx_mock__'):
47
+ # Skip unwrapping mock object to avoid RecursionError
48
+ return obj
45
49
  try:
46
- if hasattr(obj, '__sphinx_mock__'):
47
- # Skip unwrapping mock object to avoid RecursionError
48
- return obj
49
- else:
50
- return inspect.unwrap(obj)
50
+ return inspect.unwrap(obj)
51
51
  except ValueError:
52
52
  # might be a mock object
53
53
  return obj
@@ -80,11 +80,9 @@ def getall(obj: Any) -> Sequence[str] | None:
80
80
  __all__ = safe_getattr(obj, '__all__', None)
81
81
  if __all__ is None:
82
82
  return None
83
- else:
84
- if (isinstance(__all__, (list, tuple)) and all(isinstance(e, str) for e in __all__)):
85
- return __all__
86
- else:
87
- raise ValueError(__all__)
83
+ if isinstance(__all__, (list, tuple)) and all(isinstance(e, str) for e in __all__):
84
+ return __all__
85
+ raise ValueError(__all__)
88
86
 
89
87
 
90
88
  def getannotations(obj: Any) -> Mapping[str, Any]:
@@ -129,7 +127,7 @@ def getorigbases(obj: Any) -> tuple[Any, ...] | None:
129
127
  return None
130
128
 
131
129
 
132
- def getslots(obj: Any) -> dict | None:
130
+ def getslots(obj: Any) -> dict[str, Any] | None:
133
131
  """Get __slots__ attribute of the class as dict.
134
132
 
135
133
  Return None if gienv *obj* does not have __slots__.
@@ -147,7 +145,7 @@ def getslots(obj: Any) -> dict | None:
147
145
  elif isinstance(__slots__, str):
148
146
  return {__slots__: None}
149
147
  elif isinstance(__slots__, (list, tuple)):
150
- return {e: None for e in __slots__}
148
+ return dict.fromkeys(__slots__)
151
149
  else:
152
150
  raise ValueError
153
151
 
@@ -156,10 +154,9 @@ def isNewType(obj: Any) -> bool:
156
154
  """Check the if object is a kind of NewType."""
157
155
  if sys.version_info[:2] >= (3, 10):
158
156
  return isinstance(obj, typing.NewType)
159
- else:
160
- __module__ = safe_getattr(obj, '__module__', None)
161
- __qualname__ = safe_getattr(obj, '__qualname__', None)
162
- return __module__ == 'typing' and __qualname__ == 'NewType.<locals>.new_type'
157
+ __module__ = safe_getattr(obj, '__module__', None)
158
+ __qualname__ = safe_getattr(obj, '__qualname__', None)
159
+ return __module__ == 'typing' and __qualname__ == 'NewType.<locals>.new_type'
163
160
 
164
161
 
165
162
  def isenumclass(x: Any) -> bool:
@@ -208,7 +205,7 @@ def isstaticmethod(obj: Any, cls: Any = None, name: str | None = None) -> bool:
208
205
  """Check if the object is staticmethod."""
209
206
  if isinstance(obj, staticmethod):
210
207
  return True
211
- elif cls and name:
208
+ if cls and name:
212
209
  # trace __mro__ if the method is defined in parent class
213
210
  #
214
211
  # .. note:: This only works well with new style classes.
@@ -216,7 +213,6 @@ def isstaticmethod(obj: Any, cls: Any = None, name: str | None = None) -> bool:
216
213
  meth = basecls.__dict__.get(name)
217
214
  if meth:
218
215
  return isinstance(meth, staticmethod)
219
-
220
216
  return False
221
217
 
222
218
 
@@ -290,17 +286,17 @@ def is_singledispatch_method(obj: Any) -> bool:
290
286
 
291
287
  def isfunction(obj: Any) -> bool:
292
288
  """Check if the object is function."""
293
- return inspect.isfunction(unwrap_all(obj))
289
+ return inspect.isfunction(unpartial(obj))
294
290
 
295
291
 
296
292
  def isbuiltin(obj: Any) -> bool:
297
- """Check if the object is builtin."""
298
- return inspect.isbuiltin(unwrap_all(obj))
293
+ """Check if the object is function."""
294
+ return inspect.isbuiltin(unpartial(obj))
299
295
 
300
296
 
301
297
  def isroutine(obj: Any) -> bool:
302
298
  """Check is any kind of function or method."""
303
- return inspect.isroutine(unwrap_all(obj))
299
+ return inspect.isroutine(unpartial(obj))
304
300
 
305
301
 
306
302
  def iscoroutinefunction(obj: Any) -> bool:
@@ -324,15 +320,8 @@ def isproperty(obj: Any) -> bool:
324
320
 
325
321
  def isgenericalias(obj: Any) -> bool:
326
322
  """Check if the object is GenericAlias."""
327
- if isinstance(obj, typing._GenericAlias): # type: ignore
328
- return True
329
- if (hasattr(types, 'GenericAlias') # only for py39+
330
- and isinstance(obj, types.GenericAlias)):
331
- return True
332
- if (hasattr(typing, '_SpecialGenericAlias') # for py39+
333
- and isinstance(obj, typing._SpecialGenericAlias)):
334
- return True
335
- return False
323
+ return isinstance(
324
+ obj, (types.GenericAlias, typing._BaseGenericAlias)) # type: ignore[attr-defined]
336
325
 
337
326
 
338
327
  def safe_getattr(obj: Any, name: str, *defargs: Any) -> Any:
@@ -357,38 +346,64 @@ def safe_getattr(obj: Any, name: str, *defargs: Any) -> Any:
357
346
  raise AttributeError(name) from exc
358
347
 
359
348
 
360
- def object_description(object: Any) -> str:
361
- """A repr() implementation that returns text safe to use in reST context."""
362
- if isinstance(object, dict):
349
+ def object_description(obj: Any, *, _seen: frozenset = frozenset()) -> str:
350
+ """A repr() implementation that returns text safe to use in reST context.
351
+
352
+ Maintains a set of 'seen' object IDs to detect and avoid infinite recursion.
353
+ """
354
+ seen = _seen
355
+ if isinstance(obj, dict):
356
+ if id(obj) in seen:
357
+ return 'dict(...)'
358
+ seen |= {id(obj)}
363
359
  try:
364
- sorted_keys = sorted(object)
365
- except Exception:
366
- pass # Cannot sort dict keys, fall back to generic repr
367
- else:
368
- items = ("%s: %s" %
369
- (object_description(key), object_description(object[key]))
370
- for key in sorted_keys)
371
- return "{%s}" % ", ".join(items)
372
- elif isinstance(object, set):
360
+ sorted_keys = sorted(obj)
361
+ except TypeError:
362
+ # Cannot sort dict keys, fall back to using descriptions as a sort key
363
+ sorted_keys = sorted(obj, key=lambda k: object_description(k, _seen=seen))
364
+
365
+ items = ((object_description(key, _seen=seen),
366
+ object_description(obj[key], _seen=seen)) for key in sorted_keys)
367
+ return '{%s}' % ', '.join(f'{key}: {value}' for (key, value) in items)
368
+ elif isinstance(obj, set):
369
+ if id(obj) in seen:
370
+ return 'set(...)'
371
+ seen |= {id(obj)}
373
372
  try:
374
- sorted_values = sorted(object)
373
+ sorted_values = sorted(obj)
375
374
  except TypeError:
376
- pass # Cannot sort set values, fall back to generic repr
377
- else:
378
- return "{%s}" % ", ".join(object_description(x) for x in sorted_values)
379
- elif isinstance(object, frozenset):
375
+ # Cannot sort set values, fall back to using descriptions as a sort key
376
+ sorted_values = sorted(obj, key=lambda x: object_description(x, _seen=seen))
377
+ return '{%s}' % ', '.join(object_description(x, _seen=seen) for x in sorted_values)
378
+ elif isinstance(obj, frozenset):
379
+ if id(obj) in seen:
380
+ return 'frozenset(...)'
381
+ seen |= {id(obj)}
380
382
  try:
381
- sorted_values = sorted(object)
383
+ sorted_values = sorted(obj)
382
384
  except TypeError:
383
- pass # Cannot sort frozenset values, fall back to generic repr
384
- else:
385
- return "frozenset({%s})" % ", ".join(object_description(x)
386
- for x in sorted_values)
387
- elif isinstance(object, enum.Enum):
388
- return f"{object.__class__.__name__}.{object.name}"
385
+ # Cannot sort frozenset values, fall back to using descriptions as a sort key
386
+ sorted_values = sorted(obj, key=lambda x: object_description(x, _seen=seen))
387
+ return 'frozenset({%s})' % ', '.join(object_description(x, _seen=seen)
388
+ for x in sorted_values)
389
+ elif isinstance(obj, enum.Enum):
390
+ return f'{obj.__class__.__name__}.{obj.name}'
391
+ elif isinstance(obj, tuple):
392
+ if id(obj) in seen:
393
+ return 'tuple(...)'
394
+ seen |= frozenset([id(obj)])
395
+ return '(%s%s)' % (
396
+ ', '.join(object_description(x, _seen=seen) for x in obj),
397
+ ',' * (len(obj) == 1),
398
+ )
399
+ elif isinstance(obj, list):
400
+ if id(obj) in seen:
401
+ return 'list(...)'
402
+ seen |= {id(obj)}
403
+ return '[%s]' % ', '.join(object_description(x, _seen=seen) for x in obj)
389
404
 
390
405
  try:
391
- s = repr(object)
406
+ s = repr(obj)
392
407
  except Exception as exc:
393
408
  raise ValueError from exc
394
409
  # Strip non-deterministic memory addresses such as
@@ -488,7 +503,7 @@ class TypeAliasModule:
488
503
  return getattr(self.__module, name)
489
504
 
490
505
 
491
- class TypeAliasNamespace(Dict[str, Any]):
506
+ class TypeAliasNamespace(dict[str, Any]):
492
507
  """Pseudo namespace class for autodoc_type_aliases.
493
508
 
494
509
  This enables to look up nested modules and classes like `mod1.mod2.Class`.
@@ -522,12 +537,14 @@ def _should_unwrap(subject: Callable) -> bool:
522
537
  return False
523
538
 
524
539
 
525
- def signature(subject: Callable, bound_method: bool = False, type_aliases: dict = {},
540
+ def signature(subject: Callable, bound_method: bool = False, type_aliases: dict | None = None,
526
541
  ) -> inspect.Signature:
527
542
  """Return a Signature object for the given *subject*.
528
543
 
529
544
  :param bound_method: Specify *subject* is a bound method or not
530
545
  """
546
+ if type_aliases is None:
547
+ type_aliases = {}
531
548
 
532
549
  try:
533
550
  if _should_unwrap(subject):
@@ -584,10 +601,7 @@ def evaluate_signature(sig: inspect.Signature, globalns: dict | None = None,
584
601
  """Evaluate unresolved type annotations in a signature object."""
585
602
  def evaluate_forwardref(ref: ForwardRef, globalns: dict, localns: dict) -> Any:
586
603
  """Evaluate a forward reference."""
587
- if sys.version_info[:2] >= (3, 9):
588
- return ref._evaluate(globalns, localns, frozenset())
589
- else:
590
- return ref._evaluate(globalns, localns)
604
+ return ref._evaluate(globalns, localns, frozenset())
591
605
 
592
606
  def evaluate(annotation: Any, globalns: dict, localns: dict) -> Any:
593
607
  """Evaluate unresolved type annotation."""
@@ -710,14 +724,15 @@ def signature_from_ast(node: ast.FunctionDef, code: str = '') -> inspect.Signatu
710
724
  positionals = len(args.args)
711
725
 
712
726
  for _ in range(len(defaults), positionals):
713
- defaults.insert(0, Parameter.empty) # type: ignore
727
+ defaults.insert(0, Parameter.empty) # type: ignore[arg-type]
714
728
 
715
729
  if hasattr(args, "posonlyargs"):
716
730
  for i, arg in enumerate(args.posonlyargs):
717
731
  if defaults[i] is Parameter.empty:
718
732
  default = Parameter.empty
719
733
  else:
720
- default = DefaultValue(ast_unparse(defaults[i], code)) # type: ignore
734
+ default = DefaultValue(
735
+ ast_unparse(defaults[i], code)) # type: ignore[assignment]
721
736
 
722
737
  annotation = ast_unparse(arg.annotation, code) or Parameter.empty
723
738
  params.append(Parameter(arg.arg, Parameter.POSITIONAL_ONLY,
@@ -728,7 +743,7 @@ def signature_from_ast(node: ast.FunctionDef, code: str = '') -> inspect.Signatu
728
743
  default = Parameter.empty
729
744
  else:
730
745
  default = DefaultValue(
731
- ast_unparse(defaults[i + posonlyargs], code), # type: ignore
746
+ ast_unparse(defaults[i + posonlyargs], code), # type: ignore[assignment]
732
747
  )
733
748
 
734
749
  annotation = ast_unparse(arg.annotation, code) or Parameter.empty
@@ -744,7 +759,8 @@ def signature_from_ast(node: ast.FunctionDef, code: str = '') -> inspect.Signatu
744
759
  if args.kw_defaults[i] is None:
745
760
  default = Parameter.empty
746
761
  else:
747
- default = DefaultValue(ast_unparse(args.kw_defaults[i], code)) # type: ignore
762
+ default = DefaultValue(
763
+ ast_unparse(args.kw_defaults[i], code)) # type: ignore[arg-type,assignment]
748
764
  annotation = ast_unparse(arg.annotation, code) or Parameter.empty
749
765
  params.append(Parameter(arg.arg, Parameter.KEYWORD_ONLY, default=default,
750
766
  annotation=annotation))
sphinx/util/inventory.py CHANGED
@@ -4,17 +4,19 @@ from __future__ import annotations
4
4
  import os
5
5
  import re
6
6
  import zlib
7
- from typing import IO, TYPE_CHECKING, Callable, Iterator
7
+ from typing import IO, TYPE_CHECKING, Callable
8
8
 
9
9
  from sphinx.util import logging
10
- from sphinx.util.typing import Inventory, InventoryItem
11
10
 
12
11
  BUFSIZE = 16 * 1024
13
12
  logger = logging.getLogger(__name__)
14
13
 
15
14
  if TYPE_CHECKING:
15
+ from collections.abc import Iterator
16
+
16
17
  from sphinx.builders import Builder
17
18
  from sphinx.environment import BuildEnvironment
19
+ from sphinx.util.typing import Inventory, InventoryItem
18
20
 
19
21
 
20
22
  class InventoryFileReader:
sphinx/util/logging.py CHANGED
@@ -6,10 +6,9 @@ import logging
6
6
  import logging.handlers
7
7
  from collections import defaultdict
8
8
  from contextlib import contextmanager
9
- from typing import IO, TYPE_CHECKING, Any, Generator
9
+ from typing import IO, TYPE_CHECKING, Any
10
10
 
11
11
  from docutils import nodes
12
- from docutils.nodes import Node
13
12
  from docutils.utils import get_source_line
14
13
 
15
14
  from sphinx.errors import SphinxWarning
@@ -17,6 +16,10 @@ from sphinx.util.console import colorize
17
16
  from sphinx.util.osutil import abspath
18
17
 
19
18
  if TYPE_CHECKING:
19
+ from collections.abc import Generator
20
+
21
+ from docutils.nodes import Node
22
+
20
23
  from sphinx.application import Sphinx
21
24
 
22
25
 
@@ -104,7 +107,7 @@ class SphinxInfoLogRecord(SphinxLogRecord):
104
107
  class SphinxWarningLogRecord(SphinxLogRecord):
105
108
  """Warning log record class supporting location"""
106
109
  @property
107
- def prefix(self) -> str: # type: ignore
110
+ def prefix(self) -> str: # type: ignore[override]
108
111
  if self.levelno >= logging.CRITICAL:
109
112
  return 'CRITICAL: '
110
113
  elif self.levelno >= logging.ERROR:
@@ -129,7 +132,7 @@ class SphinxLoggerAdapter(logging.LoggerAdapter):
129
132
  def verbose(self, msg: str, *args: Any, **kwargs: Any) -> None:
130
133
  self.log(VERBOSE, msg, *args, **kwargs)
131
134
 
132
- def process(self, msg: str, kwargs: dict) -> tuple[str, dict]: # type: ignore
135
+ def process(self, msg: str, kwargs: dict) -> tuple[str, dict]: # type: ignore[override]
133
136
  extra = kwargs.setdefault('extra', {})
134
137
  for keyword in self.KEYWORDS:
135
138
  if keyword in kwargs:
@@ -479,10 +482,10 @@ class SphinxLogRecordTranslator(logging.Filter):
479
482
  self.app = app
480
483
  super().__init__()
481
484
 
482
- def filter(self, record: SphinxWarningLogRecord) -> bool: # type: ignore
485
+ def filter(self, record: SphinxWarningLogRecord) -> bool: # type: ignore[override]
483
486
  if isinstance(record, logging.LogRecord):
484
487
  # force subclassing to handle location
485
- record.__class__ = self.LogRecordClass # type: ignore
488
+ record.__class__ = self.LogRecordClass # type: ignore[assignment]
486
489
 
487
490
  location = getattr(record, 'location', None)
488
491
  if isinstance(location, tuple):
sphinx/util/matching.py CHANGED
@@ -4,10 +4,13 @@ from __future__ import annotations
4
4
 
5
5
  import os.path
6
6
  import re
7
- from typing import Callable, Iterable, Iterator
7
+ from typing import TYPE_CHECKING, Callable
8
8
 
9
9
  from sphinx.util.osutil import canon_path, path_stabilize
10
10
 
11
+ if TYPE_CHECKING:
12
+ from collections.abc import Iterable, Iterator
13
+
11
14
 
12
15
  def _translate_pattern(pat: str) -> str:
13
16
  """Translate a shell-style glob pattern to a regular expression.
@@ -107,7 +110,7 @@ def patfilter(names: Iterable[str], pat: str) -> list[str]:
107
110
 
108
111
 
109
112
  def get_matching_files(
110
- dirname: str,
113
+ dirname: str | os.PathLike[str],
111
114
  include_patterns: Iterable[str] = ("**",),
112
115
  exclude_patterns: Iterable[str] = (),
113
116
  ) -> Iterator[str]:
sphinx/util/math.py CHANGED
@@ -2,16 +2,19 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from docutils import nodes
5
+ from typing import TYPE_CHECKING
6
6
 
7
- from sphinx.builders.html import HTML5Translator
7
+ if TYPE_CHECKING:
8
+ from docutils import nodes
9
+
10
+ from sphinx.builders.html import HTML5Translator
8
11
 
9
12
 
10
13
  def get_node_equation_number(writer: HTML5Translator, node: nodes.math_block) -> str:
11
14
  if writer.builder.config.math_numfig and writer.builder.config.numfig:
12
15
  figtype = 'displaymath'
13
16
  if writer.builder.name == 'singlehtml':
14
- key = f"{writer.docnames[-1]}/{figtype}"
17
+ key = f"{writer.docnames[-1]}/{figtype}" # type: ignore[has-type]
15
18
  else:
16
19
  key = figtype
17
20