Sphinx 8.1.2__py3-none-any.whl → 8.2.0__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (328) 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 +50 -40
  55. sphinx/domains/python/__init__.py +402 -211
  56. sphinx/domains/python/_annotations.py +134 -61
  57. sphinx/domains/python/_object.py +155 -68
  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 +66 -0
  73. sphinx/ext/apidoc/__main__.py +9 -0
  74. sphinx/ext/apidoc/_cli.py +356 -0
  75. sphinx/ext/apidoc/_extension.py +262 -0
  76. sphinx/ext/apidoc/_generate.py +356 -0
  77. sphinx/ext/apidoc/_shared.py +99 -0
  78. sphinx/ext/autodoc/__init__.py +837 -483
  79. sphinx/ext/autodoc/directive.py +57 -21
  80. sphinx/ext/autodoc/importer.py +184 -67
  81. sphinx/ext/autodoc/mock.py +25 -10
  82. sphinx/ext/autodoc/preserve_defaults.py +17 -9
  83. sphinx/ext/autodoc/type_comment.py +56 -29
  84. sphinx/ext/autodoc/typehints.py +49 -26
  85. sphinx/ext/autosectionlabel.py +28 -11
  86. sphinx/ext/autosummary/__init__.py +281 -142
  87. sphinx/ext/autosummary/generate.py +121 -51
  88. sphinx/ext/coverage.py +152 -91
  89. sphinx/ext/doctest.py +169 -101
  90. sphinx/ext/duration.py +12 -6
  91. sphinx/ext/extlinks.py +33 -21
  92. sphinx/ext/githubpages.py +8 -8
  93. sphinx/ext/graphviz.py +175 -109
  94. sphinx/ext/ifconfig.py +11 -6
  95. sphinx/ext/imgconverter.py +48 -25
  96. sphinx/ext/imgmath.py +127 -97
  97. sphinx/ext/inheritance_diagram.py +177 -103
  98. sphinx/ext/intersphinx/__init__.py +22 -13
  99. sphinx/ext/intersphinx/__main__.py +3 -1
  100. sphinx/ext/intersphinx/_cli.py +18 -14
  101. sphinx/ext/intersphinx/_load.py +91 -82
  102. sphinx/ext/intersphinx/_resolve.py +108 -74
  103. sphinx/ext/intersphinx/_shared.py +2 -2
  104. sphinx/ext/linkcode.py +28 -12
  105. sphinx/ext/mathjax.py +60 -29
  106. sphinx/ext/napoleon/__init__.py +19 -7
  107. sphinx/ext/napoleon/docstring.py +229 -231
  108. sphinx/ext/todo.py +44 -49
  109. sphinx/ext/viewcode.py +105 -57
  110. sphinx/extension.py +3 -1
  111. sphinx/highlighting.py +13 -7
  112. sphinx/io.py +9 -13
  113. sphinx/jinja2glue.py +29 -26
  114. sphinx/locale/__init__.py +8 -9
  115. sphinx/locale/ar/LC_MESSAGES/sphinx.mo +0 -0
  116. sphinx/locale/ar/LC_MESSAGES/sphinx.po +2155 -2050
  117. sphinx/locale/bg/LC_MESSAGES/sphinx.mo +0 -0
  118. sphinx/locale/bg/LC_MESSAGES/sphinx.po +2045 -1940
  119. sphinx/locale/bn/LC_MESSAGES/sphinx.mo +0 -0
  120. sphinx/locale/bn/LC_MESSAGES/sphinx.po +2175 -2070
  121. sphinx/locale/ca/LC_MESSAGES/sphinx.js +3 -3
  122. sphinx/locale/ca/LC_MESSAGES/sphinx.mo +0 -0
  123. sphinx/locale/ca/LC_MESSAGES/sphinx.po +2690 -2585
  124. sphinx/locale/ca@valencia/LC_MESSAGES/sphinx.js +63 -0
  125. sphinx/locale/ca@valencia/LC_MESSAGES/sphinx.mo +0 -0
  126. sphinx/locale/ca@valencia/LC_MESSAGES/sphinx.po +4216 -0
  127. sphinx/locale/cak/LC_MESSAGES/sphinx.mo +0 -0
  128. sphinx/locale/cak/LC_MESSAGES/sphinx.po +2096 -1991
  129. sphinx/locale/cs/LC_MESSAGES/sphinx.mo +0 -0
  130. sphinx/locale/cs/LC_MESSAGES/sphinx.po +2248 -2143
  131. sphinx/locale/cy/LC_MESSAGES/sphinx.mo +0 -0
  132. sphinx/locale/cy/LC_MESSAGES/sphinx.po +2201 -2096
  133. sphinx/locale/da/LC_MESSAGES/sphinx.mo +0 -0
  134. sphinx/locale/da/LC_MESSAGES/sphinx.po +2282 -2177
  135. sphinx/locale/de/LC_MESSAGES/sphinx.mo +0 -0
  136. sphinx/locale/de/LC_MESSAGES/sphinx.po +2261 -2156
  137. sphinx/locale/de_DE/LC_MESSAGES/sphinx.mo +0 -0
  138. sphinx/locale/de_DE/LC_MESSAGES/sphinx.po +2045 -1940
  139. sphinx/locale/el/LC_MESSAGES/sphinx.mo +0 -0
  140. sphinx/locale/el/LC_MESSAGES/sphinx.po +2604 -2499
  141. sphinx/locale/en_DE/LC_MESSAGES/sphinx.mo +0 -0
  142. sphinx/locale/en_DE/LC_MESSAGES/sphinx.po +2045 -1940
  143. sphinx/locale/en_FR/LC_MESSAGES/sphinx.mo +0 -0
  144. sphinx/locale/en_FR/LC_MESSAGES/sphinx.po +2045 -1940
  145. sphinx/locale/en_GB/LC_MESSAGES/sphinx.mo +0 -0
  146. sphinx/locale/en_GB/LC_MESSAGES/sphinx.po +2631 -2526
  147. sphinx/locale/en_HK/LC_MESSAGES/sphinx.mo +0 -0
  148. sphinx/locale/en_HK/LC_MESSAGES/sphinx.po +2045 -1940
  149. sphinx/locale/eo/LC_MESSAGES/sphinx.mo +0 -0
  150. sphinx/locale/eo/LC_MESSAGES/sphinx.po +2078 -1973
  151. sphinx/locale/es/LC_MESSAGES/sphinx.mo +0 -0
  152. sphinx/locale/es/LC_MESSAGES/sphinx.po +2633 -2528
  153. sphinx/locale/es_CO/LC_MESSAGES/sphinx.mo +0 -0
  154. sphinx/locale/es_CO/LC_MESSAGES/sphinx.po +2045 -1940
  155. sphinx/locale/et/LC_MESSAGES/sphinx.mo +0 -0
  156. sphinx/locale/et/LC_MESSAGES/sphinx.po +2449 -2344
  157. sphinx/locale/eu/LC_MESSAGES/sphinx.mo +0 -0
  158. sphinx/locale/eu/LC_MESSAGES/sphinx.po +2241 -2136
  159. sphinx/locale/fa/LC_MESSAGES/sphinx.mo +0 -0
  160. sphinx/locale/fa/LC_MESSAGES/sphinx.po +504 -500
  161. sphinx/locale/fi/LC_MESSAGES/sphinx.mo +0 -0
  162. sphinx/locale/fi/LC_MESSAGES/sphinx.po +499 -495
  163. sphinx/locale/fr/LC_MESSAGES/sphinx.mo +0 -0
  164. sphinx/locale/fr/LC_MESSAGES/sphinx.po +513 -509
  165. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.mo +0 -0
  166. sphinx/locale/fr_FR/LC_MESSAGES/sphinx.po +499 -495
  167. sphinx/locale/gl/LC_MESSAGES/sphinx.mo +0 -0
  168. sphinx/locale/gl/LC_MESSAGES/sphinx.po +2644 -2539
  169. sphinx/locale/he/LC_MESSAGES/sphinx.mo +0 -0
  170. sphinx/locale/he/LC_MESSAGES/sphinx.po +499 -495
  171. sphinx/locale/hi/LC_MESSAGES/sphinx.mo +0 -0
  172. sphinx/locale/hi/LC_MESSAGES/sphinx.po +504 -500
  173. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.mo +0 -0
  174. sphinx/locale/hi_IN/LC_MESSAGES/sphinx.po +499 -495
  175. sphinx/locale/hr/LC_MESSAGES/sphinx.mo +0 -0
  176. sphinx/locale/hr/LC_MESSAGES/sphinx.po +501 -497
  177. sphinx/locale/hu/LC_MESSAGES/sphinx.mo +0 -0
  178. sphinx/locale/hu/LC_MESSAGES/sphinx.po +499 -495
  179. sphinx/locale/id/LC_MESSAGES/sphinx.mo +0 -0
  180. sphinx/locale/id/LC_MESSAGES/sphinx.po +2609 -2504
  181. sphinx/locale/is/LC_MESSAGES/sphinx.mo +0 -0
  182. sphinx/locale/is/LC_MESSAGES/sphinx.po +499 -495
  183. sphinx/locale/it/LC_MESSAGES/sphinx.mo +0 -0
  184. sphinx/locale/it/LC_MESSAGES/sphinx.po +2265 -2160
  185. sphinx/locale/ja/LC_MESSAGES/sphinx.mo +0 -0
  186. sphinx/locale/ja/LC_MESSAGES/sphinx.po +2621 -2516
  187. sphinx/locale/ka/LC_MESSAGES/sphinx.mo +0 -0
  188. sphinx/locale/ka/LC_MESSAGES/sphinx.po +2567 -2462
  189. sphinx/locale/ko/LC_MESSAGES/sphinx.mo +0 -0
  190. sphinx/locale/ko/LC_MESSAGES/sphinx.po +2631 -2526
  191. sphinx/locale/lt/LC_MESSAGES/sphinx.mo +0 -0
  192. sphinx/locale/lt/LC_MESSAGES/sphinx.po +2214 -2109
  193. sphinx/locale/lv/LC_MESSAGES/sphinx.mo +0 -0
  194. sphinx/locale/lv/LC_MESSAGES/sphinx.po +2218 -2113
  195. sphinx/locale/mk/LC_MESSAGES/sphinx.mo +0 -0
  196. sphinx/locale/mk/LC_MESSAGES/sphinx.po +2088 -1983
  197. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.mo +0 -0
  198. sphinx/locale/nb_NO/LC_MESSAGES/sphinx.po +2247 -2142
  199. sphinx/locale/ne/LC_MESSAGES/sphinx.mo +0 -0
  200. sphinx/locale/ne/LC_MESSAGES/sphinx.po +2227 -2122
  201. sphinx/locale/nl/LC_MESSAGES/sphinx.mo +0 -0
  202. sphinx/locale/nl/LC_MESSAGES/sphinx.po +2316 -2211
  203. sphinx/locale/pl/LC_MESSAGES/sphinx.js +2 -2
  204. sphinx/locale/pl/LC_MESSAGES/sphinx.mo +0 -0
  205. sphinx/locale/pl/LC_MESSAGES/sphinx.po +2442 -2336
  206. sphinx/locale/pt/LC_MESSAGES/sphinx.mo +0 -0
  207. sphinx/locale/pt/LC_MESSAGES/sphinx.po +2045 -1940
  208. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.mo +0 -0
  209. sphinx/locale/pt_BR/LC_MESSAGES/sphinx.po +2657 -2552
  210. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.mo +0 -0
  211. sphinx/locale/pt_PT/LC_MESSAGES/sphinx.po +2243 -2138
  212. sphinx/locale/ro/LC_MESSAGES/sphinx.mo +0 -0
  213. sphinx/locale/ro/LC_MESSAGES/sphinx.po +2244 -2139
  214. sphinx/locale/ru/LC_MESSAGES/sphinx.js +1 -1
  215. sphinx/locale/ru/LC_MESSAGES/sphinx.mo +0 -0
  216. sphinx/locale/ru/LC_MESSAGES/sphinx.po +2660 -2555
  217. sphinx/locale/si/LC_MESSAGES/sphinx.mo +0 -0
  218. sphinx/locale/si/LC_MESSAGES/sphinx.po +2134 -2029
  219. sphinx/locale/sk/LC_MESSAGES/sphinx.mo +0 -0
  220. sphinx/locale/sk/LC_MESSAGES/sphinx.po +2614 -2509
  221. sphinx/locale/sl/LC_MESSAGES/sphinx.mo +0 -0
  222. sphinx/locale/sl/LC_MESSAGES/sphinx.po +2167 -2062
  223. sphinx/locale/sphinx.pot +2069 -1964
  224. sphinx/locale/sq/LC_MESSAGES/sphinx.mo +0 -0
  225. sphinx/locale/sq/LC_MESSAGES/sphinx.po +2661 -2556
  226. sphinx/locale/sr/LC_MESSAGES/sphinx.mo +0 -0
  227. sphinx/locale/sr/LC_MESSAGES/sphinx.po +2213 -2108
  228. sphinx/locale/sv/LC_MESSAGES/sphinx.mo +0 -0
  229. sphinx/locale/sv/LC_MESSAGES/sphinx.po +2229 -2124
  230. sphinx/locale/te/LC_MESSAGES/sphinx.mo +0 -0
  231. sphinx/locale/te/LC_MESSAGES/sphinx.po +2045 -1940
  232. sphinx/locale/tr/LC_MESSAGES/sphinx.mo +0 -0
  233. sphinx/locale/tr/LC_MESSAGES/sphinx.po +2608 -2503
  234. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.mo +0 -0
  235. sphinx/locale/uk_UA/LC_MESSAGES/sphinx.po +2167 -2062
  236. sphinx/locale/ur/LC_MESSAGES/sphinx.mo +0 -0
  237. sphinx/locale/ur/LC_MESSAGES/sphinx.po +2045 -1940
  238. sphinx/locale/vi/LC_MESSAGES/sphinx.mo +0 -0
  239. sphinx/locale/vi/LC_MESSAGES/sphinx.po +2204 -2099
  240. sphinx/locale/yue/LC_MESSAGES/sphinx.mo +0 -0
  241. sphinx/locale/yue/LC_MESSAGES/sphinx.po +2045 -1940
  242. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.mo +0 -0
  243. sphinx/locale/zh_HK/LC_MESSAGES/sphinx.po +2045 -1940
  244. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.mo +0 -0
  245. sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po +2659 -2554
  246. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.mo +0 -0
  247. sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.po +2045 -1940
  248. sphinx/parsers.py +8 -7
  249. sphinx/project.py +2 -2
  250. sphinx/pycode/__init__.py +31 -21
  251. sphinx/pycode/ast.py +6 -3
  252. sphinx/pycode/parser.py +14 -8
  253. sphinx/pygments_styles.py +4 -5
  254. sphinx/registry.py +192 -92
  255. sphinx/roles.py +58 -7
  256. sphinx/search/__init__.py +75 -54
  257. sphinx/search/en.py +11 -13
  258. sphinx/search/fi.py +1 -1
  259. sphinx/search/ja.py +8 -6
  260. sphinx/search/nl.py +1 -1
  261. sphinx/search/zh.py +19 -21
  262. sphinx/testing/fixtures.py +26 -29
  263. sphinx/testing/path.py +26 -62
  264. sphinx/testing/restructuredtext.py +14 -8
  265. sphinx/testing/util.py +21 -19
  266. sphinx/texinputs/make.bat.jinja +50 -50
  267. sphinx/texinputs/sphinx.sty +4 -3
  268. sphinx/texinputs/sphinxlatexadmonitions.sty +1 -1
  269. sphinx/texinputs/sphinxlatexobjects.sty +29 -10
  270. sphinx/themes/basic/static/searchtools.js +8 -5
  271. sphinx/theming.py +49 -61
  272. sphinx/transforms/__init__.py +17 -38
  273. sphinx/transforms/compact_bullet_list.py +5 -3
  274. sphinx/transforms/i18n.py +8 -21
  275. sphinx/transforms/post_transforms/__init__.py +142 -93
  276. sphinx/transforms/post_transforms/code.py +5 -5
  277. sphinx/transforms/post_transforms/images.py +28 -24
  278. sphinx/transforms/references.py +3 -1
  279. sphinx/util/__init__.py +109 -60
  280. sphinx/util/_files.py +39 -23
  281. sphinx/util/_importer.py +4 -1
  282. sphinx/util/_inventory_file_reader.py +76 -0
  283. sphinx/util/_io.py +2 -2
  284. sphinx/util/_lines.py +6 -3
  285. sphinx/util/_pathlib.py +40 -2
  286. sphinx/util/build_phase.py +2 -0
  287. sphinx/util/cfamily.py +19 -14
  288. sphinx/util/console.py +44 -179
  289. sphinx/util/display.py +9 -10
  290. sphinx/util/docfields.py +140 -122
  291. sphinx/util/docstrings.py +1 -1
  292. sphinx/util/docutils.py +118 -77
  293. sphinx/util/fileutil.py +25 -26
  294. sphinx/util/http_date.py +2 -0
  295. sphinx/util/i18n.py +77 -64
  296. sphinx/util/images.py +8 -6
  297. sphinx/util/inspect.py +147 -38
  298. sphinx/util/inventory.py +215 -116
  299. sphinx/util/logging.py +33 -33
  300. sphinx/util/matching.py +12 -4
  301. sphinx/util/nodes.py +18 -13
  302. sphinx/util/osutil.py +38 -39
  303. sphinx/util/parallel.py +22 -13
  304. sphinx/util/parsing.py +2 -1
  305. sphinx/util/png.py +6 -2
  306. sphinx/util/requests.py +33 -2
  307. sphinx/util/rst.py +3 -2
  308. sphinx/util/tags.py +1 -1
  309. sphinx/util/template.py +18 -10
  310. sphinx/util/texescape.py +8 -6
  311. sphinx/util/typing.py +148 -122
  312. sphinx/versioning.py +3 -3
  313. sphinx/writers/html.py +3 -1
  314. sphinx/writers/html5.py +63 -52
  315. sphinx/writers/latex.py +83 -67
  316. sphinx/writers/manpage.py +19 -38
  317. sphinx/writers/texinfo.py +47 -47
  318. sphinx/writers/text.py +50 -32
  319. sphinx/writers/xml.py +11 -8
  320. {sphinx-8.1.2.dist-info → sphinx-8.2.0.dist-info}/LICENSE.rst +1 -1
  321. {sphinx-8.1.2.dist-info → sphinx-8.2.0.dist-info}/METADATA +25 -15
  322. sphinx-8.2.0.dist-info/RECORD +606 -0
  323. {sphinx-8.1.2.dist-info → sphinx-8.2.0.dist-info}/WHEEL +1 -1
  324. sphinx/builders/html/transforms.py +0 -90
  325. sphinx/ext/apidoc.py +0 -721
  326. sphinx/util/exceptions.py +0 -74
  327. sphinx-8.1.2.dist-info/RECORD +0 -598
  328. {sphinx-8.1.2.dist-info → sphinx-8.2.0.dist-info}/entry_points.txt +0 -0
