Skip to content

Commit

Permalink
githubapp/webhook_handler.py (#196)
Browse files Browse the repository at this point in the history
* docstring

* installation id

[release:minor]

* style: format code with Black and isort

This commit fixes the style issues introduced in 0a69ee7 according to the output
from Black and isort.

Details: #196

* deepsource

* deepsource

* refactor: autofix issues in 1 file

Resolved issues in githubapp/config.py with DeepSource Autofix

* sonar ignore sonar.exclusions

* sonar

---------

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 committed Mar 5, 2024
1 parent 81961d3 commit 53ac5c5
Show file tree
Hide file tree
Showing 15 changed files with 63 additions and 81 deletions.
1 change: 1 addition & 0 deletions githubapp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
It is the entry point for the githubapp package and is responsible for initializing all
the necessary components and configurations. Any global settings for the package should be defined here.
"""

from githubapp.config import Config

__version__ = "0.22"
Expand Down
34 changes: 21 additions & 13 deletions githubapp/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
This module handles loading configuration values from a YAML file
and provides access to those values via the ConfigValue class.
"""

from typing import Any

import yaml
Expand All @@ -12,7 +13,12 @@


class ConfigError(AttributeError):
pass
"""
Exception raised for errors in the configuration.
Attributes:
message - explanation of the error
"""


class ConfigValue:
Expand All @@ -32,10 +38,19 @@ def set_values(self, data: dict[str, Any]) -> None:
setattr(self, attr, value)

def create_config(self, name, *, default=None, **values):
"""
Create a configuration value and nested values.
Args:
name (str): The name of the configuration value
default: The default value. If set, values cannot be provided
values (dict): Nested configuration values
Returns:
ConfigValue: The created configuration value
"""
if default is not None and values:
raise ConfigError(
"You cannot set the default value AND default values for sub values"
)
raise ConfigError("You cannot set the default value AND default values for sub values")
default = default or ConfigValue()
self.set_values({name: default})
if values:
Expand All @@ -46,21 +61,14 @@ def load_config_from_file(self, filename: str, repository: Repository) -> None:
"""Load the config from a file"""
try:
raw_data = (
yaml.safe_load(
repository.get_contents(
filename, ref=repository.default_branch
).decoded_content
)
or {}
yaml.safe_load(repository.get_contents(filename, ref=repository.default_branch).decoded_content) or {}
)
self.set_values(raw_data)
except UnknownObjectException:
pass

def __getattr__(self, item: str) -> Any:
raise ConfigError(
f"No such config value: {item}. And there is no default value for it"
)
raise ConfigError(f"No such config value: {item}. And there is no default value for it")


Config = ConfigValue()
3 changes: 2 additions & 1 deletion githubapp/events/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
This module contains imports for all the different event classes used to represent webhook payloads from GitHub.
Each event type, like a push, issue comment, or check run has a corresponding
class that is instantiated with the event payload data.
class instantiated with the event payload data.
The classes make it easy to access relevant data from the payload and provide a
common interface for handling different event types in the application code.
"""

from .check_run import CheckRunCompletedEvent, CheckRunEvent
from .check_suite import (
CheckSuiteCompletedEvent,
Expand Down
1 change: 1 addition & 0 deletions githubapp/events/check_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Module for handling GitHub check_run webhook events.
https://docs.github.com/en/webhooks/webhook-events-and-payloads#check_run
"""

from github.CheckRun import CheckRun

from githubapp.events.event import Event
Expand Down
1 change: 1 addition & 0 deletions githubapp/events/check_suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Module for handling GitHub check_suite webhook events.
https://docs.github.com/en/webhooks/webhook-events-and-payloads#check_suite
"""

from github.CheckSuite import CheckSuite

from githubapp.events.event import Event
Expand Down
24 changes: 13 additions & 11 deletions githubapp/events/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,11 @@ def __init__(self, *, gh, requester, headers, sender, repository=None, **kwargs)
Event.delivery = headers["X-Github-Delivery"]
Event.github_event = headers["X-Github-Event"]
Event.hook_id = int(headers["X-Github-Hook-Id"])
Event.hook_installation_target_id = int(
headers["X-Github-Hook-Installation-Target-Id"]
)
Event.hook_installation_target_type = headers[
"X-Github-Hook-Installation-Target-Type"
]
Event.hook_installation_target_id = int(headers["X-Github-Hook-Installation-Target-Id"])
Event.hook_installation_target_type = headers["X-Github-Hook-Installation-Target-Type"]
if installation_id := kwargs.get("installation", {}).get("id"):
installation_id = int(installation_id)
Event.installation_id = installation_id
Event._raw_headers = headers
Event._raw_body = kwargs
self.gh = gh
Expand Down Expand Up @@ -95,10 +94,7 @@ def match(cls, data):
Returns:
bool: True if the event matches the event_identifier, False otherwise
"""
return all(
(attr in data and value == data[attr])
for attr, value in cls.event_identifier.items()
)
return all((attr in data and value == data[attr]) for attr, value in cls.event_identifier.items())

@staticmethod
def fix_attributes(attributes):
Expand All @@ -123,7 +119,13 @@ def _parse_object(self, clazz: type[T], value: Any) -> Optional[T]:
)

