From 4a2e13f00b09b8fc0ae47bc8fc0df571bfad52c8 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 19 Oct 2024 19:37:04 +0100 Subject: [PATCH 1/4] Codify linkcheck status codes into a ``Literal`` --- sphinx/builders/linkcheck.py | 40 ++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index 0e1e70c2d46..ad349eb7125 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -29,7 +29,7 @@ if TYPE_CHECKING: from collections.abc import Callable, Iterator - from typing import Any + from typing import Any, Literal, TypeAlias from requests import Response @@ -38,6 +38,17 @@ from sphinx.util._pathlib import _StrPath from sphinx.util.typing import ExtensionMetadata + _Statuses: TypeAlias = Literal[ + 'broken', + 'ignored', + 'local', + 'rate-limited', + 'redirected', + 'timeout', + 'unchecked', + 'working', + ] + logger = logging.getLogger(__name__) # matches to foo:// and // (a protocol relative URL) @@ -85,7 +96,7 @@ def finish(self) -> None: def process_result(self, result: CheckResult) -> None: filename = self.env.doc2path(result.docname, False) - linkstat: dict[str, str | int] = { + linkstat: dict[str, str | int | _Statuses] = { 'filename': str(filename), 'lineno': result.lineno, 'status': result.status, @@ -189,7 +200,12 @@ def write_linkstat(self, data: dict[str, str | int]) -> None: self.json_outfile.write('\n') def write_entry( - self, what: str, docname: str, filename: _StrPath, line: int, uri: str + self, + what: _Statuses | str, + docname: str, + filename: _StrPath, + line: int, + uri: str, ) -> None: self.txt_outfile.write(f'{filename}:{line}: [{what}] {uri}\n') @@ -330,7 +346,7 @@ class CheckResult(NamedTuple): uri: str docname: str lineno: int - status: str + status: _Statuses | Literal[''] message: str code: int @@ -373,6 +389,7 @@ def __init__( self.retries: int = config.linkcheck_retries self.rate_limit_timeout = config.linkcheck_rate_limit_timeout self._allow_unauthorized = config.linkcheck_allow_unauthorized + self._timeout_status: Literal['broken', 'timeout'] if config.linkcheck_report_timeouts_as_broken: self._timeout_status = 'broken' else: @@ -423,7 +440,7 @@ def run(self) -> None: def _check( self, docname: str, uri: str, hyperlink: Hyperlink - ) -> tuple[str, str, int]: + ) -> tuple[_Statuses | Literal[''], str, int]: # check for various conditions without bothering the network for doc_matcher in self.documents_exclude: @@ -447,13 +464,12 @@ def _check( return 'broken', '', 0 # need to actually check the URI - status, info, code = '', '', 0 for _ in range(self.retries): status, info, code = self._check_uri(uri, hyperlink) if status != 'broken': - break + return status, info, code - return status, info, code + return '', '', 0 def _retrieval_methods( self, @@ -464,7 +480,7 @@ def _retrieval_methods( yield self._session.head, {'allow_redirects': True} yield self._session.get, {'stream': True} - def _check_uri(self, uri: str, hyperlink: Hyperlink) -> tuple[str, str, int]: + def _check_uri(self, uri: str, hyperlink: Hyperlink) -> tuple[_Statuses, str, int]: req_url, delimiter, anchor = uri.partition('#') if delimiter and anchor: for rex in self.anchors_ignore: @@ -556,8 +572,10 @@ def _check_uri(self, uri: str, hyperlink: Hyperlink) -> tuple[str, str, int]: # Unauthorized: the client did not provide required credentials if status_code == 401: - status = 'working' if self._allow_unauthorized else 'broken' - return status, 'unauthorized', 0 + if self._allow_unauthorized: + return 'working', 'unauthorized', 0 + else: + return 'broken', 'unauthorized', 0 # Rate limiting; back-off if allowed, or report failure otherwise if status_code == 429: From 337cd18fd8d408db06197c8e31b76906cdcb200a Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 19 Oct 2024 19:57:34 +0100 Subject: [PATCH 2/4] Codify linkcheck status codes into a ``Literal`` --- sphinx/builders/linkcheck.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index ad349eb7125..b3a115fb23e 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -193,7 +193,8 @@ def process_result(self, result: CheckResult) -> None: result.uri + ' to ' + result.message, ) else: - raise ValueError('Unknown status %s.' % result.status) + msg = f'Unknown status {result.status!r}.' + raise ValueError(msg) def write_linkstat(self, data: dict[str, str | int]) -> None: self.json_outfile.write(json.dumps(data)) @@ -464,12 +465,13 @@ def _check( return 'broken', '', 0 # need to actually check the URI + status, info, code = '', '', 0 for _ in range(self.retries): status, info, code = self._check_uri(uri, hyperlink) if status != 'broken': return status, info, code - return '', '', 0 + return status, info, code def _retrieval_methods( self, From d75dad7a3c9f6ddf277ee53dd3d4cbdb44b8308f Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 19 Oct 2024 20:02:33 +0100 Subject: [PATCH 3/4] Codify linkcheck status codes into a ``Literal`` --- sphinx/builders/linkcheck.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index b3a115fb23e..9339fd7f3e0 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -38,7 +38,7 @@ from sphinx.util._pathlib import _StrPath from sphinx.util.typing import ExtensionMetadata - _Statuses: TypeAlias = Literal[ + _Status: TypeAlias = Literal[ 'broken', 'ignored', 'local', @@ -96,7 +96,7 @@ def finish(self) -> None: def process_result(self, result: CheckResult) -> None: filename = self.env.doc2path(result.docname, False) - linkstat: dict[str, str | int | _Statuses] = { + linkstat: dict[str, str | int | _Status] = { 'filename': str(filename), 'lineno': result.lineno, 'status': result.status, @@ -202,7 +202,7 @@ def write_linkstat(self, data: dict[str, str | int]) -> None: def write_entry( self, - what: _Statuses | str, + what: _Status | str, docname: str, filename: _StrPath, line: int, @@ -347,7 +347,7 @@ class CheckResult(NamedTuple): uri: str docname: str lineno: int - status: _Statuses | Literal[''] + status: _Status | Literal[''] message: str code: int @@ -441,7 +441,7 @@ def run(self) -> None: def _check( self, docname: str, uri: str, hyperlink: Hyperlink - ) -> tuple[_Statuses | Literal[''], str, int]: + ) -> tuple[_Status | Literal[''], str, int]: # check for various conditions without bothering the network for doc_matcher in self.documents_exclude: @@ -465,11 +465,12 @@ def _check( return 'broken', '', 0 # need to actually check the URI + status: _Status | Literal[''] status, info, code = '', '', 0 for _ in range(self.retries): status, info, code = self._check_uri(uri, hyperlink) if status != 'broken': - return status, info, code + break return status, info, code @@ -482,7 +483,7 @@ def _retrieval_methods( yield self._session.head, {'allow_redirects': True} yield self._session.get, {'stream': True} - def _check_uri(self, uri: str, hyperlink: Hyperlink) -> tuple[_Statuses, str, int]: + def _check_uri(self, uri: str, hyperlink: Hyperlink) -> tuple[_Status, str, int]: req_url, delimiter, anchor = uri.partition('#') if delimiter and anchor: for rex in self.anchors_ignore: From 199fcc077f42f3d49ec1af1fe09ac58968525184 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 19 Oct 2024 20:04:39 +0100 Subject: [PATCH 4/4] fmt --- sphinx/builders/linkcheck.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index 9339fd7f3e0..9e8e661dfd7 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -201,12 +201,7 @@ def write_linkstat(self, data: dict[str, str | int]) -> None: self.json_outfile.write('\n') def write_entry( - self, - what: _Status | str, - docname: str, - filename: _StrPath, - line: int, - uri: str, + self, what: _Status | str, docname: str, filename: _StrPath, line: int, uri: str ) -> None: self.txt_outfile.write(f'{filename}:{line}: [{what}] {uri}\n')