Sphinx 7.4.6__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 (242) 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 +90 -59
  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/__init__.py +14 -9
  19. sphinx/directives/other.py +2 -3
  20. sphinx/directives/patches.py +2 -2
  21. sphinx/domains/__init__.py +4 -2
  22. sphinx/domains/c/__init__.py +2 -2
  23. sphinx/domains/c/_ast.py +3 -2
  24. sphinx/domains/c/_parser.py +4 -3
  25. sphinx/domains/cpp/__init__.py +2 -2
  26. sphinx/domains/cpp/_ast.py +1 -2
  27. sphinx/domains/cpp/_parser.py +2 -2
  28. sphinx/domains/cpp/_symbol.py +2 -2
  29. sphinx/domains/javascript.py +1 -1
  30. sphinx/domains/math.py +1 -1
  31. sphinx/domains/python/__init__.py +1 -1
  32. sphinx/domains/python/_annotations.py +23 -1
  33. sphinx/domains/python/_object.py +0 -1
  34. sphinx/domains/std/__init__.py +7 -8
  35. sphinx/environment/__init__.py +15 -32
  36. sphinx/environment/adapters/indexentries.py +4 -6
  37. sphinx/environment/adapters/toctree.py +4 -4
  38. sphinx/environment/collectors/title.py +1 -1
  39. sphinx/environment/collectors/toctree.py +1 -1
  40. sphinx/events.py +3 -1
  41. sphinx/ext/autodoc/__init__.py +25 -67
  42. sphinx/ext/autodoc/directive.py +7 -5
  43. sphinx/ext/autodoc/importer.py +2 -1
  44. sphinx/ext/autodoc/preserve_defaults.py +2 -2
  45. sphinx/ext/autosummary/__init__.py +15 -7
  46. sphinx/ext/autosummary/generate.py +5 -4
  47. sphinx/ext/doctest.py +5 -5
  48. sphinx/ext/graphviz.py +1 -1
  49. sphinx/ext/imgmath.py +1 -1
  50. sphinx/ext/inheritance_diagram.py +1 -1
  51. sphinx/ext/intersphinx/__init__.py +25 -5
  52. sphinx/ext/intersphinx/_cli.py +7 -6
  53. sphinx/ext/intersphinx/_load.py +240 -115
  54. sphinx/ext/intersphinx/_resolve.py +12 -11
  55. sphinx/ext/intersphinx/_shared.py +102 -9
  56. sphinx/ext/mathjax.py +1 -1
  57. sphinx/ext/napoleon/docstring.py +2 -2
  58. sphinx/ext/todo.py +2 -2
  59. sphinx/ext/viewcode.py +2 -1
  60. sphinx/highlighting.py +3 -3
  61. sphinx/io.py +2 -2
  62. sphinx/jinja2glue.py +13 -6
  63. sphinx/locale/__init__.py +4 -3
  64. sphinx/locale/ar/LC_MESSAGES/sphinx.mo +0 -0
  65. sphinx/locale/ar/LC_MESSAGES/sphinx.po +2383 -2186
  66. sphinx/locale/bg/LC_MESSAGES/sphinx.mo +0 -0
  67. sphinx/locale/bg/LC_MESSAGES/sphinx.po +2249 -2052
  68. sphinx/locale/bn/LC_MESSAGES/sphinx.mo +0 -0
  69. sphinx/locale/bn/LC_MESSAGES/sphinx.po +2412 -2215
  70. sphinx/locale/ca/LC_MESSAGES/sphinx.mo +0 -0
  71. sphinx/locale/ca/LC_MESSAGES/sphinx.po +3029 -2832
  72. sphinx/locale/cak/LC_MESSAGES/sphinx.mo +0 -0
  73. sphinx/locale/cak/LC_MESSAGES/sphinx.po +2308 -2111
  74. sphinx/locale/cs/LC_MESSAGES/sphinx.mo +0 -0
  75. sphinx/locale/cs/LC_MESSAGES/sphinx.po +2469 -2272
  76. sphinx/locale/cy/LC_MESSAGES/sphinx.mo +0 -0
  77. sphinx/locale/cy/LC_MESSAGES/sphinx.po +2393 -2196
  78. sphinx/locale/da/LC_MESSAGES/sphinx.mo +0 -0
  79. sphinx/locale/da/LC_MESSAGES/sphinx.po +2532 -2335
  80. sphinx/locale/de/LC_MESSAGES/sphinx.mo +0 -0
  81. sphinx/locale/de/LC_MESSAGES/sphinx.po +2492 -2295
  82. sphinx/locale/de_DE/LC_MESSAGES/sphinx.mo +0 -0
  83. sphinx/locale/de_DE/LC_MESSAGES/sphinx.po +2250 -2053
  84. sphinx/locale/el/LC_MESSAGES/sphinx.mo +0 -0
  85. sphinx/locale/el/LC_MESSAGES/sphinx.po +2879 -2682
  86. sphinx/locale/en_DE/LC_MESSAGES/sphinx.mo +0 -0
  87. sphinx/locale/en_DE/LC_MESSAGES/sphinx.po +2250 -2053
  88. sphinx/locale/en_FR/LC_MESSAGES/sphinx.mo +0 -0
  89. sphinx/locale/en_FR/LC_MESSAGES/sphinx.po +2250 -2053
  90. sphinx/locale/en_GB/LC_MESSAGES/sphinx.mo +0 -0
  91. sphinx/locale/en_GB/LC_MESSAGES/sphinx.po +2989 -2792
  92. sphinx/locale/en_HK/LC_MESSAGES/sphinx.mo +0 -0
  93. sphinx/locale/en_HK/LC_MESSAGES/sphinx.po +2250 -2053
  94. sphinx/locale/eo/LC_MESSAGES/sphinx.mo +0 -0
  95. sphinx/locale/eo/LC_MESSAGES/sphinx.po +2297 -2100
  96. sphinx/locale/es/LC_MESSAGES/sphinx.mo +0 -0
  97. sphinx/locale/es/LC_MESSAGES/sphinx.po +3017 -2820
  98. sphinx/locale/es_CO/LC_MESSAGES/sphinx.mo +0 -0
  99. sphinx/locale/es_CO/LC_MESSAGES/sphinx.po +2250 -2053
  100. sphinx/locale/et/LC_MESSAGES/sphinx.mo +0 -0
  101. sphinx/locale/et/LC_MESSAGES/sphinx.po +2748 -2551
  102. sphinx/locale/eu/LC_MESSAGES/sphinx.mo +0 -0
  103. sphinx/locale/eu/LC_MESSAGES/sphinx.po +2459 -2262
  104. sphinx/locale/fa/LC_MESSAGES/sphinx.mo +0 -0
  105. sphinx/locale/fa/LC_MESSAGES/sphinx.po +2957 -2760
  106. sphinx/locale/fi/LC_MESSAGES/sphinx.mo +0 -0
  107. sphinx/locale/fi/LC_MESSAGES/sphinx.po +2321 -2124
  108. sphinx/locale/fr/LC_MESSAGES/sphinx.mo +0 -0
  109. sphinx/locale/fr/LC_MESSAGES/sphinx.po +2977 -2780
  110. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.mo +0 -0
  111. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.po +2250 -2053
  112. sphinx/locale/gl/LC_MESSAGES/sphinx.mo +0 -0
  113. sphinx/locale/gl/LC_MESSAGES/sphinx.po +2992 -2795
  114. sphinx/locale/he/LC_MESSAGES/sphinx.mo +0 -0
  115. sphinx/locale/he/LC_MESSAGES/sphinx.po +2375 -2178
  116. sphinx/locale/hi/LC_MESSAGES/sphinx.mo +0 -0
  117. sphinx/locale/hi/LC_MESSAGES/sphinx.po +2937 -2740
  118. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.mo +0 -0
  119. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.po +2250 -2053
  120. sphinx/locale/hr/LC_MESSAGES/sphinx.mo +0 -0
  121. sphinx/locale/hr/LC_MESSAGES/sphinx.po +2532 -2335
  122. sphinx/locale/hu/LC_MESSAGES/sphinx.mo +0 -0
  123. sphinx/locale/hu/LC_MESSAGES/sphinx.po +2505 -2308
  124. sphinx/locale/id/LC_MESSAGES/sphinx.mo +0 -0
  125. sphinx/locale/id/LC_MESSAGES/sphinx.po +2925 -2728
  126. sphinx/locale/is/LC_MESSAGES/sphinx.mo +0 -0
  127. sphinx/locale/is/LC_MESSAGES/sphinx.po +2307 -2110
  128. sphinx/locale/it/LC_MESSAGES/sphinx.mo +0 -0
  129. sphinx/locale/it/LC_MESSAGES/sphinx.po +2514 -2317
  130. sphinx/locale/ja/LC_MESSAGES/sphinx.mo +0 -0
  131. sphinx/locale/ja/LC_MESSAGES/sphinx.po +2970 -2773
  132. sphinx/locale/ka/LC_MESSAGES/sphinx.mo +0 -0
  133. sphinx/locale/ka/LC_MESSAGES/sphinx.po +2868 -2671
  134. sphinx/locale/ko/LC_MESSAGES/sphinx.mo +0 -0
  135. sphinx/locale/ko/LC_MESSAGES/sphinx.po +3016 -2819
  136. sphinx/locale/lt/LC_MESSAGES/sphinx.mo +0 -0
  137. sphinx/locale/lt/LC_MESSAGES/sphinx.po +2476 -2279
  138. sphinx/locale/lv/LC_MESSAGES/sphinx.mo +0 -0
  139. sphinx/locale/lv/LC_MESSAGES/sphinx.po +2477 -2280
  140. sphinx/locale/mk/LC_MESSAGES/sphinx.mo +0 -0
  141. sphinx/locale/mk/LC_MESSAGES/sphinx.po +2292 -2095
  142. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.mo +0 -0
  143. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.po +2479 -2282
  144. sphinx/locale/ne/LC_MESSAGES/sphinx.mo +0 -0
  145. sphinx/locale/ne/LC_MESSAGES/sphinx.po +2481 -2284
  146. sphinx/locale/nl/LC_MESSAGES/sphinx.mo +0 -0
  147. sphinx/locale/nl/LC_MESSAGES/sphinx.po +2557 -2360
  148. sphinx/locale/pl/LC_MESSAGES/sphinx.mo +0 -0
  149. sphinx/locale/pl/LC_MESSAGES/sphinx.po +2696 -2499
  150. sphinx/locale/pt/LC_MESSAGES/sphinx.mo +0 -0
  151. sphinx/locale/pt/LC_MESSAGES/sphinx.po +2250 -2053
  152. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.mo +0 -0
  153. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.po +2979 -2782
  154. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.mo +0 -0
  155. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.po +2469 -2272
  156. sphinx/locale/ro/LC_MESSAGES/sphinx.mo +0 -0
  157. sphinx/locale/ro/LC_MESSAGES/sphinx.po +2473 -2276
  158. sphinx/locale/ru/LC_MESSAGES/sphinx.mo +0 -0
  159. sphinx/locale/ru/LC_MESSAGES/sphinx.po +2746 -2549
  160. sphinx/locale/si/LC_MESSAGES/sphinx.mo +0 -0
  161. sphinx/locale/si/LC_MESSAGES/sphinx.po +2331 -2134
  162. sphinx/locale/sk/LC_MESSAGES/sphinx.mo +0 -0
  163. sphinx/locale/sk/LC_MESSAGES/sphinx.po +2966 -2769
  164. sphinx/locale/sl/LC_MESSAGES/sphinx.mo +0 -0
  165. sphinx/locale/sl/LC_MESSAGES/sphinx.po +2404 -2207
  166. sphinx/locale/sphinx.pot +2262 -2065
  167. sphinx/locale/sq/LC_MESSAGES/sphinx.mo +0 -0
  168. sphinx/locale/sq/LC_MESSAGES/sphinx.po +2972 -2775
  169. sphinx/locale/sr/LC_MESSAGES/sphinx.mo +0 -0
  170. sphinx/locale/sr/LC_MESSAGES/sphinx.po +2440 -2243
  171. sphinx/locale/sv/LC_MESSAGES/sphinx.mo +0 -0
  172. sphinx/locale/sv/LC_MESSAGES/sphinx.po +2483 -2286
  173. sphinx/locale/ta/LC_MESSAGES/sphinx.js +54 -54
  174. sphinx/locale/ta/LC_MESSAGES/sphinx.mo +0 -0
  175. sphinx/locale/ta/LC_MESSAGES/sphinx.po +1578 -1843
  176. sphinx/locale/te/LC_MESSAGES/sphinx.mo +0 -0
  177. sphinx/locale/te/LC_MESSAGES/sphinx.po +2250 -2053
  178. sphinx/locale/tr/LC_MESSAGES/sphinx.mo +0 -0
  179. sphinx/locale/tr/LC_MESSAGES/sphinx.po +2892 -2695
  180. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.mo +0 -0
  181. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.po +2400 -2203
  182. sphinx/locale/ur/LC_MESSAGES/sphinx.mo +0 -0
  183. sphinx/locale/ur/LC_MESSAGES/sphinx.po +2250 -2053
  184. sphinx/locale/vi/LC_MESSAGES/sphinx.mo +0 -0
  185. sphinx/locale/vi/LC_MESSAGES/sphinx.po +2422 -2225
  186. sphinx/locale/yue/LC_MESSAGES/sphinx.mo +0 -0
  187. sphinx/locale/yue/LC_MESSAGES/sphinx.po +2250 -2053
  188. sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po +496 -704
  189. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.mo +0 -0
  190. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.po +2250 -2053
  191. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.mo +0 -0
  192. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po +3028 -2831
  193. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.mo +0 -0
  194. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.po +2250 -2053
  195. sphinx/project.py +25 -20
  196. sphinx/pycode/ast.py +2 -2
  197. sphinx/pycode/parser.py +2 -2
  198. sphinx/pygments_styles.py +3 -3
  199. sphinx/registry.py +3 -8
  200. sphinx/search/__init__.py +1 -1
  201. sphinx/testing/path.py +2 -1
  202. sphinx/testing/util.py +1 -1
  203. sphinx/texinputs/Makefile.jinja +2 -1
  204. sphinx/texinputs_win/Makefile.jinja +2 -1
  205. sphinx/theming.py +3 -12
  206. sphinx/transforms/__init__.py +5 -5
  207. sphinx/transforms/references.py +1 -1
  208. sphinx/util/__init__.py +11 -35
  209. sphinx/util/_pathlib.py +31 -19
  210. sphinx/util/_timestamps.py +12 -0
  211. sphinx/util/cfamily.py +5 -5
  212. sphinx/util/console.py +4 -3
  213. sphinx/util/display.py +3 -3
  214. sphinx/util/docfields.py +1 -1
  215. sphinx/util/docutils.py +44 -10
  216. sphinx/util/fileutil.py +41 -9
  217. sphinx/util/i18n.py +9 -4
  218. sphinx/util/images.py +3 -2
  219. sphinx/util/inspect.py +29 -44
  220. sphinx/util/inventory.py +2 -2
  221. sphinx/util/matching.py +2 -2
  222. sphinx/util/math.py +1 -1
  223. sphinx/util/nodes.py +8 -8
  224. sphinx/util/osutil.py +52 -26
  225. sphinx/util/parallel.py +2 -2
  226. sphinx/util/requests.py +1 -1
  227. sphinx/util/template.py +3 -3
  228. sphinx/util/typing.py +67 -70
  229. sphinx/writers/html.py +1 -1
  230. sphinx/writers/html5.py +1 -1
  231. sphinx/writers/latex.py +4 -4
  232. sphinx/writers/manpage.py +2 -2
  233. sphinx/writers/texinfo.py +5 -5
  234. sphinx/writers/text.py +4 -4
  235. sphinx/writers/xml.py +2 -2
  236. {sphinx-7.4.6.dist-info → sphinx-8.0.0.dist-info}/METADATA +11 -10
  237. {sphinx-7.4.6.dist-info → sphinx-8.0.0.dist-info}/RECORD +240 -241
  238. sphinx/templates/quickstart/Makefile.jinja +0 -98
  239. sphinx/templates/quickstart/make.bat.jinja +0 -110
  240. {sphinx-7.4.6.dist-info → sphinx-8.0.0.dist-info}/LICENSE.rst +0 -0
  241. {sphinx-7.4.6.dist-info → sphinx-8.0.0.dist-info}/WHEEL +0 -0
  242. {sphinx-7.4.6.dist-info → sphinx-8.0.0.dist-info}/entry_points.txt +0 -0
