Sphinx 7.4.7__py3-none-any.whl → 8.0.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 (234) hide show
  1. sphinx/__init__.py +2 -2
  2. sphinx/_cli/__init__.py +4 -4
  3. sphinx/application.py +2 -2
  4. sphinx/builders/__init__.py +2 -3
  5. sphinx/builders/_epub_base.py +33 -12
  6. sphinx/builders/changes.py +13 -5
  7. sphinx/builders/epub3.py +6 -2
  8. sphinx/builders/html/__init__.py +88 -58
  9. sphinx/builders/latex/__init__.py +38 -12
  10. sphinx/builders/latex/transforms.py +1 -1
  11. sphinx/builders/linkcheck.py +8 -49
  12. sphinx/builders/texinfo.py +12 -6
  13. sphinx/builders/text.py +7 -3
  14. sphinx/builders/xml.py +7 -3
  15. sphinx/cmd/quickstart.py +10 -20
  16. sphinx/config.py +13 -13
  17. sphinx/deprecation.py +8 -8
  18. sphinx/directives/other.py +2 -3
  19. sphinx/directives/patches.py +2 -2
  20. sphinx/domains/__init__.py +4 -2
  21. sphinx/domains/c/__init__.py +2 -2
  22. sphinx/domains/c/_ast.py +3 -2
  23. sphinx/domains/c/_parser.py +4 -3
  24. sphinx/domains/cpp/__init__.py +2 -2
  25. sphinx/domains/cpp/_ast.py +1 -2
  26. sphinx/domains/cpp/_parser.py +2 -2
  27. sphinx/domains/cpp/_symbol.py +2 -2
  28. sphinx/domains/math.py +1 -1
  29. sphinx/domains/python/_object.py +0 -1
  30. sphinx/domains/std/__init__.py +7 -8
  31. sphinx/environment/__init__.py +15 -32
  32. sphinx/environment/adapters/indexentries.py +4 -6
  33. sphinx/environment/adapters/toctree.py +4 -4
  34. sphinx/environment/collectors/title.py +1 -1
  35. sphinx/environment/collectors/toctree.py +1 -1
  36. sphinx/events.py +3 -1
  37. sphinx/ext/autodoc/__init__.py +17 -63
  38. sphinx/ext/autodoc/directive.py +7 -5
  39. sphinx/ext/autodoc/importer.py +2 -1
  40. sphinx/ext/autodoc/preserve_defaults.py +2 -2
  41. sphinx/ext/autosummary/__init__.py +7 -6
  42. sphinx/ext/autosummary/generate.py +5 -4
  43. sphinx/ext/doctest.py +5 -5
  44. sphinx/ext/graphviz.py +1 -1
  45. sphinx/ext/imgmath.py +1 -1
  46. sphinx/ext/inheritance_diagram.py +1 -1
  47. sphinx/ext/intersphinx/__init__.py +25 -5
  48. sphinx/ext/intersphinx/_cli.py +7 -6
  49. sphinx/ext/intersphinx/_load.py +240 -115
  50. sphinx/ext/intersphinx/_resolve.py +12 -11
  51. sphinx/ext/intersphinx/_shared.py +102 -9
  52. sphinx/ext/mathjax.py +1 -1
  53. sphinx/ext/napoleon/docstring.py +2 -2
  54. sphinx/ext/todo.py +2 -2
  55. sphinx/ext/viewcode.py +2 -1
  56. sphinx/highlighting.py +3 -3
  57. sphinx/io.py +2 -2
  58. sphinx/jinja2glue.py +13 -6
  59. sphinx/locale/__init__.py +4 -3
  60. sphinx/locale/ar/LC_MESSAGES/sphinx.mo +0 -0
  61. sphinx/locale/ar/LC_MESSAGES/sphinx.po +2383 -2186
  62. sphinx/locale/bg/LC_MESSAGES/sphinx.mo +0 -0
  63. sphinx/locale/bg/LC_MESSAGES/sphinx.po +2249 -2052
  64. sphinx/locale/bn/LC_MESSAGES/sphinx.mo +0 -0
  65. sphinx/locale/bn/LC_MESSAGES/sphinx.po +2412 -2215
  66. sphinx/locale/ca/LC_MESSAGES/sphinx.mo +0 -0
  67. sphinx/locale/ca/LC_MESSAGES/sphinx.po +3029 -2832
  68. sphinx/locale/cak/LC_MESSAGES/sphinx.mo +0 -0
  69. sphinx/locale/cak/LC_MESSAGES/sphinx.po +2308 -2111
  70. sphinx/locale/cs/LC_MESSAGES/sphinx.mo +0 -0
  71. sphinx/locale/cs/LC_MESSAGES/sphinx.po +2469 -2272
  72. sphinx/locale/cy/LC_MESSAGES/sphinx.mo +0 -0
  73. sphinx/locale/cy/LC_MESSAGES/sphinx.po +2393 -2196
  74. sphinx/locale/da/LC_MESSAGES/sphinx.mo +0 -0
  75. sphinx/locale/da/LC_MESSAGES/sphinx.po +2532 -2335
  76. sphinx/locale/de/LC_MESSAGES/sphinx.mo +0 -0
  77. sphinx/locale/de/LC_MESSAGES/sphinx.po +2492 -2295
  78. sphinx/locale/de_DE/LC_MESSAGES/sphinx.mo +0 -0
  79. sphinx/locale/de_DE/LC_MESSAGES/sphinx.po +2250 -2053
  80. sphinx/locale/el/LC_MESSAGES/sphinx.mo +0 -0
  81. sphinx/locale/el/LC_MESSAGES/sphinx.po +2879 -2682
  82. sphinx/locale/en_DE/LC_MESSAGES/sphinx.mo +0 -0
  83. sphinx/locale/en_DE/LC_MESSAGES/sphinx.po +2250 -2053
  84. sphinx/locale/en_FR/LC_MESSAGES/sphinx.mo +0 -0
  85. sphinx/locale/en_FR/LC_MESSAGES/sphinx.po +2250 -2053
  86. sphinx/locale/en_GB/LC_MESSAGES/sphinx.mo +0 -0
  87. sphinx/locale/en_GB/LC_MESSAGES/sphinx.po +2989 -2792
  88. sphinx/locale/en_HK/LC_MESSAGES/sphinx.mo +0 -0
  89. sphinx/locale/en_HK/LC_MESSAGES/sphinx.po +2250 -2053
  90. sphinx/locale/eo/LC_MESSAGES/sphinx.mo +0 -0
  91. sphinx/locale/eo/LC_MESSAGES/sphinx.po +2297 -2100
  92. sphinx/locale/es/LC_MESSAGES/sphinx.mo +0 -0
  93. sphinx/locale/es/LC_MESSAGES/sphinx.po +3017 -2820
  94. sphinx/locale/es_CO/LC_MESSAGES/sphinx.mo +0 -0
  95. sphinx/locale/es_CO/LC_MESSAGES/sphinx.po +2250 -2053
  96. sphinx/locale/et/LC_MESSAGES/sphinx.mo +0 -0
  97. sphinx/locale/et/LC_MESSAGES/sphinx.po +2748 -2551
  98. sphinx/locale/eu/LC_MESSAGES/sphinx.mo +0 -0
  99. sphinx/locale/eu/LC_MESSAGES/sphinx.po +2459 -2262
  100. sphinx/locale/fa/LC_MESSAGES/sphinx.mo +0 -0
  101. sphinx/locale/fa/LC_MESSAGES/sphinx.po +2957 -2760
  102. sphinx/locale/fi/LC_MESSAGES/sphinx.mo +0 -0
  103. sphinx/locale/fi/LC_MESSAGES/sphinx.po +2321 -2124
  104. sphinx/locale/fr/LC_MESSAGES/sphinx.mo +0 -0
  105. sphinx/locale/fr/LC_MESSAGES/sphinx.po +2977 -2780
  106. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.mo +0 -0
  107. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.po +2250 -2053
  108. sphinx/locale/gl/LC_MESSAGES/sphinx.mo +0 -0
  109. sphinx/locale/gl/LC_MESSAGES/sphinx.po +2992 -2795
  110. sphinx/locale/he/LC_MESSAGES/sphinx.mo +0 -0
  111. sphinx/locale/he/LC_MESSAGES/sphinx.po +2375 -2178
  112. sphinx/locale/hi/LC_MESSAGES/sphinx.mo +0 -0
  113. sphinx/locale/hi/LC_MESSAGES/sphinx.po +2937 -2740
  114. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.mo +0 -0
  115. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.po +2250 -2053
  116. sphinx/locale/hr/LC_MESSAGES/sphinx.mo +0 -0
  117. sphinx/locale/hr/LC_MESSAGES/sphinx.po +2532 -2335
  118. sphinx/locale/hu/LC_MESSAGES/sphinx.mo +0 -0
  119. sphinx/locale/hu/LC_MESSAGES/sphinx.po +2505 -2308
  120. sphinx/locale/id/LC_MESSAGES/sphinx.mo +0 -0
  121. sphinx/locale/id/LC_MESSAGES/sphinx.po +2925 -2728
  122. sphinx/locale/is/LC_MESSAGES/sphinx.mo +0 -0
  123. sphinx/locale/is/LC_MESSAGES/sphinx.po +2307 -2110
  124. sphinx/locale/it/LC_MESSAGES/sphinx.mo +0 -0
  125. sphinx/locale/it/LC_MESSAGES/sphinx.po +2514 -2317
  126. sphinx/locale/ja/LC_MESSAGES/sphinx.mo +0 -0
  127. sphinx/locale/ja/LC_MESSAGES/sphinx.po +2970 -2773
  128. sphinx/locale/ka/LC_MESSAGES/sphinx.mo +0 -0
  129. sphinx/locale/ka/LC_MESSAGES/sphinx.po +2868 -2671
  130. sphinx/locale/ko/LC_MESSAGES/sphinx.mo +0 -0
  131. sphinx/locale/ko/LC_MESSAGES/sphinx.po +3016 -2819
  132. sphinx/locale/lt/LC_MESSAGES/sphinx.mo +0 -0
  133. sphinx/locale/lt/LC_MESSAGES/sphinx.po +2476 -2279
  134. sphinx/locale/lv/LC_MESSAGES/sphinx.mo +0 -0
  135. sphinx/locale/lv/LC_MESSAGES/sphinx.po +2477 -2280
  136. sphinx/locale/mk/LC_MESSAGES/sphinx.mo +0 -0
  137. sphinx/locale/mk/LC_MESSAGES/sphinx.po +2292 -2095
  138. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.mo +0 -0
  139. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.po +2479 -2282
  140. sphinx/locale/ne/LC_MESSAGES/sphinx.mo +0 -0
  141. sphinx/locale/ne/LC_MESSAGES/sphinx.po +2481 -2284
  142. sphinx/locale/nl/LC_MESSAGES/sphinx.mo +0 -0
  143. sphinx/locale/nl/LC_MESSAGES/sphinx.po +2557 -2360
  144. sphinx/locale/pl/LC_MESSAGES/sphinx.mo +0 -0
  145. sphinx/locale/pl/LC_MESSAGES/sphinx.po +2696 -2499
  146. sphinx/locale/pt/LC_MESSAGES/sphinx.mo +0 -0
  147. sphinx/locale/pt/LC_MESSAGES/sphinx.po +2250 -2053
  148. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.mo +0 -0
  149. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.po +2979 -2782
  150. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.mo +0 -0
  151. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.po +2469 -2272
  152. sphinx/locale/ro/LC_MESSAGES/sphinx.mo +0 -0
  153. sphinx/locale/ro/LC_MESSAGES/sphinx.po +2473 -2276
  154. sphinx/locale/ru/LC_MESSAGES/sphinx.mo +0 -0
  155. sphinx/locale/ru/LC_MESSAGES/sphinx.po +2746 -2549
  156. sphinx/locale/si/LC_MESSAGES/sphinx.mo +0 -0
  157. sphinx/locale/si/LC_MESSAGES/sphinx.po +2331 -2134
  158. sphinx/locale/sk/LC_MESSAGES/sphinx.mo +0 -0
  159. sphinx/locale/sk/LC_MESSAGES/sphinx.po +2966 -2769
  160. sphinx/locale/sl/LC_MESSAGES/sphinx.mo +0 -0
  161. sphinx/locale/sl/LC_MESSAGES/sphinx.po +2404 -2207
  162. sphinx/locale/sphinx.pot +2262 -2065
  163. sphinx/locale/sq/LC_MESSAGES/sphinx.mo +0 -0
  164. sphinx/locale/sq/LC_MESSAGES/sphinx.po +2972 -2775
  165. sphinx/locale/sr/LC_MESSAGES/sphinx.mo +0 -0
  166. sphinx/locale/sr/LC_MESSAGES/sphinx.po +2440 -2243
  167. sphinx/locale/sv/LC_MESSAGES/sphinx.mo +0 -0
  168. sphinx/locale/sv/LC_MESSAGES/sphinx.po +2483 -2286
  169. sphinx/locale/te/LC_MESSAGES/sphinx.mo +0 -0
  170. sphinx/locale/te/LC_MESSAGES/sphinx.po +2250 -2053
  171. sphinx/locale/tr/LC_MESSAGES/sphinx.mo +0 -0
  172. sphinx/locale/tr/LC_MESSAGES/sphinx.po +2892 -2695
  173. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.mo +0 -0
  174. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.po +2400 -2203
  175. sphinx/locale/ur/LC_MESSAGES/sphinx.mo +0 -0
  176. sphinx/locale/ur/LC_MESSAGES/sphinx.po +2250 -2053
  177. sphinx/locale/vi/LC_MESSAGES/sphinx.mo +0 -0
  178. sphinx/locale/vi/LC_MESSAGES/sphinx.po +2422 -2225
  179. sphinx/locale/yue/LC_MESSAGES/sphinx.mo +0 -0
  180. sphinx/locale/yue/LC_MESSAGES/sphinx.po +2250 -2053
  181. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.mo +0 -0
  182. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.po +2250 -2053
  183. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.mo +0 -0
  184. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po +3028 -2831
  185. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.mo +0 -0
  186. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.po +2250 -2053
  187. sphinx/project.py +25 -20
  188. sphinx/pycode/ast.py +2 -2
  189. sphinx/pycode/parser.py +2 -2
  190. sphinx/pygments_styles.py +3 -3
  191. sphinx/registry.py +3 -8
  192. sphinx/search/__init__.py +1 -1
  193. sphinx/testing/path.py +2 -1
  194. sphinx/testing/util.py +1 -1
  195. sphinx/texinputs/Makefile.jinja +2 -1
  196. sphinx/texinputs_win/Makefile.jinja +2 -1
  197. sphinx/theming.py +3 -12
  198. sphinx/transforms/__init__.py +5 -5
  199. sphinx/transforms/references.py +1 -1
  200. sphinx/util/__init__.py +11 -35
  201. sphinx/util/_pathlib.py +31 -19
  202. sphinx/util/_timestamps.py +12 -0
  203. sphinx/util/cfamily.py +5 -5
  204. sphinx/util/console.py +4 -3
  205. sphinx/util/display.py +3 -3
  206. sphinx/util/docfields.py +1 -1
  207. sphinx/util/docutils.py +44 -10
  208. sphinx/util/fileutil.py +25 -20
  209. sphinx/util/i18n.py +9 -4
  210. sphinx/util/images.py +3 -2
  211. sphinx/util/inspect.py +28 -43
  212. sphinx/util/inventory.py +2 -2
  213. sphinx/util/matching.py +2 -2
  214. sphinx/util/math.py +1 -1
  215. sphinx/util/nodes.py +8 -8
  216. sphinx/util/osutil.py +36 -32
  217. sphinx/util/parallel.py +2 -2
  218. sphinx/util/requests.py +1 -1
  219. sphinx/util/template.py +3 -3
  220. sphinx/util/typing.py +36 -72
  221. sphinx/writers/html.py +1 -1
  222. sphinx/writers/html5.py +1 -1
  223. sphinx/writers/latex.py +4 -4
  224. sphinx/writers/manpage.py +2 -2
  225. sphinx/writers/texinfo.py +5 -5
  226. sphinx/writers/text.py +4 -4
  227. sphinx/writers/xml.py +2 -2
  228. {sphinx-7.4.7.dist-info → sphinx-8.0.0.dist-info}/METADATA +11 -10
  229. {sphinx-7.4.7.dist-info → sphinx-8.0.0.dist-info}/RECORD +232 -233
  230. sphinx/templates/quickstart/Makefile.jinja +0 -98
  231. sphinx/templates/quickstart/make.bat.jinja +0 -110
  232. {sphinx-7.4.7.dist-info → sphinx-8.0.0.dist-info}/LICENSE.rst +0 -0
  233. {sphinx-7.4.7.dist-info → sphinx-8.0.0.dist-info}/WHEEL +0 -0
  234. {sphinx-7.4.7.dist-info → sphinx-8.0.0.dist-info}/entry_points.txt +0 -0
