mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
`_StrPath
is dead; long live
_StrPath
` (#12690)
This commit is contained in:
parent
a80a11da0e
commit
b511537597
@ -68,10 +68,6 @@ Incompatible changes
|
|||||||
* #12096: Do not overwrite user-supplied files when copying assets
|
* #12096: Do not overwrite user-supplied files when copying assets
|
||||||
unless forced with ``force=True``.
|
unless forced with ``force=True``.
|
||||||
Patch by Adam Turner.
|
Patch by Adam Turner.
|
||||||
* #12650: Remove support for string methods on :py:class:`~pathlib.Path` objects.
|
|
||||||
Use :py:func:`os.fspath` to convert :py:class:`~pathlib.Path` objects to strings,
|
|
||||||
or :py:class:`~pathlib.Path`'s methods to work with path objects.
|
|
||||||
Patch by Adam Turner.
|
|
||||||
* #12646: Remove :py:func:`!sphinx.util.inspect.isNewType`.
|
* #12646: Remove :py:func:`!sphinx.util.inspect.isNewType`.
|
||||||
Patch by Adam Turner.
|
Patch by Adam Turner.
|
||||||
* Remove the long-deprecated (since Sphinx 2) alias
|
* Remove the long-deprecated (since Sphinx 2) alias
|
||||||
@ -88,6 +84,11 @@ Deprecated
|
|||||||
to ``sphinx.ext.intersphinx.validate_intersphinx_mapping``.
|
to ``sphinx.ext.intersphinx.validate_intersphinx_mapping``.
|
||||||
The old name will be removed in Sphinx 10.
|
The old name will be removed in Sphinx 10.
|
||||||
Patch by Adam Turner.
|
Patch by Adam Turner.
|
||||||
|
* #12650, #12686, #12690: Extend the deprecation for string methods on
|
||||||
|
:py:class:`~pathlib.Path` objects to Sphinx 9.
|
||||||
|
Use :py:func:`os.fspath` to convert :py:class:`~pathlib.Path` objects to strings,
|
||||||
|
or :py:class:`~pathlib.Path`'s methods to work with path objects.
|
||||||
|
Patch by Adam Turner.
|
||||||
|
|
||||||
Features added
|
Features added
|
||||||
--------------
|
--------------
|
||||||
|
@ -179,12 +179,12 @@ nitpick_ignore = {
|
|||||||
('js:func', 'number'),
|
('js:func', 'number'),
|
||||||
('js:func', 'string'),
|
('js:func', 'string'),
|
||||||
('py:attr', 'srcline'),
|
('py:attr', 'srcline'),
|
||||||
|
('py:class', '_StrPath'), # sphinx.environment.BuildEnvironment.doc2path
|
||||||
('py:class', 'Element'), # sphinx.domains.Domain
|
('py:class', 'Element'), # sphinx.domains.Domain
|
||||||
('py:class', 'Documenter'), # sphinx.application.Sphinx.add_autodocumenter
|
('py:class', 'Documenter'), # sphinx.application.Sphinx.add_autodocumenter
|
||||||
('py:class', 'IndexEntry'), # sphinx.domains.IndexEntry
|
('py:class', 'IndexEntry'), # sphinx.domains.IndexEntry
|
||||||
('py:class', 'Node'), # sphinx.domains.Domain
|
('py:class', 'Node'), # sphinx.domains.Domain
|
||||||
('py:class', 'NullTranslations'), # gettext.NullTranslations
|
('py:class', 'NullTranslations'), # gettext.NullTranslations
|
||||||
('py:class', 'Path'), # sphinx.environment.BuildEnvironment.doc2path
|
|
||||||
('py:class', 'RoleFunction'), # sphinx.domains.Domain
|
('py:class', 'RoleFunction'), # sphinx.domains.Domain
|
||||||
('py:class', 'RSTState'), # sphinx.utils.parsing.nested_parse_to_nodes
|
('py:class', 'RSTState'), # sphinx.utils.parsing.nested_parse_to_nodes
|
||||||
('py:class', 'Theme'), # sphinx.application.TemplateBridge
|
('py:class', 'Theme'), # sphinx.application.TemplateBridge
|
||||||
@ -213,6 +213,7 @@ nitpick_ignore = {
|
|||||||
('py:class', 'sphinx.roles.XRefRole'),
|
('py:class', 'sphinx.roles.XRefRole'),
|
||||||
('py:class', 'sphinx.search.SearchLanguage'),
|
('py:class', 'sphinx.search.SearchLanguage'),
|
||||||
('py:class', 'sphinx.theming.Theme'),
|
('py:class', 'sphinx.theming.Theme'),
|
||||||
|
('py:class', 'sphinx.util._pathlib._StrPath'), # sphinx.project.Project.doc2path
|
||||||
('py:class', 'sphinxcontrib.websupport.errors.DocumentNotFoundError'),
|
('py:class', 'sphinxcontrib.websupport.errors.DocumentNotFoundError'),
|
||||||
('py:class', 'sphinxcontrib.websupport.errors.UserNotAuthorizedError'),
|
('py:class', 'sphinxcontrib.websupport.errors.UserNotAuthorizedError'),
|
||||||
('py:exc', 'docutils.nodes.SkipNode'),
|
('py:exc', 'docutils.nodes.SkipNode'),
|
||||||
|
@ -13,7 +13,6 @@ from collections import deque
|
|||||||
from collections.abc import Callable, Collection, Sequence # NoQA: TCH003
|
from collections.abc import Callable, Collection, Sequence # NoQA: TCH003
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from os import path
|
from os import path
|
||||||
from pathlib import Path
|
|
||||||
from typing import IO, TYPE_CHECKING, Any, Literal
|
from typing import IO, TYPE_CHECKING, Any, Literal
|
||||||
|
|
||||||
from docutils.nodes import TextElement # NoQA: TCH002
|
from docutils.nodes import TextElement # NoQA: TCH002
|
||||||
@ -32,6 +31,7 @@ from sphinx.locale import __
|
|||||||
from sphinx.project import Project
|
from sphinx.project import Project
|
||||||
from sphinx.registry import SphinxComponentRegistry
|
from sphinx.registry import SphinxComponentRegistry
|
||||||
from sphinx.util import docutils, logging
|
from sphinx.util import docutils, logging
|
||||||
|
from sphinx.util._pathlib import _StrPath
|
||||||
from sphinx.util.build_phase import BuildPhase
|
from sphinx.util.build_phase import BuildPhase
|
||||||
from sphinx.util.console import bold
|
from sphinx.util.console import bold
|
||||||
from sphinx.util.display import progress_message
|
from sphinx.util.display import progress_message
|
||||||
@ -173,9 +173,9 @@ class Sphinx:
|
|||||||
self.registry = SphinxComponentRegistry()
|
self.registry = SphinxComponentRegistry()
|
||||||
|
|
||||||
# validate provided directories
|
# validate provided directories
|
||||||
self.srcdir = Path(srcdir).resolve()
|
self.srcdir = _StrPath(srcdir).resolve()
|
||||||
self.outdir = Path(outdir).resolve()
|
self.outdir = _StrPath(outdir).resolve()
|
||||||
self.doctreedir = Path(doctreedir).resolve()
|
self.doctreedir = _StrPath(doctreedir).resolve()
|
||||||
|
|
||||||
if not path.isdir(self.srcdir):
|
if not path.isdir(self.srcdir):
|
||||||
raise ApplicationError(__('Cannot find source directory (%s)') %
|
raise ApplicationError(__('Cannot find source directory (%s)') %
|
||||||
@ -231,7 +231,7 @@ class Sphinx:
|
|||||||
self.confdir = self.srcdir
|
self.confdir = self.srcdir
|
||||||
self.config = Config({}, confoverrides or {})
|
self.config = Config({}, confoverrides or {})
|
||||||
else:
|
else:
|
||||||
self.confdir = Path(confdir).resolve()
|
self.confdir = _StrPath(confdir).resolve()
|
||||||
self.config = Config.read(self.confdir, confoverrides or {}, self.tags)
|
self.config = Config.read(self.confdir, confoverrides or {}, self.tags)
|
||||||
|
|
||||||
# set up translation infrastructure
|
# set up translation infrastructure
|
||||||
|
@ -28,13 +28,13 @@ from sphinx.util.nodes import get_node_line
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Callable, Iterator
|
from collections.abc import Callable, Iterator
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from requests import Response
|
from requests import Response
|
||||||
|
|
||||||
from sphinx.application import Sphinx
|
from sphinx.application import Sphinx
|
||||||
from sphinx.config import Config
|
from sphinx.config import Config
|
||||||
|
from sphinx.util._pathlib import _StrPath
|
||||||
from sphinx.util.typing import ExtensionMetadata
|
from sphinx.util.typing import ExtensionMetadata
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -150,7 +150,7 @@ class CheckExternalLinksBuilder(DummyBuilder):
|
|||||||
self.json_outfile.write(json.dumps(data))
|
self.json_outfile.write(json.dumps(data))
|
||||||
self.json_outfile.write('\n')
|
self.json_outfile.write('\n')
|
||||||
|
|
||||||
def write_entry(self, what: str, docname: str, filename: Path, line: int,
|
def write_entry(self, what: str, docname: str, filename: _StrPath, line: int,
|
||||||
uri: str) -> None:
|
uri: str) -> None:
|
||||||
self.txt_outfile.write(f'{filename}:{line}: [{what}] {uri}\n')
|
self.txt_outfile.write(f'{filename}:{line}: [{what}] {uri}\n')
|
||||||
|
|
||||||
@ -226,7 +226,7 @@ class HyperlinkCollector(SphinxPostTransform):
|
|||||||
class Hyperlink(NamedTuple):
|
class Hyperlink(NamedTuple):
|
||||||
uri: str
|
uri: str
|
||||||
docname: str
|
docname: str
|
||||||
docpath: Path
|
docpath: _StrPath
|
||||||
lineno: int
|
lineno: int
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ if TYPE_CHECKING:
|
|||||||
from sphinx.domains import Domain
|
from sphinx.domains import Domain
|
||||||
from sphinx.events import EventManager
|
from sphinx.events import EventManager
|
||||||
from sphinx.project import Project
|
from sphinx.project import Project
|
||||||
|
from sphinx.util._pathlib import _StrPath
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -413,7 +414,7 @@ class BuildEnvironment:
|
|||||||
"""
|
"""
|
||||||
return self.project.path2doc(filename)
|
return self.project.path2doc(filename)
|
||||||
|
|
||||||
def doc2path(self, docname: str, base: bool = True) -> Path:
|
def doc2path(self, docname: str, base: bool = True) -> _StrPath:
|
||||||
"""Return the filename for the document name.
|
"""Return the filename for the document name.
|
||||||
|
|
||||||
If *base* is True, return absolute path under self.srcdir.
|
If *base* is True, return absolute path under self.srcdir.
|
||||||
|
@ -9,6 +9,7 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
from sphinx.locale import __
|
from sphinx.locale import __
|
||||||
from sphinx.util import logging
|
from sphinx.util import logging
|
||||||
|
from sphinx.util._pathlib import _StrPath
|
||||||
from sphinx.util.matching import get_matching_files
|
from sphinx.util.matching import get_matching_files
|
||||||
from sphinx.util.osutil import path_stabilize
|
from sphinx.util.osutil import path_stabilize
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ class Project:
|
|||||||
|
|
||||||
def __init__(self, srcdir: str | os.PathLike[str], source_suffix: Iterable[str]) -> None:
|
def __init__(self, srcdir: str | os.PathLike[str], source_suffix: Iterable[str]) -> None:
|
||||||
#: Source directory.
|
#: Source directory.
|
||||||
self.srcdir = Path(srcdir)
|
self.srcdir = _StrPath(srcdir)
|
||||||
|
|
||||||
#: source_suffix. Same as :confval:`source_suffix`.
|
#: source_suffix. Same as :confval:`source_suffix`.
|
||||||
self.source_suffix = tuple(source_suffix)
|
self.source_suffix = tuple(source_suffix)
|
||||||
@ -106,7 +107,7 @@ class Project:
|
|||||||
# the file does not have a docname
|
# the file does not have a docname
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def doc2path(self, docname: str, absolute: bool) -> Path:
|
def doc2path(self, docname: str, absolute: bool) -> _StrPath:
|
||||||
"""Return the filename for the document name.
|
"""Return the filename for the document name.
|
||||||
|
|
||||||
If *absolute* is True, return as an absolute path.
|
If *absolute* is True, return as an absolute path.
|
||||||
@ -119,5 +120,5 @@ class Project:
|
|||||||
filename = Path(docname + self._first_source_suffix)
|
filename = Path(docname + self._first_source_suffix)
|
||||||
|
|
||||||
if absolute:
|
if absolute:
|
||||||
return self.srcdir / filename
|
return _StrPath(self.srcdir / filename)
|
||||||
return filename
|
return _StrPath(filename)
|
||||||
|
132
sphinx/util/_pathlib.py
Normal file
132
sphinx/util/_pathlib.py
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
"""What follows is awful and will be gone in Sphinx 9.
|
||||||
|
|
||||||
|
Instances of _StrPath should not be constructed except in Sphinx itself.
|
||||||
|
Consumers of Sphinx APIs should prefer using ``pathlib.Path`` objects
|
||||||
|
where possible. _StrPath objects can be treated as equivalent to ``Path``,
|
||||||
|
save that ``_StrPath.replace`` is overriden with ``str.replace``.
|
||||||
|
|
||||||
|
To continue treating path-like objects as strings, use ``os.fspath``,
|
||||||
|
or explicit string coercion.
|
||||||
|
|
||||||
|
In Sphinx 9, ``Path`` objects will be expected and returned in all instances
|
||||||
|
that ``_StrPath`` is currently used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import warnings
|
||||||
|
from pathlib import Path, PosixPath, PurePath, WindowsPath
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from sphinx.deprecation import RemovedInSphinx90Warning
|
||||||
|
|
||||||
|
_STR_METHODS = frozenset(str.__dict__)
|
||||||
|
_PATH_NAME = Path().__class__.__name__
|
||||||
|
|
||||||
|
_MSG = (
|
||||||
|
'Sphinx 8 will drop support for representing paths as strings. '
|
||||||
|
'Use "pathlib.Path" or "os.fspath" instead.'
|
||||||
|
)
|
||||||
|
|
||||||
|
# https://docs.python.org/3/library/stdtypes.html#typesseq-common
|
||||||
|
# https://docs.python.org/3/library/stdtypes.html#string-methods
|
||||||
|
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
class _StrPath(WindowsPath):
|
||||||
|
def replace( # type: ignore[override]
|
||||||
|
self, old: str, new: str, count: int = -1, /,
|
||||||
|
) -> str:
|
||||||
|
# replace exists in both Path and str;
|
||||||
|
# in Path it makes filesystem changes, so we use the safer str version
|
||||||
|
warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
|
||||||
|
return self.__str__().replace(old, new, count) # NoQA: PLC2801
|
||||||
|
|
||||||
|
def __getattr__(self, item: str) -> Any:
|
||||||
|
if item in _STR_METHODS:
|
||||||
|
warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
|
||||||
|
return getattr(self.__str__(), item)
|
||||||
|
msg = f'{_PATH_NAME!r} has no attribute {item!r}'
|
||||||
|
raise AttributeError(msg)
|
||||||
|
|
||||||
|
def __add__(self, other: str) -> str:
|
||||||
|
warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
|
||||||
|
return self.__str__() + other
|
||||||
|
|
||||||
|
def __bool__(self) -> bool:
|
||||||
|
if not self.__str__():
|
||||||
|
warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __contains__(self, item: str) -> bool:
|
||||||
|
warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
|
||||||
|
return item in self.__str__()
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
if isinstance(other, PurePath):
|
||||||
|
return super().__eq__(other)
|
||||||
|
if isinstance(other, str):
|
||||||
|
warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
|
||||||
|
return self.__str__() == other
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return super().__hash__()
|
||||||
|
|
||||||
|
def __getitem__(self, item: int | slice) -> str:
|
||||||
|
warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
|
||||||
|
return self.__str__()[item]
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
|
||||||
|
return len(self.__str__())
|
||||||
|
else:
|
||||||
|
class _StrPath(PosixPath):
|
||||||
|
def replace( # type: ignore[override]
|
||||||
|
self, old: str, new: str, count: int = -1, /,
|
||||||
|
) -> str:
|
||||||
|
# replace exists in both Path and str;
|
||||||
|
# in Path it makes filesystem changes, so we use the safer str version
|
||||||
|
warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
|
||||||
|
return self.__str__().replace(old, new, count) # NoQA: PLC2801
|
||||||
|
|
||||||
|
def __getattr__(self, item: str) -> Any:
|
||||||
|
if item in _STR_METHODS:
|
||||||
|
warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
|
||||||
|
return getattr(self.__str__(), item)
|
||||||
|
msg = f'{_PATH_NAME!r} has no attribute {item!r}'
|
||||||
|
raise AttributeError(msg)
|
||||||
|
|
||||||
|
def __add__(self, other: str) -> str:
|
||||||
|
warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
|
||||||
|
return self.__str__() + other
|
||||||
|
|
||||||
|
def __bool__(self) -> bool:
|
||||||
|
if not self.__str__():
|
||||||
|
warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __contains__(self, item: str) -> bool:
|
||||||
|
warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
|
||||||
|
return item in self.__str__()
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
if isinstance(other, PurePath):
|
||||||
|
return super().__eq__(other)
|
||||||
|
if isinstance(other, str):
|
||||||
|
warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
|
||||||
|
return self.__str__() == other
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return super().__hash__()
|
||||||
|
|
||||||
|
def __getitem__(self, item: int | slice) -> str:
|
||||||
|
warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
|
||||||
|
return self.__str__()[item]
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
warnings.warn(_MSG, RemovedInSphinx90Warning, stacklevel=2)
|
||||||
|
return len(self.__str__())
|
@ -10,6 +10,7 @@ from typing import TYPE_CHECKING
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from sphinx.builders.html import validate_html_extra_path, validate_html_static_path
|
from sphinx.builders.html import validate_html_extra_path, validate_html_static_path
|
||||||
|
from sphinx.deprecation import RemovedInSphinx90Warning
|
||||||
from sphinx.errors import ConfigError
|
from sphinx.errors import ConfigError
|
||||||
from sphinx.util.console import strip_colors
|
from sphinx.util.console import strip_colors
|
||||||
from sphinx.util.inventory import InventoryFile
|
from sphinx.util.inventory import InventoryFile
|
||||||
@ -329,6 +330,7 @@ def test_validate_html_extra_path(app):
|
|||||||
app.outdir, # outdir
|
app.outdir, # outdir
|
||||||
app.outdir / '_static', # inside outdir
|
app.outdir / '_static', # inside outdir
|
||||||
]
|
]
|
||||||
|
with pytest.warns(RemovedInSphinx90Warning, match='Use "pathlib.Path" or "os.fspath" instead'):
|
||||||
validate_html_extra_path(app, app.config)
|
validate_html_extra_path(app, app.config)
|
||||||
assert app.config.html_extra_path == ['_static']
|
assert app.config.html_extra_path == ['_static']
|
||||||
|
|
||||||
@ -342,6 +344,7 @@ def test_validate_html_static_path(app):
|
|||||||
app.outdir, # outdir
|
app.outdir, # outdir
|
||||||
app.outdir / '_static', # inside outdir
|
app.outdir / '_static', # inside outdir
|
||||||
]
|
]
|
||||||
|
with pytest.warns(RemovedInSphinx90Warning, match='Use "pathlib.Path" or "os.fspath" instead'):
|
||||||
validate_html_static_path(app, app.config)
|
validate_html_static_path(app, app.config)
|
||||||
assert app.config.html_static_path == ['_static']
|
assert app.config.html_static_path == ['_static']
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ import time
|
|||||||
import wsgiref.handlers
|
import wsgiref.handlers
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from http.server import BaseHTTPRequestHandler
|
from http.server import BaseHTTPRequestHandler
|
||||||
from pathlib import Path
|
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
@ -29,6 +28,7 @@ from sphinx.builders.linkcheck import (
|
|||||||
compile_linkcheck_allowed_redirects,
|
compile_linkcheck_allowed_redirects,
|
||||||
)
|
)
|
||||||
from sphinx.util import requests
|
from sphinx.util import requests
|
||||||
|
from sphinx.util._pathlib import _StrPath
|
||||||
from sphinx.util.console import strip_colors
|
from sphinx.util.console import strip_colors
|
||||||
|
|
||||||
from tests.utils import CERT_FILE, serve_application
|
from tests.utils import CERT_FILE, serve_application
|
||||||
@ -1061,7 +1061,7 @@ def test_connection_contention(get_adapter, app, capsys):
|
|||||||
wqueue: Queue[CheckRequest] = Queue()
|
wqueue: Queue[CheckRequest] = Queue()
|
||||||
rqueue: Queue[CheckResult] = Queue()
|
rqueue: Queue[CheckResult] = Queue()
|
||||||
for _ in range(link_count):
|
for _ in range(link_count):
|
||||||
wqueue.put(CheckRequest(0, Hyperlink(f"http://{address}", "test", Path("test.rst"), 1)))
|
wqueue.put(CheckRequest(0, Hyperlink(f"http://{address}", "test", _StrPath("test.rst"), 1)))
|
||||||
|
|
||||||
begin = time.time()
|
begin = time.time()
|
||||||
checked: list[CheckResult] = []
|
checked: list[CheckResult] = []
|
||||||
|
Loading…
Reference in New Issue
Block a user