From 4c64b28920c8eea834e31b18e2aef6b08d1a94a0 Mon Sep 17 00:00:00 2001 From: Jannis Gebauer Date: Mon, 10 Jul 2017 13:14:09 +0200 Subject: [PATCH] added repository collaborator invitation --- github/AuthenticatedUser.py | 18 +++ github/Invitation.py | 109 ++++++++++++++++++ github/MainClass.py | 1 + github/Repository.py | 7 +- github/tests/AuthenticatedUser.py | 3 + ...AuthenticatedUser.testAcceptInvitation.txt | 11 ++ .../ReplayData/Issue214.testCollaborators.txt | 2 +- .../ReplayData/Repository.testAssignees.txt | 2 +- .../Repository.testCollaborators.txt | 2 +- 9 files changed, 151 insertions(+), 4 deletions(-) create mode 100644 github/Invitation.py create mode 100644 github/tests/ReplayData/AuthenticatedUser.testAcceptInvitation.txt diff --git a/github/AuthenticatedUser.py b/github/AuthenticatedUser.py index ad1523c4..760a264e 100644 --- a/github/AuthenticatedUser.py +++ b/github/AuthenticatedUser.py @@ -1051,6 +1051,24 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "/user/watched/" + watched._identity ) + def accept_invitation(self, invitation): + """ + :calls: `PATCH /user/repository_invitations/:invitation_id ` + :param invitation: :class:`github.Invitation.Invitation` or int + :rtype: None + """ + assert isinstance(invitation, github.Invitation.Invitation) or isinstance(invitation, int) + + if isinstance(invitation, github.Invitation.Invitation): + invitation = invitation.id + + headers, data = self._requester.requestJsonAndCheck( + "PATCH", + "/user/repository_invitations/" + str(invitation), + headers={'Accept': 'application/vnd.github.swamp-thing-preview+json'}, + input={} + ) + def _initAttributes(self): self._avatar_url = github.GithubObject.NotSet self._bio = github.GithubObject.NotSet diff --git a/github/Invitation.py b/github/Invitation.py new file mode 100644 index 00000000..bf2668cc --- /dev/null +++ b/github/Invitation.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- + +# ########################## Copyrights and license ############################ +# # +# Copyright 2017 Jannis Gebauer # +# # +# This file is part of PyGithub. # +# http://pygithub.github.io/PyGithub/v1/index.html # +# # +# PyGithub is free software: you can redistribute it and/or modify it under # +# the terms of the GNU Lesser General Public License as published by the Free # +# Software Foundation, either version 3 of the License, or (at your option) # +# any later version. # +# # +# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # +# details. # +# # +# You should have received a copy of the GNU Lesser General Public License # +# along with PyGithub. If not, see . # +# # +# ############################################################################## + +import urllib + +import github.GithubObject + + +class Invitation(github.GithubObject.CompletableGithubObject): + """ + This class represents repository invitations. The reference can be found here https://developer.github.com/v3/repos/invitations/ + """ + + def __repr__(self): + return self.get__repr__({"id": self._id.value}) + + @property + def id(self): + """ + :type: integer + """ + self._completeIfNotSet(self._id) + return self._id.value + + @property + def permissions(self): + """ + :type: string + """ + self._completeIfNotSet(self._permissions) + return self._permissions.value + + @property + def created_at(self): + """ + :type: string + """ + self._completeIfNotSet(self._created_at) + return self._created_at.value + + @property + def url(self): + """ + :type: string + """ + self._completeIfNotSet(self._url) + return self._url.value + + + @property + def html_url(self): + """ + :type: string + """ + self._completeIfNotSet(self._html_url) + return self._html_url.value + + @property + def repository(self): + """ + :type: Repository + """ + self._completeIfNotSet(self._repository) + return self._repository.value + + def _initAttributes(self): + self._id = github.GithubObject.NotSet + self._permissions = github.GithubObject.NotSet + self._created_at = github.GithubObject.NotSet + self._url = github.GithubObject.NotSet + self._html_url = github.GithubObject.NotSet + self._repository = github.GithubObject.NotSet + + def _useAttributes(self, attributes): + if "repository" in attributes: # pragma no branch + self._assignee = self._makeClassAttribute(github.Repository.Repository, attributes["repository"]) + if "created_at" in attributes: # pragma no branch + self._closed_at = self._makeDatetimeAttribute(attributes["created_at"]) + if "id" in attributes: # pragma no branch + self._id = self._makeIntAttribute(attributes["id"]) + + if "permissions" in attributes: # pragma no branch + self._permissions = self._makeStringAttribute(attributes["permissions"]) + if "url" in attributes: # pragma no branch + self._url = self._makeStringAttribute(attributes["url"]) + if "html_url" in attributes: # pragma no branch + self._html_url = self._makeStringAttribute(attributes["html_url"]) + diff --git a/github/MainClass.py b/github/MainClass.py index 2184547e..024f0a46 100644 --- a/github/MainClass.py +++ b/github/MainClass.py @@ -50,6 +50,7 @@ import StatusMessage import RateLimit import InstallationAuthorization import GithubException +import Invitation atLeastPython3 = sys.hexversion >= 0x03000000 diff --git a/github/Repository.py b/github/Repository.py index b079bc88..5ae3d975 100644 --- a/github/Repository.py +++ b/github/Repository.py @@ -673,8 +673,13 @@ class Repository(github.GithubObject.CompletableGithubObject): headers, data = self._requester.requestJsonAndCheck( "PUT", - self.url + "/collaborators/" + collaborator + self.url + "/collaborators/" + collaborator, + headers={'Accept': 'application/vnd.github.swamp-thing-preview+json'} ) + # return an invitation object if there's data returned by the API. If data is empty + # there's a pending invitation for the given user. + return github.Invitation.Invitation(self._requester, headers, data, completed=True) if \ + data is not None else None def compare(self, base, head): """ diff --git a/github/tests/AuthenticatedUser.py b/github/tests/AuthenticatedUser.py index 57dd1d59..bb00ccc7 100644 --- a/github/tests/AuthenticatedUser.py +++ b/github/tests/AuthenticatedUser.py @@ -234,3 +234,6 @@ class AuthenticatedUser(Framework.TestCase): def testGetTeams(self): self.assertListKeyEqual(self.user.get_teams(), lambda t: t.name, ["Owners", "Honoraries", "Honoraries", "Honoraries", "Honoraries", "Honoraries", "Honoraries", "Honoraries", "Honoraries", "Honoraries"]) + + def testAcceptInvitation(self): + self.assertIsNone(self.user.accept_invitation(4294886)) \ No newline at end of file diff --git a/github/tests/ReplayData/AuthenticatedUser.testAcceptInvitation.txt b/github/tests/ReplayData/AuthenticatedUser.testAcceptInvitation.txt new file mode 100644 index 00000000..e6f4a1f3 --- /dev/null +++ b/github/tests/ReplayData/AuthenticatedUser.testAcceptInvitation.txt @@ -0,0 +1,11 @@ +https +PATCH +api.github.com +None +/user/repository_invitations/4294886 +{'Content-Type': 'application/json', 'Authorization': 'Basic login_and_password_removed', 'Accept': 'application/vnd.github.swamp-thing-preview+json', 'User-Agent': 'PyGithub/Python'} +{} +204 +[('status', '204 No Content'), ('x-ratelimit-remaining', '4981'), ('x-github-media-type', 'github.swamp-thing-preview; format=json'), ('x-content-type-options', 'nosniff'), ('content-security-policy', "default-src 'none'"), ('access-control-expose-headers', 'ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('x-github-request-id', 'D039:11DAA:17FC875:24A1090:59635F0C'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('vary', 'Accept-Encoding'), ('server', 'GitHub.com'), ('x-ratelimit-limit', '5000'), ('x-runtime-rack', '0.072659'), ('x-xss-protection', '1; mode=block'), ('x-served-by', '7f48e2f7761567e923121f17538d7a6d'), ('date', 'Mon, 10 Jul 2017 11:03:40 GMT'), ('access-control-allow-origin', '*'), ('x-frame-options', 'deny'), ('x-ratelimit-reset', '1499685825')] + + diff --git a/github/tests/ReplayData/Issue214.testCollaborators.txt b/github/tests/ReplayData/Issue214.testCollaborators.txt index 5880b26a..26219ad2 100644 --- a/github/tests/ReplayData/Issue214.testCollaborators.txt +++ b/github/tests/ReplayData/Issue214.testCollaborators.txt @@ -36,7 +36,7 @@ PUT api.github.com None /repos/farrd/PyGithub/collaborators/marcmenges -{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +{'Accept': 'application/vnd.github.swamp-thing-preview+json', 'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} null 204 [('status', '204 No Content'), ('x-ratelimit-remaining', '4762'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('access-control-expose-headers', 'ETag, Link, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('x-github-request-id', 'A99159CE:6DAA:6192A15:52AF94DC'), ('vary', 'Accept-Encoding'), ('server', 'GitHub.com'), ('x-ratelimit-limit', '5000'), ('access-control-allow-credentials', 'true'), ('date', 'Tue, 17 Dec 2013 00:03:41 GMT'), ('access-control-allow-origin', '*'), ('x-ratelimit-reset', '1387240227')] diff --git a/github/tests/ReplayData/Repository.testAssignees.txt b/github/tests/ReplayData/Repository.testAssignees.txt index cf02ca6a..d7138afd 100644 --- a/github/tests/ReplayData/Repository.testAssignees.txt +++ b/github/tests/ReplayData/Repository.testAssignees.txt @@ -47,7 +47,7 @@ PUT api.github.com None /repos/jacquev6/PyGithub/collaborators/Lyloa -{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +{'Accept': 'application/vnd.github.swamp-thing-preview+json', 'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} null 204 [('status', '204 No Content'), ('x-ratelimit-remaining', '4993'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('cache-control', ''), ('date', 'Fri, 07 Sep 2012 23:12:27 GMT')] diff --git a/github/tests/ReplayData/Repository.testCollaborators.txt b/github/tests/ReplayData/Repository.testCollaborators.txt index 393599f2..1465123f 100644 --- a/github/tests/ReplayData/Repository.testCollaborators.txt +++ b/github/tests/ReplayData/Repository.testCollaborators.txt @@ -25,7 +25,7 @@ PUT api.github.com None /repos/jacquev6/PyGithub/collaborators/Lyloa -{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +{'Accept': 'application/vnd.github.swamp-thing-preview+json', 'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} null 204 [('status', '204 No Content'), ('x-ratelimit-remaining', '4953'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"d41d8cd98f00b204e9800998ecf8427e"'), ('date', 'Sun, 27 May 2012 05:34:27 GMT')]