def start_check_run(
self, name, sha, title, summary=None, text=None, status="in_progress"
self,
name: str,
sha: str,
title: str,
summary: Optional[str] = None,
text: Optional[str] = None,
status: str = "in_progress",
):
"""Start a check run"""
output = {"title": title or name, "summary": summary or ""}
Expand Down
10 changes: 2 additions & 8 deletions githubapp/events/issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,8 @@ def __init__(
**kwargs,
):
super().__init__(**kwargs)
self.old_issue = (
self._parse_object(Issue, changes.get("old_issue")) if changes else None
)
self.old_repository = (
self._parse_object(Repository, changes.get("old_repository"))
if changes
else None
)
self.old_issue = self._parse_object(Issue, changes.get("old_issue")) if changes else None
self.old_repository = self._parse_object(Repository, changes.get("old_repository")) if changes else None


class IssueEditedEvent(IssuesEvent):
Expand Down
4 changes: 3 additions & 1 deletion githubapp/webhook_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,9 @@ def handle_with_flask(
app (Flask): The Flask application to register the webhook_handler with.
use_default_index (bool): Whether to register the root handler with the Flask application. Default is False.
webhook_endpoint (str): The endpoint to register the webhook_handler with. Default is "/".
auth_callback_handler (Callable[[int, AccessToken], None]): The function to handle the auth_callback. Default is None.
auth_callback_handler (Callable[[int, AccessToken], None]): The function to handle the auth_callback.
Default is None.
version (str): The version of the App.
versions_to_show (str): The libraries to show the version.
config_file (str): The config file path to autoload
Expand Down
10 changes: 5 additions & 5 deletions payload_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ def github_payload():
args = parser.parse_args()
if args.record:
print(1)
with Popen(
"smee -u https://smee.io/polidoro-testing -p 3333 -P /".split()
) as p:
with Popen("smee -u https://smee.io/polidoro-testing -p 3333 -P /".split()) as p:
print(2)
app.debug = True
app.run(port=3333)
Expand All @@ -58,6 +56,8 @@ def github_payload():
headers = payload["headers"]
data = payload["data"]

headers["X-GitHub-Hook-Installation-Target-ID"] = args.installation_target_id[0]
data["installation"]["id"] = args.installation_id[0]
if args.installation_target_id:
headers["X-GitHub-Hook-Installation-Target-ID"] = args.installation_target_id[0]
if args.installation_id:
data["installation"]["id"] = args.installation_id[0]
print(post(headers=headers, json=data, url="http://127.0.0.1:5000"))
2 changes: 1 addition & 1 deletion sonar-project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ sonar.python.version=3.9
#sonar.python.version=3.9, 3.10, 3.11, 3.12

sonar.sources=.
sonar.exclusions=tests/**
sonar.exclusions=tests/**,payload_helper.py
4 changes: 1 addition & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ def requester():

@pytest.fixture
def mock_auth():
with patch(
"githubapp.webhook_handler._get_auth", return_value=Mock(autospec=Auth)
) as mock:
with patch("githubapp.webhook_handler._get_auth", return_value=Mock(autospec=Auth)) as mock:
yield mock


Expand Down
10 changes: 2 additions & 8 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,10 @@ def test_no_config_value():
with pytest.raises(ConfigError) as err:
# noinspection PyStatementEffect
Config.config1
assert (
str(err.value)
== "No such config value: config1. And there is no default value for it"
)
assert str(err.value) == "No such config value: config1. And there is no default value for it"


def test_validate_default_or_values():
with pytest.raises(ConfigError) as err:
Config.create_config("config1", default="value1", value2="value2")
assert (
str(err.value)
== "You cannot set the default value AND default values for sub values"
)
assert str(err.value) == "You cannot set the default value AND default values for sub values"
24 changes: 6 additions & 18 deletions tests/test_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,7 @@ def test_parse_object():
self = Mock(requester="requester")
EventTest._parse_object(self, mocked_class, {"a": 1})
self.fix_attributes.assert_called_with({"a": 1})
mocked_class.assert_called_with(
requester="requester", headers={}, attributes={"a": 1}, completed=False
)
mocked_class.assert_called_with(requester="requester", headers={}, attributes={"a": 1}, completed=False)


# noinspection PyTypeChecker
Expand Down Expand Up @@ -291,9 +289,7 @@ def all_events():
],
)
def test_instantiate_events(event_class, event_action_request, all_events):
test_event, event_identifier, check_instance = TEST_INSTANTIATE_EVENTS_VALUES.get(
event_class
)
test_event, event_identifier, check_instance = TEST_INSTANTIATE_EVENTS_VALUES.get(event_class)
check_instance.update({"sender": NamedUser, "repository": Repository})
headers, default_body = event_action_request
headers["X-Github-Event"] = test_event
Expand All @@ -316,9 +312,7 @@ def test_instantiate_events(event_class, event_action_request, all_events):
"old_issue": body.pop("old_issue"),
"old_repository": body.pop("old_repository"),
}
event_instance = Event.get_event(headers, body)(
gh=Mock(), requester=Mock(), headers=headers, **body
)
event_instance = Event.get_event(headers, body)(gh=Mock(), requester=Mock(), headers=headers, **body)
assert isinstance(event_instance, event_class)
assert isinstance(event_instance.repository, Repository)
assert isinstance(event_instance.sender, NamedUser)
Expand All @@ -328,9 +322,7 @@ def test_instantiate_events(event_class, event_action_request, all_events):
attr_type = attr_type[0]
else:
value = getattr(event_instance, attribute)
assert isinstance(
value, attr_type
), f"{attribute} is {type(value)} not {attr_type}"
assert isinstance(value, attr_type), f"{attribute} is {type(value)} not {attr_type}"
all_events.remove(event_class)


Expand Down Expand Up @@ -366,19 +358,15 @@ def test_update_check_run_with_only_conclusion(event):
def test_update_check_run_with_output(event):
event.start_check_run("name", "sha", "title", "summary")
event.update_check_run(title="new_title", summary="new_summary")
event.check_run.edit.assert_called_with(
output={"title": "new_title", "summary": "new_summary"}
)
event.check_run.edit.assert_called_with(output={"title": "new_title", "summary": "new_summary"})


def test_update_check_run_with_only_output_text(event):
event.start_check_run("name", "sha", "title")
event.check_run.output.title = "title"
event.check_run.output.summary = "summary"
event.update_check_run(text="text")
event.check_run.edit.assert_called_with(
output={"title": "title", "summary": "summary", "text": "text"}
)
event.check_run.edit.assert_called_with(output={"title": "title", "summary": "summary", "text": "text"})


def test_update_check_run_with_nothing(event):
Expand Down
12 changes: 3 additions & 9 deletions tests/test_webhook_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,16 @@ def test_get_auth_app_user_auth(monkeypatch):
monkeypatch.setenv("CLIENT_ID", "client_id")
monkeypatch.setenv("CLIENT_SECRET", "client_secret")
monkeypatch.setenv("TOKEN", "token")
with patch(
"githubapp.webhook_handler.AppUserAuth", autospec=AppUserAuth
) as appuserauth:
with patch("githubapp.webhook_handler.AppUserAuth", autospec=AppUserAuth) as appuserauth:
assert isinstance(_get_auth(), AppUserAuth)
appuserauth.assert_called_once_with(
client_id="client_id", client_secret="client_secret", token="token"
)
appuserauth.assert_called_once_with(client_id="client_id", client_secret="client_secret", token="token")


def test_get_auth_app_auth_when_private_key_in_env(monkeypatch):
monkeypatch.setenv("PRIVATE_KEY", "private_key")

get_access_token = Mock(return_value=Mock(token="token"))
githubintegration = Mock(
autospec=GithubIntegration, get_access_token=get_access_token
)
githubintegration = Mock(autospec=GithubIntegration, get_access_token=get_access_token)
with (
patch("githubapp.webhook_handler.AppAuth") as appauth,
patch(
Expand Down
4 changes: 1 addition & 3 deletions tests/test_webhook_handler_flask.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ def app():
def test_handle_with_flask(app):
handle_with_flask(app)
assert app.route.call_count == 2
app.route.assert_has_calls(
[call("/", methods=["GET"]), call("/", methods=["POST"])], any_order=True
)
app.route.assert_has_calls([call("/", methods=["GET"]), call("/", methods=["POST"])], any_order=True)


def test_handle_with_flask_validation(app):
Expand Down

0 comments on commit 53ac5c5

Please sign in to comment.