Repository: PyGithub/PyGithub

Issue:
Organization webhooks support?
Hi,

Does it support organization webhook to listen to repository delete event?


Discussion:
See https://github.com/PyGithub/PyGithub/issues/324
@turly221 A PR would be much appreciated.
Taking this up. 
@namratachaudhary any update on this?
Writing tests for the same. A little stalled. Will try to raise a PR in couple of days 

Resolving patch:
diff --git a/github/Organization.py b/github/Organization.py
--- a/github/Organization.py
+++ b/github/Organization.py
@@ -308,6 +308,34 @@ def create_fork(self, repo):
         )
         return github.Repository.Repository(self._requester, headers, data, completed=True)
 
+    def create_hook(self, name, config, events=github.GithubObject.NotSet, active=github.GithubObject.NotSet):
+        """
+        :calls: `POST /orgs/:owner/hooks <http://developer.github.com/v3/orgs/hooks>`_
+        :param name: string
+        :param config: dict
+        :param events: list of string
+        :param active: bool
+        :rtype: :class:`github.Hook.Hook`
+        """
+        assert isinstance(name, (str, unicode)), name
+        assert isinstance(config, dict), config
+        assert events is github.GithubObject.NotSet or all(isinstance(element, (str, unicode)) for element in events), events
+        assert active is github.GithubObject.NotSet or isinstance(active, bool), active
+        post_parameters = {
+            "name": name,
+            "config": config,
+        }
+        if events is not github.GithubObject.NotSet:
+            post_parameters["events"] = events
+        if active is not github.GithubObject.NotSet:
+            post_parameters["active"] = active
+        headers, data = self._requester.requestJsonAndCheck(
+            "POST",
+            self.url + "/hooks",
+            input=post_parameters
+        )
+        return github.Hook.Hook(self._requester, headers, data, completed=True)
+
     def create_repo(self, name, description=github.GithubObject.NotSet, homepage=github.GithubObject.NotSet,
                     private=github.GithubObject.NotSet, has_issues=github.GithubObject.NotSet,
                     has_wiki=github.GithubObject.NotSet, has_downloads=github.GithubObject.NotSet,
@@ -412,6 +440,18 @@ def create_team(self, name, repo_names=github.GithubObject.NotSet, permission=gi
         )
         return github.Team.Team(self._requester, headers, data, completed=True)
 
+    def delete_hook(self, id):
+        """
+        :calls: `DELETE /orgs/:owner/hooks/:id <http://developer.github.com/v3/orgs/hooks>`_
+        :param id: integer
+        :rtype: None`
+        """
+        assert isinstance(id, (int, long)), id
+        headers, data = self._requester.requestJsonAndCheck(
+            "DELETE",
+            self.url + "/hooks/" + str(id)
+        )
+
     def edit(self, billing_email=github.GithubObject.NotSet, blog=github.GithubObject.NotSet, company=github.GithubObject.NotSet, email=github.GithubObject.NotSet, location=github.GithubObject.NotSet, name=github.GithubObject.NotSet):
         """
         :calls: `PATCH /orgs/:org <http://developer.github.com/v3/orgs>`_
@@ -449,6 +489,36 @@ def edit(self, billing_email=github.GithubObject.NotSet, blog=github.GithubObjec
         )
         self._useAttributes(data)
 
+    def edit_hook(self, id, name, config, events=github.GithubObject.NotSet, active=github.GithubObject.NotSet):
+        """
+        :calls: `PATCH /orgs/:owner/hooks/:id <http://developer.github.com/v3/orgs/hooks>`_
+        :param id: integer
+        :param name: string
+        :param config: dict
+        :param events: list of string
+        :param active: bool
+        :rtype: :class:`github.Hook.Hook`
+        """
+        assert isinstance(id, (int, long)), id
+        assert isinstance(name, (str, unicode)), name
+        assert isinstance(config, dict), config
+        assert events is github.GithubObject.NotSet or all(isinstance(element, (str, unicode)) for element in events), events
+        assert active is github.GithubObject.NotSet or isinstance(active, bool), active
+        post_parameters = {
+            "name": name,
+            "config": config,
+        }
+        if events is not github.GithubObject.NotSet:
+            post_parameters["events"] = events
+        if active is not github.GithubObject.NotSet:
+            post_parameters["active"] = active
+        headers, data = self._requester.requestJsonAndCheck(
+            "PATCH",
+            self.url + "/hooks/" + str(id),
+            input=post_parameters
+        )
+        return github.Hook.Hook(self._requester, headers, data, completed=True)
+
     def get_events(self):
         """
         :calls: `GET /orgs/:org/events <http://developer.github.com/v3/activity/events>`_
@@ -461,6 +531,31 @@ def get_events(self):
             None
         )
 