sphinx/project.py CHANGED
@@ -4,13 +4,14 @@ from __future__ import annotations
4
4
 
5
5
  import contextlib
6
6
  import os
7
- from glob import glob
7
+ from pathlib import Path
8
8
  from typing import TYPE_CHECKING
9
9
 
10
10
  from sphinx.locale import __
11
11
  from sphinx.util import logging
12
+ from sphinx.util._pathlib import _StrPath
12
13
  from sphinx.util.matching import get_matching_files
13
- from sphinx.util.osutil import path_stabilize, relpath
14
+ from sphinx.util.osutil import path_stabilize
14
15
 
15
16
  if TYPE_CHECKING:
16
17
  from collections.abc import Iterable
@@ -24,7 +25,7 @@ class Project:
24
25
 
25
26
  def __init__(self, srcdir: str | os.PathLike[str], source_suffix: Iterable[str]) -> None:
26
27
  #: Source directory.
27
- self.srcdir = srcdir
28
+ self.srcdir = _StrPath(srcdir)
28
29
 
29
30
  #: source_suffix. Same as :confval:`source_suffix`.
30
31
  self.source_suffix = tuple(source_suffix)
@@ -34,8 +35,8 @@ class Project:
34
35
  self.docnames: set[str] = set()
35
36
 