@@ -1,35 +1,55 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections.abc import Callable
4
- from typing import TYPE_CHECKING, Any
4
+ from typing import TYPE_CHECKING
5
5
 
6
6
  from docutils import nodes
7
7
  from docutils.statemachine import StringList
8
- from docutils.utils import Reporter, assemble_option_dict
8
+ from docutils.utils import assemble_option_dict
9
9
 
10
- from sphinx.ext.autodoc import Documenter, Options
10
+ from sphinx.ext.autodoc import Options
11
11
  from sphinx.util import logging
12
12
  from sphinx.util.docutils import SphinxDirective, switch_source_input
13
13
  from sphinx.util.parsing import nested_parse_to_nodes
14
14
 
15
15
  if TYPE_CHECKING:
16
+ from typing import Any
17
+
16
18
  from docutils.nodes import Node
17
19
  from docutils.parsers.rst.states import RSTState
20
+ from docutils.utils import Reporter
18
21
 
19
22
  from sphinx.config import Config
20
23
  from sphinx.environment import BuildEnvironment
24
+ from sphinx.ext.autodoc import Documenter
21
25
 
22
26
  logger = logging.getLogger(__name__)
23
27
 
24
28
 
25
29
  # common option names for autodoc directives
26
- AUTODOC_DEFAULT_OPTIONS = ['members', 'undoc-members', 'inherited-members',
27
- 'show-inheritance', 'private-members', 'special-members',
28
- 'ignore-module-all', 'exclude-members', 'member-order',
29
- 'imported-members', 'class-doc-from', 'no-value']
30
-
31
- AUTODOC_EXTENDABLE_OPTIONS = ['members', 'private-members', 'special-members',
32
- 'exclude-members']
30
+ AUTODOC_DEFAULT_OPTIONS = [
31
+ 'members',
32
+ 'undoc-members',
33
+ 'no-index',
34
+ 'no-index-entry',
35
+ 'inherited-members',
36
+ 'show-inheritance',
37
+ 'private-members',
38
+ 'special-members',
39
+ 'ignore-module-all',
40
+ 'exclude-members',
41
+ 'member-order',
42
+ 'imported-members',
43
+ 'class-doc-from',
44
+ 'no-value',
45
+ ]
46
+
47
+ AUTODOC_EXTENDABLE_OPTIONS = frozenset({
48
+ 'members',
49
+ 'private-members',
50
+ 'special-members',
51
+ 'exclude-members',
52
+ })
33
53
 
34
54
 
35
55
  class DummyOptionSpec(dict[str, Callable[[str], str]]):
@@ -46,8 +66,14 @@ class DummyOptionSpec(dict[str, Callable[[str], str]]):
46
66
  class DocumenterBridge:
47
67
  """A parameters container for Documenters."""
48
68
 
49
- def __init__(self, env: BuildEnvironment, reporter: Reporter | None, options: Options,
50
- lineno: int, state: Any) -> None:
69
+ def __init__(
70
+ self,
71
+ env: BuildEnvironment,
72
+ reporter: Reporter | None,
73
+ options: Options,
74
+ lineno: int,
75
+ state: Any,
76
+ ) -> None:
51
77
  self.env = env
52
78
  self._reporter = reporter
53
79
  self.genopt = options
@@ -58,7 +84,7 @@ class DocumenterBridge:
58
84
 
59
85
 
60
86
  def process_documenter_options(
61
- documenter: type[Documenter], config: Config, options: dict[str, str],
87
+ documenter: type[Documenter], config: Config, options: dict[str, str]
62
88
  ) -> Options:
63
89
  """Recognize options of Documenter from user input."""
64
90
  default_options = config.autodoc_default_options
@@ -83,8 +109,9 @@ def process_documenter_options(
83
109
  return Options(assemble_option_dict(options.items(), documenter.option_spec))
84
110
 
85
111
 
86
- def parse_generated_content(state: RSTState, content: StringList, documenter: Documenter,
87
- ) -> list[Node]:
112
+ def parse_generated_content(
113
+ state: RSTState, content: StringList, documenter: Documenter
114
+ ) -> list[Node]:
88
115
  """Parse an item of content generated by Documenter."""
89
116
  with switch_source_input(state, content):
90
117
  if documenter.titles_allowed:
@@ -115,26 +142,35 @@ class AutodocDirective(SphinxDirective):
115
142
 
116
143
  try:
117
144
  source, lineno = reporter.get_source_and_line( # type: ignore[attr-defined]
118
- self.lineno)
145
+ self.lineno
146
+ )
119
147
  except AttributeError:
