Skip to content

Commit

Permalink
big_refactoring (#258)
Browse files Browse the repository at this point in the history
* refactor(PullRequestManager): new way

* style: format code with Black, isort and Ruff Formatter

This commit fixes the style issues introduced in 0b7c254 according to the output
from Black, isort and Ruff Formatter.

Details: #258

* refactor(PullRequestManager): update pull requests

* style: format code with Black, isort and Ruff Formatter

This commit fixes the style issues introduced in a1de8c8 according to the output
from Black, isort and Ruff Formatter.

Details: #258

* final

* style: format code with Black, isort and Ruff Formatter

This commit fixes the style issues introduced in f7f9b68 according to the output
from Black, isort and Ruff Formatter.

Details: #258

* Update requirements.txt to use exact package versions

The requirements.txt file has been updated to use exact versions of packages instead of specifying a maximum version. This is to ensure consistency and avoid potential issues caused by updated versions of these packages.

[release:minor]

* Optimize and clean up code across various modules

The commit features a series of optimizations and code cleanups across the solution. Extraneous imports were removed, conditional statements were streamlined, and method calls were simplified. Additionally, library version dependencies were updated in the requirements file. Lastly, irrelevant comments and unnecessary white space were eliminated for clarity.

---------

Co-authored-by: Heitor Polidoro <[email protected]>
Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Jun 10, 2024
1 parent 9e2ad0f commit 173449c
Show file tree
Hide file tree
Showing 28 changed files with 989 additions and 821 deletions.
33 changes: 7 additions & 26 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,20 @@
import os
import sys
from multiprocessing import Process
from typing import NoReturn

import markdown
import sentry_sdk
from flask import Flask, Response, jsonify, render_template, request
from flask.cli import load_dotenv
from githubapp import Config, webhook_handler
from githubapp.events import (
CheckSuiteCompletedEvent,
CheckSuiteRequestedEvent,
CheckSuiteRerequestedEvent,
IssueClosedEvent,
IssueEditedEvent,
IssueOpenedEvent,
IssuesEvent,
)
from githubapp.events.issues import IssueClosedEvent, IssuesEvent

from config import default_configs
from src.helpers import request_helper
Expand All @@ -34,7 +33,7 @@
logger = logging.getLogger(__name__)


def sentry_init() -> NoReturn: # pragma: no cover
def sentry_init() -> None: # pragma: no cover
"""Initialize sentry only if SENTRY_DSN is present"""
if sentry_dsn := os.getenv("SENTRY_DSN"):
# Initialize Sentry SDK for error logging
Expand All @@ -53,17 +52,15 @@ def sentry_init() -> NoReturn: # pragma: no cover

app = Flask(__name__)
sentry_init()
webhook_handler.handle_with_flask(
app, use_default_index=False, config_file=".bartholomew.yaml"
)
webhook_handler.handle_with_flask(app, use_default_index=False, config_file=".bartholomew.yaml")

load_dotenv()
default_configs()


@webhook_handler.add_handler(CheckSuiteRequestedEvent)
@webhook_handler.add_handler(CheckSuiteRerequestedEvent)
def handle_check_suite_requested(event: CheckSuiteRequestedEvent) -> NoReturn:
def handle_check_suite_requested(event: CheckSuiteRequestedEvent) -> None:
"""
handle the Check Suite Request and Rerequest events
Calling the Pull Request manager to:
Expand All @@ -74,26 +71,12 @@ def handle_check_suite_requested(event: CheckSuiteRequestedEvent) -> NoReturn:
"""
pull_request_manager.manage(event)
release_manager.manage(event)
pull_request_manager.auto_approve(event)


@webhook_handler.add_handler(CheckSuiteCompletedEvent)
def handle_check_suite_completed(event: CheckSuiteCompletedEvent) -> NoReturn:
"""
handle the Check Suite Request and Rerequest events
Calling the Pull Request manager to:
- Create Pull Request
- Enable auto merge
- Update Pull Requests
- Auto approve Pull Requests
"""
pull_request_manager.auto_update_pull_requests(event)


@webhook_handler.add_handler(IssueOpenedEvent)
@webhook_handler.add_handler(IssueEditedEvent)
@webhook_handler.add_handler(IssueClosedEvent)
def handle_issue(event: IssuesEvent) -> NoReturn:
def handle_issue(event: IssuesEvent) -> None:
"""
handle the Issues Open, Edit and Close events
Calling the Issue Manager to:
Expand All @@ -118,9 +101,7 @@ def process_jobs_endpoint(issue_url: str = None) -> tuple[Response, int]:
if issue_job := next(iter(IssueJobService.filter(issue_url=issue_url)), None):
if process.is_alive():
IssueJobService.update(issue_job, issue_job_status=IssueJobStatus.PENDING)
request_helper.make_thread_request(
request_helper.get_request_url("process_jobs_endpoint"), issue_url
)
request_helper.make_thread_request(request_helper.get_request_url("process_jobs_endpoint"), issue_url)
process.terminate()
return jsonify({"status": issue_job.issue_job_status.value}), 200
return jsonify({"error": f"IssueJob for {issue_url=} not found"}), 404
Expand Down
2 changes: 1 addition & 1 deletion config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from githubapp import Config


def default_configs() -> NoReturn:
def default_configs() -> None:
"""Create the default configs"""
Config.BOT_NAME = "bartholomew-smith[bot]"
Config.TIMEOUT = "8"
Expand Down
27 changes: 0 additions & 27 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,30 +1,3 @@
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

[project]
name = "Bartholomew \"The Butler\" Smith"
authors = [
{ name="Heitor Luis Polidoro" },
]
description = "Package to help creates Github Apps."
readme = "README.md"
requires-python = ">=3.9"
keywords = []
license = { text = "MIT" }
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
dynamic = ["version", "dependencies"]

[project.urls]
"Homepage" = "https://github.com/heitorpolidoro/bartholomew-smith"

[tool.setuptools.dynamic]
version = {attr = ".__version__"}
dependencies = {file = ["requirements.txt"]}

[tool.coverage.run]
source = ["."]
Expand Down
4 changes: 2 additions & 2 deletions requirements.test.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
-r requirements.txt
pytest==7.4.4
pytest-cov==4.1.0
pytest==8.2.1
pytest-cov==5.0.0
16 changes: 8 additions & 8 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
github-app-handler<1
sentry-sdk<3
flask<4
PyGithub<3
python-dotenv<2
pymdown-extensions<11
boto3<2
pydantic<3
github-app-handler==0.28.4
PyGithub==2.3.0
sentry-sdk==2.5.1
flask==3.0.3
pymdown-extensions==10.8.1
boto3==1.34.123
pydantic==2.7.3
cachetools==5.3.3
20 changes: 6 additions & 14 deletions src/helpers/db_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class MetaBaseModelService(type):
This metaclass manages table and resource access for the service class.
"""

def __init__(cls: type["BaseModelService"], *args) -> NoReturn:
def __init__(cls: type["BaseModelService"], *args) -> None:
super().__init__(*args)
cls._resource = None
cls._table = None
Expand Down Expand Up @@ -142,10 +142,7 @@ def filter(cls, **kwargs) -> list[T]:
err.response["Error"]["Message"],
)
raise
return [
cls.clazz(**item)
for item in sorted(response["Items"], key=lambda item: item["created_at"])
]
return [cls.clazz(**item) for item in sorted(response["Items"], key=lambda item: item["created_at"])]