36
37
  # Bijective mapping between docnames and (srcdir relative) paths.
37
- self._path_to_docname: dict[str, str] = {}
38
- self._docname_to_path: dict[str, str] = {}
38
+ self._path_to_docname: dict[Path, str] = {}
39
+ self._docname_to_path: dict[str, Path] = {}
39
40
 
40
41
  def restore(self, other: Project) -> None:
41
42
  """Take over a result of last build."""
@@ -60,22 +61,25 @@ class Project:
60
61
  ):
61
62
  if docname := self.path2doc(filename):
62
63
  if docname in self.docnames:
63
- pattern = os.path.join(self.srcdir, docname) + '.*'
64
- files = [relpath(f, self.srcdir) for f in glob(pattern)]
64
+ files = [
65
+ str(f.relative_to(self.srcdir))
66
+ for f in self.srcdir.glob(f'{docname}.*')
67
+ ]
65
68
  logger.warning(
66
69
  __(
67
- 'multiple files found for the document "%s": %r\n'
70
+ 'multiple files found for the document "%s": %s\n'
68
71
  'Use %r for the build.'
69
72
  ),
70
73
  docname,
71
- files,
74
+ ', '.join(files),
72
75
  self.doc2path(docname, absolute=True),
73
76
  once=True,
74
77
  )
