From b71329e560795a4df84cb419178ef660824f4c0d Mon Sep 17 00:00:00 2001 From: AKFish Date: Wed, 21 Aug 2013 22:25:20 +0800 Subject: [PATCH 01/24] Implement data persistence --- github/GithubObject.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/github/GithubObject.py b/github/GithubObject.py index 3fc89992..f20873b9 100644 --- a/github/GithubObject.py +++ b/github/GithubObject.py @@ -24,10 +24,13 @@ # # ################################################################################ +from __future__ import with_statement + import datetime import GithubException +import pickle class _NotSetType: def __repr__(self): @@ -96,6 +99,27 @@ class GithubObject(object): else: return datetime.datetime.strptime(s, "%Y-%m-%dT%H:%M:%SZ") + def save(self, file_name): + ''' + Save instance to a file + + :param file_name: the full path of target file + ''' + + with open(file_name, 'wb') as f: + pickle.dump(self, f) + + @classmethod + def load(cls, file_name): + ''' + Load saved instance from file + :param file_name: the full path to saved file + :rtype: saved instance. The type of loaded instance remains its orginal one and will not be affected by from which derived class the method is called. + ''' + with open(file_name, 'rb') as f: + return pickle.load(f) + + class NonCompletableGithubObject(GithubObject): def _completeIfNeeded(self): From bd7abb58772ae1a61fd7eb44308a3a2f60432ad6 Mon Sep 17 00:00:00 2001 From: AKFish Date: Wed, 21 Aug 2013 23:01:40 +0800 Subject: [PATCH 02/24] Add update() method --- .gitignore | 1 + github/Consts.py | 49 ++++++++++++++++++++++++++++++++++++++++++ github/GithubObject.py | 41 ++++++++++++++++++++++++++++++++++- 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 github/Consts.py diff --git a/.gitignore b/.gitignore index 3a7eff09..733d3ccd 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ GithubCredentials.py *.cfg *.bat +*.py~ diff --git a/github/Consts.py b/github/Consts.py new file mode 100644 index 00000000..2e551756 --- /dev/null +++ b/github/Consts.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- + +############################ Copyrights and license ############################ +# # +# Copyright 2013 AKFish # +# # +# This file is part of PyGithub. http://jacquev6.github.com/PyGithub/ # +# # +# 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 . # +# # +################################################################################ + +# TODO: As of Thu Aug 21 22:40:13 (BJT) Chinese Standard Time 2013 +# lots of consts in this project are explict +# should realy round them up and reference them by consts + +################################################################################ +# Helper Function # +################################################################################ +def get(dic, key): + if key in dic: + return key[dic] + return None + + +################################################################################ +# Request Header # +# (Case sensitive) # +################################################################################ +REQ_IF_NONE_MATCH = "If-None-Match" +REQ_IF_MODIFIED_SINCE = "If-Modified-Since" + +################################################################################ +# Response Header # +# (Lower Case) # +################################################################################ +RES_ETAG = "etag" +RES_LAST_MODIFED = "last-modified" diff --git a/github/GithubObject.py b/github/GithubObject.py index f20873b9..ae71cf7c 100644 --- a/github/GithubObject.py +++ b/github/GithubObject.py @@ -32,6 +32,8 @@ import GithubException import pickle +import Consts + class _NotSetType: def __repr__(self): return "NotSet" @@ -118,8 +120,45 @@ class GithubObject(object): ''' with open(file_name, 'rb') as f: return pickle.load(f) - + @property + def etag(self): + ''' + :type str + ''' + return Consts.get(self._headers, Consts.RES_ETAG) + + @property + def last_modified(self): + ''' + :type str + ''' + return Consts.get(self._headers, Consts.RES_LAST_MODIFED) + + + def update(self): + ''' + Check and update the object with conditional request + :rtype: Boolean value indicating whether the object is changed + ''' + conditionalRequestHeader = dict() + if self.etag is not None: + conditionalRequestHeader[Consts.REQ_IF_NONE_MATCH] = self.etag + if self.last_modified is not None: + conditionalRequestHeader[Consts.REQ_IF_MODIFIED_SINCE] = self.last_modified + + try: + headers, data = self._requester.requestJsonAndCheck( + "GET", + self._url, + conditionalRequestHeader, + None + ) + self._storeAndUseAttributes(data) + self.__completed = True + return True + except: #GithubException.NotModifiedException: + return False class NonCompletableGithubObject(GithubObject): def _completeIfNeeded(self): From 1e9ec2df089973db73aaf99b4ef147efd4614e7c Mon Sep 17 00:00:00 2001 From: AKFish Date: Wed, 21 Aug 2013 23:04:07 +0800 Subject: [PATCH 03/24] Add NotModifiedException class --- github/GithubException.py | 6 ++++++ github/GithubObject.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/github/GithubException.py b/github/GithubException.py index 12c9af62..53e2d302 100644 --- a/github/GithubException.py +++ b/github/GithubException.py @@ -5,6 +5,7 @@ # Copyright 2012 Vincent Jacques # # Copyright 2012 Zearin # # Copyright 2013 Vincent Jacques # +# Copyright 2013 AKFish # # # # This file is part of PyGithub. http://jacquev6.github.com/PyGithub/ # # # @@ -76,3 +77,8 @@ class RateLimitExceededException(GithubException): """ Exception raised when the rate limit is exceeded (when Github API replies with a 403 rate limit exceeded HTML status) """ + +class NotModifiedException(GithubException): + """ + Exception raised when conditional request is made to a resoure that has not changed + """ diff --git a/github/GithubObject.py b/github/GithubObject.py index ae71cf7c..6c68ce1f 100644 --- a/github/GithubObject.py +++ b/github/GithubObject.py @@ -157,7 +157,7 @@ class GithubObject(object): self._storeAndUseAttributes(data) self.__completed = True return True - except: #GithubException.NotModifiedException: + except GithubException.NotModifiedException: return False class NonCompletableGithubObject(GithubObject): From 6fd05baf6bea732dd846e08c40891c28060e7c64 Mon Sep 17 00:00:00 2001 From: AKFish Date: Wed, 21 Aug 2013 23:09:30 +0800 Subject: [PATCH 04/24] Handle response code 304 --- github/Requester.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/github/Requester.py b/github/Requester.py index 7d944c3e..a739cdf3 100644 --- a/github/Requester.py +++ b/github/Requester.py @@ -11,7 +11,7 @@ # Copyright 2012 Zearin # # Copyright 2013 Jonathan J Hunt # # Copyright 2013 Vincent Jacques # -# Copyright 2013 akfish # +# Copyright 2013 AKFish # # # # This file is part of PyGithub. http://jacquev6.github.com/PyGithub/ # # # @@ -175,6 +175,8 @@ class Requester: # Log frame self.DEBUG_ON_RESPONSE(status, responseHeaders, output) + if status == 304: + raise GithubException.NotModifiedException(sttatus, output) if status >= 400: raise self.__createException(status, output) return responseHeaders, output From 5b09f6c82191601cad92076ad4761fe927c511ed Mon Sep 17 00:00:00 2001 From: AKFish Date: Wed, 21 Aug 2013 23:17:59 +0800 Subject: [PATCH 05/24] Implement conditional request --- github/Requester.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/github/Requester.py b/github/Requester.py index a739cdf3..215e2c38 100644 --- a/github/Requester.py +++ b/github/Requester.py @@ -36,6 +36,7 @@ import base64 import urllib import urlparse import sys +import Consts atLeastPython26 = sys.hexversion >= 0x02060000 atLeastPython3 = sys.hexversion >= 0x03000000 @@ -231,6 +232,7 @@ class Requester: requestHeaders = dict() self.__authenticate(url, requestHeaders, parameters) + self.__conditional(requestHeaders, parameters) requestHeaders["User-Agent"] = self.__userAgent url = self.__makeAbsoluteUrl(url) @@ -279,6 +281,16 @@ class Requester: if self.__authorizationHeader is not None: requestHeaders["Authorization"] = self.__authorizationHeader + def __conditional(self, requestHeaders, parameters): + etag = Consts.get(requestHeaders, Consts.REQ_IF_NONE_MATCH) + last_modified = Consts.get(requestHeaders, Consts.REQ_IF_MODIFIED_SINCE) + if etag is not None: + requestHeaders[Consts.REQ_IF_NONE_MATCH] = etag + del parameters[Consts.REQ_IF_NONE_MATCH] + if last_modified is not None: + requestHeaders[Consts.REQ_IF_MODIFIED_SINCE] = last_modified + del parameters[Consts.REQ_IF_MODIFIED_SINCE] + def __makeAbsoluteUrl(self, url): # URLs generated locally will be relative to __base_url # URLs returned from the server will start with __base_url From 70a7e9c83dec2bf6b549dc5c77d30b53afb32457 Mon Sep 17 00:00:00 2001 From: AKFish Date: Thu, 22 Aug 2013 07:45:27 +0800 Subject: [PATCH 06/24] Fix update --- github/Consts.py | 2 +- github/Requester.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/github/Consts.py b/github/Consts.py index 2e551756..65b592ac 100644 --- a/github/Consts.py +++ b/github/Consts.py @@ -30,7 +30,7 @@ ################################################################################ def get(dic, key): if key in dic: - return key[dic] + return dic[key] return None diff --git a/github/Requester.py b/github/Requester.py index 215e2c38..a81566ab 100644 --- a/github/Requester.py +++ b/github/Requester.py @@ -177,7 +177,7 @@ class Requester: self.DEBUG_ON_RESPONSE(status, responseHeaders, output) if status == 304: - raise GithubException.NotModifiedException(sttatus, output) + raise GithubException.NotModifiedException(status, output) if status >= 400: raise self.__createException(status, output) return responseHeaders, output @@ -282,8 +282,8 @@ class Requester: requestHeaders["Authorization"] = self.__authorizationHeader def __conditional(self, requestHeaders, parameters): - etag = Consts.get(requestHeaders, Consts.REQ_IF_NONE_MATCH) - last_modified = Consts.get(requestHeaders, Consts.REQ_IF_MODIFIED_SINCE) + etag = Consts.get(parameters, Consts.REQ_IF_NONE_MATCH) + last_modified = Consts.get(parameters, Consts.REQ_IF_MODIFIED_SINCE) if etag is not None: requestHeaders[Consts.REQ_IF_NONE_MATCH] = etag del parameters[Consts.REQ_IF_NONE_MATCH] From d457afd23ccb47d9f30f09a6ca2a8e32f17dccc7 Mon Sep 17 00:00:00 2001 From: AKFish Date: Thu, 22 Aug 2013 08:45:10 +0800 Subject: [PATCH 07/24] Add test record helper --- github/tests/_record_.py | 50 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 github/tests/_record_.py diff --git a/github/tests/_record_.py b/github/tests/_record_.py new file mode 100644 index 00000000..4cf5242c --- /dev/null +++ b/github/tests/_record_.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- + +############################ Copyrights and license ############################ +# # +# Copyright 2013 AKFish # +# # +# This file is part of PyGithub. http://jacquev6.github.com/PyGithub/ # +# # +# 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 sys +import unittest + +import github.tests.Framework +import github.tests.AllTests + + +def main(argv): + if len(argv) < 2: + print "Run sepecified test in record mode." + print "Usage:" + print "_record_.py [module_name] [other_arg] ..." + print " e.g. _record_.py github.tests.AllTests" + return + + github.tests.Framework.activateRecordMode() + module_to_run = argv.pop(1) + + print "module: " + module_to_run + print "argv: ", + print argv + + unittest.main(module=module_to_run, argv=argv) + + +if __name__ == "__main__": + main(sys.argv) From c7593e84c4a92a044b717b7311c2b6ad8d9a5917 Mon Sep 17 00:00:00 2001 From: AKFish Date: Thu, 22 Aug 2013 10:20:10 +0800 Subject: [PATCH 08/24] Add test case for conditional request --- github/tests/AllTests.py | 2 ++ github/tests/ConditionalRequestUpdate.py | 35 +++++++++++++++++++ .../ConditionalRequestUpdate.setUp.txt | 22 ++++++++++++ ...ConditionalRequestUpdate.testDidUpdate.txt | 11 ++++++ github/tests/_record_.py | 2 +- 5 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 github/tests/ConditionalRequestUpdate.py create mode 100644 github/tests/ReplayData/ConditionalRequestUpdate.setUp.txt create mode 100644 github/tests/ReplayData/ConditionalRequestUpdate.testDidUpdate.txt diff --git a/github/tests/AllTests.py b/github/tests/AllTests.py index a9e156ff..53789e0f 100644 --- a/github/tests/AllTests.py +++ b/github/tests/AllTests.py @@ -79,3 +79,5 @@ from Issue140 import * # from Issue142 import * # Deactivated for Travis-CI because Github has lowered the rate limitations from Issue158 import * from Issue174 import * + +from ConditionalRequestUpdate import ConditionalRequestUpdate diff --git a/github/tests/ConditionalRequestUpdate.py b/github/tests/ConditionalRequestUpdate.py new file mode 100644 index 00000000..702e5169 --- /dev/null +++ b/github/tests/ConditionalRequestUpdate.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- + +############################ Copyrights and license ############################ +# # +# Copyright 2013 AKFish # +# # +# This file is part of PyGithub. http://jacquev6.github.com/PyGithub/ # +# # +# 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 Framework +import github + +class ConditionalRequestUpdate(Framework.TestCase): + def setUp(self): + Framework.TestCase.setUp(self) + self.repo = self.g.get_repo("akfish/PyGithub") + # Not updated + self.assertFalse(self.repo.update(), msg="The repo is not changes. But update() != False") + + def testDidUpdate(self): + self.assertTrue(self.repo.update(), msg="The repo should be changed by now. But update() != True") diff --git a/github/tests/ReplayData/ConditionalRequestUpdate.setUp.txt b/github/tests/ReplayData/ConditionalRequestUpdate.setUp.txt new file mode 100644 index 00000000..fe2eacd3 --- /dev/null +++ b/github/tests/ReplayData/ConditionalRequestUpdate.setUp.txt @@ -0,0 +1,22 @@ +https +GET +api.github.com +None +/repos/akfish/PyGithub +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +null +200 +[('status', '200 OK'), ('x-ratelimit-remaining', '4988'), ('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'), ('access-control-allow-credentials', 'true'), ('vary', 'Accept, Authorization, Cookie, Accept-Encoding'), ('content-length', '13698'), ('server', 'GitHub.com'), ('last-modified', 'Thu, 22 Aug 2013 02:09:11 GMT'), ('x-ratelimit-limit', '5000'), ('etag', '"8600bedcb7fed1d8065e1693e05529ce"'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('date', 'Thu, 22 Aug 2013 02:13:08 GMT'), ('access-control-allow-origin', '*'), ('content-type', 'application/json; charset=utf-8'), ('x-ratelimit-reset', '1377140429')] +{"id":12156762,"name":"PyGithub","full_name":"akfish/PyGithub","owner":{"login":"akfish","id":922715,"avatar_url":"https://1.gravatar.com/avatar/12a1b44d4e5c19cee59618084602b112?d=https%3A%2F%2Fidenticons.github.com%2F6eb90fb68a77fb5a5a997c6264bedf35.png","gravatar_id":"12a1b44d4e5c19cee59618084602b112","url":"https://api.github.com/users/akfish","html_url":"https://github.com/akfish","followers_url":"https://api.github.com/users/akfish/followers","following_url":"https://api.github.com/users/akfish/following{/other_user}","gists_url":"https://api.github.com/users/akfish/gists{/gist_id}","starred_url":"https://api.github.com/users/akfish/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akfish/subscriptions","organizations_url":"https://api.github.com/users/akfish/orgs","repos_url":"https://api.github.com/users/akfish/repos","events_url":"https://api.github.com/users/akfish/events{/privacy}","received_events_url":"https://api.github.com/users/akfish/received_events","type":"User"},"private":false,"html_url":"https://github.com/akfish/PyGithub","description":"Python library implementing the full Github API v3","fork":true,"url":"https://api.github.com/repos/akfish/PyGithub","forks_url":"https://api.github.com/repos/akfish/PyGithub/forks","keys_url":"https://api.github.com/repos/akfish/PyGithub/keys{/key_id}","collaborators_url":"https://api.github.com/repos/akfish/PyGithub/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/akfish/PyGithub/teams","hooks_url":"https://api.github.com/repos/akfish/PyGithub/hooks","issue_events_url":"https://api.github.com/repos/akfish/PyGithub/issues/events{/number}","events_url":"https://api.github.com/repos/akfish/PyGithub/events","assignees_url":"https://api.github.com/repos/akfish/PyGithub/assignees{/user}","branches_url":"https://api.github.com/repos/akfish/PyGithub/branches{/branch}","tags_url":"https://api.github.com/repos/akfish/PyGithub/tags","blobs_url":"https://api.github.com/repos/akfish/PyGithub/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/akfish/PyGithub/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/akfish/PyGithub/git/refs{/sha}","trees_url":"https://api.github.com/repos/akfish/PyGithub/git/trees{/sha}","statuses_url":"https://api.github.com/repos/akfish/PyGithub/statuses/{sha}","languages_url":"https://api.github.com/repos/akfish/PyGithub/languages","stargazers_url":"https://api.github.com/repos/akfish/PyGithub/stargazers","contributors_url":"https://api.github.com/repos/akfish/PyGithub/contributors","subscribers_url":"https://api.github.com/repos/akfish/PyGithub/subscribers","subscription_url":"https://api.github.com/repos/akfish/PyGithub/subscription","commits_url":"https://api.github.com/repos/akfish/PyGithub/commits{/sha}","git_commits_url":"https://api.github.com/repos/akfish/PyGithub/git/commits{/sha}","comments_url":"https://api.github.com/repos/akfish/PyGithub/comments{/number}","issue_comment_url":"https://api.github.com/repos/akfish/PyGithub/issues/comments/{number}","contents_url":"https://api.github.com/repos/akfish/PyGithub/contents/{+path}","compare_url":"https://api.github.com/repos/akfish/PyGithub/compare/{base}...{head}","merges_url":"https://api.github.com/repos/akfish/PyGithub/merges","archive_url":"https://api.github.com/repos/akfish/PyGithub/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/akfish/PyGithub/downloads","issues_url":"https://api.github.com/repos/akfish/PyGithub/issues{/number}","pulls_url":"https://api.github.com/repos/akfish/PyGithub/pulls{/number}","milestones_url":"https://api.github.com/repos/akfish/PyGithub/milestones{/number}","notifications_url":"https://api.github.com/repos/akfish/PyGithub/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/akfish/PyGithub/labels{/name}","created_at":"2013-08-16T10:56:11Z","updated_at":"2013-08-22T02:09:11Z","pushed_at":"2013-08-22T02:09:09Z","git_url":"git://github.com/akfish/PyGithub.git","ssh_url":"git@github.com:akfish/PyGithub.git","clone_url":"https://github.com/akfish/PyGithub.git","svn_url":"https://github.com/akfish/PyGithub","homepage":"http://jacquev6.github.com/PyGithub","size":6736,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"master_branch":"master","default_branch":"master","permissions":{"admin":true,"push":true,"pull":true},"network_count":70,"parent":{"id":3544490,"name":"PyGithub","full_name":"jacquev6/PyGithub","owner":{"login":"jacquev6","id":327146,"avatar_url":"https://0.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https%3A%2F%2Fidenticons.github.com%2Ffadfb5f7088ef66579d198a3c9a4935e.png","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","url":"https://api.github.com/users/jacquev6","html_url":"https://github.com/jacquev6","followers_url":"https://api.github.com/users/jacquev6/followers","following_url":"https://api.github.com/users/jacquev6/following{/other_user}","gists_url":"https://api.github.com/users/jacquev6/gists{/gist_id}","starred_url":"https://api.github.com/users/jacquev6/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jacquev6/subscriptions","organizations_url":"https://api.github.com/users/jacquev6/orgs","repos_url":"https://api.github.com/users/jacquev6/repos","events_url":"https://api.github.com/users/jacquev6/events{/privacy}","received_events_url":"https://api.github.com/users/jacquev6/received_events","type":"User"},"private":false,"html_url":"https://github.com/jacquev6/PyGithub","description":"Python library implementing the full Github API v3","fork":false,"url":"https://api.github.com/repos/jacquev6/PyGithub","forks_url":"https://api.github.com/repos/jacquev6/PyGithub/forks","keys_url":"https://api.github.com/repos/jacquev6/PyGithub/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jacquev6/PyGithub/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jacquev6/PyGithub/teams","hooks_url":"https://api.github.com/repos/jacquev6/PyGithub/hooks","issue_events_url":"https://api.github.com/repos/jacquev6/PyGithub/issues/events{/number}","events_url":"https://api.github.com/repos/jacquev6/PyGithub/events","assignees_url":"https://api.github.com/repos/jacquev6/PyGithub/assignees{/user}","branches_url":"https://api.github.com/repos/jacquev6/PyGithub/branches{/branch}","tags_url":"https://api.github.com/repos/jacquev6/PyGithub/tags","blobs_url":"https://api.github.com/repos/jacquev6/PyGithub/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jacquev6/PyGithub/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jacquev6/PyGithub/git/refs{/sha}","trees_url":"https://api.github.com/repos/jacquev6/PyGithub/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jacquev6/PyGithub/statuses/{sha}","languages_url":"https://api.github.com/repos/jacquev6/PyGithub/languages","stargazers_url":"https://api.github.com/repos/jacquev6/PyGithub/stargazers","contributors_url":"https://api.github.com/repos/jacquev6/PyGithub/contributors","subscribers_url":"https://api.github.com/repos/jacquev6/PyGithub/subscribers","subscription_url":"https://api.github.com/repos/jacquev6/PyGithub/subscription","commits_url":"https://api.github.com/repos/jacquev6/PyGithub/commits{/sha}","git_commits_url":"https://api.github.com/repos/jacquev6/PyGithub/git/commits{/sha}","comments_url":"https://api.github.com/repos/jacquev6/PyGithub/comments{/number}","issue_comment_url":"https://api.github.com/repos/jacquev6/PyGithub/issues/comments/{number}","contents_url":"https://api.github.com/repos/jacquev6/PyGithub/contents/{+path}","compare_url":"https://api.github.com/repos/jacquev6/PyGithub/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jacquev6/PyGithub/merges","archive_url":"https://api.github.com/repos/jacquev6/PyGithub/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jacquev6/PyGithub/downloads","issues_url":"https://api.github.com/repos/jacquev6/PyGithub/issues{/number}","pulls_url":"https://api.github.com/repos/jacquev6/PyGithub/pulls{/number}","milestones_url":"https://api.github.com/repos/jacquev6/PyGithub/milestones{/number}","notifications_url":"https://api.github.com/repos/jacquev6/PyGithub/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jacquev6/PyGithub/labels{/name}","created_at":"2012-02-25T12:53:47Z","updated_at":"2013-08-21T20:32:08Z","pushed_at":"2013-08-21T20:31:45Z","git_url":"git://github.com/jacquev6/PyGithub.git","ssh_url":"git@github.com:jacquev6/PyGithub.git","clone_url":"https://github.com/jacquev6/PyGithub.git","svn_url":"https://github.com/jacquev6/PyGithub","homepage":"http://jacquev6.github.com/PyGithub","size":7437,"watchers_count":248,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"forks_count":70,"mirror_url":null,"open_issues_count":17,"forks":70,"open_issues":17,"watchers":248,"master_branch":"master","default_branch":"master"},"source":{"id":3544490,"name":"PyGithub","full_name":"jacquev6/PyGithub","owner":{"login":"jacquev6","id":327146,"avatar_url":"https://0.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https%3A%2F%2Fidenticons.github.com%2Ffadfb5f7088ef66579d198a3c9a4935e.png","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","url":"https://api.github.com/users/jacquev6","html_url":"https://github.com/jacquev6","followers_url":"https://api.github.com/users/jacquev6/followers","following_url":"https://api.github.com/users/jacquev6/following{/other_user}","gists_url":"https://api.github.com/users/jacquev6/gists{/gist_id}","starred_url":"https://api.github.com/users/jacquev6/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jacquev6/subscriptions","organizations_url":"https://api.github.com/users/jacquev6/orgs","repos_url":"https://api.github.com/users/jacquev6/repos","events_url":"https://api.github.com/users/jacquev6/events{/privacy}","received_events_url":"https://api.github.com/users/jacquev6/received_events","type":"User"},"private":false,"html_url":"https://github.com/jacquev6/PyGithub","description":"Python library implementing the full Github API v3","fork":false,"url":"https://api.github.com/repos/jacquev6/PyGithub","forks_url":"https://api.github.com/repos/jacquev6/PyGithub/forks","keys_url":"https://api.github.com/repos/jacquev6/PyGithub/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jacquev6/PyGithub/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jacquev6/PyGithub/teams","hooks_url":"https://api.github.com/repos/jacquev6/PyGithub/hooks","issue_events_url":"https://api.github.com/repos/jacquev6/PyGithub/issues/events{/number}","events_url":"https://api.github.com/repos/jacquev6/PyGithub/events","assignees_url":"https://api.github.com/repos/jacquev6/PyGithub/assignees{/user}","branches_url":"https://api.github.com/repos/jacquev6/PyGithub/branches{/branch}","tags_url":"https://api.github.com/repos/jacquev6/PyGithub/tags","blobs_url":"https://api.github.com/repos/jacquev6/PyGithub/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jacquev6/PyGithub/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jacquev6/PyGithub/git/refs{/sha}","trees_url":"https://api.github.com/repos/jacquev6/PyGithub/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jacquev6/PyGithub/statuses/{sha}","languages_url":"https://api.github.com/repos/jacquev6/PyGithub/languages","stargazers_url":"https://api.github.com/repos/jacquev6/PyGithub/stargazers","contributors_url":"https://api.github.com/repos/jacquev6/PyGithub/contributors","subscribers_url":"https://api.github.com/repos/jacquev6/PyGithub/subscribers","subscription_url":"https://api.github.com/repos/jacquev6/PyGithub/subscription","commits_url":"https://api.github.com/repos/jacquev6/PyGithub/commits{/sha}","git_commits_url":"https://api.github.com/repos/jacquev6/PyGithub/git/commits{/sha}","comments_url":"https://api.github.com/repos/jacquev6/PyGithub/comments{/number}","issue_comment_url":"https://api.github.com/repos/jacquev6/PyGithub/issues/comments/{number}","contents_url":"https://api.github.com/repos/jacquev6/PyGithub/contents/{+path}","compare_url":"https://api.github.com/repos/jacquev6/PyGithub/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jacquev6/PyGithub/merges","archive_url":"https://api.github.com/repos/jacquev6/PyGithub/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jacquev6/PyGithub/downloads","issues_url":"https://api.github.com/repos/jacquev6/PyGithub/issues{/number}","pulls_url":"https://api.github.com/repos/jacquev6/PyGithub/pulls{/number}","milestones_url":"https://api.github.com/repos/jacquev6/PyGithub/milestones{/number}","notifications_url":"https://api.github.com/repos/jacquev6/PyGithub/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jacquev6/PyGithub/labels{/name}","created_at":"2012-02-25T12:53:47Z","updated_at":"2013-08-21T20:32:08Z","pushed_at":"2013-08-21T20:31:45Z","git_url":"git://github.com/jacquev6/PyGithub.git","ssh_url":"git@github.com:jacquev6/PyGithub.git","clone_url":"https://github.com/jacquev6/PyGithub.git","svn_url":"https://github.com/jacquev6/PyGithub","homepage":"http://jacquev6.github.com/PyGithub","size":7437,"watchers_count":248,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"forks_count":70,"mirror_url":null,"open_issues_count":17,"forks":70,"open_issues":17,"watchers":248,"master_branch":"master","default_branch":"master"}} + +https +GET +api.github.com +None +/repos/akfish/PyGithub +{'If-None-Match': '"8600bedcb7fed1d8065e1693e05529ce"', 'User-Agent': 'PyGithub/Python', 'Authorization': 'Basic login_and_password_removed', 'If-Modified-Since': 'Thu, 22 Aug 2013 02:09:11 GMT'} +null +304 +[('status', '304 Not Modified'), ('x-ratelimit-remaining', '4988'), ('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'), ('access-control-allow-credentials', 'true'), ('vary', 'Accept-Encoding'), ('server', 'GitHub.com'), ('last-modified', 'Thu, 22 Aug 2013 02:09:11 GMT'), ('x-ratelimit-limit', '5000'), ('etag', '"8600bedcb7fed1d8065e1693e05529ce"'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('date', 'Thu, 22 Aug 2013 02:13:10 GMT'), ('access-control-allow-origin', '*'), ('x-ratelimit-reset', '1377140429')] + + diff --git a/github/tests/ReplayData/ConditionalRequestUpdate.testDidUpdate.txt b/github/tests/ReplayData/ConditionalRequestUpdate.testDidUpdate.txt new file mode 100644 index 00000000..b6d8aeec --- /dev/null +++ b/github/tests/ReplayData/ConditionalRequestUpdate.testDidUpdate.txt @@ -0,0 +1,11 @@ +https +GET +api.github.com +None +/repos/akfish/PyGithub +{'If-None-Match': '"8600bedcb7fed1d8065e1693e05529ce"', 'User-Agent': 'PyGithub/Python', 'Authorization': 'Basic login_and_password_removed', 'If-Modified-Since': 'Thu, 22 Aug 2013 02:09:11 GMT'} +null +200 +[('status', '200 OK'), ('x-ratelimit-remaining', '4987'), ('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'), ('access-control-allow-credentials', 'true'), ('vary', 'Accept, Authorization, Cookie, Accept-Encoding'), ('content-length', '13712'), ('server', 'GitHub.com'), ('last-modified', 'Thu, 22 Aug 2013 02:14:54 GMT'), ('x-ratelimit-limit', '5000'), ('etag', '"ef281ef0e821c18f80da36902727160b"'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('date', 'Thu, 22 Aug 2013 02:15:01 GMT'), ('access-control-allow-origin', '*'), ('content-type', 'application/json; charset=utf-8'), ('x-ratelimit-reset', '1377140429')] +{"id":12156762,"name":"PyGithub","full_name":"akfish/PyGithub","owner":{"login":"akfish","id":922715,"avatar_url":"https://0.gravatar.com/avatar/12a1b44d4e5c19cee59618084602b112?d=https%3A%2F%2Fidenticons.github.com%2F6eb90fb68a77fb5a5a997c6264bedf35.png","gravatar_id":"12a1b44d4e5c19cee59618084602b112","url":"https://api.github.com/users/akfish","html_url":"https://github.com/akfish","followers_url":"https://api.github.com/users/akfish/followers","following_url":"https://api.github.com/users/akfish/following{/other_user}","gists_url":"https://api.github.com/users/akfish/gists{/gist_id}","starred_url":"https://api.github.com/users/akfish/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akfish/subscriptions","organizations_url":"https://api.github.com/users/akfish/orgs","repos_url":"https://api.github.com/users/akfish/repos","events_url":"https://api.github.com/users/akfish/events{/privacy}","received_events_url":"https://api.github.com/users/akfish/received_events","type":"User"},"private":false,"html_url":"https://github.com/akfish/PyGithub","description":"Python library implementing the full Github API v3 - AKFish Fork","fork":true,"url":"https://api.github.com/repos/akfish/PyGithub","forks_url":"https://api.github.com/repos/akfish/PyGithub/forks","keys_url":"https://api.github.com/repos/akfish/PyGithub/keys{/key_id}","collaborators_url":"https://api.github.com/repos/akfish/PyGithub/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/akfish/PyGithub/teams","hooks_url":"https://api.github.com/repos/akfish/PyGithub/hooks","issue_events_url":"https://api.github.com/repos/akfish/PyGithub/issues/events{/number}","events_url":"https://api.github.com/repos/akfish/PyGithub/events","assignees_url":"https://api.github.com/repos/akfish/PyGithub/assignees{/user}","branches_url":"https://api.github.com/repos/akfish/PyGithub/branches{/branch}","tags_url":"https://api.github.com/repos/akfish/PyGithub/tags","blobs_url":"https://api.github.com/repos/akfish/PyGithub/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/akfish/PyGithub/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/akfish/PyGithub/git/refs{/sha}","trees_url":"https://api.github.com/repos/akfish/PyGithub/git/trees{/sha}","statuses_url":"https://api.github.com/repos/akfish/PyGithub/statuses/{sha}","languages_url":"https://api.github.com/repos/akfish/PyGithub/languages","stargazers_url":"https://api.github.com/repos/akfish/PyGithub/stargazers","contributors_url":"https://api.github.com/repos/akfish/PyGithub/contributors","subscribers_url":"https://api.github.com/repos/akfish/PyGithub/subscribers","subscription_url":"https://api.github.com/repos/akfish/PyGithub/subscription","commits_url":"https://api.github.com/repos/akfish/PyGithub/commits{/sha}","git_commits_url":"https://api.github.com/repos/akfish/PyGithub/git/commits{/sha}","comments_url":"https://api.github.com/repos/akfish/PyGithub/comments{/number}","issue_comment_url":"https://api.github.com/repos/akfish/PyGithub/issues/comments/{number}","contents_url":"https://api.github.com/repos/akfish/PyGithub/contents/{+path}","compare_url":"https://api.github.com/repos/akfish/PyGithub/compare/{base}...{head}","merges_url":"https://api.github.com/repos/akfish/PyGithub/merges","archive_url":"https://api.github.com/repos/akfish/PyGithub/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/akfish/PyGithub/downloads","issues_url":"https://api.github.com/repos/akfish/PyGithub/issues{/number}","pulls_url":"https://api.github.com/repos/akfish/PyGithub/pulls{/number}","milestones_url":"https://api.github.com/repos/akfish/PyGithub/milestones{/number}","notifications_url":"https://api.github.com/repos/akfish/PyGithub/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/akfish/PyGithub/labels{/name}","created_at":"2013-08-16T10:56:11Z","updated_at":"2013-08-22T02:14:54Z","pushed_at":"2013-08-22T02:09:09Z","git_url":"git://github.com/akfish/PyGithub.git","ssh_url":"git@github.com:akfish/PyGithub.git","clone_url":"https://github.com/akfish/PyGithub.git","svn_url":"https://github.com/akfish/PyGithub","homepage":"http://jacquev6.github.com/PyGithub","size":6736,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"master_branch":"master","default_branch":"master","permissions":{"admin":true,"push":true,"pull":true},"network_count":70,"parent":{"id":3544490,"name":"PyGithub","full_name":"jacquev6/PyGithub","owner":{"login":"jacquev6","id":327146,"avatar_url":"https://0.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https%3A%2F%2Fidenticons.github.com%2Ffadfb5f7088ef66579d198a3c9a4935e.png","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","url":"https://api.github.com/users/jacquev6","html_url":"https://github.com/jacquev6","followers_url":"https://api.github.com/users/jacquev6/followers","following_url":"https://api.github.com/users/jacquev6/following{/other_user}","gists_url":"https://api.github.com/users/jacquev6/gists{/gist_id}","starred_url":"https://api.github.com/users/jacquev6/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jacquev6/subscriptions","organizations_url":"https://api.github.com/users/jacquev6/orgs","repos_url":"https://api.github.com/users/jacquev6/repos","events_url":"https://api.github.com/users/jacquev6/events{/privacy}","received_events_url":"https://api.github.com/users/jacquev6/received_events","type":"User"},"private":false,"html_url":"https://github.com/jacquev6/PyGithub","description":"Python library implementing the full Github API v3","fork":false,"url":"https://api.github.com/repos/jacquev6/PyGithub","forks_url":"https://api.github.com/repos/jacquev6/PyGithub/forks","keys_url":"https://api.github.com/repos/jacquev6/PyGithub/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jacquev6/PyGithub/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jacquev6/PyGithub/teams","hooks_url":"https://api.github.com/repos/jacquev6/PyGithub/hooks","issue_events_url":"https://api.github.com/repos/jacquev6/PyGithub/issues/events{/number}","events_url":"https://api.github.com/repos/jacquev6/PyGithub/events","assignees_url":"https://api.github.com/repos/jacquev6/PyGithub/assignees{/user}","branches_url":"https://api.github.com/repos/jacquev6/PyGithub/branches{/branch}","tags_url":"https://api.github.com/repos/jacquev6/PyGithub/tags","blobs_url":"https://api.github.com/repos/jacquev6/PyGithub/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jacquev6/PyGithub/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jacquev6/PyGithub/git/refs{/sha}","trees_url":"https://api.github.com/repos/jacquev6/PyGithub/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jacquev6/PyGithub/statuses/{sha}","languages_url":"https://api.github.com/repos/jacquev6/PyGithub/languages","stargazers_url":"https://api.github.com/repos/jacquev6/PyGithub/stargazers","contributors_url":"https://api.github.com/repos/jacquev6/PyGithub/contributors","subscribers_url":"https://api.github.com/repos/jacquev6/PyGithub/subscribers","subscription_url":"https://api.github.com/repos/jacquev6/PyGithub/subscription","commits_url":"https://api.github.com/repos/jacquev6/PyGithub/commits{/sha}","git_commits_url":"https://api.github.com/repos/jacquev6/PyGithub/git/commits{/sha}","comments_url":"https://api.github.com/repos/jacquev6/PyGithub/comments{/number}","issue_comment_url":"https://api.github.com/repos/jacquev6/PyGithub/issues/comments/{number}","contents_url":"https://api.github.com/repos/jacquev6/PyGithub/contents/{+path}","compare_url":"https://api.github.com/repos/jacquev6/PyGithub/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jacquev6/PyGithub/merges","archive_url":"https://api.github.com/repos/jacquev6/PyGithub/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jacquev6/PyGithub/downloads","issues_url":"https://api.github.com/repos/jacquev6/PyGithub/issues{/number}","pulls_url":"https://api.github.com/repos/jacquev6/PyGithub/pulls{/number}","milestones_url":"https://api.github.com/repos/jacquev6/PyGithub/milestones{/number}","notifications_url":"https://api.github.com/repos/jacquev6/PyGithub/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jacquev6/PyGithub/labels{/name}","created_at":"2012-02-25T12:53:47Z","updated_at":"2013-08-21T20:32:08Z","pushed_at":"2013-08-21T20:31:45Z","git_url":"git://github.com/jacquev6/PyGithub.git","ssh_url":"git@github.com:jacquev6/PyGithub.git","clone_url":"https://github.com/jacquev6/PyGithub.git","svn_url":"https://github.com/jacquev6/PyGithub","homepage":"http://jacquev6.github.com/PyGithub","size":7437,"watchers_count":248,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"forks_count":70,"mirror_url":null,"open_issues_count":17,"forks":70,"open_issues":17,"watchers":248,"master_branch":"master","default_branch":"master"},"source":{"id":3544490,"name":"PyGithub","full_name":"jacquev6/PyGithub","owner":{"login":"jacquev6","id":327146,"avatar_url":"https://0.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https%3A%2F%2Fidenticons.github.com%2Ffadfb5f7088ef66579d198a3c9a4935e.png","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","url":"https://api.github.com/users/jacquev6","html_url":"https://github.com/jacquev6","followers_url":"https://api.github.com/users/jacquev6/followers","following_url":"https://api.github.com/users/jacquev6/following{/other_user}","gists_url":"https://api.github.com/users/jacquev6/gists{/gist_id}","starred_url":"https://api.github.com/users/jacquev6/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jacquev6/subscriptions","organizations_url":"https://api.github.com/users/jacquev6/orgs","repos_url":"https://api.github.com/users/jacquev6/repos","events_url":"https://api.github.com/users/jacquev6/events{/privacy}","received_events_url":"https://api.github.com/users/jacquev6/received_events","type":"User"},"private":false,"html_url":"https://github.com/jacquev6/PyGithub","description":"Python library implementing the full Github API v3","fork":false,"url":"https://api.github.com/repos/jacquev6/PyGithub","forks_url":"https://api.github.com/repos/jacquev6/PyGithub/forks","keys_url":"https://api.github.com/repos/jacquev6/PyGithub/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jacquev6/PyGithub/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jacquev6/PyGithub/teams","hooks_url":"https://api.github.com/repos/jacquev6/PyGithub/hooks","issue_events_url":"https://api.github.com/repos/jacquev6/PyGithub/issues/events{/number}","events_url":"https://api.github.com/repos/jacquev6/PyGithub/events","assignees_url":"https://api.github.com/repos/jacquev6/PyGithub/assignees{/user}","branches_url":"https://api.github.com/repos/jacquev6/PyGithub/branches{/branch}","tags_url":"https://api.github.com/repos/jacquev6/PyGithub/tags","blobs_url":"https://api.github.com/repos/jacquev6/PyGithub/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jacquev6/PyGithub/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jacquev6/PyGithub/git/refs{/sha}","trees_url":"https://api.github.com/repos/jacquev6/PyGithub/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jacquev6/PyGithub/statuses/{sha}","languages_url":"https://api.github.com/repos/jacquev6/PyGithub/languages","stargazers_url":"https://api.github.com/repos/jacquev6/PyGithub/stargazers","contributors_url":"https://api.github.com/repos/jacquev6/PyGithub/contributors","subscribers_url":"https://api.github.com/repos/jacquev6/PyGithub/subscribers","subscription_url":"https://api.github.com/repos/jacquev6/PyGithub/subscription","commits_url":"https://api.github.com/repos/jacquev6/PyGithub/commits{/sha}","git_commits_url":"https://api.github.com/repos/jacquev6/PyGithub/git/commits{/sha}","comments_url":"https://api.github.com/repos/jacquev6/PyGithub/comments{/number}","issue_comment_url":"https://api.github.com/repos/jacquev6/PyGithub/issues/comments/{number}","contents_url":"https://api.github.com/repos/jacquev6/PyGithub/contents/{+path}","compare_url":"https://api.github.com/repos/jacquev6/PyGithub/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jacquev6/PyGithub/merges","archive_url":"https://api.github.com/repos/jacquev6/PyGithub/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jacquev6/PyGithub/downloads","issues_url":"https://api.github.com/repos/jacquev6/PyGithub/issues{/number}","pulls_url":"https://api.github.com/repos/jacquev6/PyGithub/pulls{/number}","milestones_url":"https://api.github.com/repos/jacquev6/PyGithub/milestones{/number}","notifications_url":"https://api.github.com/repos/jacquev6/PyGithub/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jacquev6/PyGithub/labels{/name}","created_at":"2012-02-25T12:53:47Z","updated_at":"2013-08-21T20:32:08Z","pushed_at":"2013-08-21T20:31:45Z","git_url":"git://github.com/jacquev6/PyGithub.git","ssh_url":"git@github.com:jacquev6/PyGithub.git","clone_url":"https://github.com/jacquev6/PyGithub.git","svn_url":"https://github.com/jacquev6/PyGithub","homepage":"http://jacquev6.github.com/PyGithub","size":7437,"watchers_count":248,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"forks_count":70,"mirror_url":null,"open_issues_count":17,"forks":70,"open_issues":17,"watchers":248,"master_branch":"master","default_branch":"master"}} + diff --git a/github/tests/_record_.py b/github/tests/_record_.py index 4cf5242c..36dab4e3 100644 --- a/github/tests/_record_.py +++ b/github/tests/_record_.py @@ -33,7 +33,7 @@ def main(argv): print "Run sepecified test in record mode." print "Usage:" print "_record_.py [module_name] [other_arg] ..." - print " e.g. _record_.py github.tests.AllTests" + print " e.g. _record_.py AllTests" return github.tests.Framework.activateRecordMode() From 1787765a61958617d47e764a0bea2acd70c84f72 Mon Sep 17 00:00:00 2001 From: Vincent Jacques Date: Thu, 22 Aug 2013 11:41:15 +0200 Subject: [PATCH 09/24] Review of #189: use dict.get http://docs.python.org/2/library/stdtypes.html#dict.get --- github/Consts.py | 10 +--------- github/GithubObject.py | 9 ++++----- github/Requester.py | 4 ++-- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/github/Consts.py b/github/Consts.py index 65b592ac..ea4b1830 100644 --- a/github/Consts.py +++ b/github/Consts.py @@ -24,15 +24,7 @@ # TODO: As of Thu Aug 21 22:40:13 (BJT) Chinese Standard Time 2013 # lots of consts in this project are explict # should realy round them up and reference them by consts - -################################################################################ -# Helper Function # -################################################################################ -def get(dic, key): - if key in dic: - return dic[key] - return None - +# EDIT: well, maybe :-) ################################################################################ # Request Header # diff --git a/github/GithubObject.py b/github/GithubObject.py index cd44bf45..b6d153dc 100644 --- a/github/GithubObject.py +++ b/github/GithubObject.py @@ -27,13 +27,12 @@ from __future__ import with_statement import datetime - -import GithubException - import pickle +import GithubException import Consts + class _NotSetType: def __repr__(self): return "NotSet" @@ -126,14 +125,14 @@ class GithubObject(object): ''' :type str ''' - return Consts.get(self._headers, Consts.RES_ETAG) + return self._headers.get(Consts.RES_ETAG) @property def last_modified(self): ''' :type str ''' - return Consts.get(self._headers, Consts.RES_LAST_MODIFED) + return self._headers.get(Consts.RES_LAST_MODIFED) def update(self): diff --git a/github/Requester.py b/github/Requester.py index 1b30b6fe..8072bca2 100644 --- a/github/Requester.py +++ b/github/Requester.py @@ -290,8 +290,8 @@ class Requester: requestHeaders["Authorization"] = self.__authorizationHeader def __conditional(self, requestHeaders, parameters): - etag = Consts.get(parameters, Consts.REQ_IF_NONE_MATCH) - last_modified = Consts.get(parameters, Consts.REQ_IF_MODIFIED_SINCE) + etag = parameters.get(Consts.REQ_IF_NONE_MATCH) + last_modified = parameters.get(Consts.REQ_IF_MODIFIED_SINCE) if etag is not None: requestHeaders[Consts.REQ_IF_NONE_MATCH] = etag del parameters[Consts.REQ_IF_NONE_MATCH] From 0f74e4389b3c0fa57a83083ecfbbf5c331022674 Mon Sep 17 00:00:00 2001 From: Vincent Jacques Date: Thu, 22 Aug 2013 12:12:38 +0200 Subject: [PATCH 10/24] Review of #189: pep8, copyrights, style, remarks For remarks, run: git grep "#189" They are only my first thoughts while reviewing this pull request, and should be reviewed themselves. --- github/Consts.py | 12 +++++++----- github/GithubException.py | 3 ++- github/GithubObject.py | 23 +++++++++++------------ github/Requester.py | 2 ++ github/tests/ConditionalRequestUpdate.py | 6 +++++- github/tests/_record_.py | 7 +++++-- 6 files changed, 32 insertions(+), 21 deletions(-) diff --git a/github/Consts.py b/github/Consts.py index ea4b1830..7f32628c 100644 --- a/github/Consts.py +++ b/github/Consts.py @@ -21,7 +21,9 @@ # # ################################################################################ -# TODO: As of Thu Aug 21 22:40:13 (BJT) Chinese Standard Time 2013 +# #189: Line endings should be linux style + +# TODO: As of Thu Aug 21 22:40:13 (BJT) Chinese Standard Time 2013 # lots of consts in this project are explict # should realy round them up and reference them by consts # EDIT: well, maybe :-) @@ -30,12 +32,12 @@ # Request Header # # (Case sensitive) # ################################################################################ -REQ_IF_NONE_MATCH = "If-None-Match" -REQ_IF_MODIFIED_SINCE = "If-Modified-Since" +REQ_IF_NONE_MATCH = "If-None-Match" +REQ_IF_MODIFIED_SINCE = "If-Modified-Since" ################################################################################ # Response Header # # (Lower Case) # ################################################################################ -RES_ETAG = "etag" -RES_LAST_MODIFED = "last-modified" +RES_ETAG = "etag" +RES_LAST_MODIFED = "last-modified" diff --git a/github/GithubException.py b/github/GithubException.py index 53e2d302..869c83d7 100644 --- a/github/GithubException.py +++ b/github/GithubException.py @@ -4,8 +4,8 @@ # # # Copyright 2012 Vincent Jacques # # Copyright 2012 Zearin # -# Copyright 2013 Vincent Jacques # # Copyright 2013 AKFish # +# Copyright 2013 Vincent Jacques # # # # This file is part of PyGithub. http://jacquev6.github.com/PyGithub/ # # # @@ -78,6 +78,7 @@ class RateLimitExceededException(GithubException): Exception raised when the rate limit is exceeded (when Github API replies with a 403 rate limit exceeded HTML status) """ + class NotModifiedException(GithubException): """ Exception raised when conditional request is made to a resoure that has not changed diff --git a/github/GithubObject.py b/github/GithubObject.py index b6d153dc..25e81c77 100644 --- a/github/GithubObject.py +++ b/github/GithubObject.py @@ -57,14 +57,14 @@ class GithubObject(object): self._requester = requester # Make sure headers are signed before any operations on attributes # Object creatation requires headers as parameter - self._headers = headers; + self._headers = headers self._initAttributes() self._storeAndUseAttributes(attributes) # Ask requester to do some checking, for debug and test purpose # Since it's most handy to access and kinda all-knowing - if (self.CHECK_AFTER_INIT_FLAG): - requester.check_me(self); + if self.CHECK_AFTER_INIT_FLAG: + requester.check_me(self) def _storeAndUseAttributes(self, attributes): self._useAttributes(attributes) @@ -100,22 +100,21 @@ class GithubObject(object): else: return datetime.datetime.strptime(s, "%Y-%m-%dT%H:%M:%SZ") - def save(self, file_name): + def save(self, file_name): # #189: Could we use file-like objects? It would be more "pythonic" than passing filenames. ''' Save instance to a file - :param file_name: the full path of target file ''' - with open(file_name, 'wb') as f: - pickle.dump(self, f) + pickle.dump(self, f) # #189: This will also save self._requester, and the login/password of the user. She might not appriciate. + # #189: May be better to pickle only self._rawData and self._headers and restore the object with Github.create_from_raw_data - @classmethod - def load(cls, file_name): + @classmethod # #189: Could be a @staticmethod? The docstring would be simpler (no need to explain the type will be same as saved). + def load(cls, file_name): # #189: Could we use file-like objects? It would be more "pythonic" than passing filenames. ''' Load saved instance from file :param file_name: the full path to saved file - :rtype: saved instance. The type of loaded instance remains its orginal one and will not be affected by from which derived class the method is called. + :rtype: saved instance. The type of loaded instance remains its orginal one and will not be affected by from which derived class the method is called. ''' with open(file_name, 'rb') as f: return pickle.load(f) @@ -133,7 +132,6 @@ class GithubObject(object): :type str ''' return self._headers.get(Consts.RES_LAST_MODIFED) - def update(self): ''' @@ -156,9 +154,10 @@ class GithubObject(object): self._storeAndUseAttributes(data) self.__completed = True return True - except GithubException.NotModifiedException: + except GithubException.NotModifiedException: # #189: Why raise and catch? Can't we just check? return False + class NonCompletableGithubObject(GithubObject): def _completeIfNeeded(self): pass diff --git a/github/Requester.py b/github/Requester.py index 8072bca2..abd224fd 100644 --- a/github/Requester.py +++ b/github/Requester.py @@ -290,6 +290,8 @@ class Requester: requestHeaders["Authorization"] = self.__authorizationHeader def __conditional(self, requestHeaders, parameters): + # #189: Why pass etag and last_modified by param "parameters"? + # #189: May be better to add a specific param "headers" to methods requestFoobar? etag = parameters.get(Consts.REQ_IF_NONE_MATCH) last_modified = parameters.get(Consts.REQ_IF_MODIFIED_SINCE) if etag is not None: diff --git a/github/tests/ConditionalRequestUpdate.py b/github/tests/ConditionalRequestUpdate.py index 702e5169..63121e72 100644 --- a/github/tests/ConditionalRequestUpdate.py +++ b/github/tests/ConditionalRequestUpdate.py @@ -21,13 +21,17 @@ # # ################################################################################ +# #189: Line endings should be linux style + import Framework import github + class ConditionalRequestUpdate(Framework.TestCase): def setUp(self): Framework.TestCase.setUp(self) - self.repo = self.g.get_repo("akfish/PyGithub") + self.repo = self.g.get_repo("akfish/PyGithub") + # #189: Let's separate this assert in its own test method, remove it from setUp. # Not updated self.assertFalse(self.repo.update(), msg="The repo is not changes. But update() != False") diff --git a/github/tests/_record_.py b/github/tests/_record_.py index 36dab4e3..38aa0745 100644 --- a/github/tests/_record_.py +++ b/github/tests/_record_.py @@ -28,6 +28,9 @@ import github.tests.Framework import github.tests.AllTests +# #189: This seems equivalent to "python -m github.tests ClassName.methodName --record" + + def main(argv): if len(argv) < 2: print "Run sepecified test in record mode." @@ -35,10 +38,10 @@ def main(argv): print "_record_.py [module_name] [other_arg] ..." print " e.g. _record_.py AllTests" return - + github.tests.Framework.activateRecordMode() module_to_run = argv.pop(1) - + print "module: " + module_to_run print "argv: ", print argv From fb7325884ee0b8ae73f47bf13c6f36cacbc3131c Mon Sep 17 00:00:00 2001 From: Vincent Jacques Date: Fri, 23 Aug 2013 09:32:47 +0200 Subject: [PATCH 11/24] Fix remarks on #189 to #193 --- github/Consts.py | 2 +- github/GithubObject.py | 12 ++++++------ github/Requester.py | 4 ++-- github/tests/ConditionalRequestUpdate.py | 4 ++-- github/tests/_record_.py | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/github/Consts.py b/github/Consts.py index 7f32628c..5283101d 100644 --- a/github/Consts.py +++ b/github/Consts.py @@ -21,7 +21,7 @@ # # ################################################################################ -# #189: Line endings should be linux style +# #193: Line endings should be linux style # TODO: As of Thu Aug 21 22:40:13 (BJT) Chinese Standard Time 2013 # lots of consts in this project are explict diff --git a/github/GithubObject.py b/github/GithubObject.py index 25e81c77..92efb7f2 100644 --- a/github/GithubObject.py +++ b/github/GithubObject.py @@ -100,17 +100,17 @@ class GithubObject(object): else: return datetime.datetime.strptime(s, "%Y-%m-%dT%H:%M:%SZ") - def save(self, file_name): # #189: Could we use file-like objects? It would be more "pythonic" than passing filenames. + def save(self, file_name): # #193: Could we use file-like objects? It would be more "pythonic" than passing filenames. ''' Save instance to a file :param file_name: the full path of target file ''' with open(file_name, 'wb') as f: - pickle.dump(self, f) # #189: This will also save self._requester, and the login/password of the user. She might not appriciate. - # #189: May be better to pickle only self._rawData and self._headers and restore the object with Github.create_from_raw_data + pickle.dump(self, f) # #193: This will also save self._requester, and the login/password of the user. She might not appriciate. + # #193: May be better to pickle only self._rawData and self._headers and restore the object with Github.create_from_raw_data - @classmethod # #189: Could be a @staticmethod? The docstring would be simpler (no need to explain the type will be same as saved). - def load(cls, file_name): # #189: Could we use file-like objects? It would be more "pythonic" than passing filenames. + @classmethod # #193: Could be a @staticmethod? The docstring would be simpler (no need to explain the type will be same as saved). + def load(cls, file_name): # #193: Could we use file-like objects? It would be more "pythonic" than passing filenames. ''' Load saved instance from file :param file_name: the full path to saved file @@ -154,7 +154,7 @@ class GithubObject(object): self._storeAndUseAttributes(data) self.__completed = True return True - except GithubException.NotModifiedException: # #189: Why raise and catch? Can't we just check? + except GithubException.NotModifiedException: # #193: Why raise and catch? Can't we just check? return False diff --git a/github/Requester.py b/github/Requester.py index abd224fd..9ac335e6 100644 --- a/github/Requester.py +++ b/github/Requester.py @@ -290,8 +290,8 @@ class Requester: requestHeaders["Authorization"] = self.__authorizationHeader def __conditional(self, requestHeaders, parameters): - # #189: Why pass etag and last_modified by param "parameters"? - # #189: May be better to add a specific param "headers" to methods requestFoobar? + # #193: Why pass etag and last_modified by param "parameters"? + # #193: May be better to add a specific param "headers" to methods requestFoobar? etag = parameters.get(Consts.REQ_IF_NONE_MATCH) last_modified = parameters.get(Consts.REQ_IF_MODIFIED_SINCE) if etag is not None: diff --git a/github/tests/ConditionalRequestUpdate.py b/github/tests/ConditionalRequestUpdate.py index 63121e72..6ac38c98 100644 --- a/github/tests/ConditionalRequestUpdate.py +++ b/github/tests/ConditionalRequestUpdate.py @@ -21,7 +21,7 @@ # # ################################################################################ -# #189: Line endings should be linux style +# #193: Line endings should be linux style import Framework import github @@ -31,7 +31,7 @@ class ConditionalRequestUpdate(Framework.TestCase): def setUp(self): Framework.TestCase.setUp(self) self.repo = self.g.get_repo("akfish/PyGithub") - # #189: Let's separate this assert in its own test method, remove it from setUp. + # #193: Let's separate this assert in its own test method, remove it from setUp. # Not updated self.assertFalse(self.repo.update(), msg="The repo is not changes. But update() != False") diff --git a/github/tests/_record_.py b/github/tests/_record_.py index 38aa0745..ffd32409 100644 --- a/github/tests/_record_.py +++ b/github/tests/_record_.py @@ -28,7 +28,7 @@ import github.tests.Framework import github.tests.AllTests -# #189: This seems equivalent to "python -m github.tests ClassName.methodName --record" +# #193: This seems equivalent to "python -m github.tests ClassName.methodName --record" def main(argv): From 0413c87c12e688fb4fc38d978a2f275ef791cd48 Mon Sep 17 00:00:00 2001 From: Vincent Jacques Date: Fri, 23 Aug 2013 09:39:19 +0200 Subject: [PATCH 12/24] Remove _record_.py (#193) According to https://github.com/jacquev6/PyGithub/commit/0f74e4389b3c0fa57a83083ecfbbf5c331022674#commitcomment-3919786 --- github/tests/_record_.py | 53 ---------------------------------------- 1 file changed, 53 deletions(-) delete mode 100644 github/tests/_record_.py diff --git a/github/tests/_record_.py b/github/tests/_record_.py deleted file mode 100644 index ffd32409..00000000 --- a/github/tests/_record_.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- - -############################ Copyrights and license ############################ -# # -# Copyright 2013 AKFish # -# # -# This file is part of PyGithub. http://jacquev6.github.com/PyGithub/ # -# # -# 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 sys -import unittest - -import github.tests.Framework -import github.tests.AllTests - - -# #193: This seems equivalent to "python -m github.tests ClassName.methodName --record" - - -def main(argv): - if len(argv) < 2: - print "Run sepecified test in record mode." - print "Usage:" - print "_record_.py [module_name] [other_arg] ..." - print " e.g. _record_.py AllTests" - return - - github.tests.Framework.activateRecordMode() - module_to_run = argv.pop(1) - - print "module: " + module_to_run - print "argv: ", - print argv - - unittest.main(module=module_to_run, argv=argv) - - -if __name__ == "__main__": - main(sys.argv) From bc3b819ac554a2132427c9ffe629ef371511213e Mon Sep 17 00:00:00 2001 From: Vincent Jacques Date: Fri, 23 Aug 2013 09:50:51 +0200 Subject: [PATCH 13/24] Separate tests for conditional requests (#193) --- github/tests/ConditionalRequestUpdate.py | 4 ++-- .../ReplayData/ConditionalRequestUpdate.setUp.txt | 11 ----------- .../ConditionalRequestUpdate.testDidNotUpdate.txt | 11 +++++++++++ 3 files changed, 13 insertions(+), 13 deletions(-) create mode 100755 github/tests/ReplayData/ConditionalRequestUpdate.testDidNotUpdate.txt diff --git a/github/tests/ConditionalRequestUpdate.py b/github/tests/ConditionalRequestUpdate.py index 6ac38c98..5b3eab97 100644 --- a/github/tests/ConditionalRequestUpdate.py +++ b/github/tests/ConditionalRequestUpdate.py @@ -31,8 +31,8 @@ class ConditionalRequestUpdate(Framework.TestCase): def setUp(self): Framework.TestCase.setUp(self) self.repo = self.g.get_repo("akfish/PyGithub") - # #193: Let's separate this assert in its own test method, remove it from setUp. - # Not updated + + def testDidNotUpdate(self): self.assertFalse(self.repo.update(), msg="The repo is not changes. But update() != False") def testDidUpdate(self): diff --git a/github/tests/ReplayData/ConditionalRequestUpdate.setUp.txt b/github/tests/ReplayData/ConditionalRequestUpdate.setUp.txt index fe2eacd3..dfc62ba2 100644 --- a/github/tests/ReplayData/ConditionalRequestUpdate.setUp.txt +++ b/github/tests/ReplayData/ConditionalRequestUpdate.setUp.txt @@ -9,14 +9,3 @@ null [('status', '200 OK'), ('x-ratelimit-remaining', '4988'), ('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'), ('access-control-allow-credentials', 'true'), ('vary', 'Accept, Authorization, Cookie, Accept-Encoding'), ('content-length', '13698'), ('server', 'GitHub.com'), ('last-modified', 'Thu, 22 Aug 2013 02:09:11 GMT'), ('x-ratelimit-limit', '5000'), ('etag', '"8600bedcb7fed1d8065e1693e05529ce"'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('date', 'Thu, 22 Aug 2013 02:13:08 GMT'), ('access-control-allow-origin', '*'), ('content-type', 'application/json; charset=utf-8'), ('x-ratelimit-reset', '1377140429')] {"id":12156762,"name":"PyGithub","full_name":"akfish/PyGithub","owner":{"login":"akfish","id":922715,"avatar_url":"https://1.gravatar.com/avatar/12a1b44d4e5c19cee59618084602b112?d=https%3A%2F%2Fidenticons.github.com%2F6eb90fb68a77fb5a5a997c6264bedf35.png","gravatar_id":"12a1b44d4e5c19cee59618084602b112","url":"https://api.github.com/users/akfish","html_url":"https://github.com/akfish","followers_url":"https://api.github.com/users/akfish/followers","following_url":"https://api.github.com/users/akfish/following{/other_user}","gists_url":"https://api.github.com/users/akfish/gists{/gist_id}","starred_url":"https://api.github.com/users/akfish/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akfish/subscriptions","organizations_url":"https://api.github.com/users/akfish/orgs","repos_url":"https://api.github.com/users/akfish/repos","events_url":"https://api.github.com/users/akfish/events{/privacy}","received_events_url":"https://api.github.com/users/akfish/received_events","type":"User"},"private":false,"html_url":"https://github.com/akfish/PyGithub","description":"Python library implementing the full Github API v3","fork":true,"url":"https://api.github.com/repos/akfish/PyGithub","forks_url":"https://api.github.com/repos/akfish/PyGithub/forks","keys_url":"https://api.github.com/repos/akfish/PyGithub/keys{/key_id}","collaborators_url":"https://api.github.com/repos/akfish/PyGithub/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/akfish/PyGithub/teams","hooks_url":"https://api.github.com/repos/akfish/PyGithub/hooks","issue_events_url":"https://api.github.com/repos/akfish/PyGithub/issues/events{/number}","events_url":"https://api.github.com/repos/akfish/PyGithub/events","assignees_url":"https://api.github.com/repos/akfish/PyGithub/assignees{/user}","branches_url":"https://api.github.com/repos/akfish/PyGithub/branches{/branch}","tags_url":"https://api.github.com/repos/akfish/PyGithub/tags","blobs_url":"https://api.github.com/repos/akfish/PyGithub/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/akfish/PyGithub/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/akfish/PyGithub/git/refs{/sha}","trees_url":"https://api.github.com/repos/akfish/PyGithub/git/trees{/sha}","statuses_url":"https://api.github.com/repos/akfish/PyGithub/statuses/{sha}","languages_url":"https://api.github.com/repos/akfish/PyGithub/languages","stargazers_url":"https://api.github.com/repos/akfish/PyGithub/stargazers","contributors_url":"https://api.github.com/repos/akfish/PyGithub/contributors","subscribers_url":"https://api.github.com/repos/akfish/PyGithub/subscribers","subscription_url":"https://api.github.com/repos/akfish/PyGithub/subscription","commits_url":"https://api.github.com/repos/akfish/PyGithub/commits{/sha}","git_commits_url":"https://api.github.com/repos/akfish/PyGithub/git/commits{/sha}","comments_url":"https://api.github.com/repos/akfish/PyGithub/comments{/number}","issue_comment_url":"https://api.github.com/repos/akfish/PyGithub/issues/comments/{number}","contents_url":"https://api.github.com/repos/akfish/PyGithub/contents/{+path}","compare_url":"https://api.github.com/repos/akfish/PyGithub/compare/{base}...{head}","merges_url":"https://api.github.com/repos/akfish/PyGithub/merges","archive_url":"https://api.github.com/repos/akfish/PyGithub/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/akfish/PyGithub/downloads","issues_url":"https://api.github.com/repos/akfish/PyGithub/issues{/number}","pulls_url":"https://api.github.com/repos/akfish/PyGithub/pulls{/number}","milestones_url":"https://api.github.com/repos/akfish/PyGithub/milestones{/number}","notifications_url":"https://api.github.com/repos/akfish/PyGithub/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/akfish/PyGithub/labels{/name}","created_at":"2013-08-16T10:56:11Z","updated_at":"2013-08-22T02:09:11Z","pushed_at":"2013-08-22T02:09:09Z","git_url":"git://github.com/akfish/PyGithub.git","ssh_url":"git@github.com:akfish/PyGithub.git","clone_url":"https://github.com/akfish/PyGithub.git","svn_url":"https://github.com/akfish/PyGithub","homepage":"http://jacquev6.github.com/PyGithub","size":6736,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"master_branch":"master","default_branch":"master","permissions":{"admin":true,"push":true,"pull":true},"network_count":70,"parent":{"id":3544490,"name":"PyGithub","full_name":"jacquev6/PyGithub","owner":{"login":"jacquev6","id":327146,"avatar_url":"https://0.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https%3A%2F%2Fidenticons.github.com%2Ffadfb5f7088ef66579d198a3c9a4935e.png","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","url":"https://api.github.com/users/jacquev6","html_url":"https://github.com/jacquev6","followers_url":"https://api.github.com/users/jacquev6/followers","following_url":"https://api.github.com/users/jacquev6/following{/other_user}","gists_url":"https://api.github.com/users/jacquev6/gists{/gist_id}","starred_url":"https://api.github.com/users/jacquev6/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jacquev6/subscriptions","organizations_url":"https://api.github.com/users/jacquev6/orgs","repos_url":"https://api.github.com/users/jacquev6/repos","events_url":"https://api.github.com/users/jacquev6/events{/privacy}","received_events_url":"https://api.github.com/users/jacquev6/received_events","type":"User"},"private":false,"html_url":"https://github.com/jacquev6/PyGithub","description":"Python library implementing the full Github API v3","fork":false,"url":"https://api.github.com/repos/jacquev6/PyGithub","forks_url":"https://api.github.com/repos/jacquev6/PyGithub/forks","keys_url":"https://api.github.com/repos/jacquev6/PyGithub/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jacquev6/PyGithub/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jacquev6/PyGithub/teams","hooks_url":"https://api.github.com/repos/jacquev6/PyGithub/hooks","issue_events_url":"https://api.github.com/repos/jacquev6/PyGithub/issues/events{/number}","events_url":"https://api.github.com/repos/jacquev6/PyGithub/events","assignees_url":"https://api.github.com/repos/jacquev6/PyGithub/assignees{/user}","branches_url":"https://api.github.com/repos/jacquev6/PyGithub/branches{/branch}","tags_url":"https://api.github.com/repos/jacquev6/PyGithub/tags","blobs_url":"https://api.github.com/repos/jacquev6/PyGithub/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jacquev6/PyGithub/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jacquev6/PyGithub/git/refs{/sha}","trees_url":"https://api.github.com/repos/jacquev6/PyGithub/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jacquev6/PyGithub/statuses/{sha}","languages_url":"https://api.github.com/repos/jacquev6/PyGithub/languages","stargazers_url":"https://api.github.com/repos/jacquev6/PyGithub/stargazers","contributors_url":"https://api.github.com/repos/jacquev6/PyGithub/contributors","subscribers_url":"https://api.github.com/repos/jacquev6/PyGithub/subscribers","subscription_url":"https://api.github.com/repos/jacquev6/PyGithub/subscription","commits_url":"https://api.github.com/repos/jacquev6/PyGithub/commits{/sha}","git_commits_url":"https://api.github.com/repos/jacquev6/PyGithub/git/commits{/sha}","comments_url":"https://api.github.com/repos/jacquev6/PyGithub/comments{/number}","issue_comment_url":"https://api.github.com/repos/jacquev6/PyGithub/issues/comments/{number}","contents_url":"https://api.github.com/repos/jacquev6/PyGithub/contents/{+path}","compare_url":"https://api.github.com/repos/jacquev6/PyGithub/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jacquev6/PyGithub/merges","archive_url":"https://api.github.com/repos/jacquev6/PyGithub/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jacquev6/PyGithub/downloads","issues_url":"https://api.github.com/repos/jacquev6/PyGithub/issues{/number}","pulls_url":"https://api.github.com/repos/jacquev6/PyGithub/pulls{/number}","milestones_url":"https://api.github.com/repos/jacquev6/PyGithub/milestones{/number}","notifications_url":"https://api.github.com/repos/jacquev6/PyGithub/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jacquev6/PyGithub/labels{/name}","created_at":"2012-02-25T12:53:47Z","updated_at":"2013-08-21T20:32:08Z","pushed_at":"2013-08-21T20:31:45Z","git_url":"git://github.com/jacquev6/PyGithub.git","ssh_url":"git@github.com:jacquev6/PyGithub.git","clone_url":"https://github.com/jacquev6/PyGithub.git","svn_url":"https://github.com/jacquev6/PyGithub","homepage":"http://jacquev6.github.com/PyGithub","size":7437,"watchers_count":248,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"forks_count":70,"mirror_url":null,"open_issues_count":17,"forks":70,"open_issues":17,"watchers":248,"master_branch":"master","default_branch":"master"},"source":{"id":3544490,"name":"PyGithub","full_name":"jacquev6/PyGithub","owner":{"login":"jacquev6","id":327146,"avatar_url":"https://0.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https%3A%2F%2Fidenticons.github.com%2Ffadfb5f7088ef66579d198a3c9a4935e.png","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","url":"https://api.github.com/users/jacquev6","html_url":"https://github.com/jacquev6","followers_url":"https://api.github.com/users/jacquev6/followers","following_url":"https://api.github.com/users/jacquev6/following{/other_user}","gists_url":"https://api.github.com/users/jacquev6/gists{/gist_id}","starred_url":"https://api.github.com/users/jacquev6/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jacquev6/subscriptions","organizations_url":"https://api.github.com/users/jacquev6/orgs","repos_url":"https://api.github.com/users/jacquev6/repos","events_url":"https://api.github.com/users/jacquev6/events{/privacy}","received_events_url":"https://api.github.com/users/jacquev6/received_events","type":"User"},"private":false,"html_url":"https://github.com/jacquev6/PyGithub","description":"Python library implementing the full Github API v3","fork":false,"url":"https://api.github.com/repos/jacquev6/PyGithub","forks_url":"https://api.github.com/repos/jacquev6/PyGithub/forks","keys_url":"https://api.github.com/repos/jacquev6/PyGithub/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jacquev6/PyGithub/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jacquev6/PyGithub/teams","hooks_url":"https://api.github.com/repos/jacquev6/PyGithub/hooks","issue_events_url":"https://api.github.com/repos/jacquev6/PyGithub/issues/events{/number}","events_url":"https://api.github.com/repos/jacquev6/PyGithub/events","assignees_url":"https://api.github.com/repos/jacquev6/PyGithub/assignees{/user}","branches_url":"https://api.github.com/repos/jacquev6/PyGithub/branches{/branch}","tags_url":"https://api.github.com/repos/jacquev6/PyGithub/tags","blobs_url":"https://api.github.com/repos/jacquev6/PyGithub/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jacquev6/PyGithub/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jacquev6/PyGithub/git/refs{/sha}","trees_url":"https://api.github.com/repos/jacquev6/PyGithub/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jacquev6/PyGithub/statuses/{sha}","languages_url":"https://api.github.com/repos/jacquev6/PyGithub/languages","stargazers_url":"https://api.github.com/repos/jacquev6/PyGithub/stargazers","contributors_url":"https://api.github.com/repos/jacquev6/PyGithub/contributors","subscribers_url":"https://api.github.com/repos/jacquev6/PyGithub/subscribers","subscription_url":"https://api.github.com/repos/jacquev6/PyGithub/subscription","commits_url":"https://api.github.com/repos/jacquev6/PyGithub/commits{/sha}","git_commits_url":"https://api.github.com/repos/jacquev6/PyGithub/git/commits{/sha}","comments_url":"https://api.github.com/repos/jacquev6/PyGithub/comments{/number}","issue_comment_url":"https://api.github.com/repos/jacquev6/PyGithub/issues/comments/{number}","contents_url":"https://api.github.com/repos/jacquev6/PyGithub/contents/{+path}","compare_url":"https://api.github.com/repos/jacquev6/PyGithub/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jacquev6/PyGithub/merges","archive_url":"https://api.github.com/repos/jacquev6/PyGithub/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jacquev6/PyGithub/downloads","issues_url":"https://api.github.com/repos/jacquev6/PyGithub/issues{/number}","pulls_url":"https://api.github.com/repos/jacquev6/PyGithub/pulls{/number}","milestones_url":"https://api.github.com/repos/jacquev6/PyGithub/milestones{/number}","notifications_url":"https://api.github.com/repos/jacquev6/PyGithub/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jacquev6/PyGithub/labels{/name}","created_at":"2012-02-25T12:53:47Z","updated_at":"2013-08-21T20:32:08Z","pushed_at":"2013-08-21T20:31:45Z","git_url":"git://github.com/jacquev6/PyGithub.git","ssh_url":"git@github.com:jacquev6/PyGithub.git","clone_url":"https://github.com/jacquev6/PyGithub.git","svn_url":"https://github.com/jacquev6/PyGithub","homepage":"http://jacquev6.github.com/PyGithub","size":7437,"watchers_count":248,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"forks_count":70,"mirror_url":null,"open_issues_count":17,"forks":70,"open_issues":17,"watchers":248,"master_branch":"master","default_branch":"master"}} -https -GET -api.github.com -None -/repos/akfish/PyGithub -{'If-None-Match': '"8600bedcb7fed1d8065e1693e05529ce"', 'User-Agent': 'PyGithub/Python', 'Authorization': 'Basic login_and_password_removed', 'If-Modified-Since': 'Thu, 22 Aug 2013 02:09:11 GMT'} -null -304 -[('status', '304 Not Modified'), ('x-ratelimit-remaining', '4988'), ('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'), ('access-control-allow-credentials', 'true'), ('vary', 'Accept-Encoding'), ('server', 'GitHub.com'), ('last-modified', 'Thu, 22 Aug 2013 02:09:11 GMT'), ('x-ratelimit-limit', '5000'), ('etag', '"8600bedcb7fed1d8065e1693e05529ce"'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('date', 'Thu, 22 Aug 2013 02:13:10 GMT'), ('access-control-allow-origin', '*'), ('x-ratelimit-reset', '1377140429')] - - diff --git a/github/tests/ReplayData/ConditionalRequestUpdate.testDidNotUpdate.txt b/github/tests/ReplayData/ConditionalRequestUpdate.testDidNotUpdate.txt new file mode 100755 index 00000000..025aee8c --- /dev/null +++ b/github/tests/ReplayData/ConditionalRequestUpdate.testDidNotUpdate.txt @@ -0,0 +1,11 @@ +https +GET +api.github.com +None +/repos/akfish/PyGithub +{'If-None-Match': '"8600bedcb7fed1d8065e1693e05529ce"', 'User-Agent': 'PyGithub/Python', 'Authorization': 'Basic login_and_password_removed', 'If-Modified-Since': 'Thu, 22 Aug 2013 02:09:11 GMT'} +null +304 +[('status', '304 Not Modified'), ('x-ratelimit-remaining', '4988'), ('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'), ('access-control-allow-credentials', 'true'), ('vary', 'Accept-Encoding'), ('server', 'GitHub.com'), ('last-modified', 'Thu, 22 Aug 2013 02:09:11 GMT'), ('x-ratelimit-limit', '5000'), ('etag', '"8600bedcb7fed1d8065e1693e05529ce"'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('date', 'Thu, 22 Aug 2013 02:13:10 GMT'), ('access-control-allow-origin', '*'), ('x-ratelimit-reset', '1377140429')] + + From bae0a37d180a4b224c6aa808d03722908109c57d Mon Sep 17 00:00:00 2001 From: Vincent Jacques Date: Fri, 23 Aug 2013 11:10:35 +0200 Subject: [PATCH 14/24] #193: Don't use a try-except for a usual execution flow in GithubObject.update (and factorize assignment of headers in _storeAndUseAttributes, as done for rawData) --- github/GithubException.py | 6 ------ github/GithubObject.py | 36 ++++++++++++++++++------------------ github/Requester.py | 2 -- 3 files changed, 18 insertions(+), 26 deletions(-) diff --git a/github/GithubException.py b/github/GithubException.py index 869c83d7..02e7a75e 100644 --- a/github/GithubException.py +++ b/github/GithubException.py @@ -77,9 +77,3 @@ class RateLimitExceededException(GithubException): """ Exception raised when the rate limit is exceeded (when Github API replies with a 403 rate limit exceeded HTML status) """ - - -class NotModifiedException(GithubException): - """ - Exception raised when conditional request is made to a resoure that has not changed - """ diff --git a/github/GithubObject.py b/github/GithubObject.py index 92efb7f2..6bc5a7bb 100644 --- a/github/GithubObject.py +++ b/github/GithubObject.py @@ -55,20 +55,20 @@ class GithubObject(object): def __init__(self, requester, headers, attributes, completed): self._requester = requester - # Make sure headers are signed before any operations on attributes - # Object creatation requires headers as parameter - self._headers = headers self._initAttributes() - self._storeAndUseAttributes(attributes) + self._storeAndUseAttributes(headers, attributes) # Ask requester to do some checking, for debug and test purpose # Since it's most handy to access and kinda all-knowing if self.CHECK_AFTER_INIT_FLAG: requester.check_me(self) - def _storeAndUseAttributes(self, attributes): - self._useAttributes(attributes) + def _storeAndUseAttributes(self, headers, attributes): + # Make sure headers are assigned before calling _useAttributes + # (Some derived classes will use headers in _useAttributes) + self._headers = headers self._rawData = attributes + self._useAttributes(attributes) @property def raw_data(self): @@ -144,18 +144,19 @@ class GithubObject(object): if self.last_modified is not None: conditionalRequestHeader[Consts.REQ_IF_MODIFIED_SINCE] = self.last_modified - try: - headers, data = self._requester.requestJsonAndCheck( - "GET", - self._url, - conditionalRequestHeader, - None - ) - self._storeAndUseAttributes(data) + status, responseHeaders, output = self._requester.requestJson( + "GET", + self._url, + conditionalRequestHeader, + None + ) + if status == 304: + return False + else: + headers, data = self._requester._Requester__check(status, responseHeaders, output) + self._storeAndUseAttributes(headers, data) self.__completed = True return True - except GithubException.NotModifiedException: # #193: Why raise and catch? Can't we just check? - return False class NonCompletableGithubObject(GithubObject): @@ -183,6 +184,5 @@ class CompletableGithubObject(GithubObject): None, None ) - self._headers = headers - self._storeAndUseAttributes(data) + self._storeAndUseAttributes(headers, data) self.__completed = True diff --git a/github/Requester.py b/github/Requester.py index 9ac335e6..792861d9 100644 --- a/github/Requester.py +++ b/github/Requester.py @@ -179,8 +179,6 @@ class Requester: # Log frame self.DEBUG_ON_RESPONSE(status, responseHeaders, output) - if status == 304: - raise GithubException.NotModifiedException(status, output) if status >= 400: raise self.__createException(status, output) return responseHeaders, output From 03d7fb012e9d032165c43f93a4c67bc29af9366f Mon Sep 17 00:00:00 2001 From: Vincent Jacques Date: Fri, 23 Aug 2013 11:15:17 +0200 Subject: [PATCH 15/24] #193: Add remarks --- github/GithubObject.py | 1 + github/Requester.py | 1 + 2 files changed, 2 insertions(+) diff --git a/github/GithubObject.py b/github/GithubObject.py index 6bc5a7bb..bc32b257 100644 --- a/github/GithubObject.py +++ b/github/GithubObject.py @@ -133,6 +133,7 @@ class GithubObject(object): ''' return self._headers.get(Consts.RES_LAST_MODIFED) + # #193: Should be only in CompletableGithubObject, NonCompletableGithubObjects don't have urls def update(self): ''' Check and update the object with conditional request diff --git a/github/Requester.py b/github/Requester.py index 792861d9..feed86b2 100644 --- a/github/Requester.py +++ b/github/Requester.py @@ -176,6 +176,7 @@ class Requester: def __check(self, status, responseHeaders, output): output = self.__structuredFromJson(output) + # #193: Shouldn't next line be in __requestEncode? (__check is not called on all requests) # Log frame self.DEBUG_ON_RESPONSE(status, responseHeaders, output) From 64cf539c83174f95b3410c7decd2549424385ce1 Mon Sep 17 00:00:00 2001 From: Vincent Jacques Date: Fri, 23 Aug 2013 11:31:01 +0200 Subject: [PATCH 16/24] #193: Add a param to Requester.requestXxx for request headers --- github/AuthenticatedUser.py | 25 ++++++++++++++++++++ github/Authorization.py | 2 ++ github/Commit.py | 2 ++ github/CommitComment.py | 2 ++ github/Download.py | 1 + github/Gist.py | 8 +++++++ github/GistComment.py | 2 ++ github/GitRef.py | 2 ++ github/GithubObject.py | 2 ++ github/Hook.py | 3 +++ github/Issue.py | 7 ++++++ github/IssueComment.py | 2 ++ github/Label.py | 2 ++ github/Legacy.py | 1 + github/MainClass.py | 11 +++++++++ github/Milestone.py | 2 ++ github/NamedUser.py | 3 +++ github/Organization.py | 11 +++++++++ github/PaginatedList.py | 24 +++++++++++++++++--- github/PullRequest.py | 7 ++++++ github/PullRequestComment.py | 2 ++ github/Repository.py | 44 ++++++++++++++++++++++++++++++++++++ github/RepositoryKey.py | 2 ++ github/Requester.py | 34 +++++++++------------------- github/Team.py | 8 +++++++ github/UserKey.py | 2 ++ 26 files changed, 185 insertions(+), 26 deletions(-) diff --git a/github/AuthenticatedUser.py b/github/AuthenticatedUser.py index e035f9a1..d403893f 100644 --- a/github/AuthenticatedUser.py +++ b/github/AuthenticatedUser.py @@ -258,6 +258,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "POST", "/user/emails", None, + None, post_parameters ) @@ -272,6 +273,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "PUT", "/user/following/" + following._identity, None, + None, None ) @@ -286,6 +288,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "PUT", "/user/starred/" + starred._identity, None, + None, None ) @@ -300,6 +303,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "PUT", "/user/subscriptions/" + subscription._identity, None, + None, None ) @@ -314,6 +318,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "PUT", "/user/watched/" + watched._identity, None, + None, None ) @@ -347,6 +352,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "POST", "/authorizations", None, + None, post_parameters ) return github.Authorization.Authorization(self._requester, headers, data, completed=True) @@ -362,6 +368,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "POST", "/repos/" + repo.owner.login + "/" + repo.name + "/forks", None, + None, None ) return github.Repository.Repository(self._requester, headers, data, completed=True) @@ -387,6 +394,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "POST", "/gists", None, + None, post_parameters ) return github.Gist.Gist(self._requester, headers, data, completed=True) @@ -408,6 +416,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "POST", "/user/keys", None, + None, post_parameters ) return github.UserKey.UserKey(self._requester, headers, data, completed=True) @@ -458,6 +467,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "POST", "/user/repos", None, + None, post_parameters ) return github.Repository.Repository(self._requester, headers, data, completed=True) @@ -500,6 +510,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "PATCH", "/user", None, + None, post_parameters ) self._useAttributes(data) @@ -515,6 +526,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "GET", "/authorizations/" + str(id), None, + None, None ) return github.Authorization.Authorization(self._requester, headers, data, completed=True) @@ -540,6 +552,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "GET", "/user/emails", None, + None, None ) return data @@ -679,6 +692,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "GET", "/user/keys/" + str(id), None, + None, None ) return github.UserKey.UserKey(self._requester, headers, data, completed=True) @@ -706,6 +720,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "GET", "/notifications/threads/" + id, None, + None, None ) return github.Notification.Notification(self._requester, headers, data, completed=True) @@ -770,6 +785,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "GET", "/repos/" + self.login + "/" + name, None, + None, None ) return github.Repository.Repository(self._requester, headers, data, completed=True) @@ -858,6 +874,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "GET", "/user/following/" + following._identity, None, + None, None ) return status == 204 @@ -873,6 +890,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "GET", "/user/starred/" + starred._identity, None, + None, None ) return status == 204 @@ -888,6 +906,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "GET", "/user/subscriptions/" + subscription._identity, None, + None, None ) return status == 204 @@ -903,6 +922,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "GET", "/user/watched/" + watched._identity, None, + None, None ) return status == 204 @@ -919,6 +939,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "DELETE", "/user/emails", None, + None, post_parameters ) @@ -933,6 +954,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "DELETE", "/user/following/" + following._identity, None, + None, None ) @@ -947,6 +969,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "DELETE", "/user/starred/" + starred._identity, None, + None, None ) @@ -961,6 +984,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "DELETE", "/user/subscriptions/" + subscription._identity, None, + None, None ) @@ -975,6 +999,7 @@ class AuthenticatedUser(github.GithubObject.CompletableGithubObject): "DELETE", "/user/watched/" + watched._identity, None, + None, None ) diff --git a/github/Authorization.py b/github/Authorization.py index 2289fd34..f287a2b6 100644 --- a/github/Authorization.py +++ b/github/Authorization.py @@ -115,6 +115,7 @@ class Authorization(github.GithubObject.CompletableGithubObject): "DELETE", self.url, None, + None, None ) @@ -148,6 +149,7 @@ class Authorization(github.GithubObject.CompletableGithubObject): "PATCH", self.url, None, + None, post_parameters ) self._useAttributes(data) diff --git a/github/Commit.py b/github/Commit.py index 5e909213..ad744e39 100644 --- a/github/Commit.py +++ b/github/Commit.py @@ -131,6 +131,7 @@ class Commit(github.GithubObject.CompletableGithubObject): "POST", self.url + "/comments", None, + None, post_parameters ) return github.CommitComment.CommitComment(self._requester, headers, data, completed=True) @@ -157,6 +158,7 @@ class Commit(github.GithubObject.CompletableGithubObject): "POST", self._parentUrl(self._parentUrl(self.url)) + "/statuses/" + self.sha, None, + None, post_parameters ) return github.CommitStatus.CommitStatus(self._requester, headers, data, completed=True) diff --git a/github/CommitComment.py b/github/CommitComment.py index 55c2cf86..061737bf 100644 --- a/github/CommitComment.py +++ b/github/CommitComment.py @@ -131,6 +131,7 @@ class CommitComment(github.GithubObject.CompletableGithubObject): "DELETE", self.url, None, + None, None ) @@ -148,6 +149,7 @@ class CommitComment(github.GithubObject.CompletableGithubObject): "PATCH", self.url, None, + None, post_parameters ) self._useAttributes(data) diff --git a/github/Download.py b/github/Download.py index 86b7ae44..a4b3b656 100644 --- a/github/Download.py +++ b/github/Download.py @@ -201,6 +201,7 @@ class Download(github.GithubObject.CompletableGithubObject): "DELETE", self.url, None, + None, None ) diff --git a/github/Gist.py b/github/Gist.py index a1497685..de4ded9e 100644 --- a/github/Gist.py +++ b/github/Gist.py @@ -173,6 +173,7 @@ class Gist(github.GithubObject.CompletableGithubObject): "POST", self.url + "/comments", None, + None, post_parameters ) return github.GistComment.GistComment(self._requester, headers, data, completed=True) @@ -186,6 +187,7 @@ class Gist(github.GithubObject.CompletableGithubObject): "POST", self.url + "/forks", None, + None, None ) return Gist(self._requester, headers, data, completed=True) @@ -199,6 +201,7 @@ class Gist(github.GithubObject.CompletableGithubObject): "DELETE", self.url, None, + None, None ) @@ -220,6 +223,7 @@ class Gist(github.GithubObject.CompletableGithubObject): "PATCH", self.url, None, + None, post_parameters ) self._useAttributes(data) @@ -235,6 +239,7 @@ class Gist(github.GithubObject.CompletableGithubObject): "GET", self.url + "/comments/" + str(id), None, + None, None ) return github.GistComment.GistComment(self._requester, headers, data, completed=True) @@ -260,6 +265,7 @@ class Gist(github.GithubObject.CompletableGithubObject): "GET", self.url + "/star", None, + None, None ) return status == 204 @@ -273,6 +279,7 @@ class Gist(github.GithubObject.CompletableGithubObject): "DELETE", self.url + "/star", None, + None, None ) @@ -285,6 +292,7 @@ class Gist(github.GithubObject.CompletableGithubObject): "PUT", self.url + "/star", None, + None, None ) diff --git a/github/GistComment.py b/github/GistComment.py index 6c7e2461..f0b5c80c 100644 --- a/github/GistComment.py +++ b/github/GistComment.py @@ -91,6 +91,7 @@ class GistComment(github.GithubObject.CompletableGithubObject): "DELETE", self.url, None, + None, None ) @@ -108,6 +109,7 @@ class GistComment(github.GithubObject.CompletableGithubObject): "PATCH", self.url, None, + None, post_parameters ) self._useAttributes(data) diff --git a/github/GitRef.py b/github/GitRef.py index a666156b..84ec00c5 100644 --- a/github/GitRef.py +++ b/github/GitRef.py @@ -67,6 +67,7 @@ class GitRef(github.GithubObject.CompletableGithubObject): "DELETE", self.url, None, + None, None ) @@ -88,6 +89,7 @@ class GitRef(github.GithubObject.CompletableGithubObject): "PATCH", self.url, None, + None, post_parameters ) self._useAttributes(data) diff --git a/github/GithubObject.py b/github/GithubObject.py index bc32b257..4fddd29d 100644 --- a/github/GithubObject.py +++ b/github/GithubObject.py @@ -148,6 +148,7 @@ class GithubObject(object): status, responseHeaders, output = self._requester.requestJson( "GET", self._url, + None, conditionalRequestHeader, None ) @@ -183,6 +184,7 @@ class CompletableGithubObject(GithubObject): "GET", self._url, None, + None, None ) self._storeAndUseAttributes(headers, data) diff --git a/github/Hook.py b/github/Hook.py index 721bdd7a..ea0be67b 100644 --- a/github/Hook.py +++ b/github/Hook.py @@ -115,6 +115,7 @@ class Hook(github.GithubObject.CompletableGithubObject): "DELETE", self.url, None, + None, None ) @@ -151,6 +152,7 @@ class Hook(github.GithubObject.CompletableGithubObject): "PATCH", self.url, None, + None, post_parameters ) self._useAttributes(data) @@ -164,6 +166,7 @@ class Hook(github.GithubObject.CompletableGithubObject): "POST", self.url + "/tests", None, + None, None ) diff --git a/github/Issue.py b/github/Issue.py index 1cdcc6bb..260967d3 100644 --- a/github/Issue.py +++ b/github/Issue.py @@ -204,6 +204,7 @@ class Issue(github.GithubObject.CompletableGithubObject): "POST", self.url + "/labels", None, + None, post_parameters ) @@ -221,6 +222,7 @@ class Issue(github.GithubObject.CompletableGithubObject): "POST", self.url + "/comments", None, + None, post_parameters ) return github.IssueComment.IssueComment(self._requester, headers, data, completed=True) @@ -234,6 +236,7 @@ class Issue(github.GithubObject.CompletableGithubObject): "DELETE", self.url + "/labels", None, + None, None ) @@ -271,6 +274,7 @@ class Issue(github.GithubObject.CompletableGithubObject): "PATCH", self.url, None, + None, post_parameters ) self._useAttributes(data) @@ -286,6 +290,7 @@ class Issue(github.GithubObject.CompletableGithubObject): "GET", self._parentUrl(self.url) + "/comments/" + str(id), None, + None, None ) return github.IssueComment.IssueComment(self._requester, headers, data, completed=True) @@ -337,6 +342,7 @@ class Issue(github.GithubObject.CompletableGithubObject): "DELETE", self.url + "/labels/" + label._identity, None, + None, None ) @@ -352,6 +358,7 @@ class Issue(github.GithubObject.CompletableGithubObject): "PUT", self.url + "/labels", None, + None, post_parameters ) diff --git a/github/IssueComment.py b/github/IssueComment.py index 5a3c62da..c1af97f8 100644 --- a/github/IssueComment.py +++ b/github/IssueComment.py @@ -100,6 +100,7 @@ class IssueComment(github.GithubObject.CompletableGithubObject): "DELETE", self.url, None, + None, None ) @@ -117,6 +118,7 @@ class IssueComment(github.GithubObject.CompletableGithubObject): "PATCH", self.url, None, + None, post_parameters ) self._useAttributes(data) diff --git a/github/Label.py b/github/Label.py index b3c75501..b961af18 100644 --- a/github/Label.py +++ b/github/Label.py @@ -68,6 +68,7 @@ class Label(github.GithubObject.CompletableGithubObject): "DELETE", self.url, None, + None, None ) @@ -88,6 +89,7 @@ class Label(github.GithubObject.CompletableGithubObject): "PATCH", self.url, None, + None, post_parameters ) self._useAttributes(data) diff --git a/github/Legacy.py b/github/Legacy.py index 1ed760e2..a0e3493f 100644 --- a/github/Legacy.py +++ b/github/Legacy.py @@ -59,6 +59,7 @@ class PaginatedList(github.PaginatedList.PaginatedListBase): "GET", self.__url, args, + None, None ) self.__continue = len(data[self.__key]) > 0 diff --git a/github/MainClass.py b/github/MainClass.py index ae1f612f..4eab8517 100644 --- a/github/MainClass.py +++ b/github/MainClass.py @@ -106,6 +106,7 @@ class Github(object): 'GET', '/rate_limit', None, + None, None ) return self.__requester.rate_limiting @@ -121,6 +122,7 @@ class Github(object): 'GET', '/rate_limit', None, + None, None ) return self.__requester.rate_limiting_resettime @@ -146,6 +148,7 @@ class Github(object): "GET", "/users/" + login, None, + None, None ) return github.NamedUser.NamedUser(self.__requester, headers, data, completed=True) @@ -178,6 +181,7 @@ class Github(object): "GET", "/orgs/" + login, None, + None, None ) return github.Organization.Organization(self.__requester, headers, data, completed=True) @@ -192,6 +196,7 @@ class Github(object): "GET", "/repos/" + full_name, None, + None, None ) return Repository.Repository(self.__requester, headers, data, completed=True) @@ -224,6 +229,7 @@ class Github(object): "GET", "/gists/" + id, None, + None, None ) return github.Gist.Gist(self.__requester, headers, data, completed=True) @@ -286,6 +292,7 @@ class Github(object): "GET", "/legacy/user/email/" + email, None, + None, None ) return github.NamedUser.NamedUser(self.__requester, headers, Legacy.convertUser(data["user"]), completed=False) @@ -309,6 +316,7 @@ class Github(object): "POST", "/markdown", None, + None, post_parameters ) return data @@ -322,6 +330,7 @@ class Github(object): "GET", "/hooks", None, + None, None ) return [HookDescription.HookDescription(self.__requester, headers, attributes, completed=True) for attributes in data] @@ -335,6 +344,7 @@ class Github(object): "GET", "/gitignore/templates", None, + None, None ) return data @@ -349,6 +359,7 @@ class Github(object): "GET", "/gitignore/templates/" + name, None, + None, None ) return GitignoreTemplate.GitignoreTemplate(self.__requester, headers, attributes, completed=True) diff --git a/github/Milestone.py b/github/Milestone.py index db38437f..0f23eb78 100644 --- a/github/Milestone.py +++ b/github/Milestone.py @@ -136,6 +136,7 @@ class Milestone(github.GithubObject.CompletableGithubObject): "DELETE", self.url, None, + None, None ) @@ -165,6 +166,7 @@ class Milestone(github.GithubObject.CompletableGithubObject): "PATCH", self.url, None, + None, post_parameters ) self._useAttributes(data) diff --git a/github/NamedUser.py b/github/NamedUser.py index ca29d38e..435b1bf2 100644 --- a/github/NamedUser.py +++ b/github/NamedUser.py @@ -270,6 +270,7 @@ class NamedUser(github.GithubObject.CompletableGithubObject): "POST", self.url + "/gists", None, + None, post_parameters ) return github.Gist.Gist(self._requester, headers, data, completed=True) @@ -393,6 +394,7 @@ class NamedUser(github.GithubObject.CompletableGithubObject): "GET", "/repos/" + self.login + "/" + name, None, + None, None ) return github.Repository.Repository(self._requester, headers, data, completed=True) @@ -461,6 +463,7 @@ class NamedUser(github.GithubObject.CompletableGithubObject): "GET", self.url + "/following/" + following._identity, None, + None, None ) return status == 204 diff --git a/github/Organization.py b/github/Organization.py index d252a054..aa904fce 100644 --- a/github/Organization.py +++ b/github/Organization.py @@ -246,6 +246,7 @@ class Organization(github.GithubObject.CompletableGithubObject): "PUT", self.url + "/public_members/" + public_member._identity, None, + None, None ) @@ -263,6 +264,7 @@ class Organization(github.GithubObject.CompletableGithubObject): "POST", "/repos/" + repo.owner.login + "/" + repo.name + "/forks", url_parameters, + None, None ) return github.Repository.Repository(self._requester, headers, data, completed=True) @@ -317,6 +319,7 @@ class Organization(github.GithubObject.CompletableGithubObject): "POST", self.url + "/repos", None, + None, post_parameters ) return github.Repository.Repository(self._requester, headers, data, completed=True) @@ -343,6 +346,7 @@ class Organization(github.GithubObject.CompletableGithubObject): "POST", self.url + "/teams", None, + None, post_parameters ) return github.Team.Team(self._requester, headers, data, completed=True) @@ -381,6 +385,7 @@ class Organization(github.GithubObject.CompletableGithubObject): "PATCH", self.url, None, + None, post_parameters ) self._useAttributes(data) @@ -470,6 +475,7 @@ class Organization(github.GithubObject.CompletableGithubObject): "GET", "/repos/" + self.login + "/" + name, None, + None, None ) return github.Repository.Repository(self._requester, headers, data, completed=True) @@ -502,6 +508,7 @@ class Organization(github.GithubObject.CompletableGithubObject): "GET", "/teams/" + str(id), None, + None, None ) return github.Team.Team(self._requester, headers, data, completed=True) @@ -529,6 +536,7 @@ class Organization(github.GithubObject.CompletableGithubObject): "GET", self.url + "/members/" + member._identity, None, + None, None ) return status == 204 @@ -544,6 +552,7 @@ class Organization(github.GithubObject.CompletableGithubObject): "GET", self.url + "/public_members/" + public_member._identity, None, + None, None ) return status == 204 @@ -559,6 +568,7 @@ class Organization(github.GithubObject.CompletableGithubObject): "DELETE", self.url + "/members/" + member._identity, None, + None, None ) @@ -573,6 +583,7 @@ class Organization(github.GithubObject.CompletableGithubObject): "DELETE", self.url + "/public_members/" + public_member._identity, None, + None, None ) diff --git a/github/PaginatedList.py b/github/PaginatedList.py index e450dfd8..45b2546d 100644 --- a/github/PaginatedList.py +++ b/github/PaginatedList.py @@ -119,7 +119,13 @@ class PaginatedList(PaginatedListBase): self._reversed = False def _getLastPageUrl(self): - headers, data = self.__requester.requestJsonAndCheck("GET", self.__firstUrl, self.__nextParams, None) + headers, data = self.__requester.requestJsonAndCheck( + "GET", + self.__firstUrl, + self.__nextParams, + None, + None + ) links = self.__parseLinkHeader(headers) lastUrl = links.get("last") return lastUrl @@ -140,7 +146,13 @@ class PaginatedList(PaginatedListBase): return self.__nextUrl is not None def _fetchNextPage(self): - headers, data = self.__requester.requestJsonAndCheck("GET", self.__nextUrl, self.__nextParams, None) + headers, data = self.__requester.requestJsonAndCheck( + "GET", + self.__nextUrl, + self.__nextParams, + None, + None + ) self.__nextUrl = None if len(data) > 0: @@ -177,7 +189,13 @@ class PaginatedList(PaginatedListBase): params["page"] = page + 1 if self.__requester.per_page != 30: params["per_page"] = self.__requester.per_page - headers, data = self.__requester.requestJsonAndCheck("GET", self.__firstUrl, params, None) + headers, data = self.__requester.requestJsonAndCheck( + "GET", + self.__firstUrl, + params, + None, + None + ) return [ self.__contentClass(self.__requester, headers, element, completed=False) diff --git a/github/PullRequest.py b/github/PullRequest.py index 8ea72e55..2b9b022b 100644 --- a/github/PullRequest.py +++ b/github/PullRequest.py @@ -293,6 +293,7 @@ class PullRequest(github.GithubObject.CompletableGithubObject): "POST", self.url + "/comments", None, + None, post_parameters ) return github.PullRequestComment.PullRequestComment(self._requester, headers, data, completed=True) @@ -311,6 +312,7 @@ class PullRequest(github.GithubObject.CompletableGithubObject): "POST", self._parentUrl(self._parentUrl(self.url)) + "/issues/" + str(self.number) + "/comments", None, + None, post_parameters ) return github.IssueComment.IssueComment(self._requester, headers, data, completed=True) @@ -337,6 +339,7 @@ class PullRequest(github.GithubObject.CompletableGithubObject): "PATCH", self.url, None, + None, post_parameters ) self._useAttributes(data) @@ -360,6 +363,7 @@ class PullRequest(github.GithubObject.CompletableGithubObject): "GET", self._parentUrl(self.url) + "/comments/" + str(id), None, + None, None ) return github.PullRequestComment.PullRequestComment(self._requester, headers, data, completed=True) @@ -418,6 +422,7 @@ class PullRequest(github.GithubObject.CompletableGithubObject): "GET", self._parentUrl(self._parentUrl(self.url)) + "/issues/comments/" + str(id), None, + None, None ) return github.IssueComment.IssueComment(self._requester, headers, data, completed=True) @@ -443,6 +448,7 @@ class PullRequest(github.GithubObject.CompletableGithubObject): "GET", self.url + "/merge", None, + None, None ) return status == 204 @@ -461,6 +467,7 @@ class PullRequest(github.GithubObject.CompletableGithubObject): "PUT", self.url + "/merge", None, + None, post_parameters ) return github.PullRequestMergeStatus.PullRequestMergeStatus(self._requester, headers, data, completed=True) diff --git a/github/PullRequestComment.py b/github/PullRequestComment.py index efc520cf..4d250d56 100644 --- a/github/PullRequestComment.py +++ b/github/PullRequestComment.py @@ -141,6 +141,7 @@ class PullRequestComment(github.GithubObject.CompletableGithubObject): "DELETE", self.url, None, + None, None ) @@ -158,6 +159,7 @@ class PullRequestComment(github.GithubObject.CompletableGithubObject): "PATCH", self.url, None, + None, post_parameters ) self._useAttributes(data) diff --git a/github/Repository.py b/github/Repository.py index ed5934d5..9a62e13d 100644 --- a/github/Repository.py +++ b/github/Repository.py @@ -319,6 +319,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "PUT", self.url + "/collaborators/" + collaborator._identity, None, + None, None ) @@ -335,6 +336,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", self.url + "/compare/" + base + "..." + head, None, + None, None ) return github.Comparison.Comparison(self._requester, headers, data, completed=True) @@ -364,6 +366,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "POST", self.url + "/downloads", None, + None, post_parameters ) return github.Download.Download(self._requester, headers, data, completed=True) @@ -385,6 +388,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "POST", self.url + "/git/blobs", None, + None, post_parameters ) return github.GitBlob.GitBlob(self._requester, headers, data, completed=True) @@ -417,6 +421,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "POST", self.url + "/git/commits", None, + None, post_parameters ) return github.GitCommit.GitCommit(self._requester, headers, data, completed=True) @@ -438,6 +443,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "POST", self.url + "/git/refs", None, + None, post_parameters ) return github.GitRef.GitRef(self._requester, headers, data, completed=True) @@ -469,6 +475,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "POST", self.url + "/git/tags", None, + None, post_parameters ) return github.GitTag.GitTag(self._requester, headers, data, completed=True) @@ -491,6 +498,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "POST", self.url + "/git/trees", None, + None, post_parameters ) return github.GitTree.GitTree(self._requester, headers, data, completed=True) @@ -520,6 +528,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "POST", self.url + "/hooks", None, + None, post_parameters ) return github.Hook.Hook(self._requester, headers, data, completed=True) @@ -554,6 +563,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "POST", self.url + "/issues", None, + None, post_parameters ) return github.Issue.Issue(self._requester, headers, data, completed=True) @@ -575,6 +585,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "POST", self.url + "/keys", None, + None, post_parameters ) return github.RepositoryKey.RepositoryKey(self._requester, headers, data, completed=True, repoUrl=self._url) @@ -596,6 +607,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "POST", self.url + "/labels", None, + None, post_parameters ) return github.Label.Label(self._requester, headers, data, completed=True) @@ -626,6 +638,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "POST", self.url + "/milestones", None, + None, post_parameters ) return github.Milestone.Milestone(self._requester, headers, data, completed=True) @@ -664,6 +677,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "POST", self.url + "/pulls", None, + None, post_parameters ) return github.PullRequest.PullRequest(self._requester, headers, data, completed=True) @@ -677,6 +691,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "DELETE", self.url, None, + None, None ) @@ -722,6 +737,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "PATCH", self.url, None, + None, post_parameters ) self._useAttributes(data) @@ -742,6 +758,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", url, None, + None, None ) return headers["location"] @@ -769,6 +786,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", self.url + "/branches/" + branch, None, + None, None ) return github.Branch.Branch(self._requester, headers, data, completed=True) @@ -808,6 +826,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", self.url + "/comments/" + str(id), None, + None, None ) return github.CommitComment.CommitComment(self._requester, headers, data, completed=True) @@ -835,6 +854,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", self.url + "/commits/" + sha, None, + None, None ) return github.Commit.Commit(self._requester, headers, data, completed=True) @@ -893,6 +913,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", self.url + "/contents" + path, url_parameters, + None, None ) return github.ContentFile.ContentFile(self._requester, headers, data, completed=True) @@ -913,6 +934,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", self.url + "/contents" + path, url_parameters, + None, None ) @@ -922,6 +944,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", headers['location'], url_parameters, + None, None ) @@ -953,6 +976,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", self.url + "/downloads/" + str(id), None, + None, None ) return github.Download.Download(self._requester, headers, data, completed=True) @@ -1004,6 +1028,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", self.url + "/git/blobs/" + sha, None, + None, None ) return github.GitBlob.GitBlob(self._requester, headers, data, completed=True) @@ -1019,6 +1044,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", self.url + "/git/commits/" + sha, None, + None, None ) return github.GitCommit.GitCommit(self._requester, headers, data, completed=True) @@ -1037,6 +1063,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", self.url + prefix + ref, None, + None, None ) return github.GitRef.GitRef(self._requester, headers, data, completed=True) @@ -1064,6 +1091,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", self.url + "/git/tags/" + sha, None, + None, None ) return github.GitTag.GitTag(self._requester, headers, data, completed=True) @@ -1084,6 +1112,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", self.url + "/git/trees/" + sha, url_parameters, + None, None ) return github.GitTree.GitTree(self._requester, headers, data, completed=True) @@ -1099,6 +1128,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", self.url + "/hooks/" + str(id), None, + None, None ) return github.Hook.Hook(self._requester, headers, data, completed=True) @@ -1126,6 +1156,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", self.url + "/issues/" + str(number), None, + None, None ) return github.Issue.Issue(self._requester, headers, data, completed=True) @@ -1217,6 +1248,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", self.url + "/issues/events/" + str(id), None, + None, None ) return github.IssueEvent.IssueEvent(self._requester, headers, data, completed=True) @@ -1244,6 +1276,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", self.url + "/keys/" + str(id), None, + None, None ) return github.RepositoryKey.RepositoryKey(self._requester, headers, data, completed=True, repoUrl=self._url) @@ -1271,6 +1304,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", self.url + "/labels/" + urllib.quote(name), None, + None, None ) return github.Label.Label(self._requester, headers, data, completed=True) @@ -1296,6 +1330,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", self.url + "/languages", None, + None, None ) return data @@ -1311,6 +1346,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", self.url + "/milestones/" + str(number), None, + None, None ) return github.Milestone.Milestone(self._requester, headers, data, completed=True) @@ -1363,6 +1399,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", self.url + "/pulls/" + str(number), None, + None, None ) return github.PullRequest.PullRequest(self._requester, headers, data, completed=True) @@ -1433,6 +1470,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", self.url + "/readme", url_parameters, + None, None ) return github.ContentFile.ContentFile(self._requester, headers, data, completed=True) @@ -1508,6 +1546,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", self.url + "/assignees/" + assignee._identity, None, + None, None ) return status == 204 @@ -1523,6 +1562,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", self.url + "/collaborators/" + collaborator._identity, None, + None, None ) return status == 204 @@ -1540,6 +1580,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "GET", "/legacy/issues/search/" + self.owner.login + "/" + self.name + "/" + state + "/" + urllib.quote(keyword), None, + None, None ) return [ @@ -1568,6 +1609,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "POST", self.url + "/merges", None, + None, post_parameters ) if data is None: @@ -1586,6 +1628,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "DELETE", self.url + "/collaborators/" + collaborator._identity, None, + None, None ) @@ -1627,6 +1670,7 @@ class Repository(github.GithubObject.CompletableGithubObject): "POST", "/hub", None, + None, post_parameters, ) diff --git a/github/RepositoryKey.py b/github/RepositoryKey.py index 074985d8..22b59621 100644 --- a/github/RepositoryKey.py +++ b/github/RepositoryKey.py @@ -91,6 +91,7 @@ class RepositoryKey(github.GithubObject.CompletableGithubObject): "DELETE", self.__customUrl, None, + None, None ) @@ -112,6 +113,7 @@ class RepositoryKey(github.GithubObject.CompletableGithubObject): "PATCH", self.__customUrl, None, + None, post_parameters ) self._useAttributes(data) diff --git a/github/Requester.py b/github/Requester.py index feed86b2..b161de13 100644 --- a/github/Requester.py +++ b/github/Requester.py @@ -168,11 +168,11 @@ class Requester: 'See http://developer.github.com/v3/#user-agent-required' self.__userAgent = user_agent - def requestJsonAndCheck(self, verb, url, parameters, input): - return self.__check(*self.requestJson(verb, url, parameters, input)) + def requestJsonAndCheck(self, verb, url, parameters, headers, input): + return self.__check(*self.requestJson(verb, url, parameters, headers, input)) - def requestMultipartAndCheck(self, verb, url, parameters, input): - return self.__check(*self.requestMultipart(verb, url, parameters, input)) + def requestMultipartAndCheck(self, verb, url, parameters, headers, input): + return self.__check(*self.requestMultipart(verb, url, parameters, headers, input)) def __check(self, status, responseHeaders, output): output = self.__structuredFromJson(output) @@ -208,13 +208,13 @@ class Requester: except ValueError, e: return {'data': data} - def requestJson(self, verb, url, parameters, input): + def requestJson(self, verb, url, parameters, headers, input): def encode(input): return "application/json", json.dumps(input) - return self.__requestEncode(verb, url, parameters, input, encode) + return self.__requestEncode(verb, url, parameters, headers, input, encode) - def requestMultipart(self, verb, url, parameters, input): + def requestMultipart(self, verb, url, parameters, headers, input): def encode(input): boundary = "----------------------------3c3ba8b523b2" eol = "\r\n" @@ -228,16 +228,16 @@ class Requester: encoded_input += "--" + boundary + "--" + eol return "multipart/form-data; boundary=" + boundary, encoded_input - return self.__requestEncode(verb, url, parameters, input, encode) + return self.__requestEncode(verb, url, parameters, headers, input, encode) - def __requestEncode(self, verb, url, parameters, input, encode): + def __requestEncode(self, verb, url, parameters, requestHeaders, input, encode): assert verb in ["HEAD", "GET", "POST", "PATCH", "PUT", "DELETE"] if parameters is None: parameters = dict() + if requestHeaders is None: + requestHeaders = dict() - requestHeaders = dict() self.__authenticate(url, requestHeaders, parameters) - self.__conditional(requestHeaders, parameters) requestHeaders["User-Agent"] = self.__userAgent url = self.__makeAbsoluteUrl(url) @@ -288,18 +288,6 @@ class Requester: if self.__authorizationHeader is not None: requestHeaders["Authorization"] = self.__authorizationHeader - def __conditional(self, requestHeaders, parameters): - # #193: Why pass etag and last_modified by param "parameters"? - # #193: May be better to add a specific param "headers" to methods requestFoobar? - etag = parameters.get(Consts.REQ_IF_NONE_MATCH) - last_modified = parameters.get(Consts.REQ_IF_MODIFIED_SINCE) - if etag is not None: - requestHeaders[Consts.REQ_IF_NONE_MATCH] = etag - del parameters[Consts.REQ_IF_NONE_MATCH] - if last_modified is not None: - requestHeaders[Consts.REQ_IF_MODIFIED_SINCE] = last_modified - del parameters[Consts.REQ_IF_MODIFIED_SINCE] - def __makeAbsoluteUrl(self, url): # URLs generated locally will be relative to __base_url # URLs returned from the server will start with __base_url diff --git a/github/Team.py b/github/Team.py index 804ed3ab..d8fb7379 100644 --- a/github/Team.py +++ b/github/Team.py @@ -96,6 +96,7 @@ class Team(github.GithubObject.CompletableGithubObject): "PUT", self.url + "/members/" + member._identity, None, + None, None ) @@ -110,6 +111,7 @@ class Team(github.GithubObject.CompletableGithubObject): "PUT", self.url + "/repos/" + repo._identity, None, + None, None ) @@ -122,6 +124,7 @@ class Team(github.GithubObject.CompletableGithubObject): "DELETE", self.url, None, + None, None ) @@ -143,6 +146,7 @@ class Team(github.GithubObject.CompletableGithubObject): "PATCH", self.url, None, + None, post_parameters ) self._useAttributes(data) @@ -182,6 +186,7 @@ class Team(github.GithubObject.CompletableGithubObject): "GET", self.url + "/members/" + member._identity, None, + None, None ) return status == 204 @@ -197,6 +202,7 @@ class Team(github.GithubObject.CompletableGithubObject): "GET", self.url + "/repos/" + repo._identity, None, + None, None ) return status == 204 @@ -212,6 +218,7 @@ class Team(github.GithubObject.CompletableGithubObject): "DELETE", self.url + "/members/" + member._identity, None, + None, None ) @@ -226,6 +233,7 @@ class Team(github.GithubObject.CompletableGithubObject): "DELETE", self.url + "/repos/" + repo._identity, None, + None, None ) diff --git a/github/UserKey.py b/github/UserKey.py index 44cc5808..83a26009 100644 --- a/github/UserKey.py +++ b/github/UserKey.py @@ -82,6 +82,7 @@ class UserKey(github.GithubObject.CompletableGithubObject): "DELETE", self.url, None, + None, None ) @@ -103,6 +104,7 @@ class UserKey(github.GithubObject.CompletableGithubObject): "PATCH", self.url, None, + None, post_parameters ) self._useAttributes(data) From e084b5138106d4ad371a69ca9519862f09c855ae Mon Sep 17 00:00:00 2001 From: Vincent Jacques Date: Fri, 23 Aug 2013 14:51:34 +0200 Subject: [PATCH 17/24] #193: Fix test coverage --- github/GithubObject.py | 58 +++++++++++++------ github/Requester.py | 26 ++++----- github/tests/ConditionalRequestUpdate.py | 4 ++ ...uestUpdate.testUpdateObjectWithoutEtag.txt | 22 +++++++ 4 files changed, 77 insertions(+), 33 deletions(-) create mode 100755 github/tests/ReplayData/ConditionalRequestUpdate.testUpdateObjectWithoutEtag.txt diff --git a/github/GithubObject.py b/github/GithubObject.py index 4fddd29d..29e8a7bb 100644 --- a/github/GithubObject.py +++ b/github/GithubObject.py @@ -60,7 +60,7 @@ class GithubObject(object): # Ask requester to do some checking, for debug and test purpose # Since it's most handy to access and kinda all-knowing - if self.CHECK_AFTER_INIT_FLAG: + if self.CHECK_AFTER_INIT_FLAG: # pragma no branch (Flag always set in tests) requester.check_me(self) def _storeAndUseAttributes(self, headers, attributes): @@ -100,24 +100,46 @@ class GithubObject(object): else: return datetime.datetime.strptime(s, "%Y-%m-%dT%H:%M:%SZ") - def save(self, file_name): # #193: Could we use file-like objects? It would be more "pythonic" than passing filenames. - ''' - Save instance to a file - :param file_name: the full path of target file - ''' - with open(file_name, 'wb') as f: - pickle.dump(self, f) # #193: This will also save self._requester, and the login/password of the user. She might not appriciate. - # #193: May be better to pickle only self._rawData and self._headers and restore the object with Github.create_from_raw_data + # #193: I temporarily comment out those two methods + # We need to address the following: + # - The interface should use file-like objects (not file names) + # - it's more "pythonic" + # - it allows user to save several objects in the same physical file + # - it's easier to unit-test because we can inject in-memory file-like objects + # - We should not save identification information + # - We should not re-create several instances of Requester when loading objects + # - This would lead to very surprising behaviors, when changing Github.per_page or anything impacting this central part of PyGithub + # - It should be possible to restore a saved object without knowing its previous type + # - The "load" method should not make the user think she must know this previous type + # - In particular, it shouldn't be a classmethod of GithubObject + # - They should be covered by unit tests + # + # My proposal, to be experimented and discussed: + # - in "save", pickle a tuple containing the class of the object, its rawData and its headers + # - make "load" a method of class Github + # - it will unpickle everything and call Github.create_from_raw_data + # - I would even make "save" a method of Github, to keep it symetric with "load" + # + # Using __get_state__ would not be enought because we wouldn't have access + # to the Requester instance in __set_state__. - @classmethod # #193: Could be a @staticmethod? The docstring would be simpler (no need to explain the type will be same as saved). - def load(cls, file_name): # #193: Could we use file-like objects? It would be more "pythonic" than passing filenames. - ''' - Load saved instance from file - :param file_name: the full path to saved file - :rtype: saved instance. The type of loaded instance remains its orginal one and will not be affected by from which derived class the method is called. - ''' - with open(file_name, 'rb') as f: - return pickle.load(f) + # def save(self, file_name): + # ''' + # Save instance to a file + # :param file_name: the full path of target file + # ''' + # with open(file_name, 'wb') as f: + # pickle.dump(self, f) + + # @classmethod + # def load(cls, file_name): + # ''' + # Load saved instance from file + # :param file_name: the full path to saved file + # :rtype: saved instance. The type of loaded instance remains its orginal one and will not be affected by from which derived class the method is called. + # ''' + # with open(file_name, 'rb') as f: + # return pickle.load(f) @property def etag(self): diff --git a/github/Requester.py b/github/Requester.py index b161de13..d39715a9 100644 --- a/github/Requester.py +++ b/github/Requester.py @@ -90,30 +90,26 @@ class Requester: The structure of a frame: [requestHeader, statusCode, responseHeader, raw_data] Some of them may be None ''' - if not self.DEBUG_FLAG: - return + if self.DEBUG_FLAG: # pragma no branch (Flag always set in tests) + new_frame = [requestHeader, None, None, None] + if self._frameCount < self.DEBUG_FRAME_BUFFER_SIZE - 1: # pragma no branch (Should be covered) + self._frameBuffer.append(new_frame) + else: + self._frameBuffer[0] = new_frame # pragma no cover (Should be covered) - new_frame = [requestHeader, None, None, None] - if self._frameCount < self.DEBUG_FRAME_BUFFER_SIZE - 1: - self._frameBuffer.append(new_frame) - else: - self._frameBuffer[0] = new_frame - - self._frameCount = len(self._frameBuffer) - 1 + self._frameCount = len(self._frameBuffer) - 1 def DEBUG_ON_RESPONSE(self, statusCode, responseHeader, data): ''' Update current frame with response Current frame index will be attached to responseHeader ''' - if not self.DEBUG_FLAG: - return - - self._frameBuffer[self._frameCount][1:4] = [statusCode, responseHeader, data] - responseHeader[self.DEBUG_HEADER_KEY] = self._frameCount + if self.DEBUG_FLAG: # pragma no branch (Flag always set in tests) + self._frameBuffer[self._frameCount][1:4] = [statusCode, responseHeader, data] + responseHeader[self.DEBUG_HEADER_KEY] = self._frameCount def check_me(self, obj): - if self.DEBUG_FLAG and self.ON_CHECK_ME is not None: + if self.DEBUG_FLAG and self.ON_CHECK_ME is not None: # pragma no branch (Flag always set in tests) frame = None if self.DEBUG_HEADER_KEY in obj._headers: frame_index = obj._headers[self.DEBUG_HEADER_KEY] diff --git a/github/tests/ConditionalRequestUpdate.py b/github/tests/ConditionalRequestUpdate.py index 5b3eab97..157834e2 100644 --- a/github/tests/ConditionalRequestUpdate.py +++ b/github/tests/ConditionalRequestUpdate.py @@ -37,3 +37,7 @@ class ConditionalRequestUpdate(Framework.TestCase): def testDidUpdate(self): self.assertTrue(self.repo.update(), msg="The repo should be changed by now. But update() != True") + + def testUpdateObjectWithoutEtag(self): + r = self.g.get_repo("jacquev6/PyGithub") + self.assertTrue(r.update()) diff --git a/github/tests/ReplayData/ConditionalRequestUpdate.testUpdateObjectWithoutEtag.txt b/github/tests/ReplayData/ConditionalRequestUpdate.testUpdateObjectWithoutEtag.txt new file mode 100755 index 00000000..6ef154b5 --- /dev/null +++ b/github/tests/ReplayData/ConditionalRequestUpdate.testUpdateObjectWithoutEtag.txt @@ -0,0 +1,22 @@ +https +GET +api.github.com +None +/repos/jacquev6/PyGithub +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +null +200 +[('status', '200 OK'), ('x-ratelimit-remaining', '4911'), ('content-length', '1129'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('date', 'Sun, 27 May 2012 07:17:09 GMT'), ('content-type', 'application/json; charset=utf-8')] +{"clone_url":"https://github.com/jacquev6/PyGithub.git","has_downloads":true,"watchers":15,"git_url":"git://github.com/jacquev6/PyGithub.git","updated_at":"2012-05-27T06:55:28Z","permissions":{"pull":true,"admin":true,"push":true},"homepage":"http://vincent-jacques.net/PyGithub","url":"https://api.github.com/repos/jacquev6/PyGithub","mirror_url":null,"has_wiki":false,"has_issues":true,"fork":false,"forks":3,"size":308,"private":false,"open_issues":16,"svn_url":"https://github.com/jacquev6/PyGithub","owner":{"url":"https://api.github.com/users/jacquev6","avatar_url":"https://secure.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","login":"jacquev6","id":327146},"name":"PyGithub","language":"Python","description":"Python library implementing the full Github API v3","ssh_url":"git@github.com:jacquev6/PyGithub.git","pushed_at":"2012-05-27T06:00:28Z","created_at":"2012-02-25T12:53:47Z","id":3544490,"html_url":"https://github.com/jacquev6/PyGithub","full_name":"jacquev6/PyGithub"} + +https +GET +api.github.com +None +/repos/jacquev6/PyGithub +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +null +200 +[('status', '200 OK'), ('x-ratelimit-remaining', '4911'), ('content-length', '1129'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('date', 'Sun, 27 May 2012 07:17:09 GMT'), ('content-type', 'application/json; charset=utf-8')] +{"clone_url":"https://github.com/jacquev6/PyGithub.git","has_downloads":true,"watchers":15,"git_url":"git://github.com/jacquev6/PyGithub.git","updated_at":"2012-05-27T06:55:28Z","permissions":{"pull":true,"admin":true,"push":true},"homepage":"http://vincent-jacques.net/PyGithub","url":"https://api.github.com/repos/jacquev6/PyGithub","mirror_url":null,"has_wiki":false,"has_issues":true,"fork":false,"forks":3,"size":308,"private":false,"open_issues":16,"svn_url":"https://github.com/jacquev6/PyGithub","owner":{"url":"https://api.github.com/users/jacquev6","avatar_url":"https://secure.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","login":"jacquev6","id":327146},"name":"PyGithub","language":"Python","description":"Python library implementing the full Github API v3","ssh_url":"git@github.com:jacquev6/PyGithub.git","pushed_at":"2012-05-27T06:00:28Z","created_at":"2012-02-25T12:53:47Z","id":3544490,"html_url":"https://github.com/jacquev6/PyGithub","full_name":"jacquev6/PyGithub"} + From 020a3c9917f42d98c1761527825061d2db8352fd Mon Sep 17 00:00:00 2001 From: Vincent Jacques Date: Fri, 23 Aug 2013 18:45:16 +0200 Subject: [PATCH 18/24] Move method update to CompletableGithubObject --- github/GithubObject.py | 53 +++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/github/GithubObject.py b/github/GithubObject.py index 29e8a7bb..5f4a979c 100644 --- a/github/GithubObject.py +++ b/github/GithubObject.py @@ -155,33 +155,6 @@ class GithubObject(object): ''' return self._headers.get(Consts.RES_LAST_MODIFED) - # #193: Should be only in CompletableGithubObject, NonCompletableGithubObjects don't have urls - def update(self): - ''' - Check and update the object with conditional request - :rtype: Boolean value indicating whether the object is changed - ''' - conditionalRequestHeader = dict() - if self.etag is not None: - conditionalRequestHeader[Consts.REQ_IF_NONE_MATCH] = self.etag - if self.last_modified is not None: - conditionalRequestHeader[Consts.REQ_IF_MODIFIED_SINCE] = self.last_modified - - status, responseHeaders, output = self._requester.requestJson( - "GET", - self._url, - None, - conditionalRequestHeader, - None - ) - if status == 304: - return False - else: - headers, data = self._requester._Requester__check(status, responseHeaders, output) - self._storeAndUseAttributes(headers, data) - self.__completed = True - return True - class NonCompletableGithubObject(GithubObject): def _completeIfNeeded(self): @@ -211,3 +184,29 @@ class CompletableGithubObject(GithubObject): ) self._storeAndUseAttributes(headers, data) self.__completed = True + + def update(self): + ''' + Check and update the object with conditional request + :rtype: Boolean value indicating whether the object is changed + ''' + conditionalRequestHeader = dict() + if self.etag is not None: + conditionalRequestHeader[Consts.REQ_IF_NONE_MATCH] = self.etag + if self.last_modified is not None: + conditionalRequestHeader[Consts.REQ_IF_MODIFIED_SINCE] = self.last_modified + + status, responseHeaders, output = self._requester.requestJson( + "GET", + self._url, + None, + conditionalRequestHeader, + None + ) + if status == 304: + return False + else: + headers, data = self._requester._Requester__check(status, responseHeaders, output) + self._storeAndUseAttributes(headers, data) + self.__completed = True + return True From fb6980ce36766e4dd1ab03b48ac4b5adf876dc84 Mon Sep 17 00:00:00 2001 From: Vincent Jacques Date: Fri, 23 Aug 2013 19:05:53 +0200 Subject: [PATCH 19/24] Move the DEBUG_ON_RESPONSE call to Requester.__requestEncode --- github/Requester.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/github/Requester.py b/github/Requester.py index d39715a9..27f871e5 100644 --- a/github/Requester.py +++ b/github/Requester.py @@ -172,10 +172,6 @@ class Requester: def __check(self, status, responseHeaders, output): output = self.__structuredFromJson(output) - # #193: Shouldn't next line be in __requestEncode? (__check is not called on all requests) - # Log frame - self.DEBUG_ON_RESPONSE(status, responseHeaders, output) - if status >= 400: raise self.__createException(status, output) return responseHeaders, output @@ -255,6 +251,8 @@ class Requester: if "x-oauth-scopes" in responseHeaders: self.oauth_scopes = responseHeaders["x-oauth-scopes"].split(", ") + self.DEBUG_ON_RESPONSE(status, responseHeaders, output) + return status, responseHeaders, output def __requestRaw(self, verb, url, requestHeaders, input): From 38b137fb37c0fdc74f8802a4184518e105db9121 Mon Sep 17 00:00:00 2001 From: AKFish Date: Sat, 24 Aug 2013 07:21:41 +0800 Subject: [PATCH 20/24] Fix line ending --- github/Consts.py | 86 ++++++++++++------------ github/tests/ConditionalRequestUpdate.py | 86 ++++++++++++------------ 2 files changed, 86 insertions(+), 86 deletions(-) diff --git a/github/Consts.py b/github/Consts.py index 5283101d..b3b47914 100644 --- a/github/Consts.py +++ b/github/Consts.py @@ -1,43 +1,43 @@ -# -*- coding: utf-8 -*- - -############################ Copyrights and license ############################ -# # -# Copyright 2013 AKFish # -# # -# This file is part of PyGithub. http://jacquev6.github.com/PyGithub/ # -# # -# 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 . # -# # -################################################################################ - -# #193: Line endings should be linux style - -# TODO: As of Thu Aug 21 22:40:13 (BJT) Chinese Standard Time 2013 -# lots of consts in this project are explict -# should realy round them up and reference them by consts -# EDIT: well, maybe :-) - -################################################################################ -# Request Header # -# (Case sensitive) # -################################################################################ -REQ_IF_NONE_MATCH = "If-None-Match" -REQ_IF_MODIFIED_SINCE = "If-Modified-Since" - -################################################################################ -# Response Header # -# (Lower Case) # -################################################################################ -RES_ETAG = "etag" -RES_LAST_MODIFED = "last-modified" +# -*- coding: utf-8 -*- + +############################ Copyrights and license ############################ +# # +# Copyright 2013 AKFish # +# # +# This file is part of PyGithub. http://jacquev6.github.com/PyGithub/ # +# # +# 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 . # +# # +################################################################################ + +# #193: Line endings should be linux style + +# TODO: As of Thu Aug 21 22:40:13 (BJT) Chinese Standard Time 2013 +# lots of consts in this project are explict +# should realy round them up and reference them by consts +# EDIT: well, maybe :-) + +################################################################################ +# Request Header # +# (Case sensitive) # +################################################################################ +REQ_IF_NONE_MATCH = "If-None-Match" +REQ_IF_MODIFIED_SINCE = "If-Modified-Since" + +################################################################################ +# Response Header # +# (Lower Case) # +################################################################################ +RES_ETAG = "etag" +RES_LAST_MODIFED = "last-modified" diff --git a/github/tests/ConditionalRequestUpdate.py b/github/tests/ConditionalRequestUpdate.py index 157834e2..bfd3f87d 100644 --- a/github/tests/ConditionalRequestUpdate.py +++ b/github/tests/ConditionalRequestUpdate.py @@ -1,43 +1,43 @@ -# -*- coding: utf-8 -*- - -############################ Copyrights and license ############################ -# # -# Copyright 2013 AKFish # -# # -# This file is part of PyGithub. http://jacquev6.github.com/PyGithub/ # -# # -# 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 . # -# # -################################################################################ - -# #193: Line endings should be linux style - -import Framework -import github - - -class ConditionalRequestUpdate(Framework.TestCase): - def setUp(self): - Framework.TestCase.setUp(self) - self.repo = self.g.get_repo("akfish/PyGithub") - - def testDidNotUpdate(self): - self.assertFalse(self.repo.update(), msg="The repo is not changes. But update() != False") - - def testDidUpdate(self): - self.assertTrue(self.repo.update(), msg="The repo should be changed by now. But update() != True") - - def testUpdateObjectWithoutEtag(self): - r = self.g.get_repo("jacquev6/PyGithub") - self.assertTrue(r.update()) +# -*- coding: utf-8 -*- + +############################ Copyrights and license ############################ +# # +# Copyright 2013 AKFish # +# # +# This file is part of PyGithub. http://jacquev6.github.com/PyGithub/ # +# # +# 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 . # +# # +################################################################################ + +# #193: Line endings should be linux style + +import Framework +import github + + +class ConditionalRequestUpdate(Framework.TestCase): + def setUp(self): + Framework.TestCase.setUp(self) + self.repo = self.g.get_repo("akfish/PyGithub") + + def testDidNotUpdate(self): + self.assertFalse(self.repo.update(), msg="The repo is not changes. But update() != False") + + def testDidUpdate(self): + self.assertTrue(self.repo.update(), msg="The repo should be changed by now. But update() != True") + + def testUpdateObjectWithoutEtag(self): + r = self.g.get_repo("jacquev6/PyGithub") + self.assertTrue(r.update()) From 3fe9edf08707d2c289d4e6a05f7521751cf9f8e4 Mon Sep 17 00:00:00 2001 From: Vincent Jacques Date: Wed, 4 Sep 2013 23:28:44 +0200 Subject: [PATCH 21/24] Implement object persistence This follows my proposal for #193. Largely inspired by AKFish's work. --- github/GithubObject.py | 52 +++--------------- github/MainClass.py | 26 ++++++++- github/tests/AllTests.py | 1 + github/tests/Persistence.py | 54 +++++++++++++++++++ github/tests/ReplayData/Persistence.setUp.txt | 11 ++++ .../Persistence.testLoadAndUpdate.txt | 11 ++++ 6 files changed, 110 insertions(+), 45 deletions(-) create mode 100644 github/tests/Persistence.py create mode 100644 github/tests/ReplayData/Persistence.setUp.txt create mode 100644 github/tests/ReplayData/Persistence.testLoadAndUpdate.txt diff --git a/github/GithubObject.py b/github/GithubObject.py index 5f4a979c..c2ae7f6d 100644 --- a/github/GithubObject.py +++ b/github/GithubObject.py @@ -24,10 +24,7 @@ # # ################################################################################ -from __future__ import with_statement - import datetime -import pickle import GithubException import Consts @@ -78,6 +75,14 @@ class GithubObject(object): self._completeIfNeeded() return self._rawData + @property + def raw_headers(self): + """ + :type: dict + """ + self._completeIfNeeded() + return self._headers + @staticmethod def _parentUrl(url): return "/".join(url.split("/")[: -1]) @@ -100,47 +105,6 @@ class GithubObject(object): else: return datetime.datetime.strptime(s, "%Y-%m-%dT%H:%M:%SZ") - # #193: I temporarily comment out those two methods - # We need to address the following: - # - The interface should use file-like objects (not file names) - # - it's more "pythonic" - # - it allows user to save several objects in the same physical file - # - it's easier to unit-test because we can inject in-memory file-like objects - # - We should not save identification information - # - We should not re-create several instances of Requester when loading objects - # - This would lead to very surprising behaviors, when changing Github.per_page or anything impacting this central part of PyGithub - # - It should be possible to restore a saved object without knowing its previous type - # - The "load" method should not make the user think she must know this previous type - # - In particular, it shouldn't be a classmethod of GithubObject - # - They should be covered by unit tests - # - # My proposal, to be experimented and discussed: - # - in "save", pickle a tuple containing the class of the object, its rawData and its headers - # - make "load" a method of class Github - # - it will unpickle everything and call Github.create_from_raw_data - # - I would even make "save" a method of Github, to keep it symetric with "load" - # - # Using __get_state__ would not be enought because we wouldn't have access - # to the Requester instance in __set_state__. - - # def save(self, file_name): - # ''' - # Save instance to a file - # :param file_name: the full path of target file - # ''' - # with open(file_name, 'wb') as f: - # pickle.dump(self, f) - - # @classmethod - # def load(cls, file_name): - # ''' - # Load saved instance from file - # :param file_name: the full path to saved file - # :rtype: saved instance. The type of loaded instance remains its orginal one and will not be affected by from which derived class the method is called. - # ''' - # with open(file_name, 'rb') as f: - # return pickle.load(f) - @property def etag(self): ''' diff --git a/github/MainClass.py b/github/MainClass.py index 4eab8517..3efde171 100644 --- a/github/MainClass.py +++ b/github/MainClass.py @@ -26,6 +26,7 @@ ################################################################################ import urllib +import pickle from Requester import Requester import AuthenticatedUser @@ -366,10 +367,33 @@ class Github(object): def create_from_raw_data(self, klass, raw_data, headers={}): """ - Creates an object from raw_data previously obtained by :attr:`github.GithubObject.GithubObject.raw_data` + Creates an object from raw_data previously obtained by :attr:`github.GithubObject.GithubObject.raw_data`, + and optionaly headers previously obtained by :attr:`github.GithubObject.GithubObject.raw_headers`. :param klass: the class of the object to create :param raw_data: dict + :param headers: dict :rtype: instance of class ``klass`` """ return klass(self.__requester, headers, raw_data, completed=True) + + def dump(self, obj, file, protocol=0): + """ + Dumps (pickles) a PyGithub object to a file-like object. + Some effort is made to not pickle sensitive informations like the Github credentials used in the :class:`Github` instance. + But NO EFFORT is made to remove sensitive information from the object's attributes. + + :param obj: the object to pickle + :param file: the file-like object to pickle to + :param protocol: the `pickling protocol `_ + """ + pickle.dump((obj.__class__, obj.raw_data, obj.raw_headers), file, protocol) + + def load(self, f): + """ + Loads (unpickles) a PyGithub object from a file-like object. + + :param f: the file-like object to unpickle from + :return: the unpickled object + """ + return self.create_from_raw_data(*pickle.load(f)) diff --git a/github/tests/AllTests.py b/github/tests/AllTests.py index 53789e0f..5c2ce6da 100644 --- a/github/tests/AllTests.py +++ b/github/tests/AllTests.py @@ -81,3 +81,4 @@ from Issue158 import * from Issue174 import * from ConditionalRequestUpdate import ConditionalRequestUpdate +from Persistence import Persistence diff --git a/github/tests/Persistence.py b/github/tests/Persistence.py new file mode 100644 index 00000000..deb52e70 --- /dev/null +++ b/github/tests/Persistence.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- + +############################ Copyrights and license ############################ +# # +# Copyright 2013 Vincent Jacques # +# # +# This file is part of PyGithub. http://jacquev6.github.com/PyGithub/ # +# # +# 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 Framework +import github + +if Framework.atLeastPython26: + from io import BytesIO as IO +else: + from StringIO import StringIO as IO + +class Persistence(Framework.TestCase): + def setUp(self): + Framework.TestCase.setUp(self) + self.repo = self.g.get_repo("akfish/PyGithub") + + self.dumpedRepo = IO() + self.g.dump(self.repo, self.dumpedRepo) + self.dumpedRepo.seek(0) + + def tearDown(self): + self.dumpedRepo.close() + + def testLoad(self): + loadedRepo = self.g.load(self.dumpedRepo) + self.assertIsInstance(loadedRepo, github.Repository.Repository) + self.assertIs(loadedRepo._requester, self.repo._requester) + self.assertIs(loadedRepo.owner._requester, self.repo._requester) + self.assertEqual(loadedRepo.name, "PyGithub") + self.assertEqual(loadedRepo.url, "https://api.github.com/repos/akfish/PyGithub") + + def testLoadAndUpdate(self): + loadedRepo = self.g.load(self.dumpedRepo) + self.assertTrue(loadedRepo.update()) diff --git a/github/tests/ReplayData/Persistence.setUp.txt b/github/tests/ReplayData/Persistence.setUp.txt new file mode 100644 index 00000000..dfc62ba2 --- /dev/null +++ b/github/tests/ReplayData/Persistence.setUp.txt @@ -0,0 +1,11 @@ +https +GET +api.github.com +None +/repos/akfish/PyGithub +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +null +200 +[('status', '200 OK'), ('x-ratelimit-remaining', '4988'), ('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'), ('access-control-allow-credentials', 'true'), ('vary', 'Accept, Authorization, Cookie, Accept-Encoding'), ('content-length', '13698'), ('server', 'GitHub.com'), ('last-modified', 'Thu, 22 Aug 2013 02:09:11 GMT'), ('x-ratelimit-limit', '5000'), ('etag', '"8600bedcb7fed1d8065e1693e05529ce"'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('date', 'Thu, 22 Aug 2013 02:13:08 GMT'), ('access-control-allow-origin', '*'), ('content-type', 'application/json; charset=utf-8'), ('x-ratelimit-reset', '1377140429')] +{"id":12156762,"name":"PyGithub","full_name":"akfish/PyGithub","owner":{"login":"akfish","id":922715,"avatar_url":"https://1.gravatar.com/avatar/12a1b44d4e5c19cee59618084602b112?d=https%3A%2F%2Fidenticons.github.com%2F6eb90fb68a77fb5a5a997c6264bedf35.png","gravatar_id":"12a1b44d4e5c19cee59618084602b112","url":"https://api.github.com/users/akfish","html_url":"https://github.com/akfish","followers_url":"https://api.github.com/users/akfish/followers","following_url":"https://api.github.com/users/akfish/following{/other_user}","gists_url":"https://api.github.com/users/akfish/gists{/gist_id}","starred_url":"https://api.github.com/users/akfish/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akfish/subscriptions","organizations_url":"https://api.github.com/users/akfish/orgs","repos_url":"https://api.github.com/users/akfish/repos","events_url":"https://api.github.com/users/akfish/events{/privacy}","received_events_url":"https://api.github.com/users/akfish/received_events","type":"User"},"private":false,"html_url":"https://github.com/akfish/PyGithub","description":"Python library implementing the full Github API v3","fork":true,"url":"https://api.github.com/repos/akfish/PyGithub","forks_url":"https://api.github.com/repos/akfish/PyGithub/forks","keys_url":"https://api.github.com/repos/akfish/PyGithub/keys{/key_id}","collaborators_url":"https://api.github.com/repos/akfish/PyGithub/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/akfish/PyGithub/teams","hooks_url":"https://api.github.com/repos/akfish/PyGithub/hooks","issue_events_url":"https://api.github.com/repos/akfish/PyGithub/issues/events{/number}","events_url":"https://api.github.com/repos/akfish/PyGithub/events","assignees_url":"https://api.github.com/repos/akfish/PyGithub/assignees{/user}","branches_url":"https://api.github.com/repos/akfish/PyGithub/branches{/branch}","tags_url":"https://api.github.com/repos/akfish/PyGithub/tags","blobs_url":"https://api.github.com/repos/akfish/PyGithub/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/akfish/PyGithub/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/akfish/PyGithub/git/refs{/sha}","trees_url":"https://api.github.com/repos/akfish/PyGithub/git/trees{/sha}","statuses_url":"https://api.github.com/repos/akfish/PyGithub/statuses/{sha}","languages_url":"https://api.github.com/repos/akfish/PyGithub/languages","stargazers_url":"https://api.github.com/repos/akfish/PyGithub/stargazers","contributors_url":"https://api.github.com/repos/akfish/PyGithub/contributors","subscribers_url":"https://api.github.com/repos/akfish/PyGithub/subscribers","subscription_url":"https://api.github.com/repos/akfish/PyGithub/subscription","commits_url":"https://api.github.com/repos/akfish/PyGithub/commits{/sha}","git_commits_url":"https://api.github.com/repos/akfish/PyGithub/git/commits{/sha}","comments_url":"https://api.github.com/repos/akfish/PyGithub/comments{/number}","issue_comment_url":"https://api.github.com/repos/akfish/PyGithub/issues/comments/{number}","contents_url":"https://api.github.com/repos/akfish/PyGithub/contents/{+path}","compare_url":"https://api.github.com/repos/akfish/PyGithub/compare/{base}...{head}","merges_url":"https://api.github.com/repos/akfish/PyGithub/merges","archive_url":"https://api.github.com/repos/akfish/PyGithub/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/akfish/PyGithub/downloads","issues_url":"https://api.github.com/repos/akfish/PyGithub/issues{/number}","pulls_url":"https://api.github.com/repos/akfish/PyGithub/pulls{/number}","milestones_url":"https://api.github.com/repos/akfish/PyGithub/milestones{/number}","notifications_url":"https://api.github.com/repos/akfish/PyGithub/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/akfish/PyGithub/labels{/name}","created_at":"2013-08-16T10:56:11Z","updated_at":"2013-08-22T02:09:11Z","pushed_at":"2013-08-22T02:09:09Z","git_url":"git://github.com/akfish/PyGithub.git","ssh_url":"git@github.com:akfish/PyGithub.git","clone_url":"https://github.com/akfish/PyGithub.git","svn_url":"https://github.com/akfish/PyGithub","homepage":"http://jacquev6.github.com/PyGithub","size":6736,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"master_branch":"master","default_branch":"master","permissions":{"admin":true,"push":true,"pull":true},"network_count":70,"parent":{"id":3544490,"name":"PyGithub","full_name":"jacquev6/PyGithub","owner":{"login":"jacquev6","id":327146,"avatar_url":"https://0.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https%3A%2F%2Fidenticons.github.com%2Ffadfb5f7088ef66579d198a3c9a4935e.png","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","url":"https://api.github.com/users/jacquev6","html_url":"https://github.com/jacquev6","followers_url":"https://api.github.com/users/jacquev6/followers","following_url":"https://api.github.com/users/jacquev6/following{/other_user}","gists_url":"https://api.github.com/users/jacquev6/gists{/gist_id}","starred_url":"https://api.github.com/users/jacquev6/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jacquev6/subscriptions","organizations_url":"https://api.github.com/users/jacquev6/orgs","repos_url":"https://api.github.com/users/jacquev6/repos","events_url":"https://api.github.com/users/jacquev6/events{/privacy}","received_events_url":"https://api.github.com/users/jacquev6/received_events","type":"User"},"private":false,"html_url":"https://github.com/jacquev6/PyGithub","description":"Python library implementing the full Github API v3","fork":false,"url":"https://api.github.com/repos/jacquev6/PyGithub","forks_url":"https://api.github.com/repos/jacquev6/PyGithub/forks","keys_url":"https://api.github.com/repos/jacquev6/PyGithub/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jacquev6/PyGithub/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jacquev6/PyGithub/teams","hooks_url":"https://api.github.com/repos/jacquev6/PyGithub/hooks","issue_events_url":"https://api.github.com/repos/jacquev6/PyGithub/issues/events{/number}","events_url":"https://api.github.com/repos/jacquev6/PyGithub/events","assignees_url":"https://api.github.com/repos/jacquev6/PyGithub/assignees{/user}","branches_url":"https://api.github.com/repos/jacquev6/PyGithub/branches{/branch}","tags_url":"https://api.github.com/repos/jacquev6/PyGithub/tags","blobs_url":"https://api.github.com/repos/jacquev6/PyGithub/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jacquev6/PyGithub/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jacquev6/PyGithub/git/refs{/sha}","trees_url":"https://api.github.com/repos/jacquev6/PyGithub/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jacquev6/PyGithub/statuses/{sha}","languages_url":"https://api.github.com/repos/jacquev6/PyGithub/languages","stargazers_url":"https://api.github.com/repos/jacquev6/PyGithub/stargazers","contributors_url":"https://api.github.com/repos/jacquev6/PyGithub/contributors","subscribers_url":"https://api.github.com/repos/jacquev6/PyGithub/subscribers","subscription_url":"https://api.github.com/repos/jacquev6/PyGithub/subscription","commits_url":"https://api.github.com/repos/jacquev6/PyGithub/commits{/sha}","git_commits_url":"https://api.github.com/repos/jacquev6/PyGithub/git/commits{/sha}","comments_url":"https://api.github.com/repos/jacquev6/PyGithub/comments{/number}","issue_comment_url":"https://api.github.com/repos/jacquev6/PyGithub/issues/comments/{number}","contents_url":"https://api.github.com/repos/jacquev6/PyGithub/contents/{+path}","compare_url":"https://api.github.com/repos/jacquev6/PyGithub/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jacquev6/PyGithub/merges","archive_url":"https://api.github.com/repos/jacquev6/PyGithub/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jacquev6/PyGithub/downloads","issues_url":"https://api.github.com/repos/jacquev6/PyGithub/issues{/number}","pulls_url":"https://api.github.com/repos/jacquev6/PyGithub/pulls{/number}","milestones_url":"https://api.github.com/repos/jacquev6/PyGithub/milestones{/number}","notifications_url":"https://api.github.com/repos/jacquev6/PyGithub/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jacquev6/PyGithub/labels{/name}","created_at":"2012-02-25T12:53:47Z","updated_at":"2013-08-21T20:32:08Z","pushed_at":"2013-08-21T20:31:45Z","git_url":"git://github.com/jacquev6/PyGithub.git","ssh_url":"git@github.com:jacquev6/PyGithub.git","clone_url":"https://github.com/jacquev6/PyGithub.git","svn_url":"https://github.com/jacquev6/PyGithub","homepage":"http://jacquev6.github.com/PyGithub","size":7437,"watchers_count":248,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"forks_count":70,"mirror_url":null,"open_issues_count":17,"forks":70,"open_issues":17,"watchers":248,"master_branch":"master","default_branch":"master"},"source":{"id":3544490,"name":"PyGithub","full_name":"jacquev6/PyGithub","owner":{"login":"jacquev6","id":327146,"avatar_url":"https://0.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https%3A%2F%2Fidenticons.github.com%2Ffadfb5f7088ef66579d198a3c9a4935e.png","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","url":"https://api.github.com/users/jacquev6","html_url":"https://github.com/jacquev6","followers_url":"https://api.github.com/users/jacquev6/followers","following_url":"https://api.github.com/users/jacquev6/following{/other_user}","gists_url":"https://api.github.com/users/jacquev6/gists{/gist_id}","starred_url":"https://api.github.com/users/jacquev6/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jacquev6/subscriptions","organizations_url":"https://api.github.com/users/jacquev6/orgs","repos_url":"https://api.github.com/users/jacquev6/repos","events_url":"https://api.github.com/users/jacquev6/events{/privacy}","received_events_url":"https://api.github.com/users/jacquev6/received_events","type":"User"},"private":false,"html_url":"https://github.com/jacquev6/PyGithub","description":"Python library implementing the full Github API v3","fork":false,"url":"https://api.github.com/repos/jacquev6/PyGithub","forks_url":"https://api.github.com/repos/jacquev6/PyGithub/forks","keys_url":"https://api.github.com/repos/jacquev6/PyGithub/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jacquev6/PyGithub/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jacquev6/PyGithub/teams","hooks_url":"https://api.github.com/repos/jacquev6/PyGithub/hooks","issue_events_url":"https://api.github.com/repos/jacquev6/PyGithub/issues/events{/number}","events_url":"https://api.github.com/repos/jacquev6/PyGithub/events","assignees_url":"https://api.github.com/repos/jacquev6/PyGithub/assignees{/user}","branches_url":"https://api.github.com/repos/jacquev6/PyGithub/branches{/branch}","tags_url":"https://api.github.com/repos/jacquev6/PyGithub/tags","blobs_url":"https://api.github.com/repos/jacquev6/PyGithub/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jacquev6/PyGithub/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jacquev6/PyGithub/git/refs{/sha}","trees_url":"https://api.github.com/repos/jacquev6/PyGithub/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jacquev6/PyGithub/statuses/{sha}","languages_url":"https://api.github.com/repos/jacquev6/PyGithub/languages","stargazers_url":"https://api.github.com/repos/jacquev6/PyGithub/stargazers","contributors_url":"https://api.github.com/repos/jacquev6/PyGithub/contributors","subscribers_url":"https://api.github.com/repos/jacquev6/PyGithub/subscribers","subscription_url":"https://api.github.com/repos/jacquev6/PyGithub/subscription","commits_url":"https://api.github.com/repos/jacquev6/PyGithub/commits{/sha}","git_commits_url":"https://api.github.com/repos/jacquev6/PyGithub/git/commits{/sha}","comments_url":"https://api.github.com/repos/jacquev6/PyGithub/comments{/number}","issue_comment_url":"https://api.github.com/repos/jacquev6/PyGithub/issues/comments/{number}","contents_url":"https://api.github.com/repos/jacquev6/PyGithub/contents/{+path}","compare_url":"https://api.github.com/repos/jacquev6/PyGithub/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jacquev6/PyGithub/merges","archive_url":"https://api.github.com/repos/jacquev6/PyGithub/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jacquev6/PyGithub/downloads","issues_url":"https://api.github.com/repos/jacquev6/PyGithub/issues{/number}","pulls_url":"https://api.github.com/repos/jacquev6/PyGithub/pulls{/number}","milestones_url":"https://api.github.com/repos/jacquev6/PyGithub/milestones{/number}","notifications_url":"https://api.github.com/repos/jacquev6/PyGithub/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jacquev6/PyGithub/labels{/name}","created_at":"2012-02-25T12:53:47Z","updated_at":"2013-08-21T20:32:08Z","pushed_at":"2013-08-21T20:31:45Z","git_url":"git://github.com/jacquev6/PyGithub.git","ssh_url":"git@github.com:jacquev6/PyGithub.git","clone_url":"https://github.com/jacquev6/PyGithub.git","svn_url":"https://github.com/jacquev6/PyGithub","homepage":"http://jacquev6.github.com/PyGithub","size":7437,"watchers_count":248,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"forks_count":70,"mirror_url":null,"open_issues_count":17,"forks":70,"open_issues":17,"watchers":248,"master_branch":"master","default_branch":"master"}} + diff --git a/github/tests/ReplayData/Persistence.testLoadAndUpdate.txt b/github/tests/ReplayData/Persistence.testLoadAndUpdate.txt new file mode 100644 index 00000000..b6d8aeec --- /dev/null +++ b/github/tests/ReplayData/Persistence.testLoadAndUpdate.txt @@ -0,0 +1,11 @@ +https +GET +api.github.com +None +/repos/akfish/PyGithub +{'If-None-Match': '"8600bedcb7fed1d8065e1693e05529ce"', 'User-Agent': 'PyGithub/Python', 'Authorization': 'Basic login_and_password_removed', 'If-Modified-Since': 'Thu, 22 Aug 2013 02:09:11 GMT'} +null +200 +[('status', '200 OK'), ('x-ratelimit-remaining', '4987'), ('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'), ('access-control-allow-credentials', 'true'), ('vary', 'Accept, Authorization, Cookie, Accept-Encoding'), ('content-length', '13712'), ('server', 'GitHub.com'), ('last-modified', 'Thu, 22 Aug 2013 02:14:54 GMT'), ('x-ratelimit-limit', '5000'), ('etag', '"ef281ef0e821c18f80da36902727160b"'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('date', 'Thu, 22 Aug 2013 02:15:01 GMT'), ('access-control-allow-origin', '*'), ('content-type', 'application/json; charset=utf-8'), ('x-ratelimit-reset', '1377140429')] +{"id":12156762,"name":"PyGithub","full_name":"akfish/PyGithub","owner":{"login":"akfish","id":922715,"avatar_url":"https://0.gravatar.com/avatar/12a1b44d4e5c19cee59618084602b112?d=https%3A%2F%2Fidenticons.github.com%2F6eb90fb68a77fb5a5a997c6264bedf35.png","gravatar_id":"12a1b44d4e5c19cee59618084602b112","url":"https://api.github.com/users/akfish","html_url":"https://github.com/akfish","followers_url":"https://api.github.com/users/akfish/followers","following_url":"https://api.github.com/users/akfish/following{/other_user}","gists_url":"https://api.github.com/users/akfish/gists{/gist_id}","starred_url":"https://api.github.com/users/akfish/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akfish/subscriptions","organizations_url":"https://api.github.com/users/akfish/orgs","repos_url":"https://api.github.com/users/akfish/repos","events_url":"https://api.github.com/users/akfish/events{/privacy}","received_events_url":"https://api.github.com/users/akfish/received_events","type":"User"},"private":false,"html_url":"https://github.com/akfish/PyGithub","description":"Python library implementing the full Github API v3 - AKFish Fork","fork":true,"url":"https://api.github.com/repos/akfish/PyGithub","forks_url":"https://api.github.com/repos/akfish/PyGithub/forks","keys_url":"https://api.github.com/repos/akfish/PyGithub/keys{/key_id}","collaborators_url":"https://api.github.com/repos/akfish/PyGithub/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/akfish/PyGithub/teams","hooks_url":"https://api.github.com/repos/akfish/PyGithub/hooks","issue_events_url":"https://api.github.com/repos/akfish/PyGithub/issues/events{/number}","events_url":"https://api.github.com/repos/akfish/PyGithub/events","assignees_url":"https://api.github.com/repos/akfish/PyGithub/assignees{/user}","branches_url":"https://api.github.com/repos/akfish/PyGithub/branches{/branch}","tags_url":"https://api.github.com/repos/akfish/PyGithub/tags","blobs_url":"https://api.github.com/repos/akfish/PyGithub/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/akfish/PyGithub/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/akfish/PyGithub/git/refs{/sha}","trees_url":"https://api.github.com/repos/akfish/PyGithub/git/trees{/sha}","statuses_url":"https://api.github.com/repos/akfish/PyGithub/statuses/{sha}","languages_url":"https://api.github.com/repos/akfish/PyGithub/languages","stargazers_url":"https://api.github.com/repos/akfish/PyGithub/stargazers","contributors_url":"https://api.github.com/repos/akfish/PyGithub/contributors","subscribers_url":"https://api.github.com/repos/akfish/PyGithub/subscribers","subscription_url":"https://api.github.com/repos/akfish/PyGithub/subscription","commits_url":"https://api.github.com/repos/akfish/PyGithub/commits{/sha}","git_commits_url":"https://api.github.com/repos/akfish/PyGithub/git/commits{/sha}","comments_url":"https://api.github.com/repos/akfish/PyGithub/comments{/number}","issue_comment_url":"https://api.github.com/repos/akfish/PyGithub/issues/comments/{number}","contents_url":"https://api.github.com/repos/akfish/PyGithub/contents/{+path}","compare_url":"https://api.github.com/repos/akfish/PyGithub/compare/{base}...{head}","merges_url":"https://api.github.com/repos/akfish/PyGithub/merges","archive_url":"https://api.github.com/repos/akfish/PyGithub/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/akfish/PyGithub/downloads","issues_url":"https://api.github.com/repos/akfish/PyGithub/issues{/number}","pulls_url":"https://api.github.com/repos/akfish/PyGithub/pulls{/number}","milestones_url":"https://api.github.com/repos/akfish/PyGithub/milestones{/number}","notifications_url":"https://api.github.com/repos/akfish/PyGithub/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/akfish/PyGithub/labels{/name}","created_at":"2013-08-16T10:56:11Z","updated_at":"2013-08-22T02:14:54Z","pushed_at":"2013-08-22T02:09:09Z","git_url":"git://github.com/akfish/PyGithub.git","ssh_url":"git@github.com:akfish/PyGithub.git","clone_url":"https://github.com/akfish/PyGithub.git","svn_url":"https://github.com/akfish/PyGithub","homepage":"http://jacquev6.github.com/PyGithub","size":6736,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"master_branch":"master","default_branch":"master","permissions":{"admin":true,"push":true,"pull":true},"network_count":70,"parent":{"id":3544490,"name":"PyGithub","full_name":"jacquev6/PyGithub","owner":{"login":"jacquev6","id":327146,"avatar_url":"https://0.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https%3A%2F%2Fidenticons.github.com%2Ffadfb5f7088ef66579d198a3c9a4935e.png","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","url":"https://api.github.com/users/jacquev6","html_url":"https://github.com/jacquev6","followers_url":"https://api.github.com/users/jacquev6/followers","following_url":"https://api.github.com/users/jacquev6/following{/other_user}","gists_url":"https://api.github.com/users/jacquev6/gists{/gist_id}","starred_url":"https://api.github.com/users/jacquev6/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jacquev6/subscriptions","organizations_url":"https://api.github.com/users/jacquev6/orgs","repos_url":"https://api.github.com/users/jacquev6/repos","events_url":"https://api.github.com/users/jacquev6/events{/privacy}","received_events_url":"https://api.github.com/users/jacquev6/received_events","type":"User"},"private":false,"html_url":"https://github.com/jacquev6/PyGithub","description":"Python library implementing the full Github API v3","fork":false,"url":"https://api.github.com/repos/jacquev6/PyGithub","forks_url":"https://api.github.com/repos/jacquev6/PyGithub/forks","keys_url":"https://api.github.com/repos/jacquev6/PyGithub/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jacquev6/PyGithub/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jacquev6/PyGithub/teams","hooks_url":"https://api.github.com/repos/jacquev6/PyGithub/hooks","issue_events_url":"https://api.github.com/repos/jacquev6/PyGithub/issues/events{/number}","events_url":"https://api.github.com/repos/jacquev6/PyGithub/events","assignees_url":"https://api.github.com/repos/jacquev6/PyGithub/assignees{/user}","branches_url":"https://api.github.com/repos/jacquev6/PyGithub/branches{/branch}","tags_url":"https://api.github.com/repos/jacquev6/PyGithub/tags","blobs_url":"https://api.github.com/repos/jacquev6/PyGithub/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jacquev6/PyGithub/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jacquev6/PyGithub/git/refs{/sha}","trees_url":"https://api.github.com/repos/jacquev6/PyGithub/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jacquev6/PyGithub/statuses/{sha}","languages_url":"https://api.github.com/repos/jacquev6/PyGithub/languages","stargazers_url":"https://api.github.com/repos/jacquev6/PyGithub/stargazers","contributors_url":"https://api.github.com/repos/jacquev6/PyGithub/contributors","subscribers_url":"https://api.github.com/repos/jacquev6/PyGithub/subscribers","subscription_url":"https://api.github.com/repos/jacquev6/PyGithub/subscription","commits_url":"https://api.github.com/repos/jacquev6/PyGithub/commits{/sha}","git_commits_url":"https://api.github.com/repos/jacquev6/PyGithub/git/commits{/sha}","comments_url":"https://api.github.com/repos/jacquev6/PyGithub/comments{/number}","issue_comment_url":"https://api.github.com/repos/jacquev6/PyGithub/issues/comments/{number}","contents_url":"https://api.github.com/repos/jacquev6/PyGithub/contents/{+path}","compare_url":"https://api.github.com/repos/jacquev6/PyGithub/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jacquev6/PyGithub/merges","archive_url":"https://api.github.com/repos/jacquev6/PyGithub/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jacquev6/PyGithub/downloads","issues_url":"https://api.github.com/repos/jacquev6/PyGithub/issues{/number}","pulls_url":"https://api.github.com/repos/jacquev6/PyGithub/pulls{/number}","milestones_url":"https://api.github.com/repos/jacquev6/PyGithub/milestones{/number}","notifications_url":"https://api.github.com/repos/jacquev6/PyGithub/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jacquev6/PyGithub/labels{/name}","created_at":"2012-02-25T12:53:47Z","updated_at":"2013-08-21T20:32:08Z","pushed_at":"2013-08-21T20:31:45Z","git_url":"git://github.com/jacquev6/PyGithub.git","ssh_url":"git@github.com:jacquev6/PyGithub.git","clone_url":"https://github.com/jacquev6/PyGithub.git","svn_url":"https://github.com/jacquev6/PyGithub","homepage":"http://jacquev6.github.com/PyGithub","size":7437,"watchers_count":248,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"forks_count":70,"mirror_url":null,"open_issues_count":17,"forks":70,"open_issues":17,"watchers":248,"master_branch":"master","default_branch":"master"},"source":{"id":3544490,"name":"PyGithub","full_name":"jacquev6/PyGithub","owner":{"login":"jacquev6","id":327146,"avatar_url":"https://0.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https%3A%2F%2Fidenticons.github.com%2Ffadfb5f7088ef66579d198a3c9a4935e.png","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","url":"https://api.github.com/users/jacquev6","html_url":"https://github.com/jacquev6","followers_url":"https://api.github.com/users/jacquev6/followers","following_url":"https://api.github.com/users/jacquev6/following{/other_user}","gists_url":"https://api.github.com/users/jacquev6/gists{/gist_id}","starred_url":"https://api.github.com/users/jacquev6/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jacquev6/subscriptions","organizations_url":"https://api.github.com/users/jacquev6/orgs","repos_url":"https://api.github.com/users/jacquev6/repos","events_url":"https://api.github.com/users/jacquev6/events{/privacy}","received_events_url":"https://api.github.com/users/jacquev6/received_events","type":"User"},"private":false,"html_url":"https://github.com/jacquev6/PyGithub","description":"Python library implementing the full Github API v3","fork":false,"url":"https://api.github.com/repos/jacquev6/PyGithub","forks_url":"https://api.github.com/repos/jacquev6/PyGithub/forks","keys_url":"https://api.github.com/repos/jacquev6/PyGithub/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jacquev6/PyGithub/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jacquev6/PyGithub/teams","hooks_url":"https://api.github.com/repos/jacquev6/PyGithub/hooks","issue_events_url":"https://api.github.com/repos/jacquev6/PyGithub/issues/events{/number}","events_url":"https://api.github.com/repos/jacquev6/PyGithub/events","assignees_url":"https://api.github.com/repos/jacquev6/PyGithub/assignees{/user}","branches_url":"https://api.github.com/repos/jacquev6/PyGithub/branches{/branch}","tags_url":"https://api.github.com/repos/jacquev6/PyGithub/tags","blobs_url":"https://api.github.com/repos/jacquev6/PyGithub/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jacquev6/PyGithub/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jacquev6/PyGithub/git/refs{/sha}","trees_url":"https://api.github.com/repos/jacquev6/PyGithub/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jacquev6/PyGithub/statuses/{sha}","languages_url":"https://api.github.com/repos/jacquev6/PyGithub/languages","stargazers_url":"https://api.github.com/repos/jacquev6/PyGithub/stargazers","contributors_url":"https://api.github.com/repos/jacquev6/PyGithub/contributors","subscribers_url":"https://api.github.com/repos/jacquev6/PyGithub/subscribers","subscription_url":"https://api.github.com/repos/jacquev6/PyGithub/subscription","commits_url":"https://api.github.com/repos/jacquev6/PyGithub/commits{/sha}","git_commits_url":"https://api.github.com/repos/jacquev6/PyGithub/git/commits{/sha}","comments_url":"https://api.github.com/repos/jacquev6/PyGithub/comments{/number}","issue_comment_url":"https://api.github.com/repos/jacquev6/PyGithub/issues/comments/{number}","contents_url":"https://api.github.com/repos/jacquev6/PyGithub/contents/{+path}","compare_url":"https://api.github.com/repos/jacquev6/PyGithub/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jacquev6/PyGithub/merges","archive_url":"https://api.github.com/repos/jacquev6/PyGithub/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jacquev6/PyGithub/downloads","issues_url":"https://api.github.com/repos/jacquev6/PyGithub/issues{/number}","pulls_url":"https://api.github.com/repos/jacquev6/PyGithub/pulls{/number}","milestones_url":"https://api.github.com/repos/jacquev6/PyGithub/milestones{/number}","notifications_url":"https://api.github.com/repos/jacquev6/PyGithub/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jacquev6/PyGithub/labels{/name}","created_at":"2012-02-25T12:53:47Z","updated_at":"2013-08-21T20:32:08Z","pushed_at":"2013-08-21T20:31:45Z","git_url":"git://github.com/jacquev6/PyGithub.git","ssh_url":"git@github.com:jacquev6/PyGithub.git","clone_url":"https://github.com/jacquev6/PyGithub.git","svn_url":"https://github.com/jacquev6/PyGithub","homepage":"http://jacquev6.github.com/PyGithub","size":7437,"watchers_count":248,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"forks_count":70,"mirror_url":null,"open_issues_count":17,"forks":70,"open_issues":17,"watchers":248,"master_branch":"master","default_branch":"master"}} + From c412d49c9fd28406156dff664a1f848da1e95d0b Mon Sep 17 00:00:00 2001 From: Vincent Jacques Date: Thu, 5 Sep 2013 17:50:57 +0200 Subject: [PATCH 22/24] Adapt to Python 2.5 --- github/tests/Persistence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github/tests/Persistence.py b/github/tests/Persistence.py index deb52e70..49519b71 100644 --- a/github/tests/Persistence.py +++ b/github/tests/Persistence.py @@ -43,7 +43,7 @@ class Persistence(Framework.TestCase): def testLoad(self): loadedRepo = self.g.load(self.dumpedRepo) - self.assertIsInstance(loadedRepo, github.Repository.Repository) + self.assertTrue(isinstance(loadedRepo, github.Repository.Repository)) self.assertIs(loadedRepo._requester, self.repo._requester) self.assertIs(loadedRepo.owner._requester, self.repo._requester) self.assertEqual(loadedRepo.name, "PyGithub") From 6cb149dce41cf1f110ae1f1d6a5c6bdd66790b69 Mon Sep 17 00:00:00 2001 From: Vincent Jacques Date: Thu, 5 Sep 2013 17:53:31 +0200 Subject: [PATCH 23/24] Adapt to Python 2.5 (again:)) --- github/tests/Persistence.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/github/tests/Persistence.py b/github/tests/Persistence.py index 49519b71..c20f9a7c 100644 --- a/github/tests/Persistence.py +++ b/github/tests/Persistence.py @@ -44,8 +44,8 @@ class Persistence(Framework.TestCase): def testLoad(self): loadedRepo = self.g.load(self.dumpedRepo) self.assertTrue(isinstance(loadedRepo, github.Repository.Repository)) - self.assertIs(loadedRepo._requester, self.repo._requester) - self.assertIs(loadedRepo.owner._requester, self.repo._requester) + self.assertTrue(loadedRepo._requester is self.repo._requester) + self.assertTrue(loadedRepo.owner._requester is self.repo._requester) self.assertEqual(loadedRepo.name, "PyGithub") self.assertEqual(loadedRepo.url, "https://api.github.com/repos/akfish/PyGithub") From d18d1b0354a5c7de920b30ef1e5950a5479dd866 Mon Sep 17 00:00:00 2001 From: Vincent Jacques Date: Thu, 5 Sep 2013 18:01:03 +0200 Subject: [PATCH 24/24] Update readme --- README.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 88c8fa06..5c724258 100644 --- a/README.rst +++ b/README.rst @@ -10,8 +10,11 @@ PyGithub is stable. I will maintain it up to date with the API, and fix bugs if What's new? =========== -`Version 1.19.0 `_ (?? ??th, 2013) +`Version 1.19.0 `_ (September ??th, 2013) (AKFish's edition) +----------------------------------------------------------------------------------------------------------------------------------- +* Implement `conditional requests `_ by the method ``GithubObject.update``. Thank you very much `akfish `_ for the pull request and your collaboration! +* Implement persistence of PyGithub objects: ``Github.save`` and ``Github.load``. Don't forget to ``update`` your objects after loading them, it won't decrease your rate limiting quota if nothing has changed. Again, thank you `akfish `_ * Implement ``Github.get_repos`` to get all public repositories * Implement ``NamedUser.has_in_following`` * Technical change: HTTP headers are now stored in retrieved objects. This is a base for new functionalities. Thank you `akfish `_ for the pull request