sphinx/util/osutil.py CHANGED
@@ -11,13 +11,12 @@ import sys
11
11
  import unicodedata
12
12
  from io import StringIO
13
13
  from os import path
14
+ from pathlib import Path
14
15
  from typing import TYPE_CHECKING
15
16
 
16
- from sphinx.deprecation import _deprecation_warning
17
+ from sphinx.locale import __
17
18
 
18
19
  if TYPE_CHECKING:
19
- from collections.abc import Iterator
20
- from pathlib import Path
21
20
  from types import TracebackType
22
21
  from typing import Any
23
22
 
@@ -51,7 +50,7 @@ def relative_uri(base: str, to: str) -> str:
51
50
  b2 = base.split('#')[0].split(SEP)
52
51
  t2 = to.split('#')[0].split(SEP)
53
52
  # remove common segments (except the last segment)
54
- for x, y in zip(b2[:-1], t2[:-1]):
53
+ for x, y in zip(b2[:-1], t2[:-1], strict=False):
55
54
  if x != y:
56
55
  break
57
56
  b2.pop(0)
@@ -72,40 +71,71 @@ def ensuredir(file: str | os.PathLike[str]) -> None:
72
71
  os.makedirs(file, exist_ok=True)
73
72
 
74
73
 
75
- def mtimes_of_files(dirnames: list[str], suffix: str) -> Iterator[float]:
76
- for dirname in dirnames:
77
- for root, _dirs, files in os.walk(dirname):
78
- for sfile in files:
79
- if sfile.endswith(suffix):
80
- with contextlib.suppress(OSError):
81
- yield path.getmtime(path.join(root, sfile))
74
+ def _last_modified_time(source: str | os.PathLike[str], /) -> int:
75
+ """Return the last modified time of ``filename``.
82
76
 
77
+ The time is returned as integer microseconds.
78
+ The lowest common denominator of modern file-systems seems to be
79
+ microsecond-level precision.
83
80
 
84
- def copytimes(source: str | os.PathLike[str], dest: str | os.PathLike[str]) -> None:
81
+ We prefer to err on the side of re-rendering a file,
82
+ so we round up to the nearest microsecond.
83
+ """
84
+ st = source.stat() if isinstance(source, os.DirEntry) else os.stat(source)
85
+ # upside-down floor division to get the ceiling
86
+ return -(st.st_mtime_ns // -1_000)
87
+
88
+
89
+ def _copy_times(source: str | os.PathLike[str], dest: str | os.PathLike[str]) -> None:
85
90
  """Copy a file's modification times."""
86
- st = os.stat(source)
87
- if hasattr(os, 'utime'):
88
- os.utime(dest, (st.st_atime, st.st_mtime))
91
+ st = source.stat() if isinstance(source, os.DirEntry) else os.stat(source)
92
+ os.utime(dest, ns=(st.st_atime_ns, st.st_mtime_ns))
89
93
 
90
94
 
91
- def copyfile(source: str | os.PathLike[str], dest: str | os.PathLike[str]) -> None:
95
+ def copyfile(
96
+ source: str | os.PathLike[str],
97
+ dest: str | os.PathLike[str],
98
+ *,
99
+ force: bool = False,
100
+ ) -> None:
92
101
  """Copy a file and its modification times, if possible.
93
102
 
94
103
  :param source: An existing source to copy.
95
104
  :param dest: The destination path.
105
+ :param bool force: Overwrite the destination file even if it exists.
96
106
  :raise FileNotFoundError: The *source* does not exist.
97
107
 
98
108
  .. note:: :func:`copyfile` is a no-op if *source* and *dest* are identical.
99
109
  """
100
- if not path.exists(source):
101
- msg = f'{os.fsdecode(source)} does not exist'
110
+ # coerce to Path objects
111
+ source = Path(source)
112
+ dest = Path(dest)
113
+ if not source.exists():
114
+ msg = f'{source} does not exist'
102
115
  raise FileNotFoundError(msg)
103
116
 
104
- if not path.exists(dest) or not filecmp.cmp(source, dest):
117
+ if (
118
+ not (dest_exists := dest.exists()) or
119
+ # comparison must be done using shallow=False since
120
+ # two different files might have the same size
121
+ not filecmp.cmp(source, dest, shallow=False)
122
+ ):
123
+ if not force and dest_exists:
124
+ # sphinx.util.logging imports sphinx.util.osutil,
125
+ # so use a local import to avoid circular imports
126
+ from sphinx.util import logging
127
+ logger = logging.getLogger(__name__)
128
+
129
+ msg = __('Aborted attempted copy from %s to %s '
130
+ '(the destination path has existing data).')
131
+ logger.warning(msg, source, dest,
132
+ type='misc', subtype='copy_overwrite')
133
+ return
134
+
105
135
  shutil.copyfile(source, dest)
106
136
  with contextlib.suppress(OSError):
107
137
  # don't do full copystat because the source may be read-only
108
- copytimes(source, dest)
138
+ _copy_times(source, dest)
109
139
 
110
140
 
111
141
  _no_fn_re = re.compile(r'[^a-zA-Z0-9_-]')
@@ -161,12 +191,8 @@ class _chdir:
161
191
  os.chdir(self._dirs.pop())
162
192
 
163
193
 
164
- @contextlib.contextmanager
165
- def cd(target_dir: str) -> Iterator[None]:
166
- if sys.version_info[:2] >= (3, 11):
167
- _deprecation_warning(__name__, 'cd', 'contextlib.chdir', remove=(8, 0))
168
- with _chdir(target_dir):
169
- yield
194
+ if sys.version_info[:2] < (3, 11):
195
+ cd = _chdir
170
196
 
171
197
 
172
198
  class FileAvoidWrite:
sphinx/util/parallel.py CHANGED
@@ -6,7 +6,7 @@ import os
6
6
  import time
7
7
  import traceback
8
8
  from math import sqrt
9
- from typing import TYPE_CHECKING, Any, Callable
9
+ from typing import TYPE_CHECKING, Any
10
10
 
11
11
  try:
12
12
  import multiprocessing
@@ -18,7 +18,7 @@ from sphinx.errors import SphinxParallelError
18
18
  from sphinx.util import logging
19
19
 
20
20
  if TYPE_CHECKING:
21
- from collections.abc import Sequence
21
+ from collections.abc import Callable, Sequence
22
22
 
23
23
  logger = logging.getLogger(__name__)
24
24
 
sphinx/util/requests.py CHANGED
@@ -19,7 +19,7 @@ def _get_tls_cacert(url: str, certs: str | dict[str, str] | None) -> str | bool:
19
19
  """Get additional CA cert for a specific URL."""
20
20
  if not certs:
21
21
  return True
22
- elif isinstance(certs, (str, tuple)):
22
+ elif isinstance(certs, str | tuple):
23
23
  return certs
24
24
  else:
25
25
  hostname = urlsplit(url).netloc
sphinx/util/template.py CHANGED
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import os
6
6
  from functools import partial
7
7
  from os import path
8
- from typing import TYPE_CHECKING, Any, Callable
8
+ from typing import TYPE_CHECKING, Any
9
9
 
10
10
  from jinja2 import TemplateNotFound
11
11
  from jinja2.loaders import BaseLoader
@@ -17,7 +17,7 @@ from sphinx.locale import get_translator
17
17
  from sphinx.util import rst, texescape
18
18
 
19
19
  if TYPE_CHECKING:
20
- from collections.abc import Sequence
20
+ from collections.abc import Callable, Sequence
21
21
 
22
22
  from jinja2.environment import Environment
23
23
 
@@ -38,7 +38,7 @@ class BaseRenderer:
38
38
 
39
39
  class FileRenderer(BaseRenderer):
40
40
  def __init__(self, search_path: Sequence[str | os.PathLike[str]]) -> None:
41
- if isinstance(search_path, (str, os.PathLike)):
41
+ if isinstance(search_path, str | os.PathLike):
42
42
  search_path = [search_path]
43
43
  else:
44
44
  # filter "None" paths
sphinx/util/typing.py CHANGED
@@ -2,18 +2,19 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import dataclasses
5
6
  import sys
6
7
  import types
7
8
  import typing
8
- from collections.abc import Sequence
9
+ from collections.abc import Callable, Sequence
9
10
  from contextvars import Context, ContextVar, Token
10
11
  from struct import Struct
11
12
  from typing import (
12
13
  TYPE_CHECKING,
13
14
  Annotated,
14
15
  Any,
15
- Callable,
16
16
  ForwardRef,
17
+ NewType,
17
18
  TypedDict,
18
19
  TypeVar,
19
20
  Union,
@@ -24,9 +25,9 @@ from docutils.parsers.rst.states import Inliner
24
25
 
25
26
  if TYPE_CHECKING:
26
27
  from collections.abc import Mapping
27
- from typing import Final, Literal, Protocol
28
+ from typing import Final, Literal, Protocol, TypeAlias
28
29
 
29
- from typing_extensions import TypeAlias, TypeIs
30
+ from typing_extensions import TypeIs
30
31
 
31
32
  from sphinx.application import Sphinx
32
33
 
@@ -40,10 +41,6 @@ if TYPE_CHECKING:
40
41
  'smart',
41
42
  ]
42
43
 
43
- if sys.version_info >= (3, 10):
44
- from types import UnionType
45
- else:
46
- UnionType = None
47
44
 
48
45
  # classes that have an incorrect .__module__ attribute
49
46
  _INVALID_BUILTIN_CLASSES: Final[Mapping[object, str]] = {
@@ -84,13 +81,10 @@ def is_invalid_builtin_class(obj: Any) -> bool:
84
81
 
85
82
 
86
83
  # Text like nodes which are initialized with text and rawsource
87
- TextlikeNode = Union[nodes.Text, nodes.TextElement]
88
-
89
- # type of None
90
- NoneType = type(None)
84
+ TextlikeNode: TypeAlias = nodes.Text | nodes.TextElement
91
85
 
92
86
  # path matcher
93
- PathMatcher = Callable[[str], bool]
87
+ PathMatcher: TypeAlias = Callable[[str], bool]
94
88
 
95
89
  # common role functions
96
90
  if TYPE_CHECKING:
@@ -108,25 +102,25 @@ if TYPE_CHECKING:
108
102
  ) -> tuple[list[nodes.Node], list[nodes.system_message]]:
109
103
  ...
110
104
  else:
111
- RoleFunction = Callable[
105
+ RoleFunction: TypeAlias = Callable[
112
106
  [str, str, str, int, Inliner, dict[str, Any], Sequence[str]],
113
107
  tuple[list[nodes.Node], list[nodes.system_message]],
114
108
  ]
115
109
 
116
110
  # A option spec for directive
117
- OptionSpec = dict[str, Callable[[str], Any]]
111
+ OptionSpec: TypeAlias = dict[str, Callable[[str], Any]]
118
112
 
119
113
  # title getter functions for enumerable nodes (see sphinx.domains.std)
120
- TitleGetter = Callable[[nodes.Node], str]
114
+ TitleGetter: TypeAlias = Callable[[nodes.Node], str]
121
115
 
122
116
  # inventory data on memory
123
- InventoryItem = tuple[
117
+ InventoryItem: TypeAlias = tuple[
124
118
  str, # project name
125
119
  str, # project version
126
120
  str, # URL
127
121
  str, # display name
128
122
  ]
129
- Inventory = dict[str, dict[str, InventoryItem]]
123
+ Inventory: TypeAlias = dict[str, dict[str, InventoryItem]]
130
124
 
131
125
 
132
126
  class ExtensionMetadata(TypedDict, total=False):
@@ -150,13 +144,14 @@ class ExtensionMetadata(TypedDict, total=False):
150
144
 
151
145
 
152
146
  if TYPE_CHECKING:
153
- _ExtensionSetupFunc = Callable[[Sphinx], ExtensionMetadata]
147
+ _ExtensionSetupFunc: TypeAlias = Callable[[Sphinx], ExtensionMetadata]
154
148
 
155
149
 
156
150
  def get_type_hints(
157
151
  obj: Any,
158
152
  globalns: dict[str, Any] | None = None,
159
153
  localns: dict[str, Any] | None = None,
154
+ include_extras: bool = False,
160
155
  ) -> dict[str, Any]:
161
156
  """Return a dictionary containing type hints for a function, method, module or class
162
157
  object.
@@ -167,7 +162,7 @@ def get_type_hints(
167
162
  from sphinx.util.inspect import safe_getattr # lazy loading
168
163
 
169
164
  try:
170
- return typing.get_type_hints(obj, globalns, localns)
165
+ return typing.get_type_hints(obj, globalns, localns, include_extras=include_extras)
171
166
  except NameError:
172
167
  # Failed to evaluate ForwardRef (maybe TYPE_CHECKING)
173
168
  return safe_getattr(obj, '__annotations__', {})
@@ -202,24 +197,14 @@ def _is_unpack_form(obj: Any) -> bool:
202
197
  # that typing_extensions.Unpack should not be used in that case
203
198
  return typing.get_origin(obj) is Unpack
204
199
 
205
- # 3.9 and 3.10 require typing_extensions.Unpack
200
+ # Python 3.10 requires typing_extensions.Unpack
206
201
  origin = typing.get_origin(obj)
207
202
  return (
208
203
  getattr(origin, '__module__', None) == 'typing_extensions'
209
- and _typing_internal_name(origin) == 'Unpack'
204
+ and origin.__name__ == 'Unpack'
210
205
  )
211
206
 
212
207
 
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
208
  def restify(cls: Any, mode: _RestifyMode = 'fully-qualified-except-typing') -> str:
224
209
  """Convert a type-like object to a reST reference.
225
210
 
@@ -232,7 +217,7 @@ def restify(cls: Any, mode: _RestifyMode = 'fully-qualified-except-typing') -> s
232
217
  Show the name of the annotation.
233
218
  """
234
219
  from sphinx.ext.autodoc.mock import ismock, ismockmodule # lazy loading
235
- from sphinx.util import inspect # lazy loading
220
+ from sphinx.util.inspect import isgenericalias, object_description # lazy loading
236
221
 
237
222
  valid_modes = {'fully-qualified-except-typing', 'smart'}
238
223
  if mode not in valid_modes:
@@ -241,7 +226,7 @@ def restify(cls: Any, mode: _RestifyMode = 'fully-qualified-except-typing') -> s
241
226
  raise ValueError(msg)
242
227
 
243
228
  # things that are not types
244
- if cls is None or cls == NoneType:
229
+ if cls is None or cls == types.NoneType:
245
230
  return ':py:obj:`None`'
246
231
  if cls is Ellipsis:
247
232
  return '...'
@@ -267,18 +252,28 @@ def restify(cls: Any, mode: _RestifyMode = 'fully-qualified-except-typing') -> s
267
252
  return f':py:class:`{module_prefix}{_INVALID_BUILTIN_CLASSES[cls]}`'
268
253
  elif _is_annotated_form(cls):
269
254
  args = restify(cls.__args__[0], mode)
270
- meta = ', '.join(map(repr, cls.__metadata__))
255
+ meta_args = []
256
+ for m in cls.__metadata__:
257
+ if isinstance(m, type):
258
+ meta_args.append(restify(m, mode))
259
+ elif dataclasses.is_dataclass(m):
260
+ # use restify for the repr of field values rather than repr
261
+ d_fields = ', '.join([
262
+ fr"{f.name}=\ {restify(getattr(m, f.name), mode)}"
263
+ for f in dataclasses.fields(m) if f.repr
264
+ ])
265
+ meta_args.append(fr'{restify(type(m), mode)}\ ({d_fields})')
266
+ else:
267
+ meta_args.append(repr(m))
268
+ meta = ', '.join(meta_args)
271
269
  if sys.version_info[:2] <= (3, 11):
272
270
  # Hardcoded to fix errors on Python 3.11 and earlier.
273
271
  return fr':py:class:`~typing.Annotated`\ [{args}, {meta}]'
274
272
  return (f':py:class:`{module_prefix}{cls.__module__}.{cls.__name__}`'
275
273
  fr'\ [{args}, {meta}]')
276
- elif inspect.isNewType(cls):
277
- if sys.version_info[:2] >= (3, 10):
278
- # newtypes have correct module info since Python 3.10+
279
- return f':py:class:`{module_prefix}{cls.__module__}.{cls.__name__}`'
280
- return f':py:class:`{cls.__name__}`'
281
- elif UnionType and isinstance(cls, UnionType):
274
+ elif isinstance(cls, NewType):
275
+ return f':py:class:`{module_prefix}{cls.__module__}.{cls.__name__}`' # type: ignore[attr-defined]
276
+ elif isinstance(cls, types.UnionType):
282
277
  # Union types (PEP 585) retain their definition order when they
283
278
  # are printed natively and ``None``-like types are kept as is.
284
279
  return ' | '.join(restify(a, mode) for a in cls.__args__)
@@ -290,23 +285,18 @@ def restify(cls: Any, mode: _RestifyMode = 'fully-qualified-except-typing') -> s
290
285
  concatenated_args = ', '.join(restify(arg, mode) for arg in cls.__args__)
291
286
  return fr':py:class:`{cls.__name__}`\ [{concatenated_args}]'
292
287
  return f':py:class:`{cls.__name__}`'
293
- elif (inspect.isgenericalias(cls)
288
+ elif (isgenericalias(cls)
294
289
  and cls_module_is_typing
295
290
  and cls.__origin__ is Union):
296
291
  # *cls* is defined in ``typing``, and thus ``__args__`` must exist
297
292
  return ' | '.join(restify(a, mode) for a in cls.__args__)
298
- elif inspect.isgenericalias(cls):
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
-
293
+ elif isgenericalias(cls):
304
294
  if isinstance(cls.__origin__, typing._SpecialForm):
305
295
  # ClassVar; Concatenate; Final; Literal; Unpack; TypeGuard; TypeIs
306
296
  # Required/NotRequired
307
297
  text = restify(cls.__origin__, mode)
308
- elif cls_name:
309
- text = f':py:class:`{module_prefix}{cls.__module__}.{cls_name}`'
298
+ elif cls.__name__:
299
+ text = f':py:class:`{module_prefix}{cls.__module__}.{cls.__name__}`'
310
300
  else:
311
301
  text = restify(cls.__origin__, mode)
312
302
 
@@ -319,14 +309,14 @@ def restify(cls: Any, mode: _RestifyMode = 'fully-qualified-except-typing') -> s
319
309
 
320
310
  # Callable has special formatting
321
311
  if (
322
- (cls_module_is_typing and _typing_internal_name(cls) == 'Callable')
312
+ (cls_module_is_typing and cls.__name__ == 'Callable')
323
313
  or (cls.__module__ == 'collections.abc' and cls.__name__ == 'Callable')
324
314
  ):
325
315
  args = ', '.join(restify(a, mode) for a in __args__[:-1])
326
316
  returns = restify(__args__[-1], mode)
327
317
  return fr'{text}\ [[{args}], {returns}]'
328
318
 
329
- if cls_module_is_typing and _typing_internal_name(cls.__origin__) == 'Literal':
319
+ if cls_module_is_typing and cls.__origin__.__name__ == 'Literal':
330
320
  args = ', '.join(_format_literal_arg_restify(a, mode=mode)
331
321
  for a in cls.__args__)
332
322
  return fr'{text}\ [{args}]'
@@ -335,8 +325,7 @@ def restify(cls: Any, mode: _RestifyMode = 'fully-qualified-except-typing') -> s
335
325
  args = ', '.join(restify(a, mode) for a in __args__)
336
326
  return fr'{text}\ [{args}]'
337
327
  elif isinstance(cls, typing._SpecialForm):
338
- cls_name = _typing_internal_name(cls)
339
- return f':py:obj:`~{cls.__module__}.{cls_name}`'
328
+ return f':py:obj:`~{cls.__module__}.{cls.__name__}`' # type: ignore[attr-defined]
340
329
  elif sys.version_info[:2] >= (3, 11) and cls is typing.Any:
341
330
  # handle bpo-46998
342
331
  return f':py:obj:`~{cls.__module__}.{cls.__name__}`'
@@ -348,7 +337,7 @@ def restify(cls: Any, mode: _RestifyMode = 'fully-qualified-except-typing') -> s
348
337
  # not a class (ex. TypeVar) but should have a __name__
349
338
  return f':py:obj:`{module_prefix}{cls.__module__}.{cls.__name__}`'
350
339
  except (AttributeError, TypeError):
351
- return inspect.object_description(cls)
340
+ return object_description(cls)
352
341
 
353
342
 
354
343
  def _format_literal_arg_restify(arg: Any, /, *, mode: str) -> str:
@@ -383,7 +372,6 @@ def stringify_annotation(
383
372
  Show the module name and qualified name of the annotation.
384
373
  """
385
374
  from sphinx.ext.autodoc.mock import ismock, ismockmodule # lazy loading
386
- from sphinx.util.inspect import isNewType # lazy loading
387
375
 
388
376
  valid_modes = {'fully-qualified-except-typing', 'fully-qualified', 'smart'}
389
377
  if mode not in valid_modes:
@@ -392,7 +380,7 @@ def stringify_annotation(
392
380
  raise ValueError(msg)
393
381
 
394
382
  # things that are not types
395
- if annotation is None or annotation == NoneType:
383
+ if annotation is None or annotation == types.NoneType:
396
384
  return 'None'
397
385
  if annotation is Ellipsis:
398
386
  return '...'
@@ -418,18 +406,15 @@ def stringify_annotation(
418
406
  if annotation_module_is_typing and mode in {'fully-qualified-except-typing', 'smart'}:
419
407
  return annotation_name
420
408
  return module_prefix + f'{annotation_module}.{annotation_name}'
421
- elif isNewType(annotation):
422
- if sys.version_info[:2] >= (3, 10):
423
- # newtypes have correct module info since Python 3.10+
424
- return module_prefix + f'{annotation_module}.{annotation_name}'
425
- return annotation_name
409
+ elif isinstance(annotation, NewType):
410
+ return module_prefix + f'{annotation_module}.{annotation_name}'
426
411
  elif ismockmodule(annotation):
427
412
  return module_prefix + annotation_name
428
413
  elif ismock(annotation):
429
414
  return module_prefix + f'{annotation_module}.{annotation_name}'
430
415
  elif is_invalid_builtin_class(annotation):
431
416
  return module_prefix + _INVALID_BUILTIN_CLASSES[annotation]
432
- elif _is_annotated_form(annotation): # for py39+
417
+ elif _is_annotated_form(annotation): # for py310+
433
418
  pass
434
419
  elif annotation_module == 'builtins' and annotation_qualname:
435
420
  args = getattr(annotation, '__args__', None)
@@ -463,8 +448,8 @@ def stringify_annotation(
463
448
  # handle ForwardRefs
464
449
  qualname = annotation_forward_arg
465
450
  else:
466
- if internal_name := _typing_internal_name(annotation):
467
- qualname = internal_name
451
+ if annotation_name:
452
+ qualname = annotation_name
468
453
  elif annotation_qualname:
469
454
  qualname = annotation_qualname
470
455
  else:
@@ -478,7 +463,7 @@ def stringify_annotation(
478
463
  elif hasattr(annotation, '__origin__'):
479
464
  # instantiated generic provided by a user
480
465
  qualname = stringify_annotation(annotation.__origin__, mode)
481
- elif UnionType and isinstance(annotation, UnionType): # types.UnionType (for py3.10+)
466
+ elif isinstance(annotation, types.UnionType):
482
467
  qualname = 'types.UnionType'
483
468
  else:
484
469
  # we weren't able to extract the base type, appending arguments would
@@ -488,7 +473,7 @@ def stringify_annotation(
488
473
  # Process the generic arguments (if any).
489
474
  # They must be a list or a tuple, otherwise they are considered 'broken'.
490
475
  annotation_args = getattr(annotation, '__args__', ())
491
- if annotation_args and isinstance(annotation_args, (list, tuple)):
476
+ if annotation_args and isinstance(annotation_args, list | tuple):
492
477
  if (
493
478
  qualname in {'Union', 'types.UnionType'}
494
479
  and all(getattr(a, '__origin__', ...) is typing.Literal for a in annotation_args)
@@ -508,9 +493,22 @@ def stringify_annotation(
508
493
  args = ', '.join(_format_literal_arg_stringify(a, mode=mode)
509
494
  for a in annotation_args)
510
495
  return f'{module_prefix}Literal[{args}]'
511
- elif _is_annotated_form(annotation): # for py39+
496
+ elif _is_annotated_form(annotation): # for py310+
512
497
  args = stringify_annotation(annotation_args[0], mode)
513
- meta = ', '.join(map(repr, annotation.__metadata__))
498
+ meta_args = []
499
+ for m in annotation.__metadata__:
500
+ if isinstance(m, type):
501
+ meta_args.append(stringify_annotation(m, mode))
502
+ elif dataclasses.is_dataclass(m):
503
+ # use stringify_annotation for the repr of field values rather than repr
504
+ d_fields = ', '.join([
505
+ f"{f.name}={stringify_annotation(getattr(m, f.name), mode)}"
506
+ for f in dataclasses.fields(m) if f.repr
507
+ ])
508
+ meta_args.append(f'{stringify_annotation(type(m), mode)}({d_fields})')
509
+ else:
510
+ meta_args.append(repr(m))
511
+ meta = ', '.join(meta_args)
514
512
  if sys.version_info[:2] <= (3, 11):
515
513
  if mode == 'fully-qualified-except-typing':
516
514
  return f'Annotated[{args}, {meta}]'
@@ -542,7 +540,6 @@ def _format_literal_arg_stringify(arg: Any, /, *, mode: str) -> str:
542
540
 
543
541
  # deprecated name -> (object to return, canonical path or empty string, removal version)
544
542
  _DEPRECATED_OBJECTS: dict[str, tuple[Any, str, tuple[int, int]]] = {
545
- 'stringify': (stringify_annotation, 'sphinx.util.typing.stringify_annotation', (8, 0)),
546
543
  }
547
544
 
548
545
 
sphinx/writers/html.py CHANGED
@@ -20,7 +20,7 @@ HTMLTranslator = HTML5Translator
20
20
  # https://www.arnebrodowski.de/blog/write-your-own-restructuredtext-writer.html
21
21
 
22
22
 
23
- class HTMLWriter(Writer):
23
+ class HTMLWriter(Writer): # type: ignore[misc]
24
24
 
25
25
  # override embed-stylesheet default value to False.
26
26
  settings_default_overrides = {"embed_stylesheet": False}
sphinx/writers/html5.py CHANGED
@@ -43,7 +43,7 @@ def multiply_length(length: str, scale: int) -> str:
43
43
  return f"{int(result)}{unit}"
44
44
 
45
45
 
46
- class HTML5Translator(SphinxTranslator, BaseTranslator):
46
+ class HTML5Translator(SphinxTranslator, BaseTranslator): # type: ignore[misc]
47
47
  """
48
48
  Our custom HTML translator.
49
49
  """
sphinx/writers/latex.py CHANGED
@@ -66,7 +66,7 @@ class UnsupportedError(SphinxError):
66
66
  category = 'Markup is unsupported in LaTeX'
67
67
 
68
68
 
69
- class LaTeXWriter(writers.Writer):
69
+ class LaTeXWriter(writers.Writer): # type: ignore[misc]
70
70
 
71
71
  supported = ('sphinxlatex',)
72
72
 
@@ -1369,7 +1369,7 @@ class LaTeXTranslator(SphinxTranslator):
1369
1369
  not isinstance(node.parent[index - 1], nodes.compound)):
1370
1370
  # insert blank line, if the paragraph follows a non-paragraph node in a compound
1371
1371
  self.body.append(r'\noindent' + CR)
1372
- elif index == 1 and isinstance(node.parent, (nodes.footnote, footnotetext)):
1372
+ elif index == 1 and isinstance(node.parent, nodes.footnote | footnotetext):
1373
1373
  # don't insert blank line, if the paragraph is second child of a footnote
1374
1374
  # (first one is label node)
1375
1375
  pass
@@ -2081,7 +2081,7 @@ class LaTeXTranslator(SphinxTranslator):
2081
2081
  done = 0
2082
2082
  if len(node.children) == 1:
2083
2083
  child = node.children[0]
2084
- if isinstance(child, (nodes.bullet_list, nodes.enumerated_list)):
2084
+ if isinstance(child, nodes.bullet_list | nodes.enumerated_list):
2085
2085
  done = 1
2086
2086
  if not done:
2087
2087
  self.body.append(r'\begin{quote}' + CR)
@@ -2092,7 +2092,7 @@ class LaTeXTranslator(SphinxTranslator):
2092
2092
  done = 0
2093
2093
  if len(node.children) == 1:
2094
2094
  child = node.children[0]
2095
- if isinstance(child, (nodes.bullet_list, nodes.enumerated_list)):
2095
+ if isinstance(child, nodes.bullet_list | nodes.enumerated_list):
2096
2096
  done = 1
2097
2097
  if not done:
2098
2098
  self.body.append(r'\end{quote}' + CR)
sphinx/writers/manpage.py CHANGED
@@ -24,7 +24,7 @@ if TYPE_CHECKING:
24
24
  logger = logging.getLogger(__name__)
25
25
 
26
26
 
27
- class ManualPageWriter(Writer):
27
+ class ManualPageWriter(Writer): # type: ignore[misc]
28
28
  def __init__(self, builder: Builder) -> None:
29
29
  super().__init__()
30
30
  self.builder = builder
@@ -70,7 +70,7 @@ class NestedInlineTransform:
70
70
  node.parent.remove(node)
71
71
 
72
72
 
73
- class ManualPageTranslator(SphinxTranslator, BaseTranslator):
73
+ class ManualPageTranslator(SphinxTranslator, BaseTranslator): # type: ignore[misc]
74
74
  """
75
75
  Custom man page translator.
76
76
  """
sphinx/writers/texinfo.py CHANGED
@@ -105,7 +105,7 @@ def smart_capwords(s: str, sep: str | None = None) -> str:
105
105
  return (sep or ' ').join(words)
106
106
 
107
107
 
108
- class TexinfoWriter(writers.Writer):
108
+ class TexinfoWriter(writers.Writer): # type: ignore[misc]
109
109
  """Texinfo writer for generating Texinfo documents."""
110
110
 
111
111
  supported = ('texinfo', 'texi')
@@ -297,7 +297,7 @@ class TexinfoTranslator(SphinxTranslator):
297
297
  # try to find a suitable "Top" node
298
298
  title = self.document.next_node(nodes.title)
299
299
  top = title.parent if title else self.document
300
- if not isinstance(top, (nodes.document, nodes.section)):
300
+ if not isinstance(top, nodes.document | nodes.section):
301
301
  top = self.document
302
302
  if top is not self.document:
303
303
  entries = node_menus[top['node_name']]
@@ -625,7 +625,7 @@ class TexinfoTranslator(SphinxTranslator):
625
625
  parent = node.parent
626
626
  if isinstance(parent, nodes.table):
627
627
  return
628
- if isinstance(parent, (nodes.Admonition, nodes.sidebar, nodes.topic)):
628
+ if isinstance(parent, nodes.Admonition | nodes.sidebar | nodes.topic):
629
629
  raise nodes.SkipNode
630
630
  if not isinstance(parent, nodes.section):
631
631
  logger.warning(__('encountered title node not in section, topic, table, '
@@ -694,7 +694,7 @@ class TexinfoTranslator(SphinxTranslator):
694
694
  def visit_reference(self, node: Element) -> None:
695
695
  # an xref's target is displayed in Info so we ignore a few
696
696
  # cases for the sake of appearance
697
- if isinstance(node.parent, (nodes.title, addnodes.desc_type)):
697
+ if isinstance(node.parent, nodes.title | addnodes.desc_type):
698
698
  return
699
699
  if len(node) != 0 and isinstance(node[0], nodes.image):
700
700
  return
@@ -987,7 +987,7 @@ class TexinfoTranslator(SphinxTranslator):
987
987
  self.add_anchor(id, node)
988
988
  # anchors and indexes need to go in front
989
989
  for n in node[::]:
990
- if isinstance(n, (addnodes.index, nodes.target)):
990
+ if isinstance(n, addnodes.index | nodes.target):
991
991
  n.walkabout(self)
992
992
  node.remove(n)
993
993
  self.body.append('\n%s ' % self.at_item_x)