120
148
  source, lineno = (None, None)
121
149
  logger.debug('[autodoc] %s:%s: input:\n%s', source, lineno, self.block_text)
122
150
 
123
151
  # look up target Documenter
124
152
  objtype = self.name[4:] # strip prefix (auto-).
125
- doccls = self.env.app.registry.documenters[objtype]
153
+ doccls = self.env._registry.documenters[objtype]
126
154
 
127
155
  # process the options with the selected documenter's option_spec
128
156
  try:
129
- documenter_options = process_documenter_options(doccls, self.config, self.options)
157
+ documenter_options = process_documenter_options(
158
+ doccls, self.config, self.options
159
+ )
130
160
  except (KeyError, ValueError, TypeError) as exc:
131
161
  # an option is either unknown or has a wrong type
132
- logger.error('An option to %s is either unknown or has an invalid value: %s',
133
- self.name, exc, location=(self.env.docname, lineno))
162
+ logger.error( # NoQA: TRY400
163
+ 'An option to %s is either unknown or has an invalid value: %s',
164
+ self.name,
165
+ exc,
166
+ location=(self.env.docname, lineno),
167
+ )
134
168
  return []
135
169
 
136
170
  # generate the output
137
- params = DocumenterBridge(self.env, reporter, documenter_options, lineno, self.state)
171
+ params = DocumenterBridge(
172
+ self.env, reporter, documenter_options, lineno, self.state
173
+ )
138
174
  documenter = doccls(params, self.arguments[0])