+    def get_hook(self, id):
+        """
+        :calls: `GET /orgs/:owner/hooks/:id <http://developer.github.com/v3/orgs/hooks>`_
+        :param id: integer
+        :rtype: :class:`github.Hook.Hook`
+        """
+        assert isinstance(id, (int, long)), id
+        headers, data = self._requester.requestJsonAndCheck(
+            "GET",
+            self.url + "/hooks/" + str(id)
+        )
+        return github.Hook.Hook(self._requester, headers, data, completed=True)
+
+    def get_hooks(self):
+        """
+        :calls: `GET /orgs/:owner/hooks <http://developer.github.com/v3/orgs/hooks>`_
+        :rtype: :class:`github.PaginatedList.PaginatedList` of :class:`github.Hook.Hook`
+        """
+        return github.PaginatedList.PaginatedList(
+            github.Hook.Hook,
+            self._requester,
+            self.url + "/hooks",
+            None
+        )
+
     def get_issues(self, filter=github.GithubObject.NotSet, state=github.GithubObject.NotSet, labels=github.GithubObject.NotSet, sort=github.GithubObject.NotSet, direction=github.GithubObject.NotSet, since=github.GithubObject.NotSet):
         """
         :calls: `GET /orgs/:org/issues <http://developer.github.com/v3/issues>`_


Test patch:
diff --git a/github/tests/Organization.py b/github/tests/Organization.py
--- a/github/tests/Organization.py
+++ b/github/tests/Organization.py
@@ -77,6 +77,18 @@ def testEditWithAllArguments(self):
         self.assertEqual(self.org.location, "Location edited by PyGithub")
         self.assertEqual(self.org.name, "Name edited by PyGithub")
 
+    def testEditHookWithMinimalParameters(self):
+        hook = self.org.create_hook("web", {"url": "http://foobar.com"})
+        hook = self.org.edit_hook(hook.id, "mobile", {"url": "http://barfoo.com"})
+        self.assertEqual(hook.name, "mobile")
+
+    def testEditHookWithAllParameters(self):
+        hook = self.org.create_hook("web", {"url": "http://foobar.com"}, ["fork"], False)
+        hook = self.org.edit_hook(hook.id, "mobile", {"url": "http://barfoo.com"}, ["spoon"], True)
+        self.assertEqual(hook.name, "mobile")
+        self.assertEqual(hook.events, ["spoon"])
+        self.assertEqual(hook.active, True)
+
     def testCreateTeam(self):
         team = self.org.create_team("Team created by PyGithub")
         self.assertEqual(team.id, 189850)
@@ -86,6 +98,10 @@ def testCreateTeamWithAllArguments(self):
         team = self.org.create_team("Team also created by PyGithub", [repo], "push")
         self.assertEqual(team.id, 189852)
 
+    def testDeleteHook(self):
+        hook = self.org.create_hook("web", {"url": "http://foobar.com"})
+        self.org.delete_hook(hook.id)
+
     def testPublicMembers(self):
         lyloa = self.g.get_user("Lyloa")
         self.assertFalse(self.org.has_in_public_members(lyloa))
@@ -97,6 +113,9 @@ def testPublicMembers(self):
     def testGetPublicMembers(self):
         self.assertListKeyEqual(self.org.get_public_members(), lambda u: u.login, ["jacquev6"])
 
+    def testGetHooks(self):
+        self.assertListKeyEqual(self.org.get_hooks(), lambda h: h.id, [257993])
+
     def testGetIssues(self):
         self.assertListKeyEqual(self.org.get_issues(), lambda i: i.id, [])
 
@@ -126,6 +145,15 @@ def testGetEvents(self):
     def testGetTeams(self):
         self.assertListKeyEqual(self.org.get_teams(), lambda t: t.name, ["Members", "Owners"])
 
+    def testCreateHookWithMinimalParameters(self):
+        hook = self.org.create_hook("web", {"url": "http://foobar.com"})
+        self.assertEqual(hook.id, 257967)
+
+    def testCreateHookWithAllParameters(self):
+        hook = self.org.create_hook("web", {"url": "http://foobar.com"}, ["fork"], False)
+        self.assertTrue(hook.active)
+        self.assertEqual(hook.id, 257993)
+
     def testCreateRepoWithMinimalArguments(self):
         repo = self.org.create_repo(name="TestPyGithub")
         self.assertEqual(repo.url, "https://api.github.com/repos/BeaverSoftware/TestPyGithub")
diff --git a/github/tests/ReplayData/Organization.testCreateHookWithAllParameters.txt b/github/tests/ReplayData/Organization.testCreateHookWithAllParameters.txt
new file mode 100644
--- /dev/null
+++ b/github/tests/ReplayData/Organization.testCreateHookWithAllParameters.txt
@@ -0,0 +1,10 @@
+https
+POST
+api.github.com
+None
+/orgs/BeaverSoftware/hooks
+{'Content-Type': 'application/json', 'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
+{"active": false, "config": {"url": "http://foobar.com"}, "name": "web", "events": ["fork"]}
+200
+[('status', '200 Ok'), ('x-ratelimit-remaining', '4997'), ('content-length', '298'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"c3b371e4de1a0ec350b3fcc0c458e0f9"'), ('date', 'Sat, 19 May 2012 06:01:45 GMT'), ('content-type', 'application/json; charset=utf-8'), ('location', 'https://api.github.com/orgs/BeaverSoftware/hooks/257993')]
+{"updated_at":"2012-05-19T06:01:45Z","last_response":{"status":"unused","message":null,"code":null},"events":["fork"],"url":"https://api.github.com/orgs/BeaverSoftware/hooks/257993","active":true,"name":"web","config":{"url":"http://foobar.com"},"id":257993,"created_at":"2012-05-19T06:01:45Z"}
diff --git a/github/tests/ReplayData/Organization.testCreateHookWithMinimalParameters.txt b/github/tests/ReplayData/Organization.testCreateHookWithMinimalParameters.txt
new file mode 100644
--- /dev/null
+++ b/github/tests/ReplayData/Organization.testCreateHookWithMinimalParameters.txt
@@ -0,0 +1,10 @@
+https
+POST
+api.github.com
+None
+/orgs/BeaverSoftware/hooks
+{'Content-Type': 'application/json', 'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
+{"config": {"url": "http://foobar.com"}, "name": "web"}
+201
+[('status', '201 Created'), ('x-ratelimit-remaining', '4994'), ('content-length', '298'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"276d18854081948260c44cf645c54bd0"'), ('date', 'Sat, 19 May 2012 05:03:14 GMT'), ('content-type', 'application/json; charset=utf-8'), ('location', 'https://api.github.com/orgs/BeaverSoftware/hooks/257967')]
+{"updated_at":"2012-05-19T05:03:14Z","url":"https://api.github.com/orgs/BeaverSoftware/hooks/257967","config":{"url":"http://foobar.com"},"last_response":{"status":"unused","message":null,"code":null},"active":true,"events":["push"],"name":"web","created_at":"2012-05-19T05:03:14Z","id":257967}
diff --git a/github/tests/ReplayData/Organization.testDeleteHook.txt b/github/tests/ReplayData/Organization.testDeleteHook.txt
new file mode 100644
--- /dev/null
+++ b/github/tests/ReplayData/Organization.testDeleteHook.txt
@@ -0,0 +1,20 @@
+https
+POST
+api.github.com
+None
+/orgs/BeaverSoftware/hooks
+{'Content-Type': 'application/json', 'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
+{"config": {"url": "http://foobar.com"}, "name": "web"}
+201
+[('status', '201 Created'), ('x-ratelimit-remaining', '4994'), ('content-length', '298'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"276d18854081948260c44cf645c54bd0"'), ('date', 'Sat, 19 May 2012 05:03:14 GMT'), ('content-type', 'application/json; charset=utf-8'), ('location', 'https://api.github.com/orgs/BeaverSoftware/hooks/257967')]
+{"updated_at":"2012-05-19T05:03:14Z","url":"https://api.github.com/orgs/BeaverSoftware/hooks/257967","config":{"url":"http://foobar.com"},"last_response":{"status":"unused","message":null,"code":null},"active":true,"events":["push"],"name":"web","created_at":"2012-05-19T05:03:14Z","id":257967}
+
+https
+DELETE
+api.github.com
+None
+/orgs/BeaverSoftware/hooks/257967
+{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
+None
+204
+[('status', '204 No Content'), ('x-ratelimit-remaining', '4965'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"d41d8cd98f00b204e9800998ecf8427e"'), ('date', 'Sat, 19 May 2012 07:26:55 GMT')]
diff --git a/github/tests/ReplayData/Organization.testEditHookWithAllParameters.txt b/github/tests/ReplayData/Organization.testEditHookWithAllParameters.txt
new file mode 100644
--- /dev/null
+++ b/github/tests/ReplayData/Organization.testEditHookWithAllParameters.txt
@@ -0,0 +1,21 @@
+https
+POST
+api.github.com
+None
+/orgs/BeaverSoftware/hooks
+{'Content-Type': 'application/json', 'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
+{"active": false, "config": {"url": "http://foobar.com"}, "name": "web", "events": ["fork"]}
+200
+[('status', '200 Ok'), ('x-ratelimit-remaining', '4997'), ('content-length', '298'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"c3b371e4de1a0ec350b3fcc0c458e0f9"'), ('date', 'Sat, 19 May 2012 06:01:45 GMT'), ('content-type', 'application/json; charset=utf-8'), ('location', 'https://api.github.com/orgs/BeaverSoftware/hooks/257993')]
+{"updated_at":"2012-05-19T06:01:45Z","last_response":{"status":"unused","message":null,"code":null},"events":["fork"],"url":"https://api.github.com/orgs/BeaverSoftware/hooks/257993","active":false,"name":"web","config":{"url":"http://foobar.com"},"id":257993,"created_at":"2012-05-19T06:01:45Z"}
+
+https
+PATCH
+api.github.com
+None
+/orgs/BeaverSoftware/hooks/257993
+{'Content-Type': 'application/json', 'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
+{"active": true, "config": {"url": "http://barfoo.com"}, "name": "mobile", "events": ["spoon"]}
+200
+[('status', '200 Ok'), ('x-ratelimit-remaining', '4965'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"d41d8cd98f00b204e9800998ecf8427e"'), ('date', 'Sat, 19 May 2012 07:26:55 GMT')]
+{"updated_at":"2012-05-19T06:01:45Z","last_response":{"status":"unused","message":null,"code":null},"events":["spoon"],"url":"https://api.github.com/orgs/BeaverSoftware/hooks/257993","active":true,"name":"mobile","config":{"url":"http://barfoo.com"},"id":257993,"created_at":"2012-05-19T06:01:45Z"}
diff --git a/github/tests/ReplayData/Organization.testEditHookWithMinimalParameters.txt b/github/tests/ReplayData/Organization.testEditHookWithMinimalParameters.txt
new file mode 100644
--- /dev/null
+++ b/github/tests/ReplayData/Organization.testEditHookWithMinimalParameters.txt
@@ -0,0 +1,21 @@
+https
+POST
+api.github.com
+None
+/orgs/BeaverSoftware/hooks
+{'Content-Type': 'application/json', 'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
+{"config": {"url": "http://foobar.com"}, "name": "web"}
+200
+[('status', '200 Ok'), ('x-ratelimit-remaining', '4997'), ('content-length', '298'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"c3b371e4de1a0ec350b3fcc0c458e0f9"'), ('date', 'Sat, 19 May 2012 06:01:45 GMT'), ('content-type', 'application/json; charset=utf-8'), ('location', 'https://api.github.com/orgs/BeaverSoftware/hooks/257993')]
+{"updated_at":"2012-05-19T06:01:45Z","last_response":{"status":"unused","message":null,"code":null},"events":["fork"],"url":"https://api.github.com/orgs/BeaverSoftware/hooks/257993","active":true,"name":"web","config":{"url":"http://foobar.com"},"id":257993,"created_at":"2012-05-19T06:01:45Z"}
+
+https
+PATCH
+api.github.com
+None
+/orgs/BeaverSoftware/hooks/257993
+{'Content-Type': 'application/json', 'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
+{"config": {"url": "http://barfoo.com"}, "name": "mobile"}
+200
+[('status', '200 Ok'), ('x-ratelimit-remaining', '4965'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"d41d8cd98f00b204e9800998ecf8427e"'), ('date', 'Sat, 19 May 2012 07:26:55 GMT')]
+{"updated_at":"2012-05-19T06:01:45Z","last_response":{"status":"unused","message":null,"code":null},"events":["fork"],"url":"https://api.github.com/orgs/BeaverSoftware/hooks/257993","active":true,"name":"mobile","config":{"url":"http://foobar.com"},"id":257993,"created_at":"2012-05-19T06:01:45Z"}
diff --git a/github/tests/ReplayData/Organization.testGetHooks.txt b/github/tests/ReplayData/Organization.testGetHooks.txt
new file mode 100644
--- /dev/null
+++ b/github/tests/ReplayData/Organization.testGetHooks.txt
@@ -0,0 +1,10 @@
+https
+GET
+api.github.com
+None
+/orgs/BeaverSoftware/hooks
+{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
+None
+200
+[('status', '200 OK'), ('x-ratelimit-remaining', '4953'), ('content-length', '295'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"07e5e1a2fafd1a5e2de62eb3afd007d5"'), ('date', 'Sun, 27 May 2012 07:02:25 GMT'), ('content-type', 'application/json; charset=utf-8')]
+[{"updated_at":"2012-05-27T06:00:32Z","last_response":{"status":"ok","message":"OK","code":200},"events":["push"],"url":"https://api.github.com/orgs/BeaverSoftware/hooks/257993","active":true,"name":"web","config":{"url":"http://foobar.com"},"id":257993,"created_at":"2012-05-19T06:01:45Z"}]