From b83ffbf8937895f3d052ccfb528725a924d1d50b Mon Sep 17 00:00:00 2001 From: Enix Yu Date: Wed, 23 Dec 2015 10:35:09 +0800 Subject: [PATCH 01/10] add content file create/update/delete api --- github/Repository.py | 147 +++++++++++++++++++++++++++++++++++++ github/tests/Repository.py | 10 +++ 2 files changed, 157 insertions(+) diff --git a/github/Repository.py b/github/Repository.py index be765169..54ab14d6 100644 --- a/github/Repository.py +++ b/github/Repository.py @@ -32,6 +32,7 @@ import urllib import datetime +from base64 import b64encode import github.GithubObject import github.PaginatedList @@ -1216,6 +1217,152 @@ class Repository(github.GithubObject.CompletableGithubObject): ) return github.ContentFile.ContentFile(self._requester, headers, data, completed=True) + def create_file(self, path, message, content, + branch=github.GithubObject.NotSet, + committer=github.GithubObject.NotSet, + author=github.GithubObject.NotSet): + """Create a file in this repository. + :calls: `PUT /repos/:owner/:repo/contents/:path `_ + :param path: string, (required), path of the file in the repository + :param message: string, (required), commit message + :param content: bytes, (required), the actual data in the file + :param branch: string, (optional), branch to create the commit on. Defaults to the default branch of the repository + :param committer: dict, (optional), if no information is given the authenticated user's information will be used. You must specify both a name and email. + :param author: dict, (optional), if omitted this will be filled in with committer information. If passed, you must specify both a name and email. + :rtype: { + 'content': :class:`ContentFile `:, + 'commit': :class:`Commit `} + """ + assert isinstance(path, (str, unicode)), \ + 'path must be str/unicode object' + assert isinstance(message, (str, unicode)), \ + 'message must be str/unicode object' + assert isinstance(content, bytes), \ + 'content must be a byte object' + assert branch is github.GithubObject.NotSet \ + or isinstance(branch, (str, unicode)), \ + 'branch must be a str/unicode object' + assert author is github.GithubObject.NotSet \ + or isinstance(author, github.InputGitAuthor), \ + 'author must be a github.InputGitAuthor object' + assert committer is github.GithubObject.NotSet \ + or isinstance(committer, github.InputGitAuthor), \ + 'committer must be a github.InputGitAuthor object' + + content = b64encode(content).decode('utf-8') + put_parameters = {'message': message, 'content': content} + + if branch is not github.GithubObject.NotSet: + put_parameters['branch'] = branch + if author is not github.GithubObject.NotSet: + put_parameters["author"] = author._identity + if committer is not github.GithubObject.NotSet: + put_parameters["committer"] = committer._identity + + headers, data = self._requester.requestJsonAndCheck( + "PUT", + self.url + "/contents" + path, + parameters=put_parameters + ) + + if headers.get('status') == '201 Created' \ + and 'content' in data and 'commit' in data: + data['content'] = github.ContentFile.ContentFile( + self._requester, headers, data, completed=True) + data['commit'] = github.Commit.Commit( + self._requester, headers, data, completed=True) + + return data + + def update_file(self, path, message, content, sha, + branch=github.GithubObject.NotSet, + committer=github.GithubObject.NotSet, + author=github.GithubObject.NotSet): + """This method updates a file in a repository + :calls: `PUT /repos/:owner/:repo/contents/:path `_ + :param path: string, Required. The content path. + :param message: string, Required. The commit message. + :param content: string, Required. The updated file content, Base64 encoded. + :param sha: string, Required. The blob SHA of the file being replaced. + :param branch: string. The branch name. Default: the repository’s default branch (usually master) + :rtype: { + 'content': :class:`ContentFile `:, + 'commit': :class:`Commit `} + """ + assert isinstance(path, (str, unicode)), \ + 'path must be str/unicode object' + assert isinstance(message, (str, unicode)), \ + 'message must be str/unicode object' + assert isinstance(content, bytes), \ + 'content must be a byte object' + assert isinstance(sha, (str, unicode)), \ + 'sha must be a str/unicode object' + assert branch is github.GithubObject.NotSet \ + or isinstance(branch, (str, unicode)), \ + 'branch must be a str/unicode object' + assert author is github.GithubObject.NotSet \ + or isinstance(author, github.InputGitAuthor), \ + 'author must be a github.InputGitAuthor object' + assert committer is github.GithubObject.NotSet \ + or isinstance(committer, github.InputGitAuthor), \ + 'committer must be a github.InputGitAuthor object' + + content = b64encode(content).decode('utf-8') + put_parameters = {'message': message, 'content': content, + 'sha': sha} + + if branch is not github.GithubObject.NotSet: + put_parameters['branch'] = branch + if author is not github.GithubObject.NotSet: + put_parameters["author"] = author._identity + if committer is not github.GithubObject.NotSet: + put_parameters["committer"] = committer._identity + + headers, data = self._requester.requestJsonAndCheck( + "PUT", + self.url + "/contents" + path, + parameters=put_parameters + ) + + if headers.get('status') == '200 OK' \ + and 'content' in data and 'commit' in data: + data['content'] = github.ContentFile.ContentFile( + self._requester, headers, data, completed=True) + data['commit'] = github.Commit.Commit( + self._requester, headers, data, completed=True) + + return data + + def delete_file(self, path, message, sha, + branch=github.GithubObject.NotSet): + """This method delete a file in a repository + :calls: `DELETE /repos/:owner/:repo/contents/:path `_ + :param path: string, Required. The content path. + :param message: string, Required. The commit message. + :param sha: string, Required. The blob SHA of the file being replaced. + :param branch: string. The branch name. Default: the repository’s default branch (usually master) + :rtype: None + """ + assert isinstance(path, (str, unicode)), \ + 'path must be str/unicode object' + assert isinstance(message, (str, unicode)), \ + 'message must be str/unicode object' + assert isinstance(sha, (str, unicode)), \ + 'sha must be a str/unicode object' + assert branch is github.GithubObject.NotSet \ + or isinstance(branch, (str, unicode)), \ + 'branch must be a str/unicode object' + + url_parameters = {'message': message, 'sha': sha} + if branch is not github.GithubObject.NotSet: + url_parameters['branch'] = branch + + headers, data = self._requester.requestJsonAndCheck( + "DELETE", + self.url + "/contents/" + path, + parameters=url_parameters + ) + def get_dir_contents(self, path, ref=github.GithubObject.NotSet): """ :calls: `GET /repos/:owner/:repo/contents/:path `_ diff --git a/github/tests/Repository.py b/github/tests/Repository.py index a2d9bb76..f016407c 100644 --- a/github/tests/Repository.py +++ b/github/tests/Repository.py @@ -412,6 +412,16 @@ class Repository(Framework.TestCase): self.assertEqual(len(self.repo.get_readme(ref="refs/heads/topic/ExperimentOnDocumentation").content), 6747) self.assertEqual(len(self.repo.get_contents("doc/ReferenceOfClasses.md", ref="refs/heads/topic/ExperimentOnDocumentation").content), 43929) + def testCreateFile(self): + self.repo.create_file('', '', ) + + def testUpdateFile(self): + pass + + def testDeleteFile(self): + pass + + def testGetArchiveLink(self): self.assertEqual(self.repo.get_archive_link("tarball"), "https://nodeload.github.com/jacquev6/PyGithub/tarball/master") self.assertEqual(self.repo.get_archive_link("zipball"), "https://nodeload.github.com/jacquev6/PyGithub/zipball/master") From ae37d4449f95c27f89a7744185d7911a9824a9f8 Mon Sep 17 00:00:00 2001 From: Enix Yu Date: Sat, 26 Dec 2015 10:26:22 +0800 Subject: [PATCH 02/10] fix request parameters issue --- github/Repository.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/github/Repository.py b/github/Repository.py index 54ab14d6..999bfb36 100644 --- a/github/Repository.py +++ b/github/Repository.py @@ -1249,7 +1249,7 @@ class Repository(github.GithubObject.CompletableGithubObject): or isinstance(committer, github.InputGitAuthor), \ 'committer must be a github.InputGitAuthor object' - content = b64encode(content).decode('utf-8') + content = b64encode(content) put_parameters = {'message': message, 'content': content} if branch is not github.GithubObject.NotSet: @@ -1262,7 +1262,7 @@ class Repository(github.GithubObject.CompletableGithubObject): headers, data = self._requester.requestJsonAndCheck( "PUT", self.url + "/contents" + path, - parameters=put_parameters + input=put_parameters ) if headers.get('status') == '201 Created' \ @@ -1307,7 +1307,7 @@ class Repository(github.GithubObject.CompletableGithubObject): or isinstance(committer, github.InputGitAuthor), \ 'committer must be a github.InputGitAuthor object' - content = b64encode(content).decode('utf-8') + content = b64encode(content) put_parameters = {'message': message, 'content': content, 'sha': sha} @@ -1321,7 +1321,7 @@ class Repository(github.GithubObject.CompletableGithubObject): headers, data = self._requester.requestJsonAndCheck( "PUT", self.url + "/contents" + path, - parameters=put_parameters + input=put_parameters ) if headers.get('status') == '200 OK' \ @@ -1360,7 +1360,7 @@ class Repository(github.GithubObject.CompletableGithubObject): headers, data = self._requester.requestJsonAndCheck( "DELETE", self.url + "/contents/" + path, - parameters=url_parameters + input=url_parameters ) def get_dir_contents(self, path, ref=github.GithubObject.NotSet): From 4aaeb9e73bac38a9a89d39bc87e586a2572f9feb Mon Sep 17 00:00:00 2001 From: Enix Yu Date: Fri, 15 Jan 2016 12:03:26 +0800 Subject: [PATCH 03/10] Add repo content create/update/delete testcase --- github/Repository.py | 20 +++------------- .../ReplayData/Repository.testCreateFile.txt | 10 ++++++++ .../ReplayData/Repository.testDeleteFile.txt | 21 ++++++++++++++++ .../ReplayData/Repository.testGetContents.txt | 2 +- .../Repository.testGetContentsWithRef.txt | 2 +- .../ReplayData/Repository.testUpdateFile.txt | 21 ++++++++++++++++ github/tests/Repository.py | 24 ++++++++++++++----- 7 files changed, 75 insertions(+), 25 deletions(-) create mode 100644 github/tests/ReplayData/Repository.testCreateFile.txt create mode 100644 github/tests/ReplayData/Repository.testDeleteFile.txt create mode 100644 github/tests/ReplayData/Repository.testUpdateFile.txt diff --git a/github/Repository.py b/github/Repository.py index 999bfb36..07272179 100644 --- a/github/Repository.py +++ b/github/Repository.py @@ -1265,14 +1265,7 @@ class Repository(github.GithubObject.CompletableGithubObject): input=put_parameters ) - if headers.get('status') == '201 Created' \ - and 'content' in data and 'commit' in data: - data['content'] = github.ContentFile.ContentFile( - self._requester, headers, data, completed=True) - data['commit'] = github.Commit.Commit( - self._requester, headers, data, completed=True) - - return data + return github.ContentFile.ContentFile(self._requester, headers, data, completed=True) def update_file(self, path, message, content, sha, branch=github.GithubObject.NotSet, @@ -1324,14 +1317,7 @@ class Repository(github.GithubObject.CompletableGithubObject): input=put_parameters ) - if headers.get('status') == '200 OK' \ - and 'content' in data and 'commit' in data: - data['content'] = github.ContentFile.ContentFile( - self._requester, headers, data, completed=True) - data['commit'] = github.Commit.Commit( - self._requester, headers, data, completed=True) - - return data + return github.ContentFile.ContentFile(self._requester, headers, data, completed=True) def delete_file(self, path, message, sha, branch=github.GithubObject.NotSet): @@ -1359,7 +1345,7 @@ class Repository(github.GithubObject.CompletableGithubObject): headers, data = self._requester.requestJsonAndCheck( "DELETE", - self.url + "/contents/" + path, + self.url + "/contents" + path, input=url_parameters ) diff --git a/github/tests/ReplayData/Repository.testCreateFile.txt b/github/tests/ReplayData/Repository.testCreateFile.txt new file mode 100644 index 00000000..6015d67e --- /dev/null +++ b/github/tests/ReplayData/Repository.testCreateFile.txt @@ -0,0 +1,10 @@ +https +PUT +api.github.com +None +/repos/jacquev6/PyGithub/contents/doc/testCreateUpdateDeleteFile.md +{'Content-Type': 'application/json', 'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +{"author": {"date": "2016-01-15T16:13:30+12:00", "email": "enix223@163.com", "name": "Enix Yu"}, "branch": "master", "committer": {"date": "2016-01-15T16:13:30+12:00", "email": "enix223@163.com", "name": "Enix Yu"}, "content": "SGVsbG8gd29ybGQ=", "message": "Create file for testCreateFile"} +201 +[('status', '201 Created'), ('x-ratelimit-remaining', '4997'), ('content-length', '16'), ('server', 'nginx/1.0.13'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '5000'), ('etag', '"f5cc2fa3ba4de95ac0eb8c2ca47350c0"'), ('date', 'Fri, 11 May 2012 11:43:09 GMT'), ('content-type', 'application/json; charset=utf-8'), ('location', 'https://api.github.com/repos/jacquev6/PyGithub/contents/doc/testCreateUpdateDeleteFile.md')] +{"content": {"name": "hello.txt", "url": "https://api.github.com/repos/PyGithub/contents/doc/testCreateUpdateDeleteFile.md", "html_url": "https://github.com/PyGithub/doc/testCreateUpdateDeleteFile.md", "download_url": "https://raw.githubusercontent.com/PyGithub/doc/testCreateUpdateDeleteFile.md", "sha": "95b966ae1c166bd92f8ae7d1c313e738c731dfc3", "_links": {"self": "https://api.github.com/repos/PyGithub/contents/doc/testCreateUpdateDeleteFile.md", "git": "https://api.github.com/repos/PyGithub/git/blobs/95b966ae1c166bd92f8ae7d1c313e738c731dfc3", "html": "https://github.com/jacquev6/PyGithub/blob/master/doc/testCreateUpdateDeleteFile.md"}, "git_url": "https://api.github.com/repos/jacquev6/PyGithub/git/blobs/95b966ae1c166bd92f8ae7d1c313e738c731dfc3", "path": "doc/testCreateUpdateDeleteFile.md", "type": "file", "size": 9}, "commit": {"committer": {"date": "2014-11-07T22:01:45Z", "name": "Enix Yu", "email": "enix223@gmail.com"}, "author": {"date": "2016-01-15T16:13:30+12:00", "name": "Enix Yu", "email": "enix223@gmail.com"}, "url": "https://api.github.com/repos/jacquev6/PyGithub/git/commits/7638417db6d59f3c431d3e1f261cc637155684cd", "tree": {"url": "https://api.github.com/repos/jacquev6/PyGithub/git/trees/691272480426f78a0138979dd3ce63b77f706feb", "sha": "691272480426f78a0138979dd3ce63b77f706feb"}, "html_url": "https://github.com/jacquev6/PyGithub/git/commit/7638417db6d59f3c431d3e1f261cc637155684cd", "sha": "7638417db6d59f3c431d3e1f261cc637155684cd", "parents": [{"url": "https://api.github.com/repos/jacquev6/PyGithub/git/commits/1acc419d4d6a9ce985db7be48c6349a0475975b5", "sha": "1acc419d4d6a9ce985db7be48c6349a0475975b5", "html_url": "https://github.com/jacquev6/PyGithub/git/commit/1acc419d4d6a9ce985db7be48c6349a0475975b5"}], "Create file for testCreateFile"}} \ No newline at end of file diff --git a/github/tests/ReplayData/Repository.testDeleteFile.txt b/github/tests/ReplayData/Repository.testDeleteFile.txt new file mode 100644 index 00000000..e977db6f --- /dev/null +++ b/github/tests/ReplayData/Repository.testDeleteFile.txt @@ -0,0 +1,21 @@ +https +GET +api.github.com +None +/repos/jacquev6/PyGithub/contents/doc/testCreateUpdateDeleteFile.md +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +null +200 +[('status', '200 OK'), ('content-length', '16'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('x-ratelimit-limit', '5000'), ('vary', 'Accept, Authorization, Cookie'), ('x-ratelimit-remaining', '4997'), ('server', 'nginx/1.0.13'), ('last-modified', 'Wed, 05 Sep 2012 17:54:40 GMT'), ('connection', 'keep-alive'), ('etag', '"71786feb5f476112c5a8aa894ee7ca6c"'), ('cache-control', 'private, s-maxage=60, max-age=60'), ('date', 'Sat, 08 Sep 2012 10:43:48 GMT'), ('content-type', 'application/json; charset=utf-8')] +{"type":"file","sha":"5628799a7d517a4aaa0c1a7004d07569cd154df0","path":"doc/testCreateUpdateDeleteFile.md","encoding":"base64","_links":{"self":"https://api.github.com/repos/jacquev6/PyGithub/contents/doc/testCreateUpdateDeleteFile.md","html":"https://github.com/jacquev6/PyGithub/blob/master/doc/testCreateUpdateDeleteFile.md","git":"https://api.github.com/repos/jacquev6/PyGithub/git/blobs/5628799a7d517a4aaa0c1a7004d07569cd154df0"},"content":"SGVsbG8gd29ybGQ=","size":16,"name":"doc/testCreateUpdateDeleteFile.md"} + +https +DELETE +api.github.com +None +/repos/jacquev6/PyGithub/contents/doc/testCreateUpdateDeleteFile.md +{'Content-Type': 'application/json', 'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +{"branch": "master", "message": "Delete file for testDeleteFile", "sha": "5628799a7d517a4aaa0c1a7004d07569cd154df0"} +200 +[('status', '200 OK'), ('content-length', '16'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('x-ratelimit-limit', '5000'), ('vary', 'Accept, Authorization, Cookie'), ('x-ratelimit-remaining', '4997'), ('server', 'nginx/1.0.13'), ('last-modified', 'Wed, 05 Sep 2012 17:54:40 GMT'), ('connection', 'keep-alive'), ('etag', '"71786feb5f476112c5a8aa894ee7ca6c"'), ('cache-control', 'private, s-maxage=60, max-age=60'), ('date', 'Sat, 08 Sep 2012 10:43:48 GMT'), ('content-type', 'application/json; charset=utf-8')] +{"content": None, "commit": {"committer": {"date": "2014-11-07T22:01:45Z", "name": "Scott Chacon", "email": "schacon@gmail.com"}, "author": {"date": "2014-11-07T22:01:45Z", "name": "Scott Chacon", "email": "schacon@gmail.com"}, "url": "https://api.github.com/repos/jacquev6/PyGithub/git/commits/7638417db6d59f3c431d3e1f261cc637155684cd", "tree": {"url": "https://api.github.com/repos/jacquev6/PyGithub/git/trees/691272480426f78a0138979dd3ce63b77f706feb", "sha": "691272480426f78a0138979dd3ce63b77f706feb"}, "html_url": "https://github.com/jacquev6/PyGithub/git/commit/7638417db6d59f3c431d3e1f261cc637155684cd", "sha": "7638417db6d59f3c431d3e1f261cc637155684cd", "parents": [{"url": "https://api.github.com/repos/jacquev6/PyGithub/git/commits/1acc419d4d6a9ce985db7be48c6349a0475975b5", "sha": "1acc419d4d6a9ce985db7be48c6349a0475975b5", "html_url": "https://github.com/jacquev6/PyGithub/git/commit/1acc419d4d6a9ce985db7be48c6349a0475975b5"}], "message": "Delete file for testDeleteFile"}} diff --git a/github/tests/ReplayData/Repository.testGetContents.txt b/github/tests/ReplayData/Repository.testGetContents.txt index c8201fb2..24662a87 100644 --- a/github/tests/ReplayData/Repository.testGetContents.txt +++ b/github/tests/ReplayData/Repository.testGetContents.txt @@ -13,7 +13,7 @@ https GET api.github.com None -/repos/jacquev6/PyGithub/contentsdoc/ReferenceOfClasses.md +/repos/jacquev6/PyGithub/contents/doc/ReferenceOfClasses.md {'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} null 200 diff --git a/github/tests/ReplayData/Repository.testGetContentsWithRef.txt b/github/tests/ReplayData/Repository.testGetContentsWithRef.txt index 882d26ff..ce33693a 100644 --- a/github/tests/ReplayData/Repository.testGetContentsWithRef.txt +++ b/github/tests/ReplayData/Repository.testGetContentsWithRef.txt @@ -13,7 +13,7 @@ https GET api.github.com None -/repos/jacquev6/PyGithub/contentsdoc/ReferenceOfClasses.md?ref=refs%2Fheads%2Ftopic%2FExperimentOnDocumentation +/repos/jacquev6/PyGithub/contents/doc/ReferenceOfClasses.md?ref=refs%2Fheads%2Ftopic%2FExperimentOnDocumentation {'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} null 200 diff --git a/github/tests/ReplayData/Repository.testUpdateFile.txt b/github/tests/ReplayData/Repository.testUpdateFile.txt new file mode 100644 index 00000000..db6c03bc --- /dev/null +++ b/github/tests/ReplayData/Repository.testUpdateFile.txt @@ -0,0 +1,21 @@ +https +GET +api.github.com +None +/repos/jacquev6/PyGithub/contents/doc/testCreateUpdateDeleteFile.md +{'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +null +200 +[('status', '200 OK'), ('content-length', '16'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('x-ratelimit-limit', '5000'), ('vary', 'Accept, Authorization, Cookie'), ('x-ratelimit-remaining', '4997'), ('server', 'nginx/1.0.13'), ('last-modified', 'Wed, 05 Sep 2012 17:54:40 GMT'), ('connection', 'keep-alive'), ('etag', '"71786feb5f476112c5a8aa894ee7ca6c"'), ('cache-control', 'private, s-maxage=60, max-age=60'), ('date', 'Sat, 08 Sep 2012 10:43:48 GMT'), ('content-type', 'application/json; charset=utf-8')] +{"type":"file","sha":"5628799a7d517a4aaa0c1a7004d07569cd154df0","path":"doc/testCreateUpdateDeleteFile.md","encoding":"base64","_links":{"self":"https://api.github.com/repos/jacquev6/PyGithub/contents/doc/testCreateUpdateDeleteFile.md","html":"https://github.com/jacquev6/PyGithub/blob/master/doc/testCreateUpdateDeleteFile.md","git":"https://api.github.com/repos/jacquev6/PyGithub/git/blobs/5628799a7d517a4aaa0c1a7004d07569cd154df0"},"content":"SGVsbG8gd29ybGQ=","size":16,"name":"doc/testCreateUpdateDeleteFile.md"} + +https +PUT +api.github.com +None +/repos/jacquev6/PyGithub/contents/doc/testCreateUpdateDeleteFile.md +{'Content-Type': 'application/json', 'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} +{"author": {"date": "2016-01-15T16:13:30+12:00", "email": "enix223@163.com", "name": "Enix Yu"}, "branch": "master", "committer": {"date": "2016-01-15T16:13:30+12:00", "email": "enix223@163.com", "name": "Enix Yu"}, "content": "SGVsbG8gV29ybGQ=", "message": "Update file for testUpdateFile", "sha": "5628799a7d517a4aaa0c1a7004d07569cd154df0"} +200 +[('status', '200 OK'), ('content-length', '16'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('x-ratelimit-limit', '5000'), ('vary', 'Accept, Authorization, Cookie'), ('x-ratelimit-remaining', '4997'), ('server', 'nginx/1.0.13'), ('last-modified', 'Wed, 05 Sep 2012 17:54:40 GMT'), ('connection', 'keep-alive'), ('etag', '"71786feb5f476112c5a8aa894ee7ca6c"'), ('cache-control', 'private, s-maxage=60, max-age=60'), ('date', 'Sat, 08 Sep 2012 10:43:48 GMT'), ('content-type', 'application/json; charset=utf-8')] +{"content": {"name": "testCreateUpdateDeleteFile.md", "url": "https://api.github.com/repos/jacquev6/PyGithub/contents/doc/testCreateUpdateDeleteFile.md", "html_url": "https://github.com/jacquev6/PyGithub/blob/master/doc/testCreateUpdateDeleteFile.md", "download_url": "https://raw.githubusercontent.com/octocat/HelloWorld/master/doc/testCreateUpdateDeleteFile.md", "sha": "95b966ae1c166bd92f8ae7d1c313e738c731dfc3", "_links": {"self": "https://api.github.com/repos/jacquev6/PyGithub/contents/doc/testCreateUpdateDeleteFile.md", "git": "https://api.github.com/repos/jacquev6/PyGithub/git/blobs/95b966ae1c166bd92f8ae7d1c313e738c731dfc3", "html": "https://github.com/jacquev6/PyGithub/blob/master/doc/testCreateUpdateDeleteFile.md"}, "git_url": "https://api.github.com/repos/jacquev6/PyGithub/git/blobs/95b966ae1c166bd92f8ae7d1c313e738c731dfc3", "path": "doc/testCreateUpdateDeleteFile.md", "type": "file", "size": 9}, "commit": {"committer": {"date": "2016-01-15T16:13:30+12:00", "name": "Enix Yu", "email": "enix223@gmail.com"}, "author": {"date": "2016-01-15T16:13:30+12:00", "name": "Enix Yu", "email": "enix223@gmail.com"}, "url": "https://api.github.com/repos/jacquev6/PyGithub/git/commits/7638417db6d59f3c431d3e1f261cc637155684cd", "tree": {"url": "https://api.github.com/repos/jacquev6/PyGithub/git/trees/691272480426f78a0138979dd3ce63b77f706feb", "sha": "691272480426f78a0138979dd3ce63b77f706feb"}, "html_url": "https://github.com/jacquev6/PyGithub/git/commit/7638417db6d59f3c431d3e1f261cc637155684cd", "sha": "7638417db6d59f3c431d3e1f261cc637155684cd", "parents": [{"url": "https://api.github.com/repos/jacquev6/PyGithub/git/commits/1acc419d4d6a9ce985db7be48c6349a0475975b5", "sha": "1acc419d4d6a9ce985db7be48c6349a0475975b5", "html_url": "https://github.com/jacquev6/PyGithub/git/commit/1acc419d4d6a9ce985db7be48c6349a0475975b5"}], "message": "my commit message"}} \ No newline at end of file diff --git a/github/tests/Repository.py b/github/tests/Repository.py index f016407c..3c90af89 100644 --- a/github/tests/Repository.py +++ b/github/tests/Repository.py @@ -406,21 +406,33 @@ class Repository(Framework.TestCase): def testGetContents(self): self.assertEqual(len(self.repo.get_readme().content), 10212) - self.assertEqual(len(self.repo.get_contents("doc/ReferenceOfClasses.md").content), 38121) + self.assertEqual(len(self.repo.get_contents("/doc/ReferenceOfClasses.md").content), 38121) def testGetContentsWithRef(self): self.assertEqual(len(self.repo.get_readme(ref="refs/heads/topic/ExperimentOnDocumentation").content), 6747) - self.assertEqual(len(self.repo.get_contents("doc/ReferenceOfClasses.md", ref="refs/heads/topic/ExperimentOnDocumentation").content), 43929) + self.assertEqual(len(self.repo.get_contents("/doc/ReferenceOfClasses.md", ref="refs/heads/topic/ExperimentOnDocumentation").content), 43929) def testCreateFile(self): - self.repo.create_file('', '', ) + newFile = '/doc/testCreateUpdateDeleteFile.md' + content = bytes('Hello world') + self.repo.create_file( + path=newFile, message='Create file for testCreateFile', content=content, + branch="master", committer=github.InputGitAuthor("Enix Yu", "enix223@163.com", "2016-01-15T16:13:30+12:00"), + author=github.InputGitAuthor("Enix Yu", "enix223@163.com", "2016-01-15T16:13:30+12:00")) def testUpdateFile(self): - pass + updateFile = '/doc/testCreateUpdateDeleteFile.md' + content = bytes('Hello World') + sha = self.repo.get_contents(updateFile).sha + self.repo.update_file( + path=updateFile, message='Update file for testUpdateFile', content=content, sha=sha, + branch="master", committer=github.InputGitAuthor("Enix Yu", "enix223@163.com", "2016-01-15T16:13:30+12:00"), + author=github.InputGitAuthor("Enix Yu", "enix223@163.com", "2016-01-15T16:13:30+12:00")) def testDeleteFile(self): - pass - + deleteFile = '/doc/testCreateUpdateDeleteFile.md' + sha = self.repo.get_contents(deleteFile).sha + self.repo.delete_file(path=deleteFile, message='Delete file for testDeleteFile', sha=sha, branch="master") def testGetArchiveLink(self): self.assertEqual(self.repo.get_archive_link("tarball"), "https://nodeload.github.com/jacquev6/PyGithub/tarball/master") From 00777dbff55303565feef6f663cc8c89187b2407 Mon Sep 17 00:00:00 2001 From: Enix Yu Date: Fri, 15 Jan 2016 12:37:32 +0800 Subject: [PATCH 04/10] fix python3 compatability error in test case --- github/tests/Repository.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/github/tests/Repository.py b/github/tests/Repository.py index 3c90af89..918fb445 100644 --- a/github/tests/Repository.py +++ b/github/tests/Repository.py @@ -414,7 +414,7 @@ class Repository(Framework.TestCase): def testCreateFile(self): newFile = '/doc/testCreateUpdateDeleteFile.md' - content = bytes('Hello world') + content = bytes('Hello world'.encode('utf-8')) self.repo.create_file( path=newFile, message='Create file for testCreateFile', content=content, branch="master", committer=github.InputGitAuthor("Enix Yu", "enix223@163.com", "2016-01-15T16:13:30+12:00"), @@ -422,7 +422,7 @@ class Repository(Framework.TestCase): def testUpdateFile(self): updateFile = '/doc/testCreateUpdateDeleteFile.md' - content = bytes('Hello World') + content = bytes('Hello World'.encode('utf-8')) sha = self.repo.get_contents(updateFile).sha self.repo.update_file( path=updateFile, message='Update file for testUpdateFile', content=content, sha=sha, From 46895df13dbf710943d3dd32e9fc4cac9b9df64b Mon Sep 17 00:00:00 2001 From: Enix Yu Date: Fri, 15 Jan 2016 12:40:52 +0800 Subject: [PATCH 05/10] change replay data for update file test case --- github/tests/ReplayData/Repository.testUpdateFile.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github/tests/ReplayData/Repository.testUpdateFile.txt b/github/tests/ReplayData/Repository.testUpdateFile.txt index db6c03bc..81fb4860 100644 --- a/github/tests/ReplayData/Repository.testUpdateFile.txt +++ b/github/tests/ReplayData/Repository.testUpdateFile.txt @@ -18,4 +18,4 @@ None {"author": {"date": "2016-01-15T16:13:30+12:00", "email": "enix223@163.com", "name": "Enix Yu"}, "branch": "master", "committer": {"date": "2016-01-15T16:13:30+12:00", "email": "enix223@163.com", "name": "Enix Yu"}, "content": "SGVsbG8gV29ybGQ=", "message": "Update file for testUpdateFile", "sha": "5628799a7d517a4aaa0c1a7004d07569cd154df0"} 200 [('status', '200 OK'), ('content-length', '16'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('x-ratelimit-limit', '5000'), ('vary', 'Accept, Authorization, Cookie'), ('x-ratelimit-remaining', '4997'), ('server', 'nginx/1.0.13'), ('last-modified', 'Wed, 05 Sep 2012 17:54:40 GMT'), ('connection', 'keep-alive'), ('etag', '"71786feb5f476112c5a8aa894ee7ca6c"'), ('cache-control', 'private, s-maxage=60, max-age=60'), ('date', 'Sat, 08 Sep 2012 10:43:48 GMT'), ('content-type', 'application/json; charset=utf-8')] -{"content": {"name": "testCreateUpdateDeleteFile.md", "url": "https://api.github.com/repos/jacquev6/PyGithub/contents/doc/testCreateUpdateDeleteFile.md", "html_url": "https://github.com/jacquev6/PyGithub/blob/master/doc/testCreateUpdateDeleteFile.md", "download_url": "https://raw.githubusercontent.com/octocat/HelloWorld/master/doc/testCreateUpdateDeleteFile.md", "sha": "95b966ae1c166bd92f8ae7d1c313e738c731dfc3", "_links": {"self": "https://api.github.com/repos/jacquev6/PyGithub/contents/doc/testCreateUpdateDeleteFile.md", "git": "https://api.github.com/repos/jacquev6/PyGithub/git/blobs/95b966ae1c166bd92f8ae7d1c313e738c731dfc3", "html": "https://github.com/jacquev6/PyGithub/blob/master/doc/testCreateUpdateDeleteFile.md"}, "git_url": "https://api.github.com/repos/jacquev6/PyGithub/git/blobs/95b966ae1c166bd92f8ae7d1c313e738c731dfc3", "path": "doc/testCreateUpdateDeleteFile.md", "type": "file", "size": 9}, "commit": {"committer": {"date": "2016-01-15T16:13:30+12:00", "name": "Enix Yu", "email": "enix223@gmail.com"}, "author": {"date": "2016-01-15T16:13:30+12:00", "name": "Enix Yu", "email": "enix223@gmail.com"}, "url": "https://api.github.com/repos/jacquev6/PyGithub/git/commits/7638417db6d59f3c431d3e1f261cc637155684cd", "tree": {"url": "https://api.github.com/repos/jacquev6/PyGithub/git/trees/691272480426f78a0138979dd3ce63b77f706feb", "sha": "691272480426f78a0138979dd3ce63b77f706feb"}, "html_url": "https://github.com/jacquev6/PyGithub/git/commit/7638417db6d59f3c431d3e1f261cc637155684cd", "sha": "7638417db6d59f3c431d3e1f261cc637155684cd", "parents": [{"url": "https://api.github.com/repos/jacquev6/PyGithub/git/commits/1acc419d4d6a9ce985db7be48c6349a0475975b5", "sha": "1acc419d4d6a9ce985db7be48c6349a0475975b5", "html_url": "https://github.com/jacquev6/PyGithub/git/commit/1acc419d4d6a9ce985db7be48c6349a0475975b5"}], "message": "my commit message"}} \ No newline at end of file +{"content": {"name": "testCreateUpdateDeleteFile.md", "url": "https://api.github.com/repos/jacquev6/PyGithub/contents/doc/testCreateUpdateDeleteFile.md", "html_url": "https://github.com/jacquev6/PyGithub/blob/master/doc/testCreateUpdateDeleteFile.md", "download_url": "https://raw.githubusercontent.com/jacquev6/PyGithub/master/doc/testCreateUpdateDeleteFile.md", "sha": "95b966ae1c166bd92f8ae7d1c313e738c731dfc3", "_links": {"self": "https://api.github.com/repos/jacquev6/PyGithub/contents/doc/testCreateUpdateDeleteFile.md", "git": "https://api.github.com/repos/jacquev6/PyGithub/git/blobs/95b966ae1c166bd92f8ae7d1c313e738c731dfc3", "html": "https://github.com/jacquev6/PyGithub/blob/master/doc/testCreateUpdateDeleteFile.md"}, "git_url": "https://api.github.com/repos/jacquev6/PyGithub/git/blobs/95b966ae1c166bd92f8ae7d1c313e738c731dfc3", "path": "doc/testCreateUpdateDeleteFile.md", "type": "file", "size": 9}, "commit": {"committer": {"date": "2016-01-15T16:13:30+12:00", "name": "Enix Yu", "email": "enix223@gmail.com"}, "author": {"date": "2016-01-15T16:13:30+12:00", "name": "Enix Yu", "email": "enix223@gmail.com"}, "url": "https://api.github.com/repos/jacquev6/PyGithub/git/commits/7638417db6d59f3c431d3e1f261cc637155684cd", "tree": {"url": "https://api.github.com/repos/jacquev6/PyGithub/git/trees/691272480426f78a0138979dd3ce63b77f706feb", "sha": "691272480426f78a0138979dd3ce63b77f706feb"}, "html_url": "https://github.com/jacquev6/PyGithub/git/commit/7638417db6d59f3c431d3e1f261cc637155684cd", "sha": "7638417db6d59f3c431d3e1f261cc637155684cd", "parents": [{"url": "https://api.github.com/repos/jacquev6/PyGithub/git/commits/1acc419d4d6a9ce985db7be48c6349a0475975b5", "sha": "1acc419d4d6a9ce985db7be48c6349a0475975b5", "html_url": "https://github.com/jacquev6/PyGithub/git/commit/1acc419d4d6a9ce985db7be48c6349a0475975b5"}], "message": "my commit message"}} \ No newline at end of file From 9c6f88122041fbed655de08ccb2575dff21af844 Mon Sep 17 00:00:00 2001 From: Enix Yu Date: Fri, 15 Jan 2016 12:50:03 +0800 Subject: [PATCH 06/10] remove not covered API from readme --- README.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.rst b/README.rst index 5316a221..395ea57f 100644 --- a/README.rst +++ b/README.rst @@ -75,8 +75,6 @@ A lot of things including the following URLs, and every new things published by * ``/notifications/threads/:id/subscription`` (DELETE) * ``/notifications/threads/:id/subscription`` (GET) * ``/notifications/threads/:id/subscription`` (PUT) -* ``/repos/:owner/:repo/contents/:path`` (DELETE) -* ``/repos/:owner/:repo/contents/:path`` (PUT) * ``/repos/:owner/:repo/notifications`` (GET) * ``/repos/:owner/:repo/notifications`` (PUT) * ``/repos/:owner/:repo/releases/:id/assets`` (GET) From 5b7f0bb6a862725102b48211d91d8a54ff992f8f Mon Sep 17 00:00:00 2001 From: Enix Yu Date: Sat, 16 Jan 2016 16:11:34 +0800 Subject: [PATCH 07/10] fix python3 compatibility issue for using json/base64 --- github/Repository.py | 25 +++++++++++++++++-------- github/tests/Repository.py | 4 ++-- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/github/Repository.py b/github/Repository.py index 07272179..2f43bd78 100644 --- a/github/Repository.py +++ b/github/Repository.py @@ -29,7 +29,7 @@ # along with PyGithub. If not, see . # # # # ############################################################################## - +import sys import urllib import datetime from base64 import b64encode @@ -71,6 +71,8 @@ import github.StatsParticipation import github.StatsPunchCard import github.Stargazer +atLeastPython26 = sys.hexversion >= 0x02060000 +atLeastPython3 = sys.hexversion >= 0x03000000 class Repository(github.GithubObject.CompletableGithubObject): """ @@ -1225,7 +1227,7 @@ class Repository(github.GithubObject.CompletableGithubObject): :calls: `PUT /repos/:owner/:repo/contents/:path `_ :param path: string, (required), path of the file in the repository :param message: string, (required), commit message - :param content: bytes, (required), the actual data in the file + :param content: string, (required), the actual data in the file :param branch: string, (optional), branch to create the commit on. Defaults to the default branch of the repository :param committer: dict, (optional), if no information is given the authenticated user's information will be used. You must specify both a name and email. :param author: dict, (optional), if omitted this will be filled in with committer information. If passed, you must specify both a name and email. @@ -1237,8 +1239,8 @@ class Repository(github.GithubObject.CompletableGithubObject): 'path must be str/unicode object' assert isinstance(message, (str, unicode)), \ 'message must be str/unicode object' - assert isinstance(content, bytes), \ - 'content must be a byte object' + assert isinstance(content, (str, unicode)), \ + 'content must be a str/unicode object' assert branch is github.GithubObject.NotSet \ or isinstance(branch, (str, unicode)), \ 'branch must be a str/unicode object' @@ -1249,7 +1251,10 @@ class Repository(github.GithubObject.CompletableGithubObject): or isinstance(committer, github.InputGitAuthor), \ 'committer must be a github.InputGitAuthor object' - content = b64encode(content) + if atLeastPython3: + content = b64encode(content.encode('utf-8')).decode('utf-8') + else: + content = b64encode(content) put_parameters = {'message': message, 'content': content} if branch is not github.GithubObject.NotSet: @@ -1286,8 +1291,8 @@ class Repository(github.GithubObject.CompletableGithubObject): 'path must be str/unicode object' assert isinstance(message, (str, unicode)), \ 'message must be str/unicode object' - assert isinstance(content, bytes), \ - 'content must be a byte object' + assert isinstance(content, (str, unicode)), \ + 'content must be a str/unicode object' assert isinstance(sha, (str, unicode)), \ 'sha must be a str/unicode object' assert branch is github.GithubObject.NotSet \ @@ -1300,7 +1305,11 @@ class Repository(github.GithubObject.CompletableGithubObject): or isinstance(committer, github.InputGitAuthor), \ 'committer must be a github.InputGitAuthor object' - content = b64encode(content) + if atLeastPython3: + content = b64encode(content.encode('utf-8')).decode('utf-8') + else: + content = b64encode(content) + put_parameters = {'message': message, 'content': content, 'sha': sha} diff --git a/github/tests/Repository.py b/github/tests/Repository.py index 918fb445..1c3752a3 100644 --- a/github/tests/Repository.py +++ b/github/tests/Repository.py @@ -414,7 +414,7 @@ class Repository(Framework.TestCase): def testCreateFile(self): newFile = '/doc/testCreateUpdateDeleteFile.md' - content = bytes('Hello world'.encode('utf-8')) + content = 'Hello world' self.repo.create_file( path=newFile, message='Create file for testCreateFile', content=content, branch="master", committer=github.InputGitAuthor("Enix Yu", "enix223@163.com", "2016-01-15T16:13:30+12:00"), @@ -422,7 +422,7 @@ class Repository(Framework.TestCase): def testUpdateFile(self): updateFile = '/doc/testCreateUpdateDeleteFile.md' - content = bytes('Hello World'.encode('utf-8')) + content = 'Hello World' sha = self.repo.get_contents(updateFile).sha self.repo.update_file( path=updateFile, message='Update file for testUpdateFile', content=content, sha=sha, From a0a4511f2970a38b4868fa42211fa6313eac242c Mon Sep 17 00:00:00 2001 From: Enix Yu Date: Mon, 27 Jun 2016 09:52:11 +0800 Subject: [PATCH 08/10] fix update/delete/create content return value invalid issue --- github/Repository.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/github/Repository.py b/github/Repository.py index 07272179..65c17a6c 100644 --- a/github/Repository.py +++ b/github/Repository.py @@ -1265,7 +1265,8 @@ class Repository(github.GithubObject.CompletableGithubObject): input=put_parameters ) - return github.ContentFile.ContentFile(self._requester, headers, data, completed=True) + return {'content': github.ContentFile.ContentFile(self._requester, headers['content'], data, completed=True), + 'commit': github.Commit.Commit(self._requester, headers, data['commit'], completed=True)} def update_file(self, path, message, content, sha, branch=github.GithubObject.NotSet, @@ -1317,7 +1318,8 @@ class Repository(github.GithubObject.CompletableGithubObject): input=put_parameters ) - return github.ContentFile.ContentFile(self._requester, headers, data, completed=True) + return {'commit': github.Commit.Commit(self._requester, headers, data['commit'], completed=True), + 'content': github.ContentFile.ContentFile(self._requester, headers, data['content'], completed=True)} def delete_file(self, path, message, sha, branch=github.GithubObject.NotSet): @@ -1327,7 +1329,9 @@ class Repository(github.GithubObject.CompletableGithubObject): :param message: string, Required. The commit message. :param sha: string, Required. The blob SHA of the file being replaced. :param branch: string. The branch name. Default: the repository’s default branch (usually master) - :rtype: None + :rtype: { + 'content': :class:`null `:, + 'commit': :class:`Commit `} """ assert isinstance(path, (str, unicode)), \ 'path must be str/unicode object' @@ -1349,6 +1353,9 @@ class Repository(github.GithubObject.CompletableGithubObject): input=url_parameters ) + return {'commit': github.Commit.Commit(self._requester, headers, data['commit'], completed=True), + 'content': github.GithubObject.NotSet} + def get_dir_contents(self, path, ref=github.GithubObject.NotSet): """ :calls: `GET /repos/:owner/:repo/contents/:path `_ From a7929ac5c4254d86993ed0472841229eb6a92385 Mon Sep 17 00:00:00 2001 From: Enix Yu Date: Mon, 27 Jun 2016 09:56:39 +0800 Subject: [PATCH 09/10] fix typo --- github/Repository.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github/Repository.py b/github/Repository.py index b6e6c988..e0440bca 100644 --- a/github/Repository.py +++ b/github/Repository.py @@ -1270,7 +1270,7 @@ class Repository(github.GithubObject.CompletableGithubObject): input=put_parameters ) - return {'content': github.ContentFile.ContentFile(self._requester, headers['content'], data, completed=True), + return {'content': github.ContentFile.ContentFile(self._requester, headers, data['content'], completed=True), 'commit': github.Commit.Commit(self._requester, headers, data['commit'], completed=True)} def update_file(self, path, message, content, sha, From 8bb765a2dacfac73e98d09191e1decb2bdbe2584 Mon Sep 17 00:00:00 2001 From: Enix Yu Date: Mon, 27 Jun 2016 10:04:17 +0800 Subject: [PATCH 10/10] fix update/create/delete file api return value issue --- github/Repository.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/github/Repository.py b/github/Repository.py index e0440bca..814982bc 100644 --- a/github/Repository.py +++ b/github/Repository.py @@ -1270,8 +1270,8 @@ class Repository(github.GithubObject.CompletableGithubObject): input=put_parameters ) - return {'content': github.ContentFile.ContentFile(self._requester, headers, data['content'], completed=True), - 'commit': github.Commit.Commit(self._requester, headers, data['commit'], completed=True)} + return {'content': github.ContentFile.ContentFile(self._requester, headers, data, completed=True), + 'commit': github.Commit.Commit(self._requester, headers, data, completed=True)} def update_file(self, path, message, content, sha, branch=github.GithubObject.NotSet, @@ -1327,8 +1327,8 @@ class Repository(github.GithubObject.CompletableGithubObject): input=put_parameters ) - return {'commit': github.Commit.Commit(self._requester, headers, data['commit'], completed=True), - 'content': github.ContentFile.ContentFile(self._requester, headers, data['content'], completed=True)} + return {'commit': github.Commit.Commit(self._requester, headers, data, completed=True), + 'content': github.ContentFile.ContentFile(self._requester, headers, data, completed=True)} def delete_file(self, path, message, sha, branch=github.GithubObject.NotSet): @@ -1362,7 +1362,7 @@ class Repository(github.GithubObject.CompletableGithubObject): input=url_parameters ) - return {'commit': github.Commit.Commit(self._requester, headers, data['commit'], completed=True), + return {'commit': github.Commit.Commit(self._requester, headers, data, completed=True), 'content': github.GithubObject.NotSet} def get_dir_contents(self, path, ref=github.GithubObject.NotSet):