Skip to content

Commit

Permalink
Add module for checking news file in a specific pull request
Browse files Browse the repository at this point in the history
  • Loading branch information
Shivansh-007 committed Oct 18, 2021
1 parent aa8bc6f commit 99dbd36
Show file tree
Hide file tree
Showing 7 changed files with 287 additions and 1 deletion.
100 changes: 99 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ mkdocs-material = ">=7.1.9,<8.0.0"
mkdocs-markdownextradata-plugin = ">=0.1.7,<0.2.0"
click = "^8.0.3"
Jinja2 = "^3.0.2"
gidgethub = "^5.0.1"

[build-system]
requires = ["poetry-core>=1.0.0"]
Expand Down
Binary file modified scripts/news/__pycache__/__main__.cpython-39.pyc
Binary file not shown.
Binary file modified scripts/news/__pycache__/utils.cpython-39.pyc
Binary file not shown.
21 changes: 21 additions & 0 deletions scripts/news/check_news_workflow/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""
Module for checking if the user has made a news file for the specific pull request.
Slight modifications have been made to support our project.
Original Source: https://github.com/python/bedevere/blob/master/LICENSE
Copyright 2017 The Python Software Foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
84 changes: 84 additions & 0 deletions scripts/news/check_news_workflow/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import functools
import pathlib
import re
from typing import Any, Dict

import gidgethub.routing
from gidgethub import sansio
from gidgethub.abc import GitHubAPI

from ..utils import load_toml_config
from . import utils


router = gidgethub.routing.Router()
create_status = functools.partial(utils.create_status, "Check News")

CONFIG = load_toml_config()
SECTIONS = [_type for _type, _ in CONFIG.get("types").items()]
CHANGELOG_IT_URL = "TODO: URL TO CHANGE-LOGGING PR SECTION IN README"
FILENAME_RE = re.compile(
r"^\d{4}-(0?[1-9]|1[012])-(0?[1-9]|[12][0-9]|3[01])$\." # match `yyyy-mm-dd` or `yyyy-m-d`
r"pr-\d+(?:,\d+)*\." # Issue number(s)
fr"({'|'.join(SECTIONS)})\." # Section type
r"[A-Za-z0-9_=-]+\." # Nonce (URL-safe base64)
r"md", # File extension"""
re.VERBOSE,
)

SKIP_LABEL_STATUS = create_status(utils.StatusState.SUCCESS, description='"skip changelog" label found')


async def check_news(gh: GitHubAPI, pull_request: Dict[str, Any]) -> None:
"""
Check for a news entry.
The routing is handled through the filepath module.
"""
files = await utils.files_for_pr(gh, pull_request)
in_next_dir = file_found = False

for file in files:
if not utils.is_news_dir(file["file_name"]):
continue
in_next_dir = True
file_path = pathlib.PurePath(file["file_name"])
if len(file_path.parts) != 3: # news, next, <entry>
continue
file_found = True
if FILENAME_RE.match(file_path.name) and len(file["patch"]) >= 1:
status = create_status(
utils.StatusState.SUCCESS, description=f"News entry found in {utils.NEWS_NEXT_DIR}"
)
break
else:
issue = await utils.issue_for_pr(gh, pull_request)
if utils.skip(issue):
status = SKIP_LABEL_STATUS
else:
if not in_next_dir:
description = f'No news entry in {utils.NEWS_NEXT_DIR} or "skip news" label found'
elif not file_found:
description = "News entry not in an appropriate directory"
else:
description = "News entry file name incorrectly formatted"
status = create_status(
utils.StatusState.FAILURE, description=description, target_url=CHANGELOG_IT_URL
)

await gh.post(pull_request["statuses_url"], data=status)


@router.register("pull_request", action="labeled")
async def label_added(event: sansio.Event, gh: GitHubAPI, *args, **kwargs) -> None:
if utils.label_name(event.data) == utils.SKIP_NEWS_LABEL:
await utils.post_status(gh, event, SKIP_LABEL_STATUS)


@router.register("pull_request", action="unlabeled")
async def label_removed(event: sansio.Event, gh: GitHubAPI, *args, **kwargs) -> None:
if utils.no_labels(event.data):
return
elif utils.label_name(event.data) == utils.SKIP_NEWS_LABEL:
pull_request = event.data["pull_request"]
await check_news(gh, pull_request)
82 changes: 82 additions & 0 deletions scripts/news/check_news_workflow/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import enum
import sys
from typing import Any, Dict, List

from gidgethub.abc import GitHubAPI


NEWS_NEXT_DIR = "news/next/"
SKIP_NEWS_LABEL = "skip changelog"


class StatusState(enum.Enum):
SUCCESS = "success"
ERROR = "error"
FAILURE = "failure"


def create_status(
context: str, state: StatusState, *, description: str = None, target_url: str = None
) -> dict:
"""
Create the data for a status.
The argument order is such that you can use functools.partial() to set the
context to avoid repeatedly specifying it throughout a module.
"""
status = {
"context": context,
"state": state.value,
}
if description is not None:
status["description"] = description
if target_url is not None:
status["target_url"] = target_url

return status


async def post_status(gh: GitHubAPI, event, status: Any) -> None:
"""Post a status in reaction to an event."""
await gh.post(event.data["pull_request"]["statuses_url"], data=status)


def skip(issue: Dict[str, Any]) -> bool:
"""See if an issue has a "SKIP_NEWS_LABEL" label."""
return SKIP_NEWS_LABEL in {label_data["name"] for label_data in issue["labels"]}


def label_name(event_data: Dict[str, Any]) -> str:
"""Get the label name from a label-related webhook event."""
return event_data["label"]["name"]


async def files_for_pr(gh: GitHubAPI, pull_request: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Get files for a pull request."""
# For some unknown reason there isn't any files URL in a pull request payload.
files_url = f'{pull_request["url"]}/files'
data = []
async for filedata in gh.getiter(files_url):
data.append({"file_name": filedata["filename"], "patch": filedata.get("patch", "")})
return data


async def issue_for_pr(gh: GitHubAPI, pull_request: Dict[str, Any]) -> Any:
"""Get the issue data for a pull request."""
return await gh.getitem(pull_request["issue_url"])


def is_news_dir(filename: str) -> bool:
"""Return True if file is in the News directory."""
return filename.startswith(NEWS_NEXT_DIR)


def no_labels(event_data: Dict[str, Any]) -> bool:
if "label" not in event_data:
print(
"no 'label' key in payload; " "'unlabeled' event triggered by label deletion?",
file=sys.stderr,
)
return True
else:
return False

0 comments on commit 99dbd36

Please sign in to comment.