Source code for click_extra.sphinx.linkcheck
# Copyright Kevin Deldycke <kevin@deldycke.com> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
"""Sign the `linkcheck` requests to github.com with a CI token.
GitHub throttles anonymous requests to about one a minute, which overruns the
link check of a site with many links to github.com. When the environment holds
{data}`GITHUB_TOKEN_ENV`, as a CI workflow sets it on its link-check step, the
requests to github.com carry that token. A local build has no token, so its
requests stay anonymous.
Release downloads stay anonymous: with an `Authorization` header, github.com
redirects them to the legacy `objects.githubusercontent.com` host, whose signed
URL answers 401. A page linking one download per released binary would then
report every one of them as broken. `linkcheck_auth` carries the exception
because its patterns are regular expressions, where `linkcheck_request_headers`
matches whole hosts only.
"""
from __future__ import annotations
import os
TYPE_CHECKING = False
if TYPE_CHECKING:
from requests import PreparedRequest
from sphinx.application import Sphinx
from sphinx.config import Config
GITHUB_TOKEN_ENV = "GITHUB_TOKEN"
"""Environment variable holding the token the requests to github.com carry."""
GITHUB_URL_PATTERN = r"https://github\.com/(?!.*/releases/(?:latest/)?download/)"
"""URLs the token signs: every page of github.com, but a release download."""
[docs]
def github_bearer_auth(request: PreparedRequest) -> PreparedRequest:
"""Sign `request` with the token {data}`GITHUB_TOKEN_ENV` holds.
The token is read when the request goes out. A function also stays out of
the build environment Sphinx pickles, where an object holding the token would
have written it to disk.
:return: the request, as `requests` expects from an `auth` callable.
"""
request.headers["Authorization"] = f"Bearer {os.environ[GITHUB_TOKEN_ENV]}"
return request
[docs]
def add_github_auth(app: Sphinx, config: Config) -> None:
"""Append the github.com entry to `linkcheck_auth`, if a token is set.
The entry goes after the ones `conf.py` declares. Sphinx signs a request with
the first entry whose pattern matches its URL, so a project entry keeps
precedence.
"""
if not os.environ.get(GITHUB_TOKEN_ENV):
return
config.linkcheck_auth = [
*config.linkcheck_auth,
(GITHUB_URL_PATTERN, github_bearer_auth),
]
[docs]
def setup(app: Sphinx) -> None:
"""Register the github.com authentication on `app`.
Called from {func}`click_extra.sphinx.setup` so projects only need to list
`"click_extra.sphinx"` in their `extensions`. `config-inited` fires once
`conf.py` is read, so its own `linkcheck_auth` entries come first.
"""
app.connect("config-inited", add_github_auth)