From: Sebastián Ramírez Date: Thu, 16 Jul 2026 18:38:36 +0000 (+0200) Subject: 🔥 Remove now-obsolete scripts to generate data for FastAPI People (#16016) X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=7f05e26d0898e8e1491df331c2dd4368f5e35054;p=thirdparty%2Ffastapi%2Ffastapi.git 🔥 Remove now-obsolete scripts to generate data for FastAPI People (#16016) --- diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml deleted file mode 100644 index 04d82c34a7..0000000000 --- a/.github/workflows/contributors.yml +++ /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 index a7dd8a4fb1..0000000000 --- a/.github/workflows/people.yml +++ /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 index 5e9d72d643..0000000000 --- a/scripts/contributors.py +++ /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 index 72b591367c..0000000000 --- a/scripts/people.py +++ /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()