@classmethod
def create_table(cls) -> ServiceResource:
Expand All @@ -169,9 +166,7 @@ def create_table(cls) -> ServiceResource:
python_type = python_type.__args__[0]
elif issubclass(python_type, Enum):
python_type = str
attribute_definitions.append(
{"AttributeName": attr_name, "AttributeType": type_map[python_type]}
)
attribute_definitions.append({"AttributeName": attr_name, "AttributeType": type_map[python_type]})
table = cls.resource.create_table(
TableName=cls.table_name,
KeySchema=dy_key_schema,
Expand Down Expand Up @@ -199,14 +194,14 @@ def insert_one(cls, item: T) -> T:
return item

@classmethod
def insert_many(cls, items: list["BaseModel"]) -> NoReturn:
def insert_many(cls, items: list["BaseModel"]) -> None:
"""Insert a list of items in the table"""
with cls.table.batch_writer() as writer:
for item in items:
writer.put_item(Item=item.dynamo_dict())

@classmethod
def update(cls, item: "BaseModel", **kwargs) -> NoReturn:
def update(cls, item: "BaseModel", **kwargs) -> None:
"""Update an item in the table"""
dy_key = {}
key_schema = cls.clazz.key_schema
Expand Down Expand Up @@ -242,10 +237,7 @@ class BaseModel(PydanticBaseModel):

def dynamo_dict(self) -> dict[str, Any]:
"""Returns a dict that dynamo will understand"""
return {
k: v.value if isinstance(v, Enum) else v
for k, v in self.model_dump().items()
}
return {k: v.value if isinstance(v, Enum) else v for k, v in self.model_dump().items()}

def __hash__(self) -> int:
"""Return the hash of this item"""
Expand Down
20 changes: 20 additions & 0 deletions src/helpers/exception_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Module to help with GithubExceptions"""

from github import GithubException


def extract_message_from_error(error: dict[str, str]) -> str:
"""Extract the message from error"""
if message := error.get("message"):
return message

if (field := error.get("field")) and (code := error.get("code")):
return f"{field} {code}"

return str(error)


def extract_github_error(exception: GithubException) -> str:
"""'Extract the message from GithubException"""
data = exception.data
return extract_message_from_error(data.get("errors")[0])
4 changes: 1 addition & 3 deletions src/helpers/issue_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ def handle_issue_state(checked: bool, task_issue: Issue) -> bool:
return False


def update_issue_comment_status(
issue: Issue, comment: str, issue_comment_id: Optional[int] = None
) -> IssueComment:
def update_issue_comment_status(issue: Issue, comment: str, issue_comment_id: Optional[int] = None) -> IssueComment:
"""Update a github issue comment. If `issue_commend_id` is None, create a new github issue comment"""
if issue_comment_id:
issue_comment = issue.get_comment(issue_comment_id)
Expand Down
102 changes: 41 additions & 61 deletions src/helpers/pull_request_helper.py
Original file line number Diff line number Diff line change
@@ -1,92 +1,74 @@
"""Method to helps with Github PullRequests"""
"""Method to helps with GitHub PullRequests"""

import logging
from typing import NoReturn, Optional, Union
from typing import Optional

import github
from cachetools import Cache
from github.PullRequest import PullRequest
from github.Repository import Repository
from githubapp import Config

from src.helpers import repository_helper

logger = logging.getLogger(__name__)
cache = Cache(10)


def get_existing_pull_request(
repository: Repository, head: str
) -> Optional[PullRequest]:
def get_existing_pull_request(repository: Repository, branch: str) -> Optional[PullRequest]:
"""
Returns an existing PR if it exists.
:param repository: The Repository to get the PR from.
:param head: The branch to check for an existing PR.
:param branch: The branch to check for an existing PR.
:return: Exists PR or None.
"""
return next(iter(repository.get_pulls(state="open", head=head)), None)
key = f"{repository.owner.login}:{branch}"
if pull_request := cache.get(key):
return pull_request
return next(iter(repository.get_pulls(state="open", head=key)), None)


def create_pull_request(
repository: Repository,
branch: str,
title: Optional[str] = None,
body: Optional[str] = None,
) -> Optional[Union[PullRequest, str]]:
def create_pull_request(repository: Repository, branch: str, title: str = None, body: str = None) -> None:
"""
Creates a PR from the default branch to the given branch.
If the branch name match an issue-9999 pattern, the title and the body of the PR will be generated using
the information from the issue
:param repository: The Repository to create the PR in.
:param branch: The head branch to create the Pull Request
:param title: The title of the Pull Request
:param body: The body of the Pull Request
:return: Created PR or None.
:raises: GithubException if and error occurs, except if the error is "No commits between 'master' and 'branch'"
in that case ignores the exception, and it returns None.
Create a pull request in the given repository.
:param repository: The repository object where the pull request will be created.
:type repository: Repository
:param branch: The name of the branch for the pull request.
:type branch: str
:param title: The title of the pull request. If not provided, the branch name will be used.
:type title: str, optional
:param body: The description or body of the pull request. If not provided, a default message will be used.
:type body: str, optional
:return: None
"""
try:
return repository.create_pull(
repository.default_branch,
branch,
title=title or branch,
body=body or "Pull Request automatically created",
draft=False,
)
except github.GithubException as ghe:
possible_errors = [
f"No commits between {repository.default_branch} and {branch}",
f"The {branch} branch has no history in common with main",
]
for error in ghe.data["errors"]:
message = error.get("message")
if message in possible_errors:
logger.warning(message)
return message

raise
pull_request = repository.create_pull(
repository.default_branch,
branch,
title=title or branch,
body=body or "Pull Request automatically created",
draft=False,
)
cache[f"{repository.owner.login}:{branch}"] = pull_request


def update_pull_requests(repository: Repository, base_branch: str) -> NoReturn:
def update_pull_requests(repository: Repository, base_branch: str) -> list[PullRequest]:
"""Updates all the pull requests in the given branch if is updatable."""
updated_pull_requests = []
for pull_request in repository.get_pulls(state="open", base=base_branch):
print(pull_request.mergeable_state)
if pull_request.mergeable_state == "behind":
pull_request.update_branch()
if pull_request.mergeable_state == "behind" and pull_request.update_branch():
updated_pull_requests.append(pull_request)
return updated_pull_requests


def approve(
auto_approve_pat: str, repository: Repository, pull_request: PullRequest
) -> None:
def approve(auto_approve_pat: str, repository: Repository, pull_request: PullRequest) -> None:
"""Approve the Pull Request if the branch creator is the same of the repository owner"""
pr_commits = pull_request.get_commits()
first_commit = pr_commits[0]

branch_owner = first_commit.author
repository_owner_login = repository.owner.login
branch_owner_login = branch_owner.login
allowed_logins = Config.pull_request_manager.auto_approve_logins + [
repository_owner_login
]
allowed_logins = Config.pull_request_manager.auto_approve_logins + [repository_owner_login]
if branch_owner_login not in allowed_logins:
logger.info(
'The branch "%s" owner, "%s", is not the same as the repository owner, "%s" '
Expand All @@ -104,10 +86,8 @@ def approve(
)
return

pull_request = repository_helper.get_repo_cached(
repository.full_name, pat=auto_approve_pat
).get_pull(pull_request.number)
pull_request.create_review(event="APPROVE")
logger.info(
"Pull Request %s#%d approved", repository.full_name, pull_request.number
pull_request = repository_helper.get_repo_cached(repository.full_name, pat=auto_approve_pat).get_pull(
pull_request.number
)
pull_request.create_review(event="APPROVE")
logger.info("Pull Request %s#%d approved", repository.full_name, pull_request.number)
Loading

0 comments on commit 173449c

Please sign in to comment.