139
175
  documenter.generate(more_content=self.content)
140
176
  if not params.result:
@@ -9,6 +9,10 @@ import sys
9
9
  import traceback
10
10
  import typing
11
11
  from enum import Enum
12
+ from importlib.abc import FileLoader
13
+ from importlib.machinery import EXTENSION_SUFFIXES
14
+ from importlib.util import decode_source, find_spec, module_from_spec, spec_from_loader
15
+ from pathlib import Path
12
16
  from typing import TYPE_CHECKING, NamedTuple
13
17
 
14
18
  from sphinx.errors import PycodeError
@@ -26,18 +30,24 @@ from sphinx.util.inspect import (
26
30
  )
27
31
 
28
32
  if TYPE_CHECKING:
29
- from collections.abc import Callable, Iterator, Mapping
33
+ from collections.abc import Iterator, Mapping
34
+ from importlib.machinery import ModuleSpec
30
35
  from types import ModuleType
31
- from typing import Any
36
+ from typing import Any, Protocol
32
37
 
33
38
  from sphinx.ext.autodoc import ObjectMember
34
39
 
40
+ class _AttrGetter(Protocol):
41
+ def __call__(self, obj: Any, name: str, default: Any = ..., /) -> Any: ...
42
+
43
+
44
+ _NATIVE_SUFFIXES: frozenset[str] = frozenset({'.pyx', *EXTENSION_SUFFIXES})
35
45
  logger = logging.getLogger(__name__)
36
46
 
37
47
 
38
48
  def _filter_enum_dict(
39
49
  enum_class: type[Enum],
40
- attrgetter: Callable[[Any, str, Any], Any],
50
+ attrgetter: _AttrGetter,
41
51
  enum_class_dict: Mapping[str, object],
42
52
  ) -> Iterator[tuple[str, type, Any]]:
43
53
  """Find the attributes to document of an enumeration class.
@@ -51,19 +61,21 @@ def _filter_enum_dict(
51
61
  candidate_in_mro: set[str] = set()
52
62
  # sunder names that were picked up (and thereby allowed to be redefined)
53
63
  # see: https://docs.python.org/3/howto/enum.html#supported-dunder-names
54
- sunder_names = {'_name_', '_value_', '_missing_', '_order_', '_generate_next_value_'}
64
+ sunder_names = {
65
+ '_name_',
66
+ '_value_',
67
+ '_missing_',
68
+ '_order_',
69
+ '_generate_next_value_',
70
+ }
55
71
  # attributes that can be picked up on a mixin type or the enum's data type
56
72
  public_names = {'name', 'value', *object.__dict__, *sunder_names}
57
73
  # names that are ignored by default
58
74
  ignore_names = Enum.__dict__.keys() - public_names
59
75
 
60
- def is_native_api(obj: object, name: str) -> bool:
61
- """Check whether *obj* is the same as ``Enum.__dict__[name]``."""
62
- return unwrap_all(obj) is unwrap_all(Enum.__dict__[name])
63
-
64
76
  def should_ignore(name: str, value: Any) -> bool:
65
77
  if name in sunder_names:
66
- return is_native_api(value, name)
78
+ return _is_native_enum_api(value, name)
67
79
  return name in ignore_names
68
80
 
69
81
  sentinel = object()
@@ -71,7 +83,7 @@ def _filter_enum_dict(
71
83
  def query(name: str, defining_class: type) -> tuple[str, type, Any] | None:
72
84
  value = attrgetter(enum_class, name, sentinel)
73
85
  if value is not sentinel:
74
- return (name, defining_class, value)
86
+ return name, defining_class, value
75
87
  return None
76
88
 
77
89
  # attributes defined on a parent type, possibly shadowed later by
@@ -92,8 +104,14 @@ def _filter_enum_dict(
92
104
  # exclude members coming from the native Enum unless
93
105
  # they were redefined on a mixin type or the data type
94
106
  excluded_members = Enum.__dict__.keys() - candidate_in_mro
95
- yield from filter(None, (query(name, enum_class) for name in enum_class_dict
96
- if name not in excluded_members))
107
+ yield from filter(
108
+ None,
109
+ (
110
+ query(name, enum_class)
111
+ for name in enum_class_dict
112
+ if name not in excluded_members
113
+ ),
114
+ )
97
115
 
98
116
  # check if allowed members from ``Enum`` were redefined at the enum level
99
117
  special_names = sunder_names | public_names
@@ -101,17 +119,22 @@ def _filter_enum_dict(
101
119
  special_names &= Enum.__dict__.keys()
102
120
  for name in special_names:
103
121
  if (
104
- not is_native_api(enum_class_dict[name], name)
122
+ not _is_native_enum_api(enum_class_dict[name], name)
105
123
  and (item := query(name, enum_class)) is not None
106
124
  ):
107
125
  yield item
108
126
 
109
127
 
128
+ def _is_native_enum_api(obj: object, name: str) -> bool:
129
+ """Check whether *obj* is the same as ``Enum.__dict__[name]``."""
130
+ return unwrap_all(obj) is unwrap_all(Enum.__dict__[name])
131
+
132
+
110
133
  def mangle(subject: Any, name: str) -> str:
111
134
  """Mangle the given name."""
112
135
  try:
113
136
  if isclass(subject) and name.startswith('__') and not name.endswith('__'):
114
- return f"_{subject.__name__}{name}"
137
+ return f'_{subject.__name__}{name}'
115
138
  except AttributeError:
116
139
  pass
117
140
 
@@ -122,12 +145,12 @@ def unmangle(subject: Any, name: str) -> str | None:
122
145
  """Unmangle the given name."""
123
146
  try:
124
147
  if isclass(subject) and not name.endswith('__'):
125
- prefix = "_%s__" % subject.__name__
148
+ prefix = f'_{subject.__name__}__'
126
149
  if name.startswith(prefix):
127
- return name.replace(prefix, "__", 1)
150
+ return name.replace(prefix, '__', 1)
128
151
  else:
129
152
  for cls in subject.__mro__:
130
- prefix = "_%s__" % cls.__name__
153
+ prefix = f'_{cls.__name__}__'
131
154
  if name.startswith(prefix):
132
155
  # mangled attribute defined in parent class
133
156
  return None
@@ -137,20 +160,87 @@ def unmangle(subject: Any, name: str) -> str | None:
137
160
  return name
138
161
 
139
162
 
140
- def import_module(modname: str) -> Any:
141
- """Call importlib.import_module(modname), convert exceptions to ImportError."""
163
+ def import_module(modname: str, try_reload: bool = False) -> Any:
164
+ if modname in sys.modules:
165
+ return sys.modules[modname]
166
+
167
+ original_module_names = frozenset(sys.modules)
142
168
  try:
143
- return importlib.import_module(modname)
169
+ spec = find_spec(modname)
170
+ if spec is None:
171
+ msg = f'No module named {modname!r}'
172
+ raise ModuleNotFoundError(msg, name=modname) # NoQA: TRY301
173
+ spec, pyi_path = _find_type_stub_spec(spec, modname)
174
+ if pyi_path is None:
175
+ module = importlib.import_module(modname)
176
+ else:
177
+ if spec.loader is None:
178
+ msg = 'missing loader'
179
+ raise ImportError(msg, name=spec.name) # NoQA: TRY301
180
+ sys.modules[modname] = module = module_from_spec(spec)
181
+ spec.loader.exec_module(module)
182
+ except ImportError:
183
+ raise
144
184
  except BaseException as exc:
145
185
  # Importing modules may cause any side effects, including
146
186
  # SystemExit, so we need to catch all errors.
147
187
  raise ImportError(exc, traceback.format_exc()) from exc
188
+ if try_reload and os.environ.get('SPHINX_AUTODOC_RELOAD_MODULES'):
189
+ new_modules = [m for m in sys.modules if m not in original_module_names]
190
+ # Try reloading modules with ``typing.TYPE_CHECKING == True``.
191
+ try:
192
+ typing.TYPE_CHECKING = True # type: ignore[misc]
193
+ # Ignore failures; we've already successfully loaded these modules
194
+ with contextlib.suppress(ImportError, KeyError):
195
+ for m in new_modules:
196
+ mod_path = getattr(sys.modules[m], '__file__', '')
197
+ if mod_path and mod_path.endswith('.pyi'):
198
+ continue
199
+ _reload_module(sys.modules[m])
200
+ finally:
201
+ typing.TYPE_CHECKING = False # type: ignore[misc]
202
+ module = sys.modules[modname]
203
+ return module
204
+
205
+
206
+ def _find_type_stub_spec(
207
+ spec: ModuleSpec, modname: str
208
+ ) -> tuple[ModuleSpec, Path | None]:
209
+ """Try finding a spec for a PEP 561 '.pyi' stub file for native modules."""
210
+ if spec.origin is None:
211
+ return spec, None
212
+
213
+ for suffix in _NATIVE_SUFFIXES:
214
+ if not spec.origin.endswith(suffix):
215
+ continue
216
+ pyi_path = Path(spec.origin.removesuffix(suffix) + '.pyi')
217
+ if not pyi_path.is_file():
218
+ continue
219
+ pyi_loader = _StubFileLoader(modname, path=str(pyi_path))
220
+ pyi_spec = spec_from_loader(modname, loader=pyi_loader)
221
+ if pyi_spec is not None:
222
+ return pyi_spec, pyi_path
223
+ return spec, None
224
+
225
+
226
+ class _StubFileLoader(FileLoader):
227
+ """Load modules from ``.pyi`` stub files."""
228
+
229
+ def get_source(self, fullname: str) -> str:
230
+ path = self.get_filename(fullname)
231
+ for suffix in _NATIVE_SUFFIXES:
232
+ if not path.endswith(suffix):
233
+ continue
234
+ path = path.removesuffix(suffix) + '.pyi'
235
+ try:
236
+ source_bytes = self.get_data(path)
237
+ except OSError as exc:
238
+ raise ImportError from exc
239
+ return decode_source(source_bytes)
148
240
 
149
241
 
150
242
  def _reload_module(module: ModuleType) -> Any:
151
- """
152
- Call importlib.reload(module), convert exceptions to ImportError
153
- """
243
+ """Call importlib.reload(module), convert exceptions to ImportError"""
154
244
  try:
155
245
  return importlib.reload(module)
156
246
  except BaseException as exc:
@@ -159,8 +249,12 @@ def _reload_module(module: ModuleType) -> Any:
159
249
  raise ImportError(exc, traceback.format_exc()) from exc
160
250
 
161
251
 
162
- def import_object(modname: str, objpath: list[str], objtype: str = '',
163
- attrgetter: Callable[[Any, str], Any] = safe_getattr) -> Any:
252
+ def import_object(
253
+ modname: str,
254
+ objpath: list[str],
255
+ objtype: str = '',
256
+ attrgetter: _AttrGetter = safe_getattr,
257
+ ) -> Any:
164
258
  if objpath:
165
259
  logger.debug('[autodoc] from %s import %s', modname, '.'.join(objpath))
166
260
  else:
@@ -172,20 +266,7 @@ def import_object(modname: str, objpath: list[str], objtype: str = '',
172
266
  objpath = objpath.copy()
173
267
  while module is None:
174
268
  try:
175
- original_module_names = frozenset(sys.modules)
176
- module = import_module(modname)
177
- if os.environ.get('SPHINX_AUTODOC_RELOAD_MODULES'):
178
- new_modules = [m for m in sys.modules if m not in original_module_names]
179
- # Try reloading modules with ``typing.TYPE_CHECKING == True``.
180
- try:
181
- typing.TYPE_CHECKING = True
182
- # Ignore failures; we've already successfully loaded these modules
183
- with contextlib.suppress(ImportError, KeyError):
184
- for m in new_modules:
185
- _reload_module(sys.modules[m])
186
- finally:
187
- typing.TYPE_CHECKING = False
188
- module = sys.modules[modname]
269
+ module = import_module(modname, try_reload=True)
189
270
  logger.debug('[autodoc] import %s => %r', modname, module)
190
271
  except ImportError as exc:
191
272
  logger.debug('[autodoc] import %s => failed', modname)
@@ -210,7 +291,7 @@ def import_object(modname: str, objpath: list[str], objtype: str = '',
210
291
  logger.debug('[autodoc] => %r', obj)
211
292
  except TypeError:
212
293
  # fallback of failure on logging for broken object
213
- # refs: https://github.com/sphinx-doc/sphinx/issues/9095
294
+ # See: https://github.com/sphinx-doc/sphinx/issues/9095
214
295
  logger.debug('[autodoc] => %r', (obj,))
215
296
 
216
297
  object_name = attrname
@@ -221,24 +302,32 @@ def import_object(modname: str, objpath: list[str], objtype: str = '',
221
302
  exc = exc_on_importing
222
303
 
223
304
  if objpath:
224
- errmsg = ('autodoc: failed to import %s %r from module %r' %
225
- (objtype, '.'.join(objpath), modname))
305
+ errmsg = 'autodoc: failed to import %s %r from module %r' % (
306
+ objtype,
307
+ '.'.join(objpath),
308
+ modname,
309
+ )
226
310
  else:
227
311
  errmsg = f'autodoc: failed to import {objtype} {modname!r}'
228
312
 
229
313
  if isinstance(exc, ImportError):
230
314
  # import_module() raises ImportError having real exception obj and
231
315
  # traceback
232
- real_exc, traceback_msg = exc.args
316
+ real_exc = exc.args[0]
317
+ traceback_msg = traceback.format_exception(exc)
233
318
  if isinstance(real_exc, SystemExit):
234
- errmsg += ('; the module executes module level statement '
235
- 'and it might call sys.exit().')
319
+ errmsg += (
320
+ '; the module executes module level statement '
321
+ 'and it might call sys.exit().'
322
+ )
236
323
  elif isinstance(real_exc, ImportError) and real_exc.args:
237
324
  errmsg += '; the following exception was raised:\n%s' % real_exc.args[0]
238
325
  else:
239
326
  errmsg += '; the following exception was raised:\n%s' % traceback_msg
240
327
  else:
241
- errmsg += '; the following exception was raised:\n%s' % traceback.format_exc()
328
+ errmsg += (
329
+ '; the following exception was raised:\n%s' % traceback.format_exc()
330
+ )
242
331
 
243
332
  logger.debug(errmsg)
244
333
  raise ImportError(errmsg) from exc
@@ -253,7 +342,7 @@ class Attribute(NamedTuple):
253
342
  def get_object_members(
254
343
  subject: Any,
255
344
  objpath: list[str],
256
- attrgetter: Callable,
345
+ attrgetter: _AttrGetter,
257
346
  analyzer: ModuleAnalyzer | None = None,
258
347
  ) -> dict[str, Attribute]:
259
348
  """Get members and attributes of target object."""
@@ -266,11 +355,17 @@ def get_object_members(
266
355
 
267
356
  # enum members
268
357
  if isenumclass(subject):
269
- for name, defining_class, value in _filter_enum_dict(subject, attrgetter, obj_dict):
358
+ for name, defining_class, value in _filter_enum_dict(
359
+ subject, attrgetter, obj_dict
360
+ ):
270
361
  # the order of occurrence of *name* matches the subject's MRO,
271
362
  # allowing inherited attributes to be shadowed correctly
272
363
  if unmangled := unmangle(defining_class, name):
273
- members[unmangled] = Attribute(unmangled, defining_class is subject, value)
364
+ members[unmangled] = Attribute(
365
+ name=unmangled,
366
+ directly_defined=defining_class is subject,
367
+ value=value,
368
+ )
274
369
 
275
370
  # members in __slots__
276
371
  try:
@@ -279,7 +374,9 @@ def get_object_members(
279
374
  from sphinx.ext.autodoc import SLOTSATTR
280
375
 
281
376
  for name in subject___slots__:
282
- members[name] = Attribute(name, True, SLOTSATTR)
377
+ members[name] = Attribute(
378
+ name=name, directly_defined=True, value=SLOTSATTR
379
+ )
283
380
  except (TypeError, ValueError):
284
381
  pass
285
382
 
@@ -290,7 +387,9 @@ def get_object_members(
290
387
  directly_defined = name in obj_dict
291
388
  unmangled = unmangle(subject, name)
292
389
  if unmangled and unmangled not in members:
293
- members[unmangled] = Attribute(unmangled, directly_defined, value)
390
+ members[unmangled] = Attribute(
391
+ name=unmangled, directly_defined=directly_defined, value=value
392
+ )
294
393
  except AttributeError:
295
394
  continue
296
395
 
@@ -299,20 +398,25 @@ def get_object_members(
299
398
  for name in getannotations(cls):
300
399
  unmangled = unmangle(cls, name)
301
400
  if unmangled and unmangled not in members:
302
- members[unmangled] = Attribute(unmangled, cls is subject, INSTANCEATTR)
401
+ members[unmangled] = Attribute(
402
+ name=unmangled, directly_defined=cls is subject, value=INSTANCEATTR
403
+ )
303
404
 
304
405
  if analyzer:
305
406
  # append instance attributes (cf. self.attr1) if analyzer knows
306
407
  namespace = '.'.join(objpath)
307
- for (ns, name) in analyzer.find_attr_docs():
408
+ for ns, name in analyzer.find_attr_docs():
308
409
  if namespace == ns and name not in members:
309
- members[name] = Attribute(name, True, INSTANCEATTR)
410
+ members[name] = Attribute(
411
+ name=name, directly_defined=True, value=INSTANCEATTR
412
+ )
310
413
 
311
414
  return members
312
415
 
313
416
 
314
- def get_class_members(subject: Any, objpath: Any, attrgetter: Callable,
315
- inherit_docstrings: bool = True) -> dict[str, ObjectMember]:
417
+ def get_class_members(
418
+ subject: Any, objpath: Any, attrgetter: _AttrGetter, inherit_docstrings: bool = True
419
+ ) -> dict[str, ObjectMember]:
316
420
  """Get members and attributes of target class."""
317
421
  from sphinx.ext.autodoc import INSTANCEATTR, ObjectMember
318
422
 
@@ -323,11 +427,15 @@ def get_class_members(subject: Any, objpath: Any, attrgetter: Callable,
323
427
 
324
428
  # enum members
325
429
  if isenumclass(subject):
326
- for name, defining_class, value in _filter_enum_dict(subject, attrgetter, obj_dict):
430
+ for name, defining_class, value in _filter_enum_dict(
431
+ subject, attrgetter, obj_dict
432
+ ):
327
433
  # the order of occurrence of *name* matches the subject's MRO,
328
434
  # allowing inherited attributes to be shadowed correctly
329
435
  if unmangled := unmangle(defining_class, name):
330
- members[unmangled] = ObjectMember(unmangled, value, class_=defining_class)
436
+ members[unmangled] = ObjectMember(
437
+ unmangled, value, class_=defining_class
438
+ )
331
439
 
332
440
  # members in __slots__
333
441
  try:
@@ -336,8 +444,9 @@ def get_class_members(subject: Any, objpath: Any, attrgetter: Callable,
336
444
  from sphinx.ext.autodoc import SLOTSATTR
337
445
 
338
446
  for name, docstring in subject___slots__.items():
339
- members[name] = ObjectMember(name, SLOTSATTR, class_=subject,
340
- docstring=docstring)
447
+ members[name] = ObjectMember(
448
+ name, SLOTSATTR, class_=subject, docstring=docstring
449
+ )
341
450
  except (TypeError, ValueError):
342
451
  pass
343
452
 
@@ -379,19 +488,27 @@ def get_class_members(subject: Any, objpath: Any, attrgetter: Callable,
379
488
  else:
380
489
  docstring = None
381
490
 
382
- members[unmangled] = ObjectMember(unmangled, INSTANCEATTR, class_=cls,
383
- docstring=docstring)
491
+ members[unmangled] = ObjectMember(
492
+ unmangled, INSTANCEATTR, class_=cls, docstring=docstring
493
+ )
384
494
 
385
495
  # append or complete instance attributes (cf. self.attr1) if analyzer knows
386
496
  if analyzer:
387
497
  for (ns, name), docstring in analyzer.attr_docs.items():
388
498
  if ns == qualname and name not in members:
389
499
  # otherwise unknown instance attribute
390
- members[name] = ObjectMember(name, INSTANCEATTR, class_=cls,
391
- docstring='\n'.join(docstring))
392
- elif (ns == qualname and docstring and
393
- isinstance(members[name], ObjectMember) and
394
- not members[name].docstring):
500
+ members[name] = ObjectMember(
501
+ name,
502
+ INSTANCEATTR,
503
+ class_=cls,
504
+ docstring='\n'.join(docstring),
505
+ )
506
+ elif (
507
+ ns == qualname
508
+ and docstring
509
+ and isinstance(members[name], ObjectMember)
510
+ and not members[name].docstring
511
+ ):
395
512
  if cls != subject and not inherit_docstrings:
396
513
  # If we are in the MRO of the class and not the class itself,
397
514
  # and we do not want to inherit docstrings, then skip setting
@@ -35,8 +35,12 @@ class _MockObject:
35
35
  superclass = args[1][-1].__class__
36
36
  if superclass is cls:
37
37
  # subclassing MockObject
38
- return _make_subclass(args[0], superclass.__display_name__,
39
- superclass=superclass, attributes=args[2])
38
+ return _make_subclass(
39
+ args[0],
40
+ superclass.__display_name__,
41
+ superclass=superclass,
42
+ attributes=args[2],
43
+ )
40
44
 
41
45
  return super().__new__(cls)
42
46
 
@@ -70,12 +74,19 @@ class _MockObject:
70
74
  return self.__display_name__
71
75
 
72
76
 
73
- def _make_subclass(name: str, module: str, superclass: Any = _MockObject,
74
- attributes: Any = None, decorator_args: tuple[Any, ...] = ()) -> Any:
75
- attrs = {'__module__': module,
76
- '__display_name__': module + '.' + name,
77
- '__name__': name,
78
- '__sphinx_decorator_args__': decorator_args}
77
+ def _make_subclass(
78
+ name: str,
79
+ module: str,
80
+ superclass: Any = _MockObject,
81
+ attributes: Any = None,
82
+ decorator_args: tuple[Any, ...] = (),
83
+ ) -> Any:
84
+ attrs = {
85
+ '__module__': module,
86
+ '__display_name__': module + '.' + name,
87
+ '__name__': name,
88
+ '__sphinx_decorator_args__': decorator_args,
89
+ }
79
90
  attrs.update(attributes or {})
80
91
 
81
92
  return type(name, (superclass,), attrs)
@@ -124,8 +135,12 @@ class MockFinder(MetaPathFinder):
124
135
  self.loader = MockLoader(self)
125
136
  self.mocked_modules: list[str] = []
126
137
 
127
- def find_spec(self, fullname: str, path: Sequence[bytes | str] | None,
128
- target: ModuleType | None = None) -> ModuleSpec | None:
138
+ def find_spec(
139
+ self,
140
+ fullname: str,
141
+ path: Sequence[bytes | str] | None,
142
+ target: ModuleType | None = None,
143
+ ) -> ModuleSpec | None:
129
144
  for modname in self.modnames:
130
145
  # check if fullname is (or is a descendant of) one of our targets
131
146
  if modname == fullname or fullname.startswith(modname + '.'):