Browse Source

♻️ Refactor and move `scripts/notify_translations.py`, no need for a custom GitHub Action (#13270)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
pull/13271/head
Sebastián Ramírez 2 months ago
committed by GitHub
parent
commit
326fec16b9
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 7
      .github/actions/notify-translations/Dockerfile
  2. 10
      .github/actions/notify-translations/action.yml
  3. 20
      .github/workflows/notify-translations.yml
  4. 63
      scripts/notify_translations.py

7
.github/actions/notify-translations/Dockerfile

@ -1,7 +0,0 @@
FROM python:3.9
RUN pip install httpx PyGithub "pydantic==1.5.1" "pyyaml>=5.3.1,<6.0.0"
COPY ./app /app
CMD ["python", "/app/main.py"]

10
.github/actions/notify-translations/action.yml

@ -1,10 +0,0 @@
name: "Notify Translations"
description: "Notify in the issue for a translation when there's a new PR available"
author: "Sebastián Ramírez <[email protected]>"
inputs:
token:
description: 'Token, to read the GitHub API. Can be passed in using {{ secrets.GITHUB_TOKEN }}'
required: true
runs:
using: 'docker'
image: 'Dockerfile'

20
.github/workflows/notify-translations.yml

@ -15,15 +15,14 @@ on:
required: false required: false
default: 'false' default: 'false'
permissions:
discussions: write
env: env:
UV_SYSTEM_PYTHON: 1 UV_SYSTEM_PYTHON: 1
jobs: jobs:
notify-translations: job:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
discussions: write
steps: steps:
- name: Dump GitHub context - name: Dump GitHub context
env: env:
@ -42,12 +41,19 @@ jobs:
cache-dependency-glob: | cache-dependency-glob: |
requirements**.txt requirements**.txt
pyproject.toml pyproject.toml
- name: Install Dependencies
run: uv pip install -r requirements-github-actions.txt
# Allow debugging with tmate # Allow debugging with tmate
- name: Setup tmate session - name: Setup tmate session
uses: mxschmitt/action-tmate@v3 uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
with: with:
limit-access-to-actor: true limit-access-to-actor: true
- uses: ./.github/actions/notify-translations env:
with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
token: ${{ secrets.GITHUB_TOKEN }} - name: Notify Translations
run: python ./scripts/notify_translations.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NUMBER: ${{ github.event.inputs.number || null }}
DEBUG: ${{ github.event.inputs.debug_enabled || 'false' }}

63
.github/actions/notify-translations/app/main.py → scripts/notify_translations.py

@ -7,12 +7,13 @@ from typing import Any, Dict, List, Union, cast
import httpx import httpx
from github import Github from github import Github
from pydantic import BaseModel, BaseSettings, SecretStr from pydantic import BaseModel, SecretStr
from pydantic_settings import BaseSettings
awaiting_label = "awaiting-review" awaiting_label = "awaiting-review"
lang_all_label = "lang-all" lang_all_label = "lang-all"
approved_label = "approved-1" approved_label = "approved-1"
translations_path = Path(__file__).parent / "translations.yml"
github_graphql_url = "https://api.github.com/graphql" github_graphql_url = "https://api.github.com/graphql"
questions_translations_category_id = "DIC_kwDOCZduT84CT5P9" questions_translations_category_id = "DIC_kwDOCZduT84CT5P9"
@ -176,19 +177,20 @@ class AllDiscussionsResponse(BaseModel):
class Settings(BaseSettings): class Settings(BaseSettings):
github_repository: str github_repository: str
input_token: SecretStr github_token: SecretStr
github_event_path: Path github_event_path: Path
github_event_name: Union[str, None] = None github_event_name: Union[str, None] = None
httpx_timeout: int = 30 httpx_timeout: int = 30
input_debug: Union[bool, None] = False debug: Union[bool, None] = False
number: int | None = None
class PartialGitHubEventIssue(BaseModel): class PartialGitHubEventIssue(BaseModel):
number: int number: int | None = None
class PartialGitHubEvent(BaseModel): class PartialGitHubEvent(BaseModel):
pull_request: PartialGitHubEventIssue pull_request: PartialGitHubEventIssue | None = None
def get_graphql_response( def get_graphql_response(
@ -202,9 +204,7 @@ def get_graphql_response(
comment_id: Union[str, None] = None, comment_id: Union[str, None] = None,
body: Union[str, None] = None, body: Union[str, None] = None,
) -> Dict[str, Any]: ) -> Dict[str, Any]:
headers = {"Authorization": f"token {settings.input_token.get_secret_value()}"} headers = {"Authorization": f"token {settings.github_token.get_secret_value()}"}
# some fields are only used by one query, but GraphQL allows unused variables, so
# keep them here for simplicity
variables = { variables = {
"after": after, "after": after,
"category_id": category_id, "category_id": category_id,
@ -228,37 +228,40 @@ def get_graphql_response(
data = response.json() data = response.json()
if "errors" in data: if "errors" in data:
logging.error(f"Errors in response, after: {after}, category_id: {category_id}") logging.error(f"Errors in response, after: {after}, category_id: {category_id}")
logging.error(data["errors"])
logging.error(response.text) logging.error(response.text)
raise RuntimeError(response.text) raise RuntimeError(response.text)
return cast(Dict[str, Any], data) return cast(Dict[str, Any], data)
def get_graphql_translation_discussions(*, settings: Settings): def get_graphql_translation_discussions(
*, settings: Settings
) -> List[AllDiscussionsDiscussionNode]:
data = get_graphql_response( data = get_graphql_response(
settings=settings, settings=settings,
query=all_discussions_query, query=all_discussions_query,
category_id=questions_translations_category_id, category_id=questions_translations_category_id,
) )
graphql_response = AllDiscussionsResponse.parse_obj(data) graphql_response = AllDiscussionsResponse.model_validate(data)
return graphql_response.data.repository.discussions.nodes return graphql_response.data.repository.discussions.nodes
def get_graphql_translation_discussion_comments_edges( def get_graphql_translation_discussion_comments_edges(
*, settings: Settings, discussion_number: int, after: Union[str, None] = None *, settings: Settings, discussion_number: int, after: Union[str, None] = None
): ) -> List[CommentsEdge]:
data = get_graphql_response( data = get_graphql_response(
settings=settings, settings=settings,
query=translation_discussion_query, query=translation_discussion_query,
discussion_number=discussion_number, discussion_number=discussion_number,
after=after, after=after,
) )
graphql_response = CommentsResponse.parse_obj(data) graphql_response = CommentsResponse.model_validate(data)
return graphql_response.data.repository.discussion.comments.edges return graphql_response.data.repository.discussion.comments.edges
def get_graphql_translation_discussion_comments( def get_graphql_translation_discussion_comments(
*, settings: Settings, discussion_number: int *, settings: Settings, discussion_number: int
): ) -> list[Comment]:
comment_nodes: List[Comment] = [] comment_nodes: List[Comment] = []
discussion_edges = get_graphql_translation_discussion_comments_edges( discussion_edges = get_graphql_translation_discussion_comments_edges(
settings=settings, discussion_number=discussion_number settings=settings, discussion_number=discussion_number
@ -276,43 +279,49 @@ def get_graphql_translation_discussion_comments(
return comment_nodes return comment_nodes
def create_comment(*, settings: Settings, discussion_id: str, body: str): def create_comment(*, settings: Settings, discussion_id: str, body: str) -> Comment:
data = get_graphql_response( data = get_graphql_response(
settings=settings, settings=settings,
query=add_comment_mutation, query=add_comment_mutation,
discussion_id=discussion_id, discussion_id=discussion_id,
body=body, body=body,
) )
response = AddCommentResponse.parse_obj(data) response = AddCommentResponse.model_validate(data)
return response.data.addDiscussionComment.comment return response.data.addDiscussionComment.comment
def update_comment(*, settings: Settings, comment_id: str, body: str): def update_comment(*, settings: Settings, comment_id: str, body: str) -> Comment:
data = get_graphql_response( data = get_graphql_response(
settings=settings, settings=settings,
query=update_comment_mutation, query=update_comment_mutation,
comment_id=comment_id, comment_id=comment_id,
body=body, body=body,
) )
response = UpdateCommentResponse.parse_obj(data) response = UpdateCommentResponse.model_validate(data)
return response.data.updateDiscussionComment.comment return response.data.updateDiscussionComment.comment
if __name__ == "__main__": def main() -> None:
settings = Settings() settings = Settings()
if settings.input_debug: if settings.debug:
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
else: else:
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logging.debug(f"Using config: {settings.json()}") logging.debug(f"Using config: {settings.model_dump_json()}")
g = Github(settings.input_token.get_secret_value()) g = Github(settings.github_token.get_secret_value())
repo = g.get_repo(settings.github_repository) repo = g.get_repo(settings.github_repository)
if not settings.github_event_path.is_file(): if not settings.github_event_path.is_file():
raise RuntimeError( raise RuntimeError(
f"No github event file available at: {settings.github_event_path}" f"No github event file available at: {settings.github_event_path}"
) )
contents = settings.github_event_path.read_text() contents = settings.github_event_path.read_text()
github_event = PartialGitHubEvent.parse_raw(contents) github_event = PartialGitHubEvent.model_validate_json(contents)
logging.info(f"Using GitHub event: {github_event}")
number = (
github_event.pull_request and github_event.pull_request.number
) or settings.number
if number is None:
raise RuntimeError("No PR number available")
# Avoid race conditions with multiple labels # Avoid race conditions with multiple labels
sleep_time = random.random() * 10 # random number between 0 and 10 seconds sleep_time = random.random() * 10 # random number between 0 and 10 seconds
@ -323,8 +332,8 @@ if __name__ == "__main__":
time.sleep(sleep_time) time.sleep(sleep_time)
# Get PR # Get PR
logging.debug(f"Processing PR: #{github_event.pull_request.number}") logging.debug(f"Processing PR: #{number}")
pr = repo.get_pull(github_event.pull_request.number) pr = repo.get_pull(number)
label_strs = {label.name for label in pr.get_labels()} label_strs = {label.name for label in pr.get_labels()}
langs = [] langs = []
for label in label_strs: for label in label_strs:
@ -415,3 +424,7 @@ if __name__ == "__main__":
f"There doesn't seem to be anything to be done about PR #{pr.number}" f"There doesn't seem to be anything to be done about PR #{pr.number}"
) )
logging.info("Finished") logging.info("Finished")
if __name__ == "__main__":
main()
Loading…
Cancel
Save