-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #11652 from pradyunsg/rtd-redirects
Enable managing RTD redirects in-tree
- Loading branch information
Showing
5 changed files
with
199 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
name: Update documentation redirects | ||
|
||
on: | ||
push: | ||
branches: [main] | ||
schedule: | ||
- cron: 0 0 * * MON # Run every Monday at 00:00 UTC | ||
|
||
env: | ||
FORCE_COLOR: "1" | ||
|
||
concurrency: | ||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} | ||
cancel-in-progress: true | ||
|
||
jobs: | ||
update-rtd-redirects: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: actions/setup-python@v4 | ||
with: | ||
python-version: "3.11" | ||
- run: pip install httpx requests pyyaml | ||
- run: python tools/update-rtd-redirects.py | ||
env: | ||
RTD_API_TOKEN: ${{ secrets.RTD_API_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# This file is read by tools/update-rtd-redirects.py. | ||
# It is related to Read the Docs, but is not a file processed by the platform. | ||
|
||
/dev/news-entry-failure: >- | ||
https://pip.pypa.io/en/stable/development/contributing/#news-entries | ||
/errors/resolution-impossible: >- | ||
https://pip.pypa.io/en/latest/topics/dependency-resolution/#dealing-with-dependency-conflicts | ||
/surveys/backtracking: >- | ||
https://forms.gle/LkZP95S4CfqBAU1N6 | ||
/warnings/backtracking: >- | ||
https://pip.pypa.io/en/stable/topics/dependency-resolution/#possible-ways-to-reduce-backtracking | ||
/warnings/enable-long-paths: >- | ||
https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later | ||
/warnings/venv: >- | ||
https://docs.python.org/3/tutorial/venv.html |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
"""Update the 'exact' redirects on Read the Docs to match an in-tree file's contents. | ||
Relevant API reference: https://docs.readthedocs.io/en/stable/api/v3.html#redirects | ||
""" | ||
import operator | ||
import os | ||
import sys | ||
from pathlib import Path | ||
|
||
import httpx | ||
import rich | ||
import yaml | ||
|
||
try: | ||
_TOKEN = os.environ["RTD_API_TOKEN"] | ||
except KeyError: | ||
rich.print( | ||
"[bold]error[/]: [red]No API token provided. Please set `RTD_API_TOKEN`.[/]", | ||
file=sys.stderr, | ||
) | ||
sys.exit(1) | ||
|
||
RTD_API_HEADERS = {"Authorization": f"token {_TOKEN}"} | ||
RTD_API_BASE_URL = "https://readthedocs.org/api/v3/projects/pip/" | ||
REPO_ROOT = Path(__file__).resolve().parent.parent | ||
|
||
|
||
# -------------------------------------------------------------------------------------- | ||
# Helpers | ||
# -------------------------------------------------------------------------------------- | ||
def next_step(msg: str) -> None: | ||
rich.print(f"> [blue]{msg}[/]") | ||
|
||
|
||
def log_response(response: httpx.Response) -> None: | ||
request = response.request | ||
rich.print(f"[bold magenta]{request.method}[/] {request.url} -> {response}") | ||
|
||
|
||
def get_rtd_api() -> httpx.Client: | ||
return httpx.Client( | ||
headers=RTD_API_HEADERS, | ||
base_url=RTD_API_BASE_URL, | ||
event_hooks={"response": [log_response]}, | ||
) | ||
|
||
|
||
# -------------------------------------------------------------------------------------- | ||
# Actual logic | ||
# -------------------------------------------------------------------------------------- | ||
next_step("Loading local redirects from the yaml file.") | ||
|
||
with open(REPO_ROOT / ".readthedocs-custom-redirects.yml") as f: | ||
local_redirects = yaml.safe_load(f) | ||
|
||
rich.print("Loaded local redirects!") | ||
for src, dst in sorted(local_redirects.items()): | ||
rich.print(f" [yellow]{src}[/] --> {dst}") | ||
rich.print(f"{len(local_redirects)} entries.") | ||
|
||
|
||
next_step("Fetch redirects configured on RTD.") | ||
|
||
with get_rtd_api() as rtd_api: | ||
response = rtd_api.get("redirects/") | ||
response.raise_for_status() | ||
|
||
rtd_redirects = response.json() | ||
|
||
for redirect in sorted( | ||
rtd_redirects["results"], key=operator.itemgetter("type", "from_url", "to_url") | ||
): | ||
if redirect["type"] != "exact": | ||
rich.print(f" [magenta]{redirect['type']}[/]") | ||
continue | ||
|
||
pk = redirect["pk"] | ||
src = redirect["from_url"] | ||
dst = redirect["to_url"] | ||
rich.print(f" [yellow]{src}[/] -({pk:^5})-> {dst}") | ||
|
||
rich.print(f"{rtd_redirects['count']} entries.") | ||
|
||
|
||
next_step("Compare and determine modifications.") | ||
|
||
redirects_to_remove: list[int] = [] | ||
redirects_to_add: dict[str, str] = {} | ||
|
||
for redirect in rtd_redirects["results"]: | ||
if redirect["type"] != "exact": | ||
continue | ||
|
||
rtd_src = redirect["from_url"] | ||
rtd_dst = redirect["to_url"] | ||
redirect_id = redirect["pk"] | ||
|
||
if rtd_src not in local_redirects: | ||
redirects_to_remove.append(redirect_id) | ||
continue | ||
|
||
local_dst = local_redirects[rtd_src] | ||
if local_dst != rtd_dst: | ||
redirects_to_remove.append(redirect_id) | ||
redirects_to_add[rtd_src] = local_dst | ||
|
||
del local_redirects[rtd_src] | ||
|
||
for src, dst in sorted(local_redirects.items()): | ||
redirects_to_add[src] = dst | ||
del local_redirects[src] | ||
|
||
assert not local_redirects | ||
|
||
if not redirects_to_remove: | ||
rich.print("Nothing to remove.") | ||
else: | ||
rich.print(f"To remove: ({len(redirects_to_remove)} entries)") | ||
for redirect_id in redirects_to_remove: | ||
rich.print(" ", redirect_id) | ||
|
||
if not redirects_to_add: | ||
rich.print("Nothing to add.") | ||
else: | ||
rich.print(f"To add: ({len(redirects_to_add)} entries)") | ||
for src, dst in redirects_to_add.items(): | ||
rich.print(f" {src} --> {dst}") | ||
|
||
|
||
next_step("Update the RTD redirects.") | ||
|
||
if not (redirects_to_add or redirects_to_remove): | ||
rich.print("[green]Nothing to do![/]") | ||
sys.exit(0) | ||
|
||
exit_code = 0 | ||
with get_rtd_api() as rtd_api: | ||
for redirect_id in redirects_to_remove: | ||
response = rtd_api.delete(f"redirects/{redirect_id}/") | ||
response.raise_for_status() | ||
if response.status_code != 204: | ||
rich.print("[red]This might not have been removed correctly.[/]") | ||
exit_code = 1 | ||
|
||
for src, dst in redirects_to_add.items(): | ||
response = rtd_api.post( | ||
"redirects/", | ||
json={"from_url": src, "to_url": dst, "type": "exact"}, | ||
) | ||
response.raise_for_status() | ||
if response.status_code != 201: | ||
rich.print("[red]This might not have been added correctly.[/]") | ||
exit_code = 1 | ||
|
||
sys.exit(exit_code) |