mirror of
https://github.com/fastapi/fastapi.git
synced 2025-12-26 07:40:57 -05:00
Compare commits
12 Commits
feature/py
...
debug
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4234f2d6f5 | ||
|
|
81e10fecef | ||
|
|
13dc329207 | ||
|
|
43dccdda9d | ||
|
|
1a29cb841d | ||
|
|
343d0e6221 | ||
|
|
bf6c96f417 | ||
|
|
7a913806d6 | ||
|
|
56090f4db8 | ||
|
|
206633f5a0 | ||
|
|
1e89b4f2c3 | ||
|
|
e55f0e0688 |
57
.github/workflows/contributors.yml
vendored
Normal file
57
.github/workflows/contributors.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
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"
|
||||||
|
# TODO: fix this
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
env:
|
||||||
|
UV_SYSTEM_PYTHON: 1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
job:
|
||||||
|
if: github.repository_owner == 'fastapi'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- name: Dump GitHub context
|
||||||
|
env:
|
||||||
|
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||||
|
run: echo "$GITHUB_CONTEXT"
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.11"
|
||||||
|
- name: Setup uv
|
||||||
|
uses: astral-sh/setup-uv@v5
|
||||||
|
with:
|
||||||
|
version: "0.4.15"
|
||||||
|
enable-cache: true
|
||||||
|
cache-dependency-glob: |
|
||||||
|
requirements**.txt
|
||||||
|
pyproject.toml
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: uv pip install -r requirements-github-actions.txt
|
||||||
|
# Allow debugging with tmate
|
||||||
|
- name: Setup tmate session
|
||||||
|
uses: mxschmitt/action-tmate@v3
|
||||||
|
# TODO: fix this
|
||||||
|
# if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
|
||||||
|
with:
|
||||||
|
limit-access-to-actor: true
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: FastAPI People Contributors
|
||||||
|
run: python ./scripts/contributors.py
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
0
docs/en/data/contributors.yml
Normal file
0
docs/en/data/contributors.yml
Normal file
0
docs/en/data/translation_reviewers.yml
Normal file
0
docs/en/data/translation_reviewers.yml
Normal file
0
docs/en/data/translators.yml
Normal file
0
docs/en/data/translators.yml
Normal file
@@ -13,15 +13,13 @@ Hey! 👋
|
|||||||
|
|
||||||
This is me:
|
This is me:
|
||||||
|
|
||||||
{% if people %}
|
|
||||||
<div class="user-list user-list-center">
|
<div class="user-list user-list-center">
|
||||||
{% for user in people.maintainers %}
|
{% for user in people.maintainers %}
|
||||||
|
|
||||||
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Answers: {{ user.answers }}</div><div class="count">Pull Requests: {{ user.prs }}</div></div>
|
<div class="user"><a href="{{ contributors.tiangolo.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ contributors.tiangolo.avatarUrl }}"/></div><div class="title">@{{ contributors.tiangolo.login }}</div></a> <div class="count">Answers: {{ user.answers }}</div><div class="count">Pull Requests: {{ contributors.tiangolo.count }}</div></div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
I'm the creator of **FastAPI**. You can read more about that in [Help FastAPI - Get Help - Connect with the author](help-fastapi.md#connect-with-the-author){.internal-link target=_blank}.
|
I'm the creator of **FastAPI**. You can read more about that in [Help FastAPI - Get Help - Connect with the author](help-fastapi.md#connect-with-the-author){.internal-link target=_blank}.
|
||||||
|
|
||||||
@@ -84,7 +82,6 @@ You can see the **FastAPI Experts** for:
|
|||||||
|
|
||||||
These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last month. 🤓
|
These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last month. 🤓
|
||||||
|
|
||||||
{% if people %}
|
|
||||||
<div class="user-list user-list-center">
|
<div class="user-list user-list-center">
|
||||||
{% for user in people.last_month_experts[:10] %}
|
{% for user in people.last_month_experts[:10] %}
|
||||||
|
|
||||||
@@ -92,13 +89,11 @@ These are the users that have been [helping others the most with questions in Gi
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
### FastAPI Experts - 3 Months
|
### FastAPI Experts - 3 Months
|
||||||
|
|
||||||
These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last 3 months. 😎
|
These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last 3 months. 😎
|
||||||
|
|
||||||
{% if people %}
|
|
||||||
<div class="user-list user-list-center">
|
<div class="user-list user-list-center">
|
||||||
{% for user in people.three_months_experts[:10] %}
|
{% for user in people.three_months_experts[:10] %}
|
||||||
|
|
||||||
@@ -106,13 +101,11 @@ These are the users that have been [helping others the most with questions in Gi
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
### FastAPI Experts - 6 Months
|
### FastAPI Experts - 6 Months
|
||||||
|
|
||||||
These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last 6 months. 🧐
|
These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last 6 months. 🧐
|
||||||
|
|
||||||
{% if people %}
|
|
||||||
<div class="user-list user-list-center">
|
<div class="user-list user-list-center">
|
||||||
{% for user in people.six_months_experts[:10] %}
|
{% for user in people.six_months_experts[:10] %}
|
||||||
|
|
||||||
@@ -120,13 +113,11 @@ These are the users that have been [helping others the most with questions in Gi
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
### FastAPI Experts - 1 Year
|
### FastAPI Experts - 1 Year
|
||||||
|
|
||||||
These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last year. 🧑🔬
|
These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last year. 🧑🔬
|
||||||
|
|
||||||
{% if people %}
|
|
||||||
<div class="user-list user-list-center">
|
<div class="user-list user-list-center">
|
||||||
{% for user in people.one_year_experts[:20] %}
|
{% for user in people.one_year_experts[:20] %}
|
||||||
|
|
||||||
@@ -134,7 +125,6 @@ These are the users that have been [helping others the most with questions in Gi
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
### FastAPI Experts - All Time
|
### FastAPI Experts - All Time
|
||||||
|
|
||||||
@@ -142,7 +132,6 @@ Here are the all time **FastAPI Experts**. 🤓🤯
|
|||||||
|
|
||||||
These are the users that have [helped others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} through *all time*. 🧙
|
These are the users that have [helped others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} through *all time*. 🧙
|
||||||
|
|
||||||
{% if people %}
|
|
||||||
<div class="user-list user-list-center">
|
<div class="user-list user-list-center">
|
||||||
{% for user in people.experts[:50] %}
|
{% for user in people.experts[:50] %}
|
||||||
|
|
||||||
@@ -150,7 +139,6 @@ These are the users that have [helped others the most with questions in GitHub](
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
## Top Contributors
|
## Top Contributors
|
||||||
|
|
||||||
@@ -158,19 +146,42 @@ Here are the **Top Contributors**. 👷
|
|||||||
|
|
||||||
These users have [created the most Pull Requests](help-fastapi.md#create-a-pull-request){.internal-link target=_blank} that have been *merged*.
|
These users have [created the most Pull Requests](help-fastapi.md#create-a-pull-request){.internal-link target=_blank} that have been *merged*.
|
||||||
|
|
||||||
They have contributed source code, documentation, translations, etc. 📦
|
They have contributed source code, documentation, etc. 📦
|
||||||
|
|
||||||
{% if people %}
|
|
||||||
<div class="user-list user-list-center">
|
<div class="user-list user-list-center">
|
||||||
{% for user in people.top_contributors[:50] %}
|
{% for user in (contributors.values() | list)[:50] %}
|
||||||
|
|
||||||
|
{% if user.login not in skip_users %}
|
||||||
|
|
||||||
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Pull Requests: {{ user.count }}</div></div>
|
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Pull Requests: {{ user.count }}</div></div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
There are hundreds of other contributors, you can see them all in the <a href="https://github.com/fastapi/fastapi/graphs/contributors" class="external-link" target="_blank">FastAPI GitHub Contributors page</a>. 👷
|
||||||
|
|
||||||
|
## Top Translators
|
||||||
|
|
||||||
|
These are the **Top Translators**. 🌐
|
||||||
|
|
||||||
|
These users have created the most Pull Requests with [translations to other languages](contributing.md#translations){.internal-link target=_blank} that have been *merged*.
|
||||||
|
|
||||||
|
<div class="user-list user-list-center">
|
||||||
|
|
||||||
|
{% for user in (translators.values() | list)[:50] %}
|
||||||
|
|
||||||
|
{% if user.login not in skip_users %}
|
||||||
|
|
||||||
|
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Translations: {{ user.count }}</div></div>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
There are many other contributors (more than a hundred), you can see them all in the <a href="https://github.com/fastapi/fastapi/graphs/contributors" class="external-link" target="_blank">FastAPI GitHub Contributors page</a>. 👷
|
{% endfor %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
## Top Translation Reviewers
|
## Top Translation Reviewers
|
||||||
|
|
||||||
@@ -178,15 +189,18 @@ These users are the **Top Translation Reviewers**. 🕵️
|
|||||||
|
|
||||||
I only speak a few languages (and not very well 😅). So, the reviewers are the ones that have the [**power to approve translations**](contributing.md#translations){.internal-link target=_blank} of the documentation. Without them, there wouldn't be documentation in several other languages.
|
I only speak a few languages (and not very well 😅). So, the reviewers are the ones that have the [**power to approve translations**](contributing.md#translations){.internal-link target=_blank} of the documentation. Without them, there wouldn't be documentation in several other languages.
|
||||||
|
|
||||||
{% if people %}
|
|
||||||
<div class="user-list user-list-center">
|
<div class="user-list user-list-center">
|
||||||
{% for user in people.top_translations_reviewers[:50] %}
|
{% for user in (translation_reviewers.values() | list)[:50] %}
|
||||||
|
|
||||||
|
{% if user.login not in skip_users %}
|
||||||
|
|
||||||
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Reviews: {{ user.count }}</div></div>
|
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Reviews: {{ user.count }}</div></div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
@@ -251,7 +265,7 @@ The main intention of this page is to highlight the effort of the community to h
|
|||||||
|
|
||||||
Especially including efforts that are normally less visible, and in many cases more arduous, like helping others with questions and reviewing Pull Requests with translations.
|
Especially including efforts that are normally less visible, and in many cases more arduous, like helping others with questions and reviewing Pull Requests with translations.
|
||||||
|
|
||||||
The data is calculated each month, you can read the <a href="https://github.com/fastapi/fastapi/blob/master/.github/actions/people/app/main.py" class="external-link" target="_blank">source code here</a>.
|
The data is calculated each month, you can read the <a href="https://github.com/fastapi/fastapi/blob/master/scripts/" class="external-link" target="_blank">source code here</a>.
|
||||||
|
|
||||||
Here I'm also highlighting contributions from sponsors.
|
Here I'm also highlighting contributions from sponsors.
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,10 @@ plugins:
|
|||||||
- external_links: ../en/data/external_links.yml
|
- external_links: ../en/data/external_links.yml
|
||||||
- github_sponsors: ../en/data/github_sponsors.yml
|
- github_sponsors: ../en/data/github_sponsors.yml
|
||||||
- people: ../en/data/people.yml
|
- people: ../en/data/people.yml
|
||||||
|
- contributors: ../en/data/contributors.yml
|
||||||
|
- translators: ../en/data/translators.yml
|
||||||
|
- translation_reviewers: ../en/data/translation_reviewers.yml
|
||||||
|
- skip_users: ../en/data/skip_users.yml
|
||||||
- members: ../en/data/members.yml
|
- members: ../en/data/members.yml
|
||||||
- sponsors_badge: ../en/data/sponsors_badge.yml
|
- sponsors_badge: ../en/data/sponsors_badge.yml
|
||||||
- sponsors: ../en/data/sponsors.yml
|
- sponsors: ../en/data/sponsors.yml
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ PyGithub>=2.3.0,<3.0.0
|
|||||||
pydantic>=2.5.3,<3.0.0
|
pydantic>=2.5.3,<3.0.0
|
||||||
pydantic-settings>=2.1.0,<3.0.0
|
pydantic-settings>=2.1.0,<3.0.0
|
||||||
httpx>=0.27.0,<0.28.0
|
httpx>=0.27.0,<0.28.0
|
||||||
|
pyyaml >=5.3.1,<7.0.0
|
||||||
smokeshow
|
smokeshow
|
||||||
|
|||||||
314
scripts/contributors.py
Normal file
314
scripts/contributors.py
Normal file
@@ -0,0 +1,314 @@
|
|||||||
|
import logging
|
||||||
|
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()
|
||||||
|
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"], check=True)
|
||||||
|
subprocess.run(
|
||||||
|
["git", "config", "user.email", "github-actions@github.com"], check=True
|
||||||
|
)
|
||||||
|
branch_name = "fastapi-people-contributors"
|
||||||
|
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()
|
||||||
Reference in New Issue
Block a user