mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Add linkcheck_auth option
This commit is contained in:
parent
125179e76e
commit
df2f80c755
2
CHANGES
2
CHANGES
@ -39,6 +39,8 @@ Features added
|
|||||||
* #2546: apidoc: .so file support
|
* #2546: apidoc: .so file support
|
||||||
* #6798: autosummary: emit ``autodoc-skip-member`` event on generating stub file
|
* #6798: autosummary: emit ``autodoc-skip-member`` event on generating stub file
|
||||||
* #6483: i18n: make explicit titles in toctree translatable
|
* #6483: i18n: make explicit titles in toctree translatable
|
||||||
|
* #6816: linkcheck: Add :confval:`linkcheck_auth` option to provide
|
||||||
|
authentication information when doing ``linkcheck`` builds
|
||||||
|
|
||||||
Bugs fixed
|
Bugs fixed
|
||||||
----------
|
----------
|
||||||
|
@ -538,7 +538,7 @@ General configuration
|
|||||||
directory pointed ``REQUESTS_CA_BUNDLE`` environment
|
directory pointed ``REQUESTS_CA_BUNDLE`` environment
|
||||||
variable if ``tls_cacerts`` not set.
|
variable if ``tls_cacerts`` not set.
|
||||||
|
|
||||||
.. _requests: http://docs.python-requests.org/en/master/
|
.. _requests: https://requests.readthedocs.io/en/master/
|
||||||
|
|
||||||
.. confval:: today
|
.. confval:: today
|
||||||
today_fmt
|
today_fmt
|
||||||
@ -2369,6 +2369,34 @@ Options for the linkcheck builder
|
|||||||
|
|
||||||
.. versionadded:: 1.5
|
.. versionadded:: 1.5
|
||||||
|
|
||||||
|
.. confval:: linkcheck_auth
|
||||||
|
|
||||||
|
Pass authentication information when doing a ``linkcheck`` build.
|
||||||
|
|
||||||
|
A list of ``(regex_pattern, auth_info)`` tuples where the items are:
|
||||||
|
|
||||||
|
*regex_pattern*
|
||||||
|
A regular expression that matches a URI.
|
||||||
|
*auth_info*
|
||||||
|
Authentication information to use for that URI. The value can be anything
|
||||||
|
that is understood by the ``requests`` library (see `requests
|
||||||
|
Authentication <requests-auth>`_ for details).
|
||||||
|
|
||||||
|
.. _requests-auth: https://requests.readthedocs.io/en/master/user/authentication/
|
||||||
|
|
||||||
|
The ``linkcheck`` builder will use the first matching ``auth_info`` value
|
||||||
|
it can find in the :confval:`linkcheck_auth` list, so values earlier in the
|
||||||
|
list have higher priority.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
linkcheck_auth = [
|
||||||
|
('https://foo\.yourcompany\.com/.+', ('johndoe', 'secret')),
|
||||||
|
('https://.+\.yourcompany\.com/.+', HTTPDigestAuth(...)),
|
||||||
|
]
|
||||||
|
|
||||||
|
.. versionadded:: 2.3
|
||||||
|
|
||||||
|
|
||||||
Options for the XML builder
|
Options for the XML builder
|
||||||
---------------------------
|
---------------------------
|
||||||
|
@ -81,6 +81,8 @@ class CheckExternalLinksBuilder(Builder):
|
|||||||
self.to_ignore = [re.compile(x) for x in self.app.config.linkcheck_ignore]
|
self.to_ignore = [re.compile(x) for x in self.app.config.linkcheck_ignore]
|
||||||
self.anchors_ignore = [re.compile(x)
|
self.anchors_ignore = [re.compile(x)
|
||||||
for x in self.app.config.linkcheck_anchors_ignore]
|
for x in self.app.config.linkcheck_anchors_ignore]
|
||||||
|
self.auth = [(re.compile(pattern), auth_info) for pattern, auth_info
|
||||||
|
in self.app.config.linkcheck_auth]
|
||||||
self.good = set() # type: Set[str]
|
self.good = set() # type: Set[str]
|
||||||
self.broken = {} # type: Dict[str, str]
|
self.broken = {} # type: Dict[str, str]
|
||||||
self.redirected = {} # type: Dict[str, Tuple[str, int]]
|
self.redirected = {} # type: Dict[str, Tuple[str, int]]
|
||||||
@ -127,11 +129,18 @@ class CheckExternalLinksBuilder(Builder):
|
|||||||
except UnicodeError:
|
except UnicodeError:
|
||||||
req_url = encode_uri(req_url)
|
req_url = encode_uri(req_url)
|
||||||
|
|
||||||
|
# Get auth info, if any
|
||||||
|
for pattern, auth_info in self.auth:
|
||||||
|
if pattern.match(uri):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
auth_info = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if anchor and self.app.config.linkcheck_anchors:
|
if anchor and self.app.config.linkcheck_anchors:
|
||||||
# Read the whole document and see if #anchor exists
|
# Read the whole document and see if #anchor exists
|
||||||
response = requests.get(req_url, stream=True, config=self.app.config,
|
response = requests.get(req_url, stream=True, config=self.app.config,
|
||||||
**kwargs)
|
auth=auth_info, **kwargs)
|
||||||
found = check_anchor(response, unquote(anchor))
|
found = check_anchor(response, unquote(anchor))
|
||||||
|
|
||||||
if not found:
|
if not found:
|
||||||
@ -140,13 +149,14 @@ class CheckExternalLinksBuilder(Builder):
|
|||||||
try:
|
try:
|
||||||
# try a HEAD request first, which should be easier on
|
# try a HEAD request first, which should be easier on
|
||||||
# the server and the network
|
# the server and the network
|
||||||
response = requests.head(req_url, config=self.app.config, **kwargs)
|
response = requests.head(req_url, config=self.app.config,
|
||||||
|
auth=auth_info, **kwargs)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
except HTTPError:
|
except HTTPError:
|
||||||
# retry with GET request if that fails, some servers
|
# retry with GET request if that fails, some servers
|
||||||
# don't like HEAD requests.
|
# don't like HEAD requests.
|
||||||
response = requests.get(req_url, stream=True, config=self.app.config,
|
response = requests.get(req_url, stream=True, config=self.app.config,
|
||||||
**kwargs)
|
auth=auth_info, **kwargs)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
except HTTPError as err:
|
except HTTPError as err:
|
||||||
if err.response.status_code == 401:
|
if err.response.status_code == 401:
|
||||||
@ -305,6 +315,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
|
|||||||
app.add_builder(CheckExternalLinksBuilder)
|
app.add_builder(CheckExternalLinksBuilder)
|
||||||
|
|
||||||
app.add_config_value('linkcheck_ignore', [], None)
|
app.add_config_value('linkcheck_ignore', [], None)
|
||||||
|
app.add_config_value('linkcheck_auth', [], None)
|
||||||
app.add_config_value('linkcheck_retries', 1, None)
|
app.add_config_value('linkcheck_retries', 1, None)
|
||||||
app.add_config_value('linkcheck_timeout', None, None, [int])
|
app.add_config_value('linkcheck_timeout', None, None, [int])
|
||||||
app.add_config_value('linkcheck_workers', 5, None)
|
app.add_config_value('linkcheck_workers', 5, None)
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
:license: BSD, see LICENSE for details.
|
:license: BSD, see LICENSE for details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from unittest import mock
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
@ -47,3 +48,25 @@ def test_anchors_ignored(app, status, warning):
|
|||||||
|
|
||||||
# expect all ok when excluding #top
|
# expect all ok when excluding #top
|
||||||
assert not content
|
assert not content
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.sphinx(
|
||||||
|
'linkcheck', testroot='linkcheck', freshenv=True,
|
||||||
|
confoverrides={'linkcheck_auth': [
|
||||||
|
(r'.+google\.com/image.+', 'authinfo1'),
|
||||||
|
(r'.+google\.com.+', 'authinfo2'),
|
||||||
|
]
|
||||||
|
})
|
||||||
|
def test_auth(app, status, warning):
|
||||||
|
mock_req = mock.MagicMock()
|
||||||
|
mock_req.return_value = 'fake-response'
|
||||||
|
|
||||||
|
with mock.patch.multiple('requests', get=mock_req, head=mock_req):
|
||||||
|
app.builder.build_all()
|
||||||
|
for c_args, c_kwargs in mock_req.call_args_list:
|
||||||
|
if 'google.com/image' in c_args[0]:
|
||||||
|
assert c_kwargs['auth'] == 'authinfo1'
|
||||||
|
elif 'google.com' in c_args[0]:
|
||||||
|
assert c_kwargs['auth'] == 'authinfo2'
|
||||||
|
else:
|
||||||
|
assert not c_kwargs['auth']
|
||||||
|
Loading…
Reference in New Issue
Block a user