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 sigmavirus24#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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy