Mark notification as read (#932)

Wraps the parts of the notification API that allows the user to mark notifications as read.
New methods are : 
 
- Notification.mark_as_read : marks a single notification thread as read
- Repository.mark_notifications_as_read : marks all the notifications for a given repository as read
- AuthenticatedUser.mark_notifications_as_read : marks all the notifications as read

Aims to fix : https://github.com/PyGithub/PyGithub/issues/571 and simply uses the APIs described on this page : https://developer.github.com/enterprise/11.10.340/v3/activity/notifications/

A weird thing I noticed doing this is that Repository.notifications_url doesn't seem to be usable directly. I used Repository.url + "/notifications" instead.
This commit is contained in:
Alice GIRARD
2018-10-19 07:24:45 +08:00
committed by Wan Liuyang
parent 2b2ecfad4d
commit 0a10d7cdf0
12 changed files with 166 additions and 0 deletions
+16
View File
@@ -1044,6 +1044,22 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject):
)
return status == 200
def mark_notifications_as_read(self, last_read_at=datetime.datetime.utcnow()):
"""
:calls: `PUT /notifications <https://developer.github.com/v3/activity/notifications>`_
:param last_read_at: datetime
"""
assert isinstance(last_read_at, datetime.datetime)
put_parameters = {
"last_read_at": last_read_at.strftime('%Y-%m-%dT%H:%M:%SZ')
}
headers, data = self._requester.requestJsonAndCheck(
"PUT",
"/notifications",
input=put_parameters
)
def remove_from_emails(self, *emails):
"""
:calls: `DELETE /user/emails <http://developer.github.com/v3/users/emails>`_
+9
View File
@@ -115,6 +115,15 @@ class Notification(github.GithubObject.CompletableGithubObject):
self._completeIfNotSet(self._url)
return self._url.value
def mark_as_read(self):
"""
:calls: `PATCH /notifications/threads/:id <https://developer.github.com/v3/activity/notifications/>`_
"""
headers, data = self._requester.requestJsonAndCheck(
"PATCH",
self.url,
)
def _initAttributes(self):
self._id = github.GithubObject.NotSet
self._last_read_at = github.GithubObject.NotSet
+16
View File
@@ -2473,6 +2473,22 @@ class Repository(github.GithubObject.CompletableGithubObject):
for element in data["issues"]
]
def mark_notifications_as_read(self, last_read_at=datetime.datetime.utcnow()):
"""
:calls: `PUT /repos/:owner/:repo/notifications <https://developer.github.com/v3/activity/notifications>`_
:param last_read_at: datetime
"""
assert isinstance(last_read_at, datetime.datetime)
put_parameters = {
"last_read_at": last_read_at.strftime('%Y-%m-%dT%H:%M:%SZ')
}
headers, data = self._requester.requestJsonAndCheck(
"PUT",
self.url + "/notifications",
input=put_parameters
)
def merge(self, base, head, commit_message=github.GithubObject.NotSet):
"""
:calls: `POST /repos/:owner/:repo/merges <http://developer.github.com/v3/repos/merging>`_
+1
View File
@@ -71,6 +71,7 @@ from Label import *
from Milestone import *
from NamedUser import *
from Markdown import *
from Notification import *
from OrganizationHasInMembers import *
from Organization import *
from Project import *
+3
View File
@@ -243,6 +243,9 @@ class AuthenticatedUser(Framework.TestCase):
def testGetNotificationsWithOtherArguments(self):
self.assertListKeyEqual(self.user.get_notifications(all=True), lambda n: n.id, [])
def testMarkNotificationsAsRead(self):
self.user.mark_notifications_as_read(datetime.datetime(2018, 10, 18, 18, 20, 01, 0))
def testGetTeams(self):
self.assertListKeyEqual(self.user.get_teams(), lambda t: t.name, ["Owners", "Honoraries", "Honoraries", "Honoraries", "Honoraries", "Honoraries", "Honoraries", "Honoraries", "Honoraries", "Honoraries"])
+42
View File
@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
############################ Copyrights and license ############################
# #
# Copyright 2012 Vincent Jacques <vincent@vincent-jacques.net> #
# Copyright 2012 Zearin <zearin@gonk.net> #
# Copyright 2013 Vincent Jacques <vincent@vincent-jacques.net> #
# Copyright 2014 Vincent Jacques <vincent@vincent-jacques.net> #
# Copyright 2016 Jannis Gebauer <ja.geb@me.com> #
# Copyright 2016 Peter Buckley <dx-pbuckley@users.noreply.github.com> #
# Copyright 2018 sfdye <tsfdye@gmail.com> #
# #
# This file is part of PyGithub. #
# http://pygithub.readthedocs.io/ #
# #
# 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 <http://www.gnu.org/licenses/>. #
# #
################################################################################
import Framework
import datetime
class Notification(Framework.TestCase):
def setUp(self):
Framework.TestCase.setUp(self)
self.notification = self.g.get_user().get_notifications()[0]
def testMarkAsRead(self):
self.notification.mark_as_read()
@@ -0,0 +1,11 @@
https
PUT
api.github.com
None
/notifications
{'Content-Type': 'application/json', 'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
{"last_read_at": "2018-10-18T18:20:01Z"}
205
[('Server', 'GitHub.com'), ('Date', 'Thu, 18 Oct 2018 18:41:54 GMT'), ('Content-Type', 'text/plain;charset=utf-8'), ('Content-Length', '0'), ('Status', '205 Reset Content'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4989'), ('X-RateLimit-Reset', '1539890344'), ('X-OAuth-Scopes', 'admin:gpg_key, admin:public_key, admin:repo_hook, gist, notifications, repo, user, write:discussion'), ('X-Accepted-OAuth-Scopes', 'notifications, repo'), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('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'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '1; mode=block'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('X-GitHub-Request-Id', 'B236:4C25:2ABE5CA:58BE030:5BC8D3F2')]
@@ -0,0 +1,11 @@
https
GET
api.github.com
None
/notifications
{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
None
200
[('Server', 'GitHub.com'), ('Date', 'Thu, 18 Oct 2018 18:46:58 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Transfer-Encoding', 'chunked'), ('Status', '200 OK'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4984'), ('X-RateLimit-Reset', '1539890344'), ('Cache-Control', 'private, max-age=60, s-maxage=60'), ('Vary', 'Accept, Authorization, Cookie, X-GitHub-OTP'), ('ETag', 'W/""'), ('Last-Modified', 'Thu, 18 Oct 2018 18:29:47 GMT'), ('X-Poll-Interval', '60'), ('X-OAuth-Scopes', 'admin:gpg_key, admin:public_key, admin:repo_hook, gist, notifications, repo, user, write:discussion'), ('X-Accepted-OAuth-Scopes', 'notifications, repo'), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('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'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '1; mode=block'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('Content-Encoding', 'gzip'), ('X-GitHub-Request-Id', 'B148:4C25:2AC480C:58CB140:5BC8D522')]
[{"id":"397777914","unread":true,"reason":"subscribed","updated_at":"2018-10-18T18:29:47Z","last_read_at":"2018-10-18T13:20:08Z","subject":{"title":"chore: Add more repos migrated on Quay","url":"https://api.github.com/repos/dailymotion/jarvis/pulls/103","latest_comment_url":"https://api.github.com/repos/dailymotion/jarvis/pulls/103","type":"PullRequest"},"repository":{"id":117231874,"node_id":"MDEwOlJlcG9zaXRvcnkxMTcyMzE4NzQ=","name":"jarvis","full_name":"dailymotion/jarvis","private":true,"owner":{"login":"dailymotion","id":115313,"node_id":"MDEyOk9yZ2FuaXphdGlvbjExNTMxMw==","avatar_url":"https://avatars2.githubusercontent.com/u/115313?v=4","gravatar_id":"","url":"https://api.github.com/users/dailymotion","html_url":"https://github.com/dailymotion","followers_url":"https://api.github.com/users/dailymotion/followers","following_url":"https://api.github.com/users/dailymotion/following{/other_user}","gists_url":"https://api.github.com/users/dailymotion/gists{/gist_id}","starred_url":"https://api.github.com/users/dailymotion/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dailymotion/subscriptions","organizations_url":"https://api.github.com/users/dailymotion/orgs","repos_url":"https://api.github.com/users/dailymotion/repos","events_url":"https://api.github.com/users/dailymotion/events{/privacy}","received_events_url":"https://api.github.com/users/dailymotion/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/dailymotion/jarvis","description":"A shared library containing Jenkins pipeline steps and utilities","fork":false,"url":"https://api.github.com/repos/dailymotion/jarvis","forks_url":"https://api.github.com/repos/dailymotion/jarvis/forks","keys_url":"https://api.github.com/repos/dailymotion/jarvis/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dailymotion/jarvis/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dailymotion/jarvis/teams","hooks_url":"https://api.github.com/repos/dailymotion/jarvis/hooks","issue_events_url":"https://api.github.com/repos/dailymotion/jarvis/issues/events{/number}","events_url":"https://api.github.com/repos/dailymotion/jarvis/events","assignees_url":"https://api.github.com/repos/dailymotion/jarvis/assignees{/user}","branches_url":"https://api.github.com/repos/dailymotion/jarvis/branches{/branch}","tags_url":"https://api.github.com/repos/dailymotion/jarvis/tags","blobs_url":"https://api.github.com/repos/dailymotion/jarvis/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dailymotion/jarvis/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dailymotion/jarvis/git/refs{/sha}","trees_url":"https://api.github.com/repos/dailymotion/jarvis/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dailymotion/jarvis/statuses/{sha}","languages_url":"https://api.github.com/repos/dailymotion/jarvis/languages","stargazers_url":"https://api.github.com/repos/dailymotion/jarvis/stargazers","contributors_url":"https://api.github.com/repos/dailymotion/jarvis/contributors","subscribers_url":"https://api.github.com/repos/dailymotion/jarvis/subscribers","subscription_url":"https://api.github.com/repos/dailymotion/jarvis/subscription","commits_url":"https://api.github.com/repos/dailymotion/jarvis/commits{/sha}","git_commits_url":"https://api.github.com/repos/dailymotion/jarvis/git/commits{/sha}","comments_url":"https://api.github.com/repos/dailymotion/jarvis/comments{/number}","issue_comment_url":"https://api.github.com/repos/dailymotion/jarvis/issues/comments{/number}","contents_url":"https://api.github.com/repos/dailymotion/jarvis/contents/{+path}","compare_url":"https://api.github.com/repos/dailymotion/jarvis/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dailymotion/jarvis/merges","archive_url":"https://api.github.com/repos/dailymotion/jarvis/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dailymotion/jarvis/downloads","issues_url":"https://api.github.com/repos/dailymotion/jarvis/issues{/number}","pulls_url":"https://api.github.com/repos/dailymotion/jarvis/pulls{/number}","milestones_url":"https://api.github.com/repos/dailymotion/jarvis/milestones{/number}","notifications_url":"https://api.github.com/repos/dailymotion/jarvis/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dailymotion/jarvis/labels{/name}","releases_url":"https://api.github.com/repos/dailymotion/jarvis/releases{/id}","deployments_url":"https://api.github.com/repos/dailymotion/jarvis/deployments"},"url":"https://api.github.com/notifications/threads/397777914","subscription_url":"https://api.github.com/notifications/threads/397777914/subscription"}]
@@ -0,0 +1,11 @@
https
PATCH
api.github.com
None
/notifications/threads/397777914
{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'}
None
205
[('Server', 'GitHub.com'), ('Date', 'Thu, 18 Oct 2018 18:46:59 GMT'), ('Content-Type', 'text/plain;charset=utf-8'), ('Content-Length', '0'), ('Status', '205 Reset Content'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4983'), ('X-RateLimit-Reset', '1539890344'), ('X-OAuth-Scopes', 'admin:gpg_key, admin:public_key, admin:repo_hook, gist, notifications, repo, user, write:discussion'), ('X-Accepted-OAuth-Scopes', 'notifications, repo'), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('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'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '1; mode=block'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('X-GitHub-Request-Id', 'B14A:4C25:2AC483C:58CB194:5BC8D523')]
File diff suppressed because one or more lines are too long
+4
View File
@@ -500,6 +500,10 @@ class Repository(Framework.TestCase):
self.assertListKeyEqual(issues[0].labels, lambda l: l.name, ["Functionalities", "RequestedByUser"])
self.assertEqual(issues[0].state, "open")
def testMarkNotificationsAsRead(self):
repo = self.g.get_user().get_repo("PyGithub")
repo.mark_notifications_as_read(datetime.datetime(2018, 10, 18, 18, 19, 43, 0))
def testAssignees(self):
lyloa = self.g.get_user("Lyloa")
jacquev6 = self.g.get_user("jacquev6")