Skip to content

Commit 61476a4

Browse files
committed
Add support for repository invitations
Add support for the repository invitations API documented in https://developer.github.com/v3/repos/invitations/. Closes #739
1 parent 8aa3b39 commit 61476a4

16 files changed

+460
-0
lines changed

src/github3/github.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from . import auths
1111
from . import events
1212
from . import gists
13+
from .repos import invitation
1314
from . import issues
1415
from . import licenses
1516
from . import models
@@ -1524,6 +1525,23 @@ def repository(self, owner, repository):
15241525
json = self._json(self._get(url), 200)
15251526
return self._instance_or_null(repo.Repository, json)
15261527

1528+
@requires_auth
1529+
def repository_invitations(self, number=-1, etag=None):
1530+
"""Iterate over the repository invitations for the current user.
1531+
1532+
:param int number:
1533+
(optional), number of invitations to return. Default: -1 returns
1534+
all available invitations
1535+
:param str etag:
1536+
(optional), ETag from a previous request to the same endpoint
1537+
:returns:
1538+
generator of repository invitation objects
1539+
:rtype:
1540+
:class:`~github3.repos.invitation.Invitation`
1541+
"""
1542+
url = self._build_url('user', 'repository_invitations')
1543+
return self._iter(int(number), url, invitation.Invitation, etag=etag)
1544+
15271545
def repository_with_id(self, number):
15281546
"""Retrieve the repository with the globally unique id.
15291547

src/github3/repos/invitation.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# -*- coding: utf-8 -*-
2+
"""Invitation related logic."""
3+
from __future__ import unicode_literals
4+
5+
from json import dumps
6+
7+
from .. import models
8+
from .. import users
9+
10+
from ..decorators import requires_auth
11+
12+
13+
class Invitation(models.GitHubCore):
14+
"""Representation of an invitation to collaborate on a repository.
15+
16+
.. attribute:: created_at
17+
18+
A :class:`~datetime.datetime` instance representing the time and date
19+
when this invitation was created.
20+
21+
.. attribute:: html_url
22+
23+
The URL to view this invitation in a browser.
24+
25+
.. attribute:: id
26+
27+
The unique identifier for this invitation.
28+
29+
.. attribute:: invitee
30+
31+
A :class:`~github3.users.ShortUser` representing the user who was
32+
invited to collaborate.
33+
34+
.. attribute:: inviter
35+
36+
A :class:`~github3.users.ShortUser` representing the user who invited
37+
the ``invitee``.
38+
39+
.. attribute:: permissions
40+
41+
The permissions that the ``invitee`` will have on the repository. Valid
42+
values are ``read``, ``write``, and ``admin``.
43+
44+
.. attribute:: repository
45+
46+
A :class:`~github3.repos.ShortRepository` representing the repository
47+
on which the ``invitee` was invited to collaborate.
48+
49+
.. attribute:: url
50+
51+
The API URL that the ``invitee`` can use to respond to the invitation.
52+
Note that the ``inviter`` must use a different URL, not returned by
53+
the API, to update or cancel the invitation.
54+
"""
55+
56+
class_name = 'Invitation'
57+
allowed_permissions = frozenset(['admin', 'read', 'write'])
58+
59+
def _update_attributes(self, invitation):
60+
from . import repo
61+
self.created_at = self._strptime(invitation['created_at'])
62+
self.html_url = invitation['html_url']
63+
self.id = invitation['id']
64+
self.invitee = users.ShortUser(invitation['invitee'], self)
65+
self.inviter = users.ShortUser(invitation['inviter'], self)
66+
self.permissions = invitation['permissions']
67+
self.repository = repo.ShortRepository(invitation['repository'], self)
68+
self.url = invitation['url']
69+
70+
def _repr(self):
71+
return '<Invitation [{0}]>'.format(self.repository.full_name)
72+
73+
@requires_auth
74+
def accept(self):
75+
"""Accept this invitation.
76+
77+
:returns:
78+
True if successful, False otherwise
79+
:rtype:
80+
bool
81+
"""
82+
return self._boolean(self._patch(self.url), 204, 404)
83+
84+
@requires_auth
85+
def decline(self):
86+
"""Decline this invitation.
87+
88+
:returns:
89+
True if successful, False otherwise
90+
:rtype:
91+
bool
92+
"""
93+
return self._boolean(self._delete(self.url), 204, 404)
94+
95+
@requires_auth
96+
def delete(self):
97+
"""Delete this invitation.
98+
99+
:returns:
100+
True if successful, False otherwise
101+
:rtype:
102+
bool
103+
"""
104+
url = self._build_url(
105+
'invitations', self.id, base_url=self.repository.url)
106+
return self._boolean(self._delete(url), 204, 404)
107+
108+
@requires_auth
109+
def update(self, permissions):
110+
"""Update this invitation.
111+
112+
:param str permissions:
113+
(required), the permissions that will be granted by this invitation
114+
once it has been updated. Options: 'admin', 'read', 'write'
115+
:returns:
116+
The updated invitation
117+
:rtype:
118+
:class:`~github3.repos.invitation.Invitation`
119+
"""
120+
if permissions not in self.allowed_permissions:
121+
raise ValueError("'permissions' must be one of {0}".format(
122+
', '.join(sorted(self.allowed_permissions))
123+
))
124+
url = self._build_url(
125+
'invitations', self.id, base_url=self.repository.url)
126+
data = {'permissions': permissions}
127+
json = self._json(self._patch(url, data=dumps(data)), 200)
128+
return self._instance_or_null(Invitation, json)

src/github3/repos/repo.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
from . import contents
3535
from . import deployment
3636
from . import hook
37+
from . import invitation
3738
from . import issue_import
3839
from . import pages
3940
from . import release
@@ -1536,6 +1537,23 @@ def import_issue(self, title, body, created_at, assignee=None,
15361537
json = self._json(data, 202)
15371538
return self._instance_or_null(issue_import.ImportedIssue, json)
15381539

1540+
@requires_auth
1541+
def invitations(self, number=-1, etag=None):
1542+
"""Iterate over the invitations to this repository.
1543+
1544+
:param int number:
1545+
(optional), number of invitations to return. Default: -1 returns
1546+
all available invitations
1547+
:param str etag:
1548+
(optional), ETag from a previous request to the same endpoint
1549+
:returns:
1550+
generator of repository invitation objects
1551+
:rtype:
1552+
:class:`~github3.repos.invitation.Invitation`
1553+
"""
1554+
url = self._build_url('invitations', base_url=self._api)
1555+
return self._iter(int(number), url, invitation.Invitation, etag=etag)
1556+
15391557
def is_assignee(self, username):
15401558
"""Check if the user can be assigned an issue on this repository.
15411559
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"http_interactions": [{"request": {"body": {"string": "", "encoding": "utf-8"}, "headers": {"Accept-Encoding": ["gzip, deflate"], "Accept": ["application/vnd.github.v3.full+json"], "User-Agent": ["github3.py/1.1.0"], "Accept-Charset": ["utf-8"], "Connection": ["keep-alive"], "Content-Type": ["application/json"], "Authorization": ["token <AUTH_TOKEN>"]}, "method": "GET", "uri": "https://api.github.com/user"}, "response": {"body": {"string": "", "base64_string": "H4sIAAAAAAAAA51U24rbMBT8lUXPyfqSixNDKIVSaGkWCmlZ+mJkWXG0VSRVkhPSkH/vyHaWbFgKyVPi4zOj0ZwzPhKpa6FITl4o+9NwKzgZEFGRfDRL0lk8IEpXvAgFsvz0ffrz+Umyl+V4ufoyXn5cLNBMd9RTWzRWomfjvXF5FHVFlz7Wwm+asnHcMq08V/6R6W3URB39h91iDIra9iTtOShckRnR83RgkLnoUu/Gb+WVgO7ctv+yc62l1Hvgr/X+94joFQZt3X+h6nsoADtG2m84DMM1TuHywvkb5bSQYxR+MJpA4jACy6vbJPUgCNoraDlGlhvdsjWlY1YYL7S6UdobKKi0rakSf+kdVIA6MARRN4poIYDyHRbuRmyHOUbGih1lh2CH5YyLHdy9h+8KDDp/MBxr/gPzD14LzwtabUMI11Q6jsjRbWj4Spk2+uFJY7bO8zVVGv3YaEPVgeSqkXJASuS3zx1i97rq57yIAJGatf6j79tBq8HDZ0sVC0HnWyoQ245qIyynpcTR3jaQUQLcvzJNKQUrOl/z+WxA+kq7iSRPs3MuEC2SJ6PxRU7wnEwhHOweJlIPHWmcxMN4NhzFqzTN4zRP01/Q05jqTc9sGGfDJFvFE3yP8jQJPe1g4Fl/9GQER2GRLM4vepX4doW9rt6pV8L9Rv5ojbtOskmWBFulpKW21OtwA4D9XhdryvBc0AaJVV6cbeznZCSFp8fzvNaWB0+dofA2n2fTyTQdz+fvcV9LPZ3+AVw/d5eJBQAA", "encoding": "utf-8"}, "headers": {"X-XSS-Protection": ["1; mode=block"], "Content-Security-Policy": ["default-src 'none'"], "Access-Control-Expose-Headers": ["ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval"], "Transfer-Encoding": ["chunked"], "Last-Modified": ["Tue, 17 Jul 2018 05:38:21 GMT"], "Access-Control-Allow-Origin": ["*"], "X-Frame-Options": ["deny"], "Status": ["200 OK"], "X-GitHub-Request-Id": ["88EE:3FAB:10C7C9D:2446B09:5B62EA4B"], "ETag": ["W/\"2c29c3ba637f55254842a060df00153d\""], "Date": ["Thu, 02 Aug 2018 11:26:03 GMT"], "X-RateLimit-Remaining": ["4996"], "Strict-Transport-Security": ["max-age=31536000; includeSubdomains; preload"], "Server": ["GitHub.com"], "X-OAuth-Scopes": ["read:user, repo:invite"], "X-GitHub-Media-Type": ["github.v3; param=full; format=json"], "X-Content-Type-Options": ["nosniff"], "Content-Encoding": ["gzip"], "X-Runtime-rack": ["0.057858"], "Vary": ["Accept, Authorization, Cookie, X-GitHub-OTP"], "X-RateLimit-Limit": ["5000"], "Cache-Control": ["private, max-age=60, s-maxage=60"], "Referrer-Policy": ["origin-when-cross-origin, strict-origin-when-cross-origin"], "Content-Type": ["application/json; charset=utf-8"], "X-Accepted-OAuth-Scopes": [""], "X-RateLimit-Reset": ["1533211541"]}, "status": {"message": "OK", "code": 200}, "url": "https://api.github.com/user"}, "recorded_at": "2018-08-02T11:26:03"}, {"request": {"body": {"string": "", "encoding": "utf-8"}, "headers": {"Accept-Encoding": ["gzip, deflate"], "Accept": ["application/vnd.github.v3.full+json"], "User-Agent": ["github3.py/1.1.0"], "Accept-Charset": ["utf-8"], "Connection": ["keep-alive"], "Content-Type": ["application/json"], "Authorization": ["token <AUTH_TOKEN>"]}, "method": "GET", "uri": "https://api.github.com/user/repository_invitations?per_page=100"}, "response": {"body": {"string": "", "base64_string": "H4sIAAAAAAAAA+2Za2/iOBSG/wrK16WYW2cpUjWqtLMjqoXOhe5Ws1ohJzHEbWJnbYeKRv3ve2yHJFBNgLrSfqlU9ZL6PHlz7OMcv/ydezT0xr3u4Hz44bzb9hgPyUJf8qa/TR5v4us4+HzxhO++rQMWX/vsRxp+/r2L/7rIpvOrp9n8a382v7r02p4gKZdUcbHxxhZ6cf7rqHc+HO5CP+1BH85n82A4nd92Z1eXGsRwQuD2lMmUCnIW8nvJGVxfZnG8KP6Z0CDCCZfoxSj+yIjQCmK+ogw426FA0I/VH4Gsi8Gupq8f/rybxcH9ZHgzvx3czKdaB15jhcUiEzFQIqVSOUbIXpSDzoqqKPMzSUTAmSJMdQKeoAwV/I/ryyEwVqKgmIzChT1aSguQjQaaRDXBkUriPQH2vmZ4beCSxzF/hOh9uU03QGWUzq4hULZ6BQGicsRVRCBb8AjP+sGpVKeJMRE50j9g/WmGhPQLEp4kqIgBOXodPOdIL0sDy3wZCJoqytlpwnYigcTFCjP6hE8nQaQEgKmUk57KREAkWcNCOy3UhuQoFXSNg41OhSABoWtI7Ctwe7FAU5tUl+stTLxOM1VkgcNEl94Sx5I8tz1zawWDzIU21NIxq/plaYeknEG44WT2/cvk26czmZKALmnQEllMZEvxlhKYySUXSWspeNK6m/6hr15/v5m1MAtbPg4eOiAVRjx4YyUykNRYlib7ZbW91KVBByalEQF1CABQ9EA2ThwdnyP4XhRQADWNfS4w7MpO4B1Qjup/6gWlCE6c+AYAoIhzt0waAIColBk5an03z4zhSLQtIpYlvt3fjimdZrQlgFYsJV0xQpwyWEJytN2CfSiDIHLDbhk5sr+Z2cYrJ6k6HjB+zH0nDrwJkYHkSEbYvnTUwlWdpmrGDlSQpbNUzSihSjjOt5GpISUS3nwKpt5J55aB8iKjMWarDK/cqCUEZl2/n1f46WCn0lw7FQWQuv8S1M/cN7mKo5XahgHq3S2lFaaCmi6kuQM4kIBaN2NSkCT0UGPQTCwQO8v+DbB6ne6j9d+H+5jDcjUjR9WebDf9gu6S3WLX3+qs36No9p2WxJaB8l9SrCK9c8GtUiyIi+gCgXIfQ9fV6XTyiGDTRydEOFawJQAKiyCC1tFFZ75l6A4NK9OiL7XMEFr2mOPQKbclBIB2Gl20WkJ9/lM4hDoJNIA6MaHQtirO3PbYilJnM650a3zMUaW53HZA+UdJWUDaOI7bsGoVDSisYzgE6lmEhpO4ZcgS4DHAB7DHlZjAknbKuiCWkSN7tAxJGvON8y5Uw3hw0KFsDccfOOhU7sM9Dv7NiKBEr0fwAAajXn+0Z7Ns3YcpuCCT4dS6IE3uQ/+n7oPFO5gPdb3N57T6yJPshzLw9f7DPsLFgKhYTg5EhXk7C6LGrLsXsJRO9iAq1KkmRBVptgi4+TFnH30IkaiKfRsboq5lx8MAVYd9CFue7+Zg6WW+m4Pv5qDpof9Pc5CIBNos7cqCqfco4AUKxRwIAo5huMAKLva7vdFZF776815v3B+Mu4MfMKbRs9P7j/F+7UcSC1P7tg9C2w87gNH8ivvJpwzgAJYw7/mf/wB9sVVYQxkAAA==", "encoding": "utf-8"}, "headers": {"X-XSS-Protection": ["1; mode=block"], "Content-Security-Policy": ["default-src 'none'"], "Access-Control-Expose-Headers": ["ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval"], "Transfer-Encoding": ["chunked"], "Access-Control-Allow-Origin": ["*"], "X-Frame-Options": ["deny"], "Status": ["200 OK"], "X-GitHub-Request-Id": ["88EE:3FAB:10C7CC5:2446B27:5B62EA4B"], "ETag": ["W/\"c36a06096decdef54b48591988b79b89\""], "Date": ["Thu, 02 Aug 2018 11:26:03 GMT"], "X-RateLimit-Remaining": ["4995"], "Strict-Transport-Security": ["max-age=31536000; includeSubdomains; preload"], "Server": ["GitHub.com"], "X-OAuth-Scopes": ["read:user, repo:invite"], "X-GitHub-Media-Type": ["github.v3; param=full; format=json"], "X-Content-Type-Options": ["nosniff"], "Content-Encoding": ["gzip"], "X-Runtime-rack": ["0.044125"], "Vary": ["Accept, Authorization, Cookie, X-GitHub-OTP"], "X-RateLimit-Limit": ["5000"], "Cache-Control": ["private, max-age=60, s-maxage=60"], "Referrer-Policy": ["origin-when-cross-origin, strict-origin-when-cross-origin"], "Content-Type": ["application/json; charset=utf-8"], "X-Accepted-OAuth-Scopes": ["public_repo, repo, repo:invite"], "X-RateLimit-Reset": ["1533211541"]}, "status": {"message": "OK", "code": 200}, "url": "https://api.github.com/user/repository_invitations?per_page=100"}, "recorded_at": "2018-08-02T11:26:03"}], "recorded_with": "betamax/0.8.1"}

0 commit comments

Comments
 (0)