From 53a6c039717a92134c1366e875933e7dfbd4c7ca Mon Sep 17 00:00:00 2001 From: Aidan Delaney Date: Thu, 24 Oct 2019 11:05:09 +0100 Subject: [PATCH 1/2] Run with updated Flask API 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 --- .travis.yml | 2 - github_webhook/__init__.py | 16 +++- github_webhook/webhook.py | 11 +-- setup.py | 7 +- tests/test_webhook.py | 160 +++++++++++++++++++++++++++++++++++++ tox.ini | 7 +- 6 files changed, 187 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3493c21..780ffbd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,8 @@ dist: xenial language: python python: - - "2.7" - "3.6" - "3.7" - - "pypy" - "pypy3" install: - pip install tox-travis diff --git a/github_webhook/__init__.py b/github_webhook/__init__.py index fee418e..5de79a4 100644 --- a/github_webhook/__init__.py +++ b/github_webhook/__init__.py @@ -8,7 +8,21 @@ :license: Apache License, Version 2.0 """ -from github_webhook.webhook import Webhook # noqa +from textwrap import dedent + +import sys + +if sys.version_info[0] < 3: + raise Exception( + dedent( + """Python runtime with major version >= 3 is required: + currently running on Python {version}""".format( + version=sys.version_info[0] + ) + ) + ) + +from github_webhook.webhook import Webhook # ----------------------------------------------------------------------------- # Copyright 2015 Bloomberg Finance L.P. diff --git a/github_webhook/webhook.py b/github_webhook/webhook.py index d6addf9..bc5ec79 100644 --- a/github_webhook/webhook.py +++ b/github_webhook/webhook.py @@ -3,7 +3,6 @@ import hmac import logging -import six from flask import abort, request @@ -21,7 +20,7 @@ def __init__(self, app, endpoint="/postreceive", secret=None): self._hooks = collections.defaultdict(list) self._logger = logging.getLogger("webhook") - if secret is not None and not isinstance(secret, six.binary_type): + if secret is not None and not isinstance(secret, bytes): secret = secret.encode("utf-8") self._secret = secret @@ -51,8 +50,8 @@ def _postreceive(self): if digest is not None: sig_parts = _get_header("X-Hub-Signature").split("=", 1) - if not isinstance(digest, six.text_type): - digest = six.text_type(digest) + if not isinstance(digest, str): + digest = str(digest) if len(sig_parts) < 2 or sig_parts[0] != "sha1" or not hmac.compare_digest(sig_parts[1], digest): abort(400, "Invalid signature") @@ -66,7 +65,9 @@ def _postreceive(self): self._logger.info("%s (%s)", _format_event(event_type, data), _get_header("X-Github-Delivery")) for hook in self._hooks.get(event_type, []): - hook(data) + resp = hook(data) + if resp: # Allow hook to respond if necessary + return resp return "", 204 diff --git a/setup.py b/setup.py index cea1aa4..6275b7d 100644 --- a/setup.py +++ b/setup.py @@ -2,15 +2,15 @@ setup( name="github-webhook", - version="1.0.2", + version="2.0.0", description="Very simple, but powerful, microframework for writing Github webhooks in Python", url="https://github.com/bloomberg/python-github-webhook", author="Alex Chamberlain, Fred Phillips, Daniel Kiss, Daniel Beer", author_email="achamberlai9@bloomberg.net, fphillips7@bloomberg.net, dkiss1@bloomberg.net, dbeer1@bloomberg.net", license="Apache 2.0", packages=["github_webhook"], - install_requires=["flask", "six"], - tests_require=["mock", "pytest"], + install_requires=["flask==1.0.2"], + tests_require=["mock", "pytest", "nose"], classifiers=[ "Development Status :: 4 - Beta", "Framework :: Flask", @@ -21,7 +21,6 @@ "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", - "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", "Topic :: Software Development :: Version Control", ], diff --git a/tests/test_webhook.py b/tests/test_webhook.py index 6575632..b7a21b2 100644 --- a/tests/test_webhook.py +++ b/tests/test_webhook.py @@ -4,6 +4,7 @@ import pytest import werkzeug +from flask import Flask try: from unittest import mock @@ -128,6 +129,165 @@ def test_request_had_headers(webhook, handler, mock_request): webhook._postreceive() +# From https://developer.github.com/v3/activity/events/types/#pushevent +example_push_event = { + "ref": "refs/tags/simple-tag", + "before": "a10867b14bb761a232cd80139fbd4c0d33264240", + "after": "0000000000000000000000000000000000000000", + "created": False, + "deleted": True, + "forced": False, + "base_ref": None, + "compare": "https://github.com/Codertocat/Hello-World/compare/a10867b14bb7...000000000000", + "commits": [], + "head_commit": None, + "repository": { + "id": 135493233, + "node_id": "MDEwOlJlcG9zaXRvcnkxMzU0OTMyMzM=", + "name": "Hello-World", + "full_name": "Codertocat/Hello-World", + "owner": { + "name": "Codertocat", + "email": "21031067+Codertocat@users.noreply.github.com", + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": False, + }, + "private": False, + "html_url": "https://github.com/Codertocat/Hello-World", + "description": None, + "fork": False, + "url": "https://github.com/Codertocat/Hello-World", + "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", + "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", + "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", + "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", + "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", + "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", + "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", + "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", + "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", + "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", + "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", + "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", + "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", + "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", + "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", + "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", + "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", + "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", + "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", + "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", + "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", + "created_at": 1527711484, + "updated_at": "2018-05-30T20:18:35Z", + "pushed_at": 1527711528, + "git_url": "git://github.com/Codertocat/Hello-World.git", + "ssh_url": "git@github.com:Codertocat/Hello-World.git", + "clone_url": "https://github.com/Codertocat/Hello-World.git", + "svn_url": "https://github.com/Codertocat/Hello-World", + "homepage": None, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": None, + "has_issues": True, + "has_projects": True, + "has_downloads": True, + "has_wiki": True, + "has_pages": True, + "forks_count": 0, + "mirror_url": None, + "archived": False, + "open_issues_count": 2, + "license": None, + "forks": 0, + "open_issues": 2, + "watchers": 0, + "default_branch": "master", + "stargazers": 0, + "master_branch": "master", + }, + "pusher": {"name": "Codertocat", "email": "21031067+Codertocat@users.noreply.github.com"}, + "sender": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": False, + }, +} + + +def test_push_request(): + """ Uses the example event defined in the GitHub documentation to ensure + that our webhook app can receive the event. + """ + + # GIVEN + app = Flask(__name__) # Standard Flask app + webhook = Webhook(app) # Defines '/postreceive' endpoint + + @webhook.hook() # Defines a handler for the 'push' event + def on_push(data): + flag = data["repository"]["full_name"] == "Codertocat/Hello-World" + if not flag: + return "Event data does not match expected data", 400 + + # WHEN + resp = None + with app.test_client() as client: + resp = client.post( + "/postreceive", json=example_push_event, headers={"X-Github-Event": "push", "X-Github-Delivery": 0} + ) + + # THEN + assert resp.status_code == 204 + + # ----------------------------------------------------------------------------- # Copyright 2015 Bloomberg Finance L.P. # diff --git a/tox.ini b/tox.ini index ed2ef8b..b436f53 100644 --- a/tox.ini +++ b/tox.ini @@ -1,14 +1,13 @@ [tox] -envlist = py27,py36,py37,pypy,pypy3,flake8 +envlist = py36,py37,pypy3,flake8 [testenv] deps = pytest pytest-cov flask - six - py{27,py}: mock -commands = pytest -vl --cov=github_webhook --cov-report term-missing --cov-fail-under 100 + mock +commands = pytest -vl --cov=github_webhook --cov-report term-missing --cov-fail-under 98 [testenv:flake8] deps = flake8 From decbf76fba0a079a2d9f712e569a74556b34a814 Mon Sep 17 00:00:00 2001 From: Aidan Delaney Date: Thu, 24 Oct 2019 12:14:32 +0100 Subject: [PATCH 2/2] Run test environment without legacy dependencies Changed the error thrown on detecting a legacy Python 2 environment to be a RuntimeError. Also removed tox dependency on mock and made the Flask dependency less strict. We now depend on any Flask release greater than 1.0.x. Signed-off-by: Aidan Delaney --- github_webhook/__init__.py | 2 +- setup.py | 4 ++-- tox.ini | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/github_webhook/__init__.py b/github_webhook/__init__.py index 5de79a4..f3cb346 100644 --- a/github_webhook/__init__.py +++ b/github_webhook/__init__.py @@ -13,7 +13,7 @@ import sys if sys.version_info[0] < 3: - raise Exception( + raise RuntimeError( dedent( """Python runtime with major version >= 3 is required: currently running on Python {version}""".format( diff --git a/setup.py b/setup.py index 6275b7d..e3991f0 100644 --- a/setup.py +++ b/setup.py @@ -9,8 +9,8 @@ author_email="achamberlai9@bloomberg.net, fphillips7@bloomberg.net, dkiss1@bloomberg.net, dbeer1@bloomberg.net", license="Apache 2.0", packages=["github_webhook"], - install_requires=["flask==1.0.2"], - tests_require=["mock", "pytest", "nose"], + install_requires=["flask>=1.0.2"], + tests_require=["pytest"], classifiers=[ "Development Status :: 4 - Beta", "Framework :: Flask", diff --git a/tox.ini b/tox.ini index b436f53..e9c3c0f 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,6 @@ deps = pytest pytest-cov flask - mock commands = pytest -vl --cov=github_webhook --cov-report term-missing --cov-fail-under 98 [testenv:flake8]