Skip to content

Commit d939b0a

Browse files
committed
Run with updated Flask XPI and Python 3 only
This change uses the post 1.0 version of the Flask API. We drop support for Python 2 which allows us to remove dependencies on `mock` and `six`. Furthermore, we test against the documented `postevent` from GitHub. We choose to use the entire object rather than a `postevent`-like object in order to test against "real world" data. Signed-off-by: Aidan Delaney <[email protected]>
1 parent 1a4590b commit d939b0a

File tree

6 files changed

+187
-16
lines changed

6 files changed

+187
-16
lines changed

.travis.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
dist: xenial
22
language: python
33
python:
4-
- "2.7"
54
- "3.6"
65
- "3.7"
7-
- "pypy"
86
- "pypy3"
97
install:
108
- pip install tox-travis

github_webhook/__init__.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,21 @@
88
:license: Apache License, Version 2.0
99
"""
1010

11-
from github_webhook.webhook import Webhook # noqa
11+
from textwrap import dedent
12+
13+
import sys
14+
15+
if sys.version_info[0] < 3:
16+
raise Exception(
17+
dedent(
18+
"""Python runtime with major version >= 3 is required:
19+
currently running on Python {version}""".format(
20+
version=sys.version_info[0]
21+
)
22+
)
23+
)
24+
25+
from github_webhook.webhook import Webhook
1226

1327
# -----------------------------------------------------------------------------
1428
# Copyright 2015 Bloomberg Finance L.P.

github_webhook/webhook.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import hmac
44
import logging
55

6-
import six
76
from flask import abort, request
87

98

@@ -21,7 +20,7 @@ def __init__(self, app, endpoint="/postreceive", secret=None):
2120

2221
self._hooks = collections.defaultdict(list)
2322
self._logger = logging.getLogger("webhook")
24-
if secret is not None and not isinstance(secret, six.binary_type):
23+
if secret is not None and not isinstance(secret, bytes):
2524
secret = secret.encode("utf-8")
2625
self._secret = secret
2726

@@ -51,8 +50,8 @@ def _postreceive(self):
5150

5251
if digest is not None:
5352
sig_parts = _get_header("X-Hub-Signature").split("=", 1)
54-
if not isinstance(digest, six.text_type):
55-
digest = six.text_type(digest)
53+
if not isinstance(digest, str):
54+
digest = str(digest)
5655

5756
if len(sig_parts) < 2 or sig_parts[0] != "sha1" or not hmac.compare_digest(sig_parts[1], digest):
5857
abort(400, "Invalid signature")
@@ -66,7 +65,9 @@ def _postreceive(self):
6665
self._logger.info("%s (%s)", _format_event(event_type, data), _get_header("X-Github-Delivery"))
6766

6867
for hook in self._hooks.get(event_type, []):
69-
hook(data)
68+
resp = hook(data)
69+
if resp: # Allow hook to respond if necessary
70+
return resp
7071

7172
return "", 204
7273

setup.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22

33
setup(
44
name="github-webhook",
5-
version="1.0.2",
5+
version="2.0.0",
66
description="Very simple, but powerful, microframework for writing Github webhooks in Python",
77
url="https://github.com/bloomberg/python-github-webhook",
88
author="Alex Chamberlain, Fred Phillips, Daniel Kiss, Daniel Beer",
99
1010
license="Apache 2.0",
1111
packages=["github_webhook"],
12-
install_requires=["flask", "six"],
13-
tests_require=["mock", "pytest"],
12+
install_requires=["flask==1.0.2"],
13+
tests_require=["mock", "pytest", "nose"],
1414
classifiers=[
1515
"Development Status :: 4 - Beta",
1616
"Framework :: Flask",
@@ -21,7 +21,6 @@
2121
"Operating System :: MacOS :: MacOS X",
2222
"Operating System :: Microsoft :: Windows",
2323
"Operating System :: POSIX",
24-
"Programming Language :: Python :: 2",
2524
"Programming Language :: Python :: 3",
2625
"Topic :: Software Development :: Version Control",
2726
],

tests/test_webhook.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import pytest
66
import werkzeug
7+
from flask import Flask
78

89
try:
910
from unittest import mock
@@ -128,6 +129,165 @@ def test_request_had_headers(webhook, handler, mock_request):
128129
webhook._postreceive()
129130

130131

132+
# From https://developer.github.com/v3/activity/events/types/#pushevent
133+
example_push_event = {
134+
"ref": "refs/tags/simple-tag",
135+
"before": "a10867b14bb761a232cd80139fbd4c0d33264240",
136+
"after": "0000000000000000000000000000000000000000",
137+
"created": False,
138+
"deleted": True,
139+
"forced": False,
140+
"base_ref": None,
141+
"compare": "https://github.com/Codertocat/Hello-World/compare/a10867b14bb7...000000000000",
142+
"commits": [],
143+
"head_commit": None,
144+
"repository": {
145+
"id": 135493233,
146+
"node_id": "MDEwOlJlcG9zaXRvcnkxMzU0OTMyMzM=",
147+
"name": "Hello-World",
148+
"full_name": "Codertocat/Hello-World",
149+
"owner": {
150+
"name": "Codertocat",
151+
"email": "[email protected]",
152+
"login": "Codertocat",
153+
"id": 21031067,
154+
"node_id": "MDQ6VXNlcjIxMDMxMDY3",
155+
"avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4",
156+
"gravatar_id": "",
157+
"url": "https://api.github.com/users/Codertocat",
158+
"html_url": "https://github.com/Codertocat",
159+
"followers_url": "https://api.github.com/users/Codertocat/followers",
160+
"following_url": "https://api.github.com/users/Codertocat/following{/other_user}",
161+
"gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}",
162+
"starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}",
163+
"subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions",
164+
"organizations_url": "https://api.github.com/users/Codertocat/orgs",
165+
"repos_url": "https://api.github.com/users/Codertocat/repos",
166+
"events_url": "https://api.github.com/users/Codertocat/events{/privacy}",
167+
"received_events_url": "https://api.github.com/users/Codertocat/received_events",
168+
"type": "User",
169+
"site_admin": False,
170+
},
171+
"private": False,
172+
"html_url": "https://github.com/Codertocat/Hello-World",
173+
"description": None,
174+
"fork": False,
175+
"url": "https://github.com/Codertocat/Hello-World",
176+
"forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks",
177+
"keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}",
178+
"collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}",
179+
"teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams",
180+
"hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks",
181+
"issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}",
182+
"events_url": "https://api.github.com/repos/Codertocat/Hello-World/events",
183+
"assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}",
184+
"branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}",
185+
"tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags",
186+
"blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}",
187+
"git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}",
188+
"git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}",
189+
"trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}",
190+
"statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}",
191+
"languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages",
192+
"stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers",
193+
"contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors",
194+
"subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers",
195+
"subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription",
196+
"commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}",
197+
"git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}",
198+
"comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}",
199+
"issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}",
200+
"contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}",
201+
"compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}",
202+
"merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges",
203+
"archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}",
204+
"downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads",
205+
"issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}",
206+
"pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}",
207+
"milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}",
208+
"notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}",
209+
"labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}",
210+
"releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}",
211+
"deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments",
212+
"created_at": 1527711484,
213+
"updated_at": "2018-05-30T20:18:35Z",
214+
"pushed_at": 1527711528,
215+
"git_url": "git://github.com/Codertocat/Hello-World.git",
216+
"ssh_url": "[email protected]:Codertocat/Hello-World.git",
217+
"clone_url": "https://github.com/Codertocat/Hello-World.git",
218+
"svn_url": "https://github.com/Codertocat/Hello-World",
219+
"homepage": None,
220+
"size": 0,
221+
"stargazers_count": 0,
222+
"watchers_count": 0,
223+
"language": None,
224+
"has_issues": True,
225+
"has_projects": True,
226+
"has_downloads": True,
227+
"has_wiki": True,
228+
"has_pages": True,
229+
"forks_count": 0,
230+
"mirror_url": None,
231+
"archived": False,
232+
"open_issues_count": 2,
233+
"license": None,
234+
"forks": 0,
235+
"open_issues": 2,
236+
"watchers": 0,
237+
"default_branch": "master",
238+
"stargazers": 0,
239+
"master_branch": "master",
240+
},
241+
"pusher": {"name": "Codertocat", "email": "[email protected]"},
242+
"sender": {
243+
"login": "Codertocat",
244+
"id": 21031067,
245+
"node_id": "MDQ6VXNlcjIxMDMxMDY3",
246+
"avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4",
247+
"gravatar_id": "",
248+
"url": "https://api.github.com/users/Codertocat",
249+
"html_url": "https://github.com/Codertocat",
250+
"followers_url": "https://api.github.com/users/Codertocat/followers",
251+
"following_url": "https://api.github.com/users/Codertocat/following{/other_user}",
252+
"gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}",
253+
"starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}",
254+
"subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions",
255+
"organizations_url": "https://api.github.com/users/Codertocat/orgs",
256+
"repos_url": "https://api.github.com/users/Codertocat/repos",
257+
"events_url": "https://api.github.com/users/Codertocat/events{/privacy}",
258+
"received_events_url": "https://api.github.com/users/Codertocat/received_events",
259+
"type": "User",
260+
"site_admin": False,
261+
},
262+
}
263+
264+
265+
def test_push_request():
266+
""" Uses the example event defined in the GitHub documentation to ensure
267+
that our webhook app can receive the event.
268+
"""
269+
270+
# GIVEN
271+
app = Flask(__name__) # Standard Flask app
272+
webhook = Webhook(app) # Defines '/postreceive' endpoint
273+
274+
@webhook.hook() # Defines a handler for the 'push' event
275+
def on_push(data):
276+
flag = data["repository"]["full_name"] == "Codertocat/Hello-World"
277+
if not flag:
278+
return "Event data does not match expected data", 400
279+
280+
# WHEN
281+
resp = None
282+
with app.test_client() as client:
283+
resp = client.post(
284+
"/postreceive", json=example_push_event, headers={"X-Github-Event": "push", "X-Github-Delivery": 0}
285+
)
286+
287+
# THEN
288+
assert resp.status_code == 204
289+
290+
131291
# -----------------------------------------------------------------------------
132292
# Copyright 2015 Bloomberg Finance L.P.
133293
#

tox.ini

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
[tox]
2-
envlist = py27,py36,py37,pypy,pypy3,flake8
2+
envlist = py36,py37,pypy3,flake8
33

44
[testenv]
55
deps =
66
pytest
77
pytest-cov
88
flask
9-
six
10-
py{27,py}: mock
11-
commands = pytest -vl --cov=github_webhook --cov-report term-missing --cov-fail-under 100
9+
mock
10+
commands = pytest -vl --cov=github_webhook --cov-report term-missing --cov-fail-under 98
1211

1312
[testenv:flake8]
1413
deps = flake8

0 commit comments

Comments
 (0)