Skip to content

Commit b95fe54

Browse files
committed
Added function hooks and reworked functionWebhook
1 parent 20e1f21 commit b95fe54

File tree

3 files changed

+63
-13
lines changed

3 files changed

+63
-13
lines changed

gitWebhook/functionWebhook.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from .webhook import webhookBlueprint
22
from typing import Callable, Any
33
from logging import Logger
4+
import json
45

56
class functionWebhookBlueprint(webhookBlueprint):
67
"""A subclass of webhookBlueprint that processes the webhook data using a list of functions. The functions should return True if the webhook data is valid, and False otherwise. If the function returns a string, it will be included in the output."""
78

8-
def __init__(self, webhookToken: str | None, functions: list[Callable[[dict[str, Any]], bool | str]], log:Logger | None = None, name:str="webhook", github:bool=True, gitlab:bool=True, gitea:bool=True, ipWhitelist:list[str] | None = None, *args, **kwargs):
9+
def __init__(self, webhookToken: str | None, functions: list[Callable[[dict[str, Any]], bool | Any]], log:Logger | None = None, name:str="webhook", github:bool=True, gitlab:bool=True, gitea:bool=True, ipWhitelist:list[str] | None = None, *args, **kwargs):
910
"""Initialize the webhook blueprint with a list of functions to process the webhook data.
1011
1112
Args:
@@ -38,24 +39,36 @@ def processWebhook(self, data: dict[str, Any]) -> tuple[int, str]:
3839
"""
3940
if self.log is not None:
4041
self.log.debug(f"Processing webhook: {data}")
41-
output = []
42+
success = True
43+
output:dict[str, str | bool] = {}
4244
for function in self.functions:
43-
res = function(data)
44-
if isinstance(res, str):
45+
try:
46+
res = function(data)
47+
except Exception as e:
48+
output[function.__name__] = str(e)
4549
if self.log is not None:
46-
self.log.debug(f"Function {function.__name__} returned a string")
47-
output.append(res)
48-
elif isinstance(res, bool):
50+
self.log.error(f"Function {function.__name__} raised an exception: {e}")
51+
success = False
52+
continue
53+
if isinstance(res, bool):
4954
if not res:
5055
if self.log is not None:
5156
self.log.error(f"Function {function.__name__} returned false")
52-
return 400, f"Function {function.__name__} returned false"
57+
success = False
5358
else:
5459
if self.log is not None:
5560
self.log.debug(f"Function {function.__name__} returned true")
56-
output.append(str(res))
61+
output[function.__name__] = res
5762
else:
5863
if self.log is not None:
59-
self.log.error(f"Function {function.__name__} returned an invalid type")
60-
return 500, f"Function {function.__name__} returned an invalid type"
61-
return 200, str(output)
64+
self.log.debug(f"Function {function.__name__} returned a string")
65+
try:
66+
output[function.__name__] = str(res)
67+
except Exception as e:
68+
if self.log is not None:
69+
self.log.error(f"Function {function.__name__} returned an invalid type")
70+
return 500, f"Function {function.__name__} returned an invalid type"
71+
if success:
72+
return 200, str(output)
73+
else:
74+
return 400, json.dumps(output)

gitWebhook/webhook.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from flask import Blueprint, request, Response, abort, Request
22
from hashlib import sha256
33
from hmac import new as hmacNew
4-
from typing import Any
4+
from typing import Any, Callable
55
from logging import Logger
66
from .abstractWebhook import gitWebhookBlueprintABC
77

@@ -56,6 +56,7 @@ def __init__(self, webhookToken:str | None, log:Logger | None = None, name:str="
5656
self.gitlab = gitlab
5757
self.gitea = gitea
5858
self.ipWhitelist = ipWhitelist
59+
self.hooks = []
5960
self.route("/", methods=["POST"])(self.receiveWebhook)
6061

6162
def receiveWebhook(self) -> Response:
@@ -96,6 +97,8 @@ def receiveWebhook(self) -> Response:
9697
self.log.warning("A request with no signature found")
9798
abort(401) #no feedback, in case somebody is trying to guess the token
9899
#logs beforehand were warnings, so that messages regarding unauthorized requests can be filtered
100+
for hook in self.hooks:
101+
hook()
99102
data = request.json
100103
if data is None or not isinstance(data, dict):
101104
if self.log is not None:
@@ -117,3 +120,18 @@ def processWebhook(self, data:dict[str, Any]) -> tuple[int, str]:
117120
tuple[int, str]: HTTP return code with a message
118121
"""
119122
return (200, "OK")
123+
124+
def hook(self, func:Callable[..., Any]) -> Callable[..., Any]:
125+
"""Adds a function to the list of functions that will be called when a webhook is received.
126+
This is different from what functionWebhook functions do as this function is called before the processWebhook method and the return value is not checked.
127+
128+
Args:
129+
func (Callable): The function to add to the list of functions that will be called when a webhook is received.
130+
"""
131+
self.hooks.append(func)
132+
def inner(*args, **kwargs):
133+
print("test")
134+
if self.log is not None:
135+
self.log.debug("Received a POST request to a hooked function")
136+
return func(*args, **kwargs)
137+
return inner

test.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,5 +143,24 @@ def testReceiveWebhookInvalidCheck(self):
143143
def testProcessWebhook(self):
144144
self.assertEqual(self.webhook.processWebhook({"test":"test"}), (400, "Function <lambda> returned false"))
145145

146+
class TestHooks(unittest.TestCase):
147+
def setUp(self) -> None:
148+
self.webhook = webhookBlueprint(VALID_TOKEN, name="valid")
149+
self.app = Flask(__name__)
150+
self.app.register_blueprint(self.webhook, url_prefix="/valid")
151+
self.app.config.update({"TESTING": True})
152+
self.client = self.app.test_client()
153+
self.hooked = False
154+
return super().setUp()
155+
156+
def testHook(self):
157+
@self.webhook.hook
158+
def hook():
159+
self.hooked = True
160+
request = testingRequest(VALID_TOKEN)
161+
request.headers["Content-Type"] = "application/json"
162+
resp = self.client.post("/valid/", headers=request.headers, data=request.data)
163+
self.assertTrue(self.hooked)
164+
146165
if __name__ == "__main__":
147166
unittest.main()

0 commit comments

Comments
 (0)