Repository: deepset-ai/haystack
Pull request: ci: Add script to delete documentation that don't exist anymore
### Related Issues

- fixes #7081

### Proposed Changes:

This PR adds a `delete_outdated_docs.py` script that can be used to delete documentation from Readme if it doesn't exist anymore.

This script deletes docs only from the categories found in the config files, as of now that's only `haystack-api`.

The `readme_sync.yml` workflow has been changed to run the script after syncing.

### How did you test it?

I tested manually to verify that only actual documents that don't exist anymore would be deleted, I didn't delete anything though.

### Notes for the reviewer

I can't use the `rdme docs:prune` with the `readmeio/rdme` action as that would delete all docs, including those in other categories.

### Checklist

- I have read the [contributors guidelines](https://github.com/deepset-ai/haystack/blob/main/CONTRIBUTING.md) and the [code of conduct](https://github.com/deepset-ai/haystack/blob/main/code_of_conduct.txt)
- I have updated the related issue with new insights and changes
- I added unit tests and updated the docstrings
- I've used one of the [conventional commit types](https://www.conventionalcommits.org/en/v1.0.0/) for my PR title: `fix:`, `feat:`, `build:`, `chore:`, `ci:`, `docs:`, `style:`, `refactor:`, `perf:`, `test:`.
- I documented my code
- I ran [pre-commit hooks](https://github.com/deepset-ai/haystack/blob/main/CONTRIBUTING.md#installation) and fixed any issue


Problem statement:
Rework workflow that updates documentation to delete pages that don't exist anymore
The `readme_sync.yml` workflow doesn't delete pages from Readme if those don't exist anymore. 
When syncing documentation we should if all the hosted pages have local counterparts, and delete all those that don't.

The same should be done for the `haystack-core-integrations` repo.

Hints from issue discussion:

Commit message: Add script to delete documentation that don't exist anymore

Patch to review:
diff --git a/.github/utils/delete_outdated_docs.py b/.github/utils/delete_outdated_docs.py
new file mode 100644
index 0000000000..3a99641233
--- /dev/null
+++ b/.github/utils/delete_outdated_docs.py
@@ -0,0 +1,74 @@
+import argparse
+import base64
+import os
+import re
+from pathlib import Path
+from typing import List
+
+import requests
+import yaml
+
+VERSION_VALIDATOR = re.compile(r"^[0-9]+\.[0-9]+$")
+
+
+def readme_token():
+    api_key = os.getenv("RDME_API_KEY", None)
+    if not api_key:
+        raise Exception("RDME_API_KEY env var is not set")
+
+    api_key = f"{api_key}:"
+    return base64.b64encode(api_key.encode("utf-8")).decode("utf-8")
+
+
+def create_headers(version: str):
+    return {"authorization": f"Basic {readme_token()}", "x-readme-version": version}
+
+
+def get_docs_in_category(category_slug: str, version: str) -> List[str]:
+    """
+    Returns the slugs of all documents in a category for the specific version.
+    """
+    url = f"https://dash.readme.com/api/v1/categories/{category_slug}/docs"
+    headers = create_headers(version)
+    res = requests.get(url, headers=headers, timeout=10)
+    return [doc["slug"] for doc in res.json()]
+
+
+def delete_doc(slug: str, version: str):
+    url = f"https://dash.readme.com/api/v1/docs/{slug}"
+    headers = create_headers(version)
+    res = requests.delete(url, headers=headers, timeout=10)
+    res.raise_for_status()
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(
+        description="Delete outdated documentation from Readme.io. "
+        "It will delete all documents that are not present in the current config files."
+    )
+    parser.add_argument(
+        "-c", "--config-path", help="Path to folder containing YAML documentation configs", required=True, type=Path
+    )
+    parser.add_argument("-v", "--version", help="The version that will have its documents deleted", required=True)
+    args = parser.parse_args()
+
+    configs = [yaml.safe_load(c.read_text()) for c in args.config_path.glob("*.yml")]
+
+    remote_docs = {}
+    for config in configs:
+        category_slug = config["renderer"]["category_slug"]
+        if category_slug in remote_docs:
+            continue
+        docs = get_docs_in_category(category_slug, args.version)
+
+        remote_docs[category_slug] = docs
+
+    print(remote_docs)
+    for config in configs:
+        doc_slug = config["renderer"]["slug"]
+        category_slug = config["renderer"]["category_slug"]
+        if doc_slug in remote_docs[category_slug]:
+            continue
+
+        print(f"Deleting {doc_slug} from {args.version}")
+        # delete_doc(doc_slug, args.version)
diff --git a/.github/workflows/readme_sync.yml b/.github/workflows/readme_sync.yml
index a6126f7b3a..d60e44f960 100644
--- a/.github/workflows/readme_sync.yml
+++ b/.github/workflows/readme_sync.yml
@@ -40,3 +40,9 @@ jobs:
         uses: readmeio/rdme@v8
         with:
           rdme: docs ./docs/pydoc/temp --key=${{ secrets.README_API_KEY }} --version=v2.0
+
+      - name: Delete outdated docs for 2.0
+        if: github.ref_name == 'main' && github.event_name == 'push'
+        env:
+          README_API_KEY: ${{ secrets.README_API_KEY }}
+        run: python ./.github/utils/delete_outdated_docs.py --version=v2.0 --config-path ./docs/pydoc/config


Code review comments:
diff_hunk: @@ -0,0 +1,74 @@
+import argparse
+import base64
+import os
+import re
+from pathlib import Path
+from typing import List
+
+import requests
+import yaml
+
+VERSION_VALIDATOR = re.compile(r"^[0-9]+\.[0-9]+$")
+
+
+def readme_token():
+    api_key = os.getenv("RDME_API_KEY", None)
+    if not api_key:
+        raise Exception("RDME_API_KEY env var is not set")
+
+    api_key = f"{api_key}:"
+    return base64.b64encode(api_key.encode("utf-8")).decode("utf-8")
+
+
+def create_headers(version: str):
+    return {"authorization": f"Basic {readme_token()}", "x-readme-version": version}
+
+
+def get_docs_in_category(category_slug: str, version: str) -> List[str]:
+    """
+    Returns the slugs of all documents in a category for the specific version.
+    """
+    url = f"https://dash.readme.com/api/v1/categories/{category_slug}/docs"
+    headers = create_headers(version)
+    res = requests.get(url, headers=headers, timeout=10)
+    return [doc["slug"] for doc in res.json()]
+
+
+def delete_doc(slug: str, version: str):
+    url = f"https://dash.readme.com/api/v1/docs/{slug}"
+    headers = create_headers(version)
+    res = requests.delete(url, headers=headers, timeout=10)
+    res.raise_for_status()
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(
+        description="Delete outdated documentation from Readme.io. "
+        "It will delete all documents that are not present in the current config files."
+    )
+    parser.add_argument(
+        "-c", "--config-path", help="Path to folder containing YAML documentation configs", required=True, type=Path
+    )
+    parser.add_argument("-v", "--version", help="The version that will have its documents deleted", required=True)
+    args = parser.parse_args()
+
+    configs = [yaml.safe_load(c.read_text()) for c in args.config_path.glob("*.yml")]
+
+    remote_docs = {}
+    for config in configs:
+        category_slug = config["renderer"]["category_slug"]
+        if category_slug in remote_docs:
+            continue
+        docs = get_docs_in_category(category_slug, args.version)
+
+        remote_docs[category_slug] = docs
+
+    print(remote_docs)
path: .github/utils/delete_outdated_docs.py
text: @user1:
do we need this in the CI?

@author:
Nope, I'll remove that.