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.
- sphinx/__init__.py +2 -2
- sphinx/_cli/__init__.py +4 -4
- sphinx/application.py +2 -2
- sphinx/builders/__init__.py +2 -3
- sphinx/builders/_epub_base.py +33 -12
- sphinx/builders/changes.py +13 -5
- sphinx/builders/epub3.py +6 -2
- sphinx/builders/html/__init__.py +90 -59
- sphinx/builders/latex/__init__.py +38 -12
- sphinx/builders/latex/transforms.py +1 -1
- sphinx/builders/linkcheck.py +8 -49
- sphinx/builders/texinfo.py +12 -6
- sphinx/builders/text.py +7 -3
- sphinx/builders/xml.py +7 -3
- sphinx/cmd/quickstart.py +10 -20
- sphinx/config.py +13 -13
- sphinx/deprecation.py +8 -8
- sphinx/directives/__init__.py +14 -9
- sphinx/directives/other.py +2 -3
- sphinx/directives/patches.py +2 -2
- sphinx/domains/__init__.py +4 -2
- sphinx/domains/c/__init__.py +2 -2
- sphinx/domains/c/_ast.py +3 -2
- sphinx/domains/c/_parser.py +4 -3
- sphinx/domains/cpp/__init__.py +2 -2
- sphinx/domains/cpp/_ast.py +1 -2
- sphinx/domains/cpp/_parser.py +2 -2
- sphinx/domains/cpp/_symbol.py +2 -2
- sphinx/domains/javascript.py +1 -1
- sphinx/domains/math.py +1 -1
- sphinx/domains/python/__init__.py +1 -1
- sphinx/domains/python/_annotations.py +23 -1
- sphinx/domains/python/_object.py +0 -1
- sphinx/domains/std/__init__.py +7 -8
- sphinx/environment/__init__.py +15 -32
- sphinx/environment/adapters/indexentries.py +4 -6
- sphinx/environment/adapters/toctree.py +4 -4
- sphinx/environment/collectors/title.py +1 -1
- sphinx/environment/collectors/toctree.py +1 -1
- sphinx/events.py +3 -1
- sphinx/ext/autodoc/__init__.py +25 -67
- sphinx/ext/autodoc/directive.py +7 -5
- sphinx/ext/autodoc/importer.py +2 -1
- sphinx/ext/autodoc/preserve_defaults.py +2 -2
- sphinx/ext/autosummary/__init__.py +15 -7
- sphinx/ext/autosummary/generate.py +5 -4
- sphinx/ext/doctest.py +5 -5
- sphinx/ext/graphviz.py +1 -1
- sphinx/ext/imgmath.py +1 -1
- sphinx/ext/inheritance_diagram.py +1 -1
- sphinx/ext/intersphinx/__init__.py +25 -5
- sphinx/ext/intersphinx/_cli.py +7 -6
- sphinx/ext/intersphinx/_load.py +240 -115
- sphinx/ext/intersphinx/_resolve.py +12 -11
- sphinx/ext/intersphinx/_shared.py +102 -9
- sphinx/ext/mathjax.py +1 -1
- sphinx/ext/napoleon/docstring.py +2 -2
- sphinx/ext/todo.py +2 -2
- sphinx/ext/viewcode.py +2 -1
- sphinx/highlighting.py +3 -3
- sphinx/io.py +2 -2
- sphinx/jinja2glue.py +13 -6
- sphinx/locale/__init__.py +4 -3
- sphinx/locale/ar/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ar/LC_MESSAGES/sphinx.po +2383 -2186
- sphinx/locale/bg/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/bg/LC_MESSAGES/sphinx.po +2249 -2052
- sphinx/locale/bn/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/bn/LC_MESSAGES/sphinx.po +2412 -2215
- sphinx/locale/ca/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ca/LC_MESSAGES/sphinx.po +3029 -2832
- sphinx/locale/cak/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/cak/LC_MESSAGES/sphinx.po +2308 -2111
- sphinx/locale/cs/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/cs/LC_MESSAGES/sphinx.po +2469 -2272
- sphinx/locale/cy/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/cy/LC_MESSAGES/sphinx.po +2393 -2196
- sphinx/locale/da/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/da/LC_MESSAGES/sphinx.po +2532 -2335
- sphinx/locale/de/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/de/LC_MESSAGES/sphinx.po +2492 -2295
- sphinx/locale/de_DE/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/de_DE/LC_MESSAGES/sphinx.po +2250 -2053
- sphinx/locale/el/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/el/LC_MESSAGES/sphinx.po +2879 -2682
- sphinx/locale/en_DE/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/en_DE/LC_MESSAGES/sphinx.po +2250 -2053
- sphinx/locale/en_FR/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/en_FR/LC_MESSAGES/sphinx.po +2250 -2053
- sphinx/locale/en_GB/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/en_GB/LC_MESSAGES/sphinx.po +2989 -2792
- sphinx/locale/en_HK/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/en_HK/LC_MESSAGES/sphinx.po +2250 -2053
- sphinx/locale/eo/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/eo/LC_MESSAGES/sphinx.po +2297 -2100
- sphinx/locale/es/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/es/LC_MESSAGES/sphinx.po +3017 -2820
- sphinx/locale/es_CO/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/es_CO/LC_MESSAGES/sphinx.po +2250 -2053
- sphinx/locale/et/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/et/LC_MESSAGES/sphinx.po +2748 -2551
- sphinx/locale/eu/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/eu/LC_MESSAGES/sphinx.po +2459 -2262
- sphinx/locale/fa/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/fa/LC_MESSAGES/sphinx.po +2957 -2760
- sphinx/locale/fi/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/fi/LC_MESSAGES/sphinx.po +2321 -2124
- sphinx/locale/fr/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/fr/LC_MESSAGES/sphinx.po +2977 -2780
- sphinx/locale/fr_FR/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/fr_FR/LC_MESSAGES/sphinx.po +2250 -2053
- sphinx/locale/gl/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/gl/LC_MESSAGES/sphinx.po +2992 -2795
- sphinx/locale/he/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/he/LC_MESSAGES/sphinx.po +2375 -2178
- sphinx/locale/hi/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/hi/LC_MESSAGES/sphinx.po +2937 -2740
- sphinx/locale/hi_IN/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/hi_IN/LC_MESSAGES/sphinx.po +2250 -2053
- sphinx/locale/hr/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/hr/LC_MESSAGES/sphinx.po +2532 -2335
- sphinx/locale/hu/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/hu/LC_MESSAGES/sphinx.po +2505 -2308
- sphinx/locale/id/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/id/LC_MESSAGES/sphinx.po +2925 -2728
- sphinx/locale/is/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/is/LC_MESSAGES/sphinx.po +2307 -2110
- sphinx/locale/it/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/it/LC_MESSAGES/sphinx.po +2514 -2317
- sphinx/locale/ja/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ja/LC_MESSAGES/sphinx.po +2970 -2773
- sphinx/locale/ka/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ka/LC_MESSAGES/sphinx.po +2868 -2671
- sphinx/locale/ko/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ko/LC_MESSAGES/sphinx.po +3016 -2819
- sphinx/locale/lt/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/lt/LC_MESSAGES/sphinx.po +2476 -2279
- sphinx/locale/lv/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/lv/LC_MESSAGES/sphinx.po +2477 -2280
- sphinx/locale/mk/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/mk/LC_MESSAGES/sphinx.po +2292 -2095
- sphinx/locale/nb_NO/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/nb_NO/LC_MESSAGES/sphinx.po +2479 -2282
- sphinx/locale/ne/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ne/LC_MESSAGES/sphinx.po +2481 -2284
- sphinx/locale/nl/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/nl/LC_MESSAGES/sphinx.po +2557 -2360
- sphinx/locale/pl/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/pl/LC_MESSAGES/sphinx.po +2696 -2499
- sphinx/locale/pt/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/pt/LC_MESSAGES/sphinx.po +2250 -2053
- sphinx/locale/pt_BR/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/pt_BR/LC_MESSAGES/sphinx.po +2979 -2782
- sphinx/locale/pt_PT/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/pt_PT/LC_MESSAGES/sphinx.po +2469 -2272
- sphinx/locale/ro/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ro/LC_MESSAGES/sphinx.po +2473 -2276
- sphinx/locale/ru/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ru/LC_MESSAGES/sphinx.po +2746 -2549
- sphinx/locale/si/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/si/LC_MESSAGES/sphinx.po +2331 -2134
- sphinx/locale/sk/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sk/LC_MESSAGES/sphinx.po +2966 -2769
- sphinx/locale/sl/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sl/LC_MESSAGES/sphinx.po +2404 -2207
- sphinx/locale/sphinx.pot +2262 -2065
- sphinx/locale/sq/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sq/LC_MESSAGES/sphinx.po +2972 -2775
- sphinx/locale/sr/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sr/LC_MESSAGES/sphinx.po +2440 -2243
- sphinx/locale/sv/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/sv/LC_MESSAGES/sphinx.po +2483 -2286
- sphinx/locale/ta/LC_MESSAGES/sphinx.js +54 -54
- sphinx/locale/ta/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ta/LC_MESSAGES/sphinx.po +1578 -1843
- sphinx/locale/te/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/te/LC_MESSAGES/sphinx.po +2250 -2053
- sphinx/locale/tr/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/tr/LC_MESSAGES/sphinx.po +2892 -2695
- sphinx/locale/uk_UA/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/uk_UA/LC_MESSAGES/sphinx.po +2400 -2203
- sphinx/locale/ur/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/ur/LC_MESSAGES/sphinx.po +2250 -2053
- sphinx/locale/vi/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/vi/LC_MESSAGES/sphinx.po +2422 -2225
- sphinx/locale/yue/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/yue/LC_MESSAGES/sphinx.po +2250 -2053
- sphinx/locale/zh_CN/LC_MESSAGES/sphinx.po +496 -704
- sphinx/locale/zh_HK/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/zh_HK/LC_MESSAGES/sphinx.po +2250 -2053
- sphinx/locale/zh_TW/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/zh_TW/LC_MESSAGES/sphinx.po +3028 -2831
- sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.mo +0 -0
- sphinx/locale/zh_TW.Big5/LC_MESSAGES/sphinx.po +2250 -2053
- sphinx/project.py +25 -20
- sphinx/pycode/ast.py +2 -2
- sphinx/pycode/parser.py +2 -2
- sphinx/pygments_styles.py +3 -3
- sphinx/registry.py +3 -8
- sphinx/search/__init__.py +1 -1
- sphinx/testing/path.py +2 -1
- sphinx/testing/util.py +1 -1
- sphinx/texinputs/Makefile.jinja +2 -1
- sphinx/texinputs_win/Makefile.jinja +2 -1
- sphinx/theming.py +3 -12
- sphinx/transforms/__init__.py +5 -5
- sphinx/transforms/references.py +1 -1
- sphinx/util/__init__.py +11 -35
- sphinx/util/_pathlib.py +31 -19
- sphinx/util/_timestamps.py +12 -0
- sphinx/util/cfamily.py +5 -5
- sphinx/util/console.py +4 -3
- sphinx/util/display.py +3 -3
- sphinx/util/docfields.py +1 -1
- sphinx/util/docutils.py +44 -10
- sphinx/util/fileutil.py +41 -9
- sphinx/util/i18n.py +9 -4
- sphinx/util/images.py +3 -2
- sphinx/util/inspect.py +29 -44
- sphinx/util/inventory.py +2 -2
- sphinx/util/matching.py +2 -2
- sphinx/util/math.py +1 -1
- sphinx/util/nodes.py +8 -8
- sphinx/util/osutil.py +52 -26
- sphinx/util/parallel.py +2 -2
- sphinx/util/requests.py +1 -1
- sphinx/util/template.py +3 -3
- sphinx/util/typing.py +67 -70
- sphinx/writers/html.py +1 -1
- sphinx/writers/html5.py +1 -1
- sphinx/writers/latex.py +4 -4
- sphinx/writers/manpage.py +2 -2
- sphinx/writers/texinfo.py +5 -5
- sphinx/writers/text.py +4 -4
- sphinx/writers/xml.py +2 -2
- {sphinx-7.4.6.dist-info → sphinx-8.0.0.dist-info}/METADATA +11 -10
- {sphinx-7.4.6.dist-info → sphinx-8.0.0.dist-info}/RECORD +240 -241
- sphinx/templates/quickstart/Makefile.jinja +0 -98
- sphinx/templates/quickstart/make.bat.jinja +0 -110
- {sphinx-7.4.6.dist-info → sphinx-8.0.0.dist-info}/LICENSE.rst +0 -0
- {sphinx-7.4.6.dist-info → sphinx-8.0.0.dist-info}/WHEEL +0 -0
- {sphinx-7.4.6.dist-info → sphinx-8.0.0.dist-info}/entry_points.txt +0 -0
sphinx/util/docfields.py
CHANGED
|
@@ -356,7 +356,7 @@ class DocFieldTransformer:
|
|
|
356
356
|
if is_typefield:
|
|
357
357
|
# filter out only inline nodes; others will result in invalid
|
|
358
358
|
# markup being written out
|
|
359
|
-
content = [n for n in content if isinstance(n,
|
|
359
|
+
content = [n for n in content if isinstance(n, nodes.Inline | nodes.Text)]
|
|
360
360
|
if content:
|
|
361
361
|
types.setdefault(typename, {})[fieldarg] = content
|
|
362
362
|
continue
|
sphinx/util/docutils.py
CHANGED
|
@@ -8,7 +8,7 @@ from collections.abc import Sequence # NoQA: TCH003
|
|
|
8
8
|
from contextlib import contextmanager
|
|
9
9
|
from copy import copy
|
|
10
10
|
from os import path
|
|
11
|
-
from typing import IO, TYPE_CHECKING, Any,
|
|
11
|
+
from typing import IO, TYPE_CHECKING, Any, cast
|
|
12
12
|
|
|
13
13
|
import docutils
|
|
14
14
|
from docutils import nodes
|
|
@@ -27,7 +27,7 @@ logger = logging.getLogger(__name__)
|
|
|
27
27
|
report_re = re.compile('^(.+?:(?:\\d+)?): \\((DEBUG|INFO|WARNING|ERROR|SEVERE)/(\\d+)?\\) ')
|
|
28
28
|
|
|
29
29
|
if TYPE_CHECKING:
|
|
30
|
-
from collections.abc import Iterator
|
|
30
|
+
from collections.abc import Callable, Iterator # NoQA: TCH003
|
|
31
31
|
from types import ModuleType
|
|
32
32
|
|
|
33
33
|
from docutils.frontend import Values
|
|
@@ -366,30 +366,47 @@ class SphinxDirective(Directive):
|
|
|
366
366
|
|
|
367
367
|
This class provides helper methods for Sphinx directives.
|
|
368
368
|
|
|
369
|
+
.. versionadded:: 1.8
|
|
370
|
+
|
|
369
371
|
.. note:: The subclasses of this class might not work with docutils.
|
|
370
372
|
This class is strongly coupled with Sphinx.
|
|
371
373
|
"""
|
|
372
374
|
|
|
373
375
|
@property
|
|
374
376
|
def env(self) -> BuildEnvironment:
|
|
375
|
-
"""Reference to the :class:`.BuildEnvironment` object.
|
|
377
|
+
"""Reference to the :class:`.BuildEnvironment` object.
|
|
378
|
+
|
|
379
|
+
.. versionadded:: 1.8
|
|
380
|
+
"""
|
|
376
381
|
return self.state.document.settings.env
|
|
377
382
|
|
|
378
383
|
@property
|
|
379
384
|
def config(self) -> Config:
|
|
380
|
-
"""Reference to the :class:`.Config` object.
|
|
385
|
+
"""Reference to the :class:`.Config` object.
|
|
386
|
+
|
|
387
|
+
.. versionadded:: 1.8
|
|
388
|
+
"""
|
|
381
389
|
return self.env.config
|
|
382
390
|
|
|
383
391
|
def get_source_info(self) -> tuple[str, int]:
|
|
384
|
-
"""Get source and line number.
|
|
392
|
+
"""Get source and line number.
|
|
393
|
+
|
|
394
|
+
.. versionadded:: 3.0
|
|
395
|
+
"""
|
|
385
396
|
return self.state_machine.get_source_and_line(self.lineno)
|
|
386
397
|
|
|
387
398
|
def set_source_info(self, node: Node) -> None:
|
|
388
|
-
"""Set source and line number to the node.
|
|
399
|
+
"""Set source and line number to the node.
|
|
400
|
+
|
|
401
|
+
.. versionadded:: 2.1
|
|
402
|
+
"""
|
|
389
403
|
node.source, node.line = self.get_source_info()
|
|
390
404
|
|
|
391
405
|
def get_location(self) -> str:
|
|
392
|
-
"""Get current location info for logging.
|
|
406
|
+
"""Get current location info for logging.
|
|
407
|
+
|
|
408
|
+
.. versionadded:: 4.2
|
|
409
|
+
"""
|
|
393
410
|
source, line = self.get_source_info()
|
|
394
411
|
if source and line:
|
|
395
412
|
return f'{source}:{line}'
|
|
@@ -473,6 +490,8 @@ class SphinxRole:
|
|
|
473
490
|
|
|
474
491
|
This class provides helper methods for Sphinx roles.
|
|
475
492
|
|
|
493
|
+
.. versionadded:: 2.0
|
|
494
|
+
|
|
476
495
|
.. note:: The subclasses of this class might not work with docutils.
|
|
477
496
|
This class is strongly coupled with Sphinx.
|
|
478
497
|
"""
|
|
@@ -517,24 +536,35 @@ class SphinxRole:
|
|
|
517
536
|
|
|
518
537
|
@property
|
|
519
538
|
def env(self) -> BuildEnvironment:
|
|
520
|
-
"""Reference to the :class:`.BuildEnvironment` object.
|
|
539
|
+
"""Reference to the :class:`.BuildEnvironment` object.
|
|
540
|
+
|
|
541
|
+
.. versionadded:: 2.0
|
|
542
|
+
"""
|
|
521
543
|
return self.inliner.document.settings.env
|
|
522
544
|
|
|
523
545
|
@property
|
|
524
546
|
def config(self) -> Config:
|
|
525
|
-
"""Reference to the :class:`.Config` object.
|
|
547
|
+
"""Reference to the :class:`.Config` object.
|
|
548
|
+
|
|
549
|
+
.. versionadded:: 2.0
|
|
550
|
+
"""
|
|
526
551
|
return self.env.config
|
|
527
552
|
|
|
528
553
|
def get_source_info(self, lineno: int | None = None) -> tuple[str, int]:
|
|
554
|
+
# .. versionadded:: 3.0
|
|
529
555
|
if lineno is None:
|
|
530
556
|
lineno = self.lineno
|
|
531
557
|
return self.inliner.reporter.get_source_and_line(lineno) # type: ignore[attr-defined]
|
|
532
558
|
|
|
533
559
|
def set_source_info(self, node: Node, lineno: int | None = None) -> None:
|
|
560
|
+
# .. versionadded:: 2.0
|
|
534
561
|
node.source, node.line = self.get_source_info(lineno)
|
|
535
562
|
|
|
536
563
|
def get_location(self) -> str:
|
|
537
|
-
"""Get current location info for logging.
|
|
564
|
+
"""Get current location info for logging.
|
|
565
|
+
|
|
566
|
+
.. versionadded:: 4.2
|
|
567
|
+
"""
|
|
538
568
|
source, line = self.get_source_info()
|
|
539
569
|
if source and line:
|
|
540
570
|
return f'{source}:{line}'
|
|
@@ -551,6 +581,8 @@ class ReferenceRole(SphinxRole):
|
|
|
551
581
|
The reference roles can accept ``link title <target>`` style as a text for
|
|
552
582
|
the role. The parsed result; link title and target will be stored to
|
|
553
583
|
``self.title`` and ``self.target``.
|
|
584
|
+
|
|
585
|
+
.. versionadded:: 2.0
|
|
554
586
|
"""
|
|
555
587
|
|
|
556
588
|
has_explicit_title: bool #: A boolean indicates the role has explicit title or not.
|
|
@@ -591,6 +623,8 @@ class SphinxTranslator(nodes.NodeVisitor):
|
|
|
591
623
|
|
|
592
624
|
It also provides helper methods for Sphinx translators.
|
|
593
625
|
|
|
626
|
+
.. versionadded:: 2.0
|
|
627
|
+
|
|
594
628
|
.. note:: The subclasses of this class might not work with docutils.
|
|
595
629
|
This class is strongly coupled with Sphinx.
|
|
596
630
|
"""
|
sphinx/util/fileutil.py
CHANGED
|
@@ -4,16 +4,22 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
6
|
import posixpath
|
|
7
|
-
from typing import TYPE_CHECKING, Any
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
8
|
|
|
9
9
|
from docutils.utils import relative_path
|
|
10
10
|
|
|
11
|
+
from sphinx.locale import __
|
|
12
|
+
from sphinx.util import logging
|
|
11
13
|
from sphinx.util.osutil import copyfile, ensuredir
|
|
12
14
|
|
|
13
15
|
if TYPE_CHECKING:
|
|
16
|
+
from collections.abc import Callable
|
|
17
|
+
|
|
14
18
|
from sphinx.util.template import BaseRenderer
|
|
15
19
|
from sphinx.util.typing import PathMatcher
|
|
16
20
|
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
17
23
|
|
|
18
24
|
def _template_basename(filename: str | os.PathLike[str]) -> str | None:
|
|
19
25
|
"""Given an input filename:
|
|
@@ -30,7 +36,9 @@ def _template_basename(filename: str | os.PathLike[str]) -> str | None:
|
|
|
30
36
|
|
|
31
37
|
def copy_asset_file(source: str | os.PathLike[str], destination: str | os.PathLike[str],
|
|
32
38
|
context: dict[str, Any] | None = None,
|
|
33
|
-
renderer: BaseRenderer | None = None
|
|
39
|
+
renderer: BaseRenderer | None = None,
|
|
40
|
+
*,
|
|
41
|
+
force: bool = False) -> None:
|
|
34
42
|
"""Copy an asset file to destination.
|
|
35
43
|
|
|
36
44
|
On copying, it expands the template variables if context argument is given and
|
|
@@ -40,6 +48,7 @@ def copy_asset_file(source: str | os.PathLike[str], destination: str | os.PathLi
|
|
|
40
48
|
:param destination: The path to destination file or directory
|
|
41
49
|
:param context: The template variables. If not given, template files are simply copied
|
|
42
50
|
:param renderer: The template engine. If not given, SphinxRenderer is used by default
|
|
51
|
+
:param bool force: Overwrite the destination file even if it exists.
|
|
43
52
|
"""
|
|
44
53
|
if not os.path.exists(source):
|
|
45
54
|
return
|
|
@@ -56,28 +65,46 @@ def copy_asset_file(source: str | os.PathLike[str], destination: str | os.PathLi
|
|
|
56
65
|
renderer = SphinxRenderer()
|
|
57
66
|
|
|
58
67
|
with open(source, encoding='utf-8') as fsrc:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
template_content = fsrc.read()
|
|
69
|
+
rendered_template = renderer.render_string(template_content, context)
|
|
70
|
+
|
|
71
|
+
if (
|
|
72
|
+
not force
|
|
73
|
+
and os.path.exists(destination)
|
|
74
|
+
and template_content != rendered_template
|
|
75
|
+
):
|
|
76
|
+
msg = __('Aborted attempted copy from rendered template %s to %s '
|
|
77
|
+
'(the destination path has existing data).')
|
|
78
|
+
logger.warning(msg, os.fsdecode(source), os.fsdecode(destination),
|
|
79
|
+
type='misc', subtype='copy_overwrite')
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
destination = _template_basename(destination) or destination
|
|
83
|
+
with open(destination, 'w', encoding='utf-8') as fdst:
|
|
84
|
+
fdst.write(rendered_template)
|
|
62
85
|
else:
|
|
63
|
-
copyfile(source, destination)
|
|
86
|
+
copyfile(source, destination, force=force)
|
|
64
87
|
|
|
65
88
|
|
|
66
89
|
def copy_asset(source: str | os.PathLike[str], destination: str | os.PathLike[str],
|
|
67
90
|
excluded: PathMatcher = lambda path: False,
|
|
68
91
|
context: dict[str, Any] | None = None, renderer: BaseRenderer | None = None,
|
|
69
|
-
onerror: Callable[[str, Exception], None] | None = None
|
|
92
|
+
onerror: Callable[[str, Exception], None] | None = None,
|
|
93
|
+
*, force: bool = False) -> None:
|
|
70
94
|
"""Copy asset files to destination recursively.
|
|
71
95
|
|
|
72
96
|
On copying, it expands the template variables if context argument is given and
|
|
73
97
|
the asset is a template file.
|
|
74
98
|
|
|
99
|
+
Use ``copy_asset_file`` instead to copy a single file.
|
|
100
|
+
|
|
75
101
|
:param source: The path to source file or directory
|
|
76
102
|
:param destination: The path to destination directory
|
|
77
103
|
:param excluded: The matcher to determine the given path should be copied or not
|
|
78
104
|
:param context: The template variables. If not given, template files are simply copied
|
|
79
105
|
:param renderer: The template engine. If not given, SphinxRenderer is used by default
|
|
80
106
|
:param onerror: The error handler.
|
|
107
|
+
:param bool force: Overwrite the destination file even if it exists.
|
|
81
108
|
"""
|
|
82
109
|
if not os.path.exists(source):
|
|
83
110
|
return
|
|
@@ -88,7 +115,10 @@ def copy_asset(source: str | os.PathLike[str], destination: str | os.PathLike[st
|
|
|
88
115
|
|
|
89
116
|
ensuredir(destination)
|
|
90
117
|
if os.path.isfile(source):
|
|
91
|
-
copy_asset_file(source, destination,
|
|
118
|
+
copy_asset_file(source, destination,
|
|
119
|
+
context=context,
|
|
120
|
+
renderer=renderer,
|
|
121
|
+
force=force)
|
|
92
122
|
return
|
|
93
123
|
|
|
94
124
|
for root, dirs, files in os.walk(source, followlinks=True):
|
|
@@ -104,7 +134,9 @@ def copy_asset(source: str | os.PathLike[str], destination: str | os.PathLike[st
|
|
|
104
134
|
try:
|
|
105
135
|
copy_asset_file(posixpath.join(root, filename),
|
|
106
136
|
posixpath.join(destination, reldir),
|
|
107
|
-
context,
|
|
137
|
+
context=context,
|
|
138
|
+
renderer=renderer,
|
|
139
|
+
force=force)
|
|
108
140
|
except Exception as exc:
|
|
109
141
|
if onerror:
|
|
110
142
|
onerror(posixpath.join(root, filename), exc)
|
sphinx/util/i18n.py
CHANGED
|
@@ -15,12 +15,17 @@ from babel.messages.pofile import read_po
|
|
|
15
15
|
from sphinx.errors import SphinxError
|
|
16
16
|
from sphinx.locale import __
|
|
17
17
|
from sphinx.util import logging
|
|
18
|
-
from sphinx.util.osutil import
|
|
18
|
+
from sphinx.util.osutil import (
|
|
19
|
+
SEP,
|
|
20
|
+
_last_modified_time,
|
|
21
|
+
canon_path,
|
|
22
|
+
relpath,
|
|
23
|
+
)
|
|
19
24
|
|
|
20
25
|
if TYPE_CHECKING:
|
|
21
26
|
import datetime as dt
|
|
22
27
|
from collections.abc import Iterator
|
|
23
|
-
from typing import Protocol,
|
|
28
|
+
from typing import Protocol, TypeAlias
|
|
24
29
|
|
|
25
30
|
from babel.core import Locale
|
|
26
31
|
|
|
@@ -52,7 +57,7 @@ if TYPE_CHECKING:
|
|
|
52
57
|
locale: str | Locale | None = ...,
|
|
53
58
|
) -> str: ...
|
|
54
59
|
|
|
55
|
-
Formatter =
|
|
60
|
+
Formatter: TypeAlias = DateFormatter | TimeFormatter | DatetimeFormatter
|
|
56
61
|
|
|
57
62
|
logger = logging.getLogger(__name__)
|
|
58
63
|
|
|
@@ -84,7 +89,7 @@ class CatalogInfo(LocaleFileInfoBase):
|
|
|
84
89
|
def is_outdated(self) -> bool:
|
|
85
90
|
return (
|
|
86
91
|
not path.exists(self.mo_path) or
|
|
87
|
-
|
|
92
|
+
_last_modified_time(self.mo_path) < _last_modified_time(self.po_path))
|
|
88
93
|
|
|
89
94
|
def write_mo(self, locale: str, use_fuzzy: bool = False) -> None:
|
|
90
95
|
with open(self.po_path, encoding=self.charset) as file_po:
|
sphinx/util/images.py
CHANGED
|
@@ -13,8 +13,9 @@ if TYPE_CHECKING:
|
|
|
13
13
|
|
|
14
14
|
try:
|
|
15
15
|
from PIL import Image
|
|
16
|
+
PILLOW_AVAILABLE = True
|
|
16
17
|
except ImportError:
|
|
17
|
-
|
|
18
|
+
PILLOW_AVAILABLE = False
|
|
18
19
|
|
|
19
20
|
mime_suffixes = {
|
|
20
21
|
'.gif': 'image/gif',
|
|
@@ -43,7 +44,7 @@ def get_image_size(filename: str) -> tuple[int, int] | None:
|
|
|
43
44
|
elif isinstance(size[0], float) or isinstance(size[1], float):
|
|
44
45
|
size = (int(size[0]), int(size[1]))
|
|
45
46
|
|
|
46
|
-
if size is None and
|
|
47
|
+
if size is None and PILLOW_AVAILABLE: # fallback to Pillow
|
|
47
48
|
with Image.open(filename) as im:
|
|
48
49
|
size = im.size
|
|
49
50
|
|
sphinx/util/inspect.py
CHANGED
|
@@ -17,19 +17,19 @@ from importlib import import_module
|
|
|
17
17
|
from inspect import Parameter, Signature
|
|
18
18
|
from io import StringIO
|
|
19
19
|
from types import ClassMethodDescriptorType, MethodDescriptorType, WrapperDescriptorType
|
|
20
|
-
from typing import TYPE_CHECKING, Any
|
|
20
|
+
from typing import TYPE_CHECKING, Any, ForwardRef
|
|
21
21
|
|
|
22
22
|
from sphinx.pycode.ast import unparse as ast_unparse
|
|
23
23
|
from sphinx.util import logging
|
|
24
|
-
from sphinx.util.typing import
|
|
24
|
+
from sphinx.util.typing import stringify_annotation
|
|
25
25
|
|
|
26
26
|
if TYPE_CHECKING:
|
|
27
27
|
from collections.abc import Callable, Sequence
|
|
28
28
|
from inspect import _ParameterKind
|
|
29
29
|
from types import MethodType, ModuleType
|
|
30
|
-
from typing import Final, Protocol,
|
|
30
|
+
from typing import Final, Protocol, TypeAlias
|
|
31
31
|
|
|
32
|
-
from typing_extensions import
|
|
32
|
+
from typing_extensions import TypeIs
|
|
33
33
|
|
|
34
34
|
class _SupportsGet(Protocol):
|
|
35
35
|
def __get__(self, __instance: Any, __owner: type | None = ...) -> Any: ... # NoQA: E704
|
|
@@ -42,21 +42,21 @@ if TYPE_CHECKING:
|
|
|
42
42
|
# instance is contravariant but we do not need that precision
|
|
43
43
|
def __delete__(self, __instance: Any) -> None: ... # NoQA: E704
|
|
44
44
|
|
|
45
|
-
_RoutineType: TypeAlias =
|
|
46
|
-
types.FunctionType
|
|
47
|
-
types.LambdaType
|
|
48
|
-
types.MethodType
|
|
49
|
-
types.BuiltinFunctionType
|
|
50
|
-
types.BuiltinMethodType
|
|
51
|
-
types.WrapperDescriptorType
|
|
52
|
-
types.MethodDescriptorType
|
|
53
|
-
types.ClassMethodDescriptorType
|
|
54
|
-
|
|
55
|
-
_SignatureType: TypeAlias =
|
|
56
|
-
Callable[..., Any]
|
|
57
|
-
staticmethod
|
|
58
|
-
classmethod
|
|
59
|
-
|
|
45
|
+
_RoutineType: TypeAlias = (
|
|
46
|
+
types.FunctionType
|
|
47
|
+
| types.LambdaType
|
|
48
|
+
| types.MethodType
|
|
49
|
+
| types.BuiltinFunctionType
|
|
50
|
+
| types.BuiltinMethodType
|
|
51
|
+
| types.WrapperDescriptorType
|
|
52
|
+
| types.MethodDescriptorType
|
|
53
|
+
| types.ClassMethodDescriptorType
|
|
54
|
+
)
|
|
55
|
+
_SignatureType: TypeAlias = (
|
|
56
|
+
Callable[..., Any]
|
|
57
|
+
| staticmethod
|
|
58
|
+
| classmethod
|
|
59
|
+
)
|
|
60
60
|
|
|
61
61
|
logger = logging.getLogger(__name__)
|
|
62
62
|
|
|
@@ -128,20 +128,14 @@ def getall(obj: Any) -> Sequence[str] | None:
|
|
|
128
128
|
__all__ = safe_getattr(obj, '__all__', None)
|
|
129
129
|
if __all__ is None:
|
|
130
130
|
return None
|
|
131
|
-
if isinstance(__all__,
|
|
131
|
+
if isinstance(__all__, list | tuple) and all(isinstance(e, str) for e in __all__):
|
|
132
132
|
return __all__
|
|
133
133
|
raise ValueError(__all__)
|
|
134
134
|
|
|
135
135
|
|
|
136
136
|
def getannotations(obj: Any) -> Mapping[str, Any]:
|
|
137
137
|
"""Safely get the ``__annotations__`` attribute of an object."""
|
|
138
|
-
|
|
139
|
-
__annotations__ = safe_getattr(obj, '__annotations__', None)
|
|
140
|
-
else:
|
|
141
|
-
# Workaround for bugfix not available until python 3.10 as recommended by docs
|
|
142
|
-
# https://docs.python.org/3.10/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older
|
|
143
|
-
__dict__ = safe_getattr(obj, '__dict__', {})
|
|
144
|
-
__annotations__ = __dict__.get('__annotations__', None)
|
|
138
|
+
__annotations__ = safe_getattr(obj, '__annotations__', None)
|
|
145
139
|
if isinstance(__annotations__, Mapping):
|
|
146
140
|
return __annotations__
|
|
147
141
|
return {}
|
|
@@ -198,21 +192,12 @@ def getslots(obj: Any) -> dict[str, Any] | dict[str, None] | None:
|
|
|
198
192
|
return __slots__
|
|
199
193
|
elif isinstance(__slots__, str):
|
|
200
194
|
return {__slots__: None}
|
|
201
|
-
elif isinstance(__slots__,
|
|
195
|
+
elif isinstance(__slots__, list | tuple):
|
|
202
196
|
return dict.fromkeys(__slots__)
|
|
203
197
|
else:
|
|
204
198
|
raise ValueError
|
|
205
199
|
|
|
206
200
|
|
|
207
|
-
def isNewType(obj: Any) -> bool:
|
|
208
|
-
"""Check the if object is a kind of :class:`~typing.NewType`."""
|
|
209
|
-
if sys.version_info[:2] >= (3, 10):
|
|
210
|
-
return isinstance(obj, typing.NewType)
|
|
211
|
-
__module__ = safe_getattr(obj, '__module__', None)
|
|
212
|
-
__qualname__ = safe_getattr(obj, '__qualname__', None)
|
|
213
|
-
return __module__ == 'typing' and __qualname__ == 'NewType.<locals>.new_type'
|
|
214
|
-
|
|
215
|
-
|
|
216
201
|
def isenumclass(x: Any) -> TypeIs[type[enum.Enum]]:
|
|
217
202
|
"""Check if the object is an :class:`enumeration class <enum.Enum>`."""
|
|
218
203
|
return isclass(x) and issubclass(x, enum.Enum)
|
|
@@ -237,7 +222,7 @@ def unpartial(obj: Any) -> Any:
|
|
|
237
222
|
|
|
238
223
|
def ispartial(obj: Any) -> TypeIs[partial | partialmethod]:
|
|
239
224
|
"""Check if the object is a partial function or method."""
|
|
240
|
-
return isinstance(obj,
|
|
225
|
+
return isinstance(obj, partial | partialmethod)
|
|
241
226
|
|
|
242
227
|
|
|
243
228
|
def isclassmethod(
|
|
@@ -397,12 +382,12 @@ def _is_wrapped_coroutine(obj: Any) -> bool:
|
|
|
397
382
|
|
|
398
383
|
def isproperty(obj: Any) -> TypeIs[property | cached_property]:
|
|
399
384
|
"""Check if the object is property (possibly cached)."""
|
|
400
|
-
return isinstance(obj,
|
|
385
|
+
return isinstance(obj, property | cached_property)
|
|
401
386
|
|
|
402
387
|
|
|
403
388
|
def isgenericalias(obj: Any) -> TypeIs[types.GenericAlias]:
|
|
404
389
|
"""Check if the object is a generic alias."""
|
|
405
|
-
return isinstance(obj,
|
|
390
|
+
return isinstance(obj, types.GenericAlias | typing._BaseGenericAlias) # type: ignore[attr-defined]
|
|
406
391
|
|
|
407
392
|
|
|
408
393
|
def safe_getattr(obj: Any, name: str, *defargs: Any) -> Any:
|
|
@@ -652,7 +637,7 @@ def signature(
|
|
|
652
637
|
try:
|
|
653
638
|
# Resolve annotations using ``get_type_hints()`` and type_aliases.
|
|
654
639
|
localns = TypeAliasNamespace(type_aliases)
|
|
655
|
-
annotations = typing.get_type_hints(subject, None, localns)
|
|
640
|
+
annotations = typing.get_type_hints(subject, None, localns, include_extras=True)
|
|
656
641
|
for i, param in enumerate(parameters):
|
|
657
642
|
if param.name in annotations:
|
|
658
643
|
annotation = annotations[param.name]
|
|
@@ -852,11 +837,11 @@ def signature_from_ast(node: ast.FunctionDef, code: str = '') -> Signature:
|
|
|
852
837
|
params: list[Parameter] = []
|
|
853
838
|
|
|
854
839
|
# positional-only arguments (introduced in Python 3.8)
|
|
855
|
-
for arg, defexpr in zip(args.posonlyargs, defaults):
|
|
840
|
+
for arg, defexpr in zip(args.posonlyargs, defaults, strict=False):
|
|
856
841
|
params.append(_define(Parameter.POSITIONAL_ONLY, arg, code, defexpr=defexpr))
|
|
857
842
|
|
|
858
843
|
# normal arguments
|
|
859
|
-
for arg, defexpr in zip(args.args, defaults[pos_only_offset:]):
|
|
844
|
+
for arg, defexpr in zip(args.args, defaults[pos_only_offset:], strict=False):
|
|
860
845
|
params.append(_define(Parameter.POSITIONAL_OR_KEYWORD, arg, code, defexpr=defexpr))
|
|
861
846
|
|
|
862
847
|
# variadic positional argument (no possible default expression)
|
|
@@ -864,7 +849,7 @@ def signature_from_ast(node: ast.FunctionDef, code: str = '') -> Signature:
|
|
|
864
849
|
params.append(_define(Parameter.VAR_POSITIONAL, args.vararg, code, defexpr=None))
|
|
865
850
|
|
|
866
851
|
# keyword-only arguments
|
|
867
|
-
for arg, defexpr in zip(args.kwonlyargs, args.kw_defaults):
|
|
852
|
+
for arg, defexpr in zip(args.kwonlyargs, args.kw_defaults, strict=False):
|
|
868
853
|
params.append(_define(Parameter.KEYWORD_ONLY, arg, code, defexpr=defexpr))
|
|
869
854
|
|
|
870
855
|
# variadic keyword argument (no possible default expression)
|
sphinx/util/inventory.py
CHANGED
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
import os
|
|
5
5
|
import re
|
|
6
6
|
import zlib
|
|
7
|
-
from typing import IO, TYPE_CHECKING
|
|
7
|
+
from typing import IO, TYPE_CHECKING
|
|
8
8
|
|
|
9
9
|
from sphinx.locale import __
|
|
10
10
|
from sphinx.util import logging
|
|
@@ -13,7 +13,7 @@ BUFSIZE = 16 * 1024
|
|
|
13
13
|
logger = logging.getLogger(__name__)
|
|
14
14
|
|
|
15
15
|
if TYPE_CHECKING:
|
|
16
|
-
from collections.abc import Iterator
|
|
16
|
+
from collections.abc import Callable, Iterator
|
|
17
17
|
|
|
18
18
|
from sphinx.builders import Builder
|
|
19
19
|
from sphinx.environment import BuildEnvironment
|
sphinx/util/matching.py
CHANGED
|
@@ -4,12 +4,12 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import os.path
|
|
6
6
|
import re
|
|
7
|
-
from typing import TYPE_CHECKING
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
8
|
|
|
9
9
|
from sphinx.util.osutil import canon_path, path_stabilize
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
|
-
from collections.abc import Iterable, Iterator
|
|
12
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def _translate_pattern(pat: str) -> str:
|
sphinx/util/math.py
CHANGED
|
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
8
|
from docutils import nodes
|
|
9
9
|
|
|
10
|
-
from sphinx.
|
|
10
|
+
from sphinx.writers.html5 import HTML5Translator
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def get_node_equation_number(writer: HTML5Translator, node: nodes.math_block) -> str:
|
sphinx/util/nodes.py
CHANGED
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import contextlib
|
|
6
6
|
import re
|
|
7
7
|
import unicodedata
|
|
8
|
-
from typing import TYPE_CHECKING, Any,
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast
|
|
9
9
|
|
|
10
10
|
from docutils import nodes
|
|
11
11
|
from docutils.nodes import Node
|
|
@@ -16,7 +16,7 @@ from sphinx.util import logging
|
|
|
16
16
|
from sphinx.util.parsing import _fresh_title_style_context
|
|
17
17
|
|
|
18
18
|
if TYPE_CHECKING:
|
|
19
|
-
from collections.abc import Iterable, Iterator
|
|
19
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
20
20
|
|
|
21
21
|
from docutils.nodes import Element
|
|
22
22
|
from docutils.parsers.rst import Directive
|
|
@@ -178,12 +178,12 @@ def apply_source_workaround(node: Element) -> None:
|
|
|
178
178
|
return
|
|
179
179
|
|
|
180
180
|
# workaround: some docutils nodes doesn't have source, line.
|
|
181
|
-
if
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
))
|
|
181
|
+
if isinstance(node, (
|
|
182
|
+
nodes.rubric # #1305 rubric directive
|
|
183
|
+
| nodes.line # #1477 line node
|
|
184
|
+
| nodes.image # #3093 image directive in substitution
|
|
185
|
+
| nodes.field_name # #3335 field list syntax
|
|
186
|
+
)):
|
|
187
187
|
logger.debug('[i18n] PATCH: %r to have source and line: %s',
|
|
188
188
|
get_full_module_name(node), repr_domxml(node))
|
|
189
189
|
try:
|