Package: octohook
OctohookOctohook makes working with incomingGithub Webhooksextremely easy.It parses the incoming payload into Python classes to allow for auto-complete and other goodies. For example,octohookprovides functions for payload values which require string interpolation.For example, almost every repository payload has anarchive_urlwith some required and conditional parameters.{"repository":{"archive_url":"https://api.github.com/repos/doodla/octohook-playground/{archive_format}{/ref}"}}TheRepositorymodel provides anarchive_url()method which hasarchive_formatas an argument andrefas an optional variable.>>> repo.archive_url("hello")
https://api.github.com/repos/doodla/octohook-playground/hello
>>> repo.archive_url("hello","world")
https://api.github.com/repos/doodla/octohook-playground/hello/world"GotchasGithub doesn't send consistent payloads for each model necessitating that the non-optional model type hints conform to the least common denominator.Depending on the event type, you can get more information for a particular model, or less.
For example, Github sends achangeskey with some payloads with theeditedaction. For other actions, the key is not present. In such cases, ourevent.changesisNone.This can happen for arbitrary payloads, so I'd suggest tailoring your code to the expected incoming webhook.If anyone has a good suggestion on how to tackle this issue, feel free to email me, or create a PR!Because Github sends different payloads for a combination ofevent typeandaction, unless I have access to all the variations, I cannot be sure that the corresponding model is correct.Current coverage is documentedhere. If you can provide any of the missing events, please make a PR.Sample Usageapp.pyfromflaskimportFlask,request,Responseimportoctohookfromoctohook.eventsimportPullRequestEventapp=Flask(__name__)@app.route('/webhook',methods=['POST'])defwebhook():github_event=request.headers.get('X-GitHub-Event')# pull_request# Assuming the incoming event was a PullRequestEventevent:PullRequestEvent=octohook.parse(github_event,request.json)# Do work with this eventreturnResponse(event.pull_request.head.user.name,status=200)@hookAlternatively, you can also letoctohookdo the heavy lifting of finding and executing the appropriate handlers for any given webhook.The@hookdecorator takes in four parameters, theWebhookEvent, a list ofWebhookEventActions, an optional list of repositories and adebugflag (defaults toFalse).Any function this decorator is applied to is invoked whenever you receive an event with the specifiedWebhookEventand a listedWebhookEventAction.If present, the hook can be filtered for one or more repositories in a multi-repository set up.Note: The "full_name" of the repositories are used. Ex: "doodla/octohook"If you setdebug=Trueon any@hook, only those hooks fire for the corresponding webhook event.@hook(WebhookEvent.PULL_REQUEST,[WebhookEventAction.CREATED,WebhookEventAction.EDITED])defwork(event:PullRequestEvent):passwork()is automatically called with the parsedPullRequestEventanytime you receive a webhook event withX-Github-Event: pull_requestand it has any of thecreatedoreditedactions.If you don't specify a list of actions, then the function is invoked foranyaction. For some events likePush, which do not have anaction, take care not to specify any actions in the decorator.hooks/do_something.pyfromoctohookimporthook,WebhookEvent,WebhookEventActionfromoctohook.eventsimportLabelEvent,PullRequestEvent@hook(WebhookEvent.LABEL,[WebhookEventAction.CREATED])defruns_when_label_event_with_created_action_is_received(event:LabelEvent):print(event.label.name)@hook(WebhookEvent.PULL_REQUEST)defruns_when_pull_request_event_with_any_action_is_received(event:PullRequestEvent):print(event.changes)app.pyfromflaskimportFlask,request,Responseimportoctohookapp=Flask(__name__)octohook.load_hooks(["hooks"])@app.route('/webhook',methods=['POST'])defwebhook():github_event=request.headers.get('X-GitHub-Event')octohook.handle_webhook(event_name=github_event,payload=request.json)returnResponse("OK",status=200)handle_hooksgoes through all the handlers sequentially and blocks till everything is done. Any exceptions are logged tologging.getLogger('octohook'). You can configure the output stream of this logger to capture the logs.Model Overridesoctohookprovides a way to extend/modify the models being provided in the event object.model_overridesis a dictionary where you can mapoctohookmodels to your own.importoctohookfromoctohook.modelsimportPullRequestclassMyPullRequest(PullRequest):defcustom_work(self):passoctohook.load_hooks(["module_a"])octohook.model_overrides={PullRequest:MyPullRequest}Now, everytimeoctohookattempts to initialize aPullRequestobject, it will initializeMyPullRequestinstead.Check thetestfor example usage.NoteThe class is initialized with the relevantpayload: dictdata from the incoming event payload.It is recommended you subclass the original model class, but it is not required.Type hints are no longer reliable for the overridden classes.