diff options
author | Pradyun Gedam <pradyunsg@users.noreply.github.com> | 2022-11-25 13:36:04 +0000 |
---|---|---|
committer | Pradyun Gedam <pradyunsg@users.noreply.github.com> | 2022-12-30 02:57:24 +0000 |
commit | fea8ae9f9ffdabb15d81cbca5636f7ab82fa419c (patch) | |
tree | d3da2459e2d8b628e20c87fafbe6354e49f039fd /tools | |
parent | 90db7b641d0d671e979e87c218f8895a50e684b9 (diff) | |
download | pip-fea8ae9f9ffdabb15d81cbca5636f7ab82fa419c.tar.gz |
Enable managing RTD redirects in-tree
This is designed as a script and a data file (in YAML format), and meant
to manage the RTD redirects with a version controlled file.
This makes it possible for pull requests to this repository to update
the redirects for this project's documentation (eg: for better error
urls) and for this evolution to be tracked as a part of version control
history.
Diffstat (limited to 'tools')
-rw-r--r-- | tools/update-rtd-redirects.py | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/tools/update-rtd-redirects.py b/tools/update-rtd-redirects.py new file mode 100644 index 000000000..8515c026c --- /dev/null +++ b/tools/update-rtd-redirects.py @@ -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) |