Sphinx 8.1.3__py3-none-any.whl → 8.2.0rc1__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 (193) hide show
  1. sphinx/__init__.py +8 -4
  2. sphinx/__main__.py +2 -0
  3. sphinx/_cli/__init__.py +2 -5
  4. sphinx/_cli/util/colour.py +34 -11
  5. sphinx/_cli/util/errors.py +128 -61
  6. sphinx/addnodes.py +51 -35
  7. sphinx/application.py +362 -230
  8. sphinx/builders/__init__.py +87 -64
  9. sphinx/builders/_epub_base.py +65 -56
  10. sphinx/builders/changes.py +17 -23
  11. sphinx/builders/dirhtml.py +8 -13
  12. sphinx/builders/epub3.py +70 -38
  13. sphinx/builders/gettext.py +93 -73
  14. sphinx/builders/html/__init__.py +240 -186
  15. sphinx/builders/html/_assets.py +9 -2
  16. sphinx/builders/html/_build_info.py +3 -0
  17. sphinx/builders/latex/__init__.py +64 -54
  18. sphinx/builders/latex/constants.py +14 -11
  19. sphinx/builders/latex/nodes.py +2 -0
  20. sphinx/builders/latex/theming.py +8 -9
  21. sphinx/builders/latex/transforms.py +7 -5
  22. sphinx/builders/linkcheck.py +193 -149
  23. sphinx/builders/manpage.py +17 -17
  24. sphinx/builders/singlehtml.py +28 -16
  25. sphinx/builders/texinfo.py +28 -21
  26. sphinx/builders/text.py +10 -15
  27. sphinx/builders/xml.py +10 -19
  28. sphinx/cmd/build.py +49 -119
  29. sphinx/cmd/make_mode.py +35 -31
  30. sphinx/cmd/quickstart.py +78 -62
  31. sphinx/config.py +265 -163
  32. sphinx/directives/__init__.py +51 -54
  33. sphinx/directives/admonitions.py +107 -0
  34. sphinx/directives/code.py +24 -19
  35. sphinx/directives/other.py +21 -42
  36. sphinx/directives/patches.py +28 -16
  37. sphinx/domains/__init__.py +54 -31
  38. sphinx/domains/_domains_container.py +22 -17
  39. sphinx/domains/_index.py +5 -8
  40. sphinx/domains/c/__init__.py +366 -245
  41. sphinx/domains/c/_ast.py +378 -256
  42. sphinx/domains/c/_ids.py +89 -31
  43. sphinx/domains/c/_parser.py +283 -214
  44. sphinx/domains/c/_symbol.py +269 -198
  45. sphinx/domains/changeset.py +39 -24
  46. sphinx/domains/citation.py +54 -24
  47. sphinx/domains/cpp/__init__.py +517 -362
  48. sphinx/domains/cpp/_ast.py +999 -682
  49. sphinx/domains/cpp/_ids.py +133 -65
  50. sphinx/domains/cpp/_parser.py +746 -588
  51. sphinx/domains/cpp/_symbol.py +692 -489
  52. sphinx/domains/index.py +10 -8
  53. sphinx/domains/javascript.py +152 -74
  54. sphinx/domains/math.py +48 -40
  55. sphinx/domains/python/__init__.py +402 -211
  56. sphinx/domains/python/_annotations.py +114 -57
  57. sphinx/domains/python/_object.py +151 -67
  58. sphinx/domains/rst.py +94 -49
  59. sphinx/domains/std/__init__.py +510 -249
  60. sphinx/environment/__init__.py +345 -61
  61. sphinx/environment/adapters/asset.py +7 -1
  62. sphinx/environment/adapters/indexentries.py +15 -20
  63. sphinx/environment/adapters/toctree.py +19 -9
  64. sphinx/environment/collectors/__init__.py +3 -1
  65. sphinx/environment/collectors/asset.py +18 -15
  66. sphinx/environment/collectors/dependencies.py +8 -10
  67. sphinx/environment/collectors/metadata.py +6 -4
  68. sphinx/environment/collectors/title.py +3 -1
  69. sphinx/environment/collectors/toctree.py +4 -4
  70. sphinx/errors.py +1 -3
  71. sphinx/events.py +4 -4
  72. sphinx/ext/apidoc/__init__.py +21 -0
  73. sphinx/ext/apidoc/__main__.py +9 -0
  74. sphinx/ext/apidoc/_cli.py +356 -0
  75. sphinx/ext/apidoc/_generate.py +356 -0
  76. sphinx/ext/apidoc/_shared.py +66 -0
  77. sphinx/ext/autodoc/__init__.py +829 -480
  78. sphinx/ext/autodoc/directive.py +57 -21
  79. sphinx/ext/autodoc/importer.py +184 -67
  80. sphinx/ext/autodoc/mock.py +25 -10
  81. sphinx/ext/autodoc/preserve_defaults.py +17 -9
  82. sphinx/ext/autodoc/type_comment.py +56 -29
  83. sphinx/ext/autodoc/typehints.py +49 -26
  84. sphinx/ext/autosectionlabel.py +28 -11
  85. sphinx/ext/autosummary/__init__.py +271 -143
  86. sphinx/ext/autosummary/generate.py +121 -51
  87. sphinx/ext/coverage.py +152 -91
  88. sphinx/ext/doctest.py +169 -101
  89. sphinx/ext/duration.py +12 -6
  90. sphinx/ext/extlinks.py +33 -21
  91. sphinx/ext/githubpages.py +8 -8
  92. sphinx/ext/graphviz.py +175 -109
  93. sphinx/ext/ifconfig.py +11 -6
  94. sphinx/ext/imgconverter.py +48 -25
  95. sphinx/ext/imgmath.py +127 -97
  96. sphinx/ext/inheritance_diagram.py +177 -103
  97. sphinx/ext/intersphinx/__init__.py +22 -13
  98. sphinx/ext/intersphinx/__main__.py +3 -1
  99. sphinx/ext/intersphinx/_cli.py +18 -14
  100. sphinx/ext/intersphinx/_load.py +91 -82
  101. sphinx/ext/intersphinx/_resolve.py +108 -74
  102. sphinx/ext/intersphinx/_shared.py +2 -2
  103. sphinx/ext/linkcode.py +28 -12
  104. sphinx/ext/mathjax.py +60 -29
  105. sphinx/ext/napoleon/__init__.py +19 -7
  106. sphinx/ext/napoleon/docstring.py +229 -231
  107. sphinx/ext/todo.py +44 -49
  108. sphinx/ext/viewcode.py +105 -57
  109. sphinx/extension.py +3 -1
  110. sphinx/highlighting.py +13 -7
  111. sphinx/io.py +9 -13
  112. sphinx/jinja2glue.py +29 -26
  113. sphinx/locale/__init__.py +8 -9
  114. sphinx/parsers.py +8 -7
  115. sphinx/project.py +2 -2
  116. sphinx/pycode/__init__.py +31 -21
  117. sphinx/pycode/ast.py +6 -3
  118. sphinx/pycode/parser.py +14 -8
  119. sphinx/pygments_styles.py +4 -5
  120. sphinx/registry.py +192 -92
  121. sphinx/roles.py +58 -7
  122. sphinx/search/__init__.py +75 -54
  123. sphinx/search/en.py +11 -13
  124. sphinx/search/fi.py +1 -1
  125. sphinx/search/ja.py +8 -6
  126. sphinx/search/nl.py +1 -1
  127. sphinx/search/zh.py +19 -21
  128. sphinx/testing/fixtures.py +26 -29
  129. sphinx/testing/path.py +26 -62
  130. sphinx/testing/restructuredtext.py +14 -8
  131. sphinx/testing/util.py +21 -19
  132. sphinx/texinputs/make.bat.jinja +50 -50
  133. sphinx/texinputs/sphinx.sty +4 -3
  134. sphinx/texinputs/sphinxlatexadmonitions.sty +1 -1
  135. sphinx/texinputs/sphinxlatexobjects.sty +29 -10
  136. sphinx/themes/basic/static/searchtools.js +8 -5
  137. sphinx/theming.py +49 -61
  138. sphinx/transforms/__init__.py +17 -38
  139. sphinx/transforms/compact_bullet_list.py +5 -3
  140. sphinx/transforms/i18n.py +8 -21
  141. sphinx/transforms/post_transforms/__init__.py +142 -93
  142. sphinx/transforms/post_transforms/code.py +5 -5
  143. sphinx/transforms/post_transforms/images.py +28 -24
  144. sphinx/transforms/references.py +3 -1
  145. sphinx/util/__init__.py +109 -60
  146. sphinx/util/_files.py +39 -23
  147. sphinx/util/_importer.py +4 -1
  148. sphinx/util/_inventory_file_reader.py +76 -0
  149. sphinx/util/_io.py +2 -2
  150. sphinx/util/_lines.py +6 -3
  151. sphinx/util/_pathlib.py +40 -2
  152. sphinx/util/build_phase.py +2 -0
  153. sphinx/util/cfamily.py +19 -14
  154. sphinx/util/console.py +44 -179
  155. sphinx/util/display.py +9 -10
  156. sphinx/util/docfields.py +140 -122
  157. sphinx/util/docstrings.py +1 -1
  158. sphinx/util/docutils.py +118 -77
  159. sphinx/util/fileutil.py +25 -26
  160. sphinx/util/http_date.py +2 -0
  161. sphinx/util/i18n.py +77 -64
  162. sphinx/util/images.py +8 -6
  163. sphinx/util/inspect.py +147 -38
  164. sphinx/util/inventory.py +215 -116
  165. sphinx/util/logging.py +33 -33
  166. sphinx/util/matching.py +12 -4
  167. sphinx/util/nodes.py +18 -13
  168. sphinx/util/osutil.py +38 -39
  169. sphinx/util/parallel.py +22 -13
  170. sphinx/util/parsing.py +2 -1
  171. sphinx/util/png.py +6 -2
  172. sphinx/util/requests.py +33 -2
  173. sphinx/util/rst.py +3 -2
  174. sphinx/util/tags.py +1 -1
  175. sphinx/util/template.py +18 -10
  176. sphinx/util/texescape.py +8 -6
  177. sphinx/util/typing.py +148 -122
  178. sphinx/versioning.py +3 -3
  179. sphinx/writers/html.py +3 -1
  180. sphinx/writers/html5.py +61 -50
  181. sphinx/writers/latex.py +80 -65
  182. sphinx/writers/manpage.py +19 -38
  183. sphinx/writers/texinfo.py +44 -45
  184. sphinx/writers/text.py +48 -30
  185. sphinx/writers/xml.py +11 -8
  186. {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/LICENSE.rst +1 -1
  187. {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/METADATA +23 -15
  188. {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/RECORD +190 -186
  189. {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/WHEEL +1 -1
  190. sphinx/builders/html/transforms.py +0 -90
  191. sphinx/ext/apidoc.py +0 -721
  192. sphinx/util/exceptions.py +0 -74
  193. {sphinx-8.1.3.dist-info → sphinx-8.2.0rc1.dist-info}/entry_points.txt +0 -0
sphinx/jinja2glue.py CHANGED
@@ -3,9 +3,10 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import os
6
- from os import path
6
+ import os.path
7
+ from pathlib import Path
7
8
  from pprint import pformat
8
- from typing import TYPE_CHECKING, Any
9
+ from typing import TYPE_CHECKING
9
10
 
10
11
  from jinja2 import BaseLoader, FileSystemLoader, TemplateNotFound
11
12
  from jinja2.sandbox import SandboxedEnvironment
@@ -13,20 +14,26 @@ from jinja2.utils import open_if_exists, pass_context
13
14
 
14
15
  from sphinx.application import TemplateBridge
15
16
  from sphinx.util import logging
17
+ from sphinx.util._pathlib import _StrPath
16
18
  from sphinx.util.osutil import _last_modified_time
17
19
 
18
20
  if TYPE_CHECKING:
19
21
  from collections.abc import Callable, Iterator
22
+ from typing import Any
20
23
 
21
24
  from jinja2.environment import Environment
22
25
 
23
26
  from sphinx.builders import Builder
27
+ from sphinx.environment.adapters.indexentries import (
28
+ _RealIndexEntries,
29
+ _RealIndexEntry,
30
+ )
24
31
  from sphinx.theming import Theme
25
32
 
26
33
 
27
34
  def _tobool(val: str) -> bool:
28
35
  if isinstance(val, str):
29
- return val.lower() in ('true', '1', 'yes', 'on')
36
+ return val.lower() in {'true', '1', 'yes', 'on'}
30
37
  return bool(val)
31
38
 
32
39
 
@@ -38,8 +45,7 @@ def _toint(val: str) -> int:
38
45
 
39
46
 
40
47
  def _todim(val: int | str) -> str:
41
- """
42
- Make val a css dimension. In particular the following transformations
48
+ """Make val a css dimension. In particular the following transformations
43
49
  are performed:
44
50
 
45
51
  - None -> 'initial' (default CSS value)
@@ -55,7 +61,9 @@ def _todim(val: int | str) -> str:
55
61
  return val # type: ignore[return-value]
56
62
 
57
63
 
58
- def _slice_index(values: list, slices: int) -> Iterator[list]:
64
+ def _slice_index(
65
+ values: _RealIndexEntries, slices: int
66
+ ) -> Iterator[list[_RealIndexEntry]]:
59
67
  seq = values.copy()
60
68
  length = 0
61
69
  for value in values:
@@ -111,8 +119,7 @@ def warning(context: dict[str, Any], message: str, *args: Any, **kwargs: Any) ->
111
119
 
112
120
 
113
121
  class SphinxFileSystemLoader(FileSystemLoader):
114
- """
115
- FileSystemLoader subclass that is not so strict about '..' entries in
122
+ """FileSystemLoader subclass that is not so strict about '..' entries in
116
123
  template names.
117
124
  """
118
125
 
@@ -125,14 +132,14 @@ class SphinxFileSystemLoader(FileSystemLoader):
125
132
  else:
126
133
  legacy_template = None
127
134
 
128
- for searchpath in self.searchpath:
129
- filename = path.join(searchpath, template)
130
- f = open_if_exists(filename)
135
+ for search_path in map(Path, self.searchpath):
136
+ filename = search_path / template
137
+ f = open_if_exists(str(filename))
131
138
  if f is not None:
132
139
  break
133
140
  if legacy_template is not None:
134
- filename = path.join(searchpath, legacy_template)
135
- f = open_if_exists(filename)
141
+ filename = search_path / legacy_template
142
+ f = open_if_exists(str(filename))
136
143
  if f is not None:
137
144
  break
138
145
  else:
@@ -149,13 +156,11 @@ class SphinxFileSystemLoader(FileSystemLoader):
149
156
  except OSError:
150
157
  return False
151
158
 
152
- return contents, filename, uptodate
159
+ return contents, str(filename), uptodate
153
160
 
154
161
 
155
162
  class BuiltinTemplateLoader(TemplateBridge, BaseLoader):
156
- """
157
- Interfaces the rendering environment of jinja2 for use in Sphinx.
158
- """
163
+ """Interfaces the rendering environment of jinja2 for use in Sphinx."""
159
164
 
160
165
  # TemplateBridge interface
161
166
 
@@ -170,10 +175,10 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader):
170
175
  # the theme's own dir and its bases' dirs
171
176
  pathchain = theme.get_theme_dirs()
172
177
  # the loader dirs: pathchain + the parent directories for all themes
173
- loaderchain = pathchain + [path.join(p, '..') for p in pathchain]
178
+ loaderchain = pathchain + [p.parent for p in pathchain]
174
179
  elif dirs:
175
- pathchain = list(dirs)
176
- loaderchain = list(dirs)
180
+ pathchain = list(map(_StrPath, dirs))
181
+ loaderchain = list(map(_StrPath, dirs))
177
182
  else:
178
183
  pathchain = []
179
184
  loaderchain = []
@@ -182,7 +187,7 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader):
182
187
  self.templatepathlen = len(builder.config.templates_path)
183
188
  if builder.config.templates_path:
184
189
  cfg_templates_path = [
185
- path.join(builder.confdir, tp) for tp in builder.config.templates_path
190
+ builder.confdir / tp for tp in builder.config.templates_path
186
191
  ]
187
192
  pathchain[0:0] = cfg_templates_path
188
193
  loaderchain[0:0] = cfg_templates_path
@@ -193,7 +198,7 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader):
193
198
  # make the paths into loaders
194
199
  self.loaders = [SphinxFileSystemLoader(x) for x in loaderchain]
195
200
 
196
- use_i18n = builder.app.translator is not None
201
+ use_i18n = builder._translator is not None
197
202
  extensions = ['jinja2.ext.i18n'] if use_i18n else []
198
203
  self.environment = SandboxedEnvironment(loader=self, extensions=extensions)
199
204
  self.environment.filters['tobool'] = _tobool
@@ -206,9 +211,7 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader):
206
211
  self.environment.globals['idgen'] = idgen
207
212
  if use_i18n:
208
213
  # ``install_gettext_translations`` is injected by the ``jinja2.ext.i18n`` extension
209
- self.environment.install_gettext_translations( # type: ignore[attr-defined]
210
- builder.app.translator
211
- )
214
+ self.environment.install_gettext_translations(builder._translator) # type: ignore[attr-defined]
212
215
 
213
216
  def render(self, template: str, context: dict[str, Any]) -> str: # type: ignore[override]
214
217
  return self.environment.get_template(template).render(context)
@@ -224,7 +227,7 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader):
224
227
 
225
228
  def _newest_template_mtime_name(self) -> tuple[float, str]:
226
229
  return max(
227
- (os.stat(os.path.join(root, sfile)).st_mtime_ns / 10**9, sfile)
230
+ (Path(root, sfile).stat().st_mtime_ns / 10**9, sfile)
228
231
  for dirname in self.pathchain
229
232
  for root, _dirs, files in os.walk(dirname)
230
233
  for sfile in files
sphinx/locale/__init__.py CHANGED
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import locale
6
6
  import sys
7
7
  from gettext import NullTranslations, translation
8
- from os import path
8
+ from pathlib import Path
9
9
  from typing import TYPE_CHECKING
10
10
 
11
11
  if TYPE_CHECKING:
@@ -15,8 +15,7 @@ if TYPE_CHECKING:
15
15
 
16
16
 
17
17
  class _TranslationProxy:
18
- """
19
- The proxy implementation attempts to be as complete as possible, so that
18
+ """The proxy implementation attempts to be as complete as possible, so that
20
19
  the lazy objects should mostly work as expected, for example for sorting.
21
20
  """
22
21
 
@@ -141,11 +140,11 @@ def init(
141
140
  return translator, has_translation
142
141
 
143
142
 
144
- _LOCALE_DIR = path.abspath(path.dirname(__file__))
143
+ _LOCALE_DIR = Path(__file__).resolve().parent
145
144
 
146
145
 
147
146
  def init_console(
148
- locale_dir: str | None = None,
147
+ locale_dir: str | os.PathLike[str] | None = None,
149
148
  catalog: str = 'sphinx',
150
149
  ) -> tuple[NullTranslations, bool]:
151
150
  """Initialize locale for console.
@@ -184,7 +183,7 @@ def get_translation(catalog: str, namespace: str = 'general') -> Callable[[str],
184
183
  The extension can use this API to translate the messages on the
185
184
  extension::
186
185
 
187
- import os
186
+ from pathlib import Path
188
187
  from sphinx.locale import get_translation
189
188
 
190
189
  MESSAGE_CATALOG_NAME = 'myextension' # name of *.pot, *.po and *.mo files
@@ -193,8 +192,8 @@ def get_translation(catalog: str, namespace: str = 'general') -> Callable[[str],
193
192
 
194
193
 
195
194
  def setup(app):
196
- package_dir = os.path.abspath(os.path.dirname(__file__))
197
- locale_dir = os.path.join(package_dir, 'locales')
195
+ package_dir = Path(__file__).resolve().parent
196
+ locale_dir = package_dir / 'locales'
198
197
  app.add_message_catalog(MESSAGE_CATALOG_NAME, locale_dir)
199
198
 
200
199
  With this code, sphinx searches a message catalog from
@@ -207,7 +206,7 @@ def get_translation(catalog: str, namespace: str = 'general') -> Callable[[str],
207
206
  def gettext(message: str) -> str:
208
207
  if not is_translator_registered(catalog, namespace):
209
208
  # not initialized yet
210
- return _TranslationProxy(catalog, namespace, message) # type: ignore[return-value] # NoQA: E501
209
+ return _TranslationProxy(catalog, namespace, message) # type: ignore[return-value]
211
210
  else:
212
211
  translator = get_translator(catalog, namespace)
213
212
  return translator.gettext(message)
sphinx/parsers.py CHANGED
@@ -6,7 +6,6 @@ from typing import TYPE_CHECKING
6
6
 
7
7
  import docutils.parsers
8
8
  import docutils.parsers.rst
9
- from docutils import nodes
10
9
  from docutils.parsers.rst import states
11
10
  from docutils.statemachine import StringList
12
11
  from docutils.transforms.universal import SmartQuotes
@@ -14,6 +13,7 @@ from docutils.transforms.universal import SmartQuotes
14
13
  from sphinx.util.rst import append_epilog, prepend_prolog
15
14
 
16
15
  if TYPE_CHECKING:
16
+ from docutils import nodes
17
17
  from docutils.transforms import Transform
18
18
 
19
19
  from sphinx.application import Sphinx
@@ -23,10 +23,12 @@ if TYPE_CHECKING:
23
23
 
24
24
 
25
25
  class Parser(docutils.parsers.Parser):
26
- """
27
- A base class of source parsers. The additional parsers should inherit this class instead
28
- of ``docutils.parsers.Parser``. Compared with ``docutils.parsers.Parser``, this class
29
- improves accessibility to Sphinx APIs.
26
+ """A base class of source parsers.
27
+
28
+ The additional parsers should inherit this class
29
+ instead of ``docutils.parsers.Parser``.
30
+ Compared with ``docutils.parsers.Parser``,
31
+ this class improves accessibility to Sphinx APIs.
30
32
 
31
33
  The subclasses can access sphinx core runtime objects (app, config and env).
32
34
  """
@@ -51,8 +53,7 @@ class RSTParser(docutils.parsers.rst.Parser, Parser):
51
53
  """A reST parser for Sphinx."""
52
54
 
53
55
  def get_transforms(self) -> list[type[Transform]]:
54
- """
55
- Sphinx's reST parser replaces a transform class for smart-quotes by its own
56
+ """Sphinx's reST parser replaces a transform class for smart-quotes by its own
56
57
 
57
58
  refs: sphinx.io.SphinxStandaloneReader
58
59
  """
sphinx/project.py CHANGED
@@ -96,10 +96,10 @@ class Project:
96
96
 
97
97
  *filename* should be absolute or relative to the source directory.
98
98
  """
99
+ path = Path(filename)
99
100
  try:
100
- return self._path_to_docname[filename] # type: ignore[index]
101
+ return self._path_to_docname[path]
101
102
  except KeyError:
102
- path = Path(filename)
103
103
  if path.is_absolute():
104
104
  with contextlib.suppress(ValueError):
105
105
  path = path.relative_to(self.srcdir)
sphinx/pycode/__init__.py CHANGED
@@ -4,14 +4,16 @@ from __future__ import annotations
4
4
 
5
5
  import tokenize
6
6
  from importlib import import_module
7
- from os import path
8
- from typing import TYPE_CHECKING, Any
7
+ from typing import TYPE_CHECKING
9
8
 
10
9
  from sphinx.errors import PycodeError
11
10
  from sphinx.pycode.parser import Parser
11
+ from sphinx.util._pathlib import _StrPath
12
12
 
13
13
  if TYPE_CHECKING:
14
+ import os
14
15
  from inspect import Signature
16
+ from typing import Any, Literal
15
17
 
16
18
 
17
19
  class ModuleAnalyzer:
@@ -23,10 +25,10 @@ class ModuleAnalyzer:
23
25
  tags: dict[str, tuple[str, int, int]]
24
26
 
25
27
  # cache for analyzer objects -- caches both by module and file name
26
- cache: dict[tuple[str, str], Any] = {}
28
+ cache: dict[tuple[Literal['file', 'module'], str | _StrPath], Any] = {}
27
29
 
28
30
  @staticmethod
29
- def get_module_source(modname: str) -> tuple[str | None, str | None]:
31
+ def get_module_source(modname: str) -> tuple[_StrPath | None, str | None]:
30
32
  """Try to find the source code for a module.
31
33
 
32
34
  Returns ('filename', 'source'). One of it can be None if
@@ -37,14 +39,15 @@ class ModuleAnalyzer:
37
39
  except Exception as err:
38
40
  raise PycodeError('error importing %r' % modname, err) from err
39
41
  loader = getattr(mod, '__loader__', None)
40
- filename = getattr(mod, '__file__', None)
42
+ filename: str | None = getattr(mod, '__file__', None)
41
43
  if loader and getattr(loader, 'get_source', None):
42
44
  # prefer Native loader, as it respects #coding directive
43
45
  try:
44
46
  source = loader.get_source(modname)
45
47
  if source:
48
+ mod_path = None if filename is None else _StrPath(filename)
46
49
  # no exception and not None - it must be module source
47
- return filename, source
50
+ return mod_path, source
48
51
  except ImportError:
49
52
  pass # Try other "source-mining" methods
50
53
  if filename is None and loader and getattr(loader, 'get_filename', None):
@@ -58,31 +61,36 @@ class ModuleAnalyzer:
58
61
  if filename is None:
59
62
  # all methods for getting filename failed, so raise...
60
63
  raise PycodeError('no source found for module %r' % modname)
61
- filename = path.normpath(path.abspath(filename))
62
- if filename.lower().endswith(('.pyo', '.pyc')):
63
- filename = filename[:-1]
64
- if not path.isfile(filename) and path.isfile(filename + 'w'):
65
- filename += 'w'
66
- elif not filename.lower().endswith(('.py', '.pyw')):
67
- raise PycodeError('source is not a .py file: %r' % filename)
68
-
69
- if not path.isfile(filename):
70
- raise PycodeError('source file is not present: %r' % filename)
71
- return filename, None
64
+ mod_path = _StrPath(filename).resolve()
65
+ if mod_path.suffix in {'.pyo', '.pyc'}:
66
+ mod_path_pyw = mod_path.with_suffix('.pyw')
67
+ if not mod_path.is_file() and mod_path_pyw.is_file():
68
+ mod_path = mod_path_pyw
69
+ else:
70
+ mod_path = mod_path.with_suffix('.py')
71
+ elif mod_path.suffix not in {'.py', '.pyw'}:
72
+ msg = f'source is not a .py file: {mod_path!r}'
73
+ raise PycodeError(msg)
74
+
75
+ if not mod_path.is_file():
76
+ msg = f'source file is not present: {mod_path!r}'
77
+ raise PycodeError(msg)
78
+ return mod_path, None
72
79
 
73
80
  @classmethod
74
81
  def for_string(
75
82
  cls: type[ModuleAnalyzer],
76
83
  string: str,
77
84
  modname: str,
78
- srcname: str = '<string>',
85
+ srcname: str | os.PathLike[str] = '<string>',
79
86
  ) -> ModuleAnalyzer:
80
87
  return cls(string, modname, srcname)
81
88
 
82
89
  @classmethod
83
90
  def for_file(
84
- cls: type[ModuleAnalyzer], filename: str, modname: str
91
+ cls: type[ModuleAnalyzer], filename: str | os.PathLike[str], modname: str
85
92
  ) -> ModuleAnalyzer:
93
+ filename = _StrPath(filename)
86
94
  if ('file', filename) in cls.cache:
87
95
  return cls.cache['file', filename]
88
96
  try:
@@ -114,9 +122,11 @@ class ModuleAnalyzer:
114
122
  cls.cache['module', modname] = obj
115
123
  return obj
116
124
 
117
- def __init__(self, source: str, modname: str, srcname: str) -> None:
125
+ def __init__(
126
+ self, source: str, modname: str, srcname: str | os.PathLike[str]
127
+ ) -> None:
118
128
  self.modname = modname # name of the module
119
- self.srcname = srcname # name of the source file
129
+ self.srcname = str(srcname) # name of the source file
120
130
 
121
131
  # cache the source code as well
122
132
  self.code = source
sphinx/pycode/ast.py CHANGED
@@ -3,7 +3,10 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import ast
6
- from typing import NoReturn, overload
6
+ from typing import TYPE_CHECKING, overload
7
+
8
+ if TYPE_CHECKING:
9
+ from typing import NoReturn
7
10
 
8
11
  OPERATORS: dict[type[ast.AST], str] = {
9
12
  ast.Add: '+',
@@ -29,11 +32,11 @@ OPERATORS: dict[type[ast.AST], str] = {
29
32
 
30
33
 
31
34
  @overload
32
- def unparse(node: None, code: str = '') -> None: ... # NoQA: E704
35
+ def unparse(node: None, code: str = '') -> None: ...
33
36
 
34
37
 
35
38
  @overload
36
- def unparse(node: ast.AST, code: str = '') -> str: ... # NoQA: E704
39
+ def unparse(node: ast.AST, code: str = '') -> str: ...
37
40
 
38
41
 
39
42
  def unparse(node: ast.AST | None, code: str = '') -> str | None:
sphinx/pycode/parser.py CHANGED
@@ -10,13 +10,16 @@ import itertools
10
10
  import operator
11
11
  import re
12
12
  import tokenize
13
- from inspect import Signature
14
13
  from token import DEDENT, INDENT, NAME, NEWLINE, NUMBER, OP, STRING
15
14
  from tokenize import COMMENT, NL
16
- from typing import Any
15
+ from typing import TYPE_CHECKING
17
16
 
18
17
  from sphinx.pycode.ast import unparse as ast_unparse
19
18
 
19
+ if TYPE_CHECKING:
20
+ from inspect import Signature
21
+ from typing import Any
22
+
20
23
  comment_re = re.compile('^\\s*#: ?(.*)\r?\n?$')
21
24
  indent_re = re.compile('^\\s*$')
22
25
  emptyline_re = re.compile('^\\s*(#.*)?$')
@@ -40,21 +43,21 @@ def get_lvar_names(node: ast.AST, self: ast.arg | None = None) -> list[str]:
40
43
  This raises `TypeError` if the assignment does not create new variable::
41
44
 
42
45
  ary[0] = 'foo'
43
- dic["bar"] = 'baz'
46
+ dic['bar'] = 'baz'
44
47
  # => TypeError
45
48
  """
46
49
  if self:
47
50
  self_id = self.arg
48
51
 
49
52
  node_name = node.__class__.__name__
50
- if node_name in ('Constant', 'Index', 'Slice', 'Subscript'):
53
+ if node_name in {'Constant', 'Index', 'Slice', 'Subscript'}:
51
54
  raise TypeError('%r does not create new variable' % node)
52
55
  if node_name == 'Name':
53
56
  if self is None or node.id == self_id: # type: ignore[attr-defined]
54
57
  return [node.id] # type: ignore[attr-defined]
55
58
  else:
56
59
  raise TypeError('The assignment %r is not instance variable' % node)
57
- elif node_name in ('Tuple', 'List'):
60
+ elif node_name in {'Tuple', 'List'}:
58
61
  members = []
59
62
  for elt in node.elts: # type: ignore[attr-defined]
60
63
  with contextlib.suppress(TypeError):
@@ -111,7 +114,7 @@ class Token:
111
114
  self.end = end
112
115
  self.source = source
113
116
 
114
- def __eq__(self, other: Any) -> bool:
117
+ def __eq__(self, other: object) -> bool:
115
118
  if isinstance(other, int):
116
119
  return self.kind == other
117
120
  elif isinstance(other, str):
@@ -123,6 +126,9 @@ class Token:
123
126
  else:
124
127
  raise ValueError('Unknown value: %r' % other)
125
128
 
129
+ def __hash__(self) -> int:
130
+ return hash((self.kind, self.value, self.start, self.end, self.source))
131
+
126
132
  def match(self, *conditions: Any) -> bool:
127
133
  return any(self == candidate for candidate in conditions)
128
134
 
@@ -280,13 +286,13 @@ class VariableCommentPicker(ast.NodeVisitor):
280
286
  qualname = self.get_qualname_for(name)
281
287
  if qualname:
282
288
  basename = '.'.join(qualname[:-1])
283
- self.comments[(basename, name)] = comment
289
+ self.comments[basename, name] = comment
284
290
 
285
291
  def add_variable_annotation(self, name: str, annotation: ast.AST) -> None:
286
292
  qualname = self.get_qualname_for(name)
287
293
  if qualname:
288
294
  basename = '.'.join(qualname[:-1])
289
- self.annotations[(basename, name)] = ast_unparse(annotation)
295
+ self.annotations[basename, name] = ast_unparse(annotation)
290
296
 
291
297
  def is_final(self, decorators: list[ast.expr]) -> bool:
292
298
  final = []
sphinx/pygments_styles.py CHANGED
@@ -1,5 +1,7 @@
1
1
  """Sphinx theme specific highlighting styles."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  from pygments.style import Style
4
6
  from pygments.styles.friendly import FriendlyStyle
5
7
  from pygments.token import (
@@ -20,8 +22,7 @@ class NoneStyle(Style):
20
22
 
21
23
 
22
24
  class SphinxStyle(Style):
23
- """
24
- Like friendly, but a bit darker to enhance contrast on the green
25
+ """Like friendly, but a bit darker to enhance contrast on the green
25
26
  background.
26
27
  """
27
28
 
@@ -37,9 +38,7 @@ class SphinxStyle(Style):
37
38
 
38
39
 
39
40
  class PyramidStyle(Style):
40
- """
41
- Pylons/pyramid pygments style based on friendly style, by Blaise Laflamme.
42
- """
41
+ """Pylons/pyramid pygments style based on friendly style, by Blaise Laflamme."""
43
42
 
44
43
  # work in progress...
45
44