]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
🔥 Remove now-obsolete scripts to generate data for FastAPI People (#16016)
authorSebastián Ramírez <tiangolo@gmail.com>
Thu, 16 Jul 2026 18:38:36 +0000 (20:38 +0200)
committerGitHub <noreply@github.com>
Thu, 16 Jul 2026 18:38:36 +0000 (18:38 +0000)
.github/workflows/contributors.yml [deleted file]
.github/workflows/people.yml [deleted file]
scripts/contributors.py [deleted file]
scripts/people.py [deleted file]

diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml
deleted file mode 100644 (file)
index 04d82c3..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-name: FastAPI People Contributors
-
-on:
-  schedule:
-    - cron: "0 3 1 * *"
-  workflow_dispatch:
-    inputs:
-      debug_enabled:
-        description: "Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)"
-        required: false
-        default: "false"
-
-permissions: {}
-
-jobs:
-  job:
-    if: github.repository_owner == 'fastapi'
-    runs-on: ubuntu-latest
-    permissions:
-      contents: write
-    steps:
-      - name: Dump GitHub context
-        env:
-          GITHUB_CONTEXT: ${{ toJson(github) }}
-        run: echo "$GITHUB_CONTEXT"
-      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
-        with:
-          persist-credentials: true # Required for `git push` in `contributors.py`
-      - name: Set up Python
-        uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
-        with:
-          python-version-file: ".python-version"
-      - name: Setup uv
-        uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
-        with:
-          # Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
-          # See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
-          version: "0.11.18"
-          enable-cache: true
-          cache-dependency-glob: |
-            pyproject.toml
-            uv.lock
-      - name: Install Dependencies
-        run: uv sync --locked --no-dev --group github-actions
-      # Allow debugging with tmate
-      - name: Setup tmate session
-        uses: mxschmitt/action-tmate@35b54afac29c97fb54faba5b513f8fbd1882f113 # v3.24
-        if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
-        with:
-          limit-access-to-actor: true
-        env:
-          GITHUB_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }} # zizmor: ignore[secrets-outside-env]
-      - name: FastAPI People Contributors
-        run: uv run ./scripts/contributors.py
-        env:
-          GITHUB_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }} # zizmor: ignore[secrets-outside-env]
diff --git a/.github/workflows/people.yml b/.github/workflows/people.yml
deleted file mode 100644 (file)
index a7dd8a4..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-name: FastAPI People
-
-on:
-  schedule:
-    - cron: "0 14 1 * *"
-  workflow_dispatch:
-    inputs:
-      debug_enabled:
-        description: Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)
-        required: false
-        default: "false"
-
-permissions: {}
-
-jobs:
-  job:
-    if: github.repository_owner == 'fastapi'
-    runs-on: ubuntu-latest
-    permissions:
-      contents: write
-    steps:
-      - name: Dump GitHub context
-        env:
-          GITHUB_CONTEXT: ${{ toJson(github) }}
-        run: echo "$GITHUB_CONTEXT"
-      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
-        with:
-          persist-credentials: true # Required for `git push` in `people.py`
-      - name: Set up Python
-        uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
-        with:
-          python-version-file: ".python-version"
-      - name: Setup uv
-        uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
-        with:
-          # Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
-          # See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
-          version: "0.11.18"
-          enable-cache: true
-          cache-dependency-glob: |
-            pyproject.toml
-            uv.lock
-      - name: Install Dependencies
-        run: uv sync --locked --no-dev --group github-actions
-      # Allow debugging with tmate
-      - name: Setup tmate session
-        uses: mxschmitt/action-tmate@35b54afac29c97fb54faba5b513f8fbd1882f113 # v3.24
-        if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
-        with:
-          limit-access-to-actor: true
-        env:
-          GITHUB_TOKEN: ${{ secrets.FASTAPI_PEOPLE }} # zizmor: ignore[secrets-outside-env]
-      - name: FastAPI People Experts
-        run: uv run ./scripts/people.py
-        env:
-          GITHUB_TOKEN: ${{ secrets.FASTAPI_PEOPLE }} # zizmor: ignore[secrets-outside-env]
-          SLEEP_INTERVAL: ${{ vars.PEOPLE_SLEEP_INTERVAL }}
diff --git a/scripts/contributors.py b/scripts/contributors.py
deleted file mode 100644 (file)
index 5e9d72d..0000000
+++ /dev/null
@@ -1,316 +0,0 @@
-import logging
-import secrets
-import subprocess
-from collections import Counter
-from datetime import datetime
-from pathlib import Path
-from typing import Any
-
-import httpx
-import yaml
-from github import Github
-from pydantic import BaseModel, SecretStr
-from pydantic_settings import BaseSettings
-
-github_graphql_url = "https://api.github.com/graphql"
-
-
-prs_query = """
-query Q($after: String) {
-  repository(name: "fastapi", owner: "fastapi") {
-    pullRequests(first: 100, after: $after) {
-      edges {
-        cursor
-        node {
-          number
-          labels(first: 100) {
-            nodes {
-              name
-            }
-          }
-          author {
-            login
-            avatarUrl
-            url
-          }
-          title
-          createdAt
-          lastEditedAt
-          updatedAt
-          state
-          reviews(first:100) {
-            nodes {
-              author {
-                login
-                avatarUrl
-                url
-              }
-              state
-            }
-          }
-        }
-      }
-    }
-  }
-}
-"""
-
-
-class Author(BaseModel):
-    login: str
-    avatarUrl: str
-    url: str
-
-
-class LabelNode(BaseModel):
-    name: str
-
-
-class Labels(BaseModel):
-    nodes: list[LabelNode]
-
-
-class ReviewNode(BaseModel):
-    author: Author | None = None
-    state: str
-
-
-class Reviews(BaseModel):
-    nodes: list[ReviewNode]
-
-
-class PullRequestNode(BaseModel):
-    number: int
-    labels: Labels
-    author: Author | None = None
-    title: str
-    createdAt: datetime
-    lastEditedAt: datetime | None = None
-    updatedAt: datetime | None = None
-    state: str
-    reviews: Reviews
-
-
-class PullRequestEdge(BaseModel):
-    cursor: str
-    node: PullRequestNode
-
-
-class PullRequests(BaseModel):
-    edges: list[PullRequestEdge]
-
-
-class PRsRepository(BaseModel):
-    pullRequests: PullRequests
-
-
-class PRsResponseData(BaseModel):
-    repository: PRsRepository
-
-
-class PRsResponse(BaseModel):
-    data: PRsResponseData
-
-
-class Settings(BaseSettings):
-    github_token: SecretStr
-    github_repository: str
-    httpx_timeout: int = 30
-
-
-def get_graphql_response(
-    *,
-    settings: Settings,
-    query: str,
-    after: str | None = None,
-) -> dict[str, Any]:
-    headers = {"Authorization": f"token {settings.github_token.get_secret_value()}"}
-    variables = {"after": after}
-    response = httpx.post(
-        github_graphql_url,
-        headers=headers,
-        timeout=settings.httpx_timeout,
-        json={"query": query, "variables": variables, "operationName": "Q"},
-    )
-    if response.status_code != 200:
-        logging.error(f"Response was not 200, after: {after}")
-        logging.error(response.text)
-        raise RuntimeError(response.text)
-    data = response.json()
-    if "errors" in data:
-        logging.error(f"Errors in response, after: {after}")
-        logging.error(data["errors"])
-        logging.error(response.text)
-        raise RuntimeError(response.text)
-    return data
-
-
-def get_graphql_pr_edges(
-    *, settings: Settings, after: str | None = None
-) -> list[PullRequestEdge]:
-    data = get_graphql_response(settings=settings, query=prs_query, after=after)
-    graphql_response = PRsResponse.model_validate(data)
-    return graphql_response.data.repository.pullRequests.edges
-
-
-def get_pr_nodes(settings: Settings) -> list[PullRequestNode]:
-    pr_nodes: list[PullRequestNode] = []
-    pr_edges = get_graphql_pr_edges(settings=settings)
-
-    while pr_edges:
-        for edge in pr_edges:
-            pr_nodes.append(edge.node)
-        last_edge = pr_edges[-1]
-        pr_edges = get_graphql_pr_edges(settings=settings, after=last_edge.cursor)
-    return pr_nodes
-
-
-class ContributorsResults(BaseModel):
-    contributors: Counter[str]
-    translation_reviewers: Counter[str]
-    translators: Counter[str]
-    authors: dict[str, Author]
-
-
-def get_contributors(pr_nodes: list[PullRequestNode]) -> ContributorsResults:
-    contributors = Counter[str]()
-    translation_reviewers = Counter[str]()
-    translators = Counter[str]()
-    authors: dict[str, Author] = {}
-
-    for pr in pr_nodes:
-        if pr.author:
-            authors[pr.author.login] = pr.author
-        is_lang = False
-        for label in pr.labels.nodes:
-            if label.name == "lang-all":
-                is_lang = True
-                break
-        for review in pr.reviews.nodes:
-            if review.author:
-                authors[review.author.login] = review.author
-                if is_lang:
-                    translation_reviewers[review.author.login] += 1
-        if pr.state == "MERGED" and pr.author:
-            if is_lang:
-                translators[pr.author.login] += 1
-            else:
-                contributors[pr.author.login] += 1
-    return ContributorsResults(
-        contributors=contributors,
-        translation_reviewers=translation_reviewers,
-        translators=translators,
-        authors=authors,
-    )
-
-
-def get_users_to_write(
-    *,
-    counter: Counter[str],
-    authors: dict[str, Author],
-    min_count: int = 2,
-) -> dict[str, Any]:
-    users: dict[str, Any] = {}
-    for user, count in counter.most_common():
-        if count >= min_count:
-            author = authors[user]
-            users[user] = {
-                "login": user,
-                "count": count,
-                "avatarUrl": author.avatarUrl,
-                "url": author.url,
-            }
-    return users
-
-
-def update_content(*, content_path: Path, new_content: Any) -> bool:
-    old_content = content_path.read_text(encoding="utf-8")
-
-    new_content = yaml.dump(new_content, sort_keys=False, width=200, allow_unicode=True)
-    if old_content == new_content:
-        logging.info(f"The content hasn't changed for {content_path}")
-        return False
-    content_path.write_text(new_content, encoding="utf-8")
-    logging.info(f"Updated {content_path}")
-    return True
-
-
-def main() -> None:
-    logging.basicConfig(level=logging.INFO)
-    settings = Settings()  # ty: ignore[missing-argument]
-    logging.info(f"Using config: {settings.model_dump_json()}")
-    g = Github(settings.github_token.get_secret_value())
-    repo = g.get_repo(settings.github_repository)
-
-    pr_nodes = get_pr_nodes(settings=settings)
-    contributors_results = get_contributors(pr_nodes=pr_nodes)
-    authors = contributors_results.authors
-
-    top_contributors = get_users_to_write(
-        counter=contributors_results.contributors,
-        authors=authors,
-    )
-
-    top_translators = get_users_to_write(
-        counter=contributors_results.translators,
-        authors=authors,
-    )
-    top_translations_reviewers = get_users_to_write(
-        counter=contributors_results.translation_reviewers,
-        authors=authors,
-    )
-
-    # For local development
-    # contributors_path = Path("../docs/en/data/contributors.yml")
-    contributors_path = Path("./docs/en/data/contributors.yml")
-    # translators_path = Path("../docs/en/data/translators.yml")
-    translators_path = Path("./docs/en/data/translators.yml")
-    # translation_reviewers_path = Path("../docs/en/data/translation_reviewers.yml")
-    translation_reviewers_path = Path("./docs/en/data/translation_reviewers.yml")
-
-    updated = [
-        update_content(content_path=contributors_path, new_content=top_contributors),
-        update_content(content_path=translators_path, new_content=top_translators),
-        update_content(
-            content_path=translation_reviewers_path,
-            new_content=top_translations_reviewers,
-        ),
-    ]
-
-    if not any(updated):
-        logging.info("The data hasn't changed, finishing.")
-        return
-
-    logging.info("Setting up GitHub Actions git user")
-    subprocess.run(["git", "config", "user.name", "github-actions[bot]"], check=True)
-    subprocess.run(
-        ["git", "config", "user.email", "github-actions[bot]@users.noreply.github.com"],
-        check=True,
-    )
-    branch_name = f"fastapi-people-contributors-{secrets.token_hex(4)}"
-    logging.info(f"Creating a new branch {branch_name}")
-    subprocess.run(["git", "checkout", "-b", branch_name], check=True)
-    logging.info("Adding updated file")
-    subprocess.run(
-        [
-            "git",
-            "add",
-            str(contributors_path),
-            str(translators_path),
-            str(translation_reviewers_path),
-        ],
-        check=True,
-    )
-    logging.info("Committing updated file")
-    message = "👥 Update FastAPI People - Contributors and Translators"
-    subprocess.run(["git", "commit", "-m", message], check=True)
-    logging.info("Pushing branch")
-    subprocess.run(["git", "push", "origin", branch_name], check=True)
-    logging.info("Creating PR")
-    pr = repo.create_pull(title=message, body=message, base="master", head=branch_name)
-    logging.info(f"Created PR: {pr.number}")
-    logging.info("Finished")
-
-
-if __name__ == "__main__":
-    main()
diff --git a/scripts/people.py b/scripts/people.py
deleted file mode 100644 (file)
index 72b5913..0000000
+++ /dev/null
@@ -1,483 +0,0 @@
-import logging
-import secrets
-import subprocess
-import time
-from collections import Counter
-from collections.abc import Container
-from datetime import datetime, timedelta, timezone
-from math import ceil
-from pathlib import Path
-from typing import Annotated, Any
-
-import httpx
-import yaml
-from github import Github
-from pydantic import BaseModel, BeforeValidator, SecretStr
-from pydantic_settings import BaseSettings
-
-github_graphql_url = "https://api.github.com/graphql"
-questions_category_id = "DIC_kwDOCZduT84B6E2a"
-
-
-POINTS_PER_MINUTE_LIMIT = 84  # 5000 points per hour
-
-MINIMIZED_COMMENTS_REASONS_TO_EXCLUDE = {"abuse", "off-topic", "duplicate", "spam"}
-
-
-class RateLimiter:
-    def __init__(self) -> None:
-        self.last_query_cost: int = 1
-        self.remaining_points: int = 5000
-        self.reset_at: datetime = datetime.fromtimestamp(0, timezone.utc)
-        self.last_request_start_time: datetime = datetime.fromtimestamp(0, timezone.utc)
-        self.speed_multiplier: float = 1.0
-
-    def __enter__(self) -> "RateLimiter":
-        now = datetime.now(tz=timezone.utc)
-
-        # Handle primary rate limits
-        primary_limit_wait_time = 0.0
-        if self.remaining_points <= self.last_query_cost:
-            primary_limit_wait_time = (self.reset_at - now).total_seconds() + 2
-            logging.warning(
-                f"Approaching GitHub API rate limit, remaining points: {self.remaining_points}, "
-                f"reset time in {primary_limit_wait_time} seconds"
-            )
-
-        # Handle secondary rate limits
-        secondary_limit_wait_time = 0.0
-        points_per_minute = POINTS_PER_MINUTE_LIMIT * self.speed_multiplier
-        interval = 60 / (points_per_minute / self.last_query_cost)
-        time_since_last_request = (now - self.last_request_start_time).total_seconds()
-        if time_since_last_request < interval:
-            secondary_limit_wait_time = interval - time_since_last_request
-
-        final_wait_time = ceil(max(primary_limit_wait_time, secondary_limit_wait_time))
-        logging.info(f"Sleeping for {final_wait_time} seconds to respect rate limit")
-        time.sleep(max(final_wait_time, 1))
-
-        self.last_request_start_time = datetime.now(tz=timezone.utc)
-        return self
-
-    def __exit__(self, exc_type, exc_val, exc_tb) -> None:
-        pass
-
-    def update_request_info(self, cost: int, remaining: int, reset_at: str) -> None:
-        self.last_query_cost = cost
-        self.remaining_points = remaining
-        self.reset_at = datetime.fromisoformat(reset_at.replace("Z", "+00:00"))
-
-
-rate_limiter = RateLimiter()
-
-
-discussions_query = """
-query Q($after: String, $category_id: ID) {
-  repository(name: "fastapi", owner: "fastapi") {
-    discussions(first: 30, after: $after, categoryId: $category_id) {
-      edges {
-        cursor
-        node {
-          number
-          author {
-            login
-            avatarUrl
-            url
-          }
-          createdAt
-          comments(first: 50) {
-            totalCount
-            nodes {
-              createdAt
-              author {
-                login
-                avatarUrl
-                url
-              }
-              isAnswer
-              replies(first: 10) {
-                totalCount
-                nodes {
-                  createdAt
-                  author {
-                    login
-                    avatarUrl
-                    url
-                  }
-                  minimizedReason
-                }
-              }
-              minimizedReason
-            }
-          }
-        }
-      }
-    }
-  }
-  rateLimit {
-    cost
-    remaining
-    resetAt
-  }
-}
-"""
-
-LowerStr = Annotated[
-    str, BeforeValidator(lambda v: v.lower() if isinstance(v, str) else v)
-]
-
-
-class Author(BaseModel):
-    login: str
-    avatarUrl: str | None = None
-    url: str | None = None
-
-
-class CommentsNode(BaseModel):
-    createdAt: datetime
-    author: Author | None = None
-    minimizedReason: LowerStr | None = None
-
-
-class Replies(BaseModel):
-    totalCount: int
-    nodes: list[CommentsNode]
-
-
-class DiscussionsCommentsNode(CommentsNode):
-    minimizedReason: LowerStr | None = None
-    replies: Replies
-
-
-class DiscussionsComments(BaseModel):
-    totalCount: int
-    nodes: list[DiscussionsCommentsNode]
-
-
-class DiscussionsNode(BaseModel):
-    number: int
-    author: Author | None = None
-    title: str | None = None
-    createdAt: datetime
-    comments: DiscussionsComments
-
-
-class DiscussionsEdge(BaseModel):
-    cursor: str
-    node: DiscussionsNode
-
-
-class Discussions(BaseModel):
-    edges: list[DiscussionsEdge]
-
-
-class DiscussionsRepository(BaseModel):
-    discussions: Discussions
-
-
-class DiscussionsResponseData(BaseModel):
-    repository: DiscussionsRepository
-
-
-class DiscussionsResponse(BaseModel):
-    data: DiscussionsResponseData
-
-
-class Settings(BaseSettings):
-    github_token: SecretStr
-    github_repository: str
-    httpx_timeout: int = 30
-    speed_multiplier: float = 1.0
-
-
-def get_graphql_response(
-    *,
-    settings: Settings,
-    query: str,
-    after: str | None = None,
-    category_id: str | None = None,
-) -> dict[str, Any]:
-    headers = {"Authorization": f"token {settings.github_token.get_secret_value()}"}
-    variables = {"after": after, "category_id": category_id}
-    response = httpx.post(
-        github_graphql_url,
-        headers=headers,
-        timeout=settings.httpx_timeout,
-        json={"query": query, "variables": variables, "operationName": "Q"},
-    )
-    if response.status_code != 200:
-        logging.error(
-            f"Response was not 200, after: {after}, category_id: {category_id}"
-        )
-        logging.error(response.text)
-        raise RuntimeError(response.text)
-    data = response.json()
-    if "errors" in data:
-        logging.error(f"Errors in response, after: {after}, category_id: {category_id}")
-        logging.error(data["errors"])
-        logging.error(response.text)
-        raise RuntimeError(response.text)
-    return data
-
-
-def get_graphql_question_discussion_edges(
-    *,
-    settings: Settings,
-    after: str | None = None,
-) -> list[DiscussionsEdge]:
-    with rate_limiter:
-        data = get_graphql_response(
-            settings=settings,
-            query=discussions_query,
-            after=after,
-            category_id=questions_category_id,
-        )
-
-    rate_limiter.update_request_info(
-        cost=data["data"]["rateLimit"]["cost"],
-        remaining=data["data"]["rateLimit"]["remaining"],
-        reset_at=data["data"]["rateLimit"]["resetAt"],
-    )
-    graphql_response = DiscussionsResponse.model_validate(data)
-    return graphql_response.data.repository.discussions.edges
-
-
-class DiscussionExpertsResults(BaseModel):
-    commenters: Counter[str]
-    last_month_commenters: Counter[str]
-    three_months_commenters: Counter[str]
-    six_months_commenters: Counter[str]
-    one_year_commenters: Counter[str]
-    authors: dict[str, Author]
-
-
-def get_discussion_nodes(settings: Settings) -> list[DiscussionsNode]:
-    discussion_nodes: list[DiscussionsNode] = []
-    discussion_edges = get_graphql_question_discussion_edges(settings=settings)
-
-    while discussion_edges:
-        for discussion_edge in discussion_edges:
-            discussion_nodes.append(discussion_edge.node)
-        last_edge = discussion_edges[-1]
-        discussion_edges = get_graphql_question_discussion_edges(
-            settings=settings, after=last_edge.cursor
-        )
-    return discussion_nodes
-
-
-def get_discussions_experts(
-    discussion_nodes: list[DiscussionsNode],
-) -> DiscussionExpertsResults:
-    commenters = Counter[str]()
-    last_month_commenters = Counter[str]()
-    three_months_commenters = Counter[str]()
-    six_months_commenters = Counter[str]()
-    one_year_commenters = Counter[str]()
-    authors: dict[str, Author] = {}
-
-    now = datetime.now(tz=timezone.utc)
-    one_month_ago = now - timedelta(days=30)
-    three_months_ago = now - timedelta(days=90)
-    six_months_ago = now - timedelta(days=180)
-    one_year_ago = now - timedelta(days=365)
-
-    for discussion in discussion_nodes:
-        discussion_author_name = None
-        if discussion.author:
-            authors[discussion.author.login] = discussion.author
-            discussion_author_name = discussion.author.login
-        discussion_commentors: dict[str, datetime] = {}
-        for comment in discussion.comments.nodes:
-            if (
-                comment.minimizedReason not in MINIMIZED_COMMENTS_REASONS_TO_EXCLUDE
-                and comment.author
-            ):
-                authors[comment.author.login] = comment.author
-                if comment.author.login != discussion_author_name:
-                    author_time = discussion_commentors.get(
-                        comment.author.login, comment.createdAt
-                    )
-                    discussion_commentors[comment.author.login] = max(
-                        author_time, comment.createdAt
-                    )
-            for reply in comment.replies.nodes:
-                if (
-                    reply.minimizedReason not in MINIMIZED_COMMENTS_REASONS_TO_EXCLUDE
-                    and reply.author
-                ):
-                    authors[reply.author.login] = reply.author
-                    if reply.author.login != discussion_author_name:
-                        author_time = discussion_commentors.get(
-                            reply.author.login, reply.createdAt
-                        )
-                        discussion_commentors[reply.author.login] = max(
-                            author_time, reply.createdAt
-                        )
-        for author_name, author_time in discussion_commentors.items():
-            commenters[author_name] += 1
-            if author_time > one_month_ago:
-                last_month_commenters[author_name] += 1
-            if author_time > three_months_ago:
-                three_months_commenters[author_name] += 1
-            if author_time > six_months_ago:
-                six_months_commenters[author_name] += 1
-            if author_time > one_year_ago:
-                one_year_commenters[author_name] += 1
-    discussion_experts_results = DiscussionExpertsResults(
-        authors=authors,
-        commenters=commenters,
-        last_month_commenters=last_month_commenters,
-        three_months_commenters=three_months_commenters,
-        six_months_commenters=six_months_commenters,
-        one_year_commenters=one_year_commenters,
-    )
-    return discussion_experts_results
-
-
-def get_top_users(
-    *,
-    counter: Counter[str],
-    authors: dict[str, Author],
-    skip_users: Container[str],
-    min_count: int = 2,
-) -> list[dict[str, Any]]:
-    users: list[dict[str, Any]] = []
-    for commenter, count in counter.most_common(50):
-        if commenter in skip_users:
-            continue
-        if count >= min_count:
-            author = authors[commenter]
-            users.append(
-                {
-                    "login": commenter,
-                    "count": count,
-                    "avatarUrl": author.avatarUrl,
-                    "url": author.url,
-                }
-            )
-    return users
-
-
-def get_users_to_write(
-    *,
-    counter: Counter[str],
-    authors: dict[str, Author],
-    min_count: int = 2,
-) -> list[dict[str, Any]]:
-    users: dict[str, Any] = {}
-    users_list: list[dict[str, Any]] = []
-    for user, count in counter.most_common(60):
-        if count >= min_count:
-            author = authors[user]
-            user_data = {
-                "login": user,
-                "count": count,
-                "avatarUrl": author.avatarUrl,
-                "url": author.url,
-            }
-            users[user] = user_data
-            users_list.append(user_data)
-    return users_list
-
-
-def update_content(*, content_path: Path, new_content: Any) -> bool:
-    old_content = content_path.read_text(encoding="utf-8")
-
-    new_content = yaml.dump(new_content, sort_keys=False, width=200, allow_unicode=True)
-    if old_content == new_content:
-        logging.info(f"The content hasn't changed for {content_path}")
-        return False
-    content_path.write_text(new_content, encoding="utf-8")
-    logging.info(f"Updated {content_path}")
-    return True
-
-
-def main() -> None:
-    logging.basicConfig(level=logging.INFO)
-    settings = Settings()  # ty: ignore[missing-argument]
-    logging.info(f"Using config: {settings.model_dump_json()}")
-    rate_limiter.speed_multiplier = settings.speed_multiplier
-    g = Github(settings.github_token.get_secret_value())
-    repo = g.get_repo(settings.github_repository)
-
-    discussion_nodes = get_discussion_nodes(settings=settings)
-    experts_results = get_discussions_experts(discussion_nodes=discussion_nodes)
-
-    authors = experts_results.authors
-    maintainers_logins = {"tiangolo"}
-    maintainers = []
-    for login in maintainers_logins:
-        user = authors[login]
-        maintainers.append(
-            {
-                "login": login,
-                "answers": experts_results.commenters[login],
-                "avatarUrl": user.avatarUrl,
-                "url": user.url,
-            }
-        )
-
-    experts = get_users_to_write(
-        counter=experts_results.commenters,
-        authors=authors,
-    )
-    last_month_experts = get_users_to_write(
-        counter=experts_results.last_month_commenters,
-        authors=authors,
-    )
-    three_months_experts = get_users_to_write(
-        counter=experts_results.three_months_commenters,
-        authors=authors,
-    )
-    six_months_experts = get_users_to_write(
-        counter=experts_results.six_months_commenters,
-        authors=authors,
-    )
-    one_year_experts = get_users_to_write(
-        counter=experts_results.one_year_commenters,
-        authors=authors,
-    )
-
-    people = {
-        "maintainers": maintainers,
-        "experts": experts,
-        "last_month_experts": last_month_experts,
-        "three_months_experts": three_months_experts,
-        "six_months_experts": six_months_experts,
-        "one_year_experts": one_year_experts,
-    }
-
-    # For local development
-    # people_path = Path("../docs/en/data/people.yml")
-    people_path = Path("./docs/en/data/people.yml")
-
-    updated = update_content(content_path=people_path, new_content=people)
-
-    if not updated:
-        logging.info("The data hasn't changed, finishing.")
-        return
-
-    logging.info("Setting up GitHub Actions git user")
-    subprocess.run(["git", "config", "user.name", "github-actions[bot]"], check=True)
-    subprocess.run(
-        ["git", "config", "user.email", "github-actions[bot]@users.noreply.github.com"],
-        check=True,
-    )
-    branch_name = f"fastapi-people-experts-{secrets.token_hex(4)}"
-    logging.info(f"Creating a new branch {branch_name}")
-    subprocess.run(["git", "checkout", "-b", branch_name], check=True)
-    logging.info("Adding updated file")
-    subprocess.run(["git", "add", str(people_path)], check=True)
-    logging.info("Committing updated file")
-    message = "👥 Update FastAPI People - Experts"
-    subprocess.run(["git", "commit", "-m", message], check=True)
-    logging.info("Pushing branch")
-    subprocess.run(["git", "push", "origin", branch_name], check=True)
-    logging.info("Creating PR")
-    pr = repo.create_pull(title=message, body=message, base="master", head=branch_name)
-    logging.info(f"Created PR: {pr.number}")
-    logging.info("Finished")
-
-
-if __name__ == "__main__":
-    main()