75
- elif os.access(os.path.join(self.srcdir, filename), os.R_OK):
78
+ elif os.access(self.srcdir / filename, os.R_OK):
76
79
  self.docnames.add(docname)
77
- self._path_to_docname[filename] = docname
78
- self._docname_to_path[docname] = filename
80
+ path = Path(filename)
81
+ self._path_to_docname[path] = docname
82
+ self._docname_to_path[docname] = path
79
83
  else:
80
84
  logger.warning(
81
85
  __('Ignored unreadable document %r.'), filename, location=docname
@@ -91,18 +95,19 @@ class Project:
91
95
  try:
92
96
  return self._path_to_docname[filename] # type: ignore[index]
93
97
  except KeyError:
94
- if os.path.isabs(filename):
98
+ path = Path(filename)
99
+ if path.is_absolute():
95
100
  with contextlib.suppress(ValueError):
96
- filename = os.path.relpath(filename, self.srcdir)
101
+ path = path.relative_to(self.srcdir)
97
102
 
98
103
  for suffix in self.source_suffix:
99
- if os.path.basename(filename).endswith(suffix):
100
- return path_stabilize(filename).removesuffix(suffix)
104
+ if path.name.endswith(suffix):
105
+ return path_stabilize(path).removesuffix(suffix)
101
106
 
102
107
  # the file does not have a docname
103
108
  return None
104
109
 
105
- def doc2path(self, docname: str, absolute: bool) -> str:
110
+ def doc2path(self, docname: str, absolute: bool) -> _StrPath:
106
111
  """Return the filename for the document name.
107
112
 
108
113
  If *absolute* is True, return as an absolute path.
@@ -112,8 +117,8 @@ class Project:
112
117
  filename = self._docname_to_path[docname]
113
118
  except KeyError:
114
119
  # Backwards compatibility: the document does not exist
115
- filename = docname + self._first_source_suffix
120
+ filename = Path(docname + self._first_source_suffix)
116
121
 
117
122
  if absolute:
118
- return os.path.join(self.srcdir, filename)
119
- return filename
123
+ return _StrPath(self.srcdir / filename)
124
+ return _StrPath(filename)
sphinx/pycode/ast.py CHANGED
@@ -130,7 +130,7 @@ class _UnparseVisitor(ast.NodeVisitor):
130
130
  def visit_Constant(self, node: ast.Constant) -> str:
131
131
  if node.value is Ellipsis:
132
132
  return "..."
133
- elif isinstance(node.value, (int, float, complex)):
133
+ elif isinstance(node.value, int | float | complex):
134
134
  if self.code:
135
135
  return ast.get_source_segment(self.code, node) or repr(node.value)
136
136
  else:
@@ -141,7 +141,7 @@ class _UnparseVisitor(ast.NodeVisitor):
141
141
  def visit_Dict(self, node: ast.Dict) -> str:
142
142
  keys = (self.visit(k) for k in node.keys if k is not None)
143
143
  values = (self.visit(v) for v in node.values)
144
- items = (k + ": " + v for k, v in zip(keys, values))
144
+ items = (k + ": " + v for k, v in zip(keys, values, strict=True))
145
145
  return "{" + ", ".join(items) + "}"
146
146
 
147
147
  def visit_Lambda(self, node: ast.Lambda) -> str:
sphinx/pycode/parser.py CHANGED
@@ -108,7 +108,7 @@ class Token:
108
108
  return self.kind == other
109
109
  elif isinstance(other, str):
110
110
  return self.value == other
111
- elif isinstance(other, (list, tuple)):
111
+ elif isinstance(other, list | tuple):
112
112
  return [self.kind, self.value] == list(other)
113
113
  elif other is None:
114
114
  return False
@@ -404,7 +404,7 @@ class VariableCommentPicker(ast.NodeVisitor):
404
404
 
405
405
  def visit_Expr(self, node: ast.Expr) -> None:
406
406
  """Handles Expr node and pick up a comment if string."""
407
- if (isinstance(self.previous, (ast.Assign, ast.AnnAssign)) and
407
+ if (isinstance(self.previous, ast.Assign | ast.AnnAssign) and
408
408
  isinstance(node.value, ast.Constant) and isinstance(node.value.value, str)):
409
409
  try:
410
410
  targets = get_assign_targets(self.previous)
sphinx/pygments_styles.py CHANGED
@@ -28,12 +28,12 @@ class SphinxStyle(Style):
28
28
  background_color = '#eeffcc'
29
29
  default_style = ''
30
30
 
31
- styles = FriendlyStyle.styles
32
- styles.update({
31
+ styles = {
32
+ **FriendlyStyle.styles,
33
33
  Generic.Output: '#333',
34
34
  Comment: 'italic #408090',
35
35
  Number: '#208050',
36
- })
36
+ }
37
37
 
38
38
 
39
39
  class PyramidStyle(Style):
sphinx/registry.py CHANGED
@@ -2,16 +2,11 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import sys
6
5
  import traceback
7
6
  from importlib import import_module
7
+ from importlib.metadata import entry_points
8
8
  from types import MethodType
9
- from typing import TYPE_CHECKING, Any, Callable
10
-
11
- if sys.version_info >= (3, 10):
12
- from importlib.metadata import entry_points
13
- else:
14
- from importlib_metadata import entry_points
9
+ from typing import TYPE_CHECKING, Any
15
10
 
16
11
  from sphinx.domains import Domain, Index, ObjType
17
12
  from sphinx.domains.std import GenericObject, Target
@@ -25,7 +20,7 @@ from sphinx.util import logging
25
20
  from sphinx.util.logging import prefixed_warnings
26
21
 
27
22
  if TYPE_CHECKING:
28
- from collections.abc import Iterator, Sequence
23
+ from collections.abc import Callable, Iterator, Sequence
29
24
 
30
25
  from docutils import nodes
31
26
  from docutils.core import Publisher
sphinx/search/__init__.py CHANGED
@@ -487,7 +487,7 @@ class IndexBuilder:
487
487
  self._index_entries[docname] = sorted(_index_entries)
488
488
 
489
489
  def _word_collector(self, doctree: nodes.document) -> WordStore:
490
- def _visit_nodes(node):
490
+ def _visit_nodes(node: nodes.Node) -> None:
491
491
  if isinstance(node, nodes.comment):
492
492
  return
493
493
  elif isinstance(node, nodes.raw):
sphinx/testing/path.py CHANGED
@@ -4,12 +4,13 @@ import os
4
4
  import shutil
5
5
  import sys
6
6
  import warnings
7
- from typing import IO, TYPE_CHECKING, Any, Callable
7
+ from typing import IO, TYPE_CHECKING, Any
8
8
 
9
9
  from sphinx.deprecation import RemovedInSphinx90Warning
10
10
 
11
11
  if TYPE_CHECKING:
12
12
  import builtins
13
+ from collections.abc import Callable
13
14
 
14
15
  warnings.warn("'sphinx.testing.path' is deprecated. "
15
16
  "Use 'os.path' or 'pathlib' instead.",
sphinx/testing/util.py CHANGED
@@ -43,7 +43,7 @@ def assert_node(node: Node, cls: Any = None, xpath: str = "", **kwargs: Any) ->
43
43
  'The node%s has %d child nodes, not one' % (xpath, len(node))
44
44
  assert_node(node[0], cls[1:], xpath=xpath + "[0]", **kwargs)
45
45
  elif isinstance(cls, tuple):
46
- assert isinstance(node, (list, nodes.Element)), \
46
+ assert isinstance(node, list | nodes.Element), \
47
47
  'The node%s does not have any items' % xpath
48
48
  assert len(node) == len(cls), \
49
49
  'The node%s has %d child nodes, not %r' % (xpath, len(node), len(cls))
@@ -77,7 +77,8 @@ tar: all-$(FMT)
77
77
  rm -r $(ARCHIVEPREFIX)docs-$(FMT)
78
78
 
79
79
  gz: tar
80
- gzip -9 < $(ARCHIVEPREFIX)docs-$(FMT).tar > $(ARCHIVEPREFIX)docs-$(FMT).tar.gz
80
+ # -n to omit mtime from gzip headers
81
+ gzip -n -9 < $(ARCHIVEPREFIX)docs-$(FMT).tar > $(ARCHIVEPREFIX)docs-$(FMT).tar.gz
81
82
 
82
83
  bz2: tar
83
84
  bzip2 -9 -k $(ARCHIVEPREFIX)docs-$(FMT).tar
@@ -49,7 +49,8 @@ tar: all-$(FMT)
49
49
  rm -r $(ARCHIVEPREFIX)docs-$(FMT)
50
50
 
51
51
  gz: tar
52
- gzip -9 < $(ARCHIVEPREFIX)docs-$(FMT).tar > $(ARCHIVEPREFIX)docs-$(FMT).tar.gz
52
+ # -n to omit mtime from gzip headers
53
+ gzip -n -9 < $(ARCHIVEPREFIX)docs-$(FMT).tar > $(ARCHIVEPREFIX)docs-$(FMT).tar.gz
53
54
 
54
55
  bz2: tar
55
56
  bzip2 -9 -k $(ARCHIVEPREFIX)docs-$(FMT).tar
sphinx/theming.py CHANGED
@@ -10,6 +10,7 @@ import os
10
10
  import shutil
11
11
  import sys
12
12
  import tempfile
13
+ from importlib.metadata import entry_points
13
14
  from os import path
14
15
  from typing import TYPE_CHECKING, Any
15
16
  from zipfile import ZipFile
@@ -26,10 +27,6 @@ if sys.version_info >= (3, 11):
26
27
  else:
27
28
  import tomli as tomllib
28
29
 
29
- if sys.version_info >= (3, 10):
30
- from importlib.metadata import entry_points
31
- else:
32
- from importlib_metadata import entry_points
33
30
 
34
31
  if TYPE_CHECKING:
35
32
  from collections.abc import Callable
@@ -121,17 +118,11 @@ class Theme:
121
118
  elif section == 'options':
122
119
  value = self._options.get(name, default)
123
120
  else:
124
- # https://github.com/sphinx-doc/sphinx/issues/12305
125
- # For backwards compatibility when attempting to read a value
126
- # from an unsupported configuration section.
127
- # xref: RemovedInSphinx80Warning
128
121
  msg = __(
129
122
  'Theme configuration sections other than [theme] and [options] '
130
- 'are not supported, returning the default value instead '
131
- '(tried to get a value from %r)'
123
+ 'are not supported (tried to get a value from %r).'
132
124
  )
133
- logger.info(msg, section)
134
- value = default
125
+ raise ThemeError(msg)
135
126
  if value is _NO_DEFAULT:
136
127
  msg = __('setting %s.%s occurs in none of the searched theme configs') % (
137
128
  section,
@@ -22,10 +22,10 @@ from sphinx.util.nodes import apply_source_workaround, is_smartquotable
22
22
 
23
23
  if TYPE_CHECKING:
24
24
  from collections.abc import Iterator
25
- from typing import Literal
25
+ from typing import Literal, TypeAlias
26
26
 
27
27
  from docutils.nodes import Node, Text
28
- from typing_extensions import TypeAlias, TypeIs
28
+ from typing_extensions import TypeIs
29
29
 
30
30
  from sphinx.application import Sphinx
31
31
  from sphinx.config import Config
@@ -247,7 +247,7 @@ class ApplySourceWorkaround(SphinxTransform):
247
247
 
248
248
  def apply(self, **kwargs: Any) -> None:
249
249
  for node in self.document.findall(): # type: Node
250
- if isinstance(node, (nodes.TextElement, nodes.image, nodes.topic)):
250
+ if isinstance(node, nodes.TextElement | nodes.image | nodes.topic):
251
251
  apply_source_workaround(node)
252
252
 
253
253
 
@@ -364,7 +364,7 @@ class SphinxSmartQuotes(SmartQuotes, SphinxTransform):
364
364
  # override default settings with :confval:`smartquotes_action`
365
365
  self.smartquotes_action = self.config.smartquotes_action
366
366
 
367
- super().apply()
367
+ super().apply() # type: ignore[no-untyped-call]
368
368
 
369
369
  def is_available(self) -> bool:
370
370
  builders = self.config.smartquotes_excludes.get('builders', [])
@@ -477,7 +477,7 @@ def _reorder_index_target_nodes(start_node: nodes.target) -> None:
477
477
  # as we want *consecutive* target & index nodes.
478
478
  node: nodes.Node
479
479
  for node in start_node.findall(descend=False, siblings=True):
480
- if isinstance(node, (nodes.target, addnodes.index)):
480
+ if isinstance(node, nodes.target | addnodes.index):
481
481
  nodes_to_reorder.append(node)
482
482
  continue
483
483
  break # must be a consecutive run of target or index nodes
@@ -23,7 +23,7 @@ class SphinxDanglingReferences(DanglingReferences):
23
23
 
24
24
  # suppress INFO level messages for a while
25
25
  reporter.report_level = max(reporter.WARNING_LEVEL, reporter.report_level)
26
- super().apply()
26
+ super().apply() # type: ignore[no-untyped-call]
27
27
  finally:
28
28
  reporter.report_level = report_level
29
29
 
sphinx/util/__init__.py CHANGED
@@ -13,12 +13,8 @@ from urllib.parse import parse_qsl, quote_plus, urlencode, urlsplit, urlunsplit
13
13
 
14
14
  from sphinx.errors import ExtensionError, FiletypeNotFoundError
15
15
  from sphinx.locale import __
16
- from sphinx.util import display as _display
17
- from sphinx.util import exceptions as _exceptions
18
- from sphinx.util import http_date as _http_date
19
16
  from sphinx.util import index_entries as _index_entries
20
17
  from sphinx.util import logging
21
- from sphinx.util import osutil as _osutil
22
18
  from sphinx.util.console import strip_colors # NoQA: F401
23
19
  from sphinx.util.matching import patfilter # NoQA: F401
24
20
  from sphinx.util.nodes import ( # NoQA: F401
@@ -33,10 +29,8 @@ from sphinx.util.nodes import ( # NoQA: F401
33
29
  from sphinx.util.osutil import ( # NoQA: F401
34
30
  SEP,
35
31
  copyfile,
36
- copytimes,
37
32
  ensuredir,
38
33
  make_filename,
39
- mtimes_of_files,
40
34
  os_path,
41
35
  relative_uri,
42
36
  )
@@ -54,9 +48,9 @@ def docname_join(basedocname: str, docname: str) -> str:
54
48
  return posixpath.normpath(posixpath.join('/' + basedocname, '..', docname))[1:]
55
49
 
56
50
 
57
- def get_filetype(source_suffix: dict[str, str], filename: str) -> str:
51
+ def get_filetype(source_suffix: dict[str, str], filename: str | os.PathLike) -> str:
58
52
  for suffix, filetype in source_suffix.items():
59
- if filename.endswith(suffix):
53
+ if os.fspath(filename).endswith(suffix):
60
54
  # If default filetype (None), considered as restructuredtext.
61
55
  return filetype or 'restructuredtext'
62
56
  raise FiletypeNotFoundError
@@ -258,32 +252,16 @@ def isurl(url: str) -> bool:
258
252
  return bool(url) and '://' in url
259
253
 
260
254
 
261
- def _xml_name_checker() -> re.Pattern[str]:
262
- # to prevent import cycles
263
- from sphinx.builders.epub3 import _XML_NAME_PATTERN
264
-
265
- return _XML_NAME_PATTERN
266
-
267
-
268
255
  # deprecated name -> (object to return, canonical path or empty string)
269
- _DEPRECATED_OBJECTS: dict[str, tuple[Any, str] | tuple[Any, str, tuple[int, int]]] = {
270
- 'path_stabilize': (_osutil.path_stabilize, 'sphinx.util.osutil.path_stabilize'),
271
- 'display_chunk': (_display.display_chunk, 'sphinx.util.display.display_chunk'),
272
- 'status_iterator': (_display.status_iterator, 'sphinx.util.display.status_iterator'),
273
- 'SkipProgressMessage': (_display.SkipProgressMessage,
274
- 'sphinx.util.display.SkipProgressMessage'),
275
- 'progress_message': (_display.progress_message, 'sphinx.util.display.progress_message'),
276
- 'epoch_to_rfc1123': (_http_date.epoch_to_rfc1123, 'sphinx.http_date.epoch_to_rfc1123'),
277
- 'rfc1123_to_epoch': (_http_date.rfc1123_to_epoch, 'sphinx.http_date.rfc1123_to_epoch'),
278
- 'save_traceback': (_exceptions.save_traceback, 'sphinx.exceptions.save_traceback'),
279
- 'format_exception_cut_frames': (_exceptions.format_exception_cut_frames,
280
- 'sphinx.exceptions.format_exception_cut_frames'),
281
- 'xmlname_checker': (_xml_name_checker, 'sphinx.builders.epub3._XML_NAME_PATTERN'),
256
+ _DEPRECATED_OBJECTS: dict[str, tuple[Any, str, tuple[int, int]]] = {
282
257
  'split_index_msg': (_index_entries.split_index_msg,
283
- 'sphinx.util.index_entries.split_index_msg'),
284
- 'split_into': (_index_entries.split_index_msg, 'sphinx.util.index_entries.split_into'),
285
- 'md5': (_md5, ''),
286
- 'sha1': (_sha1, ''),
258
+ 'sphinx.util.index_entries.split_index_msg',
259
+ (9, 0)),
260
+ 'split_into': (_index_entries.split_index_msg,
261
+ 'sphinx.util.index_entries.split_into',
262
+ (9, 0)),
263
+ 'md5': (_md5, '', (9, 0)),
264
+ 'sha1': (_sha1, '', (9, 0)),
287
265
  }
288
266
 
289
267
 
@@ -294,8 +272,6 @@ def __getattr__(name: str) -> Any:
294
272
 
295
273
  from sphinx.deprecation import _deprecation_warning
296
274
 
297
- info = _DEPRECATED_OBJECTS[name]
298
- deprecated_object, canonical_name = info[:2]
299
- remove = info[2] if len(info) == 3 else (8, 0)
275
+ deprecated_object, canonical_name, remove = _DEPRECATED_OBJECTS[name]
300
276
  _deprecation_warning(__name__, name, canonical_name, remove=remove)
301
277
  return deprecated_object
sphinx/util/_pathlib.py CHANGED
@@ -1,4 +1,16 @@
1
- """What follows is awful and will be gone in Sphinx 8"""
1
+ """What follows is awful and will be gone in Sphinx 9.
2
+
3
+ Instances of _StrPath should not be constructed except in Sphinx itself.
4
+ Consumers of Sphinx APIs should prefer using ``pathlib.Path`` objects
5
+ where possible. _StrPath objects can be treated as equivalent to ``Path``,
6
+ save that ``_StrPath.replace`` is overriden with ``str.replace``.
7
+
8
+ To continue treating path-like objects as strings, use ``os.fspath``,
9
+ or explicit string coercion.
10
+
11
+ In Sphinx 9, ``Path`` objects will be expected and returned in all instances
12
+ that ``_StrPath`` is currently used.
13
+ """
2
14
 
3
15
  from __future__ import annotations
4
16
 
@@ -7,13 +19,13 @@ import warnings
7
19
  from pathlib import Path, PosixPath, PurePath, WindowsPath
8
20
  from typing import Any
9
21
 
10
- from sphinx.deprecation import RemovedInSphinx80Warning
22
+ from sphinx.deprecation import RemovedInSphinx90Warning
11
23
 
12
24
  _STR_METHODS = frozenset(str.__dict__)
13
25
  _PATH_NAME = Path().__class__.__name__
14
26
 
15
27
  _MSG = (
16
- 'Sphinx 8 will drop support for representing paths as strings. '
28
+ 'Sphinx 9 will drop support for representing paths as strings. '
17
29
  'Use "pathlib.Path" or "os.fspath" instead.'
18
30
  )
19
31
 
@@ -27,35 +39,35 @@ if sys.platform == 'win32':
27
39
  ) -> str:
28
40
  # replace exists in both Path and str;
29
41
  # in Path it makes filesystem changes, so we use the safer str version
30
- warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
42
+ warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
31
43
  return self.__str__().replace(old, new, count) # NoQA: PLC2801
32
44
 
33
45
  def __getattr__(self, item: str) -> Any:
34
46
  if item in _STR_METHODS:
35
- warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
47
+ warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
36
48
  return getattr(self.__str__(), item)
37
49
  msg = f'{_PATH_NAME!r} has no attribute {item!r}'
38
50
  raise AttributeError(msg)
39
51
 
40
52
  def __add__(self, other: str) -> str:
41
- warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
53
+ warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
42
54
  return self.__str__() + other
43
55
 
44
56
  def __bool__(self) -> bool:
45
57
  if not self.__str__():
46
- warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
58
+ warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
47
59
  return False
48
60
  return True
49
61
 
50
62
  def __contains__(self, item: str) -> bool:
51
- warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
63
+ warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
52
64
  return item in self.__str__()
53
65
 
54
66
  def __eq__(self, other: object) -> bool:
55
67
  if isinstance(other, PurePath):
56
68
  return super().__eq__(other)
57
69
  if isinstance(other, str):
58
- warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
70
+ warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
59
71
  return self.__str__() == other
60
72
  return NotImplemented
61
73
 
@@ -63,11 +75,11 @@ if sys.platform == 'win32':
63
75
  return super().__hash__()
64
76
 
65
77
  def __getitem__(self, item: int | slice) -> str:
66
- warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
78
+ warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
67
79
  return self.__str__()[item]
68
80
 
69
81
  def __len__(self) -> int:
70
- warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
82
+ warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
71
83
  return len(self.__str__())
72
84
  else:
73
85
  class _StrPath(PosixPath):
@@ -76,35 +88,35 @@ else:
76
88
  ) -> str:
77
89
  # replace exists in both Path and str;
78
90
  # in Path it makes filesystem changes, so we use the safer str version
79
- warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
91
+ warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
80
92
  return self.__str__().replace(old, new, count) # NoQA: PLC2801
81
93
 
82
94
  def __getattr__(self, item: str) -> Any:
83
95
  if item in _STR_METHODS:
84
- warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
96
+ warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
85
97
  return getattr(self.__str__(), item)
86
98
  msg = f'{_PATH_NAME!r} has no attribute {item!r}'
87
99
  raise AttributeError(msg)
88
100
 
89
101
  def __add__(self, other: str) -> str:
90
- warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
102
+ warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
91
103
  return self.__str__() + other
92
104
 
93
105
  def __bool__(self) -> bool:
94
106
  if not self.__str__():
95
- warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
107
+ warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
96
108
  return False
97
109
  return True
98
110
 
99
111
  def __contains__(self, item: str) -> bool:
100
- warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
112
+ warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
101
113
  return item in self.__str__()
102
114
 
103
115
  def __eq__(self, other: object) -> bool:
104
116
  if isinstance(other, PurePath):
105
117
  return super().__eq__(other)
106
118
  if isinstance(other, str):
107
- warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
119
+ warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
108
120
  return self.__str__() == other
109
121
  return NotImplemented
110
122
 
@@ -112,9 +124,9 @@ else:
112
124
  return super().__hash__()
113
125
 
114
126
  def __getitem__(self, item: int | slice) -> str:
115
- warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
127
+ warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
116
128
  return self.__str__()[item]
117
129
 
118
130
  def __len__(self) -> int:
119
- warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
131
+ warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
120
132
  return len(self.__str__())
@@ -0,0 +1,12 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+
5
+
6
+ def _format_rfc3339_microseconds(timestamp: int, /) -> str:
7
+ """Return an RFC 3339 formatted string representing the given timestamp.
8
+
9
+ :param timestamp: The timestamp to format, in microseconds.
10
+ """
11
+ seconds, fraction = divmod(timestamp, 10**6)
12
+ return time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(seconds)) + f'.{fraction // 1_000}'
sphinx/util/cfamily.py CHANGED
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import re
6
6
  from copy import deepcopy
7
- from typing import TYPE_CHECKING, Any, Callable
7
+ from typing import TYPE_CHECKING
8
8
 
9
9
  from docutils import nodes
10
10
 
@@ -12,16 +12,16 @@ from sphinx import addnodes
12
12
  from sphinx.util import logging
13
13
 
14
14
  if TYPE_CHECKING:
15
- from collections.abc import Sequence
15
+ from collections.abc import Callable, Sequence
16
+ from typing import Any, TypeAlias
16
17
 
17
18
  from docutils.nodes import TextElement
18
19
 
19
20
  from sphinx.config import Config
20
21
 
21
- logger = logging.getLogger(__name__)
22
-
23
- StringifyTransform = Callable[[Any], str]
22
+ StringifyTransform: TypeAlias = Callable[[Any], str]
24
23
 
24
+ logger = logging.getLogger(__name__)
25
25
 
26
26
  _whitespace_re = re.compile(r'\s+')
27
27
  anon_identifier_re = re.compile(r'(@[a-zA-Z0-9_])[a-zA-Z0-9_]*\b')
sphinx/util/console.py CHANGED
@@ -41,8 +41,9 @@ if TYPE_CHECKING:
41
41
  try:
42
42
  # check if colorama is installed to support color on Windows
43
43
  import colorama
44
+ COLORAMA_AVAILABLE = True
44
45
  except ImportError:
45
- colorama = None
46
+ COLORAMA_AVAILABLE = False
46
47
 
47
48
  _CSI: Final[str] = re.escape('\x1b[') # 'ESC [': Control Sequence Introducer
48
49
 
@@ -92,7 +93,7 @@ def term_width_line(text: str) -> str:
92
93
  def color_terminal() -> bool:
93
94
  if 'NO_COLOR' in os.environ:
94
95
  return False
95
- if sys.platform == 'win32' and colorama is not None:
96
+ if sys.platform == 'win32' and COLORAMA_AVAILABLE:
96
97
  colorama.just_fix_windows_console()
97
98
  return True
98
99
  if 'FORCE_COLOR' in os.environ:
@@ -108,7 +109,7 @@ def color_terminal() -> bool:
108
109
 
109
110
 
110
111
  def nocolor() -> None:
111
- if sys.platform == 'win32' and colorama is not None:
112
+ if sys.platform == 'win32' and COLORAMA_AVAILABLE:
112
113
  colorama.deinit()
113
114
  codes.clear()
114
115
 
sphinx/util/display.py CHANGED
@@ -7,9 +7,9 @@ from sphinx.util import logging
7
7
  from sphinx.util.console import bold, color_terminal
8
8
 
9
9
  if False:
10
- from collections.abc import Iterable, Iterator
10
+ from collections.abc import Callable, Iterable, Iterator
11
11
  from types import TracebackType
12
- from typing import Any, Callable, TypeVar
12
+ from typing import Any, TypeVar
13
13
 
14
14
  from typing_extensions import ParamSpec
15
15
 
@@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
21
21
 
22
22
 
23
23
  def display_chunk(chunk: Any) -> str:
24
- if isinstance(chunk, (list, tuple)):
24
+ if isinstance(chunk, list | tuple):
25
25
  if len(chunk) == 1:
26
26
  return str(chunk[0])
27
27
  return f'{chunk[0]} .. {chunk[-1]}'